Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mRemoteNG/PuTTYNG.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDimitrij <kvarkas@gmail.com>2022-10-31 00:45:23 +0300
committerDimitrij <kvarkas@gmail.com>2022-10-31 00:45:23 +0300
commit302fb2e8ddea1c993552c9a30c02f41d01ca54a9 (patch)
treed6cf1b32664296ef2cecda33caeafbe39e6695c1
parent59105d9b26363e47f00676bd365b2ac8d4cb536a (diff)
parent4ff82ab29a22936b78510c68f544a99e677efed3 (diff)
Merge tag 'tags/0.78'HEADmaster
-rw-r--r--.gitignore53
-rw-r--r--BE_NONE.C15
-rw-r--r--BE_NOSSH.C21
-rw-r--r--BE_NOS_S.C22
-rw-r--r--BE_SSH.C18
-rw-r--r--BUILDSCR183
-rw-r--r--BUILDSCR.CV14
-rw-r--r--Buildscr183
-rw-r--r--Buildscr.cv14
-rw-r--r--CHARSET/LOCALENC.C4
-rw-r--r--CHARSET/MIMEENC.C2
-rw-r--r--CHARSET/SBCSGEN.PL14
-rw-r--r--CHARSET/XENC.C2
-rw-r--r--CHECKLST.txt64
-rw-r--r--CMDGEN.C527
-rw-r--r--CMDLINE.C99
-rw-r--r--CMakeLists.txt156
-rw-r--r--CONF.C593
-rw-r--r--CONFIG.C605
-rw-r--r--CONTRIB/gdb.py19
-rw-r--r--CPROXY.C179
-rw-r--r--DIALOG.C156
-rw-r--r--DIALOG.H792
-rw-r--r--DOC/BLURB.BUT1
-rw-r--r--DOC/CONFIG.BUT466
-rw-r--r--DOC/ERRORS.BUT44
-rw-r--r--DOC/FAQ.BUT50
-rw-r--r--DOC/GS.BUT13
-rw-r--r--DOC/INDEX.BUT40
-rw-r--r--DOC/MAKEFILE80
-rw-r--r--DOC/MAN-PSCP.BUT13
-rw-r--r--DOC/PAGEANT.BUT125
-rw-r--r--DOC/PGPKEYS.BUT46
-rw-r--r--DOC/PLINK.BUT4
-rw-r--r--DOC/PSCP.BUT4
-rw-r--r--DOC/PUBKEY.BUT161
-rw-r--r--DOC/SSHNAMES.BUT2
-rw-r--r--DOC/UDP.BUT135
-rw-r--r--DOC/USING.BUT66
-rw-r--r--DOC/man-pageant.but14
-rw-r--r--DOC/man-plink.but13
-rw-r--r--DOC/man-psftp.but13
-rw-r--r--DOC/man-psocks.but8
-rw-r--r--DOC/man-psusan.but64
-rw-r--r--DOC/man-pterm.but2
-rw-r--r--DOC/man-putty.but2
-rw-r--r--DOC/man-puttygen.but62
-rw-r--r--DOC/man-puttytel.but2
-rw-r--r--DOC/pubkeyfmt.but6
-rw-r--r--ICONS/MAKEFILE18
-rw-r--r--IMPORT.C119
-rw-r--r--LATEST.VER2
-rw-r--r--LDISC.C277
-rw-r--r--LDISC.H27
-rw-r--r--LICENCE2
-rw-r--r--LOGGING.C5
-rw-r--r--MINIBIDI.C2025
-rw-r--r--MISC.C422
-rw-r--r--MISC.H114
-rw-r--r--MKAUTO.SH11
-rw-r--r--MKFILES.PL2092
-rw-r--r--MKUNXARC.SH11
-rw-r--r--NETWORK.H188
-rw-r--r--NOCPROXY.C35
-rw-r--r--NOTIMING.C21
-rw-r--r--PGSSAPI.C105
-rw-r--r--PGSSAPI.H333
-rw-r--r--PORTFWD.C1174
-rw-r--r--PPROXY.C17
-rw-r--r--PROXY.C1513
-rw-r--r--PROXY.H111
-rw-r--r--PSCP.C43
-rw-r--r--PSFTP.C60
-rw-r--r--PUTTY.H836
-rw-r--r--PUTTYMEM.H13
-rw-r--r--PUTTYPS.H18
-rw-r--r--RAW.C330
-rw-r--r--README139
-rw-r--r--RECIPE431
-rw-r--r--RESOURCE.H15
-rw-r--r--RLOGIN.C428
-rw-r--r--Recipe431
-rw-r--r--SETTINGS.C56
-rw-r--r--SFTP.C1205
-rw-r--r--SFTP.H544
-rw-r--r--SIGN.SH4
-rw-r--r--SSH.C1250
-rw-r--r--SSH.H375
-rw-r--r--SSHAES.C1913
-rw-r--r--SSHARCF.C141
-rw-r--r--SSHBLOWF.C699
-rw-r--r--SSHCRC.C108
-rw-r--r--SSHDES.C1048
-rw-r--r--SSHDH.C272
-rw-r--r--SSHDSS.C503
-rw-r--r--SSHDSSG.C103
-rw-r--r--SSHGSS.H217
-rw-r--r--SSHGSSC.C288
-rw-r--r--SSHGSSC.H24
-rw-r--r--SSHPUBK.C207
-rw-r--r--SSHRSA.C1109
-rw-r--r--SSHSH256.C940
-rw-r--r--SSHSH512.C836
-rw-r--r--SSHSHA.C934
-rw-r--r--SSHZLIB.C1253
-rw-r--r--STORAGE.H31
-rw-r--r--TELNET.C1070
-rw-r--r--TERMINAL.C7656
-rw-r--r--TERMINAL.H529
-rw-r--r--TESTBACK.C214
-rw-r--r--TREE234.C1611
-rw-r--r--TREE234.H22
-rw-r--r--UNIX/GTKCFG.C160
-rw-r--r--UNIX/GTKCOLS.C1201
-rw-r--r--UNIX/GTKCOLS.H65
-rw-r--r--UNIX/GTKDLG.C4195
-rw-r--r--UNIX/GTKFONT.C3808
-rw-r--r--UNIX/GTKFONT.H186
-rw-r--r--UNIX/GTKWIN.C5431
-rw-r--r--UNIX/UNIX.H460
-rw-r--r--UNIX/UXAGENTC.C226
-rw-r--r--UNIX/UXCFG.C70
-rw-r--r--UNIX/UXCONS.C530
-rw-r--r--UNIX/UXGEN.C64
-rw-r--r--UNIX/UXGSS.C175
-rw-r--r--UNIX/UXMISC.C371
-rw-r--r--UNIX/UXNET.C1761
-rw-r--r--UNIX/UXPLINK.C966
-rw-r--r--UNIX/UXPRINT.C58
-rw-r--r--UNIX/UXPROXY.C105
-rw-r--r--UNIX/UXPTERM.C50
-rw-r--r--UNIX/UXPTY.C1612
-rw-r--r--UNIX/UXPUTTY.C92
-rw-r--r--UNIX/UXSEL.C4
-rw-r--r--UNIX/UXSER.C595
-rw-r--r--UNIX/UXSFTP.C578
-rw-r--r--UNIX/UXSIGNAL.C47
-rw-r--r--UNIX/UXSTORE.C822
-rw-r--r--UNIX/UXUCS.C268
-rw-r--r--UNIX/UX_X11.C208
-rw-r--r--UNIX/XKEYSYM.C1011
-rw-r--r--UNIX/configure3
-rw-r--r--UNIX/gtkapp.c347
-rw-r--r--UNIX/gtkask.c662
-rw-r--r--UNIX/gtkcomm.c241
-rw-r--r--UNIX/gtkmain.c673
-rw-r--r--UNIX/gtkmisc.c223
-rw-r--r--UNIX/procnet.c4
-rw-r--r--UNIX/uxfdsock.c357
-rw-r--r--UNIX/uxnogtk.c11
-rw-r--r--UNIX/uxpeer.c32
-rw-r--r--UNIX/uxpgnt.c1540
-rw-r--r--UNIX/uxpoll.c169
-rw-r--r--UNIX/uxpsusan.c422
-rw-r--r--UNIX/uxserver.c850
-rw-r--r--UNIX/uxsftpserver.c703
-rw-r--r--UNIX/uxshare.c370
-rw-r--r--UNIX/uxsocks.c178
-rw-r--r--UNIX/uxutils.c65
-rw-r--r--UNIX/uxutils.h65
-rw-r--r--UNIX/x11misc.c83
-rw-r--r--UNIX/x11misc.h8
-rw-r--r--VERSION.C28
-rw-r--r--WCWIDTH.C558
-rw-r--r--WILDCARD.C486
-rw-r--r--WINDOWS/PAGEANT.RC8
-rw-r--r--WINDOWS/PUTTY.RC8
-rw-r--r--WINDOWS/PUTTYGEN.RC12
-rw-r--r--WINDOWS/PUTTYTEL.RC8
-rw-r--r--WINDOWS/RCSTUFF.H15
-rw-r--r--WINDOWS/WINCFG.C405
-rw-r--r--WINDOWS/WINCONS.C452
-rw-r--r--WINDOWS/WINCTRLS.C2600
-rw-r--r--WINDOWS/WINDEFS.C40
-rw-r--r--WINDOWS/WINDLG.C1156
-rw-r--r--WINDOWS/WINDOW.C404
-rw-r--r--WINDOWS/WINGSS.C703
-rw-r--r--WINDOWS/WINHANDL.C731
-rw-r--r--WINDOWS/WINHELP.C250
-rw-r--r--WINDOWS/WINHELP.H208
-rw-r--r--WINDOWS/WINJUMP.C748
-rw-r--r--WINDOWS/WINMISC.C467
-rw-r--r--WINDOWS/WINNET.C1825
-rw-r--r--WINDOWS/WINNOJMP.C8
-rw-r--r--WINDOWS/WINPGEN.C2026
-rw-r--r--WINDOWS/WINPGNT.C1723
-rw-r--r--WINDOWS/WINPGNTC.C348
-rw-r--r--WINDOWS/WINPLINK.C535
-rw-r--r--WINDOWS/WINPRINT.C224
-rw-r--r--WINDOWS/WINPROXY.C107
-rw-r--r--WINDOWS/WINSER.C465
-rw-r--r--WINDOWS/WINSFTP.C650
-rw-r--r--WINDOWS/WINSTORE.C873
-rw-r--r--WINDOWS/WINSTUFF.H704
-rw-r--r--WINDOWS/WINTIME.C26
-rw-r--r--WINDOWS/WINUCS.C1213
-rw-r--r--WINDOWS/WINUTILS.C788
-rw-r--r--WINDOWS/WINX11.C19
-rw-r--r--WINDOWS/WIN_RES.H49
-rw-r--r--WINDOWS/WIN_RES.RC2137
-rw-r--r--WINDOWS/installer.wxs15
-rw-r--r--WINDOWS/wincapi.c93
-rw-r--r--WINDOWS/wincapi.h31
-rw-r--r--WINDOWS/wincliloop.c136
-rw-r--r--WINDOWS/winhelp.rc28
-rw-r--r--WINDOWS/winhsock.c343
-rw-r--r--WINDOWS/winmiscs.c285
-rw-r--r--WINDOWS/winnohlp.c15
-rw-r--r--WINDOWS/winnpc.c98
-rw-r--r--WINDOWS/winnps.c240
-rw-r--r--WINDOWS/winseat.h14
-rw-r--r--WINDOWS/winsecur.c334
-rw-r--r--WINDOWS/winsecur.h53
-rw-r--r--WINDOWS/winselcli.c78
-rw-r--r--WINDOWS/winselgui.c38
-rw-r--r--WINDOWS/winshare.c151
-rw-r--r--X11FWD.C1201
-rw-r--r--agentf.c249
-rw-r--r--be_list.c118
-rw-r--r--be_misc.c154
-rw-r--r--be_none.c15
-rw-r--r--be_nos_s.c22
-rw-r--r--be_nossh.c21
-rw-r--r--be_ssh.c18
-rw-r--r--cgtest.c6
-rw-r--r--charset/CMakeLists.txt30
-rw-r--r--charset/localenc.c4
-rw-r--r--charset/mimeenc.c2
-rwxr-xr-x[-rw-r--r--]charset/sbcsgen.pl14
-rw-r--r--charset/xenc.c2
-rw-r--r--cmake/cmake.h.in60
-rw-r--r--cmake/gitcommit.cmake63
-rw-r--r--cmake/gtk.cmake89
-rw-r--r--cmake/licence.cmake39
-rw-r--r--cmake/platforms/unix.cmake234
-rw-r--r--cmake/platforms/windows.cmake202
-rw-r--r--cmake/setup.cmake113
-rw-r--r--cmake/toolchain-mingw.cmake12
-rw-r--r--cmake/toolchain-winegcc.cmake33
-rwxr-xr-xcmake/winegcc29
-rw-r--r--cmdgen.c527
-rw-r--r--cmdline.c99
-rw-r--r--conf.c593
-rw-r--r--config.c605
-rw-r--r--configure.ac264
-rw-r--r--console.c47
-rw-r--r--console.h7
-rwxr-xr-xcontrib/authplugin-example.py287
-rw-r--r--contrib/gdb.py19
-rwxr-xr-xcontrib/proveprime.py162
-rw-r--r--cproxy.c179
-rw-r--r--crypto/CMakeLists.txt242
-rw-r--r--crypto/aes-common.c20
-rw-r--r--crypto/aes-neon.c359
-rw-r--r--crypto/aes-ni.c341
-rw-r--r--crypto/aes-select.c110
-rw-r--r--crypto/aes-sw.c1133
-rw-r--r--crypto/aes.h160
-rw-r--r--crypto/aesgcm-clmul.c180
-rw-r--r--crypto/aesgcm-common.c8
-rw-r--r--crypto/aesgcm-footer.h368
-rw-r--r--crypto/aesgcm-neon.c164
-rw-r--r--crypto/aesgcm-ref-poly.c364
-rw-r--r--crypto/aesgcm-select.c38
-rw-r--r--crypto/aesgcm-sw.c145
-rw-r--r--crypto/aesgcm.h44
-rw-r--r--crypto/arcfour.c143
-rw-r--r--crypto/argon2.c565
-rw-r--r--crypto/bcrypt.c118
-rw-r--r--crypto/blake2.c (renamed from sshblake2.c)0
-rw-r--r--crypto/blowfish.c702
-rw-r--r--crypto/blowfish.h14
-rw-r--r--crypto/chacha20-poly1305.c1079
-rw-r--r--crypto/crc32.c113
-rw-r--r--crypto/des.c1053
-rw-r--r--crypto/diffie-hellman.c439
-rw-r--r--crypto/dsa.c517
-rw-r--r--crypto/ecc-arithmetic.c1171
-rw-r--r--crypto/ecc-ssh.c1809
-rw-r--r--crypto/ecc.h243
-rw-r--r--crypto/hash_simple.c13
-rw-r--r--crypto/hmac.c263
-rw-r--r--crypto/mac.c (renamed from sshmac.c)0
-rw-r--r--crypto/mac_simple.c16
-rw-r--r--crypto/md5.c (renamed from SSHMD5.C)0
-rw-r--r--crypto/mpint.c2810
-rw-r--r--crypto/mpint_i.h324
-rw-r--r--crypto/ntru.c1889
-rw-r--r--crypto/ntru.h53
-rw-r--r--crypto/openssh-certs.c1160
-rw-r--r--crypto/prng.c287
-rw-r--r--crypto/pubkey-pem.c32
-rw-r--r--crypto/pubkey-ppk.c29
-rw-r--r--crypto/pubkey-ssh1.c38
-rw-r--r--crypto/rsa.c1158
-rw-r--r--crypto/sha1-common.c10
-rw-r--r--crypto/sha1-neon.c190
-rw-r--r--crypto/sha1-ni.c325
-rw-r--r--crypto/sha1-select.c44
-rw-r--r--crypto/sha1-sw.c155
-rw-r--r--crypto/sha1.h109
-rw-r--r--crypto/sha256-common.c30
-rw-r--r--crypto/sha256-neon.c162
-rw-r--r--crypto/sha256-ni.c342
-rw-r--r--crypto/sha256-select.c44
-rw-r--r--crypto/sha256-sw.c157
-rw-r--r--crypto/sha256.h105
-rw-r--r--crypto/sha3.c (renamed from sshsha3.c)0
-rw-r--r--crypto/sha512-common.c71
-rw-r--r--crypto/sha512-neon.c329
-rw-r--r--crypto/sha512-select.c61
-rw-r--r--crypto/sha512-sw.c168
-rw-r--r--crypto/sha512.h131
-rw-r--r--crypto/xdmauth.c53
-rw-r--r--defs.h95
-rw-r--r--dialog.c156
-rw-r--r--dialog.h792
-rw-r--r--doc/CMakeLists.txt182
-rw-r--r--doc/Makefile80
-rw-r--r--doc/authplugin.but519
-rw-r--r--doc/blurb.but1
-rw-r--r--doc/chmextra.but6
-rw-r--r--doc/config.but466
-rw-r--r--doc/errors.but44
-rw-r--r--doc/faq.but50
-rw-r--r--doc/gs.but13
-rw-r--r--doc/index.but40
-rw-r--r--doc/man-pageant.but14
-rw-r--r--doc/man-plink.but13
-rw-r--r--doc/man-pscp.but13
-rw-r--r--doc/man-psftp.but13
-rw-r--r--doc/man-psocks.but8
-rw-r--r--doc/man-psusan.but64
-rw-r--r--doc/man-pterm.but2
-rw-r--r--doc/man-putty.but2
-rw-r--r--doc/man-puttygen.but62
-rw-r--r--doc/man-puttytel.but2
-rw-r--r--doc/pageant.but125
-rw-r--r--doc/pgpkeys.but46
-rw-r--r--doc/plink.but4
-rw-r--r--doc/pscp.but4
-rw-r--r--doc/pubkey.but161
-rw-r--r--doc/pubkeyfmt.but6
-rw-r--r--doc/sshnames.but2
-rw-r--r--doc/udp.but135
-rw-r--r--doc/using.but66
-rw-r--r--ecc.c1167
-rw-r--r--ecc.h243
-rw-r--r--fuzzterm.c215
-rw-r--r--icons/Makefile18
-rwxr-xr-xicons/mksvg.py938
-rw-r--r--import.c119
-rw-r--r--keygen/CMakeLists.txt10
-rw-r--r--keygen/dsa.c103
-rw-r--r--keygen/ecdsa.c (renamed from sshecdsag.c)0
-rw-r--r--keygen/millerrabin.c288
-rw-r--r--keygen/mpunsafe.c48
-rw-r--r--keygen/mpunsafe.h39
-rw-r--r--keygen/pockle.c450
-rw-r--r--keygen/prime.c (renamed from SSHPRIME.C)0
-rw-r--r--keygen/primecandidate.c447
-rw-r--r--keygen/rsa.c (renamed from SSHRSAG.C)0
-rw-r--r--keygen/smallprimes.c (renamed from smallprimes.c)0
-rw-r--r--ldisc.c277
-rw-r--r--ldisc.h27
-rwxr-xr-x[-rw-r--r--]licence.pl149
-rw-r--r--logging.c5
-rw-r--r--mainchan.c538
-rw-r--r--marshal.c318
-rw-r--r--marshal.h25
-rw-r--r--millerrabin.c214
-rw-r--r--minibidi.c2025
-rw-r--r--misc.c422
-rw-r--r--misc.h114
-rw-r--r--miscucs.c28
-rwxr-xr-xmkauto.sh11
-rwxr-xr-xmkfiles.pl2092
-rw-r--r--mksrcarc.sh8
-rwxr-xr-xmkunxarc.sh11
-rw-r--r--mpint.c2646
-rw-r--r--mpint.h17
-rw-r--r--mpint_i.h324
-rw-r--r--mpunsafe.c57
-rw-r--r--mpunsafe.h46
-rw-r--r--network.h188
-rw-r--r--nocmdline.c37
-rw-r--r--nocproxy.c35
-rw-r--r--nogss.c11
-rw-r--r--noprint.c38
-rw-r--r--noproxy.c32
-rw-r--r--noterm.c11
-rw-r--r--notiming.c21
-rw-r--r--nullplug.c42
-rw-r--r--otherbackends/CMakeLists.txt6
-rw-r--r--otherbackends/raw.c377
-rw-r--r--otherbackends/rlogin.c496
-rw-r--r--otherbackends/supdup.c978
-rw-r--r--otherbackends/telnet.c1123
-rw-r--r--otherbackends/testback.c200
-rw-r--r--pageant.c839
-rw-r--r--pgssapi.c105
-rw-r--r--pgssapi.h333
-rw-r--r--pockle.c450
-rw-r--r--portfwd.c1174
-rw-r--r--pproxy.c17
-rw-r--r--primecandidate.c445
-rw-r--r--proxy.c1513
-rw-r--r--proxy.h111
-rw-r--r--proxy/cproxy.c189
-rw-r--r--proxy/cproxy.h99
-rw-r--r--proxy/http.c781
-rw-r--r--proxy/interactor.c119
-rw-r--r--proxy/local.c272
-rw-r--r--proxy/nocproxy.c33
-rw-r--r--proxy/noproxy.c32
-rw-r--r--proxy/nosshproxy.c16
-rw-r--r--proxy/pproxy.c17
-rw-r--r--proxy/proxy.c656
-rw-r--r--proxy/proxy.h127
-rw-r--r--proxy/socks.h72
-rw-r--r--proxy/socks4.c136
-rw-r--r--proxy/socks5.c498
-rw-r--r--proxy/sshproxy.c746
-rw-r--r--proxy/telnet.c368
-rw-r--r--pscp.c43
-rw-r--r--psftp.c60
-rw-r--r--psftpcommon.c2
-rw-r--r--psocks.c14
-rw-r--r--putty.h836
-rw-r--r--puttymem.h13
-rw-r--r--puttyps.h18
-rw-r--r--raw.c330
-rw-r--r--release.pl30
-rw-r--r--resource.h15
-rw-r--r--rlogin.c428
-rw-r--r--scpserver.c1399
-rw-r--r--sesschan.c787
-rw-r--r--settings.c56
-rw-r--r--sftp.c1205
-rw-r--r--sftp.h544
-rwxr-xr-xsign.sh4
-rw-r--r--ssh.c1250
-rw-r--r--ssh.h375
-rw-r--r--ssh/CMakeLists.txt52
-rw-r--r--ssh/agentf.c249
-rw-r--r--ssh/bpp-bare.c206
-rw-r--r--ssh/bpp.h (renamed from sshbpp.h)0
-rw-r--r--ssh/bpp1.c389
-rw-r--r--ssh/bpp2.c990
-rw-r--r--ssh/ca-config.c497
-rw-r--r--ssh/censor1.c (renamed from ssh1censor.c)0
-rw-r--r--ssh/censor2.c (renamed from ssh2censor.c)0
-rw-r--r--ssh/channel.h316
-rw-r--r--ssh/common.c1163
-rw-r--r--ssh/connection1-client.c552
-rw-r--r--ssh/connection1-server.c365
-rw-r--r--ssh/connection1.c811
-rw-r--r--ssh/connection1.h124
-rw-r--r--ssh/connection2-client.c511
-rw-r--r--ssh/connection2-server.c306
-rw-r--r--ssh/connection2.c1746
-rw-r--r--ssh/connection2.h236
-rw-r--r--ssh/crc-attack-detector.c (renamed from SSHCRCDA.C)0
-rw-r--r--ssh/gss.h217
-rw-r--r--ssh/gssc.c288
-rw-r--r--ssh/gssc.h24
-rw-r--r--ssh/kex2-client.c1062
-rw-r--r--ssh/kex2-server.c337
-rw-r--r--ssh/login1-server.c443
-rw-r--r--ssh/login1.c1193
-rw-r--r--ssh/mainchan.c539
-rw-r--r--ssh/nogss.c (renamed from SSHNOGSS.C)0
-rw-r--r--ssh/nosharing.c (renamed from noshare.c)0
-rw-r--r--ssh/pgssapi.c135
-rw-r--r--ssh/pgssapi.h333
-rw-r--r--ssh/portfwd.c1179
-rw-r--r--ssh/ppl.h175
-rw-r--r--ssh/scpserver.c1399
-rw-r--r--ssh/server.c609
-rw-r--r--ssh/server.h145
-rw-r--r--ssh/sesschan.c798
-rw-r--r--ssh/sftp.c1204
-rw-r--r--ssh/sftp.h544
-rw-r--r--ssh/sftpcommon.c (renamed from sftpcommon.c)0
-rw-r--r--ssh/sftpserver.c (renamed from sftpserver.c)0
-rw-r--r--ssh/sharing.c2177
-rw-r--r--ssh/signal-list.h (renamed from sshsignals.h)0
-rw-r--r--ssh/ssh.c1314
-rw-r--r--ssh/transient-hostkey-cache.c (renamed from ssh2transhk.c)0
-rw-r--r--ssh/transport2.c2407
-rw-r--r--ssh/transport2.h261
-rw-r--r--ssh/ttymode-list.h (renamed from sshttymodes.h)0
-rw-r--r--ssh/userauth2-client.c2540
-rw-r--r--ssh/userauth2-server.c379
-rw-r--r--ssh/verstring.c642
-rw-r--r--ssh/x11fwd.c638
-rw-r--r--ssh/zlib.c1252
-rw-r--r--ssh1bpp.c387
-rw-r--r--ssh1connection-client.c547
-rw-r--r--ssh1connection-server.c365
-rw-r--r--ssh1connection.c815
-rw-r--r--ssh1connection.h123
-rw-r--r--ssh1login-server.c447
-rw-r--r--ssh1login.c1254
-rw-r--r--ssh2bpp-bare.c204
-rw-r--r--ssh2bpp.c982
-rw-r--r--ssh2connection-client.c505
-rw-r--r--ssh2connection-server.c306
-rw-r--r--ssh2connection.c1745
-rw-r--r--ssh2connection.h235
-rw-r--r--ssh2kex-client.c930
-rw-r--r--ssh2kex-server.c330
-rw-r--r--ssh2transport.c2181
-rw-r--r--ssh2transport.h245
-rw-r--r--ssh2userauth-server.c377
-rw-r--r--ssh2userauth.c1973
-rw-r--r--sshaes.c1913
-rw-r--r--ssharcf.c141
-rw-r--r--sshargon2.c565
-rw-r--r--sshauxcrypt.c166
-rw-r--r--sshbcrypt.c119
-rw-r--r--sshblowf.c699
-rw-r--r--sshblowf.h14
-rw-r--r--sshccp.c1062
-rw-r--r--sshchan.h316
-rw-r--r--sshcommon.c946
-rw-r--r--sshcr.h1
-rw-r--r--sshcrc.c108
-rw-r--r--sshcrcda.c171
-rw-r--r--sshdes.c1048
-rw-r--r--sshdh.c272
-rw-r--r--sshdss.c503
-rw-r--r--sshdssg.c103
-rw-r--r--sshecc.c1706
-rw-r--r--sshgss.h217
-rw-r--r--sshgssc.c288
-rw-r--r--sshgssc.h24
-rw-r--r--sshhmac.c257
-rw-r--r--sshkeygen.h10
-rw-r--r--sshmd5.c245
-rw-r--r--sshnogss.c19
-rw-r--r--sshppl.h174
-rw-r--r--sshprime.c762
-rw-r--r--sshprng.c287
-rw-r--r--sshpubk.c207
-rw-r--r--sshrsa.c1109
-rw-r--r--sshrsag.c292
-rw-r--r--sshserver.c591
-rw-r--r--sshserver.h143
-rw-r--r--sshsh256.c940
-rw-r--r--sshsh512.c836
-rw-r--r--sshsha.c934
-rw-r--r--sshshare.c2180
-rw-r--r--sshutils.c128
-rw-r--r--sshverstring.c628
-rw-r--r--sshzlib.c1253
-rw-r--r--storage.h31
-rw-r--r--stripctrl.c476
-rw-r--r--stubs/CMakeLists.txt31
-rw-r--r--stubs/no-ca-config.c14
-rw-r--r--stubs/no-cmdline.c22
-rw-r--r--stubs/no-gss.c (renamed from NOGSS.C)0
-rw-r--r--stubs/no-print.c (renamed from NOPRINT.C)0
-rw-r--r--stubs/no-rand.c (renamed from norand.c)0
-rw-r--r--stubs/no-term.c16
-rw-r--r--stubs/no-timing.c21
-rw-r--r--stubs/null-cipher.c11
-rw-r--r--stubs/null-key.c22
-rw-r--r--stubs/null-lp.c8
-rw-r--r--stubs/null-mac.c11
-rw-r--r--stubs/null-opener.c20
-rw-r--r--stubs/null-plug.c40
-rw-r--r--stubs/null-seat.c65
-rw-r--r--supdup.c923
-rw-r--r--telnet.c1070
-rw-r--r--terminal.c7656
-rw-r--r--terminal.h529
-rw-r--r--terminal/bidi.c3609
-rw-r--r--terminal/bidi.h147
-rw-r--r--terminal/bidi_gettype.c33
-rw-r--r--terminal/bidi_test.c372
-rw-r--r--terminal/terminal.c7910
-rw-r--r--terminal/terminal.h563
-rwxr-xr-xtest/agentmulti.py56
-rwxr-xr-xtest/ca.py198
-rw-r--r--test/cryptsuite.py1220
-rw-r--r--test/fuzzterm.c218
-rwxr-xr-xtest/list-accel.py38
-rw-r--r--test/primegen.py2
-rw-r--r--test/sclog/CMakeLists.txt2
-rw-r--r--test/sclog/sclog.c65
-rw-r--r--test/ssh.py8
-rw-r--r--test/testcrypt-enum.h178
-rw-r--r--test/testcrypt-func.h582
-rw-r--r--test/testcrypt.c1718
-rw-r--r--test/testcrypt.py234
-rw-r--r--test/testsc.c1945
-rw-r--r--test/testzlib.c114
-rw-r--r--testback.c214
-rw-r--r--testcrypt.c1587
-rw-r--r--testcrypt.h326
-rw-r--r--testsc.c1645
-rw-r--r--testzlib.c114
-rw-r--r--time.c16
-rw-r--r--tree234.c1611
-rw-r--r--tree234.h22
-rw-r--r--unix/CMakeLists.txt223
-rw-r--r--unix/agent-client.c226
-rw-r--r--unix/agent-socket.c (renamed from UNIX/uxagentsock.c)0
-rw-r--r--unix/askpass.c662
-rw-r--r--unix/cliloop.c (renamed from UNIX/uxcliloop.c)0
-rw-r--r--unix/columns.c1276
-rw-r--r--unix/columns.h73
-rw-r--r--unix/config-gtk.c160
-rw-r--r--unix/config-unix.c49
-rwxr-xr-xunix/configure3
-rw-r--r--unix/console.c579
-rw-r--r--unix/dialog.c4385
-rw-r--r--unix/fd-socket.c417
-rw-r--r--unix/gss.c176
-rw-r--r--unix/gtk-common.c243
-rw-r--r--unix/gtkapp.c347
-rw-r--r--unix/gtkask.c662
-rw-r--r--unix/gtkcfg.c160
-rw-r--r--unix/gtkcols.c1201
-rw-r--r--unix/gtkcols.h65
-rw-r--r--unix/gtkcomm.c241
-rw-r--r--unix/gtkdlg.c4195
-rw-r--r--unix/gtkfont.c3808
-rw-r--r--unix/gtkfont.h186
-rw-r--r--unix/gtkmain.c673
-rw-r--r--unix/gtkmisc.c223
-rw-r--r--unix/gtkwin.c5431
-rw-r--r--unix/keygen-noise.c64
-rw-r--r--unix/local-proxy.c116
-rw-r--r--unix/main-gtk-application.c326
-rw-r--r--unix/main-gtk-simple.c679
-rw-r--r--unix/network.c1755
-rw-r--r--unix/no-gtk.c11
-rw-r--r--unix/noaskpass.c19
-rw-r--r--unix/noise.c (renamed from UNIX/UXNOISE.C)0
-rw-r--r--unix/pageant.c1533
-rw-r--r--unix/peerinfo.c32
-rw-r--r--unix/platform.h474
-rw-r--r--unix/plink.c984
-rw-r--r--unix/printing.c58
-rw-r--r--unix/procnet.c4
-rw-r--r--unix/psocks.c176
-rw-r--r--unix/psusan.c421
-rw-r--r--unix/pterm-config-xpm.c (renamed from UNIX/XPMPTCFG.C)0
-rw-r--r--unix/pterm-xpm.c (renamed from UNIX/XPMPTERM.C)0
-rw-r--r--unix/pterm.c49
-rw-r--r--unix/pty.c1615
-rw-r--r--unix/putty-config-xpm.c (renamed from UNIX/XPMPUCFG.C)0
-rw-r--r--unix/putty-xpm.c (renamed from UNIX/XPMPUTTY.C)0
-rw-r--r--unix/putty.c92
-rw-r--r--unix/serial.c596
-rw-r--r--unix/sftp.c584
-rw-r--r--unix/sftpserver.c703
-rw-r--r--unix/sharing.c370
-rw-r--r--unix/storage.c976
-rw-r--r--unix/unicode.c271
-rw-r--r--unix/unifont.c3806
-rw-r--r--unix/unifont.h186
-rw-r--r--unix/unix.h460
-rw-r--r--unix/uppity.c969
-rw-r--r--unix/utils/align_label_left.c20
-rw-r--r--unix/utils/arm_arch_queries.c105
-rw-r--r--unix/utils/arm_arch_queries.h69
-rw-r--r--unix/utils/block_signal.c21
-rw-r--r--unix/utils/buildinfo_gtk_version.c14
-rw-r--r--unix/utils/cloexec.c49
-rw-r--r--unix/utils/dputs.c24
-rw-r--r--unix/utils/filename.c72
-rw-r--r--unix/utils/fontspec.c35
-rw-r--r--unix/utils/get_label_text_dimensions.c42
-rw-r--r--unix/utils/get_username.c52
-rw-r--r--unix/utils/get_x11_display.c34
-rw-r--r--unix/utils/getticks.c33
-rw-r--r--unix/utils/keysym_to_unicode.c1011
-rw-r--r--unix/utils/make_dir_and_check_ours.c60
-rw-r--r--unix/utils/make_dir_path.c39
-rw-r--r--unix/utils/make_spr_sw_abort_errno.c22
-rw-r--r--unix/utils/nonblock.c55
-rw-r--r--unix/utils/open_for_write_would_lose_data.c44
-rw-r--r--unix/utils/our_dialog.c135
-rw-r--r--unix/utils/pgp_fingerprints.c23
-rw-r--r--unix/utils/pollwrap.c190
-rw-r--r--unix/utils/signal.c30
-rw-r--r--unix/utils/string_width.c18
-rw-r--r--unix/utils/x11_ignore_error.c88
-rw-r--r--unix/ux_x11.c208
-rw-r--r--unix/uxagentc.c226
-rw-r--r--unix/uxagentsock.c91
-rw-r--r--unix/uxcfg.c70
-rw-r--r--unix/uxcliloop.c125
-rw-r--r--unix/uxcons.c530
-rw-r--r--unix/uxfdsock.c357
-rw-r--r--unix/uxgen.c64
-rw-r--r--unix/uxgss.c175
-rw-r--r--unix/uxmisc.c371
-rw-r--r--unix/uxnet.c1761
-rw-r--r--unix/uxnogtk.c11
-rw-r--r--unix/uxnoise.c130
-rw-r--r--unix/uxpeer.c32
-rw-r--r--unix/uxpgnt.c1540
-rw-r--r--unix/uxplink.c966
-rw-r--r--unix/uxpoll.c169
-rw-r--r--unix/uxprint.c58
-rw-r--r--unix/uxproxy.c105
-rw-r--r--unix/uxpsusan.c422
-rw-r--r--unix/uxpterm.c50
-rw-r--r--unix/uxpty.c1612
-rw-r--r--unix/uxputty.c92
-rw-r--r--unix/uxsel.c4
-rw-r--r--unix/uxser.c595
-rw-r--r--unix/uxserver.c850
-rw-r--r--unix/uxsftp.c578
-rw-r--r--unix/uxsftpserver.c703
-rw-r--r--unix/uxshare.c370
-rw-r--r--unix/uxsignal.c47
-rw-r--r--unix/uxsocks.c178
-rw-r--r--unix/uxstore.c822
-rw-r--r--unix/uxucs.c268
-rw-r--r--unix/uxutils.c65
-rw-r--r--unix/uxutils.h65
-rw-r--r--unix/window.c5639
-rw-r--r--unix/x11.c221
-rw-r--r--unix/x11misc.c83
-rw-r--r--unix/x11misc.h8
-rw-r--r--unix/xkeysym.c1011
-rw-r--r--unix/xpmptcfg.c150
-rw-r--r--unix/xpmpterm.c143
-rw-r--r--unix/xpmpucfg.c150
-rw-r--r--unix/xpmputty.c147
-rw-r--r--utils.c1122
-rw-r--r--utils/CMakeLists.txt78
-rw-r--r--utils/antispoof.c28
-rw-r--r--utils/backend_socket_log.c62
-rw-r--r--utils/base64_decode.c37
-rw-r--r--utils/base64_decode_atom.c54
-rw-r--r--utils/base64_encode.c40
-rw-r--r--utils/base64_encode_atom.c30
-rw-r--r--utils/base64_valid.c54
-rw-r--r--utils/bufchain.c193
-rw-r--r--utils/buildinfo.c164
-rw-r--r--utils/burnstr.c15
-rw-r--r--utils/cert-expr.c968
-rw-r--r--utils/chomp.c26
-rw-r--r--utils/cmdline_get_passwd_input_state_new.c9
-rw-r--r--utils/conf.c593
-rw-r--r--utils/conf_dest.c15
-rw-r--r--utils/conf_launchable.c14
-rw-r--r--utils/ctrlparse.c49
-rw-r--r--utils/ctrlset_normalise.c60
-rw-r--r--utils/debug.c56
-rw-r--r--utils/decode_utf8.c178
-rw-r--r--utils/decode_utf8_to_wchar.c20
-rw-r--r--utils/default_description.c22
-rw-r--r--utils/dup_mb_to_wc.c29
-rw-r--r--utils/dup_wc_to_mb.c38
-rw-r--r--utils/dupcat.c48
-rw-r--r--utils/dupprintf.c100
-rw-r--r--utils/dupstr.c19
-rw-r--r--utils/encode_utf8.c29
-rw-r--r--utils/encode_wide_string_as_utf8.c25
-rw-r--r--utils/fgetline.c25
-rw-r--r--utils/host_ca_new_free.c22
-rw-r--r--utils/host_strchr.c18
-rw-r--r--utils/host_strchr_internal.c80
-rw-r--r--utils/host_strcspn.c19
-rw-r--r--utils/host_strduptrim.c51
-rw-r--r--utils/host_strrchr.c18
-rw-r--r--utils/key_components.c93
-rw-r--r--utils/log_proxy_stderr.c103
-rw-r--r--utils/ltime.c (renamed from TIME.C)0
-rw-r--r--utils/make_spr_sw_abort_static.c21
-rw-r--r--utils/marshal.c338
-rw-r--r--utils/memory.c (renamed from memory.c)0
-rw-r--r--utils/memxor.c34
-rw-r--r--utils/nullstrcmp.c21
-rw-r--r--utils/out_of_memory.c11
-rw-r--r--utils/parse_blocksize.c40
-rw-r--r--utils/percent_decode.c41
-rw-r--r--utils/percent_encode.c34
-rw-r--r--utils/prompts.c69
-rw-r--r--utils/ptrlen.c110
-rw-r--r--utils/read_file_into.c19
-rw-r--r--utils/seat_connection_fatal.c20
-rw-r--r--utils/seat_dialog_text.c41
-rw-r--r--utils/sessprep.c (renamed from sessprep.c)0
-rw-r--r--utils/sk_free_peer_info.c14
-rw-r--r--utils/smemclr.c52
-rw-r--r--utils/smemeq.c25
-rw-r--r--utils/spr_get_error_message.c13
-rw-r--r--utils/ssh2_pick_fingerprint.c27
-rw-r--r--utils/ssh_key_clone.c32
-rw-r--r--utils/sshutils.c128
-rw-r--r--utils/strbuf.c128
-rw-r--r--utils/string_length_for_printf.c21
-rw-r--r--utils/stripctrl.c477
-rw-r--r--utils/tempseat.c430
-rw-r--r--utils/tree234.c1638
-rw-r--r--utils/utils.h12
-rw-r--r--utils/validate_manual_hostkey.c121
-rw-r--r--utils/version.c23
-rw-r--r--utils/wcwidth.c887
-rw-r--r--utils/wildcard.c486
-rw-r--r--utils/wordwrap.c36
-rw-r--r--utils/write_c_string_literal.c39
-rw-r--r--utils/x11_dehexify.c29
-rw-r--r--utils/x11_identify_auth_proto.c17
-rw-r--r--utils/x11_make_greeting.c67
-rw-r--r--utils/x11_parse_ip.c21
-rw-r--r--utils/x11authfile.c244
-rw-r--r--utils/x11authnames.c9
-rw-r--r--version.c28
-rw-r--r--version.h22
-rw-r--r--wcwidth.c558
-rw-r--r--wildcard.c486
-rw-r--r--windows/CMakeLists.txt186
-rw-r--r--windows/agent-client.c293
-rw-r--r--windows/cliloop.c134
-rw-r--r--windows/config.c384
-rw-r--r--windows/conpty.c433
-rw-r--r--windows/console.c496
-rw-r--r--windows/controls.c2658
-rw-r--r--windows/cryptoapi.h27
-rw-r--r--windows/dialog.c1342
-rw-r--r--windows/gss.c702
-rw-r--r--windows/handle-io.c687
-rw-r--r--windows/handle-socket.c512
-rw-r--r--windows/handle-wait.c143
-rw-r--r--windows/help.c250
-rw-r--r--windows/help.h219
-rw-r--r--windows/help.rc28
-rw-r--r--windows/installer.wxs15
-rw-r--r--windows/jump-list.c746
-rw-r--r--windows/local-proxy.c118
-rw-r--r--windows/named-pipe-client.c95
-rw-r--r--windows/named-pipe-server.c235
-rw-r--r--windows/network.c1879
-rw-r--r--windows/no-jump-list.c10
-rw-r--r--windows/nohelp.c15
-rw-r--r--windows/noise.c (renamed from WINDOWS/WINNOISE.C)0
-rw-r--r--windows/pageant.c1958
-rw-r--r--windows/pageant.rc8
-rw-r--r--windows/platform.h796
-rw-r--r--windows/plink.c556
-rw-r--r--windows/printing.c228
-rw-r--r--windows/psocks.c (renamed from WINDOWS/winsocks.c)0
-rw-r--r--windows/pterm.c65
-rw-r--r--windows/pterm.icobin0 -> 4078 bytes
-rw-r--r--windows/pterm.rc15
-rw-r--r--windows/ptermcfg.icobin0 -> 4078 bytes
-rw-r--r--windows/putty-common.rc298
-rw-r--r--windows/putty-rc.h51
-rw-r--r--windows/putty.c203
-rw-r--r--windows/putty.rc8
-rw-r--r--windows/puttygen.c2612
-rw-r--r--windows/puttygen.rc12
-rw-r--r--windows/puttytel.rc8
-rw-r--r--windows/rcstuff.h15
-rw-r--r--windows/security-api.h49
-rw-r--r--windows/select-cli.c78
-rw-r--r--windows/select-gui.c65
-rw-r--r--windows/serial.c468
-rw-r--r--windows/sftp.c656
-rw-r--r--windows/sharing.c113
-rw-r--r--windows/storage.c853
-rw-r--r--windows/test_screenshot.c45
-rw-r--r--windows/unicode.c1402
-rw-r--r--windows/utils/agent_mutex_name.c14
-rw-r--r--windows/utils/agent_named_pipe_name.c17
-rw-r--r--windows/utils/arm_arch_queries.c45
-rw-r--r--windows/utils/aux_match_opt.c117
-rw-r--r--windows/utils/centre_window.c20
-rw-r--r--windows/utils/cryptoapi.c89
-rw-r--r--windows/utils/defaults.c40
-rw-r--r--windows/utils/dll_hijacking_protection.c43
-rw-r--r--windows/utils/dputs.c39
-rw-r--r--windows/utils/escape_registry_key.c48
-rw-r--r--windows/utils/filename.c54
-rw-r--r--windows/utils/fontspec.c43
-rw-r--r--windows/utils/get_system_dir.c21
-rw-r--r--windows/utils/get_username.c77
-rw-r--r--windows/utils/getdlgitemtext_alloc.c20
-rw-r--r--windows/utils/interprocess_mutex.c48
-rw-r--r--windows/utils/is_console_handle.c13
-rw-r--r--windows/utils/load_system32_dll.c18
-rw-r--r--windows/utils/ltime.c27
-rw-r--r--windows/utils/make_spr_sw_abort_winerror.c22
-rw-r--r--windows/utils/makedlgitemborderless.c19
-rw-r--r--windows/utils/message_box.c49
-rw-r--r--windows/utils/minefield.c228
-rw-r--r--windows/utils/open_for_write_would_lose_data.c76
-rw-r--r--windows/utils/pgp_fingerprints_msgbox.c25
-rw-r--r--windows/utils/platform_get_x_display.c13
-rw-r--r--windows/utils/registry.c184
-rw-r--r--windows/utils/request_file.c71
-rw-r--r--windows/utils/screenshot.c126
-rw-r--r--windows/utils/security.c340
-rw-r--r--windows/utils/shinydialogbox.c111
-rw-r--r--windows/utils/split_into_argv.c707
-rw-r--r--windows/utils/strtoumax.c12
-rw-r--r--windows/utils/version.c45
-rw-r--r--windows/utils/win_strerror.c72
-rw-r--r--windows/win-gui-seat.h14
-rw-r--r--windows/win_res.h49
-rw-r--r--windows/win_res.rc2137
-rw-r--r--windows/wincapi.c93
-rw-r--r--windows/wincapi.h31
-rw-r--r--windows/wincfg.c405
-rw-r--r--windows/wincliloop.c136
-rw-r--r--windows/wincons.c452
-rw-r--r--windows/winctrls.c2600
-rw-r--r--windows/windefs.c40
-rw-r--r--windows/windlg.c1156
-rw-r--r--windows/window.c404
-rw-r--r--windows/wingss.c703
-rw-r--r--windows/winhandl.c731
-rw-r--r--windows/winhelp.c250
-rw-r--r--windows/winhelp.h208
-rw-r--r--windows/winhelp.rc28
-rw-r--r--windows/winhsock.c343
-rw-r--r--windows/winjump.c748
-rw-r--r--windows/winmisc.c467
-rw-r--r--windows/winmiscs.c285
-rw-r--r--windows/winnet.c1825
-rw-r--r--windows/winnohlp.c15
-rw-r--r--windows/winnoise.c142
-rw-r--r--windows/winnojmp.c8
-rw-r--r--windows/winnpc.c98
-rw-r--r--windows/winnps.c240
-rw-r--r--windows/winpgen.c2026
-rw-r--r--windows/winpgnt.c1723
-rw-r--r--windows/winpgntc.c348
-rw-r--r--windows/winplink.c535
-rw-r--r--windows/winprint.c224
-rw-r--r--windows/winproxy.c107
-rw-r--r--windows/winseat.h14
-rw-r--r--windows/winsecur.c334
-rw-r--r--windows/winsecur.h53
-rw-r--r--windows/winselcli.c78
-rw-r--r--windows/winselgui.c38
-rw-r--r--windows/winser.c465
-rw-r--r--windows/winsftp.c650
-rw-r--r--windows/winshare.c151
-rw-r--r--windows/winsocks.c24
-rw-r--r--windows/winstore.c873
-rw-r--r--windows/winstuff.h704
-rw-r--r--windows/wintime.c26
-rw-r--r--windows/winucs.c1213
-rw-r--r--windows/winutils.c788
-rw-r--r--windows/winx11.c19
-rw-r--r--windows/x11.c19
-rw-r--r--x11disp.c189
-rw-r--r--x11fwd.c1201
958 files changed, 162539 insertions, 226753 deletions
diff --git a/.gitignore b/.gitignore
index 92b7c10a..c663ae25 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,18 @@
*.o
*.pyc
+*.vcxproj
+*.vcxproj.filters
+.ninja_deps
+.ninja_log
+build.ninja
+/CMakeCache.txt
+CMakeFiles/
+Debug/
+Win32/
+cmake_install.cmake
+/shipped.txt
+*.dir/
+*.lib
.dirstamp
.deps
.DS_Store
@@ -16,12 +29,10 @@
/*.dsw
/*.opt
/*.dsp
+/*.sln
/*.tds
/*.td2
/*.map
-/Makefile.mgw
-/Makefile.vc
-/Makefile.lcc
/MSVC
/*.log
/*.GID
@@ -49,6 +60,9 @@
/testzlib
/cgtest
/scctest
+/test_host_strfoo
+/test_tree234
+/test_wildcard
/*.DSA
/*.RSA
/*.cnt
@@ -56,28 +70,10 @@
/.bmake
/build.log
/build.out
-/uxconfig.h
/empty.h
-/config.status
-/Makefile.am
-/Makefile.in
-/Makefile
+Makefile
/compile
-/config.status
-/configure
-/stamp-h1
-/aclocal.m4
-/ar-lib
-/autom4te.cache
-/depcomp
-/install-sh
-/local
-/missing
-/uxconfig.in
-/uxconfig.in~
-/uxconfig.h
-/licence.h
-/*.a
+*.a
/charset/sbcsdat.c
/contrib/cygtermd/cygtermd.exe
/doc/*.html
@@ -102,9 +98,6 @@
/icons/*.icns
/icons/*.xpm
/icons/*.c
-/unix/Makefile.gtk
-/unix/Makefile.ux
-/unix/Makefile.local
/unix/empty.h
/unix/plink
/unix/pterm
@@ -133,14 +126,6 @@
/windows/*.td2
/windows/*.map
/windows/*.rcpp
-/windows/Makefile.clangcl
-/windows/Makefile.mgw
-/windows/Makefile.vc
-/windows/Makefile.lcc
-/windows/MSVC
-/windows/DEVCPP
-/windows/VS2010
-/windows/VS2012
/windows/*.log
/windows/*.GID
/windows/local
diff --git a/BE_NONE.C b/BE_NONE.C
deleted file mode 100644
index 588bb1d4..00000000
--- a/BE_NONE.C
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Linking module for programs that do not support selection of backend
- * (such as pterm).
- */
-
-#include <stdio.h>
-#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/BE_NOSSH.C b/BE_NOSSH.C
deleted file mode 100644
index 1c94f3ae..00000000
--- a/BE_NOSSH.C
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Linking module for PuTTYtel: list the available backends not
- * including ssh.
- */
-
-#include <stdio.h>
-#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,
- NULL
-};
-
-const size_t n_ui_backends = 1;
diff --git a/BE_NOS_S.C b/BE_NOS_S.C
deleted file mode 100644
index 097433aa..00000000
--- a/BE_NOS_S.C
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Linking module for PuTTYtel: list the available backends not
- * including ssh.
- */
-
-#include <stdio.h>
-#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,
- NULL
-};
-
-const size_t n_ui_backends = 2;
diff --git a/BE_SSH.C b/BE_SSH.C
deleted file mode 100644
index 81be62d8..00000000
--- a/BE_SSH.C
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * 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 <stdio.h>
-#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/BUILDSCR b/BUILDSCR
index f7556f77..02de6432 100644
--- a/BUILDSCR
+++ b/BUILDSCR
@@ -35,7 +35,7 @@ module putty
ifeq "$(RELEASE)" "" set Ndate $(!builddate)
ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -pe 's/(....)(..)(..)/$$1-$$2-$$3/' > date
ifneq "$(Ndate)" "" read Date date
-set Epoch 17818 # update this at every release
+set Epoch 18293 # update this at every release
ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -ne 'use Time::Local; /(....)(..)(..)/ and print timegm(0,0,0,$$3,$$2-1,$$1) / 86400 - $(Epoch)' > days
ifneq "$(Ndate)" "" read Days days
@@ -55,7 +55,7 @@ ifneq "$(SNAPSHOT)" "" set Puttytextver PuTTY development snapshot $(Date).$(vcs
ifneq "$(SNAPSHOT)" "" set Textver Development snapshot $(Date).$(vcsid)
ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Puttytextver PuTTY custom build $(Date).$(vcsid)
ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Textver Custom build $(Date).$(vcsid)
-set Docmakever VERSION="$(Puttytextver)"
+in putty/doc do echo "\\\\versionid $(Puttytextver)" > version.but
# Set up the version string for use in the SSH connection greeting.
#
@@ -74,12 +74,6 @@ ifneq "$(PRERELEASE)" "" set Uxarcsuffix -$(PRERELEASE)~pre$(Ndate).$(vcsid)
ifneq "$(SNAPSHOT)" "" set Uxarcsuffix -$(Lastver)-$(Date).$(vcsid)
ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Uxarcsuffix -custom-$(Date).$(vcsid)
-# Set up the version number for the autoconf system.
-ifneq "$(RELEASE)" "" set Autoconfver $(RELEASE)
-ifneq "$(PRERELEASE)" "" set Autoconfver $(PRERELEASE)~pre$(Ndate).$(vcsid)
-ifneq "$(SNAPSHOT)" "" set Autoconfver $(Lastver)-$(Date).$(vcsid)
-ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Autoconfver Custom.$(Date).$(vcsid)
-
# Set up the filenames for the Windows installers (minus extension,
# which goes on later).
ifneq "$(RELEASE)" "" set Isuffix $(RELEASE)-installer
@@ -122,31 +116,44 @@ ifneq "$(SNAPSHOT)" "" in putty do echo '$#define SNAPSHOT' >> version.h
in putty do echo '$#define TEXTVER "$(Textver)"' >> version.h
in putty do echo '$#define SSHVER "$(Sshver)"' >> version.h
in putty do echo '$#define BINARY_VERSION $(Winvercommas)' >> version.h
-in putty do echo '$#define SOURCE_COMMIT "$(vcsfullid)"' >> version.h
-# Set up the extra arguments for the main Windows nmake command. The
-# user can define XFLAGS and MAKEARGS on the bob command line, to pass
-# in extra compile and make options respectively (e.g. to do a
-# debugging or Minefield build).
-set Makeargs
-ifneq "$(XFLAGS)" "" set Makeargs $(Makeargs) XFLAGS="$(XFLAGS)"
-ifneq "$(MAKEARGS)" "" set Makeargs $(Makeargs) $(MAKEARGS)
+# In cmake/gitcommit.cmake, replace the default output "unavailable"
+# with the commit string generated by bob, so that people rebuilding
+# the source archive will still get a useful value.
+in putty do sed -i '/set(DEFAULT_COMMIT/s/unavailable/$(vcsfullid)/' cmake/gitcommit.cmake
+
+in . do mkdir docbuild
+in docbuild do cmake ../putty/doc
+in docbuild do make -j$(nproc) VERBOSE=1
+in putty/doc do cp ../../docbuild/*.1 .
+in putty/doc do cp ../../docbuild/puttydoc.txt .
+in putty/doc do cp ../../docbuild/putty.chm .
+in putty/doc do cp -r ../../docbuild/html .
in putty do ./mksrcarc.sh
-in putty do ./mkunxarc.sh '$(Autoconfver)' '$(Uxarcsuffix)' $(Docmakever)
-in putty do perl mkfiles.pl
-in putty/doc do make $(Docmakever) putty.chm -j$(nproc)
+in putty do ./mkunxarc.sh '$(Uxarcsuffix)'
delegate -
# Run the test suite, under self-delegation so that we don't leave any
# cruft lying around. This involves doing a build of the Unix tools
# (which is a useful double-check anyway to pick up any build failures)
-in putty do ./mkauto.sh
-in putty do ./configure CC=clang CFLAGS="-fsanitize=address -fsanitize=leak"
-in putty do make -j$(nproc)
+in putty do cmake . -DCMAKE_C_COMPILER=clang -DCMAKE_C_FLAGS="-fsanitize=address -fsanitize=leak" -DSTRICT=ON
+in putty do make -j$(nproc) VERBOSE=1
in putty do python3 test/cryptsuite.py
enddelegate
+delegate -
+# Also, test-build the Windows tools using MinGW. This is important if
+# we want the MinGW build to carry on working, partly because of the
+# chance of compiler compatibility issues, but mostly because MinGW's
+# linker uses Unix-style library search semantics (once down the
+# library list), and no other Windows toolchain we build with is that
+# picky. So this ensures the Windows library structure continues to
+# work in the most difficult circumstance we expect it to encounter.
+in putty do cmake . -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-mingw.cmake -DSTRICT=ON
+in putty do make -j$(nproc) VERBOSE=1
+enddelegate
+
# Windowsify LICENCE, since it's going in the Windows installers.
in putty do perl -i~ -pe 'y/\015//d;s/$$/\015/' LICENCE
@@ -165,20 +172,30 @@ mkdir putty/windows/abuild64
#
# For the 32-bit ones, we set a subsystem version of 5.01, which
# allows the resulting files to still run on Windows XP.
-in putty/windows with clangcl32 do Platform=x86 make -f Makefile.clangcl BUILDDIR=build32/ SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc)
-in putty/windows with clangcl64 do Platform=x64 make -f Makefile.clangcl BUILDDIR=build64/ $(Makeargs) all -j$(nproc)
+in putty/windows/build32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DPUTTY_SUBSYSTEM_VERSION=5.01 -DSTRICT=ON
+in putty/windows/build32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
+in putty/windows/build64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON
+in putty/windows/build64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
+
+# The cmake mechanism used to set the subsystem version is a bit of a
+# bodge (it depends on knowing how cmake set up all its build command
+# variables), so just in case it breaks in future, double-check we
+# really did get the subsystem version we wanted.
+in putty/windows/build32 do objdump -x putty.exe > exe-headers.txt
+in putty/windows/build32 do grep -Ex 'MajorOSystemVersion[[:space:]]+5' exe-headers.txt
+in putty/windows/build32 do grep -Ex 'MinorOSystemVersion[[:space:]]+1' exe-headers.txt
+in putty/windows/build32 do grep -Ex 'MajorSubsystemVersion[[:space:]]+5' exe-headers.txt
+in putty/windows/build32 do grep -Ex 'MinorSubsystemVersion[[:space:]]+1' exe-headers.txt
# Build experimental Arm Windows binaries.
-in putty/windows with clangcl_a32 do Platform=arm make -f Makefile.clangcl BUILDDIR=abuild32/ SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc)
-in putty/windows with clangcl_a64 do Platform=arm64 make -f Makefile.clangcl BUILDDIR=abuild64/ $(Makeargs) all -j$(nproc)
+in putty/windows/abuild32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON
+in putty/windows/abuild32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
+in putty/windows/abuild64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON
+in putty/windows/abuild64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
-# Remove Windows binaries for the test programs we don't want to ship,
-# like testcrypt.exe. (But we still _built_ them, to ensure the build
-# worked.)
-in putty/windows do make -f Makefile.clangcl BUILDDIR=build32/ cleantestprogs
-in putty/windows do make -f Makefile.clangcl BUILDDIR=build64/ cleantestprogs
-in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild32/ cleantestprogs
-in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild64/ cleantestprogs
+# Make a list of the Windows binaries we're going to ship, so that we
+# can sign them.
+in putty/windows do for subdir in build32 abuild32 build64 abuild64; do sed "s!^!$$subdir/!" $$subdir/shipped.txt; done > to-sign.txt
# Code-sign the Windows binaries, if the local bob config provides a
# script to do so in a cross-compiling way. We assume here that the
@@ -187,17 +204,17 @@ in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild64/ cleantestprogs
# take the program name from an .exe's version resource, and that it
# can accept multiple .exe or .msi filename arguments and sign them
# all in place.
-ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ build*/*.exe abuild*/*.exe
+ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ $$(cat to-sign.txt)
# Make a preliminary set of cryptographic checksums giving the hashes
# of these versions of the binaries. We'll make the rest below.
in putty do for hash in md5 sha1 sha256 sha512; do for dir_plat in "build32 w32" "build64 w64" "abuild32 wa32" "abuild64 wa64"; do set -- $$dir_plat; (cd windows/$$1 && $${hash}sum *.exe | sed 's!\( \+\)!\1'$$2'/!;s!$$! (installer version)!') >> $${hash}sums.installer; done; done
# Build a WiX MSI installer, for each build flavour.
-in putty/windows with wixonlinux do candle -arch x86 -dRealPlatform=x86 -dDllOk=yes -dBuilddir=build32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer32.msi -spdb
-in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=x64 -dDllOk=yes -dBuilddir=build64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer64.msi -spdb
-in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm -dDllOk=no -dBuilddir=abuild32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera32.msi -spdb
-in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm64 -dDllOk=no -dBuilddir=abuild64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera64.msi -spdb
+in putty/windows with wixonlinux do candle -arch x86 -dRealPlatform=x86 -dDllOk=yes -dBuilddir=build32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer32.msi -spdb
+in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=x64 -dDllOk=yes -dBuilddir=build64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer64.msi -spdb
+in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm -dDllOk=no -dBuilddir=abuild32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera32.msi -spdb
+in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm64 -dDllOk=no -dBuilddir=abuild64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera64.msi -spdb
# Change the width field for our dialog background image so that it
# doesn't stretch across the whole dialog. (WiX's default one does; we
@@ -217,19 +234,16 @@ in putty/windows do ./msifixup.py installera64.msi --dialog-bmp-width=123 --plat
# Sign the Windows installers.
ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ -n "PuTTY Installer" installer32.msi installer64.msi installera32.msi installera64.msi
-# Delete the binaries and resource files from the build directories,
-# so that we can rebuild them differently.
-in putty/windows/build32 do rm -f *.exe *.res *.rcpp
-in putty/windows/build64 do rm -f *.exe *.res *.rcpp
-in putty/windows/abuild32 do rm -f *.exe *.res *.rcpp
-in putty/windows/abuild64 do rm -f *.exe *.res *.rcpp
-
# Build the standalone binaries, in both 32- and 64-bit flavours.
# These differ from the previous set in that they embed the help file.
-in putty/windows with clangcl32 do Platform=x86 make -f Makefile.clangcl BUILDDIR=build32/ RCFL=-DEMBED_CHM SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc)
-in putty/windows with clangcl64 do Platform=x64 make -f Makefile.clangcl BUILDDIR=build64/ RCFL=-DEMBED_CHM $(Makeargs) all -j$(nproc)
-in putty/windows with clangcl_a32 do Platform=arm make -f Makefile.clangcl BUILDDIR=abuild32/ RCFL=-DEMBED_CHM SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc)
-in putty/windows with clangcl_a64 do Platform=arm64 make -f Makefile.clangcl BUILDDIR=abuild64/ RCFL=-DEMBED_CHM $(Makeargs) all -j$(nproc)
+in putty/windows/build32 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/putty.chm)
+in putty/windows/build32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
+in putty/windows/build64 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/putty.chm)
+in putty/windows/build64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
+in putty/windows/abuild32 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/putty.chm)
+in putty/windows/abuild32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
+in putty/windows/abuild64 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/putty.chm)
+in putty/windows/abuild64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
# Build the 'old' binaries, which should still run on all 32-bit
# versions of Windows back to Win95 (but not Win32s). These link
@@ -241,55 +255,56 @@ in putty/windows with clangcl_a64 do Platform=arm64 make -f Makefile.clangcl BUI
#
# There's no installer to go with these, so they must also embed the
# help file.
-in putty/windows with clangcl32_2003 do Platform=x86 make -f Makefile.clangcl BUILDDIR=buildold/ RCFL=-DEMBED_CHM $(Makeargs) CCTARGET=i386-pc-windows-msvc13.0.0 SUBSYSVER=,4.0 EXTRA_windows=wincrt0.obj EXTRA_console=crt0.obj EXTRA_libs=libcpmt.lib XFLAGS="/arch:IA32 -Wno-pragma-pack" all -j$(nproc)
+in putty/windows/buildold with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32_2003) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DSTRICT=ON
+in putty/windows/buildold with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
-# Remove test programs again.
-in putty/windows do make -f Makefile.clangcl BUILDDIR=build32/ cleantestprogs
-in putty/windows do make -f Makefile.clangcl BUILDDIR=build64/ cleantestprogs
-in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild32/ cleantestprogs
-in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild64/ cleantestprogs
-in putty/windows do make -f Makefile.clangcl BUILDDIR=buildold/ cleantestprogs
+# Regenerate to-sign.txt with the 'old' binaries included.
+in putty/windows do for subdir in build32 abuild32 build64 abuild64 buildold; do sed "s!^!$$subdir/!" $$subdir/shipped.txt; done > to-sign.txt
# Code-sign the standalone versions of the binaries.
-ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ build*/*.exe abuild*/*.exe
+ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ $$(cat to-sign.txt)
+
+# Move the shipped (and signed) binaries into another directory to
+# deliver them from, so that we omit testcrypt and its ilk.
+in putty/windows do mkdir deliver
+in putty/windows do for subdir in build32 abuild32 build64 abuild64 buildold; do mkdir deliver/$$subdir; done
+in putty/windows do while read x; do mv $$x deliver/$$x; mv $$x.map deliver/$$x.map; done < to-sign.txt
-in putty/doc do make mostlyclean
-in putty/doc do make $(Docmakever) -j$(nproc)
-in putty/windows/buildold do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm
-in putty/windows/build32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm
-in putty/windows/build64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm
-in putty/windows/abuild32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm
-in putty/windows/abuild64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm
-in putty/doc do zip puttydoc.zip *.html
+in putty/windows/deliver/buildold do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm
+in putty/windows/deliver/build32 do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm
+in putty/windows/deliver/build64 do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm
+in putty/windows/deliver/abuild32 do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm
+in putty/windows/deliver/abuild64 do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm
+in docbuild/html do zip puttydoc.zip *.html
# Deliver the actual PuTTY release directory into a subdir `putty'.
-deliver putty/windows/buildold/*.exe putty/w32old/$@
-deliver putty/windows/buildold/putty.zip putty/w32old/$@
-deliver putty/windows/build32/*.exe putty/w32/$@
-deliver putty/windows/build32/putty.zip putty/w32/$@
-deliver putty/windows/build64/*.exe putty/w64/$@
-deliver putty/windows/build64/putty.zip putty/w64/$@
+deliver putty/windows/deliver/buildold/*.exe putty/w32old/$@
+deliver putty/windows/deliver/buildold/putty.zip putty/w32old/$@
+deliver putty/windows/deliver/build32/*.exe putty/w32/$@
+deliver putty/windows/deliver/build32/putty.zip putty/w32/$@
+deliver putty/windows/deliver/build64/*.exe putty/w64/$@
+deliver putty/windows/deliver/build64/putty.zip putty/w64/$@
deliver putty/windows/installer32.msi putty/w32/$(Ifilename32).msi
deliver putty/windows/installer64.msi putty/w64/$(Ifilename64).msi
deliver putty/windows/installera32.msi putty/wa32/$(Ifilenamea32).msi
deliver putty/windows/installera64.msi putty/wa64/$(Ifilenamea64).msi
-deliver putty/windows/abuild32/*.exe putty/wa32/$@
-deliver putty/windows/abuild32/putty.zip putty/wa32/$@
-deliver putty/windows/abuild64/*.exe putty/wa64/$@
-deliver putty/windows/abuild64/putty.zip putty/wa64/$@
-deliver putty/doc/puttydoc.zip putty/$@
-deliver putty/doc/putty.chm putty/$@
-deliver putty/doc/puttydoc.txt putty/$@
-deliver putty/doc/*.html putty/htmldoc/$@
+deliver putty/windows/deliver/abuild32/*.exe putty/wa32/$@
+deliver putty/windows/deliver/abuild32/putty.zip putty/wa32/$@
+deliver putty/windows/deliver/abuild64/*.exe putty/wa64/$@
+deliver putty/windows/deliver/abuild64/putty.zip putty/wa64/$@
+deliver docbuild/html/puttydoc.zip putty/$@
+deliver docbuild/putty.chm putty/$@
+deliver docbuild/puttydoc.txt putty/$@
+deliver docbuild/html/*.html putty/htmldoc/$@
deliver putty/putty-src.zip putty/$@
deliver putty/*.tar.gz putty/$@
# Deliver the map files alongside the `proper' release deliverables.
-deliver putty/windows/buildold/*.map maps/w32old/$@
-deliver putty/windows/build32/*.map maps/w32/$@
-deliver putty/windows/build64/*.map maps/w64/$@
-deliver putty/windows/abuild32/*.map maps/wa32/$@
-deliver putty/windows/abuild64/*.map maps/wa64/$@
+deliver putty/windows/deliver/buildold/*.map maps/w32old/$@
+deliver putty/windows/deliver/build32/*.map maps/w32/$@
+deliver putty/windows/deliver/build64/*.map maps/w64/$@
+deliver putty/windows/deliver/abuild32/*.map maps/wa32/$@
+deliver putty/windows/deliver/abuild64/*.map maps/wa64/$@
# Deliver sign.sh, so that whoever has just built PuTTY (the
# snapshot scripts or me, depending) can conveniently sign it with
diff --git a/BUILDSCR.CV b/BUILDSCR.CV
index fab61dc0..ab3107f2 100644
--- a/BUILDSCR.CV
+++ b/BUILDSCR.CV
@@ -6,16 +6,12 @@
module putty
-# Preparations.
-in putty do ./mkfiles.pl
-in putty do ./mkauto.sh
-in putty/doc do make
-
# Scan the Unix build, on a 64-bit system to differentiate as much as
# possible from the other scan of the cross-platform files.
delegate covscan64
- in putty do ./configure
- in putty do cov-build --dir cov-int make
+ in putty do mkdir linbuild
+ in putty/linbuild do cmake ..
+ in putty/linbuild do cov-build --dir ../cov-int make -j$(nproc) VERBOSE=1
in putty do tar czvf cov-int.tar.gz cov-int
return putty/cov-int.tar.gz
enddelegate
@@ -25,7 +21,9 @@ enddelegate
# Windows scanner for download).
delegate covscan32wine
in putty do tar xzvf cov-int.tar.gz
- in putty/windows do cov-build --dir ../cov-int make -f Makefile.mgw CC=winegcc RC=wrc
+ in putty do mkdir winbuild
+ in putty/winbuild do cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchain-winegcc.cmake
+ in putty/winbuild do cov-build --dir ../cov-int make -j$(nproc) VERBOSE=1
in putty do tar czvf cov-int.tar.gz cov-int
return putty/cov-int.tar.gz
enddelegate
diff --git a/Buildscr b/Buildscr
index f7556f77..02de6432 100644
--- a/Buildscr
+++ b/Buildscr
@@ -35,7 +35,7 @@ module putty
ifeq "$(RELEASE)" "" set Ndate $(!builddate)
ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -pe 's/(....)(..)(..)/$$1-$$2-$$3/' > date
ifneq "$(Ndate)" "" read Date date
-set Epoch 17818 # update this at every release
+set Epoch 18293 # update this at every release
ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -ne 'use Time::Local; /(....)(..)(..)/ and print timegm(0,0,0,$$3,$$2-1,$$1) / 86400 - $(Epoch)' > days
ifneq "$(Ndate)" "" read Days days
@@ -55,7 +55,7 @@ ifneq "$(SNAPSHOT)" "" set Puttytextver PuTTY development snapshot $(Date).$(vcs
ifneq "$(SNAPSHOT)" "" set Textver Development snapshot $(Date).$(vcsid)
ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Puttytextver PuTTY custom build $(Date).$(vcsid)
ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Textver Custom build $(Date).$(vcsid)
-set Docmakever VERSION="$(Puttytextver)"
+in putty/doc do echo "\\\\versionid $(Puttytextver)" > version.but
# Set up the version string for use in the SSH connection greeting.
#
@@ -74,12 +74,6 @@ ifneq "$(PRERELEASE)" "" set Uxarcsuffix -$(PRERELEASE)~pre$(Ndate).$(vcsid)
ifneq "$(SNAPSHOT)" "" set Uxarcsuffix -$(Lastver)-$(Date).$(vcsid)
ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Uxarcsuffix -custom-$(Date).$(vcsid)
-# Set up the version number for the autoconf system.
-ifneq "$(RELEASE)" "" set Autoconfver $(RELEASE)
-ifneq "$(PRERELEASE)" "" set Autoconfver $(PRERELEASE)~pre$(Ndate).$(vcsid)
-ifneq "$(SNAPSHOT)" "" set Autoconfver $(Lastver)-$(Date).$(vcsid)
-ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Autoconfver Custom.$(Date).$(vcsid)
-
# Set up the filenames for the Windows installers (minus extension,
# which goes on later).
ifneq "$(RELEASE)" "" set Isuffix $(RELEASE)-installer
@@ -122,31 +116,44 @@ ifneq "$(SNAPSHOT)" "" in putty do echo '$#define SNAPSHOT' >> version.h
in putty do echo '$#define TEXTVER "$(Textver)"' >> version.h
in putty do echo '$#define SSHVER "$(Sshver)"' >> version.h
in putty do echo '$#define BINARY_VERSION $(Winvercommas)' >> version.h
-in putty do echo '$#define SOURCE_COMMIT "$(vcsfullid)"' >> version.h
-# Set up the extra arguments for the main Windows nmake command. The
-# user can define XFLAGS and MAKEARGS on the bob command line, to pass
-# in extra compile and make options respectively (e.g. to do a
-# debugging or Minefield build).
-set Makeargs
-ifneq "$(XFLAGS)" "" set Makeargs $(Makeargs) XFLAGS="$(XFLAGS)"
-ifneq "$(MAKEARGS)" "" set Makeargs $(Makeargs) $(MAKEARGS)
+# In cmake/gitcommit.cmake, replace the default output "unavailable"
+# with the commit string generated by bob, so that people rebuilding
+# the source archive will still get a useful value.
+in putty do sed -i '/set(DEFAULT_COMMIT/s/unavailable/$(vcsfullid)/' cmake/gitcommit.cmake
+
+in . do mkdir docbuild
+in docbuild do cmake ../putty/doc
+in docbuild do make -j$(nproc) VERBOSE=1
+in putty/doc do cp ../../docbuild/*.1 .
+in putty/doc do cp ../../docbuild/puttydoc.txt .
+in putty/doc do cp ../../docbuild/putty.chm .
+in putty/doc do cp -r ../../docbuild/html .
in putty do ./mksrcarc.sh
-in putty do ./mkunxarc.sh '$(Autoconfver)' '$(Uxarcsuffix)' $(Docmakever)
-in putty do perl mkfiles.pl
-in putty/doc do make $(Docmakever) putty.chm -j$(nproc)
+in putty do ./mkunxarc.sh '$(Uxarcsuffix)'
delegate -
# Run the test suite, under self-delegation so that we don't leave any
# cruft lying around. This involves doing a build of the Unix tools
# (which is a useful double-check anyway to pick up any build failures)
-in putty do ./mkauto.sh
-in putty do ./configure CC=clang CFLAGS="-fsanitize=address -fsanitize=leak"
-in putty do make -j$(nproc)
+in putty do cmake . -DCMAKE_C_COMPILER=clang -DCMAKE_C_FLAGS="-fsanitize=address -fsanitize=leak" -DSTRICT=ON
+in putty do make -j$(nproc) VERBOSE=1
in putty do python3 test/cryptsuite.py
enddelegate
+delegate -
+# Also, test-build the Windows tools using MinGW. This is important if
+# we want the MinGW build to carry on working, partly because of the
+# chance of compiler compatibility issues, but mostly because MinGW's
+# linker uses Unix-style library search semantics (once down the
+# library list), and no other Windows toolchain we build with is that
+# picky. So this ensures the Windows library structure continues to
+# work in the most difficult circumstance we expect it to encounter.
+in putty do cmake . -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-mingw.cmake -DSTRICT=ON
+in putty do make -j$(nproc) VERBOSE=1
+enddelegate
+
# Windowsify LICENCE, since it's going in the Windows installers.
in putty do perl -i~ -pe 'y/\015//d;s/$$/\015/' LICENCE
@@ -165,20 +172,30 @@ mkdir putty/windows/abuild64
#
# For the 32-bit ones, we set a subsystem version of 5.01, which
# allows the resulting files to still run on Windows XP.
-in putty/windows with clangcl32 do Platform=x86 make -f Makefile.clangcl BUILDDIR=build32/ SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc)
-in putty/windows with clangcl64 do Platform=x64 make -f Makefile.clangcl BUILDDIR=build64/ $(Makeargs) all -j$(nproc)
+in putty/windows/build32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DPUTTY_SUBSYSTEM_VERSION=5.01 -DSTRICT=ON
+in putty/windows/build32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
+in putty/windows/build64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON
+in putty/windows/build64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
+
+# The cmake mechanism used to set the subsystem version is a bit of a
+# bodge (it depends on knowing how cmake set up all its build command
+# variables), so just in case it breaks in future, double-check we
+# really did get the subsystem version we wanted.
+in putty/windows/build32 do objdump -x putty.exe > exe-headers.txt
+in putty/windows/build32 do grep -Ex 'MajorOSystemVersion[[:space:]]+5' exe-headers.txt
+in putty/windows/build32 do grep -Ex 'MinorOSystemVersion[[:space:]]+1' exe-headers.txt
+in putty/windows/build32 do grep -Ex 'MajorSubsystemVersion[[:space:]]+5' exe-headers.txt
+in putty/windows/build32 do grep -Ex 'MinorSubsystemVersion[[:space:]]+1' exe-headers.txt
# Build experimental Arm Windows binaries.
-in putty/windows with clangcl_a32 do Platform=arm make -f Makefile.clangcl BUILDDIR=abuild32/ SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc)
-in putty/windows with clangcl_a64 do Platform=arm64 make -f Makefile.clangcl BUILDDIR=abuild64/ $(Makeargs) all -j$(nproc)
+in putty/windows/abuild32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON
+in putty/windows/abuild32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
+in putty/windows/abuild64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON
+in putty/windows/abuild64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
-# Remove Windows binaries for the test programs we don't want to ship,
-# like testcrypt.exe. (But we still _built_ them, to ensure the build
-# worked.)
-in putty/windows do make -f Makefile.clangcl BUILDDIR=build32/ cleantestprogs
-in putty/windows do make -f Makefile.clangcl BUILDDIR=build64/ cleantestprogs
-in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild32/ cleantestprogs
-in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild64/ cleantestprogs
+# Make a list of the Windows binaries we're going to ship, so that we
+# can sign them.
+in putty/windows do for subdir in build32 abuild32 build64 abuild64; do sed "s!^!$$subdir/!" $$subdir/shipped.txt; done > to-sign.txt
# Code-sign the Windows binaries, if the local bob config provides a
# script to do so in a cross-compiling way. We assume here that the
@@ -187,17 +204,17 @@ in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild64/ cleantestprogs
# take the program name from an .exe's version resource, and that it
# can accept multiple .exe or .msi filename arguments and sign them
# all in place.
-ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ build*/*.exe abuild*/*.exe
+ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ $$(cat to-sign.txt)
# Make a preliminary set of cryptographic checksums giving the hashes
# of these versions of the binaries. We'll make the rest below.
in putty do for hash in md5 sha1 sha256 sha512; do for dir_plat in "build32 w32" "build64 w64" "abuild32 wa32" "abuild64 wa64"; do set -- $$dir_plat; (cd windows/$$1 && $${hash}sum *.exe | sed 's!\( \+\)!\1'$$2'/!;s!$$! (installer version)!') >> $${hash}sums.installer; done; done
# Build a WiX MSI installer, for each build flavour.
-in putty/windows with wixonlinux do candle -arch x86 -dRealPlatform=x86 -dDllOk=yes -dBuilddir=build32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer32.msi -spdb
-in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=x64 -dDllOk=yes -dBuilddir=build64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer64.msi -spdb
-in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm -dDllOk=no -dBuilddir=abuild32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera32.msi -spdb
-in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm64 -dDllOk=no -dBuilddir=abuild64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera64.msi -spdb
+in putty/windows with wixonlinux do candle -arch x86 -dRealPlatform=x86 -dDllOk=yes -dBuilddir=build32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer32.msi -spdb
+in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=x64 -dDllOk=yes -dBuilddir=build64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer64.msi -spdb
+in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm -dDllOk=no -dBuilddir=abuild32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera32.msi -spdb
+in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm64 -dDllOk=no -dBuilddir=abuild64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera64.msi -spdb
# Change the width field for our dialog background image so that it
# doesn't stretch across the whole dialog. (WiX's default one does; we
@@ -217,19 +234,16 @@ in putty/windows do ./msifixup.py installera64.msi --dialog-bmp-width=123 --plat
# Sign the Windows installers.
ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ -n "PuTTY Installer" installer32.msi installer64.msi installera32.msi installera64.msi
-# Delete the binaries and resource files from the build directories,
-# so that we can rebuild them differently.
-in putty/windows/build32 do rm -f *.exe *.res *.rcpp
-in putty/windows/build64 do rm -f *.exe *.res *.rcpp
-in putty/windows/abuild32 do rm -f *.exe *.res *.rcpp
-in putty/windows/abuild64 do rm -f *.exe *.res *.rcpp
-
# Build the standalone binaries, in both 32- and 64-bit flavours.
# These differ from the previous set in that they embed the help file.
-in putty/windows with clangcl32 do Platform=x86 make -f Makefile.clangcl BUILDDIR=build32/ RCFL=-DEMBED_CHM SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc)
-in putty/windows with clangcl64 do Platform=x64 make -f Makefile.clangcl BUILDDIR=build64/ RCFL=-DEMBED_CHM $(Makeargs) all -j$(nproc)
-in putty/windows with clangcl_a32 do Platform=arm make -f Makefile.clangcl BUILDDIR=abuild32/ RCFL=-DEMBED_CHM SUBSYSVER=,5.01 $(Makeargs) all -j$(nproc)
-in putty/windows with clangcl_a64 do Platform=arm64 make -f Makefile.clangcl BUILDDIR=abuild64/ RCFL=-DEMBED_CHM $(Makeargs) all -j$(nproc)
+in putty/windows/build32 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/putty.chm)
+in putty/windows/build32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
+in putty/windows/build64 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/putty.chm)
+in putty/windows/build64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
+in putty/windows/abuild32 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/putty.chm)
+in putty/windows/abuild32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
+in putty/windows/abuild64 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/putty.chm)
+in putty/windows/abuild64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
# Build the 'old' binaries, which should still run on all 32-bit
# versions of Windows back to Win95 (but not Win32s). These link
@@ -241,55 +255,56 @@ in putty/windows with clangcl_a64 do Platform=arm64 make -f Makefile.clangcl BUI
#
# There's no installer to go with these, so they must also embed the
# help file.
-in putty/windows with clangcl32_2003 do Platform=x86 make -f Makefile.clangcl BUILDDIR=buildold/ RCFL=-DEMBED_CHM $(Makeargs) CCTARGET=i386-pc-windows-msvc13.0.0 SUBSYSVER=,4.0 EXTRA_windows=wincrt0.obj EXTRA_console=crt0.obj EXTRA_libs=libcpmt.lib XFLAGS="/arch:IA32 -Wno-pragma-pack" all -j$(nproc)
+in putty/windows/buildold with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32_2003) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DSTRICT=ON
+in putty/windows/buildold with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1
-# Remove test programs again.
-in putty/windows do make -f Makefile.clangcl BUILDDIR=build32/ cleantestprogs
-in putty/windows do make -f Makefile.clangcl BUILDDIR=build64/ cleantestprogs
-in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild32/ cleantestprogs
-in putty/windows do make -f Makefile.clangcl BUILDDIR=abuild64/ cleantestprogs
-in putty/windows do make -f Makefile.clangcl BUILDDIR=buildold/ cleantestprogs
+# Regenerate to-sign.txt with the 'old' binaries included.
+in putty/windows do for subdir in build32 abuild32 build64 abuild64 buildold; do sed "s!^!$$subdir/!" $$subdir/shipped.txt; done > to-sign.txt
# Code-sign the standalone versions of the binaries.
-ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ build*/*.exe abuild*/*.exe
+ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ $$(cat to-sign.txt)
+
+# Move the shipped (and signed) binaries into another directory to
+# deliver them from, so that we omit testcrypt and its ilk.
+in putty/windows do mkdir deliver
+in putty/windows do for subdir in build32 abuild32 build64 abuild64 buildold; do mkdir deliver/$$subdir; done
+in putty/windows do while read x; do mv $$x deliver/$$x; mv $$x.map deliver/$$x.map; done < to-sign.txt
-in putty/doc do make mostlyclean
-in putty/doc do make $(Docmakever) -j$(nproc)
-in putty/windows/buildold do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm
-in putty/windows/build32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm
-in putty/windows/build64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm
-in putty/windows/abuild32 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm
-in putty/windows/abuild64 do zip -k -j putty.zip `ls *.exe | grep -v puttytel` ../../doc/putty.chm
-in putty/doc do zip puttydoc.zip *.html
+in putty/windows/deliver/buildold do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm
+in putty/windows/deliver/build32 do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm
+in putty/windows/deliver/build64 do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm
+in putty/windows/deliver/abuild32 do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm
+in putty/windows/deliver/abuild64 do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm
+in docbuild/html do zip puttydoc.zip *.html
# Deliver the actual PuTTY release directory into a subdir `putty'.
-deliver putty/windows/buildold/*.exe putty/w32old/$@
-deliver putty/windows/buildold/putty.zip putty/w32old/$@
-deliver putty/windows/build32/*.exe putty/w32/$@
-deliver putty/windows/build32/putty.zip putty/w32/$@
-deliver putty/windows/build64/*.exe putty/w64/$@
-deliver putty/windows/build64/putty.zip putty/w64/$@
+deliver putty/windows/deliver/buildold/*.exe putty/w32old/$@
+deliver putty/windows/deliver/buildold/putty.zip putty/w32old/$@
+deliver putty/windows/deliver/build32/*.exe putty/w32/$@
+deliver putty/windows/deliver/build32/putty.zip putty/w32/$@
+deliver putty/windows/deliver/build64/*.exe putty/w64/$@
+deliver putty/windows/deliver/build64/putty.zip putty/w64/$@
deliver putty/windows/installer32.msi putty/w32/$(Ifilename32).msi
deliver putty/windows/installer64.msi putty/w64/$(Ifilename64).msi
deliver putty/windows/installera32.msi putty/wa32/$(Ifilenamea32).msi
deliver putty/windows/installera64.msi putty/wa64/$(Ifilenamea64).msi
-deliver putty/windows/abuild32/*.exe putty/wa32/$@
-deliver putty/windows/abuild32/putty.zip putty/wa32/$@
-deliver putty/windows/abuild64/*.exe putty/wa64/$@
-deliver putty/windows/abuild64/putty.zip putty/wa64/$@
-deliver putty/doc/puttydoc.zip putty/$@
-deliver putty/doc/putty.chm putty/$@
-deliver putty/doc/puttydoc.txt putty/$@
-deliver putty/doc/*.html putty/htmldoc/$@
+deliver putty/windows/deliver/abuild32/*.exe putty/wa32/$@
+deliver putty/windows/deliver/abuild32/putty.zip putty/wa32/$@
+deliver putty/windows/deliver/abuild64/*.exe putty/wa64/$@
+deliver putty/windows/deliver/abuild64/putty.zip putty/wa64/$@
+deliver docbuild/html/puttydoc.zip putty/$@
+deliver docbuild/putty.chm putty/$@
+deliver docbuild/puttydoc.txt putty/$@
+deliver docbuild/html/*.html putty/htmldoc/$@
deliver putty/putty-src.zip putty/$@
deliver putty/*.tar.gz putty/$@
# Deliver the map files alongside the `proper' release deliverables.
-deliver putty/windows/buildold/*.map maps/w32old/$@
-deliver putty/windows/build32/*.map maps/w32/$@
-deliver putty/windows/build64/*.map maps/w64/$@
-deliver putty/windows/abuild32/*.map maps/wa32/$@
-deliver putty/windows/abuild64/*.map maps/wa64/$@
+deliver putty/windows/deliver/buildold/*.map maps/w32old/$@
+deliver putty/windows/deliver/build32/*.map maps/w32/$@
+deliver putty/windows/deliver/build64/*.map maps/w64/$@
+deliver putty/windows/deliver/abuild32/*.map maps/wa32/$@
+deliver putty/windows/deliver/abuild64/*.map maps/wa64/$@
# Deliver sign.sh, so that whoever has just built PuTTY (the
# snapshot scripts or me, depending) can conveniently sign it with
diff --git a/Buildscr.cv b/Buildscr.cv
index fab61dc0..ab3107f2 100644
--- a/Buildscr.cv
+++ b/Buildscr.cv
@@ -6,16 +6,12 @@
module putty
-# Preparations.
-in putty do ./mkfiles.pl
-in putty do ./mkauto.sh
-in putty/doc do make
-
# Scan the Unix build, on a 64-bit system to differentiate as much as
# possible from the other scan of the cross-platform files.
delegate covscan64
- in putty do ./configure
- in putty do cov-build --dir cov-int make
+ in putty do mkdir linbuild
+ in putty/linbuild do cmake ..
+ in putty/linbuild do cov-build --dir ../cov-int make -j$(nproc) VERBOSE=1
in putty do tar czvf cov-int.tar.gz cov-int
return putty/cov-int.tar.gz
enddelegate
@@ -25,7 +21,9 @@ enddelegate
# Windows scanner for download).
delegate covscan32wine
in putty do tar xzvf cov-int.tar.gz
- in putty/windows do cov-build --dir ../cov-int make -f Makefile.mgw CC=winegcc RC=wrc
+ in putty do mkdir winbuild
+ in putty/winbuild do cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchain-winegcc.cmake
+ in putty/winbuild do cov-build --dir ../cov-int make -j$(nproc) VERBOSE=1
in putty do tar czvf cov-int.tar.gz cov-int
return putty/cov-int.tar.gz
enddelegate
diff --git a/CHARSET/LOCALENC.C b/CHARSET/LOCALENC.C
index 4aa4ae66..49719fbe 100644
--- a/CHARSET/LOCALENC.C
+++ b/CHARSET/LOCALENC.C
@@ -1,5 +1,5 @@
/*
- * local.c - translate our internal character set codes to and from
+ * localenc.c - translate our internal character set codes to and from
* our own set of plausibly legible character-set names. Also
* provides a canonical name for each encoding (useful for software
* announcing what character set it will be using), and a set of
@@ -103,7 +103,7 @@ int charset_from_localenc(const char *name)
p = name;
q = localencs[i].name;
while (*p || *q) {
- if (tolower((unsigned char)*p) != tolower((unsigned char)*q))
+ if (tolower((unsigned char)*p) != tolower((unsigned char)*q))
break;
p++; q++;
}
diff --git a/CHARSET/MIMEENC.C b/CHARSET/MIMEENC.C
index 2fec6d26..8a0203b3 100644
--- a/CHARSET/MIMEENC.C
+++ b/CHARSET/MIMEENC.C
@@ -207,7 +207,7 @@ int charset_from_mimeenc(const char *name)
p = name;
q = mimeencs[i].name;
while (*p || *q) {
- if (tolower((unsigned char)*p) != tolower((unsigned char)*q))
+ if (tolower((unsigned char)*p) != tolower((unsigned char)*q))
break;
p++; q++;
}
diff --git a/CHARSET/SBCSGEN.PL b/CHARSET/SBCSGEN.PL
index 078aebf7..6012c933 100644
--- a/CHARSET/SBCSGEN.PL
+++ b/CHARSET/SBCSGEN.PL
@@ -1,11 +1,19 @@
-#!/usr/bin/env perl -w
+#!/usr/bin/env perl
# This script generates sbcsdat.c (the data for all the SBCSes) from its
# source form sbcs.dat.
-$infile = "sbcs.dat";
+use warnings;
+use Getopt::Long;
+use File::Basename;
+
+$infile = (dirname __FILE__) . "/sbcs.dat";
$outfile = "sbcsdat.c";
+my $usage = "usage: sbcsgen.pl [-o OUTFILE]\n";
+GetOptions("o|output=s" => \$outfile)
+ or die $usage;
+
open FOO, $infile;
open BAR, ">$outfile";
select BAR;
@@ -30,7 +38,7 @@ my @charsetnames = ();
my @sortpriority = ();
while (<FOO>) {
- chomp;
+ chomp; y/\r//d;
if (/^charset (.*)$/) {
$charsetname = $1;
@vals = ();
diff --git a/CHARSET/XENC.C b/CHARSET/XENC.C
index 964ca6ff..24592ad1 100644
--- a/CHARSET/XENC.C
+++ b/CHARSET/XENC.C
@@ -82,7 +82,7 @@ int charset_from_xenc(const char *name)
p = name;
q = xencs[i].name;
while (*p || *q) {
- if (tolower((unsigned char)*p) != tolower((unsigned char)*q))
+ if (tolower((unsigned char)*p) != tolower((unsigned char)*q))
break;
p++; q++;
}
diff --git a/CHECKLST.txt b/CHECKLST.txt
index 5e9a7a08..3d618cbd 100644
--- a/CHECKLST.txt
+++ b/CHECKLST.txt
@@ -8,7 +8,7 @@ When we begin to work towards a release and want to enable
pre-releases on the website:
- Make a branch whose tip will be the current state of the
- pre-release. Regardless of whether the branch is from master or
+ pre-release. Regardless of whether the branch is from main or
from a prior release branch, the name of the branch must now be in
the form 'pre-X.YZ', or else the website will fail to link to it
properly in gitweb and the build script will check out the wrong
@@ -31,11 +31,26 @@ Things to do during the branch-stabilisation period:
word XXX-REVIEW-BEFORE-RELEASE. (Any such comments should state
clearly what needs to be done.)
- - Do some testing of the Windows version with Minefield (you can
- build a Minefield version using 'bob . XFLAGS=-DMINEFIELD'), and of
- the Unix version with valgrind and/or Address Sanitiser. In
- particular, any headline features for the release should get a
- workout with memory checking enabled!
+ - Test the Unix build with Address Sanitiser. In particular, any
+ headline features for the release should get a workout with memory
+ checking enabled!
+
+ - Test the Windows build with Address Sanitiser too (as of VS 2022).
+ + In the course of that, give a recent Windows pterm a try, to
+ make sure that still works.
+
+ - Test building and running on old platforms:
+ + build on Debian stretch (containing CMake 3.7, the earliest
+ CMake we claim support for)
+ + build with all three major versions of GTK
+ + build the old-Windows binaries and test-run them on Win95 (PuTTY
+ proper even without WinSock2)
+
+ - Check Coverity is happy.
+
+ - Check the side-channel tester is happy.
+
+ - Check all the non-SSH network backends still basically work.
Making a release candidate build
--------------------------------
@@ -73,9 +88,6 @@ Making a release candidate build
- Make the release tag, pointing at the version-update commit we just
generated.
- - If the release is on a branch (which I expect it generally will
- be), merge that branch to master.
-
- Make a release-candidate build from the release tag, and put the
build.out and build.log files somewhere safe. Normally I store
these inside the ~/src/putty/X.YZ directory, alongside the git
@@ -108,7 +120,7 @@ Making a release candidate build
* make sure they basically work
* check they report the right version number
* if there's any easily observable behaviour difference between
- the release branch and master, arrange to observe it
+ the release branch and main, arrange to observe it
* test that the Windows installer installs successfully
+ on x86 and Arm, and test that putty.exe runs in both cases
* test that the Unix source tarball unpacks and builds
@@ -116,9 +128,13 @@ Making a release candidate build
also try Debian sid
+ test-build with all of GTK 1, 2 and 3
+ test-build with -DNOT_X_WINDOWS
+ * test that the Windows source builds with Visual Studio (just in
+ case there's an unguarded clangism that would prevent it)
+ * quick check of the outlying network protocols (Telnet, SUPDUP
+ etc)
* feed the release-candidate source to Coverity and make sure it
didn't turn up any last-minute problems
- * make sure we have a clean run of sctest
+ * make sure we have a clean run of testsc
* do some testing on a system with a completely clean slate (no
prior saved session data)
@@ -180,25 +196,28 @@ locally, this is the procedure for putting it up on the web.
- Check that downloads via version-numbered URLs all work:
../putty/release.pl --version=X.YZ --precheck
- If this has trouble accessing chiark's ftp server, that is
- unfortunately normal; add --no-ftp and try again.
- Switch the 'latest' links over to the new release:
* Update the HTTP redirect at the:www/putty/htaccess .
- * Update the FTP symlink at chiark:ftp/putty-latest .
- Now verify that downloads via the 'latest' URLs are all redirected
correctly and work:
../putty/release.pl --version=X.YZ --postcheck
+ - If the release is on a branch (which I expect it generally will
+ be), merge that branch to main, so that the 'update version number'
+ change appears on main and the snapshots start announcing
+ themselves as post-X.YZ.
+
- Push all the git repositories:
* run 'git push' in the website checkout
* run 'git push' in the wishlist checkout
* push from the main PuTTY checkout. Typically this one will be
- pushing both the release tag and an update to the master branch,
- plus removing the pre-release branch, so you'll want some
+ pushing both the release tag and the merge we just made to the
+ main branch, plus removing the pre-release branch, so you'll
+ want some
commands along these lines:
- git push origin master # update the master branch
+ git push origin main # update the main branch
git push origin --tags # should push the new release tag
git push origin :pre-X.YZ # delete the pre-release branch
@@ -216,6 +235,17 @@ locally, this is the procedure for putting it up on the web.
subdirectory for the new version and that the links from its
latest.html point into that subdirectory.
+ - Start the process of updating our Windows Store entry:
+ + log into partner.microsoft.com and go to Partner Center
+ + start editing the existing app submission, which should
+ automatically create a new submission
+ * provide a new set of installer URLs, then click "save all"
+ which actually uploads them
+ * change the "what's new in this release" text in the store
+ listing
+ * upload revised screenshots, if necessary
+ + press Publish to submit that to the actual upload process
+
- Announce the release!
+ Construct a release announcement email whose message body is the
announcement written above, and which includes the following
diff --git a/CMDGEN.C b/CMDGEN.C
index ef9c2e1c..b12758a1 100644
--- a/CMDGEN.C
+++ b/CMDGEN.C
@@ -130,13 +130,19 @@ void help(void)
" public RFC 4716 / ssh.com public key\n"
" public-openssh OpenSSH public key\n"
" fingerprint output the key fingerprint\n"
+ " cert-info print certificate information\n"
" text output the key components as "
"'name=0x####'\n"
" -o specify output file\n"
" -l equivalent to `-O fingerprint'\n"
" -L equivalent to `-O public-openssh'\n"
" -p equivalent to `-O public'\n"
+ " --cert-info equivalent to `-O cert-info'\n"
" --dump equivalent to `-O text'\n"
+ " -E fptype specify fingerprint output type:\n"
+ " sha256, md5, sha256-cert, md5-cert\n"
+ " --certificate file incorporate a certificate into the key\n"
+ " --remove-certificate remove any certificate from the key\n"
" --reencrypt load a key and save it with fresh "
"encryption\n"
" --old-passphrase file\n"
@@ -214,6 +220,15 @@ static char *readpassphrase(const char *filename)
#define DEFAULT_RSADSA_BITS 2048
+static void spr_error(SeatPromptResult spr)
+{
+ if (spr.kind == SPRK_SW_ABORT) {
+ char *err = spr_get_error_message(spr);
+ fprintf(stderr, "puttygen: unable to read passphrase: %s", err);
+ sfree(err);
+ }
+}
+
/* For Unix in particular, but harmless if this main() is reused elsewhere */
const bool buildinfo_gtk_relevant = false;
@@ -226,7 +241,7 @@ int main(int argc, char **argv)
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;
+ OPENSSH_NEW, SSHCOM, TEXT, CERTINFO } outtype = PRIVATE;
int bits = -1;
const char *comment = NULL;
char *origcomment = NULL;
@@ -241,6 +256,8 @@ int main(int argc, char **argv)
char *old_passphrase = NULL, *new_passphrase = NULL;
bool load_encrypted;
const char *random_device = NULL;
+ char *certfile = NULL;
+ bool remove_cert = false;
int exit_status = 0;
const PrimeGenerationPolicy *primegen = &primegen_probabilistic;
bool strong_rsa = false;
@@ -286,75 +303,79 @@ int main(int argc, char **argv)
while (*p && *p != '=')
p++; /* find end of option */
if (*p == '=') {
- *p++ = '\0';
- val = 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;
- }
+ 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;
- }
+ 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;
- }
+ 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)
+ 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)
+ 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;
- }
+ if (!val && argc > 1)
+ --argc, val = *++argv;
+ if (!val) {
+ errs = true;
+ fprintf(stderr, "puttygen: option `-%s'"
+ " expects an argument\n", opt);
+ } else {
+ random_device = val;
+ }
} else if (!strcmp(opt, "-dump")) {
outtype = TEXT;
+ } else if (!strcmp(opt, "-cert-info") ||
+ !strcmp(opt, "-certinfo") ||
+ !strcmp(opt, "-cert_info")) {
+ outtype = CERTINFO;
} else if (!strcmp(opt, "-primes")) {
if (!val && argc > 1)
--argc, val = *++argv;
@@ -383,6 +404,18 @@ int main(int argc, char **argv)
}
} else if (!strcmp(opt, "-strong-rsa")) {
strong_rsa = true;
+ } else if (!strcmp(opt, "-certificate")) {
+ if (!val && argc > 1)
+ --argc, val = *++argv;
+ if (!val) {
+ errs = true;
+ fprintf(stderr, "puttygen: option `-%s'"
+ " expects an argument\n", opt);
+ } else {
+ certfile = val;
+ }
+ } else if (!strcmp(opt, "-remove-certificate")) {
+ remove_cert = true;
} else if (!strcmp(opt, "-reencrypt")) {
reencrypt = true;
} else if (!strcmp(opt, "-ppk-param") ||
@@ -461,9 +494,9 @@ int main(int argc, char **argv)
}
}
} else {
- errs = true;
- fprintf(stderr,
- "puttygen: no such option `-%s'\n", opt);
+ errs = true;
+ fprintf(stderr,
+ "puttygen: no such option `-%s'\n", opt);
}
p = NULL;
break;
@@ -569,6 +602,8 @@ int main(int argc, char **argv)
outtype = SSHCOM, sshver = 2;
else if (!strcmp(p, "text"))
outtype = TEXT;
+ else if (!strcmp(p, "cert-info"))
+ outtype = CERTINFO;
else {
fprintf(stderr,
"puttygen: unknown output type `%s'\n", p);
@@ -583,6 +618,10 @@ int main(int argc, char **argv)
fptype = SSH_FPTYPE_MD5;
else if (!strcmp(p, "sha256"))
fptype = SSH_FPTYPE_SHA256;
+ else if (!strcmp(p, "md5-cert"))
+ fptype = SSH_FPTYPE_MD5_CERT;
+ else if (!strcmp(p, "sha256-cert"))
+ fptype = SSH_FPTYPE_SHA256_CERT;
else {
fprintf(stderr, "puttygen: unknown fingerprint "
"type `%s'\n", p);
@@ -790,7 +829,8 @@ int main(int argc, char **argv)
outfiletmp = dupcat(outfile, ".tmp");
}
- if (!change_passphrase && !comment && !reencrypt) {
+ if (!change_passphrase && !comment && !reencrypt && !certfile &&
+ !remove_cert) {
fprintf(stderr, "puttygen: this command would perform no useful"
" action\n");
RETURN(1);
@@ -840,6 +880,26 @@ int main(int argc, char **argv)
RETURN(1);
}
+ /*
+ * Check consistency properties relating to certificates.
+ */
+ if (certfile && !(sshver == 2 && intype_has_private &&
+ outtype_has_private && infile)) {
+ fprintf(stderr, "puttygen: certificates can only be added to "
+ "existing SSH-2 private key files\n");
+ RETURN(1);
+ }
+ if (remove_cert && !(sshver == 2 && infile)) {
+ fprintf(stderr, "puttygen: certificates can only be removed from "
+ "existing SSH-2 key files\n");
+ RETURN(1);
+ }
+ if (certfile && remove_cert) {
+ fprintf(stderr, "puttygen: cannot both add and remove a "
+ "certificate\n");
+ RETURN(1);
+ }
+
/* ------------------------------------------------------------------
* Now we're ready to actually do some stuff.
*/
@@ -878,10 +938,10 @@ int main(int argc, char **argv)
PrimeGenerationContext *pgc = primegen_new_context(primegen);
if (keytype == DSA) {
- struct dss_key *dsskey = snew(struct dss_key);
- dsa_generate(dsskey, bits, pgc, &cmdgen_progress);
+ struct dsa_key *dsakey = snew(struct dsa_key);
+ dsa_generate(dsakey, bits, pgc, &cmdgen_progress);
ssh2key = snew(ssh2_userkey);
- ssh2key->key = &dsskey->sshk;
+ ssh2key->key = &dsakey->sshk;
ssh1key = NULL;
} else if (keytype == ECDSA) {
struct ecdsa_key *ek = snew(struct ecdsa_key);
@@ -941,16 +1001,16 @@ int main(int argc, char **argv)
if (encrypted && load_encrypted) {
if (!old_passphrase) {
prompts_t *p = new_prompts();
- int ret;
+ SeatPromptResult spr;
p->to_server = false;
p->from_server = false;
p->name = dupstr("SSH key passphrase");
add_prompt(p, dupstr("Enter passphrase to load key: "), false);
- ret = console_get_userpass_input(p);
- assert(ret >= 0);
- if (!ret) {
+ spr = console_get_userpass_input(p);
+ assert(spr.kind != SPRK_INCOMPLETE);
+ if (spr_is_abort(spr)) {
free_prompts(p);
- perror("puttygen: unable to read passphrase");
+ spr_error(spr);
RETURN(1);
} else {
old_passphrase = prompt_get_result(p->prompts[0]);
@@ -1073,6 +1133,124 @@ int main(int argc, char **argv)
}
/*
+ * Swap out the public key for a different one, if asked to.
+ */
+ if (certfile) {
+ Filename *certfilename = filename_from_str(certfile);
+ LoadedFile *certfile_lf;
+ const char *error = NULL;
+
+ if (!strcmp(certfile, "-"))
+ certfile_lf = lf_load_keyfile_fp(stdin, &error);
+ else
+ certfile_lf = lf_load_keyfile(certfilename, &error);
+
+ filename_free(certfilename);
+
+ if (!certfile_lf) {
+ fprintf(stderr, "puttygen: unable to load certificate file `%s': "
+ "%s\n", certfile, error);
+ RETURN(1);
+ }
+
+ char *algname = NULL;
+ char *comment = NULL;
+ strbuf *pub = strbuf_new();
+ if (!ppk_loadpub_s(BinarySource_UPCAST(certfile_lf), &algname,
+ BinarySink_UPCAST(pub), &comment, &error)) {
+ fprintf(stderr, "puttygen: unable to load certificate file `%s': "
+ "%s\n", certfile, error);
+ strbuf_free(pub);
+ sfree(algname);
+ sfree(comment);
+ lf_free(certfile_lf);
+ RETURN(1);
+ }
+
+ lf_free(certfile_lf);
+ sfree(comment);
+
+ const ssh_keyalg *alg = find_pubkey_alg(algname);
+ if (!alg) {
+ fprintf(stderr, "puttygen: certificate file `%s' has unsupported "
+ "algorithm name `%s'\n", certfile, algname);
+ strbuf_free(pub);
+ sfree(algname);
+ RETURN(1);
+ }
+
+ sfree(algname);
+
+ /* Check the two public keys match apart from certificates */
+ strbuf *old_basepub = strbuf_new();
+ ssh_key_public_blob(ssh_key_base_key(ssh2key->key),
+ BinarySink_UPCAST(old_basepub));
+
+ ssh_key *new_pubkey = ssh_key_new_pub(alg, ptrlen_from_strbuf(pub));
+ strbuf *new_basepub = strbuf_new();
+ ssh_key_public_blob(ssh_key_base_key(new_pubkey),
+ BinarySink_UPCAST(new_basepub));
+ ssh_key_free(new_pubkey);
+
+ bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(old_basepub),
+ ptrlen_from_strbuf(new_basepub));
+ strbuf_free(old_basepub);
+ strbuf_free(new_basepub);
+
+ if (!match) {
+ fprintf(stderr, "puttygen: certificate in `%s' does not match "
+ "public key in `%s'\n", certfile, infile);
+ strbuf_free(pub);
+ RETURN(1);
+ }
+
+ strbuf *priv = strbuf_new_nm();
+ ssh_key_private_blob(ssh2key->key, BinarySink_UPCAST(priv));
+ ssh_key *newkey = ssh_key_new_priv(
+ alg, ptrlen_from_strbuf(pub), ptrlen_from_strbuf(priv));
+ strbuf_free(pub);
+ strbuf_free(priv);
+
+ if (!newkey) {
+ fprintf(stderr, "puttygen: unable to combine certificate in `%s' "
+ "with private key\n", certfile);
+ RETURN(1);
+ }
+
+ ssh_key_free(ssh2key->key);
+ ssh2key->key = newkey;
+ } else if (remove_cert) {
+ /*
+ * Removing a certificate can be meaningfully done to a pure
+ * public key blob, as well as a full key pair.
+ */
+ if (ssh2key) {
+ ssh_key *newkey = ssh_key_clone(ssh_key_base_key(ssh2key->key));
+ ssh_key_free(ssh2key->key);
+ ssh2key->key = newkey;
+ } else if (ssh2blob) {
+ ptrlen algname = pubkey_blob_to_alg_name(
+ ptrlen_from_strbuf(ssh2blob));
+
+ const ssh_keyalg *alg = find_pubkey_alg_len(algname);
+
+ if (!alg) {
+ fprintf(stderr, "puttygen: input file `%s' has unsupported "
+ "algorithm name `%.*s'\n", infile,
+ PTRLEN_PRINTF(algname));
+ RETURN(1);
+ }
+
+ ssh_key *tmpkey = ssh_key_new_pub(
+ alg, ptrlen_from_strbuf(ssh2blob));
+ strbuf_clear(ssh2blob);
+ ssh_key_public_blob(ssh_key_base_key(tmpkey),
+ BinarySink_UPCAST(ssh2blob));
+ ssh_key_free(tmpkey);
+ }
+ }
+
+ /*
* Unless we're changing the passphrase, the old one (if any) is a
* reasonable default.
*/
@@ -1090,18 +1268,18 @@ int main(int argc, char **argv)
if (!new_passphrase && (change_passphrase ||
(keytype != NOKEYGEN && outtype != TEXT))) {
prompts_t *p = new_prompts();
- int ret;
+ SeatPromptResult spr;
p->to_server = false;
p->from_server = false;
p->name = dupstr("New SSH key passphrase");
add_prompt(p, dupstr("Enter passphrase to save key: "), false);
add_prompt(p, dupstr("Re-enter passphrase to verify: "), false);
- ret = console_get_userpass_input(p);
- assert(ret >= 0);
- if (!ret) {
+ spr = console_get_userpass_input(p);
+ assert(spr.kind != SPRK_INCOMPLETE);
+ if (spr_is_abort(spr)) {
free_prompts(p);
- perror("puttygen: unable to read new passphrase");
+ spr_error(spr);
RETURN(1);
} else {
if (strcmp(prompt_get_result_ref(p->prompts[0]),
@@ -1164,29 +1342,29 @@ int main(int argc, char **argv)
FILE *fp;
if (outfile) {
- fp = f_open(outfilename, "w", false);
- if (!fp) {
- fprintf(stderr, "unable to open output file\n");
- exit(1);
- }
+ fp = f_open(outfilename, "w", false);
+ if (!fp) {
+ fprintf(stderr, "unable to open output file\n");
+ exit(1);
+ }
} else {
- fp = stdout;
+ fp = stdout;
}
if (sshver == 1) {
- ssh1_write_pubkey(fp, ssh1key);
+ 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 (!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)
@@ -1200,26 +1378,26 @@ int main(int argc, char **argv)
char *fingerprint;
if (sshver == 1) {
- assert(ssh1key);
- fingerprint = rsa_ssh1_fingerprint(ssh1key);
+ 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 (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);
- }
+ fp = f_open(outfilename, "w", false);
+ if (!fp) {
+ fprintf(stderr, "unable to open output file\n");
+ exit(1);
+ }
} else {
- fp = stdout;
+ fp = stdout;
}
fprintf(fp, "%s\n", fingerprint);
if (outfile)
@@ -1271,9 +1449,8 @@ int main(int argc, char **argv)
} else {
assert(ssh2blob);
- BinarySource src[1];
- BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(ssh2blob));
- ptrlen algname = get_string(src);
+ ptrlen algname = pubkey_blob_to_alg_name(
+ ptrlen_from_strbuf(ssh2blob));
const ssh_keyalg *alg = find_pubkey_alg_len(algname);
if (!alg) {
fprintf(stderr, "puttygen: cannot extract key components "
@@ -1304,16 +1481,55 @@ int main(int argc, char **argv)
}
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);
+ key_component *comp = &kc->components[i];
+ fprintf(fp, "%s=", comp->name);
+ switch (comp->type) {
+ case KCT_MPINT: {
+ char *hex = mp_get_hex(comp->mp);
+ fprintf(fp, "0x%s\n", hex);
smemclr(hex, strlen(hex));
sfree(hex);
- } else {
- fprintf(fp, "%s=\"", kc->components[i].name);
- write_c_string_literal(fp, ptrlen_from_asciz(
- kc->components[i].text));
+ break;
+ }
+ case KCT_TEXT:
+ fputs("\"", fp);
+ write_c_string_literal(fp, ptrlen_from_strbuf(comp->str));
fputs("\"\n", fp);
+ break;
+ case KCT_BINARY: {
+ /*
+ * Display format for binary key components is to show
+ * them as base64, with a wrapper so that the actual
+ * printed string is along the lines of
+ * 'b64("aGVsbG8sIHdvcmxkCg==")'.
+ *
+ * That's a compromise between not being too verbose
+ * for a human reader, and still being reasonably
+ * friendly to people pasting the output of this
+ * 'puttygen --dump' option into Python code (which
+ * the format is designed to permit in general).
+ *
+ * Python users pasting a dump containing one of these
+ * will have to define a function 'b64' in advance
+ * which takes a string, which you can do most easily
+ * using this import statement, as seen in
+ * cryptsuite.py:
+ *
+ * from base64 import b64decode as b64
+ */
+ fputs("b64(\"", fp);
+ char b64[4];
+ for (size_t j = 0; j < comp->str->len; j += 3) {
+ size_t len = comp->str->len - j;
+ if (len > 3) len = 3;
+ base64_encode_atom(comp->str->u + j, len, b64);
+ fwrite(b64, 1, 4, fp);
+ }
+ fputs("\")\n", fp);
+ break;
+ }
+ default:
+ unreachable("bad key component type");
}
}
@@ -1322,6 +1538,83 @@ int main(int argc, char **argv)
key_components_free(kc);
break;
}
+
+ case CERTINFO: {
+ if (sshver == 1) {
+ fprintf(stderr, "puttygen: SSH-1 keys cannot contain "
+ "certificates\n");
+ RETURN(1);
+ }
+
+ const ssh_keyalg *alg;
+ ssh_key *sk;
+ bool sk_allocated = false;
+
+ if (ssh2key) {
+ sk = ssh2key->key;
+ alg = ssh_key_alg(sk);
+ } else {
+ assert(ssh2blob);
+ ptrlen algname = pubkey_blob_to_alg_name(
+ ptrlen_from_strbuf(ssh2blob));
+ alg = find_pubkey_alg_len(algname);
+ if (!alg) {
+ fprintf(stderr, "puttygen: cannot extract certificate info "
+ "from public key of unknown type '%.*s'\n",
+ PTRLEN_PRINTF(algname));
+ RETURN(1);
+ }
+ sk = ssh_key_new_pub(alg, ptrlen_from_strbuf(ssh2blob));
+ if (!sk) {
+ fprintf(stderr, "puttygen: unable to decode public key\n");
+ RETURN(1);
+ }
+ sk_allocated = true;
+ }
+
+ if (!alg->is_certificate) {
+ fprintf(stderr, "puttygen: key is not a certificate\n");
+ } else {
+ SeatDialogText *text = ssh_key_cert_info(sk);
+
+ FILE *fp;
+ if (outfile) {
+ fp = f_open(outfilename, "w", false);
+ if (!fp) {
+ fprintf(stderr, "unable to open output file\n");
+ exit(1);
+ }
+ } else {
+ fp = stdout;
+ }
+
+ for (SeatDialogTextItem *item = text->items,
+ *end = item+text->nitems; item < end; item++) {
+ switch (item->type) {
+ case SDT_MORE_INFO_KEY:
+ fprintf(fp, "%s", item->text);
+ break;
+ case SDT_MORE_INFO_VALUE_SHORT:
+ fprintf(fp, ": %s\n", item->text);
+ break;
+ case SDT_MORE_INFO_VALUE_BLOB:
+ fprintf(fp, ":\n%s\n", item->text);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (outfile)
+ fclose(fp);
+
+ seat_dialog_text_free(text);
+ }
+
+ if (sk_allocated)
+ ssh_key_free(sk);
+ break;
+ }
}
out:
diff --git a/CMDLINE.C b/CMDLINE.C
index 57b6a9f5..b5a36eb0 100644
--- a/CMDLINE.C
+++ b/CMDLINE.C
@@ -78,35 +78,56 @@ void cmdline_cleanup(void)
/*
* 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.
+ * SPR(K)_INCOMPLETE 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)
+SeatPromptResult cmdline_get_passwd_input(
+ prompts_t *p, cmdline_get_passwd_input_state *state, bool restartable)
{
- 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.
+ * that comes in a prompt-set on its own. Also, we don't use a
+ * command-line password for any kind of prompt which is destined
+ * for local use rather than to be sent to the server: the idea is
+ * to pre-fill _passwords_, not private-key passphrases (for which
+ * there are better alternatives available).
*/
- if (!cmdline_password || p->n_prompts != 1 || p->prompts[0]->echo) {
- return -1;
+ if (p->n_prompts != 1 || p->prompts[0]->echo || !p->to_server) {
+ return SPR_INCOMPLETE;
}
/*
* If we've tried once, return utter failure (no more passwords left
* to try).
*/
- if (tried_once)
- return 0;
+ if (state->tried)
+ return SPR_SW_ABORT("Configured password was not accepted");
+
+ /*
+ * If we never had a password available in the first place, we
+ * can't do anything in any case. (But we delay this test until
+ * after trying once, so that even if we free cmdline_password
+ * below, we'll still remember that we _used_ to have one.)
+ */
+ if (!cmdline_password)
+ return SPR_INCOMPLETE;
prompt_set_result(p->prompts[0], cmdline_password);
- smemclr(cmdline_password, strlen(cmdline_password));
- sfree(cmdline_password);
- cmdline_password = NULL;
- tried_once = true;
- return 1;
+ state->tried = true;
+
+ if (!restartable) {
+ /*
+ * If there's no possibility of needing to do this again after
+ * a 'Restart Session' event, then wipe our copy of the
+ * password out of memory.
+ */
+ smemclr(cmdline_password, strlen(cmdline_password));
+ sfree(cmdline_password);
+ cmdline_password = NULL;
+ }
+
+ return SPR_OK;
}
static bool cmdline_check_unavailable(int flag, const char *p)
@@ -584,6 +605,11 @@ int cmdline_process_param(const char *p, char *value,
cmdline_error("the -pw option can only be used with the "
"SSH protocol");
else {
+ if (cmdline_password) {
+ smemclr(cmdline_password, strlen(cmdline_password));
+ sfree(cmdline_password);
+ }
+
cmdline_password = dupstr(value);
/* Assuming that `value' is directly from argv, make a good faith
* attempt to trample it, to stop it showing up in `ps' output
@@ -592,6 +618,37 @@ int cmdline_process_param(const char *p, char *value,
}
}
+ if (!strcmp(p, "-pwfile")) {
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(1);
+ /* We delay evaluating this until after the protocol is decided,
+ * so that we can warn if it's of no use with the selected protocol */
+ if (conf_get_int(conf, CONF_protocol) != PROT_SSH)
+ cmdline_error("the -pwfile option can only be used with the "
+ "SSH protocol");
+ else {
+ Filename *fn = filename_from_str(value);
+ FILE *fp = f_open(fn, "r", false);
+ if (!fp) {
+ cmdline_error("unable to open password file '%s'", value);
+ } else {
+ if (cmdline_password) {
+ smemclr(cmdline_password, strlen(cmdline_password));
+ sfree(cmdline_password);
+ }
+
+ cmdline_password = chomp(fgetline(fp));
+ if (!cmdline_password) {
+ cmdline_error("unable to read a password from file '%s'",
+ value);
+ }
+ fclose(fp);
+ }
+ filename_free(fn);
+ }
+ }
+
if (!strcmp(p, "-agent") || !strcmp(p, "-pagent") ||
!strcmp(p, "-pageant")) {
RETURN(1);
@@ -702,13 +759,25 @@ int cmdline_process_param(const char *p, char *value,
filename_free(fn);
}
+ if (!strcmp(p, "-cert")) {
+ Filename *fn;
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ fn = filename_from_str(value);
+ conf_set_filename(conf, CONF_detached_cert, fn);
+ filename_free(fn);
+ }
+
if (!strcmp(p, "-4") || !strcmp(p, "-ipv4")) {
RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
SAVEABLE(1);
conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV4);
}
if (!strcmp(p, "-6") || !strcmp(p, "-ipv6")) {
RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
SAVEABLE(1);
conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV6);
}
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 00000000..b94d9b60
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,156 @@
+cmake_minimum_required(VERSION 3.7)
+project(putty LANGUAGES C)
+
+include(cmake/setup.cmake)
+
+# Scan the docs directory first, so that when we start calling
+# installed_program(), we'll know if we have man pages available
+add_subdirectory(doc)
+
+add_compile_definitions(HAVE_CMAKE_H)
+
+include_directories(terminal)
+
+add_library(utils STATIC
+ ${GENERATED_COMMIT_C})
+add_dependencies(utils cmake_commit_c)
+add_subdirectory(utils)
+add_subdirectory(stubs)
+
+add_library(logging OBJECT
+ logging.c)
+
+add_library(eventloop STATIC
+ callback.c timing.c)
+
+add_library(console STATIC
+ clicons.c console.c)
+
+add_library(settings STATIC
+ cmdline.c settings.c)
+
+add_library(crypto STATIC
+ proxy/cproxy.c proxy/sshproxy.c)
+add_subdirectory(crypto)
+
+add_library(network STATIC
+ errsock.c logging.c x11disp.c
+ proxy/proxy.c
+ proxy/http.c
+ proxy/socks4.c
+ proxy/socks5.c
+ proxy/telnet.c
+ proxy/local.c
+ proxy/interactor.c)
+
+add_library(keygen STATIC
+ import.c)
+add_subdirectory(keygen)
+
+add_library(agent STATIC
+ sshpubk.c pageant.c aqsync.c)
+
+add_library(guiterminal STATIC
+ terminal/terminal.c terminal/bidi.c
+ ldisc.c config.c dialog.c
+ $<TARGET_OBJECTS:logging>)
+
+add_library(noterminal STATIC
+ stubs/no-term.c ldisc.c)
+
+add_library(all-backends OBJECT
+ pinger.c)
+
+add_library(sftpclient STATIC
+ psftpcommon.c)
+add_subdirectory(ssh)
+
+add_library(otherbackends STATIC
+ $<TARGET_OBJECTS:all-backends>
+ $<TARGET_OBJECTS:logging>)
+add_subdirectory(otherbackends)
+
+add_executable(testcrypt
+ test/testcrypt.c sshpubk.c ssh/crc-attack-detector.c)
+target_link_libraries(testcrypt
+ keygen crypto utils ${platform_libraries})
+
+add_executable(test_host_strfoo
+ utils/host_strchr_internal.c)
+target_compile_definitions(test_host_strfoo PRIVATE TEST)
+target_link_libraries(test_host_strfoo utils ${platform_libraries})
+
+add_executable(test_decode_utf8
+ utils/decode_utf8.c)
+target_compile_definitions(test_decode_utf8 PRIVATE TEST)
+target_link_libraries(test_decode_utf8 utils ${platform_libraries})
+
+add_executable(test_tree234
+ utils/tree234.c)
+target_compile_definitions(test_tree234 PRIVATE TEST)
+target_link_libraries(test_tree234 utils ${platform_libraries})
+
+add_executable(test_wildcard
+ utils/wildcard.c)
+target_compile_definitions(test_wildcard PRIVATE TEST)
+target_link_libraries(test_wildcard utils ${platform_libraries})
+
+add_executable(test_cert_expr
+ utils/cert-expr.c)
+target_compile_definitions(test_cert_expr PRIVATE TEST)
+target_link_libraries(test_cert_expr utils ${platform_libraries})
+
+add_executable(bidi_gettype
+ terminal/bidi_gettype.c)
+target_link_libraries(bidi_gettype guiterminal utils ${platform_libraries})
+
+add_executable(bidi_test
+ terminal/bidi_test.c)
+target_link_libraries(bidi_test guiterminal utils ${platform_libraries})
+
+add_executable(plink
+ ${platform}/plink.c)
+# Note: if we ever port Plink to a platform where we can't implement a
+# serial backend, this be_list command will need to become platform-
+# dependent, so that it only sets the SERIAL option on platforms where
+# that backend exists. For the moment, though, we have serial port
+# backends for both our platforms, so we can do this unconditionally.
+be_list(plink Plink SSH SERIAL OTHERBACKENDS)
+target_link_libraries(plink
+ eventloop noterminal console sshclient otherbackends settings network crypto
+ utils
+ ${platform_libraries})
+installed_program(plink)
+
+add_executable(pscp
+ pscp.c)
+be_list(pscp PSCP SSH)
+target_link_libraries(pscp
+ sftpclient eventloop console sshclient settings network crypto utils
+ ${platform_libraries})
+installed_program(pscp)
+
+add_executable(psftp
+ psftp.c)
+be_list(psftp PSFTP SSH)
+target_link_libraries(psftp
+ sftpclient eventloop console sshclient settings network crypto utils
+ ${platform_libraries})
+installed_program(psftp)
+
+add_executable(psocks
+ ${platform}/psocks.c
+ psocks.c
+ stubs/no-rand.c
+ proxy/nocproxy.c
+ proxy/nosshproxy.c
+ ssh/portfwd.c)
+target_link_libraries(psocks
+ eventloop console network utils
+ ${platform_libraries})
+
+foreach(subdir ${platform} ${extra_dirs})
+ add_subdirectory(${subdir})
+endforeach()
+
+configure_file(cmake/cmake.h.in ${GENERATED_SOURCES_DIR}/cmake.h)
diff --git a/CONF.C b/CONF.C
deleted file mode 100644
index ecd26cd0..00000000
--- a/CONF.C
+++ /dev/null
@@ -1,593 +0,0 @@
-/*
- * conf.c: implementation of the internal storage format used for
- * the configuration of a PuTTY session.
- */
-
-#include <stdio.h>
-#include <stddef.h>
-#include <assert.h>
-
-#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;
-
- 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;
-
- 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);
-
- 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);
-
- 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/CONFIG.C b/CONFIG.C
index b8348530..8cdeee24 100644
--- a/CONFIG.C
+++ b/CONFIG.C
@@ -9,13 +9,14 @@
#include "putty.h"
#include "dialog.h"
#include "storage.h"
+#include "tree234.h"
#define PRINTER_DISABLED_STRING "None (printing disabled)"
#define HOST_BOX_TITLE "Host Name (or IP address)"
#define PORT_BOX_TITLE "Port"
-void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg,
+void conf_radiobutton_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
int button;
@@ -28,7 +29,7 @@ void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg,
* is the one selected.
*/
if (event == EVENT_REFRESH) {
- int val = conf_get_int(conf, ctrl->radio.context.i);
+ int val = conf_get_int(conf, ctrl->context.i);
for (button = 0; button < ctrl->radio.nbuttons; button++)
if (val == ctrl->radio.buttondata[button].i)
break;
@@ -38,12 +39,12 @@ void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg,
} 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,
+ conf_set_int(conf, ctrl->context.i,
ctrl->radio.buttondata[button].i);
}
}
-void conf_radiobutton_bool_handler(union control *ctrl, dlgparam *dlg,
+void conf_radiobutton_bool_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
int button;
@@ -55,7 +56,7 @@ void conf_radiobutton_bool_handler(union control *ctrl, dlgparam *dlg,
* config option.
*/
if (event == EVENT_REFRESH) {
- int val = conf_get_bool(conf, ctrl->radio.context.i);
+ int val = conf_get_bool(conf, ctrl->context.i);
for (button = 0; button < ctrl->radio.nbuttons; button++)
if (val == ctrl->radio.buttondata[button].i)
break;
@@ -65,13 +66,13 @@ void conf_radiobutton_bool_handler(union control *ctrl, dlgparam *dlg,
} 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,
+ conf_set_bool(conf, ctrl->context.i,
ctrl->radio.buttondata[button].i);
}
}
#define CHECKBOX_INVERT (1<<30)
-void conf_checkbox_handler(union control *ctrl, dlgparam *dlg,
+void conf_checkbox_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
int key;
@@ -82,7 +83,7 @@ void conf_checkbox_handler(union control *ctrl, dlgparam *dlg,
* For a standard checkbox, the context parameter gives the
* primary key (CONF_foo), optionally ORed with CHECKBOX_INVERT.
*/
- key = ctrl->checkbox.context.i;
+ key = ctrl->context.i;
if (key & CHECKBOX_INVERT) {
key &= ~CHECKBOX_INVERT;
invert = true;
@@ -103,27 +104,23 @@ void conf_checkbox_handler(union control *ctrl, dlgparam *dlg,
}
}
-void conf_editbox_handler(union control *ctrl, dlgparam *dlg,
+const struct conf_editbox_handler_type conf_editbox_str = {.type = EDIT_STR};
+const struct conf_editbox_handler_type conf_editbox_int = {.type = EDIT_INT};
+
+void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
/*
- * The standard edit-box handler expects the main `context'
- * field to contain the primary key. The secondary `context2'
- * field 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.)
+ * The standard edit-box handler expects the main `context' field
+ * to contain the primary key. The secondary `context2' field is a
+ * pointer to the struct conf_editbox_handler_type defined in
+ * putty.h.
*/
- int key = ctrl->editbox.context.i;
- int length = ctrl->editbox.context2.i;
+ int key = ctrl->context.i;
+ const struct conf_editbox_handler_type *type = ctrl->context2.cp;
Conf *conf = (Conf *)data;
- if (length > 0) {
+ if (type->type == EDIT_STR) {
if (event == EVENT_REFRESH) {
char *field = conf_get_str(conf, key);
dlg_editbox_set(ctrl, dlg, field);
@@ -132,30 +129,30 @@ void conf_editbox_handler(union control *ctrl, dlgparam *dlg,
conf_set_str(conf, key, field);
sfree(field);
}
- } else if (length < 0) {
+ } else {
if (event == EVENT_REFRESH) {
char str[80];
int value = conf_get_int(conf, key);
- if (length == -1)
+ if (type->type == EDIT_INT)
sprintf(str, "%d", value);
else
- sprintf(str, "%g", (double)value / (double)(-length));
+ sprintf(str, "%g", (double)value / type->denominator);
dlg_editbox_set(ctrl, dlg, str);
} else if (event == EVENT_VALCHANGE) {
char *str = dlg_editbox_get(ctrl, dlg);
- if (length == -1)
+ if (type->type == EDIT_INT)
conf_set_int(conf, key, atoi(str));
else
- conf_set_int(conf, key, (int)((-length) * atof(str)));
+ conf_set_int(conf, key, (int)(type->denominator * atof(str)));
sfree(str);
}
}
}
-void conf_filesel_handler(union control *ctrl, dlgparam *dlg,
+void conf_filesel_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
- int key = ctrl->fileselect.context.i;
+ int key = ctrl->context.i;
Conf *conf = (Conf *)data;
if (event == EVENT_REFRESH) {
@@ -168,10 +165,10 @@ void conf_filesel_handler(union control *ctrl, dlgparam *dlg,
}
}
-void conf_fontsel_handler(union control *ctrl, dlgparam *dlg,
+void conf_fontsel_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
- int key = ctrl->fontselect.context.i;
+ int key = ctrl->context.i;
Conf *conf = (Conf *)data;
if (event == EVENT_REFRESH) {
@@ -184,7 +181,7 @@ void conf_fontsel_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void config_host_handler(union control *ctrl, dlgparam *dlg,
+static void config_host_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
@@ -216,7 +213,7 @@ static void config_host_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void config_port_handler(union control *ctrl, dlgparam *dlg,
+static void config_port_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
@@ -257,7 +254,7 @@ static void config_port_handler(union control *ctrl, dlgparam *dlg,
}
struct hostport {
- union control *host, *port, *protradio, *protlist;
+ dlgcontrol *host, *port, *protradio, *protlist;
bool mid_refresh;
};
@@ -268,12 +265,12 @@ struct hostport {
* and refreshes both host and port boxes when switching to/from the
* serial backend.
*/
-static void config_protocols_handler(union control *ctrl, dlgparam *dlg,
+static void config_protocols_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
int curproto = conf_get_int(conf, CONF_protocol);
- struct hostport *hp = (struct hostport *)ctrl->generic.context.p;
+ struct hostport *hp = (struct hostport *)ctrl->context.p;
if (event == EVENT_REFRESH) {
/*
@@ -312,7 +309,7 @@ static void config_protocols_handler(union control *ctrl, dlgparam *dlg,
for (size_t i = n_ui_backends;
i < PROTOCOL_LIMIT && backends[i]; i++) {
dlg_listbox_addwithid(ctrl, dlg,
- backends[i]->displayname,
+ backends[i]->displayname_tc,
backends[i]->protocol);
if (backends[i]->protocol == curproto)
curentry = i - n_ui_backends;
@@ -421,7 +418,7 @@ static void config_protocols_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void loggingbuttons_handler(union control *ctrl, dlgparam *dlg,
+static void loggingbuttons_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
int button;
@@ -450,7 +447,7 @@ static void loggingbuttons_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void numeric_keypad_handler(union control *ctrl, dlgparam *dlg,
+static void numeric_keypad_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
int button;
@@ -481,7 +478,7 @@ static void numeric_keypad_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void cipherlist_handler(union control *ctrl, dlgparam *dlg,
+static void cipherlist_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
@@ -490,6 +487,7 @@ static void cipherlist_handler(union control *ctrl, dlgparam *dlg,
static const struct { const char *s; int c; } ciphers[] = {
{ "ChaCha20 (SSH-2 only)", CIPHER_CHACHA20 },
+ { "AES-GCM (SSH-2 only)", CIPHER_AESGCM },
{ "3DES", CIPHER_3DES },
{ "Blowfish", CIPHER_BLOWFISH },
{ "DES", CIPHER_DES },
@@ -527,7 +525,7 @@ static void cipherlist_handler(union control *ctrl, dlgparam *dlg,
}
#ifndef NO_GSSAPI
-static void gsslist_handler(union control *ctrl, dlgparam *dlg,
+static void gsslist_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
@@ -554,7 +552,7 @@ static void gsslist_handler(union control *ctrl, dlgparam *dlg,
}
#endif
-static void kexlist_handler(union control *ctrl, dlgparam *dlg,
+static void kexlist_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
@@ -562,12 +560,17 @@ static void kexlist_handler(union control *ctrl, dlgparam *dlg,
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 }
+ { "Diffie-Hellman group 1 (1024-bit)", KEX_DHGROUP1 },
+ { "Diffie-Hellman group 14 (2048-bit)", KEX_DHGROUP14 },
+ { "Diffie-Hellman group 15 (3072-bit)", KEX_DHGROUP15 },
+ { "Diffie-Hellman group 16 (4096-bit)", KEX_DHGROUP16 },
+ { "Diffie-Hellman group 17 (6144-bit)", KEX_DHGROUP17 },
+ { "Diffie-Hellman group 18 (8192-bit)", KEX_DHGROUP18 },
+ { "Diffie-Hellman group exchange", KEX_DHGEX },
+ { "RSA-based key exchange", KEX_RSA },
+ { "ECDH key exchange", KEX_ECDH },
+ { "NTRU Prime / Curve25519 hybrid kex", KEX_NTRU_HYBRID },
+ { "-- warn below here --", KEX_WARN }
};
/* Set up the "kex preference" box. */
@@ -598,8 +601,8 @@ static void kexlist_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void hklist_handler(union control *ctrl, dlgparam *dlg,
- void *data, int event)
+static void hklist_handler(dlgcontrol *ctrl, dlgparam *dlg,
+ void *data, int event)
{
Conf *conf = (Conf *)data;
if (event == EVENT_REFRESH) {
@@ -642,7 +645,7 @@ static void hklist_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void printerbox_handler(union control *ctrl, dlgparam *dlg,
+static void printerbox_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
@@ -678,7 +681,7 @@ static void printerbox_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void codepage_handler(union control *ctrl, dlgparam *dlg,
+static void codepage_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
@@ -702,7 +705,7 @@ static void codepage_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void sshbug_handler(union control *ctrl, dlgparam *dlg,
+static void sshbug_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
@@ -713,7 +716,7 @@ static void sshbug_handler(union control *ctrl, dlgparam *dlg,
* 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);
+ int oldconf = conf_get_int(conf, ctrl->context.i);
dlg_update_start(ctrl, dlg);
dlg_listbox_clear(ctrl, dlg);
dlg_listbox_addwithid(ctrl, dlg, "Auto", AUTO);
@@ -731,13 +734,44 @@ static void sshbug_handler(union control *ctrl, dlgparam *dlg,
i = AUTO;
else
i = dlg_listbox_getid(ctrl, dlg, i);
- conf_set_int(conf, ctrl->listbox.context.i, i);
+ conf_set_int(conf, ctrl->context.i, i);
+ }
+}
+
+static void sshbug_handler_manual_only(dlgcontrol *ctrl, dlgparam *dlg,
+ void *data, int event)
+{
+ /*
+ * This is just like sshbug_handler, except that there's no 'Auto'
+ * option. Used for bug workaround flags that can't be
+ * autodetected, and have to be manually enabled if they're to be
+ * used at all.
+ */
+ Conf *conf = (Conf *)data;
+ if (event == EVENT_REFRESH) {
+ int oldconf = conf_get_int(conf, ctrl->context.i);
+ dlg_update_start(ctrl, dlg);
+ dlg_listbox_clear(ctrl, dlg);
+ dlg_listbox_addwithid(ctrl, dlg, "Off", FORCE_OFF);
+ dlg_listbox_addwithid(ctrl, dlg, "On", FORCE_ON);
+ switch (oldconf) {
+ case FORCE_OFF: dlg_listbox_select(ctrl, dlg, 0); break;
+ case FORCE_ON: dlg_listbox_select(ctrl, dlg, 1); break;
+ }
+ dlg_update_done(ctrl, dlg);
+ } else if (event == EVENT_SELCHANGE) {
+ int i = dlg_listbox_index(ctrl, dlg);
+ if (i < 0)
+ i = FORCE_OFF;
+ else
+ i = dlg_listbox_getid(ctrl, dlg, i);
+ conf_set_int(conf, ctrl->context.i, i);
}
}
struct sessionsaver_data {
- union control *editbox, *listbox, *loadbutton, *savebutton, *delbutton;
- union control *okbutton, *cancelbutton;
+ dlgcontrol *editbox, *listbox, *loadbutton, *savebutton, *delbutton;
+ dlgcontrol *okbutton, *cancelbutton;
struct sesslist sesslist;
bool midsession;
char *savedsession; /* the current contents of ssd->editbox */
@@ -779,12 +813,12 @@ static bool load_selected_session(
return true;
}
-static void sessionsaver_handler(union control *ctrl, dlgparam *dlg,
+static void sessionsaver_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
struct sessionsaver_data *ssd =
- (struct sessionsaver_data *)ctrl->generic.context.p;
+ (struct sessionsaver_data *)ctrl->context.p;
if (event == EVENT_REFRESH) {
if (ctrl == ssd->editbox) {
@@ -917,15 +951,15 @@ static void sessionsaver_handler(union control *ctrl, dlgparam *dlg,
}
struct charclass_data {
- union control *listbox, *editbox, *button;
+ dlgcontrol *listbox, *editbox, *button;
};
-static void charclass_handler(union control *ctrl, dlgparam *dlg,
+static void charclass_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
struct charclass_data *ccd =
- (struct charclass_data *)ctrl->generic.context.p;
+ (struct charclass_data *)ctrl->context.p;
if (event == EVENT_REFRESH) {
if (ctrl == ccd->listbox) {
@@ -958,7 +992,7 @@ static void charclass_handler(union control *ctrl, dlgparam *dlg,
}
struct colour_data {
- union control *listbox, *redit, *gedit, *bedit, *button;
+ dlgcontrol *listbox, *redit, *gedit, *bedit, *button;
};
/* Array of the user-visible colour names defined in the list macro in
@@ -969,12 +1003,12 @@ static const char *const colours[] = {
#undef CONF_COLOUR_NAME_DECL
};
-static void colour_handler(union control *ctrl, dlgparam *dlg,
+static void colour_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
struct colour_data *cd =
- (struct colour_data *)ctrl->generic.context.p;
+ (struct colour_data *)ctrl->context.p;
bool update = false, clear = false;
int r, g, b;
@@ -1075,15 +1109,15 @@ static void colour_handler(union control *ctrl, dlgparam *dlg,
}
struct ttymodes_data {
- union control *valradio, *valbox, *setbutton, *listbox;
+ dlgcontrol *valradio, *valbox, *setbutton, *listbox;
};
-static void ttymodes_handler(union control *ctrl, dlgparam *dlg,
+static void ttymodes_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
struct ttymodes_data *td =
- (struct ttymodes_data *)ctrl->generic.context.p;
+ (struct ttymodes_data *)ctrl->context.p;
if (event == EVENT_REFRESH) {
if (ctrl == td->listbox) {
@@ -1160,15 +1194,15 @@ static void ttymodes_handler(union control *ctrl, dlgparam *dlg,
}
struct environ_data {
- union control *varbox, *valbox, *addbutton, *rembutton, *listbox;
+ dlgcontrol *varbox, *valbox, *addbutton, *rembutton, *listbox;
};
-static void environ_handler(union control *ctrl, dlgparam *dlg,
+static void environ_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
struct environ_data *ed =
- (struct environ_data *)ctrl->generic.context.p;
+ (struct environ_data *)ctrl->context.p;
if (event == EVENT_REFRESH) {
if (ctrl == ed->listbox) {
@@ -1232,19 +1266,19 @@ static void environ_handler(union control *ctrl, dlgparam *dlg,
}
struct portfwd_data {
- union control *addbutton, *rembutton, *listbox;
- union control *sourcebox, *destbox, *direction;
+ dlgcontrol *addbutton, *rembutton, *listbox;
+ dlgcontrol *sourcebox, *destbox, *direction;
#ifndef NO_IPV6
- union control *addressfamily;
+ dlgcontrol *addressfamily;
#endif
};
-static void portfwd_handler(union control *ctrl, dlgparam *dlg,
+static void portfwd_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
struct portfwd_data *pfd =
- (struct portfwd_data *)ctrl->generic.context.p;
+ (struct portfwd_data *)ctrl->context.p;
if (event == EVENT_REFRESH) {
if (ctrl == pfd->listbox) {
@@ -1400,15 +1434,15 @@ static void portfwd_handler(union control *ctrl, dlgparam *dlg,
}
struct manual_hostkey_data {
- union control *addbutton, *rembutton, *listbox, *keybox;
+ dlgcontrol *addbutton, *rembutton, *listbox, *keybox;
};
-static void manual_hostkey_handler(union control *ctrl, dlgparam *dlg,
+static void manual_hostkey_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
struct manual_hostkey_data *mh =
- (struct manual_hostkey_data *)ctrl->generic.context.p;
+ (struct manual_hostkey_data *)ctrl->context.p;
if (event == EVENT_REFRESH) {
if (ctrl == mh->listbox) {
@@ -1466,13 +1500,13 @@ static void manual_hostkey_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void clipboard_selector_handler(union control *ctrl, dlgparam *dlg,
+static void clipboard_selector_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
- int setting = ctrl->generic.context.i;
+ int setting = ctrl->context.i;
#ifdef NAMED_CLIPBOARDS
- int strsetting = ctrl->editbox.context2.i;
+ int strsetting = ctrl->context2.i;
#endif
static const struct {
@@ -1554,7 +1588,7 @@ static void clipboard_selector_handler(union control *ctrl, dlgparam *dlg,
}
static void clipboard_control(struct controlset *s, const char *label,
- char shortcut, int percentage, intorptr helpctx,
+ char shortcut, int percentage, HelpCtx helpctx,
int setting, int strsetting)
{
#ifdef NAMED_CLIPBOARDS
@@ -1567,7 +1601,7 @@ static void clipboard_control(struct controlset *s, const char *label,
#endif
}
-static void serial_parity_handler(union control *ctrl, dlgparam *dlg,
+static void serial_parity_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
static const struct {
@@ -1580,7 +1614,7 @@ static void serial_parity_handler(union control *ctrl, dlgparam *dlg,
{"Mark", SER_PAR_MARK},
{"Space", SER_PAR_SPACE},
};
- int mask = ctrl->listbox.context.i;
+ int mask = ctrl->context.i;
int i, j;
Conf *conf = (Conf *)data;
@@ -1622,7 +1656,7 @@ static void serial_parity_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void serial_flow_handler(union control *ctrl, dlgparam *dlg,
+static void serial_flow_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
static const struct {
@@ -1634,7 +1668,7 @@ static void serial_flow_handler(union control *ctrl, dlgparam *dlg,
{"RTS/CTS", SER_FLOW_RTSCTS},
{"DSR/DTR", SER_FLOW_DSRDTR},
};
- int mask = ctrl->listbox.context.i;
+ int mask = ctrl->context.i;
int i, j;
Conf *conf = (Conf *)data;
@@ -1675,6 +1709,67 @@ static void serial_flow_handler(union control *ctrl, dlgparam *dlg,
}
}
+void proxy_type_handler(dlgcontrol *ctrl, dlgparam *dlg,
+ void *data, int event)
+{
+ Conf *conf = (Conf *)data;
+ if (event == EVENT_REFRESH) {
+ /*
+ * We must fetch the previously configured value from the Conf
+ * before we start modifying the drop-down list, otherwise the
+ * spurious SELCHANGE we trigger in the process will overwrite
+ * the value we wanted to keep.
+ */
+ int proxy_type = conf_get_int(conf, CONF_proxy_type);
+
+ dlg_update_start(ctrl, dlg);
+ dlg_listbox_clear(ctrl, dlg);
+
+ int index_to_select = 0, current_index = 0;
+
+#define ADD(id, title) do { \
+ dlg_listbox_addwithid(ctrl, dlg, title, id); \
+ if (id == proxy_type) \
+ index_to_select = current_index; \
+ current_index++; \
+ } while (0)
+
+ ADD(PROXY_NONE, "None");
+ ADD(PROXY_SOCKS5, "SOCKS 5");
+ ADD(PROXY_SOCKS4, "SOCKS 4");
+ ADD(PROXY_HTTP, "HTTP CONNECT");
+ if (ssh_proxy_supported) {
+ ADD(PROXY_SSH_TCPIP, "SSH to proxy and use port forwarding");
+ ADD(PROXY_SSH_EXEC, "SSH to proxy and execute a command");
+ ADD(PROXY_SSH_SUBSYSTEM, "SSH to proxy and invoke a subsystem");
+ }
+ if (ctrl->context.i & PROXY_UI_FLAG_LOCAL) {
+ ADD(PROXY_CMD, "Local (run a subprogram to connect)");
+ }
+ ADD(PROXY_TELNET, "'Telnet' (send an ad-hoc command)");
+
+#undef ADD
+
+ dlg_listbox_select(ctrl, dlg, index_to_select);
+
+ dlg_update_done(ctrl, dlg);
+ } else if (event == EVENT_SELCHANGE) {
+ int i = dlg_listbox_index(ctrl, dlg);
+ if (i < 0)
+ i = AUTO;
+ else
+ i = dlg_listbox_getid(ctrl, dlg, i);
+ conf_set_int(conf, CONF_proxy_type, i);
+ }
+}
+
+static void host_ca_button_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ if (event == EVENT_ACTION)
+ show_ca_config_box(dp);
+}
+
void setup_config_box(struct controlbox *b, bool midsession,
int protocol, int protcfginfo)
{
@@ -1687,7 +1782,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
struct environ_data *ed;
struct portfwd_data *pfd;
struct manual_hostkey_data *mh;
- union control *c;
+ dlgcontrol *c;
bool resize_forbidden = false;
char *str;
@@ -1710,11 +1805,11 @@ void setup_config_box(struct controlbox *b, bool midsession,
HELPCTX(no_help),
sessionsaver_handler, P(ssd));
ssd->okbutton->button.isdefault = true;
- ssd->okbutton->generic.column = 3;
+ ssd->okbutton->column = 3;
ssd->cancelbutton = ctrl_pushbutton(s, "Cancel", 'c', HELPCTX(no_help),
sessionsaver_handler, P(ssd));
ssd->cancelbutton->button.iscancel = true;
- ssd->cancelbutton->generic.column = 4;
+ ssd->cancelbutton->column = 4;
/* We carefully don't close the 5-column part, so that platform-
* specific add-ons can put extra buttons alongside Open and Cancel. */
@@ -1736,12 +1831,12 @@ void setup_config_box(struct controlbox *b, bool midsession,
c = ctrl_editbox(s, HOST_BOX_TITLE, 'n', 100,
HELPCTX(session_hostname),
config_host_handler, I(0), I(0));
- c->generic.column = 0;
+ c->column = 0;
hp->host = c;
c = ctrl_editbox(s, PORT_BOX_TITLE, 'p', 100,
HELPCTX(session_hostname),
config_port_handler, I(0), I(0));
- c->generic.column = 1;
+ c->column = 1;
hp->port = c;
ctrl_columns(s, 1, 100);
@@ -1749,8 +1844,8 @@ void setup_config_box(struct controlbox *b, bool midsession,
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;
+ config_protocols_handler, P(hp));
+ c->column = 0;
hp->protradio = c;
c->radio.buttons = sresize(c->radio.buttons, PROTOCOL_LIMIT, char *);
c->radio.shortcuts = sresize(c->radio.shortcuts, PROTOCOL_LIMIT, char);
@@ -1762,7 +1857,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
for (size_t i = 0; i < n_ui_backends; i++) {
assert(backends[i]);
c->radio.buttons[c->radio.nbuttons] =
- dupstr(backends[i]->displayname);
+ dupstr(backends[i]->displayname_tc);
c->radio.shortcuts[c->radio.nbuttons] =
(backends[i]->protocol == PROT_SSH ? 's' :
backends[i]->protocol == PROT_SERIAL ? 'r' :
@@ -1785,10 +1880,10 @@ void setup_config_box(struct controlbox *b, bool midsession,
config_protocols_handler, P(hp));
hp->protlist = c;
/* droplist is populated in config_protocols_handler */
- c->generic.column = 1;
+ c->column = 1;
/* Vertically centre the two protocol controls w.r.t. each other */
- hp->protlist->generic.align_next_to = hp->protradio;
+ hp->protlist->align_next_to = hp->protradio;
ctrl_columns(s, 1, 100);
}
@@ -1804,7 +1899,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
ssd->editbox = ctrl_editbox(s, "Saved Sessions", 'e', 100,
HELPCTX(session_saved),
sessionsaver_handler, P(ssd), P(NULL));
- ssd->editbox->generic.column = 0;
+ ssd->editbox->column = 0;
/* Reset columns so that the buttons are alongside the list, rather
* than alongside that edit box. */
ctrl_columns(s, 1, 100);
@@ -1812,13 +1907,13 @@ void setup_config_box(struct controlbox *b, bool midsession,
ssd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
HELPCTX(session_saved),
sessionsaver_handler, P(ssd));
- ssd->listbox->generic.column = 0;
+ ssd->listbox->column = 0;
ssd->listbox->listbox.height = 7;
if (!midsession) {
ssd->loadbutton = ctrl_pushbutton(s, "Load", 'l',
HELPCTX(session_saved),
sessionsaver_handler, P(ssd));
- ssd->loadbutton->generic.column = 1;
+ ssd->loadbutton->column = 1;
} else {
/* We can't offer the Load button mid-session, as it would allow the
* user to load and subsequently save settings they can't see. (And
@@ -1830,12 +1925,12 @@ void setup_config_box(struct controlbox *b, bool midsession,
ssd->savebutton = ctrl_pushbutton(s, "Save", 'v',
HELPCTX(session_saved),
sessionsaver_handler, P(ssd));
- ssd->savebutton->generic.column = 1;
+ ssd->savebutton->column = 1;
if (!midsession) {
ssd->delbutton = ctrl_pushbutton(s, "Delete", 'd',
HELPCTX(session_saved),
sessionsaver_handler, P(ssd));
- ssd->delbutton->generic.column = 1;
+ ssd->delbutton->column = 1;
} else {
/* Disable the Delete button mid-session too, for UI consistency. */
ssd->delbutton = NULL;
@@ -1849,7 +1944,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
I(CONF_close_on_exit),
"Always", I(FORCE_ON),
"Never", I(FORCE_OFF),
- "Only on clean exit", I(AUTO), NULL);
+ "Only on clean exit", I(AUTO));
/*
* The Session/Logging panel.
@@ -1879,8 +1974,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
"Printable output", 'p', I(LGTYP_ASCII),
"All session output", 'l', I(LGTYP_DEBUG),
sshlogname, 's', I(LGTYP_PACKETS),
- sshrawlogname, 'r', I(LGTYP_SSHRAW),
- NULL);
+ sshrawlogname, 'r', I(LGTYP_SSHRAW));
}
ctrl_filesel(s, "Log file name:", 'f',
NULL, true, "Select session log file name",
@@ -1894,13 +1988,13 @@ void setup_config_box(struct controlbox *b, bool midsession,
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);
+ "Ask the user every time", I(LGXF_ASK));
ctrl_checkbox(s, "Flush log file frequently", 'u',
- HELPCTX(logging_flush),
- conf_checkbox_handler, I(CONF_logflush));
+ HELPCTX(logging_flush),
+ conf_checkbox_handler, I(CONF_logflush));
ctrl_checkbox(s, "Include header", 'i',
- HELPCTX(logging_header),
- conf_checkbox_handler, I(CONF_logheader));
+ HELPCTX(logging_header),
+ conf_checkbox_handler, I(CONF_logheader));
if ((midsession && protocol == PROT_SSH) ||
(!midsession && backend_vt_from_proto(PROT_SSH))) {
@@ -1940,7 +2034,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
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));
+ conf_editbox_handler, I(CONF_answerback), ED_STR);
s = ctrl_getset(b, "Terminal", "ldisc", "Line discipline options");
ctrl_radiobuttons(s, "Local echo:", 'l', 3,
@@ -1948,13 +2042,13 @@ void setup_config_box(struct controlbox *b, bool midsession,
conf_radiobutton_handler,I(CONF_localecho),
"Auto", I(AUTO),
"Force on", I(FORCE_ON),
- "Force off", I(FORCE_OFF), NULL);
+ "Force off", I(FORCE_OFF));
ctrl_radiobuttons(s, "Local line editing:", 't', 3,
HELPCTX(terminal_localedit),
conf_radiobutton_handler,I(CONF_localedit),
"Auto", I(AUTO),
"Force on", I(FORCE_ON),
- "Force off", I(FORCE_OFF), NULL);
+ "Force off", I(FORCE_OFF));
s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing");
ctrl_combobox(s, "Printer to send ANSI printer output to:", 'p', 100,
@@ -1973,18 +2067,29 @@ void setup_config_box(struct controlbox *b, bool midsession,
HELPCTX(keyboard_backspace),
conf_radiobutton_bool_handler,
I(CONF_bksp_is_delete),
- "Control-H", I(0), "Control-? (127)", I(1), NULL);
+ "Control-H", I(0), "Control-? (127)", I(1));
ctrl_radiobuttons(s, "The Home and End keys", 'e', 2,
HELPCTX(keyboard_homeend),
conf_radiobutton_bool_handler,
I(CONF_rxvt_homeend),
- "Standard", I(false), "rxvt", I(true), NULL);
- ctrl_radiobuttons(s, "The Function keys and keypad", 'f', 3,
+ "Standard", I(false), "rxvt", I(true));
+ ctrl_radiobuttons(s, "The Function keys and keypad", 'f', 4,
HELPCTX(keyboard_funkeys),
conf_radiobutton_handler,
I(CONF_funky_type),
- "ESC[n~", I(0), "Linux", I(1), "Xterm R6", I(2),
- "VT400", I(3), "VT100+", I(4), "SCO", I(5), NULL);
+ "ESC[n~", I(FUNKY_TILDE),
+ "Linux", I(FUNKY_LINUX),
+ "Xterm R6", I(FUNKY_XTERM),
+ "VT400", I(FUNKY_VT400),
+ "VT100+", I(FUNKY_VT100P),
+ "SCO", I(FUNKY_SCO),
+ "Xterm 216+", I(FUNKY_XTERM_216));
+ ctrl_radiobuttons(s, "Shift/Ctrl/Alt with the arrow keys", 'w', 2,
+ HELPCTX(keyboard_sharrow),
+ conf_radiobutton_handler,
+ I(CONF_sharrow_type),
+ "Ctrl toggles app mode", I(SHARROW_APPLICATION),
+ "xterm-style bitmap", I(SHARROW_BITMAP));
s = ctrl_getset(b, "Terminal/Keyboard", "appkeypad",
"Application keypad settings:");
@@ -1992,12 +2097,11 @@ void setup_config_box(struct controlbox *b, bool midsession,
HELPCTX(keyboard_appcursor),
conf_radiobutton_bool_handler,
I(CONF_app_cursor),
- "Normal", I(0), "Application", I(1), NULL);
+ "Normal", I(0), "Application", I(1));
ctrl_radiobuttons(s, "Initial state of numeric keypad:", 'n', 3,
HELPCTX(keyboard_appkeypad),
numeric_keypad_handler, P(NULL),
- "Normal", I(0), "Application", I(1), "NetHack", I(2),
- NULL);
+ "Normal", I(0), "Application", I(1), "NetHack", I(2));
/*
* The Terminal/Bell panel.
@@ -2011,7 +2115,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
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);
+ "Visual bell (flash window)", I(BELL_VISUAL));
s = ctrl_getset(b, "Terminal/Bell", "overload",
"Control the bell overload behaviour");
@@ -2020,17 +2124,21 @@ void setup_config_box(struct controlbox *b, bool midsession,
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));
+ conf_editbox_handler, I(CONF_bellovl_n), ED_INT);
+
+ static const struct conf_editbox_handler_type conf_editbox_tickspersec = {
+ .type = EDIT_FIXEDPOINT, .denominator = TICKSPERSEC};
+
ctrl_editbox(s, "... in this many seconds", 't', 20,
HELPCTX(bell_overload),
conf_editbox_handler, I(CONF_bellovl_t),
- I(-TICKSPERSEC));
+ CP(&conf_editbox_tickspersec));
ctrl_text(s, "The bell is re-enabled after a few seconds of silence.",
HELPCTX(bell_overload));
ctrl_editbox(s, "Seconds of silence required", 's', 20,
HELPCTX(bell_overload),
conf_editbox_handler, I(CONF_bellovl_s),
- I(-TICKSPERSEC));
+ CP(&conf_editbox_tickspersec));
/*
* The Terminal/Features panel.
@@ -2065,7 +2173,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
I(CONF_remote_qtitle_action),
"None", I(TITLE_NONE),
"Empty string", I(TITLE_EMPTY),
- "Window title", I(TITLE_REAL), NULL);
+ "Window title", I(TITLE_REAL));
ctrl_checkbox(s, "Disable remote-controlled clearing of scrollback", 'e',
HELPCTX(features_clearscroll),
conf_checkbox_handler,
@@ -2099,12 +2207,12 @@ void setup_config_box(struct controlbox *b, bool midsession,
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;
+ conf_editbox_handler, I(CONF_width), ED_INT);
+ c->column = 0;
c = ctrl_editbox(s, "Rows", 'r', 100,
HELPCTX(window_size),
- conf_editbox_handler, I(CONF_height),I(-1));
- c->generic.column = 1;
+ conf_editbox_handler, I(CONF_height),ED_INT);
+ c->column = 1;
ctrl_columns(s, 1, 100);
}
@@ -2112,7 +2220,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
"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));
+ conf_editbox_handler, I(CONF_savelines), ED_INT);
ctrl_checkbox(s, "Display scrollbar", 'd',
HELPCTX(window_scrollback),
conf_checkbox_handler, I(CONF_scrollbar));
@@ -2142,7 +2250,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
I(CONF_cursor_type),
"Block", 'l', I(0),
"Underline", 'u', I(1),
- "Vertical line", 'v', I(2), NULL);
+ "Vertical line", 'v', I(2));
ctrl_checkbox(s, "Cursor blinks", 'b',
HELPCTX(appearance_cursor),
conf_checkbox_handler, I(CONF_blink_cur));
@@ -2164,7 +2272,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
ctrl_editbox(s, "Gap between text and window edge:", 'e', 20,
HELPCTX(appearance_border),
conf_editbox_handler,
- I(CONF_window_border), I(-1));
+ I(CONF_window_border), ED_INT);
/*
* The Window/Behaviour panel.
@@ -2177,7 +2285,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
"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));
+ conf_editbox_handler, I(CONF_wintitle), ED_STR);
ctrl_checkbox(s, "Separate window and icon titles", 'i',
HELPCTX(appearance_title),
conf_checkbox_handler,
@@ -2208,13 +2316,12 @@ void setup_config_box(struct controlbox *b, bool midsession,
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_radiobuttons(
+ s, "Handling of line drawing characters:", NO_SHORTCUT,1,
+ HELPCTX(translation_linedraw),
+ conf_radiobutton_handler, I(CONF_vtmode),
+ "Use Unicode line drawing code points",'u',I(VT_UNICODE),
+ "Poor man's line drawing (+, - and |)",'p',I(VT_POORMAN));
ctrl_checkbox(s, "Copy and paste line drawing characters as lqqqk",'d',
HELPCTX(selection_linedraw),
conf_checkbox_handler, I(CONF_rawcnp));
@@ -2239,7 +2346,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
conf_radiobutton_bool_handler,
I(CONF_rect_select),
"Normal", 'n', I(false),
- "Rectangular block", 'r', I(true), NULL);
+ "Rectangular block", 'r', I(true));
s = ctrl_getset(b, "Window/Selection", "clipboards",
"Assign copy/paste actions to clipboards");
@@ -2287,11 +2394,11 @@ void setup_config_box(struct controlbox *b, bool midsession,
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->editbox->column = 0;
ccd->button = ctrl_pushbutton(s, "Set", 's',
HELPCTX(copy_charclasses),
charclass_handler, P(ccd));
- ccd->button->generic.column = 1;
+ ccd->button->column = 1;
ctrl_columns(s, 1, 100);
/*
@@ -2315,8 +2422,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
conf_radiobutton_handler, I(CONF_bold_style),
"The font", I(1),
"The colour", I(2),
- "Both", I(3),
- NULL);
+ "Both", I(3));
str = dupprintf("Adjust the precise colours %s displays", appname);
s = ctrl_getset(b, "Window/Colours", "adjust", str);
@@ -2328,22 +2434,22 @@ void setup_config_box(struct controlbox *b, bool midsession,
cd = (struct colour_data *)ctrl_alloc(b, sizeof(struct colour_data));
cd->listbox = ctrl_listbox(s, "Select a colour to adjust:", 'u',
HELPCTX(colours_config), colour_handler, P(cd));
- cd->listbox->generic.column = 0;
+ cd->listbox->column = 0;
cd->listbox->listbox.height = 7;
c = ctrl_text(s, "RGB value:", HELPCTX(colours_config));
- c->generic.column = 1;
+ c->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->redit->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->gedit->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->bedit->column = 1;
cd->button = ctrl_pushbutton(s, "Modify", 'm', HELPCTX(colours_config),
colour_handler, P(cd));
- cd->button->generic.column = 1;
+ cd->button->column = 1;
ctrl_columns(s, 1, 100);
/*
@@ -2358,8 +2464,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
"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));
+ conf_editbox_handler, I(CONF_ping_interval), ED_INT);
if (!midsession) {
s = ctrl_getset(b, "Connection", "tcp",
@@ -2374,15 +2479,14 @@ void setup_config_box(struct controlbox *b, bool midsession,
I(CONF_tcp_keepalives));
#ifndef NO_IPV6
s = ctrl_getset(b, "Connection", "ipversion",
- "Internet protocol version");
+ "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);
+ HELPCTX(connection_ipversion),
+ conf_radiobutton_handler,
+ I(CONF_addressfamily),
+ "Auto", 'u', I(ADDRTYPE_UNSPEC),
+ "IPv4", '4', I(ADDRTYPE_IPV4),
+ "IPv6", '6', I(ADDRTYPE_IPV6));
#endif
{
@@ -2393,7 +2497,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
"Logical name of remote host");
ctrl_editbox(s, label, 'm', 100,
HELPCTX(connection_loghost),
- conf_editbox_handler, I(CONF_loghost), I(1));
+ conf_editbox_handler, I(CONF_loghost), ED_STR);
}
}
@@ -2408,7 +2512,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
"Login details");
ctrl_editbox(s, "Auto-login username", 'u', 50,
HELPCTX(connection_username),
- conf_editbox_handler, I(CONF_username), I(1));
+ conf_editbox_handler, I(CONF_username), ED_STR);
{
/* We assume the local username is sufficiently stable
* to include on the dialog box. */
@@ -2421,8 +2525,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
conf_radiobutton_bool_handler,
I(CONF_username_from_env),
"Prompt", I(false),
- userlabel, I(true),
- NULL);
+ userlabel, I(true));
sfree(userlabel);
}
@@ -2430,10 +2533,10 @@ void setup_config_box(struct controlbox *b, bool midsession,
"Terminal details");
ctrl_editbox(s, "Terminal-type string", 't', 50,
HELPCTX(connection_termtype),
- conf_editbox_handler, I(CONF_termtype), I(1));
+ conf_editbox_handler, I(CONF_termtype), ED_STR);
ctrl_editbox(s, "Terminal speeds", 's', 50,
HELPCTX(connection_termspeed),
- conf_editbox_handler, I(CONF_termspeed), I(1));
+ conf_editbox_handler, I(CONF_termspeed), ED_STR);
s = ctrl_getset(b, "Connection/Data", "env",
"Environment variables");
@@ -2443,19 +2546,19 @@ void setup_config_box(struct controlbox *b, bool midsession,
ed->varbox = ctrl_editbox(s, "Variable", 'v', 60,
HELPCTX(telnet_environ),
environ_handler, P(ed), P(NULL));
- ed->varbox->generic.column = 0;
+ ed->varbox->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->valbox->column = 0;
ed->addbutton = ctrl_pushbutton(s, "Add", 'd',
HELPCTX(telnet_environ),
environ_handler, P(ed));
- ed->addbutton->generic.column = 1;
+ ed->addbutton->column = 1;
ed->rembutton = ctrl_pushbutton(s, "Remove", 'r',
HELPCTX(telnet_environ),
environ_handler, P(ed));
- ed->rembutton->generic.column = 1;
+ ed->rembutton->column = 1;
ctrl_columns(s, 1, 100);
ed->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
HELPCTX(telnet_environ),
@@ -2477,33 +2580,25 @@ void setup_config_box(struct controlbox *b, bool midsession,
"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);
+ c = ctrl_droplist(s, "Proxy type:", 't', 70,
+ HELPCTX(proxy_type), proxy_type_handler, I(0));
ctrl_columns(s, 2, 80, 20);
c = ctrl_editbox(s, "Proxy hostname", 'y', 100,
HELPCTX(proxy_main),
conf_editbox_handler,
- I(CONF_proxy_host), I(1));
- c->generic.column = 0;
+ I(CONF_proxy_host), ED_STR);
+ c->column = 0;
c = ctrl_editbox(s, "Port", 'p', 100,
HELPCTX(proxy_main),
conf_editbox_handler,
I(CONF_proxy_port),
- I(-1));
- c->generic.column = 1;
+ ED_INT);
+ c->column = 1;
ctrl_columns(s, 1, 100);
ctrl_editbox(s, "Exclude Hosts/IPs", 'e', 100,
HELPCTX(proxy_exclude),
conf_editbox_handler,
- I(CONF_proxy_exclude_list), I(1));
+ I(CONF_proxy_exclude_list), ED_STR);
ctrl_checkbox(s, "Consider proxying local host connections", 'x',
HELPCTX(proxy_exclude),
conf_checkbox_handler,
@@ -2514,20 +2609,20 @@ void setup_config_box(struct controlbox *b, bool midsession,
I(CONF_proxy_dns),
"No", I(FORCE_OFF),
"Auto", I(AUTO),
- "Yes", I(FORCE_ON), NULL);
+ "Yes", I(FORCE_ON));
ctrl_editbox(s, "Username", 'u', 60,
HELPCTX(proxy_auth),
conf_editbox_handler,
- I(CONF_proxy_username), I(1));
+ I(CONF_proxy_username), ED_STR);
c = ctrl_editbox(s, "Password", 'w', 60,
HELPCTX(proxy_auth),
conf_editbox_handler,
- I(CONF_proxy_password), I(1));
+ I(CONF_proxy_password), ED_STR);
c->editbox.password = true;
- ctrl_editbox(s, "Telnet command", 'm', 100,
+ ctrl_editbox(s, "Command to send to proxy (for some types)", 'm', 100,
HELPCTX(proxy_command),
conf_editbox_handler,
- I(CONF_proxy_telnet_command), I(1));
+ I(CONF_proxy_telnet_command), ED_STR);
ctrl_radiobuttons(s, "Print proxy diagnostics "
"in the terminal window", 'r', 5,
@@ -2536,7 +2631,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
I(CONF_proxy_log_to_term),
"No", I(FORCE_OFF),
"Yes", I(FORCE_ON),
- "Only until session starts", I(AUTO), NULL);
+ "Only until session starts", I(AUTO));
}
/*
@@ -2577,7 +2672,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
"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));
+ conf_editbox_handler, I(CONF_remote_cmd), ED_STR);
s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options");
ctrl_checkbox(s, "Don't start a shell or command at all", 'n',
@@ -2623,7 +2718,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
conf_radiobutton_handler,
I(CONF_sshprot),
"2", '2', I(3),
- "1 (INSECURE)", '1', I(0), NULL);
+ "1 (INSECURE)", '1', I(0));
}
/*
@@ -2656,19 +2751,19 @@ void setup_config_box(struct controlbox *b, bool midsession,
HELPCTX(ssh_kex_repeat),
conf_editbox_handler,
I(CONF_ssh_rekey_time),
- I(-1));
+ ED_INT);
#ifndef NO_GSSAPI
ctrl_editbox(s, "Minutes between GSS checks (0 for never)", NO_SHORTCUT, 20,
HELPCTX(ssh_kex_repeat),
conf_editbox_handler,
I(CONF_gssapirekey),
- I(-1));
+ ED_INT);
#endif
ctrl_editbox(s, "Max data before rekey (0 for no limit)", 'x', 20,
HELPCTX(ssh_kex_repeat),
conf_editbox_handler,
I(CONF_ssh_rekey_data),
- I(16));
+ ED_STR);
ctrl_text(s, "(Use 1M for 1 megabyte, 1G for 1 gigabyte etc)",
HELPCTX(ssh_kex_repeat));
}
@@ -2704,7 +2799,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
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;
+ c->column = 0;
/* You want to select from the list, _then_ hit Remove. So
* tab order should be that way round. */
mh = (struct manual_hostkey_data *)
@@ -2712,8 +2807,8 @@ void setup_config_box(struct controlbox *b, bool midsession,
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->rembutton->column = 1;
+ mh->rembutton->delay_taborder = true;
mh->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
HELPCTX(ssh_kex_manual_hostkeys),
manual_hostkey_handler, P(mh));
@@ -2727,14 +2822,25 @@ void setup_config_box(struct controlbox *b, bool midsession,
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->keybox->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;
+ mh->addbutton->column = 1;
ctrl_columns(s, 1, 100);
}
+ /*
+ * But there's no reason not to forbid access to the host CA
+ * configuration box, which is common across sessions in any
+ * case.
+ */
+ s = ctrl_getset(b, "Connection/SSH/Host keys", "ca",
+ "Configure trusted certification authorities");
+ c = ctrl_pushbutton(s, "Configure host CAs", NO_SHORTCUT,
+ HELPCTX(ssh_kex_cert),
+ host_ca_button_handler, I(0));
+
if (!midsession || !(protcfginfo == 1 || protcfginfo == -1)) {
/*
* The Connection/SSH/Cipher panel.
@@ -2792,8 +2898,8 @@ void setup_config_box(struct controlbox *b, bool midsession,
conf_checkbox_handler,
I(CONF_try_ki_auth));
- s = ctrl_getset(b, "Connection/SSH/Auth", "params",
- "Authentication parameters");
+ s = ctrl_getset(b, "Connection/SSH/Auth", "aux",
+ "Other authentication-related options");
ctrl_checkbox(s, "Allow agent forwarding", 'f',
HELPCTX(ssh_auth_agentfwd),
conf_checkbox_handler, I(CONF_agentfwd));
@@ -2801,11 +2907,26 @@ void setup_config_box(struct controlbox *b, bool midsession,
HELPCTX(ssh_auth_changeuser),
conf_checkbox_handler,
I(CONF_change_username));
+
+ ctrl_settitle(b, "Connection/SSH/Auth/Credentials",
+ "Credentials to authenticate with");
+
+ s = ctrl_getset(b, "Connection/SSH/Auth/Credentials", "publickey",
+ "Public-key authentication");
ctrl_filesel(s, "Private key file for authentication:", 'k',
FILTER_KEY_FILES, false, "Select private key file",
HELPCTX(ssh_auth_privkey),
conf_filesel_handler, I(CONF_keyfile));
-
+ ctrl_filesel(s, "Certificate to use with the private key:", 'e',
+ NULL, false, "Select certificate file",
+ HELPCTX(ssh_auth_cert),
+ conf_filesel_handler, I(CONF_detached_cert));
+
+ s = ctrl_getset(b, "Connection/SSH/Auth/Credentials", "plugin",
+ "Plugin to provide authentication responses");
+ ctrl_editbox(s, "Plugin command to run", NO_SHORTCUT, 100,
+ HELPCTX(ssh_auth_plugin),
+ conf_editbox_handler, I(CONF_auth_plugin), ED_STR);
#ifndef NO_GSSAPI
/*
* Connection/SSH/Auth/GSSAPI, which sadly won't fit on
@@ -2894,12 +3015,12 @@ void setup_config_box(struct controlbox *b, bool midsession,
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;
+ c->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;
+ td->setbutton->column = 1;
+ td->setbutton->delay_taborder = true;
ctrl_columns(s, 1, 100); /* column break */
/* Bit of a hack to get the value radio buttons and
* edit-box on the same row. */
@@ -2909,14 +3030,13 @@ void setup_config_box(struct controlbox *b, bool midsession,
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;
+ "This:", NO_SHORTCUT, P(NULL));
+ td->valradio->column = 0;
td->valbox = ctrl_editbox(s, NULL, NO_SHORTCUT, 100,
HELPCTX(ssh_ttymodes),
ttymodes_handler, P(td), P(NULL));
- td->valbox->generic.column = 1;
- td->valbox->generic.align_next_to = td->valradio;
+ td->valbox->column = 1;
+ td->valbox->align_next_to = td->valradio;
ctrl_tabdelay(s, td->setbutton);
}
@@ -2933,13 +3053,13 @@ void setup_config_box(struct controlbox *b, bool midsession,
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));
+ conf_editbox_handler, I(CONF_x11_display), ED_STR);
ctrl_radiobuttons(s, "Remote X11 authentication protocol", 'u', 2,
HELPCTX(ssh_tunnels_x11auth),
conf_radiobutton_handler,
I(CONF_x11_auth),
"MIT-Magic-Cookie-1", I(X11_MIT),
- "XDM-Authorization-1", I(X11_XDM), NULL);
+ "XDM-Authorization-1", I(X11_XDM));
}
/*
@@ -2961,15 +3081,15 @@ void setup_config_box(struct controlbox *b, bool midsession,
ctrl_columns(s, 3, 55, 20, 25);
c = ctrl_text(s, "Forwarded ports:", HELPCTX(ssh_tunnels_portfwd));
- c->generic.column = COLUMN_FIELD(0,2);
+ c->column = COLUMN_FIELD(0,2);
/* You want to select from the list, _then_ hit Remove. So tab order
* should be that way round. */
pfd = (struct portfwd_data *)ctrl_alloc(b,sizeof(struct portfwd_data));
pfd->rembutton = ctrl_pushbutton(s, "Remove", 'r',
HELPCTX(ssh_tunnels_portfwd),
portfwd_handler, P(pfd));
- pfd->rembutton->generic.column = 2;
- pfd->rembutton->generic.tabdelay = true;
+ pfd->rembutton->column = 2;
+ pfd->rembutton->delay_taborder = true;
pfd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
HELPCTX(ssh_tunnels_portfwd),
portfwd_handler, P(pfd));
@@ -2985,12 +3105,12 @@ void setup_config_box(struct controlbox *b, bool midsession,
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->addbutton->column = 2;
+ pfd->addbutton->delay_taborder = true;
pfd->sourcebox = ctrl_editbox(s, "Source port", 's', 40,
HELPCTX(ssh_tunnels_portfwd),
portfwd_handler, P(pfd), P(NULL));
- pfd->sourcebox->generic.column = 0;
+ pfd->sourcebox->column = 0;
pfd->destbox = ctrl_editbox(s, "Destination", 'i', 67,
HELPCTX(ssh_tunnels_portfwd),
portfwd_handler, P(pfd), P(NULL));
@@ -2999,8 +3119,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
portfwd_handler, P(pfd),
"Local", 'l', P(NULL),
"Remote", 'm', P(NULL),
- "Dynamic", 'y', P(NULL),
- NULL);
+ "Dynamic", 'y', P(NULL));
#ifndef NO_IPV6
pfd->addressfamily =
ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3,
@@ -3008,8 +3127,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
portfwd_handler, P(pfd),
"Auto", 'u', I(ADDRTYPE_UNSPEC),
"IPv4", '4', I(ADDRTYPE_IPV4),
- "IPv6", '6', I(ADDRTYPE_IPV6),
- NULL);
+ "IPv6", '6', I(ADDRTYPE_IPV6));
#endif
ctrl_tabdelay(s, pfd->addbutton);
ctrl_columns(s, 1, 100);
@@ -3039,6 +3157,17 @@ void setup_config_box(struct controlbox *b, bool midsession,
HELPCTX(ssh_bugs_maxpkt2),
sshbug_handler, I(CONF_sshbug_maxpkt2));
+ s = ctrl_getset(b, "Connection/SSH/Bugs", "manual",
+ "Manually enabled workarounds");
+ ctrl_droplist(s, "Discards data sent before its greeting", 'd', 20,
+ HELPCTX(ssh_bugs_dropstart),
+ sshbug_handler_manual_only,
+ I(CONF_sshbug_dropstart));
+ ctrl_droplist(s, "Chokes on PuTTY's full KEXINIT", 'p', 20,
+ HELPCTX(ssh_bugs_filter_kexinit),
+ sshbug_handler_manual_only,
+ I(CONF_sshbug_filter_kexinit));
+
ctrl_settitle(b, "Connection/SSH/More bugs",
"Further workarounds for SSH server bugs");
@@ -3090,22 +3219,26 @@ void setup_config_box(struct controlbox *b, bool midsession,
"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));
+ conf_editbox_handler, I(CONF_serline), ED_STR);
}
s = ctrl_getset(b, "Connection/Serial", "sercfg", "Configure the serial line");
ctrl_editbox(s, "Speed (baud)", 's', 40,
HELPCTX(serial_speed),
- conf_editbox_handler, I(CONF_serspeed), I(-1));
+ conf_editbox_handler, I(CONF_serspeed), ED_INT);
ctrl_editbox(s, "Data bits", 'b', 40,
HELPCTX(serial_databits),
- conf_editbox_handler, I(CONF_serdatabits), I(-1));
+ conf_editbox_handler, I(CONF_serdatabits), ED_INT);
/*
* Stop bits come in units of one half.
*/
+ static const struct conf_editbox_handler_type conf_editbox_stopbits = {
+ .type = EDIT_FIXEDPOINT, .denominator = 2};
+
ctrl_editbox(s, "Stop bits", 't', 40,
HELPCTX(serial_stopbits),
- conf_editbox_handler, I(CONF_serstopbits), I(-2));
+ conf_editbox_handler, I(CONF_serstopbits),
+ CP(&conf_editbox_stopbits));
ctrl_droplist(s, "Parity", 'p', 40,
HELPCTX(serial_parity), serial_parity_handler,
I(ser_vt->serial_parity_mask));
@@ -3131,12 +3264,12 @@ void setup_config_box(struct controlbox *b, bool midsession,
conf_radiobutton_bool_handler,
I(CONF_rfc_environ),
"BSD (commonplace)", 'b', I(false),
- "RFC 1408 (unusual)", 'f', I(true), NULL);
+ "RFC 1408 (unusual)", 'f', I(true));
ctrl_radiobuttons(s, "Telnet negotiation mode:", 't', 2,
HELPCTX(telnet_passive),
conf_radiobutton_bool_handler,
I(CONF_passive_telnet),
- "Passive", I(true), "Active", I(false), NULL);
+ "Passive", I(true), "Active", I(false));
}
ctrl_checkbox(s, "Keyboard sends Telnet special commands", 'k',
HELPCTX(telnet_specialkeys),
@@ -3159,7 +3292,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
"Data to send to the server");
ctrl_editbox(s, "Local username:", 'l', 50,
HELPCTX(rlogin_localuser),
- conf_editbox_handler, I(CONF_localusername), I(1));
+ conf_editbox_handler, I(CONF_localusername), ED_STR);
}
@@ -3175,7 +3308,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
ctrl_editbox(s, "Location string", 'l', 70,
HELPCTX(supdup_location),
conf_editbox_handler, I(CONF_supdup_location),
- I(1));
+ ED_STR);
ctrl_radiobuttons(s, "Extended ASCII Character set:", 'e', 4,
HELPCTX(supdup_ascii),
@@ -3183,7 +3316,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
I(CONF_supdup_ascii_set),
"None", I(SUPDUP_CHARSET_ASCII),
"ITS", I(SUPDUP_CHARSET_ITS),
- "WAITS", I(SUPDUP_CHARSET_WAITS), NULL);
+ "WAITS", I(SUPDUP_CHARSET_WAITS));
ctrl_checkbox(s, "**MORE** processing", 'm',
HELPCTX(supdup_more),
diff --git a/CONTRIB/gdb.py b/CONTRIB/gdb.py
index 34bbb0ec..fb7413ec 100644
--- a/CONTRIB/gdb.py
+++ b/CONTRIB/gdb.py
@@ -30,12 +30,29 @@ class PuTTYMpintPrettyPrinter(gdb.printing.PrettyPrinter):
return "mp_int(NULL)".format(address)
return "mp_int(invalid @ {:#x})".format(address)
+class PuTTYPtrlenPrettyPrinter(gdb.printing.PrettyPrinter):
+ "Pretty-print strings in PuTTY's ptrlen type."
+ name = "ptrlen"
+
+ def __init__(self, val):
+ super(PuTTYPtrlenPrettyPrinter, self).__init__(self.name)
+ self.val = val
+
+ def to_string(self):
+ length = int(self.val["len"])
+ char_array_ptr_type = gdb.lookup_type(
+ "char").const().array(length).pointer()
+ line = self.val["ptr"].cast(char_array_ptr_type).dereference()
+ return repr(bytes(int(line[i]) for i in range(length))).lstrip('b')
+
class PuTTYPrinterSelector(gdb.printing.PrettyPrinter):
def __init__(self):
super(PuTTYPrinterSelector, self).__init__("PuTTY")
def __call__(self, val):
if str(val.type) == "mp_int *":
return PuTTYMpintPrettyPrinter(val)
+ if str(val.type) == "ptrlen":
+ return PuTTYPtrlenPrettyPrinter(val)
return None
gdb.printing.register_pretty_printer(None, PuTTYPrinterSelector())
@@ -203,7 +220,7 @@ class List234(gdb.Function):
Arguments are a tree234, and optionally a value type. If no value
type is given, the result is a list of the raw void * pointers
- stored in the tree. Othewise, each one is cast to a pointer to the
+ stored in the tree. Otherwise, each one is cast to a pointer to the
value type and dereferenced.
Due to limitations of GDB's convenience function syntax, the value
diff --git a/CPROXY.C b/CPROXY.C
deleted file mode 100644
index e1d788b6..00000000
--- a/CPROXY.C
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Routines to do cryptographic interaction with proxies in PuTTY.
- * This is in a separate module from proxy.c, so that it can be
- * conveniently removed in PuTTYtel by replacing this module with
- * the stub version nocproxy.c.
- */
-
-#include <assert.h>
-#include <ctype.h>
-#include <string.h>
-
-#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/DIALOG.C b/DIALOG.C
index 7409daaa..b9306982 100644
--- a/DIALOG.C
+++ b/DIALOG.C
@@ -204,31 +204,31 @@ 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)
+static dlgcontrol *ctrl_new(struct controlset *s, int type,
+ HelpCtx helpctx, handler_fn handler,
+ intorptr context)
{
- union control *c = snew(union control);
+ dlgcontrol *c = snew(dlgcontrol);
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;
+ c->type = type;
+ c->delay_taborder = false;
+ c->column = COLUMN_FIELD(0, s->ncolumns);
+ c->helpctx = helpctx;
+ c->handler = handler;
+ c->context = context;
+ c->label = NULL;
+ c->align_next_to = NULL;
return c;
}
/* `ncolumns' is followed by that many percentages, as integers. */
-union control *ctrl_columns(struct controlset *s, int ncolumns, ...)
+dlgcontrol *ctrl_columns(struct controlset *s, int ncolumns, ...)
{
- union control *c = ctrl_new(s, CTRL_COLUMNS, P(NULL), NULL, P(NULL));
+ dlgcontrol *c = ctrl_new(s, CTRL_COLUMNS, NULL_HELPCTX, NULL, P(NULL));
assert(s->ncolumns == 1 || ncolumns == 1);
c->columns.ncols = ncolumns;
s->ncolumns = ncolumns;
@@ -246,33 +246,33 @@ union control *ctrl_columns(struct controlset *s, int ncolumns, ...)
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)
+dlgcontrol *ctrl_editbox(struct controlset *s, const char *label,
+ char shortcut, int percentage,
+ HelpCtx 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;
+ dlgcontrol *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->editbox.shortcut = shortcut;
c->editbox.percentwidth = percentage;
c->editbox.password = false;
c->editbox.has_list = false;
- c->editbox.context2 = context2;
+ c->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)
+dlgcontrol *ctrl_combobox(struct controlset *s, const char *label,
+ char shortcut, int percentage,
+ HelpCtx 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;
+ dlgcontrol *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->editbox.shortcut = shortcut;
c->editbox.percentwidth = percentage;
c->editbox.password = false;
c->editbox.has_list = true;
- c->editbox.context2 = context2;
+ c->context2 = context2;
return c;
}
@@ -282,14 +282,14 @@ union control *ctrl_combobox(struct controlset *s, const char *label,
* 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,
+dlgcontrol *ctrl_radiobuttons_fn(struct controlset *s, const char *label,
+ char shortcut, int ncolumns, HelpCtx helpctx,
handler_fn handler, intorptr context, ...)
{
va_list ap;
int i;
- union control *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context);
- c->radio.label = label ? dupstr(label) : NULL;
+ dlgcontrol *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->radio.shortcut = shortcut;
c->radio.ncolumns = ncolumns;
/*
@@ -328,24 +328,24 @@ union control *ctrl_radiobuttons(struct controlset *s, const char *label,
return c;
}
-union control *ctrl_pushbutton(struct controlset *s, const char *label,
- char shortcut, intorptr helpctx,
- handler_fn handler, intorptr context)
+dlgcontrol *ctrl_pushbutton(struct controlset *s, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context)
{
- union control *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context);
- c->button.label = label ? dupstr(label) : NULL;
+ dlgcontrol *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->button.shortcut = shortcut;
c->button.isdefault = false;
c->button.iscancel = false;
return c;
}
-union control *ctrl_listbox(struct controlset *s, const char *label,
- char shortcut, intorptr helpctx,
- handler_fn handler, intorptr context)
+dlgcontrol *ctrl_listbox(struct controlset *s, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context)
{
- union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
- c->listbox.label = label ? dupstr(label) : NULL;
+ dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->listbox.shortcut = shortcut;
c->listbox.height = 5; /* *shrug* a plausible default */
c->listbox.draglist = false;
@@ -357,12 +357,12 @@ union control *ctrl_listbox(struct controlset *s, const char *label,
return c;
}
-union control *ctrl_droplist(struct controlset *s, const char *label,
- char shortcut, int percentage, intorptr helpctx,
- handler_fn handler, intorptr context)
+dlgcontrol *ctrl_droplist(struct controlset *s, const char *label,
+ char shortcut, int percentage, HelpCtx helpctx,
+ handler_fn handler, intorptr context)
{
- union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
- c->listbox.label = label ? dupstr(label) : NULL;
+ dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->listbox.shortcut = shortcut;
c->listbox.height = 0; /* means it's a drop-down list */
c->listbox.draglist = false;
@@ -374,12 +374,12 @@ union control *ctrl_droplist(struct controlset *s, const char *label,
return c;
}
-union control *ctrl_draglist(struct controlset *s, const char *label,
- char shortcut, intorptr helpctx,
- handler_fn handler, intorptr context)
+dlgcontrol *ctrl_draglist(struct controlset *s, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context)
{
- union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
- c->listbox.label = label ? dupstr(label) : NULL;
+ dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->listbox.shortcut = shortcut;
c->listbox.height = 5; /* *shrug* a plausible default */
c->listbox.draglist = true;
@@ -391,61 +391,63 @@ union control *ctrl_draglist(struct controlset *s, const char *label,
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)
+dlgcontrol *ctrl_filesel(struct controlset *s, const char *label,
+ char shortcut, const char *filter, bool write,
+ const char *title, HelpCtx helpctx,
+ handler_fn handler, intorptr context)
{
- union control *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context);
- c->fileselect.label = label ? dupstr(label) : NULL;
+ dlgcontrol *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->fileselect.shortcut = shortcut;
c->fileselect.filter = filter;
c->fileselect.for_writing = write;
c->fileselect.title = dupstr(title);
+ c->fileselect.just_button = false;
return c;
}
-union control *ctrl_fontsel(struct controlset *s, const char *label,
- char shortcut, intorptr helpctx,
- handler_fn handler, intorptr context)
+dlgcontrol *ctrl_fontsel(struct controlset *s, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context)
{
- union control *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context);
- c->fontselect.label = label ? dupstr(label) : NULL;
+ dlgcontrol *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->fontselect.shortcut = shortcut;
return c;
}
-union control *ctrl_tabdelay(struct controlset *s, union control *ctrl)
+dlgcontrol *ctrl_tabdelay(struct controlset *s, dlgcontrol *ctrl)
{
- union control *c = ctrl_new(s, CTRL_TABDELAY, P(NULL), NULL, P(NULL));
+ dlgcontrol *c = ctrl_new(s, CTRL_TABDELAY, NULL_HELPCTX, NULL, P(NULL));
c->tabdelay.ctrl = ctrl;
return c;
}
-union control *ctrl_text(struct controlset *s, const char *text,
- intorptr helpctx)
+dlgcontrol *ctrl_text(struct controlset *s, const char *text,
+ HelpCtx helpctx)
{
- union control *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL));
- c->text.label = dupstr(text);
+ dlgcontrol *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL));
+ c->label = dupstr(text);
+ c->text.wrap = true;
return c;
}
-union control *ctrl_checkbox(struct controlset *s, const char *label,
- char shortcut, intorptr helpctx,
- handler_fn handler, intorptr context)
+dlgcontrol *ctrl_checkbox(struct controlset *s, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context)
{
- union control *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context);
- c->checkbox.label = label ? dupstr(label) : NULL;
+ dlgcontrol *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->checkbox.shortcut = shortcut;
return c;
}
-void ctrl_free(union control *ctrl)
+void ctrl_free(dlgcontrol *ctrl)
{
int i;
- sfree(ctrl->generic.label);
- switch (ctrl->generic.type) {
+ sfree(ctrl->label);
+ switch (ctrl->type) {
case CTRL_RADIO:
for (i = 0; i < ctrl->radio.nbuttons; i++)
sfree(ctrl->radio.buttons[i]);
diff --git a/DIALOG.H b/DIALOG.H
index 86ebfc20..ef9a9dfe 100644
--- a/DIALOG.H
+++ b/DIALOG.H
@@ -43,11 +43,12 @@ enum {
* included with DEFINE_INTORPTR_FNS defined. This is a total pain,
* but such is life.
*/
-typedef union { void *p; int i; } intorptr;
+typedef union { void *p; const void *cp; int i; } intorptr;
#ifndef INLINE
intorptr I(int i);
intorptr P(void *p);
+intorptr CP(const void *p);
#endif
#if defined DEFINE_INTORPTR_FNS || defined INLINE
@@ -58,6 +59,7 @@ intorptr P(void *p);
#endif
PREFIX intorptr I(int i) { intorptr ret; ret.i = i; return ret; }
PREFIX intorptr P(void *p) { intorptr ret; ret.p = p; return ret; }
+PREFIX intorptr CP(const void *p) { intorptr ret; ret.cp = p; return ret; }
#undef PREFIX
#endif
@@ -73,8 +75,6 @@ PREFIX intorptr P(void *p) { intorptr ret; ret.p = p; return ret; }
#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
@@ -103,326 +103,346 @@ enum {
EVENT_SELCHANGE,
EVENT_CALLBACK
};
-typedef void (*handler_fn)(union control *ctrl, dlgparam *dp,
+typedef void (*handler_fn)(dlgcontrol *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
+struct dlgcontrol {
+ /*
+ * Generic fields shared by all the control types.
+ */
+ int type;
+ /*
+ * Every control except CTRL_COLUMNS has _some_ sort of label. By
+ * putting it in the `generic' union as well as everywhere else,
+ * we avoid having to have an irritating switch statement when we
+ * go through and deallocate all the memory in a config-box
+ * structure.
+ *
+ * Yes, this does mean that any non-NULL value in this field is
+ * expected to be dynamically allocated and freeable.
+ *
+ * For CTRL_COLUMNS, this field MUST be NULL.
+ */
+ char *label;
+ /*
+ * If `delay_taborder' is true, it indicates that this particular
+ * control should not yet appear in the tab order. A subsequent
+ * CTRL_TABDELAY entry will place it.
+ */
+ bool delay_taborder;
+ /*
+ * Indicate which column(s) this control occupies. This can be
+ * unpacked into starting column and column span by the COLUMN
+ * macros above.
+ */
+ int column;
+ /*
+ * Most controls need to provide a function which gets called when
+ * that control's setting is changed, or when the control's
+ * setting needs initialising.
+ *
+ * The `data' parameter points to the writable data being modified
+ * as a result of the configuration activity; for example, the
+ * PuTTY `Conf' structure, although not necessarily.
+ *
+ * The `dlg' parameter is passed back to the platform- specific
+ * routines to read and write the actual control state.
+ */
+ handler_fn handler;
+ /*
+ * Almost all of the above functions will find it useful to be
+ * able to store one or two pieces of `void *' or `int' data.
+ */
+ intorptr context, context2;
+ /*
+ * For any control, we also allow the storage of a piece of data
+ * for use by context-sensitive help. For example, on Windows you
+ * can click the magic question mark and then click a control, and
+ * help for that control should spring up. Hence, here is a slot
+ * in which to store per-control data that a particular
+ * platform-specific driver can use to ensure it brings up the
+ * right piece of help text.
+ */
+ HelpCtx helpctx;
+ /*
+ * Setting this to non-NULL coerces two or more controls to have
+ * their y-coordinates adjusted so that they can sit alongside
+ * each other and look nicely aligned, even if they're different
+ * heights.
+ *
+ * Set this field on later controls (in terms of order in the data
+ * structure), pointing back to earlier ones, so that when each
+ * control is instantiated, the referred-to one is already there
+ * to be referred to.
+ *
+ * Don't expect this to change the position of the _first_
+ * control. Currently, the layout is done one control at a time,
+ * so that once the first control has been placed, the second one
+ * can't cause the first one to be retrospectively moved.
+ */
+ dlgcontrol *align_next_to;
-union 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.
+ * Union of further fields specific to each control type.
*/
- 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;
+ union {
+ struct { /* for CTRL_TABDELAY */
+ dlgcontrol *ctrl;
+ } tabdelay;
+ struct { /* for CTRL_EDITBOX */
+ char shortcut; /* keyboard shortcut */
+ /*
+ * Percentage of the dialog-box width used by the edit
+ * box. If this is set to 100, the label is on its own
+ * line; otherwise the label is on the same line as the
+ * box itself.
+ */
+ int percentwidth;
+ bool password; /* details of input are hidden */
+ /*
+ * A special case of the edit box is the combo box, which
+ * has a drop-down list built in. (Note that a _non_-
+ * editable drop-down list is done as a special case of a
+ * list box.)
+ *
+ * Don't try setting has_list and password on the same
+ * control; front ends are not required to support that
+ * combination.
+ */
+ bool has_list;
+ } editbox;
+ struct { /* for CTRL_RADIO */
+ /*
+ * `shortcut' here is a single keyboard shortcut which is
+ * expected to select the whole group of radio buttons. It
+ * can be NO_SHORTCUT if required, and there is also a way
+ * to place individual shortcuts on each button; see
+ * below.
+ */
+ char shortcut;
+ /*
+ * There are separate fields for `ncolumns' and `nbuttons'
+ * for several reasons.
+ *
+ * Firstly, we sometimes want the last of a set of buttons
+ * to have a longer label than the rest; we achieve this
+ * by setting `ncolumns' higher than `nbuttons', and the
+ * layout code is expected to understand that the final
+ * button should be given all the remaining space on the
+ * line. This sounds like a ludicrously specific special
+ * case (if we're doing this sort of thing, why not have
+ * the general ability to have a particular button span
+ * more than one column whether it's the last one or not?)
+ * but actually it's reasonably common for the sort of
+ * three-way control you get a lot of in PuTTY: `yes'
+ * versus `no' versus `some more complex way to decide'.
+ *
+ * Secondly, setting `nbuttons' higher than `ncolumns'
+ * lets us have more than one line of radio buttons for a
+ * single setting. A very important special case of this
+ * is setting `ncolumns' to 1, so that each button is on
+ * its own line.
+ */
+ int ncolumns;
+ int nbuttons;
+ /*
+ * This points to a dynamically allocated array of `char *'
+ * pointers, each of which points to a dynamically
+ * allocated string.
+ */
+ char **buttons; /* `nbuttons' button labels */
+ /*
+ * This points to a dynamically allocated array of `char'
+ * giving the individual keyboard shortcuts for each radio
+ * button. The array may be NULL if none are required.
+ */
+ char *shortcuts; /* `nbuttons' shortcuts; may be NULL */
+ /*
+ * This points to a dynamically allocated array of
+ * intorptr, giving helpful data for each button.
+ */
+ intorptr *buttondata; /* `nbuttons' entries; may be NULL */
+ } radio;
+ struct { /* for CTRL_CHECKBOX */
+ char shortcut;
+ } checkbox;
+ struct { /* for CTRL_BUTTON */
+ char shortcut;
+ /*
+ * At least Windows has the concept of a `default push
+ * button', which gets implicitly pressed when you hit
+ * Return even if it doesn't have the input focus.
+ */
+ bool isdefault;
+ /*
+ * Also, the reverse of this: a default cancel-type
+ * button, which is implicitly pressed when you hit
+ * Escape.
+ */
+ bool iscancel;
+ } button;
+ struct { /* for CTRL_LISTBOX */
+ char shortcut; /* keyboard shortcut */
+ /*
+ * Height of the list box, in approximate number of lines.
+ * If this is zero, the list is a drop-down list.
+ */
+ int height; /* height in lines */
+ /*
+ * If this is set, the list elements can be reordered by
+ * the user (by drag-and-drop or by Up and Down buttons,
+ * whatever the per-platform implementation feels
+ * comfortable with). This is not guaranteed to work on a
+ * drop-down list, so don't try it!
+ */
+ bool draglist;
+ /*
+ * If this is non-zero, the list can have more than one
+ * element selected at a time. This is not guaranteed to
+ * work on a drop-down list, so don't try it!
+ *
+ * Different non-zero values request slightly different
+ * types of multi-selection (this may well be meaningful
+ * only in GTK, so everyone else can ignore it if they
+ * want). 1 means the list box expects to have individual
+ * items selected, whereas 2 means it expects the user to
+ * want to select a large contiguous range at a time.
+ */
+ int multisel;
+ /*
+ * Percentage of the dialog-box width used by the list
+ * box. If this is set to 100, the label is on its own
+ * line; otherwise the label is on the same line as the
+ * box itself. Setting this to anything other than 100 is
+ * not guaranteed to work on a _non_-drop-down list, so
+ * don't try it!
+ */
+ int percentwidth;
+ /*
+ * Some list boxes contain strings that contain tab
+ * characters. If `ncols' is greater than 0, then
+ * `percentages' is expected to be non-zero and to contain
+ * the respective widths of `ncols' columns, which
+ * together will exactly fit the width of the list box.
+ * Otherwise `percentages' must be NULL.
+ *
+ * There should never be more than one column in a
+ * drop-down list (one with height==0), because front ends
+ * may have to implement it as a special case of an
+ * editable combo box.
+ */
+ int ncols; /* number of columns */
+ int *percentages; /* % width of each column */
+ /*
+ * Flag which can be set to false to suppress the
+ * horizontal scroll bar if a list box entry goes off the
+ * right-hand side.
+ */
+ bool hscroll;
+ } listbox;
+ struct { /* for CTRL_FILESELECT */
+ char shortcut;
+ /*
+ * `filter' dictates what type of files will be selected
+ * by default; for example, when selecting private key
+ * files the file selector would do well to only show .PPK
+ * files (on those systems where this is the chosen
+ * extension).
+ *
+ * The precise contents of `filter' are platform-defined,
+ * unfortunately. The special value NULL means `all files'
+ * and is always a valid fallback.
+ *
+ * Unlike almost all strings in this structure, this value
+ * is NOT expected to require freeing (although of course
+ * you can always use ctrl_alloc if you do need to create
+ * one on the fly). This is because the likely mode of use
+ * is to define string constants in a platform-specific
+ * header file, and directly reference those. Or worse, a
+ * particular platform might choose to cast integers into
+ * this pointer type...
+ */
+ char const *filter;
+ /*
+ * Some systems like to know whether a file selector is
+ * choosing a file to read or one to write (and possibly
+ * create).
+ */
+ bool for_writing;
+ /*
+ * On at least some platforms, the file selector is a
+ * separate dialog box, and contains a user-settable
+ * title.
+ *
+ * This value _is_ expected to require freeing.
+ */
+ char *title;
+ /*
+ * Reduce the file selector to just a single browse
+ * button.
+ *
+ * Normally, a file selector is used to set a config
+ * option that consists of a file name, so that that file
+ * will be read or written at run time. In that situation,
+ * it makes sense to have an edit box showing the
+ * currently selected file name, and a button to change it
+ * interactively.
+ *
+ * But occasionally a file selector is used to load a file
+ * _during_ configuration. For example, host CA public
+ * keys are entered directly into the configuration as
+ * strings, not stored by reference to a filename; but if
+ * you have one in a file, you want to be able to load it
+ * during the lifetime of the CA config box rather than
+ * awkwardly copy-pasting it. So in that case you just
+ * want a 'pop up a file chooser' button, and when that
+ * delivers a file name, you'll deal with it there and
+ * then and write some other thing (like the file's
+ * contents) into a nearby edit box.
+ *
+ * If you set this flag, then you may not call
+ * dlg_filesel_set on the file selector at all, because it
+ * doesn't store a filename. And you can only call
+ * dlg_filesel_get on it in the handler for EVENT_ACTION,
+ * which is what will be sent to you when the user has
+ * used it to choose a filename.
+ */
+ bool just_button;
+ } fileselect;
+ struct { /* for CTRL_COLUMNS */
+ /* In this variant, `label' MUST be NULL. */
+ int ncols; /* number of columns */
+ int *percentages; /* % width of each column */
+ /*
+ * Every time this control type appears, exactly one of
+ * `ncols' and the previous number of columns MUST be one.
+ * Attempting to allow a seamless transition from a four-
+ * to a five-column layout, for example, would be way more
+ * trouble than it was worth. If you must lay things out
+ * like that, define eight unevenly sized columns and use
+ * column-spanning a lot. But better still, just don't.
+ *
+ * `percentages' may be NULL if ncols==1, to save space.
+ */
+ } columns;
+ struct { /* for CTRL_FONTSELECT */
+ char shortcut;
+ } fontselect;
+ struct { /* for CTRL_TEXT */
+ /*
+ * If this is true (the default), the text will wrap on to
+ * multiple lines. If false, it will stay on the same
+ * line, with a horizontal scrollbar if necessary.
+ */
+ bool wrap;
+ } text;
+ };
};
#undef STANDARD_PREFIX
/*
- * `controlset' is a container holding an array of `union control'
+ * `controlset' is a container holding an array of `dlgcontrol'
* structures, together with a panel name and a title for the whole
* set. In Windows and any similar-looking GUI, each `controlset'
* in the config will be a container box within a panel.
@@ -435,9 +455,9 @@ struct controlset {
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 ncontrols; /* number of `dlgcontrol' in array */
size_t ctrlsize; /* allocated size of array */
- union control **ctrls; /* actual array */
+ dlgcontrol **ctrls; /* actual array */
};
typedef void (*ctrl_freefn_t)(void *); /* used by ctrl_alloc_with_free */
@@ -471,7 +491,7 @@ 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 *);
+void ctrl_free(dlgcontrol *);
/*
* This function works like `malloc', but the memory it returns
@@ -490,73 +510,77 @@ 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.
+ * Individual routines to create `dlgcontrol' structures in a controlset.
*
* Most of these routines allow the most common fields to be set
* directly, and put default values in the rest. Each one returns a
- * pointer to the `union control' it created, so that final tweaks
+ * pointer to the `dlgcontrol' 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);
+dlgcontrol *ctrl_columns(struct controlset *, int ncolumns, ...);
+dlgcontrol *ctrl_editbox(struct controlset *, const char *label,
+ char shortcut, int percentage, HelpCtx helpctx,
+ handler_fn handler,
+ intorptr context, intorptr context2);
+dlgcontrol *ctrl_combobox(struct controlset *, const char *label,
+ char shortcut, int percentage, HelpCtx helpctx,
+ handler_fn handler,
+ intorptr context, intorptr context2);
/*
* `ncolumns' is followed by (alternately) radio button titles and
* intorptrs, until a NULL in place of a title string is seen. Each
* title is expected to be followed by a shortcut _iff_ `shortcut'
* is NO_SHORTCUT.
*/
-union control *ctrl_radiobuttons(struct controlset *, const char *label,
- char shortcut, int ncolumns, intorptr helpctx,
+dlgcontrol *ctrl_radiobuttons_fn(struct controlset *, const char *label,
+ char shortcut, int ncolumns, HelpCtx 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,
+#define ctrl_radiobuttons(...) \
+ ctrl_radiobuttons_fn(__VA_ARGS__, (const char *)NULL)
+dlgcontrol *ctrl_pushbutton(struct controlset *, const char *label,
+ char shortcut, HelpCtx helpctx,
handler_fn handler, intorptr context);
-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 *);
+dlgcontrol *ctrl_listbox(struct controlset *, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context);
+dlgcontrol *ctrl_droplist(struct controlset *, const char *label,
+ char shortcut, int percentage, HelpCtx helpctx,
+ handler_fn handler, intorptr context);
+dlgcontrol *ctrl_draglist(struct controlset *, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context);
+dlgcontrol *ctrl_filesel(struct controlset *, const char *label,
+ char shortcut, const char *filter, bool write,
+ const char *title, HelpCtx helpctx,
+ handler_fn handler, intorptr context);
+dlgcontrol *ctrl_fontsel(struct controlset *, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context);
+dlgcontrol *ctrl_text(struct controlset *, const char *text,
+ HelpCtx helpctx);
+dlgcontrol *ctrl_checkbox(struct controlset *, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context);
+dlgcontrol *ctrl_tabdelay(struct controlset *, dlgcontrol *);
/*
* Routines the platform-independent dialog code can call to read
* and write the values of controls.
*/
-void dlg_radiobutton_set(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 */
+void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int whichbutton);
+int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp);
+void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked);
+bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp);
+void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text);
+char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp); /* result must be freed by caller */
+void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp,
+ size_t start, size_t len);
/* The `listbox' functions can also apply to combo boxes. */
-void dlg_listbox_clear(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_clear(dlgcontrol *ctrl, dlgparam *dp);
+void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index);
+void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text);
/*
* Each listbox entry may have a numeric id associated with it.
* Note that some front ends only permit a string to be stored at
@@ -564,44 +588,44 @@ void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text);
* 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,
+void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp,
char const *text, int id);
-int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index);
+int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index);
/* dlg_listbox_index returns <0 if no single element is selected. */
-int dlg_listbox_index(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);
+int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp);
+bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index);
+void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index);
+void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text);
+void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn);
+Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp);
+void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fn);
+FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp);
/*
* Bracketing a large set of updates in these two functions will
* cause the front end (if possible) to delay updating the screen
* until it's all complete, thus avoiding flicker.
*/
-void dlg_update_start(union control *ctrl, dlgparam *dp);
-void dlg_update_done(union control *ctrl, dlgparam *dp);
+void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp);
+void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp);
/*
* Set input focus into a particular control.
*/
-void dlg_set_focus(union control *ctrl, dlgparam *dp);
+void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp);
/*
* Change the label text on a control.
*/
-void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text);
+void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text);
/*
* Return the `ctrl' structure for the most recent control that had
* the input focus apart from the one mentioned. This is NOT
* GUARANTEED to work on all platforms, so don't base any critical
* functionality on it!
*/
-union control *dlg_last_focused(union control *ctrl, dlgparam *dp);
+dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp);
/*
* Find out whether a particular control is currently visible.
*/
-bool dlg_is_visible(union control *ctrl, dlgparam *dp);
+bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp);
/*
* During event processing, you might well want to give an error
* indication to the user. dlg_beep() is a quick and easy generic
@@ -629,9 +653,9 @@ void dlg_end(dlgparam *dp, int value);
* 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,
+void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp,
int r, int g, int b);
-bool dlg_coloursel_results(union control *ctrl, dlgparam *dp,
+bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp,
int *r, int *g, int *b);
/*
@@ -643,7 +667,7 @@ bool dlg_coloursel_results(union control *ctrl, dlgparam *dp,
* 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);
+void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp);
/*
* Standard helper functions for reading a controlbox structure.
@@ -663,3 +687,9 @@ int ctrl_path_elements(const char *path);
/* Return the number of matching path elements at the starts of p1 and p2,
* or INT_MAX if the paths are identical. */
int ctrl_path_compare(const char *p1, const char *p2);
+
+/*
+ * Normalise the align_next_to fields in a controlset so that they
+ * form a backwards linked list.
+ */
+void ctrlset_normalise_aligns(struct controlset *s);
diff --git a/DOC/BLURB.BUT b/DOC/BLURB.BUT
index f980e9f1..c68a6262 100644
--- a/DOC/BLURB.BUT
+++ b/DOC/BLURB.BUT
@@ -17,7 +17,6 @@ page</a>.</p>}
\cfg{chm-contents-filename}{index.html}
\cfg{chm-template-filename}{%k.html}
\cfg{chm-head-end}{<link rel="stylesheet" type="text/css" href="chm.css">}
-\cfg{chm-extra-file}{chm.css}
\cfg{xhtml-contents-filename}{index.html}
\cfg{text-filename}{puttydoc.txt}
diff --git a/DOC/CONFIG.BUT b/DOC/CONFIG.BUT
index 77313282..f258a356 100644
--- a/DOC/CONFIG.BUT
+++ b/DOC/CONFIG.BUT
@@ -596,6 +596,33 @@ through to \c{ESC [j}. With control they generate \c{ESC [k} through
to \c{ESC [v}, and with shift and control together they generate
\c{ESC [w} through to \c{ESC [\{}.
+\b In \I{xterm}Xterm 216 mode, the unshifted function keys behave the
+same as Xterm R6 mode. But pressing a function key together with Shift
+or Alt or Ctrl generates a different sequence containing an extra
+numeric parameter of the form (1 for Shift) + (2 for Alt) + (4 for
+Ctrl) + 1. For F1-F4, the basic sequences like \c{ESC OP} become
+\cw{ESC [1;}\e{bitmap}\cw{P} and similar; for F5 and above,
+\cw{ESC[}\e{index}\cw{~} becomes
+\cw{ESC[}\e{index}\cw{;}\e{bitmap}\cw{~}.
+
+If you don't know what any of this means, you probably don't need to
+fiddle with it.
+
+\S{config-sharrow} Changing the action of the \i{shifted arrow keys}
+
+This option affects the arrow keys, if you press one with any of the
+modifier keys Shift, Ctrl or Alt held down.
+
+\b In the default mode, labelled \c{Ctrl toggles app mode}, the Ctrl
+key toggles between the default arrow-key sequences like \c{ESC [A} and
+\c{ESC [B}, and the sequences Digital's terminals generate in
+\q{application cursor keys} mode, i.e. \c{ESC O A} and so on. Shift
+and Alt have no effect.
+
+\b In the \q{xterm-style bitmap} mode, Shift, Ctrl and Alt all
+generate different sequences, with a number indicating which set of
+modifiers is active.
+
If you don't know what any of this means, you probably don't need to
fiddle with it.
@@ -1342,7 +1369,7 @@ which one of the options is \q{Paste}). (This context menu is always
available by holding down Ctrl and right-clicking, regardless of the
setting of this option.)
-(When PuTTY iself is running on Unix, it follows the X Window System
+(When PuTTY itself is running on Unix, it follows the X Window System
convention.)
\S{config-mouseshift} \q{Shift overrides application's use of mouse}
@@ -1916,23 +1943,50 @@ it must always be explicitly configured.
\S{config-proxy-type} Setting the proxy type
-The \q{Proxy type} radio buttons allow you to configure what type of
+The \q{Proxy type} drop-down allows you to configure what type of
proxy you want PuTTY to use for its network connections. The default
setting is \q{None}; in this mode no proxy is used for any
connection.
-\b Selecting \I{HTTP proxy}\q{HTTP} allows you to proxy your connections
-through a web server supporting the HTTP \cw{CONNECT} command, as documented
-in \W{http://www.ietf.org/rfc/rfc2817.txt}{RFC 2817}.
+\b Selecting \I{HTTP proxy}\q{HTTP CONNECT} allows you to proxy your
+connections through a web server supporting the HTTP \cw{CONNECT} command,
+as documented in \W{https://www.rfc-editor.org/rfc/rfc2817}{RFC 2817}.
\b Selecting \q{SOCKS 4} or \q{SOCKS 5} allows you to proxy your
connections through a \i{SOCKS server}.
\b Many firewalls implement a less formal type of proxy in which a
-user can make a Telnet connection directly to the firewall machine
+user can make a Telnet or TCP connection directly to the firewall machine
and enter a command such as \c{connect myhost.com 22} to connect
through to an external host. Selecting \I{Telnet proxy}\q{Telnet}
-allows you to tell PuTTY to use this type of proxy.
+allows you to tell PuTTY to use this type of proxy, with the precise
+command specified as described in \k{config-proxy-command}.
+
+\b There are several ways to use a SSH server as a proxy. All of
+these cause PuTTY to make a secondary SSH connection to the proxy host
+(sometimes called a \q{\i{jump host}} in this context).
+
+\lcont{
+The \q{Proxy hostname} field will be interpreted as the name of a
+PuTTY saved session if one exists, or a hostname if not. This
+allows multi-hop jump paths, if the referenced saved session is
+itself configured to use an SSH proxy; and it allows combining SSH
+and non-SSH proxying.
+
+\b \q{SSH to proxy and use port forwarding} causes PuTTY to use the
+secondary SSH connection to open a port-forwarding channel to the
+final destination host (similar to OpenSSH's \cw{-J} option).
+
+\b \q{SSH to proxy and execute a command} causes PuTTY to run an
+arbitrary remote command on the proxy SSH server and use that
+command's standard input and output streams to run the primary
+connection over. The remote command line is specified as described in
+\k{config-proxy-command}.
+
+\b \q{SSH to proxy and invoke a subsystem} is similar but causes PuTTY
+to start an SSH \q{\i{subsystem}} rather than an ordinary command line.
+This might be useful with a specially set up SSH proxy server.
+}
\b Selecting \I{Local proxy}\q{Local} allows you to specify an arbitrary
command on the local machine to act as a proxy. When the session is
@@ -1945,11 +1999,6 @@ This could be used, for instance, to talk to some kind of network proxy
that PuTTY does not natively support; or you could tunnel a connection
over something other than TCP/IP entirely.
-If you want your local proxy command to make a secondary SSH
-connection to a proxy host and then tunnel the primary connection
-over that, you might well want the \c{-nc} command-line option in
-Plink. See \k{using-cmdline-ncmode} for more information.
-
You can also enable this mode on the command line; see
\k{using-cmdline-proxycmd}.
}
@@ -2007,9 +2056,9 @@ set it to \q{Yes}, PuTTY will always pass host names straight to the
proxy without trying to look them up first.
If you set this option to \q{Auto} (the default), PuTTY will do
-something it considers appropriate for each type of proxy. Telnet,
-HTTP, and SOCKS5 proxies will have host names passed straight to
-them; SOCKS4 proxies will not.
+something it considers appropriate for each type of proxy. Most
+types of proxy (HTTP, SOCK5, SSH, Telnet, and local) will have host
+names passed straight to them; SOCKS4 proxies will not.
Note that if you are doing DNS at the proxy, you should make sure
that your proxy exclusion settings (see \k{config-proxy-exclude}) do
@@ -2022,15 +2071,30 @@ is a protocol extension (SOCKS 4A) which does support it, but not
all SOCKS 4 servers provide this extension. If you enable proxy DNS
and your SOCKS 4 server cannot deal with it, this might be why.
+If you want to avoid PuTTY making \e{any} DNS query related to your
+destination host name (for example, because your local DNS resolver is
+very slow to return a negative response in that situation), then as
+well as setting this control to \q{Yes}, you may also need to turn off
+GSSAPI authentication and GSSAPI key exchange in SSH (see
+\k{config-ssh-auth-gssapi} and \k{config-ssh-gssapi-kex}
+respectively). This is because GSSAPI setup also involves a DNS query
+for the destination host name, and that query is performed by the
+separate GSSAPI library, so PuTTY can't override or reconfigure it.
+
\S{config-proxy-auth} \I{proxy username}Username and \I{proxy password}password
-If your proxy requires \I{proxy authentication}authentication, you can
-enter a username and a password in the \q{Username} and \q{Password} boxes.
+You can enter a username and a password in the \q{Username} and
+\q{Password} boxes, which will be used if your proxy requires
+\I{proxy authentication}authentication.
\I{security hazard}Note that if you save your session, the proxy
password will be saved in plain text, so anyone who can access your PuTTY
configuration data will be able to discover it.
+If PuTTY discovers that it needs a proxy username or password and you
+have not specified one here, PuTTY will prompt for it interactively in
+the terminal window.
+
Authentication is not fully supported for all forms of proxy:
\b Username and password authentication is supported for HTTP
@@ -2042,28 +2106,44 @@ proxies and SOCKS 5 proxies.
supports it (this is not supported in \i{PuTTYtel}); otherwise the
password is sent to the proxy in \I{plaintext password}plain text.
-\b With HTTP proxying, the only currently supported authentication
-method is \I{HTTP basic}\q{basic}, where the password is sent to the proxy
-in \I{plaintext password}plain text.
+\b With HTTP proxying, authentication is via \q{\i{HTTP Digest}} if
+possible (again, not supported in PuTTYtel), or \q{\i{HTTP Basic}}. In
+the latter case, the password is sent to the proxy in \I{plaintext
+password}plain text.
}
\b SOCKS 4 can use the \q{Username} field, but does not support
passwords.
+\b SSH proxying can use all the same forms of SSH authentication
+supported by PuTTY for its main connection. If the SSH server requests
+password authentication, any configured proxy password will be used,
+but other authentication methods such as public keys and GSSAPI will
+be tried first, just as for a primary SSH connection, and if they
+require credentials such as a key passphrase, PuTTY will interactively
+prompt for these.
+
\b You can specify a way to include a username and password in the
-Telnet/Local proxy command (see \k{config-proxy-command}).
+Telnet/Local proxy command (see \k{config-proxy-command}). If you do
+so, and don't also specify the actual username and/or password in the
+configuration, PuTTY will interactively prompt for them.
-\S{config-proxy-command} Specifying the Telnet or Local proxy command
+\S{config-proxy-command} Specifying the Telnet, SSH, or Local proxy command
If you are using the \i{Telnet proxy} type, the usual command required
by the firewall's Telnet server is \c{connect}, followed by a host
name and a port number. If your proxy needs a different command,
-you can enter an alternative here.
+you can enter an alternative in the \q{Command to send to proxy} box.
If you are using the \i{Local proxy} type, the local command to run
is specified here.
+If you are using the \q{SSH to proxy and execute a command} type, the
+command to run on the SSH proxy server is specified here. Similarly, if
+you are using \q{SSH to proxy and invoke a subsystem}, the subsystem
+name is constructed as specified here.
+
In this string, you can use \c{\\n} to represent a new-line, \c{\\r}
to represent a carriage return, \c{\\t} to represent a tab
character, and \c{\\x} followed by two hex digits to represent any
@@ -2071,12 +2151,15 @@ other character. \c{\\\\} is used to encode the \c{\\} character
itself.
Also, the special strings \c{%host} and \c{%port} will be replaced
-by the host name and port number you want to connect to. The strings
-\c{%user} and \c{%pass} will be replaced by the proxy username and
-password you specify. The strings \c{%proxyhost} and \c{%proxyport}
+by the host name and port number you want to connect to. For Telnet
+and Local proxy types, the strings \c{%user} and \c{%pass} will be
+replaced by the proxy username and password (which, if not specified
+in the configuration, will be prompted for) \dash this does not happen
+with SSH proxy types (because the proxy username/password are used
+for SSH authentication). The strings \c{%proxyhost} and \c{%proxyport}
will be replaced by the host details specified on the \e{Proxy} panel,
-if any (this is most likely to be useful for the Local proxy type).
-To get a literal \c{%} sign, enter \c{%%}.
+if any (this is most likely to be useful for proxy types using a
+local or remote command). To get a literal \c{%} sign, enter \c{%%}.
If a Telnet proxy server prompts for a username and password
before commands can be sent, you can use a command such as:
@@ -2086,8 +2169,8 @@ before commands can be sent, you can use a command such as:
This will send your username and password as the first two lines to
the proxy, followed by a command to connect to the desired host and
port. Note that if you do not include the \c{%user} or \c{%pass}
-tokens in the Telnet command, then the \q{Username} and \q{Password}
-configuration fields will be ignored.
+tokens in the Telnet command, then anything specified in \q{Username}
+and \q{Password} configuration fields will be ignored.
\S{config-proxy-logging} Controlling \i{proxy logging}
@@ -2264,24 +2347,51 @@ cipher selection (see \k{config-ssh-encryption}).
PuTTY currently supports the following key exchange methods:
-\b \q{ECDH}: \i{elliptic curve} \i{Diffie-Hellman key exchange}.
+\b \q{NTRU Prime / Curve25519 hybrid}: \q{\i{Streamlined NTRU Prime}}
+is a lattice-based algorithm intended to resist \i{quantum attacks}.
+In this key exchange method, it is run in parallel with a conventional
+Curve25519-based method (one of those included in \q{ECDH}), in such
+a way that it should be no \e{less} secure than that commonly-used
+method, and hopefully also resistant to a new class of attacks.
-\b \q{Group 14}: Diffie-Hellman key exchange with a well-known
-2048-bit group.
+\b \q{\i{ECDH}}: elliptic curve Diffie-Hellman key exchange,
+with a variety of standard curves and hash algorithms.
-\b \q{Group 1}: Diffie-Hellman key exchange with a well-known
-1024-bit group. We no longer recommend using this method, and it's
-not used by default in new installations; however, it may be the
-only method supported by very old server software.
+\b The original form of \i{Diffie-Hellman key exchange}, with a
+variety of well-known groups and hashes:
-\b \q{\ii{Group exchange}}: with this method, instead of using a fixed
-group, PuTTY requests that the server suggest a group to use for key
-exchange; the server can avoid groups known to be weak, and possibly
-invent new ones over time, without any changes required to PuTTY's
-configuration. We recommend use of this method instead of the
-well-known groups, if possible.
+\lcont{
+\b \q{Group 18}, a well-known 8192-bit group, used with the SHA-512
+hash function.
-\b \q{\i{RSA key exchange}}: this requires much less computational
+\b \q{Group 17}, a well-known 6144-bit group, used with the SHA-512
+hash function.
+
+\b \q{Group 16}, a well-known 4096-bit group, used with the SHA-512
+hash function.
+
+\b \q{Group 15}, a well-known 3072-bit group, used with the SHA-512
+hash function.
+
+\b \q{Group 14}: a well-known 2048-bit group, used with the SHA-256
+hash function or, if the server doesn't support that, SHA-1.
+
+\b \q{Group 1}: a well-known 1024-bit group, used with the SHA-1
+hash function. Neither we nor current SSH standards recommend using
+this method any longer, and it's not used by default in new
+installations; however, it may be the only method supported by very
+old server software.
+}
+
+\b \q{Diffie-Hellman \i{group exchange}}: with this method, instead
+of using a fixed group, PuTTY requests that the server suggest a group
+to use for a subsequent Diffie-Hellman key exchange; the server can
+avoid groups known to be weak, and possibly invent new ones over time,
+without any changes required to PuTTY's configuration. This key
+exchange method uses the SHA-256 hash or, if the server doesn't
+support that, SHA-1.
+
+\b \q{\i{RSA-based key exchange}}: this requires much less computational
effort on the part of the client, and somewhat less on the part of
the server, than Diffie-Hellman key exchange.
@@ -2303,6 +2413,10 @@ when using Kerberos V5, and not other GSSAPI mechanisms. If the user
running PuTTY has current Kerberos V5 credentials, then PuTTY will
select the GSSAPI key exchange methods in preference to any of the
ordinary SSH key exchange methods configured in the preference list.
+There's a GSSAPI-based equivalent to most of the ordinary methods
+listed in \k{config-ssh-kex-order}; server support determines which
+one will be used. (PuTTY's preference order for GSSAPI-authenticated
+key exchange methods is fixed, not controlled by the preference list.)
The advantage of doing GSSAPI authentication as part of the SSH key
exchange is apparent when you are using credential delegation (see
@@ -2317,7 +2431,8 @@ support GSSAPI in the SSH user authentication phase. This will still
let you log in using your Kerberos credentials, but will only allow
you to delegate the credentials that are active at the beginning of
the session; they can't be refreshed automatically later, in a
-long-running session.
+long-running session. See \k{config-ssh-auth-gssapi} for how to
+control GSSAPI user authentication in PuTTY.
Another effect of GSSAPI key exchange is that it replaces the usual
SSH mechanism of permanent host keys described in \k{gs-hostkey}.
@@ -2403,7 +2518,7 @@ protection than SSH-2 without rekeys.
\H{config-ssh-hostkey} The Host Keys panel
-The Host Keys panel allows you to configure options related to SSH-2
+The Host Keys panel allows you to configure options related to
\i{host key management}.
Host keys are used to prove the server's identity, and assure you that
@@ -2411,8 +2526,8 @@ the server is not being spoofed (either by a man-in-the-middle attack
or by completely replacing it on the network). See \k{gs-hostkey} for
a basic introduction to host keys.
-This entire panel is only relevant to SSH protocol version 2; none of
-these settings affect SSH-1 at all.
+Much of this panel is only relevant to SSH protocol version 2; SSH-1
+only supports one type of host key.
\S{config-ssh-hostkey-order} \ii{Host key type} selection
@@ -2431,7 +2546,7 @@ larger elliptic curve with a 448-bit instead of 255-bit modulus (so it
has a higher security level than Ed25519).
\b \q{ECDSA}: \i{elliptic curve} \i{DSA} using one of the
-NIST-standardised elliptic curves.
+\i{NIST}-standardised elliptic curves.
\b \q{DSA}: straightforward \i{DSA} using modular exponentiation.
@@ -2452,7 +2567,7 @@ to that for cipher selection (see \k{config-ssh-encryption}).
\S{config-ssh-prefer-known-hostkeys} Preferring known host keys
-By default, PuTTY will adjust the preference order for host key
+By default, PuTTY will adjust the preference order for SSH-2 host key
algorithms so that any host keys it already knows are moved to the top
of the list.
@@ -2527,6 +2642,146 @@ neither read \e{nor written}, unless you explicitly do so.
If the box is empty (as it usually is), then PuTTY's automated host
key management will work as normal.
+\S{config-ssh-kex-cert} Configuring PuTTY to accept host \i{certificates}
+
+In some environments, the SSH host keys for a lot of servers will all
+be signed in turn by a central \q{certification authority} (\q{CA} for
+short). This simplifies host key configuration for users, because if
+they configure their SSH client to accept host keys certified by that
+CA, then they don't need to individually confirm each host key the
+first time they connect to that server.
+
+In order to do this, press the \q{Configure host CAs} button in the
+\q{Host keys} configuration panel. This will launch a secondary
+configuration dialog box where you can configure what CAs PuTTY will
+accept signatures from.
+
+\s{Note that this configuration is common to all saved sessions}.
+Everything in the main PuTTY configuration is specific to one saved
+session, and you can prepare a separate session with all the
+configuration different. But there's only one copy of the host CA
+configuration, and it applies to all sessions PuTTY runs, whether
+saved or not.
+
+(Otherwise, it would be useless \dash configuring a CA by hand for
+each new host wouldn't be any more convenient than pressing the
+\q{confirm} button for each new host's host key.)
+
+To set up a new CA using this config box:
+
+First, load the CA's public key from a file, or paste it directly into
+the \q{Public key of certification authority} edit box. If your
+organisation signs its host keys in this way, they will publish the
+public key of their CA so that SSH users can include it in their
+configuration.
+
+Next, in the \q{Valid hosts this key is trusted to certify} box,
+configure at least one hostname wildcard to say what servers PuTTY
+should trust this CA to speak for. For example, suppose you work for
+Example Corporation (\cw{example.com}), and the Example Corporation IT
+department has advertised a CA that signs all the Example internal
+machines' host keys. Then probably you want to trust that CA to sign
+host keys for machines in the domain \cw{example.com}, but not for
+anything else. So you might enter \cq{*.example.com} into the \q{Valid
+hosts} box.
+
+\s{It's important to limit what the CA key is allowed to sign}. Don't
+just enter \cq{*} in that box! If you do that, you're saying that
+Example Corporation IT department is authorised to sign a host key for
+\e{anything at all} you might decide to connect to \dash even if
+you're connecting out of the company network to a machine somewhere
+else, such as your own personal server. So that configuration would
+enable the Example IT department to act as a \q{man-in-the-middle}
+between your PuTTY process and your server, and listen in to your
+communications \dash exactly the thing SSH is supposed to avoid.
+
+So, if the CA was provided to you by the sysadmins responsible for
+\cw{example.com} (or whatever), make sure PuTTY will \e{only} trust it
+for machines in the \cw{example.com} domain.
+
+For the full syntax of the \q{Valid hosts} expression, see
+\k{config-ssh-cert-valid-expr}.
+
+Finally, choose an identifying name for this CA; enter that name in
+the \q{Name for this CA} edit box at the top of the window, and press
+\q{Save} to record the CA in your configuration. The name you chose
+will appear in the list of saved CAs to the left of the \q{Save}
+button.
+
+The identifying name can be anything you like. It's there so that if
+you store multiple certificates you can tell which is which later when
+you want to edit or delete them. It also appears in the PuTTY Event
+Log when a server presents a certificate signed by that CA.
+
+To reload an existing CA configuration, select it in the list box and
+press \q{Load}. Then you can make changes, and save it again.
+
+To remove a CA from your configuration completely, select it in the
+list and press \q{Delete}.
+
+\S2{config-ssh-cert-valid-expr} Expressions you can enter in \q{Valid
+hosts}
+
+The simplest thing you can enter in the \q{Valid hosts this key is
+trusted to certify} edit box is just a hostname wildcard such as
+\cq{*.example.com}. This matches any host in any subdomain, so
+both \cq{ssh.example.com} and \cq{login.dept.example.com} would
+match, but \cq{prod.example.net} would not.
+
+But you can also enter multiple host name wildcards, and port number
+ranges, and make complicated Boolean expressions out of them using the
+operators \cq{&&} for \q{and}, \cq{||} for \q{or}, \cq{!} for \q{not},
+and parentheses.
+
+For example, here are some other things you could enter.
+
+\b \cq{*.foo.example.com || *.bar.example.com}. This means the CA is
+trusted to sign the host key for a connection if the host name matches
+\q{*.foo.example.com} \e{or} it matches \q{*.bar.example.com}. In
+other words, the CA has authority over those two particular subdomains
+of \cw{example.com}, but not for anything else, like
+\cw{www.example.com}.
+
+\b \cq{*.example.com && ! *.extrasecure.example.com}. This means the
+CA is trusted to sign the host key for a connection if the host name
+matches \q{*.example.com} \e{but does not} match
+\q{*.extrasecure.example.com}. (Imagine if there was one top-secret
+set of servers in your company that the main IT department didn't have
+security clearance to administer.)
+
+\b \cq{*.example.com && port:22}. This means the CA is trusted to sign
+the host key for a connection if the host name matches
+\q{*.example.com} \e{and} the port number is 22. SSH servers running
+on other ports would not be covered.
+
+\b \cq{(*.foo.example.com || *.bar.example.com) && port:0-1023}. This
+matches two subdomains of \cw{example.com}, as before, but \e{also}
+restricts the port number to the range 0-1023.
+
+A certificate configuration expression consists of one or more
+individual requirements which can each be a hostname wildcard, a
+single port number, or a port number range, combined together with
+these Boolean operators.
+
+Unlike other languages such as C, there is no implied priority between
+\cq{&&} and \cq{||}. If you write \cq{A && B || C} (where \cw{A},
+\cw{B} and \cw{C} are some particular requirements), then PuTTY will
+report a syntax error, because you haven't said which of the \cq{&&}
+and \cq{||} takes priority tightly. You will have to write either
+\cq{(A && B) || C}, meaning \q{both of \cw{A} and \cw{B}, or
+alternatively just \cw{C}}, or \cq{A && (B || C)} (\q{\cw{A}, and also
+at least one of \cw{B} and \cw{C}}), to make it clear.
+
+\S2{config-ssh-cert-rsa-hash} RSA signature types in certificates
+
+RSA keys can be used to generate signatures with a choice of secure
+hash function. Typically, any version of OpenSSH new enough to support
+certificates at all will also be new enough to avoid using SHA-1, so
+the default settings of accepting the more modern SHA-256 and SHA-512
+should be suitable for nearly all cases. For completeness, however,
+you can configure which types of RSA signature PuTTY will accept in a
+certificate from a CA using an RSA key.
+
\H{config-ssh-encryption} The Cipher panel
PuTTY supports a variety of different \i{encryption algorithm}s, and
@@ -2541,7 +2796,8 @@ PuTTY currently supports the following algorithms:
\b \i{ChaCha20-Poly1305}, a combined cipher and \i{MAC} (SSH-2 only)
-\b \i{AES} (Rijndael) - 256, 192, or 128-bit SDCTR or CBC (SSH-2 only)
+\b \i{AES} (Rijndael) - 256, 192, or 128-bit SDCTR or CBC, or
+256 or 128-bit GCM (SSH-2 only)
\b \i{Arcfour} (RC4) - 256 or 128-bit stream cipher (SSH-2 only)
@@ -2745,6 +3001,12 @@ username more than once, in case the server complains. If you know
your server can cope with it, you can enable the \q{Allow attempted
changes of username} option to modify PuTTY's behaviour.
+\H{config-ssh-auth-creds} The Credentials panel
+
+This subpane of the Auth panel contains configuration options that
+specify actual \e{credentials} to present to the server: key files and
+certificates.
+
\S{config-ssh-privkey} \q{\ii{Private key} file for authentication}
This box is where you enter the name of your private key file if you
@@ -2766,6 +3028,54 @@ in this case (in RFC 4716 or OpenSSH format), as that's sufficient to
identify the key to Pageant, but of course if Pageant isn't present
PuTTY can't fall back to using this file itself.
+\S{config-ssh-cert} \q{\ii{Certificate} to use with the private key}
+
+In some environments, user authentication keys can be signed in turn
+by a \q{certifying authority} (\q{CA} for short), and user accounts on
+an SSH server can be configured to automatically trust any key that's
+certified by the right signature.
+
+This can be a convenient setup if you have a very large number of
+servers. When you change your key pair, you might otherwise have to
+edit the \cw{authorized_keys} file on every server individually, to
+make them all accept the new key. But if instead you configure all
+those servers \e{once} to accept keys signed as yours by a CA, then
+when you change your public key, all you have to do is to get the new
+key certified by the same CA as before, and then all your servers will
+automatically accept it without needing individual reconfiguration.
+
+One way to use a certificate is to incorporate it into your private
+key file. \K{puttygen-cert} explains how to do that using PuTTYgen.
+But another approach is to tell PuTTY itself where to find the public
+certificate file, and then it will automatically present that
+certificate when authenticating with the corresponding private key.
+
+To do this, enter the pathname of the certificate file into the
+\q{Certificate to use with the private key} file selector.
+
+When this setting is configured, PuTTY will honour it no matter
+whether the private key is found in a file, or loaded into Pageant.
+
+\S{config-ssh-authplugin} \q{\ii{Plugin} to provide authentication responses}
+
+An SSH server can use the \q{keyboard-interactive} protocol to present
+a series of arbitrary questions and answers. Sometimes this is used
+for ordinary passwords, but sometimes the server will use the same
+mechanism for something more complicated, such as a one-time password
+system.
+
+Some of these systems can be automated. For this purpose, PuTTY allows
+you to provide a separate program to act as a \q{plugin} which will
+take over the authentication and send answers to the questions on your
+behalf.
+
+If you have been provided with a plugin of this type, you can
+configure it here, by entering a full command line in the \q{Plugin
+command to run} box.
+
+(If you want to \e{write} a plugin of this type, see \k{authplugin}
+for the full specification of how the plugin is expected to behave.)
+
\H{config-ssh-auth-gssapi} The \i{GSSAPI} panel
The \q{GSSAPI} subpanel of the \q{Auth} panel controls the use of
@@ -3206,7 +3516,10 @@ three states:
\b \q{On}: PuTTY will assume the server \e{does} have the bug.
\b \q{Auto}: PuTTY will use the server's version number announcement
-to try to guess whether or not the server has the bug.
+to try to guess whether or not the server has the bug. (This option is
+not available for bugs that \e{cannot} be detected from the server
+version, e.g. because they must be acted on before the server version
+is known.)
\S{config-ssh-bug-ignore2} \q{Chokes on SSH-2 \i{ignore message}s}
@@ -3299,6 +3612,55 @@ send an over-sized packet. If this bug is enabled when talking to a
correct server, the session will work correctly, but download
performance will be less than it could be.
+\S{config-ssh-bug-dropstart} \q{Discards data sent before its greeting}
+
+Just occasionally, an SSH connection can be established over some
+channel that will accidentally discard outgoing data very early in the
+connection.
+
+This is not typically seen as a bug in an actual SSH server, but it
+can sometimes occur in situations involving a complicated proxy
+process. An example is
+\W{https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991958}{Debian
+bug #991958}, in which a connection going over the console of a User
+Mode Linux kernel can lose outgoing data before the kernel has fully
+booted.
+
+You can work around this problem by manually enabling this bug flag,
+which will cause PuTTY to wait to send its initial SSH greeting until
+after it sees the greeting from the server.
+
+Note that this bug flag can never be automatically detected, since
+auto-detection relies on the version string in the server's greeting,
+and PuTTY has to decide whether to expect this bug \e{before} it sees
+the server's greeting. So this is a manual workaround only.
+
+\S{config-ssh-bug-filter-kexinit} \q{Chokes on PuTTY's full \cw{KEXINIT}}
+
+At the start of an SSH connection, the client and server exchange long
+messages of type \cw{SSH_MSG_KEXINIT}, containing lists of all the
+cryptographic algorithms they're prepared to use. This is used to
+negotiate a set of algorithms that both ends can speak.
+
+Occasionally, a badly written server might have a length limit on the
+list it's prepared to receive, and refuse to make a connection simply
+because PuTTY is giving it too many choices.
+
+A workaround is to enable this flag, which will make PuTTY wait to
+send \cw{KEXINIT} until after it receives the one from the server, and
+then filter its own \cw{KEXINIT} to leave out any algorithm the server
+doesn't also announce support for. This will generally make PuTTY's
+\cw{KEXINIT} at most the size of the server's, and will otherwise make
+no difference to the algorithm negotiation.
+
+This flag is a minor violation of the SSH protocol, because both sides
+are supposed to send \cw{KEXINIT} proactively. It still works provided
+\e{one} side sends its \cw{KEXINIT} without waiting, but if both
+client and server waited for the other one to speak first, the
+connection would deadlock. We don't know of any servers that do this,
+but if there is one, then this flag will make PuTTY unable to speak to
+them at all.
+
\S{config-ssh-bug-sig} \q{Requires padding on SSH-2 \i{RSA} \i{signatures}}
Versions below 3.3 of \i{OpenSSH} require SSH-2 RSA signatures to be
diff --git a/DOC/ERRORS.BUT b/DOC/ERRORS.BUT
index 36a42d94..a35a7256 100644
--- a/DOC/ERRORS.BUT
+++ b/DOC/ERRORS.BUT
@@ -10,8 +10,8 @@ self-explanatory. If you get an error message which is not listed in
this chapter and which you don't understand, report it to us as a
bug (see \k{feedback}) and we will add documentation for it.
-\H{errors-hostkey-absent} \q{The server's host key is not cached in
-the registry}
+\H{errors-hostkey-absent} \q{The host key is not cached for this
+server}
This error message occurs when PuTTY connects to a new SSH server.
Every server identifies itself by means of a host key; once PuTTY
@@ -35,10 +35,13 @@ See \k{gs-hostkey} for more information on host keys.
\H{errors-hostkey-wrong} \q{WARNING - POTENTIAL SECURITY BREACH!}
This message, followed by \q{The server's host key does not match
-the one PuTTY has cached in the registry}, means that PuTTY has
+the one PuTTY has cached for this server}, means that PuTTY has
connected to the SSH server before, knows what its host key
\e{should} be, but has found a different one.
+(If the message instead talks about a \q{certified host key}, see
+instead \k{errors-cert-mismatch}.)
+
This may mean that a malicious attacker has replaced your server
with a different one, or has redirected your network connection to
their own machine. On the other hand, it may simply mean that the
@@ -52,6 +55,41 @@ in the same way as you would if it was new.
See \k{gs-hostkey} for more information on host keys.
+\H{errors-cert-mismatch} \q{This server presented a certified host key
+which was signed by a different certification authority ...}
+
+If you've configured PuTTY to trust at least one
+\I{certificate}certification authority for signing host keys (see
+\k{config-ssh-kex-cert}), then it will ask the SSH server to send it
+any available certified host keys. If the server sends back a
+certified key signed by a \e{different} certification authority, PuTTY
+will present this variant of the host key prompt, preceded by
+\q{WARNING - POTENTIAL SECURITY BREACH!}
+
+One reason why this can happen is a deliberate attack. Just like an
+ordinary man-in-the-middle attack which substitutes a wrong host key,
+a particularly ambitious attacker might substitute an entire wrong
+certification authority, and hope that you connect anyway.
+
+But it's also possible in some situations that this error might arise
+legitimately. For example, if your organisation's IT department has
+just rolled out a new CA key which you haven't yet entered in PuTTY's
+configuration, or if your CA configuration involves two overlapping
+domains, or something similar.
+
+So, unfortunately, you'll have to work out what to do about it
+yourself: make an exception for this specific case, or abandon this
+connection and install a new CA key before trying again (if you're
+really sure you trust the CA), or edit your configuration in some
+other way, or just stop trying to use this server.
+
+If you're convinced that this particular server is legitimate even
+though the CA is not one you trust, PuTTY will let you cache the
+certified host key, treating it in the same way as an uncertified one.
+Then that particular certificate will be accepted for future
+connections to this specific server, even though other certificates
+signed by the same CA will still be rejected.
+
\H{errors-ssh-protocol} \q{SSH protocol version 2 required by our
configuration but remote only provides (old, insecure) SSH-1}
diff --git a/DOC/FAQ.BUT b/DOC/FAQ.BUT
index 474b984a..9a8deea2 100644
--- a/DOC/FAQ.BUT
+++ b/DOC/FAQ.BUT
@@ -150,7 +150,7 @@ data at the server end; it's your guarantee that it hasn't been
removed and replaced somewhere on the way. Host key checking makes
the attacker's job \e{astronomically} hard, compared to packet
sniffing, and even compared to subverting a router. Instead of
-applying a little intelligence and keeping an eye on Bugtraq, the
+applying a little intelligence and keeping an eye on oss-security, the
attacker must now perform a brute-force attack against at least one
military-strength cipher. That insignificant host key prompt really
does make \e{that} much difference.
@@ -220,7 +220,7 @@ Currently, release versions of PuTTY tools only run on Windows
systems and Unix.
As of 0.68, the supplied PuTTY executables run on versions of Windows
-from XP onwards, up to and including Windows 10; and we know of no
+from XP onwards, up to and including Windows 11; and we know of no
reason why PuTTY should not continue to work on future versions of
Windows. We provide 32-bit and 64-bit Windows executables for the
common x86 processor family; see \k{faq-32bit-64bit} for discussion
@@ -250,8 +250,7 @@ There are Unix ports of most of the traditional PuTTY tools, and also
one entirely new application.
If you look at the source release, you should find a \c{unix}
-subdirectory. There are a couple of ways of building it,
-including the usual \c{configure}/\c{make}; see the file \c{README}
+subdirectory. You need \c{cmake} to build it; see the file \c{README}
in the source distribution. This should build you:
\b Unix ports of PuTTY, Plink, PSCP, and PSFTP, which work pretty much
@@ -325,7 +324,8 @@ unfinished.
If any OS X and/or GTK programming experts are keen to have a finished
version of this, we urge them to help out with some of the remaining
-problems! See the TODO list in \c{unix/gtkapp.c} in the source code.
+problems! See the TODO list in \c{unix/main-gtk-application.c} in the
+source code.
\S{faq-epoc}{Question} Will there be a port to EPOC?
@@ -584,7 +584,7 @@ You can also paste by pressing Shift-Ins.
keys, proxying, cipher selection, etc.) in PSCP, PSFTP and Plink?
Most major features (e.g., public keys, port forwarding) are available
-through command line options. See the documentation.
+through command line options. See \k{using-general-opts}.
Not all features are accessible from the command line yet, although
we'd like to fix this. In the meantime, you can use most of
@@ -606,9 +606,16 @@ To use PSCP properly, run it from a Command Prompt window. See
\S{faq-pscp-spaces}{Question} \I{spaces in filenames}How do I use
PSCP to copy a file whose name has spaces in?
-If PSCP is using the traditional SCP protocol, this is confusing. If
-you're specifying a file at the local end, you just use one set of
-quotes as you would normally do:
+If PSCP is using the newer SFTP protocol (which is usual with most
+modern servers), this is straightforward; all filenames with spaces
+in are specified using a single pair of quotes in the obvious way:
+
+\c pscp "local file" user@host:
+\c pscp user@host:"remote file" .
+
+However, if PSCP is using the older SCP protocol for some reason,
+things are more confusing. If you're specifying a file at the local
+end, you just use one set of quotes as you would normally do:
\c pscp "local filename with spaces" user@host:
\c pscp user@host:myfile "local filename with spaces"
@@ -632,13 +639,6 @@ Instead, you need to specify the local file name in full:
\c c:\>pscp user@host:"\"oo er\"" "oo er"
-If PSCP is using the newer SFTP protocol, none of this is a problem,
-and all filenames with spaces in are specified using a single pair
-of quotes in the obvious way:
-
-\c pscp "local file" user@host:
-\c pscp user@host:"remote file" .
-
\S{faq-32bit-64bit}{Question} Should I run the 32-bit or the
64-bit version?
@@ -1152,6 +1152,22 @@ running, but it doesn't stop the process's memory as a whole from
being swapped completely out to disk when the process is long-term
inactive. And Pageant spends most of its time inactive.
+\S{faq-windowsstore}{Question} Is the version of PuTTY in the
+\i{Microsoft Store} legit?
+
+The free-of-charge \q{PuTTY} application at
+\W{https://apps.microsoft.com/store/detail/putty/XPFNZKSKLBP7RJ}{this link}
+is published and maintained by us. The copy there is the latest
+release, usually updated within a few days of us publishing it on our
+own website.
+
+There have been other copies of PuTTY on the store, some looking quite
+similar, and some charging money. Those were uploaded by other people,
+and we can't guarantee anything about them.
+
+The first version we published to the Microsoft Store was 0.76 (some
+time after its initial release on our website).
+
\H{faq-admin} Administrative questions
\S{faq-putty-org}{Question} Is \cw{putty.org} your website?
@@ -1286,7 +1302,7 @@ Small donations (tens of dollars or tens of euros) will probably be
spent on beer or curry, which helps motivate our volunteer team to
continue doing this for the world. Larger donations will be spent on
something that actually helps development, if we can find anything
-(perhaps new hardware, or a copy of Windows XP), but if we can't
+(perhaps new hardware, or a new version of Windows), but if we can't
find anything then we'll just distribute the money among the
developers. If you want to be sure your donation is going towards
something worthwhile, ask us first. If you don't like these terms,
diff --git a/DOC/GS.BUT b/DOC/GS.BUT
index e6a84923..8b915dbf 100644
--- a/DOC/GS.BUT
+++ b/DOC/GS.BUT
@@ -50,8 +50,9 @@ section.
If you are using SSH to connect to a server for the first time, you
will probably see a message looking something like this:
-\c The server's host key is not cached in the registry. You have no
-\c guarantee that the server is the computer you think it is.
+\c The host key is not cached for this server:
+\c ssh.example.com (port 22)
+\c You have no guarantee that the server is the computer you think it is.
\c The server's ssh-ed25519 key fingerprint is:
\c ssh-ed25519 255 SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w
\c If you trust this host, press "Accept" to add the key to PuTTY's
@@ -79,10 +80,10 @@ PuTTY \I{host key cache}records the host key for each server you
connect to, in the Windows \i{Registry}. Every time you connect to a
server, it checks that the host key presented by the server is the
same host key as it was the last time you connected. If it is not,
-you will see a warning, and you will have the chance to abandon your
-connection before you type any private information (such as a
-password) into it. (See \k{errors-hostkey-wrong} for what that looks
-like.)
+you will see a stronger warning, and you will have the chance to
+abandon your connection before you type any private information (such
+as a password) into it. (See \k{errors-hostkey-wrong} for what that
+looks like.)
However, when you connect to a server you have not connected to
before, PuTTY has no way of telling whether the host key is the
diff --git a/DOC/INDEX.BUT b/DOC/INDEX.BUT
index f7e7145a..187f5a1e 100644
--- a/DOC/INDEX.BUT
+++ b/DOC/INDEX.BUT
@@ -245,6 +245,7 @@ saved sessions from
\IM{-m} \c{-m} command-line option
\IM{-P-upper} \c{-P} command-line option
\IM{-pw} \c{-pw} command-line option
+\IM{-pwfile} \c{-pwfile} command-line option
\IM{-A-upper} \c{-A} command-line option
\IM{-a} \c{-a} command-line option
\IM{-X-upper} \c{-X} command-line option
@@ -607,8 +608,11 @@ saved sessions from
\IM{proxy authentication} proxy authentication
\IM{proxy authentication} authentication, to proxy
-\IM{HTTP basic} HTTP \q{basic} authentication
-\IM{HTTP basic} \q{basic} authentication (HTTP)
+\IM{HTTP Basic} HTTP Basic authentication
+\IM{HTTP Basic} \q{basic} authentication (HTTP)
+
+\IM{HTTP Digest} HTTP Digest authentication
+\IM{HTTP Digest} \q{digest} authentication (HTTP)
\IM{plaintext password} plain text password
\IM{plaintext password} password, plain text
@@ -684,6 +688,16 @@ saved sessions from
\IM{group exchange} Diffie-Hellman group exchange
\IM{group exchange} group exchange, Diffie-Hellman
+\IM{ECDH} \q{ECDH} (elliptic-curve Diffie-Hellman)
+\IM{ECDH} elliptic-curve Diffie-Hellman key exchange
+\IM{ECDH} key exchange, elliptic-curve Diffie-Hellman
+\IM{ECDH} Diffie-Hellman key exchange, with elliptic curves
+
+\IM{Streamlined NTRU Prime} Streamlined NTRU Prime
+\IM{Streamlined NTRU Prime} NTRU Prime
+
+\IM{quantum attacks} quantum attacks, resistance to
+
\IM{repeat key exchange} repeat key exchange
\IM{repeat key exchange} key exchange, repeat
@@ -815,6 +829,12 @@ saved sessions from
\IM{DSA} DSA
\IM{DSA} Digital Signature Standard
+\IM{ECDSA} ECDSA
+\IM{ECDSA} elliptic-curve DSA
+
+\IM{NIST} NIST-standardised elliptic curves
+\IM{NIST} elliptic curves, NIST-standardised
+
\IM{EdDSA} EdDSA
\IM{EdDSA} Edwards-curve DSA
@@ -863,6 +883,11 @@ saved sessions from
\IM{authentication agent} agent, authentication
\IM{-c-pageant} \c{-c} Pageant command-line option
+\IM{--keylist} \c{--keylist} Pageant command-line option
+\IM{--openssh-config} \c{--openssh-config} Pageant command-line option
+
+\IM{Windows OpenSSH} Windows OpenSSH
+\IM{Windows OpenSSH} OpenSSH, on Windows
\IM{FAQ} FAQ
\IM{FAQ} Frequently Asked Questions
@@ -930,3 +955,14 @@ saved sessions from
\IM{system tray} system tray, Windows
\IM{system tray} notification area, Windows (aka system tray)
\IM{system tray} taskbar notification area, Windows (aka system tray)
+
+\IM{shifted arrow keys} arrow keys, shifted
+\IM{shifted arrow keys} shifted arrow keys
+
+\IM{certificate}{certificates} certificates, SSH
+\IM{certificate}{certificates} SSH certificates
+\IM{certificate}{certificates} OpenSSH certificates
+\IM{certificate}{certificates} CA (certification authority)
+
+\IM{Microsoft Store} Microsoft Store
+\IM{Microsoft Store} Windows Store
diff --git a/DOC/MAKEFILE b/DOC/MAKEFILE
deleted file mode 100644
index 5ff25d54..00000000
--- a/DOC/MAKEFILE
+++ /dev/null
@@ -1,80 +0,0 @@
-all: man index.html
-
-# Decide on the versionid policy.
-#
-# If the user has passed in $(VERSION) on the command line (`make
-# VERSION="Release 0.56"'), we use that as an explicit version string.
-# Otherwise, we use `svnversion' to examine the checked-out
-# documentation source, and if that returns a single revision number
-# then we invent a version string reflecting just that number. Failing
-# _that_, we resort to versionids.but which gives 'version
-# unavailable'.
-#
-# So here, we define VERSION using svnversion if it isn't already
-# defined ...
-ifndef VERSION
-SVNVERSION=$(shell test -d .svn && svnversion .)
-BADCHARS=$(findstring :,$(SVNVERSION))$(findstring S,$(SVNVERSION))
-ifeq ($(BADCHARS),)
-ifneq ($(SVNVERSION),)
-ifneq ($(SVNVERSION),exported)
-VERSION=Built from revision $(patsubst M,,$(SVNVERSION))
-endif
-endif
-endif
-endif
-# ... and now, we condition our build behaviour on whether or not
-# VERSION _is_ defined.
-ifdef VERSION
-VERSIONIDS=vstr
-vstr.but: FORCE
- printf '\\versionid $(VERSION)\n' > vstr.but
-FORCE:;
-else
-VERSIONIDS=vids
-endif
-
-CHAPTERS := $(SITE) copy blurb intro gs using config pscp psftp plink
-CHAPTERS += pubkey pageant errors faq feedback pubkeyfmt licence udp
-CHAPTERS += pgpkeys sshnames
-CHAPTERS += index $(VERSIONIDS)
-
-INPUTS = $(patsubst %,%.but,$(CHAPTERS))
-
-# This is temporary. Hack it locally or something.
-HALIBUT = halibut
-
-index.html: $(INPUTS)
- $(HALIBUT) --text --html --chm $(INPUTS)
-
-# During formal builds it's useful to be able to build this one alone.
-putty.chm: $(INPUTS)
- $(HALIBUT) --chm $(INPUTS)
-
-# We don't ship this any more.
-putty.hlp: $(INPUTS)
- $(HALIBUT) --winhelp $(INPUTS)
-
-putty.info: $(INPUTS)
- $(HALIBUT) --info $(INPUTS)
-
-MKMAN = $(HALIBUT) --man=$@ mancfg.but $<
-MANPAGES = putty.1 puttygen.1 plink.1 pscp.1 psftp.1 puttytel.1 pterm.1 \
- pageant.1 psocks.1 psusan.1
-man: $(MANPAGES)
-
-putty.1: man-putty.but mancfg.but; $(MKMAN)
-puttygen.1: man-puttygen.but mancfg.but; $(MKMAN)
-plink.1: man-plink.but mancfg.but; $(MKMAN)
-pscp.1: man-pscp.but mancfg.but; $(MKMAN)
-psftp.1: man-psftp.but mancfg.but; $(MKMAN)
-puttytel.1: man-puttytel.but mancfg.but; $(MKMAN)
-pterm.1: man-pterm.but mancfg.but; $(MKMAN)
-pageant.1: man-pageant.but mancfg.but; $(MKMAN)
-psocks.1: man-psocks.but mancfg.but; $(MKMAN)
-psusan.1: man-psusan.but mancfg.but; $(MKMAN)
-
-mostlyclean:
- rm -f *.html *.txt *.hlp *.cnt *.1 *.info vstr.but *.hh[pck]
-clean: mostlyclean
- rm -f *.chm
diff --git a/DOC/MAN-PSCP.BUT b/DOC/MAN-PSCP.BUT
index 60ce4f5e..544d3a40 100644
--- a/DOC/MAN-PSCP.BUT
+++ b/DOC/MAN-PSCP.BUT
@@ -101,11 +101,16 @@ channel from the server, to prevent remote processes sending confusing
escape sequences. This option forces the standard error channel to not be
filtered.
+\dt \cw{-pwfile} \e{filename}
+
+\dd Open the specified file, and use the first line of text read from
+it as the remote password.
+
\dt \cw{-pw} \e{password}
\dd Set remote password to \e{password}. \e{CAUTION:} this will likely
make the password visible to other users of the local machine (via
-commands such as \q{\c{w}}).
+commands such as \q{\c{ps}} or \q{\c{w}}). Use \cw{-pwfile} instead.
\dt \cw{-1}
@@ -118,9 +123,9 @@ commands such as \q{\c{w}}).
\dt \cw{-ssh-connection}
\dd Force use of the \q{bare \cw{ssh-connection}} protocol. This is
-only likely to be useful when connecting to a \e{psusan(1)} server,
-most likely with an absolute path to a Unix-domain socket in place
-of \e{host}.
+only likely to be useful when connecting to a \cw{psusan}(\e{1})
+server, most likely with an absolute path to a Unix-domain socket in
+place of \e{host}.
\dt \cw{-ssh}
diff --git a/DOC/PAGEANT.BUT b/DOC/PAGEANT.BUT
index 8abb5cdf..de6d4cb8 100644
--- a/DOC/PAGEANT.BUT
+++ b/DOC/PAGEANT.BUT
@@ -64,21 +64,24 @@ The large list box in the Pageant main window lists the private keys
that are currently loaded into Pageant. The list might look
something like this:
-\c ssh-ed25519 SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w
-\c ssh-rsa 2048 SHA256:8DFtyHm3kQihgy52nzX96qMcEVOq7/yJmmwQQhBWYFg
+\c Ed25519 SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w
+\c RSA 2048 SHA256:8DFtyHm3kQihgy52nzX96qMcEVOq7/yJmmwQQhBWYFg
For each key, the list box will tell you:
\b The type of the key. Currently, this can be
-\c{ssh-rsa} (an RSA key for use with the SSH-2 protocol),
-\c{ssh-dss} (a DSA key for use with the SSH-2 protocol),
-\c{ecdsa-sha2-*} (an ECDSA key for use with the SSH-2 protocol),
-\c{ssh-ed25519} (an Ed25519 key for use with the SSH-2 protocol),
-\c{ssh-ed448} (an Ed448 key for use with the SSH-2 protocol),
-or \c{ssh1} (an RSA key for use with the old SSH-1 protocol).
+\q{RSA} (an RSA key for use with the SSH-2 protocol),
+\q{DSA} (a DSA key for use with the SSH-2 protocol),
+\q{\i{NIST}} (an ECDSA key for use with the SSH-2 protocol),
+\q{Ed25519} (an Ed25519 key for use with the SSH-2 protocol),
+\q{Ed448} (an Ed448 key for use with the SSH-2 protocol),
+or \q{SSH-1} (an RSA key for use with the old SSH-1 protocol).
+(If the key has an associated certificate, this is shown here with a
+\q{cert} suffix.)
\b The size (in bits) of the key, for key types that come in different
-sizes.
+sizes. (For ECDSA \q{NIST} keys, this is indicated as \q{p256} or
+\q{p384} or \q{p521}.)
\b The \I{key fingerprint}fingerprint for the public key. This should be
the same fingerprint given by PuTTYgen, and (hopefully) also the same
@@ -86,10 +89,20 @@ fingerprint shown by remote utilities such as \i\c{ssh-keygen} when
applied to your \c{authorized_keys} file.
\lcont{
-By default this is shown in the \q{SHA256} format. You can change to the
-older \q{MD5} format (which looks like \c{aa:bb:cc:...}) with the
-\q{Fingerprint type} drop-down, but bear in mind that this format is
-less secure and should be avoided for comparison purposes where possible.
+For SSH-2 keys, by default this is shown in the \q{SHA256} format. You
+can change to the older \q{MD5} format (which looks like \c{aa:bb:cc:...})
+with the \q{Fingerprint type} drop-down, but bear in mind that this
+format is less secure and should be avoided for comparison purposes
+where possible.
+
+If some of the keys loaded into Pageant have certificates attached,
+then Pageant will default to showing the fingerprint of the underlying
+key. This way, a certified and uncertified version of the same key
+will have the same fingerprint, so you can see that they match. You
+can instead use the \q{Fingerprint type} drop-down to ask for a
+different fingerprint to be shown for certified keys, which includes
+the certificate as part of the fingerprinted data. That way you can
+tell two certificates apart.
}
\b The comment attached to the key.
@@ -170,6 +183,92 @@ by the command, like this:
\c C:\PuTTY\pageant.exe d:\main.ppk -c C:\PuTTY\putty.exe
+\S{pageant-cmdline-openssh} Integrating with \i{Windows OpenSSH}
+
+Windows's own port of OpenSSH uses the same mechanism as Pageant to
+talk to its SSH agent (Windows named pipes). This means that Windows
+OpenSSH can talk directly to Pageant, if it knows where to find
+Pageant's named pipe.
+
+When Pageant starts up, it can optionally write out a file containing
+an OpenSSH configuration directive that tells the Windows \c{ssh.exe}
+where to find Pageant. If you include this file from your Windows SSH
+configuration, then \c{ssh.exe} should automatically use Pageant as
+its agent, so that you can keep your keys in one place and have both
+SSH clients able to use them.
+
+The option is \i\c{--openssh-config}, and you follow it with a filename.
+
+To refer to this file from your main OpenSSH configuration, you can
+use the \cq{Include} directive. For example, you might run Pageant
+like this (with your own username substituted, of course):
+
+\c pageant --openssh-config C:\Users\Simon\.ssh\pageant.conf
+
+and then add a directive like this to your main \cq{.ssh\\config} file
+(assuming that lives in the same directory that you just put
+\cw{pageant.conf}):
+
+\c Include pageant.conf
+
+\s{Note}: this technique only works with \e{Windows's} port of
+OpenSSH, which lives at \cw{C:\\Windows\\System32\\OpenSSH\\ssh.exe}
+if you have it installed. (If not, it can be installed as a Windows
+optional feature, e.g., via Settings > Apps & features > Optional
+features > Add a feature > OpenSSH Client.)
+
+There are other versions of OpenSSH for Windows, notably the one that
+comes with Windows \cw{git}. Those will likely not work with the same
+configuration, because they tend to depend on Unix emulation layers
+like MinGW or MSys, so they won't speak Windows native pathname syntax
+or understand named pipes. The above instructions will only work with
+Windows's own version of OpenSSH.
+
+So, if you want to use Windows \cw{git} with an SSH key held in
+Pageant, you'll have to set the environment variable \cw{GIT_SSH}, to
+point at a different program. You could point it at
+\cw{c:\\Windows\\System32\\OpenSSH\\ssh.exe} once you've done this
+setup \dash but it's just as easy to point it at Plink!
+
+\S{pageant-cmdline-unix} Unix-domain sockets: integrating with WSL 1
+
+Pageant can listen on the WinSock implementation of \q{Unix-domain
+sockets}. These interoperate with the Unix-domain sockets found in the
+original Windows Subsystem for Linux (now known as WSL 1). So if you
+ask Pageant to listen on one of these, then your WSL 1 processes can
+talk directly to Pageant.
+
+To configure this, run Pageant with the option \c{--unix}, followed
+with a pathname. Then, in WSL 1, set the environment variable
+\cw{SSH_AUTH_SOCK} to point at the WSL translation of that pathname.
+
+For example, you might run
+
+\c pageant --unix C:\Users\Simon\.ssh\agent.sock
+
+and in WSL 1, set the environment variable
+
+\c SSH_AUTH_SOCK=/mnt/c/Users/Simon/.ssh/agent.sock
+
+Alternatively, you can add a line to your \cw{.ssh/config} file inside
+WSL that says
+
+\c IdentityAgent /mnt/c/Users/Simon/.ssh/agent.sock
+
+although doing it like that may mean that \cw{ssh-add} commands won't
+find the agent, even though \cw{ssh} itself will.
+
+\s{Security note}: Unix-domain sockets are protected against access by
+other users by the file protections on their containing directory. So
+if your Windows machine is multiuser, make sure you create the socket
+inside a directory that other users can't access at all. (In fact,
+that's a good idea on general principles.)
+
+\s{Compatibility note}: WSL 2 processes cannot talk to Pageant by this
+mechanism, because WSL 2's Unix-domain sockets are managed by a
+separate Linux kernel, and not by the same kernel that WinSock talks
+to.
+
\S{pageant-cmdline-keylist} Starting with the key list visible
Start Pageant with the \i\c{--keylist} option to show the main window
diff --git a/DOC/PGPKEYS.BUT b/DOC/PGPKEYS.BUT
index 8fab6153..7dc62f89 100644
--- a/DOC/PGPKEYS.BUT
+++ b/DOC/PGPKEYS.BUT
@@ -56,25 +56,25 @@ The current issue of those keys are available for download from the
PuTTY website, and are also available on PGP keyservers using the key
IDs listed below.
-\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2018.asc}{\s{Master Key} (2018)}
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2021.asc}{\s{Master Key} (2021)}
-\dd RSA, 4096-bit. Key ID: \cw{76BC7FE4EBFD2D9E}. Fingerprint:
-\cw{24E1\_B1C5\_75EA\_3C9F\_F752\_\_A922\_76BC\_7FE4\_EBFD\_2D9E}
+\dd RSA, 3072-bit. Key ID: \cw{DD4355EAAC1119DE}. Fingerprint:
+\cw{A872\_D42F\_1660\_890F\_0E05\_223E\_DD43\_55EA\_AC11\_19DE}
-\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2018.asc}{\s{Release Key} (2018)}
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2021.asc}{\s{Release Key} (2021)}
-\dd RSA, 3072-bit. Key ID: \cw{6289A25F4AE8DA82}. Fingerprint:
-\cw{E273\_94AC\_A3F9\_D904\_9522\_\_E054\_6289\_A25F\_4AE8\_DA82}
+\dd RSA, 3072-bit. Key ID: \cw{E4F83EA2AA4915EC}. Fingerprint:
+\cw{2CF6\_134B\_D3F7\_7A65\_88EB\_D668\_E4F8\_3EA2\_AA49\_15EC}
-\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2018.asc}{\s{Snapshot Key} (2018)}
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2021.asc}{\s{Snapshot Key} (2021)}
-\dd RSA, 3072-bit. Key ID: \cw{38BA7229B7588FD1}. Fingerprint:
-\cw{C92B\_52E9\_9AB6\_1DDA\_33DB\_\_2B7A\_38BA\_7229\_B758\_8FD1}
+\dd RSA, 3072-bit. Key ID: \cw{B43979F89F446CFD}. Fingerprint:
+\cw{1FD3\_BCAC\_E532\_FBE0\_6A8C\_09E2\_B439\_79F8\_9F44\_6CFD}
-\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2018.asc}{\s{Secure Contact Key} (2018)}
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2021.asc}{\s{Secure Contact Key} (2021)}
-\dd RSA, 3072-bit. Key ID: \cw{657D487977F95C98}. Fingerprint:
-\cw{A680\_0082\_2998\_6E46\_22CA\_\_0E43\_657D\_4879\_77F9\_5C98}
+\dd RSA, 3072-bit. Key ID: \cw{012C59D4211BD62A}. Fingerprint:
+\cw{E30F\_1354\_2A04\_BE0E\_56F0\_5801\_012C\_59D4\_211B\_D62A}
\H{pgpkeys-security} Security details
@@ -169,6 +169,28 @@ generated keys.
The details of all previous keys are given here.
+\s{Keys generated in the 2018 rollover}
+
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2018.asc}{\s{Master Key} (2018)}
+
+\dd RSA, 4096-bit. Key ID: \cw{76BC7FE4EBFD2D9E}. Fingerprint:
+\cw{24E1\_B1C5\_75EA\_3C9F\_F752\_\_A922\_76BC\_7FE4\_EBFD\_2D9E}
+
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2018.asc}{\s{Release Key} (2018)}
+
+\dd RSA, 3072-bit. Key ID: \cw{6289A25F4AE8DA82}. Fingerprint:
+\cw{E273\_94AC\_A3F9\_D904\_9522\_\_E054\_6289\_A25F\_4AE8\_DA82}
+
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2018.asc}{\s{Snapshot Key} (2018)}
+
+\dd RSA, 3072-bit. Key ID: \cw{38BA7229B7588FD1}. Fingerprint:
+\cw{C92B\_52E9\_9AB6\_1DDA\_33DB\_\_2B7A\_38BA\_7229\_B758\_8FD1}
+
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2018.asc}{\s{Secure Contact Key} (2018)}
+
+\dd RSA, 3072-bit. Key ID: \cw{657D487977F95C98}. Fingerprint:
+\cw{A680\_0082\_2998\_6E46\_22CA\_\_0E43\_657D\_4879\_77F9\_5C98}
+
\s{Key generated in 2016} (when we first introduced the Secure Contact Key)
\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2016.asc}{\s{Secure Contact Key} (2016)}
diff --git a/DOC/PLINK.BUT b/DOC/PLINK.BUT
index 30dcead1..39b14f2b 100644
--- a/DOC/PLINK.BUT
+++ b/DOC/PLINK.BUT
@@ -41,7 +41,7 @@ use Plink:
\c C:\>plink
\c Plink: command-line connection utility
-\c Release 0.76
+\c Release 0.78
\c Usage: plink [options] [user@]host [command]
\c ("host" can also be a PuTTY saved session name)
\c Options:
@@ -61,7 +61,7 @@ use Plink:
\c -sercfg configuration-string (e.g. 19200,8,n,1,X)
\c Specify the serial configuration (serial only)
\c The following options only apply to SSH connections:
-\c -pw passw login with specified password
+\c -pwfile file login with password read from specified file
\c -D [listen-IP:]listen-port
\c Dynamic SOCKS-based port forwarding
\c -L [listen-IP:]listen-port:host:port
diff --git a/DOC/PSCP.BUT b/DOC/PSCP.BUT
index e816f3e5..96a2d4b9 100644
--- a/DOC/PSCP.BUT
+++ b/DOC/PSCP.BUT
@@ -39,7 +39,7 @@ use PSCP:
\c C:\>pscp
\c PuTTY Secure Copy client
-\c Release 0.76
+\c Release 0.78
\c Usage: pscp [options] [user@]host:source target
\c pscp [options] source [source...] [user@]host:target
\c pscp [options] -ls [user@]host:filespec
@@ -53,7 +53,7 @@ use PSCP:
\c -load sessname Load settings from saved session
\c -P port connect to specified port
\c -l user connect with specified username
-\c -pw passw login with specified password
+\c -pwfile file login with password read from specified file
\c -1 -2 force use of particular SSH protocol version
\c -ssh -ssh-connection
\c force use of particular SSH protocol variant
diff --git a/DOC/PUBKEY.BUT b/DOC/PUBKEY.BUT
index f40c9526..5ac59390 100644
--- a/DOC/PUBKEY.BUT
+++ b/DOC/PUBKEY.BUT
@@ -56,15 +56,15 @@ and convenience. See \k{pageant} for further details.
There is more than one \i{public-key algorithm} available. The most
common are \i{RSA} and \i{ECDSA}, but others exist, notably \i{DSA}
-(otherwise known as DSS), the USA's federal Digital Signature Standard.
+(otherwise known as \i{DSS}), the USA's federal Digital Signature Standard.
The key types supported by PuTTY are described in \k{puttygen-keytype}.
\H{pubkey-puttygen} Using \i{PuTTYgen}, the PuTTY key generator
PuTTYgen is a key generator. It \I{generating keys}generates pairs of
-public and private keys to be used with PuTTY, PSCP, and Plink, as well
-as the PuTTY authentication agent, Pageant (see \k{pageant}). PuTTYgen
-generates RSA, DSA, ECDSA, and EdDSA keys.
+public and private keys to be used with PuTTY, PSCP, PSFTP, and Plink,
+as well as the PuTTY authentication agent, Pageant (see \k{pageant}).
+PuTTYgen generates RSA, DSA, ECDSA, and EdDSA keys.
When you run PuTTYgen you will see a window where you have two main
choices: \q{Generate}, to generate a new public/private key pair, or
@@ -132,10 +132,13 @@ The \q{Number of bits} input box allows you to choose the strength
of the key PuTTYgen will generate.
\b For RSA and DSA, 2048 bits should currently be sufficient for most
-purposes.
+purposes. (Smaller keys of these types are no longer considered
+secure, and PuTTYgen will warn if you try to generate them.)
-\b For ECDSA, only 256, 384, and 521 bits are supported. (ECDSA offers
-equivalent security to RSA with smaller key sizes.)
+\b For ECDSA, only 256, 384, and 521 bits are supported, corresponding
+to \i{NIST}-standardised elliptic curves. (Elliptic-curve keys do not
+need as many bits as RSA keys for equivalent security, so these numbers
+are smaller than the RSA recommendations.)
\b For EdDSA, the only valid sizes are 255 bits (these keys are also
known as \q{\i{Ed25519}} and are commonly used) and 448 bits
@@ -145,6 +148,9 @@ the same as 255.)
\S{puttygen-primes} Selecting the \i{prime generation method}
+(This is entirely optional. Unless you know better, it's entirely
+sensible to skip this and use the default settings.)
+
On the \q{Key} menu, you can also optionally change the method for
generating the prime numbers used in the generated key. This is used
for RSA and DSA keys only. (The other key types don't require
@@ -154,9 +160,6 @@ The prime-generation method does not affect compatibility: a key
generated with any of these methods will still work with all the same
SSH servers.
-If you don't care about this, it's entirely sensible to leave it on the
-default setting.
-
The available methods are:
\b Use \i{probable primes} (fast)
@@ -177,6 +180,15 @@ are prime, because it generates the output number together with a
proof of its primality. This takes more effort, but it eliminates that
theoretical risk in the probabilistic method.
+There in one way in which PuTTYgen's \q{proven primes} method is not
+strictly better than its \q{probable primes} method. If you use
+PuTTYgen to generate an RSA key on a computer that is potentially
+susceptible to timing- or cache-based \i{side-channel attacks}, such
+as a shared computer, the \q{probable primes} method is designed to
+resist such attacks, whereas the \q{proven primes} methods are not.
+(This is only a concern for RSA keys; for other key types, primes
+are either not secret or not involved.)
+
You might choose to switch from probable to proven primes if you have
a local security standard that demands it, or if you don't trust the
probabilistic argument for the safety of the usual method.
@@ -230,9 +242,9 @@ a particular fingerprint. So some utilities, such as the Pageant key
list box (see \k{pageant-mainwin-keylist}) and the Unix \c{ssh-add}
utility, will list key fingerprints rather than the whole public key.
-By default, PuTTYgen will display fingerprints in the \q{SHA256}
-format. If you need to see the fingerprint in the older \q{MD5} format
-(which looks like \c{aa:bb:cc:...}), you can choose
+By default, PuTTYgen will display SSH-2 key fingerprints in the
+\q{SHA256} format. If you need to see the fingerprint in the older
+\q{MD5} format (which looks like \c{aa:bb:cc:...}), you can choose
\q{Show fingerprint as MD5} from the \q{Key} menu, but bear in mind
that this is less cryptographically secure; it may be feasible for
an attacker to create a key with the same fingerprint as yours.
@@ -298,6 +310,48 @@ a result.
\e{Do not forget your passphrase}. There is no way to recover it.
+\S{puttygen-cert} Adding a \i{certificate} to your key
+
+In some environments, user authentication keys can be signed in turn
+by a \q{certifying authority} (\q{CA} for short), and user accounts on
+an SSH server can be configured to automatically trust any key that's
+certified by the right signature.
+
+This can be a convenient setup if you have a very large number of
+servers. When you change your key pair, you might otherwise have to
+edit the \cw{authorized_keys} file on every server individually, to
+make them all accept the new key. But if instead you configure all
+those servers \e{once} to accept keys signed as yours by a CA, then
+when you change your public key, all you have to do is to get the new
+key certified by the same CA as before, and then all your servers will
+automatically accept it without needing individual reconfiguration.
+
+To get your key signed by a CA, you'll probably send the CA the new
+\e{public} key (not the private half), and get back a modified version
+of the public key with the certificate included.
+
+If you want to incorporate the certificate into your PPK file for
+convenience, you can use the \q{Add certificate to key} menu option in
+PuTTYgen's \q{Key} menu. This will give you a single file containing
+your private key and the certificate, which is everything you need to
+authenticate to a server prepared to accept that certificate.
+
+To remove the certificate again and restore the uncertified PPK file,
+there's also a \q{Remove certificate from key} option.
+
+(However, you don't \e{have} to incorporate the certificate into your
+PPK file. You can equally well use it separately, via the
+\q{Certificate to use with the private key} option in PuTTY itself.
+See \k{config-ssh-cert}. It's up to you which you find more
+convenient.)
+
+When the currently loaded key in PuTTYgen contains a certificate, the
+large \q{Public key for pasting} edit box (see \k{puttygen-pastekey})
+is replaced by a button that brings up an information box telling you
+about the certificate, such as who it certifies your key as belonging
+to, when it expires (if ever), and the fingerprint of the CA key that
+signed it in turn.
+
\S{puttygen-savepriv} Saving your private key to a disk file
Once you have generated a key, set a comment field and set a
@@ -389,8 +443,8 @@ These options only affect PPK version 3.
\dt Key derivation function
\dd The variant of the \i{Argon2} key derivation function to use.
-You might change this if you consider your exposure to side-channel
-attacks to be different to the norm.
+You might change this if you consider your exposure to \i{side-channel
+attacks} to be different to the norm.
\dt Memory to use for passphrase hash
@@ -469,6 +523,83 @@ you have generated an SSH-1 private key using OpenSSH or
Hence, the export options are not available if you have generated an
SSH-1 key.
+\S{puttygen-cli} PuTTYgen command-line configuration
+
+PuTTYgen supports a set of command-line options to configure many of
+the same settings you can select in the GUI. This allows you to start
+it up with your own preferences ready-selected, which might be useful
+if you generate a lot of keys. (For example, you could make a Windows
+shortcut that runs PuTTYgen with some command line options, or a batch
+file or Powershell script that you could distribute to a whole
+organisation containing your local standards.)
+
+The options supported on the command line are:
+
+\dt \cw{\-t} \e{keytype}
+
+\dd Type of key to generate. You can select \c{rsa}, \c{dsa},
+\c{ecdsa}, \c{eddsa}, \c{ed25519}, \c{ed448}, or \c{rsa1}.
+See \k{puttygen-keytype}.
+
+\dt \cw{\-b} \e{bits}
+
+\dd Size of the key to generate, in bits. See \k{puttygen-strength}.
+
+\dt \cw{\-\-primes} \e{method}
+
+\dd Method for generating prime numbers. You can select \c{probable},
+\c{proven}, and \c{proven-even}. See \k{puttygen-primes}.
+
+\dt \cw{\-\-strong-rsa}
+
+\dd When generating an RSA key, make sure the prime factors of the key
+modulus are \q{strong primes}. See \k{puttygen-primes}.
+
+\dt \cw{\-\-ppk-param} \e{key}\cw{=}\e{value}\cw{,}...
+
+\dd Allows setting all the same details of the PPK save file format
+described in \k{puttygen-save-params}.
+
+\lcont{
+
+Aspects to change are specified as a series of \e{key}\cw{=}\e{value} pairs
+separated by commas. The \e{key}s are:
+
+\dt \cw{version}
+
+\dd The PPK format version: either \cw{3} or \cw{2}.
+
+\dt \cw{kdf}
+
+\dd The variant of Argon2 to use: \cw{argon2id}, \cw{argon2i}, and
+\cw{argon2d}.
+
+\dt \cw{memory}
+
+\dd The amount of memory needed to decrypt the key, in Kbyte.
+
+\dt \cw{time}
+
+\dd Specifies how much time is required to attempt decrypting the key,
+in milliseconds.
+
+\dt \cw{passes}
+
+\dd Alternative to \cw{time}: specifies the number of hash passes
+required to attempt decrypting the key.
+
+\dt \cw{parallelism}
+
+\dd Number of parallelisable threads that can be used to decrypt the
+key.
+
+}
+
+\dt \cw{\-E} \e{fptype}
+
+\dd Algorithm to use when displaying key fingerprints. You can
+select \c{sha256} or \c{md5}. See \k{puttygen-fingerprint}.
+
\H{pubkey-gettingready} Getting ready for public key authentication
Connect to your SSH server using PuTTY with the SSH protocol. When the
diff --git a/DOC/SSHNAMES.BUT b/DOC/SSHNAMES.BUT
index 6cf82f73..a00ac678 100644
--- a/DOC/SSHNAMES.BUT
+++ b/DOC/SSHNAMES.BUT
@@ -70,7 +70,7 @@ They have been superseded by \cw{arcfour128} and \cw{arcfour256}.
The SSH agent protocol, which is only specified in an Internet-Draft
at the time of writing
-(\W{https://tools.ietf.org/html/draft-miller-ssh-agent}\cw{draft-miller-ssh-agent}),
+(\W{https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent}\cw{draft-miller-ssh-agent}),
defines an extension mechanism. These names can be sent in an
\cw{SSH_AGENTC_EXTENSION} message.
diff --git a/DOC/UDP.BUT b/DOC/UDP.BUT
index b3570d5d..12b3392b 100644
--- a/DOC/UDP.BUT
+++ b/DOC/UDP.BUT
@@ -28,9 +28,9 @@ platform-generic modules. The Unix-specific modules are all in the
\c{unix} subdirectory; the Windows-specific modules are in the
\c{windows} subdirectory.
-All the modules in the main source directory - notably \e{all} of
-the code for the various back ends - are platform-generic. We want
-to keep them that way.
+All the modules in the main source directory and other
+subdirectories - notably \e{all} of the code for the various back
+ends - are platform-generic. We want to keep them that way.
This also means you should stick to the C semantics guaranteed by the
C standard: try not to make assumptions about the precise size of
@@ -171,9 +171,7 @@ to. C++ friendliness is really a side benefit.)
We want PuTTY to continue being pure C, at least in the
platform-independent parts and the currently existing ports. Patches
which switch the Makefiles to compile it as C++ and start using
-classes will not be accepted. Also, in particular, we disapprove of
-\cw{//} comments, at least for the moment. (Perhaps once C99 becomes
-genuinely widespread we might be more lenient.)
+classes will not be accepted.
The one exception: a port to a new platform may use languages other
than C if they are necessary to code on that platform. If your
@@ -278,16 +276,17 @@ should be aware that you might be re-entered if a network event
comes in and is passed on to our window procedure by the
\cw{MessageBox()} message loop.
-Also, the front ends (in particular Windows Plink) can use multiple
-threads if they like. However, Windows Plink keeps \e{very} tight
-control of its auxiliary threads, and uses them pretty much
-exclusively as a form of \cw{select()}. Pretty much all the code
-outside \cw{windows/winplink.c} is \e{only} ever called from the one
-primary thread; the others just loop round blocking on file handles
-and send messages to the main thread when some real work needs
-doing. This is not considered a portability hazard because that bit
-of \cw{windows/winplink.c} will need rewriting on other platforms in
-any case.
+Also, the front ends can use multiple threads if they like. For
+example, the Windows front-end code spawns subthreads to deal with
+bidirectional blocking I/O on non-network streams such as Windows
+pipes. However, it keeps tight control of its auxiliary threads, and
+uses them only for that one purpose, as a form of \cw{select()}.
+Pretty much all the code outside \cw{windows/handle-io.c} is \e{only}
+ever called from the one primary thread; the others just loop round
+blocking on file handles, and signal the main thread (via Windows
+event objects) when some real work needs doing. This is not considered
+a portability hazard because that code is already Windows-specific and
+needs rewriting on other platforms.
One important consequence of this: PuTTY has only one thread in
which to do everything. That \q{everything} may include managing
@@ -333,51 +332,11 @@ on a 640\u00D7{x}480 display. If you're adding controls to either of
these boxes and you find yourself wanting to increase the size of
the whole box, \e{don't}. Split it into more panels instead.
-\H{udp-makefiles-auto} Automatically generated \cw{Makefile}s
-
-PuTTY is intended to compile on multiple platforms, and with
-multiple compilers. It would be horrifying to try to maintain a
-single \cw{Makefile} which handled all possible situations, and just
-as painful to try to directly maintain a set of matching
-\cw{Makefile}s for each different compilation environment.
-
-Therefore, we have moved the problem up by one level. In the PuTTY
-source archive is a file called \c{Recipe}, which lists which source
-files combine to produce which binaries; and there is also a script
-called \cw{mkfiles.pl}, which reads \c{Recipe} and writes out the
-real \cw{Makefile}s. (The script also reads all the source files and
-analyses their dependencies on header files, so we get an extra
-benefit from doing it this way, which is that we can supply correct
-dependency information even in environments where it's difficult to
-set up an automated \c{make depend} phase.)
-
-You should \e{never} edit any of the PuTTY \cw{Makefile}s directly.
-They are not stored in our source repository at all. They are
-automatically generated by \cw{mkfiles.pl} from the file \c{Recipe}.
-
-If you need to add a new object file to a particular binary, the
-right thing to do is to edit \c{Recipe} and re-run \cw{mkfiles.pl}.
-This will cause the new object file to be added in every tool that
-requires it, on every platform where it matters, in every
-\cw{Makefile} to which it is relevant, \e{and} to get all the
-dependency data right.
-
-If you send us a patch that modifies one of the \cw{Makefile}s, you
-just waste our time, because we will have to convert it into a
-change to \c{Recipe}. If you send us a patch that modifies \e{all}
-of the \cw{Makefile}s, you will have wasted a lot of \e{your} time
-as well!
-
-(There is a comment at the top of every \cw{Makefile} in the PuTTY
-source archive saying this, but many people don't seem to read it,
-so it's worth repeating here.)
-
-\H{udp-ssh-coroutines} Coroutines in the SSH code
-
-Large parts of the code in the various SSH modules (in fact most of
-the protocol layers) are structured using a set of macros that
-implement (something close to) Donald Knuth's \q{coroutines} concept
-in C.
+\H{udp-ssh-coroutines} Coroutines in protocol code
+
+Large parts of the code in modules implementing wire protocols
+(mainly SSH) are structured using a set of macros that implement
+(something close to) Donald Knuth's \q{coroutines} concept in C.
Essentially, the purpose of these macros are to arrange that a
function can call \cw{crReturn()} to return to its caller, and the
@@ -388,7 +347,7 @@ This means that any local (automatic) variables declared in such a
function will be corrupted every time you call \cw{crReturn}. If you
need a variable to persist for longer than that, you \e{must} make it
a field in some appropriate structure containing the persistent state
-of the coroutine \dash typically the main state structure for an SSH
+of the coroutine \dash typically the main state structure for a
protocol layer.
See
@@ -544,13 +503,13 @@ call sites. Instead, what we generally do in this code base is to
write a set of \cw{static inline} wrapper functions in the same header
file that defined the \cw{MyAbstraction} structure types, like this:
-\c static MyAbstraction *myabs_new(const MyAbstractionVtable *vt)
+\c static inline MyAbstraction *myabs_new(const MyAbstractionVtable *vt)
\c { return vt->new(vt); }
-\c static void myabs_free(MyAbstraction *myabs)
+\c static inline void myabs_free(MyAbstraction *myabs)
\c { myabs->vt->free(myabs); }
-\c static void myimpl_modify(MyAbstraction *myabs, unsigned param)
+\c static inline void myimpl_modify(MyAbstraction *myabs, unsigned param)
\c { myabs->vt->modify(myabs, param); }
-\c static unsigned myimpl_query(MyAbstraction *myabs, unsigned param)
+\c static inline unsigned myimpl_query(MyAbstraction *myabs, unsigned param)
\c { return myabs->vt->query(myabs, param); }
And now call sites can use those reasonably clean-looking wrapper
@@ -598,8 +557,8 @@ based on the offset within that structure of the field called
This system is flexible enough to permit \q{multiple inheritance}, or
rather, multiple \e{implementation}: having one object type implement
-more than one trait. For example, the \cw{Proxy} type implements both
-the \cw{Socket} trait and the \cw{Plug} trait that connects to it,
+more than one trait. For example, the \cw{ProxySocket} type implements
+both the \cw{Socket} trait and the \cw{Plug} trait that connects to it,
because it has to act as an adapter between another instance of each
of those types.
@@ -791,46 +750,6 @@ other two full implementation vtables.
}
-\H{udp-compile-once} Single compilation of each source file
-
-The PuTTY build system for any given platform works on the following
-very simple model:
-
-\b Each source file is compiled precisely once, to produce a single
-object file.
-
-\b Each binary is created by linking together some combination of
-those object files.
-
-Therefore, if you need to introduce functionality to a particular
-module which is only available in some of the tool binaries (for
-example, a cryptographic proxy authentication mechanism which needs
-to be left out of PuTTYtel to maintain its usability in
-crypto-hostile jurisdictions), the \e{wrong} way to do it is by
-adding \cw{#ifdef}s in (say) \cw{proxy.c}. This would require
-separate compilation of \cw{proxy.c} for PuTTY and PuTTYtel, which
-means that the entire \cw{Makefile}-generation architecture (see
-\k{udp-makefiles-auto}) would have to be significantly redesigned.
-Unless you are prepared to do that redesign yourself, \e{and}
-guarantee that it will still port to any future platforms we might
-decide to run on, you should not attempt this!
-
-The \e{right} way to introduce a feature like this is to put the new
-code in a separate source file, and (if necessary) introduce a
-second new source file defining the same set of functions, but
-defining them as stubs which don't provide the feature. Then the
-module whose behaviour needs to vary (\cw{proxy.c} in this example)
-can call the functions defined in these two modules, and it will
-either provide the new feature or not provide it according to which
-of your new modules it is linked with.
-
-Of course, object files are never shared \e{between} platforms; so
-it is allowable to use \cw{#ifdef} to select between platforms. This
-happens in \cw{puttyps.h} (choosing which of the platform-specific
-include files to use), and also in \cw{misc.c} (the Windows-specific
-\q{Minefield} memory diagnostic system). It should be used
-sparingly, though, if at all.
-
\H{udp-perfection} Do as we say, not as we do
The current PuTTY code probably does not conform strictly to \e{all}
diff --git a/DOC/USING.BUT b/DOC/USING.BUT
index 02a67808..5865ac95 100644
--- a/DOC/USING.BUT
+++ b/DOC/USING.BUT
@@ -838,17 +838,23 @@ any case.)
This option is equivalent to the port number control in the Session
panel of the PuTTY configuration box (see \k{config-hostname}).
-\S2{using-cmdline-pw} \i\c{-pw}: specify a \i{password}
+\S2{using-cmdline-pw} \i\c{-pwfile} and \i\c{-pw}: specify a \i{password}
A simple way to automate a remote login is to supply your password
-on the command line. This is \e{not recommended} for reasons of
-security. If you possibly can, we recommend you set up public-key
-authentication instead. See \k{pubkey} for details.
+on the command line.
-Note that the \c{-pw} option only works when you are using the SSH
-protocol. Due to fundamental limitations of Telnet, Rlogin, and
-SUPDUP, these protocols do not support automated password
-authentication.
+The \c{-pwfile} option takes a file name as an argument. The first
+line of text in that file will be used as your password.
+
+The \c{-pw} option takes the password itself as an argument. This is
+\s{NOT SECURE} if anybody else uses the same computer, because the
+whole command line (including the password) is likely to show up if
+another user lists the running processes. \c{-pw} is retained for
+backwards compatibility only; you should use \c{-pwfile} instead.
+
+Note that these options only work when you are using the SSH protocol.
+Due to fundamental limitations of Telnet, Rlogin, and SUPDUP, these
+protocols do not support automated password authentication.
\S2{using-cmdline-agentauth} \i\c{-agent} and \i\c{-noagent}:
control use of Pageant for authentication
@@ -941,15 +947,19 @@ this:
\c plink host1.example.com -nc host2.example.com:1234
-You might want to use this feature if you needed to make an SSH
-connection to a target host which you can only reach by going
-through a proxy host, and rather than using port forwarding you
-prefer to use the local proxy feature (see \k{config-proxy-type} for
-more about local proxies). In this situation you might select
-\q{Local} proxy type, set your local proxy command to be \cq{plink
-%proxyhost -nc %host:%port}, enter the target host name on the
-Session panel, and enter the directly reachable proxy host name on
-the Proxy panel.
+This can be useful if you're trying to make a connection to a target
+host which you can only reach by SSH forwarding through a proxy host.
+One way to do this would be to have an existing SSH connection to the
+proxy host, with a port forwarding, but if you prefer to have the
+connection started on demand as needed, then this approach can also
+work.
+
+However, this does depend on the program \e{using} the proxy being
+able to run a subprocess in place of making a network connection.
+PuTTY itself can do this using the \q{Local} proxy type, but there's a
+built-in more flexible way using the \q{SSH} proxy type. (See
+\k{config-proxy-type} for a description of both.) So this feature is
+probably most useful with another client program as the end user.
This feature is only available in SSH protocol version 2 (since the
version 1 protocol assumes you will always want to run a shell). It
@@ -1014,6 +1024,19 @@ This option is equivalent to the \q{Private key file for
authentication} box in the Auth panel of the PuTTY configuration box
(see \k{config-ssh-privkey}).
+\S2{using-cmdline-cert} \i\c{-cert}: specify an SSH \i{certificate}
+
+The \c{-cert} option allows you to specify the name of a certificate
+file containing a signed version of your public key. If you specify
+this option, PuTTY will present that certificate in place of the plain
+public key, whenever it tries to authenticate with a key that matches.
+(This applies whether the key is stored in Pageant or loaded directly
+from a file by PuTTY.)
+
+This option is equivalent to the \q{Certificate to use with the
+private key} box in the Auth panel of the PuTTY configuration box (see
+\k{config-ssh-cert}).
+
\S2{using-cmdline-no-trivial-auth} \i\c{-no-trivial-auth}: disconnect
if SSH authentication succeeds trivially
@@ -1152,3 +1175,12 @@ the extra protection), so it's reasonable to want to run Pageant but
not PuTTY with the ACL restrictions. You can force Pageant to start
subsidiary PuTTY processes with a restricted ACL if you also pass the
\i\c{-restrict-putty-acl} option.
+
+\S2{using-cmdline-host-ca} \i{\c{-host-ca}}: launch the
+\I{certificate}host CA configuration
+
+If you start PuTTY with the \c{-host-ca} option, it will not launch a
+session at all. Instead, it will just display the configuration dialog
+box for host certification authorities, as described in
+\k{config-ssh-kex-cert}. When you dismiss that dialog box, PuTTY will
+terminate.
diff --git a/DOC/man-pageant.but b/DOC/man-pageant.but
index 358f3a08..d202f166 100644
--- a/DOC/man-pageant.but
+++ b/DOC/man-pageant.but
@@ -41,7 +41,7 @@ extract their public half.
The agent protocol used by \c{pageant} is compatible with the PuTTY
tools and also with other implementations such as OpenSSH's SSH client
-and \e{ssh-agent(1)}. Some \c{pageant} features are implemented with
+and \cw{ssh-agent}(\e{1}). Some \c{pageant} features are implemented with
protocol extensions, so will only work if \c{pageant} is on both ends.
To run \c{pageant} as an agent, you must provide an option to tell it
@@ -256,6 +256,12 @@ be matched
\dd to indicate that it is a fingerprint of a specific format
+\dt \cq{sha256-cert:} or \cq{md5-cert:}
+
+\dd to indicate that it is a fingerprint of a specific format, and
+specifically matches the fingerprint of the public key \e{including} a
+certificate if any
+
}
\dt \cw{--public-openssh} \e{key-identifiers}, \cw{-L} \e{key-identifiers}
@@ -317,15 +323,15 @@ by the SSH agent protocol.
\dt \cw{--askpass} \e{prompt}
-\dd With this option, \c{pageant} acts as an \e{ssh-askpass(1)}
+\dd With this option, \c{pageant} acts as an \cw{ssh-askpass}(\e{1})
replacement, rather than performing any SSH agent functionality. This
may be useful if you prefer Pageant's GUI prompt style, which
minimises information leakage about your passphrase length in its
-visual feedback, compared to other \e{ssh-askpass(1)} implementations.
+visual feedback, compared to other \cw{ssh-askpass}(\e{1}) implementations.
\lcont{
-\c{pageant --askpass} implements the standard \e{ssh-askpass(1)}
+\c{pageant --askpass} implements the standard \cw{ssh-askpass}(\e{1})
interface: it can be passed a prompt to display (as a single argument)
and, if successful, prints the passphrase on standard output and
returns a zero exit status. Typically you would use the environment
diff --git a/DOC/man-plink.but b/DOC/man-plink.but
index 26e65f71..2a3b36c7 100644
--- a/DOC/man-plink.but
+++ b/DOC/man-plink.but
@@ -59,9 +59,9 @@ to aid in verifying new files released by the PuTTY team.
\dt \cw{-ssh-connection}
\dd Force use of the \q{bare \cw{ssh-connection}} protocol. This is
-only likely to be useful when connecting to a \e{psusan(1)} server,
-most likely with an absolute path to a Unix-domain socket in place
-of \e{host}.
+only likely to be useful when connecting to a \cw{psusan}(\e{1})
+server, most likely with an absolute path to a Unix-domain socket in
+place of \e{host}.
\dt \cw{\-proxycmd} \e{command}
@@ -114,11 +114,16 @@ sequences. These options override Plink's default behaviour to enable
or disabling such filtering on the standard error and standard output
channels.
+\dt \cw{-pwfile} \e{filename}
+
+\dd Open the specified file, and use the first line of text read from
+it as the remote password.
+
\dt \cw{-pw} \e{password}
\dd Set remote password to \e{password}. \e{CAUTION:} this will likely
make the password visible to other users of the local machine (via
-commands such as \q{\c{w}}).
+commands such as \q{\c{ps}} or \q{\c{w}}). Use \cw{-pwfile} instead.
\dt \cw{\-L} \cw{[}\e{srcaddr}\cw{:]}\e{srcport}\cw{:}\e{desthost}\cw{:}\e{destport}
diff --git a/DOC/man-psftp.but b/DOC/man-psftp.but
index 52617291..e0b48602 100644
--- a/DOC/man-psftp.but
+++ b/DOC/man-psftp.but
@@ -89,11 +89,16 @@ channel from the server, to prevent remote processes sending confusing
escape sequences. This option forces the standard error channel to not be
filtered.
+\dt \cw{-pwfile} \e{filename}
+
+\dd Open the specified file, and use the first line of text read from
+it as the remote password.
+
\dt \cw{-pw} \e{password}
\dd Set remote password to \e{password}. \e{CAUTION:} this will likely
make the password visible to other users of the local machine (via
-commands such as \q{\c{w}}).
+commands such as \q{\c{ps}} or \q{\c{w}}). Use \cw{-pwfile} instead.
\dt \cw{-1}
@@ -106,9 +111,9 @@ commands such as \q{\c{w}}).
\dt \cw{-ssh-connection}
\dd Force use of the \q{bare \cw{ssh-connection}} protocol. This is
-only likely to be useful when connecting to a \e{psusan(1)} server,
-most likely with an absolute path to a Unix-domain socket in place
-of \e{host}.
+only likely to be useful when connecting to a \cw{psusan}(\e{1})
+server, most likely with an absolute path to a Unix-domain socket in
+place of \e{host}.
\dt \cw{-ssh}
diff --git a/DOC/man-psocks.but b/DOC/man-psocks.but
index a9792e44..eb075a6e 100644
--- a/DOC/man-psocks.but
+++ b/DOC/man-psocks.but
@@ -18,8 +18,8 @@ IPv4 and IPv6 connections. It does not support requiring
authentication of its clients.
\cw{psocks} can be used together with an SSH client such as
-\cw{putty(1)} to implement a reverse dynamic SSH tunnel. It can also
-be used for network protocol debugging, as it can record all the
+\cw{putty}(\e{1}) to implement a reverse dynamic SSH tunnel. It can
+also be used for network protocol debugging, as it can record all the
traffic passing through it in various ways.
By default, \cw{psocks} listens to connections from localhost only,
@@ -84,8 +84,8 @@ have the connection's traffic piped into it, similar to \cw{-f}.
\S{psocks-manpage-examples} EXAMPLES
-In combination with the \e{plink(1)} SSH client, to set up a reverse
-dynamic SSH tunnel, in which the remote listening port 1080 on
+In combination with the \cw{plink}(\e{1}) SSH client, to set up a
+reverse dynamic SSH tunnel, in which the remote listening port 1080 on
remote host \cw{myhost} acts as a SOCKS server giving access to your
local network:
diff --git a/DOC/man-psusan.but b/DOC/man-psusan.but
index fa986e88..64d3a030 100644
--- a/DOC/man-psusan.but
+++ b/DOC/man-psusan.but
@@ -191,15 +191,36 @@ And the setup script \cw{uml-psusan.sh} might look like this:
\c # Choose what shell you want to run inside psusan
\e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
\c export SHELL=/bin/bash
+\c # Set up a default path
+\e iiiiiiiiiiiiiiiiiiiiiii
+\c export PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
\c # And now run psusan over the serial port
\e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
\c exec /home/simon/src/putty/misc/psusan
-Now set up a PuTTY saved session as in the Docker example above, using
-that \cw{linux} command as the local proxy command, and you'll have a
-PuTTY session that starts up a clean UML instance when you run it, and
-(if you enabled connection sharing) further instances of the same
-session will connect to the same instance again.
+Now set up a PuTTY saved session as in the Docker example above.
+Basically you'll want to use the above \cw{linux} command as the local
+proxy command. However, it's worth wrapping it in \cw{setsid}(\e{1}),
+because when UML terminates, it kills its entire process group. So
+it's better that PuTTY should not be part of that group, and should
+have the opportunity to shut down cleanly by itself. So probably you
+end up setting the proxy command to be something more like:
+
+\c setsid linux mem=512M rootfstype=hostfs rootflags=/ rw \
+\c con=fd:2,fd:2 ssl0=fd:0,fd:1 init=/some/path/to/uml-psusan.sh
+\e iiiiiiiiiiiiiiiiiiiiiiiiiii
+
+You may also find that you have to enable the bug workaround that
+indicates that the server \q{Discards data sent before its greeting},
+because otherwise PuTTY's outgoing protocol greeting can be
+accidentally lost during UML startup. (See
+\W{https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991958}{Debian
+bug #991958}.)
+
+Once you've done that, you'll have a PuTTY session that starts up a
+clean UML instance when you run it, and (if you enabled connection
+sharing) further instances of the same session will connect to the
+same instance again.
\S2{psusan-manpage-examples-wsl} Windows Subsystem for Linux
@@ -231,6 +252,39 @@ ports in and out of the WSL environment (e.g. expose a WSL2 network
service through the hypervisor's internal NAT), forward Pageant into
it, and so on.
+\S2{psusan-manpage-examples-cygwin} Cygwin
+
+Another Unix-like environment on Windows is Cygwin. That comes with
+its own GUI terminal application, \cw{mintty} (as it happens, a
+derivative of PuTTY); but if you'd prefer to use PuTTY itself to talk
+to your Cygwin terminal sessions, \cw{psusan} can help.
+
+To do this, you'll first need to build the Unix PuTTY tools inside
+Cygwin (via the usual \cw{cmake} method). Then, copy the resulting
+\cw{psusan.exe} into Cygwin's \cw{/bin} directory. (It has to be
+in that directory for non-Cygwin programs to run it; otherwise it
+won't be able to find the Cygwin DLL at startup.)
+
+Then set up your PuTTY saved session like this:
+
+\b set the local proxy command to run \cw{psusan.exe} via its real
+Windows path. You might also want to add the \cw{--sessiondir} option
+so that shell sessions start up in your Cygwin home directory. For
+example, you might use the command \cq{c:\\cygwin64\\bin\\psusan.exe
+--sessiondir /home/simon} (changing the pathname and username to match
+your setup).
+
+\b enter anything you like in the host name box; \cq{Cygwin} is
+probably a good choice
+
+\b set the protocol to \q{Bare ssh-connection}, as usual.
+
+Port forwarding is probably not particularly useful in this case,
+since Cygwin shares the same network port space as the host machine.
+But turning on agent forwarding is useful, because then the Cygwin
+command-line SSH client can talk to Pageant without any further
+configuration.
+
\S2{psusan-manpage-examples-schroot} \cw{schroot}
Another example of a container-like environment is the alternative
diff --git a/DOC/man-pterm.but b/DOC/man-pterm.but
index fec97f11..d3d1d96a 100644
--- a/DOC/man-pterm.but
+++ b/DOC/man-pterm.but
@@ -76,7 +76,7 @@ will be ignored unless the \cw{BoldAsColour} resource is set to 0 or 2.
\dt \cw{\-geometry} \e{geometry}
\dd Specify the size of the terminal, in rows and columns of text. See
-\e{X(7)} for more information on the syntax of geometry
+\cw{X}(\e{7}) for more information on the syntax of geometry
specifications.
\dt \cw{\-sl} \e{lines}
diff --git a/DOC/man-putty.but b/DOC/man-putty.but
index 858ec0b0..a85b4505 100644
--- a/DOC/man-putty.but
+++ b/DOC/man-putty.but
@@ -55,7 +55,7 @@ will be ignored unless the \cw{BoldAsColour} resource is set to 0 or 2.
\dt \cw{\-geometry} \e{geometry}
\dd Specify the size of the terminal, in rows and columns of text.
-See \e{X(7)} for more information on the syntax of geometry
+See \cw{X}(\e{7}) for more information on the syntax of geometry
specifications.
\dt \cw{\-sl} \e{lines}
diff --git a/DOC/man-puttygen.but b/DOC/man-puttygen.but
index 021af205..e6a2c990 100644
--- a/DOC/man-puttygen.but
+++ b/DOC/man-puttygen.but
@@ -12,10 +12,12 @@
\e bbbbbbbb iiiiiii bb iiiiiii bb iiii bbbbbbbb iiiiii bb
\c [ -C new-comment ] [ -P ] [ --reencrypt ]
\e bb iiiiiiiiiii bb bbbbbbbbbbb
-\c [ -O output-type | -l | -L | -p | --dump ] [ -E fptype ]
-\e bb iiiiiiiiiii bb bb bb bbbbbb bb iiiiii
-\c [ --ppk-param key=value,... ]
-\e bbbbbbbbbbb iiibiiiiib
+\c [ --certificate cert-file | --remove-certificate ]
+\e bbbbbbbbbbbbb iiiiiiiii bbbbbbbbbbbbbbbbbbbb
+\c [ -O output-type | -l | -L | -p | --dump | --cert-info ]
+\e bb iiiiiiiiiii bb bb bb bbbbbb bbbbbbbbbbb
+\c [ --ppk-param key=value,... | -E fptype ]
+\e bbbbbbbbbbb iiibiiiiib bb iiiiii
\c [ -o output-file ]
\e bb iiiiiiiiiii
@@ -58,8 +60,9 @@ ssh.com's implementation.
You can also specify a file containing only a \e{public} key here.
The operations you can do are limited to outputting another public
-key format or a fingerprint. Public keys can be in RFC 4716 or
-OpenSSH format, or the standard SSH-1 format.
+key format (possibly removing an attached certificate first), or a
+fingerprint. Public keys can be in RFC 4716 or OpenSSH format, or
+the standard SSH-1 format.
}
@@ -143,6 +146,19 @@ to type).
automatic when you are generating a new key, but not when you are
modifying an existing key.
+\dt \cw{\-\-certificate} \e{certificate-file}
+
+\dd Adds an OpenSSH-style certificate to the public half of the key,
+so that the output file contains a certified public key with the same
+private key. If the input file already contained a certificate, it
+will be replaced with the new one. (Use \cq{-} to read a certificate
+from standard input.)
+
+\dt \cw{\-\-remove\-certificate}
+
+\dd Removes any certificate that was part of the key, to recover the
+uncertified version of the underlying key.
+
\dt \cw{\-\-reencrypt}
\dd For an existing private key saved with a passphrase, refresh the
@@ -260,6 +276,13 @@ newer format even for RSA, DSA, and ECDSA keys.
\dd Save an SSH-2 private key in ssh.com's format. This option is not
permitted for SSH-1 keys.
+\dt \cw{cert-info}
+
+\dd Save a textual dump of information about the certificate on the
+key, if any: whether it's a host or a user certificate, what host(s)
+or user(s) it's certified to be, its validity period, ID and serial
+number, and the fingerprint of the signing CA.
+
\dt \cw{text}
\dd Save a textual dump of the numeric components comprising the key
@@ -269,8 +292,9 @@ SSH.
\lcont{
The output consists of a series of \cw{name=value} lines, where each
-\c{value} is either a C-like string literal in double quotes, or a
-hexadecimal number starting with \cw{0x...}
+\c{value} is either a C-like string literal in double quotes, a
+hexadecimal number starting with \cw{0x...}, or a binary blob
+encoded with base64, denoted by \cw{b64("...")}.
}
If no output type is specified, the default is \c{private}.
@@ -283,8 +307,9 @@ If no output type is specified, the default is \c{private}.
this option is not specified, \c{puttygen} will assume you want to
overwrite the original file if the input and output file types are
the same (changing a comment or passphrase), and will assume you
-want to output to stdout if you are asking for a public key or
-fingerprint. Otherwise, the \c{\-o} option is required.
+want to output to stdout if you are asking for a public key,
+fingerprint, or one of the textual dump types. Otherwise, the
+\c{\-o} option is required.
\dt \cw{\-l}
@@ -298,6 +323,10 @@ fingerprint. Otherwise, the \c{\-o} option is required.
\dd Synonym for \q{\cw{-O public}}.
+\dt \cw{\-\-cert\-info}
+
+\dd Synonym for \q{\cw{-O cert-info}}.
+
\dt \cw{\-\-dump}
\dd Synonym for \q{\cw{-O text}}.
@@ -305,7 +334,18 @@ fingerprint. Otherwise, the \c{\-o} option is required.
\dt \cw{-E} \e{fptype}
\dd Specify the algorithm to use if generating a fingerprint. The
-options are \cw{sha256} (the default) and \cw{md5}.
+available algorithms are are \cw{sha256} (the default) and \cw{md5}.
+
+\lcont{
+
+By default, when showing the fingerprint of a public key that includes
+a certificate, \c{puttygen} will not include the certificate, so that
+the fingerprint shown will be the same as the underlying public key.
+If you want the fingerprint including the certificate (for example, so
+as to tell two certified keys apart), you can specify \cw{sha256-cert}
+or \cw{md5-cert} as the fingerprint type.
+
+}
\dt \cw{\-\-new\-passphrase} \e{file}
diff --git a/DOC/man-puttytel.but b/DOC/man-puttytel.but
index bf852ddb..075eeea7 100644
--- a/DOC/man-puttytel.but
+++ b/DOC/man-puttytel.but
@@ -56,7 +56,7 @@ will be ignored unless the \cw{BoldAsColour} resource is set to 0 or 2.
\dt \cw{\-geometry} \e{geometry}
\dd Specify the size of the terminal, in rows and columns of text. See
-\e{X(7)} for more information on the syntax of geometry
+\cw{X}(\e{7}) for more information on the syntax of geometry
specifications.
\dt \cw{\-sl} \e{lines}
diff --git a/DOC/pubkeyfmt.but b/DOC/pubkeyfmt.but
index 78da4885..836ed527 100644
--- a/DOC/pubkeyfmt.but
+++ b/DOC/pubkeyfmt.but
@@ -7,7 +7,7 @@ In this appendix, binary data structures are described using data type
representations such as \cq{uint32}, \cq{string} and \cq{mpint} as
used in the SSH protocol standards themselves. These are defined
authoritatively by
-\W{https://tools.ietf.org/html/rfc4251#section-5}{RFC 4251 section 5},
+\W{https://www.rfc-editor.org/rfc/rfc4251#section-5}{RFC 4251 section 5},
\q{Data Type Representations Used in the SSH Protocols}.
\H{ppk-overview} Overview
@@ -86,7 +86,7 @@ can contain any byte values other than 13 and 10 (CR and LF).
The next part of the file gives the public key. This is stored
unencrypted but base64-encoded
-(\W{https://tools.ietf.org/html/rfc4648}{RFC 4648}), and is preceded
+(\W{https://www.rfc-editor.org/rfc/rfc4648}{RFC 4648}), and is preceded
by a header line saying how many lines of base64 data are shown,
looking like this:
@@ -241,7 +241,7 @@ of \e{y} in the group generated by \e{g} mod \e{p}.
\S{ppk-privkey-ecdsa} NIST elliptic-curve keys
-NIST elliptic-curve keys are stored using one of the following
+\i{NIST} elliptic-curve keys are stored using one of the following
\s{algorithm-name} values, each corresponding to a different elliptic
curve and key size:
diff --git a/ICONS/MAKEFILE b/ICONS/MAKEFILE
index 3bdba19c..3e3ea456 100644
--- a/ICONS/MAKEFILE
+++ b/ICONS/MAKEFILE
@@ -13,18 +13,21 @@ PNGS = $(patsubst %.pam,%.png,$(PAMS))
MONOPNGS = $(patsubst %.pam,%.png,$(MONOPAMS))
TRUEPNGS = $(patsubst %.pam,%.png,$(TRUEPAMS))
+SVGS = $(patsubst %,%.svg,$(ICONS))
+
ICOS = putty.ico puttygen.ico pscp.ico pageant.ico pageants.ico puttycfg.ico \
- puttyins.ico
+ puttyins.ico pterm.ico ptermcfg.ico
ICNS = PuTTY.icns Pterm.icns
CICONS = xpmputty.c xpmpucfg.c xpmpterm.c xpmptcfg.c
base: icos cicons
-all: pngs monopngs base icns # truepngs currently disabled by default
+all: pngs monopngs base icns svgs # truepngs currently disabled by default
pngs: $(PNGS)
monopngs: $(MONOPNGS)
truepngs: $(TRUEPNGS)
+svgs: $(SVGS)
icos: $(ICOS)
icns: $(ICNS)
@@ -46,6 +49,9 @@ $(MONOPAMS): %.pam: mkicon.py
$(TRUEPAMS): %.pam: mkicon.py
./mkicon.py -T $(MODE) $(join $(subst -, ,$(subst -true,,$(basename $@))),_icon) $@
+$(SVGS): %.svg: mksvg.py
+ ./mksvg.py $(patsubst %.svg,%_icon,$@) -o $@
+
putty.ico: putty-16.png putty-32.png putty-48.png \
putty-16-mono.png putty-32-mono.png putty-48-mono.png
./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@
@@ -69,6 +75,14 @@ pscp.ico: pscp-16.png pscp-32.png pscp-48.png \
pscp-16-mono.png pscp-32-mono.png pscp-48-mono.png
./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@
+pterm.ico: pterm-16.png pterm-32.png pterm-48.png \
+ pterm-16-mono.png pterm-32-mono.png pterm-48-mono.png
+ ./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@
+
+ptermcfg.ico: ptermcfg-16.png ptermcfg-32.png ptermcfg-48.png \
+ ptermcfg-16-mono.png ptermcfg-32-mono.png ptermcfg-48-mono.png
+ ./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@
+
# Because the installer icon makes heavy use of brown when drawing
# the cardboard box, it's worth having 8-bit versions of it in
# addition to the 4- and 1-bit ones.
diff --git a/IMPORT.C b/IMPORT.C
index 553fa750..918de50e 100644
--- a/IMPORT.C
+++ b/IMPORT.C
@@ -174,17 +174,6 @@ bool export_ssh2(const Filename *filename, int type,
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.)
*/
@@ -328,7 +317,7 @@ struct openssh_pem_key {
strbuf *keyblob;
};
-void BinarySink_put_mp_ssh2_from_string(BinarySink *bs, ptrlen str)
+static void BinarySink_put_mp_ssh2_from_string(BinarySink *bs, ptrlen str)
{
const unsigned char *bytes = (const unsigned char *)str.ptr;
size_t nbytes = str.len;
@@ -498,7 +487,7 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src,
if (errmsg_p) *errmsg_p = NULL;
return ret;
- error:
+ error:
if (line) {
smemclr(line, strlen(line));
sfree(line);
@@ -775,7 +764,7 @@ static ssh2_userkey *openssh_pem_read(
*/
assert(privptr > 0); /* should have bombed by now if not */
retkey = snew(ssh2_userkey);
- alg = (key->keytype == OP_RSA ? &ssh_rsa : &ssh_dss);
+ alg = (key->keytype == OP_RSA ? &ssh_rsa : &ssh_dsa);
retkey->key = ssh_key_new_priv(
alg, make_ptrlen(blob->u, privptr),
make_ptrlen(blob->u+privptr, blob->len-privptr));
@@ -801,7 +790,7 @@ static ssh2_userkey *openssh_pem_read(
errmsg = NULL; /* no error */
retval = retkey;
- error:
+ error:
strbuf_free(blob);
strbuf_free(key->keyblob);
smemclr(key, sizeof(*key));
@@ -811,7 +800,7 @@ static ssh2_userkey *openssh_pem_read(
}
static bool openssh_pem_write(
- const Filename *filename, ssh2_userkey *key, const char *passphrase)
+ const Filename *filename, ssh2_userkey *ukey, const char *passphrase)
{
strbuf *pubblob, *privblob, *outblob;
unsigned char *spareblob;
@@ -825,13 +814,17 @@ static bool openssh_pem_write(
FILE *fp;
BinarySource src[1];
+ /* OpenSSH's private key files never contain a certificate, so
+ * revert to the underlying base key if necessary */
+ ssh_key *key = ssh_key_base_key(ukey->key);
+
/*
* Fetch the key blobs.
*/
pubblob = strbuf_new();
- ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob));
+ ssh_key_public_blob(key, BinarySink_UPCAST(pubblob));
privblob = strbuf_new_nm();
- ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob));
+ ssh_key_private_blob(key, BinarySink_UPCAST(privblob));
spareblob = NULL;
outblob = strbuf_new_nm();
@@ -840,18 +833,18 @@ static bool openssh_pem_write(
* 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) {
+ if (ssh_key_alg(key) == &ssh_rsa ||
+ ssh_key_alg(key) == &ssh_dsa) {
strbuf *seq;
/*
- * The RSA and DSS handlers share some code because the two
+ * The RSA and DSA handlers share some code because the two
* key types have very similar ASN.1 representations, as a
* plain SEQUENCE of big integers. So we set up a list of
* bignums per key type and then construct the actual blob in
* common code after that.
*/
- if (ssh_key_alg(key->key) == &ssh_rsa) {
+ if (ssh_key_alg(key) == &ssh_rsa) {
ptrlen n, e, d, p, q, iqmp, dmp1, dmq1;
mp_int *bd, *bp, *bq, *bdmp1, *bdmq1;
@@ -947,11 +940,11 @@ static bool openssh_pem_write(
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) {
+ } else if (ssh_key_alg(key) == &ssh_ecdsa_nistp256 ||
+ ssh_key_alg(key) == &ssh_ecdsa_nistp384 ||
+ ssh_key_alg(key) == &ssh_ecdsa_nistp521) {
const unsigned char *oid;
- struct ecdsa_key *ec = container_of(key->key, struct ecdsa_key, sshk);
+ struct ecdsa_key *ec = container_of(key, struct ecdsa_key, sshk);
int oidlen;
int pointlen;
strbuf *seq, *sub;
@@ -966,7 +959,7 @@ static bool openssh_pem_write(
* [1]
* BIT STRING (0x00 public key point)
*/
- oid = ec_alg_oid(ssh_key_alg(key->key), &oidlen);
+ oid = ec_alg_oid(ssh_key_alg(key), &oidlen);
pointlen = (ec->curve->fieldBits + 7) / 8 * 2;
seq = strbuf_new_nm();
@@ -998,7 +991,7 @@ static bool openssh_pem_write(
/* Append the BIT STRING to the sequence */
put_ber_id_len(seq, 1, sub->len,
- ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED);
+ ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED);
put_data(seq, sub->s, sub->len);
strbuf_free(sub);
@@ -1075,12 +1068,12 @@ static bool openssh_pem_write(
fprintf(fp, "%02X", iv[i]);
fprintf(fp, "\n\n");
}
- base64_encode(fp, outblob->u, outblob->len, 64);
+ base64_encode_fp(fp, ptrlen_from_strbuf(outblob), 64);
fputs(footer, fp);
fclose(fp);
ret = true;
- error:
+ error:
if (outblob)
strbuf_free(outblob);
if (spareblob) {
@@ -1245,8 +1238,8 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
ret->kdfopts.bcrypt.rounds = get_uint32(opts);
if (get_err(opts)) {
- errmsg = "failed to parse bcrypt options string";
- goto error;
+ errmsg = "failed to parse bcrypt options string";
+ goto error;
}
break;
}
@@ -1294,7 +1287,7 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
if (errmsg_p) *errmsg_p = NULL;
return ret;
- error:
+ error:
if (line) {
smemclr(line, strlen(line));
sfree(line);
@@ -1363,9 +1356,8 @@ static ssh2_userkey *openssh_new_read(
memset(keybuf, 0, keysize);
break;
case ON_K_BCRYPT:
- openssh_bcrypt(passphrase,
- key->kdfopts.bcrypt.salt.ptr,
- key->kdfopts.bcrypt.salt.len,
+ openssh_bcrypt(ptrlen_from_asciz(passphrase),
+ key->kdfopts.bcrypt.salt,
key->kdfopts.bcrypt.rounds,
keybuf, keysize);
break;
@@ -1485,7 +1477,7 @@ static ssh2_userkey *openssh_new_read(
retval = retkey;
retkey = NULL; /* prevent the free */
- error:
+ error:
if (retkey) {
sfree(retkey->comment);
if (retkey->key)
@@ -1500,7 +1492,7 @@ static ssh2_userkey *openssh_new_read(
}
static bool openssh_new_write(
- const Filename *filename, ssh2_userkey *key, const char *passphrase)
+ const Filename *filename, ssh2_userkey *ukey, const char *passphrase)
{
strbuf *pubblob, *privblob, *cblob;
int padvalue;
@@ -1510,13 +1502,17 @@ static bool openssh_new_write(
const int bcrypt_rounds = 16;
FILE *fp;
+ /* OpenSSH's private key files never contain a certificate, so
+ * revert to the underlying base key if necessary */
+ ssh_key *key = ssh_key_base_key(ukey->key);
+
/*
* Fetch the key blobs and find out the lengths of things.
*/
pubblob = strbuf_new();
- ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob));
+ ssh_key_public_blob(key, BinarySink_UPCAST(pubblob));
privblob = strbuf_new_nm();
- ssh_key_openssh_blob(key->key, BinarySink_UPCAST(privblob));
+ ssh_key_openssh_blob(key, BinarySink_UPCAST(privblob));
/*
* Construct the cleartext version of the blob.
@@ -1563,11 +1559,11 @@ static bool openssh_new_write(
/* Private key. The main private blob goes inline, with no string
* wrapper. */
- put_stringz(cpblob, ssh_key_ssh_id(key->key));
+ put_stringz(cpblob, ssh_key_ssh_id(key));
put_data(cpblob, privblob->s, privblob->len);
/* Comment. */
- put_stringz(cpblob, key->comment);
+ put_stringz(cpblob, ukey->comment);
/* Pad out the encrypted section. */
padvalue = 1;
@@ -1583,9 +1579,9 @@ static bool openssh_new_write(
unsigned char keybuf[48];
ssh_cipher *cipher;
- openssh_bcrypt(passphrase,
- bcrypt_salt, sizeof(bcrypt_salt), bcrypt_rounds,
- keybuf, sizeof(keybuf));
+ openssh_bcrypt(ptrlen_from_asciz(passphrase),
+ make_ptrlen(bcrypt_salt, sizeof(bcrypt_salt)),
+ bcrypt_rounds, keybuf, sizeof(keybuf));
cipher = ssh_cipher_new(&ssh_aes256_sdctr);
ssh_cipher_setkey(cipher, keybuf);
@@ -1607,12 +1603,12 @@ static bool openssh_new_write(
if (!fp)
goto error;
fputs("-----BEGIN OPENSSH PRIVATE KEY-----\n", fp);
- base64_encode(fp, cblob->u, cblob->len, 64);
+ base64_encode_fp(fp, ptrlen_from_strbuf(cblob), 64);
fputs("-----END OPENSSH PRIVATE KEY-----\n", fp);
fclose(fp);
ret = true;
- error:
+ error:
if (cblob)
strbuf_free(cblob);
if (privblob)
@@ -1634,11 +1630,12 @@ static bool openssh_auto_write(
* 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)
+ const ssh_keyalg *alg = ssh_key_alg(ssh_key_base_key(key->key));
+ if (alg == &ssh_dsa ||
+ alg == &ssh_rsa ||
+ alg == &ssh_ecdsa_nistp256 ||
+ alg == &ssh_ecdsa_nistp384 ||
+ alg == &ssh_ecdsa_nistp521)
return openssh_pem_write(filename, key, passphrase);
else
return openssh_new_write(filename, key, passphrase);
@@ -1846,7 +1843,7 @@ static struct sshcom_key *load_sshcom_key(BinarySource *src,
if (errmsg_p) *errmsg_p = NULL;
return ret;
- error:
+ error:
if (line) {
smemclr(line, strlen(line));
sfree(line);
@@ -1884,7 +1881,7 @@ static bool sshcom_encrypted(BinarySource *filesrc, char **comment)
if (!ptrlen_eq_string(str, "none"))
answer = true;
- done:
+ done:
if (key) {
*comment = dupstr(key->comment);
strbuf_free(key->keyblob);
@@ -1896,7 +1893,7 @@ static bool sshcom_encrypted(BinarySource *filesrc, char **comment)
return answer;
}
-void BinarySink_put_mp_sshcom_from_string(BinarySink *bs, ptrlen str)
+static void BinarySink_put_mp_sshcom_from_string(BinarySink *bs, ptrlen str)
{
const unsigned char *bytes = (const unsigned char *)str.ptr;
size_t nbytes = str.len;
@@ -1980,7 +1977,7 @@ static ssh2_userkey *sshcom_read(
!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)) {
+ !memcmp(str.ptr, prefix_dsa, sizeof(prefix_dsa) - 1)) {
type = DSA;
} else {
errmsg = "key is of unknown type";
@@ -2111,7 +2108,7 @@ static ssh2_userkey *sshcom_read(
goto error;
}
- alg = &ssh_dss;
+ alg = &ssh_dsa;
put_stringz(blob, "ssh-dss");
put_mp_ssh2_from_string(blob, p);
put_mp_ssh2_from_string(blob, q);
@@ -2135,7 +2132,7 @@ static ssh2_userkey *sshcom_read(
errmsg = NULL; /* no error */
ret = retkey;
- error:
+ error:
if (blob) {
strbuf_free(blob);
}
@@ -2202,7 +2199,7 @@ static bool sshcom_write(
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) {
+ } else if (ssh_key_alg(key->key) == &ssh_dsa) {
ptrlen p, q, g, y, x;
/*
@@ -2309,12 +2306,12 @@ static bool sshcom_write(
}
fprintf(fp, "%s\"\n", c);
}
- base64_encode(fp, outblob->u, outblob->len, 70);
+ base64_encode_fp(fp, ptrlen_from_strbuf(outblob), 70);
fputs("---- END SSH2 ENCRYPTED PRIVATE KEY ----\n", fp);
fclose(fp);
ret = true;
- error:
+ error:
if (outblob)
strbuf_free(outblob);
if (privblob)
diff --git a/LATEST.VER b/LATEST.VER
index 07b41f7d..95d2ff57 100644
--- a/LATEST.VER
+++ b/LATEST.VER
@@ -1 +1 @@
-0.76
+0.78
diff --git a/LDISC.C b/LDISC.C
index f097c040..caff52d0 100644
--- a/LDISC.C
+++ b/LDISC.C
@@ -11,7 +11,62 @@
#include "putty.h"
#include "terminal.h"
-#include "ldisc.h"
+
+struct Ldisc_tag {
+ Terminal *term;
+ Backend *backend;
+ Seat *seat;
+
+ /*
+ * When the backend is not reporting true from sendok(), terminal
+ * input that comes here is stored in this bufchain instead. When
+ * the backend later decides it wants session input, we empty the
+ * queue in ldisc_check_sendok_callback(), passing its contents on
+ * to the backend. Before then, we also provide data from this
+ * queue to term_get_userpass_input() via ldisc_get_input_token(),
+ * to be interpreted as user responses to username and password
+ * prompts during authentication.
+ *
+ * Unfortunately, the data stored in this queue is not all of the
+ * same type: our output to the backend consists of both raw bytes
+ * sent to backend_send(), and also session specials such as
+ * SS_EOL and SS_EC. So we have to encode our queued data in a way
+ * that can represent both.
+ *
+ * The encoding is private to this source file, so we can change
+ * it if necessary and only have to worry about the encode and
+ * decode functions here. Currently, it is:
+ *
+ * - Bytes other than 0xFF are stored literally.
+ * - The byte 0xFF itself is stored as 0xFF 0xFF.
+ * - A session special (code, arg) is stored as 0xFF, followed by
+ * a big-endian 4-byte integer containing code, followed by
+ * another big-endian 4-byte integer containing arg.
+ *
+ * (This representation relies on session special codes being at
+ * most 0xFEFFFFFF when represented in 32 bits, so that the first
+ * byte of the 'code' integer can't be confused with the 0xFF
+ * followup byte indicating a literal 0xFF, But since session
+ * special codes are defined by an enum counting up from zero, and
+ * there are only a couple of dozen of them, that shouldn't be a
+ * problem! Even so, just in case, an assertion checks that at
+ * encode time.)
+ */
+ bufchain input_queue;
+
+ IdempotentCallback input_queue_callback;
+ prompts_t *prompts;
+
+ /*
+ * Values cached out of conf.
+ */
+ bool telnet_keyboard, telnet_newline;
+ int protocol, localecho, localedit;
+
+ char *buf;
+ size_t buflen, bufsiz;
+ bool quotenext;
+};
#define ECHOING (ldisc->localecho == FORCE_ON || \
(ldisc->localecho == AUTO && \
@@ -72,6 +127,8 @@ static void bsb(Ldisc *ldisc, int n)
c_write(ldisc, "\010 \010", 3);
}
+static void ldisc_input_queue_callback(void *ctx);
+
#define CTRL(x) (x^'@')
#define KCTRL(x) ((x^'@') | 0x100)
@@ -88,6 +145,14 @@ Ldisc *ldisc_create(Conf *conf, Terminal *term, Backend *backend, Seat *seat)
ldisc->term = term;
ldisc->seat = seat;
+ bufchain_init(&ldisc->input_queue);
+
+ ldisc->prompts = NULL;
+ ldisc->input_queue_callback.fn = ldisc_input_queue_callback;
+ ldisc->input_queue_callback.ctx = ldisc;
+ ldisc->input_queue_callback.queued = false;
+ bufchain_set_callback(&ldisc->input_queue, &ldisc->input_queue_callback);
+
ldisc_configure(ldisc, conf);
/* Link ourselves into the backend and the terminal */
@@ -110,12 +175,16 @@ void ldisc_configure(Ldisc *ldisc, Conf *conf)
void ldisc_free(Ldisc *ldisc)
{
+ bufchain_clear(&ldisc->input_queue);
if (ldisc->term)
ldisc->term->ldisc = NULL;
if (ldisc->backend)
backend_provide_ldisc(ldisc->backend, NULL);
if (ldisc->buf)
sfree(ldisc->buf);
+ if (ldisc->prompts && ldisc->prompts->ldisc_ptr_to_us == &ldisc->prompts)
+ ldisc->prompts->ldisc_ptr_to_us = NULL;
+ delete_callbacks_for_context(ldisc);
sfree(ldisc);
}
@@ -124,6 +193,173 @@ void ldisc_echoedit_update(Ldisc *ldisc)
seat_echoedit_update(ldisc->seat, ECHOING, EDITING);
}
+void ldisc_enable_prompt_callback(Ldisc *ldisc, prompts_t *prompts)
+{
+ /*
+ * Called by the terminal to indicate that there's a prompts_t
+ * currently in flight, or to indicate that one has just finished
+ * (by passing NULL). When ldisc->prompts is not null, we notify
+ * the terminal whenever new data arrives in our input queue, so
+ * that it can continue the interactive prompting process.
+ */
+ ldisc->prompts = prompts;
+ if (prompts)
+ ldisc->prompts->ldisc_ptr_to_us = &ldisc->prompts;
+}
+
+static void ldisc_input_queue_callback(void *ctx)
+{
+ /*
+ * Toplevel callback that is triggered whenever the input queue
+ * lengthens. If we're currently processing an interactive prompt,
+ * we call back the Terminal to tell it to do some more stuff with
+ * that prompt based on the new input.
+ */
+ Ldisc *ldisc = (Ldisc *)ctx;
+ if (ldisc->term && ldisc->prompts) {
+ /*
+ * The integer return value from this call is discarded,
+ * because we have no channel to pass it on to the backend
+ * that originally wanted it. But that's OK, because if the
+ * return value is >= 0 (that is, the prompts are either
+ * completely filled in, or aborted by the user), then the
+ * terminal will notify the callback in the prompts_t, and
+ * when that calls term_get_userpass_input again, it will
+ * return the same answer again.
+ */
+ term_get_userpass_input(ldisc->term, ldisc->prompts);
+ }
+}
+
+static void ldisc_to_backend_raw(
+ Ldisc *ldisc, const void *vbuf, size_t len)
+{
+ if (backend_sendok(ldisc->backend)) {
+ backend_send(ldisc->backend, vbuf, len);
+ } else {
+ const char *buf = (const char *)vbuf;
+ while (len > 0) {
+ /*
+ * Encode raw data in input_queue, by storing large chunks
+ * as long as they don't include 0xFF, and pausing every
+ * time they do to escape it.
+ */
+ const char *ff = memchr(buf, '\xFF', len);
+ size_t this_len = ff ? ff - buf : len;
+ if (this_len > 0) {
+ bufchain_add(&ldisc->input_queue, buf, len);
+ } else {
+ bufchain_add(&ldisc->input_queue, "\xFF\xFF", 2);
+ this_len = 1;
+ }
+ buf += this_len;
+ len -= this_len;
+ }
+ }
+}
+
+static void ldisc_to_backend_special(
+ Ldisc *ldisc, SessionSpecialCode code, int arg)
+{
+ if (backend_sendok(ldisc->backend)) {
+ backend_special(ldisc->backend, code, arg);
+ } else {
+ /*
+ * Encode a session special in input_queue.
+ */
+ unsigned char data[9];
+ data[0] = 0xFF;
+ PUT_32BIT_MSB_FIRST(data+1, code);
+ PUT_32BIT_MSB_FIRST(data+5, arg);
+ assert(data[1] != 0xFF &&
+ "SessionSpecialCode encoding collides with FF FF escape");
+ bufchain_add(&ldisc->input_queue, data, 9);
+ }
+}
+
+bool ldisc_has_input_buffered(Ldisc *ldisc)
+{
+ return bufchain_size(&ldisc->input_queue) > 0;
+}
+
+LdiscInputToken ldisc_get_input_token(Ldisc *ldisc)
+{
+ assert(bufchain_size(&ldisc->input_queue) > 0 &&
+ "You're not supposed to call this unless there is buffered input!");
+
+ LdiscInputToken tok;
+
+ char c;
+ bufchain_fetch_consume(&ldisc->input_queue, &c, 1);
+ if (c != '\xFF') {
+ /* A literal non-FF byte */
+ tok.is_special = false;
+ tok.chr = c;
+ return tok;
+ } else {
+ char data[8];
+
+ /* See if the byte after the FF is also FF, indicating a literal FF */
+ bufchain_fetch_consume(&ldisc->input_queue, data, 1);
+ if (data[0] == '\xFF') {
+ tok.is_special = false;
+ tok.chr = '\xFF';
+ return tok;
+ }
+
+ /* If not, get the rest of an 8-byte chunk and decode a special */
+ bufchain_fetch_consume(&ldisc->input_queue, data+1, 7);
+ tok.is_special = true;
+ tok.code = GET_32BIT_MSB_FIRST(data);
+ tok.arg = toint(GET_32BIT_MSB_FIRST(data+4));
+ return tok;
+ }
+}
+
+static void ldisc_check_sendok_callback(void *ctx)
+{
+ Ldisc *ldisc = (Ldisc *)ctx;
+
+ if (!(ldisc->backend && backend_sendok(ldisc->backend)))
+ return;
+
+ /*
+ * Flush the ldisc input queue into the backend, which is now
+ * willing to receive the data.
+ */
+ while (bufchain_size(&ldisc->input_queue) > 0) {
+ /*
+ * Process either a chunk of non-special data, or an FF
+ * escape, depending on whether the first thing we see is an
+ * FF byte.
+ */
+ ptrlen data = bufchain_prefix(&ldisc->input_queue);
+ const char *ff = memchr(data.ptr, '\xFF', data.len);
+ if (ff != data.ptr) {
+ /* Send a maximal block of data not containing any
+ * difficult bytes. */
+ if (ff)
+ data.len = ff - (const char *)data.ptr;
+ backend_send(ldisc->backend, data.ptr, data.len);
+ bufchain_consume(&ldisc->input_queue, data.len);
+ } else {
+ /* Decode either a special or an escaped FF byte. The
+ * easiest way to do this is to reuse the decoding code
+ * already in ldisc_get_input_token. */
+ LdiscInputToken tok = ldisc_get_input_token(ldisc);
+ if (tok.is_special)
+ backend_special(ldisc->backend, tok.code, tok.arg);
+ else
+ backend_send(ldisc->backend, &tok.chr, 1);
+ }
+ }
+}
+
+void ldisc_check_sendok(Ldisc *ldisc)
+{
+ queue_toplevel_callback(ldisc_check_sendok_callback, ldisc);
+}
+
void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
{
const char *buf = (const char *)vbuf;
@@ -206,7 +442,7 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
ldisc->buflen--;
}
- backend_special(ldisc->backend, SS_EL, 0);
+ ldisc_to_backend_special(ldisc, SS_EL, 0);
/*
* We don't send IP, SUSP or ABORT if the user has
* configured telnet specials off! This breaks
@@ -215,11 +451,11 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
if (!ldisc->telnet_keyboard)
goto default_case;
if (c == CTRL('C'))
- backend_special(ldisc->backend, SS_IP, 0);
+ ldisc_to_backend_special(ldisc, SS_IP, 0);
if (c == CTRL('Z'))
- backend_special(ldisc->backend, SS_SUSP, 0);
+ ldisc_to_backend_special(ldisc, SS_SUSP, 0);
if (c == CTRL('\\'))
- backend_special(ldisc->backend, SS_ABORT, 0);
+ ldisc_to_backend_special(ldisc, SS_ABORT, 0);
break;
case CTRL('R'): /* redraw line */
if (ECHOING) {
@@ -234,9 +470,9 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
break;
case CTRL('D'): /* logout or send */
if (ldisc->buflen == 0) {
- backend_special(ldisc->backend, SS_EOF, 0);
+ ldisc_to_backend_special(ldisc, SS_EOF, 0);
} else {
- backend_send(ldisc->backend, ldisc->buf, ldisc->buflen);
+ ldisc_to_backend_raw(ldisc, ldisc->buf, ldisc->buflen);
ldisc->buflen = 0;
}
break;
@@ -272,14 +508,13 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
/* FALLTHROUGH */
case KCTRL('M'): /* send with newline */
if (ldisc->buflen > 0)
- backend_send(ldisc->backend,
- ldisc->buf, ldisc->buflen);
+ ldisc_to_backend_raw(ldisc, ldisc->buf, ldisc->buflen);
if (ldisc->protocol == PROT_RAW)
- backend_send(ldisc->backend, "\r\n", 2);
+ ldisc_to_backend_raw(ldisc, "\r\n", 2);
else if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline)
- backend_special(ldisc->backend, SS_EOL, 0);
+ ldisc_to_backend_special(ldisc, SS_EOL, 0);
else
- backend_send(ldisc->backend, "\r", 1);
+ ldisc_to_backend_raw(ldisc, "\r", 1);
if (ECHOING)
c_write(ldisc, "\r\n", 2);
ldisc->buflen = 0;
@@ -287,7 +522,7 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
}
/* FALLTHROUGH */
default: /* get to this label from ^V handler */
- default_case:
+ default_case:
sgrowarray(ldisc->buf, ldisc->bufsiz, ldisc->buflen);
ldisc->buf[ldisc->buflen++] = c;
if (ECHOING)
@@ -298,7 +533,7 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
}
} else {
if (ldisc->buflen != 0) {
- backend_send(ldisc->backend, ldisc->buf, ldisc->buflen);
+ ldisc_to_backend_raw(ldisc, ldisc->buf, ldisc->buflen);
while (ldisc->buflen > 0) {
bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
ldisc->buflen--;
@@ -311,33 +546,33 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
switch (buf[0]) {
case CTRL('M'):
if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline)
- backend_special(ldisc->backend, SS_EOL, 0);
+ ldisc_to_backend_special(ldisc, SS_EOL, 0);
else
- backend_send(ldisc->backend, "\r", 1);
+ ldisc_to_backend_raw(ldisc, "\r", 1);
break;
case CTRL('?'):
case CTRL('H'):
if (ldisc->telnet_keyboard) {
- backend_special(ldisc->backend, SS_EC, 0);
+ ldisc_to_backend_special(ldisc, SS_EC, 0);
break;
}
case CTRL('C'):
if (ldisc->telnet_keyboard) {
- backend_special(ldisc->backend, SS_IP, 0);
+ ldisc_to_backend_special(ldisc, SS_IP, 0);
break;
}
case CTRL('Z'):
if (ldisc->telnet_keyboard) {
- backend_special(ldisc->backend, SS_SUSP, 0);
+ ldisc_to_backend_special(ldisc, SS_SUSP, 0);
break;
}
default:
- backend_send(ldisc->backend, buf, len);
+ ldisc_to_backend_raw(ldisc, buf, len);
break;
}
} else
- backend_send(ldisc->backend, buf, len);
+ ldisc_to_backend_raw(ldisc, buf, len);
}
}
}
diff --git a/LDISC.H b/LDISC.H
deleted file mode 100644
index 770b4b05..00000000
--- a/LDISC.H
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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/LICENCE b/LICENCE
index 0e1df3c5..57ea292b 100644
--- a/LICENCE
+++ b/LICENCE
@@ -1,4 +1,4 @@
-PuTTY is copyright 1997-2021 Simon Tatham.
+PuTTY is copyright 1997-2022 Simon Tatham.
Portions copyright Robert de Bath, Joris van Rantwijk, Delian
Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry,
diff --git a/LOGGING.C b/LOGGING.C
index 31cbccfb..e065f1a4 100644
--- a/LOGGING.C
+++ b/LOGGING.C
@@ -81,6 +81,11 @@ void logflush(LogContext *ctx)
fflush(ctx->lgfp);
}
+LogPolicy *log_get_policy(LogContext *ctx)
+{
+ return ctx->lp;
+}
+
static void logfopen_callback(void *vctx, int mode)
{
LogContext *ctx = (LogContext *)vctx;
diff --git a/MINIBIDI.C b/MINIBIDI.C
deleted file mode 100644
index 05d15b3d..00000000
--- a/MINIBIDI.C
+++ /dev/null
@@ -1,2025 +0,0 @@
-/************************************************************************
- *
- * ------------
- * 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 <stdlib.h> /* 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<count && j<count) {
-
- /* find the start of the run of level=max */
- tlevel = max;
- i = j = findIndexOfRun(level, i, count, max);
- /* find the end of the run */
- while (i<count && tlevel <= level[i]) {
- i++;
- }
- for (k = i - 1; k > 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<count; i++) {
- if (tlevel == level[i]) {
- return i;
- }
- }
- return count;
-}
-
-/*
- * Returns the bidi character type of ch.
- *
- * The data table in this function is constructed from the Unicode
- * Character Database, downloadable from unicode.org at the URL
- *
- * http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
- *
- * by the following fragment of Perl:
-
-perl -ne 'split ";"; $num = hex $_[0]; $type = $_[4];' \
- -e '$fl = ($_[1] =~ /First/ ? 1 : $_[1] =~ /Last/ ? 2 : 0);' \
- -e 'if ($type eq $runtype and ($runend == $num-1 or ' \
- -e ' ($fl==2 and $pfl==1))) {$runend = $num;} else { &reset; }' \
- -e '$pfl=$fl; END { &reset }; sub reset {' \
- -e 'printf" {0x%04x, 0x%04x, %s},\n",$runstart,$runend,$runtype' \
- -e ' if defined $runstart and $runtype ne "ON";' \
- -e '$runstart=$runend=$num; $runtype=$type;}' \
- UnicodeData.txt
-
- */
-static unsigned char getType(int ch)
-{
- static const struct {
- int first, last, type;
- } lookup[] = {
- {0x0000, 0x0008, BN},
- {0x0009, 0x0009, S},
- {0x000a, 0x000a, B},
- {0x000b, 0x000b, S},
- {0x000c, 0x000c, WS},
- {0x000d, 0x000d, B},
- {0x000e, 0x001b, BN},
- {0x001c, 0x001e, B},
- {0x001f, 0x001f, S},
- {0x0020, 0x0020, WS},
- {0x0023, 0x0025, ET},
- {0x002b, 0x002b, ES},
- {0x002c, 0x002c, CS},
- {0x002d, 0x002d, ES},
- {0x002e, 0x002f, CS},
- {0x0030, 0x0039, EN},
- {0x003a, 0x003a, CS},
- {0x0041, 0x005a, L},
- {0x0061, 0x007a, L},
- {0x007f, 0x0084, BN},
- {0x0085, 0x0085, B},
- {0x0086, 0x009f, BN},
- {0x00a0, 0x00a0, CS},
- {0x00a2, 0x00a5, ET},
- {0x00aa, 0x00aa, L},
- {0x00ad, 0x00ad, BN},
- {0x00b0, 0x00b1, ET},
- {0x00b2, 0x00b3, EN},
- {0x00b5, 0x00b5, L},
- {0x00b9, 0x00b9, EN},
- {0x00ba, 0x00ba, L},
- {0x00c0, 0x00d6, L},
- {0x00d8, 0x00f6, L},
- {0x00f8, 0x0236, L},
- {0x0250, 0x02b8, L},
- {0x02bb, 0x02c1, L},
- {0x02d0, 0x02d1, L},
- {0x02e0, 0x02e4, L},
- {0x02ee, 0x02ee, L},
- {0x0300, 0x0357, NSM},
- {0x035d, 0x036f, NSM},
- {0x037a, 0x037a, L},
- {0x0386, 0x0386, L},
- {0x0388, 0x038a, L},
- {0x038c, 0x038c, L},
- {0x038e, 0x03a1, L},
- {0x03a3, 0x03ce, L},
- {0x03d0, 0x03f5, L},
- {0x03f7, 0x03fb, L},
- {0x0400, 0x0482, L},
- {0x0483, 0x0486, NSM},
- {0x0488, 0x0489, NSM},
- {0x048a, 0x04ce, L},
- {0x04d0, 0x04f5, L},
- {0x04f8, 0x04f9, L},
- {0x0500, 0x050f, L},
- {0x0531, 0x0556, L},
- {0x0559, 0x055f, L},
- {0x0561, 0x0587, L},
- {0x0589, 0x0589, L},
- {0x0591, 0x05a1, NSM},
- {0x05a3, 0x05b9, NSM},
- {0x05bb, 0x05bd, NSM},
- {0x05be, 0x05be, R},
- {0x05bf, 0x05bf, NSM},
- {0x05c0, 0x05c0, R},
- {0x05c1, 0x05c2, NSM},
- {0x05c3, 0x05c3, R},
- {0x05c4, 0x05c4, NSM},
- {0x05d0, 0x05ea, R},
- {0x05f0, 0x05f4, R},
- {0x0600, 0x0603, AL},
- {0x060c, 0x060c, CS},
- {0x060d, 0x060d, AL},
- {0x0610, 0x0615, NSM},
- {0x061b, 0x061b, AL},
- {0x061f, 0x061f, AL},
- {0x0621, 0x063a, AL},
- {0x0640, 0x064a, AL},
- {0x064b, 0x0658, NSM},
- {0x0660, 0x0669, AN},
- {0x066a, 0x066a, ET},
- {0x066b, 0x066c, AN},
- {0x066d, 0x066f, AL},
- {0x0670, 0x0670, NSM},
- {0x0671, 0x06d5, AL},
- {0x06d6, 0x06dc, NSM},
- {0x06dd, 0x06dd, AL},
- {0x06de, 0x06e4, NSM},
- {0x06e5, 0x06e6, AL},
- {0x06e7, 0x06e8, NSM},
- {0x06ea, 0x06ed, NSM},
- {0x06ee, 0x06ef, AL},
- {0x06f0, 0x06f9, EN},
- {0x06fa, 0x070d, AL},
- {0x070f, 0x070f, BN},
- {0x0710, 0x0710, AL},
- {0x0711, 0x0711, NSM},
- {0x0712, 0x072f, AL},
- {0x0730, 0x074a, NSM},
- {0x074d, 0x074f, AL},
- {0x0780, 0x07a5, AL},
- {0x07a6, 0x07b0, NSM},
- {0x07b1, 0x07b1, AL},
- {0x0901, 0x0902, NSM},
- {0x0903, 0x0939, L},
- {0x093c, 0x093c, NSM},
- {0x093d, 0x0940, L},
- {0x0941, 0x0948, NSM},
- {0x0949, 0x094c, L},
- {0x094d, 0x094d, NSM},
- {0x0950, 0x0950, L},
- {0x0951, 0x0954, NSM},
- {0x0958, 0x0961, L},
- {0x0962, 0x0963, NSM},
- {0x0964, 0x0970, L},
- {0x0981, 0x0981, NSM},
- {0x0982, 0x0983, L},
- {0x0985, 0x098c, L},
- {0x098f, 0x0990, L},
- {0x0993, 0x09a8, L},
- {0x09aa, 0x09b0, L},
- {0x09b2, 0x09b2, L},
- {0x09b6, 0x09b9, L},
- {0x09bc, 0x09bc, NSM},
- {0x09bd, 0x09c0, L},
- {0x09c1, 0x09c4, NSM},
- {0x09c7, 0x09c8, L},
- {0x09cb, 0x09cc, L},
- {0x09cd, 0x09cd, NSM},
- {0x09d7, 0x09d7, L},
- {0x09dc, 0x09dd, L},
- {0x09df, 0x09e1, L},
- {0x09e2, 0x09e3, NSM},
- {0x09e6, 0x09f1, L},
- {0x09f2, 0x09f3, ET},
- {0x09f4, 0x09fa, L},
- {0x0a01, 0x0a02, NSM},
- {0x0a03, 0x0a03, L},
- {0x0a05, 0x0a0a, L},
- {0x0a0f, 0x0a10, L},
- {0x0a13, 0x0a28, L},
- {0x0a2a, 0x0a30, L},
- {0x0a32, 0x0a33, L},
- {0x0a35, 0x0a36, L},
- {0x0a38, 0x0a39, L},
- {0x0a3c, 0x0a3c, NSM},
- {0x0a3e, 0x0a40, L},
- {0x0a41, 0x0a42, NSM},
- {0x0a47, 0x0a48, NSM},
- {0x0a4b, 0x0a4d, NSM},
- {0x0a59, 0x0a5c, L},
- {0x0a5e, 0x0a5e, L},
- {0x0a66, 0x0a6f, L},
- {0x0a70, 0x0a71, NSM},
- {0x0a72, 0x0a74, L},
- {0x0a81, 0x0a82, NSM},
- {0x0a83, 0x0a83, L},
- {0x0a85, 0x0a8d, L},
- {0x0a8f, 0x0a91, L},
- {0x0a93, 0x0aa8, L},
- {0x0aaa, 0x0ab0, L},
- {0x0ab2, 0x0ab3, L},
- {0x0ab5, 0x0ab9, L},
- {0x0abc, 0x0abc, NSM},
- {0x0abd, 0x0ac0, L},
- {0x0ac1, 0x0ac5, NSM},
- {0x0ac7, 0x0ac8, NSM},
- {0x0ac9, 0x0ac9, L},
- {0x0acb, 0x0acc, L},
- {0x0acd, 0x0acd, NSM},
- {0x0ad0, 0x0ad0, L},
- {0x0ae0, 0x0ae1, L},
- {0x0ae2, 0x0ae3, NSM},
- {0x0ae6, 0x0aef, L},
- {0x0af1, 0x0af1, ET},
- {0x0b01, 0x0b01, NSM},
- {0x0b02, 0x0b03, L},
- {0x0b05, 0x0b0c, L},
- {0x0b0f, 0x0b10, L},
- {0x0b13, 0x0b28, L},
- {0x0b2a, 0x0b30, L},
- {0x0b32, 0x0b33, L},
- {0x0b35, 0x0b39, L},
- {0x0b3c, 0x0b3c, NSM},
- {0x0b3d, 0x0b3e, L},
- {0x0b3f, 0x0b3f, NSM},
- {0x0b40, 0x0b40, L},
- {0x0b41, 0x0b43, NSM},
- {0x0b47, 0x0b48, L},
- {0x0b4b, 0x0b4c, L},
- {0x0b4d, 0x0b4d, NSM},
- {0x0b56, 0x0b56, NSM},
- {0x0b57, 0x0b57, L},
- {0x0b5c, 0x0b5d, L},
- {0x0b5f, 0x0b61, L},
- {0x0b66, 0x0b71, L},
- {0x0b82, 0x0b82, NSM},
- {0x0b83, 0x0b83, L},
- {0x0b85, 0x0b8a, L},
- {0x0b8e, 0x0b90, L},
- {0x0b92, 0x0b95, L},
- {0x0b99, 0x0b9a, L},
- {0x0b9c, 0x0b9c, L},
- {0x0b9e, 0x0b9f, L},
- {0x0ba3, 0x0ba4, L},
- {0x0ba8, 0x0baa, L},
- {0x0bae, 0x0bb5, L},
- {0x0bb7, 0x0bb9, L},
- {0x0bbe, 0x0bbf, L},
- {0x0bc0, 0x0bc0, NSM},
- {0x0bc1, 0x0bc2, L},
- {0x0bc6, 0x0bc8, L},
- {0x0bca, 0x0bcc, L},
- {0x0bcd, 0x0bcd, NSM},
- {0x0bd7, 0x0bd7, L},
- {0x0be7, 0x0bf2, L},
- {0x0bf9, 0x0bf9, ET},
- {0x0c01, 0x0c03, L},
- {0x0c05, 0x0c0c, L},
- {0x0c0e, 0x0c10, L},
- {0x0c12, 0x0c28, L},
- {0x0c2a, 0x0c33, L},
- {0x0c35, 0x0c39, L},
- {0x0c3e, 0x0c40, NSM},
- {0x0c41, 0x0c44, L},
- {0x0c46, 0x0c48, NSM},
- {0x0c4a, 0x0c4d, NSM},
- {0x0c55, 0x0c56, NSM},
- {0x0c60, 0x0c61, L},
- {0x0c66, 0x0c6f, L},
- {0x0c82, 0x0c83, L},
- {0x0c85, 0x0c8c, L},
- {0x0c8e, 0x0c90, L},
- {0x0c92, 0x0ca8, L},
- {0x0caa, 0x0cb3, L},
- {0x0cb5, 0x0cb9, L},
- {0x0cbc, 0x0cbc, NSM},
- {0x0cbd, 0x0cc4, L},
- {0x0cc6, 0x0cc8, L},
- {0x0cca, 0x0ccb, L},
- {0x0ccc, 0x0ccd, NSM},
- {0x0cd5, 0x0cd6, L},
- {0x0cde, 0x0cde, L},
- {0x0ce0, 0x0ce1, L},
- {0x0ce6, 0x0cef, L},
- {0x0d02, 0x0d03, L},
- {0x0d05, 0x0d0c, L},
- {0x0d0e, 0x0d10, L},
- {0x0d12, 0x0d28, L},
- {0x0d2a, 0x0d39, L},
- {0x0d3e, 0x0d40, L},
- {0x0d41, 0x0d43, NSM},
- {0x0d46, 0x0d48, L},
- {0x0d4a, 0x0d4c, L},
- {0x0d4d, 0x0d4d, NSM},
- {0x0d57, 0x0d57, L},
- {0x0d60, 0x0d61, L},
- {0x0d66, 0x0d6f, L},
- {0x0d82, 0x0d83, L},
- {0x0d85, 0x0d96, L},
- {0x0d9a, 0x0db1, L},
- {0x0db3, 0x0dbb, L},
- {0x0dbd, 0x0dbd, L},
- {0x0dc0, 0x0dc6, L},
- {0x0dca, 0x0dca, NSM},
- {0x0dcf, 0x0dd1, L},
- {0x0dd2, 0x0dd4, NSM},
- {0x0dd6, 0x0dd6, NSM},
- {0x0dd8, 0x0ddf, L},
- {0x0df2, 0x0df4, L},
- {0x0e01, 0x0e30, L},
- {0x0e31, 0x0e31, NSM},
- {0x0e32, 0x0e33, L},
- {0x0e34, 0x0e3a, NSM},
- {0x0e3f, 0x0e3f, ET},
- {0x0e40, 0x0e46, L},
- {0x0e47, 0x0e4e, NSM},
- {0x0e4f, 0x0e5b, L},
- {0x0e81, 0x0e82, L},
- {0x0e84, 0x0e84, L},
- {0x0e87, 0x0e88, L},
- {0x0e8a, 0x0e8a, L},
- {0x0e8d, 0x0e8d, L},
- {0x0e94, 0x0e97, L},
- {0x0e99, 0x0e9f, L},
- {0x0ea1, 0x0ea3, L},
- {0x0ea5, 0x0ea5, L},
- {0x0ea7, 0x0ea7, L},
- {0x0eaa, 0x0eab, L},
- {0x0ead, 0x0eb0, L},
- {0x0eb1, 0x0eb1, NSM},
- {0x0eb2, 0x0eb3, L},
- {0x0eb4, 0x0eb9, NSM},
- {0x0ebb, 0x0ebc, NSM},
- {0x0ebd, 0x0ebd, L},
- {0x0ec0, 0x0ec4, L},
- {0x0ec6, 0x0ec6, L},
- {0x0ec8, 0x0ecd, NSM},
- {0x0ed0, 0x0ed9, L},
- {0x0edc, 0x0edd, L},
- {0x0f00, 0x0f17, L},
- {0x0f18, 0x0f19, NSM},
- {0x0f1a, 0x0f34, L},
- {0x0f35, 0x0f35, NSM},
- {0x0f36, 0x0f36, L},
- {0x0f37, 0x0f37, NSM},
- {0x0f38, 0x0f38, L},
- {0x0f39, 0x0f39, NSM},
- {0x0f3e, 0x0f47, L},
- {0x0f49, 0x0f6a, L},
- {0x0f71, 0x0f7e, NSM},
- {0x0f7f, 0x0f7f, L},
- {0x0f80, 0x0f84, NSM},
- {0x0f85, 0x0f85, L},
- {0x0f86, 0x0f87, NSM},
- {0x0f88, 0x0f8b, L},
- {0x0f90, 0x0f97, NSM},
- {0x0f99, 0x0fbc, NSM},
- {0x0fbe, 0x0fc5, L},
- {0x0fc6, 0x0fc6, NSM},
- {0x0fc7, 0x0fcc, L},
- {0x0fcf, 0x0fcf, L},
- {0x1000, 0x1021, L},
- {0x1023, 0x1027, L},
- {0x1029, 0x102a, L},
- {0x102c, 0x102c, L},
- {0x102d, 0x1030, NSM},
- {0x1031, 0x1031, L},
- {0x1032, 0x1032, NSM},
- {0x1036, 0x1037, NSM},
- {0x1038, 0x1038, L},
- {0x1039, 0x1039, NSM},
- {0x1040, 0x1057, L},
- {0x1058, 0x1059, NSM},
- {0x10a0, 0x10c5, L},
- {0x10d0, 0x10f8, L},
- {0x10fb, 0x10fb, L},
- {0x1100, 0x1159, L},
- {0x115f, 0x11a2, L},
- {0x11a8, 0x11f9, L},
- {0x1200, 0x1206, L},
- {0x1208, 0x1246, L},
- {0x1248, 0x1248, L},
- {0x124a, 0x124d, L},
- {0x1250, 0x1256, L},
- {0x1258, 0x1258, L},
- {0x125a, 0x125d, L},
- {0x1260, 0x1286, L},
- {0x1288, 0x1288, L},
- {0x128a, 0x128d, L},
- {0x1290, 0x12ae, L},
- {0x12b0, 0x12b0, L},
- {0x12b2, 0x12b5, L},
- {0x12b8, 0x12be, L},
- {0x12c0, 0x12c0, L},
- {0x12c2, 0x12c5, L},
- {0x12c8, 0x12ce, L},
- {0x12d0, 0x12d6, L},
- {0x12d8, 0x12ee, L},
- {0x12f0, 0x130e, L},
- {0x1310, 0x1310, L},
- {0x1312, 0x1315, L},
- {0x1318, 0x131e, L},
- {0x1320, 0x1346, L},
- {0x1348, 0x135a, L},
- {0x1361, 0x137c, L},
- {0x13a0, 0x13f4, L},
- {0x1401, 0x1676, L},
- {0x1680, 0x1680, WS},
- {0x1681, 0x169a, L},
- {0x16a0, 0x16f0, L},
- {0x1700, 0x170c, L},
- {0x170e, 0x1711, L},
- {0x1712, 0x1714, NSM},
- {0x1720, 0x1731, L},
- {0x1732, 0x1734, NSM},
- {0x1735, 0x1736, L},
- {0x1740, 0x1751, L},
- {0x1752, 0x1753, NSM},
- {0x1760, 0x176c, L},
- {0x176e, 0x1770, L},
- {0x1772, 0x1773, NSM},
- {0x1780, 0x17b6, L},
- {0x17b7, 0x17bd, NSM},
- {0x17be, 0x17c5, L},
- {0x17c6, 0x17c6, NSM},
- {0x17c7, 0x17c8, L},
- {0x17c9, 0x17d3, NSM},
- {0x17d4, 0x17da, L},
- {0x17db, 0x17db, ET},
- {0x17dc, 0x17dc, L},
- {0x17dd, 0x17dd, NSM},
- {0x17e0, 0x17e9, L},
- {0x180b, 0x180d, NSM},
- {0x180e, 0x180e, WS},
- {0x1810, 0x1819, L},
- {0x1820, 0x1877, L},
- {0x1880, 0x18a8, L},
- {0x18a9, 0x18a9, NSM},
- {0x1900, 0x191c, L},
- {0x1920, 0x1922, NSM},
- {0x1923, 0x1926, L},
- {0x1927, 0x192b, NSM},
- {0x1930, 0x1931, L},
- {0x1932, 0x1932, NSM},
- {0x1933, 0x1938, L},
- {0x1939, 0x193b, NSM},
- {0x1946, 0x196d, L},
- {0x1970, 0x1974, L},
- {0x1d00, 0x1d6b, L},
- {0x1e00, 0x1e9b, L},
- {0x1ea0, 0x1ef9, L},
- {0x1f00, 0x1f15, L},
- {0x1f18, 0x1f1d, L},
- {0x1f20, 0x1f45, L},
- {0x1f48, 0x1f4d, L},
- {0x1f50, 0x1f57, L},
- {0x1f59, 0x1f59, L},
- {0x1f5b, 0x1f5b, L},
- {0x1f5d, 0x1f5d, L},
- {0x1f5f, 0x1f7d, L},
- {0x1f80, 0x1fb4, L},
- {0x1fb6, 0x1fbc, L},
- {0x1fbe, 0x1fbe, L},
- {0x1fc2, 0x1fc4, L},
- {0x1fc6, 0x1fcc, L},
- {0x1fd0, 0x1fd3, L},
- {0x1fd6, 0x1fdb, L},
- {0x1fe0, 0x1fec, L},
- {0x1ff2, 0x1ff4, L},
- {0x1ff6, 0x1ffc, L},
- {0x2000, 0x200a, WS},
- {0x200b, 0x200d, BN},
- {0x200e, 0x200e, L},
- {0x200f, 0x200f, R},
- {0x2028, 0x2028, WS},
- {0x2029, 0x2029, B},
- {0x202a, 0x202a, LRE},
- {0x202b, 0x202b, RLE},
- {0x202c, 0x202c, PDF},
- {0x202d, 0x202d, LRO},
- {0x202e, 0x202e, RLO},
- {0x202f, 0x202f, WS},
- {0x2030, 0x2034, ET},
- {0x2044, 0x2044, CS},
- {0x205f, 0x205f, WS},
- {0x2060, 0x2063, BN},
- {0x206a, 0x206f, BN},
- {0x2070, 0x2070, EN},
- {0x2071, 0x2071, L},
- {0x2074, 0x2079, EN},
- {0x207a, 0x207b, ET},
- {0x207f, 0x207f, L},
- {0x2080, 0x2089, EN},
- {0x208a, 0x208b, ET},
- {0x20a0, 0x20b1, ET},
- {0x20d0, 0x20ea, NSM},
- {0x2102, 0x2102, L},
- {0x2107, 0x2107, L},
- {0x210a, 0x2113, L},
- {0x2115, 0x2115, L},
- {0x2119, 0x211d, L},
- {0x2124, 0x2124, L},
- {0x2126, 0x2126, L},
- {0x2128, 0x2128, L},
- {0x212a, 0x212d, L},
- {0x212e, 0x212e, ET},
- {0x212f, 0x2131, L},
- {0x2133, 0x2139, L},
- {0x213d, 0x213f, L},
- {0x2145, 0x2149, L},
- {0x2160, 0x2183, L},
- {0x2212, 0x2213, ET},
- {0x2336, 0x237a, L},
- {0x2395, 0x2395, L},
- {0x2488, 0x249b, EN},
- {0x249c, 0x24e9, L},
- {0x2800, 0x28ff, L},
- {0x3000, 0x3000, WS},
- {0x3005, 0x3007, L},
- {0x3021, 0x3029, L},
- {0x302a, 0x302f, NSM},
- {0x3031, 0x3035, L},
- {0x3038, 0x303c, L},
- {0x3041, 0x3096, L},
- {0x3099, 0x309a, NSM},
- {0x309d, 0x309f, L},
- {0x30a1, 0x30fa, L},
- {0x30fc, 0x30ff, L},
- {0x3105, 0x312c, L},
- {0x3131, 0x318e, L},
- {0x3190, 0x31b7, L},
- {0x31f0, 0x321c, L},
- {0x3220, 0x3243, L},
- {0x3260, 0x327b, L},
- {0x327f, 0x32b0, L},
- {0x32c0, 0x32cb, L},
- {0x32d0, 0x32fe, L},
- {0x3300, 0x3376, L},
- {0x337b, 0x33dd, L},
- {0x33e0, 0x33fe, L},
- {0x3400, 0x4db5, L},
- {0x4e00, 0x9fa5, L},
- {0xa000, 0xa48c, L},
- {0xac00, 0xd7a3, L},
- {0xd800, 0xdff7, L},
- {0xe000, 0xfa2d, L},
- {0xfa30, 0xfa6a, L},
- {0xfb00, 0xfb06, L},
- {0xfb13, 0xfb17, L},
- {0xfb1d, 0xfb1d, R},
- {0xfb1e, 0xfb1e, NSM},
- {0xfb1f, 0xfb28, R},
- {0xfb29, 0xfb29, ET},
- {0xfb2a, 0xfb36, R},
- {0xfb38, 0xfb3c, R},
- {0xfb3e, 0xfb3e, R},
- {0xfb40, 0xfb41, R},
- {0xfb43, 0xfb44, R},
- {0xfb46, 0xfb4f, R},
- {0xfb50, 0xfbb1, AL},
- {0xfbd3, 0xfd3d, AL},
- {0xfd50, 0xfd8f, AL},
- {0xfd92, 0xfdc7, AL},
- {0xfdf0, 0xfdfc, AL},
- {0xfe00, 0xfe0f, NSM},
- {0xfe20, 0xfe23, NSM},
- {0xfe50, 0xfe50, CS},
- {0xfe52, 0xfe52, CS},
- {0xfe55, 0xfe55, CS},
- {0xfe5f, 0xfe5f, ET},
- {0xfe62, 0xfe63, ET},
- {0xfe69, 0xfe6a, ET},
- {0xfe70, 0xfe74, AL},
- {0xfe76, 0xfefc, AL},
- {0xfeff, 0xfeff, BN},
- {0xff03, 0xff05, ET},
- {0xff0b, 0xff0b, ET},
- {0xff0c, 0xff0c, CS},
- {0xff0d, 0xff0d, ET},
- {0xff0e, 0xff0e, CS},
- {0xff0f, 0xff0f, ES},
- {0xff10, 0xff19, EN},
- {0xff1a, 0xff1a, CS},
- {0xff21, 0xff3a, L},
- {0xff41, 0xff5a, L},
- {0xff66, 0xffbe, L},
- {0xffc2, 0xffc7, L},
- {0xffca, 0xffcf, L},
- {0xffd2, 0xffd7, L},
- {0xffda, 0xffdc, L},
- {0xffe0, 0xffe1, ET},
- {0xffe5, 0xffe6, ET},
- {0x10000, 0x1000b, L},
- {0x1000d, 0x10026, L},
- {0x10028, 0x1003a, L},
- {0x1003c, 0x1003d, L},
- {0x1003f, 0x1004d, L},
- {0x10050, 0x1005d, L},
- {0x10080, 0x100fa, L},
- {0x10100, 0x10100, L},
- {0x10102, 0x10102, L},
- {0x10107, 0x10133, L},
- {0x10137, 0x1013f, L},
- {0x10300, 0x1031e, L},
- {0x10320, 0x10323, L},
- {0x10330, 0x1034a, L},
- {0x10380, 0x1039d, L},
- {0x1039f, 0x1039f, L},
- {0x10400, 0x1049d, L},
- {0x104a0, 0x104a9, L},
- {0x10800, 0x10805, R},
- {0x10808, 0x10808, R},
- {0x1080a, 0x10835, R},
- {0x10837, 0x10838, R},
- {0x1083c, 0x1083c, R},
- {0x1083f, 0x1083f, R},
- {0x1d000, 0x1d0f5, L},
- {0x1d100, 0x1d126, L},
- {0x1d12a, 0x1d166, L},
- {0x1d167, 0x1d169, NSM},
- {0x1d16a, 0x1d172, L},
- {0x1d173, 0x1d17a, BN},
- {0x1d17b, 0x1d182, NSM},
- {0x1d183, 0x1d184, L},
- {0x1d185, 0x1d18b, NSM},
- {0x1d18c, 0x1d1a9, L},
- {0x1d1aa, 0x1d1ad, NSM},
- {0x1d1ae, 0x1d1dd, L},
- {0x1d400, 0x1d454, L},
- {0x1d456, 0x1d49c, L},
- {0x1d49e, 0x1d49f, L},
- {0x1d4a2, 0x1d4a2, L},
- {0x1d4a5, 0x1d4a6, L},
- {0x1d4a9, 0x1d4ac, L},
- {0x1d4ae, 0x1d4b9, L},
- {0x1d4bb, 0x1d4bb, L},
- {0x1d4bd, 0x1d4c3, L},
- {0x1d4c5, 0x1d505, L},
- {0x1d507, 0x1d50a, L},
- {0x1d50d, 0x1d514, L},
- {0x1d516, 0x1d51c, L},
- {0x1d51e, 0x1d539, L},
- {0x1d53b, 0x1d53e, L},
- {0x1d540, 0x1d544, L},
- {0x1d546, 0x1d546, L},
- {0x1d54a, 0x1d550, L},
- {0x1d552, 0x1d6a3, L},
- {0x1d6a8, 0x1d7c9, L},
- {0x1d7ce, 0x1d7ff, EN},
- {0x20000, 0x2a6d6, L},
- {0x2f800, 0x2fa1d, L},
- {0xe0001, 0xe0001, BN},
- {0xe0020, 0xe007f, BN},
- {0xe0100, 0xe01ef, NSM},
- {0xf0000, 0xffffd, L},
- {0x100000, 0x10fffd, L}
- };
-
- int i, j, k;
-
- i = -1;
- j = lenof(lookup);
-
- while (j - i > 1) {
- k = (i + j) / 2;
- if (ch < lookup[k].first)
- j = k;
- else if (ch > lookup[k].last)
- i = k;
- else
- return lookup[k].type;
- }
-
- /*
- * If we reach here, the character was not in any of the
- * intervals listed in the lookup table. This means we return
- * ON (`Other Neutrals'). This is the appropriate code for any
- * character genuinely not listed in the Unicode table, and
- * also the table above has deliberately left out any
- * characters _explicitly_ listed as ON (to save space!).
- */
- return ON;
-}
-
-/*
- * 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<<R) | (1<<AL) | (1<<RLE) | (1<<RLO);
-
- return mask & (1 << (getType(c)));
-}
-
-/*
- * The most significant 2 bits of each level are used to store
- * Override status of each character
- * This function sets the override bits of level according
- * to the value in override, and reurns the new byte.
- */
-static unsigned char setOverrideBits(
- unsigned char level, unsigned char override)
-{
- if (override == ON)
- return level;
- else if (override == R)
- return level | OISR;
- else if (override == L)
- return level | OISL;
- return level;
-}
-
-/*
- * Find the most recent run of the same value in `level', and
- * return the value _before_ it. Used to process U+202C POP
- * DIRECTIONAL FORMATTING.
- */
-static int getPreviousLevel(unsigned char *level, int from)
-{
- if (from > 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<count; i++) {
- to[i] = line[i];
- tempShape = STYPE(line[i].wc);
- switch (tempShape) {
- case SC:
- break;
-
- case SU:
- break;
-
- case SR:
- tempShape = (i+1 < count ? STYPE(line[i+1].wc) : SU);
- if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC))
- to[i].wc = SFINAL((SISOLATED(line[i].wc)));
- else
- to[i].wc = SISOLATED(line[i].wc);
- break;
-
-
- case SD:
- /* Make Ligatures */
- tempShape = (i+1 < count ? STYPE(line[i+1].wc) : SU);
- if (line[i].wc == 0x644) {
- if (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<count; i++) {
- int type = getType(line[i].wc);
- if (type == R || type == AL) {
- yes = true;
- break;
- }
- }
- if (!yes)
- return L;
-
- /* Initialize types, levels */
- types = snewn(count, unsigned char);
- levels = snewn(count, unsigned char);
-
- /* Rule (P1) NOT IMPLEMENTED
- * P1. Split the text into separate paragraphs. A paragraph separator is
- * kept with the previous paragraph. Within each paragraph, apply all the
- * other rules of this algorithm.
- */
-
- /* Rule (P2), (P3)
- * P2. In each paragraph, find the first character of type L, AL, or R.
- * P3. If a character is found in P2 and it is of type AL or R, then set
- * the paragraph embedding level to one; otherwise, set it to zero.
- */
- paragraphLevel = 0;
- for (i=0; i<count ; i++) {
- int type = getType(line[i].wc);
- if (type == R || type == AL) {
- paragraphLevel = 1;
- break;
- } else if (type == L)
- break;
- }
-
- /* Rule (X1)
- * X1. Begin by setting the current embedding level to the paragraph
- * embedding level. Set the directional override status to neutral.
- */
- currentEmbedding = paragraphLevel;
- currentOverride = ON;
-
- /* Rule (X2), (X3), (X4), (X5), (X6), (X7), (X8)
- * X2. With each RLE, compute the least greater odd embedding level.
- * X3. With each LRE, compute the least greater even embedding level.
- * X4. With each RLO, compute the least greater odd embedding level.
- * X5. With each LRO, compute the least greater even embedding level.
- * X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
- * a. Set the level of the current character to the current
- * embedding level.
- * b. Whenever the directional override status is not neutral,
- * reset the current character type to the directional
- * override status.
- * X7. With each PDF, determine the matching embedding or override code.
- * If there was a valid matching code, restore (pop) the last
- * remembered (pushed) embedding level and directional override.
- * X8. All explicit directional embeddings and overrides are completely
- * terminated at the end of each paragraph. Paragraph separators are not
- * included in the embedding. (Useless here) NOT IMPLEMENTED
- */
- bover = false;
- for (i=0; i<count; i++) {
- tempType = getType(line[i].wc);
- switch (tempType) {
- case RLE:
- currentEmbedding = levels[i] = leastGreaterOdd(currentEmbedding);
- levels[i] = setOverrideBits(levels[i], currentOverride);
- currentOverride = ON;
- break;
-
- case LRE:
- currentEmbedding = levels[i] = leastGreaterEven(currentEmbedding);
- levels[i] = setOverrideBits(levels[i], currentOverride);
- currentOverride = ON;
- break;
-
- case RLO:
- currentEmbedding = levels[i] = leastGreaterOdd(currentEmbedding);
- tempType = currentOverride = R;
- bover = true;
- break;
-
- case LRO:
- currentEmbedding = levels[i] = leastGreaterEven(currentEmbedding);
- tempType = currentOverride = L;
- bover = true;
- break;
-
- case PDF: {
- int prevlevel = getPreviousLevel(levels, i);
-
- if (prevlevel == -1) {
- currentEmbedding = paragraphLevel;
- currentOverride = ON;
- } else {
- currentOverride = currentEmbedding & OMASK;
- currentEmbedding = currentEmbedding & ~OMASK;
- }
- levels[i] = currentEmbedding;
- break;
- }
-
- /* Whitespace is treated as neutral for now */
- case WS:
- case S:
- levels[i] = currentEmbedding;
- tempType = ON;
- if (currentOverride != ON)
- tempType = currentOverride;
- break;
-
- default:
- levels[i] = currentEmbedding;
- if (currentOverride != ON)
- tempType = currentOverride;
- break;
-
- }
- types[i] = tempType;
- }
- /* this clears out all overrides, so we can use levels safely... */
- /* checks bover first */
- if (bover)
- for (i=0; i<count; i++)
- levels[i] = levels[i] & LMASK;
-
- /* Rule (X9)
- * X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes.
- * Here, they're converted to BN.
- */
- for (i=0; i<count; i++) {
- switch (types[i]) {
- case RLE:
- case LRE:
- case RLO:
- case LRO:
- case PDF:
- types[i] = BN;
- break;
- }
- }
-
- /* Rule (W1)
- * W1. Examine each non-spacing mark (NSM) in the level run, and change
- * the type of the NSM to the type of the previous character. If the NSM
- * is at the start of the level run, it will get the type of sor.
- */
- if (types[0] == NSM)
- types[0] = paragraphLevel;
-
- for (i=1; i<count; i++) {
- if (types[i] == NSM)
- types[i] = types[i-1];
- /* Is this a safe assumption?
- * I assumed the previous, IS a character.
- */
- }
-
- /* Rule (W2)
- * W2. Search backwards from each instance of a European number until the
- * first strong type (R, L, AL, or sor) is found. If an AL is found,
- * change the type of the European number to Arabic number.
- */
- for (i=0; i<count; i++) {
- if (types[i] == EN) {
- j=i;
- while (j >= 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<count; i++) {
- if (types[i] == AL)
- types[i] = R;
- }
-
- /* Rule (W4)
- * W4. A single European separator between two European numbers changes
- * to a European number. A single common separator between two numbers
- * of the same type changes to that type.
- */
- for (i=1; i<(count-1); i++) {
- if (types[i] == ES) {
- if (types[i-1] == EN && types[i+1] == EN)
- types[i] = EN;
- } else if (types[i] == CS) {
- if (types[i-1] == EN && types[i+1] == EN)
- types[i] = EN;
- else if (types[i-1] == AN && types[i+1] == AN)
- types[i] = AN;
- }
- }
-
- /* Rule (W5)
- * W5. A sequence of European terminators adjacent to European numbers
- * changes to all European numbers.
- *
- * Optimization: lots here... else ifs need rearrangement
- */
- for (i=0; i<count; i++) {
- if (types[i] == ET) {
- if (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<count; i++) {
- switch (types[i]) {
- case ES:
- case ET:
- case CS:
- types[i] = ON;
- break;
- }
- }
-
- /* Rule (W7)
- * W7. Search backwards from each instance of a European number until
- * the first strong type (R, L, or sor) is found. If an L is found,
- * then change the type of the European number to L.
- */
- for (i=0; i<count; i++) {
- if (types[i] == EN) {
- j=i;
- while (j >= 0) {
- if (types[j] == L) {
- types[i] = L;
- break;
- } else if (types[j] == R || types[j] == AL) {
- break;
- }
- j--;
- }
- }
- }
-
- /* Rule (N1)
- * N1. A sequence of neutrals takes the direction of the surrounding
- * strong text if the text on both sides has the same direction. European
- * and Arabic numbers are treated as though they were R.
- */
- if (count >= 2 && types[0] == ON) {
- if ((types[1] == R) || (types[1] == EN) || (types[1] == AN))
- types[0] = R;
- else if (types[1] == L)
- types[0] = L;
- }
- for (i=1; i<(count-1); i++) {
- if (types[i] == ON) {
- if (types[i-1] == L) {
- j=i;
- while (j<(count-1) && types[j] == ON) {
- j++;
- }
- if (types[j] == L) {
- while (i<j) {
- types[i] = L;
- i++;
- }
- }
-
- } else if ((types[i-1] == R) ||
- (types[i-1] == EN) ||
- (types[i-1] == AN)) {
- j=i;
- while (j<(count-1) && types[j] == ON) {
- j++;
- }
- if ((types[j] == R) ||
- (types[j] == EN) ||
- (types[j] == AN)) {
- while (i<j) {
- types[i] = R;
- i++;
- }
- }
- }
- }
- }
- if (count >= 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; i<count; i++) {
- if (types[i] == ON) {
- if ((levels[i] % 2) == 0)
- types[i] = L;
- else
- types[i] = R;
- }
- }
-
- /* Rule (I1)
- * I1. For all characters with an even (left-to-right) embedding
- * direction, those of type R go up one level and those of type AN or
- * EN go up two levels.
- */
- for (i=0; i<count; i++) {
- if ((levels[i] % 2) == 0) {
- if (types[i] == R)
- levels[i] += 1;
- else if (types[i] == AN || types[i] == EN)
- levels[i] += 2;
- }
- }
-
- /* Rule (I2)
- * I2. For all characters with an odd (right-to-left) embedding direction,
- * those of type L, EN or AN go up one level.
- */
- for (i=0; i<count; i++) {
- if ((levels[i] % 2) == 1) {
- if (types[i] == L || types[i] == EN || types[i] == AN)
- levels[i] += 1;
- }
- }
-
- /* Rule (L1)
- * L1. On each line, reset the embedding level of the following characters
- * to the paragraph embedding level:
- * (1)segment separators, (2)paragraph separators,
- * (3)any sequence of whitespace characters preceding
- * a segment separator or paragraph separator,
- * (4)and any sequence of white space characters
- * at the end of the line.
- * The types of characters used here are the original types, not those
- * modified by the previous phase.
- */
- j=count-1;
- while (j>0 && (getType(line[j].wc) == WS)) {
- j--;
- }
- if (j < (count-1)) {
- for (j++; j<count; j++)
- levels[j] = paragraphLevel;
- }
- for (i=0; i<count; i++) {
- tempType = getType(line[i].wc);
- if (tempType == WS) {
- j=i;
- while (j<count && (getType(line[j].wc) == WS)) {
- j++;
- }
- if (j==count || getType(line[j].wc) == B ||
- getType(line[j].wc) == S) {
- 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<count; i++)
- if ((levels[i] % 2) == 1)
- doMirror(&line[i].wc);
-
- /* Rule (L2)
- * L2. From the highest level found in the text to the lowest odd level on
- * each line, including intermediate levels not actually present in the
- * text, reverse any contiguous sequence of characters that are at that
- * level or higher
- */
- /* we flip the character string and leave the level array */
- i=0;
- tempType = levels[0];
- while (i < count) {
- if (levels[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 <stdio.h>
-#include <assert.h>
-
-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/MISC.C b/MISC.C
deleted file mode 100644
index 56f2ba93..00000000
--- a/MISC.C
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- * 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 <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <limits.h>
-#include <ctype.h>
-#include <assert.h>
-
-#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/MISC.H b/MISC.H
index 7b4012b6..1b3d324a 100644
--- a/MISC.H
+++ b/MISC.H
@@ -1,5 +1,6 @@
/*
- * Header for misc.c.
+ * Header for miscellaneous helper functions, mostly defined in the
+ * utils subdirectory.
*/
#ifndef PUTTY_MISC_H
@@ -33,7 +34,7 @@ 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
+ * implementation struct in strbuf.c, which isn't exposed to client
* code.
*/
struct strbuf {
@@ -51,13 +52,15 @@ struct strbuf {
strbuf *strbuf_new(void);
strbuf *strbuf_new_nm(void);
+/* Helpers to allocate a strbuf containing an existing string */
+strbuf *strbuf_dup(ptrlen string);
+strbuf *strbuf_dup_nm(ptrlen string);
+
void strbuf_free(strbuf *buf);
void *strbuf_append(strbuf *buf, size_t len);
void strbuf_shrink_to(strbuf *buf, size_t new_len);
void strbuf_shrink_by(strbuf *buf, size_t amount_to_remove);
char *strbuf_to_str(strbuf *buf); /* does free buf, but you must free result */
-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);
@@ -65,13 +68,13 @@ 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). */
+ * work around the rather deficient interface of mb_to_wc. */
wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len);
wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string);
+char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len,
+ const char *defchr);
+char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string,
+ const char *defchr);
static inline int toint(unsigned u)
{
@@ -105,6 +108,20 @@ bool strendswith(const char *s, const char *t);
void base64_encode_atom(const unsigned char *data, int n, char *out);
int base64_decode_atom(const char *atom, unsigned char *out);
+void base64_decode_bs(BinarySink *bs, ptrlen data);
+void base64_decode_fp(FILE *fp, ptrlen data);
+strbuf *base64_decode_sb(ptrlen data);
+void base64_encode_bs(BinarySink *bs, ptrlen data, int cpl);
+void base64_encode_fp(FILE *fp, ptrlen data, int cpl);
+strbuf *base64_encode_sb(ptrlen data, int cpl);
+bool base64_valid(ptrlen data);
+
+void percent_encode_bs(BinarySink *bs, ptrlen data, const char *badchars);
+void percent_encode_fp(FILE *fp, ptrlen data, const char *badchars);
+strbuf *percent_encode_sb(ptrlen data, const char *badchars);
+void percent_decode_bs(BinarySink *bs, ptrlen data);
+void percent_decode_fp(FILE *fp, ptrlen data);
+strbuf *percent_decode_sb(ptrlen data);
struct bufchain_granule;
struct bufchain_tag {
@@ -123,6 +140,8 @@ ptrlen bufchain_prefix(bufchain *ch);
void bufchain_consume(bufchain *ch, size_t len);
void bufchain_fetch(bufchain *ch, void *data, size_t len);
void bufchain_fetch_consume(bufchain *ch, void *data, size_t len);
+bool bufchain_try_consume(bufchain *ch, size_t len);
+bool bufchain_try_fetch(bufchain *ch, void *data, size_t len);
bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len);
size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len);
void bufchain_set_callback_inner(
@@ -132,9 +151,9 @@ 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
+ * function. Lives here rather than in bufchain.c so that
+ * standalone programs can use the bufchain facility without this
+ * optional callback feature and not need to provide a stub of
* queue_idempotent_callback. */
bufchain_set_callback_inner(ch, ic, queue_idempotent_callback);
}
@@ -157,6 +176,21 @@ static inline ptrlen make_ptrlen(const void *ptr, size_t len)
return pl;
}
+static inline const void *ptrlen_end(ptrlen pl)
+{
+ return (const char *)pl.ptr + pl.len;
+}
+
+static inline ptrlen make_ptrlen_startend(const void *startv, const void *endv)
+{
+ const char *start = (const char *)startv, *end = (const char *)endv;
+ assert(end >= start);
+ ptrlen pl;
+ pl.ptr = start;
+ pl.len = end - start;
+ return pl;
+}
+
static inline ptrlen ptrlen_from_asciz(const char *str)
{
return make_ptrlen(str, strlen(str));
@@ -178,6 +212,8 @@ int ptrlen_strcmp(ptrlen pl1, ptrlen pl2);
bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail);
bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail);
ptrlen ptrlen_get_word(ptrlen *input, const char *separators);
+bool ptrlen_contains(ptrlen input, const char *characters);
+bool ptrlen_contains_only(ptrlen input, const char *characters);
char *mkstr(ptrlen pl);
int string_length_for_printf(size_t);
/* Derive two printf arguments from a ptrlen, suitable for "%.*s" */
@@ -196,6 +232,8 @@ int string_length_for_printf(size_t);
/* Make a ptrlen out of a constant byte array. */
#define PTRLEN_FROM_CONST_BYTES(a) make_ptrlen(a, sizeof(a))
+void wordwrap(BinarySink *bs, ptrlen input, size_t maxwid);
+
/* Wipe sensitive data out of memory that's about to be freed. Simpler
* than memset because we don't need the fill char parameter; also
* attempts (by fiddly use of volatile) to inhibit the compiler from
@@ -206,15 +244,31 @@ 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);
+ * Returns 0 for mismatch or 1 for equality (unlike memcmp), hinted at
+ * by the 'eq' in the name. */
+unsigned smemeq(const void *av, const void *bv, size_t len);
/* Encode a single UTF-8 character. Assumes that illegal characters
* (such as things in the surrogate range, or > 0x10FFFF) have already
* been removed. */
size_t encode_utf8(void *output, unsigned long ch);
+/* Encode a wide-character string into UTF-8. Tolerates surrogates if
+ * sizeof(wchar_t) == 2, assuming that in that case the wide string is
+ * encoded in UTF-16. */
+char *encode_wide_string_as_utf8(const wchar_t *wstr);
+
+/* Decode a single UTF-8 character. Returns U+FFFD for any of the
+ * illegal cases. */
+unsigned long decode_utf8(const char **utf8);
+
+/* Decode a single UTF-8 character to an output buffer of the
+ * platform's wchar_t. May write a pair of surrogates if
+ * sizeof(wchar_t) == 2, assuming that in that case the wide string is
+ * encoded in UTF-16. Otherwise, writes one character. Returns the
+ * number written. */
+size_t decode_utf8_to_wchar(const char **utf8, wchar_t *out);
+
/* Write a string out in C string-literal format. */
void write_c_string_literal(FILE *fp, ptrlen str);
@@ -374,6 +428,22 @@ static inline void PUT_16BIT_MSB_FIRST(void *vp, uint16_t value)
p[0] = (uint8_t)(value >> 8);
}
+/* For use in X11-related applications, an endianness-variable form of
+ * {GET,PUT}_16BIT which expects 'endian' to be either 'B' or 'l' */
+
+static inline uint16_t GET_16BIT_X11(char endian, const void *p)
+{
+ return endian == 'B' ? GET_16BIT_MSB_FIRST(p) : GET_16BIT_LSB_FIRST(p);
+}
+
+static inline void PUT_16BIT_X11(char endian, void *p, uint16_t value)
+{
+ if (endian == 'B')
+ PUT_16BIT_MSB_FIRST(p, value);
+ else
+ PUT_16BIT_LSB_FIRST(p, value);
+}
+
/* Replace NULL with the empty string, permitting an idiom in which we
* get a string (pointer,length) pair that might be NULL,0 and can
* then safely say things like printf("%.*s", length, NULLTOEMPTY(ptr)) */
@@ -439,4 +509,18 @@ static inline ptrlen ptrlen_from_lf(LoadedFile *lf)
* is made to handle difficult overlap cases. */
void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size);
+/* Boolean expressions used in OpenSSH certificate configuration */
+bool cert_expr_valid(const char *expression,
+ char **error_msg, ptrlen *error_loc);
+bool cert_expr_match_str(const char *expression,
+ const char *hostname, unsigned port);
+/* Build a certificate expression out of hostname wildcards. Required
+ * to handle legacy configuration from early in development, when
+ * multiple wildcards were stored separately in config, implicitly
+ * ORed together. */
+CertExprBuilder *cert_expr_builder_new(void);
+void cert_expr_builder_free(CertExprBuilder *eb);
+void cert_expr_builder_add(CertExprBuilder *eb, const char *wildcard);
+char *cert_expr_expression(CertExprBuilder *eb);
+
#endif
diff --git a/MKAUTO.SH b/MKAUTO.SH
deleted file mode 100644
index 9759438f..00000000
--- a/MKAUTO.SH
+++ /dev/null
@@ -1,11 +0,0 @@
-#! /bin/sh
-# This script makes the autoconf mechanism for the Unix port work.
-# It's separate from mkfiles.pl because it won't work (and isn't needed)
-# on a non-Unix system.
-
-# It's nice to be able to run this from inside the unix subdir as
-# well as from outside.
-test -f unix.h && cd ..
-
-# Run autoconf on our real configure.in.
-autoreconf -i && rm -rf autom4te.cache
diff --git a/MKFILES.PL b/MKFILES.PL
deleted file mode 100644
index b323e87f..00000000
--- a/MKFILES.PL
+++ /dev/null
@@ -1,2092 +0,0 @@
-#!/usr/bin/env perl
-#
-# Cross-platform Makefile generator.
-#
-# Reads the file `Recipe' to determine the list of generated
-# executables and their component objects. Then reads the source
-# files to compute #include dependencies. Finally, writes out the
-# various target Makefiles.
-
-# PuTTY specifics which could still do with removing:
-# - Mac makefile is not portabilised at all. Include directories
-# are hardwired, and also the libraries are fixed. This is
-# mainly because I was too scared to go anywhere near it.
-# - sbcsgen.pl is still run at startup.
-#
-# FIXME: no attempt made to handle !forceobj in the project files.
-
-use warnings;
-use FileHandle;
-use File::Basename;
-use Cwd;
-use Digest::SHA qw(sha512_hex);
-
-if ($#ARGV >= 0 and ($ARGV[0] eq "-u" or $ARGV[0] eq "-U")) {
- # Convenience for Unix users: -u means that after we finish what
- # we're doing here, we also run mkauto.sh and then 'configure' in
- # the Unix subdirectory. So it's a one-stop shop for regenerating
- # the actual end-product Unix makefile.
- #
- # Arguments supplied after -u go to configure.
- #
- # -U is identical, but runs 'configure' at the _top_ level, for
- # people who habitually do that.
- $do_unix = ($ARGV[0] eq "-U" ? 2 : 1);
- shift @ARGV;
- @confargs = @ARGV;
-}
-
-open IN, "Recipe" or do {
- # We want to deal correctly with being run from one of the
- # subdirs in the source tree. So if we can't find Recipe here,
- # try one level up.
- chdir "..";
- open IN, "Recipe" or die "unable to open Recipe file\n";
-};
-
-# HACK: One of the source files in `charset' is auto-generated by
-# sbcsgen.pl, and licence.h is likewise generated by licence.pl. We
-# need to generate those _now_, before attempting dependency analysis.
-eval 'chdir "charset"; require "./sbcsgen.pl"; chdir ".."; select STDOUT;';
-eval 'require "./licence.pl"; select STDOUT;';
-
-@srcdirs = ("./");
-
-$divert = undef; # ref to scalar in which text is currently being put
-$help = ""; # list of newline-free lines of help text
-$project_name = "project"; # this is a good enough default
-%makefiles = (); # maps makefile types to output makefile pathnames
-%makefile_extra = (); # maps makefile types to extra Makefile text
-%programs = (); # maps prog name + type letter to listref of objects/resources
-%groups = (); # maps group name to listref of objects/resources
-
-while (<IN>) {
- chomp;
- @_ = split;
-
- # If we're gathering help text, keep doing so.
- if (defined $divert) {
- if ((defined $_[0]) && $_[0] eq "!end") {
- $divert = undef;
- } else {
- ${$divert} .= "$_\n";
- }
- next;
- }
- # Skip comments and blank lines.
- next if /^\s*#/ or scalar @_ == 0;
-
- if ($_[0] eq "!begin" and $_[1] eq "help") { $divert = \$help; next; }
- if ($_[0] eq "!end") { $divert = undef; next; }
- if ($_[0] eq "!name") { $project_name = $_[1]; next; }
- if ($_[0] eq "!srcdir") { push @srcdirs, $_[1]; next; }
- if ($_[0] eq "!makefile" and &mfval($_[1])) { $makefiles{$_[1]}=$_[2]; next;}
- if ($_[0] eq "!specialobj" and &mfval($_[1])) { $specialobj{$_[1]}->{$_[2]} = 1; next;}
- if ($_[0] eq "!cflags" and &mfval($_[1])) {
- ($rest = $_) =~ s/^\s*\S+\s+\S+\s+\S+\s*//; # find rest of input line
- if ($rest eq "") {
- # Make sure this file doesn't get lumped together with any
- # other file's cflags.
- $rest = "F" . $_[2];
- } else {
- # Give this file a specific set of cflags, but permit it to
- # go together with other files using the same set.
- $rest = "C" . $rest;
- }
- $cflags{$_[1]}->{$_[2]} = $rest;
- next;
- }
- if ($_[0] eq "!forceobj") { $forceobj{$_[1]} = 1; next; }
- if ($_[0] eq "!begin") {
- if ($_[1] =~ /^>(.*)/) {
- $divert = \$auxfiles{$1};
- } elsif (&mfval($_[1])) {
- $sect = $_[2] ? $_[2] : "end";
- $divert = \($makefile_extra{$_[1]}->{$sect});
- } else {
- $dummy = '';
- $divert = \$dummy;
- }
- next;
- }
- # If we're gathering help/verbatim text, keep doing so.
- if (defined $divert) { ${$divert} .= "$_\n"; next; }
- # Ignore blank lines.
- next if scalar @_ == 0;
-
- # Now we have an ordinary line. See if it's an = line, a : line
- # or a + line.
- @objs = @_;
-
- if ($_[0] eq "+") {
- $listref = $lastlistref;
- $prog = undef;
- die "$.: unexpected + line\n" if !defined $lastlistref;
- } elsif ($#_ >= 1 && $_[1] eq "=") {
- $groups{$_[0]} = [] if !defined $groups{$_[0]};
- $listref = $groups{$_[0]};
- $prog = undef;
- shift @objs; # eat the group name
- } elsif ($#_ >= 1 && $_[1] eq ":") {
- $listref = [];
- $prog = $_[0];
- shift @objs; # eat the program name
- } else {
- die "$.: unrecognised line type\n";
- }
- shift @objs; # eat the +, the = or the :
-
- while (scalar @objs > 0) {
- $i = shift @objs;
- if ($groups{$i}) {
- foreach $j (@{$groups{$i}}) { unshift @objs, $j; }
- } elsif (($i =~ /^\[([A-Z]*)\]$/) and defined $prog) {
- $type = substr($i,1,(length $i)-2);
- die "unrecognised program type for $prog [$type]\n"
- if ! grep { $type eq $_ } qw(G C X U MX XT UT);
- } else {
- push @$listref, $i;
- }
- }
- if ($prog and $type) {
- die "multiple program entries for $prog [$type]\n"
- if defined $programs{$prog . "," . $type};
- $programs{$prog . "," . $type} = $listref;
- }
- $lastlistref = $listref;
-}
-
-close IN;
-
-foreach $aux (sort keys %auxfiles) {
- open AUX, ">$aux";
- print AUX $auxfiles{$aux};
- close AUX;
-}
-
-# Now retrieve the complete list of objects and resource files, and
-# construct dependency data for them. While we're here, expand the
-# object list for each program, and complain if its type isn't set.
-@prognames = sort keys %programs;
-%depends = ();
-@scanlist = ();
-foreach $i (@prognames) {
- ($prog, $type) = split ",", $i;
- # Strip duplicate object names.
- $prev = '';
- @list = grep { $status = ($prev ne $_); $prev=$_; $status }
- sort @{$programs{$i}};
- $programs{$i} = [@list];
- foreach $j (@list) {
- # Dependencies for "x" start with "x.c" or "x.m" (depending on
- # which one exists).
- # Dependencies for "x.res" start with "x.rc".
- # Dependencies for "x.rsrc" start with "x.r".
- # Both types of file are pushed on the list of files to scan.
- # Libraries (.lib) don't have dependencies at all.
- if ($j =~ /^(.*)\.res$/) {
- $file = "$1.rc";
- $depends{$j} = [$file];
- push @scanlist, $file;
- } elsif ($j =~ /^(.*)\.rsrc$/) {
- $file = "$1.r";
- $depends{$j} = [$file];
- push @scanlist, $file;
- } elsif ($j !~ /\./) {
- $file = "$j.c";
- $file = "$j.m" unless &findfile($file);
- $depends{$j} = [$file];
- push @scanlist, $file;
- }
- }
-}
-
-# Scan each file on @scanlist and find further inclusions.
-# Inclusions are given by lines of the form `#include "otherfile"'
-# (system headers are automatically ignored by this because they'll
-# be given in angle brackets). Files included by this method are
-# added back on to @scanlist to be scanned in turn (if not already
-# done).
-#
-# Resource scripts (.rc) can also include a file by means of:
-# - a line # ending `ICON "filename"';
-# - a line ending `RT_MANIFEST "filename"'.
-# Files included by this method are not added to @scanlist because
-# they can never include further files.
-#
-# In this pass we write out a hash %further which maps a source
-# file name into a listref containing further source file names.
-
-%further = ();
-%allsourcefiles = (); # this is wanted by some makefiles
-while (scalar @scanlist > 0) {
- $file = shift @scanlist;
- next if defined $further{$file}; # skip if we've already done it
- $further{$file} = [];
- $dirfile = &findfile($file);
- $allsourcefiles{$dirfile} = 1;
- open IN, "$dirfile" or die "unable to open source file $file\n";
- while (<IN>) {
- chomp;
- /^\s*#include\s+\"([^\"]+)\"/ and do {
- push @{$further{$file}}, $1;
- push @scanlist, $1;
- next;
- };
- /(RT_MANIFEST|ICON)\s+\"([^\"]+)\"\s*$/ and do {
- push @{$further{$file}}, $2;
- next;
- }
- }
- close IN;
-}
-
-# Now we're ready to generate the final dependencies section. For
-# each key in %depends, we must expand the dependencies list by
-# iteratively adding entries from %further.
-foreach $i (keys %depends) {
- %dep = ();
- @scanlist = @{$depends{$i}};
- foreach $i (@scanlist) { $dep{$i} = 1; }
- while (scalar @scanlist > 0) {
- $file = shift @scanlist;
- foreach $j (@{$further{$file}}) {
- if (!$dep{$j}) {
- $dep{$j} = 1;
- push @{$depends{$i}}, $j;
- push @scanlist, $j;
- }
- }
- }
-# printf "%s: %s\n", $i, join ' ',@{$depends{$i}};
-}
-
-# Validation of input.
-
-sub mfval($) {
- my ($type) = @_;
- # Returns true if the argument is a known makefile type. Otherwise,
- # prints a warning and returns false;
- if (grep { $type eq $_ }
- ("vc","vcproj","cygwin","lcc","devcppproj","gtk","unix",
- "am","osx","vstudio10","vstudio12","clangcl")) {
- return 1;
- }
- warn "$.:unknown makefile type '$type'\n";
- return 0;
-}
-
-# Utility routines while writing out the Makefiles.
-
-sub def {
- my ($x) = shift @_;
- return (defined $x) ? $x : "";
-}
-
-sub dirpfx {
- my ($path) = shift @_;
- my ($sep) = shift @_;
- my $ret = "";
- my $i;
-
- while (($i = index $path, $sep) >= 0 ||
- ($j = index $path, "/") >= 0) {
- if ($i >= 0 and ($j < 0 or $i < $j)) {
- $path = substr $path, ($i + length $sep);
- } else {
- $path = substr $path, ($j + 1);
- }
- $ret .= "..$sep";
- }
- return $ret;
-}
-
-sub findfile {
- my ($name) = @_;
- my $dir = '';
- my $i;
- my $outdir = undef;
- unless (defined $findfilecache{$name}) {
- $i = 0;
- foreach $dir (@srcdirs) {
- if (-f "$dir$name") {
- $outdir = $dir;
- $i++;
- $outdir =~ s/^\.\///;
- }
- }
- die "multiple instances of source file $name\n" if $i > 1;
- $findfilecache{$name} = (defined $outdir ? $outdir . $name : undef);
- }
- return $findfilecache{$name};
-}
-
-sub objects {
- my ($prog, $otmpl, $rtmpl, $ltmpl, $prefix, $dirsep) = @_;
- my @ret;
- my ($i, $x, $y);
- ($otmpl, $rtmpl, $ltmpl) = map { defined $_ ? $_ : "" } ($otmpl, $rtmpl, $ltmpl);
- @ret = ();
- foreach $i (@{$programs{$prog}}) {
- $x = "";
- if ($i =~ /^(.*)\.(res|rsrc)/) {
- $y = $1;
- ($x = $rtmpl) =~ s/X/$y/;
- } elsif ($i =~ /^(.*)\.lib/) {
- $y = $1;
- ($x = $ltmpl) =~ s/X/$y/;
- } elsif ($i !~ /\./) {
- ($x = $otmpl) =~ s/X/$i/;
- }
- push @ret, $x if $x ne "";
- }
- return join " ", @ret;
-}
-
-sub special {
- my ($prog, $suffix) = @_;
- my @ret;
- my ($i, $x, $y);
- ($otmpl, $rtmpl, $ltmpl) = map { defined $_ ? $_ : "" } ($otmpl, $rtmpl, $ltmpl);
- @ret = ();
- foreach $i (@{$programs{$prog}}) {
- if (substr($i, (length $i) - (length $suffix)) eq $suffix) {
- push @ret, $i;
- }
- }
- return (scalar @ret) ? (join " ", @ret) : undef;
-}
-
-sub splitline {
- my ($line, $width, $splitchar) = @_;
- my $result = "";
- my $len;
- $len = (defined $width ? $width : 76);
- $splitchar = (defined $splitchar ? $splitchar : '\\');
- while (length $line > $len) {
- $line =~ /^(.{0,$len})\s(.*)$/ or $line =~ /^(.{$len,})?\s(.*)$/;
- $result .= $1;
- $result .= " ${splitchar}\n\t\t" if $2 ne '';
- $line = $2;
- $len = 60;
- }
- return $result . $line;
-}
-
-sub deps {
- my ($otmpl, $rtmpl, $prefix, $dirsep, $mftyp, $depchar, $splitchar) = @_;
- my ($i, $x, $y);
- my @deps;
- my @ret;
- @ret = ();
- $depchar ||= ':';
- foreach $i (sort keys %depends) {
- next if $specialobj{$mftyp}->{$i};
- if ($i =~ /^(.*)\.(res|rsrc)/) {
- next if !defined $rtmpl;
- $y = $1;
- ($x = $rtmpl) =~ s/X/$y/;
- } else {
- ($x = $otmpl) =~ s/X/$i/;
- }
- @deps = @{$depends{$i}};
- @deps = map {
- $_ = &findfile($_);
- s/\//$dirsep/g;
- $_ = $prefix . $_;
- } @deps;
- push @ret, {obj => $x, obj_orig => $i, deps => [@deps]};
- }
- return @ret;
-}
-
-sub prognames {
- my ($types) = @_;
- my ($n, $prog, $type);
- my @ret;
- @ret = ();
- foreach $n (@prognames) {
- ($prog, $type) = split ",", $n;
- push @ret, $n if index(":$types:", ":$type:") >= 0;
- }
- return @ret;
-}
-
-sub progrealnames {
- my ($types) = @_;
- my ($n, $prog, $type);
- my @ret;
- @ret = ();
- foreach $n (@prognames) {
- ($prog, $type) = split ",", $n;
- push @ret, $prog if index(":$types:", ":$type:") >= 0;
- }
- return @ret;
-}
-
-sub manpages {
- my ($types,$suffix) = @_;
-
- # assume that all UNIX programs have a man page
- if($suffix eq "1" && $types =~ /:X:/) {
- return map("$_.1", &progrealnames($types));
- }
- return ();
-}
-
-$orig_dir = cwd;
-
-# Now we're ready to output the actual Makefiles.
-
-if (defined $makefiles{'clangcl'}) {
- $dirpfx = &dirpfx($makefiles{'clangcl'}, "/");
-
- ##-- Makefile for cross-compiling using clang-cl, lld-link, and
- ## MinGW's windres for resource compilation.
- #
- # This makefile allows a complete Linux-based cross-compile, but
- # using the real Visual Studio header files and libraries. In
- # order to run it, you will need:
- #
- # - clang-cl, llvm-rc and lld-link on your PATH.
- # * I built these from the up-to-date LLVM project trunk git
- # repositories, as of 2018-05-29.
- # - case-mashed copies of the Visual Studio include directories.
- # * On a real VS installation, run vcvars32.bat and look at
- # the resulting value of %INCLUDE%. Take a full copy of each
- # of those directories, and inside the copy, for each
- # include file that has an uppercase letter in its name,
- # make a lowercased symlink to it. Additionally, one of the
- # directories will contain files called driverspecs.h and
- # specstrings.h, and those will need symlinks called
- # DriverSpecs.h and SpecStrings.h.
- # * Now, on Linux, define the environment variable INCLUDE to
- # be a list, separated by *semicolons* (in the Windows
- # style), of those directories, but before all of them you
- # must also include lib/clang/5.0.0/include from the clang
- # installation area (which contains in particular a
- # clang-compatible stdarg.h overriding the Visual Studio
- # one).
- # - similarly case-mashed copies of the library directories.
- # * Again, on a real VS installation, run vcvars32 or
- # vcvarsx86_amd64 (as appropriate), look at %LIB%, make a
- # copy of each directory, and provide symlinks within that
- # directory so that all the files can be opened as
- # lowercase.
- # * Then set LIB to be a semicolon-separated list of those
- # directories (but you'll need to change which set of
- # directories depending on whether you want to do a 32-bit
- # or 64-bit build).
- # - for a 64-bit build, set 'Platform=x64' in the environment as
- # well, or else on the make command line.
- # * This is a variable understood only by this makefile - none
- # of the tools we invoke will know it - but it's consistent
- # with the way the VS scripts like vcvarsx86_amd64.bat set
- # things up, and since the environment has to change
- # _anyway_ between 32- and 64-bit builds (different set of
- # paths in $LIB) it's reasonable to have the choice of
- # compilation target driven by another environment variable
- # set in parallel with that one.
- # - for older versions of the VS libraries you may also have to
- # set EXTRA_console and/or EXTRA_windows to the name of an
- # object file manually extracted from one of those libraries.
- # * This is because old VS seems to manage its startup code by
- # having libcmt.lib contain lots of *crt0.obj objects, one
- # for each possible user entry point (main, WinMain and the
- # wide-char versions of both), of which the linker arranges
- # to include the right one by special-case code. But lld
- # only seems to mimic half of that code - it does include
- # the right crt0 object, but it doesn't also deliberately
- # _avoid_ including the _wrong_ ones, and since all those
- # objects define a common set of global symbols for other
- # parts of the library to use, lld may well select an
- # arbitrary one of them the first time it sees a reference
- # to one of those global symbols, and then later also select
- # the _right_ one for the application's entry point, causing
- # a multiple-definitions crash.
- # * So the workaround is to explicitly include the right
- # *crt0.obj file on the linker command line before lld even
- # begins searching libraries. Hence, for a console
- # application, you might extract crt0.obj from the library
- # in question and set EXTRA_console=crt0.obj, and for a GUI
- # application, do the same with wincrt0.obj. Then this
- # makefile will include the right one of those objects
- # alongside the matching /subsystem linker option.
- # - also for older versions of the VS libraries, you may also
- # have to set EXTRA_libs to include extra library files.
-
- open OUT, ">$makefiles{'clangcl'}"; select OUT;
- print
- "# Makefile for cross-compiling $project_name using clang-cl, lld-link,\n".
- "# and llvm-rc, using GNU make on Linux.\n".
- "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
- "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
- print $help;
- print
- "\n".
- "CCCMD = clang-cl\n".
- "RCCMD = llvm-rc\n".
- "ifeq (\$(Platform),x64)\n".
- "CCTARGET = x86_64-pc-windows-msvc18.0.0\n".
- "PLATFORMCFLAGS =\n".
- "else ifeq (\$(Platform),arm)\n".
- "CCTARGET = arm-pc-windows-msvc18.0.0\n".
- "PLATFORMCFLAGS = /D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE /GS-\n".
- "else ifeq (\$(Platform),arm64)\n".
- "CCTARGET = arm64-pc-windows-msvc18.0.0\n".
- "PLATFORMCFLAGS = /D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE /GS-\n".
- "else\n".
- "CCTARGET = i386-pc-windows-msvc18.0.0\n".
- "PLATFORMCFLAGS =\n".
- "endif\n".
- "CC = \$(CCCMD)\n".
- "RC = \$(RCCMD) /c 1252 \n".
- "RCPREPROC = \$(CCCMD) /P /TC\n".
- "LD = lld-link\n".
- "\n".
- "# C compilation flags\n".
- &splitline("CFLAGS = --target=\$(CCTARGET) /nologo /W3 /O1 -Wvla " .
- (join " ", map {"-I$dirpfx$_"} @srcdirs) .
- " /D_WINDOWS /D_WIN32_WINDOWS=0x500 /DWINVER=0x500 ".
- "/D_CRT_SECURE_NO_WARNINGS /D_WINSOCK_DEPRECATED_NO_WARNINGS").
- " \$(PLATFORMCFLAGS)\n".
- "LFLAGS = /incremental:no /dynamicbase /nxcompat\n".
- &splitline("RCPPFLAGS = ".(join " ", map {"-I$dirpfx$_"} @srcdirs).
- " -DWIN32 -D_WIN32 -DWINVER=0x0400")." \$(RCFL)\n".
- "\n".
- &def($makefile_extra{'clangcl'}->{'vars'}) .
- "\n".
- "\n";
- print &splitline("all:" . join "", map { " \$(BUILDDIR)$_.exe" } &progrealnames("G:C"));
- print "\n\n";
- foreach $p (&prognames("G:C")) {
- ($prog, $type) = split ",", $p;
- $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", undef);
- print &splitline("\$(BUILDDIR)$prog.exe: " . $objstr), "\n";
-
- $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", "X.lib");
- $subsys = ($type eq "G") ? "windows" : "console";
- print &splitline("\t\$(LD) \$(LFLAGS) \$(XLFLAGS) ".
- "/out:\$(BUILDDIR)$prog.exe ".
- "/lldmap:\$(BUILDDIR)$prog.map ".
- "/subsystem:$subsys\$(SUBSYSVER) ".
- "\$(EXTRA_$subsys) $objstr \$(EXTRA_libs)")."\n\n";
- }
- my $rc_pp_rules = "";
- foreach $d (&deps("\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", $dirpfx, "/", "vc")) {
- $extradeps = $forceobj{$d->{obj_orig}} ? ["*.c","*.h","*.rc"] : [];
- my $rule;
- my @deps = @{$d->{deps}};
- my @incdeps = grep { m!\.rc2?$! } @deps;
- my @rcdeps = grep { ! m!\.rc2$! } @deps;
- if ($d->{obj} =~ /\.res$/) {
- my $rc = $deps[0];
- my $rcpp = $rc;
- $rcpp =~ s!.*/!!;
- $rcpp =~ s/\.rc$/.rcpp/;
- $rcpp = "\$(BUILDDIR)" . $rcpp;
- $rule = "\$(RC) ".$rcpp." /FO ".$d->{obj};
- $rc_pp_rules .= &splitline(
- sprintf("%s: %s", $rcpp, join " ", @incdeps)) ."\n" .
- "\t\$(RCPREPROC) \$(RCPPFLAGS) /Fi\$\@ \$<\n\n";
- $rcdeps[0] = $rcpp;
- } else {
- $rule = "\$(CC) /Fo\$(BUILDDIR) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) /c \$<";
- }
- print &splitline(sprintf("%s: %s", $d->{obj},
- join " ", @$extradeps, @rcdeps)), "\n";
- print "\t" . $rule . "\n\n";
- }
- print "\n" . $rc_pp_rules;
- print &def($makefile_extra{'clangcl'}->{'end'});
- print "\nclean:\n".
- &splitline("\trm -f \$(BUILDDIR)*.obj \$(BUILDDIR)*.exe ".
- "\$(BUILDDIR)*.rcpp \$(BUILDDIR)*.res \$(BUILDDIR)*.map ".
- "\$(BUILDDIR)*.exe.manifest")."\n";
- select STDOUT; close OUT;
-}
-
-if (defined $makefiles{'cygwin'}) {
- $dirpfx = &dirpfx($makefiles{'cygwin'}, "/");
-
- ##-- MinGW/CygWin makefile (called 'cygwin' for historical reasons)
- open OUT, ">$makefiles{'cygwin'}"; select OUT;
- print
- "# Makefile for $project_name under MinGW, Cygwin, or Winelib.\n".
- "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
- "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
- # gcc command line option is -D not /D
- ($_ = $help) =~ s/([=" ])\/D/$1-D/gs;
- print $_;
- print
- "\n".
- "# You can define this path to point at your tools if you need to\n".
- "# TOOLPATH = c:\\cygwin\\bin\\ # or similar, if you're running Windows\n".
- "# TOOLPATH = /pkg/mingw32msvc/i386-mingw32msvc/bin/\n".
- "# TOOLPATH = i686-w64-mingw32-\n".
- "CC = \$(TOOLPATH)gcc\n".
- "RC = \$(TOOLPATH)windres\n".
- "# Uncomment the following two lines to compile under Winelib\n".
- "# CC = winegcc\n".
- "# RC = wrc\n".
- "# You may also need to tell windres where to find include files:\n".
- "# RCINC = --include-dir c:\\cygwin\\include\\\n".
- "\n".
- &splitline("CFLAGS = -Wall -O2 -std=gnu99 -Wvla -D_WINDOWS".
- " -DWIN32S_COMPAT -D_NO_OLDNAMES -D__USE_MINGW_ANSI_STDIO=1 " .
- (join " ", map {"-I$dirpfx$_"} @srcdirs)) .
- "\n".
- "LDFLAGS = -s\n".
- &splitline("RCFLAGS = \$(RCINC) --define WIN32=1 --define _WIN32=1 ".
- "--define WINVER=0x0400 ".(join " ", map {"-I$dirpfx$_"} @srcdirs))."\n".
- "\n".
- &def($makefile_extra{'cygwin'}->{'vars'}) .
- "\n".
- ".SUFFIXES:\n".
- "\n";
- print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
- print "\n\n";
- foreach $p (&prognames("G:C")) {
- ($prog, $type) = split ",", $p;
- $objstr = &objects($p, "X.o", "X.res.o", undef);
- print &splitline($prog . ".exe: " . $objstr), "\n";
- my $mw = $type eq "G" ? " -mwindows" : "";
- $libstr = &objects($p, undef, undef, "-lX");
- print &splitline("\t\$(CC)" . $mw . " \$(LDFLAGS) -o \$@ " .
- "-Wl,-Map,$prog.map " .
- $objstr . " $libstr", 69), "\n\n";
- }
- foreach $d (&deps("X.o", "X.res.o", $dirpfx, "/", "cygwin")) {
- if ($forceobj{$d->{obj_orig}}) {
- printf ("%s: FORCE\n", $d->{obj});
- } else {
- print &splitline(sprintf("%s: %s", $d->{obj},
- join " ", @{$d->{deps}})), "\n";
- }
- if ($d->{obj} =~ /\.res\.o$/) {
- print "\t\$(RC) \$(RCFL) \$(RCFLAGS) ".$d->{deps}->[0]." -o ".$d->{obj}."\n\n";
- } else {
- print "\t\$(CC) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) -c ".$d->{deps}->[0]."\n\n";
- }
- }
- print "\n";
- print &def($makefile_extra{'cygwin'}->{'end'});
- print "\nclean:\n".
- "\trm -f *.o *.exe *.res.o *.so *.map\n".
- "\n".
- "FORCE:\n";
- select STDOUT; close OUT;
-
-}
-
-if (defined $makefiles{'vc'}) {
- $dirpfx = &dirpfx($makefiles{'vc'}, "\\");
-
- ##-- Visual C++ makefile
- open OUT, ">$makefiles{'vc'}"; select OUT;
- print
- "# Makefile for $project_name under Visual C.\n".
- "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
- "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
- print $help;
- print
- "\n".
- "# If you rename this file to `Makefile', you should change this line,\n".
- "# so that the .rsp files still depend on the correct makefile.\n".
- "MAKEFILE = Makefile.vc\n".
- "\n".
- "# C compilation flags\n".
- "CFLAGS = /nologo /W3 /O1 " .
- (join " ", map {"-I$dirpfx$_"} @srcdirs) .
- " /D_WINDOWS /D_WIN32_WINDOWS=0x500 /DWINVER=0x500 /D_CRT_SECURE_NO_WARNINGS /D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE\n".
- "LFLAGS = /incremental:no /dynamicbase /nxcompat\n".
- "RCFLAGS = ".(join " ", map {"-I$dirpfx$_"} @srcdirs).
- " -DWIN32 -D_WIN32 -DWINVER=0x0400\n".
- "\n".
- &def($makefile_extra{'vc'}->{'vars'}) .
- "\n".
- "\n";
- print &splitline("all:" . join "", map { " \$(BUILDDIR)$_.exe" } &progrealnames("G:C"));
- print "\n\n";
- foreach $p (&prognames("G:C")) {
- ($prog, $type) = split ",", $p;
- $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", undef);
- print &splitline("\$(BUILDDIR)$prog.exe: " . $objstr), "\n";
-
- $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", "X.lib");
- $subsys = ($type eq "G") ? "windows" : "console";
- $inlinefilename = "link_$prog";
- print "\ttype <<$inlinefilename\n";
- @objlist = split " ", $objstr;
- @objlines = ("");
- foreach $i (@objlist) {
- if (length($objlines[$#objlines] . " $i") > 72) {
- push @objlines, "";
- }
- $objlines[$#objlines] .= " $i";
- }
- for ($i=0; $i<=$#objlines; $i++) {
- print "$objlines[$i]\n";
- }
- print "<<\n";
- print "\tlink \$(LFLAGS) \$(XLFLAGS) -out:\$(BUILDDIR)$prog.exe -map:\$(BUILDDIR)$prog.map -nologo -subsystem:$subsys\$(SUBSYSVER) \@$inlinefilename\n\n";
- }
- foreach $d (&deps("\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", $dirpfx, "\\", "vc")) {
- $extradeps = $forceobj{$d->{obj_orig}} ? ["*.c","*.h","*.rc"] : [];
- print &splitline(sprintf("%s: %s", $d->{obj},
- join " ", @$extradeps, @{$d->{deps}})), "\n";
- if ($d->{obj} =~ /.res$/) {
- print "\trc /Fo@{[$d->{obj}]} \$(RCFL) -r \$(RCFLAGS) ".$d->{deps}->[0],"\n\n";
- }
- }
- print "\n";
- foreach $real_srcdir ("", @srcdirs) {
- $srcdir = $real_srcdir;
- if ($srcdir ne "") {
- $srcdir =~ s!/!\\!g;
- $srcdir = $dirpfx . $srcdir;
- $srcdir =~ s!\\\.\\!\\!;
- $srcdir = "{$srcdir}";
- }
- # The double colon at the end of the line makes this a
- # 'batch-mode inference rule', which means that nmake will
- # aggregate multiple invocations of the rule and issue just
- # one cl command with multiple source-file arguments. That
- # noticeably speeds up builds, since starting up the cl
- # process is a noticeable overhead and now has to be done far
- # fewer times.
- print "${srcdir}.c.obj::\n\tcl /Fo\$(BUILDDIR) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) /c \$<\n\n";
- }
- print &def($makefile_extra{'vc'}->{'end'});
- print "\nclean: tidy\n".
- "\t-del \$(BUILDDIR)*.exe\n\n".
- "tidy:\n".
- "\t-del \$(BUILDDIR)*.obj\n".
- "\t-del \$(BUILDDIR)*.res\n".
- "\t-del \$(BUILDDIR)*.pch\n".
- "\t-del \$(BUILDDIR)*.aps\n".
- "\t-del \$(BUILDDIR)*.ilk\n".
- "\t-del \$(BUILDDIR)*.pdb\n".
- "\t-del \$(BUILDDIR)*.rsp\n".
- "\t-del \$(BUILDDIR)*.dsp\n".
- "\t-del \$(BUILDDIR)*.dsw\n".
- "\t-del \$(BUILDDIR)*.ncb\n".
- "\t-del \$(BUILDDIR)*.opt\n".
- "\t-del \$(BUILDDIR)*.plg\n".
- "\t-del \$(BUILDDIR)*.map\n".
- "\t-del \$(BUILDDIR)*.idb\n".
- "\t-del \$(BUILDDIR)debug.log\n";
- select STDOUT; close OUT;
-}
-
-if (defined $makefiles{'vcproj'}) {
- $dirpfx = &dirpfx($makefiles{'vcproj'}, "\\");
-
- ##-- MSVC 6 Workspace and projects
- #
- # Note: All files created in this section are written in binary
- # mode, because although MSVC's command-line make can deal with
- # LF-only line endings, MSVC project files really _need_ to be
- # CRLF. Hence, in order for mkfiles.pl to generate usable project
- # files even when run from Unix, I make sure all files are binary
- # and explicitly write the CRLFs.
- #
- # Create directories if necessary
- mkdir $makefiles{'vcproj'}
- if(! -d $makefiles{'vcproj'});
- chdir $makefiles{'vcproj'};
- @deps = &deps("X.obj", "X.res", $dirpfx, "\\", "vcproj");
- %all_object_deps = map {$_->{obj} => $_->{deps}} @deps;
- # Create the project files
- # Get names of all Windows projects (GUI and console)
- my @prognames = &prognames("G:C");
- foreach $progname (@prognames) {
- create_vc_project(\%all_object_deps, $progname);
- }
- # Create the workspace file
- open OUT, ">$project_name.dsw"; binmode OUT; select OUT;
- print
- "Microsoft Developer Studio Workspace File, Format Version 6.00\r\n".
- "# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!\r\n".
- "\r\n".
- "###############################################################################\r\n".
- "\r\n";
- # List projects
- foreach $progname (@prognames) {
- ($windows_project, $type) = split ",", $progname;
- print "Project: \"$windows_project\"=\".\\$windows_project\\$windows_project.dsp\" - Package Owner=<4>\r\n";
- }
- print
- "\r\n".
- "Package=<5>\r\n".
- "{{{\r\n".
- "}}}\r\n".
- "\r\n".
- "Package=<4>\r\n".
- "{{{\r\n".
- "}}}\r\n".
- "\r\n".
- "###############################################################################\r\n".
- "\r\n".
- "Global:\r\n".
- "\r\n".
- "Package=<5>\r\n".
- "{{{\r\n".
- "}}}\r\n".
- "\r\n".
- "Package=<3>\r\n".
- "{{{\r\n".
- "}}}\r\n".
- "\r\n".
- "###############################################################################\r\n".
- "\r\n";
- select STDOUT; close OUT;
- chdir $orig_dir;
-
- sub create_vc_project {
- my ($all_object_deps, $progname) = @_;
- # Construct program's dependency info
- %seen_objects = ();
- %lib_files = ();
- %source_files = ();
- %header_files = ();
- %resource_files = ();
- @object_files = split " ", &objects($progname, "X.obj", "X.res", "X.lib");
- foreach $object_file (@object_files) {
- next if defined $seen_objects{$object_file};
- $seen_objects{$object_file} = 1;
- if($object_file =~ /\.lib$/io) {
- $lib_files{$object_file} = 1;
- next;
- }
- $object_deps = $all_object_deps{$object_file};
- foreach $object_dep (@$object_deps) {
- if($object_dep =~ /\.c$/io) {
- $source_files{$object_dep} = 1;
- next;
- }
- if($object_dep =~ /\.h$/io) {
- $header_files{$object_dep} = 1;
- next;
- }
- if($object_dep =~ /\.(rc|ico)$/io) {
- $resource_files{$object_dep} = 1;
- next;
- }
- }
- }
- $libs = join " ", sort keys %lib_files;
- @source_files = sort keys %source_files;
- @header_files = sort keys %header_files;
- @resources = sort keys %resource_files;
- ($windows_project, $type) = split ",", $progname;
- mkdir $windows_project
- if(! -d $windows_project);
- chdir $windows_project;
- $subsys = ($type eq "G") ? "windows" : "console";
- open OUT, ">$windows_project.dsp"; binmode OUT; select OUT;
- print
- "# Microsoft Developer Studio Project File - Name=\"$windows_project\" - Package Owner=<4>\r\n".
- "# Microsoft Developer Studio Generated Build File, Format Version 6.00\r\n".
- "# ** DO NOT EDIT **\r\n".
- "\r\n".
- "# TARGTYPE \"Win32 (x86) Application\" 0x0101\r\n".
- "\r\n".
- "CFG=$windows_project - Win32 Debug\r\n".
- "!MESSAGE This is not a valid makefile. To build this project using NMAKE,\r\n".
- "!MESSAGE use the Export Makefile command and run\r\n".
- "!MESSAGE \r\n".
- "!MESSAGE NMAKE /f \"$windows_project.mak\".\r\n".
- "!MESSAGE \r\n".
- "!MESSAGE You can specify a configuration when running NMAKE\r\n".
- "!MESSAGE by defining the macro CFG on the command line. For example:\r\n".
- "!MESSAGE \r\n".
- "!MESSAGE NMAKE /f \"$windows_project.mak\" CFG=\"$windows_project - Win32 Debug\"\r\n".
- "!MESSAGE \r\n".
- "!MESSAGE Possible choices for configuration are:\r\n".
- "!MESSAGE \r\n".
- "!MESSAGE \"$windows_project - Win32 Release\" (based on \"Win32 (x86) Application\")\r\n".
- "!MESSAGE \"$windows_project - Win32 Debug\" (based on \"Win32 (x86) Application\")\r\n".
- "!MESSAGE \r\n".
- "\r\n".
- "# Begin Project\r\n".
- "# PROP AllowPerConfigDependencies 0\r\n".
- "# PROP Scc_ProjName \"\"\r\n".
- "# PROP Scc_LocalPath \"\"\r\n".
- "CPP=cl.exe\r\n".
- "MTL=midl.exe\r\n".
- "RSC=rc.exe\r\n".
- "\r\n".
- "!IF \"\$(CFG)\" == \"$windows_project - Win32 Release\"\r\n".
- "\r\n".
- "# PROP BASE Use_MFC 0\r\n".
- "# PROP BASE Use_Debug_Libraries 0\r\n".
- "# PROP BASE Output_Dir \"Release\"\r\n".
- "# PROP BASE Intermediate_Dir \"Release\"\r\n".
- "# PROP BASE Target_Dir \"\"\r\n".
- "# PROP Use_MFC 0\r\n".
- "# PROP Use_Debug_Libraries 0\r\n".
- "# PROP Output_Dir \"Release\"\r\n".
- "# PROP Intermediate_Dir \"Release\"\r\n".
- "# PROP Ignore_Export_Lib 0\r\n".
- "# PROP Target_Dir \"\"\r\n".
- "# ADD BASE CPP /nologo /W3 /GX /O2 ".
- (join " ", map {"/I \"..\\..\\$dirpfx$_\""} @srcdirs) .
- " /D \"WIN32\" /D \"NDEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /c\r\n".
- "# ADD CPP /nologo /W3 /GX /O2 ".
- (join " ", map {"/I \"..\\..\\$dirpfx$_\""} @srcdirs) .
- " /D \"WIN32\" /D \"NDEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /c\r\n".
- "# ADD BASE MTL /nologo /D \"NDEBUG\" /mktyplib203 /win32\r\n".
- "# ADD MTL /nologo /D \"NDEBUG\" /mktyplib203 /win32\r\n".
- "# ADD BASE RSC /l 0x809 /d \"NDEBUG\"\r\n".
- "# ADD RSC /l 0x809 /d \"NDEBUG\"\r\n".
- "BSC32=bscmake.exe\r\n".
- "# ADD BASE BSC32 /nologo\r\n".
- "# ADD BSC32 /nologo\r\n".
- "LINK32=link.exe\r\n".
- "# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:$subsys /machine:I386\r\n".
- "# ADD LINK32 $libs /nologo /subsystem:$subsys /machine:I386\r\n".
- "# SUBTRACT LINK32 /pdb:none\r\n".
- "\r\n".
- "!ELSEIF \"\$(CFG)\" == \"$windows_project - Win32 Debug\"\r\n".
- "\r\n".
- "# PROP BASE Use_MFC 0\r\n".
- "# PROP BASE Use_Debug_Libraries 1\r\n".
- "# PROP BASE Output_Dir \"Debug\"\r\n".
- "# PROP BASE Intermediate_Dir \"Debug\"\r\n".
- "# PROP BASE Target_Dir \"\"\r\n".
- "# PROP Use_MFC 0\r\n".
- "# PROP Use_Debug_Libraries 1\r\n".
- "# PROP Output_Dir \"Debug\"\r\n".
- "# PROP Intermediate_Dir \"Debug\"\r\n".
- "# PROP Ignore_Export_Lib 0\r\n".
- "# PROP Target_Dir \"\"\r\n".
- "# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od ".
- (join " ", map {"/I \"..\\..\\$dirpfx$_\""} @srcdirs) .
- " /D \"WIN32\" /D \"_DEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /GZ /c\r\n".
- "# ADD CPP /nologo /W3 /Gm /GX /ZI /Od ".
- (join " ", map {"/I \"..\\..\\$dirpfx$_\""} @srcdirs) .
- " /D \"WIN32\" /D \"_DEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /GZ /c\r\n".
- "# ADD BASE MTL /nologo /D \"_DEBUG\" /mktyplib203 /win32\r\n".
- "# ADD MTL /nologo /D \"_DEBUG\" /mktyplib203 /win32\r\n".
- "# ADD BASE RSC /l 0x809 /d \"_DEBUG\"\r\n".
- "# ADD RSC /l 0x809 /d \"_DEBUG\"\r\n".
- "BSC32=bscmake.exe\r\n".
- "# ADD BASE BSC32 /nologo\r\n".
- "# ADD BSC32 /nologo\r\n".
- "LINK32=link.exe\r\n".
- "# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:$subsys /debug /machine:I386 /pdbtype:sept\r\n".
- "# ADD LINK32 $libs /nologo /subsystem:$subsys /debug /machine:I386 /pdbtype:sept\r\n".
- "# SUBTRACT LINK32 /pdb:none\r\n".
- "\r\n".
- "!ENDIF \r\n".
- "\r\n".
- "# Begin Target\r\n".
- "\r\n".
- "# Name \"$windows_project - Win32 Release\"\r\n".
- "# Name \"$windows_project - Win32 Debug\"\r\n".
- "# Begin Group \"Source Files\"\r\n".
- "\r\n".
- "# PROP Default_Filter \"cpp;c;cxx;rc;def;r;odl;idl;hpj;bat\"\r\n";
- foreach $source_file (@source_files) {
- print
- "# Begin Source File\r\n".
- "\r\n".
- "SOURCE=..\\..\\$source_file\r\n";
- if($source_file =~ /ssh\.c/io) {
- # Disable 'Edit and continue' as Visual Studio can't handle the macros
- print
- "\r\n".
- "!IF \"\$(CFG)\" == \"$windows_project - Win32 Release\"\r\n".
- "\r\n".
- "!ELSEIF \"\$(CFG)\" == \"$windows_project - Win32 Debug\"\r\n".
- "\r\n".
- "# ADD CPP /Zi\r\n".
- "\r\n".
- "!ENDIF \r\n".
- "\r\n";
- }
- print "# End Source File\r\n";
- }
- print
- "# End Group\r\n".
- "# Begin Group \"Header Files\"\r\n".
- "\r\n".
- "# PROP Default_Filter \"h;hpp;hxx;hm;inl\"\r\n";
- foreach $header_file (@header_files) {
- print
- "# Begin Source File\r\n".
- "\r\n".
- "SOURCE=..\\..\\$header_file\r\n".
- "# End Source File\r\n";
- }
- print
- "# End Group\r\n".
- "# Begin Group \"Resource Files\"\r\n".
- "\r\n".
- "# PROP Default_Filter \"ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe\"\r\n";
- foreach $resource_file (@resources) {
- print
- "# Begin Source File\r\n".
- "\r\n".
- "SOURCE=..\\..\\$resource_file\r\n".
- "# End Source File\r\n";
- }
- print
- "# End Group\r\n".
- "# End Target\r\n".
- "# End Project\r\n";
- select STDOUT; close OUT;
- chdir "..";
- }
-}
-
-if (defined $makefiles{'vstudio10'} || defined $makefiles{'vstudio12'}) {
-
- ##-- Visual Studio 2010+ Solution and Projects
-
- if (defined $makefiles{'vstudio10'}) {
- create_vs_solution('vstudio10', "2010", "11.00", "v100");
- }
-
- if (defined $makefiles{'vstudio12'}) {
- create_vs_solution('vstudio12', "2012", "12.00", "v110");
- }
-
- sub create_vs_solution {
- my ($makefilename, $name, $version, $toolsver) = @_;
-
- $dirpfx = &dirpfx($makefiles{$makefilename}, "\\");
-
- @deps = &deps("X.obj", "X.res", $dirpfx, "\\", $makefilename);
- %all_object_deps = map {$_->{obj} => $_->{deps}} @deps;
-
- my @prognames = &prognames("G:C");
-
- # Create the solution file.
- mkdir $makefiles{$makefilename}
- if(! -f $makefiles{$makefilename});
- chdir $makefiles{$makefilename};
-
- open OUT, ">$project_name.sln"; select OUT;
-
- print
- "Microsoft Visual Studio Solution File, Format Version $version\n" .
- "# Visual Studio $name\n";
-
- my %projguids = ();
- foreach $progname (@prognames) {
- ($windows_project, $type) = split ",", $progname;
-
- $projguids{$windows_project} = $guid =
- &invent_guid("project:$progname");
-
- print
- "Project(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"$windows_project\", \"$windows_project\\$windows_project.vcxproj\", \"{$guid}\"\n" .
- "EndProject\n";
- }
-
- print
- "Global\n" .
- " GlobalSection(SolutionConfigurationPlatforms) = preSolution\n" .
- " Debug|Win32 = Debug|Win32\n" .
- " Release|Win32 = Release|Win32\n" .
- " EndGlobalSection\n" .
- " GlobalSection(ProjectConfigurationPlatforms) = postSolution\n" ;
-
- foreach my $projguid (values %projguids) {
- print
- " {$projguid}.Debug|Win32.ActiveCfg = Debug|Win32\n" .
- " {$projguid}.Debug|Win32.Build.0 = Debug|Win32\n" .
- " {$projguid}.Release|Win32.ActiveCfg = Release|Win32\n" .
- " {$projguid}.Release|Win32.Build.0 = Release|Win32\n";
- }
-
- print
- " EndGlobalSection\n" .
- " GlobalSection(SolutionProperties) = preSolution\n" .
- " HideSolutionNode = FALSE\n" .
- " EndGlobalSection\n" .
- "EndGlobal\n";
-
- select STDOUT; close OUT;
-
- foreach $progname (@prognames) {
- ($windows_project, $type) = split ",", $progname;
- create_vs_project(\%all_object_deps, $windows_project, $type, $projguids{$windows_project}, $toolsver);
- }
-
- chdir $orig_dir;
- }
-
- sub create_vs_project {
- my ($all_object_deps, $windows_project, $type, $projguid, $toolsver) = @_;
-
- # Break down the project's dependency information into the appropriate
- # groups.
- %seen_objects = ();
- %lib_files = ();
- %source_files = ();
- %header_files = ();
- %resource_files = ();
- %icon_files = ();
-
- @object_files = split " ", &objects($progname, "X.obj", "X.res", "X.lib");
- foreach $object_file (@object_files) {
- next if defined $seen_objects{$object_file};
- $seen_objects{$object_file} = 1;
-
- if($object_file =~ /\.lib$/io) {
- $lib_files{$object_file} = 1;
- next;
- }
-
- $object_deps = $all_object_deps{$object_file};
- foreach $object_dep (@$object_deps) {
- if($object_dep eq $object_deps->[0]) {
- if($object_dep =~ /\.c$/io) {
- $source_files{$object_dep} = 1;
- } elsif($object_dep =~ /\.rc$/io) {
- $resource_files{$object_dep} = 1;
- }
- } elsif ($object_dep =~ /\.[ch]$/io) {
- $header_files{$object_dep} = 1;
- } elsif ($object_dep =~ /\.ico$/io) {
- $icon_files{$object_dep} = 1;
- }
- }
- }
-
- $libs = join ";", sort keys %lib_files;
- @source_files = sort keys %source_files;
- @header_files = sort keys %header_files;
- @resources = sort keys %resource_files;
- @icons = sort keys %icon_files;
- $subsystem = ($type eq "G") ? "Windows" : "Console";
-
- mkdir $windows_project
- if(! -d $windows_project);
- chdir $windows_project;
- open OUT, ">$windows_project.vcxproj"; select OUT;
- open FILTERS, ">$windows_project.vcxproj.filters";
-
- # The bulk of the project file is just boilerplate stuff, so we
- # can mostly just dump it out here. Note, buried in the ClCompile
- # item definition, that we use a debug information format of
- # ProgramDatabase, which disables the edit-and-continue support
- # that breaks most of the project builds.
- print
- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" .
- "<Project DefaultTargets=\"Build\" ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n" .
- " <ItemGroup Label=\"ProjectConfigurations\">\n" .
- " <ProjectConfiguration Include=\"Debug|Win32\">\n" .
- " <Configuration>Debug</Configuration>\n" .
- " <Platform>Win32</Platform>\n" .
- " </ProjectConfiguration>\n" .
- " <ProjectConfiguration Include=\"Release|Win32\">\n" .
- " <Configuration>Release</Configuration>\n" .
- " <Platform>Win32</Platform>\n" .
- " </ProjectConfiguration>\n" .
- " </ItemGroup>\n" .
- " <PropertyGroup Label=\"Globals\">\n" .
- " <SccProjectName />\n" .
- " <SccLocalPath />\n" .
- " <ProjectGuid>{$projguid}</ProjectGuid>\n" .
- " </PropertyGroup>\n" .
- " <Import Project=\"\$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n" .
- " <PropertyGroup Condition=\"'\$(Configuration)|\$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\n" .
- " <ConfigurationType>Application</ConfigurationType>\n" .
- " <UseOfMfc>false</UseOfMfc>\n" .
- " <CharacterSet>MultiByte</CharacterSet>\n" .
- " <PlatformToolset>$toolsver</PlatformToolset>\n" .
- " </PropertyGroup>\n" .
- " <PropertyGroup Condition=\"'\$(Configuration)|\$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\n" .
- " <ConfigurationType>Application</ConfigurationType>\n" .
- " <UseOfMfc>false</UseOfMfc>\n" .
- " <CharacterSet>MultiByte</CharacterSet>\n" .
- " <PlatformToolset>$toolsver</PlatformToolset>\n" .
- " </PropertyGroup>\n" .
- " <Import Project=\"\$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n" .
- " <ImportGroup Label=\"ExtensionTargets\">\n" .
- " </ImportGroup>\n" .
- " <ImportGroup Condition=\"'\$(Configuration)|\$(Platform)'=='Debug|Win32'\" Label=\"PropertySheets\">\n" .
- " <Import Project=\"\$(UserRootDir)\\Microsoft.Cpp.\$(Platform).user.props\" Condition=\"exists('\$(UserRootDir)\\Microsoft.Cpp.\$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n" .
- " </ImportGroup>\n" .
- " <ImportGroup Condition=\"'\$(Configuration)|\$(Platform)'=='Release|Win32'\" Label=\"PropertySheets\">\n" .
- " <Import Project=\"\$(UserRootDir)\\Microsoft.Cpp.\$(Platform).user.props\" Condition=\"exists('\$(UserRootDir)\\Microsoft.Cpp.\$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n" .
- " </ImportGroup>\n" .
- " <PropertyGroup Label=\"UserMacros\" />\n" .
- " <PropertyGroup Condition=\"'\$(Configuration)|\$(Platform)'=='Release|Win32'\">\n" .
- " <OutDir>.\\Release\\</OutDir>\n" .
- " <IntDir>.\\Release\\</IntDir>\n" .
- " <LinkIncremental>false</LinkIncremental>\n" .
- " </PropertyGroup>\n" .
- " <PropertyGroup Condition=\"'\$(Configuration)|\$(Platform)'=='Debug|Win32'\">\n" .
- " <OutDir>.\\Debug\\</OutDir>\n" .
- " <IntDir>.\\Debug\\</IntDir>\n" .
- " <LinkIncremental>true</LinkIncremental>\n" .
- " </PropertyGroup>\n" .
- " <ItemDefinitionGroup Condition=\"'\$(Configuration)|\$(Platform)'=='Release|Win32'\">\n" .
- " <ClCompile>\n" .
- " <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n" .
- " <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>\n" .
- " <StringPooling>true</StringPooling>\n" .
- " <FunctionLevelLinking>true</FunctionLevelLinking>\n" .
- " <Optimization>MaxSpeed</Optimization>\n" .
- " <SuppressStartupBanner>true</SuppressStartupBanner>\n" .
- " <WarningLevel>Level3</WarningLevel>\n" .
- " <AdditionalIncludeDirectories>" . (join ";", map {"..\\..\\$dirpfx$_"} @srcdirs) . ";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n" .
- " <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;POSIX;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n" .
- " <AssemblerListingLocation>.\\Release\\</AssemblerListingLocation>\n" .
- " <PrecompiledHeaderOutputFile>.\\Release\\$windows_project.pch</PrecompiledHeaderOutputFile>\n" .
- " <ObjectFileName>.\\Release\\</ObjectFileName>\n" .
- " <ProgramDataBaseFileName>.\\Release\\</ProgramDataBaseFileName>\n" .
- " </ClCompile>\n" .
- " <Midl>\n" .
- " <SuppressStartupBanner>true</SuppressStartupBanner>\n" .
- " <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n" .
- " <TypeLibraryName>.\\Release\\$windows_project.tlb</TypeLibraryName>\n" .
- " <MkTypLibCompatible>true</MkTypLibCompatible>\n" .
- " <TargetEnvironment>Win32</TargetEnvironment>\n" .
- " </Midl>\n" .
- " <ResourceCompile>\n" .
- " <Culture>0x0809</Culture>\n" .
- " <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n" .
- " </ResourceCompile>\n" .
- " <Bscmake>\n" .
- " <SuppressStartupBanner>true</SuppressStartupBanner>\n" .
- " <OutputFile>.\\Release\\$windows_project.bsc</OutputFile>\n" .
- " </Bscmake>\n" .
- " <Link>\n" .
- " <SuppressStartupBanner>true</SuppressStartupBanner>\n" .
- " <SubSystem>$subsystem</SubSystem>\n" .
- " <OutputFile>.\\Release\\$windows_project.exe</OutputFile>\n" .
- " <AdditionalDependencies>$libs;%(AdditionalDependencies)</AdditionalDependencies>\n" .
- " </Link>\n" .
- " </ItemDefinitionGroup>\n" .
- " <ItemDefinitionGroup Condition=\"'\$(Configuration)|\$(Platform)'=='Debug|Win32'\">\n" .
- " <ClCompile>\n" .
- " <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n" .
- " <InlineFunctionExpansion>Default</InlineFunctionExpansion>\n" .
- " <FunctionLevelLinking>false</FunctionLevelLinking>\n" .
- " <Optimization>Disabled</Optimization>\n" .
- " <SuppressStartupBanner>true</SuppressStartupBanner>\n" .
- " <WarningLevel>Level3</WarningLevel>\n" .
- " <MinimalRebuild>true</MinimalRebuild>\n" .
- " <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\n" .
- " <AdditionalIncludeDirectories>" . (join ";", map {"..\\..\\$dirpfx$_"} @srcdirs) . ";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n" .
- " <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;POSIX;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n" .
- " <AssemblerListingLocation>.\\Debug\\</AssemblerListingLocation>\n" .
- " <PrecompiledHeaderOutputFile>.\\Debug\\$windows_project.pch</PrecompiledHeaderOutputFile>\n" .
- " <ObjectFileName>.\\Debug\\</ObjectFileName>\n" .
- " <ProgramDataBaseFileName>.\\Debug\\</ProgramDataBaseFileName>\n" .
- " <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\n" .
- " </ClCompile>\n" .
- " <Midl>\n" .
- " <SuppressStartupBanner>true</SuppressStartupBanner>\n" .
- " <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n" .
- " <TypeLibraryName>.\\Debug\\$windows_project.tlb</TypeLibraryName>\n" .
- " <MkTypLibCompatible>true</MkTypLibCompatible>\n" .
- " <TargetEnvironment>Win32</TargetEnvironment>\n" .
- " </Midl>\n" .
- " <ResourceCompile>\n" .
- " <Culture>0x0809</Culture>\n" .
- " <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n" .
- " </ResourceCompile>\n" .
- " <Bscmake>\n" .
- " <SuppressStartupBanner>true</SuppressStartupBanner>\n" .
- " <OutputFile>.\\Debug\\$windows_project.bsc</OutputFile>\n" .
- " </Bscmake>\n" .
- " <Link>\n" .
- " <SuppressStartupBanner>true</SuppressStartupBanner>\n" .
- " <GenerateDebugInformation>true</GenerateDebugInformation>\n" .
- " <SubSystem>$subsystem</SubSystem>\n" .
- " <OutputFile>\$(TargetPath)</OutputFile>\n" .
- " <AdditionalDependencies>$libs;%(AdditionalDependencies)</AdditionalDependencies>\n" .
- " </Link>\n" .
- " </ItemDefinitionGroup>\n";
-
- # The VC++ projects don't have physical structure to them, instead
- # the files are organized by logical "filters" that are stored in
- # a separate file, so different users can organize things differently.
- # The filters file contains a copy of the ItemGroup elements from
- # the main project file that list the included items, but tack
- # on a filter name where needed.
- print FILTERS
- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" .
- "<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n";
-
- print " <ItemGroup>\n";
- print FILTERS " <ItemGroup>\n";
- foreach $icon_file (@icons) {
- $icon_file =~ s/..\\windows\\//;
- print " <CustomBuild Include=\"..\\..\\$icon_file\" />\n";
- print FILTERS
- " <CustomBuild Include=\"..\\..\\$icon_file\">\n" .
- " <Filter>Resource Files</Filter>\n" .
- " </CustomBuild>\n";
- }
- print FILTERS " </ItemGroup>\n";
- print " </ItemGroup>\n";
-
- print " <ItemGroup>\n";
- print FILTERS " <ItemGroup>\n";
- foreach $resource_file (@resources) {
- $resource_file =~ s/..\\windows\\//;
- print
- " <ResourceCompile Include=\"..\\..\\$resource_file\">\n" .
- " <AdditionalIncludeDirectories Condition=\"'\$(Configuration)|\$(Platform)'=='Release|Win32'\">..\\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n" .
- " <AdditionalIncludeDirectories Condition=\"'\$(Configuration)|\$(Platform)'=='Debug|Win32'\">..\\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n" .
- " </ResourceCompile>\n";
- print FILTERS
- " <ResourceCompile Include=\"..\\..\\$resource_file\">\n" .
- " <Filter>Resource Files</Filter>\n" .
- " </ResourceCompile>\n";
- }
- print FILTERS " </ItemGroup>\n";
- print " </ItemGroup>\n";
-
- print " <ItemGroup>\n";
- print FILTERS " <ItemGroup>\n";
- foreach $source_file (@source_files) {
- $source_file =~ s/..\\windows\\//;
- print " <ClCompile Include=\"..\\..\\$source_file\" />\n";
- print FILTERS
- " <ClCompile Include=\"..\\..\\$source_file\">\n" .
- " <Filter>Source Files</Filter>\n" .
- " </ClCompile>";
- }
- print FILTERS " </ItemGroup>\n";
- print " </ItemGroup>\n";
-
- print " <ItemGroup>\n";
- print FILTERS " <ItemGroup>\n";
- foreach $header_file (@header_files) {
- $header_file =~ s/..\\windows\\//;
- print " <ClInclude Include=\"..\\..\\$header_file\" />\n";
- print FILTERS
- " <ClInclude Include=\"..\\..\\$header_file\">\n" .
- " <Filter>Header Files</Filter>\n" .
- " </ClInclude>";
- }
- print FILTERS " </ItemGroup>\n";
- print " </ItemGroup>\n";
-
- print
- " <Import Project=\"\$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n" .
- "</Project>";
-
- print FILTERS
- " <ItemGroup>\n" .
- " <Filter Include=\"Source Files\">\n" .
- " <UniqueIdentifier>{" . &invent_guid("sources:$windows_project") . "}</UniqueIdentifier>\n" .
- " </Filter>\n" .
- " <Filter Include=\"Header Files\">\n" .
- " <UniqueIdentifier>{" . &invent_guid("headers:$windows_project") . "}</UniqueIdentifier>\n" .
- " </Filter>\n" .
- " <Filter Include=\"Resource Files\">\n" .
- " <UniqueIdentifier>{" . &invent_guid("resources:$windows_project") . "}</UniqueIdentifier>\n" .
- " </Filter>\n" .
- " </ItemGroup>\n" .
- "</Project>";
-
- select STDOUT; close OUT; close FILTERS;
- chdir "..";
- }
-}
-
-if (defined $makefiles{'gtk'}) {
- $dirpfx = &dirpfx($makefiles{'gtk'}, "/");
-
- ##-- X/GTK/Unix makefile
- open OUT, ">$makefiles{'gtk'}"; select OUT;
- print
- "# Makefile for $project_name under X/GTK and Unix.\n".
- "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
- "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
- # gcc command line option is -D not /D
- ($_ = $help) =~ s/([=" ])\/D/$1-D/gs;
- print $_;
- print
- "\n".
- "# You can define this path to point at your tools if you need to\n".
- "# TOOLPATH = /opt/gcc/bin\n".
- "CC = \$(TOOLPATH)cc\n".
- "# If necessary set the path to krb5-config here\n".
- "KRB5CONFIG=krb5-config\n".
- "# You can manually set this to `gtk-config' or `pkg-config gtk+-1.2'\n".
- "# (depending on what works on your system) if you want to enforce\n".
- "# building with GTK 1.2, or you can set it to `pkg-config gtk+-2.0 x11'\n".
- "# if you want to enforce 2.0. The default is to try 2.0 and fall back\n".
- "# to 1.2 if it isn't found.\n".
- "GTK_CONFIG = sh -c 'pkg-config gtk+-3.0 x11 \$\$0 2>/dev/null || pkg-config gtk+-2.0 x11 \$\$0 2>/dev/null || gtk-config \$\$0'\n".
- "\n".
- "-include Makefile.local\n".
- "\n".
- "unexport CFLAGS # work around a weird issue with krb5-config\n".
- "\n".
- &splitline("CFLAGS = -O2 -Wall -std=gnu99 -Wvla -g " .
- (join " ", map {"-I$dirpfx$_"} @srcdirs) .
- " \$(shell \$(GTK_CONFIG) --cflags)").
- " -D _FILE_OFFSET_BITS=64\n".
- "XLDFLAGS = \$(LDFLAGS) \$(shell \$(GTK_CONFIG) --libs)\n".
- "ULDFLAGS = \$(LDFLAGS)\n".
- "ifeq (,\$(findstring NO_GSSAPI,\$(COMPAT)))\n".
- "ifeq (,\$(findstring STATIC_GSSAPI,\$(COMPAT)))\n".
- "XLDFLAGS+= -ldl\n".
- "ULDFLAGS+= -ldl\n".
- "else\n".
- "CFLAGS+= -DNO_LIBDL \$(shell \$(KRB5CONFIG) --cflags gssapi)\n".
- "XLDFLAGS+= \$(shell \$(KRB5CONFIG) --libs gssapi)\n".
- "ULDFLAGS+= \$(shell \$(KRB5CONFIG) --libs gssapi)\n".
- "endif\n".
- "endif\n".
- "INSTALL=install\n".
- "INSTALL_PROGRAM=\$(INSTALL)\n".
- "INSTALL_DATA=\$(INSTALL)\n".
- "prefix=/usr/local\n".
- "exec_prefix=\$(prefix)\n".
- "bindir=\$(exec_prefix)/bin\n".
- "mandir=\$(prefix)/man\n".
- "man1dir=\$(mandir)/man1\n".
- "\n".
- &def($makefile_extra{'gtk'}->{'vars'}) .
- "\n".
- ".SUFFIXES:\n".
- "\n".
- "\n";
- print &splitline("all:" . join "", map { " $_" }
- &progrealnames("X:XT:U:UT"));
- print "\n\n";
- foreach $p (&prognames("X:XT:U:UT")) {
- ($prog, $type) = split ",", $p;
- ($ldflags = $type) =~ s/T$//;
- $objstr = &objects($p, "X.o", undef, undef);
- print &splitline($prog . ": " . $objstr), "\n";
- $libstr = &objects($p, undef, undef, "-lX");
- print &splitline("\t\$(CC) -o \$@ " .
- $objstr . " \$(${ldflags}LDFLAGS) $libstr", 69), "\n\n";
- }
- foreach $d (&deps("X.o", undef, $dirpfx, "/", "gtk")) {
- if ($forceobj{$d->{obj_orig}}) {
- printf("%s: FORCE\n", $d->{obj});
- } else {
- print &splitline(sprintf("%s: %s", $d->{obj},
- join " ", @{$d->{deps}})), "\n";
- }
- print &splitline("\t\$(CC) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) -c $d->{deps}->[0]\n");
- }
- print "\n";
- print &def($makefile_extra{'gtk'}->{'end'});
- print "\nclean:\n".
- "\trm -f *.o". (join "", map { " $_" } &progrealnames("X:XT:U:UT")) . "\n";
- print "\nFORCE:\n";
- select STDOUT; close OUT;
-}
-
-if (defined $makefiles{'unix'}) {
- $dirpfx = &dirpfx($makefiles{'unix'}, "/");
-
- ##-- GTK-free pure-Unix makefile for non-GUI apps only
- open OUT, ">$makefiles{'unix'}"; select OUT;
- print
- "# Makefile for $project_name under Unix.\n".
- "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
- "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
- # gcc command line option is -D not /D
- ($_ = $help) =~ s/([=" ])\/D/$1-D/gs;
- print $_;
- print
- "\n".
- "# You can define this path to point at your tools if you need to\n".
- "# TOOLPATH = /opt/gcc/bin\n".
- "CC = \$(TOOLPATH)cc\n".
- "\n".
- "-include Makefile.local\n".
- "\n".
- "unexport CFLAGS # work around a weird issue with krb5-config\n".
- "\n".
- &splitline("CFLAGS = -O2 -Wall -std=gnu99 -Wvla -g " .
- (join " ", map {"-I$dirpfx$_"} @srcdirs)).
- " -D _FILE_OFFSET_BITS=64\n".
- "ULDFLAGS = \$(LDFLAGS)\n".
- "INSTALL=install\n".
- "INSTALL_PROGRAM=\$(INSTALL)\n".
- "INSTALL_DATA=\$(INSTALL)\n".
- "prefix=/usr/local\n".
- "exec_prefix=\$(prefix)\n".
- "bindir=\$(exec_prefix)/bin\n".
- "mandir=\$(prefix)/man\n".
- "man1dir=\$(mandir)/man1\n".
- "\n".
- &def($makefile_extra{'unix'}->{'vars'}) .
- "\n".
- ".SUFFIXES:\n".
- "\n".
- "\n";
- print &splitline("all:" . join "", map { " $_" } &progrealnames("U:UT"));
- print "\n\n";
- foreach $p (&prognames("U:UT")) {
- ($prog, $type) = split ",", $p;
- ($ldflags = $type) =~ s/T$//;
- $objstr = &objects($p, "X.o", undef, undef);
- print &splitline($prog . ": " . $objstr), "\n";
- $libstr = &objects($p, undef, undef, "-lX");
- print &splitline("\t\$(CC) -o \$@ " .
- $objstr . " \$(${ldflags}LDFLAGS) $libstr", 69), "\n\n";
- }
- foreach $d (&deps("X.o", undef, $dirpfx, "/", "unix")) {
- if ($forceobj{$d->{obj_orig}}) {
- printf("%s: FORCE\n", $d->{obj});
- } else {
- print &splitline(sprintf("%s: %s", $d->{obj},
- join " ", @{$d->{deps}})), "\n";
- }
- print &splitline("\t\$(CC) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) -c $d->{deps}->[0]\n");
- }
- print "\n";
- print &def($makefile_extra{'unix'}->{'end'});
- print "\nclean:\n".
- "\trm -f *.o". (join "", map { " $_" } &progrealnames("U:UT")) . "\n";
- print "\nFORCE:\n";
- select STDOUT; close OUT;
-}
-
-if (defined $makefiles{'am'}) {
- die "Makefile.am in a subdirectory is not supported\n"
- if &dirpfx($makefiles{'am'}, "/") ne "";
-
- ##-- Unix/autoconf Makefile.am
- open OUT, ">$makefiles{'am'}"; select OUT;
- print
- "# Makefile.am for $project_name under Unix with Autoconf/Automake.\n".
- "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
- "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n\n";
-
- # 2014-02-22: as of automake-1.14 we begin to get complained at if
- # we don't use this option
- print "AUTOMAKE_OPTIONS = subdir-objects\n\n";
-
- # Complete list of source and header files. Not used by the
- # auto-generated parts of this makefile, but Recipe might like to
- # have it available as a variable so that mandatory-rebuild things
- # (version.o) can conveniently be made to depend on it.
- @sources = ("allsources", "=",
- sort grep {$_ ne "empty.h"} keys %allsourcefiles);
- print &splitline(join " ", @sources), "\n\n";
-
- @cliprogs = ("bin_PROGRAMS", "=");
- foreach $p (&prognames("U")) {
- ($prog, $type) = split ",", $p;
- push @cliprogs, $prog;
- }
- @allprogs = @cliprogs;
- foreach $p (&prognames("X")) {
- ($prog, $type) = split ",", $p;
- push @allprogs, $prog;
- }
- print "if HAVE_GTK\n";
- print &splitline(join " ", @allprogs), "\n";
- print "else\n";
- print &splitline(join " ", @cliprogs), "\n";
- print "endif\n\n";
-
- @noinstcliprogs = ("noinst_PROGRAMS", "=");
- foreach $p (&prognames("UT")) {
- ($prog, $type) = split ",", $p;
- push @noinstcliprogs, $prog;
- }
- @noinstallprogs = @noinstcliprogs;
- foreach $p (&prognames("XT")) {
- ($prog, $type) = split ",", $p;
- push @noinstallprogs, $prog;
- }
- print "if HAVE_GTK\n";
- print &splitline(join " ", @noinstallprogs), "\n";
- print "else\n";
- print &splitline(join " ", @noinstcliprogs), "\n";
- print "endif\n\n";
-
- %objtosrc = ();
- foreach $d (&deps("X", undef, "", "/", "am")) {
- $objtosrc{$d->{obj}} = $d->{deps}->[0];
- }
-
- print &splitline(join " ", "AM_CPPFLAGS", "=",
- map {"-I\$(srcdir)/$_"} @srcdirs), "\n";
-
- @amcflags = ("\$(COMPAT)", "\$(XFLAGS)", "\$(WARNINGOPTS)");
- print "if HAVE_GTK\n";
- print &splitline(join " ", "AM_CFLAGS", "=",
- "\$(GTK_CFLAGS)", @amcflags), "\n";
- print "else\n";
- print &splitline(join " ", "AM_CFLAGS", "=", @amcflags), "\n";
- print "endif\n\n";
-
- %amspeciallibs = ();
- foreach $obj (sort { $a cmp $b } keys %{$cflags{'am'}}) {
- my $flags = $cflags{'am'}->{$obj};
- $flags = "" if $flags !~ s/^C//;
- print "lib${obj}_a_SOURCES = ", $objtosrc{$obj}, "\n";
- print &splitline(join " ", "lib${obj}_a_CFLAGS", "=", @amcflags,
- $flags), "\n";
- $amspeciallibs{$obj} = "lib${obj}.a";
- }
- print &splitline(join " ", "noinst_LIBRARIES", "=",
- sort { $a cmp $b } values %amspeciallibs), "\n\n";
-
- foreach $p (&prognames("X:XT:U:UT")) {
- ($prog, $type) = split ",", $p;
- print "if HAVE_GTK\n" if $type eq "X" || $type eq "XT";
- @progsources = ("${prog}_SOURCES", "=");
- %sourcefiles = ();
- @ldadd = ();
- $objstr = &objects($p, "X", undef, undef);
- foreach $obj (split / /,$objstr) {
- if ($amspeciallibs{$obj}) {
- push @ldadd, $amspeciallibs{$obj};
- } else {
- $sourcefiles{$objtosrc{$obj}} = 1;
- }
- }
- push @progsources, sort { $a cmp $b } keys %sourcefiles;
- print &splitline(join " ", @progsources), "\n";
- if ($type eq "X" || $type eq "XT") {
- push @ldadd, "\$(GTK_LIBS)";
- }
- if (@ldadd) {
- print &splitline(join " ", "${prog}_LDADD", "=", @ldadd), "\n";
- }
- print "endif\n" if $type eq "X" || $type eq "XT";
- print "\n";
- }
- print &def($makefile_extra{'am'}->{'end'});
- select STDOUT; close OUT;
-}
-
-if (defined $makefiles{'lcc'}) {
- $dirpfx = &dirpfx($makefiles{'lcc'}, "\\");
-
- ##-- lcc makefile
- open OUT, ">$makefiles{'lcc'}"; select OUT;
- print
- "# Makefile for $project_name under lcc.\n".
- "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
- "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
- # lcc command line option is -D not /D
- ($_ = $help) =~ s/([=" ])\/D/$1-D/gs;
- print $_;
- print
- "\n".
- "# If you rename this file to `Makefile', you should change this line,\n".
- "# so that the .rsp files still depend on the correct makefile.\n".
- "MAKEFILE = Makefile.lcc\n".
- "\n".
- "# C compilation flags\n".
- "CFLAGS = -D_WINDOWS " .
- (join " ", map {"-I$dirpfx$_"} @srcdirs) .
- "\n".
- "# Resource compilation flags\n".
- "RCFLAGS = ".(join " ", map {"-I$dirpfx$_"} @srcdirs)."\n".
- "\n".
- "# Get include directory for resource compiler\n".
- "\n".
- &def($makefile_extra{'lcc'}->{'vars'}) .
- "\n";
- print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
- print "\n\n";
- foreach $p (&prognames("G:C")) {
- ($prog, $type) = split ",", $p;
- $objstr = &objects($p, "X.obj", "X.res", undef);
- print &splitline("$prog.exe: " . $objstr ), "\n";
- $subsystemtype = '';
- if ($type eq "G") { $subsystemtype = "-subsystem windows"; }
- my $libss = "shell32.lib wsock32.lib ws2_32.lib winspool.lib winmm.lib imm32.lib";
- print &splitline("\tlcclnk $subsystemtype -o $prog.exe $objstr $libss");
- print "\n\n";
- }
-
- foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\", "lcc")) {
- if ($forceobj{$d->{obj_orig}}) {
- printf("%s: FORCE\n", $d->{obj});
- } else {
- print &splitline(sprintf("%s: %s", $d->{obj},
- join " ", @{$d->{deps}})), "\n";
- }
- if ($d->{obj} =~ /\.obj$/) {
- print &splitline("\tlcc -O -p6 \$(COMPAT)".
- " \$(CFLAGS) \$(XFLAGS) ".$d->{deps}->[0],69)."\n";
- } else {
- print &splitline("\tlrc \$(RCFL) -r \$(RCFLAGS) ".
- $d->{deps}->[0],69)."\n";
- }
- }
- print "\n";
- print &def($makefile_extra{'lcc'}->{'end'});
- print "\nclean:\n".
- "\t-del *.obj\n".
- "\t-del *.exe\n".
- "\t-del *.res\n".
- "\n".
- "FORCE:\n";
-
- select STDOUT; close OUT;
-}
-
-if (defined $makefiles{'osx'}) {
- $dirpfx = &dirpfx($makefiles{'osx'}, "/");
-
- ##-- Mac OS X makefile
- open OUT, ">$makefiles{'osx'}"; select OUT;
- print
- "# Makefile for $project_name under Mac OS X.\n".
- "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
- "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
- # gcc command line option is -D not /D
- ($_ = $help) =~ s/([=" ])\/D/$1-D/gs;
- print $_;
- print
- "CC = \$(TOOLPATH)gcc\n".
- "\n".
- &splitline("CFLAGS = -O2 -Wall -std=gnu99 -Wvla -g " .
- (join " ", map {"-I$dirpfx$_"} @srcdirs))."\n".
- "MLDFLAGS = -framework Cocoa\n".
- "ULDFLAGS =\n".
- "\n" .
- &def($makefile_extra{'osx'}->{'vars'}) .
- "\n" .
- &splitline("all:" . join "", map { " $_" } &progrealnames("MX:U:UT")) .
- "\n";
- foreach $p (&prognames("MX")) {
- ($prog, $type) = split ",", $p;
- $objstr = &objects($p, "X.o", undef, undef);
- $icon = &special($p, ".icns");
- $infoplist = &special($p, "info.plist");
- print "${prog}.app:\n\tmkdir -p \$\@\n";
- print "${prog}.app/Contents: ${prog}.app\n\tmkdir -p \$\@\n";
- print "${prog}.app/Contents/MacOS: ${prog}.app/Contents\n\tmkdir -p \$\@\n";
- $targets = "${prog}.app/Contents/MacOS/$prog";
- if (defined $icon) {
- print "${prog}.app/Contents/Resources: ${prog}.app/Contents\n\tmkdir -p \$\@\n";
- print "${prog}.app/Contents/Resources/${prog}.icns: ${prog}.app/Contents/Resources $icon\n\tcp $icon \$\@\n";
- $targets .= " ${prog}.app/Contents/Resources/${prog}.icns";
- }
- if (defined $infoplist) {
- print "${prog}.app/Contents/Info.plist: ${prog}.app/Contents/Resources $infoplist\n\tcp $infoplist \$\@\n";
- $targets .= " ${prog}.app/Contents/Info.plist";
- }
- $targets .= " \$(${prog}_extra)";
- print &splitline("${prog}: $targets", 69) . "\n\n";
- print &splitline("${prog}.app/Contents/MacOS/$prog: ".
- "${prog}.app/Contents/MacOS " . $objstr), "\n";
- $libstr = &objects($p, undef, undef, "-lX");
- print &splitline("\t\$(CC) \$(MLDFLAGS) -o \$@ " .
- $objstr . " $libstr", 69), "\n\n";
- }
- foreach $p (&prognames("U:UT")) {
- ($prog, $type) = split ",", $p;
- $objstr = &objects($p, "X.o", undef, undef);
- print &splitline($prog . ": " . $objstr), "\n";
- $libstr = &objects($p, undef, undef, "-lX");
- print &splitline("\t\$(CC) \$(ULDFLAGS) -o \$@ " .
- $objstr . " $libstr", 69), "\n\n";
- }
- foreach $d (&deps("X.o", undef, $dirpfx, "/", "osx")) {
- if ($forceobj{$d->{obj_orig}}) {
- printf("%s: FORCE\n", $d->{obj});
- } else {
- print &splitline(sprintf("%s: %s", $d->{obj},
- join " ", @{$d->{deps}})), "\n";
- }
- $firstdep = $d->{deps}->[0];
- if ($firstdep =~ /\.c$/) {
- print "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS) -c \$<\n";
- } elsif ($firstdep =~ /\.m$/) {
- print "\t\$(CC) -x objective-c \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS) -c \$<\n";
- }
- }
- print "\n".&def($makefile_extra{'osx'}->{'end'});
- print "\nclean:\n".
- "\trm -f *.o *.dmg". (join "", map { " $_" } &progrealnames("U:UT")) . "\n".
- "\trm -rf *.app\n".
- "\n".
- "FORCE:\n";
- select STDOUT; close OUT;
-}
-
-if (defined $makefiles{'devcppproj'}) {
- $dirpfx = &dirpfx($makefiles{'devcppproj'}, "\\");
- $orig_dir = cwd;
-
- ##-- Dev-C++ 5 projects
- #
- # Note: All files created in this section are written in binary
- # mode to prevent any posibility of misinterpreted line endings.
- # I don't know if Dev-C++ is as touchy as MSVC with LF-only line
- # endings. But however, CRLF line endings are the common way on
- # Win32 machines where Dev-C++ is running.
- # Hence, in order for mkfiles.pl to generate CRLF project files
- # even when run from Unix, I make sure all files are binary and
- # explicitly write the CRLFs.
- #
- # Create directories if necessary
- mkdir $makefiles{'devcppproj'}
- if(! -d $makefiles{'devcppproj'});
- chdir $makefiles{'devcppproj'};
- @deps = &deps("X.obj", "X.res", $dirpfx, "\\", "devcppproj");
- %all_object_deps = map {$_->{obj} => $_->{deps}} @deps;
- # Make dir names FAT/NTFS compatible
- my @srcdirs = @srcdirs;
- for ($i=0; $i<@srcdirs; $i++) {
- $srcdirs[$i] =~ s/\//\\/g;
- $srcdirs[$i] =~ s/\\$//;
- }
- # Create the project files
- # Get names of all Windows projects (GUI and console)
- my @prognames = &prognames("G:C");
- foreach $progname (@prognames) {
- create_devcpp_project(\%all_object_deps, $progname);
- }
-
- chdir $orig_dir;
-
- sub create_devcpp_project {
- my ($all_object_deps, $progname) = @_;
- # Construct program's dependency info (Taken from 'vcproj', seems to work right here, too.)
- %seen_objects = ();
- %lib_files = ();
- %source_files = ();
- %header_files = ();
- %resource_files = ();
- @object_files = split " ", &objects($progname, "X.obj", "X.res", "X.lib");
- foreach $object_file (@object_files) {
- next if defined $seen_objects{$object_file};
- $seen_objects{$object_file} = 1;
- if($object_file =~ /\.lib$/io) {
- $lib_files{$object_file} = 1;
- next;
- }
- $object_deps = $all_object_deps{$object_file};
- foreach $object_dep (@$object_deps) {
- if($object_dep =~ /\.c$/io) {
- $source_files{$object_dep} = 1;
- next;
- }
- if($object_dep =~ /\.h$/io) {
- $header_files{$object_dep} = 1;
- next;
- }
- if($object_dep =~ /\.(rc|ico)$/io) {
- $resource_files{$object_dep} = 1;
- next;
- }
- }
- }
- $libs = join " ", sort keys %lib_files;
- @source_files = sort keys %source_files;
- @header_files = sort keys %header_files;
- @resources = sort keys %resource_files;
- ($windows_project, $type) = split ",", $progname;
- mkdir $windows_project
- if(! -d $windows_project);
- chdir $windows_project;
-
- $subsys = ($type eq "G") ? "0" : "1"; # 0 = Win32 GUI, 1 = Win32 Console
- open OUT, ">$windows_project.dev"; binmode OUT; select OUT;
- print
- "# DEV-C++ 5 Project File - $windows_project.dev\r\n".
- "# ** DO NOT EDIT **\r\n".
- "\r\n".
- # No difference between DEBUG and RELEASE here as in 'vcproj', because
- # Dev-C++ does not support multiple compilation profiles in one single project.
- # (At least I can say this for Dev-C++ 5 Beta)
- "[Project]\r\n".
- "FileName=$windows_project.dev\r\n".
- "Name=$windows_project\r\n".
- "Ver=1\r\n".
- "IsCpp=1\r\n".
- "Type=$subsys\r\n".
- # Multimon is disabled here, as Dev-C++ (Version 5 Beta) does not have multimon.h
- "Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_\@\@_\r\n".
- "CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_\@\@_\r\n".
- "Includes=" . (join ";", map {"..\\..\\$dirpfx$_"} @srcdirs) . "\r\n".
- "Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_\@\@_\r\n".
- "Libs=\r\n".
- "UnitCount=" . (@source_files + @header_files + @resources) . "\r\n".
- "Folders=\"Header Files\",\"Resource Files\",\"Source Files\"\r\n".
- "ObjFiles=\r\n".
- "PrivateResource=${windows_project}_private.rc\r\n".
- "ResourceIncludes=..\\..\\..\\WINDOWS\r\n".
- "MakeIncludes=\r\n".
- "Icon=\r\n". # It's ok to leave this blank.
- "ExeOutput=\r\n".
- "ObjectOutput=\r\n".
- "OverrideOutput=0\r\n".
- "OverrideOutputName=$windows_project.exe\r\n".
- "HostApplication=\r\n".
- "CommandLine=\r\n".
- "UseCustomMakefile=0\r\n".
- "CustomMakefile=\r\n".
- "IncludeVersionInfo=0\r\n".
- "SupportXPThemes=0\r\n".
- "CompilerSet=0\r\n".
- "CompilerSettings=0000000000000000000000\r\n".
- "\r\n";
- $unit_count = 1;
- foreach $source_file (@source_files) {
- print
- "[Unit$unit_count]\r\n".
- "FileName=..\\..\\$source_file\r\n".
- "Folder=Source Files\r\n".
- "Compile=1\r\n".
- "CompileCpp=0\r\n".
- "Link=1\r\n".
- "Priority=1000\r\n".
- "OverrideBuildCmd=0\r\n".
- "BuildCmd=\r\n".
- "\r\n";
- $unit_count++;
- }
- foreach $header_file (@header_files) {
- print
- "[Unit$unit_count]\r\n".
- "FileName=..\\..\\$header_file\r\n".
- "Folder=Header Files\r\n".
- "Compile=1\r\n".
- "CompileCpp=1\r\n". # Dev-C++ want's to compile all header files with both compilers C and C++. It does not hurt.
- "Link=1\r\n".
- "Priority=1000\r\n".
- "OverrideBuildCmd=0\r\n".
- "BuildCmd=\r\n".
- "\r\n";
- $unit_count++;
- }
- foreach $resource_file (@resources) {
- if ($resource_file =~ /.*\.(ico|cur|bmp|dlg|rc2|rct|bin|rgs|gif|jpg|jpeg|jpe)/io) { # Default filter as in 'vcproj'
- $Compile = "0"; # Don't compile images and other binary resource files
- $CompileCpp = "0";
- } else {
- $Compile = "1";
- $CompileCpp = "1"; # Dev-C++ want's to compile all .rc files with both compilers C and C++. It does not hurt.
- }
- print
- "[Unit$unit_count]\r\n".
- "FileName=..\\..\\$resource_file\r\n".
- "Folder=Resource Files\r\n".
- "Compile=$Compile\r\n".
- "CompileCpp=$CompileCpp\r\n".
- "Link=0\r\n".
- "Priority=1000\r\n".
- "OverrideBuildCmd=0\r\n".
- "BuildCmd=\r\n".
- "\r\n";
- $unit_count++;
- }
- #Note: By default, [VersionInfo] is not used.
- print
- "[VersionInfo]\r\n".
- "Major=0\r\n".
- "Minor=0\r\n".
- "Release=1\r\n".
- "Build=1\r\n".
- "LanguageID=1033\r\n".
- "CharsetID=1252\r\n".
- "CompanyName=\r\n".
- "FileVersion=0.1\r\n".
- "FileDescription=\r\n".
- "InternalName=\r\n".
- "LegalCopyright=\r\n".
- "LegalTrademarks=\r\n".
- "OriginalFilename=$windows_project.exe\r\n".
- "ProductName=$windows_project\r\n".
- "ProductVersion=0.1\r\n".
- "AutoIncBuildNr=0\r\n";
- select STDOUT; close OUT;
- chdir "..";
- }
-}
-
-# All done, so do the Unix postprocessing if asked to.
-
-if ($do_unix) {
- chdir $orig_dir;
- system "./mkauto.sh";
- die "mkfiles.pl: mkauto.sh returned $?\n" if $? > 0;
- if ($do_unix == 1) {
- chdir ($targetdir = "unix")
- or die "$targetdir: chdir: $!\n";
- }
- system "./configure", @confargs;
- die "mkfiles.pl: configure returned $?\n" if $? > 0;
-}
-
-sub invent_guid($) {
- my ($name) = @_;
-
- # Invent a GUID for use in Visual Studio project files. We need
- # a few of these for every executable file we build.
- #
- # In order to avoid having to use the non-core Perl module
- # Data::GUID, and also arrange for GUIDs to be stable, we generate
- # our GUIDs by hashing a pile of fixed (but originally randomly
- # generated) data with the filename for which we need an id.
- #
- # Hashing _just_ the filenames would clearly be cheating (it's
- # quite conceivable that someone might hash the same string for
- # another reason and so generate a colliding GUID), but hashing a
- # whole SHA-512 data block of random gibberish as well should make
- # these GUIDs pseudo-random enough to not collide with anyone
- # else's.
-
- my $randdata = pack "N*",
- 0xD4AB035F,0x76998BA0,0x2DCCB0BD,0x6D3FA320,0x53638051,0xFE312F35,
- 0xDE1CECC0,0x784DF852,0x6C9F4589,0x54B7AC23,0x14E7A1C4,0xF9BF04DF,
- 0x19C08B6D,0x3FB69EF1,0xB2DA9043,0xDB5362F3,0x25718DB6,0x733560DA,
- 0xFEF871B0,0xFECF7A0C,0x67D19C95,0xB492E911,0xF5D562A3,0xFCE1D478,
- 0x02C50434,0xF7326B7E,0x93D39872,0xCF0D0269,0x9EF24C0F,0x827689AD,
- 0x88BD20BC,0x74EA6AFE,0x29223682,0xB9AB9287,0x7EA7CE4F,0xCF81B379,
- 0x9AE4A954,0x81C7AD97,0x2FF2F031,0xC51DA3C2,0xD311CCE7,0x0A31EB8B,
- 0x1AB04242,0xAF53B714,0xFC574D40,0x8CB4ED01,0x29FEB16F,0x4904D7ED,
- 0xF5C5F5E1,0xF138A4C2,0xA9D881CE,0xCEA65187,0x4421BA97,0x0EE8428E,
- 0x9556E384,0x6D0484C9,0x561BD84B,0xD9516A40,0x6B4FD33F,0xDDFFE4C8,
- 0x3D5DF8A5,0xFE6B7D99,0x3443371B,0xF4E30A3E,0xE62B9FDA,0x6BAA75DB,
- 0x9EF3C2C7,0x6815CA42,0xE6536076,0xF851E6E2,0x39D16E69,0xBCDF3BB6,
- 0x50EFFA41,0x378CDF2A,0xB5EC0D0C,0x1E94C433,0xE818241A,0x2689EB1F,
- 0xB649CEF9,0xD7344D46,0x59C1BB13,0x27511FDF,0x7DAD1768,0xB355E29E,
- 0xDFAE550C,0x2433005B,0x09DE10B0,0xAA00BA6B,0xC144ED2D,0x8513D007,
- 0xB0315232,0x7A10DAB6,0x1D97654E,0xF048214D,0xE3059E75,0x83C225D1,
- 0xFC7AB177,0x83F2B553,0x79F7A0AF,0x1C94582C,0xF5E4AF4B,0xFB39C865,
- 0x58ABEB27,0xAAB28058,0x52C15A89,0x0EBE9741,0x343F4D26,0xF941202A,
- 0xA32FD32F,0xDCC055B8,0x64281BF3,0x468BD7BA,0x0CEE09D3,0xBB5FD2B6,
- 0xA528D412,0xA6A6967E,0xEAAF5DAE,0xDE7B2FAE,0xCA36887B,0x0DE196EB,
- 0x74B95EF0,0x9EB8B7C2,0x020BFC83,0x1445086F,0xBF4B61B2,0x89AFACEC,
- 0x80A5CD69,0xC790F744,0x435A6998,0x8DE7AC48,0x32F31BC9,0x8F760D3D,
- 0xF02A74CB,0xD7B47E20,0x9EC91035,0x70FDE74D,0x9B531362,0x9D81739A,
- 0x59ADC2EB,0x511555B5,0xCA84B8D5,0x3EC325FF,0x2E442A4C,0x82AF30D9,
- 0xBFD3EC87,0x90C59E07,0x1C6DC991,0x2D16B822,0x7EA44EB5,0x3A655A39,
- 0xAB640886,0x09311821,0x777801D9,0x489DBE61,0xA1FFEC65,0x978B49B1,
- 0x7DB700CD,0x263CF3D6,0xF977E89F,0xBA0B3D01,0x6C6CED19,0x1BE6F23A,
- 0x19E0ED98,0x8E71A499,0x70BA3271,0x3FB7EE98,0xABA46848,0x2B797959,
- 0x72C6DE59,0xE08B795C,0x02936C39,0x02185CCB,0xD6F3CE18,0xD0157A40,
- 0x833DEC3F,0x319B00C4,0x97B59513,0x900B81FD,0x9A022379,0x16E44E1A,
- 0x0C4CC540,0xCA98E7F9,0xF9431A26,0x290BCFAC,0x406B82C0,0xBC1C4585,
- 0x55C54528,0x811EBB77,0xD4EDD4F3,0xA70DC02E,0x8AD5C0D1,0x28D64EF4,
- 0xBEFF5C69,0x99852C4A,0xB4BBFF7B,0x069230AC,0xA3E141FA,0x4E99FB0E,
- 0xBC154DAA,0x323C7F15,0x86E0247E,0x2EEA3054,0xC9CA1D32,0x8964A006,
- 0xC93978AC,0xF9B2C159,0x03F2079E,0xB051D284,0x4A7EA9A9,0xF001DA1F,
- 0xD47A0DAA,0xCF7B6B73,0xF18293B2,0x84303E34,0xF8BC76C4,0xAFBEE24F,
- 0xB589CA80,0x77B5BF86,0x21B9FD5B,0x1A5071DF,0xA3863110,0x0E50CA61,
- 0x939151A5,0xD2A59021,0x83A9CDCE,0xCEC69767,0xC906BB16,0x3EE1FF4D,
- 0x1321EAE4,0x0BF940D6,0x52471E61,0x8A087056,0x66E54293,0xF84AAB9B,
- 0x08835EF1,0x8F12B77A,0xD86935A5,0x200281D7,0xCD3C37C9,0x30ABEC05,
- 0x7067E8A0,0x608C4838,0xC9F51CDE,0xA6D318DE,0x41C05B2A,0x694CCE0E,
- 0xC7842451,0xA3194393,0xFBDC2C84,0xA6D2B577,0xC91E7924,0x01EDA708,
- 0x22FBB61E,0x662F9B7B,0xDE3150C3,0x2397058C;
- my $digest = sha512_hex($name . "\0" . $randdata);
- return sprintf("%s-%s-%04x-%04x-%s",
- substr($digest,0,8),
- substr($digest,8,4),
- 0x4000 | (0xFFF & hex(substr($digest,12,4))),
- 0x8000 | (0x3FFF & hex(substr($digest,16,4))),
- substr($digest,20,12));
-}
diff --git a/MKUNXARC.SH b/MKUNXARC.SH
index 34aee8b2..cb79c054 100644
--- a/MKUNXARC.SH
+++ b/MKUNXARC.SH
@@ -3,16 +3,10 @@
# Build a Unix source distribution from the PuTTY CVS area.
#
# Expects the following arguments:
-# - the version number to write into configure.ac
# - the suffix to put on the Unix source tarball
# - the options to put on the 'make' command line for the docs
-autoconfver="$1"
-arcsuffix="$2"
-docver="$3"
-
-perl mkfiles.pl
-(cd doc && make -s ${docver:+"$docver"})
+arcsuffix="$1"
relver=`cat LATEST.VER`
arcname="putty$arcsuffix"
@@ -27,12 +21,9 @@ find . -name uxarc -prune -o \
-name CVS -prune -o \
-name .cvsignore -prune -o \
-name .svn -prune -o \
- -name configure.ac -prune -o \
-name '*.zip' -prune -o \
-name '*.tar.gz' -prune -o \
-type f -exec ln -s $PWD/{} uxarc/$arcname/{} \;
-sed "s/^AC_INIT(putty,.*/AC_INIT(putty, $autoconfver)/" configure.ac > uxarc/$arcname/configure.ac
-(cd uxarc/$arcname && sh mkauto.sh) 2>errors || { cat errors >&2; exit 1; }
tar -C uxarc -chzof $arcname.tar.gz $arcname
rm -rf uxarc
diff --git a/NETWORK.H b/NETWORK.H
index 89419fb7..57e6662d 100644
--- a/NETWORK.H
+++ b/NETWORK.H
@@ -51,9 +51,14 @@ typedef enum PlugLogType {
PLUGLOG_PROXY_MSG,
} PlugLogType;
+typedef enum PlugCloseType {
+ PLUGCLOSE_NORMAL,
+ PLUGCLOSE_ERROR,
+ PLUGCLOSE_BROKEN_PIPE,
+ PLUGCLOSE_USER_ABORT,
+} PlugCloseType;
+
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.
@@ -67,22 +72,63 @@ struct PlugVtable {
* 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_CONNECT_SUCCESS means we have succeeded in making a
+ * connection. `addr' gives the address we connected to, if
+ * available. (But sometimes, in cases of complicated proxy
+ * setups, it might not be available, so receivers of this log
+ * event should be prepared to deal with addr==NULL.)
*
* - PLUGLOG_PROXY_MSG means that error_msg contains a line of
* logging information from whatever the connection is being
* proxied through. This will typically be a wodge of
* standard-error output from a local proxy command, so the
* receiver should probably prefix it to indicate this.
+ *
+ * Note that sometimes log messages may be sent even to Socket
+ * types that don't involve making an outgoing connection, e.g.
+ * because the same core implementation (such as Windows handle
+ * sockets) is shared between listening and connecting sockets. So
+ * all Plugs must implement this method, even if only to ignore
+ * the logged events.
*/
- void (*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);
+ void (*log)(Plug *p, PlugLogType type, SockAddr *addr, int port,
+ const char *error_msg, int error_code);
+
+ /*
+ * Notifies the Plug that the socket is closing, and something
+ * about why.
+ *
+ * - PLUGCLOSE_NORMAL means an ordinary non-error closure. In
+ * this case, error_msg should be ignored (and hopefully
+ * callers will have passed NULL).
+ *
+ * - PLUGCLOSE_ERROR indicates that an OS error occurred, and
+ * 'error_msg' contains a string describing it, for use in
+ * diagnostics. (Ownership of the string is not transferred.)
+ * This error class covers anything other than the special
+ * case below:
+ *
+ * - PLUGCLOSE_BROKEN_PIPE behaves like PLUGCLOSE_ERROR (in
+ * particular, there's still an error message provided), but
+ * distinguishes the particular error condition signalled by
+ * EPIPE / ERROR_BROKEN_PIPE, which ssh/sharing.c needs to
+ * recognise and handle specially in one situation.
+ *
+ * - PLUGCLOSE_USER_ABORT means that the close has happened as a
+ * result of some kind of deliberate user action (e.g. hitting
+ * ^C at a password prompt presented by a proxy socket setup
+ * phase). This can be used to suppress interactive error
+ * messages sent to the user (such as dialog boxes), on the
+ * grounds that the user already knows. However, 'error_msg'
+ * will still contain some appropriate text, so that
+ * non-interactive error reporting (e.g. event logs) can still
+ * record why the connection terminated.
+ */
+ void (*closing)(Plug *p, PlugCloseType type, const char *error_msg);
+
/*
+ * Provides incoming socket data to the Plug. Three cases:
+ *
* - urgent==0. `data' points to `len' bytes of perfectly
* ordinary data.
*
@@ -92,28 +138,52 @@ struct PlugVtable {
* - 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);
+ void (*receive) (Plug *p, int urgent, const char *data, size_t len);
+
/*
- * 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.
+ * 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);
+ void (*sent) (Plug *p, size_t bufsize);
+
/*
- * `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.
+ * Only called on listener-type sockets, and is passed a
+ * constructor function+context that will create a fresh Socket
+ * describing the connection. It returns nonzero if it doesn't
+ * want the connection for some reason, or 0 on success.
*/
+ int (*accepting)(Plug *p, accept_fn_t constructor, accept_ctx_t ctx);
};
-/* proxy indirection layer */
-/* NB, control of 'addr' is passed via new_connection, which takes
- * responsibility for freeing it */
+/* Proxy indirection layer.
+ *
+ * Calling new_connection transfers ownership of 'addr': the proxy
+ * layer is now responsible for freeing it, and the caller shouldn't
+ * assume it exists any more.
+ *
+ * If calling this from a backend with a Seat, you can also give it a
+ * pointer to the backend's Interactor trait. In that situation, it
+ * might replace the backend's seat with a temporary seat of its own,
+ * and give the real Seat to an Interactor somewhere in the proxy
+ * system so that it can ask for passwords (and, in the case of SSH
+ * proxying, other prompts like host key checks). If that happens,
+ * then the resulting 'temp seat' is the backend's property, and it
+ * will have to remember to free it when cleaning up, or after
+ * flushing it back into the real seat when the network connection
+ * attempt completes.
+ *
+ * You can free your TempSeat and resume using the real Seat when one
+ * of two things happens: either your Plug's closing() method is
+ * called (indicating failure to connect), or its log() method is
+ * called with PLUGLOG_CONNECT_SUCCESS. In the latter case, you'll
+ * probably want to flush the TempSeat's contents into the real Seat,
+ * of course.
+ */
Socket *new_connection(SockAddr *addr, const char *hostname,
int port, bool privport,
bool oobinline, bool nodelay, bool keepalive,
- Plug *plug, Conf *conf);
+ Plug *plug, Conf *conf, Interactor *interactor);
Socket *new_listener(const char *srcaddr, int port, Plug *plug,
bool local_host_only, Conf *conf, int addressfamily);
SockAddr *name_lookup(const char *host, int port, char **canonicalname,
@@ -125,7 +195,13 @@ SockAddr *name_lookup(const char *host, int port, char **canonicalname,
Socket *platform_new_connection(SockAddr *addr, const char *hostname,
int port, bool privport,
bool oobinline, bool nodelay, bool keepalive,
- Plug *plug, Conf *conf);
+ Plug *plug, Conf *conf, Interactor *itr);
+
+/* callback for SSH jump-host proxying */
+Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname,
+ int port, bool privport,
+ bool oobinline, bool nodelay, bool keepalive,
+ Plug *plug, Conf *conf, Interactor *itr);
/* socket functions */
@@ -171,9 +247,14 @@ static inline void sk_write_eof(Socket *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_closing(Plug *p, PlugCloseType type, const char *msg)
+{ p->vt->closing(p, type, msg); }
+static inline void plug_closing_normal(Plug *p)
+{ p->vt->closing(p, PLUGCLOSE_NORMAL, NULL); }
+static inline void plug_closing_error(Plug *p, const char *msg)
+{ p->vt->closing(p, PLUGCLOSE_ERROR, msg); }
+static inline void plug_closing_user_abort(Plug *p)
+{ p->vt->closing(p, PLUGCLOSE_USER_ABORT, "User aborted connection setup"); }
static inline void plug_receive(Plug *p, int urg, const char *data, size_t len)
{ p->vt->receive(p, urg, data, len); }
static inline void plug_sent (Plug *p, size_t bufsize)
@@ -221,7 +302,7 @@ static inline SocketPeerInfo *sk_peer_info(Socket *s)
/*
* The structure returned from sk_peer_info, and a function to free
- * one (in misc.c).
+ * one (in utils).
*/
struct SocketPeerInfo {
int addressfamily;
@@ -259,13 +340,13 @@ struct SocketPeerInfo {
void sk_free_peer_info(SocketPeerInfo *pi);
/*
- * Simple wrapper on getservbyname(), needed by ssh.c. Returns the
+ * Simple wrapper on getservbyname(), needed by portfwd.c. Returns the
* port number, in host byte order (suitable for printf and so on).
* Returns 0 on failure. Any platform not supporting getservbyname
* can just return 0 - this function is not required to handle
* numeric port specifications.
*/
-int net_service_lookup(char *service);
+int net_service_lookup(const char *service);
/*
* Look up the local hostname; return value needs freeing.
@@ -289,15 +370,25 @@ Socket *new_error_socket_consume_string(Plug *plug, char *errmsg);
*/
extern Plug *const nullplug;
+/*
+ * Some trivial no-op plug functions, also in nullplug.c; exposed here
+ * so that other Plug implementations can use them too.
+ *
+ * In particular, nullplug_log is useful to Plugs that don't need to
+ * worry about logging.
+ */
+void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr,
+ int port, const char *err_msg, int err_code);
+void nullplug_closing(Plug *plug, PlugCloseType type, const char *error_msg);
+void nullplug_receive(Plug *plug, int urgent, const char *data, size_t len);
+void nullplug_sent(Plug *plug, size_t bufsize);
+
/* ----------------------------------------------------------------------
* Functions defined outside the network code, which have to be
* declared in this header file rather than the main putty.h because
* they use types defined here.
*/
-/*
- * 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,
@@ -306,9 +397,40 @@ void backend_socket_log(Seat *seat, LogContext *logctx,
typedef struct ProxyStderrBuf {
char buf[8192];
size_t size;
+ const char *prefix; /* must be statically allocated */
} ProxyStderrBuf;
void psb_init(ProxyStderrBuf *psb);
+void psb_set_prefix(ProxyStderrBuf *psb, const char *prefix);
void log_proxy_stderr(
Plug *plug, ProxyStderrBuf *psb, const void *vdata, size_t len);
+/* ----------------------------------------------------------------------
+ * The DeferredSocketOpener trait. This is a thing that some Socket
+ * implementations may choose to own if they need to delay actually
+ * setting up the underlying connection. For example, sockets used in
+ * local-proxy handling (Unix FdSocket / Windows HandleSocket) might
+ * need to do this if they have to prompt the user interactively for
+ * parts of the command they'll run.
+ *
+ * Mostly, a DeferredSocketOpener implementation will keep to itself,
+ * arrange its own callbacks in order to do whatever setup it needs,
+ * and when it's ready, call back to its parent Socket via some
+ * implementation-specific API of its own. So the shared API here
+ * requires almost nothing: the only thing we need is a free function,
+ * so that if the owner of a Socket of this kind needs to close it
+ * before the deferred connection process is finished, the Socket can
+ * also clean up the DeferredSocketOpener dangling off it.
+ */
+
+struct DeferredSocketOpener {
+ const DeferredSocketOpenerVtable *vt;
+};
+struct DeferredSocketOpenerVtable {
+ void (*free)(DeferredSocketOpener *);
+};
+static inline void deferred_socket_opener_free(DeferredSocketOpener *dso)
+{ dso->vt->free(dso); }
+
+DeferredSocketOpener *null_deferred_socket_opener(void);
+
#endif
diff --git a/NOCPROXY.C b/NOCPROXY.C
deleted file mode 100644
index f93214fa..00000000
--- a/NOCPROXY.C
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Routines to refuse to do cryptographic interaction with proxies
- * in PuTTY. This is a stub implementation of the same interfaces
- * provided by cproxy.c, for use in PuTTYtel.
- */
-
-#include <assert.h>
-#include <ctype.h>
-#include <string.h>
-
-#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/NOTIMING.C b/NOTIMING.C
deleted file mode 100644
index 3feb5cdf..00000000
--- a/NOTIMING.C
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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/PGSSAPI.C b/PGSSAPI.C
deleted file mode 100644
index 9d33220f..00000000
--- a/PGSSAPI.C
+++ /dev/null
@@ -1,105 +0,0 @@
-/* This file actually defines the GSSAPI function pointers for
- * functions we plan to import from a GSSAPI library.
- */
-#include "putty.h"
-
-#ifndef NO_GSSAPI
-
-#include "pgssapi.h"
-
-#ifndef NO_LIBDL
-
-/* Reserved static storage for GSS_oids. 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/PGSSAPI.H b/PGSSAPI.H
deleted file mode 100644
index 53d8cb61..00000000
--- a/PGSSAPI.H
+++ /dev/null
@@ -1,333 +0,0 @@
-#ifndef PUTTY_PGSSAPI_H
-#define PUTTY_PGSSAPI_H
-
-#include "putty.h"
-
-#ifndef NO_GSSAPI
-
-/*
- * On Unix, if we're statically linking against GSSAPI, we leave the
- * declaration of all this lot to the official header. If we're
- * dynamically linking, we declare it ourselves, because that avoids
- * us needing the official header at compile time.
- *
- * However, we still need the function pointer types, because even
- * with statically linked GSSAPI we use the ssh_gss_library wrapper.
- */
-#ifdef STATIC_GSSAPI
-#include <gssapi/gssapi.h>
-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/PORTFWD.C b/PORTFWD.C
deleted file mode 100644
index f3349b80..00000000
--- a/PORTFWD.C
+++ /dev/null
@@ -1,1174 +0,0 @@
-/*
- * SSH port forwarding.
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#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);
-}
-
-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/PPROXY.C b/PPROXY.C
deleted file mode 100644
index 4b08606e..00000000
--- a/PPROXY.C
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * pproxy.c: dummy implementation of platform_new_connection(), to
- * be supplanted on any platform which has its own local proxy
- * method.
- */
-
-#include "putty.h"
-#include "network.h"
-#include "proxy.h"
-
-Socket *platform_new_connection(SockAddr *addr, const char *hostname,
- int port, int privport,
- int oobinline, int nodelay, int keepalive,
- Plug *plug, Conf *conf)
-{
- return NULL;
-}
diff --git a/PROXY.C b/PROXY.C
deleted file mode 100644
index d7069cb0..00000000
--- a/PROXY.C
+++ /dev/null
@@ -1,1513 +0,0 @@
-/*
- * Network proxy abstraction in PuTTY
- *
- * A proxy layer, if necessary, wedges itself between the network
- * code and the higher level backend.
- */
-
-#include <assert.h>
-#include <ctype.h>
-#include <string.h>
-
-#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/PROXY.H b/PROXY.H
deleted file mode 100644
index f11e1e3d..00000000
--- a/PROXY.H
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Network proxy abstraction in PuTTY
- *
- * A proxy layer, if necessary, wedges itself between the
- * network code and the higher level backend.
- *
- * Supported proxies: HTTP CONNECT, generic telnet, SOCKS 4 & 5
- */
-
-#ifndef PUTTY_PROXY_H
-#define PUTTY_PROXY_H
-
-#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/PSCP.C b/PSCP.C
index a11d1e18..77de1cd9 100644
--- a/PSCP.C
+++ b/PSCP.C
@@ -1,5 +1,5 @@
/*
- * scp.c - Scp (Secure Copy) client for PuTTY.
+ * pscp.c - Scp (Secure Copy) client for PuTTY.
* Joris van Rantwijk, Simon Tatham
*
* This is mainly based on ssh-1.2.26/scp.c by Timo Rinne & Tatu Ylonen.
@@ -22,7 +22,7 @@
#include "putty.h"
#include "psftp.h"
#include "ssh.h"
-#include "sftp.h"
+#include "ssh/sftp.h"
#include "storage.h"
static bool list = false;
@@ -49,8 +49,6 @@ static void source(const char *src);
static void rsource(const char *src);
static void sink(const char *targ, const char *src);
-const char *const appname = "PSCP";
-
/*
* The maximum amount of queued data we accept before we stop and
* wait for the server to process some.
@@ -58,29 +56,37 @@ const char *const appname = "PSCP";
#define MAX_SCP_BUFSIZE 16384
void ldisc_echoedit_update(Ldisc *ldisc) { }
+void ldisc_check_sendok(Ldisc *ldisc) { }
-static size_t pscp_output(Seat *, bool is_stderr, const void *, size_t);
+static size_t pscp_output(Seat *, SeatOutputType type, const void *, size_t);
static bool pscp_eof(Seat *);
static const SeatVtable pscp_seat_vt = {
.output = pscp_output,
.eof = pscp_eof,
+ .sent = nullseat_sent,
+ .banner = nullseat_banner_to_stderr,
.get_userpass_input = filexfer_get_userpass_input,
+ .notify_session_started = nullseat_notify_session_started,
.notify_remote_exit = nullseat_notify_remote_exit,
+ .notify_remote_disconnect = nullseat_notify_remote_disconnect,
.connection_fatal = console_connection_fatal,
.update_specials_menu = nullseat_update_specials_menu,
.get_ttymode = nullseat_get_ttymode,
.set_busy_status = nullseat_set_busy_status,
- .verify_ssh_host_key = console_verify_ssh_host_key,
+ .confirm_ssh_host_key = console_confirm_ssh_host_key,
.confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive,
.confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey,
+ .prompt_descriptions = console_prompt_descriptions,
.is_utf8 = nullseat_is_never_utf8,
.echoedit_update = nullseat_echoedit_update,
.get_x_display = nullseat_get_x_display,
.get_windowid = nullseat_get_windowid,
.get_window_pixel_size = nullseat_get_window_pixel_size,
.stripctrl_new = console_stripctrl_new,
- .set_trust_status = nullseat_set_trust_status_vacuously,
+ .set_trust_status = nullseat_set_trust_status,
+ .can_set_trust_status = nullseat_can_set_trust_status_yes,
+ .has_mixed_input_stream = nullseat_has_mixed_input_stream_no,
.verbose = cmdline_seat_verbose,
.interactive = nullseat_interactive_no,
.get_cursor_position = nullseat_get_cursor_position,
@@ -141,13 +147,14 @@ static PRINTF_LIKE(2, 3) void tell_user(FILE *stream, const char *fmt, ...)
static bufchain received_data;
static BinarySink *stderr_bs;
static size_t pscp_output(
- Seat *seat, bool is_stderr, const void *data, size_t len)
+ Seat *seat, SeatOutputType type, const void *data, size_t len)
{
/*
- * stderr data is just spouted to local stderr (optionally via a
- * sanitiser) and otherwise ignored.
+ * Non-stdout data (both stderr and SSH auth banners) is just
+ * spouted to local stderr (optionally via a sanitiser) and
+ * otherwise ignored.
*/
- if (is_stderr) {
+ if (type != SEAT_OUTPUT_STDOUT) {
put_data(stderr_bs, data, len);
return 0;
}
@@ -638,8 +645,8 @@ void scp_sftp_listdir(const char *dirname)
dirh = fxp_opendir_recv(pktin, req);
if (dirh == NULL) {
- tell_user(stderr, "Unable to open %s: %s\n", dirname, fxp_error());
- errs++;
+ 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();
@@ -848,7 +855,8 @@ int scp_send_filedata(char *data, int len)
scp_sftp_fileoffset += len;
return 0;
} else {
- int bufsize = backend_send(backend, data, len);
+ backend_send(backend, data, len);
+ int bufsize = backend_sendbuffer(backend);
/*
* If the network transfer is backing up - that is, the
@@ -1806,7 +1814,7 @@ static void sink(const char *targ, const char *src)
striptarget = stripslashes(act.name, true);
if (striptarget != act.name) {
with_stripctrl(sanname, act.name) {
- with_stripctrl(santarg, act.name) {
+ with_stripctrl(santarg, striptarget) {
tell_user(stderr, "warning: remote host sent a"
" compound pathname '%s'", sanname);
tell_user(stderr, " renaming local"
@@ -2181,8 +2189,7 @@ 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] 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");
@@ -2194,7 +2201,7 @@ static void usage(void)
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(" -pwfile file login with password read from specified file\n");
printf(" -1 -2 force use of particular SSH protocol version\n");
printf(" -ssh -ssh-connection\n");
printf(" force use of particular SSH protocol variant\n");
diff --git a/PSFTP.C b/PSFTP.C
index c4b5374b..d8b5c400 100644
--- a/PSFTP.C
+++ b/PSFTP.C
@@ -12,9 +12,7 @@
#include "psftp.h"
#include "storage.h"
#include "ssh.h"
-#include "sftp.h"
-
-const char *const appname = "PSFTP";
+#include "ssh/sftp.h"
/*
* Since SFTP is a request-response oriented protocol, it requires
@@ -41,28 +39,35 @@ static bool sent_eof = false;
* Seat vtable.
*/
-static size_t psftp_output(Seat *, bool is_stderr, const void *, size_t);
+static size_t psftp_output(Seat *, SeatOutputType type, const void *, size_t);
static bool psftp_eof(Seat *);
static const SeatVtable psftp_seat_vt = {
.output = psftp_output,
.eof = psftp_eof,
+ .sent = nullseat_sent,
+ .banner = nullseat_banner_to_stderr,
.get_userpass_input = filexfer_get_userpass_input,
+ .notify_session_started = nullseat_notify_session_started,
.notify_remote_exit = nullseat_notify_remote_exit,
+ .notify_remote_disconnect = nullseat_notify_remote_disconnect,
.connection_fatal = console_connection_fatal,
.update_specials_menu = nullseat_update_specials_menu,
.get_ttymode = nullseat_get_ttymode,
.set_busy_status = nullseat_set_busy_status,
- .verify_ssh_host_key = console_verify_ssh_host_key,
+ .confirm_ssh_host_key = console_confirm_ssh_host_key,
.confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive,
.confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey,
+ .prompt_descriptions = console_prompt_descriptions,
.is_utf8 = nullseat_is_never_utf8,
.echoedit_update = nullseat_echoedit_update,
.get_x_display = nullseat_get_x_display,
.get_windowid = nullseat_get_windowid,
.get_window_pixel_size = nullseat_get_window_pixel_size,
.stripctrl_new = console_stripctrl_new,
- .set_trust_status = nullseat_set_trust_status_vacuously,
+ .set_trust_status = nullseat_set_trust_status,
+ .can_set_trust_status = nullseat_can_set_trust_status_yes,
+ .has_mixed_input_stream = nullseat_has_mixed_input_stream_no,
.verbose = cmdline_seat_verbose,
.interactive = nullseat_interactive_yes,
.get_cursor_position = nullseat_get_cursor_position,
@@ -2444,6 +2449,7 @@ int do_sftp(int mode, int modeflags, char *batchfile)
static bool verbose = false;
void ldisc_echoedit_update(Ldisc *ldisc) { }
+void ldisc_check_sendok(Ldisc *ldisc) { }
/*
* Receive a block of data from the SSH link. Block until all data
@@ -2456,13 +2462,14 @@ void ldisc_echoedit_update(Ldisc *ldisc) { }
static bufchain received_data;
static BinarySink *stderr_bs;
static size_t psftp_output(
- Seat *seat, bool is_stderr, const void *data, size_t len)
+ Seat *seat, SeatOutputType type, const void *data, size_t len)
{
/*
- * stderr data is just spouted to local stderr (optionally via a
- * sanitiser) and otherwise ignored.
+ * Non-stdout data (both stderr and SSH auth banners) is just
+ * spouted to local stderr (optionally via a sanitiser) and
+ * otherwise ignored.
*/
- if (is_stderr) {
+ if (type != SEAT_OUTPUT_STDOUT) {
put_data(stderr_bs, data, len);
return 0;
}
@@ -2529,7 +2536,7 @@ static void usage(void)
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(" -pwfile file login with password read from specified file\n");
printf(" -1 -2 force use of particular SSH protocol version\n");
printf(" -ssh -ssh-connection\n");
printf(" force use of particular SSH protocol variant\n");
@@ -2558,10 +2565,10 @@ static void usage(void)
static void version(void)
{
- char *buildinfo_text = buildinfo("\n");
- printf("psftp: %s\n%s\n", ver, buildinfo_text);
- sfree(buildinfo_text);
- exit(0);
+ char *buildinfo_text = buildinfo("\n");
+ printf("psftp: %s\n%s\n", ver, buildinfo_text);
+ sfree(buildinfo_text);
+ exit(0);
}
/*
@@ -2783,7 +2790,7 @@ const unsigned cmdline_tooltype = TOOLTYPE_FILETRANSFER;
*/
int psftp_main(int argc, char *argv[])
{
- int i, ret;
+ int i, toret;
int portnumber = 0;
char *userhost, *user;
int mode = 0;
@@ -2800,7 +2807,7 @@ int psftp_main(int argc, char *argv[])
do_defaults(NULL, conf);
for (i = 1; i < argc; i++) {
- int ret;
+ int retd;
if (argv[i][0] != '-') {
if (userhost)
usage();
@@ -2808,12 +2815,13 @@ int psftp_main(int argc, char *argv[])
userhost = dupstr(argv[i]);
continue;
}
- ret = cmdline_process_param(argv[i], i+1<argc?argv[i+1]:NULL, 1, conf);
- if (ret == -2) {
+ retd = cmdline_process_param(
+ argv[i], i+1 < argc ? argv[i+1] : NULL, 1, conf);
+ if (retd == -2) {
cmdline_error("option \"%s\" requires an argument", argv[i]);
- } else if (ret == 2) {
+ } else if (retd == 2) {
i++; /* skip next argument */
- } else if (ret == 1) {
+ } else if (retd == 1) {
/* We have our own verbosity in addition to `flags'. */
if (cmdline_verbose())
verbose = true;
@@ -2874,10 +2882,10 @@ int psftp_main(int argc, char *argv[])
* it now.
*/
if (userhost) {
- int ret;
- ret = psftp_connect(userhost, user, portnumber);
+ int retd;
+ retd = psftp_connect(userhost, user, portnumber);
sfree(userhost);
- if (ret)
+ if (retd)
return 1;
if (do_sftp_init())
return 1;
@@ -2886,7 +2894,7 @@ int psftp_main(int argc, char *argv[])
" to connect\n");
}
- ret = do_sftp(mode, modeflags, batchfile);
+ toret = do_sftp(mode, modeflags, batchfile);
if (backend && backend_connected(backend)) {
char ch;
@@ -2905,5 +2913,5 @@ int psftp_main(int argc, char *argv[])
if (psftp_logctx)
log_free(psftp_logctx);
- return ret;
+ return toret;
}
diff --git a/PUTTY.H b/PUTTY.H
index 693a6a0e..bfb705c2 100644
--- a/PUTTY.H
+++ b/PUTTY.H
@@ -5,7 +5,7 @@
#include <limits.h> /* for INT_MAX */
#include "defs.h"
-#include "puttyps.h"
+#include "platform.h"
#include "network.h"
#include "misc.h"
#include "marshal.h"
@@ -21,14 +21,14 @@
* 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_MASTER_KEY_YEAR "2021"
+#define PGP_MASTER_KEY_DETAILS "RSA, 3072-bit"
+#define PGP_MASTER_KEY_FP \
+ "A872 D42F 1660 890F 0E05 223E DD43 55EA AC11 19DE"
+#define PGP_PREV_MASTER_KEY_YEAR "2018"
#define PGP_PREV_MASTER_KEY_DETAILS "RSA, 4096-bit"
#define PGP_PREV_MASTER_KEY_FP \
- "440D E3B5 B7A1 CA85 B3CC 1718 AB58 5DC6 0467 6F7C"
+ "24E1 B1C5 75EA 3C9F F752 A922 76BC 7FE4 EBFD 2D9E"
/*
* Definitions of three separate indexing schemes for colour palette
@@ -266,7 +266,6 @@ struct sesslist {
};
struct unicode_data {
- char **uni_tbl;
bool dbcs_screenfont;
int font_codepage;
int line_codepage;
@@ -326,13 +325,13 @@ typedef enum {
/*
* Send a POSIX-style signal. (Useful in SSH and also pterm.)
*
- * We use the master list in sshsignals.h to define these enum
+ * We use the master list in ssh/signal-list.h to define these enum
* values, which will come out looking like names of the form
* SS_SIGABRT, SS_SIGINT etc.
*/
#define SIGNAL_MAIN(name, text) SS_SIG ## name,
#define SIGNAL_SUB(name) SS_SIG ## name,
- #include "sshsignals.h"
+ #include "ssh/signal-list.h"
#undef SIGNAL_MAIN
#undef SIGNAL_SUB
@@ -356,7 +355,7 @@ struct SessionSpecial {
int arg;
};
-/* Needed by both sshchan.h and sshppl.h */
+/* Needed by both ssh/channel.h and ssh/ppl.h */
typedef void (*add_special_fn_t)(
void *ctx, const char *text, SessionSpecialCode code, int arg);
@@ -423,9 +422,14 @@ enum {
KEX_WARN,
KEX_DHGROUP1,
KEX_DHGROUP14,
+ KEX_DHGROUP15,
+ KEX_DHGROUP16,
+ KEX_DHGROUP17,
+ KEX_DHGROUP18,
KEX_DHGEX,
KEX_RSA,
KEX_ECDH,
+ KEX_NTRU_HYBRID,
KEX_MAX
};
@@ -453,6 +457,7 @@ enum {
CIPHER_DES,
CIPHER_ARCFOUR,
CIPHER_CHACHA20,
+ CIPHER_AESGCM,
CIPHER_MAX /* no. ciphers (inc warn) */
};
@@ -474,7 +479,9 @@ enum {
* Proxy types.
*/
PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5,
- PROXY_HTTP, PROXY_TELNET, PROXY_CMD, PROXY_FUZZ
+ PROXY_HTTP, PROXY_TELNET, PROXY_CMD, PROXY_SSH_TCPIP,
+ PROXY_SSH_EXEC, PROXY_SSH_SUBSYSTEM,
+ PROXY_FUZZ
};
enum {
@@ -529,7 +536,14 @@ enum {
FUNKY_XTERM,
FUNKY_VT400,
FUNKY_VT100P,
- FUNKY_SCO
+ FUNKY_SCO,
+ FUNKY_XTERM_216
+};
+
+enum {
+ /* Shifted arrow key types (CONF_sharrow_type) */
+ SHARROW_APPLICATION, /* Ctrl flips between ESC O A and ESC [ A */
+ SHARROW_BITMAP /* ESC [ 1 ; n A, where n = 1 + bitmap of CAS */
};
enum {
@@ -617,9 +631,123 @@ enum {
#define BACKEND_RESIZE_FORBIDDEN 0x01 /* Backend does not allow
resizing terminal */
#define BACKEND_NEEDS_TERMINAL 0x02 /* Backend must have terminal */
+#define BACKEND_SUPPORTS_NC_HOST 0x04 /* Backend can honour
+ CONF_ssh_nc_host */
+#define BACKEND_NOTIFIES_SESSION_START 0x08 /* Backend will call
+ seat_notify_session_started */
+
+/* In (no)sshproxy.c */
+extern const bool ssh_proxy_supported;
+
+/*
+ * This structure type wraps a Seat pointer, in a way that has no
+ * purpose except to be a different type.
+ *
+ * The Seat wrapper functions that present interactive prompts all
+ * expect one of these in place of their ordinary Seat pointer. You
+ * get one by calling interactor_announce (defined below), which will
+ * print a message (if not already done) identifying the Interactor
+ * that originated the prompt.
+ *
+ * This arranges that the C type system itself will check that no call
+ * to any of those Seat methods has omitted the mandatory call to
+ * interactor_announce beforehand.
+ */
+struct InteractionReadySeat {
+ Seat *seat;
+};
+
+/*
+ * The Interactor trait is implemented by anything that is capable of
+ * presenting interactive prompts or questions to the user during
+ * network connection setup. Every Backend that ever needs to do this
+ * is an Interactor, but also, while a Backend is making its initial
+ * network connection, it may go via network proxy code which is also
+ * an Interactor and can ask questions of its own.
+ */
+struct Interactor {
+ const InteractorVtable *vt;
+
+ /* The parent Interactor that we are a proxy for, if any. */
+ Interactor *parent;
+
+ /*
+ * If we're the top-level Interactor (parent==NULL), then this
+ * field records the last Interactor that actually did anything
+ * interactive, so that we know when to announce a changeover
+ * between levels of proxying.
+ *
+ * If parent != NULL, this field is not used.
+ */
+ Interactor *last_to_talk;
+};
+
+struct InteractorVtable {
+ /*
+ * Returns a user-facing description of the nature of the network
+ * connection being made. Used in interactive proxy authentication
+ * to announce which connection attempt is now in control of the
+ * Seat.
+ *
+ * The idea is not just to be written in natural language, but to
+ * connect with the user's idea of _why_ they think some
+ * connection is being made. For example, instead of saying 'TCP
+ * connection to 123.45.67.89 port 22', you might say 'SSH
+ * connection to [logical host name for SSH host key purposes]'.
+ *
+ * The returned string must be freed by the caller.
+ */
+ char *(*description)(Interactor *itr);
+
+ /*
+ * Returns the LogPolicy associated with this Interactor. (A
+ * Backend can derive this from its logging context; a proxy
+ * Interactor inherits it from the Interactor for the parent
+ * network connection.)
+ */
+ LogPolicy *(*logpolicy)(Interactor *itr);
+
+ /*
+ * Gets and sets the Seat that this Interactor talks to. When a
+ * Seat is borrowed and replaced with a TempSeat, this will be the
+ * mechanism by which that replacement happens.
+ */
+ Seat *(*get_seat)(Interactor *itr);
+ void (*set_seat)(Interactor *itr, Seat *seat);
+};
+
+static inline char *interactor_description(Interactor *itr)
+{ return itr->vt->description(itr); }
+static inline LogPolicy *interactor_logpolicy(Interactor *itr)
+{ return itr->vt->logpolicy(itr); }
+static inline Seat *interactor_get_seat(Interactor *itr)
+{ return itr->vt->get_seat(itr); }
+static inline void interactor_set_seat(Interactor *itr, Seat *seat)
+{ itr->vt->set_seat(itr, seat); }
+
+static inline void interactor_set_child(Interactor *parent, Interactor *child)
+{ child->parent = parent; }
+Seat *interactor_borrow_seat(Interactor *itr);
+void interactor_return_seat(Interactor *itr);
+InteractionReadySeat interactor_announce(Interactor *itr);
+
+/* Interactors that are Backends will find this helper function useful
+ * in constructing their description strings */
+char *default_description(const BackendVtable *backvt,
+ const char *host, int port);
+
+/*
+ * The Backend trait is the top-level one that governs each of the
+ * user-facing main modes that PuTTY can use to talk to some
+ * destination: SSH, Telnet, serial port, pty, etc.
+ */
struct Backend {
const BackendVtable *vt;
+
+ /* Many Backends are also Interactors. If this one is, a pointer
+ * to its Interactor trait lives here. */
+ Interactor *interactor;
};
struct BackendVtable {
char *(*init) (const BackendVtable *vt, Seat *seat,
@@ -630,9 +758,8 @@ struct BackendVtable {
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 */
+ void (*send) (Backend *be, const char *buf, size_t len);
+ /* sendbuffer() returns the current amount of buffered data */
size_t (*sendbuffer) (Backend *be);
void (*size) (Backend *be, int width, int height);
void (*special) (Backend *be, SessionSpecialCode code, int arg);
@@ -641,7 +768,14 @@ struct BackendVtable {
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). */
+ * possible (passing back-pressure on to its sender).
+ *
+ * Policy rule: no backend shall return true from sendok() while
+ * its network connection attempt is still ongoing. This ensures
+ * that if making the network connection involves a proxy type
+ * which wants to interact with the user via the terminal, the
+ * proxy implementation and the backend itself won't fight over
+ * who gets the terminal input. */
bool (*sendok) (Backend *be);
bool (*ldisc_option_state) (Backend *be, int);
void (*provide_ldisc) (Backend *be, Ldisc *ldisc);
@@ -660,9 +794,10 @@ struct BackendVtable {
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;
+ * saved-session storage. 'displayname_tc' and 'displayname_lc'
+ * are human-readable names, one in title-case for config boxes,
+ * and one in lower-case for use in mid-sentence. */
+ const char *id, *displayname_tc, *displayname_lc;
int protocol;
int default_port;
@@ -681,8 +816,8 @@ 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 void backend_send(Backend *be, const char *buf, size_t len)
+{ be->vt->send(be, buf, len); }
static inline size_t backend_sendbuffer(Backend *be)
{ return be->vt->sendbuffer(be); }
static inline void backend_size(Backend *be, int width, int height)
@@ -733,6 +868,87 @@ int hwnd_parent;
#endif // PUTTYNG
/*
+ * Used by callback.c; declared up here so that prompts_t can use it
+ */
+typedef void (*toplevel_callback_fn_t)(void *ctx);
+
+/* Enum of result types in SeatPromptResult below */
+typedef enum SeatPromptResultKind {
+ /* Answer not yet available at all; either try again later or wait
+ * for a callback (depending on the request's API) */
+ SPRK_INCOMPLETE,
+
+ /* We're abandoning the connection because the user interactively
+ * told us to. (Hence, no need to present an error message
+ * telling the user we're doing that: they already know.) */
+ SPRK_USER_ABORT,
+
+ /* We're abandoning the connection for some other reason (e.g. we
+ * were unable to present the prompt at all, or a batch-mode
+ * configuration told us to give the answer no). This may
+ * ultimately have stemmed from some user configuration, but they
+ * didn't _tell us right now_ to abandon this connection, so we
+ * still need to inform them that we've done so. */
+ SPRK_SW_ABORT,
+
+ /* We're proceeding with the connection and have all requested
+ * information (if any) */
+ SPRK_OK
+} SeatPromptResultKind;
+
+/* Small struct to present the results of interactive requests from
+ * backend to Seat (see below) */
+struct SeatPromptResult {
+ SeatPromptResultKind kind;
+
+ /*
+ * In the case of SPRK_SW_ABORT, the frontend provides an error
+ * message to present to the user. But dynamically allocating it
+ * up front would mean having to make sure it got freed at any
+ * call site where one of these structs is received (and freed
+ * _once_ no matter how many times the struct is copied). So
+ * instead we provide a function that will generate the error
+ * message into a BinarySink.
+ */
+ void (*errfn)(SeatPromptResult, BinarySink *);
+
+ /*
+ * And some fields the error function can use to construct the
+ * message (holding, e.g. an OS error code).
+ */
+ const char *errdata_lit; /* statically allocated, e.g. a string literal */
+ unsigned errdata_u;
+};
+
+/* Helper function to construct the simple versions of these
+ * structures inline */
+static inline SeatPromptResult make_spr_simple(SeatPromptResultKind kind)
+{
+ SeatPromptResult spr;
+ spr.kind = kind;
+ spr.errdata_lit = NULL;
+ return spr;
+}
+
+/* Most common constructor function for SPRK_SW_ABORT errors */
+SeatPromptResult make_spr_sw_abort_static(const char *);
+
+/* Convenience macros wrapping those constructors in turn */
+#define SPR_INCOMPLETE make_spr_simple(SPRK_INCOMPLETE)
+#define SPR_USER_ABORT make_spr_simple(SPRK_USER_ABORT)
+#define SPR_SW_ABORT(lit) make_spr_sw_abort_static(lit)
+#define SPR_OK make_spr_simple(SPRK_OK)
+
+/* Query function that folds both kinds of abort together */
+static inline bool spr_is_abort(SeatPromptResult spr)
+{
+ return spr.kind == SPRK_USER_ABORT || spr.kind == SPRK_SW_ABORT;
+}
+
+/* Function to return a dynamically allocated copy of the error message */
+char *spr_get_error_message(SeatPromptResult spr);
+
+/*
* Mechanism for getting text strings such as usernames and passwords
* from the front-end.
* The fields are mostly modelled after SSH's keyboard-interactive auth.
@@ -753,7 +969,8 @@ typedef struct {
bool echo;
strbuf *result;
} prompt_t;
-typedef struct {
+typedef struct prompts_t prompts_t;
+struct prompts_t {
/*
* Indicates whether the information entered is to be used locally
* (for instance a key passphrase prompt), or is destined for the wire.
@@ -781,7 +998,25 @@ typedef struct {
prompt_t **prompts;
void *data; /* slot for housekeeping data, managed by
* seat_get_userpass_input(); initially NULL */
-} prompts_t;
+ SeatPromptResult spr; /* some implementations need to cache one of these */
+
+ /*
+ * Callback you can fill in to be notified when all the prompts'
+ * responses are available. After you receive this notification, a
+ * further call to the get_userpass_input function will return the
+ * final state of the prompts system, which is guaranteed not to
+ * be negative for 'still ongoing'.
+ */
+ toplevel_callback_fn_t callback;
+ void *callback_ctx;
+
+ /*
+ * When this prompts_t is known to an Ldisc, we might need to
+ * break the connection if things get freed in an emergency. So
+ * this is a pointer to the Ldisc's pointer to us.
+ */
+ prompts_t **ldisc_ptr_to_us;
+};
prompts_t *new_prompts(void);
void add_prompt(prompts_t *p, char *promptstr, bool echo);
void prompt_set_result(prompt_t *pr, const char *newstr);
@@ -856,6 +1091,28 @@ typedef enum SeatInteractionContext {
SIC_BANNER, SIC_KI_PROMPTS
} SeatInteractionContext;
+typedef enum SeatOutputType {
+ SEAT_OUTPUT_STDOUT, SEAT_OUTPUT_STDERR
+} SeatOutputType;
+
+typedef enum SeatDialogTextType {
+ SDT_PARA, SDT_DISPLAY, SDT_SCARY_HEADING,
+ SDT_TITLE, SDT_PROMPT, SDT_BATCH_ABORT,
+ SDT_MORE_INFO_KEY, SDT_MORE_INFO_VALUE_SHORT, SDT_MORE_INFO_VALUE_BLOB
+} SeatDialogTextType;
+struct SeatDialogTextItem {
+ SeatDialogTextType type;
+ char *text;
+};
+struct SeatDialogText {
+ size_t nitems, itemsize;
+ SeatDialogTextItem *items;
+};
+SeatDialogText *seat_dialog_text_new(void);
+void seat_dialog_text_free(SeatDialogText *sdt);
+PRINTF_LIKE(3, 4) void seat_dialog_text_append(
+ SeatDialogText *sdt, SeatDialogTextType type, const char *fmt, ...);
+
/*
* Data type 'Seat', which is an API intended to contain essentially
* everything that a back end might need to talk to its client for:
@@ -868,14 +1125,16 @@ struct Seat {
};
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.
+ * Provide output from the remote session. 'type' indicates the
+ * type of the output (stdout or stderr), which can be used to
+ * split the output into separate message channels, if the seat
+ * wants to handle them differently. But combining the channels
+ * into one is OK too; that's what terminal-window based seats do.
*
* The return value is the current size of the output backlog.
*/
- size_t (*output)(Seat *seat, bool is_stderr, const void *data, size_t len);
+ size_t (*output)(Seat *seat, SeatOutputType type,
+ const void *data, size_t len);
/*
* Called when the back end wants to indicate that EOF has arrived
@@ -886,40 +1145,49 @@ struct SeatVtable {
bool (*eof)(Seat *seat);
/*
+ * Called by the back end to notify that the output backlog has
+ * changed size. A front end in control of the event loop won't
+ * necessarily need this (they can just keep checking it via
+ * backend_sendbuffer at every opportunity), but one buried in the
+ * depths of something else (like an SSH proxy) will need to be
+ * proactively notified that the amount of buffered data has
+ * become smaller.
+ */
+ void (*sent)(Seat *seat, size_t new_sendbuffer);
+
+ /*
+ * Provide authentication-banner output from the session setup.
+ * End-user Seats can treat this as very similar to 'output', but
+ * intermediate Seats in complex proxying situations will want to
+ * implement this and 'output' differently.
+ */
+ size_t (*banner)(Seat *seat, const void *data, size_t len);
+
+ /*
* Try to get answers from a set of interactive login prompts. The
- * prompts are provided in 'p'; 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.
+ * prompts are provided in 'p'.
*
- * (FIXME: it would be nice to distinguish two classes of cancel
- * action, so the user could specify 'I want to abandon this
+ * (FIXME: it would be nice to distinguish two classes of user-
+ * abort action, so the user could specify 'I want to abandon this
* entire attempt to start a session' or the milder 'I want to
* abandon this particular form of authentication and fall back to
* a different one' - e.g. if you turn out not to be able to
* remember your private key passphrase then perhaps you'd rather
* fall back to password auth rather than aborting the whole
* session.)
+ */
+ SeatPromptResult (*get_userpass_input)(Seat *seat, prompts_t *p);
+
+ /*
+ * Notify the seat that the main session channel has been
+ * successfully set up.
*
- * (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.)
+ * This is only used as part of the SSH proxying system, so it's
+ * not necessary to implement it in all backends. A backend must
+ * call this if it advertises the BACKEND_NOTIFIES_SESSION_START
+ * flag, and otherwise, doesn't have to.
*/
- int (*get_userpass_input)(Seat *seat, prompts_t *p, bufchain *input);
+ void (*notify_session_started)(Seat *seat);
/*
* Notify the seat that the process running at the other end of
@@ -928,6 +1196,29 @@ struct SeatVtable {
void (*notify_remote_exit)(Seat *seat);
/*
+ * Notify the seat that the whole connection has finished.
+ * (Distinct from notify_remote_exit, e.g. in the case where you
+ * have port forwardings still active when the main foreground
+ * session goes away: then you'd get notify_remote_exit when the
+ * foreground session dies, but notify_remote_disconnect when the
+ * last forwarding vanishes and the network connection actually
+ * closes.)
+ *
+ * This function might be called multiple times by accident; seats
+ * should be prepared to cope.
+ *
+ * More precisely: this function notifies the seat that
+ * backend_connected() might now return false where previously it
+ * returned true. (Note the 'might': an accidental duplicate call
+ * might happen when backend_connected() was already returning
+ * false. Or even, in weird situations, when it hadn't stopped
+ * returning true yet. The point is, when you get this
+ * notification, all it's really telling you is that it's worth
+ * _checking_ backend_connected, if you weren't already.)
+ */
+ void (*notify_remote_disconnect)(Seat *seat);
+
+ /*
* Notify the seat that the connection has suffered a fatal error.
*/
void (*connection_fatal)(Seat *seat, const char *message);
@@ -967,35 +1258,47 @@ struct SeatVtable {
/*
* 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.
+ * This is called after we've already checked it by any means we
+ * can do ourselves, such as checking against host key
+ * fingerprints in the Conf or the host key cache on disk: once we
+ * call this function, we've already decided there's nothing for
+ * it but to prompt the user.
+ *
+ * 'mismatch' reports the result of checking the host key cache:
+ * it is true if the server has presented a host key different
+ * from the one we expected, and false if we had no expectation in
+ * the first place.
+ *
+ * This call may prompt the user synchronously and not return
+ * until the answer is available, or it may present the prompt and
+ * return immediately, giving the answer later via the provided
+ * callback.
*
* Return values:
*
- * - +1 means `key was OK' (either already known or the user just
- * approved it) `so continue with the connection'
+ * - +1 means `user approved the key, so continue with the
+ * connection'
*
- * - 0 means `key was not OK, abandon the connection'
+ * - 0 means `user rejected the key, abandon the connection'
*
* - -1 means `I've initiated enquiries, please wait to be called
* back via the provided function with a result that's either 0
* or +1'.
*/
- int (*verify_ssh_host_key)(
+ SeatPromptResult (*confirm_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);
+ char *keystr, SeatDialogText *text, HelpCtx helpctx,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
/*
* Check with the seat whether it's OK to use a cryptographic
* primitive from below the 'warn below this line' threshold in
* the input Conf. Return values are the same as
- * verify_ssh_host_key above.
+ * confirm_ssh_host_key above.
*/
- int (*confirm_weak_crypto_primitive)(
+ SeatPromptResult (*confirm_weak_crypto_primitive)(
Seat *seat, const char *algtype, const char *algname,
- void (*callback)(void *ctx, int result), void *ctx);
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
/*
* Variant form of confirm_weak_crypto_primitive, which prints a
@@ -1008,9 +1311,16 @@ struct SeatVtable {
* threshold is available that we don't have cached. 'betteralgs'
* lists the better algorithm(s).
*/
- int (*confirm_weak_cached_hostkey)(
+ SeatPromptResult (*confirm_weak_cached_hostkey)(
Seat *seat, const char *algname, const char *betteralgs,
- void (*callback)(void *ctx, int result), void *ctx);
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+
+ /*
+ * Some snippets of text describing the UI actions in host key
+ * prompts / dialog boxes, to be used in ssh/common.c when it
+ * assembles the full text of those prompts.
+ */
+ const SeatDialogPromptDescriptions *(*prompt_descriptions)(Seat *seat);
/*
* Indicates whether the seat is expecting to interact with the
@@ -1062,13 +1372,31 @@ struct SeatVtable {
* (and hence, can be trusted if it's asking you for secrets such
* as your passphrase); false means output is coming from the
* server.
+ */
+ void (*set_trust_status)(Seat *seat, bool trusted);
+
+ /*
+ * Query whether this Seat can do anything user-visible in
+ * response to set_trust_status.
*
* Returns true if the seat has a way to indicate this
* distinction. Returns false if not, in which case the backend
* should use a fallback defence against spoofing of PuTTY's local
* prompts by malicious servers.
*/
- bool (*set_trust_status)(Seat *seat, bool trusted);
+ bool (*can_set_trust_status)(Seat *seat);
+
+ /*
+ * Query whether this Seat's interactive prompt responses and its
+ * session input come from the same place.
+ *
+ * If false, this is used to suppress the final 'Press Return to
+ * begin session' anti-spoofing prompt in Plink. For example,
+ * Plink itself sets this flag if its standard input is redirected
+ * (and therefore not coming from the same place as the console
+ * it's sending its prompts to).
+ */
+ bool (*has_mixed_input_stream)(Seat *seat);
/*
* Ask the seat whether it would like verbose messages.
@@ -1089,34 +1417,49 @@ struct SeatVtable {
};
static inline size_t seat_output(
- Seat *seat, bool err, const void *data, size_t len)
-{ return seat->vt->output(seat, err, data, len); }
+ Seat *seat, SeatOutputType type, const void *data, size_t len)
+{ return seat->vt->output(seat, type, data, len); }
static inline bool seat_eof(Seat *seat)
{ return seat->vt->eof(seat); }
-static inline 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_sent(Seat *seat, size_t bufsize)
+{ seat->vt->sent(seat, bufsize); }
+static inline size_t seat_banner(
+ InteractionReadySeat iseat, const void *data, size_t len)
+{ return iseat.seat->vt->banner(iseat.seat, data, len); }
+static inline SeatPromptResult seat_get_userpass_input(
+ InteractionReadySeat iseat, prompts_t *p)
+{ return iseat.seat->vt->get_userpass_input(iseat.seat, p); }
+static inline void seat_notify_session_started(Seat *seat)
+{ seat->vt->notify_session_started(seat); }
static inline void seat_notify_remote_exit(Seat *seat)
{ seat->vt->notify_remote_exit(seat); }
+static inline void seat_notify_remote_disconnect(Seat *seat)
+{ seat->vt->notify_remote_disconnect(seat); }
static inline void seat_update_specials_menu(Seat *seat)
{ seat->vt->update_specials_menu(seat); }
static inline char *seat_get_ttymode(Seat *seat, const char *mode)
{ return seat->vt->get_ttymode(seat, mode); }
static inline void seat_set_busy_status(Seat *seat, BusyStatus status)
{ seat->vt->set_busy_status(seat, status); }
-static inline 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 SeatPromptResult seat_confirm_ssh_host_key(
+ InteractionReadySeat iseat, const char *h, int p, const char *ktyp,
+ char *kstr, SeatDialogText *text, HelpCtx helpctx,
+ void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
+{ return iseat.seat->vt->confirm_ssh_host_key(
+ iseat.seat, h, p, ktyp, kstr, text, helpctx, cb, ctx); }
+static inline SeatPromptResult seat_confirm_weak_crypto_primitive(
+ InteractionReadySeat iseat, const char *atyp, const char *aname,
+ void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
+{ return iseat.seat->vt->confirm_weak_crypto_primitive(
+ iseat.seat, atyp, aname, cb, ctx); }
+static inline SeatPromptResult seat_confirm_weak_cached_hostkey(
+ InteractionReadySeat iseat, const char *aname, const char *better,
+ void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
+{ return iseat.seat->vt->confirm_weak_cached_hostkey(
+ iseat.seat, aname, better, cb, ctx); }
+static inline const SeatDialogPromptDescriptions *seat_prompt_descriptions(
+ Seat *seat)
+{ return seat->vt->prompt_descriptions(seat); }
static inline bool seat_is_utf8(Seat *seat)
{ return seat->vt->is_utf8(seat); }
static inline void seat_echoedit_update(Seat *seat, bool ec, bool ed)
@@ -1130,8 +1473,12 @@ static inline bool seat_get_window_pixel_size(Seat *seat, int *w, int *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 void seat_set_trust_status(Seat *seat, bool trusted)
+{ seat->vt->set_trust_status(seat, trusted); }
+static inline bool seat_can_set_trust_status(Seat *seat)
+{ return seat->vt->can_set_trust_status(seat); }
+static inline bool seat_has_mixed_input_stream(Seat *seat)
+{ return seat->vt->has_mixed_input_stream(seat); }
static inline bool seat_verbose(Seat *seat)
{ return seat->vt->verbose(seat); }
static inline bool seat_interactive(Seat *seat)
@@ -1141,18 +1488,32 @@ static inline bool seat_get_cursor_position(Seat *seat, int *x, int *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. */
+ * defined in utils. */
void seat_connection_fatal(Seat *seat, const char *fmt, ...) PRINTF_LIKE(2, 3);
/* Handy aliases for seat_output which set is_stderr to a fixed value. */
static inline size_t seat_stdout(Seat *seat, const void *data, size_t len)
-{ return seat_output(seat, false, data, len); }
+{ return seat_output(seat, SEAT_OUTPUT_STDOUT, data, len); }
static inline size_t seat_stdout_pl(Seat *seat, ptrlen data)
-{ return seat_output(seat, false, data.ptr, data.len); }
+{ return seat_output(seat, SEAT_OUTPUT_STDOUT, data.ptr, data.len); }
static inline size_t seat_stderr(Seat *seat, const void *data, size_t len)
-{ return seat_output(seat, true, data, len); }
+{ return seat_output(seat, SEAT_OUTPUT_STDERR, data, len); }
static inline size_t seat_stderr_pl(Seat *seat, ptrlen data)
-{ return seat_output(seat, true, data.ptr, data.len); }
+{ return seat_output(seat, SEAT_OUTPUT_STDERR, data.ptr, data.len); }
+
+/* Alternative API for seat_banner taking a ptrlen */
+static inline size_t seat_banner_pl(InteractionReadySeat iseat, ptrlen data)
+{ return iseat.seat->vt->banner(iseat.seat, data.ptr, data.len); }
+
+struct SeatDialogPromptDescriptions {
+ const char *hk_accept_action;
+ const char *hk_connect_once_action;
+ const char *hk_cancel_action, *hk_cancel_action_Participle;
+};
+
+/* In the utils subdir: print a message to the Seat which can't be
+ * spoofed by server-supplied auth-time output such as SSH banners */
+void seat_antispoof_msg(InteractionReadySeat iseat, const char *msg);
/*
* Stub methods for seat implementations that want to use the obvious
@@ -1162,24 +1523,30 @@ static inline size_t seat_stderr_pl(Seat *seat, ptrlen data)
* 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);
+ Seat *seat, SeatOutputType type, 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_sent(Seat *seat, size_t bufsize);
+size_t nullseat_banner(Seat *seat, const void *data, size_t len);
+size_t nullseat_banner_to_stderr(Seat *seat, const void *data, size_t len);
+SeatPromptResult nullseat_get_userpass_input(Seat *seat, prompts_t *p);
+void nullseat_notify_session_started(Seat *seat);
void nullseat_notify_remote_exit(Seat *seat);
+void nullseat_notify_remote_disconnect(Seat *seat);
void nullseat_connection_fatal(Seat *seat, const char *message);
void nullseat_update_specials_menu(Seat *seat);
char *nullseat_get_ttymode(Seat *seat, const char *mode);
void nullseat_set_busy_status(Seat *seat, BusyStatus status);
-int nullseat_verify_ssh_host_key(
+SeatPromptResult nullseat_confirm_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(
+ char *keystr, SeatDialogText *text, HelpCtx helpctx,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+SeatPromptResult nullseat_confirm_weak_crypto_primitive(
Seat *seat, const char *algtype, const char *algname,
- void (*callback)(void *ctx, int result), void *ctx);
-int nullseat_confirm_weak_cached_hostkey(
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+SeatPromptResult nullseat_confirm_weak_cached_hostkey(
Seat *seat, const char *algname, const char *betteralgs,
- void (*callback)(void *ctx, int result), void *ctx);
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat);
bool nullseat_is_never_utf8(Seat *seat);
bool nullseat_is_always_utf8(Seat *seat);
void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing);
@@ -1187,9 +1554,12 @@ 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);
+ Seat *seat, BinarySink *bs_out, SeatInteractionContext sic);
+void nullseat_set_trust_status(Seat *seat, bool trusted);
+bool nullseat_can_set_trust_status_yes(Seat *seat);
+bool nullseat_can_set_trust_status_no(Seat *seat);
+bool nullseat_has_mixed_input_stream_yes(Seat *seat);
+bool nullseat_has_mixed_input_stream_no(Seat *seat);
bool nullseat_verbose_no(Seat *seat);
bool nullseat_verbose_yes(Seat *seat);
bool nullseat_interactive_no(Seat *seat);
@@ -1198,30 +1568,58 @@ 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).
+ * support module (console.c in each platform subdirectory).
*/
void console_connection_fatal(Seat *seat, const char *message);
-int console_verify_ssh_host_key(
+SeatPromptResult console_confirm_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(
+ char *keystr, SeatDialogText *text, HelpCtx helpctx,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+SeatPromptResult console_confirm_weak_crypto_primitive(
Seat *seat, const char *algtype, const char *algname,
- void (*callback)(void *ctx, int result), void *ctx);
-int console_confirm_weak_cached_hostkey(
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+SeatPromptResult console_confirm_weak_cached_hostkey(
Seat *seat, const char *algname, const char *betteralgs,
- void (*callback)(void *ctx, int result), void *ctx);
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
StripCtrlChars *console_stripctrl_new(
- Seat *seat, BinarySink *bs_out, SeatInteractionContext sic);
-bool console_set_trust_status(Seat *seat, bool trusted);
+ Seat *seat, BinarySink *bs_out, SeatInteractionContext sic);
+void console_set_trust_status(Seat *seat, bool trusted);
+bool console_can_set_trust_status(Seat *seat);
+bool console_has_mixed_input_stream(Seat *seat);
+const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat);
/*
* Other centralised seat functions.
*/
-int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input);
+SeatPromptResult filexfer_get_userpass_input(Seat *seat, prompts_t *p);
bool cmdline_seat_verbose(Seat *seat);
+/*
+ * TempSeat: a seat implementation that can be given to a backend
+ * temporarily while network proxy setup is using the real seat.
+ * Buffers output and trust-status changes until the real seat is
+ * available again.
+ */
+
+/* Called by the proxy code to make a TempSeat. */
+Seat *tempseat_new(Seat *real);
+
+/* Query functions to tell if a Seat _is_ temporary, and if so, to
+ * return the underlying real Seat. */
+bool is_tempseat(Seat *seat);
+Seat *tempseat_get_real(Seat *seat);
+
+/* Called by interactor_return_seat once the proxy connection has
+ * finished setting up (or failed), to pass on any buffered stuff to
+ * the real seat. */
+void tempseat_flush(Seat *ts);
+
+/* Frees a TempSeat, without flushing anything it has buffered. (Call
+ * this after tempseat_flush, or alternatively, when you were going to
+ * abandon the whole connection anyway.) */
+void tempseat_free(Seat *ts);
+
typedef struct rgb {
uint8_t r, g, b;
} rgb;
@@ -1282,10 +1680,22 @@ struct TermWinVtable {
void (*refresh)(TermWin *);
+ /* request_resize asks the front end if the terminal can please be
+ * resized to (w,h) in characters. The front end MAY call
+ * term_size() in response to tell the terminal its new size
+ * (which MAY be the requested size, or some other size if the
+ * requested one can't be achieved). The front end MAY also not
+ * call term_size() at all. But the front end MUST reply to this
+ * request by calling term_resize_request_completed(), after the
+ * responding resize event has taken place (if any).
+ *
+ * The calls to term_size and term_resize_request_completed may be
+ * synchronous callbacks from within the call to request_resize(). */
void (*request_resize)(TermWin *, int w, int h);
- void (*set_title)(TermWin *, const char *title);
- void (*set_icon_title)(TermWin *, const char *icontitle);
+ void (*set_title)(TermWin *, const char *title, int codepage);
+ void (*set_icon_title)(TermWin *, const char *icontitle, int codepage);
+
/* set_minimised and set_maximised are assumed to set two
* independent settings, rather than a single three-way
* {min,normal,max} switch. The idea is that when you un-minimise
@@ -1312,6 +1722,11 @@ struct TermWinVtable {
* object, because that doesn't happen until term_init
* returns. */
void (*palette_get_overrides)(TermWin *, Terminal *);
+
+ /* Notify the front end that the terminal's buffer of unprocessed
+ * output has reduced. (Front ends will likely pass this straight
+ * on to backend_unthrottle.) */
+ void (*unthrottle)(TermWin *, size_t bufsize);
};
static inline bool win_setup_draw_ctx(TermWin *win)
@@ -1350,10 +1765,11 @@ 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_title(TermWin *win, const char *title, int codepage)
+{ win->vt->set_title(win, title, codepage); }
+static inline void win_set_icon_title(TermWin *win, const char *icontitle,
+ int codepage)
+{ win->vt->set_icon_title(win, icontitle, codepage); }
static inline void win_set_minimised(TermWin *win, bool minimised)
{ win->vt->set_minimised(win, minimised); }
static inline void win_set_maximised(TermWin *win, bool maximised)
@@ -1367,6 +1783,8 @@ static inline void win_palette_set(
{ win->vt->palette_set(win, start, ncolours, colours); }
static inline void win_palette_get_overrides(TermWin *win, Terminal *term)
{ win->vt->palette_get_overrides(win, term); }
+static inline void win_unthrottle(TermWin *win, size_t size)
+{ win->vt->unthrottle(win, size); }
/*
* Global functions not specific to a connection instance.
@@ -1417,6 +1835,8 @@ NORETURN void cleanup_exit(int);
X(BOOL, NONE, change_username) /* allow username switching in SSH-2 */ \
X(INT, INT, ssh_cipherlist) \
X(FILENAME, NONE, keyfile) \
+ X(FILENAME, NONE, detached_cert) \
+ X(STR, NONE, auth_plugin) \
/* \
* Which SSH protocol to use. \
* For historical reasons, the current legal values for CONF_sshprot \
@@ -1474,6 +1894,7 @@ NORETURN void cleanup_exit(int);
X(BOOL, NONE, bksp_is_delete) \
X(BOOL, NONE, rxvt_homeend) \
X(INT, NONE, funky_type) /* FUNKY_XTERM, FUNKY_LINUX, ... */ \
+ X(INT, NONE, sharrow_type) /* SHARROW_APPLICATION, SHARROW_BITMAP, ... */ \
X(BOOL, NONE, no_applic_c) /* totally disable app cursor keys */ \
X(BOOL, NONE, no_applic_k) /* totally disable app keypad */ \
X(BOOL, NONE, no_mouse_rep) /* totally disable mouse reporting */ \
@@ -1606,6 +2027,8 @@ NORETURN void cleanup_exit(int);
X(INT, NONE, sshbug_oldgex2) \
X(INT, NONE, sshbug_winadj) \
X(INT, NONE, sshbug_chanreq) \
+ X(INT, NONE, sshbug_dropstart) \
+ X(INT, NONE, sshbug_filter_kexinit) \
/* \
* ssh_simple means that we promise never to open any channel \
* other than the main one, which means it can safely use a very \
@@ -1686,7 +2109,7 @@ void fontspec_serialise(BinarySink *bs, FontSpec *f);
FontSpec *fontspec_deserialise(BinarySource *src);
/*
- * Exports from noise.c.
+ * Exports from each platform's noise.c.
*/
typedef enum NoiseSourceId {
NOISE_SOURCE_TIME,
@@ -1712,6 +2135,10 @@ void noise_get_heavy(void (*func) (void *, int));
void noise_get_light(void (*func) (void *, int));
void noise_regular(void);
void noise_ultralight(NoiseSourceId id, unsigned long data);
+
+/*
+ * Exports from sshrand.c.
+ */
void random_save_seed(void);
void random_destroy_seed(void);
@@ -1763,6 +2190,7 @@ FontSpec *platform_default_fontspec(const char *name);
Terminal *term_init(Conf *, struct unicode_data *, TermWin *);
void term_free(Terminal *);
void term_size(Terminal *, int, int, int);
+void term_resize_request_completed(Terminal *);
void term_paint(Terminal *, int, int, int, int, bool);
void term_scroll(Terminal *, int, int);
void term_scroll_to_selection(Terminal *, int);
@@ -1770,6 +2198,7 @@ void term_pwron(Terminal *, bool);
void term_clrsb(Terminal *);
void term_mouse(Terminal *, Mouse_Button, Mouse_Button, Mouse_Action,
int, int, bool, bool, bool);
+void term_cancel_selection_drag(Terminal *);
void term_key(Terminal *, Key_Sym, wchar_t *, size_t, unsigned int,
unsigned int);
void term_lost_clipboard_ownership(Terminal *, int clipboard);
@@ -1784,15 +2213,15 @@ 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);
+size_t term_data(Terminal *, const void *data, size_t len);
void term_provide_backend(Terminal *term, Backend *backend);
void term_provide_logctx(Terminal *term, LogContext *logctx);
void term_set_focus(Terminal *term, bool has_focus);
char *term_get_ttymode(Terminal *term, const char *mode);
-int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input);
+SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p);
void term_set_trust_status(Terminal *term, bool trusted);
void term_keyinput(Terminal *, int codepage, const void *buf, int len);
-void term_keyinputw(Terminal *, const wchar_t * widebuf, int len);
+void term_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);
@@ -1804,10 +2233,13 @@ void term_palette_override(Terminal *term, unsigned osc4_index, rgb rgb);
typedef enum SmallKeypadKey {
SKK_HOME, SKK_END, SKK_INSERT, SKK_DELETE, SKK_PGUP, SKK_PGDN,
} SmallKeypadKey;
-int format_arrow_key(char *buf, Terminal *term, int xkey, bool ctrl);
+int format_arrow_key(char *buf, Terminal *term, int xkey,
+ bool shift, bool ctrl, bool alt, bool *consumed_alt);
int format_function_key(char *buf, Terminal *term, int key_number,
- bool shift, bool ctrl);
-int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key);
+ bool shift, bool ctrl, bool alt, bool *consumed_alt);
+int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key,
+ bool shift, bool ctrl, bool alt,
+ bool *consumed_alt);
int format_numeric_keypad_key(char *buf, Terminal *term, char key,
bool shift, bool ctrl);
@@ -1870,7 +2302,7 @@ static inline void lp_logging_error(LogPolicy *lp, const char *event)
static inline bool lp_verbose(LogPolicy *lp)
{ return lp->vt->verbose(lp); }
-/* Defined in conscli.c, used in several console command-line tools */
+/* Defined in clicons.c, used in several console command-line tools */
extern LogPolicy console_cli_logpolicy[];
int console_askappend(LogPolicy *lp, Filename *filename,
@@ -1888,6 +2320,7 @@ void logfopen(LogContext *logctx);
void logfclose(LogContext *logctx);
void logtraffic(LogContext *logctx, unsigned char c, int logmode);
void logflush(LogContext *logctx);
+LogPolicy *log_get_policy(LogContext *logctx);
void logevent(LogContext *logctx, const char *event);
void logeventf(LogContext *logctx, const char *fmt, ...) PRINTF_LIKE(2, 3);
void logeventvf(LogContext *logctx, const char *fmt, va_list ap);
@@ -1937,7 +2370,7 @@ extern const struct BackendVtable rlogin_backend;
extern const struct BackendVtable telnet_backend;
/*
- * Exports from ssh.c.
+ * Exports from ssh/ssh.c.
*/
extern const struct BackendVtable ssh_backend;
extern const struct BackendVtable sshconn_backend;
@@ -1955,6 +2388,29 @@ void ldisc_configure(Ldisc *, Conf *);
void ldisc_free(Ldisc *);
void ldisc_send(Ldisc *, const void *buf, int len, bool interactive);
void ldisc_echoedit_update(Ldisc *);
+typedef struct LdiscInputToken {
+ /*
+ * Structure that encodes any single item of data that Ldisc can
+ * buffer: either a single character of raw data, or a session
+ * special.
+ */
+ bool is_special;
+ union {
+ struct {
+ /* if is_special == false */
+ char chr;
+ };
+ struct {
+ /* if is_special == true */
+ SessionSpecialCode code;
+ int arg;
+ };
+ };
+} LdiscInputToken;
+bool ldisc_has_input_buffered(Ldisc *);
+LdiscInputToken ldisc_get_input_token(Ldisc *); /* asserts there is input */
+void ldisc_enable_prompt_callback(Ldisc *, prompts_t *);
+void ldisc_check_sendok(Ldisc *);
/*
* Exports from sshrand.c.
@@ -1999,7 +2455,7 @@ void pinger_reconfig(Pinger *, Conf *oldconf, Conf *newconf);
void pinger_free(Pinger *);
/*
- * Exports from misc.c.
+ * Exports from modules in utils.
*/
#include "misc.h"
@@ -2012,19 +2468,13 @@ char const *conf_dest(Conf *conf);
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.
+ * Exports from version.c and cmake_commit.c.
*/
extern const char ver[];
extern const char commitid[];
/*
- * Exports from unicode.c.
+ * Exports from unicode.c in platform subdirs.
*/
#ifndef CP_UTF8
#define CP_UTF8 65001
@@ -2034,14 +2484,13 @@ 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);
+ char *mbstr, int mblen, const char *defchr);
wchar_t xlat_uskbd2cyrllic(int ch);
int check_compose(int first, int second);
-int decode_codepage(char *cp_name);
+int decode_codepage(const char *cp_name);
const char *cp_enumerate (int index);
const char *cp_name(int codepage);
-void get_unitab(int codepage, wchar_t * unitab, int ftype);
+void get_unitab(int codepage, wchar_t *unitab, int ftype);
/*
* Exports from wcwidth.c
@@ -2052,7 +2501,7 @@ int mk_wcwidth_cjk(unsigned int ucs);
int mk_wcswidth_cjk(const unsigned int *pwcs, size_t n);
/*
- * Exports from pageantc.c.
+ * Exports from agent-client.c in platform subdirs.
*
* agent_query returns NULL for here's-a-response, and non-NULL for
* query-in- progress. In the latter case there will be a call to
@@ -2071,8 +2520,8 @@ int mk_wcswidth_cjk(const unsigned int *pwcs, size_t n);
*
* 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.
+ * return NULL. The wrapper function agent_query_synchronous()
+ * (defined in its own module aqsync.c) makes this easier.
*/
typedef struct agent_pending_query agent_pending_query;
agent_pending_query *agent_query(
@@ -2094,7 +2543,7 @@ int wc_match(const char *wildcard, const char *target);
bool wc_unescape(char *output, const char *wildcard);
/*
- * Exports from frontend (windlg.c etc)
+ * Exports from frontend (dialog.c etc)
*/
void pgp_fingerprints(void);
/*
@@ -2104,11 +2553,11 @@ void pgp_fingerprints(void);
bool have_ssh_host_key(const char *host, int port, const char *keytype);
/*
- * Exports from console frontends (wincons.c, uxcons.c)
+ * Exports from console frontends (console.c in platform subdirs)
* that aren't equivalents to things in windlg.c et al.
*/
extern bool console_batch_mode, console_antispoof_prompt;
-int console_get_userpass_input(prompts_t *p);
+SeatPromptResult console_get_userpass_input(prompts_t *p);
bool is_interactive(void);
void console_print_error_msg(const char *prefix, const char *msg);
void console_print_error_msg_fmt_v(
@@ -2117,7 +2566,7 @@ void console_print_error_msg_fmt(const char *prefix, const char *fmt, ...)
PRINTF_LIKE(2, 3);
/*
- * Exports from printing.c.
+ * Exports from printing.c in platform subdirs.
*/
typedef struct printer_enum_tag printer_enum;
typedef struct printer_job_tag printer_job;
@@ -2138,10 +2587,15 @@ void printer_finish_job(printer_job *);
* zero out password arguments in the hope of not having them show up
* avoidably in Unix 'ps'.
*/
+struct cmdline_get_passwd_input_state { bool tried; };
+#define CMDLINE_GET_PASSWD_INPUT_STATE_INIT { .tried = false }
+extern const cmdline_get_passwd_input_state cmdline_get_passwd_input_state_new;
+
int cmdline_process_param(const char *, char *, int, Conf *);
void cmdline_run_saved(Conf *);
void cmdline_cleanup(void);
-int cmdline_get_passwd_input(prompts_t *p);
+SeatPromptResult cmdline_get_passwd_input(
+ prompts_t *p, cmdline_get_passwd_input_state *state, bool restartable);
bool cmdline_host_ok(Conf *);
bool cmdline_verbose(void);
bool cmdline_loaded_session(void);
@@ -2180,31 +2634,80 @@ 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 conf_radiobutton_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event);
#define CHECKBOX_INVERT (1<<30)
-void conf_checkbox_handler(union control *ctrl, dlgparam *dlg,
+void conf_checkbox_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event);
-void conf_editbox_handler(union control *ctrl, dlgparam *dlg,
+void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event);
-void conf_filesel_handler(union control *ctrl, dlgparam *dlg,
+void conf_filesel_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event);
-void conf_fontsel_handler(union control *ctrl, dlgparam *dlg,
+void conf_fontsel_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event);
+struct conf_editbox_handler_type {
+ /* Structure passed as context2 to conf_editbox_handler */
+ enum { EDIT_STR, EDIT_INT, EDIT_FIXEDPOINT } type;
+ union {
+ /*
+ * EDIT_STR means the edit box is connected to a string
+ * field in Conf. No further parameters needed.
+ */
+
+ /*
+ * EDIT_INT means the edit box is connected to an int field in
+ * Conf, and the input string is interpreted as decimal. No
+ * further parameters needed. (But we could add one here later
+ * if for some reason we wanted int fields in hex.)
+ */
+
+ /*
+ * EDIT_FIXEDPOINT means the edit box is connected to an int
+ * field in Conf, but the input string is interpreted as
+ * _floating point_, and converted to/from the output int by
+ * means of a fixed denominator. That is,
+ *
+ * (floating value in edit box) * denominator = value in Conf
+ */
+ struct {
+ double denominator;
+ };
+ };
+};
+
+extern const struct conf_editbox_handler_type conf_editbox_str;
+extern const struct conf_editbox_handler_type conf_editbox_int;
+#define ED_STR CP(&conf_editbox_str)
+#define ED_INT CP(&conf_editbox_int)
+
void setup_config_box(struct controlbox *b, bool midsession,
int protocol, int protcfginfo);
+void setup_ca_config_box(struct controlbox *b);
+
+/* Platforms provide this to be called from config.c */
+void show_ca_config_box(dlgparam *dlg);
+extern const bool has_ca_config_box; /* false if, e.g., we're PuTTYtel */
+
+/* Visible outside config.c so that platforms can use it to recognise
+ * the proxy type control */
+void proxy_type_handler(dlgcontrol *ctrl, dlgparam *dlg,
+ void *data, int event);
+/* And then they'll set this flag in its generic.context.i */
+#define PROXY_UI_FLAG_LOCAL 1 /* has a local proxy */
+
/*
- * Exports from minibidi.c.
+ * Exports from bidi.c.
*/
#define BIDI_CHAR_INDEX_NONE ((unsigned short)-1)
typedef struct bidi_char {
unsigned int origwc, wc;
unsigned short index, nchars;
} bidi_char;
-int do_bidi(bidi_char *line, int count);
+BidiContext *bidi_new_context(void);
+void bidi_free_context(BidiContext *ctx);
+void do_bidi(BidiContext *ctx, bidi_char *line, size_t count);
int do_shape(bidi_char *line, bidi_char *to, int count);
bool is_rtl(int c);
@@ -2217,7 +2720,7 @@ enum {
X11_XDM, /* XDM-AUTHORIZATION-1 */
X11_NAUTHS
};
-extern const char *const x11_authnames[]; /* declared in x11fwd.c */
+extern const char *const x11_authnames[X11_NAUTHS];
/*
* An enum for the copy-paste UI action configuration.
@@ -2371,7 +2874,6 @@ unsigned long timing_last_clock(void);
* 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);
@@ -2398,6 +2900,16 @@ void request_callback_notifications(toplevel_callback_notify_fn_t notify,
void *ctx);
/*
+ * Facility provided by the platform to spawn a parallel subprocess
+ * and present its stdio via a Socket.
+ *
+ * 'prefix' indicates the prefix that should appear on messages passed
+ * to plug_log to provide stderr output from the process.
+ */
+Socket *platform_start_subprocess(const char *cmd, Plug *plug,
+ const char *prefix);
+
+/*
* Define no-op macros for the jump list functions, on platforms that
* don't support them. (This is a bit of a hack, and it'd be nicer to
* localise even the calls to those functions into the Windows front
diff --git a/PUTTYMEM.H b/PUTTYMEM.H
index 6513d6cf..11c1f83a 100644
--- a/PUTTYMEM.H
+++ b/PUTTYMEM.H
@@ -107,18 +107,19 @@ void *safegrowarray(void *array, size_t *size, size_t eltsize,
/*
* 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.
+ * functions when allocation fails. Usually it's provided by an
+ * implementation in utils, which ties it into an application's
+ * existing modalfatalbox() system, but standalone test applications
+ * can reimplement it some other way if they prefer.
*/
NORETURN void out_of_memory(void);
#ifdef MINEFIELD
/*
* Definitions for Minefield, PuTTY's own Windows-specific malloc
- * debugger in the style of Electric Fence. Implemented in winmisc.c,
- * and referred to by the main malloc wrappers in memory.c.
+ * debugger in the style of Electric Fence. Implemented in
+ * windows/utils/minefield.c, and referred to by the main malloc
+ * wrappers in memory.c.
*/
void *minefield_c_malloc(size_t size);
void minefield_c_free(void *p);
diff --git a/PUTTYPS.H b/PUTTYPS.H
deleted file mode 100644
index 27916d27..00000000
--- a/PUTTYPS.H
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * 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/RAW.C b/RAW.C
deleted file mode 100644
index 0c454985..00000000
--- a/RAW.C
+++ /dev/null
@@ -1,330 +0,0 @@
-/*
- * "Raw" backend.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-
-#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/README b/README
index de6eb9b0..979c02ba 100644
--- a/README
+++ b/README
@@ -1,129 +1,32 @@
-This is the README for the source archive of PuTTY, a free Windows
-and Unix Telnet and SSH client.
+This is the README for PuTTY, a free Windows and Unix Telnet and SSH
+client.
-If you want to rebuild PuTTY from source, we provide a variety of
-Makefiles and equivalents. (If you have fetched the source from
-Git, you'll have to generate the Makefiles yourself -- see
-below.)
+PuTTY is built using CMake <https://cmake.org/>. To compile in the
+simplest way (on any of Linux, Windows or Mac), run these commands in
+the source directory:
-There are various compile-time directives that you can use to
-disable or modify certain features; it may be necessary to do this
-in some environments. They are documented in `Recipe', and in
-comments in many of the generated Makefiles.
+ cmake .
+ cmake --build .
-For building on Windows:
+Then, to install in the simplest way on Linux or Mac:
- - windows/Makefile.vc is for command-line builds on MS Visual C++
- systems. Change into the `windows' subdirectory and type `nmake
- -f Makefile.vc' to build all the PuTTY binaries.
+ cmake --build . --target install
- As of 2017, we successfully compile PuTTY with both Visual Studio
- 7 (2003) and Visual Studio 14 (2015), so our guess is that it will
- probably build with versions in between those as well.
-
- (The binaries from Visual Studio 14 are only compatible with
- Windows XP and up. Binaries from Visual Studio 7 ought to work
- with anything from Windows 95 onward.)
-
- - Inside the windows/MSVC subdirectory are MS Visual Studio project
- files for doing GUI-based builds of the various PuTTY utilities.
- These have been tested on Visual Studio 7 and 10.
-
- You should be able to build each PuTTY utility by loading the
- corresponding .dsp file in Visual Studio. For example,
- MSVC/putty/putty.dsp builds PuTTY itself, MSVC/plink/plink.dsp
- builds Plink, and so on.
-
- - windows/Makefile.mgw is for MinGW / Cygwin installations. Type
- `make -f Makefile.mgw' while in the `windows' subdirectory to
- build all the PuTTY binaries.
-
- MinGW and friends can lag behind other toolchains in their support
- for the Windows API. Compile-time levers are provided to exclude
- some features; the defaults are set appropriately for the
- 'mingw-w64' cross-compiler provided with Ubuntu 14.04. If you are
- using an older toolchain, you may need to exclude more features;
- alternatively, you may find that upgrading to a recent version of
- the 'w32api' package helps.
-
- - windows/Makefile.lcc is for lcc-win32. Type `make -f
- Makefile.lcc' while in the `windows' subdirectory. (You will
- probably need to specify COMPAT=-DNO_MULTIMON.)
-
- - Inside the windows/DEVCPP subdirectory are Dev-C++ project
- files for doing GUI-based builds of the various PuTTY utilities.
-
-The PuTTY team actively use Makefile.vc (with VC7/10) and Makefile.mgw
-(with mingw32), so we'll probably notice problems with those
-toolchains fairly quickly. Please report any problems with the other
-toolchains mentioned above.
-
-For building on Unix:
-
- - unix/configure is for Unix and GTK. If you don't have GTK, you
- should still be able to build the command-line utilities (PSCP,
- PSFTP, Plink, PuTTYgen) using this script. To use it, change into
- the `unix' subdirectory, run `./configure' and then `make'. Or you
- can do the same in the top-level directory (we provide a little
- wrapper that invokes configure one level down), which is more like
- a normal Unix source archive but doesn't do so well at keeping the
- per-platform stuff in each platform's subdirectory; it's up to you.
-
- - unix/Makefile.gtk and unix/Makefile.ux are for non-autoconfigured
- builds. These makefiles expect you to change into the `unix'
- subdirectory, then run `make -f Makefile.gtk' or `make -f
- Makefile.ux' respectively. Makefile.gtk builds all the programs but
- relies on Gtk, whereas Makefile.ux builds only the command-line
- utilities and has no Gtk dependence.
-
- - For the graphical utilities, any of Gtk+-1.2, Gtk+-2.0, and Gtk+-3.0
- should be supported. If you have more than one installed, you can
- manually specify which one you want by giving the option
- '--with-gtk=N' to the configure script where N is 1, 2, or 3.
- (The default is the newest available, of course.) In the absence
- of any Gtk version, the configure script will automatically
- construct a Makefile which builds only the command-line utilities;
- you can manually create this condition by giving configure the
- option '--without-gtk'.
-
- - pterm would like to be setuid or setgid, as appropriate, to permit
- it to write records of user logins to /var/run/utmp and
- /var/log/wtmp. (Of course it will not use this privilege for
- anything else, and in particular it will drop all privileges before
- starting up complex subsystems like GTK.) By default the makefile
- will not attempt to add privileges to the pterm executable at 'make
- install' time, but you can ask it to do so by running configure
- with the option '--enable-setuid=USER' or '--enable-setgid=GROUP'.
-
- - The Unix Makefiles have an `install' target. Note that by default
- it tries to install `man' pages; if you have fetched the source via
- Git then you will need to have built these using Halibut
- first - see below.
-
- - It's also possible to build the Windows version of PuTTY to run
- on Unix by using Winelib. To do this, change to the `windows'
- directory and run `make -f Makefile.mgw CC=winegcc RC=wrc'.
-
-All of the Makefiles are generated automatically from the file
-`Recipe' by the Perl script `mkfiles.pl' (except for the Unix one,
-which is generated by the `configure' script; mkfiles.pl only
-generates the input to automake). Additions and corrections to Recipe,
-mkfiles.pl and/or configure.ac are much more useful than additions and
-corrections to the actual Makefiles, Makefile.am or Makefile.in.
-
-The Unix `configure' script and its various requirements are generated
-by the shell script `mkauto.sh', which requires GNU Autoconf, GNU
-Automake, and Gtk; if you've got the source from Git rather
-than using one of our source snapshots, you'll need to run this
-yourself. The input file to Automake is generated by mkfiles.pl along
-with all the rest of the makefiles, so you will need to run mkfiles.pl
-and then mkauto.sh.
+On Unix, pterm would like to be setuid or setgid, as appropriate, to
+permit it to write records of user logins to /var/run/utmp and
+/var/log/wtmp. (Of course it will not use this privilege for
+anything else, and in particular it will drop all privileges before
+starting up complex subsystems like GTK.) The cmake install step
+doesn't attempt to add these privileges, so if you want user login
+recording to work, you should manually ch{own,grp} and chmod the
+pterm binary yourself after installation. If you don't do this,
+pterm will still work, but not update the user login databases.
Documentation (in various formats including Windows Help and Unix
`man' pages) is built from the Halibut (`.but') files in the `doc'
-subdirectory using `doc/Makefile'. If you aren't using one of our
-source snapshots, you'll need to do this yourself. Halibut can be
-found at <https://www.chiark.greenend.org.uk/~sgtatham/halibut/>.
+subdirectory. If you aren't using one of our source snapshots,
+you'll need to do this yourself. Halibut can be found at
+<https://www.chiark.greenend.org.uk/~sgtatham/halibut/>.
The PuTTY home web site is
diff --git a/RECIPE b/RECIPE
deleted file mode 100644
index ea561b63..00000000
--- a/RECIPE
+++ /dev/null
@@ -1,431 +0,0 @@
-# -*- makefile -*-
-#
-# This file describes which PuTTY programs are made up from which
-# object and resource files. It is processed into the various
-# Makefiles by means of a Perl script. Makefile changes should
-# really be made by editing this file and/or the Perl script, not
-# by editing the actual Makefiles.
-
-# ------------------------------------------------------------
-# Top-level configuration.
-
-# Overall project name.
-!name putty
-# Locations and types of output Makefiles.
-!makefile clangcl windows/Makefile.clangcl
-!makefile vc windows/Makefile.vc
-!makefile vcproj windows/MSVC
-!makefile cygwin windows/Makefile.mgw
-!makefile lcc windows/Makefile.lcc
-!makefile gtk unix/Makefile.gtk
-!makefile unix unix/Makefile.ux
-!makefile am Makefile.am
-!makefile devcppproj windows/DEVCPP
-!makefile vstudio10 windows/VS2010
-!makefile vstudio12 windows/VS2012
-# Source directories.
-!srcdir charset/
-!srcdir windows/
-!srcdir unix/
-
-# Help text added to the top of each Makefile, with /D converted
-# into -D as appropriate for the particular Makefile.
-
-!begin help
-#
-# Extra options you can set:
-#
-# - COMPAT=/DAUTO_WINSOCK (Windows only)
-# Causes PuTTY to assume that <windows.h> includes its own WinSock
-# header file, so that it won't try to include <winsock.h>.
-#
-# - COMPAT=/DWINSOCK_TWO (Windows only)
-# Causes the PuTTY utilities to include <winsock2.h> instead of
-# <winsock.h>, except Plink which _needs_ WinSock 2 so it already
-# does this.
-#
-# - COMPAT=/DNO_SECURITY (Windows only)
-# Disables use of <aclapi.h>, 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 <multimon.h>, 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 <htmlhelp.h>, 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.
-!end
-
-# ------------------------------------------------------------
-# Additional text added verbatim to each individual Makefile.
-
-!cflags am version
-!begin am
-if AUTO_GIT_COMMIT
-BUILT_SOURCES = empty.h
-CLEANFILES = empty.h
-libversion_a_CFLAGS += -DSOURCE_COMMIT=\"`git --git-dir=$(srcdir)/.git rev-parse HEAD 2>/dev/null`\"
-empty.h: $(allsources)
- echo '/* Empty file touched by automake makefile to force rebuild of version.o */' >$@
-endif
-
-# Run the cryptsuite tests as part of 'make check'. Override
-# PUTTY_TESTCRYPT so that cryptsuite will take the testcrypt binary
-# from the build directory instead of the source directory, in case
-# this is an out-of-tree build.
-check-local: testcrypt
- PUTTY_TESTCRYPT=./testcrypt $(srcdir)/test/cryptsuite.py
-
-!end
-!begin >empty.h
-/* Empty file touched by automake makefile to force rebuild of version.o */
-!end
-
-!begin vc vars
-CFLAGS = $(CFLAGS) /DHAS_GSSAPI
-!end
-
-!begin clangcl vars
-CFLAGS += /DHAS_GSSAPI
-!end
-
-# `make install' target for Unix.
-!begin gtk
-install:
- mkdir -p $(DESTDIR)$(bindir) $(DESTDIR)$(man1dir)
- $(INSTALL_PROGRAM) -m 755 pageant $(DESTDIR)$(bindir)/pageant
- $(INSTALL_PROGRAM) -m 755 plink $(DESTDIR)$(bindir)/plink
- $(INSTALL_PROGRAM) -m 755 pscp $(DESTDIR)$(bindir)/pscp
- $(INSTALL_PROGRAM) -m 755 psftp $(DESTDIR)$(bindir)/psftp
- $(INSTALL_PROGRAM) -m 755 pterm $(DESTDIR)$(bindir)/pterm
- if test -n "$(UTMP_GROUP)"; then \
- chgrp $(UTMP_GROUP) $(DESTDIR)$(bindir)/pterm && \
- chmod 2755 $(DESTDIR)$(bindir)/pterm; \
- elif test -n "$(UTMP_USER)"; then \
- chown $(UTMP_USER) $(DESTDIR)$(bindir)/pterm && \
- chmod 4755 $(DESTDIR)$(bindir)/pterm; \
- fi
- $(INSTALL_PROGRAM) -m 755 putty $(DESTDIR)$(bindir)/putty
- $(INSTALL_PROGRAM) -m 755 puttygen $(DESTDIR)$(bindir)/puttygen
- $(INSTALL_PROGRAM) -m 755 puttytel $(DESTDIR)$(bindir)/puttytel
- $(INSTALL_DATA) -m 644 ../doc/pageant.1 $(DESTDIR)$(man1dir)/pageant.1
- $(INSTALL_DATA) -m 644 ../doc/plink.1 $(DESTDIR)$(man1dir)/plink.1
- $(INSTALL_DATA) -m 644 ../doc/pscp.1 $(DESTDIR)$(man1dir)/pscp.1
- $(INSTALL_DATA) -m 644 ../doc/psftp.1 $(DESTDIR)$(man1dir)/psftp.1
- $(INSTALL_DATA) -m 644 ../doc/pterm.1 $(DESTDIR)$(man1dir)/pterm.1
- $(INSTALL_DATA) -m 644 ../doc/putty.1 $(DESTDIR)$(man1dir)/putty.1
- $(INSTALL_DATA) -m 644 ../doc/puttygen.1 $(DESTDIR)$(man1dir)/puttygen.1
- $(INSTALL_DATA) -m 644 ../doc/puttytel.1 $(DESTDIR)$(man1dir)/puttytel.1
-
-install-strip:
- $(MAKE) install INSTALL_PROGRAM="$(INSTALL_PROGRAM) -s"
-!end
-
-# List the man pages for the automake makefile.
-!begin am
-if HAVE_GTK
-man1_MANS = doc/plink.1 doc/pscp.1 doc/psftp.1 doc/puttygen.1 doc/psusan.1 \
- doc/pageant.1 doc/pterm.1 doc/putty.1 doc/puttytel.1
-else
-man1_MANS = doc/plink.1 doc/pscp.1 doc/psftp.1 doc/puttygen.1 doc/psusan.1
-endif
-!end
-
-# In automake, chgrp/chmod pterm after installation, if configured to.
-!begin am
-if HAVE_SETID_CMD
-install-exec-local:
- @SETID_CMD@ $(bindir)/pterm
- chmod @SETID_MODE@ $(bindir)/pterm
-endif
-!end
-
-# In automake makefile, build the OS X app bundle, if configured in
-# Quartz mode.
-!begin am
-if HAVE_QUARTZ
-noinst_SCRIPTS = unix/PuTTY.app unix/Pterm.app
-unix/PuTTY.app: unix/putty.bundle puttyapp osxlaunch
- rm -rf $@ && PUTTY_GTK_PREFIX_FROM_MAKEFILE=$$(pkg-config --variable=prefix gtk+-3.0) gtk-mac-bundler $<
-unix/Pterm.app: unix/pterm.bundle ptermapp osxlaunch
- rm -rf $@ && PUTTY_GTK_PREFIX_FROM_MAKEFILE=$$(pkg-config --variable=prefix gtk+-3.0) gtk-mac-bundler $<
-endif
-!end
-
-# Random symbols.
-!begin cygwin vars
-# _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
-!end
-
-# ------------------------------------------------------------
-# Definitions of object groups. A group name, followed by an =,
-# followed by any number of objects or other already-defined group
-# names. A line beginning `+' is assumed to continue the previous
-# line.
-
-# conf.c and its dependencies.
-CONF = conf marshal
-
-# Terminal emulator and its (platform-independent) dependencies.
-TERMINAL = terminal stripctrl wcwidth logging tree234 minibidi
- + config dialog CONF
-
-# GUI front end and terminal emulator (putty, puttytel).
-GUITERM = TERMINAL window windlg winctrls sizetip winprint winutils
- + wincfg winhelp winjump sessprep winselgui
-
-# Same thing on Unix.
-UXTERM = TERMINAL uxcfg uxucs uxprint timing callback miscucs
-GTKTERM = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols gtkmisc xkeysym
- + x11misc gtkcomm sessprep
-GTKMAIN = gtkmain cmdline
-
-# Non-SSH back ends (putty, puttytel, plink).
-NONSSH = telnet raw rlogin supdup ldisc pinger
-
-# SSH back end (putty, plink, pscp, psftp).
-ARITH = mpint ecc
-SSHCRYPTO = ARITH sshmd5 sshsha sshsh256 sshsh512 sshsha3 sshblake2 sshargon2
- + sshrsa sshdss sshecc
- + sshdes sshblowf sshaes sshccp ssharcf
- + sshdh sshcrc sshcrcda sshauxcrypt
- + sshhmac
-SSHCOMMON = sshcommon sshutils sshprng sshrand SSHCRYPTO
- + sshverstring
- + sshpubk sshzlib
- + sshmac marshal nullplug
- + sshgssc pgssapi wildcard ssh1censor ssh2censor ssh2bpp
- + ssh2transport ssh2transhk ssh2connection portfwd x11fwd
- + ssh1connection ssh1bpp ssh2bpp-bare
-SSH = SSHCOMMON ssh
- + ssh1login ssh2userauth
- + pinger
- + sshshare aqsync agentf
- + mainchan ssh2kex-client ssh2connection-client ssh1connection-client
-WINSSH = SSH winnoise wincapi winpgntc wingss winshare winnps winnpc
- + winhsock errsock
-UXSSH = SSH uxnoise uxagentc uxgss uxshare
-
-# SFTP implementation (pscp, psftp).
-SFTP = psftpcommon sftp sftpcommon logging cmdline
-
-# Components of the prime-generation system.
-SSHPRIME = sshprime smallprimes primecandidate millerrabin pockle mpunsafe
-
-# Miscellaneous objects appearing in all the utilities, or all the
-# network ones, or the Unix or Windows subsets of those in turn.
-MISC = misc utils marshal memory stripctrl wcwidth
-MISCNETCOMMON = timing callback MISC version tree234 CONF
-MISCNET = MISCNETCOMMON be_misc settings proxy
-WINMISC = MISCNET winstore winnet winhandl cmdline windefs winmisc winproxy
- + wintime winhsock errsock winsecur winucs miscucs winmiscs
-UXMISCCOMMON = MISCNETCOMMON uxstore uxsel uxpoll uxnet uxpeer uxmisc time
- + uxfdsock errsock
-UXMISC = MISCNET UXMISCCOMMON uxproxy uxutils
-
-# SSH server.
-SSHSERVER = SSHCOMMON sshserver settings be_none logging ssh2kex-server
- + ssh2userauth-server sshrsag SSHPRIME ssh2connection-server
- + sesschan sftpcommon sftpserver proxy cproxy ssh1login-server
- + ssh1connection-server scpserver
-
-# import.c and dependencies, for PuTTYgen-like utilities that have to
-# load foreign key files.
-IMPORT = import sshbcrypt sshblowf marshal
-
-# Character set library, for use in pterm.
-CHARSET = sbcsdat slookup sbcs utf8 toucs fromucs xenc mimeenc macenc localenc
-
-# Standard libraries.
-LIBS = advapi32.lib user32.lib gdi32.lib comdlg32.lib
- + shell32.lib imm32.lib ole32.lib
-
-# Network backend sets. This also brings in the relevant attachment
-# to proxy.c depending on whether we're crypto-avoidant or not.
-BE_ALL = be_all cproxy
-BE_NOSSH = be_nossh norand nocproxy
-BE_SSH = be_ssh cproxy
-BE_NONE = be_none nocproxy
-# More backend sets, with the additional Windows serial-port module.
-W_BE_ALL = be_all_s winser cproxy
-W_BE_NOSSH = be_nos_s norand winser nocproxy
-# And with the Unix serial-port module.
-U_BE_ALL = be_all_s uxser cproxy
-U_BE_NOSSH = be_nos_s norand uxser nocproxy
-
-# Auxiliary crypto modules used by key generators.
-KEYGEN = sshrsag sshdssg sshecdsag
-
-# ------------------------------------------------------------
-# Definitions of actual programs. The program name, followed by a
-# colon, followed by a list of objects. Also in the list may be the
-# keywords [G] for Windows GUI app, [C] for Console app, [X] for
-# X/GTK Unix app, [U] for command-line Unix app.
-
-putty : [G] GUITERM NONSSH WINSSH W_BE_ALL WINMISC winx11 putty.res LIBS
-puttytel : [G] GUITERM NONSSH W_BE_NOSSH WINMISC puttytel.res nogss LIBS
-plink : [C] winplink wincons console NONSSH WINSSH W_BE_ALL logging WINMISC
- + winx11 plink.res winnojmp sessprep noterm winnohlp winselcli
- + clicons wincliloop console LIBS
-pscp : [C] pscp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC
- + pscp.res winnojmp winnohlp winselcli clicons wincliloop
- + console LIBS
-psftp : [C] psftp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC
- + psftp.res winnojmp winnohlp winselcli clicons wincliloop
- + console LIBS
-
-pageant : [G] winpgnt pageant sshrsa sshpubk sshdes ARITH sshmd5 version
- + tree234 MISC sshaes sshsha winsecur winpgntc aqsync sshdss sshsh256
- + sshsh512 winutils sshecc winmisc winmiscs winhelp conf pageant.res
- + sshauxcrypt sshhmac wincapi winnps winnpc winhsock errsock winnet
- + winhandl callback be_misc winselgui winhandl sshsha3 sshblake2
- + sshargon2 LIBS
-
-puttygen : [G] winpgen KEYGEN SSHPRIME sshdes ARITH sshmd5 version
- + sshrand winnoise sshsha winstore MISC winctrls sshrsa sshdss winmisc
- + sshpubk sshaes sshsh256 sshsh512 IMPORT winutils puttygen.res
- + tree234 notiming winhelp winnojmp CONF LIBS wintime sshecc sshprng
- + sshauxcrypt sshhmac winsecur winmiscs sshsha3 sshblake2 sshargon2
-
-pterm : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore
- + uxsignal CHARSET cmdline uxpterm version time xpmpterm xpmptcfg
- + nogss utils memory GTKMAIN
-putty : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore
- + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11 xpmputty
- + xpmpucfg utils memory GTKMAIN
-puttytel : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_NOSSH
- + uxstore uxsignal CHARSET uxputty NONSSH UXMISC xpmputty xpmpucfg
- + nogss utils memory GTKMAIN
-
-plink : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal
- + ux_x11 noterm uxnogtk sessprep cmdline clicons uxcliloop console
-
-PUTTYGEN_UNIX = KEYGEN SSHPRIME sshdes ARITH sshmd5 version sshprng
- + sshrand uxnoise sshsha MISC sshrsa sshdss uxcons uxstore uxmisc
- + sshpubk sshaes sshsh256 sshsh512 IMPORT puttygen.res time tree234
- + uxgen notiming CONF sshecc sshsha3 uxnogtk sshauxcrypt sshhmac
- + uxpoll uxutils sshblake2 sshargon2 console
-puttygen : [U] cmdgen PUTTYGEN_UNIX
-cgtest : [UT] cgtest PUTTYGEN_UNIX
-
-pscp : [U] pscp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC uxnogtk
- + clicons uxcliloop console
-psftp : [U] psftp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC uxnogtk
- + clicons uxcliloop console
-
-pageant : [X] uxpgnt uxagentc aqsync pageant sshrsa sshpubk sshdes ARITH
- + sshmd5 version tree234 misc sshaes sshsha sshdss sshsh256 sshsh512
- + sshecc CONF uxsignal nocproxy nogss be_none x11fwd ux_x11 uxcons
- + gtkask gtkmisc nullplug logging UXMISC uxagentsock utils memory
- + sshauxcrypt sshhmac sshprng uxnoise uxcliloop sshsha3 sshblake2
- + sshargon2 console
-
-ptermapp : [XT] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore
- + uxsignal CHARSET uxpterm version time xpmpterm xpmptcfg
- + nogss gtkapp nocmdline utils memory
-puttyapp : [XT] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore
- + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11 xpmputty
- + xpmpucfg gtkapp nocmdline utils memory
-osxlaunch : [UT] osxlaunch
-
-fuzzterm : [UT] UXTERM CHARSET MISC version uxmisc uxucs fuzzterm time settings
- + uxstore be_none uxnogtk memory
-testcrypt : [UT] testcrypt SSHCRYPTO sshprng SSHPRIME sshpubk marshal utils
- + memory tree234 uxutils KEYGEN
-testcrypt : [C] testcrypt SSHCRYPTO sshprng SSHPRIME sshpubk marshal utils
- + memory tree234 winmiscs KEYGEN
-testsc : [UT] testsc SSHCRYPTO marshal utils memory tree234 wildcard
- + sshmac uxutils sshpubk
-testzlib : [UT] testzlib sshzlib utils marshal memory
-
-uppity : [UT] uxserver SSHSERVER UXMISC uxsignal uxnoise uxgss uxnogtk
- + uxpty uxsftpserver ux_x11 uxagentsock procnet uxcliloop
-psusan : [U] uxpsusan SSHSERVER UXMISC uxsignal uxnoise nogss uxnogtk
- + uxpty uxsftpserver ux_x11 uxagentsock procnet uxcliloop
-
-PSOCKS = psocks portfwd conf sshutils logging proxy nocproxy timing callback
- + time tree234 version errsock be_misc norand MISC
-psocks : [C] PSOCKS winsocks wincons winproxy winnet winmisc winselcli
- + winhsock winhandl winmiscs winnohlp wincliloop console LIBS
-psocks : [UT] PSOCKS uxsocks uxcons uxproxy uxnet uxmisc uxpoll uxsel uxnogtk
- + uxpeer uxfdsock uxcliloop uxsignal console
-
-# ----------------------------------------------------------------------
-# On Windows, provide a means of removing local test binaries that we
-# aren't going to actually ship. (I prefer this to not building them
-# in the first place, so that we find out about build breakage early.)
-!begin vc
-cleantestprogs:
- -del $(BUILDDIR)testcrypt.exe $(BUILDDIR)psocks.exe
-!end
-!begin clangcl
-cleantestprogs:
- -rm -f $(BUILDDIR)testcrypt.exe $(BUILDDIR)psocks.exe
-!end
diff --git a/RESOURCE.H b/RESOURCE.H
deleted file mode 100644
index e856d6b9..00000000
--- a/RESOURCE.H
+++ /dev/null
@@ -1,15 +0,0 @@
-//{{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/RLOGIN.C b/RLOGIN.C
deleted file mode 100644
index 2a3714e0..00000000
--- a/RLOGIN.C
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * Rlogin backend.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-#include <ctype.h>
-
-#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/Recipe b/Recipe
deleted file mode 100644
index ea561b63..00000000
--- a/Recipe
+++ /dev/null
@@ -1,431 +0,0 @@
-# -*- makefile -*-
-#
-# This file describes which PuTTY programs are made up from which
-# object and resource files. It is processed into the various
-# Makefiles by means of a Perl script. Makefile changes should
-# really be made by editing this file and/or the Perl script, not
-# by editing the actual Makefiles.
-
-# ------------------------------------------------------------
-# Top-level configuration.
-
-# Overall project name.
-!name putty
-# Locations and types of output Makefiles.
-!makefile clangcl windows/Makefile.clangcl
-!makefile vc windows/Makefile.vc
-!makefile vcproj windows/MSVC
-!makefile cygwin windows/Makefile.mgw
-!makefile lcc windows/Makefile.lcc
-!makefile gtk unix/Makefile.gtk
-!makefile unix unix/Makefile.ux
-!makefile am Makefile.am
-!makefile devcppproj windows/DEVCPP
-!makefile vstudio10 windows/VS2010
-!makefile vstudio12 windows/VS2012
-# Source directories.
-!srcdir charset/
-!srcdir windows/
-!srcdir unix/
-
-# Help text added to the top of each Makefile, with /D converted
-# into -D as appropriate for the particular Makefile.
-
-!begin help
-#
-# Extra options you can set:
-#
-# - COMPAT=/DAUTO_WINSOCK (Windows only)
-# Causes PuTTY to assume that <windows.h> includes its own WinSock
-# header file, so that it won't try to include <winsock.h>.
-#
-# - COMPAT=/DWINSOCK_TWO (Windows only)
-# Causes the PuTTY utilities to include <winsock2.h> instead of
-# <winsock.h>, except Plink which _needs_ WinSock 2 so it already
-# does this.
-#
-# - COMPAT=/DNO_SECURITY (Windows only)
-# Disables use of <aclapi.h>, 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 <multimon.h>, 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 <htmlhelp.h>, 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.
-!end
-
-# ------------------------------------------------------------
-# Additional text added verbatim to each individual Makefile.
-
-!cflags am version
-!begin am
-if AUTO_GIT_COMMIT
-BUILT_SOURCES = empty.h
-CLEANFILES = empty.h
-libversion_a_CFLAGS += -DSOURCE_COMMIT=\"`git --git-dir=$(srcdir)/.git rev-parse HEAD 2>/dev/null`\"
-empty.h: $(allsources)
- echo '/* Empty file touched by automake makefile to force rebuild of version.o */' >$@
-endif
-
-# Run the cryptsuite tests as part of 'make check'. Override
-# PUTTY_TESTCRYPT so that cryptsuite will take the testcrypt binary
-# from the build directory instead of the source directory, in case
-# this is an out-of-tree build.
-check-local: testcrypt
- PUTTY_TESTCRYPT=./testcrypt $(srcdir)/test/cryptsuite.py
-
-!end
-!begin >empty.h
-/* Empty file touched by automake makefile to force rebuild of version.o */
-!end
-
-!begin vc vars
-CFLAGS = $(CFLAGS) /DHAS_GSSAPI
-!end
-
-!begin clangcl vars
-CFLAGS += /DHAS_GSSAPI
-!end
-
-# `make install' target for Unix.
-!begin gtk
-install:
- mkdir -p $(DESTDIR)$(bindir) $(DESTDIR)$(man1dir)
- $(INSTALL_PROGRAM) -m 755 pageant $(DESTDIR)$(bindir)/pageant
- $(INSTALL_PROGRAM) -m 755 plink $(DESTDIR)$(bindir)/plink
- $(INSTALL_PROGRAM) -m 755 pscp $(DESTDIR)$(bindir)/pscp
- $(INSTALL_PROGRAM) -m 755 psftp $(DESTDIR)$(bindir)/psftp
- $(INSTALL_PROGRAM) -m 755 pterm $(DESTDIR)$(bindir)/pterm
- if test -n "$(UTMP_GROUP)"; then \
- chgrp $(UTMP_GROUP) $(DESTDIR)$(bindir)/pterm && \
- chmod 2755 $(DESTDIR)$(bindir)/pterm; \
- elif test -n "$(UTMP_USER)"; then \
- chown $(UTMP_USER) $(DESTDIR)$(bindir)/pterm && \
- chmod 4755 $(DESTDIR)$(bindir)/pterm; \
- fi
- $(INSTALL_PROGRAM) -m 755 putty $(DESTDIR)$(bindir)/putty
- $(INSTALL_PROGRAM) -m 755 puttygen $(DESTDIR)$(bindir)/puttygen
- $(INSTALL_PROGRAM) -m 755 puttytel $(DESTDIR)$(bindir)/puttytel
- $(INSTALL_DATA) -m 644 ../doc/pageant.1 $(DESTDIR)$(man1dir)/pageant.1
- $(INSTALL_DATA) -m 644 ../doc/plink.1 $(DESTDIR)$(man1dir)/plink.1
- $(INSTALL_DATA) -m 644 ../doc/pscp.1 $(DESTDIR)$(man1dir)/pscp.1
- $(INSTALL_DATA) -m 644 ../doc/psftp.1 $(DESTDIR)$(man1dir)/psftp.1
- $(INSTALL_DATA) -m 644 ../doc/pterm.1 $(DESTDIR)$(man1dir)/pterm.1
- $(INSTALL_DATA) -m 644 ../doc/putty.1 $(DESTDIR)$(man1dir)/putty.1
- $(INSTALL_DATA) -m 644 ../doc/puttygen.1 $(DESTDIR)$(man1dir)/puttygen.1
- $(INSTALL_DATA) -m 644 ../doc/puttytel.1 $(DESTDIR)$(man1dir)/puttytel.1
-
-install-strip:
- $(MAKE) install INSTALL_PROGRAM="$(INSTALL_PROGRAM) -s"
-!end
-
-# List the man pages for the automake makefile.
-!begin am
-if HAVE_GTK
-man1_MANS = doc/plink.1 doc/pscp.1 doc/psftp.1 doc/puttygen.1 doc/psusan.1 \
- doc/pageant.1 doc/pterm.1 doc/putty.1 doc/puttytel.1
-else
-man1_MANS = doc/plink.1 doc/pscp.1 doc/psftp.1 doc/puttygen.1 doc/psusan.1
-endif
-!end
-
-# In automake, chgrp/chmod pterm after installation, if configured to.
-!begin am
-if HAVE_SETID_CMD
-install-exec-local:
- @SETID_CMD@ $(bindir)/pterm
- chmod @SETID_MODE@ $(bindir)/pterm
-endif
-!end
-
-# In automake makefile, build the OS X app bundle, if configured in
-# Quartz mode.
-!begin am
-if HAVE_QUARTZ
-noinst_SCRIPTS = unix/PuTTY.app unix/Pterm.app
-unix/PuTTY.app: unix/putty.bundle puttyapp osxlaunch
- rm -rf $@ && PUTTY_GTK_PREFIX_FROM_MAKEFILE=$$(pkg-config --variable=prefix gtk+-3.0) gtk-mac-bundler $<
-unix/Pterm.app: unix/pterm.bundle ptermapp osxlaunch
- rm -rf $@ && PUTTY_GTK_PREFIX_FROM_MAKEFILE=$$(pkg-config --variable=prefix gtk+-3.0) gtk-mac-bundler $<
-endif
-!end
-
-# Random symbols.
-!begin cygwin vars
-# _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
-!end
-
-# ------------------------------------------------------------
-# Definitions of object groups. A group name, followed by an =,
-# followed by any number of objects or other already-defined group
-# names. A line beginning `+' is assumed to continue the previous
-# line.
-
-# conf.c and its dependencies.
-CONF = conf marshal
-
-# Terminal emulator and its (platform-independent) dependencies.
-TERMINAL = terminal stripctrl wcwidth logging tree234 minibidi
- + config dialog CONF
-
-# GUI front end and terminal emulator (putty, puttytel).
-GUITERM = TERMINAL window windlg winctrls sizetip winprint winutils
- + wincfg winhelp winjump sessprep winselgui
-
-# Same thing on Unix.
-UXTERM = TERMINAL uxcfg uxucs uxprint timing callback miscucs
-GTKTERM = UXTERM gtkwin gtkcfg gtkdlg gtkfont gtkcols gtkmisc xkeysym
- + x11misc gtkcomm sessprep
-GTKMAIN = gtkmain cmdline
-
-# Non-SSH back ends (putty, puttytel, plink).
-NONSSH = telnet raw rlogin supdup ldisc pinger
-
-# SSH back end (putty, plink, pscp, psftp).
-ARITH = mpint ecc
-SSHCRYPTO = ARITH sshmd5 sshsha sshsh256 sshsh512 sshsha3 sshblake2 sshargon2
- + sshrsa sshdss sshecc
- + sshdes sshblowf sshaes sshccp ssharcf
- + sshdh sshcrc sshcrcda sshauxcrypt
- + sshhmac
-SSHCOMMON = sshcommon sshutils sshprng sshrand SSHCRYPTO
- + sshverstring
- + sshpubk sshzlib
- + sshmac marshal nullplug
- + sshgssc pgssapi wildcard ssh1censor ssh2censor ssh2bpp
- + ssh2transport ssh2transhk ssh2connection portfwd x11fwd
- + ssh1connection ssh1bpp ssh2bpp-bare
-SSH = SSHCOMMON ssh
- + ssh1login ssh2userauth
- + pinger
- + sshshare aqsync agentf
- + mainchan ssh2kex-client ssh2connection-client ssh1connection-client
-WINSSH = SSH winnoise wincapi winpgntc wingss winshare winnps winnpc
- + winhsock errsock
-UXSSH = SSH uxnoise uxagentc uxgss uxshare
-
-# SFTP implementation (pscp, psftp).
-SFTP = psftpcommon sftp sftpcommon logging cmdline
-
-# Components of the prime-generation system.
-SSHPRIME = sshprime smallprimes primecandidate millerrabin pockle mpunsafe
-
-# Miscellaneous objects appearing in all the utilities, or all the
-# network ones, or the Unix or Windows subsets of those in turn.
-MISC = misc utils marshal memory stripctrl wcwidth
-MISCNETCOMMON = timing callback MISC version tree234 CONF
-MISCNET = MISCNETCOMMON be_misc settings proxy
-WINMISC = MISCNET winstore winnet winhandl cmdline windefs winmisc winproxy
- + wintime winhsock errsock winsecur winucs miscucs winmiscs
-UXMISCCOMMON = MISCNETCOMMON uxstore uxsel uxpoll uxnet uxpeer uxmisc time
- + uxfdsock errsock
-UXMISC = MISCNET UXMISCCOMMON uxproxy uxutils
-
-# SSH server.
-SSHSERVER = SSHCOMMON sshserver settings be_none logging ssh2kex-server
- + ssh2userauth-server sshrsag SSHPRIME ssh2connection-server
- + sesschan sftpcommon sftpserver proxy cproxy ssh1login-server
- + ssh1connection-server scpserver
-
-# import.c and dependencies, for PuTTYgen-like utilities that have to
-# load foreign key files.
-IMPORT = import sshbcrypt sshblowf marshal
-
-# Character set library, for use in pterm.
-CHARSET = sbcsdat slookup sbcs utf8 toucs fromucs xenc mimeenc macenc localenc
-
-# Standard libraries.
-LIBS = advapi32.lib user32.lib gdi32.lib comdlg32.lib
- + shell32.lib imm32.lib ole32.lib
-
-# Network backend sets. This also brings in the relevant attachment
-# to proxy.c depending on whether we're crypto-avoidant or not.
-BE_ALL = be_all cproxy
-BE_NOSSH = be_nossh norand nocproxy
-BE_SSH = be_ssh cproxy
-BE_NONE = be_none nocproxy
-# More backend sets, with the additional Windows serial-port module.
-W_BE_ALL = be_all_s winser cproxy
-W_BE_NOSSH = be_nos_s norand winser nocproxy
-# And with the Unix serial-port module.
-U_BE_ALL = be_all_s uxser cproxy
-U_BE_NOSSH = be_nos_s norand uxser nocproxy
-
-# Auxiliary crypto modules used by key generators.
-KEYGEN = sshrsag sshdssg sshecdsag
-
-# ------------------------------------------------------------
-# Definitions of actual programs. The program name, followed by a
-# colon, followed by a list of objects. Also in the list may be the
-# keywords [G] for Windows GUI app, [C] for Console app, [X] for
-# X/GTK Unix app, [U] for command-line Unix app.
-
-putty : [G] GUITERM NONSSH WINSSH W_BE_ALL WINMISC winx11 putty.res LIBS
-puttytel : [G] GUITERM NONSSH W_BE_NOSSH WINMISC puttytel.res nogss LIBS
-plink : [C] winplink wincons console NONSSH WINSSH W_BE_ALL logging WINMISC
- + winx11 plink.res winnojmp sessprep noterm winnohlp winselcli
- + clicons wincliloop console LIBS
-pscp : [C] pscp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC
- + pscp.res winnojmp winnohlp winselcli clicons wincliloop
- + console LIBS
-psftp : [C] psftp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC
- + psftp.res winnojmp winnohlp winselcli clicons wincliloop
- + console LIBS
-
-pageant : [G] winpgnt pageant sshrsa sshpubk sshdes ARITH sshmd5 version
- + tree234 MISC sshaes sshsha winsecur winpgntc aqsync sshdss sshsh256
- + sshsh512 winutils sshecc winmisc winmiscs winhelp conf pageant.res
- + sshauxcrypt sshhmac wincapi winnps winnpc winhsock errsock winnet
- + winhandl callback be_misc winselgui winhandl sshsha3 sshblake2
- + sshargon2 LIBS
-
-puttygen : [G] winpgen KEYGEN SSHPRIME sshdes ARITH sshmd5 version
- + sshrand winnoise sshsha winstore MISC winctrls sshrsa sshdss winmisc
- + sshpubk sshaes sshsh256 sshsh512 IMPORT winutils puttygen.res
- + tree234 notiming winhelp winnojmp CONF LIBS wintime sshecc sshprng
- + sshauxcrypt sshhmac winsecur winmiscs sshsha3 sshblake2 sshargon2
-
-pterm : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore
- + uxsignal CHARSET cmdline uxpterm version time xpmpterm xpmptcfg
- + nogss utils memory GTKMAIN
-putty : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore
- + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11 xpmputty
- + xpmpucfg utils memory GTKMAIN
-puttytel : [X] GTKTERM uxmisc misc ldisc settings uxsel U_BE_NOSSH
- + uxstore uxsignal CHARSET uxputty NONSSH UXMISC xpmputty xpmpucfg
- + nogss utils memory GTKMAIN
-
-plink : [U] uxplink uxcons NONSSH UXSSH U_BE_ALL logging UXMISC uxsignal
- + ux_x11 noterm uxnogtk sessprep cmdline clicons uxcliloop console
-
-PUTTYGEN_UNIX = KEYGEN SSHPRIME sshdes ARITH sshmd5 version sshprng
- + sshrand uxnoise sshsha MISC sshrsa sshdss uxcons uxstore uxmisc
- + sshpubk sshaes sshsh256 sshsh512 IMPORT puttygen.res time tree234
- + uxgen notiming CONF sshecc sshsha3 uxnogtk sshauxcrypt sshhmac
- + uxpoll uxutils sshblake2 sshargon2 console
-puttygen : [U] cmdgen PUTTYGEN_UNIX
-cgtest : [UT] cgtest PUTTYGEN_UNIX
-
-pscp : [U] pscp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC uxnogtk
- + clicons uxcliloop console
-psftp : [U] psftp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC uxnogtk
- + clicons uxcliloop console
-
-pageant : [X] uxpgnt uxagentc aqsync pageant sshrsa sshpubk sshdes ARITH
- + sshmd5 version tree234 misc sshaes sshsha sshdss sshsh256 sshsh512
- + sshecc CONF uxsignal nocproxy nogss be_none x11fwd ux_x11 uxcons
- + gtkask gtkmisc nullplug logging UXMISC uxagentsock utils memory
- + sshauxcrypt sshhmac sshprng uxnoise uxcliloop sshsha3 sshblake2
- + sshargon2 console
-
-ptermapp : [XT] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore
- + uxsignal CHARSET uxpterm version time xpmpterm xpmptcfg
- + nogss gtkapp nocmdline utils memory
-puttyapp : [XT] GTKTERM uxmisc misc ldisc settings uxsel U_BE_ALL uxstore
- + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11 xpmputty
- + xpmpucfg gtkapp nocmdline utils memory
-osxlaunch : [UT] osxlaunch
-
-fuzzterm : [UT] UXTERM CHARSET MISC version uxmisc uxucs fuzzterm time settings
- + uxstore be_none uxnogtk memory
-testcrypt : [UT] testcrypt SSHCRYPTO sshprng SSHPRIME sshpubk marshal utils
- + memory tree234 uxutils KEYGEN
-testcrypt : [C] testcrypt SSHCRYPTO sshprng SSHPRIME sshpubk marshal utils
- + memory tree234 winmiscs KEYGEN
-testsc : [UT] testsc SSHCRYPTO marshal utils memory tree234 wildcard
- + sshmac uxutils sshpubk
-testzlib : [UT] testzlib sshzlib utils marshal memory
-
-uppity : [UT] uxserver SSHSERVER UXMISC uxsignal uxnoise uxgss uxnogtk
- + uxpty uxsftpserver ux_x11 uxagentsock procnet uxcliloop
-psusan : [U] uxpsusan SSHSERVER UXMISC uxsignal uxnoise nogss uxnogtk
- + uxpty uxsftpserver ux_x11 uxagentsock procnet uxcliloop
-
-PSOCKS = psocks portfwd conf sshutils logging proxy nocproxy timing callback
- + time tree234 version errsock be_misc norand MISC
-psocks : [C] PSOCKS winsocks wincons winproxy winnet winmisc winselcli
- + winhsock winhandl winmiscs winnohlp wincliloop console LIBS
-psocks : [UT] PSOCKS uxsocks uxcons uxproxy uxnet uxmisc uxpoll uxsel uxnogtk
- + uxpeer uxfdsock uxcliloop uxsignal console
-
-# ----------------------------------------------------------------------
-# On Windows, provide a means of removing local test binaries that we
-# aren't going to actually ship. (I prefer this to not building them
-# in the first place, so that we find out about build breakage early.)
-!begin vc
-cleantestprogs:
- -del $(BUILDDIR)testcrypt.exe $(BUILDDIR)psocks.exe
-!end
-!begin clangcl
-cleantestprogs:
- -rm -f $(BUILDDIR)testcrypt.exe $(BUILDDIR)psocks.exe
-!end
diff --git a/SETTINGS.C b/SETTINGS.C
index 32a53c54..cd286eb4 100644
--- a/SETTINGS.C
+++ b/SETTINGS.C
@@ -8,8 +8,8 @@
#include "putty.h"
#include "storage.h"
#ifndef NO_GSSAPI
-#include "sshgssc.h"
-#include "sshgss.h"
+#include "ssh/gssc.h"
+#include "ssh/gss.h"
#endif
@@ -17,6 +17,7 @@
static const struct keyvalwhere ciphernames[] = {
{ "aes", CIPHER_AES, -1, -1 },
{ "chacha20", CIPHER_CHACHA20, CIPHER_AES, +1 },
+ { "aesgcm", CIPHER_AESGCM, CIPHER_CHACHA20, +1 },
{ "3des", CIPHER_3DES, -1, -1 },
{ "WARN", CIPHER_WARN, -1, -1 },
{ "des", CIPHER_DES, -1, -1 },
@@ -28,12 +29,24 @@ static const struct keyvalwhere ciphernames[] = {
* compatibility warts in load_open_settings(), and should be kept
* in sync with those. */
static const struct keyvalwhere kexnames[] = {
+ { "ntru-curve25519", KEX_NTRU_HYBRID, -1, +1 },
{ "ecdh", KEX_ECDH, -1, +1 },
/* This name is misleading: it covers both SHA-256 and SHA-1 variants */
{ "dh-gex-sha1", KEX_DHGEX, -1, -1 },
+ /* Again, this covers both SHA-256 and SHA-1, despite the name: */
{ "dh-group14-sha1", KEX_DHGROUP14, -1, -1 },
+ /* This one really is only SHA-1, though: */
{ "dh-group1-sha1", KEX_DHGROUP1, KEX_WARN, +1 },
{ "rsa", KEX_RSA, KEX_WARN, -1 },
+ /* Larger fixed DH groups: prefer the larger 15 and 16 over 14,
+ * but by default the even larger 17 and 18 go below 16.
+ * Rationale: diminishing returns of improving the DH strength are
+ * outweighed by increased CPU cost. Group 18 is painful on a slow
+ * machine. Users can override if they need to. */
+ { "dh-group15-sha512", KEX_DHGROUP15, KEX_DHGROUP14, -1 },
+ { "dh-group16-sha512", KEX_DHGROUP16, KEX_DHGROUP15, -1 },
+ { "dh-group17-sha512", KEX_DHGROUP17, KEX_DHGROUP16, +1 },
+ { "dh-group18-sha512", KEX_DHGROUP18, KEX_DHGROUP17, +1 },
{ "WARN", KEX_WARN, -1, -1 }
};
@@ -49,9 +62,9 @@ static const struct keyvalwhere hknames[] = {
/*
* 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).
+ * This is currently precisely the same as the set in
+ * ssh/ttymode-list.h, but could in principle differ if other backends
+ * started to support tty modes (e.g., the pty backend).
* The set of modes in in this array is currently significant for
* settings migration from old versions; if they change, review the
* gppmap() invocation for "TerminalModes".
@@ -624,12 +637,15 @@ void save_open_settings(settings_w *sesskey, Conf *conf)
write_setting_s(sesskey, "LogHost", conf_get_str(conf, CONF_loghost));
write_setting_b(sesskey, "SSH2DES", conf_get_bool(conf, CONF_ssh2_des_cbc));
write_setting_filename(sesskey, "PublicKeyFile", conf_get_filename(conf, CONF_keyfile));
+ write_setting_filename(sesskey, "DetachedCertificate", conf_get_filename(conf, CONF_detached_cert));
+ write_setting_s(sesskey, "AuthPlugin", conf_get_str(conf, CONF_auth_plugin));
write_setting_s(sesskey, "RemoteCommand", conf_get_str(conf, CONF_remote_cmd));
write_setting_b(sesskey, "RFCEnviron", conf_get_bool(conf, CONF_rfc_environ));
write_setting_b(sesskey, "PassiveTelnet", conf_get_bool(conf, CONF_passive_telnet));
write_setting_b(sesskey, "BackspaceIsDelete", conf_get_bool(conf, CONF_bksp_is_delete));
write_setting_b(sesskey, "RXVTHomeEnd", conf_get_bool(conf, CONF_rxvt_homeend));
write_setting_i(sesskey, "LinuxFunctionKeys", conf_get_int(conf, CONF_funky_type));
+ write_setting_i(sesskey, "ShiftedArrowKeys", conf_get_int(conf, CONF_sharrow_type));
write_setting_b(sesskey, "NoApplicationKeys", conf_get_bool(conf, CONF_no_applic_k));
write_setting_b(sesskey, "NoApplicationCursors", conf_get_bool(conf, CONF_no_applic_c));
write_setting_b(sesskey, "NoMouseReporting", conf_get_bool(conf, CONF_no_mouse_rep));
@@ -769,6 +785,8 @@ void save_open_settings(settings_w *sesskey, Conf *conf)
write_setting_i(sesskey, "BugOldGex2", 2-conf_get_int(conf, CONF_sshbug_oldgex2));
write_setting_i(sesskey, "BugWinadj", 2-conf_get_int(conf, CONF_sshbug_winadj));
write_setting_i(sesskey, "BugChanReq", 2-conf_get_int(conf, CONF_sshbug_chanreq));
+ write_setting_i(sesskey, "BugDropStart", 2-conf_get_int(conf, CONF_sshbug_dropstart));
+ write_setting_i(sesskey, "BugFilterKexinit", 2-conf_get_int(conf, CONF_sshbug_filter_kexinit));
write_setting_b(sesskey, "StampUtmp", conf_get_bool(conf, CONF_stamp_utmp));
write_setting_b(sesskey, "LoginShell", conf_get_bool(conf, CONF_login_shell));
write_setting_b(sesskey, "ScrollbarOnLeft", conf_get_bool(conf, CONF_scrollbar_on_left));
@@ -966,9 +984,9 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
* 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,"
+ *normal_default = "ecdh,dh-gex-sha1,dh-group18-sha512,dh-group17-sha512,dh-group16-sha512,dh-group15-sha512,dh-group14-sha1,rsa,"
"WARN,dh-group1-sha1",
- *bugdhgex2_default = "ecdh,dh-group14-sha1,rsa,"
+ *bugdhgex2_default = "ecdh,dh-group18-sha512,dh-group17-sha512,dh-group16-sha512,dh-group15-sha512,dh-group14-sha1,rsa,"
"WARN,dh-group1-sha1,dh-gex-sha1";
char *raw;
i = 2 - gppi_raw(sesskey, "BugDHGEx2", 0);
@@ -1039,12 +1057,16 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
#endif
gppb(sesskey, "SshNoShell", false, conf, CONF_ssh_no_shell);
gppfile(sesskey, "PublicKeyFile", conf, CONF_keyfile);
+ gppfile(sesskey, "DetachedCertificate", conf, CONF_detached_cert);
+ gpps(sesskey, "AuthPlugin", "", conf, CONF_auth_plugin);
gpps(sesskey, "RemoteCommand", "", conf, CONF_remote_cmd);
gppb(sesskey, "RFCEnviron", false, conf, CONF_rfc_environ);
gppb(sesskey, "PassiveTelnet", false, conf, CONF_passive_telnet);
gppb(sesskey, "BackspaceIsDelete", true, conf, CONF_bksp_is_delete);
gppb(sesskey, "RXVTHomeEnd", false, conf, CONF_rxvt_homeend);
gppi(sesskey, "LinuxFunctionKeys", 0, conf, CONF_funky_type);
+ gppi(sesskey, "ShiftedArrowKeys", SHARROW_APPLICATION, conf,
+ CONF_sharrow_type);
gppb(sesskey, "NoApplicationKeys", false, conf, CONF_no_applic_k);
gppb(sesskey, "NoApplicationCursors", false, conf, CONF_no_applic_c);
gppb(sesskey, "NoMouseReporting", false, conf, CONF_no_mouse_rep);
@@ -1244,6 +1266,8 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
i = gppi_raw(sesskey, "BugOldGex2", 0); conf_set_int(conf, CONF_sshbug_oldgex2, 2-i);
i = gppi_raw(sesskey, "BugWinadj", 0); conf_set_int(conf, CONF_sshbug_winadj, 2-i);
i = gppi_raw(sesskey, "BugChanReq", 0); conf_set_int(conf, CONF_sshbug_chanreq, 2-i);
+ i = gppi_raw(sesskey, "BugDropStart", 1); conf_set_int(conf, CONF_sshbug_dropstart, 2-i);
+ i = gppi_raw(sesskey, "BugFilterKexinit", 1); conf_set_int(conf, CONF_sshbug_filter_kexinit, 2-i);
conf_set_bool(conf, CONF_ssh_simple, false);
gppb(sesskey, "StampUtmp", true, conf, CONF_stamp_utmp);
gppb(sesskey, "LoginShell", true, conf, CONF_login_shell);
@@ -1302,6 +1326,8 @@ static int sessioncmp(const void *av, const void *bv)
return strcmp(a, b); /* otherwise, compare normally */
}
+bool sesslist_demo_mode = false;
+
void get_sesslist(struct sesslist *list, bool allocate)
{
int i;
@@ -1311,12 +1337,18 @@ void get_sesslist(struct sesslist *list, bool allocate)
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);
+ if (sesslist_demo_mode) {
+ put_asciz(sb, "demo-server");
+ put_asciz(sb, "demo-server-2");
+ } else {
+ if ((handle = enum_settings_start()) != NULL) {
+ while (enum_settings_next(handle, sb))
+ put_byte(sb, '\0');
+ enum_settings_finish(handle);
+ }
+ put_byte(sb, '\0');
}
- put_byte(sb, '\0');
+
list->buffer = strbuf_to_str(sb);
/*
diff --git a/SFTP.C b/SFTP.C
deleted file mode 100644
index a76702f8..00000000
--- a/SFTP.C
+++ /dev/null
@@ -1,1205 +0,0 @@
-/*
- * sftp.c: SFTP generic client code.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <limits.h>
-
-#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/SFTP.H b/SFTP.H
deleted file mode 100644
index 5835c16b..00000000
--- a/SFTP.H
+++ /dev/null
@@ -1,544 +0,0 @@
-/*
- * sftp.h: definitions for SFTP and the sftp.c routines.
- */
-
-#include "defs.h"
-
-#define SSH_FXP_INIT 1 /* 0x1 */
-#define SSH_FXP_VERSION 2 /* 0x2 */
-#define SSH_FXP_OPEN 3 /* 0x3 */
-#define SSH_FXP_CLOSE 4 /* 0x4 */
-#define SSH_FXP_READ 5 /* 0x5 */
-#define SSH_FXP_WRITE 6 /* 0x6 */
-#define SSH_FXP_LSTAT 7 /* 0x7 */
-#define SSH_FXP_FSTAT 8 /* 0x8 */
-#define SSH_FXP_SETSTAT 9 /* 0x9 */
-#define SSH_FXP_FSETSTAT 10 /* 0xa */
-#define SSH_FXP_OPENDIR 11 /* 0xb */
-#define SSH_FXP_READDIR 12 /* 0xc */
-#define SSH_FXP_REMOVE 13 /* 0xd */
-#define SSH_FXP_MKDIR 14 /* 0xe */
-#define SSH_FXP_RMDIR 15 /* 0xf */
-#define SSH_FXP_REALPATH 16 /* 0x10 */
-#define SSH_FXP_STAT 17 /* 0x11 */
-#define SSH_FXP_RENAME 18 /* 0x12 */
-#define SSH_FXP_STATUS 101 /* 0x65 */
-#define SSH_FXP_HANDLE 102 /* 0x66 */
-#define SSH_FXP_DATA 103 /* 0x67 */
-#define SSH_FXP_NAME 104 /* 0x68 */
-#define SSH_FXP_ATTRS 105 /* 0x69 */
-#define SSH_FXP_EXTENDED 200 /* 0xc8 */
-#define SSH_FXP_EXTENDED_REPLY 201 /* 0xc9 */
-
-#define SSH_FX_OK 0
-#define SSH_FX_EOF 1
-#define SSH_FX_NO_SUCH_FILE 2
-#define SSH_FX_PERMISSION_DENIED 3
-#define SSH_FX_FAILURE 4
-#define SSH_FX_BAD_MESSAGE 5
-#define SSH_FX_NO_CONNECTION 6
-#define SSH_FX_CONNECTION_LOST 7
-#define SSH_FX_OP_UNSUPPORTED 8
-
-#define SSH_FILEXFER_ATTR_SIZE 0x00000001
-#define SSH_FILEXFER_ATTR_UIDGID 0x00000002
-#define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004
-#define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008
-#define SSH_FILEXFER_ATTR_EXTENDED 0x80000000
-
-#define SSH_FXF_READ 0x00000001
-#define SSH_FXF_WRITE 0x00000002
-#define SSH_FXF_APPEND 0x00000004
-#define SSH_FXF_CREAT 0x00000008
-#define SSH_FXF_TRUNC 0x00000010
-#define SSH_FXF_EXCL 0x00000020
-
-#define SFTP_PROTO_VERSION 3
-
-#define PERMS_DIRECTORY 040000
-
-/*
- * External references. The sftp client module sftp.c expects to be
- * able to get at these functions.
- *
- * sftp_recvdata must never return less than len. It either blocks
- * until len is available and then returns true, or it returns false
- * for failure.
- *
- * sftp_senddata returns true on success, false on failure.
- *
- * sftp_sendbuffer returns the size of the backlog of data in the
- * transmit queue.
- */
-bool sftp_senddata(const char *data, size_t len);
-size_t sftp_sendbuffer(void);
-bool sftp_recvdata(char *data, size_t len);
-
-/*
- * Free sftp_requests
- */
-void sftp_cleanup_request(void);
-
-struct fxp_attrs {
- unsigned long flags;
- uint64_t size;
- unsigned long uid;
- unsigned long gid;
- unsigned long permissions;
- unsigned long atime;
- unsigned long mtime;
-};
-extern const struct fxp_attrs no_attrs;
-
-/*
- * Copy between the possibly-unused permissions field in an fxp_attrs
- * and a possibly-negative integer containing the same permissions.
- */
-#define PUT_PERMISSIONS(attrs, perms) \
- ((perms) >= 0 ? \
- ((attrs).flags |= SSH_FILEXFER_ATTR_PERMISSIONS, \
- (attrs).permissions = (perms)) : \
- ((attrs).flags &= ~SSH_FILEXFER_ATTR_PERMISSIONS))
-#define GET_PERMISSIONS(attrs, defaultperms) \
- ((attrs).flags & SSH_FILEXFER_ATTR_PERMISSIONS ? \
- (attrs).permissions : defaultperms)
-
-struct fxp_handle {
- char *hstring;
- int hlen;
-};
-
-struct fxp_name {
- char *filename, *longname;
- struct fxp_attrs attrs;
-};
-
-struct fxp_names {
- int nnames;
- struct fxp_name *names;
-};
-
-struct sftp_request;
-
-/*
- * Packet-manipulation functions.
- */
-
-struct sftp_packet {
- char *data;
- size_t length, maxlen, savedpos;
- int type;
- BinarySink_IMPLEMENTATION;
- BinarySource_IMPLEMENTATION;
-};
-
-/* When sending a packet, create it with sftp_pkt_init, then add
- * things to it by treating it as a BinarySink. When it's done, call
- * sftp_send_prepare, and then pkt->data and pkt->length describe its
- * wire format. */
-struct sftp_packet *sftp_pkt_init(int pkt_type);
-void sftp_send_prepare(struct sftp_packet *pkt);
-
-/* When receiving a packet, create it with sftp_recv_prepare once you
- * decode its length from the first 4 bytes of wire data. Then write
- * that many bytes into pkt->data, and call sftp_recv_finish to set up
- * the type code and BinarySource. */
-struct sftp_packet *sftp_recv_prepare(unsigned length);
-bool sftp_recv_finish(struct sftp_packet *pkt);
-
-/* Either kind of packet can be freed afterwards with sftp_pkt_free. */
-void sftp_pkt_free(struct sftp_packet *pkt);
-
-void BinarySink_put_fxp_attrs(BinarySink *bs, struct fxp_attrs attrs);
-bool BinarySource_get_fxp_attrs(BinarySource *src, struct fxp_attrs *attrs);
-#define put_fxp_attrs(bs, attrs) \
- BinarySink_put_fxp_attrs(BinarySink_UPCAST(bs), attrs)
-#define get_fxp_attrs(bs, attrs) \
- BinarySource_get_fxp_attrs(BinarySource_UPCAST(bs), attrs)
-
-/*
- * Error handling.
- */
-
-const char *fxp_error(void);
-int fxp_error_type(void);
-
-/*
- * Perform exchange of init/version packets. Return false on failure.
- */
-bool fxp_init(void);
-
-/*
- * Canonify a pathname. Concatenate the two given path elements
- * with a separating slash, unless the second is NULL.
- */
-struct sftp_request *fxp_realpath_send(const char *path);
-char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req);
-
-/*
- * Open a file. 'attrs' contains attributes to be applied to the file
- * if it's being created.
- */
-struct sftp_request *fxp_open_send(const char *path, int type,
- const struct fxp_attrs *attrs);
-struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin,
- struct sftp_request *req);
-
-/*
- * Open a directory.
- */
-struct sftp_request *fxp_opendir_send(const char *path);
-struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin,
- struct sftp_request *req);
-
-/*
- * Close a file/dir. Returns true on success, false on error.
- */
-struct sftp_request *fxp_close_send(struct fxp_handle *handle);
-bool fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req);
-
-/*
- * Make a directory.
- */
-struct sftp_request *fxp_mkdir_send(const char *path,
- const struct fxp_attrs *attrs);
-bool fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req);
-
-/*
- * Remove a directory.
- */
-struct sftp_request *fxp_rmdir_send(const char *path);
-bool fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req);
-
-/*
- * Remove a file.
- */
-struct sftp_request *fxp_remove_send(const char *fname);
-bool fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req);
-
-/*
- * Rename a file.
- */
-struct sftp_request *fxp_rename_send(const char *srcfname,
- const char *dstfname);
-bool fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req);
-
-/*
- * Return file attributes.
- */
-struct sftp_request *fxp_stat_send(const char *fname);
-bool fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req,
- struct fxp_attrs *attrs);
-struct sftp_request *fxp_fstat_send(struct fxp_handle *handle);
-bool fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req,
- struct fxp_attrs *attrs);
-
-/*
- * Set file attributes.
- */
-struct sftp_request *fxp_setstat_send(const char *fname,
- struct fxp_attrs attrs);
-bool fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *req);
-struct sftp_request *fxp_fsetstat_send(struct fxp_handle *handle,
- struct fxp_attrs attrs);
-bool fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *req);
-
-/*
- * Read from a file.
- */
-struct sftp_request *fxp_read_send(struct fxp_handle *handle,
- uint64_t offset, int len);
-int fxp_read_recv(struct sftp_packet *pktin, struct sftp_request *req,
- char *buffer, int len);
-
-/*
- * Write to a file.
- */
-struct sftp_request *fxp_write_send(struct fxp_handle *handle,
- void *buffer, uint64_t offset, int len);
-bool fxp_write_recv(struct sftp_packet *pktin, struct sftp_request *req);
-
-/*
- * Read from a directory.
- */
-struct sftp_request *fxp_readdir_send(struct fxp_handle *handle);
-struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin,
- struct sftp_request *req);
-
-/*
- * Free up an fxp_names structure.
- */
-void fxp_free_names(struct fxp_names *names);
-
-/*
- * Duplicate and free fxp_name structures.
- */
-struct fxp_name *fxp_dup_name(struct fxp_name *name);
-void fxp_free_name(struct fxp_name *name);
-
-/*
- * Store user data in an sftp_request structure.
- */
-void *fxp_get_userdata(struct sftp_request *req);
-void fxp_set_userdata(struct sftp_request *req, void *data);
-
-/*
- * These functions might well be temporary placeholders to be
- * replaced with more useful similar functions later. They form the
- * main dispatch loop for processing incoming SFTP responses.
- */
-void sftp_register(struct sftp_request *req);
-struct sftp_request *sftp_find_request(struct sftp_packet *pktin);
-struct sftp_packet *sftp_recv(void);
-
-/*
- * A wrapper to go round fxp_read_* and fxp_write_*, which manages
- * the queueing of multiple read/write requests.
- */
-
-struct fxp_xfer;
-
-struct fxp_xfer *xfer_download_init(struct fxp_handle *fh, uint64_t offset);
-void xfer_download_queue(struct fxp_xfer *xfer);
-int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin);
-bool xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len);
-
-struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64_t offset);
-bool xfer_upload_ready(struct fxp_xfer *xfer);
-void xfer_upload_data(struct fxp_xfer *xfer, char *buffer, int len);
-int xfer_upload_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin);
-
-bool xfer_done(struct fxp_xfer *xfer);
-void xfer_set_error(struct fxp_xfer *xfer);
-void xfer_cleanup(struct fxp_xfer *xfer);
-
-/*
- * Vtable for the platform-specific filesystem implementation that
- * answers requests in an SFTP server.
- */
-typedef struct SftpReplyBuilder SftpReplyBuilder;
-struct SftpServer {
- const SftpServerVtable *vt;
-};
-struct SftpServerVtable {
- SftpServer *(*new)(const SftpServerVtable *vt);
- void (*free)(SftpServer *srv);
-
- /*
- * Handle actual filesystem requests.
- *
- * Each of these functions replies by calling an 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/SIGN.SH b/SIGN.SH
index f3300322..b40c2d47 100644
--- a/SIGN.SH
+++ b/SIGN.SH
@@ -9,14 +9,14 @@
set -e
-keyname=38BA7229B7588FD1
+keyname=B43979F89F446CFD
preliminary=false
while :; do
case "$1" in
-r)
shift
- keyname=6289A25F4AE8DA82
+ keyname=E4F83EA2AA4915EC
;;
-p)
shift
diff --git a/SSH.C b/SSH.C
deleted file mode 100644
index 91e7e869..00000000
--- a/SSH.C
+++ /dev/null
@@ -1,1250 +0,0 @@
-/*
- * SSH backend.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <assert.h>
-#include <limits.h>
-#include <signal.h>
-
-#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/SSH.H b/SSH.H
index 49ab2796..5ecef0cb 100644
--- a/SSH.H
+++ b/SSH.H
@@ -299,9 +299,15 @@ struct ConnectionLayerVtable {
* subsequent channel-opens). */
void (*enable_x_fwd)(ConnectionLayer *cl);
- /* Communicate to the connection layer whether the main session
- * channel currently wants user input. */
+ /* Communicate / query whether the main session channel currently
+ * wants user input. The set function is called by mainchan; the
+ * query function is called by the top-level ssh.c. */
void (*set_wants_user_input)(ConnectionLayer *cl, bool wanted);
+ bool (*get_wants_user_input)(ConnectionLayer *cl);
+
+ /* Notify the connection layer that more data has been added to
+ * the user input queue. */
+ void (*got_user_input)(ConnectionLayer *cl);
};
struct ConnectionLayer {
@@ -371,6 +377,10 @@ static inline void ssh_enable_x_fwd(ConnectionLayer *cl)
{ cl->vt->enable_x_fwd(cl); }
static inline void ssh_set_wants_user_input(ConnectionLayer *cl, bool wanted)
{ cl->vt->set_wants_user_input(cl, wanted); }
+static inline bool ssh_get_wants_user_input(ConnectionLayer *cl)
+{ return cl->vt->get_wants_user_input(cl); }
+static inline void ssh_got_user_input(ConnectionLayer *cl)
+{ cl->vt->got_user_input(cl); }
/* Exports from portfwd.c */
PortFwdManager *portfwdmgr_new(ConnectionLayer *cl);
@@ -397,11 +407,13 @@ LogContext *ssh_get_logctx(Ssh *ssh);
void ssh_throttle_conn(Ssh *ssh, int adjust);
void ssh_got_exitcode(Ssh *ssh, int status);
void ssh_ldisc_update(Ssh *ssh);
+void ssh_check_sendok(Ssh *ssh);
void ssh_got_fallback_cmd(Ssh *ssh);
bool ssh_is_bare(Ssh *ssh);
/* Communications back to ssh.c from the BPP */
void ssh_conn_processed_data(Ssh *ssh);
+void ssh_sendbuffer_changed(Ssh *ssh);
void ssh_check_frozen(Ssh *ssh);
/* Functions to abort the connection, for various reasons. */
@@ -411,6 +423,7 @@ void ssh_proto_error(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3);
void ssh_sw_abort(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3);
void ssh_sw_abort_deferred(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3);
void ssh_user_close(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3);
+void ssh_spr_close(Ssh *ssh, SeatPromptResult spr, const char *context);
/* Bit positions in the SSH-1 cipher protocol word */
#define SSH1_CIPHER_IDEA 1
@@ -446,7 +459,7 @@ struct RSAKey {
ssh_key sshk;
};
-struct dss_key {
+struct dsa_key {
mp_int *p, *q, *g, *y, *x;
ssh_key sshk;
};
@@ -502,7 +515,7 @@ struct ec_curve {
};
const ssh_keyalg *ec_alg_by_oid(int len, const void *oid,
- const struct ec_curve **curve);
+ 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;
@@ -529,22 +542,34 @@ struct eddsa_key {
WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg);
EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg);
+typedef enum KeyComponentType {
+ KCT_TEXT, KCT_BINARY, KCT_MPINT
+} KeyComponentType;
+typedef struct key_component {
+ char *name;
+ KeyComponentType type;
+ union {
+ strbuf *str; /* used for KCT_TEXT and KCT_BINARY */
+ mp_int *mp; /* used for KCT_MPINT */
+ };
+} key_component;
typedef struct key_components {
size_t ncomponents, componentsize;
- struct {
- char *name;
- bool is_mp_int;
- union {
- char *text;
- mp_int *mp;
- };
- } *components;
+ key_component *components;
} key_components;
key_components *key_components_new(void);
void key_components_add_text(key_components *kc,
const char *name, const char *value);
+void key_components_add_text_pl(key_components *kc,
+ const char *name, ptrlen value);
+void key_components_add_binary(key_components *kc,
+ const char *name, ptrlen value);
void key_components_add_mp(key_components *kc,
const char *name, mp_int *value);
+void key_components_add_uint(key_components *kc,
+ const char *name, uintmax_t value);
+void key_components_add_copy(key_components *kc,
+ const char *name, const key_component *value);
void key_components_free(key_components *kc);
/*
@@ -572,6 +597,7 @@ bool rsa_verify(RSAKey *key);
void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key, RsaSsh1Order order);
int rsa_ssh1_public_blob_len(ptrlen data);
void rsa_ssh1_private_blob_agent(BinarySink *bs, RSAKey *key);
+void duprsakey(RSAKey *dst, const RSAKey *src);
void freersapriv(RSAKey *key);
void freersakey(RSAKey *key);
key_components *rsa_components(RSAKey *key);
@@ -603,20 +629,11 @@ 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);
+mp_int *dsa_gen_k(const char *id_string,
+ mp_int *modulus, mp_int *private_key,
+ unsigned char *digest, int digest_len);
struct ssh_cipher {
const ssh_cipheralg *vt;
@@ -634,6 +651,9 @@ struct ssh_cipheralg {
unsigned long seq);
void (*decrypt_length)(ssh_cipher *, void *blk, int len,
unsigned long seq);
+ /* For ciphers that update their state per logical message
+ * (typically, per unit independently MACed) */
+ void (*next_message)(ssh_cipher *);
const char *ssh2_id;
int blksize;
/* real_keybits is the number of bits of entropy genuinely used by
@@ -678,9 +698,13 @@ static inline void ssh_cipher_encrypt_length(
static inline void ssh_cipher_decrypt_length(
ssh_cipher *c, void *blk, int len, unsigned long seq)
{ c->vt->decrypt_length(c, blk, len, seq); }
+static inline void ssh_cipher_next_message(ssh_cipher *c)
+{ c->vt->next_message(c); }
static inline const struct ssh_cipheralg *ssh_cipher_alg(ssh_cipher *c)
{ return c->vt; }
+void nullcipher_next_message(ssh_cipher *);
+
struct ssh2_ciphers {
int nciphers;
const ssh_cipheralg *const *list;
@@ -698,6 +722,7 @@ struct ssh2_macalg {
void (*setkey)(ssh2_mac *, ptrlen key);
void (*start)(ssh2_mac *);
void (*genresult)(ssh2_mac *, unsigned char *);
+ void (*next_message)(ssh2_mac *);
const char *(*text_name)(ssh2_mac *);
const char *name, *etm_name;
int len, keylen;
@@ -717,18 +742,22 @@ static inline void ssh2_mac_start(ssh2_mac *m)
{ m->vt->start(m); }
static inline void ssh2_mac_genresult(ssh2_mac *m, unsigned char *out)
{ m->vt->genresult(m, out); }
+static inline void ssh2_mac_next_message(ssh2_mac *m)
+{ m->vt->next_message(m); }
static inline const char *ssh2_mac_text_name(ssh2_mac *m)
{ return m->vt->text_name(m); }
static inline const ssh2_macalg *ssh2_mac_alg(ssh2_mac *m)
{ return m->vt; }
-/* Centralised 'methods' for ssh2_mac, defined in sshmac.c. These run
+/* Centralised 'methods' for ssh2_mac, defined in mac.c. These run
* the MAC in a specifically SSH-2 style, i.e. taking account of a
* packet sequence number as well as the data to be authenticated. */
bool ssh2_mac_verresult(ssh2_mac *, const void *);
void ssh2_mac_generate(ssh2_mac *, void *, int, unsigned long seq);
bool ssh2_mac_verify(ssh2_mac *, const void *, int, unsigned long seq);
+void nullmac_next_message(ssh2_mac *m);
+
/* Use a MAC in its raw form, outside SSH-2 context, to MAC a given
* string with a given key in the most obvious way. */
void mac_simple(const ssh2_macalg *alg, ptrlen key, ptrlen data, void *output);
@@ -791,11 +820,20 @@ 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;
+ enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH,
+ KEXTYPE_GSS, KEXTYPE_GSS_ECDH } main_type;
const ssh_hashalg *hash;
+ union { /* publicly visible data for each type */
+ const ecdh_keyalg *ecdh_vt; /* for KEXTYPE_ECDH, KEXTYPE_GSS_ECDH */
+ };
const void *extra; /* private to the kex methods */
};
+static inline bool kex_is_gss(const struct ssh_kex *kex)
+{
+ return kex->main_type == KEXTYPE_GSS || kex->main_type == KEXTYPE_GSS_ECDH;
+}
+
struct ssh_kexes {
int nkexes;
const ssh_kex *const *list;
@@ -822,17 +860,34 @@ struct ssh_keyalg {
void (*public_blob)(ssh_key *key, BinarySink *);
void (*private_blob)(ssh_key *key, BinarySink *);
void (*openssh_blob) (ssh_key *key, BinarySink *);
+ bool (*has_private) (ssh_key *key);
char *(*cache_str) (ssh_key *key);
key_components *(*components) (ssh_key *key);
+ ssh_key *(*base_key) (ssh_key *key); /* does not confer ownership */
+ /* The following methods can be NULL if !is_certificate */
+ void (*ca_public_blob)(ssh_key *key, BinarySink *);
+ bool (*check_cert)(ssh_key *key, bool host, ptrlen principal,
+ uint64_t time, const ca_options *opts,
+ BinarySink *error);
+ void (*cert_id_string)(ssh_key *key, BinarySink *);
+ SeatDialogText *(*cert_info)(ssh_key *key);
/* 'Class methods' that don't deal with an ssh_key at all */
int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob);
+ unsigned (*supported_flags) (const ssh_keyalg *self);
+ const char *(*alternate_ssh_id) (const ssh_keyalg *self, unsigned flags);
+ char *(*alg_desc)(const ssh_keyalg *self);
+ bool (*variable_size)(const ssh_keyalg *self);
+ /* The following methods can be NULL if !is_certificate */
+ const ssh_keyalg *(*related_alg)(const ssh_keyalg *self,
+ const ssh_keyalg *base);
/* Constant data fields giving information about the key type */
const char *ssh_id; /* string identifier in the SSH protocol */
const char *cache_id; /* identifier used in PuTTY's host key cache */
const void *extra; /* private to the public key methods */
- const unsigned supported_flags; /* signature-type flags we understand */
+ bool is_certificate; /* is this a certified key type? */
+ const ssh_keyalg *base_alg; /* if so, for what underlying key alg? */
};
static inline ssh_key *ssh_key_new_pub(const ssh_keyalg *self, ptrlen pub)
@@ -858,10 +913,24 @@ static inline void ssh_key_private_blob(ssh_key *key, BinarySink *bs)
{ key->vt->private_blob(key, bs); }
static inline void ssh_key_openssh_blob(ssh_key *key, BinarySink *bs)
{ key->vt->openssh_blob(key, bs); }
+static inline bool ssh_key_has_private(ssh_key *key)
+{ return key->vt->has_private(key); }
static inline char *ssh_key_cache_str(ssh_key *key)
{ return key->vt->cache_str(key); }
static inline key_components *ssh_key_components(ssh_key *key)
{ return key->vt->components(key); }
+static inline ssh_key *ssh_key_base_key(ssh_key *key)
+{ return key->vt->base_key(key); }
+static inline void ssh_key_ca_public_blob(ssh_key *key, BinarySink *bs)
+{ key->vt->ca_public_blob(key, bs); }
+static inline void ssh_key_cert_id_string(ssh_key *key, BinarySink *bs)
+{ key->vt->cert_id_string(key, bs); }
+static inline SeatDialogText *ssh_key_cert_info(ssh_key *key)
+{ return key->vt->cert_info(key); }
+static inline bool ssh_key_check_cert(
+ ssh_key *key, bool host, ptrlen principal, uint64_t time,
+ const ca_options *opts, BinarySink *error)
+{ return key->vt->check_cert(key, host, principal, time, opts, error); }
static inline int ssh_key_public_bits(const ssh_keyalg *self, ptrlen blob)
{ return self->pubkey_bits(self, blob); }
static inline const ssh_keyalg *ssh_key_alg(ssh_key *key)
@@ -870,6 +939,73 @@ static inline const char *ssh_key_ssh_id(ssh_key *key)
{ return key->vt->ssh_id; }
static inline const char *ssh_key_cache_id(ssh_key *key)
{ return key->vt->cache_id; }
+static inline unsigned ssh_key_supported_flags(ssh_key *key)
+{ return key->vt->supported_flags(key->vt); }
+static inline unsigned ssh_keyalg_supported_flags(const ssh_keyalg *self)
+{ return self->supported_flags(self); }
+static inline const char *ssh_keyalg_alternate_ssh_id(
+ const ssh_keyalg *self, unsigned flags)
+{ return self->alternate_ssh_id(self, flags); }
+static inline char *ssh_keyalg_desc(const ssh_keyalg *self)
+{ return self->alg_desc(self); }
+static inline bool ssh_keyalg_variable_size(const ssh_keyalg *self)
+{ return self->variable_size(self); }
+static inline const ssh_keyalg *ssh_keyalg_related_alg(
+ const ssh_keyalg *self, const ssh_keyalg *base)
+{ return self->related_alg(self, base); }
+
+/* Stub functions shared between multiple key types */
+unsigned nullkey_supported_flags(const ssh_keyalg *self);
+const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags);
+ssh_key *nullkey_base_key(ssh_key *key);
+bool nullkey_variable_size_no(const ssh_keyalg *self);
+bool nullkey_variable_size_yes(const ssh_keyalg *self);
+
+/* Utility functions implemented centrally */
+ssh_key *ssh_key_clone(ssh_key *key);
+
+/*
+ * SSH2 ECDH key exchange vtable
+ */
+struct ecdh_key {
+ const ecdh_keyalg *vt;
+};
+struct ecdh_keyalg {
+ /* Unusually, the 'new' method here doesn't directly take a vt
+ * pointer, because it will also need the containing ssh_kex
+ * structure for top-level parameters, and since that contains a
+ * vt pointer anyway, we might as well _only_ pass that. */
+ ecdh_key *(*new)(const ssh_kex *kex, bool is_server);
+ void (*free)(ecdh_key *key);
+ void (*getpublic)(ecdh_key *key, BinarySink *bs);
+ bool (*getkey)(ecdh_key *key, ptrlen remoteKey, BinarySink *bs);
+ char *(*description)(const ssh_kex *kex);
+};
+static inline ecdh_key *ecdh_key_new(const ssh_kex *kex, bool is_server)
+{ return kex->ecdh_vt->new(kex, is_server); }
+static inline void ecdh_key_free(ecdh_key *key)
+{ key->vt->free(key); }
+static inline void ecdh_key_getpublic(ecdh_key *key, BinarySink *bs)
+{ key->vt->getpublic(key, bs); }
+static inline bool ecdh_key_getkey(ecdh_key *key, ptrlen remoteKey,
+ BinarySink *bs)
+{ return key->vt->getkey(key, remoteKey, bs); }
+static inline char *ecdh_keyalg_description(const ssh_kex *kex)
+{ return kex->ecdh_vt->description(kex); }
+
+/*
+ * Suffix on GSSAPI SSH protocol identifiers that indicates Kerberos 5
+ * as the mechanism.
+ *
+ * This suffix is the base64-encoded MD5 hash of the byte sequence
+ * 06 09 2A 86 48 86 F7 12 01 02 02, which in turn is the ASN.1 DER
+ * encoding of the object ID 1.2.840.113554.1.2.2 which designates
+ * Kerberos v5.
+ *
+ * (The same encoded OID, minus the two-byte DER header, is defined in
+ * ssh/pgssapi.c as GSS_MECH_KRB5.)
+ */
+#define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g=="
/*
* Enumeration of signature flags from draft-miller-ssh-agent-02
@@ -953,22 +1089,40 @@ 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_ni;
+extern const ssh_cipheralg ssh_aes256_sdctr_neon;
extern const ssh_cipheralg ssh_aes256_sdctr_sw;
+extern const ssh_cipheralg ssh_aes256_gcm;
+extern const ssh_cipheralg ssh_aes256_gcm_ni;
+extern const ssh_cipheralg ssh_aes256_gcm_neon;
+extern const ssh_cipheralg ssh_aes256_gcm_sw;
extern const ssh_cipheralg ssh_aes256_cbc;
-extern const ssh_cipheralg ssh_aes256_cbc_hw;
+extern const ssh_cipheralg ssh_aes256_cbc_ni;
+extern const ssh_cipheralg ssh_aes256_cbc_neon;
extern const ssh_cipheralg ssh_aes256_cbc_sw;
extern const ssh_cipheralg ssh_aes192_sdctr;
-extern const ssh_cipheralg ssh_aes192_sdctr_hw;
+extern const ssh_cipheralg ssh_aes192_sdctr_ni;
+extern const ssh_cipheralg ssh_aes192_sdctr_neon;
extern const ssh_cipheralg ssh_aes192_sdctr_sw;
+extern const ssh_cipheralg ssh_aes192_gcm;
+extern const ssh_cipheralg ssh_aes192_gcm_ni;
+extern const ssh_cipheralg ssh_aes192_gcm_neon;
+extern const ssh_cipheralg ssh_aes192_gcm_sw;
extern const ssh_cipheralg ssh_aes192_cbc;
-extern const ssh_cipheralg ssh_aes192_cbc_hw;
+extern const ssh_cipheralg ssh_aes192_cbc_ni;
+extern const ssh_cipheralg ssh_aes192_cbc_neon;
extern const ssh_cipheralg ssh_aes192_cbc_sw;
extern const ssh_cipheralg ssh_aes128_sdctr;
-extern const ssh_cipheralg ssh_aes128_sdctr_hw;
+extern const ssh_cipheralg ssh_aes128_sdctr_ni;
+extern const ssh_cipheralg ssh_aes128_sdctr_neon;
extern const ssh_cipheralg ssh_aes128_sdctr_sw;
+extern const ssh_cipheralg ssh_aes128_gcm;
+extern const ssh_cipheralg ssh_aes128_gcm_ni;
+extern const ssh_cipheralg ssh_aes128_gcm_neon;
+extern const ssh_cipheralg ssh_aes128_gcm_sw;
extern const ssh_cipheralg ssh_aes128_cbc;
-extern const ssh_cipheralg ssh_aes128_cbc_hw;
+extern const ssh_cipheralg ssh_aes128_cbc_ni;
+extern const ssh_cipheralg ssh_aes128_cbc_neon;
extern const ssh_cipheralg ssh_aes128_cbc_sw;
extern const ssh_cipheralg ssh_blowfish_ssh2_ctr;
extern const ssh_cipheralg ssh_blowfish_ssh2;
@@ -981,18 +1135,21 @@ extern const ssh2_ciphers ssh2_aes;
extern const ssh2_ciphers ssh2_blowfish;
extern const ssh2_ciphers ssh2_arcfour;
extern const ssh2_ciphers ssh2_ccp;
+extern const ssh2_ciphers ssh2_aesgcm;
extern const ssh_hashalg ssh_md5;
extern const ssh_hashalg ssh_sha1;
-extern const ssh_hashalg ssh_sha1_hw;
+extern const ssh_hashalg ssh_sha1_ni;
+extern const ssh_hashalg ssh_sha1_neon;
extern const ssh_hashalg ssh_sha1_sw;
extern const ssh_hashalg ssh_sha256;
-extern const ssh_hashalg ssh_sha256_hw;
+extern const ssh_hashalg ssh_sha256_ni;
+extern const ssh_hashalg ssh_sha256_neon;
extern const ssh_hashalg ssh_sha256_sw;
extern const ssh_hashalg ssh_sha384;
-extern const ssh_hashalg ssh_sha384_hw;
+extern const ssh_hashalg ssh_sha384_neon;
extern const ssh_hashalg ssh_sha384_sw;
extern const ssh_hashalg ssh_sha512;
-extern const ssh_hashalg ssh_sha512_hw;
+extern const ssh_hashalg ssh_sha512_neon;
extern const ssh_hashalg ssh_sha512_sw;
extern const ssh_hashalg ssh_sha3_224;
extern const ssh_hashalg ssh_sha3_256;
@@ -1002,8 +1159,21 @@ extern const ssh_hashalg ssh_shake256_114bytes;
extern const ssh_hashalg ssh_blake2b;
extern const ssh_kexes ssh_diffiehellman_group1;
extern const ssh_kexes ssh_diffiehellman_group14;
+extern const ssh_kexes ssh_diffiehellman_group15;
+extern const ssh_kexes ssh_diffiehellman_group16;
+extern const ssh_kexes ssh_diffiehellman_group17;
+extern const ssh_kexes ssh_diffiehellman_group18;
extern const ssh_kexes ssh_diffiehellman_gex;
+extern const ssh_kex ssh_diffiehellman_group1_sha1;
+extern const ssh_kex ssh_diffiehellman_group14_sha256;
+extern const ssh_kex ssh_diffiehellman_group14_sha1;
+extern const ssh_kex ssh_diffiehellman_group15_sha512;
+extern const ssh_kex ssh_diffiehellman_group16_sha512;
+extern const ssh_kex ssh_diffiehellman_group17_sha512;
+extern const ssh_kex ssh_diffiehellman_group18_sha512;
extern const ssh_kexes ssh_gssk5_sha1_kex;
+extern const ssh_kexes ssh_gssk5_sha2_kex;
+extern const ssh_kexes ssh_gssk5_ecdh_kex;
extern const ssh_kexes ssh_rsa_kex;
extern const ssh_kex ssh_ec_kex_curve25519;
extern const ssh_kex ssh_ec_kex_curve448;
@@ -1011,7 +1181,8 @@ 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_kexes ssh_ntru_hybrid_kex;
+extern const ssh_keyalg ssh_dsa;
extern const ssh_keyalg ssh_rsa;
extern const ssh_keyalg ssh_rsa_sha256;
extern const ssh_keyalg ssh_rsa_sha512;
@@ -1020,6 +1191,14 @@ extern const ssh_keyalg ssh_ecdsa_ed448;
extern const ssh_keyalg ssh_ecdsa_nistp256;
extern const ssh_keyalg ssh_ecdsa_nistp384;
extern const ssh_keyalg ssh_ecdsa_nistp521;
+extern const ssh_keyalg opensshcert_ssh_dsa;
+extern const ssh_keyalg opensshcert_ssh_rsa;
+extern const ssh_keyalg opensshcert_ssh_rsa_sha256;
+extern const ssh_keyalg opensshcert_ssh_rsa_sha512;
+extern const ssh_keyalg opensshcert_ssh_ecdsa_ed25519;
+extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp256;
+extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp384;
+extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp521;
extern const ssh2_macalg ssh_hmac_md5;
extern const ssh2_macalg ssh_hmac_sha1;
extern const ssh2_macalg ssh_hmac_sha1_buggy;
@@ -1027,22 +1206,31 @@ extern const ssh2_macalg ssh_hmac_sha1_96;
extern const ssh2_macalg ssh_hmac_sha1_96_buggy;
extern const ssh2_macalg ssh_hmac_sha256;
extern const ssh2_macalg ssh2_poly1305;
+extern const ssh2_macalg ssh2_aesgcm_mac;
+extern const ssh2_macalg ssh2_aesgcm_mac_sw;
+extern const ssh2_macalg ssh2_aesgcm_mac_ref_poly;
+extern const ssh2_macalg ssh2_aesgcm_mac_clmul;
+extern const ssh2_macalg ssh2_aesgcm_mac_neon;
extern const ssh_compression_alg ssh_zlib;
/* Special constructor: BLAKE2b can be instantiated with any hash
* length up to 128 bytes */
ssh_hash *blake2b_new_general(unsigned hashlen);
+/* Special test function for AES-GCM */
+void aesgcm_set_prefix_lengths(ssh2_mac *mac, size_t skip, size_t aad);
+
/*
* On some systems, you have to detect hardware crypto acceleration by
* asking the local OS API rather than OS-agnostically asking the CPU
* itself. If so, then this function should be implemented in each
* platform subdirectory.
*/
-bool platform_aes_hw_available(void);
-bool platform_sha256_hw_available(void);
-bool platform_sha1_hw_available(void);
-bool platform_sha512_hw_available(void);
+bool platform_aes_neon_available(void);
+bool platform_pmull_neon_available(void);
+bool platform_sha256_neon_available(void);
+bool platform_sha1_neon_available(void);
+bool platform_sha512_neon_available(void);
/*
* PuTTY version number formatted as an SSH version string.
@@ -1051,13 +1239,13 @@ 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
+ * that fails. This variable is the means by which pscp.c can reach
* into the SSH code and find out which one it got.
*/
extern bool ssh_fallback_cmd(Backend *backend);
/*
- * The PRNG type, defined in sshprng.c. Visible data fields are
+ * The PRNG type, defined in prng.c. Visible data fields are
* 'savesize', which suggests how many random bytes you should request
* from a particular PRNG instance to write to putty.rnd, and a
* BinarySink implementation which you can use to write seed data in
@@ -1066,7 +1254,7 @@ extern bool ssh_fallback_cmd(Backend *backend);
struct prng {
size_t savesize;
BinarySink_IMPLEMENTATION;
- /* (also there's a surrounding implementation struct in sshprng.c) */
+ /* (also there's a surrounding implementation struct in prng.c) */
};
prng *prng_new(const ssh_hashalg *hashalg);
void prng_free(prng *p);
@@ -1137,10 +1325,6 @@ struct X11FakeAuth {
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
@@ -1170,7 +1354,7 @@ 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 */
+/* X11-related helper functions in utils */
/*
* This function does the job of platform_get_x11_auth, provided
* it is told where to find a normally formatted .Xauthority file:
@@ -1189,8 +1373,13 @@ void x11_get_auth_from_authfile(struct X11Display *display,
void x11_format_auth_for_authfile(
BinarySink *bs, SockAddr *addr, int display_no,
ptrlen authproto, ptrlen authdata);
+void *x11_make_greeting(int endian, int protomajor, int protominor,
+ int auth_proto, const void *auth_data, int auth_len,
+ const char *peer_ip, int peer_port,
+ int *outlen);
int x11_identify_auth_proto(ptrlen protoname);
void *x11_dehexify(ptrlen hex, int *outlen);
+bool x11_parse_ip(const char *addr_string, unsigned long *ip);
Channel *agentf_new(SshChannel *c);
@@ -1199,7 +1388,7 @@ 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);
+mp_int *dh_create_e(dh_ctx *);
const char *dh_validate_f(dh_ctx *, mp_int *f);
mp_int *dh_find_K(dh_ctx *, mp_int *f);
@@ -1211,11 +1400,7 @@ static inline bool is_base64_char(char c)
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;
@@ -1282,6 +1467,9 @@ extern const size_t n_keyalgs;
const ssh_keyalg *find_pubkey_alg(const char *name);
const ssh_keyalg *find_pubkey_alg_len(ptrlen name);
+ptrlen pubkey_blob_to_alg_name(ptrlen blob);
+const ssh_keyalg *pubkey_blob_to_alg(ptrlen blob);
+
/* Convenient wrappers on the LoadedFile mechanism suitable for key files */
LoadedFile *lf_load_keyfile(const Filename *filename, const char **errptr);
LoadedFile *lf_load_keyfile_fp(FILE *fp, const char **errptr);
@@ -1331,12 +1519,36 @@ enum {
};
typedef enum {
+ /* Default fingerprint types strip off a certificate to show you
+ * the fingerprint of the underlying public key */
SSH_FPTYPE_MD5,
SSH_FPTYPE_SHA256,
+ /* Non-default version of each fingerprint type which is 'raw',
+ * giving you the true hash of the public key blob even if it
+ * includes a certificate */
+ SSH_FPTYPE_MD5_CERT,
+ SSH_FPTYPE_SHA256_CERT,
} FingerprintType;
+static inline bool ssh_fptype_is_cert(FingerprintType fptype)
+{
+ return fptype >= SSH_FPTYPE_MD5_CERT;
+}
+static inline FingerprintType ssh_fptype_from_cert(FingerprintType fptype)
+{
+ if (ssh_fptype_is_cert(fptype))
+ fptype -= (SSH_FPTYPE_MD5_CERT - SSH_FPTYPE_MD5);
+ return fptype;
+}
+static inline FingerprintType ssh_fptype_to_cert(FingerprintType fptype)
+{
+ if (!ssh_fptype_is_cert(fptype))
+ fptype += (SSH_FPTYPE_MD5_CERT - SSH_FPTYPE_MD5);
+ return fptype;
+}
+
+#define SSH_N_FPTYPES (SSH_FPTYPE_SHA256_CERT + 1)
#define SSH_FPTYPE_DEFAULT SSH_FPTYPE_SHA256
-#define SSH_N_FPTYPES (SSH_FPTYPE_SHA256 + 1)
FingerprintType ssh2_pick_fingerprint(char **fingerprints,
FingerprintType preferred_type);
@@ -1350,6 +1562,8 @@ void ssh2_write_pubkey(FILE *fp, const char *comment,
int keytype);
char *ssh2_fingerprint_blob(ptrlen, FingerprintType);
char *ssh2_fingerprint(ssh_key *key, FingerprintType);
+char *ssh2_double_fingerprint_blob(ptrlen, FingerprintType);
+char *ssh2_double_fingerprint(ssh_key *key, FingerprintType);
char **ssh2_all_fingerprints_for_blob(ptrlen);
char **ssh2_all_fingerprints(ssh_key *key);
void ssh2_free_all_fingerprints(char **);
@@ -1361,7 +1575,7 @@ 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 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,
@@ -1369,7 +1583,7 @@ int import_ssh1_s(BinarySource *src, int type,
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);
+ 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,
@@ -1389,8 +1603,7 @@ void aes256_decrypt_pubkey(const void *key, const void *iv,
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,
+void openssh_bcrypt(ptrlen passphrase, ptrlen salt,
int rounds, unsigned char *out, int outbytes);
/*
@@ -1603,7 +1816,7 @@ 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"
+ #include "ssh/ttymode-list.h"
#undef TTYMODE_CHAR
#undef TTYMODE_FLAG
@@ -1667,6 +1880,7 @@ void old_keyfile_warning(void);
X(BUG_CHOKES_ON_WINADJ) \
X(BUG_SENDS_LATE_REQUEST_REPLY) \
X(BUG_SSH2_OLDGEX) \
+ X(BUG_REQUIRES_FILTERED_KEXINIT) \
/* end of list */
#define TMP_DECLARE_LOG2_ENUM(thing) log2_##thing,
enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_LOG2_ENUM) };
@@ -1684,9 +1898,14 @@ unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset);
alloc_channel_id_general(tree, offsetof(type, localid)))
void add_to_commasep(strbuf *buf, const char *data);
+void add_to_commasep_pl(strbuf *buf, ptrlen data);
bool get_commasep_word(ptrlen *list, ptrlen *word);
-int verify_ssh_manual_host_key(Conf *conf, char **fingerprints, ssh_key *key);
+SeatPromptResult verify_ssh_host_key(
+ InteractionReadySeat iseat, Conf *conf, const char *host, int port,
+ ssh_key *key, const char *keytype, char *keystr, const char *keydisp,
+ char **fingerprints, int ca_count,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache;
ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void);
@@ -1698,3 +1917,35 @@ bool ssh_transient_hostkey_cache_verify(
bool ssh_transient_hostkey_cache_has(
ssh_transient_hostkey_cache *thc, const ssh_keyalg *alg);
bool ssh_transient_hostkey_cache_non_empty(ssh_transient_hostkey_cache *thc);
+
+/*
+ * Protocol definitions for authentication helper plugins
+ */
+
+#define AUTHPLUGIN_MSG_NAMES(X) \
+ X(PLUGIN_INIT, 1) \
+ X(PLUGIN_INIT_RESPONSE, 2) \
+ X(PLUGIN_PROTOCOL, 3) \
+ X(PLUGIN_PROTOCOL_ACCEPT, 4) \
+ X(PLUGIN_PROTOCOL_REJECT, 5) \
+ X(PLUGIN_AUTH_SUCCESS, 6) \
+ X(PLUGIN_AUTH_FAILURE, 7) \
+ X(PLUGIN_INIT_FAILURE, 8) \
+ X(PLUGIN_KI_SERVER_REQUEST, 20) \
+ X(PLUGIN_KI_SERVER_RESPONSE, 21) \
+ X(PLUGIN_KI_USER_REQUEST, 22) \
+ X(PLUGIN_KI_USER_RESPONSE, 23) \
+ /* end of list */
+
+#define PLUGIN_PROTOCOL_MAX_VERSION 2 /* the highest version we speak */
+
+enum {
+ #define ENUMDECL(name, value) name = value,
+ AUTHPLUGIN_MSG_NAMES(ENUMDECL)
+ #undef ENUMDECL
+
+ /* Error codes internal to this implementation, indicating failure
+ * to receive a meaningful packet at all */
+ PLUGIN_NOTYPE = 256, /* packet too short to have a type */
+ PLUGIN_EOF = 257 /* EOF from auth plugin */
+};
diff --git a/SSHAES.C b/SSHAES.C
deleted file mode 100644
index 6671879e..00000000
--- a/SSHAES.C
+++ /dev/null
@@ -1,1913 +0,0 @@
-/*
- * sshaes.c - implementation of AES
- */
-
-#include <assert.h>
-#include <stdlib.h>
-
-#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(<wmmintrin.h>) && \
- (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(<arm_neon.h>) && \
- (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 <arm64_neon.h> in place
- * of the standard <arm_neon.h> */
-# 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]/<X^8+X^4+X^3+X+1> be the finite field
- * used in AES, and let R = F[Y]/<Y^4+1> 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 <wmmintrin.h>
-#include <smmintrin.h>
-
-#if defined(__clang__) || defined(__GNUC__)
-#include <cpuid.h>
-#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 <arm64_neon.h>
-#else
-#include <arm_neon.h>
-#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/SSHARCF.C b/SSHARCF.C
deleted file mode 100644
index 53821473..00000000
--- a/SSHARCF.C
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Arcfour (RC4) implementation for PuTTY.
- *
- * Coded from Schneier.
- */
-
-#include <assert.h>
-#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/SSHBLOWF.C b/SSHBLOWF.C
deleted file mode 100644
index c74f06c0..00000000
--- a/SSHBLOWF.C
+++ /dev/null
@@ -1,699 +0,0 @@
-/*
- * Blowfish implementation for PuTTY.
- *
- * Coded from scratch from the algorithm description.
- */
-
-#include <assert.h>
-#include <stdio.h>
-#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/SSHCRC.C b/SSHCRC.C
deleted file mode 100644
index b1dc8333..00000000
--- a/SSHCRC.C
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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 <stdint.h>
-#include <stdlib.h>
-
-#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/SSHDES.C b/SSHDES.C
deleted file mode 100644
index d2ba9825..00000000
--- a/SSHDES.C
+++ /dev/null
@@ -1,1048 +0,0 @@
-/*
- * 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 <stdio.h>
-
-#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/SSHDH.C b/SSHDH.C
deleted file mode 100644
index b3756120..00000000
--- a/SSHDH.C
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Diffie-Hellman implementation for PuTTY.
- */
-
-#include <assert.h>
-
-#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/SSHDSS.C b/SSHDSS.C
deleted file mode 100644
index 3e0c7618..00000000
--- a/SSHDSS.C
+++ /dev/null
@@ -1,503 +0,0 @@
-/*
- * Digital Signature Standard implementation for PuTTY.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#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/SSHDSSG.C b/SSHDSSG.C
deleted file mode 100644
index 3b527256..00000000
--- a/SSHDSSG.C
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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/SSHGSS.H b/SSHGSS.H
deleted file mode 100644
index c640636d..00000000
--- a/SSHGSS.H
+++ /dev/null
@@ -1,217 +0,0 @@
-#ifndef PUTTY_SSHGSS_H
-#define PUTTY_SSHGSS_H
-#include "putty.h"
-#include "pgssapi.h"
-
-#ifndef NO_GSSAPI
-
-#define SSH2_GSS_OIDTYPE 0x06
-typedef void *Ssh_gss_ctx;
-
-typedef enum Ssh_gss_stat {
- SSH_GSS_OK = 0,
- SSH_GSS_S_CONTINUE_NEEDED,
- SSH_GSS_NO_MEM,
- SSH_GSS_BAD_HOST_NAME,
- SSH_GSS_BAD_MIC,
- SSH_GSS_NO_CREDS,
- SSH_GSS_FAILURE
-} Ssh_gss_stat;
-
-#define SSH_GSS_S_COMPLETE SSH_GSS_OK
-
-#define SSH_GSS_CLEAR_BUF(buf) do { \
- (*buf).length = 0; \
- (*buf).value = NULL; \
-} while (0)
-
-typedef gss_buffer_desc Ssh_gss_buf;
-typedef gss_name_t Ssh_gss_name;
-
-#define GSS_NO_EXPIRATION ((time_t)-1)
-
-#define GSS_DEF_REKEY_MINS 2 /* Default minutes between GSS cache checks */
-
-/* Functions, provided by either 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/SSHGSSC.C b/SSHGSSC.C
deleted file mode 100644
index d9f62c39..00000000
--- a/SSHGSSC.C
+++ /dev/null
@@ -1,288 +0,0 @@
-#include "putty.h"
-
-#include <string.h>
-#include <limits.h>
-#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/SSHGSSC.H b/SSHGSSC.H
deleted file mode 100644
index 07fac009..00000000
--- a/SSHGSSC.H
+++ /dev/null
@@ -1,24 +0,0 @@
-#ifndef PUTTY_SSHGSSC_H
-#define PUTTY_SSHGSSC_H
-#include "putty.h"
-#ifndef NO_GSSAPI
-
-#include "pgssapi.h"
-#include "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/SSHPUBK.C b/SSHPUBK.C
index 3fad8870..0648b4c4 100644
--- a/SSHPUBK.C
+++ b/SSHPUBK.C
@@ -199,8 +199,7 @@ static int rsa1_load_s_internal(BinarySource *src, RSAKey *key, bool pub_only,
if (enclen & 7)
goto end;
- buf = strbuf_new_nm();
- put_datapl(buf, get_data(src, enclen));
+ buf = strbuf_dup_nm(get_data(src, enclen));
unsigned char keybuf[16];
hash_simple(&ssh_md5, ptrlen_from_asciz(passphrase), keybuf);
@@ -563,12 +562,20 @@ const ssh_keyalg *const all_keyalgs[] = {
&ssh_rsa,
&ssh_rsa_sha256,
&ssh_rsa_sha512,
- &ssh_dss,
+ &ssh_dsa,
&ssh_ecdsa_nistp256,
&ssh_ecdsa_nistp384,
&ssh_ecdsa_nistp521,
&ssh_ecdsa_ed25519,
&ssh_ecdsa_ed448,
+ &opensshcert_ssh_dsa,
+ &opensshcert_ssh_rsa,
+ &opensshcert_ssh_rsa_sha256,
+ &opensshcert_ssh_rsa_sha512,
+ &opensshcert_ssh_ecdsa_ed25519,
+ &opensshcert_ssh_ecdsa_nistp256,
+ &opensshcert_ssh_ecdsa_nistp384,
+ &opensshcert_ssh_ecdsa_nistp521,
};
const size_t n_keyalgs = lenof(all_keyalgs);
@@ -586,6 +593,18 @@ const ssh_keyalg *find_pubkey_alg(const char *name)
return find_pubkey_alg_len(ptrlen_from_asciz(name));
}
+ptrlen pubkey_blob_to_alg_name(ptrlen blob)
+{
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, blob);
+ return get_string(src);
+}
+
+const ssh_keyalg *pubkey_blob_to_alg(ptrlen blob)
+{
+ return find_pubkey_alg_len(pubkey_blob_to_alg_name(blob));
+}
+
struct ppk_cipher {
const char *name;
size_t blocklen, keylen, ivlen;
@@ -1226,7 +1245,7 @@ bool ppk_loadpub_s(BinarySource *src, char **algorithm, BinarySink *bs,
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";
+ error = "not a public key or a PuTTY SSH-2 private key";
goto error;
}
@@ -1238,7 +1257,7 @@ bool ppk_loadpub_s(BinarySource *src, char **algorithm, BinarySink *bs,
if (0 == strncmp(header, "PuTTY-User-Key-File-", 20))
error = "PuTTY key format too new";
else
- error = "not a PuTTY SSH-2 private key";
+ error = "not a public key or a PuTTY SSH-2 private key";
goto error;
}
error = "file format error";
@@ -1380,37 +1399,6 @@ int base64_lines(int datalen)
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,
@@ -1550,33 +1538,33 @@ strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase,
}
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);
+ put_fmt(out, "PuTTY-User-Key-File-%u: %s\n",
+ params.fmt_version, ssh_key_ssh_id(key->key));
+ put_fmt(out, "Encryption: %s\n", cipherstr);
+ put_fmt(out, "Comment: %s\n", key->comment);
+ put_fmt(out, "Public-Lines: %d\n", base64_lines(pub_blob->len));
+ base64_encode_bs(BinarySink_UPCAST(out), ptrlen_from_strbuf(pub_blob), 64);
if (params.fmt_version == 3 && ciphertype->keylen != 0) {
- 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);
+ put_fmt(out, "Key-Derivation: %s\n",
+ params.argon2_flavour == Argon2d ? "Argon2d" :
+ params.argon2_flavour == Argon2i ? "Argon2i" : "Argon2id");
+ put_fmt(out, "Argon2-Memory: %"PRIu32"\n", params.argon2_mem);
assert(!params.argon2_passes_auto);
- 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: ");
+ put_fmt(out, "Argon2-Passes: %"PRIu32"\n", params.argon2_passes);
+ put_fmt(out, "Argon2-Parallelism: %"PRIu32"\n",
+ params.argon2_parallelism);
+ put_fmt(out, "Argon2-Salt: ");
for (size_t i = 0; i < passphrase_salt->len; i++)
- strbuf_catf(out, "%02x", passphrase_salt->u[i]);
- strbuf_catf(out, "\n");
+ put_fmt(out, "%02x", passphrase_salt->u[i]);
+ put_fmt(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: ");
+ put_fmt(out, "Private-Lines: %d\n", base64_lines(priv_encrypted_len));
+ base64_encode_bs(BinarySink_UPCAST(out),
+ make_ptrlen(priv_blob_encrypted, priv_encrypted_len), 64);
+ put_fmt(out, "Private-MAC: ");
for (i = 0; i < macalg->len; i++)
- strbuf_catf(out, "%02x", priv_mac[i]);
- strbuf_catf(out, "\n");
+ put_fmt(out, "%02x", priv_mac[i]);
+ put_fmt(out, "\n");
strbuf_free(cipher_mac_keys_blob);
strbuf_free(passphrase_salt);
@@ -1740,7 +1728,7 @@ static void ssh2_fingerprint_blob_md5(ptrlen blob, strbuf *sb)
hash_simple(&ssh_md5, blob, digest);
for (unsigned i = 0; i < 16; i++)
- strbuf_catf(sb, "%02x%s", digest[i], i==15 ? "" : ":");
+ put_fmt(sb, "%02x%s", digest[i], i==15 ? "" : ":");
}
static void ssh2_fingerprint_blob_sha256(ptrlen blob, strbuf *sb)
@@ -1764,6 +1752,7 @@ static void ssh2_fingerprint_blob_sha256(ptrlen blob, strbuf *sb)
char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype)
{
strbuf *sb = strbuf_new();
+ strbuf *tmp = NULL;
/*
* Identify the key algorithm, if possible.
@@ -1778,24 +1767,63 @@ char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype)
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);
+ put_fmt(sb, "%.*s %d ", PTRLEN_PRINTF(algname), bits);
+
+ if (!ssh_fptype_is_cert(fptype) && alg->is_certificate) {
+ ssh_key *key = ssh_key_new_pub(alg, blob);
+ if (key) {
+ tmp = strbuf_new();
+ ssh_key_public_blob(ssh_key_base_key(key),
+ BinarySink_UPCAST(tmp));
+ blob = ptrlen_from_strbuf(tmp);
+ ssh_key_free(key);
+ }
+ }
} else {
- strbuf_catf(sb, "%.*s ", PTRLEN_PRINTF(algname));
+ put_fmt(sb, "%.*s ", PTRLEN_PRINTF(algname));
}
}
- switch (fptype) {
+ switch (ssh_fptype_from_cert(fptype)) {
case SSH_FPTYPE_MD5:
ssh2_fingerprint_blob_md5(blob, sb);
break;
case SSH_FPTYPE_SHA256:
ssh2_fingerprint_blob_sha256(blob, sb);
break;
+ default:
+ unreachable("ssh_fptype_from_cert ruled out the other values");
}
+ if (tmp)
+ strbuf_free(tmp);
+
return strbuf_to_str(sb);
}
+char *ssh2_double_fingerprint_blob(ptrlen blob, FingerprintType fptype)
+{
+ if (ssh_fptype_is_cert(fptype))
+ fptype = ssh_fptype_from_cert(fptype);
+
+ char *fp = ssh2_fingerprint_blob(blob, fptype);
+ char *p = strrchr(fp, ' ');
+ char *hash = p ? p + 1 : fp;
+
+ char *fpc = ssh2_fingerprint_blob(blob, ssh_fptype_to_cert(fptype));
+ char *pc = strrchr(fpc, ' ');
+ char *hashc = pc ? pc + 1 : fpc;
+
+ if (strcmp(hash, hashc)) {
+ char *tmp = dupprintf("%s (with certificate: %s)", fp, hashc);
+ sfree(fp);
+ fp = tmp;
+ }
+
+ sfree(fpc);
+ return fp;
+}
+
char **ssh2_all_fingerprints_for_blob(ptrlen blob)
{
char **fps = snewn(SSH_N_FPTYPES, char *);
@@ -1813,6 +1841,15 @@ char *ssh2_fingerprint(ssh_key *data, FingerprintType fptype)
return ret;
}
+char *ssh2_double_fingerprint(ssh_key *data, FingerprintType fptype)
+{
+ strbuf *blob = strbuf_new();
+ ssh_key_public_blob(data, BinarySink_UPCAST(blob));
+ char *ret = ssh2_double_fingerprint_blob(ptrlen_from_strbuf(blob), fptype);
+ strbuf_free(blob);
+ return ret;
+}
+
char **ssh2_all_fingerprints(ssh_key *data)
{
strbuf *blob = strbuf_new();
@@ -1869,7 +1906,7 @@ static int key_type_s_internal(BinarySource *src)
if (find_pubkey_alg_len(get_nonchars(src, " \n")) > 0 &&
get_chars(src, " ").len == 1 &&
get_chars(src, "0123456789ABCDEFGHIJKLMNOPQRSTUV"
- "WXYZabcdefghijklmnopqrstuvwxyz+/=").len > 0 &&
+ "WXYZabcdefghijklmnopqrstuvwxyz+/=").len > 0 &&
get_nonchars(src, " \n").len == 0)
return SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH;
@@ -1936,47 +1973,3 @@ const char *key_type_to_str(int type)
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/SSHRSA.C b/SSHRSA.C
deleted file mode 100644
index e3dcbdda..00000000
--- a/SSHRSA.C
+++ /dev/null
@@ -1,1109 +0,0 @@
-/*
- * RSA implementation for PuTTY.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#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/SSHSH256.C b/SSHSH256.C
deleted file mode 100644
index db1f96bd..00000000
--- a/SSHSH256.C
+++ /dev/null
@@ -1,940 +0,0 @@
-/*
- * SHA-256 algorithm as described at
- *
- * http://csrc.nist.gov/cryptval/shs.html
- */
-
-#include "ssh.h"
-#include <assert.h>
-
-/*
- * 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(<wmmintrin.h>) && \
- (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(<arm_neon.h>) && \
- (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(&copy->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 <wmmintrin.h>
-#include <smmintrin.h>
-#include <immintrin.h>
-#if defined(__clang__) || defined(__GNUC__)
-#include <shaintrin.h>
-#endif
-
-#if defined(__clang__) || defined(__GNUC__)
-#include <cpuid.h>
-#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(&copy->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 <arm64_neon.h>
-#else
-#include <arm_neon.h>
-#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(&copy->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/SSHSH512.C b/SSHSH512.C
deleted file mode 100644
index cba7f38d..00000000
--- a/SSHSH512.C
+++ /dev/null
@@ -1,836 +0,0 @@
-/*
- * SHA-512 algorithm as described at
- *
- * http://csrc.nist.gov/cryptval/shs.html
- *
- * Modifications made for SHA-384 also
- */
-
-#include <assert.h>
-#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(<arm_neon.h>) && \
- (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(&copy->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 <arm64_neon.h>
-#else
-#include <arm_neon.h>
-#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 <arm_neon.h>.
- * 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(&copy->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/SSHSHA.C b/SSHSHA.C
deleted file mode 100644
index a5e79e6a..00000000
--- a/SSHSHA.C
+++ /dev/null
@@ -1,934 +0,0 @@
-/*
- * SHA-1 algorithm as described at
- *
- * http://csrc.nist.gov/cryptval/shs.html
- */
-
-#include "ssh.h"
-#include <assert.h>
-
-/*
- * 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(<wmmintrin.h>) && \
- (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(<arm_neon.h>) && \
- (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(&copy->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 <wmmintrin.h>
-#include <smmintrin.h>
-#include <immintrin.h>
-#if defined(__clang__) || defined(__GNUC__)
-#include <shaintrin.h>
-#endif
-
-#if defined(__clang__) || defined(__GNUC__)
-#include <cpuid.h>
-#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(&copy->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 <arm64_neon.h>
-#else
-#include <arm_neon.h>
-#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(&copy->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/SSHZLIB.C b/SSHZLIB.C
deleted file mode 100644
index 9ad04ed2..00000000
--- a/SSHZLIB.C
+++ /dev/null
@@ -1,1253 +0,0 @@
-/*
- * Zlib (RFC1950 / RFC1951) compression for PuTTY.
- *
- * There will no doubt be criticism of my decision to reimplement
- * Zlib compression from scratch instead of using the existing zlib
- * code. People will cry `reinventing the wheel'; they'll claim
- * that the `fundamental basis of OSS' is code reuse; they'll want
- * to see a really good reason for me having chosen not to use the
- * existing code.
- *
- * Well, here are my reasons. Firstly, I don't want to link the
- * whole of zlib into the PuTTY binary; PuTTY is justifiably proud
- * of its small size and I think zlib contains a lot of unnecessary
- * baggage for the kind of compression that SSH requires.
- *
- * Secondly, I also don't like the alternative of using zlib.dll.
- * Another thing PuTTY is justifiably proud of is its ease of
- * installation, and the last thing I want to do is to start
- * mandating DLLs. Not only that, but there are two _kinds_ of
- * zlib.dll kicking around, one with C calling conventions on the
- * exported functions and another with WINAPI conventions, and
- * there would be a significant danger of getting the wrong one.
- *
- * Thirdly, there seems to be a difference of opinion on the IETF
- * secsh mailing list about the correct way to round off a
- * compressed packet and start the next. In particular, there's
- * some talk of switching to a mechanism zlib isn't currently
- * capable of supporting (see below for an explanation). Given that
- * sort of uncertainty, I thought it might be better to have code
- * that will support even the zlib-incompatible worst case.
- *
- * Fourthly, it's a _second implementation_. Second implementations
- * are fundamentally a Good Thing in standardisation efforts. The
- * difference of opinion mentioned above has arisen _precisely_
- * because there has been only one zlib implementation and
- * everybody has used it. I don't intend that this should happen
- * again.
- */
-
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#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/STORAGE.H b/STORAGE.H
index 6464b69d..e9138f40 100644
--- a/STORAGE.H
+++ b/STORAGE.H
@@ -6,6 +6,8 @@
#ifndef PUTTY_STORAGE_H
#define PUTTY_STORAGE_H
+#include "defs.h"
+
/* ----------------------------------------------------------------------
* Functions to save and restore PuTTY sessions. Note that this is
* only the low-level code to do the reading and writing. The
@@ -81,8 +83,8 @@ void enum_settings_finish(settings_e *handle);
* 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);
+int check_stored_host_key(const char *hostname, int port,
+ const char *keytype, const char *key);
/*
* Write a host key into the database, overwriting any previous
@@ -92,6 +94,31 @@ void store_host_key(const char *hostname, int port,
const char *keytype, const char *key);
/* ----------------------------------------------------------------------
+ * Functions to access PuTTY's configuration for trusted host
+ * certification authorities. This must be stored separately from the
+ * saved-session data, because the whole point is to avoid having to
+ * configure CAs separately per session.
+ */
+
+struct host_ca {
+ char *name;
+ strbuf *ca_public_key;
+ char *validity_expression;
+ ca_options opts;
+};
+
+host_ca_enum *enum_host_ca_start(void);
+bool enum_host_ca_next(host_ca_enum *handle, strbuf *out);
+void enum_host_ca_finish(host_ca_enum *handle);
+
+host_ca *host_ca_load(const char *name);
+char *host_ca_save(host_ca *); /* NULL on success, or dynamic error msg */
+char *host_ca_delete(const char *name); /* likewise */
+
+host_ca *host_ca_new(void); /* initialises to default settings */
+void host_ca_free(host_ca *);
+
+/* ----------------------------------------------------------------------
* Functions to access PuTTY's random number seed file.
*/
diff --git a/TELNET.C b/TELNET.C
deleted file mode 100644
index 3a60e646..00000000
--- a/TELNET.C
+++ /dev/null
@@ -1,1070 +0,0 @@
-/*
- * Telnet backend.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-
-#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 "<unknown>";
- }
-#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 <something weird>");
- 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 <something weird>\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 <nothing>",
- 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/TERMINAL.C b/TERMINAL.C
deleted file mode 100644
index f8f9417c..00000000
--- a/TERMINAL.C
+++ /dev/null
@@ -1,7656 +0,0 @@
-/*
- * Terminal emulator.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <limits.h>
-#include <wchar.h>
-
-#include <time.h>
-#include <assert.h>
-#include "putty.h"
-#include "terminal.h"
-
-#define VT52_PLUS
-
-#define CL_ANSIMIN 0x0001 /* Codes in all ANSI like terminals. */
-#define CL_VT100 0x0002 /* VT100 */
-#define CL_VT100AVO 0x0004 /* VT100 +AVO; 132x24 (not 132x14) & attrs */
-#define CL_VT102 0x0008 /* VT102 */
-#define CL_VT220 0x0010 /* VT220 */
-#define CL_VT320 0x0020 /* VT320 */
-#define CL_VT420 0x0040 /* VT420 */
-#define CL_VT510 0x0080 /* VT510, NB VT510 includes ANSI */
-#define CL_VT340TEXT 0x0100 /* VT340 extensions that appear in the VT420 */
-#define CL_SCOANSI 0x1000 /* SCOANSI not in ANSIMIN. */
-#define CL_ANSI 0x2000 /* ANSI ECMA-48 not in the VT100..VT420 */
-#define CL_OTHER 0x4000 /* Others, Xterm, linux, putty, dunno, etc */
-
-#define TM_VT100 (CL_ANSIMIN|CL_VT100)
-#define TM_VT100AVO (TM_VT100|CL_VT100AVO)
-#define TM_VT102 (TM_VT100AVO|CL_VT102)
-#define TM_VT220 (TM_VT102|CL_VT220)
-#define TM_VTXXX (TM_VT220|CL_VT340TEXT|CL_VT510|CL_VT420|CL_VT320)
-#define TM_SCOANSI (CL_ANSIMIN|CL_SCOANSI)
-
-#define TM_PUTTY (0xFFFF)
-
-#define UPDATE_DELAY ((TICKSPERSEC+49)/50)/* ticks to defer window update */
-#define TBLINK_DELAY ((TICKSPERSEC*9+19)/20)/* ticks between text blinks*/
-#define CBLINK_DELAY (CURSORBLINK) /* ticks between cursor blinks */
-#define VBELL_DELAY (VBELL_TIMEOUT) /* visual bell timeout in ticks */
-
-#define compatibility(x) \
- if ( ((CL_##x)&term->compatibility_level) == 0 ) { \
- term->termstate=TOPLEVEL; \
- break; \
- }
-#define compatibility2(x,y) \
- if ( ((CL_##x|CL_##y)&term->compatibility_level) == 0 ) { \
- term->termstate=TOPLEVEL; \
- break; \
- }
-
-#define has_compat(x) ( ((CL_##x)&term->compatibility_level) != 0 )
-
-static const char *const EMPTY_WINDOW_TITLE = "";
-
-static const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 };
-
-#define sel_nl_sz (sizeof(sel_nl)/sizeof(wchar_t))
-static const wchar_t sel_nl[] = SEL_NL;
-
-/*
- * 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--) {
- assert(n < ldata->cols);
- readliteral(bs, ldata->chars + n, ldata, &state);
- n++;
- }
- }
- }
-
- assert(n == ldata->cols);
-}
-static void readliteral_chr(BinarySource *bs, termchar *c, termline *ldata,
- unsigned long *state)
-{
- int byte;
-
- /*
- * 00000000-0000007F: 0xxxxxxx
- * 00000080-00003FFF: 10xxxxxx xxxxxxxx
- * 00004000-001FFFFF: 110xxxxx xxxxxxxx xxxxxxxx
- * 00200000-0FFFFFFF: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx
- * 10000000-FFFFFFFF: 11110ZZZ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
- */
-
- byte = get_byte(bs);
- if (byte < 0x80) {
- c->chr = byte | *state;
- } else if (byte < 0xC0) {
- c->chr = (byte &~ 0xC0) << 8;
- c->chr |= get_byte(bs);
- } else if (byte < 0xE0) {
- c->chr = (byte &~ 0xE0) << 16;
- c->chr |= get_uint16(bs);
- } else if (byte < 0xF0) {
- c->chr = (byte &~ 0xF0) << 24;
- c->chr |= get_byte(bs) << 16;
- c->chr |= get_uint16(bs);
- } else {
- assert(byte == 0xF0);
- c->chr = get_uint32(bs);
- }
- *state = c->chr & ~0xFF;
-}
-static void readliteral_attr(BinarySource *bs, termchar *c, termline *ldata,
- unsigned long *state)
-{
- unsigned val, attr, colourbits;
-
- val = get_uint16(bs);
-
- if (val >= 0x8000) {
- val &= ~0x8000;
- val <<= 16;
- val |= get_uint16(bs);
- }
-
- colourbits = (val >> (32-9)) & 0xFF;
- attr = (val & ((1<<(32-9))-1));
-
- attr = (((attr >> (ATTR_FGSHIFT + 4)) << (ATTR_FGSHIFT + 8)) |
- (attr & ((1 << (ATTR_FGSHIFT + 4))-1)));
- attr = (((attr >> (ATTR_BGSHIFT + 4)) << (ATTR_BGSHIFT + 8)) |
- (attr & ((1 << (ATTR_BGSHIFT + 4))-1)));
-
- attr |= (colourbits >> 4) << (ATTR_BGSHIFT + 4);
- attr |= (colourbits & 0xF) << (ATTR_FGSHIFT + 4);
-
- c->attr = attr;
-}
-static void readliteral_truecolour(
- BinarySource *bs, termchar *c, termline *ldata, unsigned long *state)
-{
- int flags = get_byte(bs);
-
- if (flags & 1) {
- c->truecolour.fg.enabled = true;
- c->truecolour.fg.r = get_byte(bs);
- c->truecolour.fg.g = get_byte(bs);
- c->truecolour.fg.b = get_byte(bs);
- } else {
- c->truecolour.fg = optionalrgb_none;
- }
-
- if (flags & 2) {
- c->truecolour.bg.enabled = true;
- c->truecolour.bg.r = get_byte(bs);
- c->truecolour.bg.g = get_byte(bs);
- c->truecolour.bg.b = get_byte(bs);
- } else {
- c->truecolour.bg = optionalrgb_none;
- }
-}
-static void readliteral_cc(BinarySource *bs, termchar *c, termline *ldata,
- unsigned long *state)
-{
- termchar n;
- unsigned long zstate;
- int x = c - ldata->chars;
-
- c->cc_next = 0;
-
- while (1) {
- zstate = 0;
- readliteral_chr(bs, &n, ldata, &zstate);
- if (!n.chr)
- break;
- add_cc(ldata, x, n.chr);
- }
-}
-
-static termline *decompressline(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 <putty@projects.tartarus.org> "
- "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;
-
- assert(!screen);
-
- if (term->erase_to_scrollback &&
- term->alt_which && term->alt_screen) {
- altlines = term->alt_sblines;
- }
- if (y < -altlines) {
- whichtree = term->scrollback;
- treeindex = y + altlines + count234(term->scrollback);
- } else {
- whichtree = term->alt_screen;
- treeindex = y + term->alt_sblines;
- /* treeindex = y + count234(term->alt_screen); */
- }
- }
- if (whichtree == term->scrollback) {
- 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);
- term->xterm_mouse = 0;
- term->xterm_extended_mouse = false;
- term->urxvt_extended_mouse = false;
- 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);
- term->no_remote_charset = conf_get_bool(term->conf, CONF_no_remote_charset);
- term->no_remote_resize = conf_get_bool(term->conf, CONF_no_remote_resize);
- term->no_remote_wintitle = conf_get_bool(term->conf, CONF_no_remote_wintitle);
- term->no_remote_clearscroll = conf_get_bool(term->conf, CONF_no_remote_clearscroll);
- term->rawcnp = conf_get_bool(term->conf, CONF_rawcnp);
- term->utf8linedraw = conf_get_bool(term->conf, CONF_utf8linedraw);
- term->rect_select = conf_get_bool(term->conf, CONF_rect_select);
- term->remote_qtitle_action = conf_get_int(term->conf, CONF_remote_qtitle_action);
- term->rxvt_homeend = conf_get_bool(term->conf, CONF_rxvt_homeend);
- term->scroll_on_disp = conf_get_bool(term->conf, CONF_scroll_on_disp);
- term->scroll_on_key = conf_get_bool(term->conf, CONF_scroll_on_key);
- term->xterm_mouse_forbidden = conf_get_bool(term->conf, CONF_no_mouse_rep);
- term->xterm_256_colour = conf_get_bool(term->conf, CONF_xterm_256_colour);
- term->true_colour = conf_get_bool(term->conf, CONF_true_colour);
-
- /*
- * Parse the control-character escapes in the configured
- * answerback string.
- */
- {
- char *answerback = conf_get_str(term->conf, CONF_answerback);
- 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;
- }
- if (!conf_get_str(term->conf, CONF_printer)) {
- term_print_finish(term);
- }
- if (palette_changed)
- term_notify_palette_changed(term);
- term_schedule_tblink(term);
- term_schedule_cblink(term);
- term_copy_stuff_from_conf(term);
- term_update_raw_mouse_mode(term);
-}
-
-/*
- * Clear the scrollback.
- */
-void term_clrsb(Terminal *term)
-{
- unsigned char *line;
- int i;
-
- /*
- * Scroll forward to the current screen, if we were back in the
- * scrollback somewhere until now.
- */
- term->disptop = 0;
-
- /*
- * Clear the actual scrollback.
- */
- while ((line = delpos234(term->scrollback, 0)) != NULL) {
- sfree(line); /* this is compressed data, not a termline */
- }
-
- /*
- * When clearing the scrollback, we also truncate any termlines on
- * the current screen which have remembered data from a previous
- * larger window size. Rationale: clearing the scrollback is
- * sometimes done to protect privacy, so the user intention is
- * specifically that we should not retain evidence of what
- * previously happened in the terminal, and that ought to include
- * evidence to the right as well as evidence above.
- */
- for (i = 0; i < term->rows; i++)
- check_line_size(term, scrlineptr(i));
-
- /*
- * That operation has invalidated the selection, if it overlapped
- * the scrollback at all.
- */
- if (term->selstate != NO_SELECTION && term->selstart.y < 0)
- deselect(term);
-
- /*
- * There are now no lines of real scrollback which can be pulled
- * back into the screen by a resize, and no lines of the alternate
- * screen which should be displayed as if part of the scrollback.
- */
- term->tempsblines = 0;
- term->alt_sblines = 0;
-
- /*
- * The scrollbar will need updating to reflect the new state of
- * the world.
- */
- term->win_scrollbar_update_pending = true;
- term_schedule_update(term);
-}
-
-const optionalrgb optionalrgb_none = {0, 0, 0, 0};
-
-void term_setup_window_titles(Terminal *term, const char *title_hostname)
-{
- const char *conf_title = conf_get_str(term->conf, CONF_wintitle);
- sfree(term->window_title);
- sfree(term->icon_title);
- if (*conf_title) {
- term->window_title = dupstr(conf_title);
- term->icon_title = dupstr(conf_title);
- } else {
- if (title_hostname && *title_hostname)
- term->window_title = dupcat(title_hostname, " - ", appname);
- else
- term->window_title = dupstr(appname);
- term->icon_title = dupstr(term->window_title);
- }
- term->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);
- 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;
-
- 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;
-
- /* 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;
-
- 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;
- 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);
- 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 {
- 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);
- 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;
- 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;
- case 1047: /* alternate screen */
- compatibility(OTHER);
- deselect(term);
- swap_screen(term, term->no_alt_screen ? 0 : state, true, true);
- if (term->scroll_on_disp)
- term->disptop = 0;
- break;
- case 1048: /* save/restore cursor */
- if (!term->no_alt_screen)
- save_cursor(term, state);
- if (!state) seen_disp_event(term);
- break;
- case 1049: /* cursor & alternate screen */
- if (state && !term->no_alt_screen)
- save_cursor(term, state);
- if (!state) seen_disp_event(term);
- compatibility(OTHER);
- deselect(term);
- swap_screen(term, term->no_alt_screen ? 0 : state, true, false);
- if (!state && !term->no_alt_screen)
- save_cursor(term, state);
- if (term->scroll_on_disp)
- term->disptop = 0;
- break;
- case 2004: /* xterm bracketed paste */
- term->bracketed_paste = state ? true : false;
- break;
- }
- } else if (query == 0) {
- switch (mode) {
- case 4: /* IRM: set insert mode */
- compatibility(VT102);
- term->insert = state;
- break;
- case 12: /* SRM: set echo mode */
- term->srm_echo = !state;
- break;
- case 20: /* LNM: Return sends ... */
- term->cr_lf_return = state;
- break;
- case 34: /* WYULCURM: Make cursor BIG */
- compatibility2(OTHER, VT220);
- term->big_cursor = !state;
- }
- }
-}
-
-/*
- * Process an OSC sequence: set window title or icon name.
- */
-static void do_osc(Terminal *term)
-{
- if (term->osc_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;
- }
- }
-}
-
-/*
- * ANSI printing routines.
- */
-static void term_print_setup(Terminal *term, char *printer)
-{
- bufchain_clear(&term->printer_buf);
- term->print_job = printer_start_job(printer);
-}
-static void term_print_flush(Terminal *term)
-{
- size_t size;
- while ((size = bufchain_size(&term->printer_buf)) > 5) {
- ptrlen data = bufchain_prefix(&term->printer_buf);
- if (data.len > size-5)
- data.len = size-5;
- printer_job_data(term->print_job, data.ptr, data.len);
- bufchain_consume(&term->printer_buf, data.len);
- }
-}
-static void term_print_finish(Terminal *term)
-{
- size_t size;
- char c;
-
- if (!term->printing && !term->only_printing)
- return; /* we need do nothing */
-
- term_print_flush(term);
- while ((size = bufchain_size(&term->printer_buf)) > 0) {
- ptrlen data = bufchain_prefix(&term->printer_buf);
- c = *(char *)data.ptr;
- if (c == '\033' || c == '\233') {
- bufchain_consume(&term->printer_buf, size);
- break;
- } else {
- printer_job_data(term->print_job, &c, 1);
- bufchain_consume(&term->printer_buf, 1);
- }
- }
- printer_finish_job(term->print_job);
- term->print_job = NULL;
- term->printing = term->only_printing = false;
-}
-
-static void term_display_graphic_char(Terminal *term, unsigned long c)
-{
- termline *cline = scrlineptr(term->curs.y);
- int width = 0;
- if (DIRECT_CHAR(c))
- width = 1;
- if (!width)
- width = term_char_width(term, c);
-
- if (term->wrapnext && term->wrap && width > 0) {
- cline->lattr |= LATTR_WRAPPED;
- if (term->curs.y == term->marg_b)
- scroll(term, term->marg_t, term->marg_b, 1, true);
- else if (term->curs.y < term->rows - 1)
- term->curs.y++;
- term->curs.x = 0;
- term->wrapnext = false;
- cline = scrlineptr(term->curs.y);
- }
- if (term->insert && width > 0)
- insch(term, width);
- if (term->selstate != NO_SELECTION) {
- pos cursplus = term->curs;
- incpos(cursplus);
- check_selection(term, term->curs, cursplus);
- }
- if (((c & CSET_MASK) == CSET_ASCII ||
- (c & CSET_MASK) == 0) && term->logctx)
- logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII);
-
- check_trust_status(term, cline);
-
- int linecols = term->cols;
- if (cline->trusted)
- linecols -= TRUST_SIGIL_WIDTH;
-
- /*
- * 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 {
- return c | CSET_ASCII;
- }
- } else if ((c & 0xe0) == 0xc0) {
- utf8->size = utf8->state = 1;
- utf8->chr = (c & 0x1f);
- } else if ((c & 0xf0) == 0xe0) {
- utf8->size = utf8->state = 2;
- utf8->chr = (c & 0x0f);
- } else if ((c & 0xf8) == 0xf0) {
- utf8->size = utf8->state = 3;
- utf8->chr = (c & 0x07);
- } else if ((c & 0xfc) == 0xf8) {
- utf8->size = utf8->state = 4;
- utf8->chr = (c & 0x03);
- } else if ((c & 0xfe) == 0xfc) {
- utf8->size = utf8->state = 5;
- utf8->chr = (c & 0x01);
- } else {
- return UCSINVALID;
- }
- return UCSINCOMPLETE;
- case 1:
- case 2:
- case 3:
- case 4:
- case 5:
- if ((c & 0xC0) != 0x80) {
- utf8->state = 0;
- return UCSTRUNCATED; /* caller will then give us the
- * same byte again */
- }
- utf8->chr = (utf8->chr << 6) | (c & 0x3f);
- if (--utf8->state)
- return UCSINCOMPLETE;
-
- unsigned long t = utf8->chr;
-
- /* Is somebody trying to be evil! */
- if (t < 0x80 ||
- (t < 0x800 && utf8->size >= 2) ||
- (t < 0x10000 && utf8->size >= 3) ||
- (t < 0x200000 && utf8->size >= 4) ||
- (t < 0x4000000 && utf8->size >= 5))
- return UCSINVALID;
-
- /* Unicode line separator and paragraph separator are CR-LF */
- if (t == 0x2028 || t == 0x2029)
- return 0x85;
-
- /* High controls are probably a Baaad idea too. */
- if (t < 0xA0)
- return 0xFFFD;
-
- /* The UTF-16 surrogates are not nice either. */
- /* The standard give the option of decoding these:
- * I don't want to! */
- if (t >= 0xD800 && t < 0xE000)
- return UCSINVALID;
-
- /* ISO 10646 characters now limited to UTF-16 range. */
- if (t > 0x10FFFF)
- return UCSINVALID;
-
- /* This is currently a TagPhobic application.. */
- if (t >= 0xE0000 && t <= 0xE007F)
- return UCSINCOMPLETE;
-
- /* U+FEFF is best seen as a null. */
- if (t == 0xFEFF)
- return UCSINCOMPLETE;
- /* But U+FFFE is an error. */
- if (t == 0xFFFE || t == 0xFFFF)
- return UCSINVALID;
-
- return t;
- }
- } else if (term->sco_acs &&
- (c!='\033' && c!='\012' && c!='\015' && c!='\b')) {
- /* Are we in the nasty ACS mode? Note: no sco in utf mode. */
- if (term->sco_acs == 2)
- c |= 0x80;
-
- return c | CSET_SCOACS;
- } else {
- switch (term->cset_attr[term->cset]) {
- /*
- * Linedraw characters are different from 'ESC ( B'
- * only for a small range. For ones outside that
- * range, make sure we use the same font as well as
- * the same encoding.
- */
- case CSET_LINEDRW:
- if (term->ucsdata->unitab_ctrl[c] != 0xFF)
- return term->ucsdata->unitab_ctrl[c];
- else
- return c | CSET_LINEDRW;
- break;
-
- case CSET_GBCHR:
- /* If UK-ASCII, make the '#' a LineDraw Pound */
- if (c == '#')
- return '}' | CSET_LINEDRW;
- /* fall through */
-
- case CSET_ASCII:
- if (term->ucsdata->unitab_ctrl[c] != 0xFF)
- return term->ucsdata->unitab_ctrl[c];
- else
- return c | CSET_ASCII;
- break;
- case CSET_SCOACS:
- if (c >= ' ')
- return c | CSET_SCOACS;
- break;
- }
- }
- return c;
-}
-
-/*
- * Remove everything currently in `inbuf' and stick it up on the
- * in-memory display. There's a big state machine in here to
- * process escape sequences...
- */
-static void term_out(Terminal *term)
-{
- 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);
- 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;
- case ']': /* OSC: xterm escape sequences */
- /* 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;
- } else if (c < '@') {
- if (term->esc_query)
- term->esc_query = -1;
- else if (c == '?')
- term->esc_query = 1;
- else
- term->esc_query = c;
- term->termstate = SEEN_CSI;
- } else
-#define CLAMP(arg, lim) ((arg) = ((arg) > (lim)) ? (lim) : (arg))
- switch (ANSI(c, term->esc_query)) {
- case 'A': /* CUU: move up N lines */
- CLAMP(term->esc_args[0], term->rows);
- move(term, term->curs.x,
- term->curs.y - def(term->esc_args[0], 1), 1);
- seen_disp_event(term);
- break;
- case 'e': /* VPR: move down N lines */
- compatibility(ANSI);
- /* FALLTHROUGH */
- case 'B': /* CUD: Cursor down */
- CLAMP(term->esc_args[0], term->rows);
- move(term, term->curs.x,
- term->curs.y + def(term->esc_args[0], 1), 1);
- seen_disp_event(term);
- break;
- case 'b': /* REP: repeat previous grap */
- CLAMP(term->esc_args[0], term->rows * term->cols);
- if (term->last_graphic_char) {
- unsigned i;
- for (i = 0; i < term->esc_args[0]; i++)
- term_display_graphic_char(
- term, term->last_graphic_char);
- }
- break;
- case ANSI('c', '>'): /* DA: report xterm version */
- compatibility(OTHER);
- /* this reports xterm version 136 so that VIM can
- use the drag messages from the mouse reporting */
- if (term->ldisc)
- ldisc_send(term->ldisc, "\033[>0;136;0c", 11,
- false);
- break;
- case 'a': /* HPR: move right N cols */
- compatibility(ANSI);
- /* FALLTHROUGH */
- case 'C': /* CUF: Cursor right */
- CLAMP(term->esc_args[0], term->cols);
- move(term, term->curs.x + def(term->esc_args[0], 1),
- term->curs.y, 1);
- seen_disp_event(term);
- break;
- case 'D': /* CUB: move left N cols */
- CLAMP(term->esc_args[0], term->cols);
- move(term, term->curs.x - def(term->esc_args[0], 1),
- term->curs.y, 1);
- seen_disp_event(term);
- break;
- case 'E': /* CNL: move down N lines and CR */
- compatibility(ANSI);
- CLAMP(term->esc_args[0], term->rows);
- move(term, 0,
- term->curs.y + def(term->esc_args[0], 1), 1);
- seen_disp_event(term);
- break;
- case 'F': /* CPL: move up N lines and CR */
- compatibility(ANSI);
- CLAMP(term->esc_args[0], term->rows);
- move(term, 0,
- term->curs.y - def(term->esc_args[0], 1), 1);
- seen_disp_event(term);
- break;
- case 'G': /* CHA */
- case '`': /* HPA: set horizontal posn */
- compatibility(ANSI);
- CLAMP(term->esc_args[0], term->cols);
- move(term, def(term->esc_args[0], 1) - 1,
- term->curs.y, 0);
- seen_disp_event(term);
- break;
- case 'd': /* VPA: set vertical posn */
- compatibility(ANSI);
- CLAMP(term->esc_args[0], term->rows);
- move(term, term->curs.x,
- ((term->dec_om ? term->marg_t : 0) +
- def(term->esc_args[0], 1) - 1),
- (term->dec_om ? 2 : 0));
- seen_disp_event(term);
- break;
- case 'H': /* CUP */
- case 'f': /* HVP: set horz and vert posns at once */
- if (term->esc_nargs < 2)
- term->esc_args[1] = ARG_DEFAULT;
- CLAMP(term->esc_args[0], term->rows);
- CLAMP(term->esc_args[1], term->cols);
- move(term, def(term->esc_args[1], 1) - 1,
- ((term->dec_om ? term->marg_t : 0) +
- def(term->esc_args[0], 1) - 1),
- (term->dec_om ? 2 : 0));
- seen_disp_event(term);
- break;
- case 'J': { /* ED: erase screen or parts of it */
- unsigned int i = def(term->esc_args[0], 0);
- if (i == 3) {
- /* Erase Saved Lines (xterm)
- * This follows Thomas Dickey's xterm. */
- if (!term->no_remote_clearscroll)
- term_clrsb(term);
- } else {
- i++;
- if (i > 3)
- i = 0;
- erase_lots(term, false, !!(i & 2), !!(i & 1));
- }
- if (term->scroll_on_disp)
- term->disptop = 0;
- seen_disp_event(term);
- break;
- }
- case 'K': { /* EL: erase line or parts of it */
- unsigned int i = def(term->esc_args[0], 0) + 1;
- if (i > 3)
- i = 0;
- erase_lots(term, true, !!(i & 2), !!(i & 1));
- seen_disp_event(term);
- break;
- }
- case 'L': /* IL: insert lines */
- compatibility(VT102);
- CLAMP(term->esc_args[0], term->rows);
- if (term->curs.y <= term->marg_b)
- scroll(term, term->curs.y, term->marg_b,
- -def(term->esc_args[0], 1), false);
- seen_disp_event(term);
- break;
- case 'M': /* DL: delete lines */
- compatibility(VT102);
- CLAMP(term->esc_args[0], term->rows);
- if (term->curs.y <= term->marg_b)
- scroll(term, term->curs.y, term->marg_b,
- def(term->esc_args[0], 1),
- true);
- seen_disp_event(term);
- break;
- case '@': /* ICH: insert chars */
- /* XXX VTTEST says this is vt220, vt510 manual says vt102 */
- compatibility(VT102);
- CLAMP(term->esc_args[0], term->cols);
- insch(term, def(term->esc_args[0], 1));
- seen_disp_event(term);
- break;
- case 'P': /* DCH: delete chars */
- compatibility(VT102);
- CLAMP(term->esc_args[0], term->cols);
- insch(term, -def(term->esc_args[0], 1));
- seen_disp_event(term);
- break;
- case 'c': /* DA: terminal type query */
- compatibility(VT100);
- /* This is the response for a VT102 */
- if (term->ldisc)
- ldisc_send(term->ldisc, term->id_string,
- strlen(term->id_string), false);
- break;
- case 'n': /* DSR: cursor position query */
- if (term->ldisc) {
- if (term->esc_args[0] == 6) {
- char buf[32];
- sprintf(buf, "\033[%d;%dR", term->curs.y + 1,
- term->curs.x + 1);
- ldisc_send(term->ldisc, buf, strlen(buf),
- false);
- } else if (term->esc_args[0] == 5) {
- ldisc_send(term->ldisc, "\033[0n", 4, false);
- }
- }
- break;
- case 'h': /* SM: toggle modes to high */
- case ANSI_QUE('h'):
- compatibility(VT100);
- for (int i = 0; i < term->esc_nargs; i++)
- toggle_mode(term, term->esc_args[i],
- term->esc_query, true);
- break;
- case 'i': /* MC: Media copy */
- case ANSI_QUE('i'): {
- compatibility(VT100);
- char *printer;
- if (term->esc_nargs != 1) break;
- if (term->esc_args[0] == 5 &&
- (printer = conf_get_str(term->conf,
- CONF_printer))[0]) {
- term->printing = true;
- term->only_printing = !term->esc_query;
- term->print_state = 0;
- term_print_setup(term, printer);
- } else if (term->esc_args[0] == 4 &&
- term->printing) {
- term_print_finish(term);
- }
- break;
- }
- case 'l': /* RM: toggle modes to low */
- case ANSI_QUE('l'):
- compatibility(VT100);
- for (int i = 0; i < term->esc_nargs; i++)
- toggle_mode(term, term->esc_args[i],
- term->esc_query, false);
- break;
- case 'g': /* TBC: clear tabs */
- compatibility(VT100);
- if (term->esc_nargs == 1) {
- if (term->esc_args[0] == 0) {
- term->tabs[term->curs.x] = false;
- } else if (term->esc_args[0] == 3) {
- int i;
- for (i = 0; i < term->cols; i++)
- term->tabs[i] = false;
- }
- }
- break;
- case 'r': /* DECSTBM: set scroll margins */
- compatibility(VT100);
- if (term->esc_nargs <= 2) {
- int top, bot;
- CLAMP(term->esc_args[0], term->rows);
- CLAMP(term->esc_args[1], term->rows);
- top = def(term->esc_args[0], 1) - 1;
- bot = (term->esc_nargs <= 1
- || term->esc_args[1] == 0 ?
- term->rows :
- def(term->esc_args[1], term->rows)) - 1;
- if (bot >= term->rows)
- bot = term->rows - 1;
- /* VTTEST Bug 9 - if region is less than 2 lines
- * don't change region.
- */
- if (bot - top > 0) {
- term->marg_t = top;
- term->marg_b = bot;
- term->curs.x = 0;
- /*
- * I used to think the cursor should be
- * placed at the top of the newly marginned
- * area. Apparently not: VMS TPU falls over
- * if so.
- *
- * Well actually it should for
- * Origin mode - RDB
- */
- term->curs.y = (term->dec_om ?
- term->marg_t : 0);
- seen_disp_event(term);
- }
- }
- break;
- case 'm': /* SGR: set graphics rendition */
- /*
- * A VT100 without the AVO only had one
- * attribute, either underline or reverse
- * video depending on the cursor type, this
- * was selected by CSI 7m.
- *
- * case 2:
- * This is sometimes DIM, eg on the GIGI and
- * Linux
- * case 8:
- * This is sometimes INVIS various ANSI.
- * case 21:
- * This like 22 disables BOLD, DIM and INVIS
- *
- * The ANSI colours appear on any terminal
- * that has colour (obviously) but the
- * interaction between sgr0 and the colours
- * varies but is usually related to the
- * background colour erase item. The
- * interaction between colour attributes and
- * the mono ones is also very implementation
- * dependent.
- *
- * The 39 and 49 attributes are likely to be
- * unimplemented.
- */
- for (int i = 0; i < term->esc_nargs; i++)
- switch (def(term->esc_args[i], 0)) {
- case 0: /* restore defaults */
- term->curr_attr = term->default_attr;
- term->curr_truecolour =
- term->basic_erase_char.truecolour;
- break;
- case 1: /* enable bold */
- compatibility(VT100AVO);
- term->curr_attr |= ATTR_BOLD;
- break;
- case 2: /* enable dim */
- compatibility(OTHER);
- term->curr_attr |= ATTR_DIM;
- break;
- case 21: /* (enable double underline) */
- compatibility(OTHER);
- case 4: /* enable underline */
- compatibility(VT100AVO);
- term->curr_attr |= ATTR_UNDER;
- break;
- case 5: /* enable blink */
- compatibility(VT100AVO);
- term->curr_attr |= ATTR_BLINK;
- break;
- case 6: /* SCO light bkgrd */
- compatibility(SCOANSI);
- term->blink_is_real = false;
- term->curr_attr |= ATTR_BLINK;
- term_schedule_tblink(term);
- break;
- case 7: /* enable reverse video */
- term->curr_attr |= ATTR_REVERSE;
- break;
- case 9: /* enable strikethrough */
- term->curr_attr |= ATTR_STRIKE;
- break;
- case 10: /* SCO acs off */
- compatibility(SCOANSI);
- if (term->no_remote_charset) break;
- term->sco_acs = 0; break;
- case 11: /* SCO acs on */
- compatibility(SCOANSI);
- if (term->no_remote_charset) break;
- term->sco_acs = 1; break;
- case 12: /* SCO acs on, |0x80 */
- compatibility(SCOANSI);
- if (term->no_remote_charset) break;
- term->sco_acs = 2; break;
- case 22: /* disable bold and dim */
- compatibility2(OTHER, VT220);
- term->curr_attr &= ~(ATTR_BOLD | ATTR_DIM);
- break;
- case 24: /* disable underline */
- compatibility2(OTHER, VT220);
- term->curr_attr &= ~ATTR_UNDER;
- break;
- case 25: /* disable blink */
- compatibility2(OTHER, VT220);
- term->curr_attr &= ~ATTR_BLINK;
- break;
- case 27: /* disable reverse video */
- compatibility2(OTHER, VT220);
- term->curr_attr &= ~ATTR_REVERSE;
- break;
- case 29: /* disable strikethrough */
- term->curr_attr &= ~ATTR_STRIKE;
- break;
- case 30:
- case 31:
- case 32:
- case 33:
- case 34:
- case 35:
- case 36:
- case 37:
- /* foreground */
- term->curr_truecolour.fg.enabled = false;
- term->curr_attr &= ~ATTR_FGMASK;
- term->curr_attr |=
- (term->esc_args[i] - 30)<<ATTR_FGSHIFT;
- break;
- case 90:
- case 91:
- case 92:
- case 93:
- case 94:
- case 95:
- case 96:
- case 97:
- /* aixterm-style bright foreground */
- term->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)<<ATTR_BGSHIFT;
- break;
- case 100:
- case 101:
- case 102:
- case 103:
- case 104:
- case 105:
- case 106:
- case 107:
- /* aixterm-style bright background */
- term->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;
- }
- }
- 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; it<term->cols ; 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; it<nbc; it++) {
- int ipos = term->wcTo[it].index;
- for (int j = 0; j < term->wcTo[it].nchars; j++) {
- if (ipos != BIDI_CHAR_INDEX_NONE) {
- term->ltemp[opos] = ldata->chars[ipos];
- if (term->ltemp[opos].cc_next)
- term->ltemp[opos].cc_next -= opos - ipos;
-
- if (j > 0)
- term->ltemp[opos].chr = UCSWIDE;
- else if (term->wcTo[it].origwc != term->wcTo[it].wc)
- term->ltemp[opos].chr = term->wcTo[it].wc;
- } else {
- term->ltemp[opos] = term->basic_erase_char;
- term->ltemp[opos].chr =
- j > 0 ? UCSWIDE : term->wcTo[it].origwc;
- }
- opos++;
- }
- }
- assert(opos == term->cols);
- term_bidi_cache_store(term, scr_y, ldata->chars,
- term->ltemp, term->wcTo,
- term->cols, ldata->size, ldata->trusted);
-
- lchars = term->ltemp;
- } else {
- lchars = term->post_bidi_cache[scr_y].chars;
- }
- } else {
- lchars = NULL;
- }
-
- return lchars;
-}
-
-static void do_paint_draw(Terminal *term, termline *ldata, int x, int y,
- wchar_t *ch, int ccount,
- unsigned long attr, truecolour tc)
-{
- if (ch[0] == TRUST_SIGIL_CHAR) {
- assert(ldata->trusted);
- assert(ccount == 1);
- assert(attr & ATTR_WIDE);
- wchar_t tch[2];
- tch[0] = tch[1] = L' ';
- win_draw_text(term->win, x, y, tch, 2, term->basic_erase_char.attr,
- ldata->lattr, term->basic_erase_char.truecolour);
- win_draw_trust_sigil(term->win, x, y);
- } else {
- win_draw_text(term->win, x, y, ch, ccount, attr, ldata->lattr, tc);
- if (attr & (TATTR_ACTCURS | TATTR_PASCURS))
- win_draw_cursor(term->win, x, y, ch, ccount,
- attr, ldata->lattr, tc);
- }
-}
-
-/*
- * Given a context, update the window.
- */
-static void do_paint(Terminal *term)
-{
- int i, j, our_curs_y, our_curs_x;
- int rv, cursor;
- pos scrpos;
- wchar_t *ch;
- size_t chlen;
- termchar *newline;
-
- chlen = 1024;
- ch = snewn(chlen, wchar_t);
-
- newline = snewn(term->cols, termchar);
-
- rv = (!term->rvideo ^ !term->in_vbell ? ATTR_REVERSE : 0);
-
- /* Depends on:
- * screen array, disptop, scrtop,
- * selection, rv,
- * blinkpc, blink_is_real, tblinker,
- * curs.y, curs.x, cblinker, blink_cur, cursor_on, has_focus, wrapnext
- */
-
- /* Has the cursor position or type changed ? */
- if (term->cursor_on) {
- if (term->has_focus) {
- if (term->cblinker || !term->blink_cur)
- cursor = TATTR_ACTCURS;
- else
- cursor = 0;
- } else
- cursor = TATTR_PASCURS;
- if (term->wrapnext)
- cursor |= TATTR_RIGHTCURS;
- } else
- cursor = 0;
- our_curs_y = term->curs.y - term->disptop;
- {
- /*
- * Adjust the cursor position:
- * - for bidi
- * - in the case where it's resting on the right-hand half
- * of a CJK wide character. xterm's behaviour here,
- * which seems adequate to me, is to display the cursor
- * covering the _whole_ character, exactly as if it were
- * one space to the left.
- */
- termline *ldata = lineptr(term->curs.y);
- termchar *lchars;
-
- our_curs_x = term->curs.x;
-
- if ( (lchars = term_bidi_line(term, ldata, our_curs_y)) != NULL) {
- our_curs_x = term->post_bidi_cache[our_curs_y].forward[our_curs_x];
- } else
- lchars = ldata->chars;
-
- if (our_curs_x > 0 &&
- lchars[our_curs_x].chr == UCSWIDE)
- our_curs_x--;
-
- unlineptr(ldata);
- }
-
- /*
- * If the cursor is not where it was last time we painted, and
- * its previous position is visible on screen, invalidate its
- * previous position.
- */
- if (term->dispcursy >= 0 &&
- (term->curstype != cursor ||
- term->dispcursy != our_curs_y ||
- term->dispcursx != our_curs_x)) {
- termchar *dispcurs = term->disptext[term->dispcursy]->chars +
- term->dispcursx;
-
- if (term->dispcursx > 0 && dispcurs->chr == UCSWIDE)
- dispcurs[-1].attr |= ATTR_INVALID;
- if (term->dispcursx < term->cols-1 && dispcurs[1].chr == UCSWIDE)
- dispcurs[1].attr |= ATTR_INVALID;
- dispcurs->attr |= ATTR_INVALID;
-
- term->curstype = 0;
- }
- term->dispcursx = term->dispcursy = -1;
-
- /* The normal screen data */
- for (i = 0; i < term->rows; i++) {
- termline *ldata;
- termchar *lchars;
- bool dirty_line, dirty_run, selected;
- unsigned long attr = 0, cset = 0;
- int start = 0;
- int ccount = 0;
- bool last_run_dirty = false;
- int laststart;
- bool dirtyrect;
- int *backward;
- truecolour tc;
-
- scrpos.y = i + term->disptop;
- ldata = lineptr(scrpos.y);
-
- /* Do Arabic shaping and bidi. */
- lchars = term_bidi_line(term, ldata, i);
- if (lchars) {
- backward = term->post_bidi_cache[i].backward;
- } else {
- lchars = ldata->chars;
- backward = NULL;
- }
-
- /*
- * First loop: work along the line deciding what we want
- * each character cell to look like.
- */
- for (j = 0; j < term->cols; j++) {
- unsigned long tattr, tchar;
- termchar *d = lchars + j;
- scrpos.x = backward ? backward[j] : j;
-
- tchar = d->chr;
- tattr = d->attr;
-
- if (!term->ansi_colour)
- tattr = (tattr & ~(ATTR_FGMASK | ATTR_BGMASK)) |
- ATTR_DEFFG | ATTR_DEFBG;
-
- if (!term->xterm_256_colour) {
- int colour;
- colour = (tattr & ATTR_FGMASK) >> ATTR_FGSHIFT;
- if (colour >= 16 && colour < 256)
- tattr = (tattr &~ ATTR_FGMASK) | ATTR_DEFFG;
- colour = (tattr & ATTR_BGMASK) >> ATTR_BGSHIFT;
- if (colour >= 16 && colour < 256)
- tattr = (tattr &~ ATTR_BGMASK) | ATTR_DEFBG;
- }
-
- if (term->true_colour) {
- tc = d->truecolour;
- } else {
- tc.fg = tc.bg = optionalrgb_none;
- }
-
- switch (tchar & CSET_MASK) {
- case CSET_ASCII:
- tchar = term->ucsdata->unitab_line[tchar & 0xFF];
- break;
- case CSET_LINEDRW:
- tchar = term->ucsdata->unitab_xterm[tchar & 0xFF];
- break;
- case CSET_SCOACS:
- tchar = term->ucsdata->unitab_scoacs[tchar&0xFF];
- break;
- }
- if (j < term->cols-1 && d[1].chr == UCSWIDE)
- tattr |= ATTR_WIDE;
-
- /* Video reversing things */
- if (term->selstate == DRAGGING || term->selstate == SELECTED) {
- if (term->seltype == LEXICOGRAPHIC)
- selected = (posle(term->selstart, scrpos) &&
- poslt(scrpos, term->selend));
- else
- selected = (posPle(term->selstart, scrpos) &&
- posPle_left(scrpos, term->selend));
- } else
- selected = false;
- tattr = (tattr ^ rv
- ^ (selected ? ATTR_REVERSE : 0));
-
- /* 'Real' blinking ? */
- if (term->blink_is_real && (tattr & ATTR_BLINK)) {
- if (term->has_focus && term->tblinker) {
- tchar = term->ucsdata->unitab_line[(unsigned char)' '];
- }
- tattr &= ~ATTR_BLINK;
- }
-
- /*
- * Check the font we'll _probably_ be using to see if
- * the character is wide when we don't want it to be.
- */
- if (tchar != term->disptext[i]->chars[j].chr ||
- tattr != (term->disptext[i]->chars[j].attr &~
- (ATTR_NARROW | DATTR_MASK))) {
- if ((tattr & ATTR_WIDE) == 0 &&
- win_char_width(term->win, tchar) == 2)
- tattr |= ATTR_NARROW;
- } else if (term->disptext[i]->chars[j].attr & ATTR_NARROW)
- tattr |= ATTR_NARROW;
-
- if (i == our_curs_y && j == our_curs_x) {
- tattr |= cursor;
- term->curstype = cursor;
- term->dispcursx = j;
- term->dispcursy = i;
- }
-
- /* FULL-TERMCHAR */
- newline[j].attr = tattr;
- newline[j].chr = tchar;
- newline[j].truecolour = tc;
- /* Combining characters are still read from lchars */
- newline[j].cc_next = 0;
- }
-
- /*
- * Now loop over the line again, noting where things have
- * changed.
- *
- * During this loop, we keep track of where we last saw
- * DATTR_STARTRUN. Any mismatch automatically invalidates
- * _all_ of the containing run that was last printed: that
- * is, any rectangle that was drawn in one go in the
- * previous update should be either left completely alone
- * or overwritten in its entirety. This, along with the
- * expectation that front ends clip all text runs to their
- * bounding rectangle, should solve any possible problems
- * with fonts that overflow their character cells.
- */
- laststart = 0;
- dirtyrect = false;
- for (j = 0; j < term->cols; j++) {
- if (term->disptext[i]->chars[j].attr & DATTR_STARTRUN) {
- laststart = j;
- dirtyrect = false;
- }
-
- if (term->disptext[i]->chars[j].chr != newline[j].chr ||
- (term->disptext[i]->chars[j].attr &~ DATTR_MASK)
- != newline[j].attr) {
- int k;
-
- if (!dirtyrect) {
- for (k = laststart; k < j; k++)
- term->disptext[i]->chars[k].attr |= ATTR_INVALID;
-
- dirtyrect = true;
- }
- }
-
- if (dirtyrect)
- term->disptext[i]->chars[j].attr |= ATTR_INVALID;
- }
-
- /*
- * Finally, loop once more and actually do the drawing.
- */
- dirty_run = dirty_line = (ldata->lattr !=
- term->disptext[i]->lattr);
- term->disptext[i]->lattr = ldata->lattr;
-
- tc = term->erase_char.truecolour;
- for (j = 0; j < term->cols; j++) {
- unsigned long tattr, tchar;
- bool break_run, do_copy;
- termchar *d = lchars + j;
-
- tattr = newline[j].attr;
- tchar = newline[j].chr;
-
- if ((term->disptext[i]->chars[j].attr ^ tattr) & ATTR_WIDE)
- dirty_line = true;
-
- break_run = ((tattr ^ attr) & term->attr_mask) != 0;
-
- if (!truecolour_equal(newline[j].truecolour, tc))
- break_run = true;
-
-#ifdef USES_VTLINE_HACK
- /* Special hack for VT100 Linedraw glyphs */
- if ((tchar >= 0x23BA && tchar <= 0x23BD) ||
- (j > 0 && (newline[j-1].chr >= 0x23BA &&
- newline[j-1].chr <= 0x23BD)))
- break_run = true;
-#endif
-
- /*
- * Separate out sequences of characters that have the
- * same CSET, if that CSET is a magic one.
- */
- if (CSET_OF(tchar) != cset)
- break_run = true;
-
- /*
- * Break on both sides of any combined-character cell.
- */
- if (d->cc_next != 0 ||
- (j > 0 && d[-1].cc_next != 0))
- break_run = true;
-
- /*
- * Break on both sides of a trust sigil.
- */
- if (d->chr == TRUST_SIGIL_CHAR ||
- (j >= 2 && d[-1].chr == UCSWIDE &&
- d[-2].chr == TRUST_SIGIL_CHAR))
- break_run = true;
-
- if (!term->ucsdata->dbcs_screenfont && !dirty_line) {
- if (term->disptext[i]->chars[j].chr == tchar &&
- (term->disptext[i]->chars[j].attr &~ DATTR_MASK) == tattr)
- break_run = true;
- else if (!dirty_run && ccount == 1)
- break_run = true;
- }
-
- if (break_run) {
- if ((dirty_run || last_run_dirty) && ccount > 0)
- do_paint_draw(term, ldata, start, i, ch, ccount, attr, tc);
- start = j;
- ccount = 0;
- attr = tattr;
- tc = newline[j].truecolour;
- cset = CSET_OF(tchar);
- if (term->ucsdata->dbcs_screenfont)
- last_run_dirty = dirty_run;
- dirty_run = dirty_line;
- }
-
- do_copy = false;
- if (!termchars_equal_override(&term->disptext[i]->chars[j],
- d, tchar, tattr)) {
- do_copy = true;
- dirty_run = true;
- }
-
- sgrowarrayn(ch, chlen, ccount, 2);
-
-#ifdef PLATFORM_IS_UTF16
- if (tchar > 0x10000 && tchar < 0x110000) {
- ch[ccount++] = (wchar_t) HIGH_SURROGATE_OF(tchar);
- ch[ccount++] = (wchar_t) LOW_SURROGATE_OF(tchar);
- } else
-#endif /* PLATFORM_IS_UTF16 */
- ch[ccount++] = (wchar_t) tchar;
-
- if (d->cc_next) {
- termchar *dd = d;
-
- while (dd->cc_next) {
- unsigned long schar;
-
- dd += dd->cc_next;
-
- schar = dd->chr;
- switch (schar & CSET_MASK) {
- case CSET_ASCII:
- schar = term->ucsdata->unitab_line[schar & 0xFF];
- break;
- case CSET_LINEDRW:
- schar = term->ucsdata->unitab_xterm[schar & 0xFF];
- break;
- case CSET_SCOACS:
- schar = term->ucsdata->unitab_scoacs[schar&0xFF];
- break;
- }
-
- sgrowarrayn(ch, chlen, ccount, 2);
-
-#ifdef PLATFORM_IS_UTF16
- if (schar > 0x10000 && schar < 0x110000) {
- ch[ccount++] = (wchar_t) HIGH_SURROGATE_OF(schar);
- ch[ccount++] = (wchar_t) LOW_SURROGATE_OF(schar);
- } else
-#endif /* PLATFORM_IS_UTF16 */
- ch[ccount++] = (wchar_t) schar;
- }
-
- attr |= TATTR_COMBINING;
- }
-
- if (do_copy) {
- copy_termchar(term->disptext[i], j, d);
- term->disptext[i]->chars[j].chr = tchar;
- term->disptext[i]->chars[j].attr = tattr;
- term->disptext[i]->chars[j].truecolour = tc;
- if (start == j)
- term->disptext[i]->chars[j].attr |= DATTR_STARTRUN;
- }
-
- /* If it's a wide char step along to the next one. */
- if (tattr & ATTR_WIDE) {
- if (++j < term->cols) {
- d++;
- /*
- * By construction above, the cursor should not
- * be on the right-hand half of this character.
- * Ever.
- */
- assert(!(i == our_curs_y && j == our_curs_x));
- if (!termchars_equal(&term->disptext[i]->chars[j], d))
- dirty_run = true;
- copy_termchar(term->disptext[i], j, d);
- }
- }
- }
- if (dirty_run && ccount > 0)
- do_paint_draw(term, ldata, start, i, ch, ccount, attr, tc);
-
- unlineptr(ldata);
- }
-
- sfree(newline);
- sfree(ch);
-}
-
-/*
- * Invalidate the whole screen so it will be repainted in full.
- */
-void term_invalidate(Terminal *term)
-{
- int i, j;
-
- for (i = 0; i < term->rows; i++)
- for (j = 0; j < term->cols; j++)
- term->disptext[i]->chars[j].attr |= ATTR_INVALID;
-
- term_schedule_update(term);
-}
-
-/*
- * Paint the window in response to a WM_PAINT message.
- */
-void term_paint(Terminal *term,
- int left, int top, int right, int bottom, bool immediately)
-{
- int i, j;
- if (left < 0) left = 0;
- if (top < 0) top = 0;
- if (right >= term->cols) right = term->cols-1;
- if (bottom >= term->rows) bottom = term->rows-1;
-
- for (i = top; i <= bottom && i < term->rows; i++) {
- if ((term->disptext[i]->lattr & LATTR_MODE) == LATTR_NORM)
- for (j = left; j <= right && j < term->cols; j++)
- term->disptext[i]->chars[j].attr |= ATTR_INVALID;
- else
- for (j = left / 2; j <= right / 2 + 1 && j < term->cols; j++)
- term->disptext[i]->chars[j].attr |= ATTR_INVALID;
- }
-
- if (immediately) {
- do_paint(term);
- } else {
- term_schedule_update(term);
- }
-}
-
-/*
- * Attempt to scroll the scrollback. The second parameter gives the
- * position we want to scroll to; the first is +1 to denote that
- * this position is relative to the beginning of the scrollback, -1
- * to denote it is relative to the end, and 0 to denote that it is
- * relative to the current position.
- */
-void term_scroll(Terminal *term, int rel, int where)
-{
- int sbtop = -sblines(term);
-
- term->disptop = (rel < 0 ? 0 : rel > 0 ? sbtop : term->disptop) + where;
- if (term->disptop < sbtop)
- term->disptop = sbtop;
- if (term->disptop > 0)
- term->disptop = 0;
- term->win_scrollbar_update_pending = true;
- term_schedule_update(term);
-}
-
-/*
- * Scroll the scrollback to centre it on the beginning or end of the
- * current selection, if any.
- */
-void term_scroll_to_selection(Terminal *term, int which_end)
-{
- pos target;
- int y;
- int sbtop = -sblines(term);
-
- if (term->selstate != SELECTED)
- return;
- if (which_end)
- target = term->selend;
- else
- target = term->selstart;
-
- y = target.y - term->rows/2;
- if (y < sbtop)
- y = sbtop;
- else if (y > 0)
- y = 0;
- term_scroll(term, -1, y);
-}
-
-/*
- * Helper routine for clipme(): growing buffer.
- */
-typedef struct {
- size_t bufsize; /* amount of allocated space in textbuf/attrbuf */
- size_t bufpos; /* amount of actual data */
- wchar_t *textbuf; /* buffer for copied text */
- wchar_t *textptr; /* = textbuf + bufpos (current insertion point) */
- int *attrbuf; /* buffer for copied attributes */
- int *attrptr; /* = attrbuf + bufpos */
- truecolour *tcbuf; /* buffer for copied colours */
- truecolour *tcptr; /* = tcbuf + bufpos */
-} clip_workbuf;
-
-static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr, truecolour tc)
-{
- if (b->bufpos >= b->bufsize) {
- sgrowarray(b->textbuf, b->bufsize, b->bufpos);
- b->textptr = b->textbuf + b->bufpos;
- b->attrbuf = sresize(b->attrbuf, b->bufsize, int);
- b->attrptr = b->attrbuf + b->bufpos;
- b->tcbuf = sresize(b->tcbuf, b->bufsize, truecolour);
- b->tcptr = b->tcbuf + b->bufpos;
- }
- *b->textptr++ = chr;
- *b->attrptr++ = attr;
- *b->tcptr++ = tc;
- b->bufpos++;
-}
-
-static void clipme(Terminal *term, pos top, pos bottom, bool rect, bool desel,
- const int *clipboards, int n_clipboards)
-{
- clip_workbuf buf;
- int old_top_x;
- int attr;
- truecolour tc;
-
- buf.bufsize = 5120;
- buf.bufpos = 0;
- buf.textptr = buf.textbuf = snewn(buf.bufsize, wchar_t);
- buf.attrptr = buf.attrbuf = snewn(buf.bufsize, int);
- buf.tcptr = buf.tcbuf = snewn(buf.bufsize, truecolour);
-
- old_top_x = top.x; /* needed for rect==1 */
-
- while (poslt(top, bottom)) {
- bool nl = false;
- termline *ldata = lineptr(top.y);
- pos nlpos;
-
- /*
- * nlpos will point at the maximum position on this line we
- * should copy up to. So we start it at the end of the
- * line...
- */
- nlpos.y = top.y;
- nlpos.x = term->cols;
-
- /*
- * ... move it backwards if there's unused space at the end
- * of the line (and also set `nl' if this is the case,
- * because in normal selection mode this means we need a
- * newline at the end)...
- */
- if (!(ldata->lattr & LATTR_WRAPPED)) {
- while (nlpos.x &&
- IS_SPACE_CHR(ldata->chars[nlpos.x - 1].chr) &&
- !ldata->chars[nlpos.x - 1].cc_next &&
- poslt(top, nlpos))
- decpos(nlpos);
- if (poslt(nlpos, bottom))
- nl = true;
- } else {
- if (ldata->trusted) {
- /* A wrapped line with a trust sigil on it terminates
- * a few characters earlier. */
- nlpos.x = (nlpos.x < TRUST_SIGIL_WIDTH ? 0 :
- nlpos.x - TRUST_SIGIL_WIDTH);
- }
- if (ldata->lattr & LATTR_WRAPPED2) {
- /* Ignore the last char on the line in a WRAPPED2 line. */
- decpos(nlpos);
- }
- }
-
- /*
- * ... and then clip it to the terminal x coordinate if
- * we're doing rectangular selection. (In this case we
- * still did the above, so that copying e.g. the right-hand
- * column from a table doesn't fill with spaces on the
- * right.)
- */
- if (rect) {
- if (nlpos.x > bottom.x)
- nlpos.x = bottom.x;
- nl = (top.y < bottom.y);
- }
-
- while (poslt(top, bottom) && poslt(top, nlpos)) {
-#if 0
- char cbuf[16], *p;
- sprintf(cbuf, "<U+%04x>", (ldata[top.x] & 0xFFFF));
-#else
- wchar_t cbuf[16], *p;
- int c;
- int x = top.x;
-
- if (ldata->chars[x].chr == UCSWIDE) {
- top.x++;
- continue;
- }
-
- while (1) {
- int uc = ldata->chars[x].chr;
- attr = ldata->chars[x].attr;
- tc = ldata->chars[x].truecolour;
-
- switch (uc & CSET_MASK) {
- case CSET_LINEDRW:
- if (!term->rawcnp) {
- uc = term->ucsdata->unitab_xterm[uc & 0xFF];
- break;
- }
- case CSET_ASCII:
- uc = term->ucsdata->unitab_line[uc & 0xFF];
- break;
- case CSET_SCOACS:
- uc = term->ucsdata->unitab_scoacs[uc&0xFF];
- break;
- }
- switch (uc & CSET_MASK) {
- case CSET_ACP:
- uc = term->ucsdata->unitab_font[uc & 0xFF];
- break;
- case CSET_OEMCP:
- uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
- break;
- }
-
- c = (uc & ~CSET_MASK);
-#ifdef PLATFORM_IS_UTF16
- if (uc > 0x10000 && uc < 0x110000) {
- cbuf[0] = 0xD800 | ((uc - 0x10000) >> 10);
- cbuf[1] = 0xDC00 | ((uc - 0x10000) & 0x3FF);
- cbuf[2] = 0;
- } else
-#endif
- {
- cbuf[0] = uc;
- cbuf[1] = 0;
- }
-
- if (DIRECT_FONT(uc)) {
- if (c >= ' ' && c != 0x7F) {
- char buf[4];
- WCHAR wbuf[4];
- int rv;
- if (is_dbcs_leadbyte(term->ucsdata->font_codepage, (BYTE) c)) {
- buf[0] = c;
- buf[1] = (char) (0xFF & ldata->chars[top.x + 1].chr);
- rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 2, wbuf, 4);
- top.x++;
- } else {
- buf[0] = c;
- rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 1, wbuf, 4);
- }
-
- if (rv > 0) {
- memcpy(cbuf, wbuf, rv * sizeof(wchar_t));
- cbuf[rv] = 0;
- }
- }
- }
-#endif
-
- for (p = cbuf; *p; p++)
- clip_addchar(&buf, *p, attr, tc);
-
- if (ldata->chars[x].cc_next)
- x += ldata->chars[x].cc_next;
- else
- break;
- }
- top.x++;
- }
- if (nl) {
- int i;
- for (i = 0; i < sel_nl_sz; i++)
- clip_addchar(&buf, sel_nl[i], 0, term->basic_erase_char.truecolour);
- }
- top.y++;
- top.x = rect ? old_top_x : 0;
-
- unlineptr(ldata);
- }
-#if SELECTION_NUL_TERMINATED
- clip_addchar(&buf, 0, 0, term->basic_erase_char.truecolour);
-#endif
- /* Finally, transfer all that to the clipboard(s). */
- {
- int i;
- bool clip_local = false;
- for (i = 0; i < n_clipboards; i++) {
- if (clipboards[i] == CLIP_LOCAL) {
- clip_local = true;
- } else if (clipboards[i] != CLIP_NULL) {
- win_clip_write(
- term->win, clipboards[i], buf.textbuf, buf.attrbuf,
- buf.tcbuf, buf.bufpos, desel);
- }
- }
- if (clip_local) {
- sfree(term->last_selected_text);
- sfree(term->last_selected_attr);
- sfree(term->last_selected_tc);
- term->last_selected_text = buf.textbuf;
- term->last_selected_attr = buf.attrbuf;
- term->last_selected_tc = buf.tcbuf;
- term->last_selected_len = buf.bufpos;
- } else {
- sfree(buf.textbuf);
- sfree(buf.attrbuf);
- sfree(buf.tcbuf);
- }
- }
-}
-
-void term_copyall(Terminal *term, const int *clipboards, int n_clipboards)
-{
- pos top;
- pos bottom;
- tree234 *screen = term->screen;
- top.y = -sblines(term);
- top.x = 0;
- bottom.y = find_last_nonempty_line(term, screen);
- bottom.x = term->cols;
- clipme(term, top, bottom, false, true, clipboards, n_clipboards);
-}
-
-static void paste_from_clip_local(void *vterm)
-{
- Terminal *term = (Terminal *)vterm;
- term_do_paste(term, term->last_selected_text, term->last_selected_len);
-}
-
-void term_request_copy(Terminal *term, const int *clipboards, int n_clipboards)
-{
- int i;
- for (i = 0; i < n_clipboards; i++) {
- assert(clipboards[i] != CLIP_LOCAL);
- if (clipboards[i] != CLIP_NULL) {
- win_clip_write(term->win, clipboards[i],
- term->last_selected_text, term->last_selected_attr,
- term->last_selected_tc, term->last_selected_len,
- false);
- }
- }
-}
-
-void term_request_paste(Terminal *term, int clipboard)
-{
- switch (clipboard) {
- case CLIP_NULL:
- /* Do nothing: CLIP_NULL never has data in it. */
- break;
- case CLIP_LOCAL:
- queue_toplevel_callback(paste_from_clip_local, term);
- break;
- default:
- win_clip_request_paste(term->win, clipboard);
- break;
- }
-}
-
-/*
- * The wordness array is mainly for deciding the disposition of the
- * US-ASCII characters.
- */
-static int wordtype(Terminal *term, int uc)
-{
- struct ucsword {
- int start, end, ctype;
- };
- static const struct ucsword ucs_words[] = {
- {
- 128, 160, 0}, {
- 161, 191, 1}, {
- 215, 215, 1}, {
- 247, 247, 1}, {
- 0x037e, 0x037e, 1}, /* Greek question mark */
- {
- 0x0387, 0x0387, 1}, /* Greek ano teleia */
- {
- 0x055a, 0x055f, 1}, /* Armenian punctuation */
- {
- 0x0589, 0x0589, 1}, /* Armenian full stop */
- {
- 0x0700, 0x070d, 1}, /* Syriac punctuation */
- {
- 0x104a, 0x104f, 1}, /* Myanmar punctuation */
- {
- 0x10fb, 0x10fb, 1}, /* Georgian punctuation */
- {
- 0x1361, 0x1368, 1}, /* Ethiopic punctuation */
- {
- 0x166d, 0x166e, 1}, /* Canadian Syl. punctuation */
- {
- 0x17d4, 0x17dc, 1}, /* Khmer punctuation */
- {
- 0x1800, 0x180a, 1}, /* Mongolian punctuation */
- {
- 0x2000, 0x200a, 0}, /* Various spaces */
- {
- 0x2070, 0x207f, 2}, /* superscript */
- {
- 0x2080, 0x208f, 2}, /* subscript */
- {
- 0x200b, 0x27ff, 1}, /* punctuation and symbols */
- {
- 0x3000, 0x3000, 0}, /* ideographic space */
- {
- 0x3001, 0x3020, 1}, /* ideographic punctuation */
- {
- 0x303f, 0x309f, 3}, /* Hiragana */
- {
- 0x30a0, 0x30ff, 3}, /* Katakana */
- {
- 0x3300, 0x9fff, 3}, /* CJK Ideographs */
- {
- 0xac00, 0xd7a3, 3}, /* Hangul Syllables */
- {
- 0xf900, 0xfaff, 3}, /* CJK Ideographs */
- {
- 0xfe30, 0xfe6b, 1}, /* punctuation forms */
- {
- 0xff00, 0xff0f, 1}, /* half/fullwidth ASCII */
- {
- 0xff1a, 0xff20, 1}, /* half/fullwidth ASCII */
- {
- 0xff3b, 0xff40, 1}, /* half/fullwidth ASCII */
- {
- 0xff5b, 0xff64, 1}, /* half/fullwidth ASCII */
- {
- 0xfff0, 0xffff, 0}, /* half/fullwidth ASCII */
- {
- 0, 0, 0}
- };
- const struct ucsword *wptr;
-
- switch (uc & CSET_MASK) {
- case CSET_LINEDRW:
- uc = term->ucsdata->unitab_xterm[uc & 0xFF];
- break;
- case CSET_ASCII:
- uc = term->ucsdata->unitab_line[uc & 0xFF];
- break;
- case CSET_SCOACS:
- uc = term->ucsdata->unitab_scoacs[uc&0xFF];
- break;
- }
- switch (uc & CSET_MASK) {
- case CSET_ACP:
- uc = term->ucsdata->unitab_font[uc & 0xFF];
- break;
- case CSET_OEMCP:
- uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
- break;
- }
-
- /* For DBCS fonts I can't do anything useful. Even this will sometimes
- * fail as there's such a thing as a double width space. :-(
- */
- if (term->ucsdata->dbcs_screenfont &&
- term->ucsdata->font_codepage == term->ucsdata->line_codepage)
- return (uc != ' ');
-
- if (uc < 0x80)
- return term->wordness[uc];
-
- for (wptr = ucs_words; wptr->start; wptr++) {
- if (uc >= wptr->start && uc <= wptr->end)
- return wptr->ctype;
- }
-
- return 2;
-}
-
-static int line_cols(Terminal *term, termline *ldata)
-{
- int cols = term->cols;
- if (ldata->trusted) {
- cols -= TRUST_SIGIL_WIDTH;
- }
- if (ldata->lattr & LATTR_WRAPPED2)
- cols--;
- if (cols < 0)
- cols = 0;
- return cols;
-}
-
-/*
- * Spread the selection outwards according to the selection mode.
- */
-static pos sel_spread_half(Terminal *term, pos p, int dir)
-{
- termline *ldata;
- short wvalue;
- int topy = -sblines(term);
-
- ldata = lineptr(p.y);
-
- switch (term->selmode) {
- case SM_CHAR:
- /*
- * In this mode, every character is a separate unit, except
- * for runs of spaces at the end of a non-wrapping line.
- */
- if (!(ldata->lattr & LATTR_WRAPPED)) {
- termchar *q = ldata->chars + line_cols(term, ldata);
- while (q > ldata->chars &&
- IS_SPACE_CHR(q[-1].chr) && !q[-1].cc_next)
- q--;
- if (q == ldata->chars + term->cols)
- q--;
- if (p.x >= q - ldata->chars)
- p.x = (dir == -1 ? q - ldata->chars : term->cols - 1);
- }
- break;
- case SM_WORD:
- /*
- * In this mode, the units are maximal runs of characters
- * whose `wordness' has the same value.
- */
- wvalue = wordtype(term, UCSGET(ldata->chars, p.x));
- if (dir == +1) {
- while (1) {
- int maxcols = line_cols(term, ldata);
- if (p.x < maxcols-1) {
- if (wordtype(term, UCSGET(ldata->chars, p.x+1)) == wvalue)
- p.x++;
- else
- break;
- } else {
- if (p.y+1 < term->rows &&
- (ldata->lattr & LATTR_WRAPPED)) {
- termline *ldata2;
- ldata2 = lineptr(p.y+1);
- if (wordtype(term, UCSGET(ldata2->chars, 0))
- == wvalue) {
- p.x = 0;
- p.y++;
- unlineptr(ldata);
- ldata = ldata2;
- } else {
- unlineptr(ldata2);
- break;
- }
- } else
- break;
- }
- }
- } else {
- while (1) {
- if (p.x > 0) {
- if (wordtype(term, UCSGET(ldata->chars, p.x-1)) == wvalue)
- p.x--;
- else
- break;
- } else {
- termline *ldata2;
- int maxcols;
- if (p.y <= topy)
- break;
- ldata2 = lineptr(p.y-1);
- maxcols = line_cols(term, ldata2);
- if (ldata2->lattr & LATTR_WRAPPED) {
- if (wordtype(term, UCSGET(ldata2->chars, maxcols-1))
- == wvalue) {
- p.x = maxcols-1;
- p.y--;
- unlineptr(ldata);
- ldata = ldata2;
- } else {
- unlineptr(ldata2);
- break;
- }
- } else
- break;
- }
- }
- }
- break;
- case SM_LINE:
- /*
- * In this mode, every line is a unit.
- */
- p.x = (dir == -1 ? 0 : term->cols - 1);
- break;
- }
-
- unlineptr(ldata);
- return p;
-}
-
-static void sel_spread(Terminal *term)
-{
- if (term->seltype == LEXICOGRAPHIC) {
- term->selstart = sel_spread_half(term, term->selstart, -1);
- decpos(term->selend);
- term->selend = sel_spread_half(term, term->selend, +1);
- incpos(term->selend);
- }
-}
-
-static void term_paste_callback(void *vterm)
-{
- Terminal *term = (Terminal *)vterm;
-
- if (term->paste_len == 0)
- return;
-
- while (term->paste_pos < term->paste_len) {
- int n = 0;
- while (n + term->paste_pos < term->paste_len) {
- if (term->paste_buffer[term->paste_pos + n++] == '\015')
- break;
- }
- if (term->ldisc) {
- strbuf *buf = term_input_data_from_unicode(
- term, term->paste_buffer + term->paste_pos, n);
- term_keyinput_internal(term, buf->s, buf->len, false);
- strbuf_free(buf);
- }
- term->paste_pos += n;
-
- if (term->paste_pos < term->paste_len) {
- queue_toplevel_callback(term_paste_callback, term);
- return;
- }
- }
- term_bracketed_paste_stop(term);
- sfree(term->paste_buffer);
- term->paste_buffer = NULL;
- term->paste_len = 0;
-}
-
-/*
- * Specialist string compare function. Returns true if the buffer of
- * alen wide characters starting at a has as a prefix the buffer of
- * blen characters starting at b.
- */
-static bool wstartswith(const wchar_t *a, size_t alen,
- const wchar_t *b, size_t blen)
-{
- return alen >= blen && !wcsncmp(a, b, blen);
-}
-
-void term_do_paste(Terminal *term, const wchar_t *data, int len)
-{
- const wchar_t *p;
- bool paste_controls = conf_get_bool(term->conf, CONF_paste_controls);
-
- /*
- * Pasting data into the terminal counts as a keyboard event (for
- * purposes of the 'Reset scrollback on keypress' config option),
- * unless the paste is zero-length.
- */
- if (len == 0)
- return;
- term_seen_key_event(term);
-
- if (term->paste_buffer)
- sfree(term->paste_buffer);
- term->paste_pos = term->paste_len = 0;
- term->paste_buffer = snewn(len + 12, wchar_t);
-
- if (term->bracketed_paste)
- term_bracketed_paste_start(term);
-
- p = data;
- while (p < data + len) {
- wchar_t wc = *p++;
-
- if (wc == sel_nl[0] &&
- wstartswith(p-1, data+len-(p-1), sel_nl, sel_nl_sz)) {
- /*
- * This is the (platform-dependent) sequence that the host
- * OS uses to represent newlines in clipboard data.
- * Normalise it to a press of CR.
- */
- p += sel_nl_sz - 1;
- wc = '\015';
- }
-
- if ((wc & ~(wint_t)0x9F) == 0) {
- /*
- * This is a control code, either in the range 0x00-0x1F
- * or 0x80-0x9F. We reject all of these in pastecontrols
- * mode, except for a small set of permitted ones.
- */
- if (!paste_controls) {
- /* In line with xterm 292, accepted control chars are:
- * CR, LF, tab, backspace. (And DEL, i.e. 0x7F, but
- * that's permitted by virtue of not matching the bit
- * mask that got us into this if statement, so we
- * don't have to permit it here. */
- static const unsigned mask =
- (1<<13) | (1<<10) | (1<<9) | (1<<8);
-
- if (wc > 15 || !((mask >> wc) & 1))
- continue;
- }
-
- if (wc == '\033' && term->bracketed_paste &&
- wstartswith(p-1, data+len-(p-1), L"\033[201~", 6)) {
- /*
- * Also, in bracketed-paste mode, reject the ESC
- * character that begins the end-of-paste sequence.
- */
- continue;
- }
- }
-
- term->paste_buffer[term->paste_len++] = wc;
- }
-
- /* Assume a small paste will be OK in one go. */
- if (term->paste_len < 256) {
- if (term->ldisc) {
- strbuf *buf = term_input_data_from_unicode(
- term, term->paste_buffer, term->paste_len);
- term_keyinput_internal(term, buf->s, buf->len, false);
- strbuf_free(buf);
- }
- if (term->paste_buffer)
- sfree(term->paste_buffer);
- term_bracketed_paste_stop(term);
- term->paste_buffer = NULL;
- term->paste_pos = term->paste_len = 0;
- }
-
- queue_toplevel_callback(term_paste_callback, term);
-}
-
-void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked,
- Mouse_Action a, int x, int y, bool shift, bool ctrl, bool alt)
-{
- pos selpoint;
- termline *ldata;
- bool raw_mouse = (term->xterm_mouse &&
- !term->no_mouse_rep &&
- !(term->mouse_override && shift));
- int default_seltype;
-
- if (y < 0) {
- y = 0;
- if (a == MA_DRAG && !raw_mouse)
- term_scroll(term, 0, -1);
- }
- if (y >= term->rows) {
- y = term->rows - 1;
- if (a == MA_DRAG && !raw_mouse)
- term_scroll(term, 0, +1);
- }
- if (x < 0) {
- if (y > 0 && !raw_mouse && term->seltype != RECTANGULAR) {
- /*
- * When we're using the mouse for normal raster-based
- * selection, dragging off the left edge of a terminal row
- * is treated the same as the right-hand end of the
- * previous row, in that it's considered to identify a
- * point _before_ the first character on row y.
- *
- * But if the mouse action is going to be used for
- * anything else - rectangular selection, or xterm mouse
- * tracking - then we disable this special treatment.
- */
- x = term->cols - 1;
- y--;
- } else
- x = 0;
- }
- if (x >= term->cols)
- x = term->cols - 1;
-
- selpoint.y = y + term->disptop;
- ldata = lineptr(selpoint.y);
-
- if ((ldata->lattr & LATTR_MODE) != LATTR_NORM)
- x /= 2;
-
- /*
- * Transform x through the bidi algorithm to find the _logical_
- * click point from the physical one.
- */
- if (term_bidi_line(term, ldata, y) != NULL) {
- x = term->post_bidi_cache[y].backward[x];
- }
-
- selpoint.x = x;
- unlineptr(ldata);
-
- /*
- * If we're in the middle of a selection operation, we ignore raw
- * mouse mode until it's done (we must have been not in raw mouse
- * mode when it started).
- * This makes use of Shift for selection reliable, and avoids the
- * host seeing mouse releases for which they never saw corresponding
- * presses.
- */
- if (raw_mouse &&
- (term->selstate != ABOUT_TO) && (term->selstate != DRAGGING)) {
- int encstate = 0, r, c;
- bool wheel;
- char abuf[32];
- int len = 0;
-
- if (term->ldisc) {
-
- switch (braw) {
- case MBT_LEFT:
- encstate = 0x00; /* left button down */
- wheel = false;
- break;
- case MBT_MIDDLE:
- encstate = 0x01;
- wheel = false;
- break;
- case MBT_RIGHT:
- encstate = 0x02;
- wheel = false;
- break;
- case MBT_WHEEL_UP:
- encstate = 0x40;
- wheel = true;
- break;
- case MBT_WHEEL_DOWN:
- encstate = 0x41;
- wheel = true;
- break;
- default:
- return;
- }
- if (wheel) {
- /* For mouse wheel buttons, we only ever expect to see
- * MA_CLICK actions, and we don't try to keep track of
- * the buttons being 'pressed' (since without matching
- * click/release pairs that's pointless). */
- if (a != MA_CLICK)
- return;
- } else switch (a) {
- case MA_DRAG:
- if (term->xterm_mouse == 1)
- return;
- encstate += 0x20;
- break;
- case MA_RELEASE:
- /* If multiple extensions are enabled, the xterm 1006 is used, so it's okay to check for only that */
- if (!term->xterm_extended_mouse)
- encstate = 0x03;
- term->mouse_is_down = 0;
- break;
- case MA_CLICK:
- if (term->mouse_is_down == braw)
- return;
- term->mouse_is_down = braw;
- break;
- default:
- return;
- }
- if (shift)
- encstate += 0x04;
- if (ctrl)
- encstate += 0x10;
- r = y + 1;
- c = x + 1;
-
- /* Check the extensions in decreasing order of preference. Encoding the release event above assumes that 1006 comes first. */
- if (term->xterm_extended_mouse) {
- len = sprintf(abuf, "\033[<%d;%d;%d%c", encstate, c, r, a == MA_RELEASE ? 'm' : 'M');
- } else if (term->urxvt_extended_mouse) {
- len = sprintf(abuf, "\033[%d;%d;%dM", encstate + 32, c, r);
- } else if (c <= 223 && r <= 223) {
- len = sprintf(abuf, "\033[M%c%c%c", encstate + 32, c + 32, r + 32);
- }
- if (len > 0)
- ldisc_send(term->ldisc, abuf, len, false);
- }
- return;
- }
-
- /*
- * Set the selection type (rectangular or normal) at the start
- * of a selection attempt, from the state of Alt.
- */
- if (!alt ^ !term->rect_select)
- default_seltype = RECTANGULAR;
- else
- default_seltype = LEXICOGRAPHIC;
-
- if (term->selstate == NO_SELECTION) {
- term->seltype = default_seltype;
- }
-
- if (bcooked == MBT_SELECT && a == MA_CLICK) {
- deselect(term);
- term->selstate = ABOUT_TO;
- term->seltype = default_seltype;
- term->selanchor = selpoint;
- term->selmode = SM_CHAR;
- } else if (bcooked == MBT_SELECT && (a == MA_2CLK || a == MA_3CLK)) {
- deselect(term);
- term->selmode = (a == MA_2CLK ? SM_WORD : SM_LINE);
- term->selstate = DRAGGING;
- term->selstart = term->selanchor = selpoint;
- term->selend = term->selstart;
- incpos(term->selend);
- sel_spread(term);
- } else if ((bcooked == MBT_SELECT && a == MA_DRAG) ||
- (bcooked == MBT_EXTEND && a != MA_RELEASE)) {
- if (a == MA_DRAG &&
- (term->selstate == NO_SELECTION || term->selstate == SELECTED)) {
- /*
- * This can happen if a front end has passed us a MA_DRAG
- * without a prior MA_CLICK. OS X GTK does so, for
- * example, if the initial button press was eaten by the
- * WM when it activated the window in the first place. The
- * nicest thing to do in this situation is to ignore
- * further drags, and wait for the user to click in the
- * window again properly if they want to select.
- */
- return;
- }
- if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint))
- return;
- if (bcooked == MBT_EXTEND && a != MA_DRAG &&
- term->selstate == SELECTED) {
- if (term->seltype == LEXICOGRAPHIC) {
- /*
- * For normal selection, we extend by moving
- * whichever end of the current selection is closer
- * to the mouse.
- */
- if (posdiff(selpoint, term->selstart) <
- posdiff(term->selend, term->selstart) / 2) {
- term->selanchor = term->selend;
- decpos(term->selanchor);
- } else {
- term->selanchor = term->selstart;
- }
- } else {
- /*
- * For rectangular selection, we have a choice of
- * _four_ places to put selanchor and selpoint: the
- * four corners of the selection.
- */
- if (2*selpoint.x < term->selstart.x + term->selend.x)
- term->selanchor.x = term->selend.x-1;
- else
- term->selanchor.x = term->selstart.x;
-
- if (2*selpoint.y < term->selstart.y + term->selend.y)
- term->selanchor.y = term->selend.y;
- else
- term->selanchor.y = term->selstart.y;
- }
- term->selstate = DRAGGING;
- }
- if (term->selstate != ABOUT_TO && term->selstate != DRAGGING)
- term->selanchor = selpoint;
- term->selstate = DRAGGING;
- if (term->seltype == LEXICOGRAPHIC) {
- /*
- * For normal selection, we set (selstart,selend) to
- * (selpoint,selanchor) in some order.
- */
- if (poslt(selpoint, term->selanchor)) {
- term->selstart = selpoint;
- term->selend = term->selanchor;
- incpos(term->selend);
- } else {
- term->selstart = term->selanchor;
- term->selend = selpoint;
- incpos(term->selend);
- }
- } else {
- /*
- * For rectangular selection, we may need to
- * interchange x and y coordinates (if the user has
- * dragged in the -x and +y directions, or vice versa).
- */
- term->selstart.x = min(term->selanchor.x, selpoint.x);
- term->selend.x = 1+max(term->selanchor.x, selpoint.x);
- term->selstart.y = min(term->selanchor.y, selpoint.y);
- term->selend.y = max(term->selanchor.y, selpoint.y);
- }
- sel_spread(term);
- } else if ((bcooked == MBT_SELECT || bcooked == MBT_EXTEND) &&
- a == MA_RELEASE) {
- if (term->selstate == DRAGGING) {
- /*
- * We've completed a selection. We now transfer the
- * data to the clipboard.
- */
- clipme(term, term->selstart, term->selend,
- (term->seltype == RECTANGULAR), false,
- term->mouse_select_clipboards,
- term->n_mouse_select_clipboards);
- term->selstate = SELECTED;
- } else
- term->selstate = NO_SELECTION;
- } else if (bcooked == MBT_PASTE
- && (a == MA_CLICK
-#if MULTICLICK_ONLY_EVENT
- || a == MA_2CLK || a == MA_3CLK
-#endif
- )) {
- term_request_paste(term, term->mouse_paste_clipboard);
- }
-
- /*
- * Since terminal output is suppressed during drag-selects, we
- * should make sure to write any pending output if one has just
- * finished.
- */
- if (term->selstate != DRAGGING)
- term_out(term);
- term_schedule_update(term);
-}
-
-int format_arrow_key(char *buf, Terminal *term, int xkey, bool ctrl)
-{
- 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
- /* 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);
- }
-
- return p - buf;
-}
-
-int format_function_key(char *buf, Terminal *term, int key_number,
- bool shift, bool ctrl)
-{
- char *p = buf;
-
- static const int key_number_to_tilde_code[] = {
- -1, /* no such key as F0 */
- 11, 12, 13, 14, 15, /*gap*/ 17, 18, 19, 20, 21, /*gap*/
- 23, 24, 25, 26, /*gap*/ 28, 29, /*gap*/ 31, 32, 33, 34,
- };
-
- assert(key_number > 0);
- assert(key_number < lenof(key_number_to_tilde_code));
-
- int index = (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;
-}
-
-int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key)
-{
- char *p = buf;
-
- int code;
- switch (key) {
- case SKK_HOME: code = 1; break;
- case SKK_INSERT: code = 2; break;
- case SKK_DELETE: code = 3; break;
- case SKK_END: code = 4; break;
- case SKK_PGUP: code = 5; break;
- case SKK_PGDN: code = 6; break;
- default: unreachable("bad small keypad key enum value");
- }
-
- /* Reorder edit keys to physical order */
- if (term->funky_type == FUNKY_VT400 && code <= 6)
- code = "\0\2\1\4\5\3\6"[code];
-
- if (term->vt52_mode && code > 0 && code <= 6) {
- p += sprintf(p, "\x1B%c", " HLMEIG"[code]);
- } else if (term->funky_type == FUNKY_SCO) {
- static const char codes[] = "HL.FIG";
- if (code == 3) {
- *p++ = '\x7F';
- } else {
- p += sprintf(p, "\x1B[%c", codes[code-1]);
- }
- } else if ((code == 1 || code == 4) && term->rxvt_homeend) {
- p += sprintf(p, code == 1 ? "\x1B[H" : "\x1BOw");
- } else {
- 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)
-{
- bufchain_add(&term->inbuf, data, len);
- term_added_data(term);
-
- /*
- * 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)
-{
- term->has_focus = has_focus;
- term_schedule_cblink(term);
-}
-
-/*
- * Provide "auto" settings for remote tty modes, suitable for an
- * application with a terminal window.
- */
-char *term_get_ttymode(Terminal *term, const char *mode)
-{
- const char *val = NULL;
- if (strcmp(mode, "ERASE") == 0) {
- val = term->bksp_is_delete ? "^?" : "^H";
- } else if (strcmp(mode, "IUTF8") == 0) {
- val = (term->ucsdata->line_codepage == CP_UTF8) ? "yes" : "no";
- }
- /* FIXME: perhaps we should set ONLCR based on lfhascr as well? */
- /* FIXME: or ECHO and friends based on local echo state? */
- return dupstr(val);
-}
-
-struct term_userpass_state {
- 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/TERMINAL.H b/TERMINAL.H
deleted file mode 100644
index c463f7a8..00000000
--- a/TERMINAL.H
+++ /dev/null
@@ -1,529 +0,0 @@
-/*
- * 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;
- 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)
-
-#define OSC_STR_MAX 2048
- int osc_strlen;
- char osc_string[OSC_STR_MAX + 1];
- bool osc_w;
-
- char id_string[1024];
-
- unsigned char *tabs;
-
- enum {
- TOPLEVEL,
- SEEN_ESC,
- SEEN_CSI,
- SEEN_OSC,
- SEEN_OSC_W,
-
- DO_CTRLS,
-
- SEEN_OSC_P,
- OSC_STRING, OSC_MAYBE_ST,
- VT52_ESC,
- VT52_Y1,
- VT52_Y2,
- VT52_FG,
- VT52_BG
- } termstate;
-
- enum {
- NO_SELECTION, ABOUT_TO, DRAGGING, SELECTED
- } selstate;
- enum {
- LEXICOGRAPHIC, RECTANGULAR
- } seltype;
- enum {
- SM_CHAR, SM_WORD, SM_LINE
- } selmode;
- pos selstart, selend, selanchor;
-
- short wordness[256];
-
- /* Mask of attributes to pay attention to when painting. */
- int attr_mask;
-
- wchar_t *paste_buffer;
- int paste_len, paste_pos;
-
- Backend *backend;
-
- Ldisc *ldisc;
-
- TermWin *win;
-
- LogContext *logctx;
-
- struct unicode_data *ucsdata;
-
- unsigned long last_graphic_char;
-
- /*
- * We maintain a full copy of a Conf here, not merely a pointer
- * to it. That way, when we're passed a new one for
- * reconfiguration, we can check the differences and adjust the
- * _current_ setting of (e.g.) auto wrap mode rather than only
- * the default.
- */
- Conf *conf;
-
- /*
- * GUI implementations of seat_output call term_out, but it can
- * also be called from the ldisc if the ldisc is called _within_
- * term_out. So we have to guard against re-entrancy - if
- * seat_output is called recursively like this, it will simply add
- * data to the end of the buffer term_out is in the process of
- * working through.
- */
- bool in_term_out;
-
- /*
- * We don't permit window updates too close together, to avoid CPU
- * churn pointlessly redrawing the window faster than the user can
- * read. So after an update, we set window_update_cooldown = true
- * and schedule a timer to reset it to false. In between those
- * times, window updates are not performed, and instead we set
- * window_update_pending = true, which will remind us to perform
- * the deferred redraw when the cooldown period ends and
- * window_update_cooldown is reset to false.
- */
- bool window_update_pending, window_update_cooldown;
- long window_update_cooldown_end;
-
- /*
- * Track pending blinks and tblinks.
- */
- bool tblink_pending, cblink_pending;
- long next_tblink, next_cblink;
-
- /*
- * These are buffers used by the bidi and Arabic shaping code.
- */
- termchar *ltemp;
- int ltemp_size;
- bidi_char *wcFrom, *wcTo;
- int wcFromTo_size;
- struct bidi_cache_entry *pre_bidi_cache, *post_bidi_cache;
- size_t bidi_cache_size;
-
- /*
- * Current trust state, used to annotate every line of the
- * terminal that a graphic character is output to.
- */
- bool trusted;
-
- /*
- * We copy a bunch of stuff out of the Conf structure into local
- * fields in the Terminal structure, to avoid the repeated
- * tree234 lookups which would be involved in fetching them from
- * the former every time.
- */
- bool ansi_colour;
- 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;
- bool no_remote_charset;
- bool no_remote_resize;
- bool no_remote_wintitle;
- bool no_remote_clearscroll;
- bool rawcnp;
- bool utf8linedraw;
- bool rect_select;
- int remote_qtitle_action;
- bool rxvt_homeend;
- bool scroll_on_disp;
- bool scroll_on_key;
- bool xterm_256_colour;
- bool true_colour;
-
- wchar_t *last_selected_text;
- int *last_selected_attr;
- truecolour *last_selected_tc;
- size_t last_selected_len;
- int mouse_select_clipboards[N_CLIPBOARDS];
- int n_mouse_select_clipboards;
- int mouse_paste_clipboard;
-
- char *window_title, *icon_title;
- 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;
-};
-
-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/TESTBACK.C b/TESTBACK.C
deleted file mode 100644
index 173786ed..00000000
--- a/TESTBACK.C
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright (c) 1999 Simon Tatham
- * Copyright (c) 1999 Ben Harris
- * All rights reserved.
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use,
- * copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following
- * conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
- * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
- * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-/* PuTTY test backends */
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#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/TREE234.C b/TREE234.C
deleted file mode 100644
index a590382b..00000000
--- a/TREE234.C
+++ /dev/null
@@ -1,1611 +0,0 @@
-/*
- * tree234.c: reasonably generic counted 2-3-4 tree routines.
- *
- * This file is copyright 1999-2001 Simon Tatham.
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use,
- * copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following
- * conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR
- * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
- * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#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. */
- assert(!(equal_permitted && !e));
-
- if (cmp == NULL)
- cmp = t->cmp;
-
- search234_start(&ss, t);
- while (ss.element) {
- int cmpret;
-
- if (e) {
- cmpret = cmp(e, ss.element);
- } else {
- cmpret = -reldir; /* invent a fixed compare result */
- }
-
- if (cmpret == 0) {
- /*
- * We've found an element that compares exactly equal to
- * the query element.
- */
- if (equal_permitted) {
- /* If our search relation permits equality, we've
- * finished already. */
- if (index)
- *index = ss.index;
- return ss.element;
- } else {
- /* Otherwise, pretend this element was slightly too
- * big/small, according to the direction of search. */
- cmpret = reldir;
- }
- }
-
- search234_step(&ss, cmpret);
- }
-
- /*
- * No element compares equal to the one we were after, but
- * ss.index indicates the index that element would have if it were
- * inserted.
- *
- * So if our search relation is EQ, we must simply return failure.
- */
- if (relation == REL234_EQ)
- return NULL;
-
- /*
- * Otherwise, we must do an index lookup for the previous index
- * (if we're going left - LE or LT) or this index (if we're going
- * right - GE or GT).
- */
- if (relation == REL234_LT || relation == REL234_LE) {
- ss.index--;
- }
-
- /*
- * We know the index of the element we want; just call index234
- * to do the rest. This will return NULL if the index is out of
- * bounds, which is exactly what we want.
- */
- toret = index234(t, ss.index);
- if (toret && index)
- *index = ss.index;
- return toret;
-}
-void *find234(tree234 * t, void *e, cmpfn234 cmp)
-{
- return findrelpos234(t, e, cmp, REL234_EQ, NULL);
-}
-void *findrel234(tree234 * t, void *e, cmpfn234 cmp, int relation)
-{
- return findrelpos234(t, e, cmp, relation, NULL);
-}
-void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index)
-{
- return findrelpos234(t, e, cmp, REL234_EQ, index);
-}
-
-void search234_start(search234_state *state, tree234 *t)
-{
- state->_node = t->root;
- state->_base = 0; /* index of first element in this node's subtree */
- state->_last = -1; /* indicate that this node is not previously 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 <stdarg.h>
-#include <string.h>
-
-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/TREE234.H b/TREE234.H
index 0f2012d0..c7e3f641 100644
--- a/TREE234.H
+++ b/TREE234.H
@@ -45,13 +45,13 @@ tree234 *newtree234(cmpfn234 cmp);
/*
* Free a 2-3-4 tree (not including freeing the elements).
*/
-void freetree234(tree234 * t);
+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);
+void *add234(tree234 *t, void *e);
/*
* Add an element e to an unsorted 2-3-4 tree t. Returns e on
@@ -61,7 +61,7 @@ void *add234(tree234 * t, void *e);
* Index range can be from 0 to the tree's current element count,
* inclusive.
*/
-void *addpos234(tree234 * t, void *e, int index);
+void *addpos234(tree234 *t, void *e, int index);
/*
* Look up the element at a given numeric index in a 2-3-4 tree.
@@ -81,7 +81,7 @@ void *addpos234(tree234 * t, void *e, int index);
* consume(p);
* }
*/
-void *index234(tree234 * t, int index);
+void *index234(tree234 *t, int index);
/*
* Find an element e in a sorted 2-3-4 tree t. Returns NULL if not
@@ -126,10 +126,10 @@ void *index234(tree234 * t, int index);
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,
+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);
/*
@@ -184,12 +184,12 @@ void search234_step(search234_state *state, int direction);
* 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);
+void *del234(tree234 *t, void *e);
+void *delpos234(tree234 *t, int index);
/*
* Return the total element count of a tree234.
*/
-int count234(tree234 * t);
+int count234(tree234 *t);
#endif /* TREE234_H */
diff --git a/UNIX/GTKCFG.C b/UNIX/GTKCFG.C
deleted file mode 100644
index 93c48ce6..00000000
--- a/UNIX/GTKCFG.C
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * gtkcfg.c - the GTK-specific parts of the PuTTY configuration
- * box.
- */
-
-#include <assert.h>
-#include <stdlib.h>
-
-#include "putty.h"
-#include "dialog.h"
-#include "storage.h"
-
-static void about_handler(union control *ctrl, dlgparam *dlg,
- void *data, int event)
-{
- if (event == EVENT_ACTION) {
- about_box(ctrl->generic.context.p);
- }
-}
-
-void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win)
-{
- struct controlset *s, *s2;
- union control *c;
- int i;
-
- if (!midsession) {
- /*
- * Add the About button to the standard panel.
- */
- s = ctrl_getset(b, "", "", "");
- c = ctrl_pushbutton(s, "About", 'a', HELPCTX(no_help),
- about_handler, P(win));
- c->generic.column = 0;
- }
-
- /*
- * GTK makes it rather easier to put the scrollbar on the left
- * than Windows does!
- */
- s = ctrl_getset(b, "Window", "scrollback",
- "Control the scrollback in the window");
- ctrl_checkbox(s, "Scrollbar on left", 'l',
- HELPCTX(no_help),
- conf_checkbox_handler,
- I(CONF_scrollbar_on_left));
- /*
- * Really this wants to go just after `Display scrollbar'. See
- * if we can find that control, and do some shuffling.
- */
- for (i = 0; i < s->ncontrols; i++) {
- c = s->ctrls[i];
- if (c->generic.type == CTRL_CHECKBOX &&
- c->generic.context.i == CONF_scrollbar) {
- /*
- * Control i is the scrollbar checkbox.
- * Control s->ncontrols-1 is the scrollbar-on-left one.
- */
- if (i < s->ncontrols-2) {
- c = s->ctrls[s->ncontrols-1];
- memmove(s->ctrls+i+2, s->ctrls+i+1,
- (s->ncontrols-i-2)*sizeof(union control *));
- s->ctrls[i+1] = c;
- }
- break;
- }
- }
-
- /*
- * X requires three more fonts: bold, wide, and wide-bold; also
- * we need the fiddly shadow-bold-offset control. This would
- * make the Window/Appearance panel rather unwieldy and large,
- * so I think the sensible thing here is to _move_ this
- * controlset into a separate Window/Fonts panel!
- */
- s2 = ctrl_getset(b, "Window/Appearance", "font",
- "Font settings");
- /* Remove this controlset from b. */
- for (i = 0; i < b->nctrlsets; i++) {
- if (b->ctrlsets[i] == s2) {
- memmove(b->ctrlsets+i, b->ctrlsets+i+1,
- (b->nctrlsets-i-1) * sizeof(*b->ctrlsets));
- b->nctrlsets--;
- ctrl_free_set(s2);
- break;
- }
- }
- ctrl_settitle(b, "Window/Fonts", "Options controlling font usage");
- s = ctrl_getset(b, "Window/Fonts", "font",
- "Fonts for displaying non-bold text");
- ctrl_fontsel(s, "Font used for ordinary text", 'f',
- HELPCTX(no_help),
- conf_fontsel_handler, I(CONF_font));
- ctrl_fontsel(s, "Font used for wide (CJK) text", 'w',
- HELPCTX(no_help),
- conf_fontsel_handler, I(CONF_widefont));
- s = ctrl_getset(b, "Window/Fonts", "fontbold",
- "Fonts for displaying bolded text");
- ctrl_fontsel(s, "Font used for bolded text", 'b',
- HELPCTX(no_help),
- conf_fontsel_handler, I(CONF_boldfont));
- ctrl_fontsel(s, "Font used for bold wide text", 'i',
- HELPCTX(no_help),
- conf_fontsel_handler, I(CONF_wideboldfont));
- ctrl_checkbox(s, "Use shadow bold instead of bold fonts", 'u',
- HELPCTX(no_help),
- conf_checkbox_handler,
- I(CONF_shadowbold));
- ctrl_text(s, "(Note that bold fonts or shadow bolding are only"
- " used if you have not requested bolding to be done by"
- " changing the text colour.)",
- HELPCTX(no_help));
- ctrl_editbox(s, "Horizontal offset for shadow bold:", 'z', 20,
- HELPCTX(no_help), conf_editbox_handler,
- I(CONF_shadowboldoffset), I(-1));
-
- /*
- * Markus Kuhn feels, not totally unreasonably, that it's good
- * for all applications to shift into UTF-8 mode if they notice
- * that they've been started with a LANG setting dictating it,
- * so that people don't have to keep remembering a separate
- * UTF-8 option for every application they use. Therefore,
- * here's an override option in the Translation panel.
- */
- s = ctrl_getset(b, "Window/Translation", "trans",
- "Character set translation on received data");
- ctrl_checkbox(s, "Override with UTF-8 if locale says so", 'l',
- HELPCTX(translation_utf8_override),
- conf_checkbox_handler,
- I(CONF_utf8_override));
-
-#ifdef OSX_META_KEY_CONFIG
- /*
- * On OS X, there are multiple reasonable opinions about whether
- * Option or Command (or both, or neither) should act as a Meta
- * key, or whether they should have their normal OS functions.
- */
- s = ctrl_getset(b, "Terminal/Keyboard", "meta",
- "Choose the Meta key:");
- ctrl_checkbox(s, "Option key acts as Meta", 'p',
- HELPCTX(no_help),
- conf_checkbox_handler, I(CONF_osx_option_meta));
- ctrl_checkbox(s, "Command key acts as Meta", 'm',
- HELPCTX(no_help),
- conf_checkbox_handler, I(CONF_osx_command_meta));
-#endif
-
- if (!midsession) {
- /*
- * Allow the user to specify the window class as part of the saved
- * configuration, so that they can have their window manager treat
- * different kinds of PuTTY and pterm differently if they want to.
- */
- s = ctrl_getset(b, "Window/Behaviour", "x11",
- "X Window System settings");
- ctrl_editbox(s, "Window class name:", 'z', 50,
- HELPCTX(no_help), conf_editbox_handler,
- I(CONF_winclass), I(1));
- }
-}
diff --git a/UNIX/GTKCOLS.C b/UNIX/GTKCOLS.C
deleted file mode 100644
index 35c02875..00000000
--- a/UNIX/GTKCOLS.C
+++ /dev/null
@@ -1,1201 +0,0 @@
-/*
- * gtkcols.c - implementation of the `Columns' GTK layout container.
- */
-
-#include <assert.h>
-#include <gtk/gtk.h>
-#include "defs.h"
-#include "gtkcompat.h"
-#include "gtkcols.h"
-
-#if GTK_CHECK_VERSION(2,0,0)
-/* The "focus" method lives in GtkWidget from GTK 2 onwards, but it
- * was in GtkContainer in GTK 1 */
-#define FOCUS_METHOD_SUPERCLASS GtkWidget
-#define FOCUS_METHOD_LOCATION widget_class /* used in columns_init */
-#define CHILD_FOCUS(cont, dir) gtk_widget_child_focus(GTK_WIDGET(cont), dir)
-#else
-#define FOCUS_METHOD_SUPERCLASS GtkContainer
-#define FOCUS_METHOD_LOCATION container_class
-#define CHILD_FOCUS(cont, dir) gtk_container_focus(GTK_CONTAINER(cont), dir)
-#endif
-
-static void columns_init(Columns *cols);
-static void columns_class_init(ColumnsClass *klass);
-#if !GTK_CHECK_VERSION(2,0,0)
-static void columns_finalize(GtkObject *object);
-#else
-static void columns_finalize(GObject *object);
-#endif
-static void columns_map(GtkWidget *widget);
-static void columns_unmap(GtkWidget *widget);
-#if !GTK_CHECK_VERSION(2,0,0)
-static void columns_draw(GtkWidget *widget, GdkRectangle *area);
-static gint columns_expose(GtkWidget *widget, GdkEventExpose *event);
-#endif
-static void columns_base_add(GtkContainer *container, GtkWidget *widget);
-static void columns_remove(GtkContainer *container, GtkWidget *widget);
-static void columns_forall(GtkContainer *container, gboolean include_internals,
- GtkCallback callback, gpointer callback_data);
-static gint columns_focus(FOCUS_METHOD_SUPERCLASS *container,
- GtkDirectionType dir);
-static GType columns_child_type(GtkContainer *container);
-#if GTK_CHECK_VERSION(3,0,0)
-static void columns_get_preferred_width(GtkWidget *widget,
- gint *min, gint *nat);
-static void columns_get_preferred_height(GtkWidget *widget,
- gint *min, gint *nat);
-static void columns_get_preferred_width_for_height(GtkWidget *widget,
- gint height,
- gint *min, gint *nat);
-static void columns_get_preferred_height_for_width(GtkWidget *widget,
- gint width,
- gint *min, gint *nat);
-#else
-static void columns_size_request(GtkWidget *widget, GtkRequisition *req);
-#endif
-static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc);
-
-static GtkContainerClass *parent_class = NULL;
-
-#if !GTK_CHECK_VERSION(2,0,0)
-GType columns_get_type(void)
-{
- static GType columns_type = 0;
-
- if (!columns_type) {
- static const GtkTypeInfo columns_info = {
- "Columns",
- sizeof(Columns),
- sizeof(ColumnsClass),
- (GtkClassInitFunc) columns_class_init,
- (GtkObjectInitFunc) columns_init,
- /* reserved_1 */ NULL,
- /* reserved_2 */ NULL,
- (GtkClassInitFunc) NULL,
- };
-
- columns_type = gtk_type_unique(GTK_TYPE_CONTAINER, &columns_info);
- }
-
- return columns_type;
-}
-#else
-GType columns_get_type(void)
-{
- static GType columns_type = 0;
-
- if (!columns_type) {
- static const GTypeInfo columns_info = {
- sizeof(ColumnsClass),
- NULL,
- NULL,
- (GClassInitFunc) columns_class_init,
- NULL,
- NULL,
- sizeof(Columns),
- 0,
- (GInstanceInitFunc)columns_init,
- };
-
- columns_type = g_type_register_static(GTK_TYPE_CONTAINER, "Columns",
- &columns_info, 0);
- }
-
- return columns_type;
-}
-#endif
-
-static gint (*columns_inherited_focus)(FOCUS_METHOD_SUPERCLASS *container,
- GtkDirectionType direction);
-
-static void columns_class_init(ColumnsClass *klass)
-{
-#if !GTK_CHECK_VERSION(2,0,0)
- GtkObjectClass *object_class = (GtkObjectClass *)klass;
- GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
- GtkContainerClass *container_class = (GtkContainerClass *)klass;
-#else
- GObjectClass *object_class = G_OBJECT_CLASS(klass);
- GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
- GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
-#endif
-
-#if !GTK_CHECK_VERSION(2,0,0)
- parent_class = gtk_type_class(GTK_TYPE_CONTAINER);
-#else
- parent_class = g_type_class_peek_parent(klass);
-#endif
-
- object_class->finalize = columns_finalize;
- widget_class->map = columns_map;
- widget_class->unmap = columns_unmap;
-#if !GTK_CHECK_VERSION(2,0,0)
- widget_class->draw = columns_draw;
- widget_class->expose_event = columns_expose;
-#endif
-#if GTK_CHECK_VERSION(3,0,0)
- widget_class->get_preferred_width = columns_get_preferred_width;
- widget_class->get_preferred_height = columns_get_preferred_height;
- widget_class->get_preferred_width_for_height =
- columns_get_preferred_width_for_height;
- widget_class->get_preferred_height_for_width =
- columns_get_preferred_height_for_width;
-#else
- widget_class->size_request = columns_size_request;
-#endif
- widget_class->size_allocate = columns_size_allocate;
-
- container_class->add = columns_base_add;
- container_class->remove = columns_remove;
- container_class->forall = columns_forall;
- container_class->child_type = columns_child_type;
-
- /* Save the previous value of this method. */
- if (!columns_inherited_focus)
- columns_inherited_focus = FOCUS_METHOD_LOCATION->focus;
- FOCUS_METHOD_LOCATION->focus = columns_focus;
-}
-
-static void columns_init(Columns *cols)
-{
- gtk_widget_set_has_window(GTK_WIDGET(cols), false);
-
- cols->children = NULL;
- cols->spacing = 0;
-}
-
-static void columns_child_free(gpointer vchild)
-{
- ColumnsChild *child = (ColumnsChild *)vchild;
- if (child->percentages)
- g_free(child->percentages);
- g_free(child);
-}
-
-static void columns_finalize(
-#if !GTK_CHECK_VERSION(2,0,0)
- GtkObject *object
-#else
- GObject *object
-#endif
- )
-{
- Columns *cols;
-
- g_return_if_fail(object != NULL);
- g_return_if_fail(IS_COLUMNS(object));
-
- cols = COLUMNS(object);
-
-#if !GTK_CHECK_VERSION(2,0,0)
- {
- GList *node;
- for (node = cols->children; node; node = node->next)
- if (node->data)
- columns_child_free(node->data);
- }
- g_list_free(cols->children);
-#else
- g_list_free_full(cols->children, columns_child_free);
-#endif
-
- cols->children = NULL;
-
-#if !GTK_CHECK_VERSION(2,0,0)
- GTK_OBJECT_CLASS(parent_class)->finalize(object);
-#else
- G_OBJECT_CLASS(parent_class)->finalize(object);
-#endif
-}
-
-/*
- * These appear to be thoroughly tedious functions; the only reason
- * we have to reimplement them at all is because we defined our own
- * format for our GList of children...
- */
-static void columns_map(GtkWidget *widget)
-{
- Columns *cols;
- ColumnsChild *child;
- GList *children;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
-
- cols = COLUMNS(widget);
- gtk_widget_set_mapped(GTK_WIDGET(cols), true);
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
- if (child->widget &&
- gtk_widget_get_visible(child->widget) &&
- !gtk_widget_get_mapped(child->widget))
- gtk_widget_map(child->widget);
- }
-}
-static void columns_unmap(GtkWidget *widget)
-{
- Columns *cols;
- ColumnsChild *child;
- GList *children;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
-
- cols = COLUMNS(widget);
- gtk_widget_set_mapped(GTK_WIDGET(cols), false);
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
- if (child->widget &&
- gtk_widget_get_visible(child->widget) &&
- gtk_widget_get_mapped(child->widget))
- gtk_widget_unmap(child->widget);
- }
-}
-#if !GTK_CHECK_VERSION(2,0,0)
-static void columns_draw(GtkWidget *widget, GdkRectangle *area)
-{
- Columns *cols;
- ColumnsChild *child;
- GList *children;
- GdkRectangle child_area;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
-
- if (GTK_WIDGET_DRAWABLE(widget)) {
- cols = COLUMNS(widget);
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
- if (child->widget &&
- GTK_WIDGET_DRAWABLE(child->widget) &&
- gtk_widget_intersect(child->widget, area, &child_area))
- gtk_widget_draw(child->widget, &child_area);
- }
- }
-}
-static gint columns_expose(GtkWidget *widget, GdkEventExpose *event)
-{
- Columns *cols;
- ColumnsChild *child;
- GList *children;
- GdkEventExpose child_event;
-
- g_return_val_if_fail(widget != NULL, false);
- g_return_val_if_fail(IS_COLUMNS(widget), false);
- g_return_val_if_fail(event != NULL, false);
-
- if (GTK_WIDGET_DRAWABLE(widget)) {
- cols = COLUMNS(widget);
- child_event = *event;
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
- if (child->widget &&
- GTK_WIDGET_DRAWABLE(child->widget) &&
- GTK_WIDGET_NO_WINDOW(child->widget) &&
- gtk_widget_intersect(child->widget, &event->area,
- &child_event.area))
- gtk_widget_event(child->widget, (GdkEvent *)&child_event);
- }
- }
- return false;
-}
-#endif
-
-static void columns_base_add(GtkContainer *container, GtkWidget *widget)
-{
- Columns *cols;
-
- g_return_if_fail(container != NULL);
- g_return_if_fail(IS_COLUMNS(container));
- g_return_if_fail(widget != NULL);
-
- cols = COLUMNS(container);
-
- /*
- * Default is to add a new widget spanning all columns.
- */
- columns_add(cols, widget, 0, 0); /* 0 means ncols */
-}
-
-static void columns_remove(GtkContainer *container, GtkWidget *widget)
-{
- Columns *cols;
- ColumnsChild *child;
- GtkWidget *childw;
- GList *children;
- bool was_visible;
-
- g_return_if_fail(container != NULL);
- g_return_if_fail(IS_COLUMNS(container));
- g_return_if_fail(widget != NULL);
-
- cols = COLUMNS(container);
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
- if (child->widget != widget)
- continue;
-
- was_visible = gtk_widget_get_visible(widget);
- gtk_widget_unparent(widget);
- cols->children = g_list_remove_link(cols->children, children);
- g_list_free(children);
-
- if (child->same_height_as) {
- g_return_if_fail(child->same_height_as->same_height_as == child);
- child->same_height_as->same_height_as = NULL;
- if (gtk_widget_get_visible(child->same_height_as->widget))
- gtk_widget_queue_resize(GTK_WIDGET(container));
- }
-
- g_free(child);
- if (was_visible)
- gtk_widget_queue_resize(GTK_WIDGET(container));
- break;
- }
-
- for (children = cols->taborder;
- children && (childw = children->data);
- children = children->next) {
- if (childw != widget)
- continue;
-
- cols->taborder = g_list_remove_link(cols->taborder, children);
- g_list_free(children);
- break;
- }
-}
-
-static void columns_forall(GtkContainer *container, gboolean include_internals,
- GtkCallback callback, gpointer callback_data)
-{
- Columns *cols;
- ColumnsChild *child;
- GList *children, *next;
-
- g_return_if_fail(container != NULL);
- g_return_if_fail(IS_COLUMNS(container));
- g_return_if_fail(callback != NULL);
-
- cols = COLUMNS(container);
-
- for (children = cols->children;
- children && (child = children->data);
- children = next) {
- /*
- * We can't wait until after the callback to assign
- * `children = children->next', because the callback might
- * be gtk_widget_destroy, which would remove the link
- * `children' from the list! So instead we must get our
- * hands on the value of the `next' pointer _before_ the
- * callback.
- */
- next = children->next;
- if (child->widget)
- callback(child->widget, callback_data);
- }
-}
-
-static GType columns_child_type(GtkContainer *container)
-{
- return GTK_TYPE_WIDGET;
-}
-
-GtkWidget *columns_new(gint spacing)
-{
- Columns *cols;
-
-#if !GTK_CHECK_VERSION(2,0,0)
- cols = gtk_type_new(columns_get_type());
-#else
- cols = g_object_new(TYPE_COLUMNS, NULL);
-#endif
-
- cols->spacing = spacing;
-
- return GTK_WIDGET(cols);
-}
-
-void columns_set_cols(Columns *cols, gint ncols, const gint *percentages)
-{
- ColumnsChild *childdata;
- gint i;
-
- g_return_if_fail(cols != NULL);
- g_return_if_fail(IS_COLUMNS(cols));
- g_return_if_fail(ncols > 0);
- g_return_if_fail(percentages != NULL);
-
- childdata = g_new(ColumnsChild, 1);
- childdata->widget = NULL;
- childdata->ncols = ncols;
- childdata->percentages = g_new(gint, ncols);
- childdata->force_left = false;
- for (i = 0; i < ncols; i++)
- childdata->percentages[i] = percentages[i];
-
- cols->children = g_list_append(cols->children, childdata);
-}
-
-void columns_add(Columns *cols, GtkWidget *child,
- gint colstart, gint colspan)
-{
- ColumnsChild *childdata;
-
- g_return_if_fail(cols != NULL);
- g_return_if_fail(IS_COLUMNS(cols));
- g_return_if_fail(child != NULL);
- g_return_if_fail(gtk_widget_get_parent(child) == NULL);
-
- childdata = g_new(ColumnsChild, 1);
- childdata->widget = child;
- childdata->colstart = colstart;
- childdata->colspan = colspan;
- childdata->force_left = false;
- childdata->same_height_as = NULL;
- childdata->percentages = NULL;
-
- cols->children = g_list_append(cols->children, childdata);
- cols->taborder = g_list_append(cols->taborder, child);
-
- gtk_widget_set_parent(child, GTK_WIDGET(cols));
-
- if (gtk_widget_get_realized(GTK_WIDGET(cols)))
- gtk_widget_realize(child);
-
- if (gtk_widget_get_visible(GTK_WIDGET(cols)) &&
- gtk_widget_get_visible(child)) {
- if (gtk_widget_get_mapped(GTK_WIDGET(cols)))
- gtk_widget_map(child);
- gtk_widget_queue_resize(child);
- }
-}
-
-static ColumnsChild *columns_find_child(Columns *cols, GtkWidget *widget)
-{
- GList *children;
- ColumnsChild *child;
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
-
- if (child->widget == widget)
- return child;
- }
-
- return NULL;
-}
-
-void columns_force_left_align(Columns *cols, GtkWidget *widget)
-{
- ColumnsChild *child;
-
- g_return_if_fail(cols != NULL);
- g_return_if_fail(IS_COLUMNS(cols));
- g_return_if_fail(widget != NULL);
-
- child = columns_find_child(cols, widget);
- g_return_if_fail(child != NULL);
-
- child->force_left = true;
- if (gtk_widget_get_visible(widget))
- gtk_widget_queue_resize(GTK_WIDGET(cols));
-}
-
-void columns_force_same_height(Columns *cols, GtkWidget *cw1, GtkWidget *cw2)
-{
- ColumnsChild *child1, *child2;
-
- g_return_if_fail(cols != NULL);
- g_return_if_fail(IS_COLUMNS(cols));
- g_return_if_fail(cw1 != NULL);
- g_return_if_fail(cw2 != NULL);
-
- child1 = columns_find_child(cols, cw1);
- g_return_if_fail(child1 != NULL);
- child2 = columns_find_child(cols, cw2);
- g_return_if_fail(child2 != NULL);
-
- child1->same_height_as = child2;
- child2->same_height_as = child1;
- if (gtk_widget_get_visible(cw1) || gtk_widget_get_visible(cw2))
- gtk_widget_queue_resize(GTK_WIDGET(cols));
-}
-
-void columns_taborder_last(Columns *cols, GtkWidget *widget)
-{
- GtkWidget *childw;
- GList *children;
-
- g_return_if_fail(cols != NULL);
- g_return_if_fail(IS_COLUMNS(cols));
- g_return_if_fail(widget != NULL);
-
- for (children = cols->taborder;
- children && (childw = children->data);
- children = children->next) {
- if (childw != widget)
- continue;
-
- cols->taborder = g_list_remove_link(cols->taborder, children);
- g_list_free(children);
- cols->taborder = g_list_append(cols->taborder, widget);
- break;
- }
-}
-
-/*
- * Override GtkContainer's focus movement so the user can
- * explicitly specify the tab order.
- */
-static gint columns_focus(FOCUS_METHOD_SUPERCLASS *super, GtkDirectionType dir)
-{
- Columns *cols;
- GList *pos;
- GtkWidget *focuschild;
-
- g_return_val_if_fail(super != NULL, false);
- g_return_val_if_fail(IS_COLUMNS(super), false);
-
- cols = COLUMNS(super);
-
- if (!gtk_widget_is_drawable(GTK_WIDGET(cols)) ||
- !gtk_widget_is_sensitive(GTK_WIDGET(cols)))
- return false;
-
- if (!gtk_widget_get_can_focus(GTK_WIDGET(cols)) &&
- (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
-
- focuschild = gtk_container_get_focus_child(GTK_CONTAINER(cols));
- gtk_container_set_focus_child(GTK_CONTAINER(cols), NULL);
-
- if (dir == GTK_DIR_TAB_FORWARD)
- pos = cols->taborder;
- else
- pos = g_list_last(cols->taborder);
-
- while (pos) {
- GtkWidget *child = pos->data;
-
- if (focuschild) {
- if (focuschild == child) {
- focuschild = NULL; /* now we can start looking in here */
- if (gtk_widget_is_drawable(child) &&
- GTK_IS_CONTAINER(child) &&
- !gtk_widget_has_focus(child)) {
- if (CHILD_FOCUS(child, dir))
- return true;
- }
- }
- } else if (gtk_widget_is_drawable(child)) {
- if (GTK_IS_CONTAINER(child)) {
- if (CHILD_FOCUS(child, dir))
- return true;
- } else if (gtk_widget_get_can_focus(child)) {
- gtk_widget_grab_focus(child);
- return true;
- }
- }
-
- if (dir == GTK_DIR_TAB_FORWARD)
- pos = pos->next;
- else
- pos = pos->prev;
- }
-
- return false;
- } else
- return columns_inherited_focus(super, dir);
-}
-
-/*
- * Underlying parts of the layout algorithm, to compute the Columns
- * container's width or height given the widths or heights of its
- * children. These will be called in various ways with different
- * notions of width and height in use, so we abstract them out and
- * pass them a 'get width' or 'get height' function pointer.
- */
-
-typedef gint (*widget_dim_fn_t)(ColumnsChild *child);
-
-static gint columns_compute_width(Columns *cols, widget_dim_fn_t get_width)
-{
- ColumnsChild *child;
- GList *children;
- gint i, ncols, colspan, retwidth, childwidth;
- const gint *percentages;
- static const gint onecol[] = { 100 };
-
-#ifdef COLUMNS_WIDTH_DIAGNOSTICS
- printf("compute_width(%p): start\n", cols);
-#endif
-
- retwidth = 0;
-
- ncols = 1;
- percentages = onecol;
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
-
- if (!child->widget) {
- /* Column reconfiguration. */
- ncols = child->ncols;
- percentages = child->percentages;
- continue;
- }
-
- /* Only take visible widgets into account. */
- if (!gtk_widget_get_visible(child->widget))
- continue;
-
- childwidth = get_width(child);
- colspan = child->colspan ? child->colspan : ncols-child->colstart;
- assert(colspan > 0);
-
-#ifdef COLUMNS_WIDTH_DIAGNOSTICS
- printf("compute_width(%p): ", cols);
- if (GTK_IS_LABEL(child->widget))
- printf("label %p '%s' wrap=%s: ", child->widget,
- gtk_label_get_text(GTK_LABEL(child->widget)),
- (gtk_label_get_line_wrap(GTK_LABEL(child->widget))
- ? "true" : "false"));
- else
- printf("widget %p: ", child->widget);
- {
- gint min, nat;
- gtk_widget_get_preferred_width(child->widget, &min, &nat);
- printf("minwidth=%d natwidth=%d ", min, nat);
- }
- printf("thiswidth=%d span=%d\n", childwidth, colspan);
-#endif
-
- /*
- * To compute width: we know that childwidth + cols->spacing
- * needs to equal a certain percentage of the full width of
- * the container. So we work this value out, figure out how
- * wide the container will need to be to make that percentage
- * of it equal to that width, and ensure our returned width is
- * at least that much. Very simple really.
- */
- {
- int percent, thiswid, fullwid;
-
- percent = 0;
- for (i = 0; i < colspan; i++)
- percent += percentages[child->colstart+i];
-
- thiswid = childwidth + cols->spacing;
- /*
- * Since childwidth is (at least sometimes) the _minimum_
- * size the child needs, we must ensure that it gets _at
- * least_ that size. Hence, when scaling thiswid up to
- * fullwid, we must round up, which means adding percent-1
- * before dividing by percent.
- */
- fullwid = (thiswid * 100 + percent - 1) / percent;
-#ifdef COLUMNS_WIDTH_DIAGNOSTICS
- printf("compute_width(%p): after %p, thiswid=%d fullwid=%d\n",
- cols, child->widget, thiswid, fullwid);
-#endif
-
- /*
- * The above calculation assumes every widget gets
- * cols->spacing on the right. So we subtract
- * cols->spacing here to account for the extra load of
- * spacing on the right.
- */
- if (retwidth < fullwid - cols->spacing)
- retwidth = fullwid - cols->spacing;
- }
- }
-
- retwidth += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
-
-#ifdef COLUMNS_WIDTH_DIAGNOSTICS
- printf("compute_width(%p): done, returning %d\n", cols, retwidth);
-#endif
-
- return retwidth;
-}
-
-static void columns_alloc_horiz(Columns *cols, gint ourwidth,
- widget_dim_fn_t get_width)
-{
- ColumnsChild *child;
- GList *children;
- gint i, ncols, colspan, border, *colxpos, childwidth;
-
- border = gtk_container_get_border_width(GTK_CONTAINER(cols));
-
- ncols = 1;
- /* colxpos gives the starting x position of each column.
- * We supply n+1 of them, so that we can find the RH edge easily.
- * All ending x positions are expected to be adjusted afterwards by
- * subtracting the spacing. */
- colxpos = g_new(gint, 2);
- colxpos[0] = 0;
- colxpos[1] = ourwidth - 2*border + cols->spacing;
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
-
- if (!child->widget) {
- gint percent;
-
- /* Column reconfiguration. */
- ncols = child->ncols;
- colxpos = g_renew(gint, colxpos, ncols + 1);
- colxpos[0] = 0;
- percent = 0;
- for (i = 0; i < ncols; i++) {
- percent += child->percentages[i];
- colxpos[i+1] = (((ourwidth - 2*border) + cols->spacing)
- * percent / 100);
- }
- continue;
- }
-
- /* Only take visible widgets into account. */
- if (!gtk_widget_get_visible(child->widget))
- continue;
-
- childwidth = get_width(child);
- colspan = child->colspan ? child->colspan : ncols-child->colstart;
-
- /*
- * Starting x position is cols[colstart].
- * Ending x position is cols[colstart+colspan] - spacing.
- *
- * Unless we're forcing left, in which case the width is
- * exactly the requisition width.
- */
- child->x = colxpos[child->colstart];
- if (child->force_left)
- child->w = childwidth;
- else
- child->w = (colxpos[child->colstart+colspan] -
- colxpos[child->colstart] - cols->spacing);
- }
-
- g_free(colxpos);
-}
-
-static gint columns_compute_height(Columns *cols, widget_dim_fn_t get_height)
-{
- ColumnsChild *child;
- GList *children;
- gint i, ncols, colspan, *colypos, retheight, childheight;
-
- retheight = cols->spacing;
-
- ncols = 1;
- colypos = g_new(gint, 1);
- colypos[0] = 0;
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
-
- if (!child->widget) {
- /* Column reconfiguration. */
- for (i = 1; i < ncols; i++) {
- if (colypos[0] < colypos[i])
- colypos[0] = colypos[i];
- }
- ncols = child->ncols;
- colypos = g_renew(gint, colypos, ncols);
- for (i = 1; i < ncols; i++)
- colypos[i] = colypos[0];
- continue;
- }
-
- /* Only take visible widgets into account. */
- if (!gtk_widget_get_visible(child->widget))
- continue;
-
- childheight = get_height(child);
- if (child->same_height_as) {
- gint childheight2 = get_height(child->same_height_as);
- if (childheight < childheight2)
- childheight = childheight2;
- }
- colspan = child->colspan ? child->colspan : ncols-child->colstart;
-
- /*
- * To compute height: the widget's top will be positioned at
- * the largest y value so far reached in any of the columns it
- * crosses. Then it will go down by childheight plus padding;
- * and the point it reaches at the bottom is the new y value
- * in all those columns, and minus the padding it is also a
- * lower bound on our own height.
- */
- {
- int topy, boty;
-
- topy = 0;
- for (i = 0; i < colspan; i++) {
- if (topy < colypos[child->colstart+i])
- topy = colypos[child->colstart+i];
- }
- boty = topy + childheight + cols->spacing;
- for (i = 0; i < colspan; i++) {
- colypos[child->colstart+i] = boty;
- }
-
- if (retheight < boty - cols->spacing)
- retheight = boty - cols->spacing;
- }
- }
-
- retheight += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
-
- g_free(colypos);
-
- return retheight;
-}
-
-static void columns_alloc_vert(Columns *cols, gint ourheight,
- widget_dim_fn_t get_height)
-{
- ColumnsChild *child;
- GList *children;
- gint i, ncols, colspan, *colypos, realheight, fakeheight;
-
- ncols = 1;
- /* As in size_request, colypos is the lowest y reached in each column. */
- colypos = g_new(gint, 1);
- colypos[0] = 0;
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
- if (!child->widget) {
- /* Column reconfiguration. */
- for (i = 1; i < ncols; i++) {
- if (colypos[0] < colypos[i])
- colypos[0] = colypos[i];
- }
- ncols = child->ncols;
- colypos = g_renew(gint, colypos, ncols);
- for (i = 1; i < ncols; i++)
- colypos[i] = colypos[0];
- continue;
- }
-
- /* Only take visible widgets into account. */
- if (!gtk_widget_get_visible(child->widget))
- continue;
-
- realheight = fakeheight = get_height(child);
- if (child->same_height_as) {
- gint childheight2 = get_height(child->same_height_as);
- if (fakeheight < childheight2)
- fakeheight = childheight2;
- }
- colspan = child->colspan ? child->colspan : ncols-child->colstart;
-
- /*
- * To compute height: the widget's top will be positioned
- * at the largest y value so far reached in any of the
- * columns it crosses. Then it will go down by creq.height
- * plus padding; and the point it reaches at the bottom is
- * the new y value in all those columns.
- */
- {
- int topy, boty;
-
- topy = 0;
- for (i = 0; i < colspan; i++) {
- if (topy < colypos[child->colstart+i])
- topy = colypos[child->colstart+i];
- }
- child->y = topy + fakeheight/2 - realheight/2;
- child->h = realheight;
- boty = topy + fakeheight + cols->spacing;
- for (i = 0; i < colspan; i++) {
- colypos[child->colstart+i] = boty;
- }
- }
- }
-
- g_free(colypos);
-}
-
-/*
- * Now here comes the interesting bit. The actual layout part is
- * done in the following two functions:
- *
- * columns_size_request() examines the list of widgets held in the
- * Columns, and returns a requisition stating the absolute minimum
- * size it can bear to be.
- *
- * columns_size_allocate() is given an allocation telling it what
- * size the whole container is going to be, and it calls
- * gtk_widget_size_allocate() on all of its (visible) children to
- * set their size and position relative to the top left of the
- * container.
- */
-
-#if !GTK_CHECK_VERSION(3,0,0)
-
-static gint columns_gtk2_get_width(ColumnsChild *child)
-{
- GtkRequisition creq;
- gtk_widget_size_request(child->widget, &creq);
- return creq.width;
-}
-
-static gint columns_gtk2_get_height(ColumnsChild *child)
-{
- GtkRequisition creq;
- gtk_widget_size_request(child->widget, &creq);
- return creq.height;
-}
-
-static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
-{
- Columns *cols;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
- g_return_if_fail(req != NULL);
-
- cols = COLUMNS(widget);
-
- req->width = columns_compute_width(cols, columns_gtk2_get_width);
- req->height = columns_compute_height(cols, columns_gtk2_get_height);
-}
-
-static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
-{
- Columns *cols;
- ColumnsChild *child;
- GList *children;
- gint border;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
- g_return_if_fail(alloc != NULL);
-
- cols = COLUMNS(widget);
- gtk_widget_set_allocation(widget, alloc);
-
- border = gtk_container_get_border_width(GTK_CONTAINER(cols));
-
- columns_alloc_horiz(cols, alloc->width, columns_gtk2_get_width);
- columns_alloc_vert(cols, alloc->height, columns_gtk2_get_height);
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
- if (child->widget && gtk_widget_get_visible(child->widget)) {
- GtkAllocation call;
- call.x = alloc->x + border + child->x;
- call.y = alloc->y + border + child->y;
- call.width = child->w;
- call.height = child->h;
- gtk_widget_size_allocate(child->widget, &call);
- }
- }
-}
-
-#else /* GTK_CHECK_VERSION(3,0,0) */
-
-static gint columns_gtk3_get_min_width(ColumnsChild *child)
-{
- gint ret;
- gtk_widget_get_preferred_width(child->widget, &ret, NULL);
- return ret;
-}
-
-static gint columns_gtk3_get_nat_width(ColumnsChild *child)
-{
- gint ret;
-
- if ((GTK_IS_LABEL(child->widget) &&
- gtk_label_get_line_wrap(GTK_LABEL(child->widget))) ||
- GTK_IS_ENTRY(child->widget)) {
- /*
- * We treat wrapping GtkLabels as a special case in this
- * layout class, because the whole point of those is that I
- * _don't_ want them to take up extra horizontal space for
- * long text, but instead to wrap it to whatever size is used
- * by the rest of the layout.
- *
- * GtkEntry gets similar treatment, because in OS X GTK I've
- * found that it requests a natural width regardless of the
- * output of gtk_entry_set_width_chars.
- */
- gtk_widget_get_preferred_width(child->widget, &ret, NULL);
- } else {
- gtk_widget_get_preferred_width(child->widget, NULL, &ret);
- }
- return ret;
-}
-
-static gint columns_gtk3_get_minfh_width(ColumnsChild *child)
-{
- gint ret;
- gtk_widget_get_preferred_width_for_height(child->widget, child->h,
- &ret, NULL);
- return ret;
-}
-
-static gint columns_gtk3_get_natfh_width(ColumnsChild *child)
-{
- gint ret;
- gtk_widget_get_preferred_width_for_height(child->widget, child->h,
- NULL, &ret);
- return ret;
-}
-
-static gint columns_gtk3_get_min_height(ColumnsChild *child)
-{
- gint ret;
- gtk_widget_get_preferred_height(child->widget, &ret, NULL);
- return ret;
-}
-
-static gint columns_gtk3_get_nat_height(ColumnsChild *child)
-{
- gint ret;
- gtk_widget_get_preferred_height(child->widget, NULL, &ret);
- return ret;
-}
-
-static gint columns_gtk3_get_minfw_height(ColumnsChild *child)
-{
- gint ret;
- gtk_widget_get_preferred_height_for_width(child->widget, child->w,
- &ret, NULL);
- return ret;
-}
-
-static gint columns_gtk3_get_natfw_height(ColumnsChild *child)
-{
- gint ret;
- gtk_widget_get_preferred_height_for_width(child->widget, child->w,
- NULL, &ret);
- return ret;
-}
-
-static void columns_get_preferred_width(GtkWidget *widget,
- gint *min, gint *nat)
-{
- Columns *cols;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
-
- cols = COLUMNS(widget);
-
- if (min)
- *min = columns_compute_width(cols, columns_gtk3_get_min_width);
- if (nat)
- *nat = columns_compute_width(cols, columns_gtk3_get_nat_width);
-}
-
-static void columns_get_preferred_height(GtkWidget *widget,
- gint *min, gint *nat)
-{
- Columns *cols;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
-
- cols = COLUMNS(widget);
-
- if (min)
- *min = columns_compute_height(cols, columns_gtk3_get_min_height);
- if (nat)
- *nat = columns_compute_height(cols, columns_gtk3_get_nat_height);
-}
-
-static void columns_get_preferred_width_for_height(GtkWidget *widget,
- gint height,
- gint *min, gint *nat)
-{
- Columns *cols;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
-
- cols = COLUMNS(widget);
-
- /* FIXME: which one should the get-height function here be? */
- columns_alloc_vert(cols, height, columns_gtk3_get_nat_height);
-
- if (min)
- *min = columns_compute_width(cols, columns_gtk3_get_minfh_width);
- if (nat)
- *nat = columns_compute_width(cols, columns_gtk3_get_natfh_width);
-}
-
-static void columns_get_preferred_height_for_width(GtkWidget *widget,
- gint width,
- gint *min, gint *nat)
-{
- Columns *cols;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
-
- cols = COLUMNS(widget);
-
- /* FIXME: which one should the get-height function here be? */
- columns_alloc_horiz(cols, width, columns_gtk3_get_nat_width);
-
- if (min)
- *min = columns_compute_height(cols, columns_gtk3_get_minfw_height);
- if (nat)
- *nat = columns_compute_height(cols, columns_gtk3_get_natfw_height);
-}
-
-static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
-{
- Columns *cols;
- ColumnsChild *child;
- GList *children;
- gint border;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
- g_return_if_fail(alloc != NULL);
-
- cols = COLUMNS(widget);
- gtk_widget_set_allocation(widget, alloc);
-
- border = gtk_container_get_border_width(GTK_CONTAINER(cols));
-
- columns_alloc_horiz(cols, alloc->width, columns_gtk3_get_min_width);
- columns_alloc_vert(cols, alloc->height, columns_gtk3_get_minfw_height);
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
- if (child->widget && gtk_widget_get_visible(child->widget)) {
- GtkAllocation call;
- call.x = alloc->x + border + child->x;
- call.y = alloc->y + border + child->y;
- call.width = child->w;
- call.height = child->h;
- gtk_widget_size_allocate(child->widget, &call);
- }
- }
-}
-
-#endif
diff --git a/UNIX/GTKCOLS.H b/UNIX/GTKCOLS.H
deleted file mode 100644
index e589cf79..00000000
--- a/UNIX/GTKCOLS.H
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * gtkcols.h - header file for a columns-based widget container
- * capable of supporting the PuTTY portable dialog box layout
- * mechanism.
- */
-
-#ifndef COLUMNS_H
-#define COLUMNS_H
-
-#include <gdk/gdk.h>
-#include <gtk/gtk.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-#define TYPE_COLUMNS (columns_get_type())
-#define COLUMNS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_COLUMNS, Columns))
-#define COLUMNS_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_COLUMNS, ColumnsClass))
-#define IS_COLUMNS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_COLUMNS))
-#define IS_COLUMNS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_COLUMNS))
-
-typedef struct Columns_tag Columns;
-typedef struct ColumnsClass_tag ColumnsClass;
-typedef struct ColumnsChild_tag ColumnsChild;
-
-struct Columns_tag {
- GtkContainer container;
- /* private after here */
- GList *children; /* this holds ColumnsChild structures */
- GList *taborder; /* this just holds GtkWidgets */
- gint spacing;
-};
-
-struct ColumnsClass_tag {
- GtkContainerClass parent_class;
-};
-
-struct ColumnsChild_tag {
- /* If `widget' is non-NULL, this entry represents an actual widget. */
- GtkWidget *widget;
- gint colstart, colspan;
- bool force_left; /* for recalcitrant GtkLabels */
- ColumnsChild *same_height_as;
- /* Otherwise, this entry represents a change in the column setup. */
- gint ncols;
- gint *percentages;
- gint x, y, w, h; /* used during an individual size computation */
-};
-
-GType columns_get_type(void);
-GtkWidget *columns_new(gint spacing);
-void columns_set_cols(Columns *cols, gint ncols, const gint *percentages);
-void columns_add(Columns *cols, GtkWidget *child,
- gint colstart, gint colspan);
-void columns_taborder_last(Columns *cols, GtkWidget *child);
-void columns_force_left_align(Columns *cols, GtkWidget *child);
-void columns_force_same_height(Columns *cols, GtkWidget *ch1, GtkWidget *ch2);
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
-
-#endif /* COLUMNS_H */
diff --git a/UNIX/GTKDLG.C b/UNIX/GTKDLG.C
deleted file mode 100644
index 8e7421db..00000000
--- a/UNIX/GTKDLG.C
+++ /dev/null
@@ -1,4195 +0,0 @@
-/*
- * gtkdlg.c - GTK implementation of the PuTTY configuration box.
- */
-
-#include <assert.h>
-#include <stdarg.h>
-#include <ctype.h>
-#include <time.h>
-
-#include <gtk/gtk.h>
-#if !GTK_CHECK_VERSION(3,0,0)
-#include <gdk/gdkkeysyms.h>
-#endif
-
-#define MAY_REFER_TO_GTK_IN_HEADERS
-
-#include "putty.h"
-#include "gtkcompat.h"
-#include "gtkcols.h"
-#include "gtkfont.h"
-#include "gtkmisc.h"
-
-#ifndef NOT_X_WINDOWS
-#include <gdk/gdkx.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include "x11misc.h"
-#endif
-
-#include "storage.h"
-#include "dialog.h"
-#include "tree234.h"
-#include "licence.h"
-#include "ssh.h"
-
-#if GTK_CHECK_VERSION(2,0,0)
-/* Decide which of GtkFileChooserDialog and GtkFileSelection to use */
-#define USE_GTK_FILE_CHOOSER_DIALOG
-#endif
-
-struct Shortcut {
- GtkWidget *widget;
- struct uctrl *uc;
- int action;
-};
-
-struct Shortcuts {
- struct Shortcut sc[128];
-};
-
-struct selparam;
-
-struct uctrl {
- union control *ctrl;
- GtkWidget *toplevel;
- GtkWidget **buttons; int nbuttons; /* for radio buttons */
- GtkWidget *entry; /* for editbox, filesel, fontsel */
- GtkWidget *button; /* for filesel, fontsel */
-#if !GTK_CHECK_VERSION(2,4,0)
- GtkWidget *list; /* for listbox (in GTK1), combobox (<=GTK2.3) */
- GtkWidget *menu; /* for optionmenu (==droplist) */
- GtkWidget *optmenu; /* also for optionmenu */
-#else
- GtkWidget *combo; /* for combo box (either editable or not) */
-#endif
-#if GTK_CHECK_VERSION(2,0,0)
- GtkWidget *treeview; /* for listbox (GTK2), droplist+combo (>=2.4) */
- GtkListStore *listmodel; /* for all types of list box */
-#endif
- GtkWidget *text; /* for text */
- GtkWidget *label; /* for dlg_label_change */
- GtkAdjustment *adj; /* for the scrollbar in a list box */
- struct selparam *sp; /* which switchable pane of the box we're in */
- guint entrysig;
- guint textsig;
- int nclicks;
-};
-
-struct dlgparam {
- tree234 *byctrl, *bywidget;
- void *data;
- struct {
- unsigned char r, g, b; /* 0-255 */
- bool ok;
- } coloursel_result;
- /* `flags' are set to indicate when a GTK signal handler is being called
- * due to automatic processing and should not flag a user event. */
- int flags;
- struct Shortcuts *shortcuts;
- GtkWidget *window, *cancelbutton;
- union control *currfocus, *lastfocus;
-#if !GTK_CHECK_VERSION(2,0,0)
- GtkWidget *currtreeitem, **treeitems;
- int ntreeitems;
-#else
- size_t nselparams;
- struct selparam **selparams;
-#endif
- struct selparam *curr_panel;
- struct controlbox *ctrlbox;
- int retval;
- post_dialog_fn_t after;
- void *afterctx;
-};
-#define FLAG_UPDATING_COMBO_LIST 1
-#define FLAG_UPDATING_LISTBOX 2
-
-enum { /* values for Shortcut.action */
- SHORTCUT_EMPTY, /* no shortcut on this key */
- SHORTCUT_TREE, /* focus a tree item */
- SHORTCUT_FOCUS, /* focus the supplied widget */
- SHORTCUT_UCTRL, /* do something sane with uctrl */
- SHORTCUT_UCTRL_UP, /* uctrl is a draglist, move Up */
- SHORTCUT_UCTRL_DOWN, /* uctrl is a draglist, move Down */
-};
-
-#if GTK_CHECK_VERSION(2,0,0)
-enum {
- TREESTORE_PATH,
- TREESTORE_PARAMS,
- TREESTORE_NUM
-};
-#endif
-
-/*
- * Forward references.
- */
-static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event,
- gpointer data);
-static void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
- int chr, int action, void *ptr);
-static void shortcut_highlight(GtkWidget *label, int chr);
-#if !GTK_CHECK_VERSION(2,0,0)
-static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event,
- gpointer data);
-static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event,
- gpointer data);
-static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event,
- gpointer data);
-static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event,
- gpointer data);
-#endif
-#if !GTK_CHECK_VERSION(2,4,0)
-static void menuitem_activate(GtkMenuItem *item, gpointer data);
-#endif
-#if GTK_CHECK_VERSION(3,0,0)
-static void colourchoose_response(GtkDialog *dialog,
- gint response_id, gpointer data);
-#else
-static void coloursel_ok(GtkButton *button, gpointer data);
-static void coloursel_cancel(GtkButton *button, gpointer data);
-#endif
-static void dlgparam_destroy(GtkWidget *widget, gpointer data);
-static int get_listitemheight(GtkWidget *widget);
-
-static int uctrl_cmp_byctrl(void *av, void *bv)
-{
- struct uctrl *a = (struct uctrl *)av;
- struct uctrl *b = (struct uctrl *)bv;
- if (a->ctrl < b->ctrl)
- return -1;
- else if (a->ctrl > b->ctrl)
- return +1;
- return 0;
-}
-
-static int uctrl_cmp_byctrl_find(void *av, void *bv)
-{
- union control *a = (union control *)av;
- struct uctrl *b = (struct uctrl *)bv;
- if (a < b->ctrl)
- return -1;
- else if (a > b->ctrl)
- return +1;
- return 0;
-}
-
-static int uctrl_cmp_bywidget(void *av, void *bv)
-{
- struct uctrl *a = (struct uctrl *)av;
- struct uctrl *b = (struct uctrl *)bv;
- if (a->toplevel < b->toplevel)
- return -1;
- else if (a->toplevel > b->toplevel)
- return +1;
- return 0;
-}
-
-static int uctrl_cmp_bywidget_find(void *av, void *bv)
-{
- GtkWidget *a = (GtkWidget *)av;
- struct uctrl *b = (struct uctrl *)bv;
- if (a < b->toplevel)
- return -1;
- else if (a > b->toplevel)
- return +1;
- return 0;
-}
-
-static void dlg_init(struct dlgparam *dp)
-{
- dp->byctrl = newtree234(uctrl_cmp_byctrl);
- dp->bywidget = newtree234(uctrl_cmp_bywidget);
- dp->coloursel_result.ok = false;
- dp->window = dp->cancelbutton = NULL;
-#if !GTK_CHECK_VERSION(2,0,0)
- dp->treeitems = NULL;
- dp->currtreeitem = NULL;
-#endif
- dp->curr_panel = NULL;
- dp->flags = 0;
- dp->currfocus = NULL;
-}
-
-static void dlg_cleanup(struct dlgparam *dp)
-{
- struct uctrl *uc;
-
- freetree234(dp->byctrl); /* doesn't free the uctrls inside */
- dp->byctrl = NULL;
- while ( (uc = index234(dp->bywidget, 0)) != NULL) {
- del234(dp->bywidget, uc);
- sfree(uc->buttons);
- sfree(uc);
- }
- freetree234(dp->bywidget);
- dp->bywidget = NULL;
-#if !GTK_CHECK_VERSION(2,0,0)
- sfree(dp->treeitems);
-#endif
-}
-
-static void dlg_add_uctrl(struct dlgparam *dp, struct uctrl *uc)
-{
- add234(dp->byctrl, uc);
- add234(dp->bywidget, uc);
-}
-
-static struct uctrl *dlg_find_byctrl(struct dlgparam *dp, union control *ctrl)
-{
- if (!dp->byctrl)
- return NULL;
- return find234(dp->byctrl, ctrl, uctrl_cmp_byctrl_find);
-}
-
-static struct uctrl *dlg_find_bywidget(struct dlgparam *dp, GtkWidget *w)
-{
- struct uctrl *ret = NULL;
- if (!dp->bywidget)
- return NULL;
- do {
- ret = find234(dp->bywidget, w, uctrl_cmp_bywidget_find);
- if (ret)
- return ret;
- w = gtk_widget_get_parent(w);
- } while (w);
- return ret;
-}
-
-union control *dlg_last_focused(union control *ctrl, dlgparam *dp)
-{
- if (dp->currfocus != ctrl)
- return dp->currfocus;
- else
- return dp->lastfocus;
-}
-
-void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int which)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- assert(uc->ctrl->generic.type == CTRL_RADIO);
- assert(uc->buttons != NULL);
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->buttons[which]), true);
-}
-
-int dlg_radiobutton_get(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- int i;
-
- assert(uc->ctrl->generic.type == CTRL_RADIO);
- assert(uc->buttons != NULL);
- for (i = 0; i < uc->nbuttons; i++)
- if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->buttons[i])))
- return i;
- return 0; /* got to return something */
-}
-
-void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- assert(uc->ctrl->generic.type == CTRL_CHECKBOX);
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->toplevel), checked);
-}
-
-bool dlg_checkbox_get(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- assert(uc->ctrl->generic.type == CTRL_CHECKBOX);
- return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->toplevel));
-}
-
-void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- GtkWidget *entry;
- char *tmpstring;
- assert(uc->ctrl->generic.type == CTRL_EDITBOX);
-
-#if GTK_CHECK_VERSION(2,4,0)
- if (uc->combo)
- entry = gtk_bin_get_child(GTK_BIN(uc->combo));
- else
-#endif
- entry = uc->entry;
-
- assert(entry != NULL);
-
- /*
- * GTK 2 implements gtk_entry_set_text by means of two separate
- * operations: first delete the previous text leaving the empty
- * string, then insert the new text. This causes two calls to
- * the "changed" signal.
- *
- * The first call to "changed", if allowed to proceed normally,
- * will cause an EVENT_VALCHANGE event on the edit box, causing
- * a call to dlg_editbox_get() which will read the empty string
- * out of the GtkEntry - and promptly write it straight into the
- * Conf structure, which is precisely where our `text' pointer
- * is probably pointing, so the second editing operation will
- * insert that instead of the string we originally asked for.
- *
- * Hence, we must take our own copy of the text before we do
- * this.
- */
- tmpstring = dupstr(text);
- gtk_entry_set_text(GTK_ENTRY(entry), tmpstring);
- sfree(tmpstring);
-}
-
-char *dlg_editbox_get(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- assert(uc->ctrl->generic.type == CTRL_EDITBOX);
-
-#if GTK_CHECK_VERSION(2,4,0)
- if (uc->combo) {
- return dupstr(gtk_entry_get_text
- (GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo)))));
- }
-#endif
-
- if (uc->entry) {
- return dupstr(gtk_entry_get_text(GTK_ENTRY(uc->entry)));
- }
-
- unreachable("bad control type in editbox_get");
-}
-
-#if !GTK_CHECK_VERSION(2,4,0)
-static void container_remove_and_destroy(GtkWidget *w, gpointer data)
-{
- GtkContainer *cont = GTK_CONTAINER(data);
- /* gtk_container_remove will unref the widget for us; we need not. */
- gtk_container_remove(cont, w);
-}
-#endif
-
-/* The `listbox' functions can also apply to combo boxes. */
-void dlg_listbox_clear(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
- uc->ctrl->generic.type == CTRL_LISTBOX);
-
-#if !GTK_CHECK_VERSION(2,4,0)
- if (uc->menu) {
- gtk_container_foreach(GTK_CONTAINER(uc->menu),
- container_remove_and_destroy,
- GTK_CONTAINER(uc->menu));
- return;
- }
- if (uc->list) {
- gtk_list_clear_items(GTK_LIST(uc->list), 0, -1);
- return;
- }
-#endif
-#if GTK_CHECK_VERSION(2,0,0)
- if (uc->listmodel) {
- gtk_list_store_clear(uc->listmodel);
- return;
- }
-#endif
- unreachable("bad control type in listbox_clear");
-}
-
-void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
- uc->ctrl->generic.type == CTRL_LISTBOX);
-
-#if !GTK_CHECK_VERSION(2,4,0)
- if (uc->menu) {
- gtk_container_remove
- (GTK_CONTAINER(uc->menu),
- g_list_nth_data(GTK_MENU_SHELL(uc->menu)->children, index));
- return;
- }
- if (uc->list) {
- gtk_list_clear_items(GTK_LIST(uc->list), index, index+1);
- return;
- }
-#endif
-#if GTK_CHECK_VERSION(2,0,0)
- if (uc->listmodel) {
- GtkTreePath *path;
- GtkTreeIter iter;
- assert(uc->listmodel != NULL);
- path = gtk_tree_path_new_from_indices(index, -1);
- gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path);
- gtk_list_store_remove(uc->listmodel, &iter);
- gtk_tree_path_free(path);
- return;
- }
-#endif
- unreachable("bad control type in listbox_del");
-}
-
-void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text)
-{
- dlg_listbox_addwithid(ctrl, dp, text, 0);
-}
-
-/*
- * Each listbox entry may have a numeric id associated with it.
- * Note that some front ends only permit a string to be stored at
- * each position, which means that _if_ you put two identical
- * strings in any listbox then you MUST not assign them different
- * IDs and expect to get meaningful results back.
- */
-void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp,
- char const *text, int id)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
- uc->ctrl->generic.type == CTRL_LISTBOX);
-
- /*
- * This routine is long and complicated in both GTK 1 and 2,
- * and completely different. Sigh.
- */
- dp->flags |= FLAG_UPDATING_COMBO_LIST;
-
-#if !GTK_CHECK_VERSION(2,4,0)
- if (uc->menu) {
- /*
- * List item in a drop-down (but non-combo) list. Tabs are
- * ignored; we just provide a standard menu item with the
- * text.
- */
- GtkWidget *menuitem = gtk_menu_item_new_with_label(text);
-
- gtk_container_add(GTK_CONTAINER(uc->menu), menuitem);
- gtk_widget_show(menuitem);
-
- g_object_set_data(G_OBJECT(menuitem), "user-data",
- GINT_TO_POINTER(id));
- g_signal_connect(G_OBJECT(menuitem), "activate",
- G_CALLBACK(menuitem_activate), dp);
- goto done;
- }
- if (uc->list && uc->entry) {
- /*
- * List item in a combo-box list, which means the sensible
- * thing to do is make it a perfectly normal label. Hence
- * tabs are disregarded.
- */
- GtkWidget *listitem = gtk_list_item_new_with_label(text);
-
- gtk_container_add(GTK_CONTAINER(uc->list), listitem);
- gtk_widget_show(listitem);
-
- g_object_set_data(G_OBJECT(listitem), "user-data",
- GINT_TO_POINTER(id));
- goto done;
- }
-#endif
-#if !GTK_CHECK_VERSION(2,0,0)
- if (uc->list) {
- /*
- * List item in a non-combo-box list box. We make all of
- * these Columns containing GtkLabels. This allows us to do
- * the nasty force_left hack irrespective of whether there
- * are tabs in the thing.
- */
- GtkWidget *listitem = gtk_list_item_new();
- GtkWidget *cols = columns_new(10);
- gint *percents;
- int i, ncols;
-
- /* Count the tabs in the text, and hence determine # of columns. */
- ncols = 1;
- for (i = 0; text[i]; i++)
- if (text[i] == '\t')
- ncols++;
-
- assert(ncols <=
- (uc->ctrl->listbox.ncols ? uc->ctrl->listbox.ncols : 1));
- percents = snewn(ncols, gint);
- percents[ncols-1] = 100;
- for (i = 0; i < ncols-1; i++) {
- percents[i] = uc->ctrl->listbox.percentages[i];
- percents[ncols-1] -= percents[i];
- }
- columns_set_cols(COLUMNS(cols), ncols, percents);
- sfree(percents);
-
- for (i = 0; i < ncols; i++) {
- int len = strcspn(text, "\t");
- char *dup = dupprintf("%.*s", len, text);
- GtkWidget *label;
-
- text += len;
- if (*text) text++;
- label = gtk_label_new(dup);
- sfree(dup);
-
- columns_add(COLUMNS(cols), label, i, 1);
- columns_force_left_align(COLUMNS(cols), label);
- gtk_widget_show(label);
- }
- gtk_container_add(GTK_CONTAINER(listitem), cols);
- gtk_widget_show(cols);
- gtk_container_add(GTK_CONTAINER(uc->list), listitem);
- gtk_widget_show(listitem);
-
- if (ctrl->listbox.multisel) {
- g_signal_connect(G_OBJECT(listitem), "key_press_event",
- G_CALLBACK(listitem_multi_key), uc->adj);
- } else {
- g_signal_connect(G_OBJECT(listitem), "key_press_event",
- G_CALLBACK(listitem_single_key), uc->adj);
- }
- g_signal_connect(G_OBJECT(listitem), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- g_signal_connect(G_OBJECT(listitem), "button_press_event",
- G_CALLBACK(listitem_button_press), dp);
- g_signal_connect(G_OBJECT(listitem), "button_release_event",
- G_CALLBACK(listitem_button_release), dp);
- g_object_set_data(G_OBJECT(listitem), "user-data",
- GINT_TO_POINTER(id));
- goto done;
- }
-#else
- if (uc->listmodel) {
- GtkTreeIter iter;
- int i, cols;
-
- dp->flags |= FLAG_UPDATING_LISTBOX;/* inhibit drag-list update */
- gtk_list_store_append(uc->listmodel, &iter);
- dp->flags &= ~FLAG_UPDATING_LISTBOX;
- gtk_list_store_set(uc->listmodel, &iter, 0, id, -1);
-
- /*
- * Now go through text and divide it into columns at the tabs,
- * as necessary.
- */
- cols = (uc->ctrl->generic.type == CTRL_LISTBOX ? ctrl->listbox.ncols : 1);
- cols = cols ? cols : 1;
- for (i = 0; i < cols; i++) {
- int collen = strcspn(text, "\t");
- char *tmpstr = snewn(collen+1, char);
- memcpy(tmpstr, text, collen);
- tmpstr[collen] = '\0';
- gtk_list_store_set(uc->listmodel, &iter, i+1, tmpstr, -1);
- sfree(tmpstr);
- text += collen;
- if (*text) text++;
- }
- goto done;
- }
-#endif
- unreachable("bad control type in listbox_addwithid");
- done:
- dp->flags &= ~FLAG_UPDATING_COMBO_LIST;
-}
-
-int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
- uc->ctrl->generic.type == CTRL_LISTBOX);
-
-#if !GTK_CHECK_VERSION(2,4,0)
- if (uc->menu || uc->list) {
- GList *children;
- GObject *item;
-
- children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
- uc->list));
- item = G_OBJECT(g_list_nth_data(children, index));
- g_list_free(children);
-
- return GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "user-data"));
- }
-#endif
-#if GTK_CHECK_VERSION(2,0,0)
- if (uc->listmodel) {
- GtkTreePath *path;
- GtkTreeIter iter;
- int ret;
-
- path = gtk_tree_path_new_from_indices(index, -1);
- gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path);
- gtk_tree_model_get(GTK_TREE_MODEL(uc->listmodel), &iter, 0, &ret, -1);
- gtk_tree_path_free(path);
-
- return ret;
- }
-#endif
- unreachable("bad control type in listbox_getid");
- return -1; /* placate dataflow analysis */
-}
-
-/* dlg_listbox_index returns <0 if no single element is selected. */
-int dlg_listbox_index(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
- uc->ctrl->generic.type == CTRL_LISTBOX);
-
-#if !GTK_CHECK_VERSION(2,4,0)
- if (uc->menu || uc->list) {
- GList *children;
- GtkWidget *item, *activeitem;
- int i;
- int selected = -1;
-
- if (uc->menu)
- activeitem = gtk_menu_get_active(GTK_MENU(uc->menu));
- else
- activeitem = NULL; /* unnecessarily placate gcc */
-
- children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
- uc->list));
- for (i = 0; children!=NULL && (item = GTK_WIDGET(children->data))!=NULL;
- i++, children = children->next) {
- if (uc->menu ? activeitem == item :
- GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED) {
- if (selected == -1)
- selected = i;
- else
- selected = -2;
- }
- }
- g_list_free(children);
- return selected < 0 ? -1 : selected;
- }
-#else
- if (uc->combo) {
- /*
- * This API function already does the right thing in the
- * case of no current selection.
- */
- return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo));
- }
-#endif
-#if GTK_CHECK_VERSION(2,0,0)
- if (uc->treeview) {
- GtkTreeSelection *treesel;
- GtkTreePath *path;
- GtkTreeModel *model;
- GList *sellist;
- gint *indices;
- int ret;
-
- assert(uc->treeview != NULL);
- treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
-
- if (gtk_tree_selection_count_selected_rows(treesel) != 1)
- return -1;
-
- sellist = gtk_tree_selection_get_selected_rows(treesel, &model);
-
- assert(sellist && sellist->data);
- path = sellist->data;
-
- if (gtk_tree_path_get_depth(path) != 1) {
- ret = -1;
- } else {
- indices = gtk_tree_path_get_indices(path);
- if (!indices) {
- ret = -1;
- } else {
- ret = indices[0];
- }
- }
-
- g_list_foreach(sellist, (GFunc)gtk_tree_path_free, NULL);
- g_list_free(sellist);
-
- return ret;
- }
-#endif
- unreachable("bad control type in listbox_index");
- return -1; /* placate dataflow analysis */
-}
-
-bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
- uc->ctrl->generic.type == CTRL_LISTBOX);
-
-#if !GTK_CHECK_VERSION(2,4,0)
- if (uc->menu || uc->list) {
- GList *children;
- GtkWidget *item, *activeitem;
-
- assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
- uc->ctrl->generic.type == CTRL_LISTBOX);
- assert(uc->menu != NULL || uc->list != NULL);
-
- children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
- uc->list));
- item = GTK_WIDGET(g_list_nth_data(children, index));
- g_list_free(children);
-
- if (uc->menu) {
- activeitem = gtk_menu_get_active(GTK_MENU(uc->menu));
- return item == activeitem;
- } else {
- return GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED;
- }
- }
-#else
- if (uc->combo) {
- /*
- * This API function already does the right thing in the
- * case of no current selection.
- */
- return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo)) == index;
- }
-#endif
-#if GTK_CHECK_VERSION(2,0,0)
- if (uc->treeview) {
- GtkTreeSelection *treesel;
- GtkTreePath *path;
- bool ret;
-
- assert(uc->treeview != NULL);
- treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
-
- path = gtk_tree_path_new_from_indices(index, -1);
- ret = gtk_tree_selection_path_is_selected(treesel, path);
- gtk_tree_path_free(path);
-
- return ret;
- }
-#endif
- unreachable("bad control type in listbox_issel");
- return false; /* placate dataflow analysis */
-}
-
-void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
- uc->ctrl->generic.type == CTRL_LISTBOX);
-
-#if !GTK_CHECK_VERSION(2,4,0)
- if (uc->optmenu) {
- gtk_option_menu_set_history(GTK_OPTION_MENU(uc->optmenu), index);
- return;
- }
- if (uc->list) {
- int nitems;
- GList *items;
- gdouble newtop, newbot;
-
- gtk_list_select_item(GTK_LIST(uc->list), index);
-
- /*
- * Scroll the list box if necessary to ensure the newly
- * selected item is visible.
- */
- items = gtk_container_children(GTK_CONTAINER(uc->list));
- nitems = g_list_length(items);
- if (nitems > 0) {
- bool modified = false;
- g_list_free(items);
- newtop = uc->adj->lower +
- (uc->adj->upper - uc->adj->lower) * index / nitems;
- newbot = uc->adj->lower +
- (uc->adj->upper - uc->adj->lower) * (index+1) / nitems;
- if (uc->adj->value > newtop) {
- modified = true;
- uc->adj->value = newtop;
- } else if (uc->adj->value < newbot - uc->adj->page_size) {
- modified = true;
- uc->adj->value = newbot - uc->adj->page_size;
- }
- if (modified)
- gtk_adjustment_value_changed(uc->adj);
- }
- return;
- }
-#else
- if (uc->combo) {
- gtk_combo_box_set_active(GTK_COMBO_BOX(uc->combo), index);
- return;
- }
-#endif
-#if GTK_CHECK_VERSION(2,0,0)
- if (uc->treeview) {
- GtkTreeSelection *treesel;
- GtkTreePath *path;
-
- treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
-
- path = gtk_tree_path_new_from_indices(index, -1);
- gtk_tree_selection_select_path(treesel, path);
- gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(uc->treeview),
- path, NULL, false, 0.0, 0.0);
- gtk_tree_path_free(path);
- return;
- }
-#endif
- unreachable("bad control type in listbox_select");
-}
-
-void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- assert(uc->ctrl->generic.type == CTRL_TEXT);
- assert(uc->text != NULL);
-
- gtk_label_set_text(GTK_LABEL(uc->text), text);
-}
-
-void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- switch (uc->ctrl->generic.type) {
- case CTRL_BUTTON:
- gtk_label_set_text(GTK_LABEL(uc->toplevel), text);
- shortcut_highlight(uc->toplevel, ctrl->button.shortcut);
- break;
- case CTRL_CHECKBOX:
- gtk_label_set_text(GTK_LABEL(uc->toplevel), text);
- shortcut_highlight(uc->toplevel, ctrl->checkbox.shortcut);
- break;
- case CTRL_RADIO:
- gtk_label_set_text(GTK_LABEL(uc->label), text);
- shortcut_highlight(uc->label, ctrl->radio.shortcut);
- break;
- case CTRL_EDITBOX:
- gtk_label_set_text(GTK_LABEL(uc->label), text);
- shortcut_highlight(uc->label, ctrl->editbox.shortcut);
- break;
- case CTRL_FILESELECT:
- gtk_label_set_text(GTK_LABEL(uc->label), text);
- shortcut_highlight(uc->label, ctrl->fileselect.shortcut);
- break;
- case CTRL_FONTSELECT:
- gtk_label_set_text(GTK_LABEL(uc->label), text);
- shortcut_highlight(uc->label, ctrl->fontselect.shortcut);
- break;
- case CTRL_LISTBOX:
- gtk_label_set_text(GTK_LABEL(uc->label), text);
- shortcut_highlight(uc->label, ctrl->listbox.shortcut);
- break;
- default:
- unreachable("bad control type in label_change");
- }
-}
-
-void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- /* We must copy fn->path before passing it to gtk_entry_set_text.
- * See comment in dlg_editbox_set() for the reasons. */
- char *duppath = dupstr(fn->path);
- assert(uc->ctrl->generic.type == CTRL_FILESELECT);
- assert(uc->entry != NULL);
- gtk_entry_set_text(GTK_ENTRY(uc->entry), duppath);
- sfree(duppath);
-}
-
-Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- assert(uc->ctrl->generic.type == CTRL_FILESELECT);
- assert(uc->entry != NULL);
- return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry)));
-}
-
-void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- /* We must copy fs->name before passing it to gtk_entry_set_text.
- * See comment in dlg_editbox_set() for the reasons. */
- char *dupname = dupstr(fs->name);
- assert(uc->ctrl->generic.type == CTRL_FONTSELECT);
- assert(uc->entry != NULL);
- gtk_entry_set_text(GTK_ENTRY(uc->entry), dupname);
- sfree(dupname);
-}
-
-FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- assert(uc->ctrl->generic.type == CTRL_FONTSELECT);
- assert(uc->entry != NULL);
- return fontspec_new(gtk_entry_get_text(GTK_ENTRY(uc->entry)));
-}
-
-/*
- * Bracketing a large set of updates in these two functions will
- * cause the front end (if possible) to delay updating the screen
- * until it's all complete, thus avoiding flicker.
- */
-void dlg_update_start(union control *ctrl, dlgparam *dp)
-{
- /*
- * Apparently we can't do this at all in GTK. GtkCList supports
- * freeze and thaw, but not GtkList. Bah.
- */
-}
-
-void dlg_update_done(union control *ctrl, dlgparam *dp)
-{
- /*
- * Apparently we can't do this at all in GTK. GtkCList supports
- * freeze and thaw, but not GtkList. Bah.
- */
-}
-
-void dlg_set_focus(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- switch (ctrl->generic.type) {
- case CTRL_CHECKBOX:
- case CTRL_BUTTON:
- /* Check boxes and buttons get the focus _and_ get toggled. */
- gtk_widget_grab_focus(uc->toplevel);
- break;
- case CTRL_FILESELECT:
- case CTRL_FONTSELECT:
- case CTRL_EDITBOX:
- if (uc->entry) {
- /* Anything containing an edit box gets that focused. */
- gtk_widget_grab_focus(uc->entry);
- }
-#if GTK_CHECK_VERSION(2,4,0)
- else if (uc->combo) {
- /* Failing that, there'll be a combo box. */
- gtk_widget_grab_focus(uc->combo);
- }
-#endif
- break;
- case CTRL_RADIO:
- /*
- * Radio buttons: we find the currently selected button and
- * focus it.
- */
- for (int i = 0; i < ctrl->radio.nbuttons; i++)
- if (gtk_toggle_button_get_active
- (GTK_TOGGLE_BUTTON(uc->buttons[i]))) {
- gtk_widget_grab_focus(uc->buttons[i]);
- }
- break;
- case CTRL_LISTBOX:
-#if !GTK_CHECK_VERSION(2,4,0)
- if (uc->optmenu) {
- gtk_widget_grab_focus(uc->optmenu);
- break;
- }
-#else
- if (uc->combo) {
- gtk_widget_grab_focus(uc->combo);
- break;
- }
-#endif
-#if !GTK_CHECK_VERSION(2,0,0)
- if (uc->list) {
- /*
- * For GTK-1 style list boxes, we tell it to focus one
- * of its children, which appears to do the Right
- * Thing.
- */
- gtk_container_focus(GTK_CONTAINER(uc->list), GTK_DIR_TAB_FORWARD);
- break;
- }
-#else
- if (uc->treeview) {
- gtk_widget_grab_focus(uc->treeview);
- break;
- }
-#endif
- unreachable("bad control type in set_focus");
- }
-}
-
-/*
- * During event processing, you might well want to give an error
- * indication to the user. dlg_beep() is a quick and easy generic
- * error; dlg_error() puts up a message-box or equivalent.
- */
-void dlg_beep(dlgparam *dp)
-{
- gdk_display_beep(gdk_display_get_default());
-}
-
-static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child)
-{
-#if !GTK_CHECK_VERSION(2,0,0)
- gint x, y, w, h, dx, dy;
- GtkRequisition req;
- gtk_window_set_position(GTK_WINDOW(child), GTK_WIN_POS_NONE);
- gtk_widget_size_request(GTK_WIDGET(child), &req);
-
- gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(parent)), &x, &y);
- gdk_window_get_size(gtk_widget_get_window(GTK_WIDGET(parent)), &w, &h);
-
- /*
- * One corner of the transient will be offset inwards, by 1/4
- * of the parent window's size, from the corresponding corner
- * of the parent window. The corner will be chosen so as to
- * place the transient closer to the centre of the screen; this
- * should avoid transients going off the edge of the screen on
- * a regular basis.
- */
- if (x + w/2 < gdk_screen_width() / 2)
- dx = x + w/4; /* work from left edges */
- else
- dx = x + 3*w/4 - req.width; /* work from right edges */
- if (y + h/2 < gdk_screen_height() / 2)
- dy = y + h/4; /* work from top edges */
- else
- dy = y + 3*h/4 - req.height; /* work from bottom edges */
- gtk_widget_set_uposition(GTK_WIDGET(child), dx, dy);
-#endif
-}
-
-void trivial_post_dialog_fn(void *vctx, int result)
-{
-}
-
-void dlg_error_msg(dlgparam *dp, const char *msg)
-{
- create_message_box(
- dp->window, "Error", msg,
- string_width("Some sort of text about a config-box error message"),
- false, &buttons_ok, trivial_post_dialog_fn, NULL);
-}
-
-/*
- * This function signals to the front end that the dialog's
- * processing is completed, and passes an integer value (typically
- * a success status).
- */
-void dlg_end(dlgparam *dp, int value)
-{
- dp->retval = value;
- gtk_widget_destroy(dp->window);
-}
-
-void dlg_refresh(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc;
-
- if (ctrl) {
- if (ctrl->generic.handler != NULL)
- ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH);
- } else {
- int i;
-
- for (i = 0; (uc = index234(dp->byctrl, i)) != NULL; i++) {
- assert(uc->ctrl != NULL);
- if (uc->ctrl->generic.handler != NULL)
- uc->ctrl->generic.handler(uc->ctrl, dp,
- dp->data, EVENT_REFRESH);
- }
- }
-}
-
-void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
-#if GTK_CHECK_VERSION(3,0,0)
- GtkWidget *coloursel =
- gtk_color_chooser_dialog_new("Select a colour",
- GTK_WINDOW(dp->window));
- gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(coloursel), false);
-#else
- GtkWidget *okbutton, *cancelbutton;
- GtkWidget *coloursel =
- gtk_color_selection_dialog_new("Select a colour");
- GtkColorSelectionDialog *ccs = GTK_COLOR_SELECTION_DIALOG(coloursel);
- GtkColorSelection *cs = GTK_COLOR_SELECTION
- (gtk_color_selection_dialog_get_color_selection(ccs));
- gtk_color_selection_set_has_opacity_control(cs, false);
-#endif
-
- dp->coloursel_result.ok = false;
-
- gtk_window_set_modal(GTK_WINDOW(coloursel), true);
-
-#if GTK_CHECK_VERSION(3,0,0)
- {
- GdkRGBA rgba;
- rgba.red = r / 255.0;
- rgba.green = g / 255.0;
- rgba.blue = b / 255.0;
- rgba.alpha = 1.0; /* fully opaque! */
- gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(coloursel), &rgba);
- }
-#elif GTK_CHECK_VERSION(2,0,0)
- {
- GdkColor col;
- col.red = r * 0x0101;
- col.green = g * 0x0101;
- col.blue = b * 0x0101;
- gtk_color_selection_set_current_color(cs, &col);
- }
-#else
- {
- gdouble cvals[4];
- cvals[0] = r / 255.0;
- cvals[1] = g / 255.0;
- cvals[2] = b / 255.0;
- cvals[3] = 1.0; /* fully opaque! */
- gtk_color_selection_set_color(cs, cvals);
- }
-#endif
-
- g_object_set_data(G_OBJECT(coloursel), "user-data", (gpointer)uc);
-
-#if GTK_CHECK_VERSION(3,0,0)
- g_signal_connect(G_OBJECT(coloursel), "response",
- G_CALLBACK(colourchoose_response), (gpointer)dp);
-#else
-
-#if GTK_CHECK_VERSION(2,0,0)
- g_object_get(G_OBJECT(ccs),
- "ok-button", &okbutton,
- "cancel-button", &cancelbutton,
- (const char *)NULL);
-#else
- okbutton = ccs->ok_button;
- cancelbutton = ccs->cancel_button;
-#endif
- g_object_set_data(G_OBJECT(okbutton), "user-data",
- (gpointer)coloursel);
- g_object_set_data(G_OBJECT(cancelbutton), "user-data",
- (gpointer)coloursel);
- g_signal_connect(G_OBJECT(okbutton), "clicked",
- G_CALLBACK(coloursel_ok), (gpointer)dp);
- g_signal_connect(G_OBJECT(cancelbutton), "clicked",
- G_CALLBACK(coloursel_cancel), (gpointer)dp);
- g_signal_connect_swapped(G_OBJECT(okbutton), "clicked",
- G_CALLBACK(gtk_widget_destroy),
- (gpointer)coloursel);
- g_signal_connect_swapped(G_OBJECT(cancelbutton), "clicked",
- G_CALLBACK(gtk_widget_destroy),
- (gpointer)coloursel);
-#endif
- gtk_widget_show(coloursel);
-}
-
-bool dlg_coloursel_results(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;
-}
-
-/* ----------------------------------------------------------------------
- * Signal handlers while the dialog box is active.
- */
-
-static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event,
- gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, widget);
- union control *focus;
-
- if (uc && uc->ctrl)
- focus = uc->ctrl;
- else
- focus = NULL;
-
- if (focus != dp->currfocus) {
- dp->lastfocus = dp->currfocus;
- dp->currfocus = focus;
- }
-
- return false;
-}
-
-static void button_clicked(GtkButton *button, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
-}
-
-static void button_toggled(GtkToggleButton *tb, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tb));
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
-}
-
-static gboolean editbox_key(GtkWidget *widget, GdkEventKey *event,
- gpointer data)
-{
- /*
- * GtkEntry has a nasty habit of eating the Return key, which
- * is unhelpful since it doesn't actually _do_ anything with it
- * (it calls gtk_widget_activate, but our edit boxes never need
- * activating). So I catch Return before GtkEntry sees it, and
- * pass it straight on to the parent widget. Effect: hitting
- * Return in an edit box will now activate the default button
- * in the dialog just like it will everywhere else.
- */
- GtkWidget *parent = gtk_widget_get_parent(widget);
- if (event->keyval == GDK_KEY_Return && parent != NULL) {
- gboolean return_val;
- g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
- g_signal_emit_by_name(G_OBJECT(parent), "key_press_event",
- event, &return_val);
- return return_val;
- }
- return false;
-}
-
-static void editbox_changed(GtkEditable *ed, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- if (!(dp->flags & FLAG_UPDATING_COMBO_LIST)) {
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed));
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
- }
-}
-
-static gboolean editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event,
- gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed));
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_REFRESH);
- return false;
-}
-
-#if !GTK_CHECK_VERSION(2,0,0)
-
-/*
- * GTK 1 list box event handlers.
- */
-
-static gboolean listitem_key(GtkWidget *item, GdkEventKey *event,
- gpointer data, bool multiple)
-{
- GtkAdjustment *adj = GTK_ADJUSTMENT(data);
-
- if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
- event->keyval == GDK_Down || event->keyval == GDK_KP_Down ||
- event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up ||
- event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down) {
- /*
- * Up, Down, PgUp or PgDn have been pressed on a ListItem
- * in a list box. So, if the list box is single-selection:
- *
- * - if the list item in question isn't already selected,
- * we simply select it.
- * - otherwise, we find the next one (or next
- * however-far-away) in whichever direction we're going,
- * and select that.
- * + in this case, we must also fiddle with the
- * scrollbar to ensure the newly selected item is
- * actually visible.
- *
- * If it's multiple-selection, we do all of the above
- * except actually selecting anything, so we move the focus
- * and fiddle the scrollbar to follow it.
- */
- GtkWidget *list = item->parent;
-
- g_signal_stop_emission_by_name(G_OBJECT(item), "key_press_event");
-
- if (!multiple &&
- GTK_WIDGET_STATE(item) != GTK_STATE_SELECTED) {
- gtk_list_select_child(GTK_LIST(list), item);
- } else {
- int direction =
- (event->keyval==GDK_Up || event->keyval==GDK_KP_Up ||
- event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up)
- ? -1 : +1;
- int step =
- (event->keyval==GDK_Page_Down ||
- event->keyval==GDK_KP_Page_Down ||
- event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up)
- ? 2 : 1;
- int i, n;
- GList *children, *chead;
-
- chead = children = gtk_container_children(GTK_CONTAINER(list));
-
- n = g_list_length(children);
-
- if (step == 2) {
- /*
- * Figure out how many list items to a screenful,
- * and adjust the step appropriately.
- */
- step = 0.5 + adj->page_size * n / (adj->upper - adj->lower);
- step--; /* go by one less than that */
- }
-
- i = 0;
- while (children != NULL) {
- if (item == children->data)
- break;
- children = children->next;
- i++;
- }
-
- while (step > 0) {
- if (direction < 0 && i > 0)
- children = children->prev, i--;
- else if (direction > 0 && i < n-1)
- children = children->next, i++;
- step--;
- }
-
- if (children && children->data) {
- if (!multiple)
- gtk_list_select_child(GTK_LIST(list),
- GTK_WIDGET(children->data));
- gtk_widget_grab_focus(GTK_WIDGET(children->data));
- gtk_adjustment_clamp_page
- (adj,
- adj->lower + (adj->upper-adj->lower) * i / n,
- adj->lower + (adj->upper-adj->lower) * (i+1) / n);
- }
-
- g_list_free(chead);
- }
- return true;
- }
-
- return false;
-}
-
-static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event,
- gpointer data)
-{
- return listitem_key(item, event, data, false);
-}
-
-static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event,
- gpointer data)
-{
- return listitem_key(item, event, data, true);
-}
-
-static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event,
- gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item));
- switch (event->type) {
- default:
- case GDK_BUTTON_PRESS: uc->nclicks = 1; break;
- case GDK_2BUTTON_PRESS: uc->nclicks = 2; break;
- case GDK_3BUTTON_PRESS: uc->nclicks = 3; break;
- }
- return false;
-}
-
-static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event,
- gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item));
- if (uc->nclicks>1) {
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
- return true;
- }
- return false;
-}
-
-static void list_selchange(GtkList *list, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(list));
- if (!uc) return;
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
-}
-
-static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction)
-{
- int index = dlg_listbox_index(uc->ctrl, dp);
- GList *children = gtk_container_children(GTK_CONTAINER(uc->list));
- GtkWidget *child;
-
- if ((index < 0) ||
- (index == 0 && direction < 0) ||
- (index == g_list_length(children)-1 && direction > 0)) {
- gdk_display_beep(gdk_display_get_default());
- return;
- }
-
- child = g_list_nth_data(children, index);
- gtk_widget_ref(child);
- gtk_list_clear_items(GTK_LIST(uc->list), index, index+1);
- g_list_free(children);
-
- children = NULL;
- children = g_list_append(children, child);
- gtk_list_insert_items(GTK_LIST(uc->list), children, index + direction);
- gtk_list_select_item(GTK_LIST(uc->list), index + direction);
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
-}
-
-static void draglist_up(GtkButton *button, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
- draglist_move(dp, uc, -1);
-}
-
-static void draglist_down(GtkButton *button, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
- draglist_move(dp, uc, +1);
-}
-
-#else /* !GTK_CHECK_VERSION(2,0,0) */
-
-/*
- * GTK 2 list box event handlers.
- */
-
-static void listbox_doubleclick(GtkTreeView *treeview, GtkTreePath *path,
- GtkTreeViewColumn *column, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(treeview));
- if (uc)
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
-}
-
-static void listbox_selchange(GtkTreeSelection *treeselection,
- gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- GtkTreeView *tree = gtk_tree_selection_get_tree_view(treeselection);
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tree));
- if (uc)
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
-}
-
-struct draglist_valchange_ctx {
- struct uctrl *uc;
- struct dlgparam *dp;
-};
-
-static gboolean draglist_valchange(gpointer data)
-{
- struct draglist_valchange_ctx *ctx =
- (struct draglist_valchange_ctx *)data;
-
- ctx->uc->ctrl->generic.handler(ctx->uc->ctrl, ctx->dp,
- ctx->dp->data, EVENT_VALCHANGE);
-
- sfree(ctx);
-
- return false;
-}
-
-static void listbox_reorder(GtkTreeModel *treemodel, GtkTreePath *path,
- GtkTreeIter *iter, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- gpointer tree;
- struct uctrl *uc;
-
- if (dp->flags & FLAG_UPDATING_LISTBOX)
- return; /* not a user drag operation */
-
- tree = g_object_get_data(G_OBJECT(treemodel), "user-data");
- uc = dlg_find_bywidget(dp, GTK_WIDGET(tree));
- if (uc) {
- /*
- * We should cause EVENT_VALCHANGE on the list box, now
- * that its rows have been reordered. However, the GTK 2
- * docs say that at the point this signal is received the
- * new row might not have actually been filled in yet.
- *
- * (So what smegging use is it then, eh? Don't suppose it
- * occurred to you at any point that letting the
- * application know _after_ the reordering was compelete
- * might be helpful to someone?)
- *
- * To get round this, I schedule an idle function, which I
- * hope won't be called until the main event loop is
- * re-entered after the drag-and-drop handler has finished
- * furtling with the list store.
- */
- struct draglist_valchange_ctx *ctx =
- snew(struct draglist_valchange_ctx);
- ctx->uc = uc;
- ctx->dp = dp;
- g_idle_add(draglist_valchange, ctx);
- }
-}
-
-#endif /* !GTK_CHECK_VERSION(2,0,0) */
-
-#if !GTK_CHECK_VERSION(2,4,0)
-
-static void menuitem_activate(GtkMenuItem *item, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- GtkWidget *menushell = GTK_WIDGET(item)->parent;
- gpointer optmenu = g_object_get_data(G_OBJECT(menushell), "user-data");
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(optmenu));
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
-}
-
-#else
-
-static void droplist_selchange(GtkComboBox *combo, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(combo));
- if (uc)
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
-}
-
-#endif /* !GTK_CHECK_VERSION(2,4,0) */
-
-#ifdef USE_GTK_FILE_CHOOSER_DIALOG
-static void filechoose_response(GtkDialog *dialog, gint response,
- gpointer data)
-{
- /* struct dlgparam *dp = (struct dlgparam *)data; */
- struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data");
- if (response == GTK_RESPONSE_ACCEPT) {
- gchar *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
- gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
- g_free(name);
- }
- gtk_widget_destroy(GTK_WIDGET(dialog));
-}
-#else
-static void filesel_ok(GtkButton *button, gpointer data)
-{
- /* struct dlgparam *dp = (struct dlgparam *)data; */
- gpointer filesel = g_object_get_data(G_OBJECT(button), "user-data");
- struct uctrl *uc = g_object_get_data(G_OBJECT(filesel), "user-data");
- const char *name = gtk_file_selection_get_filename
- (GTK_FILE_SELECTION(filesel));
- gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
-}
-#endif
-
-static void fontsel_ok(GtkButton *button, gpointer data)
-{
- /* struct dlgparam *dp = (struct dlgparam *)data; */
-
-#if !GTK_CHECK_VERSION(2,0,0)
-
- gpointer fontsel = g_object_get_data(G_OBJECT(button), "user-data");
- struct uctrl *uc = g_object_get_data(G_OBJECT(fontsel), "user-data");
- const char *name = gtk_font_selection_dialog_get_font_name
- (GTK_FONT_SELECTION_DIALOG(fontsel));
- gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
-
-#else
-
- unifontsel *fontsel = (unifontsel *)g_object_get_data
- (G_OBJECT(button), "user-data");
- struct uctrl *uc = (struct uctrl *)fontsel->user_data;
- char *name = unifontsel_get_name(fontsel);
- assert(name); /* should always be ok after OK pressed */
- gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
- sfree(name);
-
-#endif
-}
-
-#if GTK_CHECK_VERSION(3,0,0)
-
-static void colourchoose_response(GtkDialog *dialog,
- gint response_id, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data");
-
- if (response_id == GTK_RESPONSE_OK) {
- GdkRGBA rgba;
- gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(dialog), &rgba);
- dp->coloursel_result.r = (int) (255 * rgba.red);
- dp->coloursel_result.g = (int) (255 * rgba.green);
- dp->coloursel_result.b = (int) (255 * rgba.blue);
- dp->coloursel_result.ok = true;
- } else {
- dp->coloursel_result.ok = false;
- }
-
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
-
- gtk_widget_destroy(GTK_WIDGET(dialog));
-}
-
-#else /* GTK 1/2 coloursel response handlers */
-
-static void coloursel_ok(GtkButton *button, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data");
- struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data");
-
-#if GTK_CHECK_VERSION(2,0,0)
- {
- GtkColorSelection *cs = GTK_COLOR_SELECTION
- (gtk_color_selection_dialog_get_color_selection
- (GTK_COLOR_SELECTION_DIALOG(coloursel)));
- GdkColor col;
- gtk_color_selection_get_current_color(cs, &col);
- dp->coloursel_result.r = col.red / 0x0100;
- dp->coloursel_result.g = col.green / 0x0100;
- dp->coloursel_result.b = col.blue / 0x0100;
- }
-#else
- {
- GtkColorSelection *cs = GTK_COLOR_SELECTION
- (gtk_color_selection_dialog_get_color_selection
- (GTK_COLOR_SELECTION_DIALOG(coloursel)));
- gdouble cvals[4];
- gtk_color_selection_get_color(cs, cvals);
- dp->coloursel_result.r = (int) (255 * cvals[0]);
- dp->coloursel_result.g = (int) (255 * cvals[1]);
- dp->coloursel_result.b = (int) (255 * cvals[2]);
- }
-#endif
- dp->coloursel_result.ok = true;
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
-}
-
-static void coloursel_cancel(GtkButton *button, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data");
- struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data");
- dp->coloursel_result.ok = false;
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
-}
-
-#endif /* end of coloursel response handlers */
-
-static void filefont_clicked(GtkButton *button, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
-
- if (uc->ctrl->generic.type == CTRL_FILESELECT) {
-#ifdef USE_GTK_FILE_CHOOSER_DIALOG
- GtkWidget *filechoose = gtk_file_chooser_dialog_new
- (uc->ctrl->fileselect.title, GTK_WINDOW(dp->window),
- (uc->ctrl->fileselect.for_writing ?
- GTK_FILE_CHOOSER_ACTION_SAVE :
- GTK_FILE_CHOOSER_ACTION_OPEN),
- STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL,
- STANDARD_OPEN_LABEL, GTK_RESPONSE_ACCEPT,
- (const gchar *)NULL);
- gtk_window_set_modal(GTK_WINDOW(filechoose), true);
- g_object_set_data(G_OBJECT(filechoose), "user-data", (gpointer)uc);
- g_signal_connect(G_OBJECT(filechoose), "response",
- G_CALLBACK(filechoose_response), (gpointer)dp);
- gtk_widget_show(filechoose);
-#else
- GtkWidget *filesel =
- gtk_file_selection_new(uc->ctrl->fileselect.title);
- gtk_window_set_modal(GTK_WINDOW(filesel), true);
- g_object_set_data
- (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data",
- (gpointer)filesel);
- g_object_set_data(G_OBJECT(filesel), "user-data", (gpointer)uc);
- g_signal_connect
- (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
- G_CALLBACK(filesel_ok), (gpointer)dp);
- g_signal_connect_swapped
- (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
- G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
- g_signal_connect_swapped
- (G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked",
- G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
- gtk_widget_show(filesel);
-#endif
- }
-
- if (uc->ctrl->generic.type == CTRL_FONTSELECT) {
- const gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry));
-
-#if !GTK_CHECK_VERSION(2,0,0)
-
- /*
- * Use the GTK 1 standard font selector.
- */
-
- gchar *spacings[] = { "c", "m", NULL };
- GtkWidget *fontsel =
- gtk_font_selection_dialog_new("Select a font");
- gtk_window_set_modal(GTK_WINDOW(fontsel), true);
- gtk_font_selection_dialog_set_filter
- (GTK_FONT_SELECTION_DIALOG(fontsel),
- GTK_FONT_FILTER_BASE, GTK_FONT_ALL,
- NULL, NULL, NULL, NULL, spacings, NULL);
- if (!gtk_font_selection_dialog_set_font_name
- (GTK_FONT_SELECTION_DIALOG(fontsel), fontname)) {
- /*
- * If the font name wasn't found as it was, try opening
- * it and extracting its FONT property. This should
- * have the effect of mapping short aliases into true
- * XLFDs.
- */
- GdkFont *font = gdk_font_load(fontname);
- if (font) {
- XFontStruct *xfs = GDK_FONT_XFONT(font);
- Display *disp = get_x11_display();
- Atom fontprop = XInternAtom(disp, "FONT", False);
- unsigned long ret;
-
- assert(disp); /* this is GTK1! */
-
- gdk_font_ref(font);
- if (XGetFontProperty(xfs, fontprop, &ret)) {
- char *name = XGetAtomName(disp, (Atom)ret);
- if (name)
- gtk_font_selection_dialog_set_font_name
- (GTK_FONT_SELECTION_DIALOG(fontsel), name);
- }
- gdk_font_unref(font);
- }
- }
- g_object_set_data
- (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
- "user-data", (gpointer)fontsel);
- g_object_set_data(G_OBJECT(fontsel), "user-data", (gpointer)uc);
- g_signal_connect
- (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
- "clicked", G_CALLBACK(fontsel_ok), (gpointer)dp);
- g_signal_connect_swapped
- (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
- "clicked", G_CALLBACK(gtk_widget_destroy),
- (gpointer)fontsel);
- g_signal_connect_swapped
- (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button),
- "clicked", G_CALLBACK(gtk_widget_destroy),
- (gpointer)fontsel);
- gtk_widget_show(fontsel);
-
-#else /* !GTK_CHECK_VERSION(2,0,0) */
-
- /*
- * Use the unifontsel code provided in gtkfont.c.
- */
-
- unifontsel *fontsel = unifontsel_new("Select a font");
-
- gtk_window_set_modal(fontsel->window, true);
- unifontsel_set_name(fontsel, fontname);
-
- g_object_set_data(G_OBJECT(fontsel->ok_button),
- "user-data", (gpointer)fontsel);
- fontsel->user_data = uc;
- g_signal_connect(G_OBJECT(fontsel->ok_button), "clicked",
- G_CALLBACK(fontsel_ok), (gpointer)dp);
- g_signal_connect_swapped(G_OBJECT(fontsel->ok_button), "clicked",
- G_CALLBACK(unifontsel_destroy),
- (gpointer)fontsel);
- g_signal_connect_swapped(G_OBJECT(fontsel->cancel_button),"clicked",
- G_CALLBACK(unifontsel_destroy),
- (gpointer)fontsel);
-
- gtk_widget_show(GTK_WIDGET(fontsel->window));
-
-#endif /* !GTK_CHECK_VERSION(2,0,0) */
-
- }
-}
-
-#if !GTK_CHECK_VERSION(3,0,0)
-static void label_sizealloc(GtkWidget *widget, GtkAllocation *alloc,
- gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, widget);
-
- gtk_widget_set_size_request(uc->text, alloc->width, -1);
- gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->generic.label);
- g_signal_handler_disconnect(G_OBJECT(uc->text), uc->textsig);
-}
-#endif
-
-/* ----------------------------------------------------------------------
- * This function does the main layout work: it reads a controlset,
- * it creates the relevant GTK controls, and returns a GtkWidget
- * containing the result. (This widget might be a title of some
- * sort, it might be a Columns containing many controls, or it
- * might be a GtkFrame containing a Columns; whatever it is, it's
- * definitely a GtkWidget and should probably be added to a
- * GtkVbox.)
- *
- * `win' is required for setting the default button. If it is
- * non-NULL, all buttons created will be default-capable (so they
- * have extra space round them for the default highlight).
- */
-GtkWidget *layout_ctrls(
- struct dlgparam *dp, struct selparam *sp, struct Shortcuts *scs,
- struct controlset *s, GtkWindow *win)
-{
- Columns *cols;
- GtkWidget *ret;
- int i;
-
- if (!s->boxname) {
- /* This controlset is a panel title. */
- assert(s->boxtitle);
- return gtk_label_new(s->boxtitle);
- }
-
- /*
- * Otherwise, we expect to be laying out actual controls, so
- * we'll start by creating a Columns for the purpose.
- */
- cols = COLUMNS(columns_new(4));
- ret = GTK_WIDGET(cols);
- gtk_widget_show(ret);
-
- /*
- * Create a containing frame if we have a box name.
- */
- if (*s->boxname) {
- ret = gtk_frame_new(s->boxtitle); /* NULL is valid here */
- gtk_container_set_border_width(GTK_CONTAINER(cols), 4);
- gtk_container_add(GTK_CONTAINER(ret), GTK_WIDGET(cols));
- gtk_widget_show(ret);
- }
-
- /*
- * Now iterate through the controls themselves, create them,
- * and add them to the Columns.
- */
- for (i = 0; i < s->ncontrols; i++) {
- union control *ctrl = s->ctrls[i];
- struct uctrl *uc;
- bool left = false;
- GtkWidget *w = NULL;
-
- switch (ctrl->generic.type) {
- case CTRL_COLUMNS: {
- static const int simplecols[1] = { 100 };
- columns_set_cols(cols, ctrl->columns.ncols,
- (ctrl->columns.percentages ?
- ctrl->columns.percentages : simplecols));
- continue; /* no actual control created */
- }
- case CTRL_TABDELAY: {
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl->tabdelay.ctrl);
- if (uc)
- columns_taborder_last(cols, uc->toplevel);
- continue; /* no actual control created */
- }
- }
-
- uc = snew(struct uctrl);
- uc->sp = sp;
- uc->ctrl = ctrl;
- uc->buttons = NULL;
- uc->entry = NULL;
-#if !GTK_CHECK_VERSION(2,4,0)
- uc->list = uc->menu = uc->optmenu = NULL;
-#else
- uc->combo = NULL;
-#endif
-#if GTK_CHECK_VERSION(2,0,0)
- uc->treeview = NULL;
- uc->listmodel = NULL;
-#endif
- uc->button = uc->text = NULL;
- uc->label = NULL;
- uc->nclicks = 0;
-
- switch (ctrl->generic.type) {
- case CTRL_BUTTON:
- w = gtk_button_new_with_label(ctrl->generic.label);
- if (win) {
- gtk_widget_set_can_default(w, true);
- if (ctrl->button.isdefault)
- gtk_window_set_default(win, w);
- if (ctrl->button.iscancel)
- dp->cancelbutton = w;
- }
- g_signal_connect(G_OBJECT(w), "clicked",
- G_CALLBACK(button_clicked), dp);
- g_signal_connect(G_OBJECT(w), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
- ctrl->button.shortcut, SHORTCUT_UCTRL, uc);
- break;
- case CTRL_CHECKBOX:
- w = gtk_check_button_new_with_label(ctrl->generic.label);
- g_signal_connect(G_OBJECT(w), "toggled",
- G_CALLBACK(button_toggled), dp);
- g_signal_connect(G_OBJECT(w), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
- ctrl->checkbox.shortcut, SHORTCUT_UCTRL, uc);
- left = true;
- break;
- case CTRL_RADIO: {
- /*
- * Radio buttons get to go inside their own Columns, no
- * matter what.
- */
- gint i, *percentages;
- GSList *group;
-
- w = columns_new(0);
- if (ctrl->generic.label) {
- GtkWidget *label = gtk_label_new(ctrl->generic.label);
- columns_add(COLUMNS(w), label, 0, 1);
- columns_force_left_align(COLUMNS(w), label);
- gtk_widget_show(label);
- shortcut_add(scs, label, ctrl->radio.shortcut,
- SHORTCUT_UCTRL, uc);
- uc->label = label;
- }
- percentages = g_new(gint, ctrl->radio.ncolumns);
- for (i = 0; i < ctrl->radio.ncolumns; i++) {
- percentages[i] =
- ((100 * (i+1) / ctrl->radio.ncolumns) -
- 100 * i / ctrl->radio.ncolumns);
- }
- columns_set_cols(COLUMNS(w), ctrl->radio.ncolumns,
- percentages);
- g_free(percentages);
- group = NULL;
-
- uc->nbuttons = ctrl->radio.nbuttons;
- uc->buttons = snewn(uc->nbuttons, GtkWidget *);
-
- for (i = 0; i < ctrl->radio.nbuttons; i++) {
- GtkWidget *b;
- gint colstart;
-
- b = (gtk_radio_button_new_with_label
- (group, ctrl->radio.buttons[i]));
- uc->buttons[i] = b;
- group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b));
- colstart = i % ctrl->radio.ncolumns;
- columns_add(COLUMNS(w), b, colstart,
- (i == ctrl->radio.nbuttons-1 ?
- ctrl->radio.ncolumns - colstart : 1));
- columns_force_left_align(COLUMNS(w), b);
- gtk_widget_show(b);
- g_signal_connect(G_OBJECT(b), "toggled",
- G_CALLBACK(button_toggled), dp);
- g_signal_connect(G_OBJECT(b), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- if (ctrl->radio.shortcuts) {
- shortcut_add(scs, gtk_bin_get_child(GTK_BIN(b)),
- ctrl->radio.shortcuts[i],
- SHORTCUT_UCTRL, uc);
- }
- }
- break;
- }
- case CTRL_EDITBOX: {
- GtkWidget *signalobject;
-
- if (ctrl->editbox.has_list) {
-#if !GTK_CHECK_VERSION(2,4,0)
- /*
- * GTK 1 combo box.
- */
- w = gtk_combo_new();
- gtk_combo_set_value_in_list(GTK_COMBO(w), false, true);
- uc->entry = GTK_COMBO(w)->entry;
- uc->list = GTK_COMBO(w)->list;
- signalobject = uc->entry;
-#else
- /*
- * GTK 2 combo box.
- */
- uc->listmodel = gtk_list_store_new(2, G_TYPE_INT,
- G_TYPE_STRING);
- w = gtk_combo_box_new_with_model_and_entry
- (GTK_TREE_MODEL(uc->listmodel));
- g_object_set(G_OBJECT(w), "entry-text-column", 1,
- (const char *)NULL);
- /* We cannot support password combo boxes. */
- assert(!ctrl->editbox.password);
- uc->combo = w;
- signalobject = uc->combo;
-#endif
- } else {
- w = gtk_entry_new();
- if (ctrl->editbox.password)
- gtk_entry_set_visibility(GTK_ENTRY(w), false);
- uc->entry = w;
- signalobject = w;
- }
- uc->entrysig =
- g_signal_connect(G_OBJECT(signalobject), "changed",
- G_CALLBACK(editbox_changed), dp);
- g_signal_connect(G_OBJECT(signalobject), "key_press_event",
- G_CALLBACK(editbox_key), dp);
- g_signal_connect(G_OBJECT(signalobject), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- g_signal_connect(G_OBJECT(signalobject), "focus_out_event",
- G_CALLBACK(editbox_lostfocus), dp);
- g_signal_connect(G_OBJECT(signalobject), "focus_out_event",
- G_CALLBACK(editbox_lostfocus), dp);
-
-#if !GTK_CHECK_VERSION(3,0,0)
- /*
- * Edit boxes, for some strange reason, have a minimum
- * width of 150 in GTK 1.2. We don't want this - we'd
- * rather the edit boxes acquired their natural width
- * from the column layout of the rest of the box.
- */
- {
- GtkRequisition req;
- gtk_widget_size_request(w, &req);
- gtk_widget_set_size_request(w, 10, req.height);
- }
-#else
- /*
- * In GTK 3, this is still true, but there's a special
- * method for GtkEntry in particular to fix it.
- */
- if (GTK_IS_ENTRY(w))
- gtk_entry_set_width_chars(GTK_ENTRY(w), 1);
-#endif
-
- if (ctrl->generic.label) {
- GtkWidget *label, *container;
-
- label = gtk_label_new(ctrl->generic.label);
-
- shortcut_add(scs, label, ctrl->editbox.shortcut,
- SHORTCUT_FOCUS, uc->entry);
-
- container = columns_new(4);
- if (ctrl->editbox.percentwidth == 100) {
- columns_add(COLUMNS(container), label, 0, 1);
- columns_force_left_align(COLUMNS(container), label);
- columns_add(COLUMNS(container), w, 0, 1);
- } else {
- gint percentages[2];
- percentages[1] = ctrl->editbox.percentwidth;
- percentages[0] = 100 - ctrl->editbox.percentwidth;
- columns_set_cols(COLUMNS(container), 2, percentages);
- columns_add(COLUMNS(container), label, 0, 1);
- columns_force_left_align(COLUMNS(container), label);
- columns_add(COLUMNS(container), w, 1, 1);
- columns_force_same_height(COLUMNS(container),
- label, w);
- }
- gtk_widget_show(label);
- gtk_widget_show(w);
-
- w = container;
- uc->label = label;
- }
- break;
- }
- case CTRL_FILESELECT:
- case CTRL_FONTSELECT: {
- GtkWidget *ww;
- const char *browsebtn =
- (ctrl->generic.type == CTRL_FILESELECT ?
- "Browse..." : "Change...");
-
- gint percentages[] = { 75, 25 };
- w = columns_new(4);
- columns_set_cols(COLUMNS(w), 2, percentages);
-
- if (ctrl->generic.label) {
- ww = gtk_label_new(ctrl->generic.label);
- columns_add(COLUMNS(w), ww, 0, 2);
- columns_force_left_align(COLUMNS(w), ww);
- gtk_widget_show(ww);
- shortcut_add(scs, ww,
- (ctrl->generic.type == CTRL_FILESELECT ?
- ctrl->fileselect.shortcut :
- ctrl->fontselect.shortcut),
- SHORTCUT_UCTRL, uc);
- uc->label = ww;
- }
-
- uc->entry = ww = gtk_entry_new();
-#if !GTK_CHECK_VERSION(3,0,0)
- {
- GtkRequisition req;
- gtk_widget_size_request(ww, &req);
- gtk_widget_set_size_request(ww, 10, req.height);
- }
-#else
- gtk_entry_set_width_chars(GTK_ENTRY(ww), 1);
-#endif
- columns_add(COLUMNS(w), ww, 0, 1);
- gtk_widget_show(ww);
-
- uc->button = ww = gtk_button_new_with_label(browsebtn);
- columns_add(COLUMNS(w), ww, 1, 1);
- gtk_widget_show(ww);
-
- columns_force_same_height(COLUMNS(w), uc->entry, uc->button);
-
- g_signal_connect(G_OBJECT(uc->entry), "key_press_event",
- G_CALLBACK(editbox_key), dp);
- uc->entrysig =
- g_signal_connect(G_OBJECT(uc->entry), "changed",
- G_CALLBACK(editbox_changed), dp);
- g_signal_connect(G_OBJECT(uc->entry), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- g_signal_connect(G_OBJECT(uc->button), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- g_signal_connect(G_OBJECT(ww), "clicked",
- G_CALLBACK(filefont_clicked), dp);
- break;
- }
- case CTRL_LISTBOX:
-
-#if GTK_CHECK_VERSION(2,0,0)
- /*
- * First construct the list data store, with the right
- * number of columns.
- */
-# if !GTK_CHECK_VERSION(2,4,0)
- /* (For GTK 2.0 to 2.3, we do this for full listboxes only,
- * because combo boxes are still done the old GTK1 way.) */
- if (ctrl->listbox.height > 0)
-# endif
- {
- GType *types;
- int i;
- int cols;
-
- cols = ctrl->listbox.ncols;
- cols = cols ? cols : 1;
- types = snewn(1 + cols, GType);
-
- types[0] = G_TYPE_INT;
- for (i = 0; i < cols; i++)
- types[i+1] = G_TYPE_STRING;
-
- uc->listmodel = gtk_list_store_newv(1 + cols, types);
-
- sfree(types);
- }
-#endif
-
- /*
- * See if it's a drop-down list (non-editable combo
- * box).
- */
- if (ctrl->listbox.height == 0) {
-#if !GTK_CHECK_VERSION(2,4,0)
- /*
- * GTK1 and early-GTK2 option-menu style of
- * drop-down list.
- */
- uc->optmenu = w = gtk_option_menu_new();
- uc->menu = gtk_menu_new();
- gtk_option_menu_set_menu(GTK_OPTION_MENU(w), uc->menu);
- g_object_set_data(G_OBJECT(uc->menu), "user-data",
- (gpointer)uc->optmenu);
- g_signal_connect(G_OBJECT(uc->optmenu), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
-#else
- /*
- * Late-GTK2 style using a GtkComboBox.
- */
- GtkCellRenderer *cr;
-
- /*
- * Create a non-editable GtkComboBox (that is, not
- * its subclass GtkComboBoxEntry).
- */
- w = gtk_combo_box_new_with_model
- (GTK_TREE_MODEL(uc->listmodel));
- uc->combo = w;
-
- /*
- * Tell it how to render a list item (i.e. which
- * column to look at in the list model).
- */
- cr = gtk_cell_renderer_text_new();
- gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, true);
- gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), cr,
- "text", 1, NULL);
-
- /*
- * And tell it to notify us when the selection
- * changes.
- */
- g_signal_connect(G_OBJECT(w), "changed",
- G_CALLBACK(droplist_selchange), dp);
-
- g_signal_connect(G_OBJECT(w), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
-#endif
- } else {
-#if !GTK_CHECK_VERSION(2,0,0)
- /*
- * GTK1-style full list box.
- */
- uc->list = gtk_list_new();
- if (ctrl->listbox.multisel == 2) {
- gtk_list_set_selection_mode(GTK_LIST(uc->list),
- GTK_SELECTION_EXTENDED);
- } else if (ctrl->listbox.multisel == 1) {
- gtk_list_set_selection_mode(GTK_LIST(uc->list),
- GTK_SELECTION_MULTIPLE);
- } else {
- gtk_list_set_selection_mode(GTK_LIST(uc->list),
- GTK_SELECTION_SINGLE);
- }
- w = gtk_scrolled_window_new(NULL, NULL);
- gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(w),
- uc->list);
- gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
- GTK_POLICY_NEVER,
- GTK_POLICY_AUTOMATIC);
- uc->adj = gtk_scrolled_window_get_vadjustment
- (GTK_SCROLLED_WINDOW(w));
-
- gtk_widget_show(uc->list);
- g_signal_connect(G_OBJECT(uc->list), "selection-changed",
- G_CALLBACK(list_selchange), dp);
- g_signal_connect(G_OBJECT(uc->list), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
-
- /*
- * Adjust the height of the scrolled window to the
- * minimum given by the height parameter.
- *
- * This piece of guesswork is a horrid hack based
- * on looking inside the GTK 1.2 sources
- * (specifically gtkviewport.c, which appears to be
- * the widget which provides the border around the
- * scrolling area). Anyone lets me know how I can
- * do this in a way which isn't at risk from GTK
- * upgrades, I'd be grateful.
- */
- {
- int edge;
- edge = GTK_WIDGET(uc->list)->style->klass->ythickness;
- gtk_widget_set_size_request(w, 10,
- 2*edge + (ctrl->listbox.height *
- get_listitemheight(w)));
- }
-
- if (ctrl->listbox.draglist) {
- /*
- * GTK doesn't appear to make it easy to
- * implement a proper draggable list; so
- * instead I'm just going to have to put an Up
- * and a Down button to the right of the actual
- * list box. Ah well.
- */
- GtkWidget *cols, *button;
- static const gint percentages[2] = { 80, 20 };
-
- cols = columns_new(4);
- columns_set_cols(COLUMNS(cols), 2, percentages);
- columns_add(COLUMNS(cols), w, 0, 1);
- gtk_widget_show(w);
- button = gtk_button_new_with_label("Up");
- columns_add(COLUMNS(cols), button, 1, 1);
- gtk_widget_show(button);
- g_signal_connect(G_OBJECT(button), "clicked",
- G_CALLBACK(draglist_up), dp);
- g_signal_connect(G_OBJECT(button), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- button = gtk_button_new_with_label("Down");
- columns_add(COLUMNS(cols), button, 1, 1);
- gtk_widget_show(button);
- g_signal_connect(G_OBJECT(button), "clicked",
- G_CALLBACK(draglist_down), dp);
- g_signal_connect(G_OBJECT(button), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
-
- w = cols;
- }
-#else
- /*
- * GTK2 treeview-based full list box.
- */
- GtkTreeSelection *sel;
-
- /*
- * Create the list box itself, its columns, and
- * its containing scrolled window.
- */
- w = gtk_tree_view_new_with_model
- (GTK_TREE_MODEL(uc->listmodel));
- g_object_set_data(G_OBJECT(uc->listmodel), "user-data",
- (gpointer)w);
- gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false);
- sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(w));
- gtk_tree_selection_set_mode
- (sel, ctrl->listbox.multisel ? GTK_SELECTION_MULTIPLE :
- GTK_SELECTION_SINGLE);
- uc->treeview = w;
- g_signal_connect(G_OBJECT(w), "row-activated",
- G_CALLBACK(listbox_doubleclick), dp);
- g_signal_connect(G_OBJECT(w), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- g_signal_connect(G_OBJECT(sel), "changed",
- G_CALLBACK(listbox_selchange), dp);
-
- if (ctrl->listbox.draglist) {
- gtk_tree_view_set_reorderable(GTK_TREE_VIEW(w), true);
- g_signal_connect(G_OBJECT(uc->listmodel), "row-inserted",
- G_CALLBACK(listbox_reorder), dp);
- }
-
- {
- int i;
- int cols;
-
- cols = ctrl->listbox.ncols;
- cols = cols ? cols : 1;
- for (i = 0; i < cols; i++) {
- GtkTreeViewColumn *column;
- GtkCellRenderer *cellrend;
- /*
- * It appears that GTK 2 doesn't leave us any
- * particularly sensible way to honour the
- * "percentages" specification in the ctrl
- * structure.
- */
- cellrend = gtk_cell_renderer_text_new();
- if (!ctrl->listbox.hscroll) {
- g_object_set(G_OBJECT(cellrend),
- "ellipsize", PANGO_ELLIPSIZE_END,
- "ellipsize-set", true,
- (const char *)NULL);
- }
- column = gtk_tree_view_column_new_with_attributes
- ("heading", cellrend, "text", i+1, (char *)NULL);
- gtk_tree_view_column_set_sizing
- (column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
- gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
- }
- }
-
- {
- GtkWidget *scroll;
-
- scroll = gtk_scrolled_window_new(NULL, NULL);
- gtk_scrolled_window_set_shadow_type
- (GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN);
- gtk_widget_show(w);
- gtk_container_add(GTK_CONTAINER(scroll), w);
- gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
- GTK_POLICY_AUTOMATIC,
- GTK_POLICY_ALWAYS);
- gtk_widget_set_size_request
- (scroll, -1,
- ctrl->listbox.height * get_listitemheight(w));
-
- w = scroll;
- }
-#endif
- }
-
- if (ctrl->generic.label) {
- GtkWidget *label, *container;
-
- label = gtk_label_new(ctrl->generic.label);
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_label_set_width_chars(GTK_LABEL(label), 3);
-#endif
-
- shortcut_add(scs, label, ctrl->listbox.shortcut,
- SHORTCUT_UCTRL, uc);
-
- container = columns_new(4);
- if (ctrl->listbox.percentwidth == 100) {
- columns_add(COLUMNS(container), label, 0, 1);
- columns_force_left_align(COLUMNS(container), label);
- columns_add(COLUMNS(container), w, 0, 1);
- } else {
- gint percentages[2];
- percentages[1] = ctrl->listbox.percentwidth;
- percentages[0] = 100 - ctrl->listbox.percentwidth;
- columns_set_cols(COLUMNS(container), 2, percentages);
- columns_add(COLUMNS(container), label, 0, 1);
- columns_force_left_align(COLUMNS(container), label);
- columns_add(COLUMNS(container), w, 1, 1);
- columns_force_same_height(COLUMNS(container),
- label, w);
- }
- gtk_widget_show(label);
- gtk_widget_show(w);
-
- w = container;
- uc->label = label;
- }
-
- break;
- case CTRL_TEXT:
-#if !GTK_CHECK_VERSION(3,0,0)
- /*
- * Wrapping text widgets don't sit well with the GTK2
- * layout model, in which widgets state a minimum size
- * and the whole window then adjusts to the smallest
- * size it can sensibly take given its contents. A
- * wrapping text widget _has_ no clear minimum size;
- * instead it has a range of possibilities. It can be
- * one line deep but 2000 wide, or two lines deep and
- * 1000 pixels, or three by 867, or four by 500 and so
- * on. It can be as short as you like provided you
- * don't mind it being wide, or as narrow as you like
- * provided you don't mind it being tall.
- *
- * Therefore, it fits very badly into the layout model.
- * Hence the only thing to do is pick a width and let
- * it choose its own number of lines. To do this I'm
- * going to cheat a little. All new wrapping text
- * widgets will be created with a minimal text content
- * "X"; then, after the rest of the dialog box is set
- * up and its size calculated, the text widgets will be
- * told their width and given their real text, which
- * will cause the size to be recomputed in the y
- * direction (because many of them will expand to more
- * than one line).
- */
- uc->text = w = gtk_label_new("X");
- uc->textsig =
- g_signal_connect(G_OBJECT(w), "size-allocate",
- G_CALLBACK(label_sizealloc), dp);
-#else
- /*
- * In GTK3, this is all fixed, because the main aim of the
- * new 'height-for-width' geometry management is to make
- * wrapping labels behave sensibly. So now we can just do
- * the obvious thing.
- */
- uc->text = w = gtk_label_new(uc->ctrl->generic.label);
-#endif
- align_label_left(GTK_LABEL(w));
- gtk_label_set_line_wrap(GTK_LABEL(w), true);
- break;
- }
-
- assert(w != NULL);
-
- columns_add(cols, w,
- COLUMN_START(ctrl->generic.column),
- COLUMN_SPAN(ctrl->generic.column));
- if (left)
- columns_force_left_align(cols, w);
- if (ctrl->generic.align_next_to) {
- /*
- * Implement align_next_to by simply forcing the two
- * controls to have the same height of size allocation. At
- * least for the controls we're currently doing this with,
- * the GTK layout system will automatically vertically
- * centre each control within its allocation, which will
- * get the two controls aligned alongside each other
- * reasonably well.
- */
- struct uctrl *uc2 = dlg_find_byctrl(
- dp, ctrl->generic.align_next_to);
- assert(uc2);
- columns_force_same_height(cols, w, uc2->toplevel);
-
-#if GTK_CHECK_VERSION(3, 10, 0)
- /* Slightly nicer to align baselines than just vertically
- * centring, where the option is available */
- gtk_widget_set_valign(w, GTK_ALIGN_BASELINE);
- gtk_widget_set_valign(uc2->toplevel, GTK_ALIGN_BASELINE);
-#endif
- }
- gtk_widget_show(w);
-
- uc->toplevel = w;
- dlg_add_uctrl(dp, uc);
- }
-
- return ret;
-}
-
-struct selparam {
- struct dlgparam *dp;
- GtkNotebook *panels;
- GtkWidget *panel;
-#if !GTK_CHECK_VERSION(2,0,0)
- GtkWidget *treeitem;
-#else
- int depth;
- GtkTreePath *treepath;
-#endif
- struct Shortcuts shortcuts;
-};
-
-#if GTK_CHECK_VERSION(2,0,0)
-static void treeselection_changed(GtkTreeSelection *treeselection,
- gpointer data)
-{
- struct selparam **sps = (struct selparam **)data, *sp;
- GtkTreeModel *treemodel;
- GtkTreeIter treeiter;
- gint spindex;
- gint page_num;
-
- if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
- return;
-
- gtk_tree_model_get(treemodel, &treeiter, TREESTORE_PARAMS, &spindex, -1);
- sp = sps[spindex];
-
- page_num = gtk_notebook_page_num(sp->panels, sp->panel);
- gtk_notebook_set_current_page(sp->panels, page_num);
-
- sp->dp->curr_panel = sp;
- dlg_refresh(NULL, sp->dp);
-
- sp->dp->shortcuts = &sp->shortcuts;
-}
-#else
-static void treeitem_sel(GtkItem *item, gpointer data)
-{
- struct selparam *sp = (struct selparam *)data;
- gint page_num;
-
- page_num = gtk_notebook_page_num(sp->panels, sp->panel);
- gtk_notebook_set_page(sp->panels, page_num);
-
- sp->dp->curr_panel = sp;
- dlg_refresh(NULL, sp->dp);
-
- sp->dp->shortcuts = &sp->shortcuts;
- sp->dp->currtreeitem = sp->treeitem;
-}
-#endif
-
-bool dlg_is_visible(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- /*
- * A control is visible if it belongs to _no_ notebook page (i.e.
- * it's one of the config-box-global buttons like Load or About),
- * or if it belongs to the currently selected page.
- */
- return uc->sp == NULL || uc->sp == dp->curr_panel;
-}
-
-#if !GTK_CHECK_VERSION(2,0,0)
-static bool tree_grab_focus(struct dlgparam *dp)
-{
- int i, f;
-
- /*
- * See if any of the treeitems has the focus.
- */
- f = -1;
- for (i = 0; i < dp->ntreeitems; i++)
- if (GTK_WIDGET_HAS_FOCUS(dp->treeitems[i])) {
- f = i;
- break;
- }
-
- if (f >= 0)
- return false;
- else {
- gtk_widget_grab_focus(dp->currtreeitem);
- return true;
- }
-}
-
-gint tree_focus(GtkContainer *container, GtkDirectionType direction,
- gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
-
- g_signal_stop_emission_by_name(G_OBJECT(container), "focus");
- /*
- * If there's a focused treeitem, we return false to cause the
- * focus to move on to some totally other control. If not, we
- * focus the selected one.
- */
- return tree_grab_focus(dp);
-}
-#endif
-
-gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
-
- if (event->keyval == GDK_KEY_Escape && dp->cancelbutton) {
- g_signal_emit_by_name(G_OBJECT(dp->cancelbutton), "clicked");
- return true;
- }
-
- if ((event->state & GDK_MOD1_MASK) &&
- (unsigned char)event->string[0] > 0 &&
- (unsigned char)event->string[0] <= 127) {
- int schr = (unsigned char)event->string[0];
- struct Shortcut *sc = &dp->shortcuts->sc[schr];
-
- switch (sc->action) {
- case SHORTCUT_TREE:
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_widget_grab_focus(sc->widget);
-#else
- tree_grab_focus(dp);
-#endif
- break;
- case SHORTCUT_FOCUS:
- gtk_widget_grab_focus(sc->widget);
- break;
- case SHORTCUT_UCTRL:
- /*
- * We must do something sensible with a uctrl.
- * Precisely what this is depends on the type of
- * control.
- */
- switch (sc->uc->ctrl->generic.type) {
- case CTRL_CHECKBOX:
- case CTRL_BUTTON:
- /* Check boxes and buttons get the focus _and_ get toggled. */
- gtk_widget_grab_focus(sc->uc->toplevel);
- g_signal_emit_by_name(G_OBJECT(sc->uc->toplevel), "clicked");
- break;
- case CTRL_FILESELECT:
- case CTRL_FONTSELECT:
- /* File/font selectors have their buttons pressed (ooer),
- * and focus transferred to the edit box. */
- g_signal_emit_by_name(G_OBJECT(sc->uc->button), "clicked");
- gtk_widget_grab_focus(sc->uc->entry);
- break;
- case CTRL_RADIO:
- /*
- * Radio buttons are fun, because they have
- * multiple shortcuts. We must find whether the
- * activated shortcut is the shortcut for the whole
- * group, or for a particular button. In the former
- * case, we find the currently selected button and
- * focus it; in the latter, we focus-and-click the
- * button whose shortcut was pressed.
- */
- if (schr == sc->uc->ctrl->radio.shortcut) {
- int i;
- for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
- if (gtk_toggle_button_get_active
- (GTK_TOGGLE_BUTTON(sc->uc->buttons[i]))) {
- gtk_widget_grab_focus(sc->uc->buttons[i]);
- }
- } else if (sc->uc->ctrl->radio.shortcuts) {
- int i;
- for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
- if (schr == sc->uc->ctrl->radio.shortcuts[i]) {
- gtk_widget_grab_focus(sc->uc->buttons[i]);
- g_signal_emit_by_name
- (G_OBJECT(sc->uc->buttons[i]), "clicked");
- }
- }
- break;
- case CTRL_LISTBOX:
-
-#if !GTK_CHECK_VERSION(2,4,0)
- if (sc->uc->optmenu) {
- GdkEventButton bev;
- gint returnval;
-
- gtk_widget_grab_focus(sc->uc->optmenu);
- /* Option menus don't work using the "clicked" signal.
- * We need to manufacture a button press event :-/ */
- bev.type = GDK_BUTTON_PRESS;
- bev.button = 1;
- g_signal_emit_by_name(G_OBJECT(sc->uc->optmenu),
- "button_press_event",
- &bev, &returnval);
- break;
- }
-#else
- if (sc->uc->combo) {
- gtk_widget_grab_focus(sc->uc->combo);
- gtk_combo_box_popup(GTK_COMBO_BOX(sc->uc->combo));
- break;
- }
-#endif
-#if !GTK_CHECK_VERSION(2,0,0)
- if (sc->uc->list) {
- /*
- * For GTK-1 style list boxes, we tell it to
- * focus one of its children, which appears to
- * do the Right Thing.
- */
- gtk_container_focus(GTK_CONTAINER(sc->uc->list),
- GTK_DIR_TAB_FORWARD);
- break;
- }
-#else
- if (sc->uc->treeview) {
- gtk_widget_grab_focus(sc->uc->treeview);
- break;
- }
-#endif
- unreachable("bad listbox type in win_key_press");
- }
- break;
- }
- }
-
- return false;
-}
-
-#if !GTK_CHECK_VERSION(2,0,0)
-gint tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
-
- if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
- event->keyval == GDK_Down || event->keyval == GDK_KP_Down) {
- int dir, i, j = -1;
- for (i = 0; i < dp->ntreeitems; i++)
- if (widget == dp->treeitems[i])
- break;
- if (i < dp->ntreeitems) {
- if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
- dir = -1;
- else
- dir = +1;
-
- while (1) {
- i += dir;
- if (i < 0 || i >= dp->ntreeitems)
- break; /* nothing in that dir to select */
- /*
- * Determine if this tree item is visible.
- */
- {
- GtkWidget *w = dp->treeitems[i];
- bool vis = true;
- while (w && (GTK_IS_TREE_ITEM(w) || GTK_IS_TREE(w))) {
- if (!GTK_WIDGET_VISIBLE(w)) {
- vis = false;
- break;
- }
- w = w->parent;
- }
- if (vis) {
- j = i; /* got one */
- break;
- }
- }
- }
- }
- g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
- if (j >= 0) {
- g_signal_emit_by_name(G_OBJECT(dp->treeitems[j]), "toggle");
- gtk_widget_grab_focus(dp->treeitems[j]);
- }
- return true;
- }
-
- /*
- * It's nice for Left and Right to expand and collapse tree
- * branches.
- */
- if (event->keyval == GDK_Left || event->keyval == GDK_KP_Left) {
- g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
- gtk_tree_item_collapse(GTK_TREE_ITEM(widget));
- return true;
- }
- if (event->keyval == GDK_Right || event->keyval == GDK_KP_Right) {
- g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
- gtk_tree_item_expand(GTK_TREE_ITEM(widget));
- return true;
- }
-
- return false;
-}
-#endif
-
-static void shortcut_highlight(GtkWidget *labelw, int chr)
-{
- GtkLabel *label = GTK_LABEL(labelw);
- const gchar *currstr;
- gchar *pattern;
- int i;
-
-#if !GTK_CHECK_VERSION(2,0,0)
- {
- gchar *currstr_nonconst;
- gtk_label_get(label, &currstr_nonconst);
- currstr = currstr_nonconst;
- }
-#else
- currstr = gtk_label_get_text(label);
-#endif
-
- for (i = 0; currstr[i]; i++)
- if (tolower((unsigned char)currstr[i]) == chr) {
- pattern = dupprintf("%*s_", i, "");
- gtk_label_set_pattern(label, pattern);
- sfree(pattern);
- break;
- }
-}
-
-void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
- int chr, int action, void *ptr)
-{
- if (chr == NO_SHORTCUT)
- return;
-
- chr = tolower((unsigned char)chr);
-
- assert(scs->sc[chr].action == SHORTCUT_EMPTY);
-
- scs->sc[chr].action = action;
-
- if (action == SHORTCUT_FOCUS || action == SHORTCUT_TREE) {
- scs->sc[chr].uc = NULL;
- scs->sc[chr].widget = (GtkWidget *)ptr;
- } else {
- scs->sc[chr].widget = NULL;
- scs->sc[chr].uc = (struct uctrl *)ptr;
- }
-
- shortcut_highlight(labelw, chr);
-}
-
-static int get_listitemheight(GtkWidget *w)
-{
-#if !GTK_CHECK_VERSION(2,0,0)
- GtkWidget *listitem = gtk_list_item_new_with_label("foo");
- GtkRequisition req;
- gtk_widget_size_request(listitem, &req);
- g_object_ref_sink(G_OBJECT(listitem));
- return req.height;
-#else
- int height;
- GtkCellRenderer *cr = gtk_cell_renderer_text_new();
-#if GTK_CHECK_VERSION(3,0,0)
- {
- GtkRequisition req;
- /*
- * Since none of my list items wraps in this GUI, no
- * interesting width-for-height behaviour should be happening,
- * so I don't think it should matter here whether I ask for
- * the minimum or natural height.
- */
- gtk_cell_renderer_get_preferred_size(cr, w, &req, NULL);
- height = req.height;
- }
-#else
- gtk_cell_renderer_get_size(cr, w, NULL, NULL, NULL, NULL, &height);
-#endif
- g_object_ref(G_OBJECT(cr));
- g_object_ref_sink(G_OBJECT(cr));
- g_object_unref(G_OBJECT(cr));
- return height;
-#endif
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-void initial_treeview_collapse(struct dlgparam *dp, GtkWidget *tree)
-{
- /*
- * Collapse the deeper branches of the treeview into the state we
- * like them to start off in. See comment below in do_config_box.
- */
- int i;
- for (i = 0; i < dp->nselparams; i++)
- if (dp->selparams[i]->depth >= 2)
- gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree),
- dp->selparams[i]->treepath);
-}
-#endif
-
-#if GTK_CHECK_VERSION(3,0,0)
-void treeview_map_event(GtkWidget *tree, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- GtkAllocation alloc;
- gtk_widget_get_allocation(tree, &alloc);
- gtk_widget_set_size_request(tree, alloc.width, -1);
- initial_treeview_collapse(dp, tree);
-}
-#endif
-
-GtkWidget *create_config_box(const char *title, Conf *conf,
- bool midsession, int protcfginfo,
- post_dialog_fn_t after, void *afterctx)
-{
- GtkWidget *window, *hbox, *vbox, *cols, *label,
- *tree, *treescroll, *panels, *panelvbox;
- int index, level, protocol;
- char *path;
-#if GTK_CHECK_VERSION(2,0,0)
- GtkTreeStore *treestore;
- GtkCellRenderer *treerenderer;
- GtkTreeViewColumn *treecolumn;
- GtkTreeSelection *treeselection;
- GtkTreeIter treeiterlevels[8];
-#else
- GtkTreeItem *treeitemlevels[8];
- GtkTree *treelevels[8];
-#endif
- struct dlgparam *dp;
- struct Shortcuts scs;
-
- struct selparam **selparams = NULL;
- size_t nselparams = 0, selparamsize = 0;
-
- dp = snew(struct dlgparam);
- dp->after = after;
- dp->afterctx = afterctx;
-
- dlg_init(dp);
-
- for (index = 0; index < lenof(scs.sc); index++) {
- scs.sc[index].action = SHORTCUT_EMPTY;
- }
-
- window = our_dialog_new();
-
- dp->ctrlbox = ctrl_new_box();
- protocol = conf_get_int(conf, CONF_protocol);
- setup_config_box(dp->ctrlbox, midsession, protocol, protcfginfo);
- unix_setup_config_box(dp->ctrlbox, midsession, protocol);
- gtk_setup_config_box(dp->ctrlbox, midsession, window);
-
- gtk_window_set_title(GTK_WINDOW(window), title);
- hbox = gtk_hbox_new(false, 4);
- our_dialog_add_to_content_area(GTK_WINDOW(window), hbox, true, true, 0);
- gtk_container_set_border_width(GTK_CONTAINER(hbox), 10);
- gtk_widget_show(hbox);
- vbox = gtk_vbox_new(false, 4);
- gtk_box_pack_start(GTK_BOX(hbox), vbox, false, false, 0);
- gtk_widget_show(vbox);
- cols = columns_new(4);
- gtk_box_pack_start(GTK_BOX(vbox), cols, false, false, 0);
- gtk_widget_show(cols);
- label = gtk_label_new("Category:");
- columns_add(COLUMNS(cols), label, 0, 1);
- columns_force_left_align(COLUMNS(cols), label);
- gtk_widget_show(label);
- treescroll = gtk_scrolled_window_new(NULL, NULL);
-#if GTK_CHECK_VERSION(2,0,0)
- treestore = gtk_tree_store_new
- (TREESTORE_NUM, G_TYPE_STRING, G_TYPE_INT);
- tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(treestore));
- gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), false);
- treerenderer = gtk_cell_renderer_text_new();
- treecolumn = gtk_tree_view_column_new_with_attributes
- ("Label", treerenderer, "text", 0, NULL);
- gtk_tree_view_append_column(GTK_TREE_VIEW(tree), treecolumn);
- treeselection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
- gtk_tree_selection_set_mode(treeselection, GTK_SELECTION_BROWSE);
- gtk_container_add(GTK_CONTAINER(treescroll), tree);
-#else
- tree = gtk_tree_new();
- gtk_tree_set_view_mode(GTK_TREE(tree), GTK_TREE_VIEW_ITEM);
- gtk_tree_set_selection_mode(GTK_TREE(tree), GTK_SELECTION_BROWSE);
- g_signal_connect(G_OBJECT(tree), "focus", G_CALLBACK(tree_focus), dp);
-#endif
- g_signal_connect(G_OBJECT(tree), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- shortcut_add(&scs, label, 'g', SHORTCUT_TREE, tree);
- gtk_widget_show(treescroll);
- gtk_box_pack_start(GTK_BOX(vbox), treescroll, true, true, 0);
- panels = gtk_notebook_new();
- gtk_notebook_set_show_tabs(GTK_NOTEBOOK(panels), false);
- gtk_notebook_set_show_border(GTK_NOTEBOOK(panels), false);
- gtk_box_pack_start(GTK_BOX(hbox), panels, true, true, 0);
- gtk_widget_show(panels);
-
- panelvbox = NULL;
- path = NULL;
- level = 0;
- for (index = 0; index < dp->ctrlbox->nctrlsets; index++) {
- struct controlset *s = dp->ctrlbox->ctrlsets[index];
- GtkWidget *w;
-
- if (!*s->pathname) {
- w = layout_ctrls(dp, NULL, &scs, s, GTK_WINDOW(window));
-
- our_dialog_set_action_area(GTK_WINDOW(window), w);
- } else {
- int j = path ? ctrl_path_compare(s->pathname, path) : 0;
- if (j != INT_MAX) { /* add to treeview, start new panel */
- char *c;
-#if GTK_CHECK_VERSION(2,0,0)
- GtkTreeIter treeiter;
-#else
- GtkWidget *treeitem;
-#endif
- bool first;
-
- /*
- * We expect never to find an implicit path
- * component. For example, we expect never to see
- * A/B/C followed by A/D/E, because that would
- * _implicitly_ create A/D. All our path prefixes
- * are expected to contain actual controls and be
- * selectable in the treeview; so we would expect
- * to see A/D _explicitly_ before encountering
- * A/D/E.
- */
- assert(j == ctrl_path_elements(s->pathname) - 1);
-
- c = strrchr(s->pathname, '/');
- if (!c)
- c = s->pathname;
- else
- c++;
-
- path = s->pathname;
-
- first = (panelvbox == NULL);
-
- panelvbox = gtk_vbox_new(false, 4);
- gtk_widget_show(panelvbox);
- gtk_notebook_append_page(GTK_NOTEBOOK(panels), panelvbox,
- NULL);
-
- struct selparam *sp = snew(struct selparam);
-
- if (first) {
- gint page_num;
-
- page_num = gtk_notebook_page_num(GTK_NOTEBOOK(panels),
- panelvbox);
- gtk_notebook_set_current_page(GTK_NOTEBOOK(panels),
- page_num);
-
- dp->curr_panel = sp;
- }
-
- sgrowarray(selparams, selparamsize, nselparams);
- selparams[nselparams] = sp;
- sp->dp = dp;
- sp->panels = GTK_NOTEBOOK(panels);
- sp->panel = panelvbox;
- sp->shortcuts = scs; /* structure copy */
-
- assert(j-1 < level);
-
-#if GTK_CHECK_VERSION(2,0,0)
- if (j > 0)
- /* treeiterlevels[j-1] will always be valid because we
- * don't allow implicit path components; see above.
- */
- gtk_tree_store_append(treestore, &treeiter,
- &treeiterlevels[j-1]);
- else
- gtk_tree_store_append(treestore, &treeiter, NULL);
- gtk_tree_store_set(treestore, &treeiter,
- TREESTORE_PATH, c,
- TREESTORE_PARAMS, nselparams,
- -1);
- treeiterlevels[j] = treeiter;
-
- sp->depth = j;
- if (j > 0) {
- sp->treepath = gtk_tree_model_get_path(
- GTK_TREE_MODEL(treestore), &treeiterlevels[j-1]);
- /*
- * We are going to collapse all tree branches
- * at depth greater than 2, but not _yet_; see
- * the comment at the call to
- * gtk_tree_view_collapse_row below.
- */
- gtk_tree_view_expand_row(GTK_TREE_VIEW(tree),
- sp->treepath, false);
- } else {
- sp->treepath = NULL;
- }
-#else
- treeitem = gtk_tree_item_new_with_label(c);
- if (j > 0) {
- if (!treelevels[j-1]) {
- treelevels[j-1] = GTK_TREE(gtk_tree_new());
- gtk_tree_item_set_subtree
- (treeitemlevels[j-1],
- GTK_WIDGET(treelevels[j-1]));
- if (j < 2)
- gtk_tree_item_expand(treeitemlevels[j-1]);
- else
- gtk_tree_item_collapse(treeitemlevels[j-1]);
- }
- gtk_tree_append(treelevels[j-1], treeitem);
- } else {
- gtk_tree_append(GTK_TREE(tree), treeitem);
- }
- treeitemlevels[j] = GTK_TREE_ITEM(treeitem);
- treelevels[j] = NULL;
-
- g_signal_connect(G_OBJECT(treeitem), "key_press_event",
- G_CALLBACK(tree_key_press), dp);
- g_signal_connect(G_OBJECT(treeitem), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
-
- gtk_widget_show(treeitem);
-
- if (first)
- gtk_tree_select_child(GTK_TREE(tree), treeitem);
- sp->treeitem = treeitem;
-#endif
-
- level = j+1;
- nselparams++;
- }
-
- w = layout_ctrls(dp, selparams[nselparams-1],
- &selparams[nselparams-1]->shortcuts, s, NULL);
- gtk_box_pack_start(GTK_BOX(panelvbox), w, false, false, 0);
- gtk_widget_show(w);
- }
- }
-
-#if GTK_CHECK_VERSION(2,0,0)
- /*
- * We want our tree view to come up with all branches at depth 2
- * or more collapsed. However, if we start off with those branches
- * collapsed, then the tree view's size request will be calculated
- * based on the width of the collapsed tree, and then when the
- * collapsed branches are expanded later, the tree view will
- * jarringly change size.
- *
- * So instead we start with everything expanded; then, once the
- * tree view has computed its resulting width requirement, we
- * collapse the relevant rows, but force the width to be the value
- * we just retrieved. This arranges that the tree view is wide
- * enough to have all branches expanded without further resizing.
- */
-
- dp->nselparams = nselparams;
- dp->selparams = selparams;
-
-#if !GTK_CHECK_VERSION(3,0,0)
- {
- /*
- * In GTK2, we can just do the job right now.
- */
- GtkRequisition req;
- gtk_widget_size_request(tree, &req);
- initial_treeview_collapse(dp, tree);
- gtk_widget_set_size_request(tree, req.width, -1);
- }
-#else
- /*
- * But in GTK3, we have to wait until the widget is about to be
- * mapped, because the size computation won't have been done yet.
- */
- g_signal_connect(G_OBJECT(tree), "map",
- G_CALLBACK(treeview_map_event), dp);
-#endif /* GTK 2 vs 3 */
-#endif /* GTK 2+ vs 1 */
-
-#if GTK_CHECK_VERSION(2,0,0)
- g_signal_connect(G_OBJECT(treeselection), "changed",
- G_CALLBACK(treeselection_changed), selparams);
-#else
- dp->ntreeitems = nselparams;
- dp->treeitems = snewn(dp->ntreeitems, GtkWidget *);
- for (index = 0; index < nselparams; index++) {
- g_signal_connect(G_OBJECT(selparams[index]->treeitem), "select",
- G_CALLBACK(treeitem_sel),
- selparams[index]);
- dp->treeitems[index] = selparams[index]->treeitem;
- }
-#endif
-
- dp->data = conf;
- dlg_refresh(NULL, dp);
-
- dp->shortcuts = &selparams[0]->shortcuts;
-#if !GTK_CHECK_VERSION(2,0,0)
- dp->currtreeitem = dp->treeitems[0];
-#endif
- dp->lastfocus = NULL;
- dp->retval = -1;
- dp->window = window;
-
- set_window_icon(window, cfg_icon, n_cfg_icon);
-
-#if !GTK_CHECK_VERSION(2,0,0)
- gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(treescroll),
- tree);
-#endif
- gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(treescroll),
- GTK_POLICY_NEVER,
- GTK_POLICY_AUTOMATIC);
- gtk_widget_show(tree);
-
- gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
- gtk_widget_show(window);
-
- /*
- * Set focus into the first available control.
- */
- for (index = 0; index < dp->ctrlbox->nctrlsets; index++) {
- struct controlset *s = dp->ctrlbox->ctrlsets[index];
- bool done = false;
- int j;
-
- if (*s->pathname) {
- for (j = 0; j < s->ncontrols; j++)
- if (s->ctrls[j]->generic.type != CTRL_TABDELAY &&
- s->ctrls[j]->generic.type != CTRL_COLUMNS &&
- s->ctrls[j]->generic.type != CTRL_TEXT) {
- dlg_set_focus(s->ctrls[j], dp);
- dp->lastfocus = s->ctrls[j];
- done = true;
- break;
- }
- }
- if (done)
- break;
- }
-
- g_signal_connect(G_OBJECT(window), "destroy",
- G_CALLBACK(dlgparam_destroy), dp);
- g_signal_connect(G_OBJECT(window), "key_press_event",
- G_CALLBACK(win_key_press), dp);
-
- return window;
-}
-
-static void dlgparam_destroy(GtkWidget *widget, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- dp->after(dp->afterctx, dp->retval);
- dlg_cleanup(dp);
- ctrl_free_box(dp->ctrlbox);
-#if GTK_CHECK_VERSION(2,0,0)
- if (dp->selparams) {
- for (size_t i = 0; i < dp->nselparams; i++) {
- if (dp->selparams[i]->treepath)
- gtk_tree_path_free(dp->selparams[i]->treepath);
- sfree(dp->selparams[i]);
- }
- sfree(dp->selparams);
- }
-#endif
- sfree(dp);
-}
-
-static void messagebox_handler(union control *ctrl, dlgparam *dp,
- void *data, int event)
-{
- if (event == EVENT_ACTION)
- dlg_end(dp, ctrl->generic.context.i);
-}
-
-static const struct message_box_button button_array_yn[] = {
- {"Yes", 'y', +1, 1},
- {"No", 'n', -1, 0},
-};
-const struct message_box_buttons buttons_yn = {
- button_array_yn, lenof(button_array_yn),
-};
-static const struct message_box_button button_array_ok[] = {
- {"OK", 'o', 1, 1},
-};
-const struct message_box_buttons buttons_ok = {
- button_array_ok, lenof(button_array_ok),
-};
-
-static GtkWidget *create_message_box_general(
- GtkWidget *parentwin, const char *title, const char *msg, int minwid,
- bool selectable, const struct message_box_buttons *buttons,
- post_dialog_fn_t after, void *afterctx,
- GtkWidget *(*action_postproc)(GtkWidget *, void *), void *postproc_ctx)
-{
- GtkWidget *window, *w0, *w1;
- struct controlset *s0, *s1;
- union control *c, *textctrl;
- struct dlgparam *dp;
- struct Shortcuts scs;
- int i, index, ncols, min_type;
-
- dp = snew(struct dlgparam);
- dp->after = after;
- dp->afterctx = afterctx;
-
- dlg_init(dp);
-
- for (index = 0; index < lenof(scs.sc); index++) {
- scs.sc[index].action = SHORTCUT_EMPTY;
- }
-
- dp->ctrlbox = ctrl_new_box();
-
- /*
- * Count up the number of buttons and find out what kinds there
- * are.
- */
- ncols = 0;
- min_type = +1;
- for (i = 0; i < buttons->nbuttons; i++) {
- const struct message_box_button *button = &buttons->buttons[i];
- ncols++;
- if (min_type > button->type)
- min_type = button->type;
- assert(button->value >= 0); /* <0 means no return value available */
- }
-
- s0 = ctrl_getset(dp->ctrlbox, "", "", "");
- c = ctrl_columns(s0, 2, 50, 50);
- c->columns.ncols = s0->ncolumns = ncols;
- c->columns.percentages = sresize(c->columns.percentages, ncols, int);
- for (index = 0; index < ncols; index++)
- c->columns.percentages[index] = (index+1)*100/ncols - index*100/ncols;
- index = 0;
- for (i = 0; i < buttons->nbuttons; i++) {
- const struct message_box_button *button = &buttons->buttons[i];
- c = ctrl_pushbutton(s0, button->title, button->shortcut,
- HELPCTX(no_help), messagebox_handler,
- I(button->value));
- c->generic.column = index++;
- if (button->type > 0)
- c->button.isdefault = true;
-
- /* We always arrange that _some_ button is labelled as
- * 'iscancel', so that pressing Escape will always cause
- * win_key_press to do something. The button we choose is
- * whichever has the smallest type value: this means that real
- * cancel buttons (labelled -1) will be picked if one is
- * there, or in cases where the options are yes/no (1,0) then
- * no will be picked, and if there's only one option (a box
- * that really is just showing a _message_ and not even asking
- * a question) then that will be picked. */
- if (button->type == min_type)
- c->button.iscancel = true;
- }
-
- s1 = ctrl_getset(dp->ctrlbox, "x", "", "");
- textctrl = ctrl_text(s1, msg, HELPCTX(no_help));
-
- window = our_dialog_new();
- gtk_window_set_title(GTK_WINDOW(window), title);
- w0 = layout_ctrls(dp, NULL, &scs, s0, GTK_WINDOW(window));
- if (action_postproc)
- w0 = action_postproc(w0, postproc_ctx);
- our_dialog_set_action_area(GTK_WINDOW(window), w0);
- gtk_widget_show(w0);
- w1 = layout_ctrls(dp, NULL, &scs, s1, GTK_WINDOW(window));
- gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
- gtk_widget_set_size_request(w1, minwid+20, -1);
- our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0);
- gtk_widget_show(w1);
-
- dp->shortcuts = &scs;
- dp->lastfocus = NULL;
- dp->retval = 0;
- dp->window = window;
-
- if (selectable) {
-#if GTK_CHECK_VERSION(2,0,0)
- struct uctrl *uc = dlg_find_byctrl(dp, textctrl);
- gtk_label_set_selectable(GTK_LABEL(uc->text), true);
-
- /*
- * GTK selectable labels have a habit of selecting their
- * entire contents when they gain focus. It's ugly to have
- * text in a message box start up all selected, so we suppress
- * this by manually selecting none of it - but we must do this
- * when the widget _already has_ focus, otherwise our work
- * will be undone when it gains it shortly.
- */
- gtk_widget_grab_focus(uc->text);
- gtk_label_select_region(GTK_LABEL(uc->text), 0, 0);
-#else
- (void)textctrl; /* placate warning */
-#endif
- }
-
- if (parentwin) {
- set_transient_window_pos(parentwin, window);
- gtk_window_set_transient_for(GTK_WINDOW(window),
- GTK_WINDOW(parentwin));
- } else
- gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
- gtk_container_set_focus_child(GTK_CONTAINER(window), NULL);
- gtk_widget_show(window);
- gtk_window_set_focus(GTK_WINDOW(window), NULL);
-
-#if !GTK_CHECK_VERSION(2,0,0)
- dp->currtreeitem = NULL;
- dp->treeitems = NULL;
-#else
- dp->selparams = NULL;
-#endif
-
- g_signal_connect(G_OBJECT(window), "destroy",
- G_CALLBACK(dlgparam_destroy), dp);
- g_signal_connect(G_OBJECT(window), "key_press_event",
- G_CALLBACK(win_key_press), dp);
-
- return window;
-}
-
-GtkWidget *create_message_box(
- GtkWidget *parentwin, const char *title, const char *msg, int minwid,
- bool selectable, const struct message_box_buttons *buttons,
- post_dialog_fn_t after, void *afterctx)
-{
- return create_message_box_general(
- parentwin, title, msg, minwid, selectable, buttons, after, afterctx,
- NULL /* action_postproc */, NULL /* postproc_ctx */);
-}
-
-struct verify_ssh_host_key_dialog_ctx {
- char *host;
- int port;
- char *keytype;
- char *keystr;
- char *more_info;
- void (*callback)(void *callback_ctx, int result);
- void *callback_ctx;
- Seat *seat;
-
- GtkWidget *main_dialog;
- GtkWidget *more_info_dialog;
-};
-
-static void verify_ssh_host_key_result_callback(void *vctx, int result)
-{
- struct verify_ssh_host_key_dialog_ctx *ctx =
- (struct verify_ssh_host_key_dialog_ctx *)vctx;
-
- if (result >= 0) {
- int logical_result;
-
- /*
- * Convert the dialog-box return value (one of three
- * possibilities) into the return value we pass back to the SSH
- * code (one of only two possibilities, because the SSH code
- * doesn't care whether we saved the host key or not).
- */
- if (result == 2) {
- store_host_key(ctx->host, ctx->port, ctx->keytype, ctx->keystr);
- logical_result = 1; /* continue with connection */
- } else if (result == 1) {
- logical_result = 1; /* continue with connection */
- } else {
- logical_result = 0; /* do not continue with connection */
- }
-
- ctx->callback(ctx->callback_ctx, logical_result);
- }
-
- /*
- * Clean up this context structure, whether or not a result was
- * ever actually delivered from the dialog box.
- */
- unregister_dialog(ctx->seat, DIALOG_SLOT_NETWORK_PROMPT);
-
- if (ctx->more_info_dialog)
- gtk_widget_destroy(ctx->more_info_dialog);
-
- sfree(ctx->host);
- sfree(ctx->keytype);
- sfree(ctx->keystr);
- sfree(ctx->more_info);
- sfree(ctx);
-}
-
-static GtkWidget *add_more_info_button(GtkWidget *w, void *vctx)
-{
- GtkWidget *box = gtk_hbox_new(false, 10);
- gtk_widget_show(box);
- gtk_box_pack_end(GTK_BOX(box), w, false, true, 0);
- GtkWidget *button = gtk_button_new_with_label("More info...");
- gtk_widget_show(button);
- gtk_box_pack_start(GTK_BOX(box), button, false, true, 0);
- *(GtkWidget **)vctx = button;
- return box;
-}
-
-static void more_info_closed(void *vctx, int result)
-{
- struct verify_ssh_host_key_dialog_ctx *ctx =
- (struct verify_ssh_host_key_dialog_ctx *)vctx;
-
- ctx->more_info_dialog = NULL;
-}
-
-static void more_info_button_clicked(GtkButton *button, gpointer vctx)
-{
- struct verify_ssh_host_key_dialog_ctx *ctx =
- (struct verify_ssh_host_key_dialog_ctx *)vctx;
-
- if (ctx->more_info_dialog)
- return;
-
- ctx->more_info_dialog = create_message_box(
- ctx->main_dialog, "Host key information", ctx->more_info,
- string_width("SHA256 fingerprint: ecdsa-sha2-nistp521 521 "
- "abcdefghkmnopqrsuvwxyzABCDEFGHJKLMNOPQRSTUW"), true,
- &buttons_ok, more_info_closed, ctx);
-}
-
-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)
-{
- static const char absenttxt[] =
- "The server's host key is not cached. You have no guarantee "
- "that the server is the computer you think it is.\n"
- "The server's %s key fingerprint is:\n"
- "%s\n"
- "If you trust this host, press \"Accept\" to add the key to "
- "PuTTY's cache and carry on connecting.\n"
- "If you want to carry on connecting just once, without "
- "adding the key to the cache, press \"Connect Once\".\n"
- "If you do not trust this host, press \"Cancel\" to abandon the "
- "connection.";
- static const char wrongtxt[] =
- "WARNING - POTENTIAL SECURITY BREACH!\n"
- "The server's host key does not match the one PuTTY has "
- "cached. This means that either the server administrator "
- "has changed the host key, or you have actually connected "
- "to another computer pretending to be the server.\n"
- "The new %s key fingerprint is:\n"
- "%s\n"
- "If you were expecting this change and trust the new key, "
- "press \"Accept\" to update PuTTY's cache and continue connecting.\n"
- "If you want to carry on connecting but without updating "
- "the cache, press \"Connect Once\".\n"
- "If you want to abandon the connection completely, press "
- "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed "
- "safe choice.";
- static const struct message_box_button button_array_hostkey[] = {
- {"Accept", 'a', 0, 2},
- {"Connect Once", 'o', 0, 1},
- {"Cancel", 'c', -1, 0},
- };
- static const struct message_box_buttons buttons_hostkey = {
- button_array_hostkey, lenof(button_array_hostkey),
- };
-
- char *text;
- int ret;
- struct verify_ssh_host_key_dialog_ctx *result_ctx;
- GtkWidget *mainwin, *msgbox;
-
- /*
- * Verify the key.
- */
- ret = verify_host_key(host, port, keytype, keystr);
-
- if (ret == 0) /* success - key matched OK */
- return 1;
-
- FingerprintType fptype_default =
- ssh2_pick_default_fingerprint(fingerprints);
-
- text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype,
- fingerprints[fptype_default]);
-
- result_ctx = snew(struct verify_ssh_host_key_dialog_ctx);
- result_ctx->callback = callback;
- result_ctx->callback_ctx = ctx;
- result_ctx->host = dupstr(host);
- result_ctx->port = port;
- result_ctx->keytype = dupstr(keytype);
- result_ctx->keystr = dupstr(keystr);
- result_ctx->seat = seat;
-
- mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
- GtkWidget *more_info_button = NULL;
- msgbox = create_message_box_general(
- mainwin, "PuTTY Security Alert", text,
- string_width(fingerprints[fptype_default]), true,
- &buttons_hostkey, verify_ssh_host_key_result_callback, result_ctx,
- add_more_info_button, &more_info_button);
-
- result_ctx->main_dialog = msgbox;
- result_ctx->more_info_dialog = NULL;
-
- strbuf *sb = strbuf_new();
- if (fingerprints[SSH_FPTYPE_SHA256])
- strbuf_catf(sb, "SHA256 fingerprint: %s\n",
- fingerprints[SSH_FPTYPE_SHA256]);
- if (fingerprints[SSH_FPTYPE_MD5])
- strbuf_catf(sb, "MD5 fingerprint: %s\n",
- fingerprints[SSH_FPTYPE_MD5]);
- strbuf_catf(sb, "Full text of host's public key:");
- /* We have to manually wrap the public key, or else the GtkLabel
- * will resize itself to accommodate the longest word, which will
- * lead to a hilariously wide message box. */
- for (const char *p = keydisp, *q = p + strlen(p); p < q ;) {
- size_t linelen = q-p;
- if (linelen > 72)
- linelen = 72;
- put_byte(sb, '\n');
- put_data(sb, p, linelen);
- p += linelen;
- }
- result_ctx->more_info = strbuf_to_str(sb);
-
- g_signal_connect(G_OBJECT(more_info_button), "clicked",
- G_CALLBACK(more_info_button_clicked), result_ctx);
-
- register_dialog(seat, DIALOG_SLOT_NETWORK_PROMPT, msgbox);
-
- sfree(text);
-
- return -1; /* dialog still in progress */
-}
-
-struct simple_prompt_result_ctx {
- void (*callback)(void *callback_ctx, int result);
- void *callback_ctx;
- Seat *seat;
- enum DialogSlot dialog_slot;
-};
-
-static void simple_prompt_result_callback(void *vctx, int result)
-{
- struct simple_prompt_result_ctx *ctx =
- (struct simple_prompt_result_ctx *)vctx;
-
- unregister_dialog(ctx->seat, ctx->dialog_slot);
-
- if (result >= 0)
- ctx->callback(ctx->callback_ctx, result);
-
- /*
- * Clean up this context structure, whether or not a result was
- * ever actually delivered from the dialog box.
- */
- sfree(ctx);
-}
-
-/*
- * Ask whether the selected algorithm is acceptable (since it was
- * below the configured 'warn' threshold).
- */
-int gtk_seat_confirm_weak_crypto_primitive(
- Seat *seat, const char *algtype, const char *algname,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- static const char msg[] =
- "The first %s supported by the server is "
- "%s, which is below the configured warning threshold.\n"
- "Continue with connection?";
-
- char *text;
- struct simple_prompt_result_ctx *result_ctx;
- GtkWidget *mainwin, *msgbox;
-
- text = dupprintf(msg, algtype, algname);
-
- result_ctx = snew(struct simple_prompt_result_ctx);
- result_ctx->callback = callback;
- result_ctx->callback_ctx = ctx;
- result_ctx->seat = seat;
- result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT;
-
- mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
- msgbox = create_message_box(
- mainwin, "PuTTY Security Alert", text,
- string_width("Reasonably long line of text as a width template"),
- false, &buttons_yn, simple_prompt_result_callback, result_ctx);
- register_dialog(seat, result_ctx->dialog_slot, msgbox);
-
- sfree(text);
-
- return -1; /* dialog still in progress */
-}
-
-int gtk_seat_confirm_weak_cached_hostkey(
- Seat *seat, const char *algname, const char *betteralgs,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- 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"
- "Continue with connection?";
-
- char *text;
- struct simple_prompt_result_ctx *result_ctx;
- GtkWidget *mainwin, *msgbox;
-
- text = dupprintf(msg, algname, betteralgs);
-
- result_ctx = snew(struct simple_prompt_result_ctx);
- result_ctx->callback = callback;
- result_ctx->callback_ctx = ctx;
- result_ctx->seat = seat;
- result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT;
-
- mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
- msgbox = create_message_box(
- mainwin, "PuTTY Security Alert", text,
- string_width("is ecdsa-nistp521, which is below the configured"
- " warning threshold."),
- false, &buttons_yn, simple_prompt_result_callback, result_ctx);
- register_dialog(seat, result_ctx->dialog_slot, msgbox);
-
- sfree(text);
-
- return -1; /* dialog still in progress */
-}
-
-void old_keyfile_warning(void)
-{
- /*
- * This should never happen on Unix. We hope.
- */
-}
-
-void nonfatal_message_box(void *window, const char *msg)
-{
- char *title = dupcat(appname, " Error");
- create_message_box(
- window, title, msg,
- string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"),
- false, &buttons_ok, trivial_post_dialog_fn, NULL);
- sfree(title);
-}
-
-void nonfatal(const char *p, ...)
-{
- va_list ap;
- char *msg;
- va_start(ap, p);
- msg = dupvprintf(p, ap);
- va_end(ap);
- nonfatal_message_box(NULL, msg);
- sfree(msg);
-}
-
-static GtkWidget *aboutbox = NULL;
-
-static void about_window_destroyed(GtkWidget *widget, gpointer data)
-{
- aboutbox = NULL;
-}
-
-static void about_close_clicked(GtkButton *button, gpointer data)
-{
- gtk_widget_destroy(aboutbox);
- aboutbox = NULL;
-}
-
-static void about_key_press(GtkWidget *widget, GdkEventKey *event,
- gpointer data)
-{
- if (event->keyval == GDK_KEY_Escape && aboutbox) {
- gtk_widget_destroy(aboutbox);
- aboutbox = NULL;
- }
-}
-
-static void licence_clicked(GtkButton *button, gpointer data)
-{
- char *title;
-
- title = dupcat(appname, " Licence");
- assert(aboutbox != NULL);
- create_message_box(aboutbox, title, LICENCE_TEXT("\n\n"),
- string_width("LONGISH LINE OF TEXT SO THE LICENCE"
- " BOX ISN'T EXCESSIVELY TALL AND THIN"),
- true, &buttons_ok, trivial_post_dialog_fn, NULL);
- sfree(title);
-}
-
-void about_box(void *window)
-{
- GtkWidget *w;
- GtkBox *action_area;
- char *title;
-
- if (aboutbox) {
- gtk_widget_grab_focus(aboutbox);
- return;
- }
-
- aboutbox = our_dialog_new();
- gtk_container_set_border_width(GTK_CONTAINER(aboutbox), 10);
- title = dupcat("About ", appname);
- gtk_window_set_title(GTK_WINDOW(aboutbox), title);
- sfree(title);
-
- g_signal_connect(G_OBJECT(aboutbox), "destroy",
- G_CALLBACK(about_window_destroyed), NULL);
-
- w = gtk_button_new_with_label("Close");
- gtk_widget_set_can_default(w, true);
- gtk_window_set_default(GTK_WINDOW(aboutbox), w);
- action_area = our_dialog_make_action_hbox(GTK_WINDOW(aboutbox));
- gtk_box_pack_end(action_area, w, false, false, 0);
- g_signal_connect(G_OBJECT(w), "clicked",
- G_CALLBACK(about_close_clicked), NULL);
- gtk_widget_show(w);
-
- w = gtk_button_new_with_label("View Licence");
- gtk_widget_set_can_default(w, true);
- gtk_box_pack_end(action_area, w, false, false, 0);
- g_signal_connect(G_OBJECT(w), "clicked",
- G_CALLBACK(licence_clicked), NULL);
- gtk_widget_show(w);
-
- {
- char *buildinfo_text = buildinfo("\n");
- char *label_text = dupprintf
- ("%s\n\n%s\n\n%s\n\n%s",
- appname, ver, buildinfo_text,
- "Copyright " SHORT_COPYRIGHT_DETAILS ". All rights reserved");
- w = gtk_label_new(label_text);
- gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_CENTER);
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_label_set_selectable(GTK_LABEL(w), true);
-#endif
- sfree(label_text);
- }
- our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, false, false, 0);
-#if GTK_CHECK_VERSION(2,0,0)
- /*
- * Same precautions against initial select-all as in
- * create_message_box().
- */
- gtk_widget_grab_focus(w);
- gtk_label_select_region(GTK_LABEL(w), 0, 0);
-#endif
- gtk_widget_show(w);
-
- g_signal_connect(G_OBJECT(aboutbox), "key_press_event",
- G_CALLBACK(about_key_press), NULL);
-
- set_transient_window_pos(GTK_WIDGET(window), aboutbox);
- if (window)
- gtk_window_set_transient_for(GTK_WINDOW(aboutbox),
- GTK_WINDOW(window));
- gtk_container_set_focus_child(GTK_CONTAINER(aboutbox), NULL);
- gtk_widget_show(aboutbox);
- gtk_window_set_focus(GTK_WINDOW(aboutbox), NULL);
-}
-
-#define LOGEVENT_INITIAL_MAX 128
-#define LOGEVENT_CIRCULAR_MAX 128
-
-struct eventlog_stuff {
- GtkWidget *parentwin, *window;
- struct controlbox *eventbox;
- struct Shortcuts scs;
- struct dlgparam dp;
- union control *listctrl;
- char **events_initial;
- char **events_circular;
- int ninitial, ncircular, circular_first;
- strbuf *seldata;
- int sellen;
- bool ignore_selchange;
-};
-
-static void eventlog_destroy(GtkWidget *widget, gpointer data)
-{
- eventlog_stuff *es = (eventlog_stuff *)data;
-
- es->window = NULL;
- dlg_cleanup(&es->dp);
- ctrl_free_box(es->eventbox);
-}
-static void eventlog_ok_handler(union control *ctrl, dlgparam *dp,
- void *data, int event)
-{
- if (event == EVENT_ACTION)
- dlg_end(dp, 0);
-}
-static void eventlog_list_handler(union control *ctrl, dlgparam *dp,
- void *data, int event)
-{
- eventlog_stuff *es = (eventlog_stuff *)data;
-
- if (event == EVENT_REFRESH) {
- int i;
-
- dlg_update_start(ctrl, dp);
- dlg_listbox_clear(ctrl, dp);
- for (i = 0; i < es->ninitial; i++) {
- dlg_listbox_add(ctrl, dp, es->events_initial[i]);
- }
- for (i = 0; i < es->ncircular; i++) {
- dlg_listbox_add(ctrl, dp, es->events_circular[(es->circular_first + i) % LOGEVENT_CIRCULAR_MAX]);
- }
- dlg_update_done(ctrl, dp);
- } else if (event == EVENT_SELCHANGE) {
- int i;
-
- /*
- * If this SELCHANGE event is happening as a result of
- * deliberate deselection because someone else has grabbed
- * the selection, the last thing we want to do is pre-empt
- * them.
- */
- if (es->ignore_selchange)
- return;
-
- /*
- * Construct the data to use as the selection.
- */
- strbuf_clear(es->seldata);
- for (i = 0; i < es->ninitial; i++) {
- if (dlg_listbox_issel(ctrl, dp, i))
- strbuf_catf(es->seldata, "%s\n", es->events_initial[i]);
- }
- for (i = 0; i < es->ncircular; i++) {
- if (dlg_listbox_issel(ctrl, dp, es->ninitial + i)) {
- int j = (es->circular_first + i) % LOGEVENT_CIRCULAR_MAX;
- strbuf_catf(es->seldata, "%s\n", es->events_circular[j]);
- }
- }
-
- if (gtk_selection_owner_set(es->window, GDK_SELECTION_PRIMARY,
- GDK_CURRENT_TIME)) {
- gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY,
- GDK_SELECTION_TYPE_STRING, 1);
- gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY,
- compound_text_atom, 1);
- }
-
- }
-}
-
-void eventlog_selection_get(GtkWidget *widget, GtkSelectionData *seldata,
- guint info, guint time_stamp, gpointer data)
-{
- eventlog_stuff *es = (eventlog_stuff *)data;
-
- gtk_selection_data_set(seldata, gtk_selection_data_get_target(seldata), 8,
- es->seldata->u, es->seldata->len);
-}
-
-gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
- gpointer data)
-{
- eventlog_stuff *es = (eventlog_stuff *)data;
- struct uctrl *uc;
-
- /*
- * Deselect everything in the list box.
- */
- uc = dlg_find_byctrl(&es->dp, es->listctrl);
- es->ignore_selchange = true;
-#if !GTK_CHECK_VERSION(2,0,0)
- assert(uc->list);
- gtk_list_unselect_all(GTK_LIST(uc->list));
-#else
- assert(uc->treeview);
- gtk_tree_selection_unselect_all
- (gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)));
-#endif
- es->ignore_selchange = false;
-
- return true;
-}
-
-void showeventlog(eventlog_stuff *es, void *parentwin)
-{
- GtkWidget *window, *w0, *w1;
- GtkWidget *parent = GTK_WIDGET(parentwin);
- struct controlset *s0, *s1;
- union control *c;
- int index;
- char *title;
-
- if (es->window) {
- gtk_widget_grab_focus(es->window);
- return;
- }
-
- dlg_init(&es->dp);
-
- for (index = 0; index < lenof(es->scs.sc); index++) {
- es->scs.sc[index].action = SHORTCUT_EMPTY;
- }
-
- es->eventbox = ctrl_new_box();
-
- s0 = ctrl_getset(es->eventbox, "", "", "");
- ctrl_columns(s0, 3, 33, 34, 33);
- c = ctrl_pushbutton(s0, "Close", 'c', HELPCTX(no_help),
- eventlog_ok_handler, P(NULL));
- c->button.column = 1;
- c->button.isdefault = true;
-
- s1 = ctrl_getset(es->eventbox, "x", "", "");
- es->listctrl = c = ctrl_listbox(s1, NULL, NO_SHORTCUT, HELPCTX(no_help),
- eventlog_list_handler, P(es));
- c->listbox.height = 10;
- c->listbox.multisel = 2;
- c->listbox.ncols = 3;
- c->listbox.percentages = snewn(3, int);
- c->listbox.percentages[0] = 25;
- c->listbox.percentages[1] = 10;
- c->listbox.percentages[2] = 65;
-
- es->window = window = our_dialog_new();
- title = dupcat(appname, " Event Log");
- gtk_window_set_title(GTK_WINDOW(window), title);
- sfree(title);
- w0 = layout_ctrls(&es->dp, NULL, &es->scs, s0, GTK_WINDOW(window));
- our_dialog_set_action_area(GTK_WINDOW(window), w0);
- gtk_widget_show(w0);
- w1 = layout_ctrls(&es->dp, NULL, &es->scs, s1, GTK_WINDOW(window));
- gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
- gtk_widget_set_size_request(w1, 20 + string_width
- ("LINE OF TEXT GIVING WIDTH OF EVENT LOG IS "
- "QUITE LONG 'COS SSH LOG ENTRIES ARE WIDE"),
- -1);
- our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0);
- gtk_widget_show(w1);
-
- es->dp.data = es;
- es->dp.shortcuts = &es->scs;
- es->dp.lastfocus = NULL;
- es->dp.retval = 0;
- es->dp.window = window;
-
- dlg_refresh(NULL, &es->dp);
-
- if (parent) {
- set_transient_window_pos(parent, window);
- gtk_window_set_transient_for(GTK_WINDOW(window),
- GTK_WINDOW(parent));
- } else
- gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
- gtk_widget_show(window);
-
- g_signal_connect(G_OBJECT(window), "destroy",
- G_CALLBACK(eventlog_destroy), es);
- g_signal_connect(G_OBJECT(window), "key_press_event",
- G_CALLBACK(win_key_press), &es->dp);
- g_signal_connect(G_OBJECT(window), "selection_get",
- G_CALLBACK(eventlog_selection_get), es);
- g_signal_connect(G_OBJECT(window), "selection_clear_event",
- G_CALLBACK(eventlog_selection_clear), es);
-}
-
-eventlog_stuff *eventlogstuff_new(void)
-{
- eventlog_stuff *es = snew(eventlog_stuff);
- memset(es, 0, sizeof(*es));
- es->seldata = strbuf_new();
- return es;
-}
-
-void eventlogstuff_free(eventlog_stuff *es)
-{
- int i;
-
- if (es->events_initial) {
- for (i = 0; i < LOGEVENT_INITIAL_MAX; i++)
- sfree(es->events_initial[i]);
- sfree(es->events_initial);
- }
- if (es->events_circular) {
- for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++)
- sfree(es->events_circular[i]);
- sfree(es->events_circular);
- }
- strbuf_free(es->seldata);
-
- sfree(es);
-}
-
-void logevent_dlg(eventlog_stuff *es, const char *string)
-{
- char timebuf[40];
- struct tm tm;
- char **location;
- size_t i;
-
- if (es->ninitial == 0) {
- es->events_initial = sresize(es->events_initial, LOGEVENT_INITIAL_MAX, char *);
- for (i = 0; i < LOGEVENT_INITIAL_MAX; i++)
- es->events_initial[i] = NULL;
- es->events_circular = sresize(es->events_circular, LOGEVENT_CIRCULAR_MAX, char *);
- for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++)
- es->events_circular[i] = NULL;
- }
-
- if (es->ninitial < LOGEVENT_INITIAL_MAX)
- location = &es->events_initial[es->ninitial];
- else
- location = &es->events_circular[(es->circular_first + es->ncircular) % LOGEVENT_CIRCULAR_MAX];
-
- tm=ltime();
- strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
-
- sfree(*location);
- *location = dupcat(timebuf, string);
- if (es->window) {
- dlg_listbox_add(es->listctrl, &es->dp, *location);
- }
- if (es->ninitial < LOGEVENT_INITIAL_MAX) {
- es->ninitial++;
- } else if (es->ncircular < LOGEVENT_CIRCULAR_MAX) {
- es->ncircular++;
- } else if (es->ncircular == LOGEVENT_CIRCULAR_MAX) {
- es->circular_first = (es->circular_first + 1) % LOGEVENT_CIRCULAR_MAX;
- sfree(es->events_circular[es->circular_first]);
- es->events_circular[es->circular_first] = dupstr("..");
- }
-}
-
-int gtkdlg_askappend(Seat *seat, Filename *filename,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- static const char msgtemplate[] =
- "The session log file \"%.*s\" already exists. "
- "You can overwrite it with a new session log, "
- "append your session log to the end of it, "
- "or disable session logging for this session.";
- static const struct message_box_button button_array_append[] = {
- {"Overwrite", 'o', 1, 2},
- {"Append", 'a', 0, 1},
- {"Disable", 'd', -1, 0},
- };
- static const struct message_box_buttons buttons_append = {
- button_array_append, lenof(button_array_append),
- };
-
- char *message;
- char *mbtitle;
- struct simple_prompt_result_ctx *result_ctx;
- GtkWidget *mainwin, *msgbox;
-
- message = dupprintf(msgtemplate, FILENAME_MAX, filename->path);
- mbtitle = dupprintf("%s Log to File", appname);
-
- result_ctx = snew(struct simple_prompt_result_ctx);
- result_ctx->callback = callback;
- result_ctx->callback_ctx = ctx;
- result_ctx->seat = seat;
- result_ctx->dialog_slot = DIALOG_SLOT_LOGFILE_PROMPT;
-
- mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
- msgbox = create_message_box(
- mainwin, mbtitle, message,
- string_width("LINE OF TEXT SUITABLE FOR THE ASKAPPEND WIDTH"),
- false, &buttons_append, simple_prompt_result_callback, result_ctx);
- register_dialog(seat, result_ctx->dialog_slot, msgbox);
-
- sfree(message);
- sfree(mbtitle);
-
- return -1; /* dialog still in progress */
-}
diff --git a/UNIX/GTKFONT.C b/UNIX/GTKFONT.C
deleted file mode 100644
index b910d14b..00000000
--- a/UNIX/GTKFONT.C
+++ /dev/null
@@ -1,3808 +0,0 @@
-/*
- * Unified font management for GTK.
- *
- * PuTTY is willing to use both old-style X server-side bitmap
- * fonts _and_ GTK2/Pango client-side fonts. This requires us to
- * do a bit of work to wrap the two wildly different APIs into
- * forms the rest of the code can switch between seamlessly, and
- * also requires a custom font selector capable of handling both
- * types of font.
- */
-
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <gtk/gtk.h>
-#if !GTK_CHECK_VERSION(3,0,0)
-#include <gdk/gdkkeysyms.h>
-#endif
-
-#define MAY_REFER_TO_GTK_IN_HEADERS
-
-#include "putty.h"
-#include "gtkfont.h"
-#include "gtkcompat.h"
-#include "gtkmisc.h"
-#include "tree234.h"
-
-#ifndef NOT_X_WINDOWS
-#include <gdk/gdkx.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-#include "x11misc.h"
-#endif
-
-/*
- * Future work:
- *
- * - it would be nice to have a display of the current font name,
- * and in particular whether it's client- or server-side,
- * during the progress of the font selector.
- */
-
-#if !GLIB_CHECK_VERSION(1,3,7)
-#define g_ascii_strcasecmp g_strcasecmp
-#define g_ascii_strncasecmp g_strncasecmp
-#endif
-
-/*
- * Ad-hoc vtable mechanism to allow font structures to be
- * polymorphic.
- *
- * Any instance of `unifont' used in the vtable functions will
- * actually be an element of a larger structure containing data
- * specific to the subtype.
- */
-
-#define FONTFLAG_CLIENTSIDE 0x0001
-#define FONTFLAG_SERVERSIDE 0x0002
-#define FONTFLAG_SERVERALIAS 0x0004
-#define FONTFLAG_NONMONOSPACED 0x0008
-
-#define FONTFLAG_SORT_MASK 0x0007 /* used to disambiguate font families */
-
-typedef void (*fontsel_add_entry)(void *ctx, const char *realfontname,
- const char *family, const char *charset,
- const char *style, const char *stylekey,
- int size, int flags,
- const struct UnifontVtable *fontclass);
-
-struct UnifontVtable {
- /*
- * `Methods' of the `class'.
- */
- unifont *(*create)(GtkWidget *widget, const char *name, bool wide,
- bool bold, int shadowoffset, bool shadowalways);
- unifont *(*create_fallback)(GtkWidget *widget, int height, bool wide,
- bool bold, int shadowoffset,
- bool shadowalways);
- void (*destroy)(unifont *font);
- bool (*has_glyph)(unifont *font, wchar_t glyph);
- void (*draw_text)(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth);
- void (*draw_combining)(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth);
- void (*enum_fonts)(GtkWidget *widget,
- fontsel_add_entry callback, void *callback_ctx);
- char *(*canonify_fontname)(GtkWidget *widget, const char *name, int *size,
- int *flags, bool resolve_aliases);
- char *(*scale_fontname)(GtkWidget *widget, const char *name, int size);
- char *(*size_increment)(unifont *font, int increment);
-
- /*
- * `Static data members' of the `class'.
- */
- const char *prefix;
-};
-
-#ifndef NOT_X_WINDOWS
-
-/* ----------------------------------------------------------------------
- * X11 font implementation, directly using Xlib calls. Conditioned out
- * if X11 fonts aren't available at all (e.g. building with GTK3 for a
- * back end other than X).
- */
-
-static bool x11font_has_glyph(unifont *font, wchar_t glyph);
-static void x11font_draw_text(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth);
-static void x11font_draw_combining(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string,
- int len, bool wide, bool bold,
- int cellwidth);
-static unifont *x11font_create(GtkWidget *widget, const char *name,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways);
-static void x11font_destroy(unifont *font);
-static void x11font_enum_fonts(GtkWidget *widget,
- fontsel_add_entry callback, void *callback_ctx);
-static char *x11font_canonify_fontname(GtkWidget *widget, const char *name,
- int *size, int *flags,
- bool resolve_aliases);
-static char *x11font_scale_fontname(GtkWidget *widget, const char *name,
- int size);
-static char *x11font_size_increment(unifont *font, int increment);
-
-#ifdef DRAW_TEXT_CAIRO
-struct cairo_cached_glyph {
- cairo_surface_t *surface;
- unsigned char *bitmap;
-};
-#endif
-
-/*
- * Structure storing a single physical XFontStruct, plus associated
- * data.
- */
-typedef struct x11font_individual {
- /* The XFontStruct itself. */
- XFontStruct *xfs;
-
- /*
- * The `allocated' flag indicates whether we've tried to fetch
- * this subfont already (thus distinguishing xfs==NULL because we
- * haven't tried yet from xfs==NULL because we tried and failed,
- * so that we don't keep trying and failing subsequently).
- */
- bool allocated;
-
-#ifdef DRAW_TEXT_CAIRO
- /*
- * A cache of glyph bitmaps downloaded from the X server when
- * we're in Cairo rendering mode. If glyphcache itself is
- * non-NULL, then entries in [0,nglyphs) are expected to be
- * initialised to either NULL or a bitmap pointer.
- */
- struct cairo_cached_glyph *glyphcache;
- int nglyphs;
-
- /*
- * X server paraphernalia for actually downloading the glyphs.
- */
- Pixmap pixmap;
- GC gc;
- int pixwidth, pixheight, pixoriginx, pixoriginy;
-
- /*
- * Paraphernalia for loading the resulting bitmaps into Cairo.
- */
- int rowsize, allsize, indexflip;
-#endif
-
-} x11font_individual;
-
-struct x11font {
- /*
- * Copy of the X display handle, so we don't have to keep
- * extracting it from GDK.
- */
- Display *disp;
- /*
- * Individual physical X fonts. We store a number of these, for
- * automatically guessed bold and wide variants.
- */
- x11font_individual fonts[4];
- /*
- * `sixteen_bit' is true iff the font object is indexed by
- * values larger than a byte. That is, this flag tells us
- * whether we use XDrawString or XDrawString16, etc.
- */
- bool sixteen_bit;
- /*
- * `variable' is true iff the font is non-fixed-pitch. This
- * enables some code which takes greater care over character
- * positioning during text drawing.
- */
- bool variable;
- /*
- * real_charset is the charset used when translating text into the
- * font's internal encoding inside draw_text(). This need not be
- * the same as the public_charset provided to the client; for
- * example, public_charset might be CS_ISO8859_1 while
- * real_charset is CS_ISO8859_1_X11.
- */
- int real_charset;
- /*
- * Data passed in to unifont_create().
- */
- int shadowoffset;
- bool wide, bold, shadowalways;
-
- unifont u;
-};
-
-static const UnifontVtable x11font_vtable = {
- .create = x11font_create,
- .create_fallback = NULL, /* no fallback fonts in X11 */
- .destroy = x11font_destroy,
- .has_glyph = x11font_has_glyph,
- .draw_text = x11font_draw_text,
- .draw_combining = x11font_draw_combining,
- .enum_fonts = x11font_enum_fonts,
- .canonify_fontname = x11font_canonify_fontname,
- .scale_fontname = x11font_scale_fontname,
- .size_increment = x11font_size_increment,
- .prefix = "server",
-};
-
-#define XLFD_STRING_PARTS_LIST(S,I) \
- S(foundry) \
- S(family_name) \
- S(weight_name) \
- S(slant) \
- S(setwidth_name) \
- S(add_style_name) \
- I(pixel_size) \
- I(point_size) \
- I(resolution_x) \
- I(resolution_y) \
- S(spacing) \
- I(average_width) \
- S(charset_registry) \
- S(charset_encoding) \
- /* end of list */
-
-/* Special value for int fields that xlfd_recompose will render as "*" */
-#define XLFD_INT_WILDCARD INT_MIN
-
-struct xlfd_decomposed {
-#define STR_FIELD(f) const char *f;
-#define INT_FIELD(f) int f;
- XLFD_STRING_PARTS_LIST(STR_FIELD, INT_FIELD)
-#undef STR_FIELD
-#undef INT_FIELD
-};
-
-static struct xlfd_decomposed *xlfd_decompose(const char *xlfd)
-{
- char *p, *components[14];
- struct xlfd_decomposed *dec;
- int i;
-
- if (!xlfd)
- return NULL;
-
- dec = snew_plus(struct xlfd_decomposed, strlen(xlfd) + 1);
- p = snew_plus_get_aux(dec);
- strcpy(p, xlfd);
-
- for (i = 0; i < 14; i++) {
- if (*p != '-') {
- /* Malformed XLFD: not enough '-' */
- sfree(dec);
- return NULL;
- }
- *p++ = '\0';
- components[i] = p;
- p += strcspn(p, "-");
- }
- if (*p) {
- /* Malformed XLFD: too many '-' */
- sfree(dec);
- return NULL;
- }
-
- i = 0;
-#define STORE_STR(f) dec->f = components[i++];
-#define STORE_INT(f) dec->f = atoi(components[i++]);
- XLFD_STRING_PARTS_LIST(STORE_STR, STORE_INT)
-#undef STORE_STR
-#undef STORE_INT
-
- return dec;
-}
-
-static char *xlfd_recompose(const struct xlfd_decomposed *dec)
-{
-#define FMT_STR(f) "-%s"
-#define ARG_STR(f) , dec->f
-#define FMT_INT(f) "%s%.*d"
-#define ARG_INT(f) \
- , dec->f == XLFD_INT_WILDCARD ? "-*" : "-" \
- , dec->f == XLFD_INT_WILDCARD ? 0 : 1 \
- , dec->f == XLFD_INT_WILDCARD ? 0 : dec->f
- return dupprintf(XLFD_STRING_PARTS_LIST(FMT_STR, FMT_INT)
- XLFD_STRING_PARTS_LIST(ARG_STR, ARG_INT));
-#undef FMT_STR
-#undef ARG_STR
-#undef FMT_INT
-#undef ARG_INT
-}
-
-static char *x11_guess_derived_font_name(Display *disp, XFontStruct *xfs,
- bool bold, bool wide)
-{
- Atom fontprop = XInternAtom(disp, "FONT", False);
- unsigned long ret;
- if (XGetFontProperty(xfs, fontprop, &ret)) {
- char *name = XGetAtomName(disp, (Atom)ret);
- struct xlfd_decomposed *xlfd = xlfd_decompose(name);
- if (!xlfd)
- return NULL;
-
- if (bold)
- xlfd->weight_name = "bold";
-
- if (wide) {
- /* Width name obviously may have changed. */
- /* Additional style may now become e.g. `ja' or `ko'. */
- xlfd->setwidth_name = xlfd->add_style_name = "*";
-
- /* Expect to double the average width. */
- xlfd->average_width *= 2;
- }
-
- {
- char *ret = xlfd_recompose(xlfd);
- sfree(xlfd);
- return ret;
- }
- }
- return NULL;
-}
-
-static int x11_font_width(XFontStruct *xfs, bool sixteen_bit)
-{
- if (sixteen_bit) {
- XChar2b space;
- space.byte1 = 0;
- space.byte2 = '0';
- return XTextWidth16(xfs, &space, 1);
- } else {
- return XTextWidth(xfs, "0", 1);
- }
-}
-
-static const XCharStruct *x11_char_struct(
- XFontStruct *xfs, unsigned char byte1, unsigned char byte2)
-{
- int index;
-
- /*
- * The man page for XQueryFont is rather confusing about how the
- * per_char array in the XFontStruct is laid out, because it gives
- * formulae for determining the two-byte X character code _from_
- * an index into the per_char array. Going the other way, it's
- * rather simpler:
- *
- * The valid character codes have byte1 between min_byte1 and
- * max_byte1 inclusive, and byte2 between min_char_or_byte2 and
- * max_char_or_byte2 inclusive. This gives a rectangle of size
- * (max_byte2-min_byte1+1) by
- * (max_char_or_byte2-min_char_or_byte2+1), which is precisely the
- * rectangle encoded in the per_char array. Hence, given a
- * character code which is valid in the sense that it falls
- * somewhere in that rectangle, its index in per_char is given by
- * setting
- *
- * x = byte2 - min_char_or_byte2
- * y = byte1 - min_byte1
- * index = y * (max_char_or_byte2-min_char_or_byte2+1) + x
- *
- * If min_byte1 and min_byte2 are both zero, that's a special case
- * which can be treated as if min_byte2 was 1 instead, i.e. the
- * per_char array just runs from min_char_or_byte2 to
- * max_char_or_byte2 inclusive, and byte1 should always be zero.
- */
-
- if (byte2 < xfs->min_char_or_byte2 || byte2 > xfs->max_char_or_byte2)
- return NULL;
-
- if (xfs->min_byte1 == 0 && xfs->max_byte1 == 0) {
- index = byte2 - xfs->min_char_or_byte2;
- } else {
- if (byte1 < xfs->min_byte1 || byte1 > xfs->max_byte1)
- return NULL;
- index = ((byte2 - xfs->min_char_or_byte2) +
- ((byte1 - xfs->min_byte1) *
- (xfs->max_char_or_byte2 - xfs->min_char_or_byte2 + 1)));
- }
-
- if (!xfs->per_char) /* per_char NULL => everything in range exists */
- return &xfs->max_bounds;
-
- return &xfs->per_char[index];
-}
-
-static bool x11_font_has_glyph(
- XFontStruct *xfs, unsigned char byte1, unsigned char byte2)
-{
- /*
- * Not to be confused with x11font_has_glyph, which is a method of
- * the x11font 'class' and hence takes a unifont as argument. This
- * is the low-level function which grubs about in an actual
- * XFontStruct to see if a given glyph exists.
- *
- * We must do this ourselves rather than letting Xlib's
- * XTextExtents16 do the job, because XTextExtents will helpfully
- * substitute the font's default_char for any missing glyph and
- * not tell us it did so, which precisely won't help us find out
- * which glyphs _are_ missing.
- */
- const XCharStruct *xcs = x11_char_struct(xfs, byte1, byte2);
- return xcs && (xcs->ascent + xcs->descent > 0 || xcs->width > 0);
-}
-
-static unifont *x11font_create(GtkWidget *widget, const char *name,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways)
-{
- struct x11font *xfont;
- XFontStruct *xfs;
- Display *disp;
- Atom charset_registry, charset_encoding, spacing;
- unsigned long registry_ret, encoding_ret, spacing_ret;
- int pubcs, realcs;
- bool sixteen_bit, variable;
- int i;
-
- if ((disp = get_x11_display()) == NULL)
- return NULL;
-
- xfs = XLoadQueryFont(disp, name);
- if (!xfs)
- return NULL;
-
- charset_registry = XInternAtom(disp, "CHARSET_REGISTRY", False);
- charset_encoding = XInternAtom(disp, "CHARSET_ENCODING", False);
-
- pubcs = realcs = CS_NONE;
- sixteen_bit = false;
- variable = true;
-
- if (XGetFontProperty(xfs, charset_registry, &registry_ret) &&
- XGetFontProperty(xfs, charset_encoding, &encoding_ret)) {
- char *reg, *enc;
- reg = XGetAtomName(disp, (Atom)registry_ret);
- enc = XGetAtomName(disp, (Atom)encoding_ret);
- if (reg && enc) {
- char *encoding = dupcat(reg, "-", enc);
- pubcs = realcs = charset_from_xenc(encoding);
-
- /*
- * iso10646-1 is the only wide font encoding we
- * support. In this case, we expect clients to give us
- * UTF-8, which this module must internally convert
- * into 16-bit Unicode.
- */
- if (!strcasecmp(encoding, "iso10646-1")) {
- sixteen_bit = true;
- pubcs = realcs = CS_UTF8;
- }
-
- /*
- * Hack for X line-drawing characters: if the primary font
- * is encoded as ISO-8859-1, and has valid glyphs in the
- * low character positions, it is assumed that those
- * glyphs are the VT100 line-drawing character set.
- */
- if (pubcs == CS_ISO8859_1) {
- int ch;
- for (ch = 1; ch < 32; ch++)
- if (!x11_font_has_glyph(xfs, 0, ch))
- break;
- if (ch == 32)
- realcs = CS_ISO8859_1_X11;
- }
-
- sfree(encoding);
- }
- }
-
- spacing = XInternAtom(disp, "SPACING", False);
- if (XGetFontProperty(xfs, spacing, &spacing_ret)) {
- char *spc;
- spc = XGetAtomName(disp, (Atom)spacing_ret);
-
- if (spc && strchr("CcMm", spc[0]))
- variable = false;
- }
-
- xfont = snew(struct x11font);
- xfont->u.vt = &x11font_vtable;
- xfont->u.width = x11_font_width(xfs, sixteen_bit);
- xfont->u.ascent = xfs->ascent;
- xfont->u.descent = xfs->descent;
- xfont->u.height = xfont->u.ascent + xfont->u.descent;
- xfont->u.public_charset = pubcs;
- xfont->u.want_fallback = true;
- xfont->u.strikethrough_y = xfont->u.ascent - (xfont->u.ascent * 3 / 8);
-#ifdef DRAW_TEXT_GDK
- xfont->u.preferred_drawtype = DRAWTYPE_GDK;
-#elif defined DRAW_TEXT_CAIRO
- xfont->u.preferred_drawtype = DRAWTYPE_CAIRO;
-#else
-#error No drawtype available at all
-#endif
- xfont->disp = disp;
- xfont->real_charset = realcs;
- xfont->sixteen_bit = sixteen_bit;
- xfont->variable = variable;
- xfont->wide = wide;
- xfont->bold = bold;
- xfont->shadowoffset = shadowoffset;
- xfont->shadowalways = shadowalways;
-
- for (i = 0; i < lenof(xfont->fonts); i++) {
- xfont->fonts[i].xfs = NULL;
- xfont->fonts[i].allocated = false;
-#ifdef DRAW_TEXT_CAIRO
- xfont->fonts[i].glyphcache = NULL;
- xfont->fonts[i].nglyphs = 0;
- xfont->fonts[i].pixmap = None;
- xfont->fonts[i].gc = None;
-#endif
- }
- xfont->fonts[0].xfs = xfs;
- xfont->fonts[0].allocated = true;
-
- return &xfont->u;
-}
-
-static void x11font_destroy(unifont *font)
-{
- struct x11font *xfont = container_of(font, struct x11font, u);
- Display *disp = xfont->disp;
- int i;
-
- for (i = 0; i < lenof(xfont->fonts); i++) {
- if (xfont->fonts[i].xfs)
- XFreeFont(disp, xfont->fonts[i].xfs);
-#ifdef DRAW_TEXT_CAIRO
- if (xfont->fonts[i].gc != None)
- XFreeGC(disp, xfont->fonts[i].gc);
- if (xfont->fonts[i].pixmap != None)
- XFreePixmap(disp, xfont->fonts[i].pixmap);
- if (xfont->fonts[i].glyphcache) {
- int j;
- for (j = 0; j < xfont->fonts[i].nglyphs; j++) {
- cairo_surface_destroy(xfont->fonts[i].glyphcache[j].surface);
- sfree(xfont->fonts[i].glyphcache[j].bitmap);
- }
- sfree(xfont->fonts[i].glyphcache);
- }
-#endif
- }
- sfree(xfont);
-}
-
-static void x11_alloc_subfont(struct x11font *xfont, int sfid)
-{
- Display *disp = xfont->disp;
- char *derived_name = x11_guess_derived_font_name
- (disp, xfont->fonts[0].xfs, sfid & 1, !!(sfid & 2));
- xfont->fonts[sfid].xfs = XLoadQueryFont(disp, derived_name);
- xfont->fonts[sfid].allocated = true;
- sfree(derived_name);
- /* Note that xfont->fonts[sfid].xfs may still be NULL, if XLQF failed. */
-}
-
-static bool x11font_has_glyph(unifont *font, wchar_t glyph)
-{
- struct x11font *xfont = container_of(font, struct x11font, u);
-
- if (xfont->sixteen_bit) {
- /*
- * This X font has 16-bit character indices, which means
- * we can directly use our Unicode input value.
- */
- return x11_font_has_glyph(xfont->fonts[0].xfs,
- glyph >> 8, glyph & 0xFF);
- } else {
- /*
- * This X font has 8-bit indices, so we must convert to the
- * appropriate character set.
- */
- char sbstring[2];
- int sblen = wc_to_mb(xfont->real_charset, 0, &glyph, 1,
- sbstring, 2, "", NULL);
- if (sblen == 0 || !sbstring[0])
- return false; /* not even in the charset */
-
- return x11_font_has_glyph(xfont->fonts[0].xfs, 0,
- (unsigned char)sbstring[0]);
- }
-}
-
-#if !GTK_CHECK_VERSION(2,0,0)
-#define GDK_DRAWABLE_XID(d) GDK_WINDOW_XWINDOW(d) /* GTK1's name for this */
-#elif GTK_CHECK_VERSION(3,0,0)
-#define GDK_DRAWABLE_XID(d) GDK_WINDOW_XID(d) /* GTK3's name for this */
-#endif
-
-static int x11font_width_16(unifont_drawctx *ctx, x11font_individual *xfi,
- const void *vstring, int start, int length)
-{
- const XChar2b *string = (const XChar2b *)vstring;
- return XTextWidth16(xfi->xfs, string+start, length);
-}
-
-static int x11font_width_8(unifont_drawctx *ctx, x11font_individual *xfi,
- const void *vstring, int start, int length)
-{
- const char *string = (const char *)vstring;
- return XTextWidth(xfi->xfs, string+start, length);
-}
-
-#ifdef DRAW_TEXT_GDK
-static void x11font_gdk_setup(unifont_drawctx *ctx, x11font_individual *xfi,
- Display *disp)
-{
- XSetFont(disp, GDK_GC_XGC(ctx->u.gdk.gc), xfi->xfs->fid);
-}
-
-static void x11font_gdk_draw_16(unifont_drawctx *ctx, x11font_individual *xfi,
- Display *disp, int x, int y,
- const void *vstring, int start, int length)
-{
- const XChar2b *string = (const XChar2b *)vstring;
- XDrawString16(disp, GDK_DRAWABLE_XID(ctx->u.gdk.target),
- GDK_GC_XGC(ctx->u.gdk.gc), x, y, string+start, length);
-}
-
-static void x11font_gdk_draw_8(unifont_drawctx *ctx, x11font_individual *xfi,
- Display *disp, int x, int y,
- const void *vstring, int start, int length)
-{
- const char *string = (const char *)vstring;
- XDrawString(disp, GDK_DRAWABLE_XID(ctx->u.gdk.target),
- GDK_GC_XGC(ctx->u.gdk.gc), x, y, string+start, length);
-}
-#endif
-
-#ifdef DRAW_TEXT_CAIRO
-static void x11font_cairo_setup(
- unifont_drawctx *ctx, x11font_individual *xfi, Display *disp)
-{
- if (xfi->pixmap == None) {
- XGCValues gcvals;
- GdkWindow *widgetwin = gtk_widget_get_window(ctx->u.cairo.widget);
- int widgetscr = GDK_SCREEN_XNUMBER(gdk_window_get_screen(widgetwin));
-
- xfi->pixwidth =
- xfi->xfs->max_bounds.rbearing - xfi->xfs->min_bounds.lbearing;
- xfi->pixheight =
- xfi->xfs->max_bounds.ascent + xfi->xfs->max_bounds.descent;
- xfi->pixoriginx = -xfi->xfs->min_bounds.lbearing;
- xfi->pixoriginy = xfi->xfs->max_bounds.ascent;
-
- xfi->rowsize = cairo_format_stride_for_width(CAIRO_FORMAT_A1,
- xfi->pixwidth);
- xfi->allsize = xfi->rowsize * xfi->pixheight;
-
- {
- /*
- * Test host endianness and use it to set xfi->indexflip,
- * which is XORed into our left-shift counts in order to
- * implement the CAIRO_FORMAT_A1 specification, in which
- * each bitmap byte is oriented LSB-first on little-endian
- * platforms and MSB-first on big-endian ones.
- *
- * This is the same technique Cairo itself uses to test
- * endianness, so hopefully it'll work in any situation
- * where Cairo is usable at all.
- */
- static const int endianness_test = 1;
- xfi->indexflip = (*((char *) &endianness_test) == 1) ? 0 : 7;
- }
-
- xfi->pixmap = XCreatePixmap
- (disp,
- GDK_DRAWABLE_XID(gtk_widget_get_window(ctx->u.cairo.widget)),
- xfi->pixwidth, xfi->pixheight, 1);
- gcvals.foreground = WhitePixel(disp, widgetscr);
- gcvals.background = BlackPixel(disp, widgetscr);
- gcvals.font = xfi->xfs->fid;
- xfi->gc = XCreateGC(disp, xfi->pixmap,
- GCForeground | GCBackground | GCFont, &gcvals);
- }
-}
-
-static void x11font_cairo_cache_glyph(
- Display *disp, x11font_individual *xfi, int glyphindex)
-{
- XImage *image;
- int x, y;
- unsigned char *bitmap;
- const XCharStruct *xcs = x11_char_struct(xfi->xfs, glyphindex >> 8,
- glyphindex & 0xFF);
-
- bitmap = snewn(xfi->allsize, unsigned char);
- memset(bitmap, 0, xfi->allsize);
-
- image = XGetImage(disp, xfi->pixmap, 0, 0,
- xfi->pixwidth, xfi->pixheight, AllPlanes, XYPixmap);
- for (y = xfi->pixoriginy - xcs->ascent;
- y < xfi->pixoriginy + xcs->descent; y++) {
- for (x = xfi->pixoriginx + xcs->lbearing;
- x < xfi->pixoriginx + xcs->rbearing; x++) {
- unsigned long pixel = XGetPixel(image, x, y);
- if (pixel) {
- int byteindex = y * xfi->rowsize + x/8;
- int bitindex = (x & 7) ^ xfi->indexflip;
- bitmap[byteindex] |= 1U << bitindex;
- }
- }
- }
- XDestroyImage(image);
-
- if (xfi->nglyphs <= glyphindex) {
- /* Round up to the next multiple of 256 on the general
- * principle that Unicode characters come in contiguous blocks
- * often used together */
- int old_nglyphs = xfi->nglyphs;
- xfi->nglyphs = (glyphindex + 0x100) & ~0xFF;
- xfi->glyphcache = sresize(xfi->glyphcache, xfi->nglyphs,
- struct cairo_cached_glyph);
-
- while (old_nglyphs < xfi->nglyphs) {
- xfi->glyphcache[old_nglyphs].surface = NULL;
- xfi->glyphcache[old_nglyphs].bitmap = NULL;
- old_nglyphs++;
- }
- }
- xfi->glyphcache[glyphindex].bitmap = bitmap;
- xfi->glyphcache[glyphindex].surface = cairo_image_surface_create_for_data
- (bitmap, CAIRO_FORMAT_A1, xfi->pixwidth, xfi->pixheight, xfi->rowsize);
-}
-
-static void x11font_cairo_draw_glyph(unifont_drawctx *ctx,
- x11font_individual *xfi, int x, int y,
- int glyphindex)
-{
- if (xfi->glyphcache[glyphindex].surface) {
- cairo_mask_surface(ctx->u.cairo.cr,
- xfi->glyphcache[glyphindex].surface,
- x - xfi->pixoriginx, y - xfi->pixoriginy);
- }
-}
-
-static void x11font_cairo_draw_16(
- unifont_drawctx *ctx, x11font_individual *xfi, Display *disp,
- int x, int y, const void *vstring, int start, int length)
-{
- const XChar2b *string = (const XChar2b *)vstring + start;
- int i;
- for (i = 0; i < length; i++) {
- if (x11_font_has_glyph(xfi->xfs, string[i].byte1, string[i].byte2)) {
- int glyphindex = (256 * (unsigned char)string[i].byte1 +
- (unsigned char)string[i].byte2);
- if (glyphindex >= xfi->nglyphs ||
- !xfi->glyphcache[glyphindex].surface) {
- XDrawImageString16(disp, xfi->pixmap, xfi->gc,
- xfi->pixoriginx, xfi->pixoriginy,
- string+i, 1);
- x11font_cairo_cache_glyph(disp, xfi, glyphindex);
- }
- x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex);
- x += XTextWidth16(xfi->xfs, string+i, 1);
- }
- }
-}
-
-static void x11font_cairo_draw_8(
- unifont_drawctx *ctx, x11font_individual *xfi, Display *disp,
- int x, int y, const void *vstring, int start, int length)
-{
- const char *string = (const char *)vstring + start;
- int i;
- for (i = 0; i < length; i++) {
- if (x11_font_has_glyph(xfi->xfs, 0, string[i])) {
- int glyphindex = (unsigned char)string[i];
- if (glyphindex >= xfi->nglyphs ||
- !xfi->glyphcache[glyphindex].surface) {
- XDrawImageString(disp, xfi->pixmap, xfi->gc,
- xfi->pixoriginx, xfi->pixoriginy,
- string+i, 1);
- x11font_cairo_cache_glyph(disp, xfi, glyphindex);
- }
- x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex);
- x += XTextWidth(xfi->xfs, string+i, 1);
- }
- }
-}
-#endif /* DRAW_TEXT_CAIRO */
-
-struct x11font_drawfuncs {
- int (*width)(unifont_drawctx *ctx, x11font_individual *xfi,
- const void *vstring, int start, int length);
- void (*setup)(unifont_drawctx *ctx, x11font_individual *xfi,
- Display *disp);
- void (*draw)(unifont_drawctx *ctx, x11font_individual *xfi, Display *disp,
- int x, int y, const void *vstring, int start, int length);
-};
-
-/*
- * This array has two entries per compiled-in drawtype; of each pair,
- * the first is for an 8-bit font and the second for 16-bit.
- */
-static const struct x11font_drawfuncs x11font_drawfuncs[2*DRAWTYPE_NTYPES] = {
-#ifdef DRAW_TEXT_GDK
- /* gdk, 8-bit */
- {
- x11font_width_8,
- x11font_gdk_setup,
- x11font_gdk_draw_8,
- },
- /* gdk, 16-bit */
- {
- x11font_width_16,
- x11font_gdk_setup,
- x11font_gdk_draw_16,
- },
-#endif
-#ifdef DRAW_TEXT_CAIRO
- /* cairo, 8-bit */
- {
- x11font_width_8,
- x11font_cairo_setup,
- x11font_cairo_draw_8,
- },
- /* [3] cairo, 16-bit */
- {
- x11font_width_16,
- x11font_cairo_setup,
- x11font_cairo_draw_16,
- },
-#endif
-};
-
-static void x11font_really_draw_text(
- const struct x11font_drawfuncs *dfns, unifont_drawctx *ctx,
- x11font_individual *xfi, Display *disp,
- int x, int y, const void *string, int nchars,
- int shadowoffset, bool fontvariable, int cellwidth)
-{
- int start = 0, step, nsteps;
- bool centre;
-
- if (fontvariable) {
- /*
- * In a variable-pitch font, we draw one character at a
- * time, and centre it in the character cell.
- */
- step = 1;
- nsteps = nchars;
- centre = true;
- } else {
- /*
- * In a fixed-pitch font, we can draw the whole lot in one go.
- */
- step = nchars;
- nsteps = 1;
- centre = false;
- }
-
- dfns->setup(ctx, xfi, disp);
-
- while (nsteps-- > 0) {
- int X = x;
- if (centre)
- X += (cellwidth - dfns->width(ctx, xfi, string, start, step)) / 2;
-
- dfns->draw(ctx, xfi, disp, X, y, string, start, step);
- if (shadowoffset)
- dfns->draw(ctx, xfi, disp, X + shadowoffset, y,
- string, start, step);
-
- x += cellwidth;
- start += step;
- }
-}
-
-static void x11font_draw_text(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth)
-{
- struct x11font *xfont = container_of(font, struct x11font, u);
- int sfid;
- int shadowoffset = 0;
- int mult = (wide ? 2 : 1);
- int index = 2 * (int)ctx->type;
-
- wide = wide && !xfont->wide;
- bold = bold && !xfont->bold;
-
- /*
- * Decide which subfont we're using, and whether we have to
- * use shadow bold.
- */
- if (xfont->shadowalways && bold) {
- shadowoffset = xfont->shadowoffset;
- bold = false;
- }
- sfid = 2 * wide + bold;
- if (!xfont->fonts[sfid].allocated)
- x11_alloc_subfont(xfont, sfid);
- if (bold && !xfont->fonts[sfid].xfs) {
- bold = false;
- shadowoffset = xfont->shadowoffset;
- sfid = 2 * wide + bold;
- if (!xfont->fonts[sfid].allocated)
- x11_alloc_subfont(xfont, sfid);
- }
-
- if (!xfont->fonts[sfid].xfs)
- return; /* we've tried our best, but no luck */
-
- if (xfont->sixteen_bit) {
- /*
- * This X font has 16-bit character indices, which means
- * we can directly use our Unicode input string.
- */
- XChar2b *xcs;
- int i;
-
- xcs = snewn(len, XChar2b);
- for (i = 0; i < len; i++) {
- xcs[i].byte1 = string[i] >> 8;
- xcs[i].byte2 = string[i];
- }
-
- x11font_really_draw_text(x11font_drawfuncs + index + 1, ctx,
- &xfont->fonts[sfid], xfont->disp, x, y,
- xcs, len, shadowoffset,
- xfont->variable, cellwidth * mult);
- sfree(xcs);
- } else {
- /*
- * This X font has 8-bit indices, so we must convert to the
- * appropriate character set.
- */
- char *sbstring = snewn(len+1, char);
- int sblen = wc_to_mb(xfont->real_charset, 0, string, len,
- sbstring, len+1, ".", NULL);
- x11font_really_draw_text(x11font_drawfuncs + index + 0, ctx,
- &xfont->fonts[sfid], xfont->disp, x, y,
- sbstring, sblen, shadowoffset,
- xfont->variable, cellwidth * mult);
- sfree(sbstring);
- }
-}
-
-static void x11font_draw_combining(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string,
- int len, bool wide, bool bold,
- int cellwidth)
-{
- /*
- * For server-side fonts, there's no sophisticated system for
- * combining characters intelligently, so the best we can do is to
- * overprint them on each other in the obvious way.
- */
- int i;
- for (i = 0; i < len; i++)
- x11font_draw_text(ctx, font, x, y, string+i, 1, wide, bold, cellwidth);
-}
-
-static void x11font_enum_fonts(GtkWidget *widget,
- fontsel_add_entry callback, void *callback_ctx)
-{
- Display *disp;
- char **fontnames;
- char *tmp = NULL;
- int nnames, i, max, tmpsize;
-
- if ((disp = get_x11_display()) == NULL)
- return;
-
- max = 32768;
- while (1) {
- fontnames = XListFonts(disp, "*", max, &nnames);
- if (nnames >= max) {
- XFreeFontNames(fontnames);
- max *= 2;
- } else
- break;
- }
-
- tmpsize = 0;
-
- for (i = 0; i < nnames; i++) {
- struct xlfd_decomposed *xlfd = xlfd_decompose(fontnames[i]);
- if (xlfd) {
- char *p, *font, *style, *stylekey, *charset;
- int weightkey, slantkey, setwidthkey;
- int thistmpsize;
-
- /*
- * Convert a dismembered XLFD into the format we'll be
- * using in the font selector.
- */
-
- thistmpsize = 4 * strlen(fontnames[i]) + 256;
- if (tmpsize < thistmpsize) {
- tmpsize = thistmpsize;
- tmp = sresize(tmp, tmpsize, char);
- }
- p = tmp;
-
- /*
- * Font name is in the form "family (foundry)". (This is
- * what the GTK 1.2 X font selector does, and it seems to
- * come out looking reasonably sensible.)
- */
- font = p;
- p += 1 + sprintf(p, "%s (%s)", xlfd->family_name, xlfd->foundry);
-
- /*
- * Character set.
- */
- charset = p;
- p += 1 + sprintf(p, "%s-%s", xlfd->charset_registry,
- xlfd->charset_encoding);
-
- /*
- * Style is a mixture of quite a lot of the fields,
- * with some strange formatting.
- */
- style = p;
- p += sprintf(p, "%s", xlfd->weight_name[0] ? xlfd->weight_name :
- "regular");
- if (!g_ascii_strcasecmp(xlfd->slant, "i"))
- p += sprintf(p, " italic");
- else if (!g_ascii_strcasecmp(xlfd->slant, "o"))
- p += sprintf(p, " oblique");
- else if (!g_ascii_strcasecmp(xlfd->slant, "ri"))
- p += sprintf(p, " reverse italic");
- else if (!g_ascii_strcasecmp(xlfd->slant, "ro"))
- p += sprintf(p, " reverse oblique");
- else if (!g_ascii_strcasecmp(xlfd->slant, "ot"))
- p += sprintf(p, " other-slant");
- if (xlfd->setwidth_name[0] &&
- g_ascii_strcasecmp(xlfd->setwidth_name, "normal"))
- p += sprintf(p, " %s", xlfd->setwidth_name);
- if (!g_ascii_strcasecmp(xlfd->spacing, "m"))
- p += sprintf(p, " [M]");
- if (!g_ascii_strcasecmp(xlfd->spacing, "c"))
- p += sprintf(p, " [C]");
- if (xlfd->add_style_name[0])
- p += sprintf(p, " %s", xlfd->add_style_name);
-
- /*
- * Style key is the same stuff as above, but with a
- * couple of transformations done on it to make it
- * sort more sensibly.
- */
- p++;
- stylekey = p;
- if (!g_ascii_strcasecmp(xlfd->weight_name, "medium") ||
- !g_ascii_strcasecmp(xlfd->weight_name, "regular") ||
- !g_ascii_strcasecmp(xlfd->weight_name, "normal") ||
- !g_ascii_strcasecmp(xlfd->weight_name, "book"))
- weightkey = 0;
- else if (!g_ascii_strncasecmp(xlfd->weight_name, "demi", 4) ||
- !g_ascii_strncasecmp(xlfd->weight_name, "semi", 4))
- weightkey = 1;
- else
- weightkey = 2;
- if (!g_ascii_strcasecmp(xlfd->slant, "r"))
- slantkey = 0;
- else if (!g_ascii_strncasecmp(xlfd->slant, "r", 1))
- slantkey = 2;
- else
- slantkey = 1;
- if (!g_ascii_strcasecmp(xlfd->setwidth_name, "normal"))
- setwidthkey = 0;
- else
- setwidthkey = 1;
-
- p += sprintf(
- p, "%04d%04d%s%04d%04d%s%04d%04d%s%04d%s%04d%s",
- weightkey, (int)strlen(xlfd->weight_name), xlfd->weight_name,
- slantkey, (int)strlen(xlfd->slant), xlfd->slant,
- setwidthkey,
- (int)strlen(xlfd->setwidth_name), xlfd->setwidth_name,
- (int)strlen(xlfd->spacing), xlfd->spacing,
- (int)strlen(xlfd->add_style_name), xlfd->add_style_name);
-
- assert(p - tmp < thistmpsize);
-
- /*
- * Flags: we need to know whether this is a monospaced
- * font, which we do by examining the spacing field
- * again.
- */
- int flags = FONTFLAG_SERVERSIDE;
- if (!strchr("CcMm", xlfd->spacing[0]))
- flags |= FONTFLAG_NONMONOSPACED;
-
- /*
- * Some fonts have a pixel size of zero, meaning they're
- * treated as scalable. For these purposes, we only want
- * fonts whose pixel size we actually know, so filter
- * those out.
- */
- if (xlfd->pixel_size)
- callback(callback_ctx, fontnames[i], font, charset,
- style, stylekey, xlfd->pixel_size, flags,
- &x11font_vtable);
-
- sfree(xlfd);
- } else {
- /*
- * This isn't an XLFD, so it must be an alias.
- * Transmit it with mostly null data.
- *
- * It would be nice to work out if it's monospaced
- * here, but at the moment I can't see that being
- * anything but computationally hideous. Ah well.
- */
- callback(callback_ctx, fontnames[i], fontnames[i], NULL,
- NULL, NULL, 0, FONTFLAG_SERVERALIAS, &x11font_vtable);
-
- sfree(xlfd);
- }
- }
- XFreeFontNames(fontnames);
- sfree(tmp);
-}
-
-static char *x11font_canonify_fontname(GtkWidget *widget, const char *name,
- int *size, int *flags,
- bool resolve_aliases)
-{
- /*
- * When given an X11 font name to try to make sense of for a
- * font selector, we must attempt to load it (to see if it
- * exists), and then canonify it by extracting its FONT
- * property, which should give its full XLFD even if what we
- * originally had was a wildcard.
- *
- * However, we must carefully avoid canonifying font
- * _aliases_, unless specifically asked to, because the font
- * selector treats them as worthwhile in their own right.
- */
- XFontStruct *xfs;
- Display *disp;
- Atom fontprop, fontprop2;
- unsigned long ret;
-
- if ((disp = get_x11_display()) == NULL)
- return NULL;
-
- xfs = XLoadQueryFont(disp, name);
-
- if (!xfs)
- return NULL; /* didn't make sense to us, sorry */
-
- fontprop = XInternAtom(disp, "FONT", False);
-
- if (XGetFontProperty(xfs, fontprop, &ret)) {
- char *newname = XGetAtomName(disp, (Atom)ret);
- if (newname) {
- unsigned long fsize = 12;
-
- fontprop2 = XInternAtom(disp, "PIXEL_SIZE", False);
- if (XGetFontProperty(xfs, fontprop2, &fsize) && fsize > 0) {
- *size = fsize;
- XFreeFont(disp, xfs);
- if (flags) {
- if (name[0] == '-' || resolve_aliases)
- *flags = FONTFLAG_SERVERSIDE;
- else
- *flags = FONTFLAG_SERVERALIAS;
- }
- return dupstr(name[0] == '-' || resolve_aliases ?
- newname : name);
- }
- }
- }
-
- XFreeFont(disp, xfs);
-
- return NULL; /* something went wrong */
-}
-
-static char *x11font_scale_fontname(GtkWidget *widget, const char *name,
- int size)
-{
- return NULL; /* shan't */
-}
-
-static char *x11font_size_increment(unifont *font, int increment)
-{
- struct x11font *xfont = container_of(font, struct x11font, u);
- Display *disp = xfont->disp;
- Atom fontprop = XInternAtom(disp, "FONT", False);
- char *returned_name = NULL;
- unsigned long ret;
- if (XGetFontProperty(xfont->fonts[0].xfs, fontprop, &ret)) {
- struct xlfd_decomposed *xlfd;
- struct xlfd_decomposed *xlfd_best;
- char *wc;
- char **fontnames;
- int nnames, i, max;
-
- xlfd = xlfd_decompose(XGetAtomName(disp, (Atom)ret));
- if (!xlfd)
- return NULL;
-
- /*
- * Form a wildcard consisting of everything in the
- * original XLFD except for the size-related fields.
- */
- {
- struct xlfd_decomposed xlfd_wc = *xlfd; /* structure copy */
- xlfd_wc.pixel_size = XLFD_INT_WILDCARD;
- xlfd_wc.point_size = XLFD_INT_WILDCARD;
- xlfd_wc.average_width = XLFD_INT_WILDCARD;
- wc = xlfd_recompose(&xlfd_wc);
- }
-
- /*
- * Fetch all the font names matching that wildcard.
- */
- max = 32768;
- while (1) {
- fontnames = XListFonts(disp, wc, max, &nnames);
- if (nnames >= max) {
- XFreeFontNames(fontnames);
- max *= 2;
- } else
- break;
- }
-
- sfree(wc);
-
- /*
- * Iterate over those to find the one closest in size to the
- * original font, in the correct direction.
- */
-
-#define FLIPPED_SIZE(xlfd) \
- (((xlfd)->pixel_size + (xlfd)->point_size) * \
- (increment < 0 ? -1 : +1))
-
- xlfd_best = NULL;
- for (i = 0; i < nnames; i++) {
- struct xlfd_decomposed *xlfd2 = xlfd_decompose(fontnames[i]);
- if (!xlfd2)
- continue;
-
- if (xlfd2->pixel_size != 0 &&
- FLIPPED_SIZE(xlfd2) > FLIPPED_SIZE(xlfd) &&
- (!xlfd_best || FLIPPED_SIZE(xlfd2)<FLIPPED_SIZE(xlfd_best))) {
- sfree(xlfd_best);
- xlfd_best = xlfd2;
- xlfd2 = NULL;
- }
-
- sfree(xlfd2);
- }
-
-#undef FLIPPED_SIZE
-
- if (xlfd_best) {
- char *bare_returned_name = xlfd_recompose(xlfd_best);
- returned_name = dupcat(
- xfont->u.vt->prefix, ":", bare_returned_name);
- sfree(bare_returned_name);
- }
-
- XFreeFontNames(fontnames);
- sfree(xlfd);
- sfree(xlfd_best);
- }
- return returned_name;
-}
-
-#endif /* NOT_X_WINDOWS */
-
-#if GTK_CHECK_VERSION(2,0,0)
-
-/* ----------------------------------------------------------------------
- * Pango font implementation (for GTK 2 only).
- */
-
-#if defined PANGO_PRE_1POINT4 && !defined PANGO_PRE_1POINT6
-#define PANGO_PRE_1POINT6 /* make life easier for pre-1.4 folk */
-#endif
-
-static bool pangofont_has_glyph(unifont *font, wchar_t glyph);
-static void pangofont_draw_text(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth);
-static void pangofont_draw_combining(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string,
- int len, bool wide, bool bold,
- int cellwidth);
-static unifont *pangofont_create(GtkWidget *widget, const char *name,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways);
-static unifont *pangofont_create_fallback(GtkWidget *widget, int height,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways);
-static void pangofont_destroy(unifont *font);
-static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback,
- void *callback_ctx);
-static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name,
- int *size, int *flags,
- bool resolve_aliases);
-static char *pangofont_scale_fontname(GtkWidget *widget, const char *name,
- int size);
-static char *pangofont_size_increment(unifont *font, int increment);
-
-struct pangofont {
- /*
- * Pango objects.
- */
- PangoFontDescription *desc;
- PangoFontset *fset;
- /*
- * The containing widget.
- */
- GtkWidget *widget;
- /*
- * Data passed in to unifont_create().
- */
- int shadowoffset;
- bool bold, shadowalways;
- /*
- * Cache of character widths, indexed by Unicode code point. In
- * pixels; -1 means we haven't asked Pango about this character
- * before.
- */
- int *widthcache;
- unsigned nwidthcache;
-
- struct unifont u;
-};
-
-static const UnifontVtable pangofont_vtable = {
- .create = pangofont_create,
- .create_fallback = pangofont_create_fallback,
- .destroy = pangofont_destroy,
- .has_glyph = pangofont_has_glyph,
- .draw_text = pangofont_draw_text,
- .draw_combining = pangofont_draw_combining,
- .enum_fonts = pangofont_enum_fonts,
- .canonify_fontname = pangofont_canonify_fontname,
- .scale_fontname = pangofont_scale_fontname,
- .size_increment = pangofont_size_increment,
- .prefix = "client",
-};
-
-/*
- * This function is used to rigorously validate a
- * PangoFontDescription. Later versions of Pango have a nasty
- * habit of accepting _any_ old string as input to
- * pango_font_description_from_string and returning a font
- * description which can actually be used to display text, even if
- * they have to do it by falling back to their most default font.
- * This is doubtless helpful in some situations, but not here,
- * because we need to know if a Pango font string actually _makes
- * sense_ in order to fall back to treating it as an X font name
- * if it doesn't. So we check that the font family is actually one
- * supported by Pango.
- */
-static bool pangofont_check_desc_makes_sense(PangoContext *ctx,
- PangoFontDescription *desc)
-{
-#ifndef PANGO_PRE_1POINT6
- PangoFontMap *map;
-#endif
- PangoFontFamily **families;
- int i, nfamilies;
- bool matched;
-
- /*
- * Ask Pango for a list of font families, and iterate through
- * them to see if one of them matches the family in the
- * PangoFontDescription.
- */
-#ifndef PANGO_PRE_1POINT6
- map = pango_context_get_font_map(ctx);
- if (!map)
- return false;
- pango_font_map_list_families(map, &families, &nfamilies);
-#else
- pango_context_list_families(ctx, &families, &nfamilies);
-#endif
-
- matched = false;
- for (i = 0; i < nfamilies; i++) {
- if (!g_ascii_strcasecmp(pango_font_family_get_name(families[i]),
- pango_font_description_get_family(desc))) {
- matched = true;
- break;
- }
- }
- g_free(families);
-
- return matched;
-}
-
-static unifont *pangofont_create_internal(GtkWidget *widget,
- PangoContext *ctx,
- PangoFontDescription *desc,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways)
-{
- struct pangofont *pfont;
-#ifndef PANGO_PRE_1POINT6
- PangoFontMap *map;
-#endif
- PangoFontset *fset;
- PangoFontMetrics *metrics;
-
-#ifndef PANGO_PRE_1POINT6
- map = pango_context_get_font_map(ctx);
- if (!map) {
- pango_font_description_free(desc);
- return NULL;
- }
- fset = pango_font_map_load_fontset(map, ctx, desc,
- pango_context_get_language(ctx));
-#else
- fset = pango_context_load_fontset(ctx, desc,
- pango_context_get_language(ctx));
-#endif
- if (!fset) {
- pango_font_description_free(desc);
- return NULL;
- }
- metrics = pango_fontset_get_metrics(fset);
- if (!metrics ||
- pango_font_metrics_get_approximate_digit_width(metrics) == 0) {
- pango_font_description_free(desc);
- g_object_unref(fset);
- return NULL;
- }
-
- pfont = snew(struct pangofont);
- pfont->u.vt = &pangofont_vtable;
- pfont->u.width =
- PANGO_PIXELS(pango_font_metrics_get_approximate_digit_width(metrics));
- pfont->u.ascent =
- PANGO_PIXELS_CEIL(pango_font_metrics_get_ascent(metrics));
- pfont->u.descent =
- PANGO_PIXELS_CEIL(pango_font_metrics_get_descent(metrics));
- pfont->u.height = pfont->u.ascent + pfont->u.descent;
- pfont->u.strikethrough_y =
- PANGO_PIXELS(pango_font_metrics_get_ascent(metrics) -
- pango_font_metrics_get_strikethrough_position(metrics));
- pfont->u.want_fallback = false;
-#ifdef DRAW_TEXT_CAIRO
- pfont->u.preferred_drawtype = DRAWTYPE_CAIRO;
-#elif defined DRAW_TEXT_GDK
- pfont->u.preferred_drawtype = DRAWTYPE_GDK;
-#else
-#error No drawtype available at all
-#endif
- /* The Pango API is hardwired to UTF-8 */
- pfont->u.public_charset = CS_UTF8;
- pfont->desc = desc;
- pfont->fset = fset;
- pfont->widget = widget;
- pfont->bold = bold;
- pfont->shadowoffset = shadowoffset;
- pfont->shadowalways = shadowalways;
- pfont->widthcache = NULL;
- pfont->nwidthcache = 0;
-
- pango_font_metrics_unref(metrics);
-
- return &pfont->u;
-}
-
-static unifont *pangofont_create(GtkWidget *widget, const char *name,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways)
-{
- PangoContext *ctx;
- PangoFontDescription *desc;
-
- desc = pango_font_description_from_string(name);
- if (!desc)
- return NULL;
- ctx = gtk_widget_get_pango_context(widget);
- if (!ctx) {
- pango_font_description_free(desc);
- return NULL;
- }
- if (!pangofont_check_desc_makes_sense(ctx, desc)) {
- pango_font_description_free(desc);
- return NULL;
- }
- return pangofont_create_internal(widget, ctx, desc, wide, bold,
- shadowoffset, shadowalways);
-}
-
-static unifont *pangofont_create_fallback(GtkWidget *widget, int height,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways)
-{
- PangoContext *ctx;
- PangoFontDescription *desc;
-
- desc = pango_font_description_from_string("Monospace");
- if (!desc)
- return NULL;
- ctx = gtk_widget_get_pango_context(widget);
- if (!ctx) {
- pango_font_description_free(desc);
- return NULL;
- }
- pango_font_description_set_absolute_size(desc, height * PANGO_SCALE);
- return pangofont_create_internal(widget, ctx, desc, wide, bold,
- shadowoffset, shadowalways);
-}
-
-static void pangofont_destroy(unifont *font)
-{
- struct pangofont *pfont = container_of(font, struct pangofont, u);
- pango_font_description_free(pfont->desc);
- sfree(pfont->widthcache);
- g_object_unref(pfont->fset);
- sfree(pfont);
-}
-
-static int pangofont_char_width(PangoLayout *layout, struct pangofont *pfont,
- wchar_t uchr, const char *utfchr, int utflen)
-{
- /*
- * Here we check whether a character has the same width as the
- * character cell it'll be drawn in. Because profiling showed that
- * asking Pango for text sizes was a huge bottleneck when we were
- * calling it every time we needed to know this, we instead call
- * it only on characters we don't already know about, and cache
- * the results.
- */
-
- if ((unsigned)uchr >= pfont->nwidthcache) {
- unsigned newsize = ((int)uchr + 0x100) & ~0xFF;
- pfont->widthcache = sresize(pfont->widthcache, newsize, int);
- while (pfont->nwidthcache < newsize)
- pfont->widthcache[pfont->nwidthcache++] = -1;
- }
-
- if (pfont->widthcache[uchr] < 0) {
- PangoRectangle rect;
- pango_layout_set_text(layout, utfchr, utflen);
- pango_layout_get_extents(layout, NULL, &rect);
- pfont->widthcache[uchr] = rect.width;
- }
-
- return pfont->widthcache[uchr];
-}
-
-static bool pangofont_has_glyph(unifont *font, wchar_t glyph)
-{
- /* Pango implements font fallback, so assume it has everything */
- return true;
-}
-
-#ifdef DRAW_TEXT_GDK
-static void pango_gdk_draw_layout(unifont_drawctx *ctx,
- gint x, gint y, PangoLayout *layout)
-{
- gdk_draw_layout(ctx->u.gdk.target, ctx->u.gdk.gc, x, y, layout);
-}
-#endif
-
-#ifdef DRAW_TEXT_CAIRO
-static void pango_cairo_draw_layout(unifont_drawctx *ctx,
- gint x, gint y, PangoLayout *layout)
-{
- cairo_move_to(ctx->u.cairo.cr, x, y);
- pango_cairo_show_layout(ctx->u.cairo.cr, layout);
-}
-#endif
-
-static void pangofont_draw_internal(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string,
- int len, bool wide, bool bold,
- int cellwidth, bool combining)
-{
- struct pangofont *pfont = container_of(font, struct pangofont, u);
- PangoLayout *layout;
- PangoRectangle rect;
- char *utfstring, *utfptr;
- int utflen;
- bool shadowbold = false;
- void (*draw_layout)(unifont_drawctx *ctx,
- gint x, gint y, PangoLayout *layout) = NULL;
-
-#ifdef DRAW_TEXT_GDK
- if (ctx->type == DRAWTYPE_GDK) {
- draw_layout = pango_gdk_draw_layout;
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (ctx->type == DRAWTYPE_CAIRO) {
- draw_layout = pango_cairo_draw_layout;
- }
-#endif
- assert(draw_layout);
-
- if (wide)
- cellwidth *= 2;
-
- y -= pfont->u.ascent;
-
- layout = pango_layout_new(gtk_widget_get_pango_context(pfont->widget));
- pango_layout_set_font_description(layout, pfont->desc);
- if (bold && !pfont->bold) {
- if (pfont->shadowalways)
- shadowbold = true;
- else {
- PangoFontDescription *desc2 =
- pango_font_description_copy_static(pfont->desc);
- pango_font_description_set_weight(desc2, PANGO_WEIGHT_BOLD);
- pango_layout_set_font_description(layout, desc2);
- }
- }
-
- /*
- * Pango always expects UTF-8, so convert the input wide character
- * string to UTF-8.
- */
- utfstring = snewn(len*6+1, char); /* UTF-8 has max 6 bytes/char */
- utflen = wc_to_mb(CS_UTF8, 0, string, len,
- utfstring, len*6+1, ".", NULL);
-
- utfptr = utfstring;
- while (utflen > 0) {
- int clen, n;
- int desired = cellwidth * PANGO_SCALE;
-
- /*
- * We want to display every character from this string in
- * the centre of its own character cell. In the worst case,
- * this requires a separate text-drawing call for each
- * character; but in the common case where the font is
- * properly fixed-width, we can draw many characters in one
- * go which is much faster.
- *
- * This still isn't really ideal. If you look at what
- * happens in the X protocol as a result of all of this, you
- * find - naturally enough - that each call to
- * gdk_draw_layout() generates a separate set of X RENDER
- * operations involving creating a picture, setting a clip
- * rectangle, doing some drawing and undoing the whole lot.
- * In an ideal world, we should _always_ be able to turn the
- * contents of this loop into a single RenderCompositeGlyphs
- * operation which internally specifies inter-character
- * deltas to get the spacing right, which would give us full
- * speed _even_ in the worst case of a non-fixed-width font.
- * However, Pango's architecture and documentation are so
- * unhelpful that I have no idea how if at all to persuade
- * them to do that.
- */
-
- if (combining) {
- /*
- * For a character with combining stuff, we just dump the
- * whole lot in one go, and expect it to take up just one
- * character cell.
- */
- clen = utflen;
- n = 1;
- } else {
- /*
- * Start by extracting a single UTF-8 character from the
- * string.
- */
- clen = 1;
- while (clen < utflen &&
- (unsigned char)utfptr[clen] >= 0x80 &&
- (unsigned char)utfptr[clen] < 0xC0)
- clen++;
- n = 1;
-
- if (is_rtl(string[0]) ||
- pangofont_char_width(layout, pfont, string[n-1],
- utfptr, clen) != desired) {
- /*
- * If this character is a right-to-left one, or has an
- * unusual width, then we must display it on its own.
- */
- } else {
- /*
- * Try to amalgamate a contiguous string of characters
- * with the expected sensible width, for the common case
- * in which we're using a monospaced font and everything
- * works as expected.
- */
- while (clen < utflen) {
- int oldclen = clen;
- clen++; /* skip UTF-8 introducer byte */
- while (clen < utflen &&
- (unsigned char)utfptr[clen] >= 0x80 &&
- (unsigned char)utfptr[clen] < 0xC0)
- clen++;
- n++;
- if (is_rtl(string[n-1]) ||
- pangofont_char_width(layout, pfont,
- string[n-1], utfptr + oldclen,
- clen - oldclen) != desired) {
- clen = oldclen;
- n--;
- break;
- }
- }
- }
- }
-
- pango_layout_set_text(layout, utfptr, clen);
- pango_layout_get_pixel_extents(layout, NULL, &rect);
-
- draw_layout(ctx,
- x + (n*cellwidth - rect.width)/2,
- y + (pfont->u.height - rect.height)/2, layout);
- if (shadowbold)
- draw_layout(ctx,
- x + (n*cellwidth - rect.width)/2 + pfont->shadowoffset,
- y + (pfont->u.height - rect.height)/2, layout);
-
- utflen -= clen;
- utfptr += clen;
- string += n;
- x += n * cellwidth;
- }
-
- sfree(utfstring);
-
- g_object_unref(layout);
-}
-
-static void pangofont_draw_text(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth)
-{
- pangofont_draw_internal(ctx, font, x, y, string, len, wide, bold,
- cellwidth, false);
-}
-
-static void pangofont_draw_combining(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string,
- int len, bool wide, bool bold,
- int cellwidth)
-{
- wchar_t *tmpstring = NULL;
- if (mk_wcwidth(string[0]) == 0) {
- /*
- * If we've been told to draw a sequence of _only_ combining
- * characters, prefix a space so that they have something to
- * combine with.
- */
- tmpstring = snewn(len+1, wchar_t);
- memcpy(tmpstring+1, string, len * sizeof(wchar_t));
- tmpstring[0] = L' ';
- string = tmpstring;
- len++;
- }
- pangofont_draw_internal(ctx, font, x, y, string, len, wide, bold,
- cellwidth, true);
- sfree(tmpstring);
-}
-
-/*
- * Dummy size value to be used when converting a
- * PangoFontDescription of a scalable font to a string for
- * internal use.
- */
-#define PANGO_DUMMY_SIZE 12
-
-static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback,
- void *callback_ctx)
-{
- PangoContext *ctx;
-#ifndef PANGO_PRE_1POINT6
- PangoFontMap *map;
-#endif
- PangoFontFamily **families;
- int i, nfamilies;
-
- ctx = gtk_widget_get_pango_context(widget);
- if (!ctx)
- return;
-
- /*
- * Ask Pango for a list of font families, and iterate through
- * them.
- */
-#ifndef PANGO_PRE_1POINT6
- map = pango_context_get_font_map(ctx);
- if (!map)
- return;
- pango_font_map_list_families(map, &families, &nfamilies);
-#else
- pango_context_list_families(ctx, &families, &nfamilies);
-#endif
- for (i = 0; i < nfamilies; i++) {
- PangoFontFamily *family = families[i];
- const char *familyname;
- int flags;
- PangoFontFace **faces;
- int j, nfaces;
-
- /*
- * Set up our flags for this font family, and get the name
- * string.
- */
- flags = FONTFLAG_CLIENTSIDE;
-#ifndef PANGO_PRE_1POINT4
- /*
- * In very early versions of Pango, we can't tell
- * monospaced fonts from non-monospaced.
- */
- if (!pango_font_family_is_monospace(family))
- flags |= FONTFLAG_NONMONOSPACED;
-#endif
- familyname = pango_font_family_get_name(family);
-
- /*
- * Go through the available font faces in this family.
- */
- pango_font_family_list_faces(family, &faces, &nfaces);
- for (j = 0; j < nfaces; j++) {
- PangoFontFace *face = faces[j];
- PangoFontDescription *desc;
- const char *facename;
- int *sizes;
- int k, nsizes, dummysize;
-
- /*
- * Get the face name string.
- */
- facename = pango_font_face_get_face_name(face);
-
- /*
- * Set up a font description with what we've got so
- * far. We'll fill in the size field manually and then
- * call pango_font_description_to_string() to give the
- * full real name of the specific font.
- */
- desc = pango_font_face_describe(face);
-
- /*
- * See if this font has a list of specific sizes.
- */
-#ifndef PANGO_PRE_1POINT4
- pango_font_face_list_sizes(face, &sizes, &nsizes);
-#else
- /*
- * In early versions of Pango, that call wasn't
- * supported; we just have to assume everything is
- * scalable.
- */
- sizes = NULL;
-#endif
- if (!sizes) {
- /*
- * Write a single entry with a dummy size.
- */
- dummysize = PANGO_DUMMY_SIZE * PANGO_SCALE;
- sizes = &dummysize;
- nsizes = 1;
- }
-
- /*
- * If so, go through them one by one.
- */
- for (k = 0; k < nsizes; k++) {
- char *fullname, *stylekey;
-
- pango_font_description_set_size(desc, sizes[k]);
-
- fullname = pango_font_description_to_string(desc);
-
- /*
- * Construct the sorting key for font styles.
- */
- {
- strbuf *buf = strbuf_new();
-
- int weight = pango_font_description_get_weight(desc);
- /* Weight: normal, then lighter, then bolder */
- if (weight <= PANGO_WEIGHT_NORMAL)
- weight = PANGO_WEIGHT_NORMAL - weight;
- strbuf_catf(buf, "%4d", weight);
-
- strbuf_catf(buf, " %2d",
- pango_font_description_get_style(desc));
-
- int stretch = pango_font_description_get_stretch(desc);
- /* Stretch: closer to normal sorts earlier */
- stretch = 2 * abs(PANGO_STRETCH_NORMAL - stretch) +
- (stretch < PANGO_STRETCH_NORMAL);
- strbuf_catf(buf, " %2d", stretch);
-
- strbuf_catf(buf, " %2d",
- pango_font_description_get_variant(desc));
-
- stylekey = strbuf_to_str(buf);
- }
-
- /*
- * Got everything. Hand off to the callback.
- * (The charset string is NULL, because only
- * server-side X fonts use it.)
- */
- callback(callback_ctx, fullname, familyname, NULL, facename,
- stylekey,
- (sizes == &dummysize ? 0 : PANGO_PIXELS(sizes[k])),
- flags, &pangofont_vtable);
-
- sfree(stylekey);
- g_free(fullname);
- }
- if (sizes != &dummysize)
- g_free(sizes);
-
- pango_font_description_free(desc);
- }
- g_free(faces);
- }
- g_free(families);
-}
-
-static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name,
- int *size, int *flags,
- bool resolve_aliases)
-{
- /*
- * When given a Pango font name to try to make sense of for a
- * font selector, we must normalise it to PANGO_DUMMY_SIZE and
- * extract its original size (in pixels) into the `size' field.
- */
- PangoContext *ctx;
-#ifndef PANGO_PRE_1POINT6
- PangoFontMap *map;
-#endif
- PangoFontDescription *desc;
- PangoFontset *fset;
- PangoFontMetrics *metrics;
- char *newname, *retname;
-
- desc = pango_font_description_from_string(name);
- if (!desc)
- return NULL;
- ctx = gtk_widget_get_pango_context(widget);
- if (!ctx) {
- pango_font_description_free(desc);
- return NULL;
- }
- if (!pangofont_check_desc_makes_sense(ctx, desc)) {
- pango_font_description_free(desc);
- return NULL;
- }
-#ifndef PANGO_PRE_1POINT6
- map = pango_context_get_font_map(ctx);
- if (!map) {
- pango_font_description_free(desc);
- return NULL;
- }
- fset = pango_font_map_load_fontset(map, ctx, desc,
- pango_context_get_language(ctx));
-#else
- fset = pango_context_load_fontset(ctx, desc,
- pango_context_get_language(ctx));
-#endif
- if (!fset) {
- pango_font_description_free(desc);
- return NULL;
- }
- metrics = pango_fontset_get_metrics(fset);
- if (!metrics ||
- pango_font_metrics_get_approximate_digit_width(metrics) == 0) {
- pango_font_description_free(desc);
- g_object_unref(fset);
- return NULL;
- }
-
- *size = PANGO_PIXELS(pango_font_description_get_size(desc));
- *flags = FONTFLAG_CLIENTSIDE;
- pango_font_description_set_size(desc, PANGO_DUMMY_SIZE * PANGO_SCALE);
- newname = pango_font_description_to_string(desc);
- retname = dupstr(newname);
- g_free(newname);
-
- pango_font_metrics_unref(metrics);
- pango_font_description_free(desc);
- g_object_unref(fset);
-
- return retname;
-}
-
-static char *pangofont_scale_fontname(GtkWidget *widget, const char *name,
- int size)
-{
- PangoFontDescription *desc;
- char *newname, *retname;
-
- desc = pango_font_description_from_string(name);
- if (!desc)
- return NULL;
- pango_font_description_set_size(desc, size * PANGO_SCALE);
- newname = pango_font_description_to_string(desc);
- retname = dupstr(newname);
- g_free(newname);
- pango_font_description_free(desc);
-
- return retname;
-}
-
-static char *pangofont_size_increment(unifont *font, int increment)
-{
- struct pangofont *pfont = container_of(font, struct pangofont, u);
- PangoFontDescription *desc;
- int size;
- char *newname, *retname;
-
- desc = pango_font_description_copy_static(pfont->desc);
-
- size = pango_font_description_get_size(desc);
- size += PANGO_SCALE * increment;
-
- if (size <= 0) {
- retname = NULL;
- } else {
- pango_font_description_set_size(desc, size);
- newname = pango_font_description_to_string(desc);
- retname = dupcat(pfont->u.vt->prefix, ":", newname);
- g_free(newname);
- }
-
- pango_font_description_free(desc);
- return retname;
-}
-
-#endif /* GTK_CHECK_VERSION(2,0,0) */
-
-/* ----------------------------------------------------------------------
- * Outermost functions which do the vtable dispatch.
- */
-
-/*
- * Complete list of font-type subclasses. Listed in preference
- * order for unifont_create(). (That is, in the extremely unlikely
- * event that the same font name is valid as both a Pango and an
- * X11 font, it will be interpreted as the former in the absence
- * of an explicit type-disambiguating prefix.)
- *
- * The 'multifont' subclass is omitted here, as discussed above.
- */
-static const struct UnifontVtable *unifont_types[] = {
-#if GTK_CHECK_VERSION(2,0,0)
- &pangofont_vtable,
-#endif
-#ifndef NOT_X_WINDOWS
- &x11font_vtable,
-#endif
-};
-
-/*
- * Function which takes a font name and processes the optional
- * scheme prefix. Returns the tail of the font name suitable for
- * passing to individual font scheme functions, and also provides
- * a subrange of the unifont_types[] array above.
- *
- * The return values `start' and `end' denote a half-open interval
- * in unifont_types[]; that is, the correct way to iterate over
- * them is
- *
- * for (i = start; i < end; i++) {...}
- */
-static const char *unifont_do_prefix(const char *name, int *start, int *end)
-{
- int colonpos = strcspn(name, ":");
- int i;
-
- if (name[colonpos]) {
- /*
- * There's a colon prefix on the font name. Use it to work
- * out which subclass to use.
- */
- for (i = 0; i < lenof(unifont_types); i++) {
- if (strlen(unifont_types[i]->prefix) == colonpos &&
- !strncmp(unifont_types[i]->prefix, name, colonpos)) {
- *start = i;
- *end = i+1;
- return name + colonpos + 1;
- }
- }
- /*
- * None matched, so return an empty scheme list to prevent
- * any scheme from being called at all.
- */
- *start = *end = 0;
- return name + colonpos + 1;
- } else {
- /*
- * No colon prefix, so just use all the subclasses.
- */
- *start = 0;
- *end = lenof(unifont_types);
- return name;
- }
-}
-
-unifont *unifont_create(GtkWidget *widget, const char *name, bool wide,
- bool bold, int shadowoffset, bool shadowalways)
-{
- int i, start, end;
-
- name = unifont_do_prefix(name, &start, &end);
-
- for (i = start; i < end; i++) {
- unifont *ret = unifont_types[i]->create(widget, name, wide, bold,
- shadowoffset, shadowalways);
- if (ret)
- return ret;
- }
- return NULL; /* font not found in any scheme */
-}
-
-void unifont_destroy(unifont *font)
-{
- font->vt->destroy(font);
-}
-
-void unifont_draw_text(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth)
-{
- font->vt->draw_text(ctx, font, x, y, string, len, wide, bold, cellwidth);
-}
-
-void unifont_draw_combining(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth)
-{
- font->vt->draw_combining(ctx, font, x, y, string, len, wide, bold,
- cellwidth);
-}
-
-char *unifont_size_increment(unifont *font, int increment)
-{
- return font->vt->size_increment(font, increment);
-}
-
-/* ----------------------------------------------------------------------
- * Multiple-font wrapper. This is a type of unifont which encapsulates
- * up to two other unifonts, permitting missing glyphs in the main
- * font to be filled in by a fallback font.
- *
- * This is a type of unifont just like the previous two, but it has a
- * separate constructor which is manually called by the client, so it
- * doesn't appear in the list of available font types enumerated by
- * unifont_create. This means it's not used by unifontsel either, so
- * it doesn't need to support any methods except draw_text and
- * destroy.
- */
-
-static void multifont_draw_text(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth);
-static void multifont_draw_combining(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string,
- int len, bool wide, bool bold,
- int cellwidth);
-static void multifont_destroy(unifont *font);
-static char *multifont_size_increment(unifont *font, int increment);
-
-struct multifont {
- unifont *main;
- unifont *fallback;
-
- struct unifont u;
-};
-
-static const UnifontVtable multifont_vtable = {
- .create = NULL, /* creation is done specially */
- .create_fallback = NULL,
- .destroy = multifont_destroy,
- .has_glyph = NULL,
- .draw_text = multifont_draw_text,
- .draw_combining = multifont_draw_combining,
- .enum_fonts = NULL,
- .canonify_fontname = NULL,
- .scale_fontname = NULL,
- .size_increment = multifont_size_increment,
- .prefix = "client",
-};
-
-unifont *multifont_create(GtkWidget *widget, const char *name,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways)
-{
- int i;
- unifont *font, *fallback;
- struct multifont *mfont;
-
- font = unifont_create(widget, name, wide, bold,
- shadowoffset, shadowalways);
- if (!font)
- return NULL;
-
- fallback = NULL;
- if (font->want_fallback) {
- for (i = 0; i < lenof(unifont_types); i++) {
- if (unifont_types[i]->create_fallback) {
- fallback = unifont_types[i]->create_fallback
- (widget, font->height, wide, bold,
- shadowoffset, shadowalways);
- if (fallback)
- break;
- }
- }
- }
-
- /*
- * Construct our multifont. Public members are all copied from the
- * primary font we're wrapping.
- */
- mfont = snew(struct multifont);
- mfont->u.vt = &multifont_vtable;
- mfont->u.width = font->width;
- mfont->u.ascent = font->ascent;
- mfont->u.descent = font->descent;
- mfont->u.height = font->height;
- mfont->u.strikethrough_y = font->strikethrough_y;
- mfont->u.public_charset = font->public_charset;
- mfont->u.want_fallback = false; /* shouldn't be needed, but just in case */
- mfont->u.preferred_drawtype = font->preferred_drawtype;
- mfont->main = font;
- mfont->fallback = fallback;
-
- return &mfont->u;
-}
-
-static void multifont_destroy(unifont *font)
-{
- struct multifont *mfont = container_of(font, struct multifont, u);
- unifont_destroy(mfont->main);
- if (mfont->fallback)
- unifont_destroy(mfont->fallback);
- sfree(mfont);
-}
-
-typedef void (*unifont_draw_func_t)(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string,
- int len, bool wide, bool bold,
- int cellwidth);
-
-static void multifont_draw_main(unifont_drawctx *ctx, unifont *font, int x,
- int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth,
- int cellinc, unifont_draw_func_t draw)
-{
- struct multifont *mfont = container_of(font, struct multifont, u);
- unifont *f;
- bool ok;
- int i;
-
- while (len > 0) {
- /*
- * Find a maximal sequence of characters which are, or are
- * not, supported by our main font.
- */
- ok = mfont->main->vt->has_glyph(mfont->main, string[0]);
- for (i = 1;
- i < len &&
- !mfont->main->vt->has_glyph(mfont->main, string[i]) == !ok;
- i++);
-
- /*
- * Now display it.
- */
- f = ok ? mfont->main : mfont->fallback;
- if (f)
- draw(ctx, f, x, y, string, i, wide, bold, cellwidth);
- string += i;
- len -= i;
- x += i * cellinc;
- }
-}
-
-static void multifont_draw_text(unifont_drawctx *ctx, unifont *font, int x,
- int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth)
-{
- multifont_draw_main(ctx, font, x, y, string, len, wide, bold,
- cellwidth, cellwidth, unifont_draw_text);
-}
-
-static void multifont_draw_combining(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string,
- int len, bool wide, bool bold,
- int cellwidth)
-{
- multifont_draw_main(ctx, font, x, y, string, len, wide, bold,
- cellwidth, 0, unifont_draw_combining);
-}
-
-static char *multifont_size_increment(unifont *font, int increment)
-{
- struct multifont *mfont = container_of(font, struct multifont, u);
- return unifont_size_increment(mfont->main, increment);
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-
-/* ----------------------------------------------------------------------
- * Implementation of a unified font selector. Used on GTK 2 only;
- * for GTK 1 we still use the standard font selector.
- */
-
-typedef struct fontinfo fontinfo;
-
-typedef struct unifontsel_internal {
- GtkListStore *family_model, *style_model, *size_model;
- GtkWidget *family_list, *style_list, *size_entry, *size_list;
- GtkWidget *filter_buttons[4];
- int n_filter_buttons;
- GtkWidget *preview_area;
-#ifndef NO_BACKING_PIXMAPS
- GdkPixmap *preview_pixmap;
-#endif
- int preview_width, preview_height;
- GdkColor preview_fg, preview_bg;
- int filter_flags;
- tree234 *fonts_by_realname, *fonts_by_selorder;
- fontinfo *selected;
- int selsize, intendedsize;
- bool inhibit_response; /* inhibit callbacks when we change GUI controls */
-
- unifontsel u;
-} unifontsel_internal;
-
-/*
- * The structure held in the tree234s. All the string members are
- * part of the same allocated area, so don't need freeing
- * separately.
- */
-struct fontinfo {
- char *realname;
- char *family, *charset, *style, *stylekey;
- int size, flags;
- /*
- * Fallback sorting key, to permit multiple identical entries
- * to exist in the selorder tree.
- */
- int index;
- /*
- * Indices mapping fontinfo structures to indices in the list
- * boxes. sizeindex is irrelevant if the font is scalable
- * (size==0).
- */
- int familyindex, styleindex, sizeindex;
- /*
- * The class of font.
- */
- const struct UnifontVtable *fontclass;
-};
-
-struct fontinfo_realname_find {
- const char *realname;
- int flags;
-};
-
-static int strnullcasecmp(const char *a, const char *b)
-{
- int i;
-
- /*
- * If exactly one of the inputs is NULL, it compares before
- * the other one.
- */
- if ((i = (!b) - (!a)) != 0)
- return i;
-
- /*
- * NULL compares equal.
- */
- if (!a)
- return 0;
-
- /*
- * Otherwise, ordinary strcasecmp.
- */
- return g_ascii_strcasecmp(a, b);
-}
-
-static int fontinfo_realname_compare(void *av, void *bv)
-{
- fontinfo *a = (fontinfo *)av;
- fontinfo *b = (fontinfo *)bv;
- int i;
-
- if ((i = strnullcasecmp(a->realname, b->realname)) != 0)
- return i;
- if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK))
- return ((a->flags & FONTFLAG_SORT_MASK) <
- (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1);
- return 0;
-}
-
-static int fontinfo_realname_find(void *av, void *bv)
-{
- struct fontinfo_realname_find *a = (struct fontinfo_realname_find *)av;
- fontinfo *b = (fontinfo *)bv;
- int i;
-
- if ((i = strnullcasecmp(a->realname, b->realname)) != 0)
- return i;
- if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK))
- return ((a->flags & FONTFLAG_SORT_MASK) <
- (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1);
- return 0;
-}
-
-static int fontinfo_selorder_compare(void *av, void *bv)
-{
- fontinfo *a = (fontinfo *)av;
- fontinfo *b = (fontinfo *)bv;
- int i;
- if ((i = strnullcasecmp(a->family, b->family)) != 0)
- return i;
- /*
- * Font class comes immediately after family, so that fonts
- * from different classes with the same family
- */
- if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK))
- return ((a->flags & FONTFLAG_SORT_MASK) <
- (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1);
- if ((i = strnullcasecmp(a->charset, b->charset)) != 0)
- return i;
- if ((i = strnullcasecmp(a->stylekey, b->stylekey)) != 0)
- return i;
- if ((i = strnullcasecmp(a->style, b->style)) != 0)
- return i;
- if (a->size != b->size)
- return (a->size < b->size ? -1 : +1);
- if (a->index != b->index)
- return (a->index < b->index ? -1 : +1);
- return 0;
-}
-
-static void unifontsel_draw_preview_text(unifontsel_internal *fs);
-
-static void unifontsel_deselect(unifontsel_internal *fs)
-{
- fs->selected = NULL;
- gtk_list_store_clear(fs->style_model);
- gtk_list_store_clear(fs->size_model);
- gtk_widget_set_sensitive(fs->u.ok_button, false);
- gtk_widget_set_sensitive(fs->size_entry, false);
- unifontsel_draw_preview_text(fs);
-}
-
-static void unifontsel_setup_familylist(unifontsel_internal *fs)
-{
- GtkTreeIter iter;
- int i, listindex, minpos = -1, maxpos = -1;
- char *currfamily = NULL;
- int currflags = -1;
- fontinfo *info;
-
- fs->inhibit_response = true;
-
- gtk_list_store_clear(fs->family_model);
- listindex = 0;
-
- /*
- * Search through the font tree for anything matching our
- * current filter criteria. When we find one, add its font
- * name to the list box.
- */
- for (i = 0 ;; i++) {
- info = (fontinfo *)index234(fs->fonts_by_selorder, i);
- /*
- * info may be NULL if we've just run off the end of the
- * tree. We must still do a processing pass in that
- * situation, in case we had an unfinished font record in
- * progress.
- */
- if (info && (info->flags &~ fs->filter_flags)) {
- info->familyindex = -1;
- continue; /* we're filtering out this font */
- }
- if (!info || strnullcasecmp(currfamily, info->family) ||
- currflags != (info->flags & FONTFLAG_SORT_MASK)) {
- /*
- * We've either finished a family, or started a new
- * one, or both.
- */
- if (currfamily) {
- gtk_list_store_append(fs->family_model, &iter);
- gtk_list_store_set(fs->family_model, &iter,
- 0, currfamily, 1, minpos, 2, maxpos+1, -1);
- listindex++;
- }
- if (info) {
- minpos = i;
- currfamily = info->family;
- currflags = info->flags & FONTFLAG_SORT_MASK;
- }
- }
- if (!info)
- break; /* now we're done */
- info->familyindex = listindex;
- maxpos = i;
- }
-
- /*
- * If we've just filtered out the previously selected font,
- * deselect it thoroughly.
- */
- if (fs->selected && fs->selected->familyindex < 0)
- unifontsel_deselect(fs);
-
- fs->inhibit_response = false;
-}
-
-static void unifontsel_setup_stylelist(unifontsel_internal *fs,
- int start, int end)
-{
- GtkTreeIter iter;
- int i, listindex, minpos = -1, maxpos = -1;
- bool started = false;
- char *currcs = NULL, *currstyle = NULL;
- fontinfo *info;
-
- gtk_list_store_clear(fs->style_model);
- listindex = 0;
- started = false;
-
- /*
- * Search through the font tree for anything matching our
- * current filter criteria. When we find one, add its charset
- * and/or style name to the list box.
- */
- for (i = start; i <= end; i++) {
- if (i == end)
- info = NULL;
- else
- info = (fontinfo *)index234(fs->fonts_by_selorder, i);
- /*
- * info may be NULL if we've just run off the end of the
- * relevant data. We must still do a processing pass in
- * that situation, in case we had an unfinished font
- * record in progress.
- */
- if (info && (info->flags &~ fs->filter_flags)) {
- info->styleindex = -1;
- continue; /* we're filtering out this font */
- }
- if (!info || !started || strnullcasecmp(currcs, info->charset) ||
- strnullcasecmp(currstyle, info->style)) {
- /*
- * We've either finished a style/charset, or started a
- * new one, or both.
- */
- started = true;
- if (currstyle) {
- gtk_list_store_append(fs->style_model, &iter);
- gtk_list_store_set(fs->style_model, &iter,
- 0, currstyle, 1, minpos, 2, maxpos+1,
- 3, true, 4, PANGO_WEIGHT_NORMAL, -1);
- listindex++;
- }
- if (info) {
- minpos = i;
- if (info->charset && strnullcasecmp(currcs, info->charset)) {
- gtk_list_store_append(fs->style_model, &iter);
- gtk_list_store_set(fs->style_model, &iter,
- 0, info->charset, 1, -1, 2, -1,
- 3, false, 4, PANGO_WEIGHT_BOLD, -1);
- listindex++;
- }
- currcs = info->charset;
- currstyle = info->style;
- }
- }
- if (!info)
- break; /* now we're done */
- info->styleindex = listindex;
- maxpos = i;
- }
-}
-
-static const int unifontsel_default_sizes[] = { 10, 12, 14, 16, 20, 24, 32 };
-
-static void unifontsel_setup_sizelist(unifontsel_internal *fs,
- int start, int end)
-{
- GtkTreeIter iter;
- int i, listindex;
- char sizetext[40];
- fontinfo *info;
-
- gtk_list_store_clear(fs->size_model);
- listindex = 0;
-
- /*
- * Search through the font tree for anything matching our
- * current filter criteria. When we find one, add its font
- * name to the list box.
- */
- for (i = start; i < end; i++) {
- info = (fontinfo *)index234(fs->fonts_by_selorder, i);
- if (!info) {
- /* _shouldn't_ happen unless font list is completely funted */
- break;
- }
- if (info->flags &~ fs->filter_flags) {
- info->sizeindex = -1;
- continue; /* we're filtering out this font */
- }
- if (info->size) {
- sprintf(sizetext, "%d", info->size);
- info->sizeindex = listindex;
- gtk_list_store_append(fs->size_model, &iter);
- gtk_list_store_set(fs->size_model, &iter,
- 0, sizetext, 1, i, 2, info->size, -1);
- listindex++;
- } else {
- int j;
-
- assert(i == start);
- assert(i+1 == end);
-
- for (j = 0; j < lenof(unifontsel_default_sizes); j++) {
- sprintf(sizetext, "%d", unifontsel_default_sizes[j]);
- gtk_list_store_append(fs->size_model, &iter);
- gtk_list_store_set(fs->size_model, &iter, 0, sizetext, 1, i,
- 2, unifontsel_default_sizes[j], -1);
- listindex++;
- }
- }
- }
-}
-
-static void unifontsel_set_filter_buttons(unifontsel_internal *fs)
-{
- int i;
-
- for (i = 0; i < fs->n_filter_buttons; i++) {
- int flagbit = GPOINTER_TO_INT(g_object_get_data
- (G_OBJECT(fs->filter_buttons[i]),
- "user-data"));
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fs->filter_buttons[i]),
- !!(fs->filter_flags & flagbit));
- }
-}
-
-static void unifontsel_draw_preview_text_inner(unifont_drawctx *dctx,
- unifontsel_internal *fs)
-{
- unifont *font;
- char *sizename = NULL;
- fontinfo *info = fs->selected;
-
- if (info) {
- sizename = info->fontclass->scale_fontname
- (GTK_WIDGET(fs->u.window), info->realname, fs->selsize);
- font = info->fontclass->create(GTK_WIDGET(fs->u.window),
- sizename ? sizename : info->realname,
- false, false, 0, 0);
- } else
- font = NULL;
-
-#ifdef DRAW_TEXT_GDK
- if (dctx->type == DRAWTYPE_GDK) {
- gdk_gc_set_foreground(dctx->u.gdk.gc, &fs->preview_bg);
- gdk_draw_rectangle(dctx->u.gdk.target, dctx->u.gdk.gc, 1, 0, 0,
- fs->preview_width, fs->preview_height);
- gdk_gc_set_foreground(dctx->u.gdk.gc, &fs->preview_fg);
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (dctx->type == DRAWTYPE_CAIRO) {
- cairo_set_source_rgb(dctx->u.cairo.cr,
- fs->preview_bg.red / 65535.0,
- fs->preview_bg.green / 65535.0,
- fs->preview_bg.blue / 65535.0);
- cairo_paint(dctx->u.cairo.cr);
- cairo_set_source_rgb(dctx->u.cairo.cr,
- fs->preview_fg.red / 65535.0,
- fs->preview_fg.green / 65535.0,
- fs->preview_fg.blue / 65535.0);
- }
-#endif
-
- if (font) {
- /*
- * The pangram used here is rather carefully
- * constructed: it contains a sequence of very narrow
- * letters (`jil') and a pair of adjacent very wide
- * letters (`wm').
- *
- * If the user selects a proportional font, it will be
- * coerced into fixed-width character cells when used
- * in the actual terminal window. We therefore display
- * it the same way in the preview pane, so as to show
- * it the way it will actually be displayed - and we
- * deliberately pick a pangram which will show the
- * resulting miskerning at its worst.
- *
- * We aren't trying to sell people these fonts; we're
- * trying to let them make an informed choice. Better
- * that they find out the problems with using
- * proportional fonts in terminal windows here than
- * that they go to the effort of selecting their font
- * and _then_ realise it was a mistake.
- */
- info->fontclass->draw_text(dctx, font,
- 0, font->ascent,
- L"bankrupt jilted showmen quiz convex fogey",
- 41, false, false, font->width);
- info->fontclass->draw_text(dctx, font,
- 0, font->ascent + font->height,
- L"BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY",
- 41, false, false, font->width);
- /*
- * The ordering of punctuation here is also selected
- * with some specific aims in mind. I put ` and '
- * together because some software (and people) still
- * use them as matched quotes no matter what Unicode
- * might say on the matter, so people can quickly
- * check whether they look silly in a candidate font.
- * The sequence #_@ is there to let people judge the
- * suitability of the underscore as an effectively
- * alphabetic character (since that's how it's often
- * used in practice, at least by programmers).
- */
- info->fontclass->draw_text(dctx, font,
- 0, font->ascent + font->height * 2,
- L"0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$",
- 42, false, false, font->width);
-
- info->fontclass->destroy(font);
- }
-
- sfree(sizename);
-}
-
-static void unifontsel_draw_preview_text(unifontsel_internal *fs)
-{
- unifont_drawctx dctx;
- GdkWindow *target;
-
-#ifndef NO_BACKING_PIXMAPS
- target = fs->preview_pixmap;
-#else
- target = gtk_widget_get_window(fs->preview_area);
-#endif
- if (!target) /* we may be called when we haven't created everything yet */
- return;
-
- dctx.type = DRAWTYPE_DEFAULT;
-#ifdef DRAW_TEXT_GDK
- if (dctx.type == DRAWTYPE_GDK) {
- dctx.u.gdk.target = target;
- dctx.u.gdk.gc = gdk_gc_new(target);
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (dctx.type == DRAWTYPE_CAIRO) {
-#if GTK_CHECK_VERSION(3,22,0)
- cairo_region_t *region;
-#endif
-
- dctx.u.cairo.widget = GTK_WIDGET(fs->preview_area);
-
-#if GTK_CHECK_VERSION(3,22,0)
- dctx.u.cairo.gdkwin = gtk_widget_get_window(dctx.u.cairo.widget);
- region = gdk_window_get_clip_region(dctx.u.cairo.gdkwin);
- dctx.u.cairo.drawctx = gdk_window_begin_draw_frame(
- dctx.u.cairo.gdkwin, region);
- dctx.u.cairo.cr = gdk_drawing_context_get_cairo_context(
- dctx.u.cairo.drawctx);
- cairo_region_destroy(region);
-#else
- dctx.u.cairo.cr = gdk_cairo_create(target);
-#endif
- }
-#endif
-
- unifontsel_draw_preview_text_inner(&dctx, fs);
-
-#ifdef DRAW_TEXT_GDK
- if (dctx.type == DRAWTYPE_GDK) {
- gdk_gc_unref(dctx.u.gdk.gc);
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (dctx.type == DRAWTYPE_CAIRO) {
-#if GTK_CHECK_VERSION(3,22,0)
- gdk_window_end_draw_frame(dctx.u.cairo.gdkwin, dctx.u.cairo.drawctx);
-#else
- cairo_destroy(dctx.u.cairo.cr);
-#endif
- }
-#endif
-
- gdk_window_invalidate_rect(gtk_widget_get_window(fs->preview_area),
- NULL, false);
-}
-
-static void unifontsel_select_font(unifontsel_internal *fs,
- fontinfo *info, int size, int leftlist,
- bool size_is_explicit)
-{
- int index;
- int minval, maxval;
- gboolean success;
- GtkTreePath *treepath;
- GtkTreeIter iter;
-
- fs->inhibit_response = true;
-
- fs->selected = info;
- fs->selsize = size;
- if (size_is_explicit)
- fs->intendedsize = size;
-
- gtk_widget_set_sensitive(fs->u.ok_button, true);
-
- /*
- * Find the index of this fontinfo in the selorder list.
- */
- index = -1;
- findpos234(fs->fonts_by_selorder, info, NULL, &index);
- assert(index >= 0);
-
- /*
- * Adjust the font selector flags and redo the font family
- * list box, if necessary.
- */
- if (leftlist <= 0 &&
- (fs->filter_flags | info->flags) != fs->filter_flags) {
- fs->filter_flags |= info->flags;
- unifontsel_set_filter_buttons(fs);
- unifontsel_setup_familylist(fs);
- }
-
- /*
- * Find the appropriate family name and select it in the list.
- */
- assert(info->familyindex >= 0);
- treepath = gtk_tree_path_new_from_indices(info->familyindex, -1);
- gtk_tree_selection_select_path
- (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->family_list)),
- treepath);
- gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->family_list),
- treepath, NULL, false, 0.0, 0.0);
- success = gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model),
- &iter, treepath);
- assert(success);
- gtk_tree_path_free(treepath);
-
- /*
- * Now set up the font style list.
- */
- gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter,
- 1, &minval, 2, &maxval, -1);
- if (leftlist <= 1)
- unifontsel_setup_stylelist(fs, minval, maxval);
-
- /*
- * Find the appropriate style name and select it in the list.
- */
- if (info->style) {
- assert(info->styleindex >= 0);
- treepath = gtk_tree_path_new_from_indices(info->styleindex, -1);
- gtk_tree_selection_select_path
- (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->style_list)),
- treepath);
- gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->style_list),
- treepath, NULL, false, 0.0, 0.0);
- gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->style_model),
- &iter, treepath);
- gtk_tree_path_free(treepath);
-
- /*
- * And set up the size list.
- */
- gtk_tree_model_get(GTK_TREE_MODEL(fs->style_model), &iter,
- 1, &minval, 2, &maxval, -1);
- if (leftlist <= 2)
- unifontsel_setup_sizelist(fs, minval, maxval);
-
- /*
- * Find the appropriate size, and select it in the list.
- */
- if (info->size) {
- assert(info->sizeindex >= 0);
- treepath = gtk_tree_path_new_from_indices(info->sizeindex, -1);
- gtk_tree_selection_select_path
- (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->size_list)),
- treepath);
- gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list),
- treepath, NULL, false, 0.0, 0.0);
- gtk_tree_path_free(treepath);
- size = info->size;
- } else {
- int j;
- for (j = 0; j < lenof(unifontsel_default_sizes); j++)
- if (unifontsel_default_sizes[j] == size) {
- treepath = gtk_tree_path_new_from_indices(j, -1);
- gtk_tree_view_set_cursor(GTK_TREE_VIEW(fs->size_list),
- treepath, NULL, false);
- gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list),
- treepath, NULL, false, 0.0,
- 0.0);
- gtk_tree_path_free(treepath);
- }
- }
-
- /*
- * And set up the font size text entry box.
- */
- {
- char sizetext[40];
- sprintf(sizetext, "%d", size);
- gtk_entry_set_text(GTK_ENTRY(fs->size_entry), sizetext);
- }
- } else {
- if (leftlist <= 2)
- unifontsel_setup_sizelist(fs, 0, 0);
- gtk_entry_set_text(GTK_ENTRY(fs->size_entry), "");
- }
-
- /*
- * Grey out the font size edit box if we're not using a
- * scalable font.
- */
- gtk_editable_set_editable(GTK_EDITABLE(fs->size_entry),
- fs->selected->size == 0);
- gtk_widget_set_sensitive(fs->size_entry, fs->selected->size == 0);
-
- unifontsel_draw_preview_text(fs);
-
- fs->inhibit_response = false;
-}
-
-static void unifontsel_button_toggled(GtkToggleButton *tb, gpointer data)
-{
- unifontsel_internal *fs = (unifontsel_internal *)data;
- bool newstate = gtk_toggle_button_get_active(tb);
- int newflags;
- int flagbit = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(tb),
- "user-data"));
-
- if (newstate)
- newflags = fs->filter_flags | flagbit;
- else
- newflags = fs->filter_flags & ~flagbit;
-
- if (fs->filter_flags != newflags) {
- fs->filter_flags = newflags;
- unifontsel_setup_familylist(fs);
- }
-}
-
-static void unifontsel_add_entry(void *ctx, const char *realfontname,
- const char *family, const char *charset,
- const char *style, const char *stylekey,
- int size, int flags,
- const struct UnifontVtable *fontclass)
-{
- unifontsel_internal *fs = (unifontsel_internal *)ctx;
- fontinfo *info;
- int totalsize;
- char *p;
-
- totalsize = sizeof(fontinfo) + strlen(realfontname) +
- (family ? strlen(family) : 0) + (charset ? strlen(charset) : 0) +
- (style ? strlen(style) : 0) + (stylekey ? strlen(stylekey) : 0) + 10;
- info = (fontinfo *)smalloc(totalsize);
- info->fontclass = fontclass;
- p = (char *)info + sizeof(fontinfo);
- info->realname = p;
- strcpy(p, realfontname);
- p += 1+strlen(p);
- if (family) {
- info->family = p;
- strcpy(p, family);
- p += 1+strlen(p);
- } else
- info->family = NULL;
- if (charset) {
- info->charset = p;
- strcpy(p, charset);
- p += 1+strlen(p);
- } else
- info->charset = NULL;
- if (style) {
- info->style = p;
- strcpy(p, style);
- p += 1+strlen(p);
- } else
- info->style = NULL;
- if (stylekey) {
- info->stylekey = p;
- strcpy(p, stylekey);
- p += 1+strlen(p);
- } else
- info->stylekey = NULL;
- assert(p - (char *)info <= totalsize);
- info->size = size;
- info->flags = flags;
- info->index = count234(fs->fonts_by_selorder);
-
- /*
- * It's just conceivable that a misbehaving font enumerator
- * might tell us about the same font real name more than once,
- * in which case we should silently drop the new one.
- */
- if (add234(fs->fonts_by_realname, info) != info) {
- sfree(info);
- return;
- }
- /*
- * However, we should never get a duplicate key in the
- * selorder tree, because the index field carefully
- * disambiguates otherwise identical records.
- */
- add234(fs->fonts_by_selorder, info);
-}
-
-static fontinfo *update_for_intended_size(unifontsel_internal *fs,
- fontinfo *info)
-{
- fontinfo info2, *below, *above;
- int pos;
-
- /*
- * Copy the info structure. This doesn't copy its dynamic
- * string fields, but that's unimportant because all we're
- * going to do is to adjust the size field and use it in one
- * tree search.
- */
- info2 = *info;
- info2.size = fs->intendedsize;
-
- /*
- * Search in the tree to find the fontinfo structure which
- * best approximates the size the user last requested.
- */
- below = findrelpos234(fs->fonts_by_selorder, &info2, NULL,
- REL234_LE, &pos);
- if (!below)
- pos = -1;
- above = index234(fs->fonts_by_selorder, pos+1);
-
- /*
- * See if we've found it exactly, which is an easy special
- * case. If we have, it'll be in `below' and not `above',
- * because we did a REL234_LE rather than REL234_LT search.
- */
- if (below && !fontinfo_selorder_compare(&info2, below))
- return below;
-
- /*
- * Now we've either found two suitable fonts, one smaller and
- * one larger, or we're at one or other extreme end of the
- * scale. Find out which, by NULLing out either of below and
- * above if it differs from this one in any respect but size
- * (and the disambiguating index field). Bear in mind, also,
- * that either one might _already_ be NULL if we're at the
- * extreme ends of the font list.
- */
- if (below) {
- info2.size = below->size;
- info2.index = below->index;
- if (fontinfo_selorder_compare(&info2, below))
- below = NULL;
- }
- if (above) {
- info2.size = above->size;
- info2.index = above->index;
- if (fontinfo_selorder_compare(&info2, above))
- above = NULL;
- }
-
- /*
- * Now return whichever of above and below is non-NULL, if
- * that's unambiguous.
- */
- if (!above)
- return below;
- if (!below)
- return above;
-
- /*
- * And now we really do have to make a choice about whether to
- * round up or down. We'll do it by rounding to nearest,
- * breaking ties by rounding up.
- */
- if (above->size - fs->intendedsize <= fs->intendedsize - below->size)
- return above;
- else
- return below;
-}
-
-static void family_changed(GtkTreeSelection *treeselection, gpointer data)
-{
- unifontsel_internal *fs = (unifontsel_internal *)data;
- GtkTreeModel *treemodel;
- GtkTreeIter treeiter;
- int minval;
- fontinfo *info;
-
- if (fs->inhibit_response) /* we made this change ourselves */
- return;
-
- if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
- return;
-
- gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1);
- info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
- if (!info)
- return; /* _shouldn't_ happen unless font list is completely funted */
- info = update_for_intended_size(fs, info);
- if (!info)
- return; /* similarly shouldn't happen */
- if (!info->size)
- fs->selsize = fs->intendedsize; /* font is scalable */
- unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize,
- 1, false);
-}
-
-static void style_changed(GtkTreeSelection *treeselection, gpointer data)
-{
- unifontsel_internal *fs = (unifontsel_internal *)data;
- GtkTreeModel *treemodel;
- GtkTreeIter treeiter;
- int minval;
- fontinfo *info;
-
- if (fs->inhibit_response) /* we made this change ourselves */
- return;
-
- if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
- return;
-
- gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1);
- if (minval < 0)
- return; /* somehow a charset heading got clicked */
- info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
- if (!info)
- return; /* _shouldn't_ happen unless font list is completely funted */
- info = update_for_intended_size(fs, info);
- if (!info)
- return; /* similarly shouldn't happen */
- if (!info->size)
- fs->selsize = fs->intendedsize; /* font is scalable */
- unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize,
- 2, false);
-}
-
-static void size_changed(GtkTreeSelection *treeselection, gpointer data)
-{
- unifontsel_internal *fs = (unifontsel_internal *)data;
- GtkTreeModel *treemodel;
- GtkTreeIter treeiter;
- int minval, size;
- fontinfo *info;
-
- if (fs->inhibit_response) /* we made this change ourselves */
- return;
-
- if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
- return;
-
- gtk_tree_model_get(treemodel, &treeiter, 1, &minval, 2, &size, -1);
- info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
- if (!info)
- return; /* _shouldn't_ happen unless font list is completely funted */
- unifontsel_select_font(fs, info, info->size ? info->size : size, 3, true);
-}
-
-static void size_entry_changed(GtkEditable *ed, gpointer data)
-{
- unifontsel_internal *fs = (unifontsel_internal *)data;
- const char *text;
- int size;
-
- if (fs->inhibit_response) /* we made this change ourselves */
- return;
-
- text = gtk_entry_get_text(GTK_ENTRY(ed));
- size = atoi(text);
-
- if (size > 0) {
- assert(fs->selected->size == 0);
- unifontsel_select_font(fs, fs->selected, size, 3, true);
- }
-}
-
-static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path,
- GtkTreeViewColumn *column, gpointer data)
-{
- unifontsel_internal *fs = (unifontsel_internal *)data;
- GtkTreeIter iter;
- int minval, newsize;
- fontinfo *info, *newinfo;
- char *newname;
-
- if (fs->inhibit_response) /* we made this change ourselves */
- return;
-
- gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, path);
- gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter, 1,&minval, -1);
- info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
- if (info) {
- int flags;
- struct fontinfo_realname_find f;
-
- newname = info->fontclass->canonify_fontname
- (GTK_WIDGET(fs->u.window), info->realname, &newsize, &flags, true);
-
- f.realname = newname;
- f.flags = flags;
- newinfo = find234(fs->fonts_by_realname, &f, fontinfo_realname_find);
-
- sfree(newname);
- if (!newinfo)
- return; /* font name not in our index */
- if (newinfo == info)
- return; /* didn't change under canonification => not an alias */
- unifontsel_select_font(fs, newinfo,
- newinfo->size ? newinfo->size : newsize,
- 1, true);
- }
-}
-
-#if GTK_CHECK_VERSION(3,0,0)
-static gint unifontsel_draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
-{
- unifontsel_internal *fs = (unifontsel_internal *)data;
- unifont_drawctx dctx;
-
- dctx.type = DRAWTYPE_CAIRO;
- dctx.u.cairo.widget = widget;
- dctx.u.cairo.cr = cr;
- unifontsel_draw_preview_text_inner(&dctx, fs);
-
- return true;
-}
-#else
-static gint unifontsel_expose_area(GtkWidget *widget, GdkEventExpose *event,
- gpointer data)
-{
- unifontsel_internal *fs = (unifontsel_internal *)data;
-
-#ifndef NO_BACKING_PIXMAPS
- if (fs->preview_pixmap) {
- gdk_draw_pixmap(gtk_widget_get_window(widget),
- (gtk_widget_get_style(widget)->fg_gc
- [gtk_widget_get_state(widget)]),
- fs->preview_pixmap,
- event->area.x, event->area.y,
- event->area.x, event->area.y,
- event->area.width, event->area.height);
- }
-#else
- unifontsel_draw_preview_text(fs);
-#endif
-
- return true;
-}
-#endif
-
-static gint unifontsel_configure_area(GtkWidget *widget,
- GdkEventConfigure *event, gpointer data)
-{
-#ifndef NO_BACKING_PIXMAPS
- unifontsel_internal *fs = (unifontsel_internal *)data;
- int ox, oy, nx, ny, x, y;
-
- /*
- * Enlarge the pixmap, but never shrink it.
- */
- ox = fs->preview_width;
- oy = fs->preview_height;
- x = event->width;
- y = event->height;
- if (x > ox || y > oy) {
- if (fs->preview_pixmap)
- gdk_pixmap_unref(fs->preview_pixmap);
-
- nx = (x > ox ? x : ox);
- ny = (y > oy ? y : oy);
- fs->preview_pixmap = gdk_pixmap_new(gtk_widget_get_window(widget),
- nx, ny, -1);
- fs->preview_width = nx;
- fs->preview_height = ny;
-
- unifontsel_draw_preview_text(fs);
- }
-#endif
-
- gdk_window_invalidate_rect(gtk_widget_get_window(widget), NULL, false);
-
- return true;
-}
-
-unifontsel *unifontsel_new(const char *wintitle)
-{
- unifontsel_internal *fs = snew(unifontsel_internal);
- GtkWidget *table, *label, *w, *ww, *scroll;
- GtkListStore *model;
- GtkTreeViewColumn *column;
- int lists_height, preview_height, font_width, style_width, size_width;
- int i;
-
- fs->inhibit_response = false;
- fs->selected = NULL;
-
- {
- int width, height;
-
- /*
- * Invent some magic size constants.
- */
- get_label_text_dimensions("Quite Long Font Name (Foundry)",
- &width, &height);
- font_width = width;
- lists_height = 14 * height;
- preview_height = 5 * height;
-
- get_label_text_dimensions("Italic Extra Condensed", &width, &height);
- style_width = width;
-
- get_label_text_dimensions("48000", &width, &height);
- size_width = width;
- }
-
- /*
- * Create the dialog box and initialise the user-visible
- * fields in the returned structure.
- */
- fs->u.user_data = NULL;
- fs->u.window = GTK_WINDOW(gtk_dialog_new());
- gtk_window_set_title(fs->u.window, wintitle);
- fs->u.cancel_button = gtk_dialog_add_button
- (GTK_DIALOG(fs->u.window), STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL);
- fs->u.ok_button = gtk_dialog_add_button
- (GTK_DIALOG(fs->u.window), STANDARD_OK_LABEL, GTK_RESPONSE_OK);
- gtk_widget_grab_default(fs->u.ok_button);
-
- /*
- * Now set up the internal fields, including in particular all
- * the controls that actually allow the user to select fonts.
- */
-#if GTK_CHECK_VERSION(3,0,0)
- table = gtk_grid_new();
- gtk_grid_set_column_spacing(GTK_GRID(table), 8);
-#else
- table = gtk_table_new(8, 3, false);
- gtk_table_set_col_spacings(GTK_TABLE(table), 8);
-#endif
- gtk_widget_show(table);
-
-#if GTK_CHECK_VERSION(3,0,0)
- /* GtkAlignment has become deprecated and we use the "margin"
- * property */
- w = table;
- g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL);
-#elif GTK_CHECK_VERSION(2,4,0)
- /* GtkAlignment seems to be the simplest way to put padding round things */
- w = gtk_alignment_new(0, 0, 1, 1);
- gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8);
- gtk_container_add(GTK_CONTAINER(w), table);
- gtk_widget_show(w);
-#else
- /* In GTK < 2.4, even that isn't available */
- w = table;
-#endif
-
- gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area
- (GTK_DIALOG(fs->u.window))),
- w, true, true, 0);
-
- label = gtk_label_new_with_mnemonic("_Font:");
- gtk_widget_show(label);
- align_label_left(GTK_LABEL(label));
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1);
- g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
-#endif
-
- /*
- * The Font list box displays only a string, but additionally
- * stores two integers which give the limits within the
- * tree234 of the font entries covered by this list entry.
- */
- model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
- w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
- gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false);
- gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
- gtk_widget_show(w);
- column = gtk_tree_view_column_new_with_attributes
- ("Font", gtk_cell_renderer_text_new(),
- "text", 0, (char *)NULL);
- gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
- gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
- g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
- "changed", G_CALLBACK(family_changed), fs);
- g_signal_connect(G_OBJECT(w), "row-activated",
- G_CALLBACK(alias_resolve), fs);
-
- scroll = gtk_scrolled_window_new(NULL, NULL);
- gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
- GTK_SHADOW_IN);
- gtk_container_add(GTK_CONTAINER(scroll), w);
- gtk_widget_show(scroll);
- gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
- GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
- gtk_widget_set_size_request(scroll, font_width, lists_height);
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), scroll, 0, 1, 1, 2);
- g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL,
- GTK_EXPAND | GTK_FILL, 0, 0);
-#endif
- fs->family_model = model;
- fs->family_list = w;
-
- label = gtk_label_new_with_mnemonic("_Style:");
- gtk_widget_show(label);
- align_label_left(GTK_LABEL(label));
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), label, 1, 0, 1, 1);
- g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
-#endif
-
- /*
- * The Style list box can contain insensitive elements (character
- * set headings for server-side fonts), so we add an extra column
- * to the list store to hold that information. Also, since GTK3 at
- * least doesn't seem to display insensitive elements differently
- * by default, we add a further column to change their style.
- */
- model = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT,
- G_TYPE_BOOLEAN, G_TYPE_INT);
- w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
- gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false);
- gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
- gtk_widget_show(w);
- column = gtk_tree_view_column_new_with_attributes
- ("Style", gtk_cell_renderer_text_new(),
- "text", 0, "sensitive", 3, "weight", 4, (char *)NULL);
- gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
- gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
- g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
- "changed", G_CALLBACK(style_changed), fs);
-
- scroll = gtk_scrolled_window_new(NULL, NULL);
- gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
- GTK_SHADOW_IN);
- gtk_container_add(GTK_CONTAINER(scroll), w);
- gtk_widget_show(scroll);
- gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
- GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
- gtk_widget_set_size_request(scroll, style_width, lists_height);
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), scroll, 1, 1, 1, 2);
- g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL,
- GTK_EXPAND | GTK_FILL, 0, 0);
-#endif
- fs->style_model = model;
- fs->style_list = w;
-
- label = gtk_label_new_with_mnemonic("Si_ze:");
- gtk_widget_show(label);
- align_label_left(GTK_LABEL(label));
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), label, 2, 0, 1, 1);
- g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0);
-#endif
-
- /*
- * The Size label attaches primarily to a text input box so
- * that the user can select a size of their choice. The list
- * of available sizes is secondary.
- */
- fs->size_entry = w = gtk_entry_new();
- gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
- gtk_widget_set_size_request(w, size_width, -1);
- gtk_widget_show(w);
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), w, 2, 1, 1, 1);
- g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), w, 2, 3, 1, 2, GTK_FILL, 0, 0, 0);
-#endif
- g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(size_entry_changed),
- fs);
-
- model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
- w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
- gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false);
- gtk_widget_show(w);
- column = gtk_tree_view_column_new_with_attributes
- ("Size", gtk_cell_renderer_text_new(),
- "text", 0, (char *)NULL);
- gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
- gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
- g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
- "changed", G_CALLBACK(size_changed), fs);
-
- scroll = gtk_scrolled_window_new(NULL, NULL);
- gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
- GTK_SHADOW_IN);
- gtk_container_add(GTK_CONTAINER(scroll), w);
- gtk_widget_show(scroll);
- gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
- GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), scroll, 2, 2, 1, 1);
- g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), scroll, 2, 3, 2, 3, GTK_FILL,
- GTK_EXPAND | GTK_FILL, 0, 0);
-#endif
- fs->size_model = model;
- fs->size_list = w;
-
- /*
- * Preview widget.
- */
- fs->preview_area = gtk_drawing_area_new();
-#ifndef NO_BACKING_PIXMAPS
- fs->preview_pixmap = NULL;
-#endif
- fs->preview_width = 0;
- fs->preview_height = 0;
- fs->preview_fg.pixel = fs->preview_bg.pixel = 0;
- fs->preview_fg.red = fs->preview_fg.green = fs->preview_fg.blue = 0x0000;
- fs->preview_bg.red = fs->preview_bg.green = fs->preview_bg.blue = 0xFFFF;
-#if !GTK_CHECK_VERSION(3,0,0)
- gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_fg,
- false, false);
- gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_bg,
- false, false);
-#endif
-#if GTK_CHECK_VERSION(3,0,0)
- g_signal_connect(G_OBJECT(fs->preview_area), "draw",
- G_CALLBACK(unifontsel_draw_area), fs);
-#else
- g_signal_connect(G_OBJECT(fs->preview_area), "expose_event",
- G_CALLBACK(unifontsel_expose_area), fs);
-#endif
- g_signal_connect(G_OBJECT(fs->preview_area), "configure_event",
- G_CALLBACK(unifontsel_configure_area), fs);
- gtk_widget_set_size_request(fs->preview_area, 1, preview_height);
- gtk_widget_show(fs->preview_area);
- ww = fs->preview_area;
- w = gtk_frame_new(NULL);
- gtk_container_add(GTK_CONTAINER(w), ww);
- gtk_widget_show(w);
-
-#if GTK_CHECK_VERSION(3,0,0)
- /* GtkAlignment has become deprecated and we use the "margin"
- * property */
- g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL);
-#elif GTK_CHECK_VERSION(2,4,0)
- ww = w;
- /* GtkAlignment seems to be the simplest way to put padding round things */
- w = gtk_alignment_new(0, 0, 1, 1);
- gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8);
- gtk_container_add(GTK_CONTAINER(w), ww);
- gtk_widget_show(w);
-#endif
-
- ww = w;
- w = gtk_frame_new("Preview of font");
- gtk_container_add(GTK_CONTAINER(w), ww);
- gtk_widget_show(w);
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), w, 0, 3, 3, 1);
- g_object_set(G_OBJECT(w), "expand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), w, 0, 3, 3, 4,
- GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 8);
-#endif
-
- /*
- * We only provide the checkboxes for client- and server-side
- * fonts if we have the X11 back end available, because that's the
- * only situation in which more than one class of font is
- * available anyway.
- */
- fs->n_filter_buttons = 0;
-#ifndef NOT_X_WINDOWS
- w = gtk_check_button_new_with_label("Show client-side fonts");
- g_object_set_data(G_OBJECT(w), "user-data",
- GINT_TO_POINTER(FONTFLAG_CLIENTSIDE));
- g_signal_connect(G_OBJECT(w), "toggled",
- G_CALLBACK(unifontsel_button_toggled), fs);
- gtk_widget_show(w);
- fs->filter_buttons[fs->n_filter_buttons++] = w;
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), w, 0, 4, 3, 1);
- g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), w, 0, 3, 4, 5, GTK_FILL, 0, 0, 0);
-#endif
- w = gtk_check_button_new_with_label("Show server-side fonts");
- g_object_set_data(G_OBJECT(w), "user-data",
- GINT_TO_POINTER(FONTFLAG_SERVERSIDE));
- g_signal_connect(G_OBJECT(w), "toggled",
- G_CALLBACK(unifontsel_button_toggled), fs);
- gtk_widget_show(w);
- fs->filter_buttons[fs->n_filter_buttons++] = w;
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), w, 0, 5, 3, 1);
- g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), w, 0, 3, 5, 6, GTK_FILL, 0, 0, 0);
-#endif
- w = gtk_check_button_new_with_label("Show server-side font aliases");
- g_object_set_data(G_OBJECT(w), "user-data",
- GINT_TO_POINTER(FONTFLAG_SERVERALIAS));
- g_signal_connect(G_OBJECT(w), "toggled",
- G_CALLBACK(unifontsel_button_toggled), fs);
- gtk_widget_show(w);
- fs->filter_buttons[fs->n_filter_buttons++] = w;
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), w, 0, 6, 3, 1);
- g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), w, 0, 3, 6, 7, GTK_FILL, 0, 0, 0);
-#endif
-#endif /* NOT_X_WINDOWS */
- w = gtk_check_button_new_with_label("Show non-monospaced fonts");
- g_object_set_data(G_OBJECT(w), "user-data",
- GINT_TO_POINTER(FONTFLAG_NONMONOSPACED));
- g_signal_connect(G_OBJECT(w), "toggled",
- G_CALLBACK(unifontsel_button_toggled), fs);
- gtk_widget_show(w);
- fs->filter_buttons[fs->n_filter_buttons++] = w;
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), w, 0, 7, 3, 1);
- g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0);
-#endif
-
- assert(fs->n_filter_buttons <= lenof(fs->filter_buttons));
- fs->filter_flags = FONTFLAG_CLIENTSIDE | FONTFLAG_SERVERSIDE |
- FONTFLAG_SERVERALIAS;
- unifontsel_set_filter_buttons(fs);
-
- /*
- * Go and find all the font names, and set up our master font
- * list.
- */
- fs->fonts_by_realname = newtree234(fontinfo_realname_compare);
- fs->fonts_by_selorder = newtree234(fontinfo_selorder_compare);
- for (i = 0; i < lenof(unifont_types); i++)
- unifont_types[i]->enum_fonts(GTK_WIDGET(fs->u.window),
- unifontsel_add_entry, fs);
-
- /*
- * And set up the initial font names list.
- */
- unifontsel_setup_familylist(fs);
-
- fs->selsize = fs->intendedsize = 13; /* random default */
- gtk_widget_set_sensitive(fs->u.ok_button, false);
-
- return &fs->u;
-}
-
-void unifontsel_destroy(unifontsel *fontsel)
-{
- unifontsel_internal *fs = container_of(fontsel, unifontsel_internal, u);
- fontinfo *info;
-
-#ifndef NO_BACKING_PIXMAPS
- if (fs->preview_pixmap)
- gdk_pixmap_unref(fs->preview_pixmap);
-#endif
-
- freetree234(fs->fonts_by_selorder);
- while ((info = delpos234(fs->fonts_by_realname, 0)) != NULL)
- sfree(info);
- freetree234(fs->fonts_by_realname);
-
- gtk_widget_destroy(GTK_WIDGET(fs->u.window));
- sfree(fs);
-}
-
-void unifontsel_set_name(unifontsel *fontsel, const char *fontname)
-{
- unifontsel_internal *fs = container_of(fontsel, unifontsel_internal, u);
- int i, start, end, size, flags;
- const char *fontname2 = NULL;
- fontinfo *info;
-
- /*
- * Provide a default if given an empty or null font name.
- */
- if (!fontname || !*fontname)
- fontname = DEFAULT_GTK_FONT;
-
- /*
- * Call the canonify_fontname function.
- */
- fontname = unifont_do_prefix(fontname, &start, &end);
- for (i = start; i < end; i++) {
- fontname2 = unifont_types[i]->canonify_fontname
- (GTK_WIDGET(fs->u.window), fontname, &size, &flags, false);
- if (fontname2)
- break;
- }
- if (i == end)
- return; /* font name not recognised */
-
- /*
- * Now look up the canonified font name in our index.
- */
- {
- struct fontinfo_realname_find f;
- f.realname = fontname2;
- f.flags = flags;
- info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find);
- }
-
- /*
- * If we've found the font, and its size field is either
- * correct or zero (the latter indicating a scalable font),
- * then we're done. Otherwise, try looking up the original
- * font name instead.
- */
- if (!info || (info->size != size && info->size != 0)) {
- struct fontinfo_realname_find f;
- f.realname = fontname;
- f.flags = flags;
-
- info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find);
- if (!info || info->size != size)
- return; /* font name not in our index */
- }
-
- /*
- * Now we've got a fontinfo structure and a font size, so we
- * know everything we need to fill in all the fields in the
- * dialog.
- */
- unifontsel_select_font(fs, info, size, 0, true);
-}
-
-char *unifontsel_get_name(unifontsel *fontsel)
-{
- unifontsel_internal *fs = container_of(fontsel, unifontsel_internal, u);
- char *name;
-
- if (!fs->selected)
- return NULL;
-
- if (fs->selected->size == 0) {
- name = fs->selected->fontclass->scale_fontname
- (GTK_WIDGET(fs->u.window), fs->selected->realname, fs->selsize);
- if (name) {
- char *ret = dupcat(fs->selected->fontclass->prefix, ":", name);
- sfree(name);
- return ret;
- }
- }
-
- return dupcat(fs->selected->fontclass->prefix, ":",
- fs->selected->realname);
-}
-
-#endif /* GTK_CHECK_VERSION(2,0,0) */
diff --git a/UNIX/GTKFONT.H b/UNIX/GTKFONT.H
deleted file mode 100644
index e43a748d..00000000
--- a/UNIX/GTKFONT.H
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Header file for gtkfont.c. Has to be separate from unix.h
- * because it depends on GTK data types, hence can't be included
- * from cross-platform code (which doesn't go near GTK).
- */
-
-#ifndef PUTTY_GTKFONT_H
-#define PUTTY_GTKFONT_H
-
-/*
- * We support two entirely different drawing systems: the old
- * GDK1/GDK2 one which works on server-side X drawables, and the
- * new-style Cairo one. GTK1 only supports GDK drawing; GTK3 only
- * supports Cairo; GTK2 supports both, but deprecates GTK, so we only
- * enable it if we aren't trying on purpose to compile without the
- * deprecated functions.
- *
- * Our different font classes may prefer different drawing systems: X
- * server-side fonts are a lot faster to draw with GDK, but for
- * everything else we prefer Cairo, on general grounds of modernness
- * and also in particular because its matrix-based scaling system
- * gives much nicer results for double-width and double-height text
- * when a scalable font is in use.
- */
-#if !GTK_CHECK_VERSION(3,0,0) && !defined GDK_DISABLE_DEPRECATED
-#define DRAW_TEXT_GDK
-#endif
-#if GTK_CHECK_VERSION(2,8,0)
-#define DRAW_TEXT_CAIRO
-#endif
-
-#if GTK_CHECK_VERSION(3,0,0) || defined GDK_DISABLE_DEPRECATED
-/*
- * Where the facility is available, we prefer to render text on to a
- * persistent server-side pixmap, and redraw windows by simply
- * blitting rectangles of that pixmap into them as needed. This is
- * better for performance since we avoid expensive font rendering
- * calls where possible, and it's particularly good over a non-local X
- * connection because the response to an expose event can now be a
- * very simple rectangle-copy operation rather than a lot of fiddly
- * drawing or bitmap transfer.
- *
- * However, GTK is deprecating the use of server-side pixmaps, so we
- * have to disable this mode under some circumstances.
- */
-#define NO_BACKING_PIXMAPS
-#endif
-
-/*
- * Exports from gtkfont.c.
- */
-typedef struct UnifontVtable UnifontVtable; /* contents internal to
- * gtkfont.c */
-typedef struct unifont {
- const struct UnifontVtable *vt;
- /*
- * `Non-static data members' of the `class', accessible to
- * external code.
- */
-
- /*
- * public_charset is the charset used when the user asks for
- * `Use font encoding'.
- */
- int public_charset;
-
- /*
- * Font dimensions needed by clients.
- */
- int width, height, ascent, descent, strikethrough_y;
-
- /*
- * Indicates whether this font is capable of handling all glyphs
- * (Pango fonts can do this because Pango automatically supplies
- * missing glyphs from other fonts), or whether it would like a
- * fallback font to cope with missing glyphs.
- */
- bool want_fallback;
-
- /*
- * Preferred drawing API to use when this class of font is active.
- * (See the enum below, in unifont_drawctx.)
- */
- int preferred_drawtype;
-} unifont;
-
-/* A default drawtype, for the case where no font exists to make the
- * decision with. */
-#ifdef DRAW_TEXT_CAIRO
-#define DRAW_DEFAULT_CAIRO
-#define DRAWTYPE_DEFAULT DRAWTYPE_CAIRO
-#elif defined DRAW_TEXT_GDK
-#define DRAW_DEFAULT_GDK
-#define DRAWTYPE_DEFAULT DRAWTYPE_GDK
-#else
-#error No drawtype available at all
-#endif
-
-/*
- * Drawing context passed in to unifont_draw_text, which contains
- * everything required to know where and how to draw the requested
- * text.
- */
-typedef struct unifont_drawctx {
- enum {
-#ifdef DRAW_TEXT_GDK
- DRAWTYPE_GDK,
-#endif
-#ifdef DRAW_TEXT_CAIRO
- DRAWTYPE_CAIRO,
-#endif
- DRAWTYPE_NTYPES
- } type;
- union {
-#ifdef DRAW_TEXT_GDK
- struct {
- GdkDrawable *target;
- GdkGC *gc;
- } gdk;
-#endif
-#ifdef DRAW_TEXT_CAIRO
- struct {
- /* Need an actual widget, in order to backtrack to its X
- * screen number when creating server-side pixmaps */
- GtkWidget *widget;
- cairo_t *cr;
- cairo_matrix_t origmatrix;
-#if GTK_CHECK_VERSION(3,22,0)
- GdkWindow *gdkwin;
- GdkDrawingContext *drawctx;
-#endif
- } cairo;
-#endif
- } u;
-} unifont_drawctx;
-
-unifont *unifont_create(GtkWidget *widget, const char *name,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways);
-void unifont_destroy(unifont *font);
-void unifont_draw_text(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth);
-/* Same as unifont_draw_text, but expects 'string' to contain one
- * normal char plus combining chars, and overdraws them all in the
- * same character cell. */
-void unifont_draw_combining(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth);
-/* Return a name that will select a bigger/smaller font than this one,
- * or NULL if no such name is available. */
-char *unifont_size_increment(unifont *font, int increment);
-
-/*
- * This function behaves exactly like the low-level unifont_create,
- * except that as well as the requested font it also allocates (if
- * necessary) a fallback font for filling in replacement glyphs.
- *
- * Return value is usable with unifont_destroy and unifont_draw_text
- * as if it were an ordinary unifont.
- */
-unifont *multifont_create(GtkWidget *widget, const char *name,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways);
-
-/*
- * Unified font selector dialog. I can't be bothered to do a
- * proper GTK subclassing today, so this will just be an ordinary
- * data structure with some useful members.
- *
- * (Of course, these aren't the only members; this structure is
- * contained within a bigger one which holds data visible only to
- * the implementation.)
- */
-typedef struct unifontsel {
- void *user_data; /* settable by the user */
- GtkWindow *window;
- GtkWidget *ok_button, *cancel_button;
-} unifontsel;
-
-unifontsel *unifontsel_new(const char *wintitle);
-void unifontsel_destroy(unifontsel *fontsel);
-void unifontsel_set_name(unifontsel *fontsel, const char *fontname);
-char *unifontsel_get_name(unifontsel *fontsel);
-
-#endif /* PUTTY_GTKFONT_H */
diff --git a/UNIX/GTKWIN.C b/UNIX/GTKWIN.C
deleted file mode 100644
index 61e77187..00000000
--- a/UNIX/GTKWIN.C
+++ /dev/null
@@ -1,5431 +0,0 @@
-/*
- * gtkwin.c: the main code that runs a PuTTY terminal emulator and
- * backend in a GTK window.
- */
-
-#define _GNU_SOURCE
-
-#include <string.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-#include <signal.h>
-#include <stdio.h>
-#include <time.h>
-#include <errno.h>
-#include <locale.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <gtk/gtk.h>
-#if !GTK_CHECK_VERSION(3,0,0)
-#include <gdk/gdkkeysyms.h>
-#endif
-
-#if GTK_CHECK_VERSION(2,0,0)
-#include <gtk/gtkimmodule.h>
-#endif
-
-#define MAY_REFER_TO_GTK_IN_HEADERS
-
-#include "putty.h"
-#include "terminal.h"
-#include "gtkcompat.h"
-#include "gtkfont.h"
-#include "gtkmisc.h"
-
-#ifndef NOT_X_WINDOWS
-#include <gdk/gdkx.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-#endif
-
-#include "x11misc.h"
-
-GdkAtom compound_text_atom, utf8_string_atom;
-static GdkAtom clipboard_atom
-#if GTK_CHECK_VERSION(2,0,0) /* GTK1 will have to fill this in at startup */
- = GDK_SELECTION_CLIPBOARD
-#endif
- ;
-
-#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
-/*
- * Because calling gtk_clipboard_set_with_data triggers a call to the
- * clipboard_clear function from the last time, we need to arrange a
- * way to distinguish a real call to clipboard_clear for the _new_
- * instance of the clipboard data from the leftover call for the
- * outgoing one. We do this by setting the user data field in our
- * gtk_clipboard_set_with_data() call, instead of the obvious pointer
- * to 'inst', to one of these.
- */
-struct clipboard_data_instance {
- char *pasteout_data_utf8;
- int pasteout_data_utf8_len;
- struct clipboard_state *state;
- struct clipboard_data_instance *next, *prev;
-};
-#endif
-
-struct clipboard_state {
- GtkFrontend *inst;
- int clipboard;
- GdkAtom atom;
-#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
- GtkClipboard *gtkclipboard;
- struct clipboard_data_instance *current_cdi;
-#else
- char *pasteout_data, *pasteout_data_ctext, *pasteout_data_utf8;
- int pasteout_data_len, pasteout_data_ctext_len, pasteout_data_utf8_len;
-#endif
-};
-
-typedef struct XpmHolder XpmHolder; /* only used for GTK 1 */
-
-struct GtkFrontend {
- GtkWidget *window, *area, *sbar;
- gboolean sbar_visible;
- gboolean drawing_area_got_size, drawing_area_realised;
- gboolean drawing_area_setup_needed;
- GtkBox *hbox;
- GtkAdjustment *sbar_adjust;
- GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2,
- *restartitem;
- GtkWidget *sessionsmenu;
-#ifndef NOT_X_WINDOWS
- Display *disp;
-#endif
-#ifndef NO_BACKING_PIXMAPS
- /*
- * Server-side pixmap which we use to cache the terminal window's
- * contents. When we draw text in the terminal, we draw it to this
- * pixmap first, and then blit from there to the actual window;
- * this way, X expose events can be handled with an absolute
- * minimum of network traffic, by just sending a command to
- * re-blit an appropriate rectangle from this pixmap.
- */
- GdkPixmap *pixmap;
-#endif
-#ifdef DRAW_TEXT_CAIRO
- /*
- * If we're drawing using Cairo, we cache the same image on the
- * client side in a Cairo surface.
- *
- * In GTK2+Cairo, this happens _as well_ as having the server-side
- * pixmap cache above; in GTK3+Cairo, server-side pixmaps are
- * deprecated, so we _just_ have this client-side cache. In the
- * latter case that means we have to transmit a big wodge of
- * bitmap data over the X connection on every expose event; but
- * GTK3 apparently deliberately provides no way to avoid that
- * inefficiency, and at least this way we don't _also_ have to
- * redo any font rendering just because the window was temporarily
- * covered.
- */
- cairo_surface_t *surface;
-#endif
- int backing_w, backing_h;
-#if GTK_CHECK_VERSION(2,0,0)
- GtkIMContext *imc;
-#endif
- unifont *fonts[4]; /* normal, bold, wide, widebold */
- int xpos, ypos, gravity;
- bool gotpos;
- GdkCursor *rawcursor, *textcursor, *blankcursor, *waitcursor, *currcursor;
- GdkColor cols[OSC4_NCOLOURS]; /* indexed by xterm colour indices */
-#if !GTK_CHECK_VERSION(3,0,0)
- GdkColormap *colmap;
-#endif
- bool direct_to_font;
- struct clipboard_state clipstates[N_CLIPBOARDS];
-#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
- /* Remember all clipboard_data_instance structures currently
- * associated with this GtkFrontend, in case they're still around
- * when it gets destroyed */
- struct clipboard_data_instance cdi_headtail;
-#endif
- int clipboard_ctrlshiftins, clipboard_ctrlshiftcv;
- int font_width, font_height;
- int width, height, scale;
- bool ignore_sbar;
- bool mouseptr_visible;
- BusyStatus busy_status;
- int alt_keycode;
- int alt_digits;
- char *wintitle;
- char *icontitle;
- int master_fd, master_func_id;
- Ldisc *ldisc;
- Backend *backend;
- Terminal *term;
- LogContext *logctx;
- bool exited;
- struct unicode_data ucsdata;
- Conf *conf;
- eventlog_stuff *eventlogstuff;
- guint32 input_event_time; /* Timestamp of the most recent input event. */
- GtkWidget *dialogs[DIALOG_SLOT_LIMIT];
-#if GTK_CHECK_VERSION(3,4,0)
- gdouble cumulative_scroll;
-#endif
- /* Cached things out of conf that we refer to a lot */
- int bold_style;
- int window_border;
- int cursor_type;
- int drawtype;
- int meta_mod_mask;
-#ifdef OSX_META_KEY_CONFIG
- int system_mod_mask;
-#endif
- bool send_raw_mouse;
- bool pointer_indicates_raw_mouse;
- unifont_drawctx uctx;
-#if GTK_CHECK_VERSION(2,0,0)
- GdkPixbuf *trust_sigil_pb;
-#else
- GdkPixmap *trust_sigil_pm;
-#endif
- int trust_sigil_w, trust_sigil_h;
-
- Seat seat;
- TermWin termwin;
- LogPolicy logpolicy;
-};
-
-static void cache_conf_values(GtkFrontend *inst)
-{
- inst->bold_style = conf_get_int(inst->conf, CONF_bold_style);
- inst->window_border = conf_get_int(inst->conf, CONF_window_border);
- inst->cursor_type = conf_get_int(inst->conf, CONF_cursor_type);
-#ifdef OSX_META_KEY_CONFIG
- inst->meta_mod_mask = 0;
- if (conf_get_bool(inst->conf, CONF_osx_option_meta))
- inst->meta_mod_mask |= GDK_MOD1_MASK;
- if (conf_get_bool(inst->conf, CONF_osx_command_meta))
- inst->meta_mod_mask |= GDK_MOD2_MASK;
- inst->system_mod_mask = GDK_MOD2_MASK & ~inst->meta_mod_mask;
-#else
- inst->meta_mod_mask = GDK_MOD1_MASK;
-#endif
-}
-
-static void start_backend(GtkFrontend *inst);
-static void exit_callback(void *vinst);
-static void destroy_inst_connection(GtkFrontend *inst);
-static void delete_inst(GtkFrontend *inst);
-
-static void post_fatal_message_box_toplevel(void *vctx)
-{
- GtkFrontend *inst = (GtkFrontend *)vctx;
- gtk_widget_destroy(inst->window);
-}
-
-static void post_fatal_message_box(void *vctx, int result)
-{
- GtkFrontend *inst = (GtkFrontend *)vctx;
- unregister_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL);
- queue_toplevel_callback(post_fatal_message_box_toplevel, inst);
-}
-
-static void common_connfatal_message_box(
- GtkFrontend *inst, const char *msg, post_dialog_fn_t postfn)
-{
- char *title = dupcat(appname, " Fatal Error");
- GtkWidget *dialog = create_message_box(
- inst->window, title, msg,
- string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"),
- false, &buttons_ok, postfn, inst);
- register_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL, dialog);
- sfree(title);
-}
-
-void fatal_message_box(GtkFrontend *inst, const char *msg)
-{
- common_connfatal_message_box(inst, msg, post_fatal_message_box);
-}
-
-static void connection_fatal_callback(void *vctx)
-{
- GtkFrontend *inst = (GtkFrontend *)vctx;
- destroy_inst_connection(inst);
-}
-
-static void post_nonfatal_message_box(void *vctx, int result)
-{
- GtkFrontend *inst = (GtkFrontend *)vctx;
- unregister_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL);
-}
-
-static void gtk_seat_connection_fatal(Seat *seat, const char *msg)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- if (conf_get_int(inst->conf, CONF_close_on_exit) == FORCE_ON) {
- fatal_message_box(inst, msg);
- } else {
- common_connfatal_message_box(inst, msg, post_nonfatal_message_box);
- }
-
- inst->exited = true; /* suppress normal exit handling */
- queue_toplevel_callback(connection_fatal_callback, inst);
-}
-
-/*
- * Default settings that are specific to pterm.
- */
-FontSpec *platform_default_fontspec(const char *name)
-{
- if (!strcmp(name, "Font"))
- return fontspec_new(DEFAULT_GTK_FONT);
- else
- return fontspec_new("");
-}
-
-Filename *platform_default_filename(const char *name)
-{
- if (!strcmp(name, "LogFileName"))
- return filename_from_str("putty.log");
- else
- return filename_from_str("");
-}
-
-char *platform_default_s(const char *name)
-{
- if (!strcmp(name, "SerialLine"))
- return dupstr("/dev/ttyS0");
- return NULL;
-}
-
-bool platform_default_b(const char *name, bool def)
-{
- if (!strcmp(name, "WinNameAlways")) {
- /* X natively supports icon titles, so use 'em by default */
- return false;
- }
- return def;
-}
-
-int platform_default_i(const char *name, int def)
-{
- if (!strcmp(name, "CloseOnExit"))
- return 2; /* maps to FORCE_ON after painful rearrangement :-( */
- return def;
-}
-
-static char *gtk_seat_get_ttymode(Seat *seat, const char *mode)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- return term_get_ttymode(inst->term, mode);
-}
-
-static size_t gtk_seat_output(Seat *seat, bool is_stderr,
- const void *data, size_t len)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- return term_data(inst->term, is_stderr, data, len);
-}
-
-static bool gtk_seat_eof(Seat *seat)
-{
- /* GtkFrontend *inst = container_of(seat, GtkFrontend, seat); */
- return true; /* do respond to incoming EOF with outgoing */
-}
-
-static int gtk_seat_get_userpass_input(Seat *seat, prompts_t *p,
- bufchain *input)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- int ret;
- ret = cmdline_get_passwd_input(p);
- if (ret == -1)
- ret = term_get_userpass_input(inst->term, p, input);
- return ret;
-}
-
-static bool gtk_seat_is_utf8(Seat *seat)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- return inst->ucsdata.line_codepage == CS_UTF8;
-}
-
-static void get_window_pixel_size(GtkFrontend *inst, int *w, int *h)
-{
- /*
- * I assume that when the GTK version of this call is available
- * we should use it. Not sure how it differs from the GDK one,
- * though.
- */
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_window_get_size(GTK_WINDOW(inst->window), w, h);
-#else
- gdk_window_get_size(gtk_widget_get_window(inst->window), w, h);
-#endif
-}
-
-static bool gtk_seat_get_window_pixel_size(Seat *seat, int *w, int *h)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- get_window_pixel_size(inst, w, h);
- return true;
-}
-
-StripCtrlChars *gtk_seat_stripctrl_new(
- Seat *seat, BinarySink *bs_out, SeatInteractionContext sic)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- return stripctrl_new_term(bs_out, false, 0, inst->term);
-}
-
-static void gtk_seat_notify_remote_exit(Seat *seat);
-static void gtk_seat_update_specials_menu(Seat *seat);
-static void gtk_seat_set_busy_status(Seat *seat, BusyStatus status);
-static const char *gtk_seat_get_x_display(Seat *seat);
-#ifndef NOT_X_WINDOWS
-static bool gtk_seat_get_windowid(Seat *seat, long *id);
-#endif
-static bool gtk_seat_set_trust_status(Seat *seat, bool trusted);
-static bool gtk_seat_get_cursor_position(Seat *seat, int *x, int *y);
-
-static const SeatVtable gtk_seat_vt = {
- .output = gtk_seat_output,
- .eof = gtk_seat_eof,
- .get_userpass_input = gtk_seat_get_userpass_input,
- .notify_remote_exit = gtk_seat_notify_remote_exit,
- .connection_fatal = gtk_seat_connection_fatal,
- .update_specials_menu = gtk_seat_update_specials_menu,
- .get_ttymode = gtk_seat_get_ttymode,
- .set_busy_status = gtk_seat_set_busy_status,
- .verify_ssh_host_key = gtk_seat_verify_ssh_host_key,
- .confirm_weak_crypto_primitive = gtk_seat_confirm_weak_crypto_primitive,
- .confirm_weak_cached_hostkey = gtk_seat_confirm_weak_cached_hostkey,
- .is_utf8 = gtk_seat_is_utf8,
- .echoedit_update = nullseat_echoedit_update,
- .get_x_display = gtk_seat_get_x_display,
-#ifdef NOT_X_WINDOWS
- .get_windowid = nullseat_get_windowid,
-#else
- .get_windowid = gtk_seat_get_windowid,
-#endif
- .get_window_pixel_size = gtk_seat_get_window_pixel_size,
- .stripctrl_new = gtk_seat_stripctrl_new,
- .set_trust_status = gtk_seat_set_trust_status,
- .verbose = nullseat_verbose_yes,
- .interactive = nullseat_interactive_yes,
- .get_cursor_position = gtk_seat_get_cursor_position,
-};
-
-static void gtk_eventlog(LogPolicy *lp, const char *string)
-{
- GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy);
- logevent_dlg(inst->eventlogstuff, string);
-}
-
-static int gtk_askappend(LogPolicy *lp, Filename *filename,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy);
- return gtkdlg_askappend(&inst->seat, filename, callback, ctx);
-}
-
-static void gtk_logging_error(LogPolicy *lp, const char *event)
-{
- GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy);
-
- /* Send 'can't open log file' errors to the terminal window.
- * (Marked as stderr, although terminal.c won't care.) */
- seat_stderr_pl(&inst->seat, ptrlen_from_asciz(event));
- seat_stderr_pl(&inst->seat, PTRLEN_LITERAL("\r\n"));
-}
-
-static const LogPolicyVtable gtk_logpolicy_vt = {
- .eventlog = gtk_eventlog,
- .askappend = gtk_askappend,
- .logging_error = gtk_logging_error,
- .verbose = null_lp_verbose_yes,
-};
-
-/*
- * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
- * into a cooked one (SELECT, EXTEND, PASTE).
- *
- * In Unix, this is not configurable; the X button arrangement is
- * rock-solid across all applications, everyone has a three-button
- * mouse or a means of faking it, and there is no need to switch
- * buttons around at all.
- */
-static Mouse_Button translate_button(Mouse_Button button)
-{
- if (button == MBT_LEFT)
- return MBT_SELECT;
- if (button == MBT_MIDDLE)
- return MBT_PASTE;
- if (button == MBT_RIGHT)
- return MBT_EXTEND;
- return 0; /* shouldn't happen */
-}
-
-/*
- * Return the top-level GtkWindow associated with a particular
- * front end instance.
- */
-GtkWidget *gtk_seat_get_window(Seat *seat)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- return inst->window;
-}
-
-/*
- * Set and clear a pointer to a dialog box created as a result of the
- * network code wanting to ask an asynchronous user question (e.g.
- * 'what about this dodgy host key, then?').
- */
-void register_dialog(Seat *seat, enum DialogSlot slot, GtkWidget *dialog)
-{
- GtkFrontend *inst;
- assert(seat->vt == &gtk_seat_vt);
- inst = container_of(seat, GtkFrontend, seat);
- assert(slot < DIALOG_SLOT_LIMIT);
- assert(!inst->dialogs[slot]);
- inst->dialogs[slot] = dialog;
-}
-void unregister_dialog(Seat *seat, enum DialogSlot slot)
-{
- GtkFrontend *inst;
- assert(seat->vt == &gtk_seat_vt);
- inst = container_of(seat, GtkFrontend, seat);
- assert(slot < DIALOG_SLOT_LIMIT);
- assert(inst->dialogs[slot]);
- inst->dialogs[slot] = NULL;
-}
-
-/*
- * Minimise or restore the window in response to a server-side
- * request.
- */
-static void gtkwin_set_minimised(TermWin *tw, bool minimised)
-{
- /*
- * GTK 1.2 doesn't know how to do this.
- */
-#if GTK_CHECK_VERSION(2,0,0)
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- if (minimised)
- gtk_window_iconify(GTK_WINDOW(inst->window));
- else
- gtk_window_deiconify(GTK_WINDOW(inst->window));
-#endif
-}
-
-/*
- * Move the window in response to a server-side request.
- */
-static void gtkwin_move(TermWin *tw, int x, int y)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- /*
- * I assume that when the GTK version of this call is available
- * we should use it. Not sure how it differs from the GDK one,
- * though.
- */
-#if GTK_CHECK_VERSION(2,0,0)
- /* in case we reset this at startup due to a geometry string */
- gtk_window_set_gravity(GTK_WINDOW(inst->window), GDK_GRAVITY_NORTH_EAST);
- gtk_window_move(GTK_WINDOW(inst->window), x, y);
-#else
- gdk_window_move(gtk_widget_get_window(inst->window), x, y);
-#endif
-}
-
-/*
- * Move the window to the top or bottom of the z-order in response
- * to a server-side request.
- */
-static void gtkwin_set_zorder(TermWin *tw, bool top)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- if (top)
- gdk_window_raise(gtk_widget_get_window(inst->window));
- else
- gdk_window_lower(gtk_widget_get_window(inst->window));
-}
-
-/*
- * Refresh the window in response to a server-side request.
- */
-static void gtkwin_refresh(TermWin *tw)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- term_invalidate(inst->term);
-}
-
-/*
- * Maximise or restore the window in response to a server-side
- * request.
- */
-static void gtkwin_set_maximised(TermWin *tw, bool maximised)
-{
- /*
- * GTK 1.2 doesn't know how to do this.
- */
-#if GTK_CHECK_VERSION(2,0,0)
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- if (maximised)
- gtk_window_maximize(GTK_WINDOW(inst->window));
- else
- gtk_window_unmaximize(GTK_WINDOW(inst->window));
-#endif
-}
-
-/*
- * Find out whether a dialog box already exists for this window in a
- * particular DialogSlot. If it does, uniconify it (if we can) and
- * raise it, so that the user realises they've already been asked this
- * question.
- */
-static bool find_and_raise_dialog(GtkFrontend *inst, enum DialogSlot slot)
-{
- GtkWidget *dialog = inst->dialogs[slot];
- if (!dialog)
- return false;
-
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_window_deiconify(GTK_WINDOW(dialog));
-#endif
- gdk_window_raise(gtk_widget_get_window(dialog));
- return true;
-}
-
-static void warn_on_close_callback(void *vctx, int result)
-{
- GtkFrontend *inst = (GtkFrontend *)vctx;
- unregister_dialog(&inst->seat, DIALOG_SLOT_WARN_ON_CLOSE);
- if (result)
- gtk_widget_destroy(inst->window);
-}
-
-/*
- * Handle the 'delete window' event (e.g. user clicking the WM close
- * button). The return value false means the window should close, and
- * true means it shouldn't.
- *
- * (That's counterintuitive, but really, in GTK terms, true means 'I
- * have done everything necessary to handle this event, so the default
- * handler need not do anything', i.e. 'suppress default handler',
- * i.e. 'do not close the window'.)
- */
-gint delete_window(GtkWidget *widget, GdkEvent *event, GtkFrontend *inst)
-{
- if (!inst->exited && conf_get_bool(inst->conf, CONF_warn_on_close)) {
- /*
- * We're not going to exit right now. We must put up a
- * warn-on-close dialog, unless one already exists, in which
- * case we'll just re-emphasise that one.
- */
- if (!find_and_raise_dialog(inst, DIALOG_SLOT_WARN_ON_CLOSE)) {
- char *title = dupcat(appname, " Exit Confirmation");
- char *msg, *additional = NULL;
- if (inst->backend && inst->backend->vt->close_warn_text) {
- additional = inst->backend->vt->close_warn_text(inst->backend);
- }
- msg = dupprintf("Are you sure you want to close this session?%s%s",
- additional ? "\n" : "",
- additional ? additional : "");
- GtkWidget *dialog = create_message_box(
- inst->window, title, msg,
- string_width("Most of the width of the above text"),
- false, &buttons_yn, warn_on_close_callback, inst);
- register_dialog(&inst->seat, DIALOG_SLOT_WARN_ON_CLOSE, dialog);
- sfree(title);
- sfree(msg);
- sfree(additional);
- }
- return true;
- }
- return false;
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-static void window_state_event(GtkWidget *widget, GdkEventWindowState *event,
- gpointer user_data)
-{
- GtkFrontend *inst = (GtkFrontend *)user_data;
- term_notify_minimised(
- inst->term, event->new_window_state & GDK_WINDOW_STATE_ICONIFIED);
-}
-#endif
-
-static void update_mouseptr(GtkFrontend *inst)
-{
- switch (inst->busy_status) {
- case BUSY_NOT:
- if (!inst->mouseptr_visible) {
- gdk_window_set_cursor(gtk_widget_get_window(inst->area),
- inst->blankcursor);
- } else if (inst->pointer_indicates_raw_mouse) {
- gdk_window_set_cursor(gtk_widget_get_window(inst->area),
- inst->rawcursor);
- } else {
- gdk_window_set_cursor(gtk_widget_get_window(inst->area),
- inst->textcursor);
- }
- break;
- case BUSY_WAITING: /* XXX can we do better? */
- case BUSY_CPU:
- /* We always display these cursors. */
- gdk_window_set_cursor(gtk_widget_get_window(inst->area),
- inst->waitcursor);
- break;
- default:
- unreachable("Bad busy_status");
- }
-}
-
-static void show_mouseptr(GtkFrontend *inst, bool show)
-{
- if (!conf_get_bool(inst->conf, CONF_hide_mouseptr))
- show = true;
- inst->mouseptr_visible = show;
- update_mouseptr(inst);
-}
-
-static void draw_backing_rect(GtkFrontend *inst);
-
-static void drawing_area_setup(GtkFrontend *inst, int width, int height)
-{
- int w, h, new_scale;
- bool need_size = false;
-
- /*
- * See if the terminal size has changed.
- */
- w = (width - 2*inst->window_border) / inst->font_width;
- h = (height - 2*inst->window_border) / inst->font_height;
- if (w != inst->width || h != inst->height) {
- /*
- * Update conf.
- */
- inst->width = w;
- inst->height = h;
- conf_set_int(inst->conf, CONF_width, inst->width);
- conf_set_int(inst->conf, CONF_height, inst->height);
- /*
- * We'll need to tell terminal.c about the resize below.
- */
- need_size = true;
- /*
- * And we must refresh the window's backing image.
- */
- inst->drawing_area_setup_needed = true;
- }
-
-#if GTK_CHECK_VERSION(3,10,0)
- new_scale = gtk_widget_get_scale_factor(inst->area);
- if (new_scale != inst->scale)
- inst->drawing_area_setup_needed = true;
-#else
- new_scale = 1;
-#endif
-
- int new_backing_w = w * inst->font_width + 2*inst->window_border;
- int new_backing_h = h * inst->font_height + 2*inst->window_border;
- new_backing_w *= new_scale;
- new_backing_h *= new_scale;
-
- if (inst->backing_w != new_backing_w || inst->backing_h != new_backing_h)
- inst->drawing_area_setup_needed = true;
-
- /*
- * This event might be spurious; some GTK setups have been known
- * to call it when nothing at all has changed. Check if we have
- * any reason to proceed.
- */
- if (!inst->drawing_area_setup_needed)
- return;
-
- inst->drawing_area_setup_needed = false;
- inst->scale = new_scale;
- inst->backing_w = new_backing_w;
- inst->backing_h = new_backing_h;
-
-#ifndef NO_BACKING_PIXMAPS
- if (inst->pixmap) {
- gdk_pixmap_unref(inst->pixmap);
- inst->pixmap = NULL;
- }
-
- inst->pixmap = gdk_pixmap_new(gtk_widget_get_window(inst->area),
- inst->backing_w, inst->backing_h, -1);
-#endif
-
-#ifdef DRAW_TEXT_CAIRO
- if (inst->surface) {
- cairo_surface_destroy(inst->surface);
- inst->surface = NULL;
- }
-
- inst->surface = cairo_image_surface_create(
- CAIRO_FORMAT_ARGB32, inst->backing_w, inst->backing_h);
-#endif
-
- draw_backing_rect(inst);
-
- if (need_size && inst->term) {
- term_size(inst->term, h, w, conf_get_int(inst->conf, CONF_savelines));
- }
-
- if (inst->term)
- term_invalidate(inst->term);
-
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_im_context_set_client_window(
- inst->imc, gtk_widget_get_window(inst->area));
-#endif
-}
-
-static void drawing_area_setup_simple(GtkFrontend *inst)
-{
- /*
- * Wrapper on drawing_area_setup which fetches the width and
- * height of the drawing area. We go directly to the inner version
- * in the case where a new size allocation comes in (just in case
- * GTK hasn't installed it in the normal place yet).
- */
-#if GTK_CHECK_VERSION(2,0,0)
- GdkRectangle alloc;
- gtk_widget_get_allocation(inst->area, &alloc);
-#else
- GtkAllocation alloc = inst->area->allocation;
-#endif
- drawing_area_setup(inst, alloc.width, alloc.height);
-}
-
-static void area_realised(GtkWidget *widget, GtkFrontend *inst)
-{
- inst->drawing_area_realised = true;
- if (inst->drawing_area_realised && inst->drawing_area_got_size &&
- inst->drawing_area_setup_needed)
- drawing_area_setup_simple(inst);
-}
-
-static void area_size_allocate(
- GtkWidget *widget, GdkRectangle *alloc, GtkFrontend *inst)
-{
- inst->drawing_area_got_size = true;
- if (inst->drawing_area_realised && inst->drawing_area_got_size)
- drawing_area_setup(inst, alloc->width, alloc->height);
-}
-
-#if GTK_CHECK_VERSION(3,10,0)
-static void area_check_scale(GtkFrontend *inst)
-{
- if (!inst->drawing_area_setup_needed &&
- inst->scale != gtk_widget_get_scale_factor(inst->area)) {
- drawing_area_setup_simple(inst);
- if (inst->term) {
- term_invalidate(inst->term);
- term_update(inst->term);
- }
- }
-}
-#endif
-
-static gboolean window_configured(
- GtkWidget *widget, GdkEventConfigure *event, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- if (inst->term) {
- term_notify_window_pos(inst->term, event->x, event->y);
- term_notify_window_size_pixels(
- inst->term, event->width, event->height);
- }
- return false;
-}
-
-#if GTK_CHECK_VERSION(3,10,0)
-static gboolean area_configured(
- GtkWidget *widget, GdkEventConfigure *event, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- area_check_scale(inst);
- return false;
-}
-#endif
-
-#ifdef DRAW_TEXT_CAIRO
-static void cairo_setup_draw_ctx(GtkFrontend *inst)
-{
- cairo_get_matrix(inst->uctx.u.cairo.cr,
- &inst->uctx.u.cairo.origmatrix);
- cairo_set_line_width(inst->uctx.u.cairo.cr, 1.0);
- cairo_set_line_cap(inst->uctx.u.cairo.cr, CAIRO_LINE_CAP_SQUARE);
- cairo_set_line_join(inst->uctx.u.cairo.cr, CAIRO_LINE_JOIN_MITER);
- /* This antialiasing setting appears to be ignored for Pango
- * font rendering but honoured for stroking and filling paths;
- * I don't quite understand the logic of that, but I won't
- * complain since it's exactly what I happen to want */
- cairo_set_antialias(inst->uctx.u.cairo.cr, CAIRO_ANTIALIAS_NONE);
-}
-#endif
-
-#if GTK_CHECK_VERSION(3,0,0)
-static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
-
-#if GTK_CHECK_VERSION(3,10,0)
- /*
- * This may be the first we hear of the window scale having
- * changed, in which case we must hastily reconstruct our backing
- * surface before we copy the wrong one into the newly resized
- * real window.
- */
- area_check_scale(inst);
-#endif
-
- /*
- * GTK3 window redraw: we always expect Cairo to be enabled, so
- * that inst->surface exists, and pixmaps to be disabled, so that
- * inst->pixmap does not exist. Hence, we just blit from
- * inst->surface to the window.
- */
- if (inst->surface) {
- GdkRectangle dirtyrect;
- cairo_surface_t *target_surface;
- double orig_sx, orig_sy;
- cairo_matrix_t m;
-
- /*
- * Furtle around in the Cairo setup to force the device scale
- * back to 1, so that when we blit a collection of pixels from
- * our backing surface into the window, they really are
- * _pixels_ and not some confusing antialiased slightly-offset
- * 2x2 rectangle of pixeloids.
- *
- * I have no idea whether GTK expects me not to mess with the
- * device scale in the cairo_surface_t backing its window, so
- * I carefully put it back when I've finished.
- *
- * In some GTK setups, the Cairo context we're given may not
- * have a zero translation offset in its matrix, in which case
- * we have to adjust that to compensate for the change of
- * scale, or else the old translation offset (designed for the
- * old scale) will be multiplied by the new scale instead and
- * put everything in the wrong place.
- */
- target_surface = cairo_get_target(cr);
- cairo_get_matrix(cr, &m);
- cairo_surface_get_device_scale(target_surface, &orig_sx, &orig_sy);
- cairo_surface_set_device_scale(target_surface, 1.0, 1.0);
- cairo_translate(cr, m.x0 * (orig_sx - 1.0), m.y0 * (orig_sy - 1.0));
-
- gdk_cairo_get_clip_rectangle(cr, &dirtyrect);
-
- cairo_set_source_surface(cr, inst->surface, 0, 0);
- cairo_rectangle(cr, dirtyrect.x, dirtyrect.y,
- dirtyrect.width, dirtyrect.height);
- cairo_fill(cr);
-
- cairo_surface_set_device_scale(target_surface, orig_sx, orig_sy);
- }
-
- return true;
-}
-#else
-gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
-
-#ifndef NO_BACKING_PIXMAPS
- /*
- * Draw to the exposed part of the window from the server-side
- * backing pixmap.
- */
- if (inst->pixmap) {
- gdk_draw_pixmap(gtk_widget_get_window(widget),
- (gtk_widget_get_style(widget)->fg_gc
- [gtk_widget_get_state(widget)]),
- inst->pixmap,
- event->area.x, event->area.y,
- event->area.x, event->area.y,
- event->area.width, event->area.height);
- }
-#else
- /*
- * Failing that, draw from the client-side Cairo surface. (We
- * should never be compiled in a context where we have _neither_
- * inst->surface nor inst->pixmap.)
- */
- if (inst->surface) {
- cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(widget));
- cairo_set_source_surface(cr, inst->surface, 0, 0);
- cairo_rectangle(cr, event->area.x, event->area.y,
- event->area.width, event->area.height);
- cairo_fill(cr);
- cairo_destroy(cr);
- }
-#endif
-
- return true;
-}
-#endif
-
-#define KEY_PRESSED(k) \
- (inst->keystate[(k) / 32] & (1 << ((k) % 32)))
-
-#ifdef KEY_EVENT_DIAGNOSTICS
-char *dup_keyval_name(guint keyval)
-{
- const char *name = gdk_keyval_name(keyval);
- if (name)
- return dupstr(name);
- else
- return dupprintf("UNKNOWN[%u]", (unsigned)keyval);
-}
-#endif
-
-static void change_font_size(GtkFrontend *inst, int increment);
-static void key_pressed(GtkFrontend *inst);
-
-/* Subroutine used in key_event */
-static int return_key(GtkFrontend *inst, char *output, bool *special)
-{
- int end;
-
- /* Ugly label so we can come here as a fallback from
- * numeric keypad Enter handling */
- if (inst->term->cr_lf_return) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Return in cr_lf_return mode, translating as 0d 0a\n");
-#endif
- output[1] = '\015';
- output[2] = '\012';
- end = 3;
- } else {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Return special case, translating as 0d + special\n");
-#endif
- output[1] = '\015';
- end = 2;
- *special = true;
- }
-
- return end;
-}
-
-gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- char output[256];
- wchar_t ucsoutput[2];
- int ucsval, start, end, output_charset;
- bool special, use_ucsoutput;
- bool force_format_numeric_keypad = false;
- bool generated_something = false;
- char num_keypad_key = '\0';
- const char *event_string = event->string ? event->string : "";
-
- noise_ultralight(NOISE_SOURCE_KEY, event->keyval);
-
-#ifdef OSX_META_KEY_CONFIG
- if (event->state & inst->system_mod_mask)
- return false; /* let GTK process OS X Command key */
-#endif
-
- /* Remember the timestamp. */
- inst->input_event_time = event->time;
-
- /* By default, nothing is generated. */
- end = start = 0;
- special = use_ucsoutput = false;
- output_charset = CS_ISO8859_1;
-
-#ifdef KEY_EVENT_DIAGNOSTICS
- {
- char *type_string, *state_string, *keyval_string, *string_string;
-
- type_string = (event->type == GDK_KEY_PRESS ? dupstr("PRESS") :
- event->type == GDK_KEY_RELEASE ? dupstr("RELEASE") :
- dupprintf("UNKNOWN[%d]", (int)event->type));
-
- {
- static const struct {
- int mod_bit;
- const char *name;
- } mod_bits[] = {
- {GDK_SHIFT_MASK, "SHIFT"},
- {GDK_LOCK_MASK, "LOCK"},
- {GDK_CONTROL_MASK, "CONTROL"},
- {GDK_MOD1_MASK, "MOD1"},
- {GDK_MOD2_MASK, "MOD2"},
- {GDK_MOD3_MASK, "MOD3"},
- {GDK_MOD4_MASK, "MOD4"},
- {GDK_MOD5_MASK, "MOD5"},
- {GDK_SUPER_MASK, "SUPER"},
- {GDK_HYPER_MASK, "HYPER"},
- {GDK_META_MASK, "META"},
- };
- int i;
- int val = event->state;
-
- state_string = dupstr("");
-
- for (i = 0; i < lenof(mod_bits); i++) {
- if (val & mod_bits[i].mod_bit) {
- char *old = state_string;
- state_string = dupcat(state_string,
- state_string[0] ? "|" : "",
- mod_bits[i].name);
- sfree(old);
-
- val &= ~mod_bits[i].mod_bit;
- }
- }
-
- if (val || !state_string[0]) {
- char *old = state_string;
- state_string = dupprintf("%s%s%d", state_string,
- state_string[0] ? "|" : "", val);
- sfree(old);
- }
- }
-
- keyval_string = dup_keyval_name(event->keyval);
-
- string_string = dupstr("");
- {
- int i;
- for (i = 0; event_string[i]; i++) {
- char *old = string_string;
- string_string = dupprintf("%s%s%02x", string_string,
- string_string[0] ? " " : "",
- (unsigned)event_string[i] & 0xFF);
- sfree(old);
- }
- }
-
- debug("key_event: type=%s keyval=%s state=%s "
- "hardware_keycode=%d is_modifier=%s string=[%s]\n",
- type_string, keyval_string, state_string,
- (int)event->hardware_keycode,
- event->is_modifier ? "true" : "false",
- string_string);
-
- sfree(type_string);
- sfree(state_string);
- sfree(keyval_string);
- sfree(string_string);
- }
-#endif /* KEY_EVENT_DIAGNOSTICS */
-
- /*
- * If Alt is being released after typing an Alt+numberpad
- * sequence, we should generate the code that was typed.
- *
- * Note that we only do this if more than one key was actually
- * pressed - I don't think Alt+NumPad4 should be ^D or that
- * Alt+NumPad3 should be ^C, for example. There's no serious
- * inconvenience in having to type a zero before a single-digit
- * character code.
- */
- if (event->type == GDK_KEY_RELEASE) {
- if ((event->keyval == GDK_KEY_Meta_L ||
- event->keyval == GDK_KEY_Meta_R ||
- event->keyval == GDK_KEY_Alt_L ||
- event->keyval == GDK_KEY_Alt_R) &&
- inst->alt_keycode >= 0 && inst->alt_digits > 1) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - modifier release terminates Alt+numberpad input, "
- "keycode = %d\n", inst->alt_keycode);
-#endif
- /*
- * FIXME: we might usefully try to do something clever here
- * about interpreting the generated key code in a way that's
- * appropriate to the line code page.
- */
- output[0] = inst->alt_keycode;
- end = 1;
- goto done;
- }
-#if GTK_CHECK_VERSION(2,0,0)
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - key release, passing to IM\n");
-#endif
- if (gtk_im_context_filter_keypress(inst->imc, event)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - key release accepted by IM\n");
-#endif
- return true;
- } else {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - key release not accepted by IM\n");
-#endif
- }
-#endif
- }
-
- if (event->type == GDK_KEY_PRESS) {
- /*
- * If Alt has just been pressed, we start potentially
- * accumulating an Alt+numberpad code. We do this by
- * setting alt_keycode to -1 (nothing yet but plausible).
- */
- if ((event->keyval == GDK_KEY_Meta_L ||
- event->keyval == GDK_KEY_Meta_R ||
- event->keyval == GDK_KEY_Alt_L ||
- event->keyval == GDK_KEY_Alt_R)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - modifier press potentially begins Alt+numberpad "
- "input\n");
-#endif
- inst->alt_keycode = -1;
- inst->alt_digits = 0;
- goto done; /* this generates nothing else */
- }
-
- /*
- * If we're seeing a numberpad key press with Meta down,
- * consider adding it to alt_keycode if that's sensible.
- * Anything _else_ with Meta down cancels any possibility
- * of an ALT keycode: we set alt_keycode to -2.
- */
- if ((event->state & inst->meta_mod_mask) && inst->alt_keycode != -2) {
- int digit = -1;
- switch (event->keyval) {
- case GDK_KEY_KP_0: case GDK_KEY_KP_Insert: digit = 0; break;
- case GDK_KEY_KP_1: case GDK_KEY_KP_End: digit = 1; break;
- case GDK_KEY_KP_2: case GDK_KEY_KP_Down: digit = 2; break;
- case GDK_KEY_KP_3: case GDK_KEY_KP_Page_Down: digit = 3; break;
- case GDK_KEY_KP_4: case GDK_KEY_KP_Left: digit = 4; break;
- case GDK_KEY_KP_5: case GDK_KEY_KP_Begin: digit = 5; break;
- case GDK_KEY_KP_6: case GDK_KEY_KP_Right: digit = 6; break;
- case GDK_KEY_KP_7: case GDK_KEY_KP_Home: digit = 7; break;
- case GDK_KEY_KP_8: case GDK_KEY_KP_Up: digit = 8; break;
- case GDK_KEY_KP_9: case GDK_KEY_KP_Page_Up: digit = 9; break;
- }
- if (digit < 0)
- inst->alt_keycode = -2; /* it's invalid */
- else {
-#if defined(DEBUG) && defined(KEY_EVENT_DIAGNOSTICS)
- int old_keycode = inst->alt_keycode;
-#endif
- if (inst->alt_keycode == -1)
- inst->alt_keycode = digit; /* one-digit code */
- else
- inst->alt_keycode = inst->alt_keycode * 10 + digit;
- inst->alt_digits++;
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Alt+numberpad digit %d added to keycode %d"
- " gives %d\n", digit, old_keycode, inst->alt_keycode);
-#endif
- /* Having used this digit, we now do nothing more with it. */
- goto done;
- }
- }
-
- if (event->keyval == GDK_KEY_greater &&
- (event->state & GDK_CONTROL_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl->: increase font size\n");
-#endif
- change_font_size(inst, +1);
- return true;
- }
- if (event->keyval == GDK_KEY_less &&
- (event->state & GDK_CONTROL_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-<: increase font size\n");
-#endif
- change_font_size(inst, -1);
- return true;
- }
-
- /*
- * Shift-PgUp and Shift-PgDn don't even generate keystrokes
- * at all.
- */
- if (event->keyval == GDK_KEY_Page_Up &&
- ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) ==
- (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Shift-PgUp scroll\n");
-#endif
- term_scroll(inst->term, 1, 0);
- return true;
- }
- if (event->keyval == GDK_KEY_Page_Up &&
- (event->state & GDK_SHIFT_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Shift-PgUp scroll\n");
-#endif
- term_scroll(inst->term, 0, -inst->height/2);
- return true;
- }
- if (event->keyval == GDK_KEY_Page_Up &&
- (event->state & GDK_CONTROL_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-PgUp scroll\n");
-#endif
- term_scroll(inst->term, 0, -1);
- return true;
- }
- if (event->keyval == GDK_KEY_Page_Down &&
- ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) ==
- (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-shift-PgDn scroll\n");
-#endif
- term_scroll(inst->term, -1, 0);
- return true;
- }
- if (event->keyval == GDK_KEY_Page_Down &&
- (event->state & GDK_SHIFT_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Shift-PgDn scroll\n");
-#endif
- term_scroll(inst->term, 0, +inst->height/2);
- return true;
- }
- if (event->keyval == GDK_KEY_Page_Down &&
- (event->state & GDK_CONTROL_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-PgDn scroll\n");
-#endif
- term_scroll(inst->term, 0, +1);
- return true;
- }
-
- /*
- * Neither do Shift-Ins or Ctrl-Ins (if enabled).
- */
- if (event->keyval == GDK_KEY_Insert &&
- (event->state & GDK_SHIFT_MASK)) {
- int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftins);
-
- switch (cfgval) {
- case CLIPUI_IMPLICIT:
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Shift-Insert: paste from PRIMARY\n");
-#endif
- term_request_paste(inst->term, CLIP_PRIMARY);
- return true;
- case CLIPUI_EXPLICIT:
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Shift-Insert: paste from CLIPBOARD\n");
-#endif
- term_request_paste(inst->term, CLIP_CLIPBOARD);
- return true;
- case CLIPUI_CUSTOM:
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Shift-Insert: paste from custom clipboard\n");
-#endif
- term_request_paste(inst->term, inst->clipboard_ctrlshiftins);
- return true;
- default:
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Shift-Insert: no paste action\n");
-#endif
- break;
- }
- }
- if (event->keyval == GDK_KEY_Insert &&
- (event->state & GDK_CONTROL_MASK)) {
- static const int clips_clipboard[] = { CLIP_CLIPBOARD };
- int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftins);
-
- switch (cfgval) {
- case CLIPUI_IMPLICIT:
- /* do nothing; re-copy to PRIMARY is not needed */
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Insert: non-copy to PRIMARY\n");
-#endif
- return true;
- case CLIPUI_EXPLICIT:
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Insert: copy to CLIPBOARD\n");
-#endif
- term_request_copy(inst->term,
- clips_clipboard, lenof(clips_clipboard));
- return true;
- case CLIPUI_CUSTOM:
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Insert: copy to custom clipboard\n");
-#endif
- term_request_copy(inst->term,
- &inst->clipboard_ctrlshiftins, 1);
- return true;
- default:
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Insert: no copy action\n");
-#endif
- break;
- }
- }
-
- /*
- * Another pair of copy-paste keys.
- */
- if ((event->state & GDK_SHIFT_MASK) &&
- (event->state & GDK_CONTROL_MASK) &&
- (event->keyval == GDK_KEY_C || event->keyval == GDK_KEY_c ||
- event->keyval == GDK_KEY_V || event->keyval == GDK_KEY_v)) {
- int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftcv);
- bool paste = (event->keyval == GDK_KEY_V ||
- event->keyval == GDK_KEY_v);
-
- switch (cfgval) {
- case CLIPUI_IMPLICIT:
- if (paste) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Shift-V: paste from PRIMARY\n");
-#endif
- term_request_paste(inst->term, CLIP_PRIMARY);
- } else {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Shift-C: non-copy to PRIMARY\n");
-#endif
- }
- return true;
- case CLIPUI_EXPLICIT:
- if (paste) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Shift-V: paste from CLIPBOARD\n");
-#endif
- term_request_paste(inst->term, CLIP_CLIPBOARD);
- } else {
- static const int clips[] = { CLIP_CLIPBOARD };
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Shift-C: copy to CLIPBOARD\n");
-#endif
- term_request_copy(inst->term, clips, lenof(clips));
- }
- return true;
- case CLIPUI_CUSTOM:
- if (paste) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Shift-V: paste from custom clipboard\n");
-#endif
- term_request_paste(inst->term,
- inst->clipboard_ctrlshiftcv);
- } else {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Shift-C: copy to custom clipboard\n");
-#endif
- term_request_copy(inst->term,
- &inst->clipboard_ctrlshiftcv, 1);
- }
- return true;
- }
- }
-
- special = false;
- use_ucsoutput = false;
-
- /* ALT+things gives leading Escape. */
- output[0] = '\033';
-#if !GTK_CHECK_VERSION(2,0,0)
- /*
- * In vanilla X, and hence also GDK 1.2, the string received
- * as part of a keyboard event is assumed to be in
- * ISO-8859-1. (Seems woefully shortsighted in i18n terms,
- * but it's true: see the man page for XLookupString(3) for
- * confirmation.)
- */
- output_charset = CS_ISO8859_1;
- strncpy(output+1, event_string, lenof(output)-1);
-#else /* !GTK_CHECK_VERSION(2,0,0) */
- /*
- * Most things can now be passed to
- * gtk_im_context_filter_keypress without breaking anything
- * below this point. An exception is the numeric keypad if
- * we're in Nethack or application mode: the IM will eat
- * numeric keypad presses if Num Lock is on, but we don't want
- * it to.
- */
- bool numeric = false;
- bool nethack_mode = conf_get_bool(inst->conf, CONF_nethack_keypad);
- bool app_keypad_mode = (inst->term->app_keypad_keys &&
- !conf_get_bool(inst->conf, CONF_no_applic_k));
-
- switch (event->keyval) {
- case GDK_KEY_Num_Lock: num_keypad_key = 'G'; break;
- case GDK_KEY_KP_Divide: num_keypad_key = '/'; break;
- case GDK_KEY_KP_Multiply: num_keypad_key = '*'; break;
- case GDK_KEY_KP_Subtract: num_keypad_key = '-'; break;
- case GDK_KEY_KP_Add: num_keypad_key = '+'; break;
- case GDK_KEY_KP_Enter: num_keypad_key = '\r'; break;
- case GDK_KEY_KP_0: num_keypad_key = '0'; numeric = true; break;
- case GDK_KEY_KP_Insert: num_keypad_key = '0'; break;
- case GDK_KEY_KP_1: num_keypad_key = '1'; numeric = true; break;
- case GDK_KEY_KP_End: num_keypad_key = '1'; break;
- case GDK_KEY_KP_2: num_keypad_key = '2'; numeric = true; break;
- case GDK_KEY_KP_Down: num_keypad_key = '2'; break;
- case GDK_KEY_KP_3: num_keypad_key = '3'; numeric = true; break;
- case GDK_KEY_KP_Page_Down: num_keypad_key = '3'; break;
- case GDK_KEY_KP_4: num_keypad_key = '4'; numeric = true; break;
- case GDK_KEY_KP_Left: num_keypad_key = '4'; break;
- case GDK_KEY_KP_5: num_keypad_key = '5'; numeric = true; break;
- case GDK_KEY_KP_Begin: num_keypad_key = '5'; break;
- case GDK_KEY_KP_6: num_keypad_key = '6'; numeric = true; break;
- case GDK_KEY_KP_Right: num_keypad_key = '6'; break;
- case GDK_KEY_KP_7: num_keypad_key = '7'; numeric = true; break;
- case GDK_KEY_KP_Home: num_keypad_key = '7'; break;
- case GDK_KEY_KP_8: num_keypad_key = '8'; numeric = true; break;
- case GDK_KEY_KP_Up: num_keypad_key = '8'; break;
- case GDK_KEY_KP_9: num_keypad_key = '9'; numeric = true; break;
- case GDK_KEY_KP_Page_Up: num_keypad_key = '9'; break;
- case GDK_KEY_KP_Decimal: num_keypad_key = '.'; numeric = true; break;
- case GDK_KEY_KP_Delete: num_keypad_key = '.'; break;
- }
- if ((app_keypad_mode && num_keypad_key &&
- (numeric || inst->term->funky_type != FUNKY_XTERM)) ||
- (nethack_mode && num_keypad_key >= '1' && num_keypad_key <= '9')) {
- /* In these modes, we override the keypad handling:
- * regardless of Num Lock, the keys are handled by
- * format_numeric_keypad_key below. */
- force_format_numeric_keypad = true;
- } else {
- bool try_filter = true;
-
-#ifdef META_MANUAL_MASK
- if (event->state & META_MANUAL_MASK & inst->meta_mod_mask) {
- /*
- * If this key event had a Meta modifier bit set which
- * is also in META_MANUAL_MASK, that means passing
- * such an event to the GtkIMContext will be unhelpful
- * (it will eat the keystroke and turn it into
- * something not what we wanted).
- */
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Meta modifier requiring manual intervention, "
- "suppressing IM filtering\n");
-#endif
- try_filter = false;
- }
-#endif
-
- if (try_filter) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - general key press, passing to IM\n");
-#endif
- if (gtk_im_context_filter_keypress(inst->imc, event)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - key press accepted by IM\n");
-#endif
- return true;
- } else {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - key press not accepted by IM\n");
-#endif
- }
- }
- }
-
- /*
- * GDK 2.0 arranges to have done some translation for us: in
- * GDK 2.0, event->string is encoded in the current locale.
- *
- * So we use the standard C library function mbstowcs() to
- * convert from the current locale into Unicode; from there
- * we can convert to whatever PuTTY is currently working in.
- * (In fact I convert straight back to UTF-8 from
- * wide-character Unicode, for the sake of simplicity: that
- * way we can still use exactly the same code to manipulate
- * the string, such as prefixing ESC.)
- */
- output_charset = CS_UTF8;
- {
- wchar_t widedata[32];
- const wchar_t *wp;
- int wlen;
- int ulen;
-
- wlen = mb_to_wc(DEFAULT_CODEPAGE, 0,
- event_string, strlen(event_string),
- widedata, lenof(widedata)-1);
-
-#ifdef KEY_EVENT_DIAGNOSTICS
- {
- char *string_string = dupstr("");
- int i;
-
- for (i = 0; i < wlen; i++) {
- char *old = string_string;
- string_string = dupprintf("%s%s%04x", string_string,
- string_string[0] ? " " : "",
- (unsigned)widedata[i]);
- sfree(old);
- }
- debug(" - string translated into Unicode = [%s]\n",
- string_string);
- sfree(string_string);
- }
-#endif
-
- wp = widedata;
- ulen = charset_from_unicode(&wp, &wlen, output+1, lenof(output)-2,
- CS_UTF8, NULL, NULL, 0);
-
-#ifdef KEY_EVENT_DIAGNOSTICS
- {
- char *string_string = dupstr("");
- int i;
-
- for (i = 0; i < ulen; i++) {
- char *old = string_string;
- string_string = dupprintf("%s%s%02x", string_string,
- string_string[0] ? " " : "",
- (unsigned)output[i+1] & 0xFF);
- sfree(old);
- }
- debug(" - string translated into UTF-8 = [%s]\n",
- string_string);
- sfree(string_string);
- }
-#endif
-
- output[1+ulen] = '\0';
- }
-#endif /* !GTK_CHECK_VERSION(2,0,0) */
-
- if (!output[1] &&
- (ucsval = keysym_to_unicode(event->keyval)) >= 0) {
- ucsoutput[0] = '\033';
- ucsoutput[1] = ucsval;
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - keysym_to_unicode gave %04x\n",
- (unsigned)ucsoutput[1]);
-#endif
- use_ucsoutput = true;
- end = 2;
- } else {
- output[lenof(output)-1] = '\0';
- end = strlen(output);
- }
- if (event->state & inst->meta_mod_mask) {
- start = 0;
- if (end == 1) end = 0;
-
-#ifdef META_MANUAL_MASK
- if (event->state & META_MANUAL_MASK) {
- /*
- * Key events which have a META_MANUAL_MASK meta bit
- * set may have a keyval reflecting that, e.g. on OS X
- * the Option key acts as an AltGr-like modifier and
- * causes different Unicode characters to be output.
- *
- * To work around this, we clear the dangerous
- * modifier bit and retranslate from the hardware
- * keycode as if the key had been pressed without that
- * modifier. Then we prefix Esc to *that*.
- */
- guint new_keyval;
- GdkModifierType consumed;
- if (gdk_keymap_translate_keyboard_state
- (gdk_keymap_get_for_display(gdk_display_get_default()),
- event->hardware_keycode, event->state & ~META_MANUAL_MASK,
- 0, &new_keyval, NULL, NULL, &consumed)) {
- ucsoutput[0] = '\033';
- ucsoutput[1] = gdk_keyval_to_unicode(new_keyval);
-#ifdef KEY_EVENT_DIAGNOSTICS
- {
- char *keyval_name = dup_keyval_name(new_keyval);
- debug(" - retranslation for manual Meta: "
- "new keyval = %s, Unicode = %04x\n",
- keyval_name, (unsigned)ucsoutput[1]);
- sfree(keyval_name);
- }
-#endif
- use_ucsoutput = true;
- end = 2;
- }
- }
-#endif
- } else
- start = 1;
-
- /* Control-` is the same as Control-\ (unless gtk has a better idea) */
- if (!output[1] && event->keyval == '`' &&
- (event->state & GDK_CONTROL_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-` special case, translating as 1c\n");
-#endif
- output[1] = '\x1C';
- use_ucsoutput = false;
- end = 2;
- }
-
- /* Some GTK backends (e.g. Quartz) do not change event->string
- * in response to the Control modifier. So we do it ourselves
- * here, if it's not already happened.
- *
- * The translations below are in line with X11 policy as far
- * as I know. */
- if ((event->state & GDK_CONTROL_MASK) && end == 2) {
- int orig = use_ucsoutput ? ucsoutput[1] : output[1];
- int new = orig;
-
- if (new >= '3' && new <= '7') {
- /* ^3,...,^7 map to 0x1B,...,0x1F */
- new += '\x1B' - '3';
- } else if (new == '2' || new == ' ') {
- /* ^2 and ^Space are both ^@, i.e. \0 */
- new = '\0';
- } else if (new == '8') {
- /* ^8 is DEL */
- new = '\x7F';
- } else if (new == '/') {
- /* ^/ is the same as ^_ */
- new = '\x1F';
- } else if (new >= 0x40 && new < 0x7F) {
- /* Everything anywhere near the alphabetics just gets
- * masked. */
- new &= 0x1F;
- }
- /* Anything else, e.g. '0', is unchanged. */
-
- if (orig == new) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - manual Ctrl key handling did nothing\n");
-#endif
- } else {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - manual Ctrl key handling: %02x -> %02x\n",
- (unsigned)orig, (unsigned)new);
-#endif
- output[1] = new;
- use_ucsoutput = false;
- }
- }
-
- /* Control-Break sends a Break special to the backend */
- if (event->keyval == GDK_KEY_Break &&
- (event->state & GDK_CONTROL_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Break special case, sending SS_BRK\n");
-#endif
- if (inst->backend)
- backend_special(inst->backend, SS_BRK, 0);
- return true;
- }
-
- /* We handle Return ourselves, because it needs to be flagged as
- * special to ldisc. */
- if (event->keyval == GDK_KEY_Return) {
- end = return_key(inst, output, &special);
- use_ucsoutput = false;
- }
-
- /* Control-2, Control-Space and Control-@ are NUL */
- if (!output[1] &&
- (event->keyval == ' ' || event->keyval == '2' ||
- event->keyval == '@') &&
- (event->state & (GDK_SHIFT_MASK |
- GDK_CONTROL_MASK)) == GDK_CONTROL_MASK) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-{space,2,@} special case, translating as 00\n");
-#endif
- output[1] = '\0';
- use_ucsoutput = false;
- end = 2;
- }
-
- /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */
- if (!output[1] && event->keyval == ' ' &&
- (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) ==
- (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Shift-space special case, translating as 00a0\n");
-#endif
- output[1] = '\240';
- output_charset = CS_ISO8859_1;
- use_ucsoutput = false;
- end = 2;
- }
-
- /* We don't let GTK tell us what Backspace is! We know better. */
- if (event->keyval == GDK_KEY_BackSpace &&
- !(event->state & GDK_SHIFT_MASK)) {
- output[1] = conf_get_bool(inst->conf, CONF_bksp_is_delete) ?
- '\x7F' : '\x08';
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Backspace, translating as %02x\n",
- (unsigned)output[1]);
-#endif
- use_ucsoutput = false;
- end = 2;
- special = true;
- }
- /* For Shift Backspace, do opposite of what is configured. */
- if (event->keyval == GDK_KEY_BackSpace &&
- (event->state & GDK_SHIFT_MASK)) {
- output[1] = conf_get_bool(inst->conf, CONF_bksp_is_delete) ?
- '\x08' : '\x7F';
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Shift-Backspace, translating as %02x\n",
- (unsigned)output[1]);
-#endif
- use_ucsoutput = false;
- end = 2;
- special = true;
- }
-
- /* Shift-Tab is ESC [ Z */
- if (event->keyval == GDK_KEY_ISO_Left_Tab ||
- (event->keyval == GDK_KEY_Tab &&
- (event->state & GDK_SHIFT_MASK))) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Shift-Tab, translating as ESC [ Z\n");
-#endif
- end = 1 + sprintf(output+1, "\033[Z");
- use_ucsoutput = false;
- }
- /* And normal Tab is Tab, if the keymap hasn't already told us.
- * (Curiously, at least one version of the MacOS 10.5 X server
- * doesn't translate Tab for us. */
- if (event->keyval == GDK_KEY_Tab && end <= 1) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Tab, translating as 09\n");
-#endif
- output[1] = '\t';
- end = 2;
- }
-
- if (num_keypad_key && force_format_numeric_keypad) {
- end = 1 + format_numeric_keypad_key(
- output+1, inst->term, num_keypad_key,
- event->state & GDK_SHIFT_MASK,
- event->state & GDK_CONTROL_MASK);
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - numeric keypad key");
-#endif
- use_ucsoutput = false;
- goto done;
- }
-
- switch (event->keyval) {
- int fkey_number;
- case GDK_KEY_F1: fkey_number = 1; goto numbered_function_key;
- case GDK_KEY_F2: fkey_number = 2; goto numbered_function_key;
- case GDK_KEY_F3: fkey_number = 3; goto numbered_function_key;
- case GDK_KEY_F4: fkey_number = 4; goto numbered_function_key;
- case GDK_KEY_F5: fkey_number = 5; goto numbered_function_key;
- case GDK_KEY_F6: fkey_number = 6; goto numbered_function_key;
- case GDK_KEY_F7: fkey_number = 7; goto numbered_function_key;
- case GDK_KEY_F8: fkey_number = 8; goto numbered_function_key;
- case GDK_KEY_F9: fkey_number = 9; goto numbered_function_key;
- case GDK_KEY_F10: fkey_number = 10; goto numbered_function_key;
- case GDK_KEY_F11: fkey_number = 11; goto numbered_function_key;
- case GDK_KEY_F12: fkey_number = 12; goto numbered_function_key;
- case GDK_KEY_F13: fkey_number = 13; goto numbered_function_key;
- case GDK_KEY_F14: fkey_number = 14; goto numbered_function_key;
- case GDK_KEY_F15: fkey_number = 15; goto numbered_function_key;
- case GDK_KEY_F16: fkey_number = 16; goto numbered_function_key;
- case GDK_KEY_F17: fkey_number = 17; goto numbered_function_key;
- case GDK_KEY_F18: fkey_number = 18; goto numbered_function_key;
- case GDK_KEY_F19: fkey_number = 19; goto numbered_function_key;
- case GDK_KEY_F20: fkey_number = 20; goto numbered_function_key;
- numbered_function_key:
- end = 1 + format_function_key(output+1, inst->term, fkey_number,
- event->state & GDK_SHIFT_MASK,
- event->state & GDK_CONTROL_MASK);
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - function key F%d", fkey_number);
-#endif
- use_ucsoutput = false;
- goto done;
-
- SmallKeypadKey sk_key;
- case GDK_KEY_Home: case GDK_KEY_KP_Home:
- sk_key = SKK_HOME; goto small_keypad_key;
- case GDK_KEY_Insert: case GDK_KEY_KP_Insert:
- sk_key = SKK_INSERT; goto small_keypad_key;
- case GDK_KEY_Delete: case GDK_KEY_KP_Delete:
- sk_key = SKK_DELETE; goto small_keypad_key;
- case GDK_KEY_End: case GDK_KEY_KP_End:
- sk_key = SKK_END; goto small_keypad_key;
- case GDK_KEY_Page_Up: case GDK_KEY_KP_Page_Up:
- sk_key = SKK_PGUP; goto small_keypad_key;
- case GDK_KEY_Page_Down: case GDK_KEY_KP_Page_Down:
- sk_key = SKK_PGDN; goto small_keypad_key;
- small_keypad_key:
- /* These keys don't generate terminal input with Ctrl */
- if (event->state & GDK_CONTROL_MASK)
- break;
-
- end = 1 + format_small_keypad_key(output+1, inst->term, sk_key);
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - small keypad key");
-#endif
- use_ucsoutput = false;
- goto done;
-
- int xkey;
- case GDK_KEY_Up: case GDK_KEY_KP_Up:
- xkey = 'A'; goto arrow_key;
- case GDK_KEY_Down: case GDK_KEY_KP_Down:
- xkey = 'B'; goto arrow_key;
- case GDK_KEY_Right: case GDK_KEY_KP_Right:
- xkey = 'C'; goto arrow_key;
- case GDK_KEY_Left: case GDK_KEY_KP_Left:
- xkey = 'D'; goto arrow_key;
- case GDK_KEY_Begin: case GDK_KEY_KP_Begin:
- xkey = 'G'; goto arrow_key;
- arrow_key:
- end = 1 + format_arrow_key(output+1, inst->term, xkey,
- event->state & GDK_CONTROL_MASK);
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - arrow key");
-#endif
- use_ucsoutput = false;
- goto done;
- }
-
- if (num_keypad_key) {
- end = 1 + format_numeric_keypad_key(
- output+1, inst->term, num_keypad_key,
- event->state & GDK_SHIFT_MASK,
- event->state & GDK_CONTROL_MASK);
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - numeric keypad key");
-#endif
-
- if (end == 1 && num_keypad_key == '\r') {
- /* Keypad Enter, lacking any other translation,
- * becomes the same special Return code as normal
- * Return. */
- end = return_key(inst, output, &special);
- use_ucsoutput = false;
- }
-
- use_ucsoutput = false;
- goto done;
- }
-
- goto done;
- }
-
- done:
-
- if (end-start > 0) {
- if (special) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- char *string_string = dupstr("");
- int i;
-
- for (i = start; i < end; i++) {
- char *old = string_string;
- string_string = dupprintf("%s%s%02x", string_string,
- string_string[0] ? " " : "",
- (unsigned)output[i] & 0xFF);
- sfree(old);
- }
- debug(" - final output, special, generic encoding = [%s]\n",
- string_string);
- sfree(string_string);
-#endif
- /*
- * For special control characters, the character set
- * should never matter.
- */
- output[end] = '\0'; /* NUL-terminate */
- generated_something = true;
- term_keyinput(inst->term, -1, output+start, -2);
- } else if (!inst->direct_to_font) {
- if (!use_ucsoutput) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- char *string_string = dupstr("");
- int i;
-
- for (i = start; i < end; i++) {
- char *old = string_string;
- string_string = dupprintf("%s%s%02x", string_string,
- string_string[0] ? " " : "",
- (unsigned)output[i] & 0xFF);
- sfree(old);
- }
- debug(" - final output in %s = [%s]\n",
- charset_to_localenc(output_charset), string_string);
- sfree(string_string);
-#endif
- generated_something = true;
- term_keyinput(inst->term, output_charset,
- output+start, end-start);
- } else {
-#ifdef KEY_EVENT_DIAGNOSTICS
- char *string_string = dupstr("");
- int i;
-
- for (i = start; i < end; i++) {
- char *old = string_string;
- string_string = dupprintf("%s%s%04x", string_string,
- string_string[0] ? " " : "",
- (unsigned)ucsoutput[i]);
- sfree(old);
- }
- debug(" - final output in Unicode = [%s]\n",
- string_string);
- sfree(string_string);
-#endif
-
- /*
- * We generated our own Unicode key data from the
- * keysym, so use that instead.
- */
- generated_something = true;
- term_keyinputw(inst->term, ucsoutput+start, end-start);
- }
- } else {
- /*
- * In direct-to-font mode, we just send the string
- * exactly as we received it.
- */
-#ifdef KEY_EVENT_DIAGNOSTICS
- char *string_string = dupstr("");
- int i;
-
- for (i = start; i < end; i++) {
- char *old = string_string;
- string_string = dupprintf("%s%s%02x", string_string,
- string_string[0] ? " " : "",
- (unsigned)output[i] & 0xFF);
- sfree(old);
- }
- debug(" - final output in direct-to-font encoding = [%s]\n",
- string_string);
- sfree(string_string);
-#endif
- generated_something = true;
- term_keyinput(inst->term, -1, output+start, end-start);
- }
-
- show_mouseptr(inst, false);
- }
-
- if (generated_something)
- key_pressed(inst);
- return true;
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-void input_method_commit_event(GtkIMContext *imc, gchar *str, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
-
-#ifdef KEY_EVENT_DIAGNOSTICS
- char *string_string = dupstr("");
- int i;
-
- for (i = 0; str[i]; i++) {
- char *old = string_string;
- string_string = dupprintf("%s%s%02x", string_string,
- string_string[0] ? " " : "",
- (unsigned)str[i] & 0xFF);
- sfree(old);
- }
- debug(" - IM commit event in UTF-8 = [%s]\n", string_string);
- sfree(string_string);
-#endif
-
- term_keyinput(inst->term, CS_UTF8, str, strlen(str));
- show_mouseptr(inst, false);
- key_pressed(inst);
-}
-#endif
-
-#define SCROLL_INCREMENT_LINES 5
-
-#if GTK_CHECK_VERSION(3,4,0)
-gboolean scroll_internal(GtkFrontend *inst, gdouble delta, guint state,
- gdouble ex, gdouble ey)
-{
- int x, y;
- bool shift, ctrl, alt, raw_mouse_mode;
-
- show_mouseptr(inst, true);
-
- shift = state & GDK_SHIFT_MASK;
- ctrl = state & GDK_CONTROL_MASK;
- alt = state & inst->meta_mod_mask;
-
- x = (ex - inst->window_border) / inst->font_width;
- y = (ey - inst->window_border) / inst->font_height;
-
- raw_mouse_mode = (inst->send_raw_mouse &&
- !(shift && conf_get_bool(inst->conf,
- CONF_mouse_override)));
-
- inst->cumulative_scroll += delta * SCROLL_INCREMENT_LINES;
-
- if (!raw_mouse_mode) {
- int scroll_lines = (int)inst->cumulative_scroll; /* rounds toward 0 */
- if (scroll_lines) {
- term_scroll(inst->term, 0, scroll_lines);
- inst->cumulative_scroll -= scroll_lines;
- }
- return true;
- } else {
- int scroll_events = (int)(inst->cumulative_scroll /
- SCROLL_INCREMENT_LINES);
- if (scroll_events) {
- int button;
-
- inst->cumulative_scroll -= scroll_events * SCROLL_INCREMENT_LINES;
-
- if (scroll_events > 0) {
- button = MBT_WHEEL_DOWN;
- } else {
- button = MBT_WHEEL_UP;
- scroll_events = -scroll_events;
- }
-
- while (scroll_events-- > 0) {
- term_mouse(inst->term, button, translate_button(button),
- MA_CLICK, x, y, shift, ctrl, alt);
- }
- }
- return true;
- }
-}
-#endif
-
-static gboolean button_internal(GtkFrontend *inst, GdkEventButton *event)
-{
- bool shift, ctrl, alt, raw_mouse_mode;
- int x, y, button, act;
-
- /* Remember the timestamp. */
- inst->input_event_time = event->time;
-
- noise_ultralight(NOISE_SOURCE_MOUSEBUTTON, event->button);
-
- show_mouseptr(inst, true);
-
- shift = event->state & GDK_SHIFT_MASK;
- ctrl = event->state & GDK_CONTROL_MASK;
- alt = event->state & inst->meta_mod_mask;
-
- raw_mouse_mode = (inst->send_raw_mouse &&
- !(shift && conf_get_bool(inst->conf,
- CONF_mouse_override)));
-
- if (!raw_mouse_mode) {
- if (event->button == 4 && event->type == GDK_BUTTON_PRESS) {
- term_scroll(inst->term, 0, -SCROLL_INCREMENT_LINES);
- return true;
- }
- if (event->button == 5 && event->type == GDK_BUTTON_PRESS) {
- term_scroll(inst->term, 0, +SCROLL_INCREMENT_LINES);
- return true;
- }
- }
-
- if (event->button == 3 && ctrl) {
-#if GTK_CHECK_VERSION(3,22,0)
- gtk_menu_popup_at_pointer(GTK_MENU(inst->menu), (GdkEvent *)event);
-#else
- gtk_menu_popup(GTK_MENU(inst->menu), NULL, NULL, NULL, NULL,
- event->button, event->time);
-#endif
- return true;
- }
-
- if (event->button == 1)
- button = MBT_LEFT;
- else if (event->button == 2)
- button = MBT_MIDDLE;
- else if (event->button == 3)
- button = MBT_RIGHT;
- else if (event->button == 4)
- button = MBT_WHEEL_UP;
- else if (event->button == 5)
- button = MBT_WHEEL_DOWN;
- else
- return false; /* don't even know what button! */
-
- switch (event->type) {
- case GDK_BUTTON_PRESS: act = MA_CLICK; break;
- case GDK_BUTTON_RELEASE: act = MA_RELEASE; break;
- case GDK_2BUTTON_PRESS: act = MA_2CLK; break;
- case GDK_3BUTTON_PRESS: act = MA_3CLK; break;
- default: return false; /* don't know this event type */
- }
-
- if (raw_mouse_mode && act != MA_CLICK && act != MA_RELEASE)
- return true; /* we ignore these in raw mouse mode */
-
- x = (event->x - inst->window_border) / inst->font_width;
- y = (event->y - inst->window_border) / inst->font_height;
-
- term_mouse(inst->term, button, translate_button(button), act,
- x, y, shift, ctrl, alt);
-
- return true;
-}
-
-gboolean button_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- return button_internal(inst, event);
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-/*
- * In GTK 2, mouse wheel events have become a new type of event.
- * This handler translates them back into button-4 and button-5
- * presses so that I don't have to change my old code too much :-)
- */
-gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- GdkScrollDirection dir;
-
-#if GTK_CHECK_VERSION(3,4,0)
- gdouble dx, dy;
- if (gdk_event_get_scroll_deltas((GdkEvent *)event, &dx, &dy)) {
- return scroll_internal(inst, dy, event->state, event->x, event->y);
- } else if (!gdk_event_get_scroll_direction((GdkEvent *)event, &dir)) {
- return false;
- }
-#else
- dir = event->direction;
-#endif
-
- guint button;
- GdkEventButton *event_button;
- gboolean ret;
-
- if (dir == GDK_SCROLL_UP)
- button = 4;
- else if (dir == GDK_SCROLL_DOWN)
- button = 5;
- else
- return false;
-
- event_button = (GdkEventButton *)gdk_event_new(GDK_BUTTON_PRESS);
- event_button->window = g_object_ref(event->window);
- event_button->send_event = event->send_event;
- event_button->time = event->time;
- event_button->x = event->x;
- event_button->y = event->y;
- event_button->axes = NULL;
- event_button->state = event->state;
- event_button->button = button;
- event_button->device = g_object_ref(event->device);
- event_button->x_root = event->x_root;
- event_button->y_root = event->y_root;
- ret = button_internal(inst, event_button);
- gdk_event_free((GdkEvent *)event_button);
- return ret;
-}
-#endif
-
-gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- bool shift, ctrl, alt;
- int x, y, button;
-
- /* Remember the timestamp. */
- inst->input_event_time = event->time;
-
- noise_ultralight(NOISE_SOURCE_MOUSEPOS,
- ((uint32_t)event->x << 16) | (uint32_t)event->y);
-
- show_mouseptr(inst, true);
-
- shift = event->state & GDK_SHIFT_MASK;
- ctrl = event->state & GDK_CONTROL_MASK;
- alt = event->state & inst->meta_mod_mask;
- if (event->state & GDK_BUTTON1_MASK)
- button = MBT_LEFT;
- else if (event->state & GDK_BUTTON2_MASK)
- button = MBT_MIDDLE;
- else if (event->state & GDK_BUTTON3_MASK)
- button = MBT_RIGHT;
- else
- return false; /* don't even know what button! */
-
- x = (event->x - inst->window_border) / inst->font_width;
- y = (event->y - inst->window_border) / inst->font_height;
-
- term_mouse(inst->term, button, translate_button(button), MA_DRAG,
- x, y, shift, ctrl, alt);
-
- return true;
-}
-
-static void key_pressed(GtkFrontend *inst)
-{
- /*
- * If our child process has exited but not closed, terminate on
- * any keypress.
- *
- * This is a UI feature specific to GTK PuTTY, because GTK PuTTY
- * will (at least sometimes) be running under X, and under X the
- * window manager is sometimes absent (very occasionally on
- * purpose, more usually temporarily because it's crashed). So
- * it's useful to have a way to close an application window
- * without depending on protocols like WM_DELETE_WINDOW that are
- * typically generated by the WM (e.g. in response to a close
- * button in the window frame).
- */
- if (inst->exited)
- gtk_widget_destroy(inst->window);
-}
-
-static void exit_callback(void *vctx)
-{
- GtkFrontend *inst = (GtkFrontend *)vctx;
- int exitcode, close_on_exit;
-
- if (!inst->exited &&
- (exitcode = backend_exitcode(inst->backend)) >= 0) {
- destroy_inst_connection(inst);
-
- close_on_exit = conf_get_int(inst->conf, CONF_close_on_exit);
- if (close_on_exit == FORCE_ON ||
- (close_on_exit == AUTO && exitcode == 0)) {
- gtk_widget_destroy(inst->window);
- }
- }
-}
-
-static void gtk_seat_notify_remote_exit(Seat *seat)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- queue_toplevel_callback(exit_callback, inst);
-}
-
-static void destroy_inst_connection(GtkFrontend *inst)
-{
- inst->exited = true;
- if (inst->ldisc) {
- ldisc_free(inst->ldisc);
- inst->ldisc = NULL;
- }
- if (inst->backend) {
- backend_free(inst->backend);
- inst->backend = NULL;
- }
- if (inst->term)
- term_provide_backend(inst->term, NULL);
- if (inst->menu) {
- seat_update_specials_menu(&inst->seat);
- gtk_widget_set_sensitive(inst->restartitem, true);
- }
-}
-
-static void delete_inst(GtkFrontend *inst)
-{
- int dialog_slot;
- for (dialog_slot = 0; dialog_slot < DIALOG_SLOT_LIMIT; dialog_slot++) {
- if (inst->dialogs[dialog_slot]) {
- gtk_widget_destroy(inst->dialogs[dialog_slot]);
- inst->dialogs[dialog_slot] = NULL;
- }
- }
- if (inst->window) {
- gtk_widget_destroy(inst->window);
- inst->window = NULL;
- }
- if (inst->menu) {
- gtk_widget_destroy(inst->menu);
- inst->menu = NULL;
- }
- destroy_inst_connection(inst);
- if (inst->term) {
- term_free(inst->term);
- inst->term = NULL;
- }
- if (inst->conf) {
- conf_free(inst->conf);
- inst->conf = NULL;
- }
- if (inst->logctx) {
- log_free(inst->logctx);
- inst->logctx = NULL;
- }
-#if GTK_CHECK_VERSION(2,0,0)
- if (inst->trust_sigil_pb) {
- g_object_unref(G_OBJECT(inst->trust_sigil_pb));
- inst->trust_sigil_pb = NULL;
- }
-#else
- if (inst->trust_sigil_pm) {
- gdk_pixmap_unref(inst->trust_sigil_pm);
- inst->trust_sigil_pm = NULL;
- }
-#endif
-
-#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
- /*
- * Clear up any in-flight clipboard_data_instances. We can't
- * actually _free_ them, but we detach them from the inst that's
- * about to be destroyed.
- */
- while (inst->cdi_headtail.next != &inst->cdi_headtail) {
- struct clipboard_data_instance *cdi = inst->cdi_headtail.next;
- cdi->state = NULL;
- cdi->next->prev = cdi->prev;
- cdi->prev->next = cdi->next;
- cdi->next = cdi->prev = cdi;
- }
-#endif
-
- /*
- * Delete any top-level callbacks associated with inst, which
- * would otherwise become stale-pointer dereferences waiting to
- * happen. We do this last, because some of the above cleanups
- * (notably shutting down the backend) might themelves queue such
- * callbacks, so we need to make sure they don't do that _after_
- * we're supposed to have cleaned everything up.
- */
- delete_callbacks_for_context(inst);
-
- eventlogstuff_free(inst->eventlogstuff);
-
- sfree(inst);
-}
-
-void destroy(GtkWidget *widget, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- inst->window = NULL;
- delete_inst(inst);
- session_window_closed();
-}
-
-gint focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- term_set_focus(inst->term, event->in);
- term_update(inst->term);
- show_mouseptr(inst, true);
- return false;
-}
-
-static void gtk_seat_set_busy_status(Seat *seat, BusyStatus status)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- inst->busy_status = status;
- update_mouseptr(inst);
-}
-
-static void gtkwin_set_raw_mouse_mode(TermWin *tw, bool activate)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- inst->send_raw_mouse = activate;
-}
-
-static void gtkwin_set_raw_mouse_mode_pointer(TermWin *tw, bool activate)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- inst->pointer_indicates_raw_mouse = activate;
- update_mouseptr(inst);
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-static void compute_whole_window_size(GtkFrontend *inst,
- int wchars, int hchars,
- int *wpix, int *hpix);
-#endif
-
-static void gtkwin_request_resize(TermWin *tw, int w, int h)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
-
-#if !GTK_CHECK_VERSION(3,0,0)
-
- int large_x, large_y;
- int offset_x, offset_y;
- int area_x, area_y;
- GtkRequisition inner, outer;
-
- /*
- * This is a heinous hack dreamed up by the gnome-terminal
- * people to get around a limitation in gtk. The problem is
- * that in order to set the size correctly we really need to be
- * calling gtk_window_resize - but that needs to know the size
- * of the _whole window_, not the drawing area. So what we do
- * is to set an artificially huge size request on the drawing
- * area, recompute the resulting size request on the window,
- * and look at the difference between the two. That gives us
- * the x and y offsets we need to translate drawing area size
- * into window size for real, and then we call
- * gtk_window_resize.
- */
-
- /*
- * We start by retrieving the current size of the whole window.
- * Adding a bit to _that_ will give us a value we can use as a
- * bogus size request which guarantees to be bigger than the
- * current size of the drawing area.
- */
- get_window_pixel_size(inst, &large_x, &large_y);
- large_x += 32;
- large_y += 32;
-
- gtk_widget_set_size_request(inst->area, large_x, large_y);
- gtk_widget_size_request(inst->area, &inner);
- gtk_widget_size_request(inst->window, &outer);
-
- offset_x = outer.width - inner.width;
- offset_y = outer.height - inner.height;
-
- area_x = inst->font_width * w + 2*inst->window_border;
- area_y = inst->font_height * h + 2*inst->window_border;
-
- /*
- * Now we must set the size request on the drawing area back to
- * something sensible before we commit the real resize. Best
- * way to do this, I think, is to set it to what the size is
- * really going to end up being.
- */
- gtk_widget_set_size_request(inst->area, area_x, area_y);
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_window_resize(GTK_WINDOW(inst->window),
- area_x + offset_x, area_y + offset_y);
-#else
- gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), area_x, area_y);
- /*
- * I can no longer remember what this call to
- * gtk_container_dequeue_resize_handler is for. It was
- * introduced in r3092 with no comment, and the commit log
- * message was uninformative. I'm _guessing_ its purpose is to
- * prevent gratuitous resize processing on the window given
- * that we're about to resize it anyway, but I have no idea
- * why that's so incredibly vital.
- *
- * I've tried removing the call, and nothing seems to go
- * wrong. I've backtracked to r3092 and tried removing the
- * call there, and still nothing goes wrong. So I'm going to
- * adopt the working hypothesis that it's superfluous; I won't
- * actually remove it from the GTK 1.2 code, but I won't
- * attempt to replicate its functionality in the GTK 2 code
- * above.
- */
- gtk_container_dequeue_resize_handler(GTK_CONTAINER(inst->window));
- gdk_window_resize(gtk_widget_get_window(inst->window),
- area_x + offset_x, area_y + offset_y);
-#endif
-
-#else /* GTK_CHECK_VERSION(3,0,0) */
-
- int wp, hp;
- compute_whole_window_size(inst, w, h, &wp, &hp);
- gtk_window_resize(GTK_WINDOW(inst->window), wp, hp);
-
-#endif
-
-}
-
-#if GTK_CHECK_VERSION(3,0,0)
-char *colour_to_css(const GdkColor *col)
-{
- GdkRGBA rgba;
- rgba.red = col->red / 65535.0;
- rgba.green = col->green / 65535.0;
- rgba.blue = col->blue / 65535.0;
- rgba.alpha = 1.0;
- return gdk_rgba_to_string(&rgba);
-}
-#endif
-
-void set_gtk_widget_background(GtkWidget *widget, const GdkColor *col)
-{
-#if GTK_CHECK_VERSION(3,0,0)
- GtkCssProvider *provider = gtk_css_provider_new();
- char *col_css = colour_to_css(col);
- char *data = dupprintf(
- "#drawing-area, #top-level { background-color: %s; }\n", col_css);
- gtk_css_provider_load_from_data(provider, data, -1, NULL);
- GtkStyleContext *context = gtk_widget_get_style_context(widget);
- gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider),
- GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
- free(data);
- free(col_css);
-#else
- if (gtk_widget_get_window(widget)) {
- /* For GTK1, which doesn't have a 'const' on
- * gdk_window_set_background's second parameter type. */
- GdkColor col_mutable = *col;
- gdk_window_set_background(gtk_widget_get_window(widget), &col_mutable);
- }
-#endif
-}
-
-void set_window_background(GtkFrontend *inst)
-{
- if (inst->area)
- set_gtk_widget_background(GTK_WIDGET(inst->area), &inst->cols[258]);
- if (inst->window)
- set_gtk_widget_background(GTK_WIDGET(inst->window), &inst->cols[258]);
-}
-
-static void gtkwin_palette_set(TermWin *tw, unsigned start, unsigned ncolours,
- const rgb *colours)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
-
- assert(start <= OSC4_NCOLOURS);
- assert(ncolours <= OSC4_NCOLOURS - start);
-
-#if !GTK_CHECK_VERSION(3,0,0)
- if (!inst->colmap) {
- inst->colmap = gdk_colormap_get_system();
- } else {
- gdk_colormap_free_colors(inst->colmap, inst->cols, OSC4_NCOLOURS);
- }
-#endif
-
- for (unsigned i = 0; i < ncolours; i++) {
- const rgb *in = &colours[i];
- GdkColor *out = &inst->cols[start + i];
-
- out->red = in->r * 0x0101;
- out->green = in->g * 0x0101;
- out->blue = in->b * 0x0101;
- }
-
-#if !GTK_CHECK_VERSION(3,0,0)
- {
- gboolean success[OSC4_NCOLOURS];
- gdk_colormap_alloc_colors(inst->colmap, inst->cols + start,
- ncolours, false, true, success);
- for (unsigned i = 0; i < ncolours; i++) {
- if (!success[i])
- g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n",
- appname, start + i,
- conf_get_int_int(inst->conf, CONF_colours, i*3+0),
- conf_get_int_int(inst->conf, CONF_colours, i*3+1),
- conf_get_int_int(inst->conf, CONF_colours, i*3+2));
- }
- }
-#endif
-
- if (start <= OSC4_COLOUR_bg && OSC4_COLOUR_bg < start + ncolours) {
- /* Default Background has changed, so ensure that space between text
- * area and window border is refreshed. */
- set_window_background(inst);
- if (inst->area && gtk_widget_get_window(inst->area)) {
- draw_backing_rect(inst);
- gtk_widget_queue_draw(inst->area);
- }
- }
-}
-
-static void gtkwin_palette_get_overrides(TermWin *tw, Terminal *term)
-{
- /* GTK has no analogue of Windows's 'standard system colours', so GTK PuTTY
- * has no config option to override the normally configured colours from
- * it */
-}
-
-static struct clipboard_state *clipboard_from_atom(
- GtkFrontend *inst, GdkAtom atom)
-{
- int i;
-
- for (i = 0; i < N_CLIPBOARDS; i++) {
- struct clipboard_state *state = &inst->clipstates[i];
- if (state->inst == inst && state->atom == atom)
- return state;
- }
-
- return NULL;
-}
-
-#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
-
-/* ----------------------------------------------------------------------
- * Clipboard handling, using the high-level GtkClipboard interface in
- * as hands-off a way as possible. We write and read the clipboard as
- * UTF-8 text, and let GTK deal with converting to any other text
- * formats it feels like.
- */
-
-void set_clipboard_atom(GtkFrontend *inst, int clipboard, GdkAtom atom)
-{
- struct clipboard_state *state = &inst->clipstates[clipboard];
-
- state->inst = inst;
- state->clipboard = clipboard;
- state->atom = atom;
-
- if (state->atom != GDK_NONE) {
- state->gtkclipboard = gtk_clipboard_get_for_display(
- gdk_display_get_default(), state->atom);
- g_object_set_data(G_OBJECT(state->gtkclipboard), "user-data", state);
- } else {
- state->gtkclipboard = NULL;
- }
-}
-
-int init_clipboard(GtkFrontend *inst)
-{
- set_clipboard_atom(inst, CLIP_PRIMARY, GDK_SELECTION_PRIMARY);
- set_clipboard_atom(inst, CLIP_CLIPBOARD, clipboard_atom);
- return true;
-}
-
-static void clipboard_provide_data(GtkClipboard *clipboard,
- GtkSelectionData *selection_data,
- guint info, gpointer data)
-{
- struct clipboard_data_instance *cdi =
- (struct clipboard_data_instance *)data;
-
- if (cdi->state && cdi->state->current_cdi == cdi) {
- gtk_selection_data_set_text(selection_data, cdi->pasteout_data_utf8,
- cdi->pasteout_data_utf8_len);
- }
-}
-
-static void clipboard_clear(GtkClipboard *clipboard, gpointer data)
-{
- struct clipboard_data_instance *cdi =
- (struct clipboard_data_instance *)data;
-
- if (cdi->state && cdi->state->current_cdi == cdi) {
- if (cdi->state->inst && cdi->state->inst->term) {
- term_lost_clipboard_ownership(cdi->state->inst->term,
- cdi->state->clipboard);
- }
- cdi->state->current_cdi = NULL;
- }
- sfree(cdi->pasteout_data_utf8);
- cdi->next->prev = cdi->prev;
- cdi->prev->next = cdi->next;
- sfree(cdi);
-}
-
-static void gtkwin_clip_write(
- TermWin *tw, int clipboard, wchar_t *data, int *attr,
- truecolour *truecolour, int len, bool must_deselect)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- struct clipboard_state *state = &inst->clipstates[clipboard];
- struct clipboard_data_instance *cdi;
-
- if (inst->direct_to_font) {
- /* In this clipboard mode, we just can't paste if we're in
- * direct-to-font mode. Fortunately, that shouldn't be
- * important, because we'll only use this clipboard handling
- * code on systems where that kind of font doesn't exist
- * anyway. */
- return;
- }
-
- if (!state->gtkclipboard)
- return;
-
- cdi = snew(struct clipboard_data_instance);
- cdi->state = state;
- state->current_cdi = cdi;
- cdi->pasteout_data_utf8 = snewn(len*6, char);
- cdi->prev = inst->cdi_headtail.prev;
- cdi->next = &inst->cdi_headtail;
- cdi->next->prev = cdi;
- cdi->prev->next = cdi;
- {
- const wchar_t *tmp = data;
- int tmplen = len;
- cdi->pasteout_data_utf8_len =
- charset_from_unicode(&tmp, &tmplen, cdi->pasteout_data_utf8,
- len*6, CS_UTF8, NULL, NULL, 0);
- }
-
- /*
- * It would be nice to just call gtk_clipboard_set_text() in place
- * of all of the faffing below. Unfortunately, that won't give me
- * access to the clipboard-clear event, which we use to visually
- * deselect text in the terminal.
- */
- {
- GtkTargetList *targetlist;
- GtkTargetEntry *targettable;
- gint n_targets;
-
- targetlist = gtk_target_list_new(NULL, 0);
- gtk_target_list_add_text_targets(targetlist, 0);
- targettable = gtk_target_table_new_from_list(targetlist, &n_targets);
- gtk_clipboard_set_with_data(state->gtkclipboard, targettable,
- n_targets, clipboard_provide_data,
- clipboard_clear, cdi);
- gtk_target_table_free(targettable, n_targets);
- gtk_target_list_unref(targetlist);
- }
-}
-
-static void clipboard_text_received(GtkClipboard *clipboard,
- const gchar *text, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- wchar_t *paste;
- int paste_len;
- int length;
-
- if (!text)
- return;
-
- length = strlen(text);
-
- paste = snewn(length, wchar_t);
- paste_len = mb_to_wc(CS_UTF8, 0, text, length, paste, length);
-
- term_do_paste(inst->term, paste, paste_len);
-
- sfree(paste);
-}
-
-static void gtkwin_clip_request_paste(TermWin *tw, int clipboard)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- struct clipboard_state *state = &inst->clipstates[clipboard];
-
- if (!state->gtkclipboard)
- return;
-
- gtk_clipboard_request_text(state->gtkclipboard,
- clipboard_text_received, inst);
-}
-
-#else /* JUST_USE_GTK_CLIPBOARD_UTF8 */
-
-/* ----------------------------------------------------------------------
- * Clipboard handling for X, using the low-level gtk_selection_*
- * interface, handling conversions to fiddly things like compound text
- * ourselves, and storing in X cut buffers too.
- *
- * This version of the clipboard code has to be kept around for GTK1,
- * which doesn't have the higher-level GtkClipboard interface at all.
- * And since it works on GTK2 and GTK3 too and has had a good few
- * years of shakedown and bug fixing, we might as well keep using it
- * where it's applicable.
- *
- * It's _possible_ that we might be able to replicate all the
- * important wrinkles of this code in GtkClipboard. (In particular,
- * cut buffers or local analogue look as if they might be accessible
- * via gtk_clipboard_set_can_store(), and delivering text in
- * non-Unicode formats only in the direct-to-font case ought to be
- * possible if we can figure out the right set of things to put in the
- * GtkTargetList.) But that work can wait until there's a need for it!
- */
-
-#ifndef NOT_X_WINDOWS
-
-/* Store the data in a cut-buffer. */
-static void store_cutbuffer(GtkFrontend *inst, char *ptr, int len)
-{
- if (inst->disp) {
- /* ICCCM says we must rotate the buffers before storing to buffer 0. */
- XRotateBuffers(inst->disp, 1);
- XStoreBytes(inst->disp, ptr, len);
- }
-}
-
-/* Retrieve data from a cut-buffer.
- * Returned data needs to be freed with XFree().
- */
-static char *retrieve_cutbuffer(GtkFrontend *inst, int *nbytes)
-{
- char *ptr;
- if (!inst->disp) {
- *nbytes = 0;
- return NULL;
- }
- ptr = XFetchBytes(inst->disp, nbytes);
- if (*nbytes <= 0 && ptr != 0) {
- XFree(ptr);
- ptr = 0;
- }
- return ptr;
-}
-
-#endif /* NOT_X_WINDOWS */
-
-static void gtkwin_clip_write(
- TermWin *tw, int clipboard, wchar_t *data, int *attr,
- truecolour *truecolour, int len, bool must_deselect)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- struct clipboard_state *state = &inst->clipstates[clipboard];
-
- if (state->pasteout_data)
- sfree(state->pasteout_data);
- if (state->pasteout_data_ctext)
- sfree(state->pasteout_data_ctext);
- if (state->pasteout_data_utf8)
- sfree(state->pasteout_data_utf8);
-
- /*
- * Set up UTF-8 and compound text paste data. This only happens
- * if we aren't in direct-to-font mode using the D800 hack.
- */
- if (!inst->direct_to_font) {
- const wchar_t *tmp = data;
- int tmplen = len;
-#ifndef NOT_X_WINDOWS
- XTextProperty tp;
- char *list[1];
-#endif
-
- state->pasteout_data_utf8 = snewn(len*6, char);
- state->pasteout_data_utf8_len = len*6;
- state->pasteout_data_utf8_len =
- charset_from_unicode(&tmp, &tmplen, state->pasteout_data_utf8,
- state->pasteout_data_utf8_len,
- CS_UTF8, NULL, NULL, 0);
- if (state->pasteout_data_utf8_len == 0) {
- sfree(state->pasteout_data_utf8);
- state->pasteout_data_utf8 = NULL;
- } else {
- state->pasteout_data_utf8 =
- sresize(state->pasteout_data_utf8,
- state->pasteout_data_utf8_len + 1, char);
- state->pasteout_data_utf8[state->pasteout_data_utf8_len] = '\0';
- }
-
- /*
- * Now let Xlib convert our UTF-8 data into compound text.
- */
-#ifndef NOT_X_WINDOWS
- list[0] = state->pasteout_data_utf8;
- if (inst->disp && Xutf8TextListToTextProperty(
- inst->disp, list, 1, XCompoundTextStyle, &tp) == 0) {
- state->pasteout_data_ctext = snewn(tp.nitems+1, char);
- memcpy(state->pasteout_data_ctext, tp.value, tp.nitems);
- state->pasteout_data_ctext_len = tp.nitems;
- XFree(tp.value);
- } else
-#endif
- {
- state->pasteout_data_ctext = NULL;
- state->pasteout_data_ctext_len = 0;
- }
- } else {
- state->pasteout_data_utf8 = NULL;
- state->pasteout_data_utf8_len = 0;
- state->pasteout_data_ctext = NULL;
- state->pasteout_data_ctext_len = 0;
- }
-
- state->pasteout_data = snewn(len*6, char);
- state->pasteout_data_len = len*6;
- state->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0,
- data, len, state->pasteout_data,
- state->pasteout_data_len,
- NULL, NULL);
- if (state->pasteout_data_len == 0) {
- sfree(state->pasteout_data);
- state->pasteout_data = NULL;
- } else {
- state->pasteout_data =
- sresize(state->pasteout_data, state->pasteout_data_len, char);
- }
-
-#ifndef NOT_X_WINDOWS
- /* The legacy X cut buffers go with PRIMARY, not any other clipboard */
- if (state->atom == GDK_SELECTION_PRIMARY)
- store_cutbuffer(inst, state->pasteout_data, state->pasteout_data_len);
-#endif
-
- if (gtk_selection_owner_set(inst->area, state->atom,
- inst->input_event_time)) {
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_selection_clear_targets(inst->area, state->atom);
-#endif
- gtk_selection_add_target(inst->area, state->atom,
- GDK_SELECTION_TYPE_STRING, 1);
- if (state->pasteout_data_ctext)
- gtk_selection_add_target(inst->area, state->atom,
- compound_text_atom, 1);
- if (state->pasteout_data_utf8)
- gtk_selection_add_target(inst->area, state->atom,
- utf8_string_atom, 1);
- }
-
- if (must_deselect)
- term_lost_clipboard_ownership(inst->term, clipboard);
-}
-
-static void selection_get(GtkWidget *widget, GtkSelectionData *seldata,
- guint info, guint time_stamp, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- GdkAtom target = gtk_selection_data_get_target(seldata);
- struct clipboard_state *state = clipboard_from_atom(
- inst, gtk_selection_data_get_selection(seldata));
-
- if (!state)
- return;
-
- if (target == utf8_string_atom)
- gtk_selection_data_set(seldata, target, 8,
- (unsigned char *)state->pasteout_data_utf8,
- state->pasteout_data_utf8_len);
- else if (target == compound_text_atom)
- gtk_selection_data_set(seldata, target, 8,
- (unsigned char *)state->pasteout_data_ctext,
- state->pasteout_data_ctext_len);
- else
- gtk_selection_data_set(seldata, target, 8,
- (unsigned char *)state->pasteout_data,
- state->pasteout_data_len);
-}
-
-static gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
- gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- struct clipboard_state *state = clipboard_from_atom(
- inst, seldata->selection);
-
- if (!state)
- return true;
-
- term_lost_clipboard_ownership(inst->term, state->clipboard);
- if (state->pasteout_data)
- sfree(state->pasteout_data);
- if (state->pasteout_data_ctext)
- sfree(state->pasteout_data_ctext);
- if (state->pasteout_data_utf8)
- sfree(state->pasteout_data_utf8);
- state->pasteout_data = NULL;
- state->pasteout_data_len = 0;
- state->pasteout_data_ctext = NULL;
- state->pasteout_data_ctext_len = 0;
- state->pasteout_data_utf8 = NULL;
- state->pasteout_data_utf8_len = 0;
- return true;
-}
-
-static void gtkwin_clip_request_paste(TermWin *tw, int clipboard)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- struct clipboard_state *state = &inst->clipstates[clipboard];
-
- /*
- * In Unix, pasting is asynchronous: all we can do at the
- * moment is to call gtk_selection_convert(), and when the data
- * comes back _then_ we can call term_do_paste().
- */
-
- if (!inst->direct_to_font) {
- /*
- * First we attempt to retrieve the selection as a UTF-8
- * string (which we will convert to the correct code page
- * before sending to the session, of course). If that
- * fails, selection_received() will be informed and will
- * fall back to an ordinary string.
- */
- gtk_selection_convert(inst->area, state->atom, utf8_string_atom,
- inst->input_event_time);
- } else {
- /*
- * If we're in direct-to-font mode, we disable UTF-8
- * pasting, and go straight to ordinary string data.
- */
- gtk_selection_convert(inst->area, state->atom,
- GDK_SELECTION_TYPE_STRING,
- inst->input_event_time);
- }
-}
-
-static void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
- guint time, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- char *text;
- int length;
-#ifndef NOT_X_WINDOWS
- char **list;
- bool free_list_required = false;
- bool free_required = false;
-#endif
- int charset;
- GdkAtom seldata_target = gtk_selection_data_get_target(seldata);
- GdkAtom seldata_type = gtk_selection_data_get_data_type(seldata);
- const guchar *seldata_data = gtk_selection_data_get_data(seldata);
- gint seldata_length = gtk_selection_data_get_length(seldata);
- wchar_t *paste;
- int paste_len;
- struct clipboard_state *state = clipboard_from_atom(
- inst, gtk_selection_data_get_selection(seldata));
-
- if (!state)
- return;
-
- if (seldata_target == utf8_string_atom && seldata_length <= 0) {
- /*
- * Failed to get a UTF-8 selection string. Try compound
- * text next.
- */
- gtk_selection_convert(inst->area, state->atom,
- compound_text_atom,
- inst->input_event_time);
- return;
- }
-
- if (seldata_target == compound_text_atom && seldata_length <= 0) {
- /*
- * Failed to get UTF-8 or compound text. Try an ordinary
- * string.
- */
- gtk_selection_convert(inst->area, state->atom,
- GDK_SELECTION_TYPE_STRING,
- inst->input_event_time);
- return;
- }
-
- /*
- * If we have data, but it's not of a type we can deal with,
- * we have to ignore the data.
- */
- if (seldata_length > 0 &&
- seldata_type != GDK_SELECTION_TYPE_STRING &&
- seldata_type != compound_text_atom &&
- seldata_type != utf8_string_atom)
- return;
-
- /*
- * If we have no data, try looking in a cut buffer.
- */
- if (seldata_length <= 0) {
-#ifndef NOT_X_WINDOWS
- text = retrieve_cutbuffer(inst, &length);
- if (length == 0)
- return;
- /* Xterm is rumoured to expect Latin-1, though I havn't checked the
- * source, so use that as a de-facto standard. */
- charset = CS_ISO8859_1;
- free_required = true;
-#else
- return;
-#endif
- } else {
- /*
- * Convert COMPOUND_TEXT into UTF-8.
- */
- if (seldata_type == compound_text_atom) {
-#ifndef NOT_X_WINDOWS
- XTextProperty tp;
- int ret, count;
-
- tp.value = (unsigned char *)seldata_data;
- tp.encoding = (Atom) seldata_type;
- tp.format = gtk_selection_data_get_format(seldata);
- tp.nitems = seldata_length;
- ret = inst->disp == NULL ? -1 :
- Xutf8TextPropertyToTextList(inst->disp, &tp, &list, &count);
- if (ret == 0 && count == 1) {
- text = list[0];
- length = strlen(list[0]);
- charset = CS_UTF8;
- free_list_required = true;
- } else
-#endif
- {
- /*
- * Compound text failed; fall back to STRING.
- */
- gtk_selection_convert(inst->area, state->atom,
- GDK_SELECTION_TYPE_STRING,
- inst->input_event_time);
- return;
- }
- } else {
- text = (char *)seldata_data;
- length = seldata_length;
- charset = (seldata_type == utf8_string_atom ?
- CS_UTF8 : inst->ucsdata.line_codepage);
- }
- }
-
- paste = snewn(length, wchar_t);
- paste_len = mb_to_wc(charset, 0, text, length, paste, length);
-
- term_do_paste(inst->term, paste, paste_len);
-
- sfree(paste);
-
-#ifndef NOT_X_WINDOWS
- if (free_list_required)
- XFreeStringList(list);
- if (free_required)
- XFree(text);
-#endif
-}
-
-static void init_one_clipboard(GtkFrontend *inst, int clipboard)
-{
- struct clipboard_state *state = &inst->clipstates[clipboard];
-
- state->inst = inst;
- state->clipboard = clipboard;
-}
-
-void set_clipboard_atom(GtkFrontend *inst, int clipboard, GdkAtom atom)
-{
- struct clipboard_state *state = &inst->clipstates[clipboard];
-
- state->inst = inst;
- state->clipboard = clipboard;
-
- state->atom = atom;
-}
-
-void init_clipboard(GtkFrontend *inst)
-{
-#ifndef NOT_X_WINDOWS
- /*
- * Ensure that all the cut buffers exist - according to the ICCCM,
- * we must do this before we start using cut buffers.
- */
- if (inst->disp) {
- unsigned char empty[] = "";
- x11_ignore_error(inst->disp, BadMatch);
- XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER0,
- XA_STRING, 8, PropModeAppend, empty, 0);
- x11_ignore_error(inst->disp, BadMatch);
- XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER1,
- XA_STRING, 8, PropModeAppend, empty, 0);
- x11_ignore_error(inst->disp, BadMatch);
- XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER2,
- XA_STRING, 8, PropModeAppend, empty, 0);
- x11_ignore_error(inst->disp, BadMatch);
- XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER3,
- XA_STRING, 8, PropModeAppend, empty, 0);
- x11_ignore_error(inst->disp, BadMatch);
- XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER4,
- XA_STRING, 8, PropModeAppend, empty, 0);
- x11_ignore_error(inst->disp, BadMatch);
- XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER5,
- XA_STRING, 8, PropModeAppend, empty, 0);
- x11_ignore_error(inst->disp, BadMatch);
- XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER6,
- XA_STRING, 8, PropModeAppend, empty, 0);
- x11_ignore_error(inst->disp, BadMatch);
- XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER7,
- XA_STRING, 8, PropModeAppend, empty, 0);
- }
-#endif
-
- inst->clipstates[CLIP_PRIMARY].atom = GDK_SELECTION_PRIMARY;
- inst->clipstates[CLIP_CLIPBOARD].atom = clipboard_atom;
- init_one_clipboard(inst, CLIP_PRIMARY);
- init_one_clipboard(inst, CLIP_CLIPBOARD);
-
- g_signal_connect(G_OBJECT(inst->area), "selection_received",
- G_CALLBACK(selection_received), inst);
- g_signal_connect(G_OBJECT(inst->area), "selection_get",
- G_CALLBACK(selection_get), inst);
- g_signal_connect(G_OBJECT(inst->area), "selection_clear_event",
- G_CALLBACK(selection_clear), inst);
-}
-
-/*
- * End of selection/clipboard handling.
- * ----------------------------------------------------------------------
- */
-
-#endif /* JUST_USE_GTK_CLIPBOARD_UTF8 */
-
-static void set_window_titles(GtkFrontend *inst)
-{
- /*
- * We must always call set_icon_name after calling set_title,
- * since set_title will write both names. Irritating, but such
- * is life.
- */
- gtk_window_set_title(GTK_WINDOW(inst->window), inst->wintitle);
- if (!conf_get_bool(inst->conf, CONF_win_name_always))
- gdk_window_set_icon_name(gtk_widget_get_window(inst->window),
- inst->icontitle);
-}
-
-static void gtkwin_set_title(TermWin *tw, const char *title)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- sfree(inst->wintitle);
- inst->wintitle = dupstr(title);
- set_window_titles(inst);
-}
-
-static void gtkwin_set_icon_title(TermWin *tw, const char *title)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- sfree(inst->icontitle);
- inst->icontitle = dupstr(title);
- set_window_titles(inst);
-}
-
-static void gtkwin_set_scrollbar(TermWin *tw, int total, int start, int page)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- if (!conf_get_bool(inst->conf, CONF_scrollbar))
- return;
- inst->ignore_sbar = true;
- gtk_adjustment_set_lower(inst->sbar_adjust, 0);
- gtk_adjustment_set_upper(inst->sbar_adjust, total);
- gtk_adjustment_set_value(inst->sbar_adjust, start);
- gtk_adjustment_set_page_size(inst->sbar_adjust, page);
- gtk_adjustment_set_step_increment(inst->sbar_adjust, 1);
- gtk_adjustment_set_page_increment(inst->sbar_adjust, page/2);
-#if !GTK_CHECK_VERSION(3,18,0)
- gtk_adjustment_changed(inst->sbar_adjust);
-#endif
- inst->ignore_sbar = false;
-}
-
-void scrollbar_moved(GtkAdjustment *adj, GtkFrontend *inst)
-{
- if (!conf_get_bool(inst->conf, CONF_scrollbar))
- return;
- if (!inst->ignore_sbar)
- term_scroll(inst->term, 1, (int)gtk_adjustment_get_value(adj));
-}
-
-static void show_scrollbar(GtkFrontend *inst, gboolean visible)
-{
- inst->sbar_visible = visible;
- if (visible)
- gtk_widget_show(inst->sbar);
- else
- gtk_widget_hide(inst->sbar);
-}
-
-static void gtkwin_set_cursor_pos(TermWin *tw, int x, int y)
-{
- /*
- * This is meaningless under X.
- */
-}
-
-/*
- * This is still called when mode==BELL_VISUAL, even though the
- * visual bell is handled entirely within terminal.c, because we
- * may want to perform additional actions on any kind of bell (for
- * example, taskbar flashing in Windows).
- */
-static void gtkwin_bell(TermWin *tw, int mode)
-{
- /* GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); */
- if (mode == BELL_DEFAULT)
- gdk_display_beep(gdk_display_get_default());
-}
-
-static int gtkwin_char_width(TermWin *tw, int uc)
-{
- /*
- * In this front end, double-width characters are handled using a
- * separate font, so this can safely just return 1 always.
- */
- return 1;
-}
-
-static bool gtkwin_setup_draw_ctx(TermWin *tw)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
-
- if (!gtk_widget_get_window(inst->area))
- return false;
-
- inst->uctx.type = inst->drawtype;
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
- /* If we're doing GDK-based drawing, then we also expect
- * inst->pixmap to exist. */
- inst->uctx.u.gdk.target = inst->pixmap;
- inst->uctx.u.gdk.gc = gdk_gc_new(gtk_widget_get_window(inst->area));
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- inst->uctx.u.cairo.widget = GTK_WIDGET(inst->area);
- /* If we're doing Cairo drawing, we expect inst->surface to
- * exist, and we draw to that first, regardless of whether we
- * subsequently copy the results to inst->pixmap. */
- inst->uctx.u.cairo.cr = cairo_create(inst->surface);
- cairo_scale(inst->uctx.u.cairo.cr, inst->scale, inst->scale);
- cairo_setup_draw_ctx(inst);
- }
-#endif
- return true;
-}
-
-static void gtkwin_free_draw_ctx(TermWin *tw)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
- gdk_gc_unref(inst->uctx.u.gdk.gc);
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_destroy(inst->uctx.u.cairo.cr);
- }
-#endif
-}
-
-
-static void draw_update(GtkFrontend *inst, int x, int y, int w, int h)
-{
-#if defined DRAW_TEXT_CAIRO && !defined NO_BACKING_PIXMAPS
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- /*
- * If inst->surface and inst->pixmap both exist, then we've
- * just drawn new content to the former which we must copy to
- * the latter.
- */
- cairo_t *cr = gdk_cairo_create(inst->pixmap);
- cairo_set_source_surface(cr, inst->surface, 0, 0);
- cairo_rectangle(cr, x, y, w, h);
- cairo_fill(cr);
- cairo_destroy(cr);
- }
-#endif
-
- /*
- * Now we just queue a window redraw, which will cause
- * inst->surface or inst->pixmap (whichever is appropriate for our
- * compile mode) to be copied to the real window when we receive
- * the resulting "expose" or "draw" event.
- *
- * Amazingly, this one API call is actually valid in all versions
- * of GTK :-)
- */
- gtk_widget_queue_draw_area(inst->area, x, y, w, h);
-}
-
-#ifdef DRAW_TEXT_CAIRO
-static void cairo_set_source_rgb_dim(cairo_t *cr, double r, double g, double b,
- bool dim)
-{
- if (dim)
- cairo_set_source_rgb(cr, r * 2 / 3, g * 2 / 3, b * 2 / 3);
- else
- cairo_set_source_rgb(cr, r, g, b);
-}
-#endif
-
-static void draw_set_colour(GtkFrontend *inst, int col, bool dim)
-{
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
- if (dim) {
-#if GTK_CHECK_VERSION(2,0,0)
- GdkColor color;
- color.red = inst->cols[col].red * 2 / 3;
- color.green = inst->cols[col].green * 2 / 3;
- color.blue = inst->cols[col].blue * 2 / 3;
- gdk_gc_set_rgb_fg_color(inst->uctx.u.gdk.gc, &color);
-#else
- /* Poor GTK1 fallback */
- gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[col]);
-#endif
- } else {
- gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[col]);
- }
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_set_source_rgb_dim(inst->uctx.u.cairo.cr,
- inst->cols[col].red / 65535.0,
- inst->cols[col].green / 65535.0,
- inst->cols[col].blue / 65535.0, dim);
- }
-#endif
-}
-
-static void draw_set_colour_rgb(GtkFrontend *inst, optionalrgb orgb, bool dim)
-{
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
-#if GTK_CHECK_VERSION(2,0,0)
- GdkColor color;
- color.red = orgb.r * 256;
- color.green = orgb.g * 256;
- color.blue = orgb.b * 256;
- if (dim) {
- color.red = color.red * 2 / 3;
- color.green = color.green * 2 / 3;
- color.blue = color.blue * 2 / 3;
- }
- gdk_gc_set_rgb_fg_color(inst->uctx.u.gdk.gc, &color);
-#else
- /* Poor GTK1 fallback */
- gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[256]);
-#endif
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_set_source_rgb_dim(inst->uctx.u.cairo.cr, orgb.r / 255.0,
- orgb.g / 255.0, orgb.b / 255.0, dim);
- }
-#endif
-}
-
-static void draw_rectangle(GtkFrontend *inst, bool filled,
- int x, int y, int w, int h)
-{
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
- gdk_draw_rectangle(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc,
- filled, x, y, w, h);
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_new_path(inst->uctx.u.cairo.cr);
- if (filled) {
- cairo_rectangle(inst->uctx.u.cairo.cr, x, y, w, h);
- cairo_fill(inst->uctx.u.cairo.cr);
- } else {
- cairo_rectangle(inst->uctx.u.cairo.cr,
- x + 0.5, y + 0.5, w, h);
- cairo_close_path(inst->uctx.u.cairo.cr);
- cairo_stroke(inst->uctx.u.cairo.cr);
- }
- }
-#endif
-}
-
-static void draw_clip(GtkFrontend *inst, int x, int y, int w, int h)
-{
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
- GdkRectangle r;
-
- r.x = x;
- r.y = y;
- r.width = w;
- r.height = h;
-
- gdk_gc_set_clip_rectangle(inst->uctx.u.gdk.gc, &r);
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_reset_clip(inst->uctx.u.cairo.cr);
- cairo_new_path(inst->uctx.u.cairo.cr);
- cairo_rectangle(inst->uctx.u.cairo.cr, x, y, w, h);
- cairo_clip(inst->uctx.u.cairo.cr);
- }
-#endif
-}
-
-static void draw_point(GtkFrontend *inst, int x, int y)
-{
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
- gdk_draw_point(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, x, y);
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_new_path(inst->uctx.u.cairo.cr);
- cairo_rectangle(inst->uctx.u.cairo.cr, x, y, 1, 1);
- cairo_fill(inst->uctx.u.cairo.cr);
- }
-#endif
-}
-
-static void draw_line(GtkFrontend *inst, int x0, int y0, int x1, int y1)
-{
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
- gdk_draw_line(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc,
- x0, y0, x1, y1);
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_new_path(inst->uctx.u.cairo.cr);
- cairo_move_to(inst->uctx.u.cairo.cr, x0 + 0.5, y0 + 0.5);
- cairo_line_to(inst->uctx.u.cairo.cr, x1 + 0.5, y1 + 0.5);
- cairo_stroke(inst->uctx.u.cairo.cr);
- }
-#endif
-}
-
-static void draw_stretch_before(GtkFrontend *inst, int x, int y,
- int w, bool wdouble,
- int h, bool hdouble, bool hbothalf)
-{
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_matrix_t matrix;
-
- matrix.xy = 0;
- matrix.yx = 0;
-
- if (wdouble) {
- matrix.xx = 2;
- matrix.x0 = -x;
- } else {
- matrix.xx = 1;
- matrix.x0 = 0;
- }
-
- if (hdouble) {
- matrix.yy = 2;
- if (hbothalf) {
- matrix.y0 = -(y+h);
- } else {
- matrix.y0 = -y;
- }
- } else {
- matrix.yy = 1;
- matrix.y0 = 0;
- }
- cairo_transform(inst->uctx.u.cairo.cr, &matrix);
- }
-#endif
-}
-
-static void draw_stretch_after(GtkFrontend *inst, int x, int y,
- int w, bool wdouble,
- int h, bool hdouble, bool hbothalf)
-{
-#ifdef DRAW_TEXT_GDK
-#ifndef NO_BACKING_PIXMAPS
- if (inst->uctx.type == DRAWTYPE_GDK) {
- /*
- * I can't find any plausible StretchBlt equivalent in the X
- * server, so I'm going to do this the slow and painful way.
- * This will involve repeated calls to gdk_draw_pixmap() to
- * stretch the text horizontally. It's O(N^2) in time and O(N)
- * in network bandwidth, but you try thinking of a better way.
- * :-(
- */
- int i;
- if (wdouble) {
- for (i = 0; i < w; i++) {
- gdk_draw_pixmap(inst->uctx.u.gdk.target,
- inst->uctx.u.gdk.gc,
- inst->uctx.u.gdk.target,
- x + 2*i, y,
- x + 2*i+1, y,
- w - i, h);
- }
- w *= 2;
- }
-
- if (hdouble) {
- int dt, db;
- /* Now stretch vertically, in the same way. */
- if (hbothalf)
- dt = 0, db = 1;
- else
- dt = 1, db = 0;
- for (i = 0; i < h; i += 2) {
- gdk_draw_pixmap(inst->uctx.u.gdk.target,
- inst->uctx.u.gdk.gc,
- inst->uctx.u.gdk.target,
- x, y + dt*i + db,
- x, y + dt*(i+1),
- w, h-i-1);
- }
- }
- }
-#else
-#error No way to implement stretching in GDK without a reliable backing pixmap
-#endif
-#endif /* DRAW_TEXT_GDK */
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_set_matrix(inst->uctx.u.cairo.cr,
- &inst->uctx.u.cairo.origmatrix);
- }
-#endif
-}
-
-static void draw_backing_rect(GtkFrontend *inst)
-{
- int w, h;
-
- if (!win_setup_draw_ctx(&inst->termwin))
- return;
-
- w = inst->width * inst->font_width + 2*inst->window_border;
- h = inst->height * inst->font_height + 2*inst->window_border;
- draw_set_colour(inst, 258, false);
- draw_rectangle(inst, true, 0, 0, w, h);
- draw_update(inst, 0, 0, w, h);
- win_free_draw_ctx(&inst->termwin);
-}
-
-/*
- * Draw a line of text in the window, at given character
- * coordinates, in given attributes.
- *
- * We are allowed to fiddle with the contents of `text'.
- */
-static void do_text_internal(
- GtkFrontend *inst, int x, int y, wchar_t *text, int len,
- unsigned long attr, int lattr, truecolour truecolour)
-{
- int ncombining;
- int nfg, nbg, t, fontid, rlen, widefactor;
- bool bold;
- bool monochrome =
- gdk_visual_get_depth(gtk_widget_get_visual(inst->area)) == 1;
-
- if (attr & TATTR_COMBINING) {
- ncombining = len;
- len = 1;
- } else
- ncombining = 1;
-
- if (monochrome)
- truecolour.fg = truecolour.bg = optionalrgb_none;
-
- nfg = ((monochrome ? ATTR_DEFFG : (attr & ATTR_FGMASK)) >> ATTR_FGSHIFT);
- nbg = ((monochrome ? ATTR_DEFBG : (attr & ATTR_BGMASK)) >> ATTR_BGSHIFT);
- if (!!(attr & ATTR_REVERSE) ^ (monochrome && (attr & TATTR_ACTCURS))) {
- struct optionalrgb trgb;
-
- t = nfg;
- nfg = nbg;
- nbg = t;
-
- trgb = truecolour.fg;
- truecolour.fg = truecolour.bg;
- truecolour.bg = trgb;
- }
- if ((inst->bold_style & 2) && (attr & ATTR_BOLD)) {
- if (nfg < 16) nfg |= 8;
- else if (nfg >= 256) nfg |= 1;
- }
- if ((inst->bold_style & 2) && (attr & ATTR_BLINK)) {
- if (nbg < 16) nbg |= 8;
- else if (nbg >= 256) nbg |= 1;
- }
- if ((attr & TATTR_ACTCURS) && !monochrome) {
- truecolour.fg = truecolour.bg = optionalrgb_none;
- nfg = 260;
- nbg = 261;
- attr &= ~ATTR_DIM; /* don't dim the cursor */
- }
-
- fontid = 0;
-
- if (attr & ATTR_WIDE) {
- widefactor = 2;
- fontid |= 2;
- } else {
- widefactor = 1;
- }
-
- if ((attr & ATTR_BOLD) && (inst->bold_style & 1)) {
- bold = true;
- fontid |= 1;
- } else {
- bold = false;
- }
-
- if (!inst->fonts[fontid]) {
- int i;
- /*
- * Fall back through font ids with subsets of this one's
- * set bits, in order.
- */
- for (i = fontid; i-- > 0 ;) {
- if (i & ~fontid)
- continue; /* some other bit is set */
- if (inst->fonts[i]) {
- fontid = i;
- break;
- }
- }
- assert(inst->fonts[fontid]); /* we should at least have hit zero */
- }
-
- if ((lattr & LATTR_MODE) != LATTR_NORM) {
- x *= 2;
- if (x >= inst->term->cols)
- return;
- if (x + len*2*widefactor > inst->term->cols) {
- len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */
- if (len == 0)
- return; /* rounded down half a double-width char to zero */
- }
- rlen = len * 2;
- } else
- rlen = len;
-
- draw_clip(inst,
- x*inst->font_width+inst->window_border,
- y*inst->font_height+inst->window_border,
- rlen*widefactor*inst->font_width,
- inst->font_height);
-
- if ((lattr & LATTR_MODE) != LATTR_NORM) {
- draw_stretch_before(inst,
- x*inst->font_width+inst->window_border,
- y*inst->font_height+inst->window_border,
- rlen*widefactor*inst->font_width, true,
- inst->font_height,
- ((lattr & LATTR_MODE) != LATTR_WIDE),
- ((lattr & LATTR_MODE) == LATTR_BOT));
- }
-
- if (truecolour.bg.enabled)
- draw_set_colour_rgb(inst, truecolour.bg, attr & ATTR_DIM);
- else
- draw_set_colour(inst, nbg, attr & ATTR_DIM);
- draw_rectangle(inst, true,
- x*inst->font_width+inst->window_border,
- y*inst->font_height+inst->window_border,
- rlen*widefactor*inst->font_width, inst->font_height);
-
- if (truecolour.fg.enabled)
- draw_set_colour_rgb(inst, truecolour.fg, attr & ATTR_DIM);
- else
- draw_set_colour(inst, nfg, attr & ATTR_DIM);
- if (ncombining > 1) {
- assert(len == 1);
- unifont_draw_combining(&inst->uctx, inst->fonts[fontid],
- x*inst->font_width+inst->window_border,
- (y*inst->font_height+inst->window_border+
- inst->fonts[0]->ascent),
- text, ncombining, widefactor > 1,
- bold, inst->font_width);
- } else {
- unifont_draw_text(&inst->uctx, inst->fonts[fontid],
- x*inst->font_width+inst->window_border,
- (y*inst->font_height+inst->window_border+
- inst->fonts[0]->ascent),
- text, len, widefactor > 1,
- bold, inst->font_width);
- }
-
- if (attr & ATTR_UNDER) {
- int uheight = inst->fonts[0]->ascent + 1;
- if (uheight >= inst->font_height)
- uheight = inst->font_height - 1;
- draw_line(inst, x*inst->font_width+inst->window_border,
- y*inst->font_height + uheight + inst->window_border,
- (x+len)*widefactor*inst->font_width-1+inst->window_border,
- y*inst->font_height + uheight + inst->window_border);
- }
-
- if (attr & ATTR_STRIKE) {
- int sheight = inst->fonts[fontid]->strikethrough_y;
- draw_line(inst, x*inst->font_width+inst->window_border,
- y*inst->font_height + sheight + inst->window_border,
- (x+len)*widefactor*inst->font_width-1+inst->window_border,
- y*inst->font_height + sheight + inst->window_border);
- }
-
- if ((lattr & LATTR_MODE) != LATTR_NORM) {
- draw_stretch_after(inst,
- x*inst->font_width+inst->window_border,
- y*inst->font_height+inst->window_border,
- rlen*widefactor*inst->font_width, true,
- inst->font_height,
- ((lattr & LATTR_MODE) != LATTR_WIDE),
- ((lattr & LATTR_MODE) == LATTR_BOT));
- }
-}
-
-static void gtkwin_draw_text(
- TermWin *tw, int x, int y, wchar_t *text, int len,
- unsigned long attr, int lattr, truecolour truecolour)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- int widefactor;
-
- do_text_internal(inst, x, y, text, len, attr, lattr, truecolour);
-
- if (attr & ATTR_WIDE) {
- widefactor = 2;
- } else {
- widefactor = 1;
- }
-
- if ((lattr & LATTR_MODE) != LATTR_NORM) {
- x *= 2;
- if (x >= inst->term->cols)
- return;
- if (x + len*2*widefactor > inst->term->cols)
- len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */
- len *= 2;
- }
-
- draw_update(inst,
- x*inst->font_width+inst->window_border,
- y*inst->font_height+inst->window_border,
- len*widefactor*inst->font_width, inst->font_height);
-}
-
-static void gtkwin_draw_cursor(
- TermWin *tw, int x, int y, wchar_t *text, int len,
- unsigned long attr, int lattr, truecolour truecolour)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- bool active, passive;
- int widefactor;
-
- if (attr & TATTR_PASCURS) {
- attr &= ~TATTR_PASCURS;
- passive = true;
- } else
- passive = false;
- if ((attr & TATTR_ACTCURS) && inst->cursor_type != 0) {
- attr &= ~TATTR_ACTCURS;
- active = true;
- } else
- active = false;
- do_text_internal(inst, x, y, text, len, attr, lattr, truecolour);
-
- if (attr & TATTR_COMBINING)
- len = 1;
-
- if (attr & ATTR_WIDE) {
- widefactor = 2;
- } else {
- widefactor = 1;
- }
-
- if ((lattr & LATTR_MODE) != LATTR_NORM) {
- x *= 2;
- if (x >= inst->term->cols)
- return;
- if (x + len*2*widefactor > inst->term->cols)
- len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */
- len *= 2;
- }
-
- if (inst->cursor_type == 0) {
- /*
- * An active block cursor will already have been done by
- * the above do_text call, so we only need to do anything
- * if it's passive.
- */
- if (passive) {
- draw_set_colour(inst, 261, false);
- draw_rectangle(inst, false,
- x*inst->font_width+inst->window_border,
- y*inst->font_height+inst->window_border,
- len*widefactor*inst->font_width-1,
- inst->font_height-1);
- }
- } else {
- int uheight;
- int startx, starty, dx, dy, length, i;
-
- int char_width;
-
- if ((attr & ATTR_WIDE) || (lattr & LATTR_MODE) != LATTR_NORM)
- char_width = 2*inst->font_width;
- else
- char_width = inst->font_width;
-
- if (inst->cursor_type == 1) {
- uheight = inst->fonts[0]->ascent + 1;
- if (uheight >= inst->font_height)
- uheight = inst->font_height - 1;
-
- startx = x * inst->font_width + inst->window_border;
- starty = y * inst->font_height + inst->window_border + uheight;
- dx = 1;
- dy = 0;
- length = len * widefactor * char_width;
- } else {
- int xadjust = 0;
- if (attr & TATTR_RIGHTCURS)
- xadjust = char_width - 1;
- startx = x * inst->font_width + inst->window_border + xadjust;
- starty = y * inst->font_height + inst->window_border;
- dx = 0;
- dy = 1;
- length = inst->font_height;
- }
-
- draw_set_colour(inst, 261, false);
- if (passive) {
- for (i = 0; i < length; i++) {
- if (i % 2 == 0) {
- draw_point(inst, startx, starty);
- }
- startx += dx;
- starty += dy;
- }
- } else if (active) {
- draw_line(inst, startx, starty,
- startx + (length-1) * dx, starty + (length-1) * dy);
- } /* else no cursor (e.g., blinked off) */
- }
-
- draw_update(inst,
- x*inst->font_width+inst->window_border,
- y*inst->font_height+inst->window_border,
- len*widefactor*inst->font_width, inst->font_height);
-
-#if GTK_CHECK_VERSION(2,0,0)
- {
- GdkRectangle cursorrect;
- cursorrect.x = x*inst->font_width+inst->window_border;
- cursorrect.y = y*inst->font_height+inst->window_border;
- cursorrect.width = len*widefactor*inst->font_width;
- cursorrect.height = inst->font_height;
- gtk_im_context_set_cursor_location(inst->imc, &cursorrect);
- }
-#endif
-}
-
-#if !GTK_CHECK_VERSION(2,0,0)
-/*
- * For GTK 1, manual code to scale an in-memory XPM, producing a new
- * one as output. It will be ugly, but good enough to use as a trust
- * sigil.
- */
-struct XpmHolder {
- char **strings;
- size_t nstrings;
-};
-
-static void xpmholder_free(XpmHolder *xh)
-{
- for (size_t i = 0; i < xh->nstrings; i++)
- sfree(xh->strings[i]);
- sfree(xh->strings);
- sfree(xh);
-}
-
-static XpmHolder *xpm_scale(const char *const *xpm, int wo, int ho)
-{
- /* Get image dimensions, # colours, and chars-per-pixel */
- int wi = 0, hi = 0, nc = 0, cpp = 0;
- int retd = sscanf(xpm[0], "%d %d %d %d", &wi, &hi, &nc, &cpp);
- assert(retd == 4);
-
- /* Make output XpmHolder */
- XpmHolder *xh = snew(XpmHolder);
- xh->nstrings = 1 + nc + ho;
- xh->strings = snewn(xh->nstrings, char *);
-
- /* Set up header */
- xh->strings[0] = dupprintf("%d %d %d %d", wo, ho, nc, cpp);
- for (int i = 0; i < nc; i++)
- xh->strings[1 + i] = dupstr(xpm[1 + i]);
-
- /* Scale image */
- for (int yo = 0; yo < ho; yo++) {
- int yi = yo * hi / ho;
- char *ro = snewn(cpp * wo + 1, char);
- ro[cpp * wo] = '\0';
- xh->strings[1 + nc + yo] = ro;
- const char *ri = xpm[1 + nc + yi];
-
- for (int xo = 0; xo < wo; xo++) {
- int xi = xo * wi / wo;
- memcpy(ro + cpp * xo, ri + cpp * xi, cpp);
- }
- }
-
- return xh;
-}
-#endif /* !GTK_CHECK_VERSION(2,0,0) */
-
-static void gtkwin_draw_trust_sigil(TermWin *tw, int cx, int cy)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
-
- int x = cx * inst->font_width + inst->window_border;
- int y = cy * inst->font_height + inst->window_border;
- int w = 2*inst->font_width, h = inst->font_height;
-
- if (inst->trust_sigil_w != w || inst->trust_sigil_h != h ||
-#if GTK_CHECK_VERSION(2,0,0)
- !inst->trust_sigil_pb
-#else
- !inst->trust_sigil_pm
-#endif
- ) {
-
-#if GTK_CHECK_VERSION(2,0,0)
- if (inst->trust_sigil_pb)
- g_object_unref(G_OBJECT(inst->trust_sigil_pb));
-#else
- if (inst->trust_sigil_pm)
- gdk_pixmap_unref(inst->trust_sigil_pm);
-#endif
-
- int best_icon_index = 0;
- unsigned score = UINT_MAX;
- for (int i = 0; i < n_main_icon; i++) {
- int iw, ih;
- if (sscanf(main_icon[i][0], "%d %d", &iw, &ih) == 2) {
- int this_excess = (iw + ih) - (w + h);
- unsigned this_score = (abs(this_excess) |
- (this_excess > 0 ? 0 : 0x80000000U));
- if (this_score < score) {
- best_icon_index = i;
- score = this_score;
- }
- }
- }
-
-#if GTK_CHECK_VERSION(2,0,0)
- GdkPixbuf *icon_unscaled = gdk_pixbuf_new_from_xpm_data(
- (const gchar **)main_icon[best_icon_index]);
- inst->trust_sigil_pb = gdk_pixbuf_scale_simple(
- icon_unscaled, w, h, GDK_INTERP_BILINEAR);
- g_object_unref(G_OBJECT(icon_unscaled));
-#else
- XpmHolder *xh = xpm_scale(main_icon[best_icon_index], w, h);
- inst->trust_sigil_pm = gdk_pixmap_create_from_xpm_d(
- gtk_widget_get_window(inst->window), NULL,
- &inst->cols[258], xh->strings);
- xpmholder_free(xh);
-#endif
-
- inst->trust_sigil_w = w;
- inst->trust_sigil_h = h;
- }
-
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
-#if GTK_CHECK_VERSION(2,0,0)
- gdk_draw_pixbuf(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc,
- inst->trust_sigil_pb, 0, 0, x, y, w, h,
- GDK_RGB_DITHER_NORMAL, 0, 0);
-#else
- gdk_draw_pixmap(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc,
- inst->trust_sigil_pm, 0, 0, x, y, w, h);
-#endif
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- inst->uctx.u.cairo.widget = GTK_WIDGET(inst->area);
- cairo_save(inst->uctx.u.cairo.cr);
- cairo_translate(inst->uctx.u.cairo.cr, x, y);
- gdk_cairo_set_source_pixbuf(inst->uctx.u.cairo.cr,
- inst->trust_sigil_pb, 0, 0);
- cairo_rectangle(inst->uctx.u.cairo.cr, 0, 0, w, h);
- cairo_fill(inst->uctx.u.cairo.cr);
- cairo_restore(inst->uctx.u.cairo.cr);
- }
-#endif
-
- draw_update(inst, x, y, w, h);
-}
-
-GdkCursor *make_mouse_ptr(GtkFrontend *inst, int cursor_val)
-{
- if (cursor_val == -1) {
-#if GTK_CHECK_VERSION(2,16,0)
- cursor_val = GDK_BLANK_CURSOR;
-#else
- /*
- * Work around absence of GDK_BLANK_CURSOR by inventing a
- * blank pixmap.
- */
- GdkCursor *ret;
- GdkColor bg = { 0, 0, 0, 0 };
- GdkPixmap *pm = gdk_pixmap_new(NULL, 1, 1, 1);
- GdkGC *gc = gdk_gc_new(pm);
- gdk_gc_set_foreground(gc, &bg);
- gdk_draw_rectangle(pm, gc, 1, 0, 0, 1, 1);
- gdk_gc_unref(gc);
- ret = gdk_cursor_new_from_pixmap(pm, pm, &bg, &bg, 1, 1);
- gdk_pixmap_unref(pm);
- return ret;
-#endif
- }
-
- return gdk_cursor_new(cursor_val);
-}
-
-void modalfatalbox(const char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "FATAL ERROR: ");
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
- exit(1);
-}
-
-static const char *gtk_seat_get_x_display(Seat *seat)
-{
- return gdk_get_display();
-}
-
-#ifndef NOT_X_WINDOWS
-static bool gtk_seat_get_windowid(Seat *seat, long *id)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- GdkWindow *window = gtk_widget_get_window(inst->area);
- if (!GDK_IS_X11_WINDOW(window))
- return false;
- *id = GDK_WINDOW_XID(window);
- return true;
-}
-#endif
-
-char *setup_fonts_ucs(GtkFrontend *inst)
-{
- bool shadowbold = conf_get_bool(inst->conf, CONF_shadowbold);
- int shadowboldoffset = conf_get_int(inst->conf, CONF_shadowboldoffset);
- FontSpec *fs;
- unifont *fonts[4];
- int i;
-
- fs = conf_get_fontspec(inst->conf, CONF_font);
- fonts[0] = multifont_create(inst->area, fs->name, false, false,
- shadowboldoffset, shadowbold);
- if (!fonts[0]) {
- return dupprintf("unable to load font \"%s\"", fs->name);
- }
-
- fs = conf_get_fontspec(inst->conf, CONF_boldfont);
- if (shadowbold || !fs->name[0]) {
- fonts[1] = NULL;
- } else {
- fonts[1] = multifont_create(inst->area, fs->name, false, true,
- shadowboldoffset, shadowbold);
- if (!fonts[1]) {
- if (fonts[0])
- unifont_destroy(fonts[0]);
- return dupprintf("unable to load bold font \"%s\"", fs->name);
- }
- }
-
- fs = conf_get_fontspec(inst->conf, CONF_widefont);
- if (fs->name[0]) {
- fonts[2] = multifont_create(inst->area, fs->name, true, false,
- shadowboldoffset, shadowbold);
- if (!fonts[2]) {
- for (i = 0; i < 2; i++)
- if (fonts[i])
- unifont_destroy(fonts[i]);
- return dupprintf("unable to load wide font \"%s\"", fs->name);
- }
- } else {
- fonts[2] = NULL;
- }
-
- fs = conf_get_fontspec(inst->conf, CONF_wideboldfont);
- if (shadowbold || !fs->name[0]) {
- fonts[3] = NULL;
- } else {
- fonts[3] = multifont_create(inst->area, fs->name, true, true,
- shadowboldoffset, shadowbold);
- if (!fonts[3]) {
- for (i = 0; i < 3; i++)
- if (fonts[i])
- unifont_destroy(fonts[i]);
- return dupprintf("unable to load wide bold font \"%s\"", fs->name);
- }
- }
-
- /*
- * Now we've got past all the possible error conditions, we can
- * actually update our state.
- */
-
- for (i = 0; i < 4; i++) {
- if (inst->fonts[i])
- unifont_destroy(inst->fonts[i]);
- inst->fonts[i] = fonts[i];
- }
-
- if (inst->font_width != inst->fonts[0]->width ||
- inst->font_height != inst->fonts[0]->height) {
-
- inst->font_width = inst->fonts[0]->width;
- inst->font_height = inst->fonts[0]->height;
-
- /*
- * The font size has changed, so force the next call to
- * drawing_area_setup to regenerate the backing surface.
- */
- inst->drawing_area_setup_needed = true;
- }
-
- inst->direct_to_font = init_ucs(&inst->ucsdata,
- conf_get_str(inst->conf, CONF_line_codepage),
- conf_get_bool(inst->conf, CONF_utf8_override),
- inst->fonts[0]->public_charset,
- conf_get_int(inst->conf, CONF_vtmode));
-
- inst->drawtype = inst->fonts[0]->preferred_drawtype;
-
- return NULL;
-}
-
-#if GTK_CHECK_VERSION(3,0,0)
-struct find_app_menu_bar_ctx {
- GtkWidget *area, *menubar;
-};
-static void find_app_menu_bar(GtkWidget *widget, gpointer data)
-{
- struct find_app_menu_bar_ctx *ctx = (struct find_app_menu_bar_ctx *)data;
- if (widget != ctx->area && GTK_IS_MENU_BAR(widget))
- ctx->menubar = widget;
-}
-#endif
-
-static void compute_geom_hints(GtkFrontend *inst, GdkGeometry *geom)
-{
- /*
- * Unused fields in geom.
- */
- geom->max_width = geom->max_height = -1;
- geom->min_aspect = geom->max_aspect = 0;
-
- /*
- * Set up the geometry fields we care about, with reference to
- * just the drawing area. We'll correct for other widgets in a
- * moment.
- */
- geom->min_width = inst->font_width + 2*inst->window_border;
- geom->min_height = inst->font_height + 2*inst->window_border;
- geom->base_width = 2*inst->window_border;
- geom->base_height = 2*inst->window_border;
- geom->width_inc = inst->font_width;
- geom->height_inc = inst->font_height;
-
- /*
- * If we've got a scrollbar visible, then we must include its
- * width as part of the base and min width, and also ensure that
- * our window's minimum height is at least the height required by
- * the scrollbar.
- *
- * In the latter case, we must also take care to arrange that
- * (geom->min_height - geom->base_height) is an integer multiple of
- * geom->height_inc, because if it's not, then some window managers
- * (we know of xfwm4) get confused, with the effect that they
- * resize our window to a height based on min_height instead of
- * base_height, which we then round down and the window ends up
- * too short.
- */
- if (inst->sbar_visible) {
- GtkRequisition req;
- int min_sb_height;
-
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_widget_get_preferred_size(inst->sbar, &req, NULL);
-#else
- gtk_widget_size_request(inst->sbar, &req);
-#endif
-
- /* Compute rounded-up scrollbar height. */
- min_sb_height = req.height;
- min_sb_height += geom->height_inc - 1;
- min_sb_height -= ((min_sb_height - geom->base_height%geom->height_inc)
- % geom->height_inc);
-
- geom->min_width += req.width;
- geom->base_width += req.width;
- if (geom->min_height < min_sb_height)
- geom->min_height = min_sb_height;
- }
-
-#if GTK_CHECK_VERSION(3,0,0)
- /*
- * And if we're running a gtkapp.c based program and
- * GtkApplicationWindow has given us a menu bar inside the window,
- * then we must take that into account as well.
- *
- * In its unbounded wisdom, GtkApplicationWindow doesn't actually
- * give us a direct function call to _find_ the menu bar widget.
- * Fortunately, we can find it by enumerating the children of the
- * top-level window and looking for one we didn't put there
- * ourselves.
- */
- {
- struct find_app_menu_bar_ctx ctx[1];
- ctx->area = inst->area;
- ctx->menubar = NULL;
- gtk_container_foreach(GTK_CONTAINER(inst->window),
- find_app_menu_bar, ctx);
-
- if (ctx->menubar) {
- GtkRequisition req;
- int min_menu_width;
- gtk_widget_get_preferred_size(ctx->menubar, NULL, &req);
-
- /*
- * This time, the height adjustment is easy (the menu bar
- * sits above everything), but we have to take care with
- * the _width_ to ensure we keep min_width and base_width
- * congruent modulo width_inc.
- */
- geom->min_height += req.height;
- geom->base_height += req.height;
-
- min_menu_width = req.width;
- min_menu_width += geom->width_inc - 1;
- min_menu_width -=
- ((min_menu_width - geom->base_width%geom->width_inc)
- % geom->width_inc);
- if (geom->min_width < min_menu_width)
- geom->min_width = min_menu_width;
- }
- }
-#endif
-}
-
-void set_geom_hints(GtkFrontend *inst)
-{
- const struct BackendVtable *vt;
- GdkGeometry geom;
- gint flags = GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE | GDK_HINT_RESIZE_INC;
- compute_geom_hints(inst, &geom);
-#if GTK_CHECK_VERSION(2,0,0)
- if (inst->gotpos)
- flags |= GDK_HINT_USER_POS;
-#endif
- vt = backend_vt_from_proto(conf_get_int(inst->conf, CONF_protocol));
- if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) {
- /* Window resizing forbidden. Set both minimum and maximum
- * dimensions to be the initial size. */
- geom.min_width = inst->width*inst->font_width + 2*inst->window_border;
- geom.min_height = inst->height*inst->font_height + 2*inst->window_border;
- geom.max_width = geom.min_width;
- geom.max_height = geom.min_height;
- flags |= GDK_HINT_MAX_SIZE;
- }
- gtk_window_set_geometry_hints(GTK_WINDOW(inst->window),
- NULL, &geom, flags);
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-static void compute_whole_window_size(GtkFrontend *inst,
- int wchars, int hchars,
- int *wpix, int *hpix)
-{
- GdkGeometry geom;
- compute_geom_hints(inst, &geom);
- if (wpix) *wpix = geom.base_width + wchars * geom.width_inc;
- if (hpix) *hpix = geom.base_height + hchars * geom.height_inc;
-}
-#endif
-
-void clear_scrollback_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- term_clrsb(inst->term);
-}
-
-void reset_terminal_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- term_pwron(inst->term, true);
- if (inst->ldisc)
- ldisc_echoedit_update(inst->ldisc);
-}
-
-void copy_clipboard_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- static const int clips[] = { MENU_CLIPBOARD };
- term_request_copy(inst->term, clips, lenof(clips));
-}
-
-void paste_clipboard_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- term_request_paste(inst->term, MENU_CLIPBOARD);
-}
-
-void copy_all_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- static const int clips[] = { COPYALL_CLIPBOARDS };
- term_copyall(inst->term, clips, lenof(clips));
-}
-
-void special_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- SessionSpecial *sc = g_object_get_data(G_OBJECT(item), "user-data");
-
- if (inst->backend)
- backend_special(inst->backend, sc->code, sc->arg);
-}
-
-void about_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- about_box(inst->window);
-}
-
-void event_log_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- showeventlog(inst->eventlogstuff, inst->window);
-}
-
-void setup_clipboards(GtkFrontend *inst, Terminal *term, Conf *conf)
-{
- assert(term->mouse_select_clipboards[0] == CLIP_LOCAL);
-
- term->n_mouse_select_clipboards = 1;
- term->mouse_select_clipboards[
- term->n_mouse_select_clipboards++] = MOUSE_SELECT_CLIPBOARD;
-
- if (conf_get_bool(conf, CONF_mouseautocopy)) {
- term->mouse_select_clipboards[
- term->n_mouse_select_clipboards++] = CLIP_CLIPBOARD;
- }
-
- set_clipboard_atom(inst, CLIP_CUSTOM_1, GDK_NONE);
- set_clipboard_atom(inst, CLIP_CUSTOM_2, GDK_NONE);
- set_clipboard_atom(inst, CLIP_CUSTOM_3, GDK_NONE);
-
- switch (conf_get_int(conf, CONF_mousepaste)) {
- case CLIPUI_IMPLICIT:
- term->mouse_paste_clipboard = MOUSE_PASTE_CLIPBOARD;
- break;
- case CLIPUI_EXPLICIT:
- term->mouse_paste_clipboard = CLIP_CLIPBOARD;
- break;
- case CLIPUI_CUSTOM:
- term->mouse_paste_clipboard = CLIP_CUSTOM_1;
- set_clipboard_atom(inst, CLIP_CUSTOM_1,
- gdk_atom_intern(
- conf_get_str(conf, CONF_mousepaste_custom),
- false));
- break;
- default:
- term->mouse_paste_clipboard = CLIP_NULL;
- break;
- }
-
- if (conf_get_int(conf, CONF_ctrlshiftins) == CLIPUI_CUSTOM) {
- GdkAtom atom = gdk_atom_intern(
- conf_get_str(conf, CONF_ctrlshiftins_custom), false);
- struct clipboard_state *state = clipboard_from_atom(inst, atom);
- if (state) {
- inst->clipboard_ctrlshiftins = state->clipboard;
- } else {
- inst->clipboard_ctrlshiftins = CLIP_CUSTOM_2;
- set_clipboard_atom(inst, CLIP_CUSTOM_2, atom);
- }
- }
-
- if (conf_get_int(conf, CONF_ctrlshiftcv) == CLIPUI_CUSTOM) {
- GdkAtom atom = gdk_atom_intern(
- conf_get_str(conf, CONF_ctrlshiftcv_custom), false);
- struct clipboard_state *state = clipboard_from_atom(inst, atom);
- if (state) {
- inst->clipboard_ctrlshiftins = state->clipboard;
- } else {
- inst->clipboard_ctrlshiftcv = CLIP_CUSTOM_3;
- set_clipboard_atom(inst, CLIP_CUSTOM_3, atom);
- }
- }
-}
-
-struct after_change_settings_dialog_ctx {
- GtkFrontend *inst;
- Conf *newconf;
-};
-
-static void after_change_settings_dialog(void *vctx, int retval);
-
-void change_settings_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- struct after_change_settings_dialog_ctx *ctx;
- GtkWidget *dialog;
- char *title;
-
- if (find_and_raise_dialog(inst, DIALOG_SLOT_RECONFIGURE))
- return;
-
- title = dupcat(appname, " Reconfiguration");
-
- ctx = snew(struct after_change_settings_dialog_ctx);
- ctx->inst = inst;
- ctx->newconf = conf_copy(inst->conf);
-
- term_pre_reconfig(inst->term, ctx->newconf);
-
- dialog = create_config_box(
- title, ctx->newconf, true,
- inst->backend ? backend_cfg_info(inst->backend) : 0,
- after_change_settings_dialog, ctx);
- register_dialog(&inst->seat, DIALOG_SLOT_RECONFIGURE, dialog);
-
- sfree(title);
-}
-
-static void after_change_settings_dialog(void *vctx, int retval)
-{
- struct after_change_settings_dialog_ctx ctx =
- *(struct after_change_settings_dialog_ctx *)vctx;
- GtkFrontend *inst = ctx.inst;
- Conf *oldconf = inst->conf, *newconf = ctx.newconf;
- bool need_size;
-
- sfree(vctx); /* we've copied this already */
-
- unregister_dialog(&inst->seat, DIALOG_SLOT_RECONFIGURE);
-
- if (retval > 0) {
- inst->conf = newconf;
-
- /* Pass new config data to the logging module */
- log_reconfig(inst->logctx, inst->conf);
- /*
- * Flush the line discipline's edit buffer in the case
- * where local editing has just been disabled.
- */
- if (inst->ldisc) {
- ldisc_configure(inst->ldisc, inst->conf);
- ldisc_echoedit_update(inst->ldisc);
- }
- /* Pass new config data to the terminal */
- term_reconfig(inst->term, inst->conf);
- setup_clipboards(inst, inst->term, inst->conf);
- /* Pass new config data to the back end */
- if (inst->backend)
- backend_reconfig(inst->backend, inst->conf);
-
- cache_conf_values(inst);
-
- need_size = false;
-
- /*
- * If the scrollbar needs to be shown, hidden, or moved
- * from one end to the other of the window, do so now.
- */
- if (conf_get_bool(oldconf, CONF_scrollbar) !=
- conf_get_bool(newconf, CONF_scrollbar)) {
- show_scrollbar(inst, conf_get_bool(newconf, CONF_scrollbar));
- need_size = true;
- }
- if (conf_get_bool(oldconf, CONF_scrollbar_on_left) !=
- conf_get_bool(newconf, CONF_scrollbar_on_left)) {
- gtk_box_reorder_child(inst->hbox, inst->sbar,
- conf_get_bool(newconf, CONF_scrollbar_on_left)
- ? 0 : 1);
- }
-
- /*
- * Redo the whole tangled fonts and Unicode mess if
- * necessary.
- */
- if (strcmp(conf_get_fontspec(oldconf, CONF_font)->name,
- conf_get_fontspec(newconf, CONF_font)->name) ||
- strcmp(conf_get_fontspec(oldconf, CONF_boldfont)->name,
- conf_get_fontspec(newconf, CONF_boldfont)->name) ||
- strcmp(conf_get_fontspec(oldconf, CONF_widefont)->name,
- conf_get_fontspec(newconf, CONF_widefont)->name) ||
- strcmp(conf_get_fontspec(oldconf, CONF_wideboldfont)->name,
- conf_get_fontspec(newconf, CONF_wideboldfont)->name) ||
- strcmp(conf_get_str(oldconf, CONF_line_codepage),
- conf_get_str(newconf, CONF_line_codepage)) ||
- conf_get_bool(oldconf, CONF_utf8_override) !=
- conf_get_bool(newconf, CONF_utf8_override) ||
- conf_get_int(oldconf, CONF_vtmode) !=
- conf_get_int(newconf, CONF_vtmode) ||
- conf_get_bool(oldconf, CONF_shadowbold) !=
- conf_get_bool(newconf, CONF_shadowbold) ||
- conf_get_int(oldconf, CONF_shadowboldoffset) !=
- conf_get_int(newconf, CONF_shadowboldoffset)) {
- char *errmsg = setup_fonts_ucs(inst);
- if (errmsg) {
- char *msgboxtext =
- dupprintf("Could not change fonts in terminal window: %s\n",
- errmsg);
- create_message_box(
- inst->window, "Font setup error", msgboxtext,
- string_width("Could not change fonts in terminal window:"),
- false, &buttons_ok, trivial_post_dialog_fn, NULL);
- sfree(msgboxtext);
- sfree(errmsg);
- } else {
- need_size = true;
- }
- }
-
- /*
- * Resize the window.
- */
- if (conf_get_int(oldconf, CONF_width) !=
- conf_get_int(newconf, CONF_width) ||
- conf_get_int(oldconf, CONF_height) !=
- conf_get_int(newconf, CONF_height) ||
- conf_get_int(oldconf, CONF_window_border) !=
- conf_get_int(newconf, CONF_window_border) ||
- need_size) {
- set_geom_hints(inst);
- win_request_resize(&inst->termwin,
- conf_get_int(newconf, CONF_width),
- conf_get_int(newconf, CONF_height));
- } else {
- /*
- * The above will have caused a call to term_size() for
- * us if it happened. If the user has fiddled with only
- * the scrollback size, the above will not have
- * happened and we will need an explicit term_size()
- * here.
- */
- if (conf_get_int(oldconf, CONF_savelines) !=
- conf_get_int(newconf, CONF_savelines))
- term_size(inst->term, inst->term->rows, inst->term->cols,
- conf_get_int(newconf, CONF_savelines));
- }
-
- term_invalidate(inst->term);
-
- /*
- * We do an explicit full redraw here to ensure the window
- * border has been redrawn as well as the text area.
- */
- gtk_widget_queue_draw(inst->area);
-
- conf_free(oldconf);
- } else {
- conf_free(newconf);
- }
-}
-
-static void change_font_size(GtkFrontend *inst, int increment)
-{
- static const int conf_keys[lenof(inst->fonts)] = {
- CONF_font, CONF_boldfont, CONF_widefont, CONF_wideboldfont,
- };
- FontSpec *oldfonts[lenof(inst->fonts)];
- FontSpec *newfonts[lenof(inst->fonts)];
- char *errmsg = NULL;
- int i;
-
- for (i = 0; i < lenof(newfonts); i++)
- oldfonts[i] = newfonts[i] = NULL;
-
- for (i = 0; i < lenof(inst->fonts); i++) {
- if (inst->fonts[i]) {
- char *newname = unifont_size_increment(inst->fonts[i], increment);
- if (!newname)
- goto cleanup;
- newfonts[i] = fontspec_new(newname);
- sfree(newname);
- }
- }
-
- for (i = 0; i < lenof(newfonts); i++) {
- if (newfonts[i]) {
- oldfonts[i] = fontspec_copy(
- conf_get_fontspec(inst->conf, conf_keys[i]));
- conf_set_fontspec(inst->conf, conf_keys[i], newfonts[i]);
- }
- }
-
- errmsg = setup_fonts_ucs(inst);
- if (errmsg)
- goto cleanup;
-
- /* Success, so suppress putting everything back */
- for (i = 0; i < lenof(newfonts); i++) {
- if (oldfonts[i]) {
- fontspec_free(oldfonts[i]);
- oldfonts[i] = NULL;
- }
- }
-
- set_geom_hints(inst);
- win_request_resize(&inst->termwin, conf_get_int(inst->conf, CONF_width),
- conf_get_int(inst->conf, CONF_height));
- term_invalidate(inst->term);
- gtk_widget_queue_draw(inst->area);
-
- cleanup:
- for (i = 0; i < lenof(oldfonts); i++) {
- if (oldfonts[i]) {
- conf_set_fontspec(inst->conf, conf_keys[i], oldfonts[i]);
- fontspec_free(oldfonts[i]);
- }
- if (newfonts[i])
- fontspec_free(newfonts[i]);
- }
- sfree(errmsg);
-}
-
-void dup_session_menuitem(GtkMenuItem *item, gpointer gdata)
-{
- GtkFrontend *inst = (GtkFrontend *)gdata;
-
- launch_duplicate_session(inst->conf);
-}
-
-void new_session_menuitem(GtkMenuItem *item, gpointer data)
-{
- launch_new_session();
-}
-
-void restart_session_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
-
- if (!inst->backend) {
- logevent(inst->logctx, "----- Session restarted -----");
- term_pwron(inst->term, false);
- start_backend(inst);
- inst->exited = false;
- }
-}
-
-void saved_session_menuitem(GtkMenuItem *item, gpointer data)
-{
- char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data");
-
- launch_saved_session(str);
-}
-
-void saved_session_freedata(GtkMenuItem *item, gpointer data)
-{
- char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data");
-
- sfree(str);
-}
-
-void app_menu_action(GtkFrontend *frontend, enum MenuAction action)
-{
- GtkFrontend *inst = (GtkFrontend *)frontend;
- switch (action) {
- case MA_COPY:
- copy_clipboard_menuitem(NULL, inst);
- break;
- case MA_PASTE:
- paste_clipboard_menuitem(NULL, inst);
- break;
- case MA_COPY_ALL:
- copy_all_menuitem(NULL, inst);
- break;
- case MA_DUPLICATE_SESSION:
- dup_session_menuitem(NULL, inst);
- break;
- case MA_RESTART_SESSION:
- restart_session_menuitem(NULL, inst);
- break;
- case MA_CHANGE_SETTINGS:
- change_settings_menuitem(NULL, inst);
- break;
- case MA_CLEAR_SCROLLBACK:
- clear_scrollback_menuitem(NULL, inst);
- break;
- case MA_RESET_TERMINAL:
- reset_terminal_menuitem(NULL, inst);
- break;
- case MA_EVENT_LOG:
- event_log_menuitem(NULL, inst);
- break;
- }
-}
-
-static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- struct sesslist sesslist;
- int i;
-
- gtk_container_foreach(GTK_CONTAINER(inst->sessionsmenu),
- (GtkCallback)gtk_widget_destroy, NULL);
-
- get_sesslist(&sesslist, true);
- /* skip sesslist.sessions[0] == Default Settings */
- for (i = 1; i < sesslist.nsessions; i++) {
- GtkWidget *menuitem =
- gtk_menu_item_new_with_label(sesslist.sessions[i]);
- gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem);
- gtk_widget_show(menuitem);
- g_object_set_data(G_OBJECT(menuitem), "user-data",
- dupstr(sesslist.sessions[i]));
- g_signal_connect(G_OBJECT(menuitem), "activate",
- G_CALLBACK(saved_session_menuitem),
- inst);
- g_signal_connect(G_OBJECT(menuitem), "destroy",
- G_CALLBACK(saved_session_freedata),
- inst);
- }
- if (sesslist.nsessions <= 1) {
- GtkWidget *menuitem =
- gtk_menu_item_new_with_label("(No sessions)");
- gtk_widget_set_sensitive(menuitem, false);
- gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem);
- gtk_widget_show(menuitem);
- }
- get_sesslist(&sesslist, false); /* free up */
-}
-
-void set_window_icon(GtkWidget *window, const char *const *const *icon,
- int n_icon)
-{
-#if GTK_CHECK_VERSION(2,0,0)
- GList *iconlist;
- int n;
-#else
- GdkPixmap *iconpm;
- GdkBitmap *iconmask;
-#endif
-
- if (!n_icon)
- return;
-
- gtk_widget_realize(window);
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_window_set_icon(GTK_WINDOW(window),
- gdk_pixbuf_new_from_xpm_data((const gchar **)icon[0]));
-#else
- iconpm = gdk_pixmap_create_from_xpm_d(gtk_widget_get_window(window),
- &iconmask, NULL, (gchar **)icon[0]);
- gdk_window_set_icon(gtk_widget_get_window(window), NULL, iconpm, iconmask);
-#endif
-
-#if GTK_CHECK_VERSION(2,0,0)
- iconlist = NULL;
- for (n = 0; n < n_icon; n++) {
- iconlist =
- g_list_append(iconlist,
- gdk_pixbuf_new_from_xpm_data((const gchar **)
- icon[n]));
- }
- gtk_window_set_icon_list(GTK_WINDOW(window), iconlist);
-#endif
-}
-
-static void free_special_cmd(gpointer data) { sfree(data); }
-
-static void gtk_seat_update_specials_menu(Seat *seat)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- const SessionSpecial *specials;
-
- if (inst->backend)
- specials = backend_get_specials(inst->backend);
- else
- specials = NULL;
-
- /* I believe this disposes of submenus too. */
- gtk_container_foreach(GTK_CONTAINER(inst->specialsmenu),
- (GtkCallback)gtk_widget_destroy, NULL);
- if (specials) {
- int i;
- GtkWidget *menu = inst->specialsmenu;
- /* A lame "stack" for submenus that will do for now. */
- GtkWidget *saved_menu = NULL;
- int nesting = 1;
- for (i = 0; nesting > 0; i++) {
- GtkWidget *menuitem = NULL;
- switch (specials[i].code) {
- case SS_SUBMENU:
- assert (nesting < 2);
- saved_menu = menu; /* XXX lame stacking */
- menu = gtk_menu_new();
- menuitem = gtk_menu_item_new_with_label(specials[i].name);
- gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
- gtk_container_add(GTK_CONTAINER(saved_menu), menuitem);
- gtk_widget_show(menuitem);
- menuitem = NULL;
- nesting++;
- break;
- case SS_EXITMENU:
- nesting--;
- if (nesting) {
- menu = saved_menu; /* XXX lame stacking */
- saved_menu = NULL;
- }
- break;
- case SS_SEP:
- menuitem = gtk_menu_item_new();
- break;
- default: {
- menuitem = gtk_menu_item_new_with_label(specials[i].name);
- SessionSpecial *sc = snew(SessionSpecial);
- *sc = specials[i]; /* structure copy */
- g_object_set_data_full(G_OBJECT(menuitem), "user-data",
- sc, free_special_cmd);
- g_signal_connect(G_OBJECT(menuitem), "activate",
- G_CALLBACK(special_menuitem), inst);
- break;
- }
- }
- if (menuitem) {
- gtk_container_add(GTK_CONTAINER(menu), menuitem);
- gtk_widget_show(menuitem);
- }
- }
- gtk_widget_show(inst->specialsitem1);
- gtk_widget_show(inst->specialsitem2);
- } else {
- gtk_widget_hide(inst->specialsitem1);
- gtk_widget_hide(inst->specialsitem2);
- }
-}
-
-static void start_backend(GtkFrontend *inst)
-{
- const struct BackendVtable *vt;
- char *error, *realhost;
-
- vt = select_backend(inst->conf);
-
- seat_set_trust_status(&inst->seat, true);
- error = backend_init(vt, &inst->seat, &inst->backend,
- inst->logctx, inst->conf,
- conf_get_str(inst->conf, CONF_host),
- conf_get_int(inst->conf, CONF_port),
- &realhost,
- conf_get_bool(inst->conf, CONF_tcp_nodelay),
- conf_get_bool(inst->conf, CONF_tcp_keepalives));
-
- if (error) {
- seat_connection_fatal(&inst->seat,
- "Unable to open connection to %s:\n%s",
- conf_dest(inst->conf), error);
- sfree(error);
- inst->exited = true;
- return;
- }
-
- term_setup_window_titles(inst->term, realhost);
- sfree(realhost);
-
- term_provide_backend(inst->term, inst->backend);
-
- inst->ldisc = ldisc_create(inst->conf, inst->term, inst->backend,
- &inst->seat);
-
- gtk_widget_set_sensitive(inst->restartitem, false);
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-static void get_monitor_geometry(GtkWidget *widget, GdkRectangle *geometry)
-{
-#if GTK_CHECK_VERSION(3,4,0)
- GdkDisplay *display = gtk_widget_get_display(widget);
- GdkWindow *gdkwindow = gtk_widget_get_window(widget);
-# if GTK_CHECK_VERSION(3,22,0)
- GdkMonitor *monitor;
- if (gdkwindow)
- monitor = gdk_display_get_monitor_at_window(display, gdkwindow);
- else
- monitor = gdk_display_get_monitor(display, 0);
- gdk_monitor_get_geometry(monitor, geometry);
-# else
- GdkScreen *screen = gdk_display_get_default_screen(display);
- gint monitor_num = gdk_screen_get_monitor_at_window(screen, gdkwindow);
- gdk_screen_get_monitor_geometry(screen, monitor_num, geometry);
-# endif
-#else
- geometry->x = geometry->y = 0;
- geometry->width = gdk_screen_width();
- geometry->height = gdk_screen_height();
-#endif
-}
-#endif
-
-static const TermWinVtable gtk_termwin_vt = {
- .setup_draw_ctx = gtkwin_setup_draw_ctx,
- .draw_text = gtkwin_draw_text,
- .draw_cursor = gtkwin_draw_cursor,
- .draw_trust_sigil = gtkwin_draw_trust_sigil,
- .char_width = gtkwin_char_width,
- .free_draw_ctx = gtkwin_free_draw_ctx,
- .set_cursor_pos = gtkwin_set_cursor_pos,
- .set_raw_mouse_mode = gtkwin_set_raw_mouse_mode,
- .set_raw_mouse_mode_pointer = gtkwin_set_raw_mouse_mode_pointer,
- .set_scrollbar = gtkwin_set_scrollbar,
- .bell = gtkwin_bell,
- .clip_write = gtkwin_clip_write,
- .clip_request_paste = gtkwin_clip_request_paste,
- .refresh = gtkwin_refresh,
- .request_resize = gtkwin_request_resize,
- .set_title = gtkwin_set_title,
- .set_icon_title = gtkwin_set_icon_title,
- .set_minimised = gtkwin_set_minimised,
- .set_maximised = gtkwin_set_maximised,
- .move = gtkwin_move,
- .set_zorder = gtkwin_set_zorder,
- .palette_set = gtkwin_palette_set,
- .palette_get_overrides = gtkwin_palette_get_overrides,
-};
-
-void new_session_window(Conf *conf, const char *geometry_string)
-{
- GtkFrontend *inst;
-
- prepare_session(conf);
-
- /*
- * Create an instance structure and initialise to zeroes
- */
- inst = snew(GtkFrontend);
- memset(inst, 0, sizeof(*inst));
-#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
- inst->cdi_headtail.next = inst->cdi_headtail.prev = &inst->cdi_headtail;
-#endif
- inst->alt_keycode = -1; /* this one needs _not_ to be zero */
- inst->busy_status = BUSY_NOT;
- inst->conf = conf;
- inst->wintitle = inst->icontitle = NULL;
- inst->drawtype = DRAWTYPE_DEFAULT;
-#if GTK_CHECK_VERSION(3,4,0)
- inst->cumulative_scroll = 0.0;
-#endif
- inst->drawing_area_setup_needed = true;
-
- inst->termwin.vt = &gtk_termwin_vt;
- inst->seat.vt = &gtk_seat_vt;
- inst->logpolicy.vt = &gtk_logpolicy_vt;
-
-#ifndef NOT_X_WINDOWS
- inst->disp = get_x11_display();
- if (geometry_string) {
- int flags, x, y;
- unsigned int w, h;
- flags = XParseGeometry(geometry_string, &x, &y, &w, &h);
- if (flags & WidthValue)
- conf_set_int(conf, CONF_width, w);
- if (flags & HeightValue)
- conf_set_int(conf, CONF_height, h);
-
- if (flags & (XValue | YValue)) {
- inst->xpos = x;
- inst->ypos = y;
- inst->gotpos = true;
- inst->gravity = ((flags & XNegative ? 1 : 0) |
- (flags & YNegative ? 2 : 0));
- }
- }
-#endif
-
- if (!compound_text_atom)
- compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", false);
- if (!utf8_string_atom)
- utf8_string_atom = gdk_atom_intern("UTF8_STRING", false);
- if (!clipboard_atom)
- clipboard_atom = gdk_atom_intern("CLIPBOARD", false);
-
- inst->area = gtk_drawing_area_new();
- gtk_widget_set_name(GTK_WIDGET(inst->area), "drawing-area");
-
- {
- char *errmsg = setup_fonts_ucs(inst);
- if (errmsg) {
- window_setup_error(errmsg);
- sfree(errmsg);
- gtk_widget_destroy(inst->area);
- sfree(inst);
- return;
- }
- }
-
-#if GTK_CHECK_VERSION(2,0,0)
- inst->imc = gtk_im_multicontext_new();
-#endif
-
- inst->window = make_gtk_toplevel_window(inst);
- gtk_widget_set_name(GTK_WIDGET(inst->window), "top-level");
- {
- const char *winclass = conf_get_str(inst->conf, CONF_winclass);
- if (*winclass) {
-#if GTK_CHECK_VERSION(3,22,0)
-#ifndef NOT_X_WINDOWS
- GdkWindow *gdkwin;
- gtk_widget_realize(GTK_WIDGET(inst->window));
- gdkwin = gtk_widget_get_window(GTK_WIDGET(inst->window));
- if (inst->disp && gdk_window_ensure_native(gdkwin)) {
- XClassHint *xch = XAllocClassHint();
- xch->res_name = (char *)winclass;
- xch->res_class = (char *)winclass;
- XSetClassHint(inst->disp, GDK_WINDOW_XID(gdkwin), xch);
- XFree(xch);
- }
-#endif
- /*
- * If we do have NOT_X_WINDOWS set, then we don't have any
- * function in GTK 3.22 equivalent to the above. But then,
- * surely in that situation the deprecated
- * gtk_window_set_wmclass wouldn't have done anything
- * meaningful in previous GTKs either.
- */
-#else
- gtk_window_set_wmclass(GTK_WINDOW(inst->window),
- winclass, winclass);
-#endif
- }
- }
-
- inst->width = conf_get_int(inst->conf, CONF_width);
- inst->height = conf_get_int(inst->conf, CONF_height);
- cache_conf_values(inst);
-
- init_clipboard(inst);
-
- inst->sbar_adjust = GTK_ADJUSTMENT(gtk_adjustment_new(0,0,0,0,0,0));
- inst->sbar = gtk_vscrollbar_new(inst->sbar_adjust);
- inst->hbox = GTK_BOX(gtk_hbox_new(false, 0));
- /*
- * We always create the scrollbar; it remains invisible if
- * unwanted, so we can pop it up quickly if it suddenly becomes
- * desirable.
- */
- if (conf_get_bool(inst->conf, CONF_scrollbar_on_left))
- gtk_box_pack_start(inst->hbox, inst->sbar, false, false, 0);
- gtk_box_pack_start(inst->hbox, inst->area, true, true, 0);
- if (!conf_get_bool(inst->conf, CONF_scrollbar_on_left))
- gtk_box_pack_start(inst->hbox, inst->sbar, false, false, 0);
-
- gtk_container_add(GTK_CONTAINER(inst->window), GTK_WIDGET(inst->hbox));
-
- gtk_widget_show(inst->area);
- show_scrollbar(inst, conf_get_bool(inst->conf, CONF_scrollbar));
- gtk_widget_show(GTK_WIDGET(inst->hbox));
-
- /*
- * We must call gtk_widget_realize before setting up the geometry
- * hints, so that GtkApplicationWindow will have actually created
- * its menu bar (if it's going to) and hence compute_geom_hints
- * can find it to take its size into account.
- */
- gtk_widget_realize(inst->window);
- set_geom_hints(inst);
-
-#if GTK_CHECK_VERSION(3,0,0)
- {
- int wp, hp;
- compute_whole_window_size(inst, inst->width, inst->height, &wp, &hp);
- gtk_window_set_default_size(GTK_WINDOW(inst->window), wp, hp);
- }
-#else
- {
- int w = inst->font_width * inst->width + 2*inst->window_border;
- int h = inst->font_height * inst->height + 2*inst->window_border;
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_widget_set_size_request(inst->area, w, h);
-#else
- gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), w, h);
-#endif
- }
-#endif
-
-#if GTK_CHECK_VERSION(2,0,0)
- if (inst->gotpos) {
- static const GdkGravity gravities[] = {
- GDK_GRAVITY_NORTH_WEST,
- GDK_GRAVITY_NORTH_EAST,
- GDK_GRAVITY_SOUTH_WEST,
- GDK_GRAVITY_SOUTH_EAST,
- };
- int x = inst->xpos, y = inst->ypos;
- int wp, hp;
- GdkRectangle monitor_geometry;
- compute_whole_window_size(inst, inst->width, inst->height, &wp, &hp);
- get_monitor_geometry(GTK_WIDGET(inst->window), &monitor_geometry);
- if (inst->gravity & 1) x += (monitor_geometry.width - wp);
- if (inst->gravity & 2) y += (monitor_geometry.height - hp);
- gtk_window_set_gravity(GTK_WINDOW(inst->window),
- gravities[inst->gravity & 3]);
- gtk_window_move(GTK_WINDOW(inst->window), x, y);
- }
-#else
- if (inst->gotpos) {
- int x = inst->xpos, y = inst->ypos;
- GtkRequisition req;
- gtk_widget_size_request(GTK_WIDGET(inst->window), &req);
- if (inst->gravity & 1) x += gdk_screen_width() - req.width;
- if (inst->gravity & 2) y += gdk_screen_height() - req.height;
- gtk_window_set_position(GTK_WINDOW(inst->window), GTK_WIN_POS_NONE);
- gtk_widget_set_uposition(GTK_WIDGET(inst->window), x, y);
- }
-#endif
-
- g_signal_connect(G_OBJECT(inst->window), "destroy",
- G_CALLBACK(destroy), inst);
- g_signal_connect(G_OBJECT(inst->window), "delete_event",
- G_CALLBACK(delete_window), inst);
- g_signal_connect(G_OBJECT(inst->window), "key_press_event",
- G_CALLBACK(key_event), inst);
- g_signal_connect(G_OBJECT(inst->window), "key_release_event",
- G_CALLBACK(key_event), inst);
- g_signal_connect(G_OBJECT(inst->window), "focus_in_event",
- G_CALLBACK(focus_event), inst);
- g_signal_connect(G_OBJECT(inst->window), "focus_out_event",
- G_CALLBACK(focus_event), inst);
- g_signal_connect(G_OBJECT(inst->area), "realize",
- G_CALLBACK(area_realised), inst);
- g_signal_connect(G_OBJECT(inst->area), "size_allocate",
- G_CALLBACK(area_size_allocate), inst);
- g_signal_connect(G_OBJECT(inst->window), "configure_event",
- G_CALLBACK(window_configured), inst);
-#if GTK_CHECK_VERSION(3,10,0)
- g_signal_connect(G_OBJECT(inst->area), "configure_event",
- G_CALLBACK(area_configured), inst);
-#endif
-#if GTK_CHECK_VERSION(3,0,0)
- g_signal_connect(G_OBJECT(inst->area), "draw",
- G_CALLBACK(draw_area), inst);
-#else
- g_signal_connect(G_OBJECT(inst->area), "expose_event",
- G_CALLBACK(expose_area), inst);
-#endif
- g_signal_connect(G_OBJECT(inst->area), "button_press_event",
- G_CALLBACK(button_event), inst);
- g_signal_connect(G_OBJECT(inst->area), "button_release_event",
- G_CALLBACK(button_event), inst);
-#if GTK_CHECK_VERSION(2,0,0)
- g_signal_connect(G_OBJECT(inst->area), "scroll_event",
- G_CALLBACK(scroll_event), inst);
-#endif
- g_signal_connect(G_OBJECT(inst->area), "motion_notify_event",
- G_CALLBACK(motion_event), inst);
-#if GTK_CHECK_VERSION(2,0,0)
- g_signal_connect(G_OBJECT(inst->imc), "commit",
- G_CALLBACK(input_method_commit_event), inst);
-#endif
- if (conf_get_bool(inst->conf, CONF_scrollbar))
- g_signal_connect(G_OBJECT(inst->sbar_adjust), "value_changed",
- G_CALLBACK(scrollbar_moved), inst);
- gtk_widget_add_events(GTK_WIDGET(inst->area),
- GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
- GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
- GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK
-#if GTK_CHECK_VERSION(3,4,0)
- | GDK_SMOOTH_SCROLL_MASK
-#endif
- );
-
- set_window_icon(inst->window, main_icon, n_main_icon);
-
- gtk_widget_show(inst->window);
-
- set_window_background(inst);
-
- /*
- * Set up the Ctrl+rightclick context menu.
- */
- {
- GtkWidget *menuitem;
- char *s;
-
- inst->menu = gtk_menu_new();
-
-#define MKMENUITEM(title, func) do \
- { \
- menuitem = gtk_menu_item_new_with_label(title); \
- gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \
- gtk_widget_show(menuitem); \
- g_signal_connect(G_OBJECT(menuitem), "activate", \
- G_CALLBACK(func), inst); \
- } while (0)
-
-#define MKSUBMENU(title) do \
- { \
- menuitem = gtk_menu_item_new_with_label(title); \
- gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \
- gtk_widget_show(menuitem); \
- } while (0)
-
-#define MKSEP() do \
- { \
- menuitem = gtk_menu_item_new(); \
- gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \
- gtk_widget_show(menuitem); \
- } while (0)
-
- if (new_session)
- MKMENUITEM("New Session...", new_session_menuitem);
- MKMENUITEM("Restart Session", restart_session_menuitem);
- inst->restartitem = menuitem;
- gtk_widget_set_sensitive(inst->restartitem, false);
- MKMENUITEM("Duplicate Session", dup_session_menuitem);
- if (saved_sessions) {
- inst->sessionsmenu = gtk_menu_new();
- /* sessionsmenu will be updated when it's invoked */
- /* XXX is this the right way to do dynamic menus in Gtk? */
- MKMENUITEM("Saved Sessions", update_savedsess_menu);
- gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem),
- inst->sessionsmenu);
- }
- MKSEP();
- MKMENUITEM("Change Settings...", change_settings_menuitem);
- MKSEP();
- if (use_event_log)
- MKMENUITEM("Event Log", event_log_menuitem);
- MKSUBMENU("Special Commands");
- inst->specialsmenu = gtk_menu_new();
- gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), inst->specialsmenu);
- inst->specialsitem1 = menuitem;
- MKSEP();
- inst->specialsitem2 = menuitem;
- gtk_widget_hide(inst->specialsitem1);
- gtk_widget_hide(inst->specialsitem2);
- MKMENUITEM("Clear Scrollback", clear_scrollback_menuitem);
- MKMENUITEM("Reset Terminal", reset_terminal_menuitem);
- MKSEP();
- MKMENUITEM("Copy to " CLIPNAME_EXPLICIT_OBJECT,
- copy_clipboard_menuitem);
- MKMENUITEM("Paste from " CLIPNAME_EXPLICIT_OBJECT,
- paste_clipboard_menuitem);
- MKMENUITEM("Copy All", copy_all_menuitem);
- MKSEP();
- s = dupcat("About ", appname);
- MKMENUITEM(s, about_menuitem);
- sfree(s);
-#undef MKMENUITEM
-#undef MKSUBMENU
-#undef MKSEP
- }
-
- inst->textcursor = make_mouse_ptr(inst, GDK_XTERM);
- inst->rawcursor = make_mouse_ptr(inst, GDK_LEFT_PTR);
- inst->waitcursor = make_mouse_ptr(inst, GDK_WATCH);
- inst->blankcursor = make_mouse_ptr(inst, -1);
- inst->currcursor = inst->textcursor;
- show_mouseptr(inst, true);
-
- inst->eventlogstuff = eventlogstuff_new();
-
- inst->term = term_init(inst->conf, &inst->ucsdata, &inst->termwin);
- setup_clipboards(inst, inst->term, inst->conf);
- inst->logctx = log_init(&inst->logpolicy, inst->conf);
- term_provide_logctx(inst->term, inst->logctx);
-
- term_size(inst->term, inst->height, inst->width,
- conf_get_int(inst->conf, CONF_savelines));
-
-#if GTK_CHECK_VERSION(2,0,0)
- /* Delay this signal connection until after inst->term exists */
- g_signal_connect(G_OBJECT(inst->window), "window_state_event",
- G_CALLBACK(window_state_event), inst);
-#endif
-
- inst->exited = false;
-
- start_backend(inst);
-
- if (inst->ldisc) /* early backend failure might make this NULL already */
- ldisc_echoedit_update(inst->ldisc); /* cause ldisc to notice changes */
-}
-
-static bool gtk_seat_set_trust_status(Seat *seat, bool trusted)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- term_set_trust_status(inst->term, trusted);
- return true;
-}
-
-static bool gtk_seat_get_cursor_position(Seat *seat, int *x, int *y)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- if (inst->term) {
- term_get_cursor_position(inst->term, x, y);
- return true;
- }
- return false;
-}
diff --git a/UNIX/UNIX.H b/UNIX/UNIX.H
deleted file mode 100644
index 2c0664b2..00000000
--- a/UNIX/UNIX.H
+++ /dev/null
@@ -1,460 +0,0 @@
-#ifndef PUTTY_UNIX_H
-#define PUTTY_UNIX_H
-
-#ifdef HAVE_CONFIG_H
-# include "uxconfig.h" /* Space to hide it from mkfiles.pl */
-#endif
-
-#include <stdio.h> /* for FILENAME_MAX */
-#include <stdint.h> /* C99 int types */
-#ifndef NO_LIBDL
-#include <dlfcn.h> /* Dynamic library loading */
-#endif /* NO_LIBDL */
-#include "charset.h"
-#include <sys/types.h> /* 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, "<custom#1>") \
- X(CLIP_CUSTOM_2, "<custom#2>") \
- X(CLIP_CUSTOM_3, "<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/UNIX/UXAGENTC.C b/UNIX/UXAGENTC.C
deleted file mode 100644
index e4d671d2..00000000
--- a/UNIX/UXAGENTC.C
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * SSH agent client code.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <unistd.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <fcntl.h>
-
-#include "putty.h"
-#include "misc.h"
-#include "tree234.h"
-#include "puttymem.h"
-
-bool agent_exists(void)
-{
- const char *p = getenv("SSH_AUTH_SOCK");
- if (p && *p)
- return true;
- return false;
-}
-
-static tree234 *agent_pending_queries;
-struct agent_pending_query {
- int fd;
- char *retbuf;
- char sizebuf[4];
- int retsize, retlen;
- void (*callback)(void *, void *, int);
- void *callback_ctx;
-};
-static int agent_conncmp(void *av, void *bv)
-{
- agent_pending_query *a = (agent_pending_query *) av;
- agent_pending_query *b = (agent_pending_query *) bv;
- if (a->fd < b->fd)
- return -1;
- if (a->fd > b->fd)
- return +1;
- return 0;
-}
-static int agent_connfind(void *av, void *bv)
-{
- int afd = *(int *) av;
- agent_pending_query *b = (agent_pending_query *) bv;
- if (afd < b->fd)
- return -1;
- if (afd > b->fd)
- return +1;
- return 0;
-}
-
-/*
- * Attempt to read from an agent socket fd. Returns false if the
- * expected response is as yet incomplete; returns true if it's either
- * complete (conn->retbuf non-NULL and filled with something useful)
- * or has failed totally (conn->retbuf is NULL).
- */
-static bool agent_try_read(agent_pending_query *conn)
-{
- int ret;
-
- ret = read(conn->fd, conn->retbuf+conn->retlen,
- conn->retsize-conn->retlen);
- if (ret <= 0) {
- if (conn->retbuf != conn->sizebuf) sfree(conn->retbuf);
- conn->retbuf = NULL;
- conn->retlen = 0;
- return true;
- }
- conn->retlen += ret;
- if (conn->retsize == 4 && conn->retlen == 4) {
- conn->retsize = toint(GET_32BIT_MSB_FIRST(conn->retbuf) + 4);
- if (conn->retsize <= 0) {
- conn->retbuf = NULL;
- conn->retlen = 0;
- return true; /* way too large */
- }
- assert(conn->retbuf == conn->sizebuf);
- conn->retbuf = snewn(conn->retsize, char);
- memcpy(conn->retbuf, conn->sizebuf, 4);
- }
-
- if (conn->retlen < conn->retsize)
- return false; /* more data to come */
-
- return true;
-}
-
-void agent_cancel_query(agent_pending_query *conn)
-{
- uxsel_del(conn->fd);
- close(conn->fd);
- del234(agent_pending_queries, conn);
- if (conn->retbuf && conn->retbuf != conn->sizebuf)
- sfree(conn->retbuf);
- sfree(conn);
-}
-
-static void agent_select_result(int fd, int event)
-{
- agent_pending_query *conn;
-
- assert(event == SELECT_R); /* not selecting for anything but R */
-
- conn = find234(agent_pending_queries, &fd, agent_connfind);
- if (!conn) {
- uxsel_del(fd);
- return;
- }
-
- if (!agent_try_read(conn))
- return; /* more data to come */
-
- /*
- * We have now completed the agent query. Do the callback.
- */
- conn->callback(conn->callback_ctx, conn->retbuf, conn->retlen);
- /* Null out conn->retbuf, since ownership of that buffer has
- * passed to the callback. */
- conn->retbuf = NULL;
- agent_cancel_query(conn);
-}
-
-static const char *agent_socket_path(void)
-{
- return getenv("SSH_AUTH_SOCK");
-}
-
-Socket *agent_connect(Plug *plug)
-{
- const char *path = agent_socket_path();
- if (!path)
- return new_error_socket_fmt(plug, "SSH_AUTH_SOCK not set");
- return sk_new(unix_sock_addr(path), 0, false, false, false, false, plug);
-}
-
-agent_pending_query *agent_query(
- strbuf *query, void **out, int *outlen,
- void (*callback)(void *, void *, int), void *callback_ctx)
-{
- const char *name;
- int sock;
- struct sockaddr_un addr;
- int done;
- agent_pending_query *conn;
-
- name = agent_socket_path();
- if (!name || strlen(name) >= sizeof(addr.sun_path))
- goto failure;
-
- sock = socket(PF_UNIX, SOCK_STREAM, 0);
- if (sock < 0) {
- perror("socket(PF_UNIX)");
- exit(1);
- }
-
- cloexec(sock);
-
- addr.sun_family = AF_UNIX;
- strcpy(addr.sun_path, name);
- if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
- close(sock);
- goto failure;
- }
-
- strbuf_finalise_agent_query(query);
-
- for (done = 0; done < query->len ;) {
- int ret = write(sock, query->s + done,
- query->len - done);
- if (ret <= 0) {
- close(sock);
- goto failure;
- }
- done += ret;
- }
-
- conn = snew(agent_pending_query);
- conn->fd = sock;
- conn->retbuf = conn->sizebuf;
- conn->retsize = 4;
- conn->retlen = 0;
- conn->callback = callback;
- conn->callback_ctx = callback_ctx;
-
- if (!callback) {
- /*
- * Bodge to permit making deliberately synchronous agent
- * requests. Used by Unix Pageant in command-line client mode,
- * which is legit because it really is true that no other part
- * of the program is trying to get anything useful done
- * simultaneously. But this special case shouldn't be used in
- * any more general program.
- */
- no_nonblock(conn->fd);
- while (!agent_try_read(conn))
- /* empty loop body */;
-
- *out = conn->retbuf;
- *outlen = conn->retlen;
- sfree(conn);
- return NULL;
- }
-
- /*
- * Otherwise do it properly: add conn to the tree of agent
- * connections currently in flight, return 0 to indicate that the
- * response hasn't been received yet, and call the callback when
- * select_result comes back to us.
- */
- if (!agent_pending_queries)
- agent_pending_queries = newtree234(agent_conncmp);
- add234(agent_pending_queries, conn);
-
- uxsel_set(sock, SELECT_R, agent_select_result);
- return conn;
-
- failure:
- *out = NULL;
- *outlen = 0;
- return NULL;
-}
diff --git a/UNIX/UXCFG.C b/UNIX/UXCFG.C
deleted file mode 100644
index 8397a0ac..00000000
--- a/UNIX/UXCFG.C
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * uxcfg.c - the Unix-specific parts of the PuTTY configuration
- * box.
- */
-
-#include <assert.h>
-#include <stdlib.h>
-
-#include "putty.h"
-#include "dialog.h"
-#include "storage.h"
-
-void unix_setup_config_box(struct controlbox *b, bool midsession, int protocol)
-{
- struct controlset *s;
- union control *c;
-
- /*
- * The Conf structure contains two Unix-specific elements which
- * are not configured in here: stamp_utmp and login_shell. This
- * is because pterm does not put up a configuration box right at
- * the start, which is the only time when these elements would
- * be useful to configure.
- */
-
- /*
- * On Unix, we don't have a drop-down list for the printer
- * control.
- */
- s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing");
- assert(s->ncontrols == 1 && s->ctrls[0]->generic.type == CTRL_EDITBOX);
- s->ctrls[0]->editbox.has_list = false;
-
- /*
- * Unix supports a local-command proxy. This also means we must
- * adjust the text on the `Telnet command' control.
- */
- if (!midsession) {
- int i;
- s = ctrl_getset(b, "Connection/Proxy", "basics", NULL);
- for (i = 0; i < s->ncontrols; i++) {
- c = s->ctrls[i];
- if (c->generic.type == CTRL_RADIO &&
- c->generic.context.i == CONF_proxy_type) {
- assert(c->generic.handler == conf_radiobutton_handler);
- c->radio.nbuttons++;
- c->radio.buttons =
- sresize(c->radio.buttons, c->radio.nbuttons, char *);
- c->radio.buttons[c->radio.nbuttons-1] =
- dupstr("Local");
- c->radio.buttondata =
- sresize(c->radio.buttondata, c->radio.nbuttons, intorptr);
- c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_CMD);
- break;
- }
- }
-
- for (i = 0; i < s->ncontrols; i++) {
- c = s->ctrls[i];
- if (c->generic.type == CTRL_EDITBOX &&
- c->generic.context.i == CONF_proxy_telnet_command) {
- assert(c->generic.handler == conf_editbox_handler);
- sfree(c->generic.label);
- c->generic.label = dupstr("Telnet command, or local"
- " proxy command");
- break;
- }
- }
- }
-}
diff --git a/UNIX/UXCONS.C b/UNIX/UXCONS.C
deleted file mode 100644
index 90e73a98..00000000
--- a/UNIX/UXCONS.C
+++ /dev/null
@@ -1,530 +0,0 @@
-/*
- * uxcons.c: various interactive-prompt routines shared between the
- * Unix console PuTTY tools
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <errno.h>
-
-#include <termios.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <sys/time.h>
-
-#include "putty.h"
-#include "storage.h"
-#include "ssh.h"
-#include "console.h"
-
-static struct termios orig_termios_stderr;
-static bool stderr_is_a_tty;
-
-void stderr_tty_init()
-{
- /* Ensure that if stderr is a tty, we can get it back to a sane state. */
- if (isatty(STDERR_FILENO)) {
- stderr_is_a_tty = true;
- tcgetattr(STDERR_FILENO, &orig_termios_stderr);
- }
-}
-
-void premsg(struct termios *cf)
-{
- if (stderr_is_a_tty) {
- tcgetattr(STDERR_FILENO, cf);
- tcsetattr(STDERR_FILENO, TCSADRAIN, &orig_termios_stderr);
- }
-}
-void postmsg(struct termios *cf)
-{
- if (stderr_is_a_tty)
- tcsetattr(STDERR_FILENO, TCSADRAIN, cf);
-}
-
-void cleanup_exit(int code)
-{
- /*
- * Clean up.
- */
- sk_cleanup();
- random_save_seed();
- exit(code);
-}
-
-void console_print_error_msg(const char *prefix, const char *msg)
-{
- struct termios cf;
- premsg(&cf);
- fputs(prefix, stderr);
- fputs(": ", stderr);
- fputs(msg, stderr);
- fputc('\n', stderr);
- fflush(stderr);
- postmsg(&cf);
-}
-
-/*
- * Wrapper around Unix read(2), suitable for use on a file descriptor
- * that's been set into nonblocking mode. Handles EAGAIN/EWOULDBLOCK
- * by means of doing a one-fd poll and then trying again; all other
- * errors (including errors from poll) are returned to the caller.
- */
-static int block_and_read(int fd, void *buf, size_t len)
-{
- int ret;
- pollwrapper *pw = pollwrap_new();
-
- while ((ret = read(fd, buf, len)) < 0 && (
-#ifdef EAGAIN
- (errno == EAGAIN) ||
-#endif
-#ifdef EWOULDBLOCK
- (errno == EWOULDBLOCK) ||
-#endif
- false)) {
-
- pollwrap_clear(pw);
- pollwrap_add_fd_rwx(pw, fd, SELECT_R);
- do {
- ret = pollwrap_poll_endless(pw);
- } while (ret < 0 && errno == EINTR);
- assert(ret != 0);
- if (ret < 0) {
- pollwrap_free(pw);
- return ret;
- }
- assert(pollwrap_check_fd_rwx(pw, fd, SELECT_R));
- }
-
- pollwrap_free(pw);
- return ret;
-}
-
-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;
-
- char line[32];
- struct termios cf;
- const char *common_fmt, *intro, *prompt;
-
- /*
- * Verify the key.
- */
- ret = verify_host_key(host, port, keytype, keystr);
-
- if (ret == 0) /* success - key matched OK */
- return 1;
-
- premsg(&cf);
- if (ret == 2) { /* key was different */
- 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]);
- if (console_batch_mode) {
- fputs(console_abandoned_msg, stderr);
- return 0;
- }
-
- fputs(intro, stderr);
- fflush(stderr);
- while (true) {
- fputs(prompt, stderr);
- fflush(stderr);
-
- struct termios oldmode, newmode;
- tcgetattr(0, &oldmode);
- newmode = oldmode;
- newmode.c_lflag |= ECHO | ISIG | ICANON;
- tcsetattr(0, TCSANOW, &newmode);
- line[0] = '\0';
- if (block_and_read(0, line, sizeof(line) - 1) <= 0)
- /* handled below */;
- tcsetattr(0, TCSANOW, &oldmode);
-
- if (line[0] == 'i' || line[0] == 'I') {
- 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);
- postmsg(&cf);
- return 1;
- } else {
- fputs(console_abandoned_msg, stderr);
- postmsg(&cf);
- return 0;
- }
-}
-
-int console_confirm_weak_crypto_primitive(
- Seat *seat, const char *algtype, const char *algname,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- char line[32];
- struct termios cf;
-
- premsg(&cf);
- fprintf(stderr, weakcrypto_msg_common_fmt, algtype, algname);
-
- if (console_batch_mode) {
- fputs(console_abandoned_msg, stderr);
- postmsg(&cf);
- return 0;
- }
-
- fputs(console_continue_prompt, stderr);
- fflush(stderr);
-
- {
- struct termios oldmode, newmode;
- tcgetattr(0, &oldmode);
- newmode = oldmode;
- newmode.c_lflag |= ECHO | ISIG | ICANON;
- tcsetattr(0, TCSANOW, &newmode);
- line[0] = '\0';
- if (block_and_read(0, line, sizeof(line) - 1) <= 0)
- /* handled below */;
- tcsetattr(0, TCSANOW, &oldmode);
- }
-
- if (line[0] == 'y' || line[0] == 'Y') {
- postmsg(&cf);
- return 1;
- } else {
- fputs(console_abandoned_msg, stderr);
- postmsg(&cf);
- return 0;
- }
-}
-
-int console_confirm_weak_cached_hostkey(
- Seat *seat, const char *algname, const char *betteralgs,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- char line[32];
- struct termios cf;
-
- premsg(&cf);
- fprintf(stderr, weakhk_msg_common_fmt, algname, betteralgs);
-
- if (console_batch_mode) {
- fputs(console_abandoned_msg, stderr);
- postmsg(&cf);
- return 0;
- }
-
- fputs(console_continue_prompt, stderr);
- fflush(stderr);
-
- {
- struct termios oldmode, newmode;
- tcgetattr(0, &oldmode);
- newmode = oldmode;
- newmode.c_lflag |= ECHO | ISIG | ICANON;
- tcsetattr(0, TCSANOW, &newmode);
- line[0] = '\0';
- if (block_and_read(0, line, sizeof(line) - 1) <= 0)
- /* handled below */;
- tcsetattr(0, TCSANOW, &oldmode);
- }
-
- if (line[0] == 'y' || line[0] == 'Y') {
- postmsg(&cf);
- return 1;
- } else {
- fputs(console_abandoned_msg, stderr);
- postmsg(&cf);
- return 0;
- }
-}
-
-/*
- * Ask whether to wipe a session log file before writing to it.
- * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
- */
-int console_askappend(LogPolicy *lp, Filename *filename,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- static const char msgtemplate[] =
- "The session log file \"%.*s\" already exists.\n"
- "You can overwrite it with a new session log,\n"
- "append your session log to the end of it,\n"
- "or disable session logging for this session.\n"
- "Enter \"y\" to wipe the file, \"n\" to append to it,\n"
- "or just press Return to disable logging.\n"
- "Wipe the log file? (y/n, Return cancels logging) ";
-
- static const char msgtemplate_batch[] =
- "The session log file \"%.*s\" already exists.\n"
- "Logging will not be enabled.\n";
-
- char line[32];
- struct termios cf;
-
- premsg(&cf);
- if (console_batch_mode) {
- fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename->path);
- fflush(stderr);
- return 0;
- }
- fprintf(stderr, msgtemplate, FILENAME_MAX, filename->path);
- fflush(stderr);
-
- {
- struct termios oldmode, newmode;
- tcgetattr(0, &oldmode);
- newmode = oldmode;
- newmode.c_lflag |= ECHO | ISIG | ICANON;
- tcsetattr(0, TCSANOW, &newmode);
- line[0] = '\0';
- if (block_and_read(0, line, sizeof(line) - 1) <= 0)
- /* handled below */;
- tcsetattr(0, TCSANOW, &oldmode);
- }
-
- postmsg(&cf);
- if (line[0] == 'y' || line[0] == 'Y')
- return 2;
- else if (line[0] == 'n' || line[0] == 'N')
- return 1;
- else
- return 0;
-}
-
-bool console_antispoof_prompt = true;
-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;
-}
-
-/*
- * Warn about the obsolescent key file format.
- *
- * Uniquely among these functions, this one does _not_ expect a
- * frontend handle. This means that if PuTTY is ported to a
- * platform which requires frontend handles, this function will be
- * an anomaly. Fortunately, the problem it addresses will not have
- * been present on that platform, so it can plausibly be
- * implemented as an empty function.
- */
-void old_keyfile_warning(void)
-{
- static const char message[] =
- "You are loading an SSH-2 private key which has an\n"
- "old version of the file format. This means your key\n"
- "file is not fully tamperproof. Future versions of\n"
- "PuTTY may stop supporting this private key format,\n"
- "so we recommend you convert your key to the new\n"
- "format.\n"
- "\n"
- "Once the key is loaded into PuTTYgen, you can perform\n"
- "this conversion simply by saving it again.\n";
-
- struct termios cf;
- premsg(&cf);
- fputs(message, stderr);
- postmsg(&cf);
-}
-
-void console_logging_error(LogPolicy *lp, const char *string)
-{
- /* Errors setting up logging are considered important, so they're
- * displayed to standard error even when not in verbose mode */
- struct termios cf;
- premsg(&cf);
- fprintf(stderr, "%s\n", string);
- fflush(stderr);
- postmsg(&cf);
-}
-
-
-void console_eventlog(LogPolicy *lp, const char *string)
-{
- /* Ordinary Event Log entries are displayed in the same way as
- * logging errors, but only in verbose mode */
- if (lp_verbose(lp))
- console_logging_error(lp, string);
-}
-
-StripCtrlChars *console_stripctrl_new(
- Seat *seat, BinarySink *bs_out, SeatInteractionContext sic)
-{
- return stripctrl_new(bs_out, false, 0);
-}
-
-/*
- * Special functions to read and print to the console for password
- * prompts and the like. Uses /dev/tty or stdin/stderr, in that order
- * of preference; also sanitises escape sequences out of the text, on
- * the basis that it might have been sent by a hostile SSH server
- * doing malicious keyboard-interactive.
- */
-static void console_open(FILE **outfp, int *infd)
-{
- int fd;
-
- if ((fd = open("/dev/tty", O_RDWR)) >= 0) {
- *infd = fd;
- *outfp = fdopen(*infd, "w");
- } else {
- *infd = 0;
- *outfp = stderr;
- }
-}
-static void console_close(FILE *outfp, int infd)
-{
- if (outfp != stderr)
- fclose(outfp); /* will automatically close infd too */
-}
-
-static void console_write(FILE *outfp, ptrlen data)
-{
- fwrite(data.ptr, 1, data.len, outfp);
- fflush(outfp);
-}
-
-int console_get_userpass_input(prompts_t *p)
-{
- size_t curr_prompt;
- FILE *outfp = NULL;
- int infd;
-
- /*
- * Zero all the results, in case we abort half-way through.
- */
- {
- int i;
- for (i = 0; i < p->n_prompts; i++)
- prompt_set_result(p->prompts[i], "");
- }
-
- if (p->n_prompts && console_batch_mode)
- return 0;
-
- console_open(&outfp, &infd);
-
- /*
- * Preamble.
- */
- /* We only print the `name' caption if we have to... */
- if (p->name_reqd && p->name) {
- ptrlen plname = ptrlen_from_asciz(p->name);
- console_write(outfp, plname);
- if (!ptrlen_endswith(plname, PTRLEN_LITERAL("\n"), NULL))
- console_write(outfp, PTRLEN_LITERAL("\n"));
- }
- /* ...but we always print any `instruction'. */
- if (p->instruction) {
- ptrlen plinst = ptrlen_from_asciz(p->instruction);
- console_write(outfp, plinst);
- if (!ptrlen_endswith(plinst, PTRLEN_LITERAL("\n"), NULL))
- console_write(outfp, PTRLEN_LITERAL("\n"));
- }
-
- for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) {
-
- struct termios oldmode, newmode;
- prompt_t *pr = p->prompts[curr_prompt];
-
- tcgetattr(infd, &oldmode);
- newmode = oldmode;
- newmode.c_lflag |= ISIG | ICANON;
- if (!pr->echo)
- newmode.c_lflag &= ~ECHO;
- else
- newmode.c_lflag |= ECHO;
- tcsetattr(infd, TCSANOW, &newmode);
-
- console_write(outfp, ptrlen_from_asciz(pr->prompt));
-
- bool failed = false;
- while (1) {
- size_t toread = 65536;
- size_t prev_result_len = pr->result->len;
- void *ptr = strbuf_append(pr->result, toread);
- int ret = read(infd, ptr, toread);
-
- if (ret <= 0) {
- failed = true;
- break;
- }
-
- strbuf_shrink_to(pr->result, prev_result_len + ret);
- if (strbuf_chomp(pr->result, '\n'))
- break;
- }
-
- tcsetattr(infd, TCSANOW, &oldmode);
-
- if (!pr->echo)
- console_write(outfp, PTRLEN_LITERAL("\n"));
-
- if (failed) {
- console_close(outfp, infd);
- return 0; /* failure due to read error */
- }
- }
-
- console_close(outfp, infd);
-
- return 1; /* success */
-}
-
-bool is_interactive(void)
-{
- return isatty(0);
-}
-
-/*
- * X11-forwarding-related things suitable for console.
- */
-
-char *platform_get_x_display(void) {
- return dupstr(getenv("DISPLAY"));
-}
diff --git a/UNIX/UXGEN.C b/UNIX/UXGEN.C
deleted file mode 100644
index da5e8f05..00000000
--- a/UNIX/UXGEN.C
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * uxgen.c: Unix implementation of get_heavy_noise() from cmdgen.c.
- */
-
-#include <stdio.h>
-#include <errno.h>
-
-#include <fcntl.h>
-#include <unistd.h>
-
-#include "putty.h"
-
-char *get_random_data(int len, const char *device)
-{
- char *buf = snewn(len, char);
- int fd;
- int ngot, ret;
-
- if (!device) {
- static const char *const default_devices[] = {
- "/dev/urandom", "/dev/random"
- };
- size_t i;
-
- for (i = 0; i < lenof(default_devices); i++) {
- if (access(default_devices[i], R_OK) == 0) {
- device = default_devices[i];
- break;
- }
- }
-
- if (!device) {
- sfree(buf);
- fprintf(stderr, "puttygen: cannot find a readable "
- "random number source; use --random-device\n");
- return NULL;
- }
- }
-
- fd = open(device, O_RDONLY);
- if (fd < 0) {
- sfree(buf);
- fprintf(stderr, "puttygen: %s: open: %s\n",
- device, strerror(errno));
- return NULL;
- }
-
- ngot = 0;
- while (ngot < len) {
- ret = read(fd, buf+ngot, len-ngot);
- if (ret < 0) {
- close(fd);
- sfree(buf);
- fprintf(stderr, "puttygen: %s: read: %s\n",
- device, strerror(errno));
- return NULL;
- }
- ngot += ret;
- }
-
- close(fd);
-
- return buf;
-}
diff --git a/UNIX/UXGSS.C b/UNIX/UXGSS.C
deleted file mode 100644
index 2d71c543..00000000
--- a/UNIX/UXGSS.C
+++ /dev/null
@@ -1,175 +0,0 @@
-#include "putty.h"
-#ifndef NO_GSSAPI
-#include "pgssapi.h"
-#include "sshgss.h"
-#include "sshgssc.h"
-
-/* Unix code to set up the GSSAPI library list. */
-
-#if !defined NO_LIBDL && !defined NO_GSSAPI
-
-const int ngsslibs = 4;
-const char *const gsslibnames[4] = {
- "libgssapi (Heimdal)",
- "libgssapi_krb5 (MIT Kerberos)",
- "libgss (Sun)",
- "User-specified GSSAPI library",
-};
-const struct keyvalwhere gsslibkeywords[] = {
- { "libgssapi", 0, -1, -1 },
- { "libgssapi_krb5", 1, -1, -1 },
- { "libgss", 2, -1, -1 },
- { "custom", 3, -1, -1 },
-};
-
-/*
- * Run-time binding against a choice of GSSAPI implementations. We
- * try loading several libraries, and produce an entry in
- * ssh_gss_libraries[] for each one.
- */
-
-static void gss_init(struct ssh_gss_library *lib, void *dlhandle,
- int id, const char *msg)
-{
- lib->id = id;
- lib->gsslogmsg = msg;
- lib->handle = dlhandle;
-
-#define BIND_GSS_FN(name) \
- lib->u.gssapi.name = (t_gss_##name) dlsym(dlhandle, "gss_" #name)
-
- BIND_GSS_FN(delete_sec_context);
- BIND_GSS_FN(display_status);
- BIND_GSS_FN(get_mic);
- BIND_GSS_FN(verify_mic);
- BIND_GSS_FN(import_name);
- BIND_GSS_FN(init_sec_context);
- BIND_GSS_FN(release_buffer);
- BIND_GSS_FN(release_cred);
- BIND_GSS_FN(release_name);
- BIND_GSS_FN(acquire_cred);
- BIND_GSS_FN(inquire_cred_by_mech);
-
-#undef BIND_GSS_FN
-
- ssh_gssapi_bind_fns(lib);
-}
-
-/* Dynamically load gssapi libs. */
-struct ssh_gss_liblist *ssh_gss_setup(Conf *conf)
-{
- void *gsslib;
- char *gsspath;
- struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist);
-
- list->libraries = snewn(4, struct ssh_gss_library);
- list->nlibraries = 0;
-
- /* Heimdal's GSSAPI Library */
- if ((gsslib = dlopen("libgssapi.so.2", RTLD_LAZY)) != NULL)
- gss_init(&list->libraries[list->nlibraries++], gsslib,
- 0, "Using GSSAPI from libgssapi.so.2");
-
- /* MIT Kerberos's GSSAPI Library */
- if ((gsslib = dlopen("libgssapi_krb5.so.2", RTLD_LAZY)) != NULL)
- gss_init(&list->libraries[list->nlibraries++], gsslib,
- 1, "Using GSSAPI from libgssapi_krb5.so.2");
-
- /* Sun's GSSAPI Library */
- if ((gsslib = dlopen("libgss.so.1", RTLD_LAZY)) != NULL)
- gss_init(&list->libraries[list->nlibraries++], gsslib,
- 2, "Using GSSAPI from libgss.so.1");
-
- /* User-specified GSSAPI library */
- gsspath = conf_get_filename(conf, CONF_ssh_gss_custom)->path;
- if (*gsspath && (gsslib = dlopen(gsspath, RTLD_LAZY)) != NULL)
- gss_init(&list->libraries[list->nlibraries++], gsslib,
- 3, dupprintf("Using GSSAPI from user-specified"
- " library '%s'", gsspath));
-
- return list;
-}
-
-void ssh_gss_cleanup(struct ssh_gss_liblist *list)
-{
- int i;
-
- /*
- * dlopen and dlclose are defined to employ reference counting
- * in the case where the same library is repeatedly dlopened, so
- * even in a multiple-sessions-per-process context it's safe to
- * naively dlclose everything here without worrying about
- * destroying it under the feet of another SSH instance still
- * using it.
- */
- for (i = 0; i < list->nlibraries; i++) {
- dlclose(list->libraries[i].handle);
- if (list->libraries[i].id == 3) {
- /* The 'custom' id involves a dynamically allocated message.
- * Note that we must cast away the 'const' to free it. */
- sfree((char *)list->libraries[i].gsslogmsg);
- }
- }
- sfree(list->libraries);
- sfree(list);
-}
-
-#elif !defined NO_GSSAPI
-
-const int ngsslibs = 1;
-const char *const gsslibnames[1] = {
- "static",
-};
-const struct keyvalwhere gsslibkeywords[] = {
- { "static", 0, -1, -1 },
-};
-
-/*
- * Link-time binding against GSSAPI. Here we just construct a single
- * library structure containing pointers to the functions we linked
- * against.
- */
-
-#include <gssapi/gssapi.h>
-
-/* Dynamically load gssapi libs. */
-struct ssh_gss_liblist *ssh_gss_setup(Conf *conf)
-{
- struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist);
-
- list->libraries = snew(struct ssh_gss_library);
- list->nlibraries = 1;
-
- list->libraries[0].gsslogmsg = "Using statically linked GSSAPI";
-
-#define BIND_GSS_FN(name) \
- list->libraries[0].u.gssapi.name = (t_gss_##name) gss_##name
-
- BIND_GSS_FN(delete_sec_context);
- BIND_GSS_FN(display_status);
- BIND_GSS_FN(get_mic);
- BIND_GSS_FN(verify_mic);
- BIND_GSS_FN(import_name);
- BIND_GSS_FN(init_sec_context);
- BIND_GSS_FN(release_buffer);
- BIND_GSS_FN(release_cred);
- BIND_GSS_FN(release_name);
- BIND_GSS_FN(acquire_cred);
- BIND_GSS_FN(inquire_cred_by_mech);
-
-#undef BIND_GSS_FN
-
- ssh_gssapi_bind_fns(&list->libraries[0]);
-
- return list;
-}
-
-void ssh_gss_cleanup(struct ssh_gss_liblist *list)
-{
- sfree(list->libraries);
- sfree(list);
-}
-
-#endif /* NO_LIBDL */
-
-#endif /* NO_GSSAPI */
diff --git a/UNIX/UXMISC.C b/UNIX/UXMISC.C
deleted file mode 100644
index 57df7b7a..00000000
--- a/UNIX/UXMISC.C
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
- * PuTTY miscellaneous Unix stuff
- */
-
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <errno.h>
-#include <unistd.h>
-#include <time.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <pwd.h>
-
-#include "putty.h"
-
-unsigned long getticks(void)
-{
- /*
- * We want to use milliseconds rather than the microseconds or
- * nanoseconds given by the underlying clock functions, because we
- * need a decent number of them to fit into a 32-bit word so it
- * can be used for keepalives.
- */
-#if defined HAVE_CLOCK_GETTIME && defined HAVE_DECL_CLOCK_MONOTONIC
- {
- /* Use CLOCK_MONOTONIC if available, so as to be unconfused if
- * the system clock changes. */
- struct timespec ts;
- if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
- return ts.tv_sec * TICKSPERSEC +
- ts.tv_nsec / (1000000000 / TICKSPERSEC);
- }
-#endif
- {
- struct timeval tv;
- gettimeofday(&tv, NULL);
- return tv.tv_sec * TICKSPERSEC + tv.tv_usec / (1000000 / TICKSPERSEC);
- }
-}
-
-Filename *filename_from_str(const char *str)
-{
- Filename *ret = snew(Filename);
- ret->path = dupstr(str);
- return ret;
-}
-
-Filename *filename_copy(const Filename *fn)
-{
- return filename_from_str(fn->path);
-}
-
-const char *filename_to_str(const Filename *fn)
-{
- return fn->path;
-}
-
-bool filename_equal(const Filename *f1, const Filename *f2)
-{
- return !strcmp(f1->path, f2->path);
-}
-
-bool filename_is_null(const Filename *fn)
-{
- return !fn->path[0];
-}
-
-void filename_free(Filename *fn)
-{
- sfree(fn->path);
- sfree(fn);
-}
-
-void filename_serialise(BinarySink *bs, const Filename *f)
-{
- put_asciz(bs, f->path);
-}
-Filename *filename_deserialise(BinarySource *src)
-{
- return filename_from_str(get_asciz(src));
-}
-
-char filename_char_sanitise(char c)
-{
- if (c == '/')
- return '.';
- return c;
-}
-
-#ifdef DEBUG
-static FILE *debug_fp = NULL;
-
-void dputs(const char *buf)
-{
- if (!debug_fp) {
- debug_fp = fopen("debug.log", "w");
- }
-
- if (write(1, buf, strlen(buf)) < 0) {} /* 'error check' to placate gcc */
-
- fputs(buf, debug_fp);
- fflush(debug_fp);
-}
-#endif
-
-char *get_username(void)
-{
- struct passwd *p;
- uid_t uid = getuid();
- char *user, *ret = NULL;
-
- /*
- * First, find who we think we are using getlogin. If this
- * agrees with our uid, we'll go along with it. This should
- * allow sharing of uids between several login names whilst
- * coping correctly with people who have su'ed.
- */
- user = getlogin();
-#if HAVE_SETPWENT
- setpwent();
-#endif
- if (user)
- p = getpwnam(user);
- else
- p = NULL;
- if (p && p->pw_uid == uid) {
- /*
- * The result of getlogin() really does correspond to
- * our uid. Fine.
- */
- ret = user;
- } else {
- /*
- * If that didn't work, for whatever reason, we'll do
- * the simpler version: look up our uid in the password
- * file and map it straight to a name.
- */
- p = getpwuid(uid);
- if (!p)
- return NULL;
- ret = p->pw_name;
- }
-#if HAVE_ENDPWENT
- endpwent();
-#endif
-
- return dupstr(ret);
-}
-
-/*
- * Display the fingerprints of the PGP Master Keys to the user.
- * (This is here rather than in uxcons because it's appropriate even for
- * Unix GUI apps.)
- */
-void pgp_fingerprints(void)
-{
- fputs("These are the fingerprints of the PuTTY PGP Master Keys. They can\n"
- "be used to establish a trust path from this executable to another\n"
- "one. See the manual for more information.\n"
- "(Note: these fingerprints have nothing to do with SSH!)\n"
- "\n"
- "PuTTY Master Key as of " 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);
-}
-
-/*
- * Set and clear fcntl options on a file descriptor. We don't
- * realistically expect any of these operations to fail (the most
- * plausible error condition is EBADF, but we always believe ourselves
- * to be passing a valid fd so even that's an assertion-fail sort of
- * response), so we don't make any effort to return sensible error
- * codes to the caller - we just log to standard error and die
- * unceremoniously. However, nonblock and no_nonblock do return the
- * previous state of O_NONBLOCK.
- */
-void cloexec(int fd) {
- int fdflags;
-
- fdflags = fcntl(fd, F_GETFD);
- if (fdflags < 0) {
- fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno));
- exit(1);
- }
- if (fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC) < 0) {
- fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno));
- exit(1);
- }
-}
-void noncloexec(int fd) {
- int fdflags;
-
- fdflags = fcntl(fd, F_GETFD);
- if (fdflags < 0) {
- fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno));
- exit(1);
- }
- if (fcntl(fd, F_SETFD, fdflags & ~FD_CLOEXEC) < 0) {
- fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno));
- exit(1);
- }
-}
-bool nonblock(int fd) {
- int fdflags;
-
- fdflags = fcntl(fd, F_GETFL);
- if (fdflags < 0) {
- fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno));
- exit(1);
- }
- if (fcntl(fd, F_SETFL, fdflags | O_NONBLOCK) < 0) {
- fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno));
- exit(1);
- }
-
- return fdflags & O_NONBLOCK;
-}
-bool no_nonblock(int fd) {
- int fdflags;
-
- fdflags = fcntl(fd, F_GETFL);
- if (fdflags < 0) {
- fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno));
- exit(1);
- }
- if (fcntl(fd, F_SETFL, fdflags & ~O_NONBLOCK) < 0) {
- fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno));
- exit(1);
- }
-
- return fdflags & O_NONBLOCK;
-}
-
-FILE *f_open(const Filename *filename, char const *mode, bool is_private)
-{
- if (!is_private) {
- return fopen(filename->path, mode);
- } else {
- int fd;
- assert(mode[0] == 'w'); /* is_private is meaningless for read,
- and tricky for append */
- fd = open(filename->path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
- if (fd < 0)
- return NULL;
- return fdopen(fd, mode);
- }
-}
-
-FontSpec *fontspec_new(const char *name)
-{
- FontSpec *f = snew(FontSpec);
- f->name = dupstr(name);
- return f;
-}
-FontSpec *fontspec_copy(const FontSpec *f)
-{
- return fontspec_new(f->name);
-}
-void fontspec_free(FontSpec *f)
-{
- sfree(f->name);
- sfree(f);
-}
-void fontspec_serialise(BinarySink *bs, FontSpec *f)
-{
- put_asciz(bs, f->name);
-}
-FontSpec *fontspec_deserialise(BinarySource *src)
-{
- return fontspec_new(get_asciz(src));
-}
-
-char *make_dir_and_check_ours(const char *dirname)
-{
- struct stat st;
-
- /*
- * Create the directory. We might have created it before, so
- * EEXIST is an OK error; but anything else is doom.
- */
- if (mkdir(dirname, 0700) < 0 && errno != EEXIST)
- return dupprintf("%s: mkdir: %s", dirname, strerror(errno));
-
- /*
- * Now check that that directory is _owned by us_ and not writable
- * by anybody else. This protects us against somebody else
- * previously having created the directory in a way that's
- * writable to us, and thus manipulating us into creating the
- * actual socket in a directory they can see so that they can
- * connect to it and use our authenticated SSH sessions.
- */
- if (stat(dirname, &st) < 0)
- return dupprintf("%s: stat: %s", dirname, strerror(errno));
- if (st.st_uid != getuid())
- return dupprintf("%s: directory owned by uid %d, not by us",
- dirname, st.st_uid);
- if ((st.st_mode & 077) != 0)
- return dupprintf("%s: directory has overgenerous permissions %03o"
- " (expected 700)", dirname, st.st_mode & 0777);
-
- return NULL;
-}
-
-char *make_dir_path(const char *path, mode_t mode)
-{
- int pos = 0;
- char *prefix;
-
- while (1) {
- pos += strcspn(path + pos, "/");
-
- if (pos > 0) {
- prefix = dupprintf("%.*s", pos, path);
-
- if (mkdir(prefix, mode) < 0 && errno != EEXIST) {
- char *ret = dupprintf("%s: mkdir: %s",
- prefix, strerror(errno));
- sfree(prefix);
- return ret;
- }
-
- sfree(prefix);
- }
-
- if (!path[pos])
- return NULL;
- pos += strspn(path + pos, "/");
- }
-}
-
-bool open_for_write_would_lose_data(const Filename *fn)
-{
- struct stat st;
-
- if (stat(fn->path, &st) < 0) {
- /*
- * If the file doesn't even exist, we obviously want to return
- * false. If we failed to stat it for any other reason,
- * ignoring the precise error code and returning false still
- * doesn't seem too unreasonable, because then we'll try to
- * open the file for writing and report _that_ error, which is
- * likely to be more to the point.
- */
- return false;
- }
-
- /*
- * OK, something exists at this pathname and we've found out
- * something about it. But an open-for-write will only
- * destructively truncate it if it's a regular file with nonzero
- * size. If it's empty, or some other kind of special thing like a
- * character device (e.g. /dev/tty) or a named pipe, then opening
- * it for write is already non-destructive and it's pointless and
- * annoying to warn about it just because the same file can be
- * opened for reading. (Indeed, if it's a named pipe, opening it
- * for reading actually _causes inconvenience_ in its own right,
- * even before the question of whether it gives misleading
- * information.)
- */
- if (S_ISREG(st.st_mode) && st.st_size > 0) {
- return true;
- }
-
- return false;
-}
diff --git a/UNIX/UXNET.C b/UNIX/UXNET.C
deleted file mode 100644
index bd6ebb1d..00000000
--- a/UNIX/UXNET.C
+++ /dev/null
@@ -1,1761 +0,0 @@
-/*
- * Unix networking abstraction.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/ioctl.h>
-#include <arpa/inet.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <netdb.h>
-#include <sys/un.h>
-#include <pwd.h>
-#include <grp.h>
-
-#include "putty.h"
-#include "network.h"
-#include "tree234.h"
-
-/* Solaris needs <sys/sockio.h> for SIOCATMARK. */
-#ifndef SIOCATMARK
-#include <sys/sockio.h>
-#endif
-
-#ifndef X11_UNIX_PATH
-# define X11_UNIX_PATH "/tmp/.X11-unix/X"
-#endif
-
-/*
- * Access to sockaddr types without breaking C strict aliasing rules.
- */
-union sockaddr_union {
- struct sockaddr_storage storage;
- struct sockaddr sa;
- struct sockaddr_in sin;
-#ifndef NO_IPV6
- struct sockaddr_in6 sin6;
-#endif
- struct sockaddr_un su;
-};
-
-/*
- * Mutable state that goes with a SockAddr: stores information
- * about where in the list of candidate IP(v*) addresses we've
- * currently got to.
- */
-typedef struct SockAddrStep_tag SockAddrStep;
-struct SockAddrStep_tag {
-#ifndef NO_IPV6
- struct addrinfo *ai; /* steps along addr->ais */
-#endif
- int curraddr;
-};
-
-typedef struct NetSocket NetSocket;
-struct NetSocket {
- const char *error;
- int s;
- Plug *plug;
- bufchain output_data;
- bool connected; /* irrelevant for listening sockets */
- bool writable;
- bool frozen; /* this causes readability notifications to be ignored */
- bool localhost_only; /* for listening sockets */
- char oobdata[1];
- size_t sending_oob;
- bool oobpending; /* is there OOB data available to read? */
- bool oobinline;
- enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
- bool incomingeof;
- int pending_error; /* in case send() returns error */
- bool listener;
- bool nodelay, keepalive; /* for connect()-type sockets */
- bool privport;
- int port; /* and again */
- SockAddr *addr;
- SockAddrStep step;
- /*
- * We sometimes need pairs of Socket structures to be linked:
- * if we are listening on the same IPv6 and v4 port, for
- * example. So here we define `parent' and `child' pointers to
- * track this link.
- */
- NetSocket *parent, *child;
-
- Socket sock;
-};
-
-struct SockAddr {
- int refcount;
- const char *error;
- enum { UNRESOLVED, UNIX, IP } superfamily;
-#ifndef NO_IPV6
- struct addrinfo *ais; /* Addresses IPv6 style. */
-#else
- unsigned long *addresses; /* Addresses IPv4 style. */
- int naddresses;
-#endif
- char hostname[512]; /* Store an unresolved host name. */
-};
-
-/*
- * Which address family this address belongs to. AF_INET for IPv4;
- * AF_INET6 for IPv6; AF_UNSPEC indicates that name resolution has
- * not been done and a simple host name is held in this SockAddr
- * structure.
- */
-#ifndef NO_IPV6
-#define SOCKADDR_FAMILY(addr, step) \
- ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \
- (addr)->superfamily == UNIX ? AF_UNIX : \
- (step).ai ? (step).ai->ai_family : AF_INET)
-#else
-/* Here we gratuitously reference 'step' to avoid gcc warnings about
- * 'set but not used' when compiling -DNO_IPV6 */
-#define SOCKADDR_FAMILY(addr, step) \
- ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \
- (addr)->superfamily == UNIX ? AF_UNIX : \
- (step).curraddr ? AF_INET : AF_INET)
-#endif
-
-/*
- * Start a SockAddrStep structure to step through multiple
- * addresses.
- */
-#ifndef NO_IPV6
-#define START_STEP(addr, step) \
- ((step).ai = (addr)->ais, (step).curraddr = 0)
-#else
-#define START_STEP(addr, step) \
- ((step).curraddr = 0)
-#endif
-
-static tree234 *sktree;
-
-static void uxsel_tell(NetSocket *s);
-
-static int cmpfortree(void *av, void *bv)
-{
- NetSocket *a = (NetSocket *) av, *b = (NetSocket *) bv;
- int as = a->s, bs = b->s;
- if (as < bs)
- return -1;
- if (as > bs)
- return +1;
- if (a < b)
- return -1;
- if (a > b)
- return +1;
- return 0;
-}
-
-static int cmpforsearch(void *av, void *bv)
-{
- NetSocket *b = (NetSocket *) bv;
- int as = *(int *)av, bs = b->s;
- if (as < bs)
- return -1;
- if (as > bs)
- return +1;
- return 0;
-}
-
-void sk_init(void)
-{
- sktree = newtree234(cmpfortree);
-}
-
-void sk_cleanup(void)
-{
- NetSocket *s;
- int i;
-
- if (sktree) {
- for (i = 0; (s = index234(sktree, i)) != NULL; i++) {
- close(s->s);
- }
- }
-}
-
-SockAddr *sk_namelookup(const char *host, char **canonicalname, int address_family)
-{
- if (host[0] == '/') {
- *canonicalname = dupstr(host);
- return unix_sock_addr(host);
- }
-
- SockAddr *ret = snew(SockAddr);
-#ifndef NO_IPV6
- struct addrinfo hints;
- int err;
-#else
- unsigned long a;
- struct hostent *h = NULL;
- int n;
-#endif
- strbuf *realhost = strbuf_new();
-
- /* Clear the structure and default to IPv4. */
- memset(ret, 0, sizeof(SockAddr));
- ret->superfamily = UNRESOLVED;
- ret->error = NULL;
- ret->refcount = 1;
-
-#ifndef NO_IPV6
- hints.ai_flags = AI_CANONNAME;
- hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET :
- address_family == ADDRTYPE_IPV6 ? AF_INET6 :
- AF_UNSPEC);
- hints.ai_socktype = SOCK_STREAM;
- hints.ai_protocol = 0;
- hints.ai_addrlen = 0;
- hints.ai_addr = NULL;
- hints.ai_canonname = NULL;
- hints.ai_next = NULL;
- {
- char *trimmed_host = host_strduptrim(host); /* strip [] on literals */
- err = getaddrinfo(trimmed_host, NULL, &hints, &ret->ais);
- sfree(trimmed_host);
- }
- if (err != 0) {
- ret->error = gai_strerror(err);
- strbuf_free(realhost);
- return ret;
- }
- ret->superfamily = IP;
-
- if (ret->ais->ai_canonname != NULL)
- strbuf_catf(realhost, "%s", ret->ais->ai_canonname);
- else
- strbuf_catf(realhost, "%s", host);
-#else
- if ((a = inet_addr(host)) == (unsigned long)(in_addr_t)(-1)) {
- /*
- * Otherwise use the IPv4-only gethostbyname... (NOTE:
- * we don't use gethostbyname as a fallback!)
- */
- if (ret->superfamily == UNRESOLVED) {
- /*debug("Resolving \"%s\" with gethostbyname() (IPv4 only)...\n", host); */
- if ( (h = gethostbyname(host)) )
- ret->superfamily = IP;
- }
- if (ret->superfamily == UNRESOLVED) {
- ret->error = (h_errno == HOST_NOT_FOUND ||
- h_errno == NO_DATA ||
- h_errno == NO_ADDRESS ? "Host does not exist" :
- h_errno == TRY_AGAIN ?
- "Temporary name service failure" :
- "gethostbyname: unknown error");
- strbuf_free(realhost);
- return ret;
- }
- /* This way we are always sure the h->h_name is valid :) */
- strbuf_clear(realhost);
- strbuf_catf(realhost, "%s", h->h_name);
- for (n = 0; h->h_addr_list[n]; n++);
- ret->addresses = snewn(n, unsigned long);
- ret->naddresses = n;
- for (n = 0; n < ret->naddresses; n++) {
- memcpy(&a, h->h_addr_list[n], sizeof(a));
- ret->addresses[n] = ntohl(a);
- }
- } else {
- /*
- * This must be a numeric IPv4 address because it caused a
- * success return from inet_addr.
- */
- ret->superfamily = IP;
- strbuf_clear(realhost);
- strbuf_catf(realhost, "%s", host);
- ret->addresses = snew(unsigned long);
- ret->naddresses = 1;
- ret->addresses[0] = ntohl(a);
- }
-#endif
- *canonicalname = strbuf_to_str(realhost);
- return ret;
-}
-
-SockAddr *sk_nonamelookup(const char *host)
-{
- SockAddr *ret = snew(SockAddr);
- ret->error = NULL;
- ret->superfamily = UNRESOLVED;
- strncpy(ret->hostname, host, lenof(ret->hostname));
- ret->hostname[lenof(ret->hostname)-1] = '\0';
-#ifndef NO_IPV6
- ret->ais = NULL;
-#else
- ret->addresses = NULL;
-#endif
- ret->refcount = 1;
- return ret;
-}
-
-static bool sk_nextaddr(SockAddr *addr, SockAddrStep *step)
-{
-#ifndef NO_IPV6
- if (step->ai && step->ai->ai_next) {
- step->ai = step->ai->ai_next;
- return true;
- } else
- return false;
-#else
- if (step->curraddr+1 < addr->naddresses) {
- step->curraddr++;
- return true;
- } else {
- return false;
- }
-#endif
-}
-
-void sk_getaddr(SockAddr *addr, char *buf, int buflen)
-{
- if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) {
- strncpy(buf, addr->hostname, buflen);
- buf[buflen-1] = '\0';
- } else {
-#ifndef NO_IPV6
- if (getnameinfo(addr->ais->ai_addr, addr->ais->ai_addrlen, buf, buflen,
- NULL, 0, NI_NUMERICHOST) != 0) {
- buf[0] = '\0';
- strncat(buf, "<unknown>", buflen - 1);
- }
-#else
- struct in_addr a;
- SockAddrStep step;
- START_STEP(addr, step);
- assert(SOCKADDR_FAMILY(addr, step) == AF_INET);
- a.s_addr = htonl(addr->addresses[0]);
- strncpy(buf, inet_ntoa(a), buflen);
- buf[buflen-1] = '\0';
-#endif
- }
-}
-
-/*
- * This constructs a SockAddr that points at one specific sub-address
- * of a parent SockAddr. The returned SockAddr does not own all its
- * own memory: it points into the old one's data structures, so it
- * MUST NOT be used after the old one is freed, and it MUST NOT be
- * passed to sk_addr_free. (The latter is why it's returned by value
- * rather than dynamically allocated - that should clue in anyone
- * writing a call to it that something is weird about it.)
- */
-static SockAddr sk_extractaddr_tmp(
- SockAddr *addr, const SockAddrStep *step)
-{
- SockAddr toret;
- toret = *addr; /* structure copy */
- toret.refcount = 1;
-
- if (addr->superfamily == IP) {
-#ifndef NO_IPV6
- toret.ais = step->ai;
-#else
- assert(SOCKADDR_FAMILY(addr, *step) == AF_INET);
- toret.addresses += step->curraddr;
-#endif
- }
-
- return toret;
-}
-
-bool sk_addr_needs_port(SockAddr *addr)
-{
- if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) {
- return false;
- } else {
- return true;
- }
-}
-
-bool sk_hostname_is_local(const char *name)
-{
- return !strcmp(name, "localhost") ||
- !strcmp(name, "::1") ||
- !strncmp(name, "127.", 4);
-}
-
-#define ipv4_is_loopback(addr) \
- (((addr).s_addr & htonl(0xff000000)) == htonl(0x7f000000))
-
-static bool sockaddr_is_loopback(struct sockaddr *sa)
-{
- union sockaddr_union *u = (union sockaddr_union *)sa;
- switch (u->sa.sa_family) {
- case AF_INET:
- return ipv4_is_loopback(u->sin.sin_addr);
-#ifndef NO_IPV6
- case AF_INET6:
- return IN6_IS_ADDR_LOOPBACK(&u->sin6.sin6_addr);
-#endif
- case AF_UNIX:
- return true;
- default:
- return false;
- }
-}
-
-bool sk_address_is_local(SockAddr *addr)
-{
- if (addr->superfamily == UNRESOLVED)
- return false; /* we don't know; assume not */
- else if (addr->superfamily == UNIX)
- return true;
- else {
-#ifndef NO_IPV6
- return sockaddr_is_loopback(addr->ais->ai_addr);
-#else
- struct in_addr a;
- SockAddrStep step;
- START_STEP(addr, step);
- assert(SOCKADDR_FAMILY(addr, step) == AF_INET);
- a.s_addr = htonl(addr->addresses[0]);
- return ipv4_is_loopback(a);
-#endif
- }
-}
-
-bool sk_address_is_special_local(SockAddr *addr)
-{
- return addr->superfamily == UNIX;
-}
-
-int sk_addrtype(SockAddr *addr)
-{
- SockAddrStep step;
- int family;
- START_STEP(addr, step);
- family = SOCKADDR_FAMILY(addr, step);
-
- return (family == AF_INET ? ADDRTYPE_IPV4 :
-#ifndef NO_IPV6
- family == AF_INET6 ? ADDRTYPE_IPV6 :
-#endif
- ADDRTYPE_NAME);
-}
-
-void sk_addrcopy(SockAddr *addr, char *buf)
-{
- SockAddrStep step;
- int family;
- START_STEP(addr, step);
- family = SOCKADDR_FAMILY(addr, step);
-
-#ifndef NO_IPV6
- if (family == AF_INET)
- memcpy(buf, &((struct sockaddr_in *)step.ai->ai_addr)->sin_addr,
- sizeof(struct in_addr));
- else if (family == AF_INET6)
- memcpy(buf, &((struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr,
- sizeof(struct in6_addr));
- else
- unreachable("bad address family in sk_addrcopy");
-#else
- struct in_addr a;
-
- assert(family == AF_INET);
- a.s_addr = htonl(addr->addresses[step.curraddr]);
- memcpy(buf, (char*) &a.s_addr, 4);
-#endif
-}
-
-void sk_addr_free(SockAddr *addr)
-{
- if (--addr->refcount > 0)
- return;
-#ifndef NO_IPV6
- if (addr->ais != NULL)
- freeaddrinfo(addr->ais);
-#else
- sfree(addr->addresses);
-#endif
- sfree(addr);
-}
-
-SockAddr *sk_addr_dup(SockAddr *addr)
-{
- addr->refcount++;
- return addr;
-}
-
-static Plug *sk_net_plug(Socket *sock, Plug *p)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
- Plug *ret = s->plug;
- if (p)
- s->plug = p;
- return ret;
-}
-
-static void sk_net_close(Socket *s);
-static size_t sk_net_write(Socket *s, const void *data, size_t len);
-static size_t sk_net_write_oob(Socket *s, const void *data, size_t len);
-static void sk_net_write_eof(Socket *s);
-static void sk_net_set_frozen(Socket *s, bool is_frozen);
-static SocketPeerInfo *sk_net_peer_info(Socket *s);
-static const char *sk_net_socket_error(Socket *s);
-
-static const SocketVtable NetSocket_sockvt = {
- .plug = sk_net_plug,
- .close = sk_net_close,
- .write = sk_net_write,
- .write_oob = sk_net_write_oob,
- .write_eof = sk_net_write_eof,
- .set_frozen = sk_net_set_frozen,
- .socket_error = sk_net_socket_error,
- .peer_info = sk_net_peer_info,
-};
-
-static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug)
-{
- int sockfd = ctx.i;
- NetSocket *ret;
-
- /*
- * Create NetSocket structure.
- */
- ret = snew(NetSocket);
- ret->sock.vt = &NetSocket_sockvt;
- ret->error = NULL;
- ret->plug = plug;
- bufchain_init(&ret->output_data);
- ret->writable = true; /* to start with */
- ret->sending_oob = 0;
- ret->frozen = true;
- ret->localhost_only = false; /* unused, but best init anyway */
- ret->pending_error = 0;
- ret->oobpending = false;
- ret->outgoingeof = EOF_NO;
- ret->incomingeof = false;
- ret->listener = false;
- ret->parent = ret->child = NULL;
- ret->addr = NULL;
- ret->connected = true;
-
- ret->s = sockfd;
-
- if (ret->s < 0) {
- ret->error = strerror(errno);
- return &ret->sock;
- }
-
- ret->oobinline = false;
-
- uxsel_tell(ret);
- add234(sktree, ret);
-
- return &ret->sock;
-}
-
-static int try_connect(NetSocket *sock)
-{
- int s;
- union sockaddr_union u;
- const union sockaddr_union *sa;
- int err = 0;
- short localport;
- int salen, family;
-
- /*
- * Remove the socket from the tree before we overwrite its
- * internal socket id, because that forms part of the tree's
- * sorting criterion. We'll add it back before exiting this
- * function, whether we changed anything or not.
- */
- del234(sktree, sock);
-
- if (sock->s >= 0)
- close(sock->s);
-
- {
- SockAddr thisaddr = sk_extractaddr_tmp(
- sock->addr, &sock->step);
- plug_log(sock->plug, PLUGLOG_CONNECT_TRYING,
- &thisaddr, sock->port, NULL, 0);
- }
-
- /*
- * Open socket.
- */
- family = SOCKADDR_FAMILY(sock->addr, sock->step);
- assert(family != AF_UNSPEC);
- s = socket(family, SOCK_STREAM, 0);
- sock->s = s;
-
- if (s < 0) {
- err = errno;
- goto ret;
- }
-
- cloexec(s);
-
- if (sock->oobinline) {
- int b = 1;
- if (setsockopt(s, SOL_SOCKET, SO_OOBINLINE,
- (void *) &b, sizeof(b)) < 0) {
- err = errno;
- close(s);
- goto ret;
- }
- }
-
- if (sock->nodelay && family != AF_UNIX) {
- int b = 1;
- if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY,
- (void *) &b, sizeof(b)) < 0) {
- err = errno;
- close(s);
- goto ret;
- }
- }
-
- if (sock->keepalive) {
- int b = 1;
- if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
- (void *) &b, sizeof(b)) < 0) {
- err = errno;
- close(s);
- goto ret;
- }
- }
-
- /*
- * Bind to local address.
- */
- if (sock->privport)
- localport = 1023; /* count from 1023 downwards */
- else
- localport = 0; /* just use port 0 (ie kernel picks) */
-
- /* BSD IP stacks need sockaddr_in zeroed before filling in */
- memset(&u,'\0',sizeof(u));
-
- /* We don't try to bind to a local address for UNIX domain sockets. (Why
- * do we bother doing the bind when localport == 0 anyway?) */
- if (family != AF_UNIX) {
- /* Loop round trying to bind */
- while (1) {
- int retcode;
-
-#ifndef NO_IPV6
- if (family == AF_INET6) {
- /* XXX use getaddrinfo to get a local address? */
- u.sin6.sin6_family = AF_INET6;
- u.sin6.sin6_addr = in6addr_any;
- u.sin6.sin6_port = htons(localport);
- retcode = bind(s, &u.sa, sizeof(u.sin6));
- } else
-#endif
- {
- assert(family == AF_INET);
- u.sin.sin_family = AF_INET;
- u.sin.sin_addr.s_addr = htonl(INADDR_ANY);
- u.sin.sin_port = htons(localport);
- retcode = bind(s, &u.sa, sizeof(u.sin));
- }
- if (retcode >= 0) {
- err = 0;
- break; /* done */
- } else {
- err = errno;
- if (err != EADDRINUSE) /* failed, for a bad reason */
- break;
- }
-
- if (localport == 0)
- break; /* we're only looping once */
- localport--;
- if (localport == 0)
- break; /* we might have got to the end */
- }
-
- if (err)
- goto ret;
- }
-
- /*
- * Connect to remote address.
- */
- switch(family) {
-#ifndef NO_IPV6
- case AF_INET:
- /* XXX would be better to have got getaddrinfo() to fill in the port. */
- ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port =
- htons(sock->port);
- sa = (const union sockaddr_union *)sock->step.ai->ai_addr;
- salen = sock->step.ai->ai_addrlen;
- break;
- case AF_INET6:
- ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port =
- htons(sock->port);
- sa = (const union sockaddr_union *)sock->step.ai->ai_addr;
- salen = sock->step.ai->ai_addrlen;
- break;
-#else
- case AF_INET:
- u.sin.sin_family = AF_INET;
- u.sin.sin_addr.s_addr = htonl(sock->addr->addresses[sock->step.curraddr]);
- u.sin.sin_port = htons((short) sock->port);
- sa = &u;
- salen = sizeof u.sin;
- break;
-#endif
- case AF_UNIX:
- assert(strlen(sock->addr->hostname) < sizeof u.su.sun_path);
- u.su.sun_family = AF_UNIX;
- strcpy(u.su.sun_path, sock->addr->hostname);
- sa = &u;
- salen = sizeof u.su;
- break;
-
- default:
- unreachable("unknown address family");
- exit(1); /* XXX: GCC doesn't understand assert() on some systems. */
- }
-
- nonblock(s);
-
- if ((connect(s, &(sa->sa), salen)) < 0) {
- if ( errno != EINPROGRESS ) {
- err = errno;
- goto ret;
- }
- } else {
- /*
- * If we _don't_ get EWOULDBLOCK, the connect has completed
- * and we should set the socket as connected and writable.
- */
- sock->connected = true;
- sock->writable = true;
-
- SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step);
- plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS,
- &thisaddr, sock->port, NULL, 0);
- }
-
- uxsel_tell(sock);
-
- ret:
-
- /*
- * No matter what happened, put the socket back in the tree.
- */
- add234(sktree, sock);
-
- if (err) {
- SockAddr thisaddr = sk_extractaddr_tmp(
- sock->addr, &sock->step);
- plug_log(sock->plug, PLUGLOG_CONNECT_FAILED,
- &thisaddr, sock->port, strerror(err), err);
- }
- return err;
-}
-
-Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline,
- bool nodelay, bool keepalive, Plug *plug)
-{
- NetSocket *ret;
- int 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 */
- ret->writable = false; /* to start with */
- ret->sending_oob = 0;
- ret->frozen = false;
- ret->localhost_only = false; /* unused, but best init anyway */
- ret->pending_error = 0;
- ret->parent = ret->child = NULL;
- ret->oobpending = false;
- ret->outgoingeof = EOF_NO;
- ret->incomingeof = false;
- ret->listener = false;
- ret->addr = addr;
- START_STEP(ret->addr, ret->step);
- ret->s = -1;
- ret->oobinline = oobinline;
- ret->nodelay = nodelay;
- ret->keepalive = keepalive;
- ret->privport = privport;
- ret->port = port;
-
- do {
- err = try_connect(ret);
- } while (err && sk_nextaddr(ret->addr, &ret->step));
-
- if (err)
- ret->error = strerror(err);
-
- return &ret->sock;
-}
-
-Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
- bool local_host_only, int orig_address_family)
-{
- int s;
-#ifndef NO_IPV6
- struct addrinfo hints, *ai = NULL;
- char portstr[6];
-#endif
- union sockaddr_union u;
- union sockaddr_union *addr;
- int addrlen;
- NetSocket *ret;
- int retcode;
- int address_family;
- int on = 1;
-
- /*
- * Create NetSocket structure.
- */
- ret = snew(NetSocket);
- ret->sock.vt = &NetSocket_sockvt;
- ret->error = NULL;
- ret->plug = plug;
- bufchain_init(&ret->output_data);
- ret->writable = false; /* to start with */
- ret->sending_oob = 0;
- ret->frozen = false;
- ret->localhost_only = local_host_only;
- ret->pending_error = 0;
- ret->parent = ret->child = NULL;
- ret->oobpending = false;
- ret->outgoingeof = EOF_NO;
- ret->incomingeof = false;
- ret->listener = true;
- ret->addr = NULL;
- ret->s = -1;
-
- /*
- * Translate address_family from platform-independent constants
- * into local reality.
- */
- address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET :
-#ifndef NO_IPV6
- orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 :
-#endif
- AF_UNSPEC);
-
-#ifndef NO_IPV6
- /* Let's default to IPv6.
- * If the stack doesn't support IPv6, we will fall back to IPv4. */
- if (address_family == AF_UNSPEC) address_family = AF_INET6;
-#else
- /* No other choice, default to IPv4 */
- if (address_family == AF_UNSPEC) address_family = AF_INET;
-#endif
-
- /*
- * Open socket.
- */
- s = socket(address_family, SOCK_STREAM, 0);
-
-#ifndef NO_IPV6
- /* If the host doesn't support IPv6 try fallback to IPv4. */
- if (s < 0 && address_family == AF_INET6) {
- address_family = AF_INET;
- s = socket(address_family, SOCK_STREAM, 0);
- }
-#endif
-
- if (s < 0) {
- ret->error = strerror(errno);
- return &ret->sock;
- }
-
- cloexec(s);
-
- ret->oobinline = false;
-
- if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
- (const char *)&on, sizeof(on)) < 0) {
- ret->error = strerror(errno);
- close(s);
- return &ret->sock;
- }
-
- retcode = -1;
- addr = NULL; addrlen = -1; /* placate optimiser */
-
- if (srcaddr != NULL) {
-#ifndef NO_IPV6
- hints.ai_flags = AI_NUMERICHOST;
- hints.ai_family = address_family;
- hints.ai_socktype = SOCK_STREAM;
- hints.ai_protocol = 0;
- hints.ai_addrlen = 0;
- hints.ai_addr = NULL;
- hints.ai_canonname = NULL;
- hints.ai_next = NULL;
- assert(port >= 0 && port <= 99999);
- sprintf(portstr, "%d", port);
- {
- char *trimmed_addr = host_strduptrim(srcaddr);
- retcode = getaddrinfo(trimmed_addr, portstr, &hints, &ai);
- sfree(trimmed_addr);
- }
- if (retcode == 0) {
- addr = (union sockaddr_union *)ai->ai_addr;
- addrlen = ai->ai_addrlen;
- }
-#else
- memset(&u,'\0',sizeof u);
- u.sin.sin_family = AF_INET;
- u.sin.sin_port = htons(port);
- u.sin.sin_addr.s_addr = inet_addr(srcaddr);
- if (u.sin.sin_addr.s_addr != (in_addr_t)(-1)) {
- /* Override localhost_only with specified listen addr. */
- ret->localhost_only = ipv4_is_loopback(u.sin.sin_addr);
- }
- addr = &u;
- addrlen = sizeof(u.sin);
- retcode = 0;
-#endif
- }
-
- if (retcode != 0) {
- memset(&u,'\0',sizeof u);
-#ifndef NO_IPV6
- if (address_family == AF_INET6) {
- u.sin6.sin6_family = AF_INET6;
- u.sin6.sin6_port = htons(port);
- if (local_host_only)
- u.sin6.sin6_addr = in6addr_loopback;
- else
- u.sin6.sin6_addr = in6addr_any;
- addr = &u;
- addrlen = sizeof(u.sin6);
- } else
-#endif
- {
- u.sin.sin_family = AF_INET;
- u.sin.sin_port = htons(port);
- if (local_host_only)
- u.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- else
- u.sin.sin_addr.s_addr = htonl(INADDR_ANY);
- addr = &u;
- addrlen = sizeof(u.sin);
- }
- }
-
- retcode = bind(s, &addr->sa, addrlen);
-
-#ifndef NO_IPV6
- if (ai)
- freeaddrinfo(ai);
-#endif
-
- if (retcode < 0) {
- close(s);
- ret->error = strerror(errno);
- return &ret->sock;
- }
-
- if (listen(s, SOMAXCONN) < 0) {
- close(s);
- ret->error = strerror(errno);
- return &ret->sock;
- }
-
-#ifndef NO_IPV6
- /*
- * If we were given ADDRTYPE_UNSPEC, we must also create an
- * IPv4 listening socket and link it to this one.
- */
- if (address_family == AF_INET6 && orig_address_family == ADDRTYPE_UNSPEC) {
- NetSocket *other;
-
- other = container_of(
- sk_newlistener(srcaddr, port, plug,
- local_host_only, ADDRTYPE_IPV4),
- NetSocket, sock);
-
- if (other) {
- if (!other->error) {
- other->parent = ret;
- ret->child = other;
- } else {
- /* If we couldn't create a listening socket on IPv4 as well
- * as IPv6, we must return an error overall. */
- close(s);
- sfree(ret);
- return &other->sock;
- }
- }
- }
-#endif
-
- ret->s = s;
-
- uxsel_tell(ret);
- add234(sktree, ret);
-
- return &ret->sock;
-}
-
-static void sk_net_close(Socket *sock)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
-
- if (s->child)
- sk_net_close(&s->child->sock);
-
- bufchain_clear(&s->output_data);
-
- del234(sktree, s);
- if (s->s >= 0) {
- uxsel_del(s->s);
- close(s->s);
- }
- if (s->addr)
- sk_addr_free(s->addr);
- delete_callbacks_for_context(s);
- sfree(s);
-}
-
-void *sk_getxdmdata(Socket *sock, int *lenp)
-{
- NetSocket *s;
- union sockaddr_union u;
- socklen_t addrlen;
- char *buf;
- static unsigned int unix_addr = 0xFFFFFFFF;
-
- /*
- * We must check that this socket really _is_ a NetSocket before
- * downcasting it.
- */
- if (sock->vt != &NetSocket_sockvt)
- return NULL; /* failure */
- s = container_of(sock, NetSocket, sock);
-
- addrlen = sizeof(u);
- if (getsockname(s->s, &u.sa, &addrlen) < 0)
- return NULL;
- switch(u.sa.sa_family) {
- case AF_INET:
- *lenp = 6;
- buf = snewn(*lenp, char);
- PUT_32BIT_MSB_FIRST(buf, ntohl(u.sin.sin_addr.s_addr));
- PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin.sin_port));
- break;
-#ifndef NO_IPV6
- case AF_INET6:
- *lenp = 6;
- buf = snewn(*lenp, char);
- if (IN6_IS_ADDR_V4MAPPED(&u.sin6.sin6_addr)) {
- memcpy(buf, u.sin6.sin6_addr.s6_addr + 12, 4);
- PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin6.sin6_port));
- } else
- /* This is stupid, but it's what XLib does. */
- memset(buf, 0, 6);
- break;
-#endif
- case AF_UNIX:
- *lenp = 6;
- buf = snewn(*lenp, char);
- PUT_32BIT_MSB_FIRST(buf, unix_addr--);
- PUT_16BIT_MSB_FIRST(buf+4, getpid());
- break;
-
- /* XXX IPV6 */
-
- default:
- return NULL;
- }
-
- return buf;
-}
-
-/*
- * 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, strerror(s->pending_error), s->pending_error, 0);
-}
-
-/*
- * The function which tries to send on a socket once it's deemed
- * writable.
- */
-void try_send(NetSocket *s)
-{
- while (s->sending_oob || bufchain_size(&s->output_data) > 0) {
- int nsent;
- int err;
- const void *data;
- size_t len;
- int urgentflag;
-
- if (s->sending_oob) {
- urgentflag = MSG_OOB;
- len = s->sending_oob;
- data = &s->oobdata;
- } else {
- urgentflag = 0;
- ptrlen bufdata = bufchain_prefix(&s->output_data);
- data = bufdata.ptr;
- len = bufdata.len;
- }
- nsent = send(s->s, data, len, urgentflag);
- noise_ultralight(NOISE_SOURCE_IOLEN, nsent);
- if (nsent <= 0) {
- err = (nsent < 0 ? errno : 0);
- if (err == EWOULDBLOCK) {
- /*
- * Perfectly normal: we've sent all we can for the moment.
- */
- s->writable = false;
- return;
- } else {
- /*
- * We unfortunately can't just call plug_closing(),
- * because it's quite likely that we're currently
- * _in_ a call from the code we'd be calling back
- * to, so we'd have to make half the SSH code
- * reentrant. Instead we flag a pending error on
- * the socket, to be dealt with (by calling
- * plug_closing()) at some suitable future moment.
- */
- s->pending_error = err;
- /*
- * Immediately cease selecting on this socket, so that
- * we don't tight-loop repeatedly trying to do
- * whatever it was that went wrong.
- */
- uxsel_tell(s);
- /*
- * Arrange to be called back from the top level to
- * deal with the error condition on this socket.
- */
- queue_toplevel_callback(socket_error_callback, s);
- return;
- }
- } else {
- if (s->sending_oob) {
- if (nsent < len) {
- memmove(s->oobdata, s->oobdata+nsent, len-nsent);
- s->sending_oob = len - nsent;
- } else {
- s->sending_oob = 0;
- }
- } else {
- bufchain_consume(&s->output_data, nsent);
- }
- }
- }
-
- /*
- * If we reach here, we've finished sending everything we might
- * have needed to send. Send EOF, if we need to.
- */
- if (s->outgoingeof == EOF_PENDING) {
- shutdown(s->s, SHUT_WR);
- s->outgoingeof = EOF_SENT;
- }
-
- /*
- * Also update the select status, because we don't need to select
- * for writing any more.
- */
- uxsel_tell(s);
-}
-
-static size_t sk_net_write(Socket *sock, const void *buf, size_t len)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
-
- assert(s->outgoingeof == EOF_NO);
-
- /*
- * Add the data to the buffer list on the socket.
- */
- bufchain_add(&s->output_data, buf, len);
-
- /*
- * Now try sending from the start of the buffer list.
- */
- if (s->writable)
- try_send(s);
-
- /*
- * Update the select() status to correctly reflect whether or
- * not we should be selecting for write.
- */
- uxsel_tell(s);
-
- return bufchain_size(&s->output_data);
-}
-
-static size_t sk_net_write_oob(Socket *sock, const void *buf, size_t len)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
-
- assert(s->outgoingeof == EOF_NO);
-
- /*
- * Replace the buffer list on the socket with the data.
- */
- bufchain_clear(&s->output_data);
- assert(len <= sizeof(s->oobdata));
- memcpy(s->oobdata, buf, len);
- s->sending_oob = len;
-
- /*
- * Now try sending from the start of the buffer list.
- */
- if (s->writable)
- try_send(s);
-
- /*
- * Update the select() status to correctly reflect whether or
- * not we should be selecting for write.
- */
- uxsel_tell(s);
-
- return s->sending_oob;
-}
-
-static void sk_net_write_eof(Socket *sock)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
-
- assert(s->outgoingeof == EOF_NO);
-
- /*
- * Mark the socket as pending outgoing EOF.
- */
- s->outgoingeof = EOF_PENDING;
-
- /*
- * Now try sending from the start of the buffer list.
- */
- if (s->writable)
- try_send(s);
-
- /*
- * Update the select() status to correctly reflect whether or
- * not we should be selecting for write.
- */
- uxsel_tell(s);
-}
-
-static void net_select_result(int fd, int event)
-{
- int ret;
- char buf[20480]; /* nice big buffer for plenty of speed */
- NetSocket *s;
- bool atmark = true;
-
- /* Find the Socket structure */
- s = find234(sktree, &fd, cmpforsearch);
- if (!s)
- return; /* boggle */
-
- noise_ultralight(NOISE_SOURCE_IOID, fd);
-
- switch (event) {
- case SELECT_X: /* exceptional */
- if (!s->oobinline) {
- /*
- * On a non-oobinline socket, this indicates that we
- * can immediately perform an OOB read and get back OOB
- * data, which we will send to the back end with
- * type==2 (urgent data).
- */
- ret = recv(s->s, buf, sizeof(buf), MSG_OOB);
- noise_ultralight(NOISE_SOURCE_IOLEN, ret);
- if (ret <= 0) {
- plug_closing(s->plug,
- ret == 0 ? "Internal networking trouble" :
- strerror(errno), errno, 0);
- } else {
- /*
- * Receiving actual data on a socket means we can
- * stop falling back through the candidate
- * addresses to connect to.
- */
- if (s->addr) {
- sk_addr_free(s->addr);
- s->addr = NULL;
- }
- plug_receive(s->plug, 2, buf, ret);
- }
- break;
- }
-
- /*
- * If we reach here, this is an oobinline socket, which
- * means we should set s->oobpending and then deal with it
- * when we get called for the readability event (which
- * should also occur).
- */
- s->oobpending = true;
- break;
- case SELECT_R: /* readable; also acceptance */
- if (s->listener) {
- /*
- * On a listening socket, the readability event means a
- * connection is ready to be accepted.
- */
- union sockaddr_union su;
- socklen_t addrlen = sizeof(su);
- accept_ctx_t actx;
- int t; /* socket of connection */
-
- memset(&su, 0, addrlen);
- t = accept(s->s, &su.sa, &addrlen);
- if (t < 0) {
- break;
- }
-
- nonblock(t);
- actx.i = t;
-
- if ((!s->addr || s->addr->superfamily != UNIX) &&
- s->localhost_only && !sockaddr_is_loopback(&su.sa)) {
- close(t); /* someone let nonlocal through?! */
- } else if (plug_accepting(s->plug, sk_net_accept, actx)) {
- close(t); /* denied or error */
- }
- break;
- }
-
- /*
- * If we reach here, this is not a listening socket, so
- * readability really means readability.
- */
-
- /* In the case the socket is still frozen, we don't even bother */
- if (s->frozen)
- break;
-
- /*
- * We have received data on the socket. For an oobinline
- * socket, this might be data _before_ an urgent pointer,
- * in which case we send it to the back end with type==1
- * (data prior to urgent).
- */
- if (s->oobinline && s->oobpending) {
- int atmark_from_ioctl;
- if (ioctl(s->s, SIOCATMARK, &atmark_from_ioctl) == 0) {
- atmark = atmark_from_ioctl;
- if (atmark)
- s->oobpending = false; /* clear this indicator */
- }
- } else
- atmark = true;
-
- ret = recv(s->s, buf, s->oobpending ? 1 : sizeof(buf), 0);
- noise_ultralight(NOISE_SOURCE_IOLEN, ret);
- if (ret < 0) {
- if (errno == EWOULDBLOCK) {
- break;
- }
- }
- if (ret < 0) {
- plug_closing(s->plug, strerror(errno), errno, 0);
- } else if (0 == ret) {
- s->incomingeof = true; /* stop trying to read now */
- uxsel_tell(s);
- plug_closing(s->plug, NULL, 0, 0);
- } else {
- /*
- * Receiving actual data on a socket means we can
- * stop falling back through the candidate
- * addresses to connect to.
- */
- if (s->addr) {
- sk_addr_free(s->addr);
- s->addr = NULL;
- }
- plug_receive(s->plug, atmark ? 0 : 1, buf, ret);
- }
- break;
- case SELECT_W: /* writable */
- if (!s->connected) {
- /*
- * select/poll reports a socket as _writable_ when an
- * asynchronous connect() attempt either completes or
- * fails. So first we must find out which.
- */
- {
- int err;
- socklen_t errlen = sizeof(err);
- char *errmsg = NULL;
- if (getsockopt(s->s, SOL_SOCKET, SO_ERROR, &err, &errlen)<0) {
- errmsg = dupprintf("getsockopt(SO_ERROR): %s",
- strerror(errno));
- err = errno; /* got to put something in here */
- } else if (err != 0) {
- errmsg = dupstr(strerror(err));
- }
- if (errmsg) {
- /*
- * The asynchronous connection attempt failed.
- * Report the problem via plug_log, and try again
- * with the next candidate address, if we have
- * more than one.
- */
- SockAddr thisaddr;
- assert(s->addr);
-
- thisaddr = sk_extractaddr_tmp(s->addr, &s->step);
- plug_log(s->plug, PLUGLOG_CONNECT_FAILED,
- &thisaddr, s->port, errmsg, err);
-
- while (err && s->addr && sk_nextaddr(s->addr, &s->step)) {
- err = try_connect(s);
- }
- if (err) {
- plug_closing(s->plug, strerror(err), err, 0);
- return; /* socket is now presumably defunct */
- }
- if (!s->connected)
- return; /* another async attempt in progress */
- } else {
- /*
- * The connection attempt succeeded.
- */
- SockAddr thisaddr = sk_extractaddr_tmp(s->addr, &s->step);
- plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS,
- &thisaddr, s->port, NULL, 0);
- }
- }
-
- /*
- * If we get here, we've managed to make a connection.
- */
- if (s->addr) {
- sk_addr_free(s->addr);
- s->addr = NULL;
- }
- s->connected = true;
- s->writable = true;
- uxsel_tell(s);
- } else {
- size_t bufsize_before, bufsize_after;
- s->writable = true;
- bufsize_before = s->sending_oob + bufchain_size(&s->output_data);
- try_send(s);
- bufsize_after = s->sending_oob + bufchain_size(&s->output_data);
- if (bufsize_after < bufsize_before)
- plug_sent(s->plug, bufsize_after);
- }
- break;
- }
-}
-
-/*
- * Special error values are returned from sk_namelookup and sk_new
- * if there's a problem. These functions extract an error message,
- * or return NULL if there's no problem.
- */
-const char *sk_addr_error(SockAddr *addr)
-{
- return addr->error;
-}
-static const char *sk_net_socket_error(Socket *sock)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
- return s->error;
-}
-
-static void sk_net_set_frozen(Socket *sock, bool is_frozen)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
- if (s->frozen == is_frozen)
- return;
- s->frozen = is_frozen;
- uxsel_tell(s);
-}
-
-static SocketPeerInfo *sk_net_peer_info(Socket *sock)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
- union sockaddr_union addr;
- socklen_t addrlen = sizeof(addr);
-#ifndef NO_IPV6
- char buf[INET6_ADDRSTRLEN];
-#endif
- SocketPeerInfo *pi;
-
- if (getpeername(s->s, &addr.sa, &addrlen) < 0)
- return NULL;
-
- pi = snew(SocketPeerInfo);
- pi->addressfamily = ADDRTYPE_UNSPEC;
- pi->addr_text = NULL;
- pi->port = -1;
- pi->log_text = NULL;
-
- if (addr.storage.ss_family == AF_INET) {
- pi->addressfamily = ADDRTYPE_IPV4;
- memcpy(pi->addr_bin.ipv4, &addr.sin.sin_addr, 4);
- pi->port = ntohs(addr.sin.sin_port);
- pi->addr_text = dupstr(inet_ntoa(addr.sin.sin_addr));
- pi->log_text = dupprintf("%s:%d", pi->addr_text, pi->port);
-
-#ifndef NO_IPV6
- } else if (addr.storage.ss_family == AF_INET6) {
- pi->addressfamily = ADDRTYPE_IPV6;
- memcpy(pi->addr_bin.ipv6, &addr.sin6.sin6_addr, 16);
- pi->port = ntohs(addr.sin6.sin6_port);
- pi->addr_text = dupstr(
- inet_ntop(AF_INET6, &addr.sin6.sin6_addr, buf, sizeof(buf)));
- pi->log_text = dupprintf("[%s]:%d", pi->addr_text, pi->port);
-#endif
-
- } else if (addr.storage.ss_family == AF_UNIX) {
- pi->addressfamily = ADDRTYPE_LOCAL;
-
- /*
- * For Unix sockets, the source address is unlikely to be
- * helpful, so we leave addr_txt NULL (and we certainly can't
- * fill in port, obviously). Instead, we try SO_PEERCRED and
- * try to get the source pid, and put that in the log text.
- */
- int pid, uid, gid;
- if (so_peercred(s->s, &pid, &uid, &gid)) {
- char uidbuf[64], gidbuf[64];
- sprintf(uidbuf, "%d", uid);
- sprintf(gidbuf, "%d", gid);
- struct passwd *pw = getpwuid(uid);
- struct group *gr = getgrgid(gid);
- pi->log_text = dupprintf("pid %d (%s:%s)", pid,
- pw ? pw->pw_name : uidbuf,
- gr ? gr->gr_name : gidbuf);
- }
- } else {
- sfree(pi);
- return NULL;
- }
-
- return pi;
-}
-
-int sk_net_get_fd(Socket *sock)
-{
- /* This function is not fully general: it only works on NetSocket */
- if (sock->vt != &NetSocket_sockvt)
- return -1; /* failure */
- NetSocket *s = container_of(sock, NetSocket, sock);
- return s->s;
-}
-
-static void uxsel_tell(NetSocket *s)
-{
- int rwx = 0;
- if (!s->pending_error) {
- if (s->listener) {
- rwx |= SELECT_R; /* read == accept */
- } else {
- if (!s->connected)
- rwx |= SELECT_W; /* write == connect */
- if (s->connected && !s->frozen && !s->incomingeof)
- rwx |= SELECT_R | SELECT_X;
- if (bufchain_size(&s->output_data))
- rwx |= SELECT_W;
- }
- }
- uxsel_set(s->s, rwx, net_select_result);
-}
-
-int net_service_lookup(char *service)
-{
- struct servent *se;
- se = getservbyname(service, NULL);
- if (se != NULL)
- return ntohs(se->s_port);
- else
- return 0;
-}
-
-char *get_hostname(void)
-{
- size_t size = 0;
- char *hostname = NULL;
- do {
- sgrowarray(hostname, size, size);
- if ((gethostname(hostname, size) < 0) && (errno != ENAMETOOLONG)) {
- sfree(hostname);
- hostname = NULL;
- break;
- }
- } while (strlen(hostname) >= size-1);
- return hostname;
-}
-
-SockAddr *platform_get_x11_unix_address(const char *sockpath, int displaynum)
-{
- SockAddr *ret = snew(SockAddr);
- int n;
-
- memset(ret, 0, sizeof *ret);
- ret->superfamily = UNIX;
- /*
- * In special circumstances (notably Mac OS X Leopard), we'll
- * have been passed an explicit Unix socket path.
- */
- if (sockpath) {
- n = snprintf(ret->hostname, sizeof ret->hostname,
- "%s", sockpath);
- } else {
- n = snprintf(ret->hostname, sizeof ret->hostname,
- "%s%d", X11_UNIX_PATH, displaynum);
- }
-
- if (n < 0)
- ret->error = "snprintf failed";
- else if (n >= sizeof ret->hostname)
- ret->error = "X11 UNIX name too long";
-
-#ifndef NO_IPV6
- ret->ais = NULL;
-#else
- ret->addresses = NULL;
- ret->naddresses = 0;
-#endif
- ret->refcount = 1;
- return ret;
-}
-
-SockAddr *unix_sock_addr(const char *path)
-{
- SockAddr *ret = snew(SockAddr);
- int n;
-
- memset(ret, 0, sizeof *ret);
- ret->superfamily = UNIX;
- n = snprintf(ret->hostname, sizeof ret->hostname, "%s", path);
-
- if (n < 0)
- ret->error = "snprintf failed";
- else if (n >= sizeof ret->hostname ||
- n >= sizeof(((struct sockaddr_un *)0)->sun_path))
- ret->error = "socket pathname too long";
-
-#ifndef NO_IPV6
- ret->ais = NULL;
-#else
- ret->addresses = NULL;
- ret->naddresses = 0;
-#endif
- ret->refcount = 1;
- return ret;
-}
-
-Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug)
-{
- int s;
- union sockaddr_union u;
- union sockaddr_union *addr;
- int addrlen;
- NetSocket *ret;
- int retcode;
-
- /*
- * Create NetSocket structure.
- */
- ret = snew(NetSocket);
- ret->sock.vt = &NetSocket_sockvt;
- ret->error = NULL;
- ret->plug = plug;
- bufchain_init(&ret->output_data);
- ret->writable = false; /* to start with */
- ret->sending_oob = 0;
- ret->frozen = false;
- ret->localhost_only = true;
- ret->pending_error = 0;
- ret->parent = ret->child = NULL;
- ret->oobpending = false;
- ret->outgoingeof = EOF_NO;
- ret->incomingeof = false;
- ret->listener = true;
- ret->addr = listenaddr;
- ret->s = -1;
-
- assert(listenaddr->superfamily == UNIX);
-
- /*
- * Open socket.
- */
- s = socket(AF_UNIX, SOCK_STREAM, 0);
- if (s < 0) {
- ret->error = strerror(errno);
- return &ret->sock;
- }
-
- cloexec(s);
-
- ret->oobinline = false;
-
- memset(&u, '\0', sizeof(u));
- u.su.sun_family = AF_UNIX;
-#if __GNUC__ >= 8
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wstringop-truncation"
-#endif // __GNUC__ >= 8
- strncpy(u.su.sun_path, listenaddr->hostname, sizeof(u.su.sun_path)-1);
-#if __GNUC__ >= 8
-# pragma GCC diagnostic pop
-#endif // __GNUC__ >= 8
- addr = &u;
- addrlen = sizeof(u.su);
-
- if (unlink(u.su.sun_path) < 0 && errno != ENOENT) {
- close(s);
- ret->error = strerror(errno);
- return &ret->sock;
- }
-
- retcode = bind(s, &addr->sa, addrlen);
- if (retcode < 0) {
- close(s);
- ret->error = strerror(errno);
- return &ret->sock;
- }
-
- if (listen(s, SOMAXCONN) < 0) {
- close(s);
- ret->error = strerror(errno);
- return &ret->sock;
- }
-
- ret->s = s;
-
- uxsel_tell(ret);
- add234(sktree, ret);
-
- return &ret->sock;
-}
diff --git a/UNIX/UXPLINK.C b/UNIX/UXPLINK.C
deleted file mode 100644
index 240783f4..00000000
--- a/UNIX/UXPLINK.C
+++ /dev/null
@@ -1,966 +0,0 @@
-/*
- * PLink - a command-line (stdin/stdout) variant of PuTTY.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <signal.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <termios.h>
-#include <pwd.h>
-#include <sys/ioctl.h>
-#include <sys/time.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "storage.h"
-#include "tree234.h"
-
-#define MAX_STDIN_BACKLOG 4096
-
-static LogContext *logctx;
-
-static struct termios orig_termios;
-
-void cmdline_error(const char *fmt, ...)
-{
- va_list ap;
- va_start(ap, fmt);
- console_print_error_msg_fmt_v("plink", fmt, ap);
- va_end(ap);
- exit(1);
-}
-
-static bool local_tty = false; /* do we have a local tty? */
-
-static Backend *backend;
-static Conf *conf;
-
-/*
- * Default settings that are specific to Unix plink.
- */
-char *platform_default_s(const char *name)
-{
- if (!strcmp(name, "TermType"))
- return dupstr(getenv("TERM"));
- if (!strcmp(name, "SerialLine"))
- return dupstr("/dev/ttyS0");
- return NULL;
-}
-
-bool platform_default_b(const char *name, bool def)
-{
- return def;
-}
-
-int platform_default_i(const char *name, int def)
-{
- return def;
-}
-
-FontSpec *platform_default_fontspec(const char *name)
-{
- return fontspec_new("");
-}
-
-Filename *platform_default_filename(const char *name)
-{
- if (!strcmp(name, "LogFileName"))
- return filename_from_str("putty.log");
- else
- return filename_from_str("");
-}
-
-char *x_get_default(const char *key)
-{
- return NULL; /* this is a stub */
-}
-static void plink_echoedit_update(Seat *seat, bool echo, bool edit)
-{
- /* Update stdin read mode to reflect changes in line discipline. */
- struct termios mode;
-
- if (!local_tty) return;
-
- mode = orig_termios;
-
- if (echo)
- mode.c_lflag |= ECHO;
- else
- mode.c_lflag &= ~ECHO;
-
- if (edit) {
- mode.c_iflag |= ICRNL;
- mode.c_lflag |= ISIG | ICANON;
- mode.c_oflag |= OPOST;
- } else {
- mode.c_iflag &= ~ICRNL;
- mode.c_lflag &= ~(ISIG | ICANON);
- mode.c_oflag &= ~OPOST;
- /* Solaris sets these to unhelpful values */
- mode.c_cc[VMIN] = 1;
- mode.c_cc[VTIME] = 0;
- /* FIXME: perhaps what we do with IXON/IXOFF should be an
- * argument to the echoedit_update() method, to allow
- * implementation of SSH-2 "xon-xoff" and Rlogin's
- * equivalent? */
- mode.c_iflag &= ~IXON;
- mode.c_iflag &= ~IXOFF;
- }
- /*
- * Mark parity errors and (more important) BREAK on input. This
- * is more complex than it need be because POSIX-2001 suggests
- * that escaping of valid 0xff in the input stream is dependent on
- * IGNPAR being clear even though marking of BREAK isn't. NetBSD
- * 2.0 goes one worse and makes it dependent on INPCK too. We
- * deal with this by forcing these flags into a useful state and
- * then faking the state in which we found them in from_tty() if
- * we get passed a parity or framing error.
- */
- mode.c_iflag = (mode.c_iflag | INPCK | PARMRK) & ~IGNPAR;
-
- tcsetattr(STDIN_FILENO, TCSANOW, &mode);
-}
-
-/* Helper function to extract a special character from a termios. */
-static char *get_ttychar(struct termios *t, int index)
-{
- cc_t c = t->c_cc[index];
-#if defined(_POSIX_VDISABLE)
- if (c == _POSIX_VDISABLE)
- return dupstr("");
-#endif
- return dupprintf("^<%d>", c);
-}
-
-static char *plink_get_ttymode(Seat *seat, const char *mode)
-{
- /*
- * Propagate appropriate terminal modes from the local terminal,
- * if any.
- */
- if (!local_tty) return NULL;
-
-#define GET_CHAR(ourname, uxname) \
- do { \
- if (strcmp(mode, ourname) == 0) \
- return get_ttychar(&orig_termios, uxname); \
- } while(0)
-#define GET_BOOL(ourname, uxname, uxmemb, transform) \
- do { \
- if (strcmp(mode, ourname) == 0) { \
- bool b = (orig_termios.uxmemb & uxname) != 0; \
- transform; \
- return dupprintf("%d", b); \
- } \
- } while (0)
-
- /*
- * Modes that want to be the same on all terminal devices involved.
- */
- /* All the special characters supported by SSH */
-#if defined(VINTR)
- GET_CHAR("INTR", VINTR);
-#endif
-#if defined(VQUIT)
- GET_CHAR("QUIT", VQUIT);
-#endif
-#if defined(VERASE)
- GET_CHAR("ERASE", VERASE);
-#endif
-#if defined(VKILL)
- GET_CHAR("KILL", VKILL);
-#endif
-#if defined(VEOF)
- GET_CHAR("EOF", VEOF);
-#endif
-#if defined(VEOL)
- GET_CHAR("EOL", VEOL);
-#endif
-#if defined(VEOL2)
- GET_CHAR("EOL2", VEOL2);
-#endif
-#if defined(VSTART)
- GET_CHAR("START", VSTART);
-#endif
-#if defined(VSTOP)
- GET_CHAR("STOP", VSTOP);
-#endif
-#if defined(VSUSP)
- GET_CHAR("SUSP", VSUSP);
-#endif
-#if defined(VDSUSP)
- GET_CHAR("DSUSP", VDSUSP);
-#endif
-#if defined(VREPRINT)
- GET_CHAR("REPRINT", VREPRINT);
-#endif
-#if defined(VWERASE)
- GET_CHAR("WERASE", VWERASE);
-#endif
-#if defined(VLNEXT)
- GET_CHAR("LNEXT", VLNEXT);
-#endif
-#if defined(VFLUSH)
- GET_CHAR("FLUSH", VFLUSH);
-#endif
-#if defined(VSWTCH)
- GET_CHAR("SWTCH", VSWTCH);
-#endif
-#if defined(VSTATUS)
- GET_CHAR("STATUS", VSTATUS);
-#endif
-#if defined(VDISCARD)
- GET_CHAR("DISCARD", VDISCARD);
-#endif
- /* Modes that "configure" other major modes. These should probably be
- * considered as user preferences. */
- /* Configuration of ICANON */
-#if defined(ECHOK)
- GET_BOOL("ECHOK", ECHOK, c_lflag, );
-#endif
-#if defined(ECHOKE)
- GET_BOOL("ECHOKE", ECHOKE, c_lflag, );
-#endif
-#if defined(ECHOE)
- GET_BOOL("ECHOE", ECHOE, c_lflag, );
-#endif
-#if defined(ECHONL)
- GET_BOOL("ECHONL", ECHONL, c_lflag, );
-#endif
-#if defined(XCASE)
- GET_BOOL("XCASE", XCASE, c_lflag, );
-#endif
-#if defined(IUTF8)
- GET_BOOL("IUTF8", IUTF8, c_iflag, );
-#endif
- /* Configuration of ECHO */
-#if defined(ECHOCTL)
- GET_BOOL("ECHOCTL", ECHOCTL, c_lflag, );
-#endif
- /* Configuration of IXON/IXOFF */
-#if defined(IXANY)
- GET_BOOL("IXANY", IXANY, c_iflag, );
-#endif
- /* Configuration of OPOST */
-#if defined(OLCUC)
- GET_BOOL("OLCUC", OLCUC, c_oflag, );
-#endif
-#if defined(ONLCR)
- GET_BOOL("ONLCR", ONLCR, c_oflag, );
-#endif
-#if defined(OCRNL)
- GET_BOOL("OCRNL", OCRNL, c_oflag, );
-#endif
-#if defined(ONOCR)
- GET_BOOL("ONOCR", ONOCR, c_oflag, );
-#endif
-#if defined(ONLRET)
- GET_BOOL("ONLRET", ONLRET, c_oflag, );
-#endif
-
- /*
- * Modes that want to be set in only one place, and that we have
- * squashed locally.
- */
-#if defined(ISIG)
- GET_BOOL("ISIG", ISIG, c_lflag, );
-#endif
-#if defined(ICANON)
- GET_BOOL("ICANON", ICANON, c_lflag, );
-#endif
-#if defined(ECHO)
- GET_BOOL("ECHO", ECHO, c_lflag, );
-#endif
-#if defined(IXON)
- GET_BOOL("IXON", IXON, c_iflag, );
-#endif
-#if defined(IXOFF)
- GET_BOOL("IXOFF", IXOFF, c_iflag, );
-#endif
-#if defined(OPOST)
- GET_BOOL("OPOST", OPOST, c_oflag, );
-#endif
-
- /*
- * We do not propagate the following modes:
- * - Parity/serial settings, which are a local affair and don't
- * make sense propagated over SSH's 8-bit byte-stream.
- * IGNPAR PARMRK INPCK CS7 CS8 PARENB PARODD
- * - Things that want to be enabled in one place that we don't
- * squash locally.
- * IUCLC
- * - Status bits.
- * PENDIN
- * - Things I don't know what to do with. (FIXME)
- * ISTRIP IMAXBEL NOFLSH TOSTOP IEXTEN
- * INLCR IGNCR ICRNL
- */
-
-#undef GET_CHAR
-#undef GET_BOOL
-
- /* Fall through to here for unrecognised names, or ones that are
- * unsupported on this platform */
- return NULL;
-}
-
-void cleanup_termios(void)
-{
- if (local_tty)
- tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
-}
-
-static bufchain stdout_data, stderr_data;
-static bufchain_sink stdout_bcs, stderr_bcs;
-static StripCtrlChars *stdout_scc, *stderr_scc;
-static BinarySink *stdout_bs, *stderr_bs;
-
-static enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
-
-size_t try_output(bool is_stderr)
-{
- bufchain *chain = (is_stderr ? &stderr_data : &stdout_data);
- int fd = (is_stderr ? STDERR_FILENO : STDOUT_FILENO);
- ssize_t ret;
-
- if (bufchain_size(chain) > 0) {
- bool prev_nonblock = nonblock(fd);
- ptrlen senddata;
- do {
- senddata = bufchain_prefix(chain);
- ret = write(fd, senddata.ptr, senddata.len);
- if (ret > 0)
- bufchain_consume(chain, ret);
- } while (ret == senddata.len && bufchain_size(chain) != 0);
- if (!prev_nonblock)
- no_nonblock(fd);
- if (ret < 0 && errno != EAGAIN) {
- perror(is_stderr ? "stderr: write" : "stdout: write");
- exit(1);
- }
- }
- if (outgoingeof == EOF_PENDING && bufchain_size(&stdout_data) == 0) {
- close(STDOUT_FILENO);
- outgoingeof = EOF_SENT;
- }
- return bufchain_size(&stdout_data) + bufchain_size(&stderr_data);
-}
-
-static size_t plink_output(
- Seat *seat, bool is_stderr, const void *data, size_t len)
-{
- assert(is_stderr || outgoingeof == EOF_NO);
-
- BinarySink *bs = is_stderr ? stderr_bs : stdout_bs;
- put_data(bs, data, len);
-
- return try_output(is_stderr);
-}
-
-static bool plink_eof(Seat *seat)
-{
- assert(outgoingeof == EOF_NO);
- outgoingeof = EOF_PENDING;
- try_output(false);
- return false; /* do not respond to incoming EOF with outgoing */
-}
-
-static 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 = plink_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 }};
-
-/*
- * Handle data from a local tty in PARMRK format.
- */
-static void from_tty(void *vbuf, unsigned len)
-{
- char *p, *q, *end, *buf = vbuf;
- static enum {NORMAL, FF, FF00} state = NORMAL;
-
- p = buf; end = buf + len;
- while (p < end) {
- switch (state) {
- case NORMAL:
- if (*p == '\xff') {
- p++;
- state = FF;
- } else {
- q = memchr(p, '\xff', end - p);
- if (q == NULL) q = end;
- backend_send(backend, p, q - p);
- p = q;
- }
- break;
- case FF:
- if (*p == '\xff') {
- backend_send(backend, p, 1);
- p++;
- state = NORMAL;
- } else if (*p == '\0') {
- p++;
- state = FF00;
- } else abort();
- break;
- case FF00:
- if (*p == '\0') {
- backend_special(backend, SS_BRK, 0);
- } else {
- /*
- * Pretend that PARMRK wasn't set. This involves
- * faking what INPCK and IGNPAR would have done if
- * we hadn't overridden them. Unfortunately, we
- * can't do this entirely correctly because INPCK
- * distinguishes between framing and parity
- * errors, but PARMRK format represents both in
- * the same way. We assume that parity errors are
- * more common than framing errors, and hence
- * treat all input errors as being subject to
- * INPCK.
- */
- if (orig_termios.c_iflag & INPCK) {
- /* If IGNPAR is set, we throw away the character. */
- if (!(orig_termios.c_iflag & IGNPAR)) {
- /* PE/FE get passed on as NUL. */
- *p = 0;
- backend_send(backend, p, 1);
- }
- } else {
- /* INPCK not set. Assume we got a parity error. */
- backend_send(backend, p, 1);
- }
- }
- p++;
- state = NORMAL;
- }
- }
-}
-
-static int signalpipe[2];
-
-void sigwinch(int signum)
-{
- if (write(signalpipe[1], "x", 1) <= 0)
- /* not much we can do about it */;
-}
-
-/*
- * Short description of parameters.
- */
-static void usage(void)
-{
- printf("Plink: command-line connection utility\n");
- printf("%s\n", ver);
- printf("Usage: plink [options] [user@]host [command]\n");
- printf(" (\"host\" can also be a PuTTY saved session name)\n");
- printf("Options:\n");
- printf(" -V print version information and exit\n");
- printf(" -pgpfp print PGP key fingerprints and exit\n");
- printf(" -v show verbose messages\n");
- printf(" -load sessname Load settings from saved session\n");
- printf(" -ssh -telnet -rlogin -raw -serial\n");
- printf(" force use of a particular protocol\n");
- printf(" -ssh-connection\n");
- printf(" force use of the bare ssh-connection protocol\n");
- printf(" -P port connect to specified port\n");
- printf(" -l user connect with specified username\n");
- printf(" -batch disable all interactive prompts\n");
- printf(" -proxycmd command\n");
- printf(" use 'command' as local proxy\n");
- printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n");
- printf(" Specify the serial configuration (serial only)\n");
- printf("The following options only apply to SSH connections:\n");
- printf(" -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");
- exit(1);
-}
-
-static void version(void)
-{
- char *buildinfo_text = buildinfo("\n");
- printf("plink: %s\n%s\n", ver, buildinfo_text);
- sfree(buildinfo_text);
- exit(0);
-}
-
-void frontend_net_error_pending(void) {}
-
-const bool share_can_be_downstream = true;
-const bool share_can_be_upstream = true;
-
-const bool buildinfo_gtk_relevant = false;
-
-const unsigned cmdline_tooltype =
- TOOLTYPE_HOST_ARG |
- TOOLTYPE_HOST_ARG_CAN_BE_SESSION |
- TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX |
- TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD;
-
-static bool seen_stdin_eof = false;
-
-static bool plink_pw_setup(void *vctx, pollwrapper *pw)
-{
- pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R);
-
- if (!seen_stdin_eof &&
- backend_connected(backend) &&
- backend_sendok(backend) &&
- backend_sendbuffer(backend) < MAX_STDIN_BACKLOG) {
- /* If we're OK to send, then try to read from stdin. */
- pollwrap_add_fd_rwx(pw, STDIN_FILENO, SELECT_R);
- }
-
- if (bufchain_size(&stdout_data) > 0) {
- /* If we have data for stdout, try to write to stdout. */
- pollwrap_add_fd_rwx(pw, STDOUT_FILENO, SELECT_W);
- }
-
- if (bufchain_size(&stderr_data) > 0) {
- /* If we have data for stderr, try to write to stderr. */
- pollwrap_add_fd_rwx(pw, STDERR_FILENO, SELECT_W);
- }
-
- return true;
-}
-
-static void plink_pw_check(void *vctx, pollwrapper *pw)
-{
- if (pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) {
- char c[1];
- struct winsize size;
- if (read(signalpipe[0], c, 1) <= 0)
- /* ignore error */;
- /* ignore its value; it'll be `x' */
- if (ioctl(STDIN_FILENO, TIOCGWINSZ, (void *)&size) >= 0)
- backend_size(backend, size.ws_col, size.ws_row);
- }
-
- if (pollwrap_check_fd_rwx(pw, STDIN_FILENO, SELECT_R)) {
- char buf[4096];
- int ret;
-
- if (backend_connected(backend)) {
- ret = read(STDIN_FILENO, buf, sizeof(buf));
- noise_ultralight(NOISE_SOURCE_IOLEN, ret);
- if (ret < 0) {
- perror("stdin: read");
- exit(1);
- } else if (ret == 0) {
- backend_special(backend, SS_EOF, 0);
- seen_stdin_eof = true;
- } else {
- if (local_tty)
- from_tty(buf, ret);
- else
- backend_send(backend, buf, ret);
- }
- }
- }
-
- if (pollwrap_check_fd_rwx(pw, STDOUT_FILENO, SELECT_W)) {
- backend_unthrottle(backend, try_output(false));
- }
-
- if (pollwrap_check_fd_rwx(pw, STDERR_FILENO, SELECT_W)) {
- backend_unthrottle(backend, try_output(true));
- }
-}
-
-static bool plink_continue(void *vctx, bool found_any_fd,
- bool ran_any_callback)
-{
- if (!backend_connected(backend) &&
- bufchain_size(&stdout_data) == 0 && bufchain_size(&stderr_data) == 0)
- return false; /* terminate main loop */
- return true;
-}
-
-int main(int argc, char **argv)
-{
- int exitcode;
- bool errors;
- enum TriState sanitise_stdout = AUTO, sanitise_stderr = AUTO;
- bool use_subsystem = false;
- bool just_test_share_exists = false;
- struct winsize size;
- const struct BackendVtable *backvt;
-
- /*
- * Initialise port and protocol to sensible defaults. (These
- * will be overridden by more or less anything.)
- */
- settings_set_default_protocol(PROT_SSH);
- settings_set_default_port(22);
-
- bufchain_init(&stdout_data);
- bufchain_init(&stderr_data);
- bufchain_sink_init(&stdout_bcs, &stdout_data);
- bufchain_sink_init(&stderr_bcs, &stderr_data);
- stdout_bs = BinarySink_UPCAST(&stdout_bcs);
- stderr_bs = BinarySink_UPCAST(&stderr_bcs);
- outgoingeof = EOF_NO;
-
- stderr_tty_init();
- /*
- * Process the command line.
- */
- conf = conf_new();
- do_defaults(NULL, conf);
- settings_set_default_protocol(conf_get_int(conf, CONF_protocol));
- settings_set_default_port(conf_get_int(conf, CONF_port));
- errors = false;
- {
- /*
- * Override the default protocol if PLINK_PROTOCOL is set.
- */
- char *p = getenv("PLINK_PROTOCOL");
- if (p) {
- const struct BackendVtable *vt = backend_vt_from_name(p);
- if (vt) {
- settings_set_default_protocol(vt->protocol);
- settings_set_default_port(vt->default_port);
- conf_set_int(conf, CONF_protocol, vt->protocol);
- conf_set_int(conf, CONF_port, vt->default_port);
- }
- }
- }
- while (--argc) {
- char *p = *++argv;
- int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
- 1, conf);
- if (ret == -2) {
- fprintf(stderr,
- "plink: option \"%s\" requires an argument\n", p);
- errors = true;
- } else if (ret == 2) {
- --argc, ++argv;
- } else if (ret == 1) {
- continue;
- } else if (!strcmp(p, "-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();
- exit(0);
- } else if (!strcmp(p, "-pgpfp")) {
- pgp_fingerprints();
- exit(1);
- } else if (!strcmp(p, "-o")) {
- if (argc <= 1) {
- fprintf(stderr,
- "plink: option \"-o\" requires an argument\n");
- errors = true;
- } else {
- --argc;
- /* Explicitly pass "plink" in place of appname for
- * error reporting purposes. appname will have been
- * set by be_foo.c to something more generic, probably
- * "PuTTY". */
- provide_xrm_string(*++argv, "plink");
- }
- } else if (!strcmp(p, "-shareexists")) {
- just_test_share_exists = true;
- } else if (!strcmp(p, "-fuzznet")) {
- conf_set_int(conf, CONF_proxy_type, PROXY_FUZZ);
- conf_set_str(conf, CONF_proxy_telnet_command, "%host");
- } else if (!strcmp(p, "-sanitise-stdout") ||
- !strcmp(p, "-sanitize-stdout")) {
- sanitise_stdout = FORCE_ON;
- } else if (!strcmp(p, "-no-sanitise-stdout") ||
- !strcmp(p, "-no-sanitize-stdout")) {
- sanitise_stdout = FORCE_OFF;
- } else if (!strcmp(p, "-sanitise-stderr") ||
- !strcmp(p, "-sanitize-stderr")) {
- sanitise_stderr = FORCE_ON;
- } else if (!strcmp(p, "-no-sanitise-stderr") ||
- !strcmp(p, "-no-sanitize-stderr")) {
- sanitise_stderr = FORCE_OFF;
- } else if (!strcmp(p, "-no-antispoof")) {
- console_antispoof_prompt = false;
- } else if (*p != '-') {
- strbuf *cmdbuf = strbuf_new();
-
- while (argc > 0) {
- if (cmdbuf->len > 0)
- put_byte(cmdbuf, ' '); /* add space separator */
- put_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);
-
- /*
- * If we have no better ideas for the remote username, use the local
- * one, as 'ssh' does.
- */
- if (conf_get_str(conf, CONF_username)[0] == '\0') {
- char *user = get_username();
- if (user) {
- conf_set_str(conf, CONF_username, user);
- sfree(user);
- }
- }
-
- /*
- * Apply subsystem status.
- */
- if (use_subsystem)
- conf_set_bool(conf, CONF_ssh_subsys, true);
-
- /*
- * Select protocol. This is farmed out into a table in a
- * separate file to enable an ssh-free variant.
- */
- backvt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol));
- if (!backvt) {
- fprintf(stderr,
- "Internal fault: Unsupported protocol found\n");
- return 1;
- }
-
- if (backvt->flags & BACKEND_NEEDS_TERMINAL) {
- fprintf(stderr,
- "Plink doesn't support %s, which needs terminal emulation\n",
- backvt->displayname);
- return 1;
- }
-
- /*
- * Block SIGPIPE, so that we'll get EPIPE individually on
- * particular network connections that go wrong.
- */
- putty_signal(SIGPIPE, SIG_IGN);
-
- /*
- * Set up the pipe we'll use to tell us about SIGWINCH.
- */
- if (pipe(signalpipe) < 0) {
- perror("pipe");
- exit(1);
- }
- /* We don't want the signal handler to block if the pipe's full. */
- nonblock(signalpipe[0]);
- nonblock(signalpipe[1]);
- cloexec(signalpipe[0]);
- cloexec(signalpipe[1]);
- putty_signal(SIGWINCH, sigwinch);
-
- /*
- * Now that we've got the SIGWINCH handler installed, try to find
- * out the initial terminal size.
- */
- if (ioctl(STDIN_FILENO, TIOCGWINSZ, &size) >= 0) {
- conf_set_int(conf, CONF_width, size.ws_col);
- conf_set_int(conf, CONF_height, size.ws_row);
- }
-
- /*
- * Decide whether to sanitise control sequences out of standard
- * output and standard error.
- *
- * If we weren't given a command-line override, we do this if (a)
- * the fd in question is pointing at a terminal, and (b) we aren't
- * trying to allocate a terminal as part of the session.
- *
- * (Rationale: the risk of control sequences is that they cause
- * confusion when sent to a local terminal, so if there isn't one,
- * no problem. Also, if we allocate a remote terminal, then we
- * sent a terminal type, i.e. we told it what kind of escape
- * sequences we _like_, i.e. we were expecting to receive some.)
- */
- if (sanitise_stdout == FORCE_ON ||
- (sanitise_stdout == AUTO && isatty(STDOUT_FILENO) &&
- conf_get_bool(conf, CONF_nopty))) {
- stdout_scc = stripctrl_new(stdout_bs, true, L'\0');
- stdout_bs = BinarySink_UPCAST(stdout_scc);
- }
- if (sanitise_stderr == FORCE_ON ||
- (sanitise_stderr == AUTO && isatty(STDERR_FILENO) &&
- conf_get_bool(conf, CONF_nopty))) {
- stderr_scc = stripctrl_new(stderr_bs, true, L'\0');
- stderr_bs = BinarySink_UPCAST(stderr_scc);
- }
-
- sk_init();
- uxsel_init();
-
- /*
- * Plink doesn't provide any way to add forwardings after the
- * connection is set up, so if there are none now, we can safely set
- * the "simple" flag.
- */
- if (conf_get_int(conf, CONF_protocol) == PROT_SSH &&
- !conf_get_bool(conf, CONF_x11_forward) &&
- !conf_get_bool(conf, CONF_agentfwd) &&
- !conf_get_str_nthstrkey(conf, CONF_portfwd, 0))
- conf_set_bool(conf, CONF_ssh_simple, true);
-
- if (just_test_share_exists) {
- if (!backvt->test_for_upstream) {
- fprintf(stderr, "Connection sharing not supported for this "
- "connection type (%s)'\n", backvt->displayname);
- return 1;
- }
- if (backvt->test_for_upstream(conf_get_str(conf, CONF_host),
- conf_get_int(conf, CONF_port), conf))
- return 0;
- else
- return 1;
- }
-
- /*
- * Start up the connection.
- */
- logctx = log_init(console_cli_logpolicy, conf);
- {
- char *error, *realhost;
- /* nodelay is only useful if stdin is a terminal device */
- bool nodelay = conf_get_bool(conf, CONF_tcp_nodelay) && isatty(0);
-
- /* This is a good place for a fuzzer to fork us. */
-#ifdef __AFL_HAVE_MANUAL_CONTROL
- __AFL_INIT();
-#endif
-
- error = backend_init(backvt, plink_seat, &backend, logctx, conf,
- conf_get_str(conf, CONF_host),
- conf_get_int(conf, CONF_port),
- &realhost, nodelay,
- conf_get_bool(conf, CONF_tcp_keepalives));
- if (error) {
- fprintf(stderr, "Unable to open connection:\n%s\n", error);
- sfree(error);
- return 1;
- }
- ldisc_create(conf, NULL, backend, plink_seat);
- sfree(realhost);
- }
-
- /*
- * Set up the initial console mode. We don't care if this call
- * fails, because we know we aren't necessarily running in a
- * console.
- */
- local_tty = (tcgetattr(STDIN_FILENO, &orig_termios) == 0);
- atexit(cleanup_termios);
- seat_echoedit_update(plink_seat, 1, 1);
-
- cli_main_loop(plink_pw_setup, plink_pw_check, plink_continue, NULL);
-
- exitcode = backend_exitcode(backend);
- if (exitcode < 0) {
- fprintf(stderr, "Remote process exit code unavailable\n");
- exitcode = 1; /* this is an error condition */
- }
- cleanup_exit(exitcode);
- return exitcode; /* shouldn't happen, but placates gcc */
-}
diff --git a/UNIX/UXPRINT.C b/UNIX/UXPRINT.C
deleted file mode 100644
index 3de6d21b..00000000
--- a/UNIX/UXPRINT.C
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Printing interface for PuTTY.
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include "putty.h"
-
-struct printer_job_tag {
- FILE *fp;
-};
-
-printer_job *printer_start_job(char *printer)
-{
- printer_job *ret = snew(printer_job);
- /*
- * On Unix, we treat the printer string as the name of a
- * command to pipe to - typically lpr, of course.
- */
- ret->fp = popen(printer, "w");
- if (!ret->fp) {
- sfree(ret);
- ret = NULL;
- }
- return ret;
-}
-
-void printer_job_data(printer_job *pj, const void *data, size_t len)
-{
- if (!pj)
- return;
-
- if (fwrite(data, 1, len, pj->fp) < len)
- /* ignore */;
-}
-
-void printer_finish_job(printer_job *pj)
-{
- if (!pj)
- return;
-
- pclose(pj->fp);
- sfree(pj);
-}
-
-/*
- * There's no sensible way to enumerate printers under Unix, since
- * practically any valid Unix command is a valid printer :-) So
- * these are useless stub functions, and uxcfg.c will disable the
- * drop-down list in the printer configurer.
- */
-printer_enum *printer_start_enum(int *nprinters_ptr) {
- *nprinters_ptr = 0;
- return NULL;
-}
-char *printer_get_name(printer_enum *pe, int i) { return NULL;
-}
-void printer_finish_enum(printer_enum *pe) { }
diff --git a/UNIX/UXPROXY.C b/UNIX/UXPROXY.C
deleted file mode 100644
index 0a637bd9..00000000
--- a/UNIX/UXPROXY.C
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * uxproxy.c: Unix implementation of platform_new_connection(),
- * supporting an OpenSSH-like proxy command.
- */
-
-#include <stdio.h>
-#include <assert.h>
-#include <errno.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-#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;
-
- int to_cmd_pipe[2], from_cmd_pipe[2], cmd_err_pipe[2], pid, proxytype;
- int infd, outfd, inerrfd;
-
- proxytype = conf_get_int(conf, CONF_proxy_type);
- if (proxytype != PROXY_CMD && proxytype != PROXY_FUZZ)
- return NULL;
-
- if (proxytype == PROXY_CMD) {
- cmd = format_telnet_command(addr, port, conf);
-
- {
- char *logmsg = dupprintf("Starting local proxy command: %s", cmd);
- plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
- sfree(logmsg);
- }
-
- /*
- * Create the pipes to the proxy command, and spawn the proxy
- * command process.
- */
- if (pipe(to_cmd_pipe) < 0 ||
- pipe(from_cmd_pipe) < 0 ||
- pipe(cmd_err_pipe) < 0) {
- sfree(cmd);
- return new_error_socket_fmt(plug, "pipe: %s", strerror(errno));
- }
- cloexec(to_cmd_pipe[1]);
- cloexec(from_cmd_pipe[0]);
- cloexec(cmd_err_pipe[0]);
-
- pid = fork();
- if (pid == 0) {
- close(0);
- close(1);
- dup2(to_cmd_pipe[0], 0);
- dup2(from_cmd_pipe[1], 1);
- close(to_cmd_pipe[0]);
- close(from_cmd_pipe[1]);
- dup2(cmd_err_pipe[1], 2);
- noncloexec(0);
- noncloexec(1);
- execl("/bin/sh", "sh", "-c", cmd, (void *)NULL);
- _exit(255);
- }
-
- sfree(cmd);
-
- if (pid < 0)
- return new_error_socket_fmt(plug, "fork: %s", strerror(errno));
-
- close(to_cmd_pipe[0]);
- close(from_cmd_pipe[1]);
- close(cmd_err_pipe[1]);
-
- outfd = to_cmd_pipe[1];
- infd = from_cmd_pipe[0];
- inerrfd = cmd_err_pipe[0];
- } else {
- cmd = format_telnet_command(addr, port, conf);
- outfd = open("/dev/null", O_WRONLY);
- if (outfd == -1) {
- sfree(cmd);
- return new_error_socket_fmt(
- plug, "/dev/null: %s", strerror(errno));
- }
- infd = open(cmd, O_RDONLY);
- if (infd == -1) {
- Socket *toret = new_error_socket_fmt(
- plug, "%s: %s", cmd, strerror(errno));
- sfree(cmd);
- close(outfd);
- return toret;
- }
- sfree(cmd);
- inerrfd = -1;
- }
-
- /* We are responsible for this and don't need it any more */
- sk_addr_free(addr);
-
- return make_fd_socket(infd, outfd, inerrfd, plug);
-}
diff --git a/UNIX/UXPTERM.C b/UNIX/UXPTERM.C
deleted file mode 100644
index b18e3442..00000000
--- a/UNIX/UXPTERM.C
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * pterm main program.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "putty.h"
-
-const char *const appname = "pterm";
-const bool use_event_log = false; /* pterm doesn't need it */
-const bool new_session = false, saved_sessions = false; /* or these */
-const bool dup_check_launchable = false; /* no need to check host name
- * in conf */
-const bool use_pty_argv = true;
-
-const unsigned cmdline_tooltype = TOOLTYPE_NONNETWORK;
-
-/* gtkwin.c will call this, and in pterm it's not needed */
-void noise_ultralight(NoiseSourceId id, unsigned long data) { }
-
-const struct BackendVtable *select_backend(Conf *conf)
-{
- return &pty_backend;
-}
-
-void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx)
-{
- /*
- * This is a no-op in pterm, except that we'll ensure the protocol
- * is set to -1 to inhibit the useless Connection panel in the
- * config box. So we do that and then just immediately call the
- * post-dialog function with a positive result.
- */
- conf_set_int(conf, CONF_protocol, -1);
- after(afterctx, 1);
-}
-
-void cleanup_exit(int code)
-{
- exit(code);
-}
-
-void setup(bool single)
-{
- settings_set_default_protocol(-1);
-
- if (single)
- pty_pre_init();
-}
diff --git a/UNIX/UXPTY.C b/UNIX/UXPTY.C
deleted file mode 100644
index bfd0de57..00000000
--- a/UNIX/UXPTY.C
+++ /dev/null
@@ -1,1612 +0,0 @@
-/*
- * Pseudo-tty backend for pterm.
- */
-
-#define _GNU_SOURCE
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <signal.h>
-#include <assert.h>
-#include <fcntl.h>
-#include <termios.h>
-#include <grp.h>
-#include <utmp.h>
-#include <pwd.h>
-#include <time.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <sys/ioctl.h>
-#include <errno.h>
-#include <termios.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "sshserver.h" /* to check the prototypes of server-needed things */
-#include "tree234.h"
-
-#ifndef OMIT_UTMP
-#include <utmpx.h>
-#endif
-
-/* updwtmpx() needs the name of the wtmp file. Try to find it. */
-#ifndef WTMPX_FILE
-#ifdef _PATH_WTMPX
-#define WTMPX_FILE _PATH_WTMPX
-#else
-#define WTMPX_FILE "/var/log/wtmpx"
-#endif
-#endif
-
-#ifndef LASTLOG_FILE
-#ifdef _PATH_LASTLOG
-#define LASTLOG_FILE _PATH_LASTLOG
-#else
-#define LASTLOG_FILE "/var/log/lastlog"
-#endif
-#endif
-
-/*
- * Set up a default for vaguely sane systems. The idea is that if
- * OMIT_UTMP is not defined, then at least one of the symbols which
- * enable particular forms of utmp processing should be, if only so
- * that a link error can warn you that you should have defined
- * OMIT_UTMP if you didn't want any. Currently HAVE_PUTUTLINE is
- * the only such symbol.
- */
-#ifndef OMIT_UTMP
-#if !defined HAVE_PUTUTLINE
-#define HAVE_PUTUTLINE
-#endif
-#endif
-
-typedef struct Pty Pty;
-
-/*
- * The pty_signal_pipe, along with the SIGCHLD handler, must be
- * process-global rather than session-specific.
- */
-static int pty_signal_pipe[2] = { -1, -1 }; /* obviously bogus initial val */
-
-typedef struct PtyFd {
- int fd;
- Pty *pty;
-} PtyFd;
-
-struct Pty {
- Conf *conf;
-
- int master_fd, slave_fd;
- int pipefds[6];
- PtyFd fds[3];
- int master_i, master_o, master_e;
-
- Seat *seat;
- char name[FILENAME_MAX];
- pid_t child_pid;
- int term_width, term_height;
- bool child_dead, finished;
- int exit_code;
- bufchain output_data;
- bool pending_eof;
- Backend backend;
-};
-
-/*
- * We store all the (active) PtyFd structures in a tree sorted by fd,
- * so that when we get an uxsel notification we know which backend
- * instance is the owner of the pty that caused it, and then we can
- * find out which fd is the relevant one too.
- */
-static int ptyfd_compare(void *av, void *bv)
-{
- PtyFd *a = (PtyFd *)av;
- PtyFd *b = (PtyFd *)bv;
-
- if (a->fd < b->fd)
- return -1;
- else if (a->fd > b->fd)
- return +1;
- return 0;
-}
-
-static int ptyfd_find(void *av, void *bv)
-{
- int a = *(int *)av;
- PtyFd *b = (PtyFd *)bv;
-
- if (a < b->fd)
- return -1;
- else if (a > b->fd)
- return +1;
- return 0;
-}
-
-static tree234 *ptyfds = NULL;
-
-/*
- * We also have a tree of Pty structures themselves, sorted by child
- * pid, so that when we wait() in response to the signal we know which
- * backend instance is the owner of the process that caused the
- * signal.
- */
-static int pty_compare_by_pid(void *av, void *bv)
-{
- Pty *a = (Pty *)av;
- Pty *b = (Pty *)bv;
-
- if (a->child_pid < b->child_pid)
- return -1;
- else if (a->child_pid > b->child_pid)
- return +1;
- return 0;
-}
-
-static int pty_find_by_pid(void *av, void *bv)
-{
- pid_t a = *(pid_t *)av;
- Pty *b = (Pty *)bv;
-
- if (a < b->child_pid)
- return -1;
- else if (a > b->child_pid)
- return +1;
- return 0;
-}
-
-static tree234 *ptys_by_pid = NULL;
-
-/*
- * If we are using pty_pre_init(), it will need to have already
- * allocated a pty structure, which we must then return from
- * pty_init() rather than allocating a new one. Here we store that
- * structure between allocation and use.
- *
- * Note that although most of this module is entirely capable of
- * handling multiple ptys in a single process, pty_pre_init() is
- * fundamentally _dependent_ on there being at most one pty per
- * process, so the normal static-data constraints don't apply.
- *
- * Likewise, since utmp is only used via pty_pre_init, it too must
- * be single-instance, so we can declare utmp-related variables
- * here.
- */
-static Pty *single_pty = NULL;
-
-#ifndef OMIT_UTMP
-static pid_t pty_utmp_helper_pid = -1;
-static int pty_utmp_helper_pipe = -1;
-static bool pty_stamped_utmp;
-static struct utmpx utmp_entry;
-#endif
-
-/*
- * pty_argv is a grievous hack to allow a proper argv to be passed
- * through from the Unix command line. Again, it doesn't really
- * make sense outside a one-pty-per-process setup.
- */
-char **pty_argv;
-
-char *pty_osx_envrestore_prefix;
-
-static void pty_close(Pty *pty);
-static void pty_try_write(Pty *pty);
-
-#ifndef OMIT_UTMP
-static void setup_utmp(char *ttyname, char *location)
-{
-#ifdef HAVE_LASTLOG
- struct lastlog lastlog_entry;
- FILE *lastlog;
-#endif
- struct passwd *pw;
- struct timeval tv;
-
- pw = getpwuid(getuid());
- if (!pw)
- return; /* can't stamp utmp if we don't have a username */
- memset(&utmp_entry, 0, sizeof(utmp_entry));
- utmp_entry.ut_type = USER_PROCESS;
- utmp_entry.ut_pid = getpid();
-#if __GNUC__ >= 8
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wstringop-truncation"
-#endif // __GNUC__ >= 8
- strncpy(utmp_entry.ut_line, ttyname+5, lenof(utmp_entry.ut_line));
- strncpy(utmp_entry.ut_id, ttyname+8, lenof(utmp_entry.ut_id));
- strncpy(utmp_entry.ut_user, pw->pw_name, lenof(utmp_entry.ut_user));
- strncpy(utmp_entry.ut_host, location, lenof(utmp_entry.ut_host));
-#if __GNUC__ >= 8
-# pragma GCC diagnostic pop
-#endif // __GNUC__ >= 8
- /*
- * Apparently there are some architectures where (struct
- * utmpx).ut_tv is not essentially struct timeval (e.g. Linux
- * amd64). Hence the temporary.
- */
- gettimeofday(&tv, NULL);
- utmp_entry.ut_tv.tv_sec = tv.tv_sec;
- utmp_entry.ut_tv.tv_usec = tv.tv_usec;
-
- setutxent();
- pututxline(&utmp_entry);
- endutxent();
-
- updwtmpx(WTMPX_FILE, &utmp_entry);
-
-#ifdef HAVE_LASTLOG
- memset(&lastlog_entry, 0, sizeof(lastlog_entry));
- strncpy(lastlog_entry.ll_line, ttyname+5, lenof(lastlog_entry.ll_line));
- strncpy(lastlog_entry.ll_host, location, lenof(lastlog_entry.ll_host));
- time(&lastlog_entry.ll_time);
- if ((lastlog = fopen(LASTLOG_FILE, "r+")) != NULL) {
- fseek(lastlog, sizeof(lastlog_entry) * getuid(), SEEK_SET);
- fwrite(&lastlog_entry, 1, sizeof(lastlog_entry), lastlog);
- fclose(lastlog);
- }
-#endif
-
- pty_stamped_utmp = true;
-
-}
-
-static void cleanup_utmp(void)
-{
- struct timeval tv;
-
- if (!pty_stamped_utmp)
- return;
-
- utmp_entry.ut_type = DEAD_PROCESS;
- memset(utmp_entry.ut_user, 0, lenof(utmp_entry.ut_user));
- gettimeofday(&tv, NULL);
- utmp_entry.ut_tv.tv_sec = tv.tv_sec;
- utmp_entry.ut_tv.tv_usec = tv.tv_usec;
-
- updwtmpx(WTMPX_FILE, &utmp_entry);
-
- memset(utmp_entry.ut_line, 0, lenof(utmp_entry.ut_line));
- utmp_entry.ut_tv.tv_sec = 0;
- utmp_entry.ut_tv.tv_usec = 0;
-
- setutxent();
- pututxline(&utmp_entry);
- endutxent();
-
- pty_stamped_utmp = false; /* ensure we never double-cleanup */
-}
-#endif
-
-static void sigchld_handler(int signum)
-{
- if (write(pty_signal_pipe[1], "x", 1) <= 0)
- /* not much we can do about it */;
-}
-
-static void pty_setup_sigchld_handler(void)
-{
- static bool setup = false;
- if (!setup) {
- putty_signal(SIGCHLD, sigchld_handler);
- setup = true;
- }
-}
-
-#ifndef OMIT_UTMP
-static void fatal_sig_handler(int signum)
-{
- putty_signal(signum, SIG_DFL);
- cleanup_utmp();
- raise(signum);
-}
-#endif
-
-static int pty_open_slave(Pty *pty)
-{
- if (pty->slave_fd < 0) {
- pty->slave_fd = open(pty->name, O_RDWR);
- cloexec(pty->slave_fd);
- }
-
- return pty->slave_fd;
-}
-
-static void pty_open_master(Pty *pty)
-{
-#ifdef BSD_PTYS
- const char chars1[] = "pqrstuvwxyz";
- const char chars2[] = "0123456789abcdef";
- const char *p1, *p2;
- char master_name[20];
- struct group *gp;
-
- for (p1 = chars1; *p1; p1++)
- for (p2 = chars2; *p2; p2++) {
- sprintf(master_name, "/dev/pty%c%c", *p1, *p2);
- pty->master_fd = open(master_name, O_RDWR);
- if (pty->master_fd >= 0) {
- if (geteuid() == 0 ||
- access(master_name, R_OK | W_OK) == 0) {
- /*
- * We must also check at this point that we are
- * able to open the slave side of the pty. We
- * wouldn't want to allocate the wrong master,
- * get all the way down to forking, and _then_
- * find we're unable to open the slave.
- */
- strcpy(pty->name, master_name);
- pty->name[5] = 't'; /* /dev/ptyXX -> /dev/ttyXX */
-
- cloexec(pty->master_fd);
-
- if (pty_open_slave(pty) >= 0 &&
- access(pty->name, R_OK | W_OK) == 0)
- goto got_one;
- if (pty->slave_fd > 0)
- close(pty->slave_fd);
- pty->slave_fd = -1;
- }
- close(pty->master_fd);
- }
- }
-
- /* If we get here, we couldn't get a tty at all. */
- fprintf(stderr, "pterm: unable to open a pseudo-terminal device\n");
- exit(1);
-
- got_one:
-
- /* We need to chown/chmod the /dev/ttyXX device. */
- gp = getgrnam("tty");
- chown(pty->name, getuid(), gp ? gp->gr_gid : -1);
- chmod(pty->name, 0600);
-#else
-
- const int flags = O_RDWR
-#ifdef O_NOCTTY
- | O_NOCTTY
-#endif
- ;
-
-#ifdef HAVE_POSIX_OPENPT
-#ifdef SET_NONBLOCK_VIA_OPENPT
- /*
- * OS X, as of 10.10 at least, doesn't permit me to set O_NONBLOCK
- * on pty master fds via the usual fcntl mechanism. Fortunately,
- * it does let me work around this by adding O_NONBLOCK to the
- * posix_openpt flags parameter, which isn't a documented use of
- * the API but seems to work. So we'll do that for now.
- */
- pty->master_fd = posix_openpt(flags | O_NONBLOCK);
-#else
- pty->master_fd = posix_openpt(flags);
-#endif
-
- if (pty->master_fd < 0) {
- perror("posix_openpt");
- exit(1);
- }
-#else
- pty->master_fd = open("/dev/ptmx", flags);
-
- if (pty->master_fd < 0) {
- perror("/dev/ptmx: open");
- exit(1);
- }
-#endif
-
- if (grantpt(pty->master_fd) < 0) {
- perror("grantpt");
- exit(1);
- }
-
- if (unlockpt(pty->master_fd) < 0) {
- perror("unlockpt");
- exit(1);
- }
-
- cloexec(pty->master_fd);
-
- pty->name[FILENAME_MAX-1] = '\0';
- strncpy(pty->name, ptsname(pty->master_fd), FILENAME_MAX-1);
-#endif
-
-#ifndef SET_NONBLOCK_VIA_OPENPT
- nonblock(pty->master_fd);
-#endif
-}
-
-static Pty *new_pty_struct(void)
-{
- Pty *pty = snew(Pty);
- pty->conf = NULL;
- pty->pending_eof = false;
- bufchain_init(&pty->output_data);
- return pty;
-}
-
-/*
- * Pre-initialisation. This is here to get around the fact that GTK
- * doesn't like being run in setuid/setgid programs (probably
- * sensibly). So before we initialise GTK - and therefore before we
- * even process the command line - we check to see if we're running
- * set[ug]id. If so, we open our pty master _now_, chown it as
- * necessary, and drop privileges. We can always close it again
- * later. If we're potentially going to be doing utmp as well, we
- * also fork off a utmp helper process and communicate with it by
- * means of a pipe; the utmp helper will keep privileges in order
- * to clean up utmp when we exit (i.e. when its end of our pipe
- * closes).
- */
-void pty_pre_init(void)
-{
-#ifndef NO_PTY_PRE_INIT
-
- Pty *pty;
-
-#ifndef OMIT_UTMP
- pid_t pid;
- int pipefd[2];
-#endif
-
- pty = single_pty = new_pty_struct();
-
- /* set the child signal handler straight away; it needs to be set
- * before we ever fork. */
- pty_setup_sigchld_handler();
- pty->master_fd = pty->slave_fd = -1;
-#ifndef OMIT_UTMP
- pty_stamped_utmp = false;
-#endif
-
- if (geteuid() != getuid() || getegid() != getgid()) {
- pty_open_master(pty);
-
-#ifndef OMIT_UTMP
- /*
- * Fork off the utmp helper.
- */
- if (pipe(pipefd) < 0) {
- perror("pterm: pipe");
- exit(1);
- }
- cloexec(pipefd[0]);
- cloexec(pipefd[1]);
- pid = fork();
- if (pid < 0) {
- perror("pterm: fork");
- exit(1);
- } else if (pid == 0) {
- char display[128], buffer[128];
- int dlen, ret;
-
- close(pipefd[1]);
- /*
- * Now sit here until we receive a display name from the
- * other end of the pipe, and then stamp utmp. Unstamp utmp
- * again, and exit, when the pipe closes.
- */
-
- dlen = 0;
- while (1) {
-
- ret = read(pipefd[0], buffer, lenof(buffer));
- if (ret <= 0) {
- cleanup_utmp();
- _exit(0);
- } else if (!pty_stamped_utmp) {
- if (dlen < lenof(display))
- memcpy(display+dlen, buffer,
- min(ret, lenof(display)-dlen));
- if (buffer[ret-1] == '\0') {
- /*
- * Now we have a display name. NUL-terminate
- * it, and stamp utmp.
- */
- display[lenof(display)-1] = '\0';
- /*
- * Trap as many fatal signals as we can in the
- * hope of having the best possible chance to
- * clean up utmp before termination. We are
- * unfortunately unprotected against SIGKILL,
- * but that's life.
- */
- putty_signal(SIGHUP, fatal_sig_handler);
- putty_signal(SIGINT, fatal_sig_handler);
- putty_signal(SIGQUIT, fatal_sig_handler);
- putty_signal(SIGILL, fatal_sig_handler);
- putty_signal(SIGABRT, fatal_sig_handler);
- putty_signal(SIGFPE, fatal_sig_handler);
- putty_signal(SIGPIPE, fatal_sig_handler);
- putty_signal(SIGALRM, fatal_sig_handler);
- putty_signal(SIGTERM, fatal_sig_handler);
- putty_signal(SIGSEGV, fatal_sig_handler);
- putty_signal(SIGUSR1, fatal_sig_handler);
- putty_signal(SIGUSR2, fatal_sig_handler);
-#ifdef SIGBUS
- putty_signal(SIGBUS, fatal_sig_handler);
-#endif
-#ifdef SIGPOLL
- putty_signal(SIGPOLL, fatal_sig_handler);
-#endif
-#ifdef SIGPROF
- putty_signal(SIGPROF, fatal_sig_handler);
-#endif
-#ifdef SIGSYS
- putty_signal(SIGSYS, fatal_sig_handler);
-#endif
-#ifdef SIGTRAP
- putty_signal(SIGTRAP, fatal_sig_handler);
-#endif
-#ifdef SIGVTALRM
- putty_signal(SIGVTALRM, fatal_sig_handler);
-#endif
-#ifdef SIGXCPU
- putty_signal(SIGXCPU, fatal_sig_handler);
-#endif
-#ifdef SIGXFSZ
- putty_signal(SIGXFSZ, fatal_sig_handler);
-#endif
-#ifdef SIGIO
- putty_signal(SIGIO, fatal_sig_handler);
-#endif
- setup_utmp(pty->name, display);
- }
- }
- }
- } else {
- close(pipefd[0]);
- pty_utmp_helper_pid = pid;
- pty_utmp_helper_pipe = pipefd[1];
- }
-#endif
- }
-
- /* Drop privs. */
- {
-#ifndef HAVE_NO_SETRESUID
- int gid = getgid(), uid = getuid();
- int setresgid(gid_t, gid_t, gid_t);
- int setresuid(uid_t, uid_t, uid_t);
- if (setresgid(gid, gid, gid) < 0) {
- perror("setresgid");
- exit(1);
- }
- if (setresuid(uid, uid, uid) < 0) {
- perror("setresuid");
- exit(1);
- }
-#else
- if (setgid(getgid()) < 0) {
- perror("setgid");
- exit(1);
- }
- if (setuid(getuid()) < 0) {
- perror("setuid");
- exit(1);
- }
-#endif
- }
-
-#endif /* NO_PTY_PRE_INIT */
-
-}
-
-static void pty_try_wait(void);
-
-static void pty_real_select_result(Pty *pty, int fd, int event, int status)
-{
- char buf[4096];
- int ret;
- bool finished = false;
-
- if (event < 0) {
- /*
- * We've been called because our child process did
- * something. `status' tells us what.
- */
- if ((WIFEXITED(status) || WIFSIGNALED(status))) {
- /*
- * The primary child process died.
- */
- pty->child_dead = true;
- del234(ptys_by_pid, pty);
- pty->exit_code = status;
-
- /*
- * If this is an ordinary pty session, this is also the
- * moment to terminate the whole backend.
- *
- * We _could_ instead keep the terminal open for remaining
- * subprocesses to output to, but conventional wisdom
- * seems to feel that that's the Wrong Thing for an
- * xterm-alike, so we bail out now (though we don't
- * necessarily _close_ the window, depending on the state
- * of Close On Exit). This would be easy enough to change
- * or make configurable if necessary.
- */
- if (pty->master_fd >= 0)
- finished = true;
- }
- } else {
- if (event == SELECT_R) {
- bool is_stdout = (fd == pty->master_o);
-
- ret = read(fd, buf, sizeof(buf));
-
- /*
- * Treat EIO on a pty master as equivalent to EOF (because
- * that's how the kernel seems to report the event where
- * the last process connected to the other end of the pty
- * went away).
- */
- if (fd == pty->master_fd && ret < 0 && errno == EIO)
- ret = 0;
-
- if (ret == 0) {
- /*
- * EOF on this input fd, so to begin with, we may as
- * well close it, and remove all references to it in
- * the pty's fd fields.
- */
- uxsel_del(fd);
- close(fd);
- if (pty->master_fd == fd)
- pty->master_fd = -1;
- if (pty->master_o == fd)
- pty->master_o = -1;
- if (pty->master_e == fd)
- pty->master_e = -1;
-
- if (is_stdout) {
- /*
- * We assume a clean exit if the pty (or stdout
- * pipe) has closed, but the actual child process
- * hasn't. The only way I can imagine this
- * happening is if it detaches itself from the pty
- * and goes daemonic - in which case the expected
- * usage model would precisely _not_ be for the
- * pterm window to hang around!
- */
- finished = true;
- pty_try_wait(); /* one last effort to collect exit code */
- if (!pty->child_dead)
- pty->exit_code = 0;
- }
- } else if (ret < 0) {
- perror("read pty master");
- exit(1);
- } else if (ret > 0) {
- seat_output(pty->seat, !is_stdout, buf, ret);
- }
- } else if (event == SELECT_W) {
- /*
- * Attempt to send data down the pty.
- */
- pty_try_write(pty);
- }
- }
-
- if (finished && !pty->finished) {
- int close_on_exit;
- int i;
-
- for (i = 0; i < 3; i++)
- if (pty->fds[i].fd >= 0)
- uxsel_del(pty->fds[i].fd);
-
- pty_close(pty);
-
- pty->finished = true;
-
- /*
- * This is a slight layering-violation sort of hack: only
- * if we're not closing on exit (COE is set to Never, or to
- * Only On Clean and it wasn't a clean exit) do we output a
- * `terminated' message.
- */
- close_on_exit = conf_get_int(pty->conf, CONF_close_on_exit);
- if (close_on_exit == FORCE_OFF ||
- (close_on_exit == AUTO && pty->exit_code != 0)) {
- char *message;
- if (WIFEXITED(pty->exit_code)) {
- message = dupprintf(
- "\r\n[pterm: process terminated with exit code %d]\r\n",
- WEXITSTATUS(pty->exit_code));
- } else if (WIFSIGNALED(pty->exit_code)) {
-#ifdef HAVE_NO_STRSIGNAL
- message = dupprintf(
- "\r\n[pterm: process terminated on signal %d]\r\n",
- WTERMSIG(pty->exit_code));
-#else
- message = dupprintf(
- "\r\n[pterm: process terminated on signal %d (%s)]\r\n",
- WTERMSIG(pty->exit_code),
- strsignal(WTERMSIG(pty->exit_code)));
-#endif
- } else {
- /* _Shouldn't_ happen, but if it does, a vague message
- * is better than no message at all */
- message = dupprintf("\r\n[pterm: process terminated]\r\n");
- }
- seat_stdout_pl(pty->seat, ptrlen_from_asciz(message));
- sfree(message);
- }
-
- seat_eof(pty->seat);
- seat_notify_remote_exit(pty->seat);
- }
-}
-
-static void pty_try_wait(void)
-{
- Pty *pty;
- pid_t pid;
- int status;
-
- do {
- pid = waitpid(-1, &status, WNOHANG);
-
- pty = find234(ptys_by_pid, &pid, pty_find_by_pid);
-
- if (pty)
- pty_real_select_result(pty, -1, -1, status);
- } while (pid > 0);
-}
-
-void pty_select_result(int fd, int event)
-{
- if (fd == pty_signal_pipe[0]) {
- char c[1];
-
- if (read(pty_signal_pipe[0], c, 1) <= 0)
- /* ignore error */;
- /* ignore its value; it'll be `x' */
-
- pty_try_wait();
- } else {
- PtyFd *ptyfd = find234(ptyfds, &fd, ptyfd_find);
-
- if (ptyfd)
- pty_real_select_result(ptyfd->pty, fd, event, 0);
- }
-}
-
-static void pty_uxsel_setup_fd(Pty *pty, int fd)
-{
- int rwx = 0;
-
- if (fd < 0)
- return;
-
- /* read from standard output and standard error pipes */
- if (pty->master_o == fd || pty->master_e == fd)
- rwx |= SELECT_R;
- /* write to standard input pipe if we have any data */
- if (pty->master_i == fd && bufchain_size(&pty->output_data))
- rwx |= SELECT_W;
-
- uxsel_set(fd, rwx, pty_select_result);
-}
-
-static void pty_uxsel_setup(Pty *pty)
-{
- /*
- * We potentially have three separate fds here, but on the other
- * hand, some of them might be the same (if they're a pty master).
- * So we can't just call uxsel_set(master_o, SELECT_R) and then
- * uxsel_set(master_i, SELECT_W), without the latter potentially
- * undoing the work of the former if master_o == master_i.
- *
- * Instead, here we call a single uxsel on each one of these fds
- * (if it exists at all), and for each one, check it against all
- * three to see which bits to set.
- */
- pty_uxsel_setup_fd(pty, pty->master_o);
- pty_uxsel_setup_fd(pty, pty->master_e);
- pty_uxsel_setup_fd(pty, pty->master_i);
-
- /*
- * In principle this only needs calling once for all pty
- * backend instances, but it's simplest just to call it every
- * time; uxsel won't mind.
- */
- uxsel_set(pty_signal_pipe[0], SELECT_R, pty_select_result);
-}
-
-static void copy_ttymodes_into_termios(
- struct termios *attrs, struct ssh_ttymodes modes)
-{
-#define TTYMODE_CHAR(name, ssh_opcode, cc_index) { \
- if (modes.have_mode[ssh_opcode]) { \
- unsigned value = modes.mode_val[ssh_opcode]; \
- /* normalise wire value of 255 to local _POSIX_VDISABLE */ \
- attrs->c_cc[cc_index] = (value == 255 ? \
- _POSIX_VDISABLE : value); \
- } \
- }
-
-#define TTYMODE_FLAG(flagval, ssh_opcode, field, flagmask) { \
- if (modes.have_mode[ssh_opcode]) { \
- attrs->c_##field##flag &= ~flagmask; \
- if (modes.mode_val[ssh_opcode]) \
- attrs->c_##field##flag |= flagval; \
- } \
- }
-
-#define TTYMODES_LOCAL_ONLY /* omit any that this platform doesn't know */
-#include "sshttymodes.h"
-
-#undef TTYMODES_LOCAL_ONLY
-#undef TTYMODE_CHAR
-#undef TTYMODE_FLAG
-
- if (modes.have_mode[TTYMODE_ISPEED])
- cfsetispeed(attrs, modes.mode_val[TTYMODE_ISPEED]);
- if (modes.have_mode[TTYMODE_OSPEED])
- cfsetospeed(attrs, modes.mode_val[TTYMODE_OSPEED]);
-}
-
-/*
- * The main setup function for the pty back end. This doesn't match
- * the signature of backend_init(), partly because it has to be able
- * to take extra arguments such as an argv array, and also because
- * once we're changing the type signature _anyway_ we can discard the
- * stuff that's not really applicable to this backend like host names
- * and port numbers.
- */
-Backend *pty_backend_create(
- Seat *seat, LogContext *logctx, Conf *conf, char **argv, const char *cmd,
- struct ssh_ttymodes ttymodes, bool pipes_instead, const char *dir,
- const char *const *env_vars_to_unset)
-{
- int slavefd;
- pid_t pid, pgrp;
-#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */
- bool got_windowid;
- long windowid;
-#endif
- Pty *pty;
- int i;
-
- /* No local authentication phase in this protocol */
- seat_set_trust_status(seat, false);
-
- if (single_pty) {
- pty = single_pty;
- assert(pty->conf == NULL);
- } else {
- pty = new_pty_struct();
- pty->master_fd = pty->slave_fd = -1;
-#ifndef OMIT_UTMP
- pty_stamped_utmp = false;
-#endif
- }
- for (i = 0; i < 6; i++)
- pty->pipefds[i] = -1;
- for (i = 0; i < 3; i++) {
- pty->fds[i].fd = -1;
- pty->fds[i].pty = pty;
- }
-
- if (pty_signal_pipe[0] < 0) {
- if (pipe(pty_signal_pipe) < 0) {
- perror("pipe");
- exit(1);
- }
- cloexec(pty_signal_pipe[0]);
- cloexec(pty_signal_pipe[1]);
- }
-
- pty->seat = seat;
- pty->backend.vt = &pty_backend;
-
- pty->conf = conf_copy(conf);
- pty->term_width = conf_get_int(conf, CONF_width);
- pty->term_height = conf_get_int(conf, CONF_height);
-
- if (!ptyfds)
- ptyfds = newtree234(ptyfd_compare);
-
- if (pipes_instead) {
- if (pty->master_fd >= 0) {
- /* If somehow we've got a pty master already and don't
- * need it, throw it away! */
- close(pty->master_fd);
-#ifndef OMIT_UTMP
- if (pty_utmp_helper_pipe >= 0) {
- close(pty_utmp_helper_pipe); /* don't need this either */
- pty_utmp_helper_pipe = -1;
- }
-#endif
- }
-
-
- for (i = 0; i < 6; i += 2) {
- if (pipe(pty->pipefds + i) < 0) {
- backend_free(&pty->backend);
- return NULL;
- }
- }
-
- pty->fds[0].fd = pty->master_i = pty->pipefds[1];
- pty->fds[1].fd = pty->master_o = pty->pipefds[2];
- pty->fds[2].fd = pty->master_e = pty->pipefds[4];
-
- add234(ptyfds, &pty->fds[0]);
- add234(ptyfds, &pty->fds[1]);
- add234(ptyfds, &pty->fds[2]);
- } else {
- if (pty->master_fd < 0)
- pty_open_master(pty);
-
-#ifndef OMIT_UTMP
- /*
- * Stamp utmp (that is, tell the utmp helper process to do so),
- * or not.
- */
- if (pty_utmp_helper_pipe >= 0) { /* if it's < 0, we can't anyway */
- if (!conf_get_bool(conf, CONF_stamp_utmp)) {
- /* We're not stamping utmp, so just let the child
- * process die that was waiting to unstamp it later. */
- close(pty_utmp_helper_pipe);
- pty_utmp_helper_pipe = -1;
- } else {
- const char *location = seat_get_x_display(pty->seat);
- int len = strlen(location)+1, pos = 0; /* +1 to include NUL */
- while (pos < len) {
- int ret = write(pty_utmp_helper_pipe,
- location + pos, len - pos);
- if (ret < 0) {
- perror("pterm: writing to utmp helper process");
- close(pty_utmp_helper_pipe); /* arrgh, just give up */
- pty_utmp_helper_pipe = -1;
- break;
- }
- pos += ret;
- }
- }
- }
-#endif
-
- pty->master_i = pty->master_fd;
- pty->master_o = pty->master_fd;
- pty->master_e = -1;
-
- pty->fds[0].fd = pty->master_fd;
- add234(ptyfds, &pty->fds[0]);
- }
-
-#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */
- got_windowid = seat_get_windowid(pty->seat, &windowid);
-#endif
-
- /*
- * Set up the signal handler to catch SIGCHLD, if pty_pre_init
- * didn't already do it.
- */
- pty_setup_sigchld_handler();
-
- /*
- * Fork and execute the command.
- */
- pid = fork();
- if (pid < 0) {
- perror("fork");
- exit(1);
- }
-
- if (pid == 0) {
- struct termios attrs;
-
- /*
- * We are the child.
- */
-
- if (pty_osx_envrestore_prefix) {
- int plen = strlen(pty_osx_envrestore_prefix);
- extern char **environ;
- char **ep;
-
- restart_osx_env_restore:
- for (ep = environ; *ep; ep++) {
- char *e = *ep;
-
- if (!strncmp(e, pty_osx_envrestore_prefix, plen)) {
- bool unset = (e[plen] == 'u');
- char *pname = dupprintf("%.*s", (int)strcspn(e, "="), e);
- char *name = pname + plen + 1;
- char *value = e + strcspn(e, "=");
- if (*value) value++;
- value = dupstr(value);
- if (unset)
- unsetenv(name);
- else
- setenv(name, value, 1);
- unsetenv(pname);
- sfree(pname);
- sfree(value);
- goto restart_osx_env_restore;
- }
- }
- }
-
- pgrp = getpid();
-
- if (pipes_instead) {
- int i;
- dup2(pty->pipefds[0], 0);
- dup2(pty->pipefds[3], 1);
- dup2(pty->pipefds[5], 2);
- for (i = 0; i < 6; i++)
- close(pty->pipefds[i]);
-
- setsid();
- } else {
- slavefd = pty_open_slave(pty);
- if (slavefd < 0) {
- perror("slave pty: open");
- _exit(1);
- }
-
- close(pty->master_fd);
- noncloexec(slavefd);
- dup2(slavefd, 0);
- dup2(slavefd, 1);
- dup2(slavefd, 2);
- close(slavefd);
- setsid();
-#ifdef TIOCSCTTY
- ioctl(0, TIOCSCTTY, 1);
-#endif
- tcsetpgrp(0, pgrp);
-
- /*
- * Set up configuration-dependent termios settings on the new
- * pty. Linux would have let us do this on the pty master
- * before we forked, but that fails on OS X, so we do it here
- * instead.
- */
- if (tcgetattr(0, &attrs) == 0) {
- /*
- * Set the backspace character to be whichever of ^H and
- * ^? is specified by bksp_is_delete.
- */
- attrs.c_cc[VERASE] = conf_get_bool(conf, CONF_bksp_is_delete)
- ? '\177' : '\010';
-
- /*
- * Set the IUTF8 bit iff the character set is UTF-8.
- */
-#ifdef IUTF8
- if (seat_is_utf8(seat))
- attrs.c_iflag |= IUTF8;
- else
- attrs.c_iflag &= ~IUTF8;
-#endif
-
- copy_ttymodes_into_termios(&attrs, ttymodes);
-
- tcsetattr(0, TCSANOW, &attrs);
- }
- }
-
- setpgid(pgrp, pgrp);
- if (!pipes_instead) {
- int ptyfd = open(pty->name, O_WRONLY, 0);
- if (ptyfd >= 0)
- close(ptyfd);
- }
- setpgid(pgrp, pgrp);
-
- if (env_vars_to_unset)
- for (const char *const *p = env_vars_to_unset; *p; p++)
- unsetenv(*p);
-
- if (!pipes_instead) {
- char *term_env_var = dupprintf("TERM=%s",
- conf_get_str(conf, CONF_termtype));
- putenv(term_env_var);
- /* We mustn't free term_env_var, as putenv links it into the
- * environment in place.
- */
- }
-#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */
- if (got_windowid) {
- char *windowid_env_var = dupprintf("WINDOWID=%ld", windowid);
- putenv(windowid_env_var);
- /* We mustn't free windowid_env_var, as putenv links it into the
- * environment in place.
- */
- }
- {
- /*
- * In case we were invoked with a --display argument that
- * doesn't match DISPLAY in our actual environment, we
- * should set DISPLAY for processes running inside the
- * terminal to match the display the terminal itself is
- * on.
- */
- const char *x_display = seat_get_x_display(pty->seat);
- if (x_display) {
- char *x_display_env_var = dupprintf("DISPLAY=%s", x_display);
- putenv(x_display_env_var);
- /* As above, we don't free this. */
- } else {
- unsetenv("DISPLAY");
- }
- }
-#endif
- {
- char *key, *val;
-
- for (val = conf_get_str_strs(conf, CONF_environmt, NULL, &key);
- val != NULL;
- val = conf_get_str_strs(conf, CONF_environmt, key, &key)) {
- char *varval = dupcat(key, "=", val);
- putenv(varval);
- /*
- * We must not free varval, since putenv links it
- * into the environment _in place_. Weird, but
- * there we go. Memory usage will be rationalised
- * as soon as we exec anyway.
- */
- }
- }
-
- if (dir) {
- if (chdir(dir) < 0) {
- /* Ignore the error - nothing we can sensibly do about it,
- * and our existing cwd is as good a fallback as any. */
- }
- }
-
- /*
- * SIGINT, SIGQUIT and SIGPIPE may have been set to ignored by
- * our parent, particularly by things like sh -c 'pterm &' and
- * some window or session managers. SIGPIPE was also
- * (potentially) blocked by us during startup. Reverse all
- * this for our child process.
- */
- putty_signal(SIGINT, SIG_DFL);
- putty_signal(SIGQUIT, SIG_DFL);
- putty_signal(SIGPIPE, SIG_DFL);
- block_signal(SIGPIPE, false);
- if (argv || cmd) {
- /*
- * If we were given a separated argument list, try to exec
- * it.
- */
- if (argv) {
- execvp(argv[0], argv);
- }
- /*
- * Otherwise, if we were given a single command string,
- * try passing that to $SHELL -c.
- *
- * In the case of pterm, this system of fallbacks arranges
- * that we can _either_ follow 'pterm -e' with a list of
- * argv elements to be fed directly to exec, _or_ with a
- * single argument containing a command to be parsed by a
- * shell (but, in cases of doubt, the former is more
- * reliable). We arrange this by setting argv to the full
- * argument list, and also setting cmd to the single
- * element of argv if it's a length-1 list.
- *
- * A quick survey of other terminal emulators' -e options
- * (as of Debian squeeze) suggests that:
- *
- * - xterm supports both modes, more or less like this
- * - gnome-terminal will only accept a one-string shell command
- * - Eterm, kterm and rxvt will only accept a list of
- * argv elements (as did older versions of pterm).
- *
- * It therefore seems important to support both usage
- * modes in order to be a drop-in replacement for either
- * xterm or gnome-terminal, and hence for anyone's
- * plausible uses of the Debian-style alias
- * 'x-terminal-emulator'.
- *
- * In other use cases, a caller can set only one of argv
- * and cmd to get a fixed handling of the input.
- */
- if (cmd) {
- char *shell = getenv("SHELL");
- if (shell)
- execl(shell, shell, "-c", cmd, (void *)NULL);
- }
- } else {
- const char *shell = getenv("SHELL");
- if (!shell)
- shell = "/bin/sh";
- char *shellname;
- if (conf_get_bool(conf, CONF_login_shell)) {
- const char *p = strrchr(shell, '/');
- shellname = snewn(2+strlen(shell), char);
- p = p ? p+1 : shell;
- sprintf(shellname, "-%s", p);
- } else
- shellname = (char *)shell;
- execl(shell, shellname, (void *)NULL);
- }
-
- /*
- * If we're here, exec has gone badly foom.
- */
- perror("exec");
- _exit(127);
- } else {
- pty->child_pid = pid;
- pty->child_dead = false;
- pty->finished = false;
- if (pty->slave_fd > 0)
- close(pty->slave_fd);
- if (!ptys_by_pid)
- ptys_by_pid = newtree234(pty_compare_by_pid);
- if (pty->pipefds[0] >= 0) {
- close(pty->pipefds[0]);
- pty->pipefds[0] = -1;
- }
- if (pty->pipefds[3] >= 0) {
- close(pty->pipefds[3]);
- pty->pipefds[3] = -1;
- }
- if (pty->pipefds[5] >= 0) {
- close(pty->pipefds[5]);
- pty->pipefds[5] = -1;
- }
- add234(ptys_by_pid, pty);
- }
-
- pty_uxsel_setup(pty);
-
- return &pty->backend;
-}
-
-/*
- * This is the pty backend's _official_ init method, for BackendVtable
- * purposes. Its job is just to be an API converter, ignoring the
- * irrelevant input parameters and making up auxiliary outputs. Also
- * it gets the argv array from the global variable pty_argv, expecting
- * that it will have been invoked by pterm.
- */
-static char *pty_init(const BackendVtable *vt, Seat *seat,
- Backend **backend_handle, LogContext *logctx,
- Conf *conf, const char *host, int port,
- char **realhost, bool nodelay, bool keepalive)
-{
- const char *cmd = NULL;
- struct ssh_ttymodes modes;
-
- memset(&modes, 0, sizeof(modes));
-
- if (pty_argv && pty_argv[0] && !pty_argv[1])
- cmd = pty_argv[0];
-
- assert(vt == &pty_backend);
- *backend_handle = pty_backend_create(
- seat, logctx, conf, pty_argv, cmd, modes, false, NULL, NULL);
- *realhost = dupstr("");
- return NULL;
-}
-
-static void pty_reconfig(Backend *be, Conf *conf)
-{
- Pty *pty = container_of(be, Pty, backend);
- /*
- * We don't have much need to reconfigure this backend, but
- * unfortunately we do need to pick up the setting of Close On
- * Exit so we know whether to give a `terminated' message.
- */
- conf_copy_into(pty->conf, conf);
-}
-
-/*
- * Stub routine (never called in pterm).
- */
-static void pty_free(Backend *be)
-{
- Pty *pty = container_of(be, Pty, backend);
- int i;
-
- pty_close(pty);
-
- /* Either of these may fail `not found'. That's fine with us. */
- del234(ptys_by_pid, pty);
- for (i = 0; i < 3; i++)
- if (pty->fds[i].fd >= 0)
- del234(ptyfds, &pty->fds[i]);
-
- bufchain_clear(&pty->output_data);
-
- conf_free(pty->conf);
- pty->conf = NULL;
-
- if (pty == single_pty) {
- /*
- * Leave this structure around in case we need to Restart
- * Session.
- */
- } else {
- sfree(pty);
- }
-}
-
-static void pty_try_write(Pty *pty)
-{
- ssize_t ret;
-
- assert(pty->master_i >= 0);
-
- while (bufchain_size(&pty->output_data) > 0) {
- ptrlen data = bufchain_prefix(&pty->output_data);
- ret = write(pty->master_i, data.ptr, data.len);
-
- if (ret < 0 && (errno == EWOULDBLOCK)) {
- /*
- * We've sent all we can for the moment.
- */
- break;
- }
- if (ret < 0) {
- perror("write pty master");
- exit(1);
- }
- bufchain_consume(&pty->output_data, ret);
- }
-
- if (pty->pending_eof && bufchain_size(&pty->output_data) == 0) {
- /* This should only happen if pty->master_i is a pipe that
- * doesn't alias either output fd */
- assert(pty->master_i != pty->master_o);
- assert(pty->master_i != pty->master_e);
- uxsel_del(pty->master_i);
- close(pty->master_i);
- pty->master_i = -1;
- pty->pending_eof = false;
- }
-
- pty_uxsel_setup(pty);
-}
-
-/*
- * Called to send data down the pty.
- */
-static size_t pty_send(Backend *be, const char *buf, size_t len)
-{
- Pty *pty = container_of(be, Pty, backend);
-
- if (pty->master_i < 0 || pty->pending_eof)
- return 0; /* ignore all writes if fd closed */
-
- bufchain_add(&pty->output_data, buf, len);
- pty_try_write(pty);
-
- return bufchain_size(&pty->output_data);
-}
-
-static void pty_close(Pty *pty)
-{
- int i;
-
- if (pty->master_o >= 0)
- uxsel_del(pty->master_o);
- if (pty->master_e >= 0)
- uxsel_del(pty->master_e);
- if (pty->master_i >= 0)
- uxsel_del(pty->master_i);
-
- if (pty->master_fd >= 0) {
- close(pty->master_fd);
- pty->master_fd = -1;
- }
- for (i = 0; i < 6; i++) {
- if (pty->pipefds[i] >= 0)
- close(pty->pipefds[i]);
- pty->pipefds[i] = -1;
- }
- pty->master_i = pty->master_o = pty->master_e = -1;
-#ifndef OMIT_UTMP
- if (pty_utmp_helper_pipe >= 0) {
- close(pty_utmp_helper_pipe); /* this causes utmp to be cleaned up */
- pty_utmp_helper_pipe = -1;
- }
-#endif
-}
-
-/*
- * Called to query the current socket sendability status.
- */
-static size_t pty_sendbuffer(Backend *be)
-{
- /* Pty *pty = container_of(be, Pty, backend); */
- return 0;
-}
-
-/*
- * Called to set the size of the window
- */
-static void pty_size(Backend *be, int width, int height)
-{
- Pty *pty = container_of(be, Pty, backend);
- struct winsize size;
- int xpixel = 0, ypixel = 0;
-
- pty->term_width = width;
- pty->term_height = height;
-
- if (pty->master_fd < 0)
- return;
-
- seat_get_window_pixel_size(pty->seat, &xpixel, &ypixel);
-
- size.ws_row = (unsigned short)pty->term_height;
- size.ws_col = (unsigned short)pty->term_width;
- size.ws_xpixel = (unsigned short)xpixel;
- size.ws_ypixel = (unsigned short)ypixel;
- ioctl(pty->master_fd, TIOCSWINSZ, (void *)&size);
- return;
-}
-
-/*
- * Send special codes.
- */
-static void pty_special(Backend *be, SessionSpecialCode code, int arg)
-{
- Pty *pty = container_of(be, Pty, backend);
-
- if (code == SS_BRK) {
- if (pty->master_fd >= 0)
- tcsendbreak(pty->master_fd, 0);
- return;
- }
-
- if (code == SS_EOF) {
- if (pty->master_i >= 0 && pty->master_i != pty->master_fd) {
- pty->pending_eof = true;
- pty_try_write(pty);
- }
- return;
- }
-
- {
- int sig = -1;
-
- #define SIGNAL_SUB(name) if (code == SS_SIG ## name) sig = SIG ## name;
- #define SIGNAL_MAIN(name, text) SIGNAL_SUB(name)
- #define SIGNALS_LOCAL_ONLY
- #include "sshsignals.h"
- #undef SIGNAL_SUB
- #undef SIGNAL_MAIN
- #undef SIGNALS_LOCAL_ONLY
-
- if (sig != -1) {
- if (!pty->child_dead)
- kill(pty->child_pid, sig);
- return;
- }
- }
-
- return;
-}
-
-/*
- * Return a list of the special codes that make sense in this
- * protocol.
- */
-static const SessionSpecial *pty_get_specials(Backend *be)
-{
- /* Pty *pty = container_of(be, Pty, backend); */
- /*
- * Hmm. When I get round to having this actually usable, it
- * might be quite nice to have the ability to deliver a few
- * well chosen signals to the child process - SIGINT, SIGTERM,
- * SIGKILL at least.
- */
- return NULL;
-}
-
-static bool pty_connected(Backend *be)
-{
- /* Pty *pty = container_of(be, Pty, backend); */
- return true;
-}
-
-static bool pty_sendok(Backend *be)
-{
- /* Pty *pty = container_of(be, Pty, backend); */
- return true;
-}
-
-static void pty_unthrottle(Backend *be, size_t backlog)
-{
- /* Pty *pty = container_of(be, Pty, backend); */
- /* do nothing */
-}
-
-static bool pty_ldisc(Backend *be, int option)
-{
- /* Pty *pty = container_of(be, Pty, backend); */
- return false; /* neither editing nor echoing */
-}
-
-static void pty_provide_ldisc(Backend *be, Ldisc *ldisc)
-{
- /* Pty *pty = container_of(be, Pty, backend); */
- /* This is a stub. */
-}
-
-static int pty_exitcode(Backend *be)
-{
- Pty *pty = container_of(be, Pty, backend);
- if (!pty->finished)
- return -1; /* not dead yet */
- else if (WIFSIGNALED(pty->exit_code))
- return 128 + WTERMSIG(pty->exit_code);
- else
- return WEXITSTATUS(pty->exit_code);
-}
-
-int pty_backend_exit_signum(Backend *be)
-{
- Pty *pty = container_of(be, Pty, backend);
-
- if (!pty->finished || !WIFSIGNALED(pty->exit_code))
- return -1;
-
- return WTERMSIG(pty->exit_code);
-}
-
-ptrlen pty_backend_exit_signame(Backend *be, char **aux_msg)
-{
- *aux_msg = NULL;
-
- int sig = pty_backend_exit_signum(be);
- if (sig < 0)
- return PTRLEN_LITERAL("");
-
- #define SIGNAL_SUB(s) { \
- if (sig == SIG ## s) \
- return PTRLEN_LITERAL(#s); \
- }
- #define SIGNAL_MAIN(s, desc) SIGNAL_SUB(s)
- #define SIGNALS_LOCAL_ONLY
- #include "sshsignals.h"
- #undef SIGNAL_MAIN
- #undef SIGNAL_SUB
- #undef SIGNALS_LOCAL_ONLY
-
- *aux_msg = dupprintf("untranslatable signal number %d: %s",
- sig, strsignal(sig));
- return PTRLEN_LITERAL("HUP"); /* need some kind of default */
-}
-
-static int pty_cfg_info(Backend *be)
-{
- /* Pty *pty = container_of(be, Pty, backend); */
- return 0;
-}
-
-const BackendVtable pty_backend = {
- .init = pty_init,
- .free = pty_free,
- .reconfig = pty_reconfig,
- .send = pty_send,
- .sendbuffer = pty_sendbuffer,
- .size = pty_size,
- .special = pty_special,
- .get_specials = pty_get_specials,
- .connected = pty_connected,
- .exitcode = pty_exitcode,
- .sendok = pty_sendok,
- .ldisc_option_state = pty_ldisc,
- .provide_ldisc = pty_provide_ldisc,
- .unthrottle = pty_unthrottle,
- .cfg_info = pty_cfg_info,
- .id = "pty",
- .displayname = "pty",
- .protocol = -1,
-};
diff --git a/UNIX/UXPUTTY.C b/UNIX/UXPUTTY.C
deleted file mode 100644
index 7a808087..00000000
--- a/UNIX/UXPUTTY.C
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Unix PuTTY main program.
- */
-
-#include <stdio.h>
-#include <ctype.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <unistd.h>
-#include <gtk/gtk.h>
-#include <gdk/gdk.h>
-
-#define MAY_REFER_TO_GTK_IN_HEADERS
-
-#include "putty.h"
-#include "ssh.h"
-#include "storage.h"
-
-#include "gtkcompat.h"
-
-/*
- * Stubs to avoid uxpty.c needing to be linked in.
- */
-const bool use_pty_argv = false;
-char **pty_argv; /* never used */
-char *pty_osx_envrestore_prefix;
-
-/*
- * Clean up and exit.
- */
-void cleanup_exit(int code)
-{
- /*
- * Clean up.
- */
- sk_cleanup();
- random_save_seed();
- exit(code);
-}
-
-const struct BackendVtable *select_backend(Conf *conf)
-{
- const struct BackendVtable *vt =
- backend_vt_from_proto(conf_get_int(conf, CONF_protocol));
- assert(vt != NULL);
- return vt;
-}
-
-void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx)
-{
- char *title = dupcat(appname, " Configuration");
- create_config_box(title, conf, false, 0, after, afterctx);
- sfree(title);
-}
-
-const bool use_event_log = true, new_session = true, saved_sessions = true;
-const bool dup_check_launchable = true;
-
-/*
- * X11-forwarding-related things suitable for Gtk app.
- */
-
-char *platform_get_x_display(void) {
- const char *display;
- /* Try to take account of --display and what have you. */
- if (!(display = gdk_get_display()))
- /* fall back to traditional method */
- display = getenv("DISPLAY");
- return dupstr(display);
-}
-
-const bool share_can_be_downstream = true;
-const bool share_can_be_upstream = true;
-
-const unsigned cmdline_tooltype =
- TOOLTYPE_HOST_ARG |
- TOOLTYPE_PORT_ARG |
- TOOLTYPE_NO_VERBOSE_OPTION;
-
-void setup(bool single)
-{
- sk_init();
- settings_set_default_protocol(be_default_protocol);
- /* Find the appropriate default port. */
- {
- const struct BackendVtable *vt =
- backend_vt_from_proto(be_default_protocol);
- settings_set_default_port(0); /* illegal */
- if (vt)
- settings_set_default_port(vt->default_port);
- }
-}
diff --git a/UNIX/UXSEL.C b/UNIX/UXSEL.C
index eb3abed3..18d512ac 100644
--- a/UNIX/UXSEL.C
+++ b/UNIX/UXSEL.C
@@ -2,10 +2,10 @@
* uxsel.c
*
* This module is a sort of all-purpose interchange for file
- * descriptors. At one end it talks to uxnet.c and pty.c and
+ * descriptors. At one end it talks to network.c and pty.c and
* anything else which might have one or more fds that need
* select() or poll()-type things doing to them during an extended
- * program run; at the other end it talks to pterm.c or uxplink.c or
+ * program run; at the other end it talks to window.c or plink.c or
* anything else which might have its own means of actually doing
* those select()-type things.
*/
diff --git a/UNIX/UXSER.C b/UNIX/UXSER.C
deleted file mode 100644
index d4a1e0ba..00000000
--- a/UNIX/UXSER.C
+++ /dev/null
@@ -1,595 +0,0 @@
-/*
- * Serial back end (Unix-specific).
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <limits.h>
-
-#include <errno.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <termios.h>
-
-#include "putty.h"
-#include "tree234.h"
-
-#define SERIAL_MAX_BACKLOG 4096
-
-typedef struct Serial Serial;
-struct Serial {
- Seat *seat;
- LogContext *logctx;
- int fd;
- bool finished;
- size_t inbufsize;
- bufchain output_data;
- Backend backend;
-};
-
-/*
- * We store our serial backends in a tree sorted by fd, so that
- * when we get an uxsel notification we know which backend instance
- * is the owner of the serial port that caused it.
- */
-static int serial_compare_by_fd(void *av, void *bv)
-{
- Serial *a = (Serial *)av;
- Serial *b = (Serial *)bv;
-
- if (a->fd < b->fd)
- return -1;
- else if (a->fd > b->fd)
- return +1;
- return 0;
-}
-
-static int serial_find_by_fd(void *av, void *bv)
-{
- int a = *(int *)av;
- Serial *b = (Serial *)bv;
-
- if (a < b->fd)
- return -1;
- else if (a > b->fd)
- return +1;
- return 0;
-}
-
-static tree234 *serial_by_fd = NULL;
-
-static void serial_select_result(int fd, int event);
-static void serial_uxsel_setup(Serial *serial);
-static void serial_try_write(Serial *serial);
-
-static char *serial_configure(Serial *serial, Conf *conf)
-{
- struct termios options;
- int bflag, bval, speed, flow, parity;
- const char *str;
-
- if (serial->fd < 0)
- return dupstr("Unable to reconfigure already-closed "
- "serial connection");
-
- tcgetattr(serial->fd, &options);
-
- /*
- * Find the appropriate baud rate flag.
- */
- speed = conf_get_int(conf, CONF_serspeed);
-#define SETBAUD(x) (bflag = B ## x, bval = x)
-#define CHECKBAUD(x) do { if (speed >= x) SETBAUD(x); } while (0)
- SETBAUD(50);
-#ifdef B75
- CHECKBAUD(75);
-#endif
-#ifdef B110
- CHECKBAUD(110);
-#endif
-#ifdef B134
- CHECKBAUD(134);
-#endif
-#ifdef B150
- CHECKBAUD(150);
-#endif
-#ifdef B200
- CHECKBAUD(200);
-#endif
-#ifdef B300
- CHECKBAUD(300);
-#endif
-#ifdef B600
- CHECKBAUD(600);
-#endif
-#ifdef B1200
- CHECKBAUD(1200);
-#endif
-#ifdef B1800
- CHECKBAUD(1800);
-#endif
-#ifdef B2400
- CHECKBAUD(2400);
-#endif
-#ifdef B4800
- CHECKBAUD(4800);
-#endif
-#ifdef B9600
- CHECKBAUD(9600);
-#endif
-#ifdef B19200
- CHECKBAUD(19200);
-#endif
-#ifdef B38400
- CHECKBAUD(38400);
-#endif
-#ifdef B57600
- CHECKBAUD(57600);
-#endif
-#ifdef B76800
- CHECKBAUD(76800);
-#endif
-#ifdef B115200
- CHECKBAUD(115200);
-#endif
-#ifdef B153600
- CHECKBAUD(153600);
-#endif
-#ifdef B230400
- CHECKBAUD(230400);
-#endif
-#ifdef B307200
- CHECKBAUD(307200);
-#endif
-#ifdef B460800
- CHECKBAUD(460800);
-#endif
-#ifdef B500000
- CHECKBAUD(500000);
-#endif
-#ifdef B576000
- CHECKBAUD(576000);
-#endif
-#ifdef B921600
- CHECKBAUD(921600);
-#endif
-#ifdef B1000000
- CHECKBAUD(1000000);
-#endif
-#ifdef B1152000
- CHECKBAUD(1152000);
-#endif
-#ifdef B1500000
- CHECKBAUD(1500000);
-#endif
-#ifdef B2000000
- CHECKBAUD(2000000);
-#endif
-#ifdef B2500000
- CHECKBAUD(2500000);
-#endif
-#ifdef B3000000
- CHECKBAUD(3000000);
-#endif
-#ifdef B3500000
- CHECKBAUD(3500000);
-#endif
-#ifdef B4000000
- CHECKBAUD(4000000);
-#endif
-#undef CHECKBAUD
-#undef SETBAUD
- cfsetispeed(&options, bflag);
- cfsetospeed(&options, bflag);
- logeventf(serial->logctx, "Configuring baud rate %d", bval);
-
- options.c_cflag &= ~CSIZE;
- switch (conf_get_int(conf, CONF_serdatabits)) {
- case 5: options.c_cflag |= CS5; break;
- case 6: options.c_cflag |= CS6; break;
- case 7: options.c_cflag |= CS7; break;
- case 8: options.c_cflag |= CS8; break;
- default: return dupstr("Invalid number of data bits "
- "(need 5, 6, 7 or 8)");
- }
- logeventf(serial->logctx, "Configuring %d data bits",
- conf_get_int(conf, CONF_serdatabits));
-
- if (conf_get_int(conf, CONF_serstopbits) >= 4) {
- options.c_cflag |= CSTOPB;
- } else {
- options.c_cflag &= ~CSTOPB;
- }
- logeventf(serial->logctx, "Configuring %s",
- (options.c_cflag & CSTOPB ? "2 stop bits" : "1 stop bit"));
-
- options.c_iflag &= ~(IXON|IXOFF);
-#ifdef CRTSCTS
- options.c_cflag &= ~CRTSCTS;
-#endif
-#ifdef CNEW_RTSCTS
- options.c_cflag &= ~CNEW_RTSCTS;
-#endif
- flow = conf_get_int(conf, CONF_serflow);
- if (flow == SER_FLOW_XONXOFF) {
- options.c_iflag |= IXON | IXOFF;
- str = "XON/XOFF";
- } else if (flow == SER_FLOW_RTSCTS) {
-#ifdef CRTSCTS
- options.c_cflag |= CRTSCTS;
-#endif
-#ifdef CNEW_RTSCTS
- options.c_cflag |= CNEW_RTSCTS;
-#endif
- str = "RTS/CTS";
- } else
- str = "no";
- logeventf(serial->logctx, "Configuring %s flow control", str);
-
- /* Parity */
- parity = conf_get_int(conf, CONF_serparity);
- if (parity == SER_PAR_ODD) {
- options.c_cflag |= PARENB;
- options.c_cflag |= PARODD;
- str = "odd";
- } else if (parity == SER_PAR_EVEN) {
- options.c_cflag |= PARENB;
- options.c_cflag &= ~PARODD;
- str = "even";
- } else {
- options.c_cflag &= ~PARENB;
- str = "no";
- }
- logeventf(serial->logctx, "Configuring %s parity", str);
-
- options.c_cflag |= CLOCAL | CREAD;
- options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
- options.c_iflag &= ~(ISTRIP | IGNCR | INLCR | ICRNL
-#ifdef IUCLC
- | IUCLC
-#endif
- );
- options.c_oflag &= ~(OPOST
-#ifdef ONLCR
- | ONLCR
-#endif
-#ifdef OCRNL
- | OCRNL
-#endif
-#ifdef ONOCR
- | ONOCR
-#endif
-#ifdef ONLRET
- | ONLRET
-#endif
- );
- options.c_cc[VMIN] = 1;
- options.c_cc[VTIME] = 0;
-
- if (tcsetattr(serial->fd, TCSANOW, &options) < 0)
- return dupprintf("Configuring serial port: %s", strerror(errno));
-
- return NULL;
-}
-
-/*
- * Called to set up the serial connection.
- *
- * Returns an error message, or NULL on success.
- *
- * Also places the canonical host name into `realhost'. It must be
- * freed by the caller.
- */
-static char *serial_init(const BackendVtable *vt, Seat *seat,
- Backend **backend_handle, LogContext *logctx,
- Conf *conf, const char *host, int port,
- char **realhost, bool nodelay, bool keepalive)
-{
- Serial *serial;
- char *err;
- char *line;
-
- /* No local authentication phase in this protocol */
- seat_set_trust_status(seat, false);
-
- serial = snew(Serial);
- serial->backend.vt = vt;
- *backend_handle = &serial->backend;
-
- serial->seat = seat;
- serial->logctx = logctx;
- serial->finished = false;
- serial->inbufsize = 0;
- bufchain_init(&serial->output_data);
-
- line = conf_get_str(conf, CONF_serline);
- logeventf(serial->logctx, "Opening serial device %s", line);
-
- serial->fd = open(line, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
- if (serial->fd < 0)
- return dupprintf("Opening serial port '%s': %s",
- line, strerror(errno));
-
- cloexec(serial->fd);
-
- err = serial_configure(serial, conf);
- if (err)
- return err;
-
- *realhost = dupstr(line);
-
- if (!serial_by_fd)
- serial_by_fd = newtree234(serial_compare_by_fd);
- add234(serial_by_fd, serial);
-
- serial_uxsel_setup(serial);
-
- /*
- * Specials are always available.
- */
- seat_update_specials_menu(serial->seat);
-
- return NULL;
-}
-
-static void serial_close(Serial *serial)
-{
- if (serial->fd >= 0) {
- uxsel_del(serial->fd);
- close(serial->fd);
- serial->fd = -1;
- }
-}
-
-static void serial_free(Backend *be)
-{
- Serial *serial = container_of(be, Serial, backend);
-
- serial_close(serial);
-
- bufchain_clear(&serial->output_data);
-
- sfree(serial);
-}
-
-static void serial_reconfig(Backend *be, Conf *conf)
-{
- Serial *serial = container_of(be, Serial, backend);
-
- char *err = serial_configure(serial, conf);
- if (err) {
- /*
- * FIXME: apart from freeing the dynamically allocated
- * message, what should we do if this returns an error?
- */
- sfree(err);
- }
-}
-
-static void serial_select_result(int fd, int event)
-{
- Serial *serial;
- char buf[4096];
- int ret;
- bool finished = false;
-
- serial = find234(serial_by_fd, &fd, serial_find_by_fd);
-
- if (!serial)
- return; /* spurious event; keep going */
-
- if (event == 1) {
- ret = read(serial->fd, buf, sizeof(buf));
-
- if (ret == 0) {
- /*
- * Shouldn't happen on a real serial port, but I'm open
- * to the idea that there might be two-way devices we
- * can treat _like_ serial ports which can return EOF.
- */
- finished = true;
- } else if (ret < 0) {
-#ifdef EAGAIN
- if (errno == EAGAIN)
- return; /* spurious */
-#endif
-#ifdef EWOULDBLOCK
- if (errno == EWOULDBLOCK)
- return; /* spurious */
-#endif
- perror("read serial port");
- exit(1);
- } else if (ret > 0) {
- serial->inbufsize = seat_stdout(serial->seat, buf, ret);
- serial_uxsel_setup(serial); /* might acquire backlog and freeze */
- }
- } else if (event == 2) {
- /*
- * Attempt to send data down the pty.
- */
- serial_try_write(serial);
- }
-
- if (finished) {
- serial_close(serial);
-
- serial->finished = true;
-
- seat_notify_remote_exit(serial->seat);
- }
-}
-
-static void serial_uxsel_setup(Serial *serial)
-{
- int rwx = 0;
-
- if (serial->inbufsize <= SERIAL_MAX_BACKLOG)
- rwx |= SELECT_R;
- if (bufchain_size(&serial->output_data))
- rwx |= SELECT_W; /* might also want to write to it */
- uxsel_set(serial->fd, rwx, serial_select_result);
-}
-
-static void serial_try_write(Serial *serial)
-{
- ssize_t ret;
-
- assert(serial->fd >= 0);
-
- while (bufchain_size(&serial->output_data) > 0) {
- ptrlen data = bufchain_prefix(&serial->output_data);
- ret = write(serial->fd, data.ptr, data.len);
-
- if (ret < 0 && (errno == EWOULDBLOCK)) {
- /*
- * We've sent all we can for the moment.
- */
- break;
- }
- if (ret < 0) {
- perror("write serial port");
- exit(1);
- }
- bufchain_consume(&serial->output_data, ret);
- }
-
- serial_uxsel_setup(serial);
-}
-
-/*
- * 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->fd < 0)
- return 0;
-
- bufchain_add(&serial->output_data, buf, len);
- serial_try_write(serial);
-
- return bufchain_size(&serial->output_data);
-}
-
-/*
- * Called to query the current sendability status.
- */
-static size_t serial_sendbuffer(Backend *be)
-{
- Serial *serial = container_of(be, Serial, backend);
- return bufchain_size(&serial->output_data);
-}
-
-/*
- * Called to set the size of the window
- */
-static void serial_size(Backend *be, int width, int height)
-{
- /* Do nothing! */
- return;
-}
-
-/*
- * Send serial special codes.
- */
-static void serial_special(Backend *be, SessionSpecialCode code, int arg)
-{
- Serial *serial = container_of(be, Serial, backend);
-
- if (serial->fd >= 0 && code == SS_BRK) {
- tcsendbreak(serial->fd, 0);
- logevent(serial->logctx, "Sending serial break at user request");
- }
-
- return;
-}
-
-/*
- * Return a list of the special codes that make sense in this
- * protocol.
- */
-static const SessionSpecial *serial_get_specials(Backend *be)
-{
- static const struct SessionSpecial specials[] = {
- {"Break", SS_BRK},
- {NULL, SS_EXITMENU}
- };
- return specials;
-}
-
-static bool serial_connected(Backend *be)
-{
- return true; /* always connected */
-}
-
-static bool serial_sendok(Backend *be)
-{
- return true;
-}
-
-static void serial_unthrottle(Backend *be, size_t backlog)
-{
- Serial *serial = container_of(be, Serial, backend);
- serial->inbufsize = backlog;
- serial_uxsel_setup(serial);
-}
-
-static bool serial_ldisc(Backend *be, int option)
-{
- /*
- * Local editing and local echo are off by default.
- */
- return false;
-}
-
-static void serial_provide_ldisc(Backend *be, Ldisc *ldisc)
-{
- /* This is a stub. */
-}
-
-static int serial_exitcode(Backend *be)
-{
- Serial *serial = container_of(be, Serial, backend);
- if (serial->fd >= 0)
- return -1; /* still connected */
- else
- /* Exit codes are a meaningless concept with serial ports */
- return INT_MAX;
-}
-
-/*
- * cfg_info for Serial does nothing at all.
- */
-static int serial_cfg_info(Backend *be)
-{
- return 0;
-}
-
-const BackendVtable serial_backend = {
- .init = serial_init,
- .free = serial_free,
- .reconfig = serial_reconfig,
- .send = serial_send,
- .sendbuffer = serial_sendbuffer,
- .size = serial_size,
- .special = serial_special,
- .get_specials = serial_get_specials,
- .connected = serial_connected,
- .exitcode = serial_exitcode,
- .sendok = serial_sendok,
- .ldisc_option_state = serial_ldisc,
- .provide_ldisc = serial_provide_ldisc,
- .unthrottle = serial_unthrottle,
- .cfg_info = serial_cfg_info,
- .id = "serial",
- .displayname = "Serial",
- .protocol = PROT_SERIAL,
- .serial_parity_mask = ((1 << SER_PAR_NONE) |
- (1 << SER_PAR_ODD) |
- (1 << SER_PAR_EVEN)),
- .serial_flow_mask = ((1 << SER_FLOW_NONE) |
- (1 << SER_FLOW_XONXOFF) |
- (1 << SER_FLOW_RTSCTS)),
-};
diff --git a/UNIX/UXSFTP.C b/UNIX/UXSFTP.C
deleted file mode 100644
index 89a81c92..00000000
--- a/UNIX/UXSFTP.C
+++ /dev/null
@@ -1,578 +0,0 @@
-/*
- * uxsftp.c: the Unix-specific parts of PSFTP and PSCP.
- */
-
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <stdlib.h>
-#include <fcntl.h>
-#include <dirent.h>
-#include <unistd.h>
-#include <utime.h>
-#include <errno.h>
-#include <assert.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "psftp.h"
-
-#if HAVE_GLOB_H
-#include <glob.h>
-#endif
-
-char *x_get_default(const char *key)
-{
- return NULL; /* this is a stub */
-}
-
-void platform_get_x11_auth(struct X11Display *display, Conf *conf)
-{
- /* Do nothing, therefore no auth. */
-}
-const bool platform_uses_x11_unix_by_default = true;
-
-/*
- * Default settings that are specific to PSFTP.
- */
-char *platform_default_s(const char *name)
-{
- return NULL;
-}
-
-bool platform_default_b(const char *name, bool def)
-{
- return def;
-}
-
-int platform_default_i(const char *name, int def)
-{
- return def;
-}
-
-FontSpec *platform_default_fontspec(const char *name)
-{
- return fontspec_new("");
-}
-
-Filename *platform_default_filename(const char *name)
-{
- if (!strcmp(name, "LogFileName"))
- return filename_from_str("putty.log");
- else
- return filename_from_str("");
-}
-
-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;
-}
-
-/*
- * Set local current directory. Returns NULL on success, or else an
- * error message which must be freed after printing.
- */
-char *psftp_lcd(char *dir)
-{
- if (chdir(dir) < 0)
- return dupprintf("%s: chdir: %s", dir, strerror(errno));
- else
- return NULL;
-}
-
-/*
- * Get local current directory. Returns a string which must be
- * freed.
- */
-char *psftp_getcwd(void)
-{
- char *buffer, *ret;
- size_t size = 256;
-
- buffer = snewn(size, char);
- while (1) {
- ret = getcwd(buffer, size);
- if (ret != NULL)
- return ret;
- if (errno != ERANGE) {
- sfree(buffer);
- return dupprintf("[cwd unavailable: %s]", strerror(errno));
- }
- /*
- * Otherwise, ERANGE was returned, meaning the buffer
- * wasn't big enough.
- */
- sgrowarray(buffer, size, size);
- }
-}
-
-struct RFile {
- int fd;
-};
-
-RFile *open_existing_file(const char *name, uint64_t *size,
- unsigned long *mtime, unsigned long *atime,
- long *perms)
-{
- int fd;
- RFile *ret;
-
- fd = open(name, O_RDONLY);
- if (fd < 0)
- return NULL;
-
- ret = snew(RFile);
- ret->fd = fd;
-
- if (size || mtime || atime || perms) {
- struct stat statbuf;
- if (fstat(fd, &statbuf) < 0) {
- fprintf(stderr, "%s: stat: %s\n", name, strerror(errno));
- memset(&statbuf, 0, sizeof(statbuf));
- }
-
- if (size)
- *size = statbuf.st_size;
-
- if (mtime)
- *mtime = statbuf.st_mtime;
-
- if (atime)
- *atime = statbuf.st_atime;
-
- if (perms)
- *perms = statbuf.st_mode;
- }
-
- return ret;
-}
-
-int read_from_file(RFile *f, void *buffer, int length)
-{
- return read(f->fd, buffer, length);
-}
-
-void close_rfile(RFile *f)
-{
- close(f->fd);
- sfree(f);
-}
-
-struct WFile {
- int fd;
- char *name;
-};
-
-WFile *open_new_file(const char *name, long perms)
-{
- int fd;
- WFile *ret;
-
- fd = open(name, O_CREAT | O_TRUNC | O_WRONLY,
- (mode_t)(perms ? perms : 0666));
- if (fd < 0)
- return NULL;
-
- ret = snew(WFile);
- ret->fd = fd;
- ret->name = dupstr(name);
-
- return ret;
-}
-
-
-WFile *open_existing_wfile(const char *name, uint64_t *size)
-{
- int fd;
- WFile *ret;
-
- fd = open(name, O_APPEND | O_WRONLY);
- if (fd < 0)
- return NULL;
-
- ret = snew(WFile);
- ret->fd = fd;
- ret->name = dupstr(name);
-
- if (size) {
- struct stat statbuf;
- if (fstat(fd, &statbuf) < 0) {
- fprintf(stderr, "%s: stat: %s\n", name, strerror(errno));
- memset(&statbuf, 0, sizeof(statbuf));
- }
-
- *size = statbuf.st_size;
- }
-
- return ret;
-}
-
-int write_to_file(WFile *f, void *buffer, int length)
-{
- char *p = (char *)buffer;
- int so_far = 0;
-
- /* Keep trying until we've really written as much as we can. */
- while (length > 0) {
- int ret = write(f->fd, p, length);
-
- if (ret < 0)
- return ret;
-
- if (ret == 0)
- break;
-
- p += ret;
- length -= ret;
- so_far += ret;
- }
-
- return so_far;
-}
-
-void set_file_times(WFile *f, unsigned long mtime, unsigned long atime)
-{
- struct utimbuf ut;
-
- ut.actime = atime;
- ut.modtime = mtime;
-
- utime(f->name, &ut);
-}
-
-/* Closes and frees the WFile */
-void close_wfile(WFile *f)
-{
- close(f->fd);
- sfree(f->name);
- sfree(f);
-}
-
-/* Seek offset bytes through file, from whence, where whence is
- FROM_START, FROM_CURRENT, or FROM_END */
-int seek_file(WFile *f, uint64_t offset, int whence)
-{
- int lseek_whence;
-
- switch (whence) {
- case FROM_START:
- lseek_whence = SEEK_SET;
- break;
- case FROM_CURRENT:
- lseek_whence = SEEK_CUR;
- break;
- case FROM_END:
- lseek_whence = SEEK_END;
- break;
- default:
- return -1;
- }
-
- return lseek(f->fd, offset, lseek_whence) >= 0 ? 0 : -1;
-}
-
-uint64_t get_file_posn(WFile *f)
-{
- return lseek(f->fd, (off_t) 0, SEEK_CUR);
-}
-
-int file_type(const char *name)
-{
- struct stat statbuf;
-
- if (stat(name, &statbuf) < 0) {
- if (errno != ENOENT)
- fprintf(stderr, "%s: stat: %s\n", name, strerror(errno));
- return FILE_TYPE_NONEXISTENT;
- }
-
- if (S_ISREG(statbuf.st_mode))
- return FILE_TYPE_FILE;
-
- if (S_ISDIR(statbuf.st_mode))
- return FILE_TYPE_DIRECTORY;
-
- return FILE_TYPE_WEIRD;
-}
-
-struct DirHandle {
- DIR *dir;
-};
-
-DirHandle *open_directory(const char *name, const char **errmsg)
-{
- DIR *dir;
- DirHandle *ret;
-
- dir = opendir(name);
- if (!dir) {
- *errmsg = strerror(errno);
- return NULL;
- }
-
- ret = snew(DirHandle);
- ret->dir = dir;
- return ret;
-}
-
-char *read_filename(DirHandle *dir)
-{
- struct dirent *de;
-
- do {
- de = readdir(dir->dir);
- if (de == NULL)
- return NULL;
- } while ((de->d_name[0] == '.' &&
- (de->d_name[1] == '\0' ||
- (de->d_name[1] == '.' && de->d_name[2] == '\0'))));
-
- return dupstr(de->d_name);
-}
-
-void close_directory(DirHandle *dir)
-{
- closedir(dir->dir);
- sfree(dir);
-}
-
-int test_wildcard(const char *name, bool cmdline)
-{
- struct stat statbuf;
-
- if (stat(name, &statbuf) == 0) {
- return WCTYPE_FILENAME;
- } else if (cmdline) {
- /*
- * On Unix, we never need to parse wildcards coming from
- * the command line, because the shell will have expanded
- * them into a filename list already.
- */
- return WCTYPE_NONEXISTENT;
- } else {
-#if HAVE_GLOB_H
- glob_t globbed;
- int ret = WCTYPE_NONEXISTENT;
-
- if (glob(name, GLOB_ERR, NULL, &globbed) == 0) {
- if (globbed.gl_pathc > 0)
- ret = WCTYPE_WILDCARD;
- globfree(&globbed);
- }
-
- return ret;
-#else
- /* On a system without glob.h, we just have to return a
- * failure code */
- return WCTYPE_NONEXISTENT;
-#endif
- }
-}
-
-/*
- * Actually return matching file names for a local wildcard.
- */
-#if HAVE_GLOB_H
-struct WildcardMatcher {
- glob_t globbed;
- int i;
-};
-WildcardMatcher *begin_wildcard_matching(const char *name) {
- WildcardMatcher *ret = snew(WildcardMatcher);
-
- if (glob(name, 0, NULL, &ret->globbed) < 0) {
- sfree(ret);
- return NULL;
- }
-
- ret->i = 0;
-
- return ret;
-}
-char *wildcard_get_filename(WildcardMatcher *dir) {
- if (dir->i < dir->globbed.gl_pathc) {
- return dupstr(dir->globbed.gl_pathv[dir->i++]);
- } else
- return NULL;
-}
-void finish_wildcard_matching(WildcardMatcher *dir) {
- globfree(&dir->globbed);
- sfree(dir);
-}
-#else
-WildcardMatcher *begin_wildcard_matching(const char *name)
-{
- return NULL;
-}
-char *wildcard_get_filename(WildcardMatcher *dir)
-{
- unreachable("Can't construct a valid WildcardMatcher without <glob.h>");
-}
-void finish_wildcard_matching(WildcardMatcher *dir)
-{
- unreachable("Can't construct a valid WildcardMatcher without <glob.h>");
-}
-#endif
-
-char *stripslashes(const char *str, bool local)
-{
- char *p;
-
- /*
- * On Unix, we do the same thing regardless of the 'local'
- * parameter.
- */
- p = strrchr(str, '/');
- if (p) str = p+1;
-
- return (char *)str;
-}
-
-bool vet_filename(const char *name)
-{
- if (strchr(name, '/'))
- return false;
-
- if (name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2])))
- return false;
-
- return true;
-}
-
-bool create_directory(const char *name)
-{
- return mkdir(name, 0777) == 0;
-}
-
-char *dir_file_cat(const char *dir, const char *file)
-{
- ptrlen dir_pl = ptrlen_from_asciz(dir);
- return dupcat(
- dir, ptrlen_endswith(dir_pl, PTRLEN_LITERAL("/"), NULL) ? "" : "/",
- file);
-}
-
-/*
- * Do a select() between all currently active network fds and
- * optionally stdin, using cli_main_loop.
- */
-
-struct ssh_sftp_mainloop_ctx {
- bool include_stdin, no_fds_ok;
- int toret;
-};
-static bool ssh_sftp_pw_setup(void *vctx, pollwrapper *pw)
-{
- struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx;
- int fdstate, rwx;
-
- if (!ctx->no_fds_ok && !toplevel_callback_pending() &&
- first_fd(&fdstate, &rwx) < 0) {
- ctx->toret = -1;
- return false; /* terminate cli_main_loop */
- }
-
- if (ctx->include_stdin)
- pollwrap_add_fd_rwx(pw, 0, SELECT_R);
-
- return true;
-}
-static void ssh_sftp_pw_check(void *vctx, pollwrapper *pw)
-{
- struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx;
-
- if (ctx->include_stdin && pollwrap_check_fd_rwx(pw, 0, SELECT_R))
- ctx->toret = 1;
-}
-static bool ssh_sftp_mainloop_continue(void *vctx, bool found_any_fd,
- bool ran_any_callback)
-{
- struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx;
- if (ctx->toret != 0 || found_any_fd || ran_any_callback)
- return false; /* finish the loop */
- return true;
-}
-static int ssh_sftp_do_select(bool include_stdin, bool no_fds_ok)
-{
- struct ssh_sftp_mainloop_ctx ctx[1];
- ctx->include_stdin = include_stdin;
- ctx->no_fds_ok = no_fds_ok;
- ctx->toret = 0;
-
- cli_main_loop(ssh_sftp_pw_setup, ssh_sftp_pw_check,
- ssh_sftp_mainloop_continue, ctx);
-
- return ctx->toret;
-}
-
-/*
- * Wait for some network data and process it.
- */
-int ssh_sftp_loop_iteration(void)
-{
- return ssh_sftp_do_select(false, false);
-}
-
-/*
- * Read a PSFTP command line from stdin.
- */
-char *ssh_sftp_get_cmdline(const char *prompt, bool no_fds_ok)
-{
- char *buf;
- size_t buflen, bufsize;
- int ret;
-
- fputs(prompt, stdout);
- fflush(stdout);
-
- buf = NULL;
- buflen = bufsize = 0;
-
- while (1) {
- ret = ssh_sftp_do_select(true, no_fds_ok);
- if (ret < 0) {
- printf("connection died\n");
- sfree(buf);
- return NULL; /* woop woop */
- }
- if (ret > 0) {
- sgrowarray(buf, bufsize, buflen);
- ret = read(0, buf+buflen, 1);
- if (ret < 0) {
- perror("read");
- sfree(buf);
- return NULL;
- }
- if (ret == 0) {
- /* eof on stdin; no error, but no answer either */
- sfree(buf);
- return NULL;
- }
-
- if (buf[buflen++] == '\n') {
- /* we have a full line */
- return buf;
- }
- }
- }
-}
-
-void frontend_net_error_pending(void) {}
-
-void platform_psftp_pre_conn_setup(LogPolicy *lp) {}
-
-const bool buildinfo_gtk_relevant = false;
-
-/*
- * Main program: do platform-specific initialisation and then call
- * psftp_main().
- */
-int main(int argc, char *argv[])
-{
- uxsel_init();
- return psftp_main(argc, argv);
-}
diff --git a/UNIX/UXSIGNAL.C b/UNIX/UXSIGNAL.C
deleted file mode 100644
index d75cce43..00000000
--- a/UNIX/UXSIGNAL.C
+++ /dev/null
@@ -1,47 +0,0 @@
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "defs.h"
-
-/*
- * Calling signal() is non-portable, as it varies in meaning
- * between platforms and depending on feature macros, and has
- * stupid semantics at least some of the time.
- *
- * This function provides the same interface as the libc function,
- * but provides consistent semantics. It assumes POSIX semantics
- * for sigaction() (so you might need to do some more work if you
- * port to something ancient like SunOS 4)
- */
-void (*putty_signal(int sig, void (*func)(int)))(int) {
- struct sigaction sa;
- struct sigaction old;
-
- sa.sa_handler = func;
- if(sigemptyset(&sa.sa_mask) < 0)
- return SIG_ERR;
- sa.sa_flags = SA_RESTART;
- if(sigaction(sig, &sa, &old) < 0)
- return SIG_ERR;
- return old.sa_handler;
-}
-
-void block_signal(int sig, bool block_it)
-{
- sigset_t ss;
-
- sigemptyset(&ss);
- sigaddset(&ss, sig);
- if(sigprocmask(block_it ? SIG_BLOCK : SIG_UNBLOCK, &ss, 0) < 0) {
- perror("sigprocmask");
- exit(1);
- }
-}
-
-/*
-Local Variables:
-c-basic-offset:4
-comment-column:40
-End:
-*/
diff --git a/UNIX/UXSTORE.C b/UNIX/UXSTORE.C
deleted file mode 100644
index 9db713d1..00000000
--- a/UNIX/UXSTORE.C
+++ /dev/null
@@ -1,822 +0,0 @@
-/*
- * uxstore.c: Unix-specific implementation of the interface defined
- * in storage.h.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <errno.h>
-#include <ctype.h>
-#include <limits.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <dirent.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <pwd.h>
-#include "putty.h"
-#include "storage.h"
-#include "tree234.h"
-
-#ifdef PATH_MAX
-#define FNLEN PATH_MAX
-#else
-#define FNLEN 1024 /* XXX */
-#endif
-
-enum {
- INDEX_DIR, INDEX_HOSTKEYS, INDEX_HOSTKEYS_TMP, INDEX_RANDSEED,
- INDEX_SESSIONDIR, INDEX_SESSION,
-};
-
-static const char hex[16] = "0123456789ABCDEF";
-
-static void make_session_filename(const char *in, strbuf *out)
-{
- if (!in || !*in)
- in = "Default Settings";
-
- while (*in) {
- /*
- * There are remarkably few punctuation characters that
- * aren't shell-special in some way or likely to be used as
- * separators in some file format or another! Hence we use
- * opt-in for safe characters rather than opt-out for
- * specific unsafe ones...
- */
- if (*in!='+' && *in!='-' && *in!='.' && *in!='@' && *in!='_' &&
- !(*in >= '0' && *in <= '9') &&
- !(*in >= 'A' && *in <= 'Z') &&
- !(*in >= 'a' && *in <= 'z')) {
- put_byte(out, '%');
- put_byte(out, hex[((unsigned char) *in) >> 4]);
- put_byte(out, hex[((unsigned char) *in) & 15]);
- } else
- put_byte(out, *in);
- in++;
- }
-}
-
-static void decode_session_filename(const char *in, strbuf *out)
-{
- while (*in) {
- if (*in == '%' && in[1] && in[2]) {
- int i, j;
-
- i = in[1] - '0';
- i -= (i > 9 ? 7 : 0);
- j = in[2] - '0';
- j -= (j > 9 ? 7 : 0);
-
- put_byte(out, (i << 4) + j);
- in += 3;
- } else {
- put_byte(out, *in++);
- }
- }
-}
-
-static char *make_filename(int index, const char *subname)
-{
- char *env, *tmp, *ret;
-
- /*
- * Allow override of the PuTTY configuration location, and of
- * specific subparts of it, by means of environment variables.
- */
- if (index == INDEX_DIR) {
- struct passwd *pwd;
- char *xdg_dir, *old_dir, *old_dir2, *old_dir3, *home, *pwd_home;
-
- env = getenv("PUTTYDIR");
- if (env)
- return dupstr(env);
-
- home = getenv("HOME");
- pwd = getpwuid(getuid());
- if (pwd && pwd->pw_dir) {
- pwd_home = pwd->pw_dir;
- } else {
- pwd_home = NULL;
- }
-
- xdg_dir = NULL;
- env = getenv("XDG_CONFIG_HOME");
- if (env && *env) {
- xdg_dir = dupprintf("%s/putty", env);
- }
- if (!xdg_dir) {
- if (home) {
- tmp = home;
- } else if (pwd_home) {
- tmp = pwd_home;
- } else {
- tmp = "";
- }
- xdg_dir = dupprintf("%s/.config/putty", tmp);
- }
- if (xdg_dir && access(xdg_dir, F_OK) == 0) {
- return xdg_dir;
- }
-
- old_dir = old_dir2 = old_dir3 = NULL;
- if (home) {
- old_dir = dupprintf("%s/.putty", home);
- }
- if (pwd_home) {
- old_dir2 = dupprintf("%s/.putty", pwd_home);
- }
- old_dir3 = dupstr("/.putty");
-
- if (old_dir && access(old_dir, F_OK) == 0) {
- ret = old_dir;
- goto out;
- }
- if (old_dir2 && access(old_dir2, F_OK) == 0) {
- ret = old_dir2;
- goto out;
- }
- if (access(old_dir3, F_OK) == 0) {
- ret = old_dir3;
- goto out;
- }
-#ifdef XDG_DEFAULT
- if (xdg_dir) {
- ret = xdg_dir;
- goto out;
- }
-#endif
- ret = old_dir ? old_dir : (old_dir2 ? old_dir2 : old_dir3);
-
- out:
- if (ret != old_dir)
- sfree(old_dir);
- if (ret != old_dir2)
- sfree(old_dir2);
- if (ret != old_dir3)
- sfree(old_dir3);
- if (ret != xdg_dir)
- sfree(xdg_dir);
- return ret;
- }
- if (index == INDEX_SESSIONDIR) {
- env = getenv("PUTTYSESSIONS");
- if (env)
- return dupstr(env);
- tmp = make_filename(INDEX_DIR, NULL);
- ret = dupprintf("%s/sessions", tmp);
- sfree(tmp);
- return ret;
- }
- if (index == INDEX_SESSION) {
- strbuf *sb = strbuf_new();
- tmp = make_filename(INDEX_SESSIONDIR, NULL);
- strbuf_catf(sb, "%s/", tmp);
- sfree(tmp);
- make_session_filename(subname, sb);
- return strbuf_to_str(sb);
- }
- if (index == INDEX_HOSTKEYS) {
- env = getenv("PUTTYSSHHOSTKEYS");
- if (env)
- return dupstr(env);
- tmp = make_filename(INDEX_DIR, NULL);
- ret = dupprintf("%s/sshhostkeys", tmp);
- sfree(tmp);
- return ret;
- }
- if (index == INDEX_HOSTKEYS_TMP) {
- tmp = make_filename(INDEX_HOSTKEYS, NULL);
- ret = dupprintf("%s.tmp", tmp);
- sfree(tmp);
- return ret;
- }
- if (index == INDEX_RANDSEED) {
- env = getenv("PUTTYRANDOMSEED");
- if (env)
- return dupstr(env);
- tmp = make_filename(INDEX_DIR, NULL);
- ret = dupprintf("%s/randomseed", tmp);
- sfree(tmp);
- return ret;
- }
- tmp = make_filename(INDEX_DIR, NULL);
- ret = dupprintf("%s/ERROR", tmp);
- sfree(tmp);
- return ret;
-}
-
-struct settings_w {
- FILE *fp;
-};
-
-settings_w *open_settings_w(const char *sessionname, char **errmsg)
-{
- char *filename, *err;
- FILE *fp;
-
- *errmsg = NULL;
-
- /*
- * Start by making sure the .putty directory and its sessions
- * subdir actually exist.
- */
- filename = make_filename(INDEX_DIR, NULL);
- if ((err = make_dir_path(filename, 0700)) != NULL) {
- *errmsg = dupprintf("Unable to save session: %s", err);
- sfree(err);
- sfree(filename);
- return NULL;
- }
- sfree(filename);
-
- filename = make_filename(INDEX_SESSIONDIR, NULL);
- if ((err = make_dir_path(filename, 0700)) != NULL) {
- *errmsg = dupprintf("Unable to save session: %s", err);
- sfree(err);
- sfree(filename);
- return NULL;
- }
- sfree(filename);
-
- filename = make_filename(INDEX_SESSION, sessionname);
- fp = fopen(filename, "w");
- if (!fp) {
- *errmsg = dupprintf("Unable to save session: open(\"%s\") "
- "returned '%s'", filename, strerror(errno));
- sfree(filename);
- return NULL; /* can't open */
- }
- sfree(filename);
-
- settings_w *toret = snew(settings_w);
- toret->fp = fp;
- return toret;
-}
-
-void write_setting_s(settings_w *handle, const char *key, const char *value)
-{
- fprintf(handle->fp, "%s=%s\n", key, value);
-}
-
-void write_setting_i(settings_w *handle, const char *key, int value)
-{
- fprintf(handle->fp, "%s=%d\n", key, value);
-}
-
-void close_settings_w(settings_w *handle)
-{
- fclose(handle->fp);
- sfree(handle);
-}
-
-/* ----------------------------------------------------------------------
- * System for treating X resources as a fallback source of defaults,
- * after data read from a saved-session disk file.
- *
- * The read_setting_* functions will call get_setting(key) as a
- * fallback if the setting isn't in the file they loaded. That in turn
- * will hand on to x_get_default, which the front end application
- * provides, and which actually reads resources from the X server (if
- * appropriate). In between, there's a tree234 of X-resource shaped
- * settings living locally in this file: the front end can call
- * provide_xrm_string() to insert a setting into this tree (typically
- * in response to an -xrm command line option or similar), and those
- * will override the actual X resources.
- */
-
-struct skeyval {
- const char *key;
- const char *value;
-};
-
-static tree234 *xrmtree = NULL;
-
-static int keycmp(void *av, void *bv)
-{
- struct skeyval *a = (struct skeyval *)av;
- struct skeyval *b = (struct skeyval *)bv;
- return strcmp(a->key, b->key);
-}
-
-void provide_xrm_string(const char *string, const char *progname)
-{
- const char *p, *q;
- char *key;
- struct skeyval *xrms, *ret;
-
- p = q = strchr(string, ':');
- if (!q) {
- fprintf(stderr, "%s: expected a colon in resource string"
- " \"%s\"\n", progname, string);
- return;
- }
- q++;
- while (p > string && p[-1] != '.' && p[-1] != '*')
- p--;
- xrms = snew(struct skeyval);
- key = snewn(q-p, char);
- memcpy(key, p, q-p);
- key[q-p-1] = '\0';
- xrms->key = key;
- while (*q && isspace((unsigned char)*q))
- q++;
- xrms->value = dupstr(q);
-
- if (!xrmtree)
- xrmtree = newtree234(keycmp);
-
- ret = add234(xrmtree, xrms);
- if (ret) {
- /* Override an existing string. */
- del234(xrmtree, ret);
- add234(xrmtree, xrms);
- }
-}
-
-static const char *get_setting(const char *key)
-{
- struct skeyval tmp, *ret;
- tmp.key = key;
- if (xrmtree) {
- ret = find234(xrmtree, &tmp, NULL);
- if (ret)
- return ret->value;
- }
- return x_get_default(key);
-}
-
-/* ----------------------------------------------------------------------
- * Main code for reading settings from a disk file, calling the above
- * get_setting() as a fallback if necessary.
- */
-
-struct settings_r {
- tree234 *t;
-};
-
-settings_r *open_settings_r(const char *sessionname)
-{
- char *filename;
- FILE *fp;
- char *line;
- settings_r *toret;
-
- filename = make_filename(INDEX_SESSION, sessionname);
- fp = fopen(filename, "r");
- sfree(filename);
- if (!fp)
- return NULL; /* can't open */
-
- toret = snew(settings_r);
- toret->t = newtree234(keycmp);
-
- while ( (line = fgetline(fp)) ) {
- char *value = strchr(line, '=');
- struct skeyval *kv;
-
- if (!value) {
- sfree(line);
- continue;
- }
- *value++ = '\0';
- value[strcspn(value, "\r\n")] = '\0'; /* trim trailing NL */
-
- kv = snew(struct skeyval);
- kv->key = dupstr(line);
- kv->value = dupstr(value);
- add234(toret->t, kv);
-
- sfree(line);
- }
-
- fclose(fp);
-
- return toret;
-}
-
-char *read_setting_s(settings_r *handle, const char *key)
-{
- const char *val;
- struct skeyval tmp, *kv;
-
- tmp.key = key;
- if (handle != NULL &&
- (kv = find234(handle->t, &tmp, NULL)) != NULL) {
- val = kv->value;
- assert(val != NULL);
- } else
- val = get_setting(key);
-
- if (!val)
- return NULL;
- else
- return dupstr(val);
-}
-
-int read_setting_i(settings_r *handle, const char *key, int defvalue)
-{
- const char *val;
- struct skeyval tmp, *kv;
-
- tmp.key = key;
- if (handle != NULL &&
- (kv = find234(handle->t, &tmp, NULL)) != NULL) {
- val = kv->value;
- assert(val != NULL);
- } else
- val = get_setting(key);
-
- if (!val)
- return defvalue;
- else
- return atoi(val);
-}
-
-FontSpec *read_setting_fontspec(settings_r *handle, const char *name)
-{
- /*
- * In GTK1-only PuTTY, we used to store font names simply as a
- * valid X font description string (logical or alias), under a
- * bare key such as "Font".
- *
- * In GTK2 PuTTY, we have a prefix system where "client:"
- * indicates a Pango font and "server:" an X one; existing
- * configuration needs to be reinterpreted as having the
- * "server:" prefix, so we change the storage key from the
- * provided name string (e.g. "Font") to a suffixed one
- * ("FontName").
- */
- char *suffname = dupcat(name, "Name");
- char *tmp;
-
- if ((tmp = read_setting_s(handle, suffname)) != NULL) {
- FontSpec *fs = fontspec_new(tmp);
- sfree(suffname);
- sfree(tmp);
- return fs; /* got new-style name */
- }
- sfree(suffname);
-
- /* Fall back to old-style name. */
- tmp = read_setting_s(handle, name);
- if (tmp && *tmp) {
- char *tmp2 = dupcat("server:", tmp);
- FontSpec *fs = fontspec_new(tmp2);
- sfree(tmp2);
- sfree(tmp);
- return fs;
- } else {
- sfree(tmp);
- return NULL;
- }
-}
-Filename *read_setting_filename(settings_r *handle, const char *name)
-{
- char *tmp = read_setting_s(handle, name);
- if (tmp) {
- Filename *ret = filename_from_str(tmp);
- sfree(tmp);
- return ret;
- } else
- return NULL;
-}
-
-void write_setting_fontspec(settings_w *handle, const char *name, FontSpec *fs)
-{
- /*
- * read_setting_fontspec had to handle two cases, but when
- * writing our settings back out we simply always generate the
- * new-style name.
- */
- char *suffname = dupcat(name, "Name");
- write_setting_s(handle, suffname, fs->name);
- sfree(suffname);
-}
-void write_setting_filename(settings_w *handle,
- const char *name, Filename *result)
-{
- write_setting_s(handle, name, result->path);
-}
-
-void close_settings_r(settings_r *handle)
-{
- struct skeyval *kv;
-
- if (!handle)
- return;
-
- while ( (kv = index234(handle->t, 0)) != NULL) {
- del234(handle->t, kv);
- sfree((char *)kv->key);
- sfree((char *)kv->value);
- sfree(kv);
- }
-
- freetree234(handle->t);
- sfree(handle);
-}
-
-void del_settings(const char *sessionname)
-{
- char *filename;
- filename = make_filename(INDEX_SESSION, sessionname);
- unlink(filename);
- sfree(filename);
-}
-
-struct settings_e {
- DIR *dp;
-};
-
-settings_e *enum_settings_start(void)
-{
- DIR *dp;
- char *filename;
-
- filename = make_filename(INDEX_SESSIONDIR, NULL);
- dp = opendir(filename);
- sfree(filename);
-
- settings_e *toret = snew(settings_e);
- toret->dp = dp;
- return toret;
-}
-
-bool enum_settings_next(settings_e *handle, strbuf *out)
-{
- struct dirent *de;
- struct stat st;
- strbuf *fullpath;
-
- if (!handle->dp)
- return NULL;
-
- fullpath = strbuf_new();
-
- char *sessiondir = make_filename(INDEX_SESSIONDIR, NULL);
- put_datapl(fullpath, ptrlen_from_asciz(sessiondir));
- sfree(sessiondir);
- put_byte(fullpath, '/');
-
- size_t baselen = fullpath->len;
-
- while ( (de = readdir(handle->dp)) != NULL ) {
- strbuf_shrink_to(fullpath, baselen);
- put_datapl(fullpath, ptrlen_from_asciz(de->d_name));
-
- if (stat(fullpath->s, &st) < 0 || !S_ISREG(st.st_mode))
- continue; /* try another one */
-
- decode_session_filename(de->d_name, out);
- strbuf_free(fullpath);
- return true;
- }
-
- strbuf_free(fullpath);
- return false;
-}
-
-void enum_settings_finish(settings_e *handle)
-{
- if (handle->dp)
- closedir(handle->dp);
- sfree(handle);
-}
-
-/*
- * Lines in the host keys file are of the form
- *
- * type@port:hostname keydata
- *
- * e.g.
- *
- * rsa@22:foovax.example.org 0x23,0x293487364395345345....2343
- */
-int verify_host_key(const char *hostname, int port,
- const char *keytype, const char *key)
-{
- FILE *fp;
- char *filename;
- char *line;
- int ret;
-
- filename = make_filename(INDEX_HOSTKEYS, NULL);
- fp = fopen(filename, "r");
- sfree(filename);
- if (!fp)
- return 1; /* key does not exist */
-
- ret = 1;
- while ( (line = fgetline(fp)) ) {
- int i;
- char *p = line;
- char porttext[20];
-
- line[strcspn(line, "\n")] = '\0'; /* strip trailing newline */
-
- i = strlen(keytype);
- if (strncmp(p, keytype, i))
- goto done;
- p += i;
-
- if (*p != '@')
- goto done;
- p++;
-
- sprintf(porttext, "%d", port);
- i = strlen(porttext);
- if (strncmp(p, porttext, i))
- goto done;
- p += i;
-
- if (*p != ':')
- goto done;
- p++;
-
- i = strlen(hostname);
- if (strncmp(p, hostname, i))
- goto done;
- p += i;
-
- if (*p != ' ')
- goto done;
- p++;
-
- /*
- * Found the key. Now just work out whether it's the right
- * one or not.
- */
- if (!strcmp(p, key))
- ret = 0; /* key matched OK */
- else
- ret = 2; /* key mismatch */
-
- done:
- sfree(line);
- if (ret != 1)
- break;
- }
-
- fclose(fp);
- return ret;
-}
-
-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)
-{
- FILE *rfp, *wfp;
- char *newtext, *line;
- int headerlen;
- char *filename, *tmpfilename;
-
- /*
- * Open both the old file and a new file.
- */
- tmpfilename = make_filename(INDEX_HOSTKEYS_TMP, NULL);
- wfp = fopen(tmpfilename, "w");
- if (!wfp && errno == ENOENT) {
- char *dir, *errmsg;
-
- dir = make_filename(INDEX_DIR, NULL);
- if ((errmsg = make_dir_path(dir, 0700)) != NULL) {
- nonfatal("Unable to store host key: %s", errmsg);
- sfree(errmsg);
- sfree(dir);
- sfree(tmpfilename);
- return;
- }
- sfree(dir);
-
- wfp = fopen(tmpfilename, "w");
- }
- if (!wfp) {
- nonfatal("Unable to store host key: open(\"%s\") "
- "returned '%s'", tmpfilename, strerror(errno));
- sfree(tmpfilename);
- return;
- }
- filename = make_filename(INDEX_HOSTKEYS, NULL);
- rfp = fopen(filename, "r");
-
- newtext = dupprintf("%s@%d:%s %s\n", keytype, port, hostname, key);
- headerlen = 1 + strcspn(newtext, " "); /* count the space too */
-
- /*
- * Copy all lines from the old file to the new one that _don't_
- * involve the same host key identifier as the one we're adding.
- */
- if (rfp) {
- while ( (line = fgetline(rfp)) ) {
- if (strncmp(line, newtext, headerlen))
- fputs(line, wfp);
- sfree(line);
- }
- fclose(rfp);
- }
-
- /*
- * Now add the new line at the end.
- */
- fputs(newtext, wfp);
-
- fclose(wfp);
-
- if (rename(tmpfilename, filename) < 0) {
- nonfatal("Unable to store host key: rename(\"%s\",\"%s\")"
- " returned '%s'", tmpfilename, filename,
- strerror(errno));
- }
-
- sfree(tmpfilename);
- sfree(filename);
- sfree(newtext);
-}
-
-void read_random_seed(noise_consumer_t consumer)
-{
- int fd;
- char *fname;
-
- fname = make_filename(INDEX_RANDSEED, NULL);
- fd = open(fname, O_RDONLY);
- sfree(fname);
- if (fd >= 0) {
- char buf[512];
- int ret;
- while ( (ret = read(fd, buf, sizeof(buf))) > 0)
- consumer(buf, ret);
- close(fd);
- }
-}
-
-void write_random_seed(void *data, int len)
-{
- int fd;
- char *fname;
-
- fname = make_filename(INDEX_RANDSEED, NULL);
- /*
- * Don't truncate the random seed file if it already exists; if
- * something goes wrong half way through writing it, it would
- * be better to leave the old data there than to leave it empty.
- */
- fd = open(fname, O_CREAT | O_WRONLY, 0600);
- if (fd < 0) {
- if (errno != ENOENT) {
- nonfatal("Unable to write random seed: open(\"%s\") "
- "returned '%s'", fname, strerror(errno));
- sfree(fname);
- return;
- }
- char *dir, *errmsg;
-
- dir = make_filename(INDEX_DIR, NULL);
- if ((errmsg = make_dir_path(dir, 0700)) != NULL) {
- nonfatal("Unable to write random seed: %s", errmsg);
- sfree(errmsg);
- sfree(fname);
- sfree(dir);
- return;
- }
- sfree(dir);
-
- fd = open(fname, O_CREAT | O_WRONLY, 0600);
- if (fd < 0) {
- nonfatal("Unable to write random seed: open(\"%s\") "
- "returned '%s'", fname, strerror(errno));
- sfree(fname);
- return;
- }
- }
-
- while (len > 0) {
- int ret = write(fd, data, len);
- if (ret < 0) {
- nonfatal("Unable to write random seed: write "
- "returned '%s'", strerror(errno));
- break;
- }
- len -= ret;
- data = (char *)data + len;
- }
-
- close(fd);
- sfree(fname);
-}
-
-void cleanup_all(void)
-{
-}
diff --git a/UNIX/UXUCS.C b/UNIX/UXUCS.C
deleted file mode 100644
index c1d76a42..00000000
--- a/UNIX/UXUCS.C
+++ /dev/null
@@ -1,268 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <locale.h>
-#include <limits.h>
-#include <wchar.h>
-
-#include <time.h>
-
-#include "putty.h"
-#include "charset.h"
-#include "terminal.h"
-#include "misc.h"
-
-/*
- * Unix Unicode-handling routines.
- */
-
-bool is_dbcs_leadbyte(int codepage, char byte)
-{
- return false; /* we don't do DBCS */
-}
-
-int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen,
- wchar_t *wcstr, int wclen)
-{
- if (codepage == DEFAULT_CODEPAGE) {
- int n = 0;
- mbstate_t state;
-
- memset(&state, 0, sizeof state);
-
- while (mblen > 0) {
- size_t i = mbrtowc(wcstr+n, mbstr, (size_t)mblen, &state);
- if (i == (size_t)-1 || i == (size_t)-2)
- break;
- n++;
- mbstr += i;
- mblen -= i;
- }
-
- return n;
- } else if (codepage == CS_NONE) {
- int n = 0;
-
- while (mblen > 0) {
- wcstr[n] = 0xD800 | (mbstr[0] & 0xFF);
- n++;
- mbstr++;
- mblen--;
- }
-
- return n;
- } else
- return charset_to_unicode(&mbstr, &mblen, wcstr, wclen, codepage,
- NULL, NULL, 0);
-}
-
-int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen,
- char *mbstr, int mblen, const char *defchr,
- struct unicode_data *ucsdata)
-{
- if (codepage == DEFAULT_CODEPAGE) {
- char output[MB_LEN_MAX];
- mbstate_t state;
- int n = 0;
-
- memset(&state, 0, sizeof state);
-
- while (wclen > 0) {
- size_t i = wcrtomb(output, wcstr[0], &state);
- if (i == (size_t)-1 || i > n - mblen)
- break;
- memcpy(mbstr+n, output, i);
- n += i;
- wcstr++;
- wclen--;
- }
-
- return n;
- } else if (codepage == CS_NONE) {
- int n = 0;
- while (wclen > 0 && n < mblen) {
- if (*wcstr >= 0xD800 && *wcstr < 0xD900)
- mbstr[n++] = (*wcstr & 0xFF);
- else if (defchr)
- mbstr[n++] = *defchr;
- wcstr++;
- wclen--;
- }
- return n;
- } else {
- return charset_from_unicode(&wcstr, &wclen, mbstr, mblen, codepage,
- NULL, defchr?defchr:NULL, defchr?1:0);
- }
-}
-
-/*
- * Return value is true if pterm is to run in direct-to-font mode.
- */
-bool init_ucs(struct unicode_data *ucsdata, char *linecharset,
- bool utf8_override, int font_charset, int vtmode)
-{
- int i;
- bool ret = false;
-
- /*
- * In the platform-independent parts of the code, font_codepage
- * is used only for system DBCS support - which we don't
- * support at all. So we set this to something which will never
- * be used.
- */
- ucsdata->font_codepage = -1;
-
- /*
- * If utf8_override is set and the POSIX locale settings
- * dictate a UTF-8 character set, then just go straight for
- * UTF-8.
- */
- ucsdata->line_codepage = CS_NONE;
- if (utf8_override) {
- const char *s;
- if (((s = getenv("LC_ALL")) && *s) ||
- ((s = getenv("LC_CTYPE")) && *s) ||
- ((s = getenv("LANG")) && *s)) {
- if (strstr(s, "UTF-8"))
- ucsdata->line_codepage = CS_UTF8;
- }
- }
-
- /*
- * Failing that, line_codepage should be decoded from the
- * specification in conf.
- */
- if (ucsdata->line_codepage == CS_NONE)
- ucsdata->line_codepage = decode_codepage(linecharset);
-
- /*
- * If line_codepage is _still_ CS_NONE, we assume we're using
- * the font's own encoding. This has been passed in to us, so
- * we use that. If it's still CS_NONE after _that_ - i.e. the
- * font we were given had an incomprehensible charset - then we
- * fall back to using the D800 page.
- */
- if (ucsdata->line_codepage == CS_NONE)
- ucsdata->line_codepage = font_charset;
-
- if (ucsdata->line_codepage == CS_NONE)
- ret = true;
-
- /*
- * Set up unitab_line, by translating each individual character
- * in the line codepage into Unicode.
- */
- for (i = 0; i < 256; i++) {
- char c[1];
- const char *p;
- wchar_t wc[1];
- int len;
- c[0] = i;
- p = c;
- len = 1;
- if (ucsdata->line_codepage == CS_NONE)
- ucsdata->unitab_line[i] = 0xD800 | i;
- else if (1 == charset_to_unicode(&p, &len, wc, 1,
- ucsdata->line_codepage,
- NULL, L"", 0))
- ucsdata->unitab_line[i] = wc[0];
- else
- ucsdata->unitab_line[i] = 0xFFFD;
- }
-
- /*
- * Set up unitab_xterm. This is the same as unitab_line except
- * in the line-drawing regions, where it follows the Unicode
- * encoding.
- *
- * (Note that the strange X encoding of line-drawing characters
- * in the bottom 32 glyphs of ISO8859-1 fonts is taken care of
- * by the font encoding, which will spot such a font and act as
- * if it were in a variant encoding of ISO8859-1.)
- */
- for (i = 0; i < 256; i++) {
- static const wchar_t unitab_xterm_std[32] = {
- 0x2666, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1,
- 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba,
- 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c,
- 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x0020
- };
- static const wchar_t unitab_xterm_poorman[32] =
- L"*#****o~**+++++-----++++|****L. ";
-
- const wchar_t *ptr;
-
- if (vtmode == VT_POORMAN)
- ptr = unitab_xterm_poorman;
- else
- ptr = unitab_xterm_std;
-
- if (i >= 0x5F && i < 0x7F)
- ucsdata->unitab_xterm[i] = ptr[i & 0x1F];
- else
- ucsdata->unitab_xterm[i] = ucsdata->unitab_line[i];
- }
-
- /*
- * Set up unitab_scoacs. The SCO Alternate Character Set is
- * simply CP437.
- */
- for (i = 0; i < 256; i++) {
- char c[1];
- const char *p;
- wchar_t wc[1];
- int len;
- c[0] = i;
- p = c;
- len = 1;
- if (1 == charset_to_unicode(&p, &len, wc, 1, CS_CP437, NULL, L"", 0))
- ucsdata->unitab_scoacs[i] = wc[0];
- else
- ucsdata->unitab_scoacs[i] = 0xFFFD;
- }
-
- /*
- * Find the control characters in the line codepage. For
- * direct-to-font mode using the D800 hack, we assume 00-1F and
- * 7F are controls, but allow 80-9F through. (It's as good a
- * guess as anything; and my bet is that half the weird fonts
- * used in this way will be IBM or MS code pages anyway.)
- */
- for (i = 0; i < 256; i++) {
- int lineval = ucsdata->unitab_line[i];
- if (lineval < ' ' || (lineval >= 0x7F && lineval < 0xA0) ||
- (lineval >= 0xD800 && lineval < 0xD820) || (lineval == 0xD87F))
- ucsdata->unitab_ctrl[i] = i;
- else
- ucsdata->unitab_ctrl[i] = 0xFF;
- }
-
- return ret;
-}
-
-const char *cp_name(int codepage)
-{
- if (codepage == CS_NONE)
- return "Use font encoding";
- return charset_to_localenc(codepage);
-}
-
-const char *cp_enumerate(int index)
-{
- int charset;
- charset = charset_localenc_nth(index);
- if (charset == CS_NONE) {
- /* "Use font encoding" comes after all the named charsets */
- if (charset_localenc_nth(index-1) != CS_NONE)
- return "Use font encoding";
- return NULL;
- }
- return charset_to_localenc(charset);
-}
-
-int decode_codepage(char *cp_name)
-{
- if (!cp_name || !*cp_name)
- return CS_UTF8;
- return charset_from_localenc(cp_name);
-}
diff --git a/UNIX/UX_X11.C b/UNIX/UX_X11.C
deleted file mode 100644
index 7a0c2218..00000000
--- a/UNIX/UX_X11.C
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * ux_x11.c: fetch local auth data for X forwarding.
- */
-
-#include <ctype.h>
-#include <unistd.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "network.h"
-
-void platform_get_x11_auth(struct X11Display *disp, Conf *conf)
-{
- char *xauthfile;
- bool needs_free;
-
- /*
- * Find the .Xauthority file.
- */
- needs_free = false;
- xauthfile = getenv("XAUTHORITY");
- if (!xauthfile) {
- xauthfile = getenv("HOME");
- if (xauthfile) {
- xauthfile = dupcat(xauthfile, "/.Xauthority");
- needs_free = true;
- }
- }
-
- if (xauthfile) {
- x11_get_auth_from_authfile(disp, xauthfile);
- if (needs_free)
- sfree(xauthfile);
- }
-}
-
-const bool platform_uses_x11_unix_by_default = true;
-
-int platform_make_x11_server(Plug *plug, const char *progname, int mindisp,
- const char *screen_number_suffix,
- ptrlen authproto, ptrlen authdata,
- Socket **sockets, Conf *conf)
-{
- char *tmpdir;
- char *authfilename = NULL;
- strbuf *authfiledata = NULL;
- char *unix_path = NULL;
-
- SockAddr *a_tcp = NULL, *a_unix = NULL;
-
- int authfd;
- FILE *authfp;
-
- int displayno;
-
- authfiledata = strbuf_new_nm();
-
- int nsockets = 0;
-
- /*
- * Look for a free TCP port to run our server on.
- */
- for (displayno = mindisp;; displayno++) {
- const char *err;
- int tcp_port = displayno + 6000;
- int addrtype = ADDRTYPE_IPV4;
-
- sockets[nsockets] = new_listener(
- NULL, tcp_port, plug, false, conf, addrtype);
-
- err = sk_socket_error(sockets[nsockets]);
- if (!err) {
- char *hostname = get_hostname();
- if (hostname) {
- char *canonicalname = NULL;
- a_tcp = sk_namelookup(hostname, &canonicalname, addrtype);
- sfree(canonicalname);
- }
- sfree(hostname);
- nsockets++;
- break; /* success! */
- } else {
- sk_close(sockets[nsockets]);
- }
-
- if (!strcmp(err, strerror(EADDRINUSE))) /* yuck! */
- goto out;
- }
-
- if (a_tcp) {
- x11_format_auth_for_authfile(
- BinarySink_UPCAST(authfiledata),
- a_tcp, displayno, authproto, authdata);
- }
-
- /*
- * Try to establish the Unix-domain analogue. That may or may not
- * work - file permissions in /tmp may prevent it, for example -
- * but it's worth a try, and we don't consider it a fatal error if
- * it doesn't work.
- */
- unix_path = dupprintf("/tmp/.X11-unix/X%d", displayno);
- a_unix = unix_sock_addr(unix_path);
-
- sockets[nsockets] = new_unix_listener(a_unix, plug);
- if (!sk_socket_error(sockets[nsockets])) {
- x11_format_auth_for_authfile(
- BinarySink_UPCAST(authfiledata),
- a_unix, displayno, authproto, authdata);
- nsockets++;
- } else {
- sk_close(sockets[nsockets]);
- sfree(unix_path);
- unix_path = NULL;
- }
-
- /*
- * Decide where the authority data will be written.
- */
-
- tmpdir = getenv("TMPDIR");
- if (!tmpdir || !*tmpdir)
- tmpdir = "/tmp";
-
- authfilename = dupcat(tmpdir, "/", progname, "-Xauthority-XXXXXX");
-
- {
- int oldumask = umask(077);
- authfd = mkstemp(authfilename);
- umask(oldumask);
- }
- if (authfd < 0) {
- while (nsockets-- > 0)
- sk_close(sockets[nsockets]);
- goto out;
- }
-
- /*
- * Spawn a subprocess which will try to reliably delete our
- * auth file when we terminate, in case we die unexpectedly.
- */
- {
- int cleanup_pipe[2];
- pid_t pid;
-
- /* Don't worry if pipe or fork fails; it's not _that_ critical. */
- if (!pipe(cleanup_pipe)) {
- if ((pid = fork()) == 0) {
- int buf[1024];
- /*
- * Our parent process holds the writing end of
- * this pipe, and writes nothing to it. Hence,
- * we expect read() to return EOF as soon as
- * that process terminates.
- */
-
- close(0);
- close(1);
- close(2);
-
- setpgid(0, 0);
- close(cleanup_pipe[1]);
- close(authfd);
- while (read(cleanup_pipe[0], buf, sizeof(buf)) > 0);
- unlink(authfilename);
- if (unix_path)
- unlink(unix_path);
- _exit(0);
- } else if (pid < 0) {
- close(cleanup_pipe[0]);
- close(cleanup_pipe[1]);
- } else {
- close(cleanup_pipe[0]);
- cloexec(cleanup_pipe[1]);
- }
- }
- }
-
- authfp = fdopen(authfd, "wb");
- fwrite(authfiledata->u, 1, authfiledata->len, authfp);
- fclose(authfp);
-
- {
- char *display = dupprintf(":%d%s", displayno, screen_number_suffix);
- conf_set_str_str(conf, CONF_environmt, "DISPLAY", display);
- sfree(display);
- }
- conf_set_str_str(conf, CONF_environmt, "XAUTHORITY", authfilename);
-
- /*
- * FIXME: return at least the DISPLAY and XAUTHORITY env settings,
- * and perhaps also the display number
- */
-
- out:
- if (a_tcp)
- sk_addr_free(a_tcp);
- /* a_unix doesn't need freeing, because new_unix_listener took it over */
- sfree(authfilename);
- strbuf_free(authfiledata);
- sfree(unix_path);
- return nsockets;
-}
diff --git a/UNIX/XKEYSYM.C b/UNIX/XKEYSYM.C
deleted file mode 100644
index aa9f9539..00000000
--- a/UNIX/XKEYSYM.C
+++ /dev/null
@@ -1,1011 +0,0 @@
-/*
- * xkeysym.c: mapping from X keysyms to Unicode values
- *
- * The basic idea of this is shamelessly cribbed from xterm. The
- * actual character data is generated from Markus Kuhn's proposed
- * redraft of the X11 keysym mapping table, using the following
- * piece of Perl/sh code:
-
-wget -q -O - http://www.cl.cam.ac.uk/~mgk25/ucs/X11.keysyms | \
-perl -ne '/^(\d+)\s+(\d+)\s+[\d\/]+\s+U\+([\dA-Fa-f]+)/ and' \
- -e ' do { $a{$1 * 256+ $2} = hex $3; };' \
- -e 'END { foreach $i (sort {$a <=> $b} keys %a) {' \
- -e ' printf " {0x%x, 0x%x},\n", $i, $a{$i} } }' \
- -e 'BEGIN { $a{0x13a4} = 0x20ac }'
-
- * (The BEGIN clause inserts a mapping for the Euro sign which for
- * some reason isn't in the list but xterm supports. *shrug*.)
- */
-
-#include "misc.h"
-
-struct keysym {
- /*
- * Currently nothing in here is above 0xFFFF, so I'll use
- * `unsigned short' to save space.
- */
- unsigned short keysym;
- unsigned short unicode;
-};
-
-static struct keysym keysyms[] = {
- {0x20, 0x20},
- {0x21, 0x21},
- {0x22, 0x22},
- {0x23, 0x23},
- {0x24, 0x24},
- {0x25, 0x25},
- {0x26, 0x26},
- {0x27, 0x27},
- {0x28, 0x28},
- {0x29, 0x29},
- {0x2a, 0x2a},
- {0x2b, 0x2b},
- {0x2c, 0x2c},
- {0x2d, 0x2d},
- {0x2e, 0x2e},
- {0x2f, 0x2f},
- {0x30, 0x30},
- {0x31, 0x31},
- {0x32, 0x32},
- {0x33, 0x33},
- {0x34, 0x34},
- {0x35, 0x35},
- {0x36, 0x36},
- {0x37, 0x37},
- {0x38, 0x38},
- {0x39, 0x39},
- {0x3a, 0x3a},
- {0x3b, 0x3b},
- {0x3c, 0x3c},
- {0x3d, 0x3d},
- {0x3e, 0x3e},
- {0x3f, 0x3f},
- {0x40, 0x40},
- {0x41, 0x41},
- {0x42, 0x42},
- {0x43, 0x43},
- {0x44, 0x44},
- {0x45, 0x45},
- {0x46, 0x46},
- {0x47, 0x47},
- {0x48, 0x48},
- {0x49, 0x49},
- {0x4a, 0x4a},
- {0x4b, 0x4b},
- {0x4c, 0x4c},
- {0x4d, 0x4d},
- {0x4e, 0x4e},
- {0x4f, 0x4f},
- {0x50, 0x50},
- {0x51, 0x51},
- {0x52, 0x52},
- {0x53, 0x53},
- {0x54, 0x54},
- {0x55, 0x55},
- {0x56, 0x56},
- {0x57, 0x57},
- {0x58, 0x58},
- {0x59, 0x59},
- {0x5a, 0x5a},
- {0x5b, 0x5b},
- {0x5c, 0x5c},
- {0x5d, 0x5d},
- {0x5e, 0x5e},
- {0x5f, 0x5f},
- {0x60, 0x60},
- {0x61, 0x61},
- {0x62, 0x62},
- {0x63, 0x63},
- {0x64, 0x64},
- {0x65, 0x65},
- {0x66, 0x66},
- {0x67, 0x67},
- {0x68, 0x68},
- {0x69, 0x69},
- {0x6a, 0x6a},
- {0x6b, 0x6b},
- {0x6c, 0x6c},
- {0x6d, 0x6d},
- {0x6e, 0x6e},
- {0x6f, 0x6f},
- {0x70, 0x70},
- {0x71, 0x71},
- {0x72, 0x72},
- {0x73, 0x73},
- {0x74, 0x74},
- {0x75, 0x75},
- {0x76, 0x76},
- {0x77, 0x77},
- {0x78, 0x78},
- {0x79, 0x79},
- {0x7a, 0x7a},
- {0x7b, 0x7b},
- {0x7c, 0x7c},
- {0x7d, 0x7d},
- {0x7e, 0x7e},
- {0xa0, 0xa0},
- {0xa1, 0xa1},
- {0xa2, 0xa2},
- {0xa3, 0xa3},
- {0xa4, 0xa4},
- {0xa5, 0xa5},
- {0xa6, 0xa6},
- {0xa7, 0xa7},
- {0xa8, 0xa8},
- {0xa9, 0xa9},
- {0xaa, 0xaa},
- {0xab, 0xab},
- {0xac, 0xac},
- {0xad, 0xad},
- {0xae, 0xae},
- {0xaf, 0xaf},
- {0xb0, 0xb0},
- {0xb1, 0xb1},
- {0xb2, 0xb2},
- {0xb3, 0xb3},
- {0xb4, 0xb4},
- {0xb5, 0xb5},
- {0xb6, 0xb6},
- {0xb7, 0xb7},
- {0xb8, 0xb8},
- {0xb9, 0xb9},
- {0xba, 0xba},
- {0xbb, 0xbb},
- {0xbc, 0xbc},
- {0xbd, 0xbd},
- {0xbe, 0xbe},
- {0xbf, 0xbf},
- {0xc0, 0xc0},
- {0xc1, 0xc1},
- {0xc2, 0xc2},
- {0xc3, 0xc3},
- {0xc4, 0xc4},
- {0xc5, 0xc5},
- {0xc6, 0xc6},
- {0xc7, 0xc7},
- {0xc8, 0xc8},
- {0xc9, 0xc9},
- {0xca, 0xca},
- {0xcb, 0xcb},
- {0xcc, 0xcc},
- {0xcd, 0xcd},
- {0xce, 0xce},
- {0xcf, 0xcf},
- {0xd0, 0xd0},
- {0xd1, 0xd1},
- {0xd2, 0xd2},
- {0xd3, 0xd3},
- {0xd4, 0xd4},
- {0xd5, 0xd5},
- {0xd6, 0xd6},
- {0xd7, 0xd7},
- {0xd8, 0xd8},
- {0xd9, 0xd9},
- {0xda, 0xda},
- {0xdb, 0xdb},
- {0xdc, 0xdc},
- {0xdd, 0xdd},
- {0xde, 0xde},
- {0xdf, 0xdf},
- {0xe0, 0xe0},
- {0xe1, 0xe1},
- {0xe2, 0xe2},
- {0xe3, 0xe3},
- {0xe4, 0xe4},
- {0xe5, 0xe5},
- {0xe6, 0xe6},
- {0xe7, 0xe7},
- {0xe8, 0xe8},
- {0xe9, 0xe9},
- {0xea, 0xea},
- {0xeb, 0xeb},
- {0xec, 0xec},
- {0xed, 0xed},
- {0xee, 0xee},
- {0xef, 0xef},
- {0xf0, 0xf0},
- {0xf1, 0xf1},
- {0xf2, 0xf2},
- {0xf3, 0xf3},
- {0xf4, 0xf4},
- {0xf5, 0xf5},
- {0xf6, 0xf6},
- {0xf7, 0xf7},
- {0xf8, 0xf8},
- {0xf9, 0xf9},
- {0xfa, 0xfa},
- {0xfb, 0xfb},
- {0xfc, 0xfc},
- {0xfd, 0xfd},
- {0xfe, 0xfe},
- {0xff, 0xff},
- {0x1a1, 0x104},
- {0x1a2, 0x2d8},
- {0x1a3, 0x141},
- {0x1a5, 0x13d},
- {0x1a6, 0x15a},
- {0x1a9, 0x160},
- {0x1aa, 0x15e},
- {0x1ab, 0x164},
- {0x1ac, 0x179},
- {0x1ae, 0x17d},
- {0x1af, 0x17b},
- {0x1b1, 0x105},
- {0x1b2, 0x2db},
- {0x1b3, 0x142},
- {0x1b5, 0x13e},
- {0x1b6, 0x15b},
- {0x1b7, 0x2c7},
- {0x1b9, 0x161},
- {0x1ba, 0x15f},
- {0x1bb, 0x165},
- {0x1bc, 0x17a},
- {0x1bd, 0x2dd},
- {0x1be, 0x17e},
- {0x1bf, 0x17c},
- {0x1c0, 0x154},
- {0x1c3, 0x102},
- {0x1c5, 0x139},
- {0x1c6, 0x106},
- {0x1c8, 0x10c},
- {0x1ca, 0x118},
- {0x1cc, 0x11a},
- {0x1cf, 0x10e},
- {0x1d0, 0x110},
- {0x1d1, 0x143},
- {0x1d2, 0x147},
- {0x1d5, 0x150},
- {0x1d8, 0x158},
- {0x1d9, 0x16e},
- {0x1db, 0x170},
- {0x1de, 0x162},
- {0x1e0, 0x155},
- {0x1e3, 0x103},
- {0x1e5, 0x13a},
- {0x1e6, 0x107},
- {0x1e8, 0x10d},
- {0x1ea, 0x119},
- {0x1ec, 0x11b},
- {0x1ef, 0x10f},
- {0x1f0, 0x111},
- {0x1f1, 0x144},
- {0x1f2, 0x148},
- {0x1f5, 0x151},
- {0x1f8, 0x159},
- {0x1f9, 0x16f},
- {0x1fb, 0x171},
- {0x1fe, 0x163},
- {0x1ff, 0x2d9},
- {0x2a1, 0x126},
- {0x2a6, 0x124},
- {0x2a9, 0x130},
- {0x2ab, 0x11e},
- {0x2ac, 0x134},
- {0x2b1, 0x127},
- {0x2b6, 0x125},
- {0x2b9, 0x131},
- {0x2bb, 0x11f},
- {0x2bc, 0x135},
- {0x2c5, 0x10a},
- {0x2c6, 0x108},
- {0x2d5, 0x120},
- {0x2d8, 0x11c},
- {0x2dd, 0x16c},
- {0x2de, 0x15c},
- {0x2e5, 0x10b},
- {0x2e6, 0x109},
- {0x2f5, 0x121},
- {0x2f8, 0x11d},
- {0x2fd, 0x16d},
- {0x2fe, 0x15d},
- {0x3a2, 0x138},
- {0x3a3, 0x156},
- {0x3a5, 0x128},
- {0x3a6, 0x13b},
- {0x3aa, 0x112},
- {0x3ab, 0x122},
- {0x3ac, 0x166},
- {0x3b3, 0x157},
- {0x3b5, 0x129},
- {0x3b6, 0x13c},
- {0x3ba, 0x113},
- {0x3bb, 0x123},
- {0x3bc, 0x167},
- {0x3bd, 0x14a},
- {0x3bf, 0x14b},
- {0x3c0, 0x100},
- {0x3c7, 0x12e},
- {0x3cc, 0x116},
- {0x3cf, 0x12a},
- {0x3d1, 0x145},
- {0x3d2, 0x14c},
- {0x3d3, 0x136},
- {0x3d9, 0x172},
- {0x3dd, 0x168},
- {0x3de, 0x16a},
- {0x3e0, 0x101},
- {0x3e7, 0x12f},
- {0x3ec, 0x117},
- {0x3ef, 0x12b},
- {0x3f1, 0x146},
- {0x3f2, 0x14d},
- {0x3f3, 0x137},
- {0x3f9, 0x173},
- {0x3fd, 0x169},
- {0x3fe, 0x16b},
- {0x47e, 0x203e},
- {0x4a1, 0x3002},
- {0x4a2, 0x300c},
- {0x4a3, 0x300d},
- {0x4a4, 0x3001},
- {0x4a5, 0x30fb},
- {0x4a6, 0x30f2},
- {0x4a7, 0x30a1},
- {0x4a8, 0x30a3},
- {0x4a9, 0x30a5},
- {0x4aa, 0x30a7},
- {0x4ab, 0x30a9},
- {0x4ac, 0x30e3},
- {0x4ad, 0x30e5},
- {0x4ae, 0x30e7},
- {0x4af, 0x30c3},
- {0x4b0, 0x30fc},
- {0x4b1, 0x30a2},
- {0x4b2, 0x30a4},
- {0x4b3, 0x30a6},
- {0x4b4, 0x30a8},
- {0x4b5, 0x30aa},
- {0x4b6, 0x30ab},
- {0x4b7, 0x30ad},
- {0x4b8, 0x30af},
- {0x4b9, 0x30b1},
- {0x4ba, 0x30b3},
- {0x4bb, 0x30b5},
- {0x4bc, 0x30b7},
- {0x4bd, 0x30b9},
- {0x4be, 0x30bb},
- {0x4bf, 0x30bd},
- {0x4c0, 0x30bf},
- {0x4c1, 0x30c1},
- {0x4c2, 0x30c4},
- {0x4c3, 0x30c6},
- {0x4c4, 0x30c8},
- {0x4c5, 0x30ca},
- {0x4c6, 0x30cb},
- {0x4c7, 0x30cc},
- {0x4c8, 0x30cd},
- {0x4c9, 0x30ce},
- {0x4ca, 0x30cf},
- {0x4cb, 0x30d2},
- {0x4cc, 0x30d5},
- {0x4cd, 0x30d8},
- {0x4ce, 0x30db},
- {0x4cf, 0x30de},
- {0x4d0, 0x30df},
- {0x4d1, 0x30e0},
- {0x4d2, 0x30e1},
- {0x4d3, 0x30e2},
- {0x4d4, 0x30e4},
- {0x4d5, 0x30e6},
- {0x4d6, 0x30e8},
- {0x4d7, 0x30e9},
- {0x4d8, 0x30ea},
- {0x4d9, 0x30eb},
- {0x4da, 0x30ec},
- {0x4db, 0x30ed},
- {0x4dc, 0x30ef},
- {0x4dd, 0x30f3},
- {0x4de, 0x309b},
- {0x4df, 0x309c},
- {0x5ac, 0x60c},
- {0x5bb, 0x61b},
- {0x5bf, 0x61f},
- {0x5c1, 0x621},
- {0x5c2, 0x622},
- {0x5c3, 0x623},
- {0x5c4, 0x624},
- {0x5c5, 0x625},
- {0x5c6, 0x626},
- {0x5c7, 0x627},
- {0x5c8, 0x628},
- {0x5c9, 0x629},
- {0x5ca, 0x62a},
- {0x5cb, 0x62b},
- {0x5cc, 0x62c},
- {0x5cd, 0x62d},
- {0x5ce, 0x62e},
- {0x5cf, 0x62f},
- {0x5d0, 0x630},
- {0x5d1, 0x631},
- {0x5d2, 0x632},
- {0x5d3, 0x633},
- {0x5d4, 0x634},
- {0x5d5, 0x635},
- {0x5d6, 0x636},
- {0x5d7, 0x637},
- {0x5d8, 0x638},
- {0x5d9, 0x639},
- {0x5da, 0x63a},
- {0x5e0, 0x640},
- {0x5e1, 0x641},
- {0x5e2, 0x642},
- {0x5e3, 0x643},
- {0x5e4, 0x644},
- {0x5e5, 0x645},
- {0x5e6, 0x646},
- {0x5e7, 0x647},
- {0x5e8, 0x648},
- {0x5e9, 0x649},
- {0x5ea, 0x64a},
- {0x5eb, 0x64b},
- {0x5ec, 0x64c},
- {0x5ed, 0x64d},
- {0x5ee, 0x64e},
- {0x5ef, 0x64f},
- {0x5f0, 0x650},
- {0x5f1, 0x651},
- {0x5f2, 0x652},
- {0x6a1, 0x452},
- {0x6a2, 0x453},
- {0x6a3, 0x451},
- {0x6a4, 0x454},
- {0x6a5, 0x455},
- {0x6a6, 0x456},
- {0x6a7, 0x457},
- {0x6a8, 0x458},
- {0x6a9, 0x459},
- {0x6aa, 0x45a},
- {0x6ab, 0x45b},
- {0x6ac, 0x45c},
- {0x6ae, 0x45e},
- {0x6af, 0x45f},
- {0x6b0, 0x2116},
- {0x6b1, 0x402},
- {0x6b2, 0x403},
- {0x6b3, 0x401},
- {0x6b4, 0x404},
- {0x6b5, 0x405},
- {0x6b6, 0x406},
- {0x6b7, 0x407},
- {0x6b8, 0x408},
- {0x6b9, 0x409},
- {0x6ba, 0x40a},
- {0x6bb, 0x40b},
- {0x6bc, 0x40c},
- {0x6be, 0x40e},
- {0x6bf, 0x40f},
- {0x6c0, 0x44e},
- {0x6c1, 0x430},
- {0x6c2, 0x431},
- {0x6c3, 0x446},
- {0x6c4, 0x434},
- {0x6c5, 0x435},
- {0x6c6, 0x444},
- {0x6c7, 0x433},
- {0x6c8, 0x445},
- {0x6c9, 0x438},
- {0x6ca, 0x439},
- {0x6cb, 0x43a},
- {0x6cc, 0x43b},
- {0x6cd, 0x43c},
- {0x6ce, 0x43d},
- {0x6cf, 0x43e},
- {0x6d0, 0x43f},
- {0x6d1, 0x44f},
- {0x6d2, 0x440},
- {0x6d3, 0x441},
- {0x6d4, 0x442},
- {0x6d5, 0x443},
- {0x6d6, 0x436},
- {0x6d7, 0x432},
- {0x6d8, 0x44c},
- {0x6d9, 0x44b},
- {0x6da, 0x437},
- {0x6db, 0x448},
- {0x6dc, 0x44d},
- {0x6dd, 0x449},
- {0x6de, 0x447},
- {0x6df, 0x44a},
- {0x6e0, 0x42e},
- {0x6e1, 0x410},
- {0x6e2, 0x411},
- {0x6e3, 0x426},
- {0x6e4, 0x414},
- {0x6e5, 0x415},
- {0x6e6, 0x424},
- {0x6e7, 0x413},
- {0x6e8, 0x425},
- {0x6e9, 0x418},
- {0x6ea, 0x419},
- {0x6eb, 0x41a},
- {0x6ec, 0x41b},
- {0x6ed, 0x41c},
- {0x6ee, 0x41d},
- {0x6ef, 0x41e},
- {0x6f0, 0x41f},
- {0x6f1, 0x42f},
- {0x6f2, 0x420},
- {0x6f3, 0x421},
- {0x6f4, 0x422},
- {0x6f5, 0x423},
- {0x6f6, 0x416},
- {0x6f7, 0x412},
- {0x6f8, 0x42c},
- {0x6f9, 0x42b},
- {0x6fa, 0x417},
- {0x6fb, 0x428},
- {0x6fc, 0x42d},
- {0x6fd, 0x429},
- {0x6fe, 0x427},
- {0x6ff, 0x42a},
- {0x7a1, 0x386},
- {0x7a2, 0x388},
- {0x7a3, 0x389},
- {0x7a4, 0x38a},
- {0x7a5, 0x3aa},
- {0x7a7, 0x38c},
- {0x7a8, 0x38e},
- {0x7a9, 0x3ab},
- {0x7ab, 0x38f},
- {0x7ae, 0x385},
- {0x7af, 0x2015},
- {0x7b1, 0x3ac},
- {0x7b2, 0x3ad},
- {0x7b3, 0x3ae},
- {0x7b4, 0x3af},
- {0x7b5, 0x3ca},
- {0x7b6, 0x390},
- {0x7b7, 0x3cc},
- {0x7b8, 0x3cd},
- {0x7b9, 0x3cb},
- {0x7ba, 0x3b0},
- {0x7bb, 0x3ce},
- {0x7c1, 0x391},
- {0x7c2, 0x392},
- {0x7c3, 0x393},
- {0x7c4, 0x394},
- {0x7c5, 0x395},
- {0x7c6, 0x396},
- {0x7c7, 0x397},
- {0x7c8, 0x398},
- {0x7c9, 0x399},
- {0x7ca, 0x39a},
- {0x7cb, 0x39b},
- {0x7cc, 0x39c},
- {0x7cd, 0x39d},
- {0x7ce, 0x39e},
- {0x7cf, 0x39f},
- {0x7d0, 0x3a0},
- {0x7d1, 0x3a1},
- {0x7d2, 0x3a3},
- {0x7d4, 0x3a4},
- {0x7d5, 0x3a5},
- {0x7d6, 0x3a6},
- {0x7d7, 0x3a7},
- {0x7d8, 0x3a8},
- {0x7d9, 0x3a9},
- {0x7e1, 0x3b1},
- {0x7e2, 0x3b2},
- {0x7e3, 0x3b3},
- {0x7e4, 0x3b4},
- {0x7e5, 0x3b5},
- {0x7e6, 0x3b6},
- {0x7e7, 0x3b7},
- {0x7e8, 0x3b8},
- {0x7e9, 0x3b9},
- {0x7ea, 0x3ba},
- {0x7eb, 0x3bb},
- {0x7ec, 0x3bc},
- {0x7ed, 0x3bd},
- {0x7ee, 0x3be},
- {0x7ef, 0x3bf},
- {0x7f0, 0x3c0},
- {0x7f1, 0x3c1},
- {0x7f2, 0x3c3},
- {0x7f3, 0x3c2},
- {0x7f4, 0x3c4},
- {0x7f5, 0x3c5},
- {0x7f6, 0x3c6},
- {0x7f7, 0x3c7},
- {0x7f8, 0x3c8},
- {0x7f9, 0x3c9},
- {0x8a1, 0x23b7},
- {0x8a2, 0x250c},
- {0x8a3, 0x2500},
- {0x8a4, 0x2320},
- {0x8a5, 0x2321},
- {0x8a6, 0x2502},
- {0x8a7, 0x23a1},
- {0x8a8, 0x23a3},
- {0x8a9, 0x23a4},
- {0x8aa, 0x23a6},
- {0x8ab, 0x239b},
- {0x8ac, 0x239d},
- {0x8ad, 0x239e},
- {0x8ae, 0x23a0},
- {0x8af, 0x23a8},
- {0x8b0, 0x23ac},
- {0x8bc, 0x2264},
- {0x8bd, 0x2260},
- {0x8be, 0x2265},
- {0x8bf, 0x222b},
- {0x8c0, 0x2234},
- {0x8c1, 0x221d},
- {0x8c2, 0x221e},
- {0x8c5, 0x2207},
- {0x8c8, 0x223c},
- {0x8c9, 0x2243},
- {0x8cd, 0x21d4},
- {0x8ce, 0x21d2},
- {0x8cf, 0x2261},
- {0x8d6, 0x221a},
- {0x8da, 0x2282},
- {0x8db, 0x2283},
- {0x8dc, 0x2229},
- {0x8dd, 0x222a},
- {0x8de, 0x2227},
- {0x8df, 0x2228},
- {0x8ef, 0x2202},
- {0x8f6, 0x192},
- {0x8fb, 0x2190},
- {0x8fc, 0x2191},
- {0x8fd, 0x2192},
- {0x8fe, 0x2193},
- {0x9e0, 0x25c6},
- {0x9e1, 0x2592},
- {0x9e2, 0x2409},
- {0x9e3, 0x240c},
- {0x9e4, 0x240d},
- {0x9e5, 0x240a},
- {0x9e8, 0x2424},
- {0x9e9, 0x240b},
- {0x9ea, 0x2518},
- {0x9eb, 0x2510},
- {0x9ec, 0x250c},
- {0x9ed, 0x2514},
- {0x9ee, 0x253c},
- {0x9ef, 0x23ba},
- {0x9f0, 0x23bb},
- {0x9f1, 0x2500},
- {0x9f2, 0x23bc},
- {0x9f3, 0x23bd},
- {0x9f4, 0x251c},
- {0x9f5, 0x2524},
- {0x9f6, 0x2534},
- {0x9f7, 0x252c},
- {0x9f8, 0x2502},
- {0xaa1, 0x2003},
- {0xaa2, 0x2002},
- {0xaa3, 0x2004},
- {0xaa4, 0x2005},
- {0xaa5, 0x2007},
- {0xaa6, 0x2008},
- {0xaa7, 0x2009},
- {0xaa8, 0x200a},
- {0xaa9, 0x2014},
- {0xaaa, 0x2013},
- {0xaae, 0x2026},
- {0xaaf, 0x2025},
- {0xab0, 0x2153},
- {0xab1, 0x2154},
- {0xab2, 0x2155},
- {0xab3, 0x2156},
- {0xab4, 0x2157},
- {0xab5, 0x2158},
- {0xab6, 0x2159},
- {0xab7, 0x215a},
- {0xab8, 0x2105},
- {0xabb, 0x2012},
- {0xabc, 0x2329},
- {0xabe, 0x232a},
- {0xac3, 0x215b},
- {0xac4, 0x215c},
- {0xac5, 0x215d},
- {0xac6, 0x215e},
- {0xac9, 0x2122},
- {0xaca, 0x2613},
- {0xacc, 0x25c1},
- {0xacd, 0x25b7},
- {0xace, 0x25cb},
- {0xacf, 0x25af},
- {0xad0, 0x2018},
- {0xad1, 0x2019},
- {0xad2, 0x201c},
- {0xad3, 0x201d},
- {0xad4, 0x211e},
- {0xad6, 0x2032},
- {0xad7, 0x2033},
- {0xad9, 0x271d},
- {0xadb, 0x25ac},
- {0xadc, 0x25c0},
- {0xadd, 0x25b6},
- {0xade, 0x25cf},
- {0xadf, 0x25ae},
- {0xae0, 0x25e6},
- {0xae1, 0x25ab},
- {0xae2, 0x25ad},
- {0xae3, 0x25b3},
- {0xae4, 0x25bd},
- {0xae5, 0x2606},
- {0xae6, 0x2022},
- {0xae7, 0x25aa},
- {0xae8, 0x25b2},
- {0xae9, 0x25bc},
- {0xaea, 0x261c},
- {0xaeb, 0x261e},
- {0xaec, 0x2663},
- {0xaed, 0x2666},
- {0xaee, 0x2665},
- {0xaf0, 0x2720},
- {0xaf1, 0x2020},
- {0xaf2, 0x2021},
- {0xaf3, 0x2713},
- {0xaf4, 0x2717},
- {0xaf5, 0x266f},
- {0xaf6, 0x266d},
- {0xaf7, 0x2642},
- {0xaf8, 0x2640},
- {0xaf9, 0x260e},
- {0xafa, 0x2315},
- {0xafb, 0x2117},
- {0xafc, 0x2038},
- {0xafd, 0x201a},
- {0xafe, 0x201e},
- {0xba3, 0x3c},
- {0xba6, 0x3e},
- {0xba8, 0x2228},
- {0xba9, 0x2227},
- {0xbc0, 0xaf},
- {0xbc2, 0x22a5},
- {0xbc3, 0x2229},
- {0xbc4, 0x230a},
- {0xbc6, 0x5f},
- {0xbca, 0x2218},
- {0xbcc, 0x2395},
- {0xbce, 0x22a4},
- {0xbcf, 0x25cb},
- {0xbd3, 0x2308},
- {0xbd6, 0x222a},
- {0xbd8, 0x2283},
- {0xbda, 0x2282},
- {0xbdc, 0x22a2},
- {0xbfc, 0x22a3},
- {0xcdf, 0x2017},
- {0xce0, 0x5d0},
- {0xce1, 0x5d1},
- {0xce2, 0x5d2},
- {0xce3, 0x5d3},
- {0xce4, 0x5d4},
- {0xce5, 0x5d5},
- {0xce6, 0x5d6},
- {0xce7, 0x5d7},
- {0xce8, 0x5d8},
- {0xce9, 0x5d9},
- {0xcea, 0x5da},
- {0xceb, 0x5db},
- {0xcec, 0x5dc},
- {0xced, 0x5dd},
- {0xcee, 0x5de},
- {0xcef, 0x5df},
- {0xcf0, 0x5e0},
- {0xcf1, 0x5e1},
- {0xcf2, 0x5e2},
- {0xcf3, 0x5e3},
- {0xcf4, 0x5e4},
- {0xcf5, 0x5e5},
- {0xcf6, 0x5e6},
- {0xcf7, 0x5e7},
- {0xcf8, 0x5e8},
- {0xcf9, 0x5e9},
- {0xcfa, 0x5ea},
- {0xda1, 0xe01},
- {0xda2, 0xe02},
- {0xda3, 0xe03},
- {0xda4, 0xe04},
- {0xda5, 0xe05},
- {0xda6, 0xe06},
- {0xda7, 0xe07},
- {0xda8, 0xe08},
- {0xda9, 0xe09},
- {0xdaa, 0xe0a},
- {0xdab, 0xe0b},
- {0xdac, 0xe0c},
- {0xdad, 0xe0d},
- {0xdae, 0xe0e},
- {0xdaf, 0xe0f},
- {0xdb0, 0xe10},
- {0xdb1, 0xe11},
- {0xdb2, 0xe12},
- {0xdb3, 0xe13},
- {0xdb4, 0xe14},
- {0xdb5, 0xe15},
- {0xdb6, 0xe16},
- {0xdb7, 0xe17},
- {0xdb8, 0xe18},
- {0xdb9, 0xe19},
- {0xdba, 0xe1a},
- {0xdbb, 0xe1b},
- {0xdbc, 0xe1c},
- {0xdbd, 0xe1d},
- {0xdbe, 0xe1e},
- {0xdbf, 0xe1f},
- {0xdc0, 0xe20},
- {0xdc1, 0xe21},
- {0xdc2, 0xe22},
- {0xdc3, 0xe23},
- {0xdc4, 0xe24},
- {0xdc5, 0xe25},
- {0xdc6, 0xe26},
- {0xdc7, 0xe27},
- {0xdc8, 0xe28},
- {0xdc9, 0xe29},
- {0xdca, 0xe2a},
- {0xdcb, 0xe2b},
- {0xdcc, 0xe2c},
- {0xdcd, 0xe2d},
- {0xdce, 0xe2e},
- {0xdcf, 0xe2f},
- {0xdd0, 0xe30},
- {0xdd1, 0xe31},
- {0xdd2, 0xe32},
- {0xdd3, 0xe33},
- {0xdd4, 0xe34},
- {0xdd5, 0xe35},
- {0xdd6, 0xe36},
- {0xdd7, 0xe37},
- {0xdd8, 0xe38},
- {0xdd9, 0xe39},
- {0xdda, 0xe3a},
- {0xddf, 0xe3f},
- {0xde0, 0xe40},
- {0xde1, 0xe41},
- {0xde2, 0xe42},
- {0xde3, 0xe43},
- {0xde4, 0xe44},
- {0xde5, 0xe45},
- {0xde6, 0xe46},
- {0xde7, 0xe47},
- {0xde8, 0xe48},
- {0xde9, 0xe49},
- {0xdea, 0xe4a},
- {0xdeb, 0xe4b},
- {0xdec, 0xe4c},
- {0xded, 0xe4d},
- {0xdf0, 0xe50},
- {0xdf1, 0xe51},
- {0xdf2, 0xe52},
- {0xdf3, 0xe53},
- {0xdf4, 0xe54},
- {0xdf5, 0xe55},
- {0xdf6, 0xe56},
- {0xdf7, 0xe57},
- {0xdf8, 0xe58},
- {0xdf9, 0xe59},
- {0xea1, 0x3131},
- {0xea2, 0x3132},
- {0xea3, 0x3133},
- {0xea4, 0x3134},
- {0xea5, 0x3135},
- {0xea6, 0x3136},
- {0xea7, 0x3137},
- {0xea8, 0x3138},
- {0xea9, 0x3139},
- {0xeaa, 0x313a},
- {0xeab, 0x313b},
- {0xeac, 0x313c},
- {0xead, 0x313d},
- {0xeae, 0x313e},
- {0xeaf, 0x313f},
- {0xeb0, 0x3140},
- {0xeb1, 0x3141},
- {0xeb2, 0x3142},
- {0xeb3, 0x3143},
- {0xeb4, 0x3144},
- {0xeb5, 0x3145},
- {0xeb6, 0x3146},
- {0xeb7, 0x3147},
- {0xeb8, 0x3148},
- {0xeb9, 0x3149},
- {0xeba, 0x314a},
- {0xebb, 0x314b},
- {0xebc, 0x314c},
- {0xebd, 0x314d},
- {0xebe, 0x314e},
- {0xebf, 0x314f},
- {0xec0, 0x3150},
- {0xec1, 0x3151},
- {0xec2, 0x3152},
- {0xec3, 0x3153},
- {0xec4, 0x3154},
- {0xec5, 0x3155},
- {0xec6, 0x3156},
- {0xec7, 0x3157},
- {0xec8, 0x3158},
- {0xec9, 0x3159},
- {0xeca, 0x315a},
- {0xecb, 0x315b},
- {0xecc, 0x315c},
- {0xecd, 0x315d},
- {0xece, 0x315e},
- {0xecf, 0x315f},
- {0xed0, 0x3160},
- {0xed1, 0x3161},
- {0xed2, 0x3162},
- {0xed3, 0x3163},
- {0xed4, 0x11a8},
- {0xed5, 0x11a9},
- {0xed6, 0x11aa},
- {0xed7, 0x11ab},
- {0xed8, 0x11ac},
- {0xed9, 0x11ad},
- {0xeda, 0x11ae},
- {0xedb, 0x11af},
- {0xedc, 0x11b0},
- {0xedd, 0x11b1},
- {0xede, 0x11b2},
- {0xedf, 0x11b3},
- {0xee0, 0x11b4},
- {0xee1, 0x11b5},
- {0xee2, 0x11b6},
- {0xee3, 0x11b7},
- {0xee4, 0x11b8},
- {0xee5, 0x11b9},
- {0xee6, 0x11ba},
- {0xee7, 0x11bb},
- {0xee8, 0x11bc},
- {0xee9, 0x11bd},
- {0xeea, 0x11be},
- {0xeeb, 0x11bf},
- {0xeec, 0x11c0},
- {0xeed, 0x11c1},
- {0xeee, 0x11c2},
- {0xeef, 0x316d},
- {0xef0, 0x3171},
- {0xef1, 0x3178},
- {0xef2, 0x317f},
- {0xef3, 0x3181},
- {0xef4, 0x3184},
- {0xef5, 0x3186},
- {0xef6, 0x318d},
- {0xef7, 0x318e},
- {0xef8, 0x11eb},
- {0xef9, 0x11f0},
- {0xefa, 0x11f9},
- {0xeff, 0x20a9},
- {0x13a4, 0x20ac},
- {0x13bc, 0x152},
- {0x13bd, 0x153},
- {0x13be, 0x178},
- {0x20a0, 0x20a0},
- {0x20a1, 0x20a1},
- {0x20a2, 0x20a2},
- {0x20a3, 0x20a3},
- {0x20a4, 0x20a4},
- {0x20a5, 0x20a5},
- {0x20a6, 0x20a6},
- {0x20a7, 0x20a7},
- {0x20a8, 0x20a8},
- {0x20aa, 0x20aa},
- {0x20ab, 0x20ab},
- {0x20ac, 0x20ac},
-};
-
-int keysym_to_unicode(int keysym)
-{
- int i, j, k;
-
- i = -1;
- j = lenof(keysyms);
-
- while (j - i >= 2) {
- k = (j + i) / 2;
- if (keysyms[k].keysym == keysym)
- return keysyms[k].unicode;
- else if (keysyms[k].keysym < keysym)
- i = k;
- else
- j = k;
- }
- return -1;
-}
diff --git a/UNIX/configure b/UNIX/configure
deleted file mode 100644
index b2b033d8..00000000
--- a/UNIX/configure
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-$(echo "$0" | sed '$s!configure$!../configure!') "$@"
diff --git a/UNIX/gtkapp.c b/UNIX/gtkapp.c
deleted file mode 100644
index e7f49df5..00000000
--- a/UNIX/gtkapp.c
+++ /dev/null
@@ -1,347 +0,0 @@
-/*
- * gtkapp.c: a top-level front end to GUI PuTTY and pterm, using
- * GtkApplication. Suitable for OS X. Currently unfinished.
- *
- * (You could run it on ordinary Linux GTK too, in principle, but I
- * don't think it would be particularly useful to do so, even once
- * it's fully working.)
- */
-
-/*
-
-To build on OS X, you will need a build environment with GTK 3 and
-gtk-mac-bundler, and also Halibut on the path (to build the man pages,
-without which the standard Makefile will complain). Then, from a clean
-checkout, do this:
-
-./mkfiles.pl -U --with-quartz
-make -C icons icns
-make -C doc
-make
-
-and you should get unix/PuTTY.app and unix/PTerm.app as output.
-
-*/
-
-/*
-
-TODO list for a sensible GTK3 PuTTY/pterm on OS X:
-
-Still to do on the application menu bar: items that have to vary with
-context or user action (saved sessions and mid-session special
-commands), and disabling/enabling the main actions in parallel with
-their counterparts in the Ctrl-rightclick context menu.
-
-Mouse wheel events and trackpad scrolling gestures don't work quite
-right in the terminal drawing area. This seems to be a combination of
-two things, neither of which I completely understand yet. Firstly, on
-OS X GTK my trackpad seems to generate GDK scroll events for which
-gdk_event_get_scroll_deltas returns integers rather than integer
-multiples of 1/30, so we end up scrolling by very large amounts;
-secondly, the window doesn't seem to receive a GTK "draw" event until
-after the entire scroll gesture is complete, which means we don't get
-constant visual feedback on how much we're scrolling by.
-
-There doesn't seem to be a resize handle on terminal windows. Then
-again, they do seem to _be_ resizable; the handle just isn't shown.
-Perhaps that's a feature (certainly in a scrollbarless configuration
-the handle gets in the way of the bottom right character cell in the
-terminal itself), but it would be nice to at least understand _why_ it
-happens and perhaps include an option to put it back again.
-
-A slight oddity with menus that pop up directly under the mouse
-pointer: mousing over the menu items doesn't highlight them initially,
-but if I mouse off the menu and back on (without un-popping-it-up)
-then suddenly that does work. I don't know if this is something I can
-fix, though; it might very well be a quirk of the underlying GTK.
-
-Does OS X have a standard system of online help that I could tie into?
-
-Need to work out what if anything we can do with Pageant on OS X.
-Perhaps it's too much bother and we should just talk to the
-system-provided SSH agent? Or perhaps not.
-
-Nice-to-have: a custom right-click menu from the application's dock
-tile, listing the saved sessions for quick launch. As far as I know
-there's nothing built in to GtkApplication that can produce this, but
-it's possible we might be able to drop a piece of native Cocoa code in
-under ifdef, substituting an application delegate of our own which
-forwards all methods we're not interested in to the GTK-provided one?
-
-At the point where this becomes polished enough to publish pre-built,
-I suppose I'll have to look into OS X code signing.
-https://wiki.gnome.org/Projects/GTK%2B/OSX/Bundling has some links.
-
- */
-
-#include <assert.h>
-#include <stdlib.h>
-
-#include <unistd.h>
-
-#include <gtk/gtk.h>
-
-#define MAY_REFER_TO_GTK_IN_HEADERS
-
-#include "putty.h"
-#include "gtkmisc.h"
-
-char *x_get_default(const char *key) { return NULL; }
-
-const bool buildinfo_gtk_relevant = true;
-
-#if !GTK_CHECK_VERSION(3,0,0)
-/* This front end only works in GTK 3. If that's not what we've got,
- * it's easier to just turn this program into a trivial stub by ifdef
- * in the source than it is to remove it in the makefile edifice. */
-int main(int argc, char **argv)
-{
- fprintf(stderr, "GtkApplication frontend doesn't work pre-GTK3\n");
- return 1;
-}
-GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend) { return NULL; }
-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) {}
-#else /* GTK_CHECK_VERSION(3,0,0) */
-
-static void startup(GApplication *app, gpointer user_data)
-{
- GMenu *menubar, *menu, *section;
-
- menubar = g_menu_new();
-
- menu = g_menu_new();
- g_menu_append_submenu(menubar, "File", G_MENU_MODEL(menu));
-
- section = g_menu_new();
- g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
- g_menu_append(section, "New Window", "app.newwin");
-
- menu = g_menu_new();
- g_menu_append_submenu(menubar, "Edit", G_MENU_MODEL(menu));
-
- section = g_menu_new();
- g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
- g_menu_append(section, "Copy", "win.copy");
- g_menu_append(section, "Paste", "win.paste");
- g_menu_append(section, "Copy All", "win.copyall");
-
- menu = g_menu_new();
- g_menu_append_submenu(menubar, "Window", G_MENU_MODEL(menu));
-
- section = g_menu_new();
- g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
- g_menu_append(section, "Restart Session", "win.restart");
- g_menu_append(section, "Duplicate Session", "win.duplicate");
-
- section = g_menu_new();
- g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
- g_menu_append(section, "Change Settings", "win.changesettings");
-
- if (use_event_log) {
- section = g_menu_new();
- g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
- g_menu_append(section, "Event Log", "win.eventlog");
- }
-
- section = g_menu_new();
- g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
- g_menu_append(section, "Clear Scrollback", "win.clearscrollback");
- g_menu_append(section, "Reset Terminal", "win.resetterm");
-
-#if GTK_CHECK_VERSION(3,12,0)
-#define SET_ACCEL(app, command, accel) do \
- { \
- static const char *const accels[] = { accel, NULL }; \
- gtk_application_set_accels_for_action( \
- GTK_APPLICATION(app), command, accels); \
- } while (0)
-#else
- /* The Gtk function used above was new in 3.12; the one below
- * was deprecated from 3.14. */
-#define SET_ACCEL(app, command, accel) \
- gtk_application_add_accelerator(GTK_APPLICATION(app), accel, \
- command, NULL)
-#endif
-
- SET_ACCEL(app, "app.newwin", "<Primary>n");
- SET_ACCEL(app, "win.copy", "<Primary>c");
- SET_ACCEL(app, "win.paste", "<Primary>v");
-
-#undef SET_ACCEL
-
- gtk_application_set_menubar(GTK_APPLICATION(app),
- G_MENU_MODEL(menubar));
-}
-
-#define WIN_ACTION_LIST(X) \
- X("copy", MA_COPY) \
- X("paste", MA_PASTE) \
- X("copyall", MA_COPY_ALL) \
- X("duplicate", MA_DUPLICATE_SESSION) \
- X("restart", MA_RESTART_SESSION) \
- X("changesettings", MA_CHANGE_SETTINGS) \
- X("clearscrollback", MA_CLEAR_SCROLLBACK) \
- X("resetterm", MA_RESET_TERMINAL) \
- X("eventlog", MA_EVENT_LOG) \
- /* end of list */
-
-#define WIN_ACTION_CALLBACK(name, id) \
-static void win_action_cb_ ## id(GSimpleAction *a, GVariant *p, gpointer d) \
-{ app_menu_action(d, id); }
-WIN_ACTION_LIST(WIN_ACTION_CALLBACK)
-#undef WIN_ACTION_CALLBACK
-
-static const GActionEntry win_actions[] = {
-#define WIN_ACTION_ENTRY(name, id) { name, win_action_cb_ ## id },
-WIN_ACTION_LIST(WIN_ACTION_ENTRY)
-#undef WIN_ACTION_ENTRY
-};
-
-static GtkApplication *app;
-GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend)
-{
- GtkWidget *win = gtk_application_window_new(app);
- g_action_map_add_action_entries(G_ACTION_MAP(win),
- win_actions,
- G_N_ELEMENTS(win_actions),
- frontend);
- return win;
-}
-
-void launch_duplicate_session(Conf *conf)
-{
- assert(!dup_check_launchable || conf_launchable(conf));
- g_application_hold(G_APPLICATION(app));
- new_session_window(conf_copy(conf), NULL);
-}
-
-void session_window_closed(void)
-{
- g_application_release(G_APPLICATION(app));
-}
-
-static void post_initial_config_box(void *vctx, int result)
-{
- Conf *conf = (Conf *)vctx;
-
- if (result > 0) {
- new_session_window(conf, NULL);
- } else if (result == 0) {
- conf_free(conf);
- g_application_release(G_APPLICATION(app));
- }
-}
-
-void launch_saved_session(const char *str)
-{
- Conf *conf = conf_new();
- do_defaults(str, conf);
-
- g_application_hold(G_APPLICATION(app));
-
- if (!conf_launchable(conf)) {
- initial_config_box(conf, post_initial_config_box, conf);
- } else {
- new_session_window(conf, NULL);
- }
-}
-
-void launch_new_session(void)
-{
- /* Same as launch_saved_session except that we pass NULL to
- * do_defaults. */
- launch_saved_session(NULL);
-}
-
-void new_app_win(GtkApplication *app)
-{
- launch_new_session();
-}
-
-static void window_setup_error_callback(void *vctx, int result)
-{
- g_application_release(G_APPLICATION(app));
-}
-
-void window_setup_error(const char *errmsg)
-{
- create_message_box(NULL, "Error creating session window", errmsg,
- string_width("Some sort of fiddly error message that "
- "might be technical"),
- true, &buttons_ok, window_setup_error_callback, NULL);
-}
-
-static void activate(GApplication *app,
- gpointer user_data)
-{
- new_app_win(GTK_APPLICATION(app));
-}
-
-static void newwin_cb(GSimpleAction *action,
- GVariant *parameter,
- gpointer user_data)
-{
- new_app_win(GTK_APPLICATION(user_data));
-}
-
-static void quit_cb(GSimpleAction *action,
- GVariant *parameter,
- gpointer user_data)
-{
- g_application_quit(G_APPLICATION(user_data));
-}
-
-static void about_cb(GSimpleAction *action,
- GVariant *parameter,
- gpointer user_data)
-{
- about_box(NULL);
-}
-
-static const GActionEntry app_actions[] = {
- { "newwin", newwin_cb },
- { "about", about_cb },
- { "quit", quit_cb },
-};
-
-int main(int argc, char **argv)
-{
- int status;
-
- /* Call the function in ux{putty,pterm}.c to do app-type
- * specific setup */
- setup(false); /* false means we are not a one-session process */
-
- if (argc > 1) {
- pty_osx_envrestore_prefix = argv[--argc];
- }
-
- {
- const char *home = getenv("HOME");
- if (home) {
- if (chdir(home)) {}
- }
- }
-
- gtkcomm_setup();
-
- app = gtk_application_new("org.tartarus.projects.putty.macputty",
- G_APPLICATION_FLAGS_NONE);
- g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
- g_signal_connect(app, "startup", G_CALLBACK(startup), NULL);
- g_action_map_add_action_entries(G_ACTION_MAP(app),
- app_actions,
- G_N_ELEMENTS(app_actions),
- app);
-
- status = g_application_run(G_APPLICATION(app), argc, argv);
- g_object_unref(app);
-
- return status;
-}
-
-#endif /* GTK_CHECK_VERSION(3,0,0) */
diff --git a/UNIX/gtkask.c b/UNIX/gtkask.c
deleted file mode 100644
index 4e95ba1a..00000000
--- a/UNIX/gtkask.c
+++ /dev/null
@@ -1,662 +0,0 @@
-/*
- * GTK implementation of a GUI password/passphrase prompt.
- */
-
-#include <assert.h>
-#include <time.h>
-#include <stdlib.h>
-
-#include <unistd.h>
-
-#include <gtk/gtk.h>
-#include <gdk/gdk.h>
-#if !GTK_CHECK_VERSION(3,0,0)
-#include <gdk/gdkkeysyms.h>
-#endif
-
-#include "defs.h"
-#include "gtkfont.h"
-#include "gtkcompat.h"
-#include "gtkmisc.h"
-
-#include "putty.h"
-#include "ssh.h"
-#include "misc.h"
-
-#define N_DRAWING_AREAS 3
-
-struct drawing_area_ctx {
- GtkWidget *area;
-#ifndef DRAW_DEFAULT_CAIRO
- GdkColor *cols;
-#endif
- int width, height;
- enum { NOT_CURRENT, CURRENT, GREYED_OUT } state;
-};
-
-struct askpass_ctx {
- GtkWidget *dialog, *promptlabel;
- struct drawing_area_ctx drawingareas[N_DRAWING_AREAS];
- int active_area;
-#if GTK_CHECK_VERSION(2,0,0)
- GtkIMContext *imc;
-#endif
-#ifndef DRAW_DEFAULT_CAIRO
- GdkColormap *colmap;
- GdkColor cols[3];
-#endif
- char *error_message; /* if we finish without a passphrase */
- char *passphrase; /* if we finish with one */
- int passlen, passsize;
-#if GTK_CHECK_VERSION(3,20,0)
- GdkSeat *seat; /* for gdk_seat_grab */
-#elif GTK_CHECK_VERSION(3,0,0)
- GdkDevice *keyboard; /* for gdk_device_grab */
-#endif
-
- int nattempts;
-};
-
-static prng *keypress_prng = NULL;
-static void feed_keypress_prng(void *data, int size)
-{
- put_data(keypress_prng, data, size);
-}
-void random_add_noise(NoiseSourceId source, const void *noise, int length)
-{
- if (keypress_prng)
- prng_add_entropy(keypress_prng, source, make_ptrlen(noise, length));
-}
-static void setup_keypress_prng(void)
-{
- keypress_prng = prng_new(&ssh_sha256);
- prng_seed_begin(keypress_prng);
- noise_get_heavy(feed_keypress_prng);
- prng_seed_finish(keypress_prng);
-}
-static void cleanup_keypress_prng(void)
-{
- prng_free(keypress_prng);
-}
-static uint64_t keypress_prng_value(void)
-{
- /*
- * Don't actually put the passphrase keystrokes themselves into
- * the PRNG; that doesn't seem like the course of wisdom when
- * that's precisely what the information displayed on the screen
- * is trying _not_ to be correlated to.
- */
- noise_ultralight(NOISE_SOURCE_KEY, 0);
- uint8_t data[8];
- prng_read(keypress_prng, data, 8);
- return GET_64BIT_MSB_FIRST(data);
-}
-static int choose_new_area(int prev_area)
-{
- int reduced = keypress_prng_value() % (N_DRAWING_AREAS - 1);
- return (prev_area + 1 + reduced) % N_DRAWING_AREAS;
-}
-
-static void visually_acknowledge_keypress(struct askpass_ctx *ctx)
-{
- int new_active = choose_new_area(ctx->active_area);
- ctx->drawingareas[ctx->active_area].state = NOT_CURRENT;
- gtk_widget_queue_draw(ctx->drawingareas[ctx->active_area].area);
- ctx->drawingareas[new_active].state = CURRENT;
- gtk_widget_queue_draw(ctx->drawingareas[new_active].area);
- ctx->active_area = new_active;
-}
-
-static int last_char_len(struct askpass_ctx *ctx)
-{
- /*
- * GTK always encodes in UTF-8, so we can do this in a fixed way.
- */
- int i;
- assert(ctx->passlen > 0);
- i = ctx->passlen - 1;
- while ((unsigned)((unsigned char)ctx->passphrase[i] - 0x80) < 0x40) {
- if (i == 0)
- break;
- i--;
- }
- return ctx->passlen - i;
-}
-
-static void add_text_to_passphrase(struct askpass_ctx *ctx, gchar *str)
-{
- int len = strlen(str);
- if (ctx->passlen + len >= ctx->passsize) {
- /* Take some care with buffer expansion, because there are
- * pieces of passphrase in the old buffer so we should ensure
- * realloc doesn't leave a copy lying around in the address
- * space. */
- int oldsize = ctx->passsize;
- char *newbuf;
-
- ctx->passsize = (ctx->passlen + len) * 5 / 4 + 1024;
- newbuf = snewn(ctx->passsize, char);
- memcpy(newbuf, ctx->passphrase, oldsize);
- smemclr(ctx->passphrase, oldsize);
- sfree(ctx->passphrase);
- ctx->passphrase = newbuf;
- }
- strcpy(ctx->passphrase + ctx->passlen, str);
- ctx->passlen += len;
- visually_acknowledge_keypress(ctx);
-}
-
-static void cancel_askpass(struct askpass_ctx *ctx, const char *msg)
-{
- smemclr(ctx->passphrase, ctx->passsize);
- ctx->passphrase = NULL;
- ctx->error_message = dupstr(msg);
- gtk_main_quit();
-}
-
-static gboolean askpass_dialog_closed(GtkWidget *widget, GdkEvent *event,
- gpointer data)
-{
- struct askpass_ctx *ctx = (struct askpass_ctx *)data;
- cancel_askpass(ctx, "passphrase input cancelled");
- /* Don't destroy dialog yet, so gtk_askpass_cleanup() can do its work */
- return true;
-}
-
-static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
-{
- struct askpass_ctx *ctx = (struct askpass_ctx *)data;
-
- if (event->keyval == GDK_KEY_Return &&
- event->type == GDK_KEY_PRESS) {
- gtk_main_quit();
- } else if (event->keyval == GDK_KEY_Escape &&
- event->type == GDK_KEY_PRESS) {
- cancel_askpass(ctx, "passphrase input cancelled");
- } else {
-#if GTK_CHECK_VERSION(2,0,0)
- if (gtk_im_context_filter_keypress(ctx->imc, event))
- return true;
-#endif
-
- if (event->type == GDK_KEY_PRESS) {
- if (!strcmp(event->string, "\x15")) {
- /* Ctrl-U. Wipe out the whole line */
- ctx->passlen = 0;
- visually_acknowledge_keypress(ctx);
- } else if (!strcmp(event->string, "\x17")) {
- /* Ctrl-W. Delete back to the last space->nonspace
- * boundary. We interpret 'space' in a really simple
- * way (mimicking terminal drivers), and don't attempt
- * to second-guess exciting Unicode space
- * characters. */
- while (ctx->passlen > 0) {
- char deleted, prior;
- ctx->passlen -= last_char_len(ctx);
- deleted = ctx->passphrase[ctx->passlen];
- prior = (ctx->passlen == 0 ? ' ' :
- ctx->passphrase[ctx->passlen-1]);
- if (!g_ascii_isspace(deleted) && g_ascii_isspace(prior))
- break;
- }
- visually_acknowledge_keypress(ctx);
- } else if (event->keyval == GDK_KEY_BackSpace) {
- /* Backspace. Delete one character. */
- if (ctx->passlen > 0)
- ctx->passlen -= last_char_len(ctx);
- visually_acknowledge_keypress(ctx);
-#if !GTK_CHECK_VERSION(2,0,0)
- } else if (event->string[0]) {
- add_text_to_passphrase(ctx, event->string);
-#endif
- }
- }
- }
- return true;
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-static void input_method_commit_event(GtkIMContext *imc, gchar *str,
- gpointer data)
-{
- struct askpass_ctx *ctx = (struct askpass_ctx *)data;
- add_text_to_passphrase(ctx, str);
-}
-#endif
-
-static gint configure_area(GtkWidget *widget, GdkEventConfigure *event,
- gpointer data)
-{
- struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
- ctx->width = event->width;
- ctx->height = event->height;
- gtk_widget_queue_draw(widget);
- return true;
-}
-
-#ifdef DRAW_DEFAULT_CAIRO
-static void askpass_redraw_cairo(cairo_t *cr, struct drawing_area_ctx *ctx)
-{
- double rgbval = (ctx->state == CURRENT ? 0 :
- ctx->state == NOT_CURRENT ? 1 : 0.5);
- cairo_set_source_rgb(cr, rgbval, rgbval, rgbval);
- cairo_paint(cr);
-}
-#else
-static void askpass_redraw_gdk(GdkWindow *win, struct drawing_area_ctx *ctx)
-{
- GdkGC *gc = gdk_gc_new(win);
- gdk_gc_set_foreground(gc, &ctx->cols[ctx->state]);
- gdk_draw_rectangle(win, gc, true, 0, 0, ctx->width, ctx->height);
- gdk_gc_unref(gc);
-}
-#endif
-
-#if GTK_CHECK_VERSION(3,0,0)
-static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
-{
- struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
- askpass_redraw_cairo(cr, ctx);
- return true;
-}
-#else
-static gint expose_area(GtkWidget *widget, GdkEventExpose *event,
- gpointer data)
-{
- struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
-
-#ifdef DRAW_DEFAULT_CAIRO
- cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(ctx->area));
- askpass_redraw_cairo(cr, ctx);
- cairo_destroy(cr);
-#else
- askpass_redraw_gdk(gtk_widget_get_window(ctx->area), ctx);
-#endif
-
- return true;
-}
-#endif
-
-static gboolean try_grab_keyboard(gpointer vctx)
-{
- struct askpass_ctx *ctx = (struct askpass_ctx *)vctx;
- int i, ret;
-
-#if GTK_CHECK_VERSION(3,20,0)
- /*
- * Grabbing the keyboard in GTK 3.20 requires the new notion of
- * GdkSeat.
- */
- GdkSeat *seat;
- GdkWindow *gdkw = gtk_widget_get_window(ctx->dialog);
- if (!GDK_IS_WINDOW(gdkw) || !gdk_window_is_visible(gdkw))
- goto fail;
-
- seat = gdk_display_get_default_seat
- (gtk_widget_get_display(ctx->dialog));
- if (!seat)
- goto fail;
-
- ctx->seat = seat;
- ret = gdk_seat_grab(seat, gdkw, GDK_SEAT_CAPABILITY_KEYBOARD,
- true, NULL, NULL, NULL, NULL);
-
- /*
- * For some reason GDK 3.22 hides the GDK window as a side effect
- * of a failed grab. I've no idea why. But if we're going to retry
- * the grab, then we need to unhide it again or else we'll just
- * get GDK_GRAB_NOT_VIEWABLE on every subsequent attempt.
- */
- if (ret != GDK_GRAB_SUCCESS)
- gdk_window_show(gdkw);
-
-#elif GTK_CHECK_VERSION(3,0,0)
- /*
- * And it has to be done differently again prior to GTK 3.20.
- */
- GdkDeviceManager *dm;
- GdkDevice *pointer, *keyboard;
-
- dm = gdk_display_get_device_manager
- (gtk_widget_get_display(ctx->dialog));
- if (!dm)
- goto fail;
-
- pointer = gdk_device_manager_get_client_pointer(dm);
- if (!pointer)
- goto fail;
- keyboard = gdk_device_get_associated_device(pointer);
- if (!keyboard)
- goto fail;
- if (gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD)
- goto fail;
-
- ctx->keyboard = keyboard;
- ret = gdk_device_grab(ctx->keyboard,
- gtk_widget_get_window(ctx->dialog),
- GDK_OWNERSHIP_NONE,
- true,
- GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
- NULL,
- GDK_CURRENT_TIME);
-#else
- /*
- * It's much simpler in GTK 1 and 2!
- */
- ret = gdk_keyboard_grab(gtk_widget_get_window(ctx->dialog),
- false, GDK_CURRENT_TIME);
-#endif
- if (ret != GDK_GRAB_SUCCESS)
- goto fail;
-
- /*
- * Now that we've got the keyboard grab, connect up our keyboard
- * handlers.
- */
-#if GTK_CHECK_VERSION(2,0,0)
- g_signal_connect(G_OBJECT(ctx->imc), "commit",
- G_CALLBACK(input_method_commit_event), ctx);
-#endif
- g_signal_connect(G_OBJECT(ctx->dialog), "key_press_event",
- G_CALLBACK(key_event), ctx);
- g_signal_connect(G_OBJECT(ctx->dialog), "key_release_event",
- G_CALLBACK(key_event), ctx);
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_im_context_set_client_window(ctx->imc,
- gtk_widget_get_window(ctx->dialog));
-#endif
-
- /*
- * And repaint the key-acknowledgment drawing areas as not greyed
- * out.
- */
- ctx->active_area = keypress_prng_value() % N_DRAWING_AREAS;
- for (i = 0; i < N_DRAWING_AREAS; i++) {
- ctx->drawingareas[i].state =
- (i == ctx->active_area ? CURRENT : NOT_CURRENT);
- gtk_widget_queue_draw(ctx->drawingareas[i].area);
- }
-
- return false;
-
- fail:
- /*
- * If we didn't get the grab, reschedule ourself on a timer to try
- * again later.
- *
- * We have to do this rather than just trying once, because there
- * is at least one important situation in which the grab may fail
- * the first time: any user who is launching an add-key operation
- * off some kind of window manager hotkey will almost by
- * definition be running this script with a keyboard grab already
- * active, namely the one-key grab that the WM (or whatever) uses
- * to detect presses of the hotkey. So at the very least we have
- * to give the user time to release that key.
- */
- if (++ctx->nattempts >= 4) {
- cancel_askpass(ctx, "unable to grab keyboard after 5 seconds");
- } else {
- g_timeout_add(1000/8, try_grab_keyboard, ctx);
- }
- return false;
-}
-
-void realize(GtkWidget *widget, gpointer vctx)
-{
- struct askpass_ctx *ctx = (struct askpass_ctx *)vctx;
-
- gtk_grab_add(ctx->dialog);
-
- /*
- * Schedule the first attempt at the keyboard grab.
- */
- ctx->nattempts = 0;
-#if GTK_CHECK_VERSION(3,20,0)
- ctx->seat = NULL;
-#elif GTK_CHECK_VERSION(3,0,0)
- ctx->keyboard = NULL;
-#endif
-
- g_idle_add(try_grab_keyboard, ctx);
-}
-
-static const char *gtk_askpass_setup(struct askpass_ctx *ctx,
- const char *window_title,
- const char *prompt_text)
-{
- int i;
- GtkBox *action_area;
-
- ctx->passlen = 0;
- ctx->passsize = 2048;
- ctx->passphrase = snewn(ctx->passsize, char);
-
- /*
- * Create widgets.
- */
- ctx->dialog = our_dialog_new();
- gtk_window_set_title(GTK_WINDOW(ctx->dialog), window_title);
- gtk_window_set_position(GTK_WINDOW(ctx->dialog), GTK_WIN_POS_CENTER);
- g_signal_connect(G_OBJECT(ctx->dialog), "delete-event",
- G_CALLBACK(askpass_dialog_closed), ctx);
- ctx->promptlabel = gtk_label_new(prompt_text);
- align_label_left(GTK_LABEL(ctx->promptlabel));
- gtk_widget_show(ctx->promptlabel);
- gtk_label_set_line_wrap(GTK_LABEL(ctx->promptlabel), true);
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_label_set_width_chars(GTK_LABEL(ctx->promptlabel), 48);
-#endif
- int margin = string_width("MM");
-#if GTK_CHECK_VERSION(3,12,0)
- gtk_widget_set_margin_start(ctx->promptlabel, margin);
- gtk_widget_set_margin_end(ctx->promptlabel, margin);
-#else
- gtk_misc_set_padding(GTK_MISC(ctx->promptlabel), margin, 0);
-#endif
- our_dialog_add_to_content_area(GTK_WINDOW(ctx->dialog),
- ctx->promptlabel, true, true, 0);
-#if GTK_CHECK_VERSION(2,0,0)
- ctx->imc = gtk_im_multicontext_new();
-#endif
-#ifndef DRAW_DEFAULT_CAIRO
- {
- gboolean success[2];
- ctx->colmap = gdk_colormap_get_system();
- ctx->cols[0].red = ctx->cols[0].green = ctx->cols[0].blue = 0xFFFF;
- ctx->cols[1].red = ctx->cols[1].green = ctx->cols[1].blue = 0;
- ctx->cols[2].red = ctx->cols[2].green = ctx->cols[2].blue = 0x8000;
- gdk_colormap_alloc_colors(ctx->colmap, ctx->cols, 2,
- false, true, success);
- if (!success[0] || !success[1])
- return "unable to allocate colours";
- }
-#endif
-
- action_area = our_dialog_make_action_hbox(GTK_WINDOW(ctx->dialog));
-
- for (i = 0; i < N_DRAWING_AREAS; i++) {
- ctx->drawingareas[i].area = gtk_drawing_area_new();
-#ifndef DRAW_DEFAULT_CAIRO
- ctx->drawingareas[i].cols = ctx->cols;
-#endif
- ctx->drawingareas[i].state = GREYED_OUT;
- ctx->drawingareas[i].width = ctx->drawingareas[i].height = 0;
- /* It would be nice to choose this size in some more
- * context-sensitive way, like measuring the size of some
- * piece of template text. */
- gtk_widget_set_size_request(ctx->drawingareas[i].area, 32, 32);
- gtk_box_pack_end(action_area, ctx->drawingareas[i].area,
- true, true, 5);
- g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
- "configure_event",
- G_CALLBACK(configure_area),
- &ctx->drawingareas[i]);
-#if GTK_CHECK_VERSION(3,0,0)
- g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
- "draw",
- G_CALLBACK(draw_area),
- &ctx->drawingareas[i]);
-#else
- g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
- "expose_event",
- G_CALLBACK(expose_area),
- &ctx->drawingareas[i]);
-#endif
-
-#if GTK_CHECK_VERSION(3,0,0)
- g_object_set(G_OBJECT(ctx->drawingareas[i].area),
- "margin-bottom", 8, (const char *)NULL);
-#endif
-
- gtk_widget_show(ctx->drawingareas[i].area);
- }
- ctx->active_area = -1;
-
- /*
- * Arrange to receive key events. We don't really need to worry
- * from a UI perspective about which widget gets the events, as
- * long as we know which it is so we can catch them. So we'll pick
- * the prompt label at random, and we'll use gtk_grab_add to
- * ensure key events go to it.
- */
- gtk_widget_set_sensitive(ctx->dialog, true);
-
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_window_set_keep_above(GTK_WINDOW(ctx->dialog), true);
-#endif
-
- /*
- * Wait for the key-receiving widget to actually be created, in
- * order to call gtk_grab_add on it.
- */
- g_signal_connect(G_OBJECT(ctx->dialog), "realize",
- G_CALLBACK(realize), ctx);
-
- /*
- * Show the window.
- */
- gtk_widget_show(ctx->dialog);
-
- return NULL;
-}
-
-static void gtk_askpass_cleanup(struct askpass_ctx *ctx)
-{
-#if GTK_CHECK_VERSION(3,20,0)
- if (ctx->seat)
- gdk_seat_ungrab(ctx->seat);
-#elif GTK_CHECK_VERSION(3,0,0)
- if (ctx->keyboard)
- gdk_device_ungrab(ctx->keyboard, GDK_CURRENT_TIME);
-#else
- gdk_keyboard_ungrab(GDK_CURRENT_TIME);
-#endif
- gtk_grab_remove(ctx->promptlabel);
-
- if (ctx->passphrase) {
- assert(ctx->passlen < ctx->passsize);
- ctx->passphrase[ctx->passlen] = '\0';
- }
-
- gtk_widget_destroy(ctx->dialog);
-}
-
-static bool setup_gtk(const char *display)
-{
- static bool gtk_initialised = false;
- int argc;
- char *real_argv[3];
- char **argv = real_argv;
- bool ret;
-
- if (gtk_initialised)
- return true;
-
- argc = 0;
- argv[argc++] = dupstr("dummy");
- argv[argc++] = dupprintf("--display=%s", display);
- argv[argc] = NULL;
- ret = gtk_init_check(&argc, &argv);
- while (argc > 0)
- sfree(argv[--argc]);
-
- gtk_initialised = ret;
- return ret;
-}
-
-const bool buildinfo_gtk_relevant = true;
-
-char *gtk_askpass_main(const char *display, const char *wintitle,
- const char *prompt, bool *success)
-{
- struct askpass_ctx ctx[1];
- const char *err;
-
- ctx->passphrase = NULL;
- ctx->error_message = NULL;
-
- /* In case gtk_init hasn't been called yet by the program */
- if (!setup_gtk(display)) {
- *success = false;
- return dupstr("unable to initialise GTK");
- }
-
- if ((err = gtk_askpass_setup(ctx, wintitle, prompt)) != NULL) {
- *success = false;
- return dupprintf("%s", err);
- }
- setup_keypress_prng();
- gtk_main();
- cleanup_keypress_prng();
- gtk_askpass_cleanup(ctx);
-
- if (ctx->passphrase) {
- *success = true;
- return ctx->passphrase;
- } else {
- *success = false;
- return ctx->error_message;
- }
-}
-
-#ifdef TEST_ASKPASS
-void modalfatalbox(const char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "FATAL ERROR: ");
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
- exit(1);
-}
-
-int main(int argc, char **argv)
-{
- bool success;
- int exitcode;
- char *ret;
-
- gtk_init(&argc, &argv);
-
- if (argc != 2) {
- success = false;
- ret = dupprintf("usage: %s <prompt text>", argv[0]);
- } else {
- ret = gtk_askpass_main(NULL, "Enter passphrase", argv[1], &success);
- }
-
- if (!success) {
- fputs(ret, stderr);
- fputc('\n', stderr);
- exitcode = 1;
- } else {
- fputs(ret, stdout);
- fputc('\n', stdout);
- exitcode = 0;
- }
-
- smemclr(ret, strlen(ret));
- return exitcode;
-}
-#endif
diff --git a/UNIX/gtkcomm.c b/UNIX/gtkcomm.c
deleted file mode 100644
index fa52bfb4..00000000
--- a/UNIX/gtkcomm.c
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * gtkcomm.c: machinery in the GTK front end which is common to all
- * programs that run a session in a terminal window, and also common
- * across all _sessions_ rather than specific to one session. (Timers,
- * uxsel etc.)
- */
-
-#define _GNU_SOURCE
-
-#include <string.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-#include <signal.h>
-#include <stdio.h>
-#include <time.h>
-#include <errno.h>
-#include <locale.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <gtk/gtk.h>
-#if !GTK_CHECK_VERSION(3,0,0)
-#include <gdk/gdkkeysyms.h>
-#endif
-
-#if GTK_CHECK_VERSION(2,0,0)
-#include <gtk/gtkimmodule.h>
-#endif
-
-#define MAY_REFER_TO_GTK_IN_HEADERS
-
-#include "putty.h"
-#include "terminal.h"
-#include "gtkcompat.h"
-#include "gtkfont.h"
-#include "gtkmisc.h"
-
-#ifndef NOT_X_WINDOWS
-#include <gdk/gdkx.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-#endif
-
-#define CAT2(x,y) x ## y
-#define CAT(x,y) CAT2(x,y)
-#define ASSERT(x) enum {CAT(assertion_,__LINE__) = 1 / (x)}
-
-#if GTK_CHECK_VERSION(2,0,0)
-ASSERT(sizeof(long) <= sizeof(gsize));
-#define LONG_TO_GPOINTER(l) GSIZE_TO_POINTER(l)
-#define GPOINTER_TO_LONG(p) GPOINTER_TO_SIZE(p)
-#else /* Gtk 1.2 */
-ASSERT(sizeof(long) <= sizeof(gpointer));
-#define LONG_TO_GPOINTER(l) ((gpointer)(long)(l))
-#define GPOINTER_TO_LONG(p) ((long)(p))
-#endif
-
-/* ----------------------------------------------------------------------
- * File descriptors and uxsel.
- */
-
-struct uxsel_id {
-#if GTK_CHECK_VERSION(2,0,0)
- GIOChannel *chan;
- guint watch_id;
-#else
- int id;
-#endif
-};
-
-#if GTK_CHECK_VERSION(2,0,0)
-gboolean fd_input_func(GIOChannel *source, GIOCondition condition,
- gpointer data)
-{
- int sourcefd = g_io_channel_unix_get_fd(source);
- /*
- * We must process exceptional notifications before ordinary
- * readability ones, or we may go straight past the urgent
- * marker.
- */
- if (condition & G_IO_PRI)
- select_result(sourcefd, SELECT_X);
- if (condition & (G_IO_IN | G_IO_HUP))
- select_result(sourcefd, SELECT_R);
- if (condition & G_IO_OUT)
- select_result(sourcefd, SELECT_W);
-
- return true;
-}
-#else
-void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition)
-{
- if (condition & GDK_INPUT_EXCEPTION)
- select_result(sourcefd, SELECT_X);
- if (condition & GDK_INPUT_READ)
- select_result(sourcefd, SELECT_R);
- if (condition & GDK_INPUT_WRITE)
- select_result(sourcefd, SELECT_W);
-}
-#endif
-
-uxsel_id *uxsel_input_add(int fd, int rwx) {
- uxsel_id *id = snew(uxsel_id);
-
-#if GTK_CHECK_VERSION(2,0,0)
- int flags = 0;
- if (rwx & SELECT_R) flags |= G_IO_IN | G_IO_HUP;
- if (rwx & SELECT_W) flags |= G_IO_OUT;
- if (rwx & SELECT_X) flags |= G_IO_PRI;
- id->chan = g_io_channel_unix_new(fd);
- g_io_channel_set_encoding(id->chan, NULL, NULL);
- id->watch_id = g_io_add_watch_full(id->chan, GDK_PRIORITY_REDRAW+1, flags,
- fd_input_func, NULL, NULL);
-#else
- int flags = 0;
- if (rwx & SELECT_R) flags |= GDK_INPUT_READ;
- if (rwx & SELECT_W) flags |= GDK_INPUT_WRITE;
- if (rwx & SELECT_X) flags |= GDK_INPUT_EXCEPTION;
- assert(flags);
- id->id = gdk_input_add(fd, flags, fd_input_func, NULL);
-#endif
-
- return id;
-}
-
-void uxsel_input_remove(uxsel_id *id) {
-#if GTK_CHECK_VERSION(2,0,0)
- g_source_remove(id->watch_id);
- g_io_channel_unref(id->chan);
-#else
- gdk_input_remove(id->id);
-#endif
- sfree(id);
-}
-
-/* ----------------------------------------------------------------------
- * Timers.
- */
-
-static guint timer_id = 0;
-
-static gint timer_trigger(gpointer data)
-{
- unsigned long now = GPOINTER_TO_LONG(data);
- unsigned long next, then;
- long ticks;
-
- /*
- * Destroy the timer we got here on.
- */
- if (timer_id) {
- g_source_remove(timer_id);
- timer_id = 0;
- }
-
- /*
- * run_timers() may cause a call to timer_change_notify, in which
- * case a new timer will already have been set up and left in
- * timer_id. If it hasn't, and run_timers reports that some timing
- * still needs to be done, we do it ourselves.
- */
- if (run_timers(now, &next) && !timer_id) {
- then = now;
- now = GETTICKCOUNT();
- if (now - then > next - then)
- ticks = 0;
- else
- ticks = next - now;
- timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next));
- }
-
- /*
- * Returning false means 'don't call this timer again', which
- * _should_ be redundant given that we removed it above, but just
- * in case, return false anyway.
- */
- return false;
-}
-
-void timer_change_notify(unsigned long next)
-{
- long ticks;
-
- if (timer_id)
- g_source_remove(timer_id);
-
- ticks = next - GETTICKCOUNT();
- if (ticks <= 0)
- ticks = 1; /* just in case */
-
- timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next));
-}
-
-/* ----------------------------------------------------------------------
- * Toplevel callbacks.
- */
-
-static guint toplevel_callback_idle_id;
-static bool idle_fn_scheduled;
-
-static void notify_toplevel_callback(void *);
-
-static gint idle_toplevel_callback_func(gpointer data)
-{
- run_toplevel_callbacks();
-
- /*
- * If we've emptied our toplevel callback queue, unschedule
- * ourself. Otherwise, leave ourselves pending so we'll be called
- * again to deal with more callbacks after another round of the
- * event loop.
- */
- if (!toplevel_callback_pending() && idle_fn_scheduled) {
- g_source_remove(toplevel_callback_idle_id);
- idle_fn_scheduled = false;
- }
-
- return true;
-}
-
-static void notify_toplevel_callback(void *vctx)
-{
- if (!idle_fn_scheduled) {
- toplevel_callback_idle_id =
- g_idle_add(idle_toplevel_callback_func, NULL);
- idle_fn_scheduled = true;
- }
-}
-
-/* ----------------------------------------------------------------------
- * Setup function. The real main program must call this.
- */
-
-void gtkcomm_setup(void)
-{
- uxsel_init();
- request_callback_notifications(notify_toplevel_callback, NULL);
-}
diff --git a/UNIX/gtkmain.c b/UNIX/gtkmain.c
deleted file mode 100644
index ec8f7da4..00000000
--- a/UNIX/gtkmain.c
+++ /dev/null
@@ -1,673 +0,0 @@
-/*
- * gtkmain.c: the common main-program code between the straight-up
- * Unix PuTTY and pterm, which they do not share with the
- * multi-session gtkapp.c.
- */
-
-#define _GNU_SOURCE
-
-#include <string.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-#include <signal.h>
-#include <stdio.h>
-#include <time.h>
-#include <errno.h>
-#include <locale.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <gtk/gtk.h>
-#if !GTK_CHECK_VERSION(3,0,0)
-#include <gdk/gdkkeysyms.h>
-#endif
-
-#if GTK_CHECK_VERSION(2,0,0)
-#include <gtk/gtkimmodule.h>
-#endif
-
-#define MAY_REFER_TO_GTK_IN_HEADERS
-
-#include "putty.h"
-#include "terminal.h"
-#include "gtkcompat.h"
-#include "gtkfont.h"
-#include "gtkmisc.h"
-
-#ifndef NOT_X_WINDOWS
-#include <gdk/gdkx.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-#include "x11misc.h"
-#endif
-
-static char *progname, **gtkargvstart;
-static int ngtkargs;
-
-static const char *app_name = "pterm";
-
-char *x_get_default(const char *key)
-{
-#ifndef NOT_X_WINDOWS
- Display *disp;
- if ((disp = get_x11_display()) == NULL)
- return NULL;
- return XGetDefault(disp, app_name, key);
-#else
- return NULL;
-#endif
-}
-
-void fork_and_exec_self(int fd_to_close, ...)
-{
- /*
- * Re-execing ourself is not an exact science under Unix. I do
- * the best I can by using /proc/self/exe if available and by
- * assuming argv[0] can be found on $PATH if not.
- *
- * Note that we also have to reconstruct the elements of the
- * original argv which gtk swallowed, since the user wants the
- * new session to appear on the same X display as the old one.
- */
- char **args;
- va_list ap;
- int i, n;
- int pid;
-
- /*
- * Collect the arguments with which to re-exec ourself.
- */
- va_start(ap, fd_to_close);
- n = 2; /* progname and terminating NULL */
- n += ngtkargs;
- while (va_arg(ap, char *) != NULL)
- n++;
- va_end(ap);
-
- args = snewn(n, char *);
- args[0] = progname;
- args[n-1] = NULL;
- for (i = 0; i < ngtkargs; i++)
- args[i+1] = gtkargvstart[i];
-
- i++;
- va_start(ap, fd_to_close);
- while ((args[i++] = va_arg(ap, char *)) != NULL);
- va_end(ap);
-
- assert(i == n);
-
- /*
- * Do the double fork.
- */
- pid = fork();
- if (pid < 0) {
- perror("fork");
- sfree(args);
- return;
- }
-
- if (pid == 0) {
- int pid2 = fork();
- if (pid2 < 0) {
- perror("fork");
- _exit(1);
- } else if (pid2 > 0) {
- /*
- * First child has successfully forked second child. My
- * Work Here Is Done. Note the use of _exit rather than
- * exit: the latter appears to cause destroy messages
- * to be sent to the X server. I suspect gtk uses
- * atexit.
- */
- _exit(0);
- }
-
- /*
- * If we reach here, we are the second child, so we now
- * actually perform the exec.
- */
- if (fd_to_close >= 0)
- close(fd_to_close);
-
- execv("/proc/self/exe", args);
- execvp(progname, args);
- perror("exec");
- _exit(127);
-
- } else {
- int status;
- sfree(args);
- waitpid(pid, &status, 0);
- }
-
-}
-
-void launch_duplicate_session(Conf *conf)
-{
- /*
- * For this feature we must marshal conf and (possibly) pty_argv
- * into a byte stream, create a pipe, and send this byte stream
- * to the child through the pipe.
- */
- int i, ret;
- strbuf *serialised;
- char option[80];
- int pipefd[2];
-
- if (pipe(pipefd) < 0) {
- perror("pipe");
- return;
- }
-
- serialised = strbuf_new();
-
- conf_serialise(BinarySink_UPCAST(serialised), conf);
- if (use_pty_argv && pty_argv)
- for (i = 0; pty_argv[i]; i++)
- put_asciz(serialised, pty_argv[i]);
-
- sprintf(option, "---[%d,%zu]", pipefd[0], serialised->len);
- noncloexec(pipefd[0]);
- fork_and_exec_self(pipefd[1], option, NULL);
- close(pipefd[0]);
-
- i = ret = 0;
- while (i < serialised->len &&
- (ret = write(pipefd[1], serialised->s + i,
- serialised->len - i)) > 0)
- i += ret;
- if (ret < 0)
- perror("write to pipe");
- close(pipefd[1]);
- strbuf_free(serialised);
-}
-
-void launch_new_session(void)
-{
- fork_and_exec_self(-1, NULL);
-}
-
-void launch_saved_session(const char *str)
-{
- fork_and_exec_self(-1, "-load", str, NULL);
-}
-
-int read_dupsession_data(Conf *conf, char *arg)
-{
- int fd, i, ret, size;
- char *data;
- BinarySource src[1];
-
- if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) {
- fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg);
- exit(1);
- }
-
- data = snewn(size, char);
- i = ret = 0;
- while (i < size && (ret = read(fd, data + i, size - i)) > 0)
- i += ret;
- if (ret < 0) {
- perror("read from pipe");
- exit(1);
- } else if (i < size) {
- fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n",
- appname);
- exit(1);
- }
-
- BinarySource_BARE_INIT(src, data, size);
- if (!conf_deserialise(conf, src)) {
- fprintf(stderr, "%s: malformed Duplicate Session data\n", appname);
- exit(1);
- }
- if (use_pty_argv) {
- int pty_argc = 0;
- size_t argv_startpos = src->pos;
-
- while (get_asciz(src), !get_err(src))
- pty_argc++;
-
- src->err = BSE_NO_ERROR;
-
- if (pty_argc > 0) {
- src->pos = argv_startpos;
-
- pty_argv = snewn(pty_argc + 1, char *);
- pty_argv[pty_argc] = NULL;
- for (i = 0; i < pty_argc; i++)
- pty_argv[i] = dupstr(get_asciz(src));
- }
- }
-
- if (get_err(src) || get_avail(src) > 0) {
- fprintf(stderr, "%s: malformed Duplicate Session data\n", appname);
- exit(1);
- }
-
- sfree(data);
- return 0;
-}
-
-static void help(FILE *fp) {
- if(fprintf(fp,
-"pterm option summary:\n"
-"\n"
-" --display DISPLAY Specify X display to use (note '--')\n"
-" -name PREFIX Prefix when looking up resources (default: pterm)\n"
-" -fn FONT Normal text font\n"
-" -fb FONT Bold text font\n"
-" -geometry GEOMETRY Position and size of window (size in characters)\n"
-" -sl LINES Number of lines of scrollback\n"
-" -fg COLOUR, -bg COLOUR Foreground/background colour\n"
-" -bfg COLOUR, -bbg COLOUR Foreground/background bold colour\n"
-" -cfg COLOUR, -bfg COLOUR Foreground/background cursor colour\n"
-" -T TITLE Window title\n"
-" -ut, +ut Do(default) or do not update utmp\n"
-" -ls, +ls Do(default) or do not make shell a login shell\n"
-" -sb, +sb Do(default) or do not display a scrollbar\n"
-" -log PATH, -sessionlog PATH Log all output to a file\n"
-" -nethack Map numeric keypad to hjklyubn direction keys\n"
-" -xrm RESOURCE-STRING Set an X resource\n"
-" -e COMMAND [ARGS...] Execute command (consumes all remaining args)\n"
- ) < 0 || fflush(fp) < 0) {
- perror("output error");
- exit(1);
- }
-}
-
-static void version(FILE *fp) {
- char *buildinfo_text = buildinfo("\n");
- if(fprintf(fp, "%s: %s\n%s\n", appname, ver, buildinfo_text) < 0 ||
- fflush(fp) < 0) {
- perror("output error");
- exit(1);
- }
- sfree(buildinfo_text);
-}
-
-static const char *geometry_string;
-
-void cmdline_error(const char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "%s: ", appname);
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
- exit(1);
-}
-
-void window_setup_error(const char *errmsg)
-{
- fprintf(stderr, "%s: %s\n", appname, errmsg);
- exit(1);
-}
-
-bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf)
-{
- bool err = false;
- char *val;
-
- /*
- * Macros to make argument handling easier.
- *
- * Note that because they need to call `continue', they cannot be
- * contained in the usual do {...} while (0) wrapper to make them
- * syntactically single statements. I use the alternative if (1)
- * {...} else ((void)0).
- */
-#define EXPECTS_ARG if (1) { \
- if (--argc <= 0) { \
- err = true; \
- fprintf(stderr, "%s: %s expects an argument\n", appname, p); \
- continue; \
- } else \
- val = *++argv; \
- } else ((void)0)
-#define SECOND_PASS_ONLY if (1) { \
- if (!do_everything) \
- continue; \
- } else ((void)0)
-
- while (--argc > 0) {
- const char *p = *++argv;
- int ret;
-
- /*
- * Shameless cheating. Debian requires all X terminal
- * emulators to support `-T title'; but
- * cmdline_process_param will eat -T (it means no-pty) and
- * complain that pterm doesn't support it. So, in pterm
- * only, we convert -T into -title.
- */
- if ((cmdline_tooltype & TOOLTYPE_NONNETWORK) &&
- !strcmp(p, "-T"))
- p = "-title";
-
- ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
- do_everything ? 1 : -1, conf);
-
- if (ret == -2) {
- cmdline_error("option \"%s\" requires an argument", p);
- } else if (ret == 2) {
- --argc, ++argv; /* skip next argument */
- continue;
- } else if (ret == 1) {
- continue;
- }
-
- if (!strcmp(p, "-fn") || !strcmp(p, "-font")) {
- FontSpec *fs;
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- fs = fontspec_new(val);
- conf_set_fontspec(conf, CONF_font, fs);
- fontspec_free(fs);
-
- } else if (!strcmp(p, "-fb")) {
- FontSpec *fs;
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- fs = fontspec_new(val);
- conf_set_fontspec(conf, CONF_boldfont, fs);
- fontspec_free(fs);
-
- } else if (!strcmp(p, "-fw")) {
- FontSpec *fs;
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- fs = fontspec_new(val);
- conf_set_fontspec(conf, CONF_widefont, fs);
- fontspec_free(fs);
-
- } else if (!strcmp(p, "-fwb")) {
- FontSpec *fs;
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- fs = fontspec_new(val);
- conf_set_fontspec(conf, CONF_wideboldfont, fs);
- fontspec_free(fs);
-
- } else if (!strcmp(p, "-cs")) {
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- conf_set_str(conf, CONF_line_codepage, val);
-
- } else if (!strcmp(p, "-geometry")) {
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- geometry_string = val;
- } else if (!strcmp(p, "-sl")) {
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- conf_set_int(conf, CONF_savelines, atoi(val));
-
- } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") ||
- !strcmp(p, "-bfg") || !strcmp(p, "-bbg") ||
- !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) {
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
-
- {
-#if GTK_CHECK_VERSION(3,0,0)
- GdkRGBA rgba;
- bool success = gdk_rgba_parse(&rgba, val);
-#else
- GdkColor col;
- bool success = gdk_color_parse(val, &col);
-#endif
-
- if (!success) {
- err = true;
- fprintf(stderr, "%s: unable to parse colour \"%s\"\n",
- appname, val);
- } else {
-#if GTK_CHECK_VERSION(3,0,0)
- int r = rgba.red * 255;
- int g = rgba.green * 255;
- int b = rgba.blue * 255;
-#else
- int r = col.red / 256;
- int g = col.green / 256;
- int b = col.blue / 256;
-#endif
-
- int index;
- index = (!strcmp(p, "-fg") ? 0 :
- !strcmp(p, "-bg") ? 2 :
- !strcmp(p, "-bfg") ? 1 :
- !strcmp(p, "-bbg") ? 3 :
- !strcmp(p, "-cfg") ? 4 :
- !strcmp(p, "-cbg") ? 5 : -1);
- assert(index != -1);
-
- conf_set_int_int(conf, CONF_colours, index*3+0, r);
- conf_set_int_int(conf, CONF_colours, index*3+1, g);
- conf_set_int_int(conf, CONF_colours, index*3+2, b);
- }
- }
-
- } else if (use_pty_argv && !strcmp(p, "-e")) {
- /* This option swallows all further arguments. */
- if (!do_everything)
- break;
-
- if (--argc > 0) {
- int i;
- pty_argv = snewn(argc+1, char *);
- ++argv;
- for (i = 0; i < argc; i++)
- pty_argv[i] = argv[i];
- pty_argv[argc] = NULL;
- break; /* finished command-line processing */
- } else
- err = true, fprintf(stderr, "%s: -e expects an argument\n",
- appname);
-
- } else if (!strcmp(p, "-title")) {
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- conf_set_str(conf, CONF_wintitle, val);
-
- } else if (!strcmp(p, "-log")) {
- Filename *fn;
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- fn = filename_from_str(val);
- conf_set_filename(conf, CONF_logfilename, fn);
- conf_set_int(conf, CONF_logtype, LGTYP_DEBUG);
- filename_free(fn);
-
- } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) {
- SECOND_PASS_ONLY;
- conf_set_bool(conf, CONF_stamp_utmp, false);
-
- } else if (!strcmp(p, "-ut")) {
- SECOND_PASS_ONLY;
- conf_set_bool(conf, CONF_stamp_utmp, true);
-
- } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) {
- SECOND_PASS_ONLY;
- conf_set_bool(conf, CONF_login_shell, false);
-
- } else if (!strcmp(p, "-ls")) {
- SECOND_PASS_ONLY;
- conf_set_bool(conf, CONF_login_shell, true);
-
- } else if (!strcmp(p, "-nethack")) {
- SECOND_PASS_ONLY;
- conf_set_bool(conf, CONF_nethack_keypad, true);
-
- } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) {
- SECOND_PASS_ONLY;
- conf_set_bool(conf, CONF_scrollbar, false);
-
- } else if (!strcmp(p, "-sb")) {
- SECOND_PASS_ONLY;
- conf_set_bool(conf, CONF_scrollbar, true);
-
- } else if (!strcmp(p, "-name")) {
- EXPECTS_ARG;
- app_name = val;
-
- } else if (!strcmp(p, "-xrm")) {
- EXPECTS_ARG;
- provide_xrm_string(val, appname);
-
- } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) {
- help(stdout);
- exit(0);
-
- } else if(!strcmp(p, "-version") || !strcmp(p, "--version")) {
- version(stdout);
- exit(0);
-
- } else if (!strcmp(p, "-pgpfp")) {
- pgp_fingerprints();
- exit(1);
-
- } else if (p[0] != '-') {
- /* Non-option arguments not handled by cmdline.c are errors. */
- if (do_everything) {
- err = true;
- fprintf(stderr, "%s: unexpected non-option argument '%s'\n",
- appname, p);
- }
-
- } else {
- err = true;
- fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p);
- }
- }
-
- return err;
-}
-
-GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend)
-{
- return gtk_window_new(GTK_WINDOW_TOPLEVEL);
-}
-
-const bool buildinfo_gtk_relevant = true;
-
-struct post_initial_config_box_ctx {
- Conf *conf;
- const char *geometry_string;
-};
-
-static void post_initial_config_box(void *vctx, int result)
-{
- struct post_initial_config_box_ctx ctx =
- *(struct post_initial_config_box_ctx *)vctx;
- sfree(vctx);
-
- if (result > 0) {
- new_session_window(ctx.conf, ctx.geometry_string);
- } else {
- /* In this main(), which only runs one session in total, a
- * negative result from the initial config box means we simply
- * terminate. */
- conf_free(ctx.conf);
- gtk_main_quit();
- }
-}
-
-void session_window_closed(void)
-{
- gtk_main_quit();
-}
-
-int main(int argc, char **argv)
-{
- Conf *conf;
- bool need_config_box;
-
- setlocale(LC_CTYPE, "");
-
- /* Call the function in ux{putty,pterm}.c to do app-type
- * specific setup */
- setup(true); /* true means we are a one-session process */
-
- progname = argv[0];
-
- /*
- * Copy the original argv before letting gtk_init fiddle with
- * it. It will be required later.
- */
- {
- int i, oldargc;
- gtkargvstart = snewn(argc-1, char *);
- for (i = 1; i < argc; i++)
- gtkargvstart[i-1] = dupstr(argv[i]);
- oldargc = argc;
- gtk_init(&argc, &argv);
- ngtkargs = oldargc - argc;
- }
-
- conf = conf_new();
-
- gtkcomm_setup();
-
- /*
- * Block SIGPIPE: if we attempt Duplicate Session or similar and
- * it falls over in some way, we certainly don't want SIGPIPE
- * terminating the main pterm/PuTTY. However, we'll have to
- * unblock it again when pterm forks.
- */
- block_signal(SIGPIPE, true);
-
- if (argc > 1 && !strncmp(argv[1], "---", 3)) {
- read_dupsession_data(conf, argv[1]);
- /* Splatter this argument so it doesn't clutter a ps listing */
- smemclr(argv[1], strlen(argv[1]));
-
- assert(!dup_check_launchable || conf_launchable(conf));
- need_config_box = false;
- } else {
- if (do_cmdline(argc, argv, false, conf))
- exit(1); /* pre-defaults pass to get -class */
- do_defaults(NULL, conf);
- if (do_cmdline(argc, argv, true, conf))
- exit(1); /* post-defaults, do everything */
-
- cmdline_run_saved(conf);
-
- if (cmdline_tooltype & TOOLTYPE_HOST_ARG)
- need_config_box = !cmdline_host_ok(conf);
- else
- need_config_box = false;
- }
-
- if (need_config_box) {
- /*
- * Put up the initial config box, which will pass the provided
- * parameters (with conf updated) to new_session_window() when
- * (if) the user selects Open. Or it might close without
- * creating a session window, if the user selects Cancel. Or
- * it might just create the session window immediately if this
- * is a pterm-style app which doesn't have an initial config
- * box at all.
- */
- struct post_initial_config_box_ctx *ctx =
- snew(struct post_initial_config_box_ctx);
- ctx->conf = conf;
- ctx->geometry_string = geometry_string;
- initial_config_box(conf, post_initial_config_box, ctx);
- } else {
- /*
- * No initial config needed; just create the session window
- * now.
- */
- new_session_window(conf, geometry_string);
- }
-
- gtk_main();
-
- return 0;
-}
diff --git a/UNIX/gtkmisc.c b/UNIX/gtkmisc.c
deleted file mode 100644
index c0c9682a..00000000
--- a/UNIX/gtkmisc.c
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Miscellaneous GTK helper functions.
- */
-
-#include <assert.h>
-#include <stdarg.h>
-#include <ctype.h>
-#include <time.h>
-
-#include <gtk/gtk.h>
-#if !GTK_CHECK_VERSION(3,0,0)
-#include <gdk/gdkkeysyms.h>
-#endif
-
-#include "putty.h"
-#include "gtkcompat.h"
-
-#ifndef NOT_X_WINDOWS
-#include <gdk/gdkx.h>
-#include <X11/Xlib.h>
-#endif
-
-void get_label_text_dimensions(const char *text, int *width, int *height)
-{
- /*
- * Determine the dimensions of a piece of text in the standard
- * font used in GTK interface elements like labels. We do this by
- * instantiating an actual GtkLabel, and then querying its size.
- *
- * But GTK2 and GTK3 require us to query the size completely
- * differently. I'm sure there ought to be an easier approach than
- * the way I'm doing this in GTK3, too!
- */
- GtkWidget *label = gtk_label_new(text);
-
-#if GTK_CHECK_VERSION(3,0,0)
- PangoLayout *layout = gtk_label_get_layout(GTK_LABEL(label));
- PangoRectangle logrect;
- pango_layout_get_extents(layout, NULL, &logrect);
- if (width)
- *width = logrect.width / PANGO_SCALE;
- if (height)
- *height = logrect.height / PANGO_SCALE;
-#else
- GtkRequisition req;
- gtk_widget_size_request(label, &req);
- if (width)
- *width = req.width;
- if (height)
- *height = req.height;
-#endif
-
- g_object_ref_sink(G_OBJECT(label));
-#if GTK_CHECK_VERSION(2,10,0)
- g_object_unref(label);
-#endif
-}
-
-int string_width(const char *text)
-{
- int ret;
- get_label_text_dimensions(text, &ret, NULL);
- return ret;
-}
-
-void align_label_left(GtkLabel *label)
-{
-#if GTK_CHECK_VERSION(3,16,0)
- gtk_label_set_xalign(label, 0.0);
-#elif GTK_CHECK_VERSION(3,14,0)
- gtk_widget_set_halign(GTK_WIDGET(label), GTK_ALIGN_START);
-#else
- gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-#endif
-}
-
-/* ----------------------------------------------------------------------
- * Functions to arrange controls in a basically dialog-like window.
- *
- * The best method for doing this has varied wildly with versions of
- * GTK, hence the set of wrapper functions here.
- *
- * In GTK 1, a GtkDialog has an 'action_area' at the bottom, which is
- * a GtkHBox which stretches to cover the full width of the dialog. So
- * we can either add buttons or other widgets to that box directly, or
- * alternatively we can fill the hbox with some layout class of our
- * own such as a Columns widget.
- *
- * In GTK 2, the action area has become a GtkHButtonBox, and its
- * layout behaviour seems to be different and not what we want. So
- * instead we abandon the dialog's action area completely: we
- * gtk_widget_hide() it in the below code, and we also call
- * gtk_dialog_set_has_separator() to remove the separator above it. We
- * then insert our own action area into the end of the dialog's main
- * vbox, and add our own separator above that.
- *
- * In GTK 3, we typically don't even want to use GtkDialog at all,
- * because GTK 3 has become a lot more restrictive about what you can
- * sensibly use GtkDialog for - it deprecates direct access to the
- * action area in favour of making you provide nothing but
- * dialog-ending buttons in the form of (text, response code) pairs,
- * so you can't put any other kind of control in there, or fiddle with
- * alignment and positioning, or even have a button that _doesn't_ end
- * the dialog (e.g. 'View Licence' in our About box). So instead of
- * GtkDialog, we use a straight-up GtkWindow and have it contain a
- * vbox as its (unique) child widget; and we implement the action area
- * by adding a separator and another widget at the bottom of that
- * vbox.
- */
-
-GtkWidget *our_dialog_new(void)
-{
-#if GTK_CHECK_VERSION(3,0,0)
- /*
- * See comment in our_dialog_set_action_area(): in GTK 3, we use
- * GtkWindow in place of GtkDialog for most purposes.
- */
- GtkWidget *w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
- GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
- gtk_container_add(GTK_CONTAINER(w), vbox);
- gtk_widget_show(vbox);
- return w;
-#else
- return gtk_dialog_new();
-#endif
-}
-
-void our_dialog_set_action_area(GtkWindow *dlg, GtkWidget *w)
-{
-#if !GTK_CHECK_VERSION(2,0,0)
-
- gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area),
- w, true, true, 0);
-
-#elif !GTK_CHECK_VERSION(3,0,0)
-
- GtkWidget *align;
- align = gtk_alignment_new(0, 0, 1, 1);
- gtk_container_add(GTK_CONTAINER(align), w);
- /*
- * The purpose of this GtkAlignment is to provide padding
- * around the buttons. The padding we use is twice the padding
- * used in our GtkColumns, because we nest two GtkColumns most
- * of the time (one separating the tree view from the main
- * controls, and another for the main controls themselves).
- */
-#if GTK_CHECK_VERSION(2,4,0)
- gtk_alignment_set_padding(GTK_ALIGNMENT(align), 8, 8, 8, 8);
-#endif
- gtk_widget_show(align);
- gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))),
- align, false, true, 0);
-
- w = gtk_hseparator_new();
- gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))),
- w, false, true, 0);
- gtk_widget_show(w);
- gtk_widget_hide(gtk_dialog_get_action_area(GTK_DIALOG(dlg)));
- g_object_set(G_OBJECT(dlg), "has-separator", true, (const char *)NULL);
-
-#else /* GTK 3 */
-
- /* GtkWindow is a GtkBin, hence contains exactly one child, which
- * here we always expect to be a vbox */
- GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(dlg)));
- GtkWidget *sep;
-
- g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL);
- gtk_box_pack_end(vbox, w, false, true, 0);
-
- sep = gtk_hseparator_new();
- gtk_box_pack_end(vbox, sep, false, true, 0);
- gtk_widget_show(sep);
-
-#endif
-}
-
-GtkBox *our_dialog_make_action_hbox(GtkWindow *dlg)
-{
-#if GTK_CHECK_VERSION(3,0,0)
- GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
- our_dialog_set_action_area(dlg, hbox);
- g_object_set(G_OBJECT(hbox), "margin", 0, (const char *)NULL);
- g_object_set(G_OBJECT(hbox), "spacing", 8, (const char *)NULL);
- gtk_widget_show(hbox);
- return GTK_BOX(hbox);
-#else /* not GTK 3 */
- return GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(dlg)));
-#endif
-}
-
-void our_dialog_add_to_content_area(GtkWindow *dlg, GtkWidget *w,
- bool expand, bool fill, guint padding)
-{
-#if GTK_CHECK_VERSION(3,0,0)
- /* GtkWindow is a GtkBin, hence contains exactly one child, which
- * here we always expect to be a vbox */
- GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(dlg)));
-
- gtk_box_pack_start(vbox, w, expand, fill, padding);
-#else
- gtk_box_pack_start
- (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))),
- w, expand, fill, padding);
-#endif
-}
-
-char *buildinfo_gtk_version(void)
-{
- return dupprintf("%d.%d.%d",
- GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION);
-}
-
-#ifndef NOT_X_WINDOWS
-Display *get_x11_display(void)
-{
-#if GTK_CHECK_VERSION(3,0,0)
- if (!GDK_IS_X11_DISPLAY(gdk_display_get_default()))
- return NULL;
-#endif
- return GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
-}
-#endif
diff --git a/UNIX/procnet.c b/UNIX/procnet.c
index 43b1f055..09373956 100644
--- a/UNIX/procnet.c
+++ b/UNIX/procnet.c
@@ -170,8 +170,8 @@ static char *format_sockaddr(const void *addr, int family)
const uint32_t *addrwords = (const uint32_t *)a->sin6_addr.s6_addr;
for (int i = 0; i < 4; i++)
- strbuf_catf(sb, "%08X", addrwords[i]);
- strbuf_catf(sb, ":%04X", ntohs(a->sin6_port));
+ put_fmt(sb, "%08X", addrwords[i]);
+ put_fmt(sb, ":%04X", ntohs(a->sin6_port));
return strbuf_to_str(sb);
} else {
diff --git a/UNIX/uxfdsock.c b/UNIX/uxfdsock.c
deleted file mode 100644
index 7697d995..00000000
--- a/UNIX/uxfdsock.c
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * uxfdsock.c: implementation of Socket that just talks to two
- * existing input and output file descriptors.
- */
-
-#include <stdio.h>
-#include <assert.h>
-#include <errno.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-#include "tree234.h"
-#include "putty.h"
-#include "network.h"
-
-typedef struct FdSocket {
- int outfd, infd, inerrfd;
-
- bufchain pending_output_data;
- bufchain pending_input_data;
- ProxyStderrBuf psb;
- enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
-
- int pending_error;
-
- Plug *plug;
-
- Socket sock;
-} FdSocket;
-
-static void fdsocket_select_result_input(int fd, int event);
-static void fdsocket_select_result_output(int fd, int event);
-static void fdsocket_select_result_input_error(int fd, int event);
-
-/*
- * Trees to look up the fds in.
- */
-static tree234 *fdsocket_by_outfd;
-static tree234 *fdsocket_by_infd;
-static tree234 *fdsocket_by_inerrfd;
-
-static int fdsocket_infd_cmp(void *av, void *bv)
-{
- FdSocket *a = (FdSocket *)av;
- FdSocket *b = (FdSocket *)bv;
- if (a->infd < b->infd)
- return -1;
- if (a->infd > b->infd)
- return +1;
- return 0;
-}
-static int fdsocket_infd_find(void *av, void *bv)
-{
- int a = *(int *)av;
- FdSocket *b = (FdSocket *)bv;
- if (a < b->infd)
- return -1;
- if (a > b->infd)
- return +1;
- return 0;
-}
-static int fdsocket_inerrfd_cmp(void *av, void *bv)
-{
- FdSocket *a = (FdSocket *)av;
- FdSocket *b = (FdSocket *)bv;
- if (a->inerrfd < b->inerrfd)
- return -1;
- if (a->inerrfd > b->inerrfd)
- return +1;
- return 0;
-}
-static int fdsocket_inerrfd_find(void *av, void *bv)
-{
- int a = *(int *)av;
- FdSocket *b = (FdSocket *)bv;
- if (a < b->inerrfd)
- return -1;
- if (a > b->inerrfd)
- return +1;
- return 0;
-}
-static int fdsocket_outfd_cmp(void *av, void *bv)
-{
- FdSocket *a = (FdSocket *)av;
- FdSocket *b = (FdSocket *)bv;
- if (a->outfd < b->outfd)
- return -1;
- if (a->outfd > b->outfd)
- return +1;
- return 0;
-}
-static int fdsocket_outfd_find(void *av, void *bv)
-{
- int a = *(int *)av;
- FdSocket *b = (FdSocket *)bv;
- if (a < b->outfd)
- return -1;
- if (a > b->outfd)
- return +1;
- return 0;
-}
-
-static Plug *fdsocket_plug(Socket *s, Plug *p)
-{
- FdSocket *fds = container_of(s, FdSocket, sock);
- Plug *ret = fds->plug;
- if (p)
- fds->plug = p;
- return ret;
-}
-
-static void fdsocket_close(Socket *s)
-{
- FdSocket *fds = container_of(s, FdSocket, sock);
-
- if (fds->outfd >= 0) {
- del234(fdsocket_by_outfd, fds);
- uxsel_del(fds->outfd);
- close(fds->outfd);
- }
-
- if (fds->infd >= 0) {
- del234(fdsocket_by_infd, fds);
- uxsel_del(fds->infd);
- close(fds->infd);
- }
-
- if (fds->inerrfd >= 0) {
- del234(fdsocket_by_inerrfd, fds);
- uxsel_del(fds->inerrfd);
- close(fds->inerrfd);
- }
-
- bufchain_clear(&fds->pending_input_data);
- bufchain_clear(&fds->pending_output_data);
-
- delete_callbacks_for_context(fds);
-
- sfree(fds);
-}
-
-static void fdsocket_error_callback(void *vs)
-{
- FdSocket *fds = (FdSocket *)vs;
-
- /*
- * Just in case other socket work has caused this socket to vanish
- * or become somehow non-erroneous before this callback arrived...
- */
- if (!fds->pending_error)
- return;
-
- /*
- * An error has occurred on this socket. Pass it to the plug.
- */
- plug_closing(fds->plug, strerror(fds->pending_error),
- fds->pending_error, 0);
-}
-
-static int fdsocket_try_send(FdSocket *fds)
-{
- int sent = 0;
-
- while (bufchain_size(&fds->pending_output_data) > 0) {
- ssize_t ret;
-
- ptrlen data = bufchain_prefix(&fds->pending_output_data);
- ret = write(fds->outfd, data.ptr, data.len);
- noise_ultralight(NOISE_SOURCE_IOID, ret);
- if (ret < 0 && errno != EWOULDBLOCK) {
- if (!fds->pending_error) {
- fds->pending_error = errno;
- queue_toplevel_callback(fdsocket_error_callback, fds);
- }
- return 0;
- } else if (ret <= 0) {
- break;
- } else {
- bufchain_consume(&fds->pending_output_data, ret);
- sent += ret;
- }
- }
-
- if (fds->outgoingeof == EOF_PENDING) {
- del234(fdsocket_by_outfd, fds);
- close(fds->outfd);
- uxsel_del(fds->outfd);
- fds->outfd = -1;
- fds->outgoingeof = EOF_SENT;
- }
-
- if (bufchain_size(&fds->pending_output_data) == 0)
- uxsel_del(fds->outfd);
- else
- uxsel_set(fds->outfd, SELECT_W, fdsocket_select_result_output);
-
- return sent;
-}
-
-static size_t fdsocket_write(Socket *s, const void *data, size_t len)
-{
- FdSocket *fds = container_of(s, FdSocket, sock);
-
- assert(fds->outgoingeof == EOF_NO);
-
- bufchain_add(&fds->pending_output_data, data, len);
-
- fdsocket_try_send(fds);
-
- return bufchain_size(&fds->pending_output_data);
-}
-
-static size_t fdsocket_write_oob(Socket *s, const void *data, size_t len)
-{
- /*
- * oob data is treated as inband; nasty, but nothing really
- * better we can do
- */
- return fdsocket_write(s, data, len);
-}
-
-static void fdsocket_write_eof(Socket *s)
-{
- FdSocket *fds = container_of(s, FdSocket, sock);
-
- assert(fds->outgoingeof == EOF_NO);
- fds->outgoingeof = EOF_PENDING;
-
- fdsocket_try_send(fds);
-}
-
-static void fdsocket_set_frozen(Socket *s, bool is_frozen)
-{
- FdSocket *fds = container_of(s, FdSocket, sock);
-
- if (fds->infd < 0)
- return;
-
- if (is_frozen)
- uxsel_del(fds->infd);
- else
- uxsel_set(fds->infd, SELECT_R, fdsocket_select_result_input);
-}
-
-static const char *fdsocket_socket_error(Socket *s)
-{
- return NULL;
-}
-
-static void fdsocket_select_result_input(int fd, int event)
-{
- FdSocket *fds;
- char buf[20480];
- int retd;
-
- if (!(fds = find234(fdsocket_by_infd, &fd, fdsocket_infd_find)))
- return;
-
- retd = read(fds->infd, buf, sizeof(buf));
- if (retd > 0) {
- plug_receive(fds->plug, 0, buf, retd);
- } else {
- if (retd < 0) {
- plug_closing(fds->plug, strerror(errno), errno, 0);
- } else {
- plug_closing(fds->plug, NULL, 0, 0);
- }
- del234(fdsocket_by_infd, fds);
- uxsel_del(fds->infd);
- close(fds->infd);
- fds->infd = -1;
- }
-}
-
-static void fdsocket_select_result_output(int fd, int event)
-{
- FdSocket *fds;
-
- if (!(fds = find234(fdsocket_by_outfd, &fd, fdsocket_outfd_find)))
- return;
-
- if (fdsocket_try_send(fds))
- plug_sent(fds->plug, bufchain_size(&fds->pending_output_data));
-}
-
-static void fdsocket_select_result_input_error(int fd, int event)
-{
- FdSocket *fds;
- char buf[20480];
- int retd;
-
- if (!(fds = find234(fdsocket_by_inerrfd, &fd, fdsocket_inerrfd_find)))
- return;
-
- retd = read(fd, buf, sizeof(buf));
- if (retd > 0) {
- log_proxy_stderr(fds->plug, &fds->psb, buf, retd);
- } else {
- del234(fdsocket_by_inerrfd, fds);
- uxsel_del(fds->inerrfd);
- close(fds->inerrfd);
- fds->inerrfd = -1;
- }
-}
-
-static const SocketVtable FdSocket_sockvt = {
- .plug = fdsocket_plug,
- .close = fdsocket_close,
- .write = fdsocket_write,
- .write_oob = fdsocket_write_oob,
- .write_eof = fdsocket_write_eof,
- .set_frozen = fdsocket_set_frozen,
- .socket_error = fdsocket_socket_error,
- .peer_info = NULL,
-};
-
-Socket *make_fd_socket(int infd, int outfd, int inerrfd, Plug *plug)
-{
- FdSocket *fds;
-
- fds = snew(FdSocket);
- fds->sock.vt = &FdSocket_sockvt;
- fds->plug = plug;
- fds->outgoingeof = EOF_NO;
- fds->pending_error = 0;
-
- fds->infd = infd;
- fds->outfd = outfd;
- fds->inerrfd = inerrfd;
-
- bufchain_init(&fds->pending_input_data);
- bufchain_init(&fds->pending_output_data);
- psb_init(&fds->psb);
-
- if (fds->outfd >= 0) {
- if (!fdsocket_by_outfd)
- fdsocket_by_outfd = newtree234(fdsocket_outfd_cmp);
- add234(fdsocket_by_outfd, fds);
- }
-
- if (fds->infd >= 0) {
- if (!fdsocket_by_infd)
- fdsocket_by_infd = newtree234(fdsocket_infd_cmp);
- add234(fdsocket_by_infd, fds);
- uxsel_set(fds->infd, SELECT_R, fdsocket_select_result_input);
- }
-
- if (fds->inerrfd >= 0) {
- assert(fds->inerrfd != fds->infd);
- if (!fdsocket_by_inerrfd)
- fdsocket_by_inerrfd = newtree234(fdsocket_inerrfd_cmp);
- add234(fdsocket_by_inerrfd, fds);
- uxsel_set(fds->inerrfd, SELECT_R, fdsocket_select_result_input_error);
- }
-
- return &fds->sock;
-}
diff --git a/UNIX/uxnogtk.c b/UNIX/uxnogtk.c
deleted file mode 100644
index c9028ebf..00000000
--- a/UNIX/uxnogtk.c
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * uxnogtk.c: link into non-GUI Unix programs so that they can tell
- * buildinfo about a lack of GTK.
- */
-
-#include "putty.h"
-
-char *buildinfo_gtk_version(void)
-{
- return NULL;
-}
diff --git a/UNIX/uxpeer.c b/UNIX/uxpeer.c
deleted file mode 100644
index 4ad26322..00000000
--- a/UNIX/uxpeer.c
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Unix: wrapper for getsockopt(SO_PEERCRED), conditionalised on
- * appropriate autoconfery.
- */
-
-#ifdef HAVE_CONFIG_H
-# include "uxconfig.h" /* leading space prevents mkfiles.pl trying to follow */
-#endif
-
-#ifdef HAVE_SO_PEERCRED
-#define _GNU_SOURCE
-#include <features.h>
-#endif
-
-#include <sys/socket.h>
-
-#include "putty.h"
-
-bool so_peercred(int fd, int *pid, int *uid, int *gid)
-{
-#ifdef HAVE_SO_PEERCRED
- struct ucred cr;
- socklen_t crlen = sizeof(cr);
- if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &crlen) == 0) {
- *pid = cr.pid;
- *uid = cr.uid;
- *gid = cr.gid;
- return true;
- }
-#endif
- return false;
-}
diff --git a/UNIX/uxpgnt.c b/UNIX/uxpgnt.c
deleted file mode 100644
index 21087d63..00000000
--- a/UNIX/uxpgnt.c
+++ /dev/null
@@ -1,1540 +0,0 @@
-/*
- * Unix Pageant, more or less similar to ssh-agent.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <assert.h>
-#include <signal.h>
-#include <ctype.h>
-
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <termios.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "misc.h"
-#include "pageant.h"
-
-void cmdline_error(const char *fmt, ...)
-{
- va_list ap;
- va_start(ap, fmt);
- console_print_error_msg_fmt_v("pageant", fmt, ap);
- va_end(ap);
- exit(1);
-}
-
-static void setup_sigchld_handler(void);
-
-typedef enum RuntimePromptType {
- RTPROMPT_UNAVAILABLE,
- RTPROMPT_DEBUG,
- RTPROMPT_GUI,
-} RuntimePromptType;
-
-static const char *progname;
-
-struct uxpgnt_client {
- FILE *logfp;
- strbuf *prompt_buf;
- RuntimePromptType prompt_type;
- bool prompt_active;
- PageantClientDialogId *dlgid;
- int passphrase_fd;
- int termination_pid;
-
- PageantListenerClient plc;
-};
-
-static void uxpgnt_log(PageantListenerClient *plc, const char *fmt, va_list ap)
-{
- struct uxpgnt_client *upc = container_of(plc, struct uxpgnt_client, plc);
-
- if (!upc->logfp)
- return;
-
- fprintf(upc->logfp, "pageant: ");
- vfprintf(upc->logfp, fmt, ap);
- fprintf(upc->logfp, "\n");
-}
-
-static int make_pipe_to_askpass(const char *msg)
-{
- int pipefds[2];
-
- setup_sigchld_handler();
-
- if (pipe(pipefds) < 0)
- return -1;
-
- pid_t pid = fork();
- if (pid < 0) {
- close(pipefds[0]);
- close(pipefds[1]);
- return -1;
- }
-
- if (pid == 0) {
- const char *args[5] = {
- progname, "--gui-prompt", "--askpass", msg, NULL
- };
-
- dup2(pipefds[1], 1);
- cloexec(pipefds[0]);
- cloexec(pipefds[1]);
-
- /*
- * See comment in fork_and_exec_self() in gtkmain.c.
- */
- execv("/proc/self/exe", (char **)args);
- execvp(progname, (char **)args);
- perror("exec");
- _exit(127);
- }
-
- close(pipefds[1]);
- return pipefds[0];
-}
-
-static bool uxpgnt_ask_passphrase(
- PageantListenerClient *plc, PageantClientDialogId *dlgid,
- const char *comment)
-{
- struct uxpgnt_client *upc = container_of(plc, struct uxpgnt_client, plc);
-
- assert(!upc->dlgid); /* Pageant core should be serialising requests */
-
- char *msg = dupprintf(
- "A client of Pageant wants to use the following encrypted key:\n"
- "%s\n"
- "If you intended this, enter the passphrase to decrypt the key.",
- comment);
-
- switch (upc->prompt_type) {
- case RTPROMPT_UNAVAILABLE:
- sfree(msg);
- return false;
-
- case RTPROMPT_GUI:
- upc->passphrase_fd = make_pipe_to_askpass(msg);
- sfree(msg);
- if (upc->passphrase_fd < 0)
- return false; /* something went wrong */
- break;
-
- case RTPROMPT_DEBUG:
- fprintf(upc->logfp, "pageant passphrase request: %s\n", msg);
- sfree(msg);
- break;
- }
-
- upc->prompt_active = true;
- upc->dlgid = dlgid;
- return true;
-}
-
-static void passphrase_done(struct uxpgnt_client *upc, bool success)
-{
- PageantClientDialogId *dlgid = upc->dlgid;
- upc->dlgid = NULL;
- upc->prompt_active = false;
-
- if (upc->logfp)
- fprintf(upc->logfp, "pageant passphrase response: %s\n",
- success ? "success" : "failure");
-
- if (success)
- pageant_passphrase_request_success(
- dlgid, ptrlen_from_strbuf(upc->prompt_buf));
- else
- pageant_passphrase_request_refused(dlgid);
-
- strbuf_free(upc->prompt_buf);
- upc->prompt_buf = strbuf_new_nm();
-}
-
-static const PageantListenerClientVtable uxpgnt_vtable = {
- .log = uxpgnt_log,
- .ask_passphrase = uxpgnt_ask_passphrase,
-};
-
-/*
- * More stubs.
- */
-void random_save_seed(void) {}
-void random_destroy_seed(void) {}
-char *platform_default_s(const char *name) { return NULL; }
-bool platform_default_b(const char *name, bool def) { return def; }
-int platform_default_i(const char *name, int def) { return def; }
-FontSpec *platform_default_fontspec(const char *name) { return fontspec_new(""); }
-Filename *platform_default_filename(const char *name) { return filename_from_str(""); }
-char *x_get_default(const char *key) { return NULL; }
-
-/*
- * Short description of parameters.
- */
-static void usage(void)
-{
- printf("Pageant: SSH agent\n");
- printf("%s\n", ver);
- printf("Usage: pageant <lifetime> [[--encrypted] key files]\n");
- printf(" pageant [[--encrypted] key files] --exec <command> [args]\n");
- printf(" pageant -a [--encrypted] [key files]\n");
- printf(" pageant -d [key identifiers]\n");
- printf(" pageant -D\n");
- printf(" pageant -r [key identifiers]\n");
- printf(" pageant -R\n");
- printf(" pageant --public [key identifiers]\n");
- printf(" pageant ( --public-openssh | -L ) [key identifiers]\n");
- printf(" pageant -l [-E fptype]\n");
- printf("Lifetime options, for running Pageant as an agent:\n");
- printf(" -X run with the lifetime of the X server\n");
- printf(" -T run with the lifetime of the controlling tty\n");
- printf(" --permanent run permanently\n");
- printf(" --debug run in debugging mode, without forking\n");
- printf(" --exec <command> run with the lifetime of that command\n");
- printf("Client options, for talking to an existing agent:\n");
- printf(" -a add key(s) to the existing agent\n");
- printf(" -l list currently loaded key fingerprints and comments\n");
- printf(" --public print public keys in RFC 4716 format\n");
- printf(" --public-openssh, -L print public keys in OpenSSH format\n");
- printf(" -d delete key(s) from the agent\n");
- printf(" -D delete all keys from the agent\n");
- printf(" -r re-encrypt keys in the agent (forget cleartext\n");
- printf(" -R re-encrypt all possible keys in the agent\n");
- printf("Other options:\n");
- printf(" -v verbose mode (in agent mode)\n");
- printf(" -s -c force POSIX or C shell syntax (in agent mode)\n");
- printf(" --symlink path create symlink to socket (in agent mode)\n");
- printf(" --encrypted when adding keys, don't decrypt\n");
- printf(" -E alg, --fptype alg fingerprint type for -l (sha256, md5)\n");
- printf(" --tty-prompt force tty-based passphrase prompt\n");
- printf(" --gui-prompt force GUI-based passphrase prompt\n");
- printf(" --askpass <prompt> behave like a standalone askpass program\n");
- exit(1);
-}
-
-static void version(void)
-{
- char *buildinfo_text = buildinfo("\n");
- printf("pageant: %s\n%s\n", ver, buildinfo_text);
- sfree(buildinfo_text);
- exit(0);
-}
-
-void keylist_update(void)
-{
- /* Nothing needs doing in Unix Pageant */
-}
-
-#define PAGEANT_DIR_PREFIX "/tmp/pageant"
-
-const char *const appname = "Pageant";
-
-static bool time_to_die = false;
-
-/* Stub functions to permit linking against x11fwd.c. These never get
- * used, because in LIFE_X11 mode we connect to the X server using a
- * straightforward Socket and don't try to create an ersatz SSH
- * forwarding too. */
-void chan_remotely_opened_confirmation(Channel *chan) { }
-void chan_remotely_opened_failure(Channel *chan, const char *err) { }
-bool chan_default_want_close(Channel *chan, bool s, bool r) { return false; }
-bool chan_no_exit_status(Channel *ch, int s) { return false; }
-bool chan_no_exit_signal(Channel *ch, ptrlen s, bool c, ptrlen m)
-{ return false; }
-bool chan_no_exit_signal_numeric(Channel *ch, int s, bool c, ptrlen m)
-{ 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) {}
-
-/*
- * These functions are part of the plug for our connection to the X
- * display, so they do get called. They needn't actually do anything,
- * except that x11_closing has to signal back to the main loop that
- * it's time to terminate.
- */
-static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port,
- const char *error_msg, int error_code) {}
-static void x11_receive(Plug *plug, int urgent, const char *data, size_t len) {}
-static void x11_sent(Plug *plug, size_t bufsize) {}
-static void x11_closing(Plug *plug, const char *error_msg, int error_code,
- bool calling_back)
-{
- time_to_die = true;
-}
-struct X11Connection {
- Plug plug;
-};
-
-static char *socketname;
-static enum { SHELL_AUTO, SHELL_SH, SHELL_CSH } shell_type = SHELL_AUTO;
-void pageant_print_env(int pid)
-{
- if (shell_type == SHELL_AUTO) {
- /* Same policy as OpenSSH: if $SHELL ends in "csh" then assume
- * it's csh-shaped. */
- const char *shell = getenv("SHELL");
- if (shell && strlen(shell) >= 3 &&
- !strcmp(shell + strlen(shell) - 3, "csh"))
- shell_type = SHELL_CSH;
- else
- shell_type = SHELL_SH;
- }
-
- /*
- * These shell snippets could usefully pay some attention to
- * escaping of interesting characters. I don't think it causes a
- * problem at the moment, because the pathnames we use are so
- * utterly boring, but it's a lurking bug waiting to happen once
- * a bit more flexibility turns up.
- */
-
- switch (shell_type) {
- case SHELL_SH:
- printf("SSH_AUTH_SOCK=%s; export SSH_AUTH_SOCK;\n"
- "SSH_AGENT_PID=%d; export SSH_AGENT_PID;\n",
- socketname, pid);
- break;
- case SHELL_CSH:
- printf("setenv SSH_AUTH_SOCK %s;\n"
- "setenv SSH_AGENT_PID %d;\n",
- socketname, pid);
- break;
- case SHELL_AUTO:
- unreachable("SHELL_AUTO should have been eliminated by now");
- break;
- }
-}
-
-void pageant_fork_and_print_env(bool retain_tty)
-{
- pid_t pid = fork();
- if (pid == -1) {
- perror("fork");
- exit(1);
- } else if (pid != 0) {
- pageant_print_env(pid);
- exit(0);
- }
-
- /*
- * Having forked off, we now daemonise ourselves as best we can.
- * It's good practice in general to setsid() ourself out of any
- * process group we didn't want to be part of, and to chdir("/")
- * to avoid holding any directories open that we don't need in
- * case someone wants to umount them; also, we should definitely
- * close standard output (because it will very likely be pointing
- * at a pipe from which some parent process is trying to read our
- * environment variable dump, so if we hold open another copy of
- * it then that process will never finish reading). We close
- * standard input too on general principles, but not standard
- * error, since we might need to shout a panicky error message
- * down that one.
- */
- if (chdir("/") < 0) {
- /* should there be an error condition, nothing we can do about
- * it anyway */
- }
- close(0);
- close(1);
- if (retain_tty) {
- /* Get out of our previous process group, to avoid being
- * blasted by passing signals. But keep our controlling tty,
- * so we can keep checking to see if we still have one. */
- setpgrp();
- } else {
- /* Do that, but also leave our entire session and detach from
- * the controlling tty (if any). */
- setsid();
- }
-}
-
-static int signalpipe[2] = { -1, -1 };
-
-static void sigchld(int signum)
-{
- if (write(signalpipe[1], "x", 1) <= 0)
- /* not much we can do about it */;
-}
-
-static void setup_sigchld_handler(void)
-{
- if (signalpipe[0] >= 0)
- return;
-
- /*
- * Set up the pipe we'll use to tell us about SIGCHLD.
- */
- if (pipe(signalpipe) < 0) {
- perror("pipe");
- exit(1);
- }
- putty_signal(SIGCHLD, sigchld);
-}
-
-#define TTY_LIFE_POLL_INTERVAL (TICKSPERSEC * 30)
-static void *dummy_timer_ctx;
-static void tty_life_timer(void *ctx, unsigned long now)
-{
- schedule_timer(TTY_LIFE_POLL_INTERVAL, tty_life_timer, &dummy_timer_ctx);
-}
-
-typedef enum {
- KEYACT_AGENT_LOAD,
- KEYACT_AGENT_LOAD_ENCRYPTED,
- KEYACT_CLIENT_BASE,
- KEYACT_CLIENT_ADD = KEYACT_CLIENT_BASE,
- KEYACT_CLIENT_ADD_ENCRYPTED,
- KEYACT_CLIENT_DEL,
- KEYACT_CLIENT_DEL_ALL,
- KEYACT_CLIENT_LIST,
- KEYACT_CLIENT_PUBLIC_OPENSSH,
- KEYACT_CLIENT_PUBLIC,
- KEYACT_CLIENT_SIGN,
- KEYACT_CLIENT_REENCRYPT,
- KEYACT_CLIENT_REENCRYPT_ALL,
-} keyact;
-struct cmdline_key_action {
- struct cmdline_key_action *next;
- keyact action;
- const char *filename;
-};
-
-bool is_agent_action(keyact action)
-{
- return action < KEYACT_CLIENT_BASE;
-}
-
-static struct cmdline_key_action *keyact_head = NULL, *keyact_tail = NULL;
-static uint32_t sign_flags = 0;
-
-void add_keyact(keyact action, const char *filename)
-{
- struct cmdline_key_action *a = snew(struct cmdline_key_action);
- a->action = action;
- a->filename = filename;
- a->next = NULL;
- if (keyact_tail)
- keyact_tail->next = a;
- else
- keyact_head = a;
- keyact_tail = a;
-}
-
-bool have_controlling_tty(void)
-{
- int fd = open("/dev/tty", O_RDONLY);
- if (fd < 0) {
- if (errno != ENXIO) {
- perror("/dev/tty: open");
- exit(1);
- }
- return false;
- } else {
- close(fd);
- return true;
- }
-}
-
-static char **exec_args = NULL;
-static enum {
- LIFE_UNSPEC, LIFE_X11, LIFE_TTY, LIFE_DEBUG, LIFE_PERM, LIFE_EXEC
-} life = LIFE_UNSPEC;
-static const char *display = NULL;
-static enum {
- PROMPT_UNSPEC, PROMPT_TTY, PROMPT_GUI
-} prompt_type = PROMPT_UNSPEC;
-static FingerprintType key_list_fptype = SSH_FPTYPE_DEFAULT;
-
-static char *askpass_tty(const char *prompt)
-{
- int ret;
- prompts_t *p = new_prompts();
- p->to_server = false;
- p->from_server = false;
- p->name = dupstr("Pageant passphrase prompt");
- add_prompt(p, dupcat(prompt, ": "), false);
- ret = console_get_userpass_input(p);
- assert(ret >= 0);
-
- if (!ret) {
- perror("pageant: unable to read passphrase");
- free_prompts(p);
- return NULL;
- } else {
- char *passphrase = prompt_get_result(p->prompts[0]);
- free_prompts(p);
- return passphrase;
- }
-}
-
-static char *askpass_gui(const char *prompt)
-{
- char *passphrase;
- bool success;
-
- passphrase = gtk_askpass_main(
- display, "Pageant passphrase prompt", prompt, &success);
- if (!success) {
- /* return value is error message */
- fprintf(stderr, "%s\n", passphrase);
- sfree(passphrase);
- passphrase = NULL;
- }
- return passphrase;
-}
-
-static char *askpass(const char *prompt)
-{
- if (prompt_type == PROMPT_TTY) {
- if (!have_controlling_tty()) {
- fprintf(stderr, "no controlling terminal available "
- "for passphrase prompt\n");
- return NULL;
- }
- return askpass_tty(prompt);
- }
-
- if (prompt_type == PROMPT_GUI) {
- if (!display) {
- fprintf(stderr, "no graphical display available "
- "for passphrase prompt\n");
- return NULL;
- }
- return askpass_gui(prompt);
- }
-
- if (have_controlling_tty()) {
- return askpass_tty(prompt);
- } else if (display) {
- return askpass_gui(prompt);
- } else {
- fprintf(stderr, "no way to read a passphrase without tty or "
- "X display\n");
- return NULL;
- }
-}
-
-static bool unix_add_keyfile(const char *filename_str, bool add_encrypted)
-{
- Filename *filename = filename_from_str(filename_str);
- int status;
- bool ret;
- char *err;
-
- ret = true;
-
- /*
- * Try without a passphrase.
- */
- status = pageant_add_keyfile(filename, NULL, &err, add_encrypted);
- if (status == PAGEANT_ACTION_OK) {
- goto cleanup;
- } else if (status == PAGEANT_ACTION_FAILURE) {
- fprintf(stderr, "pageant: %s: %s\n", filename_str, err);
- ret = false;
- goto cleanup;
- }
-
- /*
- * And now try prompting for a passphrase.
- */
- while (1) {
- char *prompt = dupprintf(
- "Enter passphrase to load key '%s'", err);
- char *passphrase = askpass(prompt);
- sfree(err);
- sfree(prompt);
- err = NULL;
- if (!passphrase)
- break;
-
- status = pageant_add_keyfile(filename, passphrase, &err,
- add_encrypted);
-
- smemclr(passphrase, strlen(passphrase));
- sfree(passphrase);
- passphrase = NULL;
-
- if (status == PAGEANT_ACTION_OK) {
- goto cleanup;
- } else if (status == PAGEANT_ACTION_FAILURE) {
- fprintf(stderr, "pageant: %s: %s\n", filename_str, err);
- ret = false;
- goto cleanup;
- }
- }
-
- cleanup:
- sfree(err);
- filename_free(filename);
- return ret;
-}
-
-void key_list_callback(void *ctx, char **fingerprints, const char *comment,
- uint32_t ext_flags, struct pageant_pubkey *key)
-{
- const char *mode = "";
- if (ext_flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY)
- mode = " (encrypted)";
- else if (ext_flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE)
- mode = " (re-encryptable)";
-
- FingerprintType this_type =
- ssh2_pick_fingerprint(fingerprints, key_list_fptype);
- printf("%s %s%s\n", fingerprints[this_type], comment, mode);
-}
-
-struct key_find_ctx {
- const char *string;
- bool match_fp, match_comment;
- bool match_fptypes[SSH_N_FPTYPES];
- struct pageant_pubkey *found;
- int nfound;
-};
-
-static bool match_fingerprint_string(
- const char *string_orig, char **fingerprints,
- const struct key_find_ctx *ctx)
-{
- const char *hash;
-
- for (unsigned fptype = 0; fptype < SSH_N_FPTYPES; fptype++) {
- if (!ctx->match_fptypes[fptype])
- continue;
-
- const char *fingerprint = fingerprints[fptype];
- if (!fingerprint)
- continue;
-
- /* Find the hash in the fingerprint string. It'll be the word
- * at the end. */
- hash = strrchr(fingerprint, ' ');
- assert(hash);
- hash++;
-
- const char *string = string_orig;
- bool case_sensitive;
- const char *ignore_chars = "";
-
- switch (fptype) {
- case SSH_FPTYPE_MD5:
- /* MD5 fingerprints are in hex, so disregard case differences. */
- case_sensitive = false;
- /* And we don't really need to force the user to type the
- * colons in between the digits, which are always the
- * same. */
- ignore_chars = ":";
- break;
- case SSH_FPTYPE_SHA256:
- /* Skip over the "SHA256:" prefix, which we don't really
- * want to force the user to type. On the other hand,
- * tolerate it on the input string. */
- assert(strstartswith(hash, "SHA256:"));
- hash += 7;
- if (strstartswith(string, "SHA256:"))
- string += 7;
- /* SHA256 fingerprints are base64, which is intrinsically
- * case sensitive. */
- case_sensitive = true;
- break;
- }
-
- /* Now see if the search string is a prefix of the full hash,
- * neglecting colons and (where appropriate) case differences. */
- while (1) {
- string += strspn(string, ignore_chars);
- hash += strspn(hash, ignore_chars);
- if (!*string)
- return true;
- char sc = *string, hc = *hash;
- if (!case_sensitive) {
- sc = tolower((unsigned char)sc);
- hc = tolower((unsigned char)hc);
- }
- if (sc != hc)
- break;
- string++;
- hash++;
- }
- }
-
- return false;
-}
-
-void key_find_callback(void *vctx, char **fingerprints,
- const char *comment, uint32_t ext_flags,
- struct pageant_pubkey *key)
-{
- struct key_find_ctx *ctx = (struct key_find_ctx *)vctx;
-
- if ((ctx->match_comment && !strcmp(ctx->string, comment)) ||
- (ctx->match_fp && match_fingerprint_string(ctx->string, fingerprints,
- ctx)))
- {
- if (!ctx->found)
- ctx->found = pageant_pubkey_copy(key);
- ctx->nfound++;
- }
-}
-
-struct pageant_pubkey *find_key(const char *string, char **retstr)
-{
- struct key_find_ctx ctx[1];
- struct pageant_pubkey key_in, *key_ret;
- bool try_file = true, try_fp = true, try_comment = true;
- bool file_errors = false;
- bool try_all_fptypes = true;
- FingerprintType fptype = SSH_FPTYPE_DEFAULT;
-
- /*
- * Trim off disambiguating prefixes telling us how to interpret
- * the provided string.
- */
- if (!strncmp(string, "file:", 5)) {
- string += 5;
- try_fp = false;
- try_comment = false;
- file_errors = true; /* also report failure to load the file */
- } else if (!strncmp(string, "comment:", 8)) {
- string += 8;
- try_file = false;
- try_fp = false;
- } else if (!strncmp(string, "fp:", 3)) {
- string += 3;
- try_file = false;
- try_comment = false;
- } else if (!strncmp(string, "fingerprint:", 12)) {
- string += 12;
- try_file = false;
- try_comment = false;
- } else if (!strnicmp(string, "md5:", 4)) {
- string += 4;
- try_file = false;
- try_comment = false;
- try_all_fptypes = false;
- fptype = SSH_FPTYPE_MD5;
- } else if (!strncmp(string, "sha256:", 7)) {
- string += 7;
- try_file = false;
- try_comment = false;
- try_all_fptypes = false;
- fptype = SSH_FPTYPE_SHA256;
- }
-
- /*
- * Try interpreting the string as a key file name.
- */
- if (try_file) {
- Filename *fn = filename_from_str(string);
- int keytype = key_type(fn);
- if (keytype == SSH_KEYTYPE_SSH1 ||
- keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
- const char *error;
-
- key_in.blob = strbuf_new();
- if (!rsa1_loadpub_f(fn, BinarySink_UPCAST(key_in.blob),
- NULL, &error)) {
- strbuf_free(key_in.blob);
- key_in.blob = NULL;
- if (file_errors) {
- *retstr = dupprintf("unable to load file '%s': %s",
- string, error);
- filename_free(fn);
- return NULL;
- }
- } else {
- /*
- * If we've successfully loaded the file, stop here - we
- * already have a key blob and need not go to the agent to
- * list things.
- */
- key_in.ssh_version = 1;
- key_in.comment = NULL;
- key_ret = pageant_pubkey_copy(&key_in);
- strbuf_free(key_in.blob);
- key_in.blob = NULL;
- filename_free(fn);
- return key_ret;
- }
- } else if (keytype == SSH_KEYTYPE_SSH2 ||
- keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
- keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
- const char *error;
-
- key_in.blob = strbuf_new();
- if (!ppk_loadpub_f(fn, NULL, BinarySink_UPCAST(key_in.blob),
- NULL, &error)) {
- strbuf_free(key_in.blob);
- key_in.blob = NULL;
- if (file_errors) {
- *retstr = dupprintf("unable to load file '%s': %s",
- string, error);
- filename_free(fn);
- return NULL;
- }
- } else {
- /*
- * If we've successfully loaded the file, stop here - we
- * already have a key blob and need not go to the agent to
- * list things.
- */
- key_in.ssh_version = 2;
- key_in.comment = NULL;
- key_ret = pageant_pubkey_copy(&key_in);
- strbuf_free(key_in.blob);
- key_in.blob = NULL;
- filename_free(fn);
- return key_ret;
- }
- } else {
- if (file_errors) {
- *retstr = dupprintf("unable to load key file '%s': %s",
- string, key_type_to_str(keytype));
- filename_free(fn);
- return NULL;
- }
- }
- filename_free(fn);
- }
-
- /*
- * Failing that, go through the keys in the agent, and match
- * against fingerprints and comments as appropriate.
- */
- ctx->string = string;
- ctx->match_fp = try_fp;
- ctx->match_comment = try_comment;
- for (unsigned i = 0; i < SSH_N_FPTYPES; i++)
- ctx->match_fptypes[i] = (try_all_fptypes || i == fptype);
- ctx->found = NULL;
- ctx->nfound = 0;
- if (pageant_enum_keys(key_find_callback, ctx, retstr) ==
- PAGEANT_ACTION_FAILURE)
- return NULL;
-
- if (ctx->nfound == 0) {
- *retstr = dupstr("no key matched");
- assert(!ctx->found);
- return NULL;
- } else if (ctx->nfound > 1) {
- *retstr = dupstr("multiple keys matched");
- assert(ctx->found);
- pageant_pubkey_free(ctx->found);
- return NULL;
- }
-
- assert(ctx->found);
- return ctx->found;
-}
-
-void run_client(void)
-{
- const struct cmdline_key_action *act;
- struct pageant_pubkey *key;
- bool errors = false;
- char *retstr;
- LoadedFile *message = lf_new(AGENT_MAX_MSGLEN);
- bool message_loaded = false, message_ok = false;
- strbuf *signature = strbuf_new();
-
- if (!agent_exists()) {
- fprintf(stderr, "pageant: no agent running to talk to\n");
- exit(1);
- }
-
- for (act = keyact_head; act; act = act->next) {
- switch (act->action) {
- case KEYACT_CLIENT_ADD:
- case KEYACT_CLIENT_ADD_ENCRYPTED:
- if (!unix_add_keyfile(act->filename,
- act->action == KEYACT_CLIENT_ADD_ENCRYPTED))
- errors = true;
- break;
- case KEYACT_CLIENT_LIST:
- if (pageant_enum_keys(key_list_callback, NULL, &retstr) ==
- PAGEANT_ACTION_FAILURE) {
- fprintf(stderr, "pageant: listing keys: %s\n", retstr);
- sfree(retstr);
- errors = true;
- }
- break;
- case KEYACT_CLIENT_DEL:
- key = NULL;
- if (!(key = find_key(act->filename, &retstr)) ||
- pageant_delete_key(key, &retstr) == PAGEANT_ACTION_FAILURE) {
- fprintf(stderr, "pageant: deleting key '%s': %s\n",
- act->filename, retstr);
- sfree(retstr);
- errors = true;
- }
- if (key)
- pageant_pubkey_free(key);
- break;
- case KEYACT_CLIENT_REENCRYPT:
- key = NULL;
- if (!(key = find_key(act->filename, &retstr)) ||
- pageant_reencrypt_key(key, &retstr) == PAGEANT_ACTION_FAILURE) {
- fprintf(stderr, "pageant: re-encrypting key '%s': %s\n",
- act->filename, retstr);
- sfree(retstr);
- errors = true;
- }
- if (key)
- pageant_pubkey_free(key);
- break;
- case KEYACT_CLIENT_PUBLIC_OPENSSH:
- case KEYACT_CLIENT_PUBLIC:
- key = NULL;
- if (!(key = find_key(act->filename, &retstr))) {
- fprintf(stderr, "pageant: finding key '%s': %s\n",
- act->filename, retstr);
- sfree(retstr);
- errors = true;
- } else {
- FILE *fp = stdout; /* FIXME: add a -o option? */
-
- if (key->ssh_version == 1) {
- BinarySource src[1];
- RSAKey rkey;
-
- BinarySource_BARE_INIT(src, key->blob->u, key->blob->len);
- memset(&rkey, 0, sizeof(rkey));
- rkey.comment = dupstr(key->comment);
- get_rsa_ssh1_pub(src, &rkey, RSA_SSH1_EXPONENT_FIRST);
- ssh1_write_pubkey(fp, &rkey);
- freersakey(&rkey);
- } else {
- ssh2_write_pubkey(fp, key->comment,
- key->blob->u,
- key->blob->len,
- (act->action == KEYACT_CLIENT_PUBLIC ?
- SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 :
- SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH));
- }
- pageant_pubkey_free(key);
- }
- break;
- case KEYACT_CLIENT_DEL_ALL:
- if (pageant_delete_all_keys(&retstr) == PAGEANT_ACTION_FAILURE) {
- fprintf(stderr, "pageant: deleting all keys: %s\n", retstr);
- sfree(retstr);
- errors = true;
- }
- break;
- case KEYACT_CLIENT_REENCRYPT_ALL: {
- int status = pageant_reencrypt_all_keys(&retstr);
- if (status == PAGEANT_ACTION_FAILURE) {
- fprintf(stderr, "pageant: re-encrypting all keys: "
- "%s\n", retstr);
- sfree(retstr);
- errors = true;
- } else if (status == PAGEANT_ACTION_WARNING) {
- fprintf(stderr, "pageant: re-encrypting all keys: "
- "warning: %s\n", retstr);
- sfree(retstr);
- }
- break;
- }
- case KEYACT_CLIENT_SIGN:
- key = NULL;
- if (!message_loaded) {
- message_loaded = true;
- switch(lf_load_fp(message, stdin)) {
- case LF_TOO_BIG:
- fprintf(stderr, "pageant: message to sign is too big\n");
- errors = true;
- break;
- case LF_ERROR:
- fprintf(stderr, "pageant: reading message to sign: %s\n",
- strerror(errno));
- errors = true;
- break;
- case LF_OK:
- message_ok = true;
- break;
- }
- }
- if (!message_ok)
- break;
- strbuf_clear(signature);
- if (!(key = find_key(act->filename, &retstr)) ||
- pageant_sign(key, ptrlen_from_lf(message), signature,
- sign_flags, &retstr) == PAGEANT_ACTION_FAILURE) {
- fprintf(stderr, "pageant: signing with key '%s': %s\n",
- act->filename, retstr);
- sfree(retstr);
- errors = true;
- } else {
- fwrite(signature->s, 1, signature->len, stdout);
- }
- if (key)
- pageant_pubkey_free(key);
- break;
- default:
- unreachable("Invalid client action found");
- }
- }
-
- lf_free(message);
- strbuf_free(signature);
-
- if (errors)
- exit(1);
-}
-
-static const PlugVtable X11Connection_plugvt = {
- .log = x11_log,
- .closing = x11_closing,
- .receive = x11_receive,
- .sent = x11_sent,
-};
-
-
-static bool agent_loop_pw_setup(void *vctx, pollwrapper *pw)
-{
- struct uxpgnt_client *upc = (struct uxpgnt_client *)vctx;
-
- if (signalpipe[0] >= 0) {
- pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R);
- }
-
- if (upc->prompt_active)
- pollwrap_add_fd_rwx(pw, upc->passphrase_fd, SELECT_R);
-
- return true;
-}
-
-static void agent_loop_pw_check(void *vctx, pollwrapper *pw)
-{
- struct uxpgnt_client *upc = (struct uxpgnt_client *)vctx;
-
- if (life == LIFE_TTY) {
- /*
- * Every time we wake up (whether it was due to tty_timer
- * elapsing or for any other reason), poll to see if we still
- * have a controlling terminal. If we don't, then our
- * containing tty session has ended, so it's time to clean up
- * and leave.
- */
- if (!have_controlling_tty()) {
- time_to_die = true;
- return;
- }
- }
-
- if (signalpipe[0] >= 0 &&
- pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) {
- char c[1];
- if (read(signalpipe[0], c, 1) <= 0)
- /* ignore error */;
- /* ignore its value; it'll be `x' */
- while (1) {
- int status;
- pid_t pid;
- pid = waitpid(-1, &status, WNOHANG);
- if (pid <= 0)
- break;
- if (pid == upc->termination_pid)
- time_to_die = true;
- }
- }
-
- if (upc->prompt_active &&
- pollwrap_check_fd_rwx(pw, upc->passphrase_fd, SELECT_R)) {
- char c;
- int retd = read(upc->passphrase_fd, &c, 1);
-
- switch (upc->prompt_type) {
- case RTPROMPT_GUI:
- if (retd <= 0) {
- close(upc->passphrase_fd);
- upc->passphrase_fd = -1;
- bool ok = (retd == 0);
- if (!strbuf_chomp(upc->prompt_buf, '\n'))
- ok = false;
- passphrase_done(upc, ok);
- } else {
- put_byte(upc->prompt_buf, c);
- }
- break;
- case RTPROMPT_DEBUG:
- if (retd <= 0) {
- passphrase_done(upc, false);
- /* Now never try to read from stdin again */
- upc->prompt_type = RTPROMPT_UNAVAILABLE;
- break;
- }
-
- switch (c) {
- case '\n':
- case '\r':
- passphrase_done(upc, true);
- break;
- case '\004':
- passphrase_done(upc, false);
- break;
- case '\b':
- case '\177':
- strbuf_shrink_by(upc->prompt_buf, 1);
- break;
- case '\025':
- strbuf_clear(upc->prompt_buf);
- break;
- default:
- put_byte(upc->prompt_buf, c);
- break;
- }
- break;
- case RTPROMPT_UNAVAILABLE:
- unreachable("Should never have started a prompt at all");
- }
- }
-}
-
-static bool agent_loop_continue(void *vctx, bool fd, bool cb)
-{
- return !time_to_die;
-}
-
-void run_agent(FILE *logfp, const char *symlink_path)
-{
- const char *err;
- char *errw;
- struct pageant_listen_state *pl;
- Plug *pl_plug;
- Socket *sock;
- bool errors = false;
- Conf *conf;
- const struct cmdline_key_action *act;
-
- pageant_init();
-
- /*
- * Start by loading any keys provided on the command line.
- */
- for (act = keyact_head; act; act = act->next) {
- assert(act->action == KEYACT_AGENT_LOAD ||
- act->action == KEYACT_AGENT_LOAD_ENCRYPTED);
- if (!unix_add_keyfile(act->filename,
- act->action == KEYACT_AGENT_LOAD_ENCRYPTED))
- errors = true;
- }
- if (errors)
- exit(1);
-
- /*
- * Set up a listening socket and run Pageant on it.
- */
- struct uxpgnt_client upc[1];
- memset(upc, 0, sizeof(upc));
- upc->plc.vt = &uxpgnt_vtable;
- upc->logfp = logfp;
- upc->passphrase_fd = -1;
- upc->termination_pid = -1;
- upc->prompt_buf = strbuf_new_nm();
- upc->prompt_type = display ? RTPROMPT_GUI : RTPROMPT_UNAVAILABLE;
- pl = pageant_listener_new(&pl_plug, &upc->plc);
- sock = platform_make_agent_socket(pl_plug, PAGEANT_DIR_PREFIX,
- &errw, &socketname);
- if (!sock) {
- fprintf(stderr, "pageant: %s\n", errw);
- sfree(errw);
- exit(1);
- }
- pageant_listener_got_socket(pl, sock);
-
- if (symlink_path) {
- /*
- * Try to make a symlink to the Unix socket, in a location of
- * the user's choosing.
- *
- * If the link already exists, we want to replace it. There
- * are two ways we could do this: either make it under another
- * name and then rename it over the top, or remove the old
- * link first. The former is what 'ln -sf' does, on the
- * grounds that it's more atomic. But I think in this case,
- * where the expected use case is that the previous agent has
- * long since shut down, atomicity isn't a critical concern
- * compared to not accidentally overwriting some non-symlink
- * that might have important data in it!
- */
- struct stat st;
- if (lstat(symlink_path, &st) == 0 && S_ISLNK(st.st_mode))
- unlink(symlink_path);
- if (symlink(socketname, symlink_path) < 0)
- fprintf(stderr, "pageant: making symlink %s: %s\n",
- symlink_path, strerror(errno));
- }
-
- conf = conf_new();
- conf_set_int(conf, CONF_proxy_type, PROXY_NONE);
-
- /*
- * Lifetime preparations.
- */
- if (life == LIFE_X11) {
- struct X11Display *disp;
- void *greeting;
- int greetinglen;
- Socket *s;
- struct X11Connection *conn;
- char *x11_setup_err;
-
- if (!display) {
- fprintf(stderr, "pageant: no DISPLAY for -X mode\n");
- exit(1);
- }
- disp = x11_setup_display(display, conf, &x11_setup_err);
- if (!disp) {
- fprintf(stderr, "pageant: unable to connect to X server: %s\n",
- x11_setup_err);
- sfree(x11_setup_err);
- exit(1);
- }
-
- conn = snew(struct X11Connection);
- conn->plug.vt = &X11Connection_plugvt;
- s = new_connection(sk_addr_dup(disp->addr),
- disp->realhost, disp->port,
- false, true, false, false, &conn->plug, conf);
- if ((err = sk_socket_error(s)) != NULL) {
- fprintf(stderr, "pageant: unable to connect to X server: %s", err);
- exit(1);
- }
- greeting = x11_make_greeting('B', 11, 0, disp->localauthproto,
- disp->localauthdata,
- disp->localauthdatalen,
- NULL, 0, &greetinglen);
- sk_write(s, greeting, greetinglen);
- smemclr(greeting, greetinglen);
- sfree(greeting);
-
- pageant_fork_and_print_env(false);
- } else if (life == LIFE_TTY) {
- schedule_timer(TTY_LIFE_POLL_INTERVAL,
- tty_life_timer, &dummy_timer_ctx);
- pageant_fork_and_print_env(true);
- } else if (life == LIFE_PERM) {
- pageant_fork_and_print_env(false);
- } else if (life == LIFE_DEBUG) {
- pageant_print_env(getpid());
- upc->logfp = stdout;
-
- struct termios orig_termios;
- upc->passphrase_fd = fileno(stdin);
- if (tcgetattr(upc->passphrase_fd, &orig_termios) == 0) {
- struct termios new_termios = orig_termios;
- new_termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON);
-
- /*
- * Try to set up a watchdog process that will restore
- * termios if we crash or are killed. If successful, turn
- * off echo, for runtime passphrase prompts.
- */
- int pipefd[2];
- if (pipe(pipefd) == 0) {
- pid_t pid = fork();
- if (pid == 0) {
- tcsetattr(upc->passphrase_fd, TCSADRAIN, &new_termios);
- close(pipefd[1]);
- char buf[4096];
- while (read(pipefd[0], buf, sizeof(buf)) > 0);
- tcsetattr(upc->passphrase_fd, TCSADRAIN, &new_termios);
- _exit(0);
- } else if (pid > 0) {
- upc->prompt_type = RTPROMPT_DEBUG;
- }
-
- close(pipefd[0]);
- if (pid < 0)
- close(pipefd[1]);
- }
- }
- } else if (life == LIFE_EXEC) {
- pid_t agentpid, pid;
-
- agentpid = getpid();
- setup_sigchld_handler();
-
- pid = fork();
- if (pid < 0) {
- perror("fork");
- exit(1);
- } else if (pid == 0) {
- setenv("SSH_AUTH_SOCK", socketname, true);
- setenv("SSH_AGENT_PID", dupprintf("%d", (int)agentpid), true);
- execvp(exec_args[0], exec_args);
- perror("exec");
- _exit(127);
- } else {
- upc->termination_pid = pid;
- }
- }
-
- if (!upc->logfp)
- upc->plc.suppress_logging = true;
-
- cli_main_loop(agent_loop_pw_setup, agent_loop_pw_check,
- agent_loop_continue, upc);
-
- /*
- * Before terminating, clean up our Unix socket file if possible.
- */
- if (unlink(socketname) < 0) {
- fprintf(stderr, "pageant: %s: %s\n", socketname, strerror(errno));
- exit(1);
- }
-
- strbuf_free(upc->prompt_buf);
- conf_free(conf);
-}
-
-int main(int argc, char **argv)
-{
- bool doing_opts = true;
- keyact curr_keyact = KEYACT_AGENT_LOAD;
- const char *standalone_askpass_prompt = NULL;
- const char *symlink_path = NULL;
- FILE *logfp = NULL;
-
- progname = argv[0];
-
- /*
- * Process the command line.
- */
- while (--argc > 0) {
- char *p = *++argv;
- if (*p == '-' && doing_opts) {
- if (!strcmp(p, "-V") || !strcmp(p, "--version")) {
- version();
- } else if (!strcmp(p, "--help")) {
- usage();
- exit(0);
- } else if (!strcmp(p, "-v")) {
- logfp = stderr;
- } else if (!strcmp(p, "-a")) {
- curr_keyact = KEYACT_CLIENT_ADD;
- } else if (!strcmp(p, "-d")) {
- curr_keyact = KEYACT_CLIENT_DEL;
- } else if (!strcmp(p, "-r")) {
- curr_keyact = KEYACT_CLIENT_REENCRYPT;
- } else if (!strcmp(p, "-s")) {
- shell_type = SHELL_SH;
- } else if (!strcmp(p, "-c")) {
- shell_type = SHELL_CSH;
- } else if (!strcmp(p, "-D")) {
- add_keyact(KEYACT_CLIENT_DEL_ALL, NULL);
- } else if (!strcmp(p, "-R")) {
- add_keyact(KEYACT_CLIENT_REENCRYPT_ALL, NULL);
- } else if (!strcmp(p, "-l")) {
- add_keyact(KEYACT_CLIENT_LIST, NULL);
- } else if (!strcmp(p, "--public")) {
- curr_keyact = KEYACT_CLIENT_PUBLIC;
- } else if (!strcmp(p, "--public-openssh") || !strcmp(p, "-L")) {
- curr_keyact = KEYACT_CLIENT_PUBLIC_OPENSSH;
- } else if (!strcmp(p, "-X")) {
- life = LIFE_X11;
- } else if (!strcmp(p, "-T")) {
- life = LIFE_TTY;
- } else if (!strcmp(p, "--no-decrypt") ||
- !strcmp(p, "-no-decrypt") ||
- !strcmp(p, "--no_decrypt") ||
- !strcmp(p, "-no_decrypt") ||
- !strcmp(p, "--nodecrypt") ||
- !strcmp(p, "-nodecrypt") ||
- !strcmp(p, "--encrypted") ||
- !strcmp(p, "-encrypted")) {
- if (curr_keyact == KEYACT_AGENT_LOAD)
- curr_keyact = KEYACT_AGENT_LOAD_ENCRYPTED;
- else if (curr_keyact == KEYACT_CLIENT_ADD)
- curr_keyact = KEYACT_CLIENT_ADD_ENCRYPTED;
- else {
- fprintf(stderr, "pageant: unexpected -E while not adding "
- "keys\n");
- exit(1);
- }
- } else if (!strcmp(p, "--debug")) {
- life = LIFE_DEBUG;
- } else if (!strcmp(p, "--test-sign")) {
- curr_keyact = KEYACT_CLIENT_SIGN;
- sign_flags = 0;
- } else if (strstartswith(p, "--test-sign-with-flags=")) {
- curr_keyact = KEYACT_CLIENT_SIGN;
- sign_flags = atoi(p + strlen("--test-sign-with-flags="));
- } else if (!strcmp(p, "--permanent")) {
- life = LIFE_PERM;
- } else if (!strcmp(p, "--exec")) {
- life = LIFE_EXEC;
- /* Now all subsequent arguments go to the exec command. */
- if (--argc > 0) {
- exec_args = ++argv;
- argc = 0; /* force end of option processing */
- } else {
- fprintf(stderr, "pageant: expected a command "
- "after --exec\n");
- exit(1);
- }
- } else if (!strcmp(p, "--tty-prompt")) {
- prompt_type = PROMPT_TTY;
- } else if (!strcmp(p, "--gui-prompt")) {
- prompt_type = PROMPT_GUI;
- } else if (!strcmp(p, "--askpass")) {
- if (--argc > 0) {
- standalone_askpass_prompt = *++argv;
- } else {
- fprintf(stderr, "pageant: expected a prompt message "
- "after --askpass\n");
- exit(1);
- }
- } else if (!strcmp(p, "--symlink")) {
- if (--argc > 0) {
- symlink_path = *++argv;
- } else {
- fprintf(stderr, "pageant: expected a pathname "
- "after --symlink\n");
- exit(1);
- }
- } else if (!strcmp(p, "-E") || !strcmp(p, "--fptype")) {
- const char *keyword;
- if (--argc > 0) {
- keyword = *++argv;
- } else {
- fprintf(stderr, "pageant: expected a type string "
- "after %s\n", p);
- exit(1);
- }
- if (!strcmp(keyword, "md5"))
- key_list_fptype = SSH_FPTYPE_MD5;
- else if (!strcmp(keyword, "sha256"))
- key_list_fptype = SSH_FPTYPE_SHA256;
- else {
- fprintf(stderr, "pageant: unknown fingerprint type `%s'\n",
- keyword);
- exit(1);
- }
- } else if (!strcmp(p, "--")) {
- doing_opts = false;
- } else {
- fprintf(stderr, "pageant: unrecognised option '%s'\n", p);
- exit(1);
- }
- } else {
- /*
- * Non-option arguments (apart from those after --exec,
- * which are treated specially above) are interpreted as
- * the names of private key files to either add or delete
- * from an agent.
- */
- add_keyact(curr_keyact, p);
- }
- }
-
- if (life == LIFE_EXEC && !exec_args) {
- fprintf(stderr, "pageant: expected a command with --exec\n");
- exit(1);
- }
-
- if (!display) {
- display = getenv("DISPLAY");
- if (display && !*display)
- display = NULL;
- }
-
- /*
- * Deal with standalone-askpass mode.
- */
- if (standalone_askpass_prompt) {
- char *passphrase = askpass(standalone_askpass_prompt);
-
- if (!passphrase)
- return 1;
-
- puts(passphrase);
- fflush(stdout);
-
- smemclr(passphrase, strlen(passphrase));
- sfree(passphrase);
- return 0;
- }
-
- /*
- * Block SIGPIPE, so that we'll get EPIPE individually on
- * particular network connections that go wrong.
- */
- putty_signal(SIGPIPE, SIG_IGN);
-
- sk_init();
- uxsel_init();
-
- /*
- * Now distinguish our two main running modes. Either we're
- * actually starting up an agent, in which case we should have a
- * lifetime mode, and no key actions of KEYACT_CLIENT_* type; or
- * else we're contacting an existing agent to add or remove keys,
- * in which case we should have no lifetime mode, and no key
- * actions of KEYACT_AGENT_* type.
- */
- {
- bool has_agent_actions = false;
- bool has_client_actions = false;
- bool has_lifetime = false;
- const struct cmdline_key_action *act;
-
- for (act = keyact_head; act; act = act->next) {
- if (is_agent_action(act->action))
- has_agent_actions = true;
- else
- has_client_actions = true;
- }
- if (life != LIFE_UNSPEC)
- has_lifetime = true;
-
- if (has_lifetime && has_client_actions) {
- fprintf(stderr, "pageant: client key actions (-a, -d, -D, -r, -R, "
- "-l, -L) do not go with an agent lifetime option\n");
- exit(1);
- }
- if (!has_lifetime && has_agent_actions) {
- fprintf(stderr, "pageant: expected an agent lifetime option with"
- " bare key file arguments\n");
- exit(1);
- }
- if (!has_lifetime && !has_client_actions) {
- fprintf(stderr, "pageant: expected an agent lifetime option"
- " or a client key action\n");
- exit(1);
- }
-
- if (has_lifetime) {
- run_agent(logfp, symlink_path);
- } else if (has_client_actions) {
- run_client();
- }
- }
-
- return 0;
-}
diff --git a/UNIX/uxpoll.c b/UNIX/uxpoll.c
deleted file mode 100644
index 474926bb..00000000
--- a/UNIX/uxpoll.c
+++ /dev/null
@@ -1,169 +0,0 @@
-/* On some systems this is needed to get poll.h to define eg.. POLLRDNORM */
-#define _XOPEN_SOURCE
-
-#include <poll.h>
-
-#include "putty.h"
-#include "tree234.h"
-
-struct pollwrapper {
- struct pollfd *fds;
- size_t nfd, fdsize;
- tree234 *fdtopos;
-};
-
-typedef struct pollwrap_fdtopos pollwrap_fdtopos;
-struct pollwrap_fdtopos {
- int fd;
- size_t pos;
-};
-
-static int pollwrap_fd_cmp(void *av, void *bv)
-{
- pollwrap_fdtopos *a = (pollwrap_fdtopos *)av;
- pollwrap_fdtopos *b = (pollwrap_fdtopos *)bv;
- return a->fd < b->fd ? -1 : a->fd > b->fd ? +1 : 0;
-}
-
-pollwrapper *pollwrap_new(void)
-{
- pollwrapper *pw = snew(pollwrapper);
- pw->fdsize = 16;
- pw->nfd = 0;
- pw->fds = snewn(pw->fdsize, struct pollfd);
- pw->fdtopos = newtree234(pollwrap_fd_cmp);
- return pw;
-}
-
-void pollwrap_free(pollwrapper *pw)
-{
- pollwrap_clear(pw);
- freetree234(pw->fdtopos);
- sfree(pw->fds);
- sfree(pw);
-}
-
-void pollwrap_clear(pollwrapper *pw)
-{
- pw->nfd = 0;
- for (pollwrap_fdtopos *f2p;
- (f2p = delpos234(pw->fdtopos, 0)) != NULL ;)
- sfree(f2p);
-}
-
-void pollwrap_add_fd_events(pollwrapper *pw, int fd, int events)
-{
- pollwrap_fdtopos *f2p, f2p_find;
-
- assert(fd >= 0);
-
- f2p_find.fd = fd;
- f2p = find234(pw->fdtopos, &f2p_find, NULL);
- if (!f2p) {
- sgrowarray(pw->fds, pw->fdsize, pw->nfd);
- size_t index = pw->nfd++;
- pw->fds[index].fd = fd;
- pw->fds[index].events = pw->fds[index].revents = 0;
-
- f2p = snew(pollwrap_fdtopos);
- f2p->fd = fd;
- f2p->pos = index;
- pollwrap_fdtopos *added = add234(pw->fdtopos, f2p);
- assert(added == f2p);
- }
-
- pw->fds[f2p->pos].events |= events;
-}
-
-/* Omit any of the POLL{RD,WR}{NORM,BAND} flag values that are still
- * not defined by poll.h, just in case */
-#ifndef POLLRDNORM
-#define POLLRDNORM 0
-#endif
-#ifndef POLLRDBAND
-#define POLLRDBAND 0
-#endif
-#ifndef POLLWRNORM
-#define POLLWRNORM 0
-#endif
-#ifndef POLLWRBAND
-#define POLLWRBAND 0
-#endif
-
-#define SELECT_R_IN (POLLIN | POLLRDNORM | POLLRDBAND)
-#define SELECT_W_IN (POLLOUT | POLLWRNORM | POLLWRBAND)
-#define SELECT_X_IN (POLLPRI)
-
-#define SELECT_R_OUT (SELECT_R_IN | POLLERR | POLLHUP)
-#define SELECT_W_OUT (SELECT_W_IN | POLLERR)
-#define SELECT_X_OUT (SELECT_X_IN)
-
-void pollwrap_add_fd_rwx(pollwrapper *pw, int fd, int rwx)
-{
- int events = 0;
- if (rwx & SELECT_R)
- events |= SELECT_R_IN;
- if (rwx & SELECT_W)
- events |= SELECT_W_IN;
- if (rwx & SELECT_X)
- events |= SELECT_X_IN;
- pollwrap_add_fd_events(pw, fd, events);
-}
-
-int pollwrap_poll_instant(pollwrapper *pw)
-{
- return poll(pw->fds, pw->nfd, 0);
-}
-
-int pollwrap_poll_endless(pollwrapper *pw)
-{
- return poll(pw->fds, pw->nfd, -1);
-}
-
-int pollwrap_poll_timeout(pollwrapper *pw, int milliseconds)
-{
- assert(milliseconds >= 0);
- return poll(pw->fds, pw->nfd, milliseconds);
-}
-
-static void pollwrap_get_fd_events_revents(pollwrapper *pw, int fd,
- int *events_p, int *revents_p)
-{
- pollwrap_fdtopos *f2p, f2p_find;
- int events = 0, revents = 0;
-
- assert(fd >= 0);
-
- f2p_find.fd = fd;
- f2p = find234(pw->fdtopos, &f2p_find, NULL);
- if (f2p) {
- events = pw->fds[f2p->pos].events;
- revents = pw->fds[f2p->pos].revents;
- }
-
- if (events_p)
- *events_p = events;
- if (revents_p)
- *revents_p = revents;
-}
-
-int pollwrap_get_fd_events(pollwrapper *pw, int fd)
-{
- int revents;
- pollwrap_get_fd_events_revents(pw, fd, NULL, &revents);
- return revents;
-}
-
-int pollwrap_get_fd_rwx(pollwrapper *pw, int fd)
-{
- int events, revents;
- pollwrap_get_fd_events_revents(pw, fd, &events, &revents);
- int rwx = 0;
- if ((events & POLLIN) && (revents & SELECT_R_OUT))
- rwx |= SELECT_R;
- if ((events & POLLOUT) && (revents & SELECT_W_OUT))
- rwx |= SELECT_W;
- if ((events & POLLPRI) && (revents & SELECT_X_OUT))
- rwx |= SELECT_X;
- return rwx;
-}
diff --git a/UNIX/uxpsusan.c b/UNIX/uxpsusan.c
deleted file mode 100644
index c60728ce..00000000
--- a/UNIX/uxpsusan.c
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- * 'psusan': Pseudo Ssh for Untappable, Separately Authenticated Networks
- *
- * This is a standalone application that speaks on its standard I/O
- * (or a listening Unix-domain socket) the server end of the bare
- * ssh-connection protocol used by PuTTY's connection sharing.
- *
- * The idea of this tool is that you can use it to communicate across
- * any 8-bit-clean data channel between two inconveniently separated
- * domains, provided the channel is already (as the name suggests)
- * adequately secured against eavesdropping and modification and
- * already authenticated as the right user.
- *
- * If you're sitting at one end of such a channel and want to type
- * commands into the other end, the most obvious thing to do is to run
- * a terminal session directly over it. But if you run psusan at one
- * end, and a PuTTY (or compatible) client at the other end, then you
- * not only get a single terminal session: you get all the other SSH
- * amenities, like the ability to spawn extra terminal sessions,
- * forward ports or X11 connections, even forward an SSH agent.
- *
- * There are a surprising number of channels of that kind; see the man
- * page for some examples.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <signal.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <termios.h>
-#include <pwd.h>
-#include <sys/ioctl.h>
-#include <sys/time.h>
-
-#include "putty.h"
-#include "mpint.h"
-#include "ssh.h"
-#include "sshserver.h"
-
-const char *const appname = "psusan";
-
-void modalfatalbox(const char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "FATAL ERROR: ");
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
- exit(1);
-}
-void nonfatal(const char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "ERROR: ");
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
-}
-
-char *platform_default_s(const char *name)
-{
- return NULL;
-}
-
-bool platform_default_b(const char *name, bool def)
-{
- return def;
-}
-
-int platform_default_i(const char *name, int def)
-{
- return def;
-}
-
-FontSpec *platform_default_fontspec(const char *name)
-{
- return fontspec_new("");
-}
-
-Filename *platform_default_filename(const char *name)
-{
- return filename_from_str("");
-}
-
-char *x_get_default(const char *key)
-{
- return NULL; /* this is a stub */
-}
-
-void old_keyfile_warning(void) { }
-
-void timer_change_notify(unsigned long next)
-{
-}
-
-char *platform_get_x_display(void) { return NULL; }
-
-void make_unix_sftp_filehandle_key(void *vdata, size_t size)
-{
- /* psusan runs without a random number generator, so we can't make
- * this up by random_read. Fortunately, psusan is also
- * non-adversarial, so it's safe to generate this trivially. */
- unsigned char *data = (unsigned char *)vdata;
- for (size_t i = 0; i < size; i++)
- data[i] = (unsigned)rand() / ((unsigned)RAND_MAX / 256);
-}
-
-static bool verbose;
-
-struct server_instance {
- unsigned id;
- LogPolicy logpolicy;
-};
-
-static void log_to_stderr(unsigned id, const char *msg)
-{
- if (!verbose)
- return;
- if (id != (unsigned)-1)
- fprintf(stderr, "#%u: ", id);
- fputs(msg, stderr);
- fputc('\n', stderr);
- fflush(stderr);
-}
-
-static void server_eventlog(LogPolicy *lp, const char *event)
-{
- struct server_instance *inst = container_of(
- lp, struct server_instance, logpolicy);
- if (verbose)
- log_to_stderr(inst->id, event);
-}
-
-static void server_logging_error(LogPolicy *lp, const char *event)
-{
- struct server_instance *inst = container_of(
- lp, struct server_instance, logpolicy);
- log_to_stderr(inst->id, event); /* unconditional */
-}
-
-static int server_askappend(
- LogPolicy *lp, Filename *filename,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- return 2; /* always overwrite (FIXME: could make this a cmdline option) */
-}
-
-static const LogPolicyVtable server_logpolicy_vt = {
- .eventlog = server_eventlog,
- .askappend = server_askappend,
- .logging_error = server_logging_error,
- .verbose = null_lp_verbose_no,
-};
-
-static void show_help(FILE *fp)
-{
- fputs("usage: psusan [options]\n"
- "options: --listen SOCKETPATH listen for connections on a Unix-domain socket\n"
- " --listen-once (with --listen) stop after one connection\n"
- " --verbose print log messages to standard error\n"
- " --sessiondir DIR cwd for session subprocess (default $HOME)\n"
- " --sshlog FILE write ssh-connection packet log to FILE\n"
- " --sshrawlog FILE write packets and raw data log to FILE\n"
- "also: psusan --help show this text\n"
- " psusan --version show version information\n", fp);
-}
-
-static void show_version_and_exit(void)
-{
- char *buildinfo_text = buildinfo("\n");
- printf("%s: %s\n%s\n", appname, ver, buildinfo_text);
- sfree(buildinfo_text);
- exit(0);
-}
-
-const bool buildinfo_gtk_relevant = false;
-
-static bool listening = false, listen_once = false;
-static bool finished = false;
-void server_instance_terminated(LogPolicy *lp)
-{
- struct server_instance *inst = container_of(
- lp, struct server_instance, logpolicy);
-
- if (listening && !listen_once) {
- log_to_stderr(inst->id, "connection terminated");
- } else {
- finished = true;
- }
-
- sfree(inst);
-}
-
-bool psusan_continue(void *ctx, bool fd, bool cb)
-{
- return !finished;
-}
-
-static bool longoptarg(const char *arg, const char *expected,
- const char **val, int *argcp, char ***argvp)
-{
- int len = strlen(expected);
- if (memcmp(arg, expected, len))
- return false;
- if (arg[len] == '=') {
- *val = arg + len + 1;
- return true;
- } else if (arg[len] == '\0') {
- if (--*argcp > 0) {
- *val = *++*argvp;
- return true;
- } else {
- fprintf(stderr, "%s: option %s expects an argument\n",
- appname, expected);
- exit(1);
- }
- }
- return false;
-}
-
-static bool longoptnoarg(const char *arg, const char *expected)
-{
- int len = strlen(expected);
- if (memcmp(arg, expected, len))
- return false;
- if (arg[len] == '=') {
- fprintf(stderr, "%s: option %s expects no argument\n",
- appname, expected);
- exit(1);
- } else if (arg[len] == '\0') {
- return true;
- }
- return false;
-}
-
-struct server_config {
- Conf *conf;
- const SshServerConfig *ssc;
-
- unsigned next_id;
-
- Socket *listening_socket;
- Plug listening_plug;
-};
-
-static Plug *server_conn_plug(
- struct server_config *cfg, struct server_instance **inst_out)
-{
- struct server_instance *inst = snew(struct server_instance);
-
- memset(inst, 0, sizeof(*inst));
-
- inst->id = cfg->next_id++;
- inst->logpolicy.vt = &server_logpolicy_vt;
-
- if (inst_out)
- *inst_out = inst;
-
- return ssh_server_plug(
- cfg->conf, cfg->ssc, NULL, 0, NULL, NULL,
- &inst->logpolicy, &unix_live_sftpserver_vt);
-}
-
-static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
- const char *error_msg, int error_code)
-{
- log_to_stderr(-1, error_msg);
-}
-
-static void server_closing(Plug *plug, const char *error_msg, int error_code,
- bool calling_back)
-{
- log_to_stderr(-1, error_msg);
-}
-
-static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
-{
- struct server_config *cfg = container_of(
- p, struct server_config, listening_plug);
- Socket *s;
- const char *err;
-
- struct server_instance *inst;
-
- if (listen_once) {
- if (!cfg->listening_socket) /* in case of rapid double-accept */
- return 1;
- sk_close(cfg->listening_socket);
- cfg->listening_socket = NULL;
- }
-
- Plug *plug = server_conn_plug(cfg, &inst);
- s = constructor(ctx, plug);
- if ((err = sk_socket_error(s)) != NULL)
- return 1;
-
- SocketPeerInfo *pi = sk_peer_info(s);
-
- char *msg = dupprintf("new connection from %s", pi->log_text);
- log_to_stderr(inst->id, msg);
- sfree(msg);
- sk_free_peer_info(pi);
-
- sk_set_frozen(s, false);
- ssh_server_start(plug, s);
- return 0;
-}
-
-static const PlugVtable server_plugvt = {
- .log = server_log,
- .closing = server_closing,
- .accepting = server_accepting,
-};
-
-unsigned auth_methods(AuthPolicy *ap)
-{ return 0; }
-bool auth_none(AuthPolicy *ap, ptrlen username)
-{ return false; }
-int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password,
- ptrlen *new_password_opt)
-{ return 0; }
-bool auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob)
-{ return false; }
-RSAKey *auth_publickey_ssh1(
- AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus)
-{ return NULL; }
-AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username)
-{ return NULL; }
-int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses)
-{ return -1; }
-char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username)
-{ return NULL; }
-bool auth_ssh1int_response(AuthPolicy *ap, ptrlen response)
-{ return false; }
-bool auth_successful(AuthPolicy *ap, ptrlen username, unsigned method)
-{ return false; }
-
-int main(int argc, char **argv)
-{
- const char *listen_socket = NULL;
-
- SshServerConfig ssc;
-
- Conf *conf = make_ssh_server_conf();
-
- memset(&ssc, 0, sizeof(ssc));
-
- ssc.application_name = "PSUSAN";
- ssc.session_starting_dir = getenv("HOME");
- ssc.bare_connection = true;
-
- while (--argc > 0) {
- const char *arg = *++argv;
- const char *val;
-
- if (longoptnoarg(arg, "--help")) {
- show_help(stdout);
- exit(0);
- } else if (longoptnoarg(arg, "--version")) {
- show_version_and_exit();
- } else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) {
- verbose = true;
- } else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) {
- ssc.session_starting_dir = val;
- } else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) ||
- longoptarg(arg, "-sshlog", &val, &argc, &argv)) {
- Filename *logfile = filename_from_str(val);
- conf_set_filename(conf, CONF_logfilename, logfile);
- filename_free(logfile);
- conf_set_int(conf, CONF_logtype, LGTYP_PACKETS);
- conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
- } else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) ||
- longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) {
- Filename *logfile = filename_from_str(val);
- conf_set_filename(conf, CONF_logfilename, logfile);
- filename_free(logfile);
- conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW);
- conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
- } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) {
- listen_socket = val;
- } else if (!strcmp(arg, "--listen-once")) {
- listen_once = true;
- } else {
- fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg);
- exit(1);
- }
- }
-
- sk_init();
- uxsel_init();
-
- struct server_config scfg;
- scfg.conf = conf;
- scfg.ssc = &ssc;
- scfg.next_id = 0;
-
- if (listen_socket) {
- listening = true;
- scfg.listening_plug.vt = &server_plugvt;
- SockAddr *addr = unix_sock_addr(listen_socket);
- scfg.listening_socket = new_unix_listener(addr, &scfg.listening_plug);
- char *msg = dupprintf("listening on Unix socket %s", listen_socket);
- log_to_stderr(-1, msg);
- sfree(msg);
- } else {
- struct server_instance *inst;
- Plug *plug = server_conn_plug(&scfg, &inst);
- ssh_server_start(plug, make_fd_socket(0, 1, -1, plug));
- log_to_stderr(inst->id, "running directly on stdio");
- }
-
- cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check,
- psusan_continue, NULL);
-
- return 0;
-}
diff --git a/UNIX/uxserver.c b/UNIX/uxserver.c
deleted file mode 100644
index 448c6515..00000000
--- a/UNIX/uxserver.c
+++ /dev/null
@@ -1,850 +0,0 @@
-/*
- * SSH server for Unix: main program.
- *
- * ======================================================================
- *
- * This server is NOT SECURE!
- *
- * DO NOT DEPLOY IT IN A HOSTILE-FACING ENVIRONMENT!
- *
- * Its purpose is to speak the server end of everything PuTTY speaks
- * on the client side, so that I can test that I haven't broken PuTTY
- * when I reorganise its code, even things like RSA key exchange or
- * chained auth methods which it's hard to find a server that speaks
- * at all.
- *
- * It has no interaction with the OS's authentication system: the
- * authentications it will accept are configurable by command-line
- * option, and once you authenticate, it will run the connection
- * protocol - including all subprocesses and shells - under the same
- * Unix user id you started it under.
- *
- * It really is only suitable for testing the actual SSH protocol.
- * Don't use it for anything more serious!
- *
- * ======================================================================
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <signal.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <termios.h>
-#include <pwd.h>
-#include <sys/ioctl.h>
-#include <sys/time.h>
-
-#include "putty.h"
-#include "mpint.h"
-#include "ssh.h"
-#include "sshserver.h"
-
-const char *const appname = "uppity";
-
-void modalfatalbox(const char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "FATAL ERROR: ");
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
- exit(1);
-}
-void nonfatal(const char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "ERROR: ");
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
-}
-
-char *platform_default_s(const char *name)
-{
- return NULL;
-}
-
-bool platform_default_b(const char *name, bool def)
-{
- return def;
-}
-
-int platform_default_i(const char *name, int def)
-{
- return def;
-}
-
-FontSpec *platform_default_fontspec(const char *name)
-{
- return fontspec_new("");
-}
-
-Filename *platform_default_filename(const char *name)
-{
- return filename_from_str("");
-}
-
-char *x_get_default(const char *key)
-{
- return NULL; /* this is a stub */
-}
-
-void old_keyfile_warning(void) { }
-
-void timer_change_notify(unsigned long next)
-{
-}
-
-char *platform_get_x_display(void) { return NULL; }
-
-void make_unix_sftp_filehandle_key(void *data, size_t size)
-{
- random_read(data, size);
-}
-
-static bool verbose;
-
-struct AuthPolicyShared {
- struct AuthPolicy_ssh1_pubkey *ssh1keys;
- struct AuthPolicy_ssh2_pubkey *ssh2keys;
-};
-
-struct AuthPolicy {
- struct AuthPolicyShared *shared;
- int kbdint_state;
-};
-
-struct server_instance {
- unsigned id;
- AuthPolicy ap;
- LogPolicy logpolicy;
-};
-
-static void log_to_stderr(unsigned id, const char *msg)
-{
- if (id != (unsigned)-1)
- fprintf(stderr, "#%u: ", id);
- fputs(msg, stderr);
- fputc('\n', stderr);
- fflush(stderr);
-}
-
-static void server_eventlog(LogPolicy *lp, const char *event)
-{
- struct server_instance *inst = container_of(
- lp, struct server_instance, logpolicy);
- if (verbose)
- log_to_stderr(inst->id, event);
-}
-
-static void server_logging_error(LogPolicy *lp, const char *event)
-{
- struct server_instance *inst = container_of(
- lp, struct server_instance, logpolicy);
- log_to_stderr(inst->id, event); /* unconditional */
-}
-
-static int server_askappend(
- LogPolicy *lp, Filename *filename,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- return 2; /* always overwrite (FIXME: could make this a cmdline option) */
-}
-
-static const LogPolicyVtable server_logpolicy_vt = {
- .eventlog = server_eventlog,
- .askappend = server_askappend,
- .logging_error = server_logging_error,
- .verbose = null_lp_verbose_no,
-};
-
-struct AuthPolicy_ssh1_pubkey {
- RSAKey key;
- struct AuthPolicy_ssh1_pubkey *next;
-};
-struct AuthPolicy_ssh2_pubkey {
- ptrlen public_blob;
- struct AuthPolicy_ssh2_pubkey *next;
-};
-
-unsigned auth_methods(AuthPolicy *ap)
-{
- return (AUTHMETHOD_PUBLICKEY | AUTHMETHOD_PASSWORD | AUTHMETHOD_KBDINT |
- AUTHMETHOD_TIS | AUTHMETHOD_CRYPTOCARD);
-}
-bool auth_none(AuthPolicy *ap, ptrlen username)
-{
- return false;
-}
-int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password,
- ptrlen *new_password_opt)
-{
- const char *PHONY_GOOD_PASSWORD = "weasel";
- const char *PHONY_BAD_PASSWORD = "ferret";
-
- if (!new_password_opt) {
- /* Accept login with our preconfigured good password */
- if (ptrlen_eq_string(password, PHONY_GOOD_PASSWORD))
- return 1;
- /* Don't outright reject the bad password, but insist on a change */
- if (ptrlen_eq_string(password, PHONY_BAD_PASSWORD))
- return 2;
- /* Reject anything else */
- return 0;
- } else {
- /* In a password-change request, expect the bad password as input */
- if (!ptrlen_eq_string(password, PHONY_BAD_PASSWORD))
- return 0;
- /* Accept a request to change it to the good password */
- if (ptrlen_eq_string(*new_password_opt, PHONY_GOOD_PASSWORD))
- return 1;
- /* Outright reject a request to change it to the same password
- * as it already 'was' */
- if (ptrlen_eq_string(*new_password_opt, PHONY_BAD_PASSWORD))
- return 0;
- /* Anything else, pretend the new pw wasn't good enough, and
- * re-request a change */
- return 2;
- }
-}
-bool auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob)
-{
- struct AuthPolicy_ssh2_pubkey *iter;
- for (iter = ap->shared->ssh2keys; iter; iter = iter->next) {
- if (ptrlen_eq_ptrlen(public_blob, iter->public_blob))
- return true;
- }
- return false;
-}
-RSAKey *auth_publickey_ssh1(
- AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus)
-{
- struct AuthPolicy_ssh1_pubkey *iter;
- for (iter = ap->shared->ssh1keys; iter; iter = iter->next) {
- if (mp_cmp_eq(rsa_modulus, iter->key.modulus))
- return &iter->key;
- }
- return NULL;
-}
-AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username)
-{
- AuthKbdInt *aki;
-
- switch (ap->kbdint_state) {
- case 0:
- aki = snew(AuthKbdInt);
- aki->title = dupstr("Initial double prompt");
- aki->instruction =
- dupstr("First prompt should echo, second should not");
- aki->nprompts = 2;
- aki->prompts = snewn(aki->nprompts, AuthKbdIntPrompt);
- aki->prompts[0].prompt = dupstr("Echoey prompt: ");
- aki->prompts[0].echo = true;
- aki->prompts[1].prompt = dupstr("Silent prompt: ");
- aki->prompts[1].echo = false;
- return aki;
- case 1:
- aki = snew(AuthKbdInt);
- aki->title = dupstr("Zero-prompt step");
- aki->instruction = dupstr("Shouldn't see any prompts this time");
- aki->nprompts = 0;
- aki->prompts = NULL;
- return aki;
- default:
- ap->kbdint_state = 0;
- return NULL;
- }
-}
-int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses)
-{
- switch (ap->kbdint_state) {
- case 0:
- if (ptrlen_eq_string(responses[0], "stoat") &&
- ptrlen_eq_string(responses[1], "weasel")) {
- ap->kbdint_state++;
- return 0; /* those are the expected responses */
- } else {
- ap->kbdint_state = 0;
- return -1;
- }
- break;
- case 1:
- return +1; /* succeed after the zero-prompt step */
- default:
- ap->kbdint_state = 0;
- return -1;
- }
-}
-char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username)
-{
- /* FIXME: test returning a challenge string without \n, and ensure
- * it gets printed as a prompt in its own right, without PuTTY
- * making up a "Response: " prompt to follow it */
- return dupprintf("This is a dummy %s challenge!\n",
- (method == AUTHMETHOD_TIS ? "TIS" : "CryptoCard"));
-}
-bool auth_ssh1int_response(AuthPolicy *ap, ptrlen response)
-{
- return ptrlen_eq_string(response, "otter");
-}
-bool auth_successful(AuthPolicy *ap, ptrlen username, unsigned method)
-{
- return true;
-}
-
-static void safety_warning(FILE *fp)
-{
- fputs(" =================================================\n"
- " THIS SSH SERVER IS NOT WRITTEN TO BE SECURE!\n"
- " DO NOT DEPLOY IT IN A HOSTILE-FACING ENVIRONMENT!\n"
- " =================================================\n", fp);
-}
-
-static void show_help(FILE *fp)
-{
- safety_warning(fp);
- fputs("\n"
- "usage: uppity [options]\n"
- "options: --listen [PORT|PATH] listen to a port on localhost, or Unix socket\n"
- " --listen-once (with --listen) stop after one "
- "connection\n"
- " --hostkey KEY SSH host key (need at least one)\n"
- " --rsakexkey KEY key for SSH-2 RSA key exchange "
- "(in SSH-1 format)\n"
- " --userkey KEY public key"
- " acceptable for user authentication\n"
- " --sessiondir DIR cwd for session subprocess (default $HOME)\n"
- " --bannertext TEXT send TEXT as SSH-2 auth banner\n"
- " --bannerfile FILE send contents of FILE as SSH-2 auth "
- "banner\n"
- " --kexinit-kex STR override list of SSH-2 KEX methods\n"
- " --kexinit-hostkey STR override list of SSH-2 host key "
- "types\n"
- " --kexinit-cscipher STR override list of SSH-2 "
- "client->server ciphers\n"
- " --kexinit-sccipher STR override list of SSH-2 "
- "server->client ciphers\n"
- " --kexinit-csmac STR override list of SSH-2 "
- "client->server MACs\n"
- " --kexinit-scmac STR override list of SSH-2 "
- "server->client MACs\n"
- " --kexinit-cscomp STR override list of SSH-2 "
- "c->s compression types\n"
- " --kexinit-sccomp STR override list of SSH-2 "
- "s->c compression types\n"
- " --ssh1-ciphers STR override list of SSH-1 ciphers\n"
- " --ssh1-no-compression forbid compression in SSH-1\n"
- " --exitsignum send buggy numeric \"exit-signal\" "
- "message\n"
- " --verbose print event log messages to standard "
- "error\n"
- " --sshlog FILE write SSH packet log to FILE\n"
- " --sshrawlog FILE write SSH packets + raw data log"
- " to FILE\n"
- "also: uppity --help show this text\n"
- " uppity --version show version information\n"
- "\n", fp);
- safety_warning(fp);
-}
-
-static void show_version_and_exit(void)
-{
- char *buildinfo_text = buildinfo("\n");
- printf("%s: %s\n%s\n", appname, ver, buildinfo_text);
- sfree(buildinfo_text);
- exit(0);
-}
-
-const bool buildinfo_gtk_relevant = false;
-
-static bool listening = false, listen_once = false;
-static bool finished = false;
-void server_instance_terminated(LogPolicy *lp)
-{
- struct server_instance *inst = container_of(
- lp, struct server_instance, logpolicy);
-
- if (listening && !listen_once) {
- log_to_stderr(inst->id, "connection terminated");
- } else {
- finished = true;
- }
-
- sfree(inst);
-}
-
-static bool longoptarg(const char *arg, const char *expected,
- const char **val, int *argcp, char ***argvp)
-{
- int len = strlen(expected);
- if (memcmp(arg, expected, len))
- return false;
- if (arg[len] == '=') {
- *val = arg + len + 1;
- return true;
- } else if (arg[len] == '\0') {
- if (--*argcp > 0) {
- *val = *++*argvp;
- return true;
- } else {
- fprintf(stderr, "%s: option %s expects an argument\n",
- appname, expected);
- exit(1);
- }
- }
- return false;
-}
-
-static bool longoptnoarg(const char *arg, const char *expected)
-{
- int len = strlen(expected);
- if (memcmp(arg, expected, len))
- return false;
- if (arg[len] == '=') {
- fprintf(stderr, "%s: option %s expects no argument\n",
- appname, expected);
- exit(1);
- } else if (arg[len] == '\0') {
- return true;
- }
- return false;
-}
-
-struct server_config {
- Conf *conf;
- const SshServerConfig *ssc;
-
- ssh_key **hostkeys;
- int nhostkeys;
-
- RSAKey *hostkey1;
-
- struct AuthPolicyShared *ap_shared;
-
- unsigned next_id;
-
- Socket *listening_socket;
- Plug listening_plug;
-};
-
-static Plug *server_conn_plug(
- struct server_config *cfg, struct server_instance **inst_out)
-{
- struct server_instance *inst = snew(struct server_instance);
-
- memset(inst, 0, sizeof(*inst));
-
- inst->id = cfg->next_id++;
- inst->ap.shared = cfg->ap_shared;
- inst->logpolicy.vt = &server_logpolicy_vt;
-
- if (inst_out)
- *inst_out = inst;
-
- return ssh_server_plug(
- cfg->conf, cfg->ssc, cfg->hostkeys, cfg->nhostkeys, cfg->hostkey1,
- &inst->ap, &inst->logpolicy, &unix_live_sftpserver_vt);
-}
-
-static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
- const char *error_msg, int error_code)
-{
- log_to_stderr((unsigned)-1, error_msg);
-}
-
-static void server_closing(Plug *plug, const char *error_msg, int error_code,
- bool calling_back)
-{
- log_to_stderr((unsigned)-1, error_msg);
-}
-
-static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
-{
- struct server_config *cfg = container_of(
- p, struct server_config, listening_plug);
- Socket *s;
- const char *err;
-
- struct server_instance *inst;
-
- if (listen_once) {
- if (!cfg->listening_socket) /* in case of rapid double-accept */
- return 1;
- sk_close(cfg->listening_socket);
- cfg->listening_socket = NULL;
- }
-
- unsigned old_next_id = cfg->next_id;
-
- Plug *plug = server_conn_plug(cfg, &inst);
- s = constructor(ctx, plug);
- if ((err = sk_socket_error(s)) != NULL)
- return 1;
-
- SocketPeerInfo *pi = sk_peer_info(s);
-
- if (pi->addressfamily != ADDRTYPE_LOCAL && !sk_peer_trusted(s)) {
- fprintf(stderr, "rejected connection from %s (untrustworthy peer)\n",
- pi->log_text);
- sk_free_peer_info(pi);
- sk_close(s);
- cfg->next_id = old_next_id;
- return 1;
- }
-
- char *msg = dupprintf("new connection from %s", pi->log_text);
- log_to_stderr(inst->id, msg);
- sfree(msg);
- sk_free_peer_info(pi);
-
- sk_set_frozen(s, false);
- ssh_server_start(plug, s);
- return 0;
-}
-
-static const PlugVtable server_plugvt = {
- .log = server_log,
- .closing = server_closing,
- .accepting = server_accepting,
-};
-
-int main(int argc, char **argv)
-{
- int listen_port = -1;
- const char *listen_socket = NULL;
-
- ssh_key **hostkeys = NULL;
- size_t nhostkeys = 0, hostkeysize = 0;
- RSAKey *hostkey1 = NULL;
-
- struct AuthPolicyShared aps;
- SshServerConfig ssc;
-
- Conf *conf = make_ssh_server_conf();
-
- aps.ssh1keys = NULL;
- aps.ssh2keys = NULL;
-
- memset(&ssc, 0, sizeof(ssc));
-
- ssc.application_name = "Uppity";
- ssc.session_starting_dir = getenv("HOME");
- ssc.ssh1_cipher_mask = SSH1_SUPPORTED_CIPHER_MASK;
- ssc.ssh1_allow_compression = true;
-
- if (argc <= 1) {
- /*
- * We're going to terminate with an error message below,
- * because there are no host keys. But we'll display the help
- * as additional standard-error output, if nothing else so
- * that people see the giant safety warning.
- */
- show_help(stderr);
- fputc('\n', stderr);
- }
-
- while (--argc > 0) {
- const char *arg = *++argv;
- const char *val;
-
- if (!strcmp(arg, "--help")) {
- show_help(stdout);
- exit(0);
- } else if (longoptnoarg(arg, "--version")) {
- show_version_and_exit();
- } else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) {
- verbose = true;
- } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) {
- if (val[0] == '/') {
- listen_port = -1;
- listen_socket = val;
- } else {
- listen_port = atoi(val);
- listen_socket = NULL;
- }
- } else if (!strcmp(arg, "--listen-once")) {
- listen_once = true;
- } else if (longoptarg(arg, "--hostkey", &val, &argc, &argv)) {
- Filename *keyfile;
- int keytype;
- const char *error;
-
- keyfile = filename_from_str(val);
- keytype = key_type(keyfile);
-
- if (keytype == SSH_KEYTYPE_SSH2) {
- ssh2_userkey *uk;
- ssh_key *key;
- uk = ppk_load_f(keyfile, NULL, &error);
- filename_free(keyfile);
- if (!uk || !uk->key) {
- fprintf(stderr, "%s: unable to load host key '%s': "
- "%s\n", appname, val, error);
- exit(1);
- }
- char *invalid = ssh_key_invalid(uk->key, 0);
- if (invalid) {
- fprintf(stderr, "%s: host key '%s' is unusable: "
- "%s\n", appname, val, invalid);
- exit(1);
- }
- key = uk->key;
- sfree(uk->comment);
- sfree(uk);
-
- for (int i = 0; i < nhostkeys; i++)
- if (ssh_key_alg(hostkeys[i]) == ssh_key_alg(key)) {
- fprintf(stderr, "%s: host key '%s' duplicates key "
- "type %s\n", appname, val,
- ssh_key_alg(key)->ssh_id);
- exit(1);
- }
-
- sgrowarray(hostkeys, hostkeysize, nhostkeys);
- hostkeys[nhostkeys++] = key;
- } else if (keytype == SSH_KEYTYPE_SSH1) {
- if (hostkey1) {
- fprintf(stderr, "%s: host key '%s' is a redundant "
- "SSH-1 host key\n", appname, val);
- exit(1);
- }
- hostkey1 = snew(RSAKey);
- if (!rsa1_load_f(keyfile, hostkey1, NULL, &error)) {
- fprintf(stderr, "%s: unable to load host key '%s': "
- "%s\n", appname, val, error);
- exit(1);
- }
- } else {
- fprintf(stderr, "%s: '%s' is not loadable as a "
- "private key (%s)", appname, val,
- key_type_to_str(keytype));
- exit(1);
- }
- } else if (longoptarg(arg, "--rsakexkey", &val, &argc, &argv)) {
- Filename *keyfile;
- int keytype;
- const char *error;
-
- keyfile = filename_from_str(val);
- keytype = key_type(keyfile);
-
- if (keytype != SSH_KEYTYPE_SSH1) {
- fprintf(stderr, "%s: '%s' is not loadable as an SSH-1 format "
- "private key (%s)", appname, val,
- key_type_to_str(keytype));
- exit(1);
- }
-
- if (ssc.rsa_kex_key) {
- freersakey(ssc.rsa_kex_key);
- } else {
- ssc.rsa_kex_key = snew(RSAKey);
- }
-
- if (!rsa1_load_f(keyfile, ssc.rsa_kex_key, NULL, &error)) {
- fprintf(stderr, "%s: unable to load RSA kex key '%s': "
- "%s\n", appname, val, error);
- exit(1);
- }
-
- ssc.rsa_kex_key->sshk.vt = &ssh_rsa;
- } else if (longoptarg(arg, "--userkey", &val, &argc, &argv)) {
- Filename *keyfile;
- int keytype;
- const char *error;
-
- keyfile = filename_from_str(val);
- keytype = key_type(keyfile);
-
- if (keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
- keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
- strbuf *sb = strbuf_new();
- struct AuthPolicy_ssh2_pubkey *node;
- void *blob;
-
- if (!ppk_loadpub_f(keyfile, NULL, BinarySink_UPCAST(sb),
- NULL, &error)) {
- fprintf(stderr, "%s: unable to load user key '%s': "
- "%s\n", appname, val, error);
- exit(1);
- }
-
- node = snew_plus(struct AuthPolicy_ssh2_pubkey, sb->len);
- blob = snew_plus_get_aux(node);
- memcpy(blob, sb->u, sb->len);
- node->public_blob = make_ptrlen(blob, sb->len);
-
- node->next = aps.ssh2keys;
- aps.ssh2keys = node;
-
- strbuf_free(sb);
- } else if (keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
- strbuf *sb = strbuf_new();
- BinarySource src[1];
- struct AuthPolicy_ssh1_pubkey *node;
-
- if (!rsa1_loadpub_f(keyfile, BinarySink_UPCAST(sb),
- NULL, &error)) {
- fprintf(stderr, "%s: unable to load user key '%s': "
- "%s\n", appname, val, error);
- exit(1);
- }
-
- node = snew(struct AuthPolicy_ssh1_pubkey);
- BinarySource_BARE_INIT(src, sb->u, sb->len);
- get_rsa_ssh1_pub(src, &node->key, RSA_SSH1_EXPONENT_FIRST);
-
- node->next = aps.ssh1keys;
- aps.ssh1keys = node;
-
- strbuf_free(sb);
- } else {
- fprintf(stderr, "%s: '%s' is not loadable as a public key "
- "(%s)\n", appname, val, key_type_to_str(keytype));
- exit(1);
- }
- } else if (longoptarg(arg, "--bannerfile", &val, &argc, &argv)) {
- FILE *fp = fopen(val, "r");
- if (!fp) {
- fprintf(stderr, "%s: %s: open: %s\n", appname,
- val, strerror(errno));
- exit(1);
- }
- strbuf *sb = strbuf_new();
- if (!read_file_into(BinarySink_UPCAST(sb), fp)) {
- fprintf(stderr, "%s: %s: read: %s\n", appname,
- val, strerror(errno));
- exit(1);
- }
- fclose(fp);
- ssc.banner = ptrlen_from_strbuf(sb);
- } else if (longoptarg(arg, "--bannertext", &val, &argc, &argv)) {
- ssc.banner = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) {
- ssc.session_starting_dir = val;
- } else if (longoptarg(arg, "--kexinit-kex", &val, &argc, &argv)) {
- ssc.kex_override[KEXLIST_KEX] = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--kexinit-hostkey", &val, &argc, &argv)) {
- ssc.kex_override[KEXLIST_HOSTKEY] = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--kexinit-cscipher", &val, &argc, &argv)) {
- ssc.kex_override[KEXLIST_CSCIPHER] = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--kexinit-csmac", &val, &argc, &argv)) {
- ssc.kex_override[KEXLIST_CSMAC] = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--kexinit-cscomp", &val, &argc, &argv)) {
- ssc.kex_override[KEXLIST_CSCOMP] = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--kexinit-sccipher", &val, &argc, &argv)) {
- ssc.kex_override[KEXLIST_SCCIPHER] = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--kexinit-scmac", &val, &argc, &argv)) {
- ssc.kex_override[KEXLIST_SCMAC] = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--kexinit-sccomp", &val, &argc, &argv)) {
- ssc.kex_override[KEXLIST_SCCOMP] = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--ssh1-ciphers", &val, &argc, &argv)) {
- ptrlen list = ptrlen_from_asciz(val);
- ptrlen word;
- unsigned long mask = 0;
- while (word = ptrlen_get_word(&list, ","), word.len != 0) {
-
-#define SSH1_CIPHER_CASE(bitpos, name) \
- if (ptrlen_eq_string(word, name)) { \
- mask |= 1U << bitpos; \
- continue; \
- }
- SSH1_SUPPORTED_CIPHER_LIST(SSH1_CIPHER_CASE);
-#undef SSH1_CIPHER_CASE
-
- fprintf(stderr, "%s: unrecognised SSH-1 cipher '%.*s'\n",
- appname, PTRLEN_PRINTF(word));
- exit(1);
- }
- ssc.ssh1_cipher_mask = mask;
- } else if (longoptnoarg(arg, "--ssh1-no-compression")) {
- ssc.ssh1_allow_compression = false;
- } else if (longoptnoarg(arg, "--exitsignum")) {
- ssc.exit_signal_numeric = true;
- } else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) ||
- longoptarg(arg, "-sshlog", &val, &argc, &argv)) {
- Filename *logfile = filename_from_str(val);
- conf_set_filename(conf, CONF_logfilename, logfile);
- filename_free(logfile);
- conf_set_int(conf, CONF_logtype, LGTYP_PACKETS);
- conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
- } else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) ||
- longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) {
- Filename *logfile = filename_from_str(val);
- conf_set_filename(conf, CONF_logfilename, logfile);
- filename_free(logfile);
- conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW);
- conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
- } else if (!strcmp(arg, "--pretend-to-accept-any-pubkey")) {
- ssc.stunt_pretend_to_accept_any_pubkey = true;
- } else if (!strcmp(arg, "--open-unconditional-agent-socket")) {
- ssc.stunt_open_unconditional_agent_socket = true;
- } else {
- fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg);
- exit(1);
- }
- }
-
- if (nhostkeys == 0 && !hostkey1) {
- fprintf(stderr, "%s: specify at least one host key\n", appname);
- exit(1);
- }
-
- random_ref();
-
- /*
- * Block SIGPIPE, so that we'll get EPIPE individually on
- * particular network connections that go wrong.
- */
- putty_signal(SIGPIPE, SIG_IGN);
-
- sk_init();
- uxsel_init();
-
- struct server_config scfg;
- scfg.conf = conf;
- scfg.ssc = &ssc;
- scfg.hostkeys = hostkeys;
- scfg.nhostkeys = nhostkeys;
- scfg.hostkey1 = hostkey1;
- scfg.ap_shared = &aps;
- scfg.next_id = 0;
-
- if (listen_port >= 0 || listen_socket) {
- listening = true;
- scfg.listening_plug.vt = &server_plugvt;
- char *msg;
- if (listen_port >= 0) {
- scfg.listening_socket = sk_newlistener(
- NULL, listen_port, &scfg.listening_plug, true,
- ADDRTYPE_UNSPEC);
- msg = dupprintf("%s: listening on port %d",
- appname, listen_port);
- } else {
- SockAddr *addr = unix_sock_addr(listen_socket);
- scfg.listening_socket = new_unix_listener(
- addr, &scfg.listening_plug);
- msg = dupprintf("%s: listening on Unix socket %s",
- appname, listen_socket);
- }
-
- log_to_stderr(-1, msg);
- sfree(msg);
- } else {
- struct server_instance *inst;
- Plug *plug = server_conn_plug(&scfg, &inst);
- ssh_server_start(plug, make_fd_socket(0, 1, -1, plug));
- log_to_stderr(inst->id, "speaking SSH on stdio");
- }
-
- cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check,
- cliloop_always_continue, NULL);
-
- return 0;
-}
diff --git a/UNIX/uxsftpserver.c b/UNIX/uxsftpserver.c
deleted file mode 100644
index acefe9bd..00000000
--- a/UNIX/uxsftpserver.c
+++ /dev/null
@@ -1,703 +0,0 @@
-/*
- * Implement the SftpServer abstraction, in the 'live' form (i.e.
- * really operating on the Unix filesystem).
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/time.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <pwd.h>
-#include <grp.h>
-#include <dirent.h>
-#include <utime.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "sshserver.h"
-#include "sftp.h"
-#include "tree234.h"
-
-typedef struct UnixSftpServer UnixSftpServer;
-
-struct UnixSftpServer {
- unsigned *fdseqs;
- bool *fdsopen;
- size_t fdsize;
-
- tree234 *dirhandles;
- int last_dirhandle_index;
-
- char handlekey[8];
-
- SftpServer srv;
-};
-
-struct uss_dirhandle {
- int index;
- DIR *dp;
-};
-
-#define USS_DIRHANDLE_SEQ (0xFFFFFFFFU)
-
-static int uss_dirhandle_cmp(void *av, void *bv)
-{
- struct uss_dirhandle *a = (struct uss_dirhandle *)av;
- struct uss_dirhandle *b = (struct uss_dirhandle *)bv;
- if (a->index < b->index)
- return -1;
- if (a->index > b->index)
- return +1;
- return 0;
-}
-
-static SftpServer *uss_new(const SftpServerVtable *vt)
-{
- UnixSftpServer *uss = snew(UnixSftpServer);
-
- memset(uss, 0, sizeof(UnixSftpServer));
-
- uss->dirhandles = newtree234(uss_dirhandle_cmp);
- uss->srv.vt = vt;
-
- make_unix_sftp_filehandle_key(uss->handlekey, sizeof(uss->handlekey));
-
- return &uss->srv;
-}
-
-static void uss_free(SftpServer *srv)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
- struct uss_dirhandle *udh;
-
- for (size_t i = 0; i < uss->fdsize; i++)
- if (uss->fdsopen[i])
- close(i);
- sfree(uss->fdseqs);
-
- while ((udh = delpos234(uss->dirhandles, 0)) != NULL) {
- closedir(udh->dp);
- sfree(udh);
- }
-
- sfree(uss);
-}
-
-static void uss_return_handle_raw(
- UnixSftpServer *uss, SftpReplyBuilder *reply, int index, unsigned seq)
-{
- unsigned char handlebuf[8];
- PUT_32BIT_MSB_FIRST(handlebuf, index);
- PUT_32BIT_MSB_FIRST(handlebuf + 4, seq);
- des_encrypt_xdmauth(uss->handlekey, handlebuf, 8);
- fxp_reply_handle(reply, make_ptrlen(handlebuf, 8));
-}
-
-static bool uss_decode_handle(
- UnixSftpServer *uss, ptrlen handle, int *index, unsigned *seq)
-{
- unsigned char handlebuf[8];
-
- if (handle.len != 8)
- return false;
- memcpy(handlebuf, handle.ptr, 8);
- des_decrypt_xdmauth(uss->handlekey, handlebuf, 8);
- *index = toint(GET_32BIT_MSB_FIRST(handlebuf));
- *seq = GET_32BIT_MSB_FIRST(handlebuf + 4);
- return true;
-}
-
-static void uss_return_new_handle(
- UnixSftpServer *uss, SftpReplyBuilder *reply, int fd)
-{
- assert(fd >= 0);
- if (fd >= uss->fdsize) {
- size_t old_size = uss->fdsize;
- sgrowarray(uss->fdseqs, uss->fdsize, fd);
- uss->fdsopen = sresize(uss->fdsopen, uss->fdsize, bool);
- while (old_size < uss->fdsize) {
- uss->fdseqs[old_size] = 0;
- uss->fdsopen[old_size] = false;
- old_size++;
- }
- }
- assert(!uss->fdsopen[fd]);
- uss->fdsopen[fd] = true;
- if (++uss->fdseqs[fd] == USS_DIRHANDLE_SEQ)
- uss->fdseqs[fd] = 0;
- uss_return_handle_raw(uss, reply, fd, uss->fdseqs[fd]);
-}
-
-static int uss_try_lookup_fd(UnixSftpServer *uss, ptrlen handle)
-{
- int fd;
- unsigned seq;
- if (!uss_decode_handle(uss, handle, &fd, &seq) ||
- fd < 0 || fd >= uss->fdsize ||
- !uss->fdsopen[fd] || uss->fdseqs[fd] != seq)
- return -1;
-
- return fd;
-}
-
-static int uss_lookup_fd(UnixSftpServer *uss, SftpReplyBuilder *reply,
- ptrlen handle)
-{
- int fd = uss_try_lookup_fd(uss, handle);
- if (fd < 0)
- fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle");
- return fd;
-}
-
-static void uss_return_new_dirhandle(
- UnixSftpServer *uss, SftpReplyBuilder *reply, DIR *dp)
-{
- struct uss_dirhandle *udh = snew(struct uss_dirhandle);
- udh->index = uss->last_dirhandle_index++;
- udh->dp = dp;
- struct uss_dirhandle *added = add234(uss->dirhandles, udh);
- assert(added == udh);
- uss_return_handle_raw(uss, reply, udh->index, USS_DIRHANDLE_SEQ);
-}
-
-static struct uss_dirhandle *uss_try_lookup_dirhandle(
- UnixSftpServer *uss, ptrlen handle)
-{
- struct uss_dirhandle key, *udh;
- unsigned seq;
-
- if (!uss_decode_handle(uss, handle, &key.index, &seq) ||
- seq != USS_DIRHANDLE_SEQ ||
- (udh = find234(uss->dirhandles, &key, NULL)) == NULL)
- return NULL;
-
- return udh;
-}
-
-static struct uss_dirhandle *uss_lookup_dirhandle(
- UnixSftpServer *uss, SftpReplyBuilder *reply, ptrlen handle)
-{
- struct uss_dirhandle *udh = uss_try_lookup_dirhandle(uss, handle);
- if (!udh)
- fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle");
- return udh;
-}
-
-static void uss_error(UnixSftpServer *uss, SftpReplyBuilder *reply)
-{
- unsigned code = SSH_FX_FAILURE;
- switch (errno) {
- case ENOENT:
- code = SSH_FX_NO_SUCH_FILE;
- break;
- case EPERM:
- code = SSH_FX_PERMISSION_DENIED;
- break;
- }
- fxp_reply_error(reply, code, strerror(errno));
-}
-
-static void uss_realpath(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen path)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
-
- char *inpath = mkstr(path);
- char *outpath = realpath(inpath, NULL);
- free(inpath);
-
- if (!outpath) {
- uss_error(uss, reply);
- } else {
- fxp_reply_simple_name(reply, ptrlen_from_asciz(outpath));
- free(outpath);
- }
-}
-
-static void uss_open(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen path, unsigned flags, struct fxp_attrs attrs)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
-
- int openflags = 0;
- if (!((SSH_FXF_READ | SSH_FXF_WRITE) &~ flags))
- openflags |= O_RDWR;
- else if (flags & SSH_FXF_WRITE)
- openflags |= O_WRONLY;
- else if (flags & SSH_FXF_READ)
- openflags |= O_RDONLY;
- if (flags & SSH_FXF_APPEND)
- openflags |= O_APPEND;
- if (flags & SSH_FXF_CREAT)
- openflags |= O_CREAT;
- if (flags & SSH_FXF_TRUNC)
- openflags |= O_TRUNC;
- if (flags & SSH_FXF_EXCL)
- openflags |= O_EXCL;
-
- char *pathstr = mkstr(path);
- int fd = open(pathstr, openflags, GET_PERMISSIONS(attrs, 0777));
- free(pathstr);
-
- if (fd < 0) {
- uss_error(uss, reply);
- } else {
- uss_return_new_handle(uss, reply, fd);
- }
-}
-
-static void uss_opendir(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen path)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
-
- char *pathstr = mkstr(path);
- DIR *dp = opendir(pathstr);
- free(pathstr);
-
- if (!dp) {
- uss_error(uss, reply);
- } else {
- uss_return_new_dirhandle(uss, reply, dp);
- }
-}
-
-static void uss_close(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen handle)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
- int fd;
- struct uss_dirhandle *udh;
-
- if ((udh = uss_try_lookup_dirhandle(uss, handle)) != NULL) {
- closedir(udh->dp);
- del234(uss->dirhandles, udh);
- sfree(udh);
- fxp_reply_ok(reply);
- } else if ((fd = uss_lookup_fd(uss, reply, handle)) >= 0) {
- close(fd);
- assert(0 <= fd && fd <= uss->fdsize);
- uss->fdsopen[fd] = false;
- fxp_reply_ok(reply);
- }
- /* if both failed, uss_lookup_fd will have filled in an error response */
-}
-
-static void uss_mkdir(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen path, struct fxp_attrs attrs)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
-
- char *pathstr = mkstr(path);
- int status = mkdir(pathstr, GET_PERMISSIONS(attrs, 0777));
- free(pathstr);
-
- if (status < 0) {
- uss_error(uss, reply);
- } else {
- fxp_reply_ok(reply);
- }
-}
-
-static void uss_rmdir(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
-
- char *pathstr = mkstr(path);
- int status = rmdir(pathstr);
- free(pathstr);
-
- if (status < 0) {
- uss_error(uss, reply);
- } else {
- fxp_reply_ok(reply);
- }
-}
-
-static void uss_remove(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen path)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
-
- char *pathstr = mkstr(path);
- int status = unlink(pathstr);
- free(pathstr);
-
- if (status < 0) {
- uss_error(uss, reply);
- } else {
- fxp_reply_ok(reply);
- }
-}
-
-static void uss_rename(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen srcpath, ptrlen dstpath)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
-
- char *srcstr = mkstr(srcpath), *dststr = mkstr(dstpath);
- int status = rename(srcstr, dststr);
- free(srcstr);
- free(dststr);
-
- if (status < 0) {
- uss_error(uss, reply);
- } else {
- fxp_reply_ok(reply);
- }
-}
-
-static struct fxp_attrs uss_translate_struct_stat(const struct stat *st)
-{
- struct fxp_attrs attrs;
-
- attrs.flags = (SSH_FILEXFER_ATTR_SIZE |
- SSH_FILEXFER_ATTR_PERMISSIONS |
- SSH_FILEXFER_ATTR_UIDGID |
- SSH_FILEXFER_ATTR_ACMODTIME);
-
- attrs.size = st->st_size;
- attrs.permissions = st->st_mode;
- attrs.uid = st->st_uid;
- attrs.gid = st->st_gid;
- attrs.atime = st->st_atime;
- attrs.mtime = st->st_mtime;
-
- return attrs;
-}
-
-static void uss_reply_struct_stat(SftpReplyBuilder *reply,
- const struct stat *st)
-{
- fxp_reply_attrs(reply, uss_translate_struct_stat(st));
-}
-
-static void uss_stat(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen path, bool follow_symlinks)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
- struct stat st;
-
- char *pathstr = mkstr(path);
- int status = (follow_symlinks ? stat : lstat) (pathstr, &st);
- free(pathstr);
-
- if (status < 0) {
- uss_error(uss, reply);
- } else {
- uss_reply_struct_stat(reply, &st);
- }
-}
-
-static void uss_fstat(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen handle)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
- struct stat st;
- int fd;
-
- if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
- return;
- int status = fstat(fd, &st);
-
- if (status < 0) {
- uss_error(uss, reply);
- } else {
- uss_reply_struct_stat(reply, &st);
- }
-}
-
-#if !HAVE_FUTIMES
-static inline int futimes(int fd, const struct timeval tv[2])
-{
- /* If the OS doesn't support futimes(3) then we have to pretend it
- * always returns failure */
- errno = EINVAL;
- return -1;
-}
-#endif
-
-/*
- * The guts of setstat and fsetstat, macroised so that they can call
- * fchown(fd,...) or chown(path,...) depending on parameters.
- */
-#define SETSTAT_GUTS(api_prefix, api_arg, attrs, success) do \
- { \
- if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) \
- if (api_prefix(truncate)(api_arg, attrs.size) < 0) \
- success = false; \
- if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) \
- if (api_prefix(chown)(api_arg, attrs.uid, attrs.gid) < 0) \
- success = false; \
- if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) \
- if (api_prefix(chmod)(api_arg, attrs.permissions) < 0) \
- success = false; \
- if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) { \
- struct timeval tv[2]; \
- tv[0].tv_sec = attrs.atime; \
- tv[1].tv_sec = attrs.mtime; \
- tv[0].tv_usec = tv[1].tv_usec = 0; \
- if (api_prefix(utimes)(api_arg, tv) < 0) \
- success = false; \
- } \
- } while (0)
-
-#define PATH_PREFIX(func) func
-#define FD_PREFIX(func) f ## func
-
-static void uss_setstat(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen path, struct fxp_attrs attrs)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
-
- char *pathstr = mkstr(path);
- bool success = true;
- SETSTAT_GUTS(PATH_PREFIX, pathstr, attrs, success);
- free(pathstr);
-
- if (!success) {
- uss_error(uss, reply);
- } else {
- fxp_reply_ok(reply);
- }
-}
-
-static void uss_fsetstat(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen handle, struct fxp_attrs attrs)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
- int fd;
-
- if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
- return;
-
- bool success = true;
- SETSTAT_GUTS(FD_PREFIX, fd, attrs, success);
-
- if (!success) {
- uss_error(uss, reply);
- } else {
- fxp_reply_ok(reply);
- }
-}
-
-static void uss_read(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen handle, uint64_t offset, unsigned length)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
- int fd;
- char *buf;
-
- if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
- return;
-
- if ((buf = malloc(length)) == NULL) {
- /* A rare case in which I bother to check malloc failure,
- * because in this case we can localise the problem easily by
- * turning it into a failure response from this one sftp
- * request */
- fxp_reply_error(reply, SSH_FX_FAILURE,
- "Out of memory for read buffer");
- return;
- }
-
- char *p = buf;
-
- int status = lseek(fd, offset, SEEK_SET);
- if (status >= 0 || errno == ESPIPE) {
- bool seekable = (status >= 0);
- while (length > 0) {
- status = read(fd, p, length);
- if (status <= 0)
- break;
-
- unsigned bytes_read = status;
- assert(bytes_read <= length);
- length -= bytes_read;
- p += bytes_read;
-
- if (!seekable) {
- /*
- * If the seek failed because the file is fundamentally
- * not a seekable kind of thing, abandon this loop after
- * one attempt, i.e. we just read whatever we could get
- * and we don't mind returning a short buffer.
- */
- }
- }
- }
-
- if (status < 0) {
- uss_error(uss, reply);
- } else if (p == buf) {
- fxp_reply_error(reply, SSH_FX_EOF, "End of file");
- } else {
- fxp_reply_data(reply, make_ptrlen(buf, p - buf));
- }
-
- free(buf);
-}
-
-static void uss_write(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen handle, uint64_t offset, ptrlen data)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
- int fd;
-
- if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
- return;
-
- const char *p = data.ptr;
- unsigned length = data.len;
-
- int status = lseek(fd, offset, SEEK_SET);
- if (status >= 0 || errno == ESPIPE) {
-
- while (length > 0) {
- status = write(fd, p, length);
- assert(status != 0);
- if (status < 0)
- break;
-
- unsigned bytes_written = status;
- assert(bytes_written <= length);
- length -= bytes_written;
- p += bytes_written;
- }
- }
-
- if (status < 0) {
- uss_error(uss, reply);
- } else {
- fxp_reply_ok(reply);
- }
-}
-
-static void uss_readdir(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen handle, int max_entries, bool omit_longname)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
- struct dirent *de;
- struct uss_dirhandle *udh;
-
- if ((udh = uss_lookup_dirhandle(uss, reply, handle)) == NULL)
- return;
-
- errno = 0;
- de = readdir(udh->dp);
- if (!de) {
- if (errno == 0) {
- fxp_reply_error(reply, SSH_FX_EOF, "End of directory");
- } else {
- uss_error(uss, reply);
- }
- } else {
- ptrlen longname = PTRLEN_LITERAL("");
- char *longnamebuf = NULL;
- struct fxp_attrs attrs = no_attrs;
-
-#if defined HAVE_FSTATAT && defined HAVE_DIRFD
- struct stat st;
- if (!fstatat(dirfd(udh->dp), de->d_name, &st, AT_SYMLINK_NOFOLLOW)) {
- char perms[11], *uidbuf = NULL, *gidbuf = NULL;
- struct passwd *pwd;
- struct group *grp;
- const char *user, *group;
- struct tm tm;
-
- attrs = uss_translate_struct_stat(&st);
-
- if (!omit_longname) {
-
- strcpy(perms, "----------");
- switch (st.st_mode & S_IFMT) {
- case S_IFBLK: perms[0] = 'b'; break;
- case S_IFCHR: perms[0] = 'c'; break;
- case S_IFDIR: perms[0] = 'd'; break;
- case S_IFIFO: perms[0] = 'p'; break;
- case S_IFLNK: perms[0] = 'l'; break;
- case S_IFSOCK: perms[0] = 's'; break;
- }
- if (st.st_mode & S_IRUSR)
- perms[1] = 'r';
- if (st.st_mode & S_IWUSR)
- perms[2] = 'w';
- if (st.st_mode & S_IXUSR)
- perms[3] = (st.st_mode & S_ISUID ? 's' : 'x');
- else
- perms[3] = (st.st_mode & S_ISUID ? 'S' : '-');
- if (st.st_mode & S_IRGRP)
- perms[4] = 'r';
- if (st.st_mode & S_IWGRP)
- perms[5] = 'w';
- if (st.st_mode & S_IXGRP)
- perms[6] = (st.st_mode & S_ISGID ? 's' : 'x');
- else
- perms[6] = (st.st_mode & S_ISGID ? 'S' : '-');
- if (st.st_mode & S_IROTH)
- perms[7] = 'r';
- if (st.st_mode & S_IWOTH)
- perms[8] = 'w';
- if (st.st_mode & S_IXOTH)
- perms[9] = 'x';
-
- if ((pwd = getpwuid(st.st_uid)) != NULL)
- user = pwd->pw_name;
- else
- user = uidbuf = dupprintf("%u", (unsigned)st.st_uid);
- if ((grp = getgrgid(st.st_gid)) != NULL)
- group = grp->gr_name;
- else
- group = gidbuf = dupprintf("%u", (unsigned)st.st_gid);
-
- tm = *localtime(&st.st_mtime);
-
- longnamebuf = dupprintf(
- "%s %3u %-8s %-8s %8"PRIuMAX" %.3s %2d %02d:%02d %s",
- perms, (unsigned)st.st_nlink, user, group,
- (uintmax_t)st.st_size,
- (&"JanFebMarAprMayJunJulAugSepOctNovDec"[3*tm.tm_mon]),
- tm.tm_mday, tm.tm_hour, tm.tm_min, de->d_name);
- longname = ptrlen_from_asciz(longnamebuf);
-
- sfree(uidbuf);
- sfree(gidbuf);
- }
- }
-#endif
-
- /* FIXME: be able to return more than one, in which case we
- * must also check max_entries */
- fxp_reply_name_count(reply, 1);
- fxp_reply_full_name(reply, ptrlen_from_asciz(de->d_name),
- longname, attrs);
-
- sfree(longnamebuf);
- }
-}
-
-const SftpServerVtable unix_live_sftpserver_vt = {
- .new = uss_new,
- .free = uss_free,
- .realpath = uss_realpath,
- .open = uss_open,
- .opendir = uss_opendir,
- .close = uss_close,
- .mkdir = uss_mkdir,
- .rmdir = uss_rmdir,
- .remove = uss_remove,
- .rename = uss_rename,
- .stat = uss_stat,
- .fstat = uss_fstat,
- .setstat = uss_setstat,
- .fsetstat = uss_fsetstat,
- .read = uss_read,
- .write = uss_write,
- .readdir = uss_readdir,
-};
diff --git a/UNIX/uxshare.c b/UNIX/uxshare.c
deleted file mode 100644
index f1ef2019..00000000
--- a/UNIX/uxshare.c
+++ /dev/null
@@ -1,370 +0,0 @@
-/*
- * Unix implementation of SSH connection-sharing IPC setup.
- */
-
-#include <stdio.h>
-#include <assert.h>
-#include <errno.h>
-#include <limits.h>
-
-#include <unistd.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/file.h>
-
-#include "tree234.h"
-#include "putty.h"
-#include "network.h"
-#include "proxy.h"
-#include "ssh.h"
-
-#define CONNSHARE_SOCKETDIR_PREFIX "/tmp/putty-connshare"
-#define SALT_FILENAME "salt"
-#define SALT_SIZE 64
-#ifndef PIPE_BUF
-#define PIPE_BUF _POSIX_PIPE_BUF
-#endif
-
-static char *make_parentdir_name(void)
-{
- char *username, *parent;
-
- username = get_username();
- parent = dupprintf("%s.%s", CONNSHARE_SOCKETDIR_PREFIX, username);
- sfree(username);
- assert(*parent == '/');
-
- return parent;
-}
-
-static char *make_dirname(const char *pi_name, char **logtext)
-{
- char *name, *parentdirname, *dirname, *err;
-
- /*
- * First, create the top-level directory for all shared PuTTY
- * connections owned by this user.
- */
- parentdirname = make_parentdir_name();
- if ((err = make_dir_and_check_ours(parentdirname)) != NULL) {
- *logtext = err;
- sfree(parentdirname);
- return NULL;
- }
-
- /*
- * Transform the platform-independent version of the connection
- * identifier into the name we'll actually use for the directory
- * containing the Unix socket.
- *
- * We do this by hashing the identifier with some user-specific
- * secret information, to avoid the privacy leak of having
- * "user@host" strings show up in 'netstat -x'. (Irritatingly, the
- * full pathname of a Unix-domain socket _does_ show up in the
- * 'netstat -x' output, at least on Linux, even if that socket is
- * in a directory not readable to the user running netstat. You'd
- * think putting things inside an 0700 directory would hide their
- * names from other users, but no.)
- *
- * The secret information we use to salt the hash lives in a file
- * inside the top-level directory we just created, so we must
- * first create that file (with some fresh random data in it) if
- * it's not already been done by a previous PuTTY.
- */
- {
- unsigned char saltbuf[SALT_SIZE];
- char *saltname;
- int saltfd, i, ret;
-
- saltname = dupprintf("%s/%s", parentdirname, SALT_FILENAME);
- saltfd = open(saltname, O_RDONLY);
- if (saltfd < 0) {
- char *tmpname;
- int pid;
-
- if (errno != ENOENT) {
- *logtext = dupprintf("%s: open: %s", saltname,
- strerror(errno));
- sfree(saltname);
- sfree(parentdirname);
- return NULL;
- }
-
- /*
- * The salt file doesn't already exist, so try to create
- * it. Another process may be attempting the same thing
- * simultaneously, so we must do this carefully: we write
- * a salt file under a different name, then hard-link it
- * into place, which guarantees that we won't change the
- * contents of an existing salt file.
- */
- pid = getpid();
- for (i = 0;; i++) {
- tmpname = dupprintf("%s/%s.tmp.%d.%d",
- parentdirname, SALT_FILENAME, pid, i);
- saltfd = open(tmpname, O_WRONLY | O_EXCL | O_CREAT, 0400);
- if (saltfd >= 0)
- break;
- if (errno != EEXIST) {
- *logtext = dupprintf("%s: open: %s", tmpname,
- strerror(errno));
- sfree(tmpname);
- sfree(saltname);
- sfree(parentdirname);
- return NULL;
- }
- sfree(tmpname); /* go round and try again with i+1 */
- }
- /*
- * Invent some random data.
- */
- random_read(saltbuf, SALT_SIZE);
- ret = write(saltfd, saltbuf, SALT_SIZE);
- /* POSIX atomicity guarantee: because we wrote less than
- * PIPE_BUF bytes, the write either completed in full or
- * failed. */
- assert(SALT_SIZE < PIPE_BUF);
- assert(ret < 0 || ret == SALT_SIZE);
- if (ret < 0) {
- close(saltfd);
- *logtext = dupprintf("%s: write: %s", tmpname,
- strerror(errno));
- sfree(tmpname);
- sfree(saltname);
- sfree(parentdirname);
- return NULL;
- }
- if (close(saltfd) < 0) {
- *logtext = dupprintf("%s: close: %s", tmpname,
- strerror(errno));
- sfree(tmpname);
- sfree(saltname);
- sfree(parentdirname);
- return NULL;
- }
-
- /*
- * Now attempt to hard-link our temp file into place. We
- * tolerate EEXIST as an outcome, because that just means
- * another PuTTY got their attempt in before we did (and
- * we only care that there is a valid salt file we can
- * agree on, no matter who created it).
- */
- if (link(tmpname, saltname) < 0 && errno != EEXIST) {
- *logtext = dupprintf("%s: link: %s", saltname,
- strerror(errno));
- sfree(tmpname);
- sfree(saltname);
- sfree(parentdirname);
- return NULL;
- }
-
- /*
- * Whether that succeeded or not, get rid of our temp file.
- */
- if (unlink(tmpname) < 0) {
- *logtext = dupprintf("%s: unlink: %s", tmpname,
- strerror(errno));
- sfree(tmpname);
- sfree(saltname);
- sfree(parentdirname);
- return NULL;
- }
-
- /*
- * And now we've arranged for there to be a salt file, so
- * we can try to open it for reading again and this time
- * expect it to work.
- */
- sfree(tmpname);
-
- saltfd = open(saltname, O_RDONLY);
- if (saltfd < 0) {
- *logtext = dupprintf("%s: open: %s", saltname,
- strerror(errno));
- sfree(saltname);
- sfree(parentdirname);
- return NULL;
- }
- }
-
- for (i = 0; i < SALT_SIZE; i++) {
- ret = read(saltfd, saltbuf, SALT_SIZE);
- if (ret <= 0) {
- close(saltfd);
- *logtext = dupprintf("%s: read: %s", saltname,
- ret == 0 ? "unexpected EOF" :
- strerror(errno));
- sfree(saltname);
- sfree(parentdirname);
- return NULL;
- }
- assert(0 < ret && ret <= SALT_SIZE - i);
- i += ret;
- }
-
- close(saltfd);
- sfree(saltname);
-
- /*
- * Now we've got our salt, hash it with the connection
- * identifier to produce our actual socket name.
- */
- {
- unsigned char digest[32];
- char retbuf[65];
-
- ssh_hash *h = ssh_hash_new(&ssh_sha256);
- put_string(h, saltbuf, SALT_SIZE);
- put_stringz(h, pi_name);
- ssh_hash_final(h, digest);
-
- /*
- * And make it printable.
- */
- for (i = 0; i < 32; i++) {
- sprintf(retbuf + 2*i, "%02x", digest[i]);
- /* the last of those will also write the trailing NUL */
- }
-
- name = dupstr(retbuf);
- }
-
- smemclr(saltbuf, sizeof(saltbuf));
- }
-
- dirname = dupprintf("%s/%s", parentdirname, name);
- sfree(parentdirname);
- sfree(name);
-
- return dirname;
-}
-
-int platform_ssh_share(const char *pi_name, Conf *conf,
- Plug *downplug, Plug *upplug, Socket **sock,
- char **logtext, char **ds_err, char **us_err,
- bool can_upstream, bool can_downstream)
-{
- char *dirname, *lockname, *sockname, *err;
- int lockfd;
- Socket *retsock;
-
- /*
- * Sort out what we're going to call the directory in which we
- * keep the socket. This has the side effect of potentially
- * creating its top-level containing dir and/or the salt file
- * within that, if they don't already exist.
- */
- dirname = make_dirname(pi_name, logtext);
- if (!dirname) {
- return SHARE_NONE;
- }
-
- /*
- * Now make sure the subdirectory exists.
- */
- if ((err = make_dir_and_check_ours(dirname)) != NULL) {
- *logtext = err;
- sfree(dirname);
- return SHARE_NONE;
- }
-
- /*
- * Acquire a lock on a file in that directory.
- */
- lockname = dupcat(dirname, "/lock");
- lockfd = open(lockname, O_CREAT | O_RDWR | O_TRUNC, 0600);
- if (lockfd < 0) {
- *logtext = dupprintf("%s: open: %s", lockname, strerror(errno));
- sfree(dirname);
- sfree(lockname);
- return SHARE_NONE;
- }
- if (flock(lockfd, LOCK_EX) < 0) {
- *logtext = dupprintf("%s: flock(LOCK_EX): %s",
- lockname, strerror(errno));
- sfree(dirname);
- sfree(lockname);
- close(lockfd);
- return SHARE_NONE;
- }
-
- sockname = dupprintf("%s/socket", dirname);
-
- *logtext = NULL;
-
- if (can_downstream) {
- retsock = new_connection(unix_sock_addr(sockname),
- "", 0, false, true, false, false,
- downplug, conf);
- if (sk_socket_error(retsock) == NULL) {
- sfree(*logtext);
- *logtext = sockname;
- *sock = retsock;
- sfree(dirname);
- sfree(lockname);
- close(lockfd);
- return SHARE_DOWNSTREAM;
- }
- sfree(*ds_err);
- *ds_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
- sk_close(retsock);
- }
-
- if (can_upstream) {
- retsock = new_unix_listener(unix_sock_addr(sockname), upplug);
- if (sk_socket_error(retsock) == NULL) {
- sfree(*logtext);
- *logtext = sockname;
- *sock = retsock;
- sfree(dirname);
- sfree(lockname);
- close(lockfd);
- return SHARE_UPSTREAM;
- }
- sfree(*us_err);
- *us_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
- sk_close(retsock);
- }
-
- /* One of the above clauses ought to have happened. */
- assert(*logtext || *ds_err || *us_err);
-
- sfree(dirname);
- sfree(lockname);
- sfree(sockname);
- close(lockfd);
- return SHARE_NONE;
-}
-
-void platform_ssh_share_cleanup(const char *name)
-{
- char *dirname, *filename, *logtext;
-
- dirname = make_dirname(name, &logtext);
- if (!dirname) {
- sfree(logtext); /* we can't do much with this */
- return;
- }
-
- filename = dupcat(dirname, "/socket");
- remove(filename);
- sfree(filename);
-
- filename = dupcat(dirname, "/lock");
- remove(filename);
- sfree(filename);
-
- rmdir(dirname);
-
- /*
- * We deliberately _don't_ clean up the parent directory
- * /tmp/putty-connshare.<username>, because if we leave it around
- * then it reduces the ability for other users to be a nuisance by
- * putting their own directory in the way of it. Also, the salt
- * file in it can be reused.
- */
-
- sfree(dirname);
-}
diff --git a/UNIX/uxsocks.c b/UNIX/uxsocks.c
deleted file mode 100644
index 91613afd..00000000
--- a/UNIX/uxsocks.c
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Main program for Unix psocks.
- */
-
-#include <string.h>
-#include <errno.h>
-
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <signal.h>
-#include <fcntl.h>
-#include <unistd.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "psocks.h"
-
-const bool buildinfo_gtk_relevant = false;
-
-typedef struct PsocksDataSinkPopen {
- stdio_sink sink[2];
- PsocksDataSink pds;
-} PsocksDataSinkPopen;
-
-static void popen_free(PsocksDataSink *pds)
-{
- PsocksDataSinkPopen *pdsp = container_of(pds, PsocksDataSinkPopen, pds);
- for (size_t i = 0; i < 2; i++)
- pclose(pdsp->sink[i].fp);
- sfree(pdsp);
-}
-
-static PsocksDataSink *open_pipes(
- const char *cmd, const char *const *direction_args,
- const char *index_arg, char **err)
-{
- FILE *fp[2];
- char *errmsg = NULL;
-
- for (size_t i = 0; i < 2; i++) {
- /* No escaping needed: the provided command is already
- * shell-quoted, and our extra arguments are simple */
- char *command = dupprintf("%s %s %s", cmd,
- direction_args[i], index_arg);
-
- fp[i] = popen(command, "w");
- sfree(command);
-
- if (!fp[i]) {
- if (!errmsg)
- errmsg = dupprintf("%s", strerror(errno));
- }
- }
-
- if (errmsg) {
- for (size_t i = 0; i < 2; i++)
- if (fp[i])
- pclose(fp[i]);
- *err = errmsg;
- return NULL;
- }
-
- PsocksDataSinkPopen *pdsp = snew(PsocksDataSinkPopen);
-
- for (size_t i = 0; i < 2; i++) {
- setvbuf(fp[i], NULL, _IONBF, 0);
- stdio_sink_init(&pdsp->sink[i], fp[i]);
- pdsp->pds.s[i] = BinarySink_UPCAST(&pdsp->sink[i]);
- }
-
- pdsp->pds.free = popen_free;
-
- return &pdsp->pds;
-}
-
-static int signalpipe[2] = { -1, -1 };
-static void sigchld(int signum)
-{
- if (write(signalpipe[1], "x", 1) <= 0)
- /* not much we can do about it */;
-}
-
-static pid_t subcommand_pid = -1;
-
-static bool still_running = true;
-
-static void start_subcommand(strbuf *args)
-{
- pid_t pid;
-
- /*
- * Set up the pipe we'll use to tell us about SIGCHLD.
- */
- if (pipe(signalpipe) < 0) {
- perror("pipe");
- exit(1);
- }
- putty_signal(SIGCHLD, sigchld);
-
- /*
- * Make an array of argument pointers that execvp will like.
- */
- size_t nargs = 0;
- for (size_t i = 0; i < args->len; i++)
- if (args->s[i] == '\0')
- nargs++;
-
- char **exec_args = snewn(nargs + 1, char *);
- char *p = args->s;
- for (size_t a = 0; a < nargs; a++) {
- exec_args[a] = p;
- size_t len = strlen(p);
- assert(len < args->len - (p - args->s));
- p += 1 + len;
- }
- exec_args[nargs] = NULL;
-
- pid = fork();
- if (pid < 0) {
- perror("fork");
- exit(1);
- } else if (pid == 0) {
- execvp(exec_args[0], exec_args);
- perror("exec");
- _exit(127);
- } else {
- subcommand_pid = pid;
- sfree(exec_args);
- }
-}
-
-static const PsocksPlatform platform = {
- open_pipes,
- start_subcommand,
-};
-
-static bool psocks_pw_setup(void *ctx, pollwrapper *pw)
-{
- if (signalpipe[0] >= 0)
- pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R);
- return true;
-}
-
-static void psocks_pw_check(void *ctx, pollwrapper *pw)
-{
- if (signalpipe[0] >= 0 &&
- pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) {
- while (true) {
- int status;
- pid_t pid = waitpid(-1, &status, WNOHANG);
- if (pid <= 0)
- break;
- if (pid == subcommand_pid)
- still_running = false;
- }
- }
-}
-
-static bool psocks_continue(void *ctx, bool found_any_fd,
- bool ran_any_callback)
-{
- return still_running;
-}
-
-typedef bool (*cliloop_continue_t)(void *ctx, bool found_any_fd,
- bool ran_any_callback);
-
-int main(int argc, char **argv)
-{
- psocks_state *ps = psocks_new(&platform);
- psocks_cmdline(ps, argc, argv);
-
- sk_init();
- uxsel_init();
- psocks_start(ps);
-
- cli_main_loop(psocks_pw_setup, psocks_pw_check, psocks_continue, NULL);
-}
diff --git a/UNIX/uxutils.c b/UNIX/uxutils.c
deleted file mode 100644
index 3a04c1be..00000000
--- a/UNIX/uxutils.c
+++ /dev/null
@@ -1,65 +0,0 @@
-#include "putty.h"
-#include "ssh.h"
-
-#include "uxutils.h"
-
-#if defined __arm__ || defined __aarch64__
-
-bool platform_aes_hw_available(void)
-{
-#if defined HWCAP_AES
- return getauxval(AT_HWCAP) & HWCAP_AES;
-#elif defined HWCAP2_AES
- return getauxval(AT_HWCAP2) & HWCAP2_AES;
-#elif defined __APPLE__
- /* M1 macOS defines no optional sysctl flag indicating presence of
- * the AES extension, which I assume to be because it's always
- * present */
- return true;
-#else
- return false;
-#endif
-}
-
-bool platform_sha256_hw_available(void)
-{
-#if defined HWCAP_SHA2
- return getauxval(AT_HWCAP) & HWCAP_SHA2;
-#elif defined HWCAP2_SHA2
- return getauxval(AT_HWCAP2) & HWCAP2_SHA2;
-#elif defined __APPLE__
- /* Assume always present on M1 macOS, similarly to AES */
- return true;
-#else
- return false;
-#endif
-}
-
-bool platform_sha1_hw_available(void)
-{
-#if defined HWCAP_SHA1
- return getauxval(AT_HWCAP) & HWCAP_SHA1;
-#elif defined HWCAP2_SHA1
- return getauxval(AT_HWCAP2) & HWCAP2_SHA1;
-#elif defined __APPLE__
- /* Assume always present on M1 macOS, similarly to AES */
- return true;
-#else
- return false;
-#endif
-}
-
-bool platform_sha512_hw_available(void)
-{
-#if defined HWCAP_SHA512
- return getauxval(AT_HWCAP) & HWCAP_SHA512;
-#elif defined HWCAP2_SHA512
- return getauxval(AT_HWCAP2) & HWCAP2_SHA512;
-#elif defined __APPLE__
- return test_sysctl_flag("hw.optional.armv8_2_sha512");
-#else
- return false;
-#endif
-}
-
-#endif /* defined __arm__ || defined __aarch64__ */
diff --git a/UNIX/uxutils.h b/UNIX/uxutils.h
deleted file mode 100644
index c9acff53..00000000
--- a/UNIX/uxutils.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * uxutils.h: header included only by uxutils.c.
- *
- * The only reason this is a header file instead of a source file is
- * so that I can define 'static inline' functions which may or may not
- * be used, without provoking a compiler warning when I turn out not
- * to use them in the subsequent source file.
- */
-
-#ifndef PUTTY_UXUTILS_H
-#define PUTTY_UXUTILS_H
-
-#if defined __APPLE__
-#ifdef HAVE_SYS_SYSCTL_H
-#include <sys/sysctl.h>
-#endif
-#endif /* defined __APPLE__ */
-
-#if defined __arm__ || defined __aarch64__
-
-#ifdef HAVE_SYS_TYPES_H
-#include <sys/types.h>
-#endif
-
-#ifdef HAVE_SYS_AUXV_H
-#include <sys/auxv.h>
-#endif
-
-#ifdef HAVE_ASM_HWCAP_H
-#include <asm/hwcap.h>
-#endif
-
-#if defined HAVE_GETAUXVAL
-/* No code needed: getauxval has just the API we want already */
-#elif defined HAVE_ELF_AUX_INFO
-/* Implement the simple getauxval API in terms of FreeBSD elf_aux_info */
-static inline u_long getauxval(int which)
-{
- u_long toret;
- if (elf_aux_info(which, &toret, sizeof(toret)) != 0)
- return 0; /* elf_aux_info didn't work */
- return toret;
-}
-#else
-/* Implement a stub getauxval which returns no capabilities */
-static inline u_long getauxval(int which) { return 0; }
-#endif
-
-#endif /* defined __arm__ || defined __aarch64__ */
-
-#if defined __APPLE__
-static inline bool test_sysctl_flag(const char *flagname)
-{
-#ifdef HAVE_SYSCTLBYNAME
- int value;
- size_t size = sizeof(value);
- return (sysctlbyname(flagname, &value, &size, NULL, 0) == 0 &&
- size == sizeof(value) && value != 0);
-#else /* HAVE_SYSCTLBYNAME */
- return false;
-#endif /* HAVE_SYSCTLBYNAME */
-}
-#endif /* defined __APPLE__ */
-
-#endif /* PUTTY_UXUTILS_H */
diff --git a/UNIX/x11misc.c b/UNIX/x11misc.c
deleted file mode 100644
index e1fd1906..00000000
--- a/UNIX/x11misc.c
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * x11misc.c: miscellaneous stuff for dealing directly with X servers.
- */
-
-#include <ctype.h>
-#include <unistd.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include "putty.h"
-
-#ifndef NOT_X_WINDOWS
-
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-
-#include "x11misc.h"
-
-/* ----------------------------------------------------------------------
- * Error handling mechanism which permits us to ignore specific X11
- * errors from particular requests. We maintain a list of upcoming
- * potential error events that we want to not treat as fatal errors.
- */
-
-static int (*orig_x11_error_handler)(Display *thisdisp, XErrorEvent *err);
-
-struct x11_err_to_ignore {
- Display *display;
- unsigned char error_code;
- unsigned long serial;
-};
-
-static struct x11_err_to_ignore *errs;
-static size_t nerrs, errsize;
-
-static int x11_error_handler(Display *thisdisp, XErrorEvent *err)
-{
- for (size_t i = 0; i < nerrs; i++) {
- if (thisdisp == errs[i].display &&
- err->serial == errs[i].serial &&
- err->error_code == errs[i].error_code) {
- /* Ok, this is an error we're happy to ignore */
- return 0;
- }
- }
-
- return (*orig_x11_error_handler)(thisdisp, err);
-}
-
-void x11_ignore_error(Display *disp, unsigned char errcode)
-{
- /*
- * Install our error handler, if we haven't already.
- */
- if (!orig_x11_error_handler)
- orig_x11_error_handler = XSetErrorHandler(x11_error_handler);
-
- /*
- * This is as good a moment as any to winnow the ignore list based
- * on requests we know to have been processed.
- */
- {
- unsigned long last = LastKnownRequestProcessed(disp);
- size_t i, j;
- for (i = j = 0; i < nerrs; i++) {
- if (errs[i].display == disp && errs[i].serial <= last)
- continue;
- errs[j++] = errs[i];
- }
- nerrs = j;
- }
-
- sgrowarray(errs, errsize, nerrs);
- errs[nerrs].display = disp;
- errs[nerrs].error_code = errcode;
- errs[nerrs].serial = NextRequest(disp);
- nerrs++;
-}
-
-#endif
-
diff --git a/UNIX/x11misc.h b/UNIX/x11misc.h
index 159d4226..5f5a2d26 100644
--- a/UNIX/x11misc.h
+++ b/UNIX/x11misc.h
@@ -7,14 +7,8 @@
#ifndef NOT_X_WINDOWS
-/*
- * x11misc.c.
- */
+/* Defined in unix/utils */
void x11_ignore_error(Display *disp, unsigned char errcode);
-
-/*
- * gtkmisc.c
- */
Display *get_x11_display(void);
#endif
diff --git a/VERSION.C b/VERSION.C
deleted file mode 100644
index 620879c3..00000000
--- a/VERSION.C
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * PuTTY version numbering
- */
-
-/*
- * The difficult part of deciding what goes in these version strings
- * is done in Buildscr, and then written into version.h. All we have
- * to do here is to drop it into variables of the right names.
- */
-
-#include "putty.h"
-#include "ssh.h"
-
-#ifdef SOURCE_COMMIT
-#include "empty.h"
-#endif
-
-#include "version.h"
-
-const char ver[] = TEXTVER;
-const char sshver[] = SSHVER;
-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/WCWIDTH.C b/WCWIDTH.C
deleted file mode 100644
index 6468fedd..00000000
--- a/WCWIDTH.C
+++ /dev/null
@@ -1,558 +0,0 @@
-/*
- * This is an implementation of wcwidth() and wcswidth() (defined in
- * IEEE Std 1002.1-2001) for Unicode.
- *
- * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
- * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
- *
- * In fixed-width output devices, Latin characters all occupy a single
- * "cell" position of equal width, whereas ideographic CJK characters
- * occupy two such cells. Interoperability between terminal-line
- * applications and (teletype-style) character terminals using the
- * UTF-8 encoding requires agreement on which character should advance
- * the cursor by how many cell positions. No established formal
- * standards exist at present on which Unicode character shall occupy
- * how many cell positions on character terminals. These routines are
- * a first attempt of defining such behavior based on simple rules
- * applied to data provided by the Unicode Consortium.
- *
- * For some graphical characters, the Unicode standard explicitly
- * defines a character-cell width via the definition of the East Asian
- * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
- * In all these cases, there is no ambiguity about which width a
- * terminal shall use. For characters in the East Asian Ambiguous (A)
- * class, the width choice depends purely on a preference of backward
- * compatibility with either historic CJK or Western practice.
- * Choosing single-width for these characters is easy to justify as
- * the appropriate long-term solution, as the CJK practice of
- * displaying these characters as double-width comes from historic
- * implementation simplicity (8-bit encoded characters were displayed
- * single-width and 16-bit ones double-width, even for Greek,
- * Cyrillic, etc.) and not any typographic considerations.
- *
- * Much less clear is the choice of width for the Not East Asian
- * (Neutral) class. Existing practice does not dictate a width for any
- * of these characters. It would nevertheless make sense
- * typographically to allocate two character cells to characters such
- * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
- * represented adequately with a single-width glyph. The following
- * routines at present merely assign a single-cell width to all
- * neutral characters, in the interest of simplicity. This is not
- * entirely satisfactory and should be reconsidered before
- * establishing a formal standard in this area. At the moment, the
- * decision which Not East Asian (Neutral) characters should be
- * represented by double-width glyphs cannot yet be answered by
- * applying a simple rule from the Unicode database content. Setting
- * up a proper standard for the behavior of UTF-8 character terminals
- * will require a careful analysis not only of each Unicode character,
- * but also of each presentation form, something the author of these
- * routines has avoided to do so far.
- *
- * http://www.unicode.org/unicode/reports/tr11/
- *
- * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
- *
- * Permission to use, copy, modify, and distribute this software
- * for any purpose and without fee is hereby granted. The author
- * disclaims all warranties with regard to this software.
- *
- * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
- */
-
-#include <wchar.h>
-
-#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/WILDCARD.C b/WILDCARD.C
deleted file mode 100644
index 697feb9a..00000000
--- a/WILDCARD.C
+++ /dev/null
@@ -1,486 +0,0 @@
-/*
- * Wildcard matching engine for use with SFTP-based file transfer
- * programs (PSFTP, new-look PSCP): since SFTP has no notion of
- * getting the remote side to do globbing (and rightly so) we have
- * to do it locally, by retrieving all the filenames in a directory
- * and checking each against the wildcard pattern.
- */
-
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-
-#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/WINDOWS/PAGEANT.RC b/WINDOWS/PAGEANT.RC
index a4a15195..1bea78b4 100644
--- a/WINDOWS/PAGEANT.RC
+++ b/WINDOWS/PAGEANT.RC
@@ -9,7 +9,7 @@
#include "pageant-rc.h"
-#include "winhelp.rc2"
+#include "help.rc2"
IDI_MAINICON ICON "pageant.ico"
IDI_TRAYICON ICON "pageants.ico"
@@ -51,8 +51,8 @@ STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Pageant Key List"
FONT 8, "MS Shell Dlg"
BEGIN
- LISTBOX 100, 10, 10, 420, 155,
- LBS_EXTENDEDSEL | LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL | WS_TABSTOP
+ LISTBOX IDC_KEYLIST_LISTBOX, 10, 10, 420, 155,
+ LBS_EXTENDEDSEL | LBS_OWNERDRAWFIXED | WS_VSCROLL | WS_TABSTOP
PUSHBUTTON "&Add Key", IDC_KEYLIST_ADDKEY, 10, 187, 60, 14
PUSHBUTTON "Add Key (&encrypted)", IDC_KEYLIST_ADDKEY_ENC, 75, 187, 80, 14
PUSHBUTTON "Re-e&ncrypt", IDC_KEYLIST_REENCRYPT, 315, 187, 60, 14
@@ -60,7 +60,7 @@ BEGIN
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
+ COMBOBOX IDC_KEYLIST_FPTYPE, 70, 170, 100, 12, CBS_DROPDOWNLIST
END
/* Accelerators used: cl */
diff --git a/WINDOWS/PUTTY.RC b/WINDOWS/PUTTY.RC
index 14f62f48..b8df49f2 100644
--- a/WINDOWS/PUTTY.RC
+++ b/WINDOWS/PUTTY.RC
@@ -1,10 +1,14 @@
#include "rcstuff.h"
+#include "putty-rc.h"
#define APPNAME "PuTTYNG"
#define APPDESC "SSH, Telnet, Rlogin, and SUPDUP client"
-#include "winhelp.rc2"
-#include "win_res.rc2"
+IDI_MAINICON ICON "putty.ico"
+IDI_CFGICON ICON "puttycfg.ico"
+
+#include "help.rc2"
+#include "putty-common.rc2"
#ifndef NO_MANIFESTS
1 RT_MANIFEST "putty.mft"
diff --git a/WINDOWS/PUTTYGEN.RC b/WINDOWS/PUTTYGEN.RC
index b910b6a3..fbe14fc3 100644
--- a/WINDOWS/PUTTYGEN.RC
+++ b/WINDOWS/PUTTYGEN.RC
@@ -7,7 +7,7 @@
#define APPNAME "PuTTYgen"
#define APPDESC "PuTTY SSH key generation utility"
-#include "winhelp.rc2"
+#include "help.rc2"
#include "puttygen-rc.h"
200 ICON "puttygen.ico"
@@ -82,6 +82,16 @@ BEGIN
PUSHBUTTON "&Cancel", IDCANCEL, 134, 80, 40, 14
END
+/* Accelerators used: clw */
+216 DIALOG DISCARDABLE 140, 40, 450, 300
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "PuTTYgen: certificate information"
+FONT 8, "MS Shell Dlg"
+CLASS "PuTTYgenCertInfo"
+BEGIN
+ DEFPUSHBUTTON "&Close", IDOK, 201, 130, 48, 14
+END
+
#include "version.rc2"
#ifndef NO_MANIFESTS
diff --git a/WINDOWS/PUTTYTEL.RC b/WINDOWS/PUTTYTEL.RC
index 259bc683..41767a10 100644
--- a/WINDOWS/PUTTYTEL.RC
+++ b/WINDOWS/PUTTYTEL.RC
@@ -1,10 +1,14 @@
#include "rcstuff.h"
+#include "putty-rc.h"
#define APPNAME "PuTTYtel"
#define APPDESC "Telnet and Rlogin client"
-#include "winhelp.rc2"
-#include "win_res.rc2"
+IDI_MAINICON ICON "putty.ico"
+IDI_CFGICON ICON "puttycfg.ico"
+
+#include "help.rc2"
+#include "putty-common.rc2"
#ifndef NO_MANIFESTS
1 RT_MANIFEST "puttytel.mft"
diff --git a/WINDOWS/RCSTUFF.H b/WINDOWS/RCSTUFF.H
index ee2c7696..dbace3f5 100644
--- a/WINDOWS/RCSTUFF.H
+++ b/WINDOWS/RCSTUFF.H
@@ -5,20 +5,15 @@
#ifndef PUTTY_RCSTUFF_H
#define PUTTY_RCSTUFF_H
-#ifdef __LCC__
-#include <win.h>
-#else
+#ifdef HAVE_CMAKE_H
+#include "cmake.h"
+#endif
-/* Some compilers don't have winresrc.h */
-#ifndef NO_WINRESRC_H
-#ifndef MSVC4
+#if HAVE_WINRESRC_H
#include <winresrc.h>
-#else
+#elif HAVE_WINRES_H
#include <winres.h>
#endif
-#endif
-
-#endif /* end #ifdef __LCC__ */
/* Some systems don't define this, so I do it myself if necessary */
#ifndef TCS_MULTILINE
diff --git a/WINDOWS/WINCFG.C b/WINDOWS/WINCFG.C
deleted file mode 100644
index fab3240f..00000000
--- a/WINDOWS/WINCFG.C
+++ /dev/null
@@ -1,405 +0,0 @@
-/*
- * wincfg.c - the Windows-specific parts of the PuTTY configuration
- * box.
- */
-
-#include <assert.h>
-#include <stdlib.h>
-
-#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));
- }
-}
-
-void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help,
- bool midsession, int protocol)
-{
- const struct BackendVtable *backvt;
- bool resize_forbidden = false;
- struct controlset *s;
- 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;
- if (has_help) {
- c = ctrl_pushbutton(s, "Help", 'h', HELPCTX(no_help),
- help_handler, P(hwndp));
- c->generic.column = 1;
- }
- }
-
- /*
- * Full-screen mode is a Windows peculiarity; hence
- * scrollbar_in_fullscreen is as well.
- */
- s = ctrl_getset(b, "Window", "scrollback",
- "Control the scrollback in the window");
- ctrl_checkbox(s, "Display scrollbar in full screen mode", 'i',
- HELPCTX(window_scrollback),
- conf_checkbox_handler,
- I(CONF_scrollbar_in_fullscreen));
- /*
- * Really this wants to go just after `Display scrollbar'. See
- * if we can find that control, and do some shuffling.
- */
- {
- int i;
- for (i = 0; i < s->ncontrols; i++) {
- c = s->ctrls[i];
- if (c->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));
- ctrl_checkbox(s, "System menu appears on ALT-Space", 'y',
- HELPCTX(behaviour_altspace),
- conf_checkbox_handler, I(CONF_alt_space));
- ctrl_checkbox(s, "System menu appears on ALT alone", 'l',
- HELPCTX(behaviour_altonly),
- conf_checkbox_handler, I(CONF_alt_only));
- ctrl_checkbox(s, "Ensure window is always on top", 'e',
- HELPCTX(behaviour_alwaysontop),
- conf_checkbox_handler, I(CONF_alwaysontop));
- ctrl_checkbox(s, "Full screen on Alt-Enter", 'f',
- HELPCTX(behaviour_altenter),
- conf_checkbox_handler,
- I(CONF_fullscreenonaltenter));
-
- /*
- * Windows supports a local-command proxy. 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));
- }
-}
diff --git a/WINDOWS/WINCONS.C b/WINDOWS/WINCONS.C
deleted file mode 100644
index 414167b4..00000000
--- a/WINDOWS/WINCONS.C
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * wincons.c - various interactive-prompt routines shared between
- * the Windows console PuTTY tools
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "putty.h"
-#include "storage.h"
-#include "ssh.h"
-#include "console.h"
-
-void cleanup_exit(int code)
-{
- /*
- * Clean up.
- */
- sk_cleanup();
-
- random_save_seed();
-
- exit(code);
-}
-
-void console_print_error_msg(const char *prefix, const char *msg)
-{
- fputs(prefix, stderr);
- fputs(": ", stderr);
- fputs(msg, stderr);
- fputc('\n', stderr);
- fflush(stderr);
-}
-
-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]);
- 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 */
-
- 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] == '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/WINDOWS/WINCTRLS.C b/WINDOWS/WINCTRLS.C
deleted file mode 100644
index 59129eab..00000000
--- a/WINDOWS/WINCTRLS.C
+++ /dev/null
@@ -1,2600 +0,0 @@
-/*
- * 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 <assert.h>
-#include <ctype.h>
-
-#include "putty.h"
-#include "misc.h"
-#include "dialog.h"
-
-#include <commctrl.h>
-
-#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);
- 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;
- 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;
- }
-
- /*
- * 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;
-}
-
-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;
- 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);
-}
diff --git a/WINDOWS/WINDEFS.C b/WINDOWS/WINDEFS.C
deleted file mode 100644
index 006e8dc5..00000000
--- a/WINDOWS/WINDEFS.C
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * windefs.c: default settings that are specific to Windows.
- */
-
-#include "putty.h"
-
-#include <commctrl.h>
-
-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"))
- return filename_from_str("putty.log");
- else
- return filename_from_str("");
-}
-
-char *platform_default_s(const char *name)
-{
- if (!strcmp(name, "SerialLine"))
- return dupstr("COM1");
- return NULL;
-}
-
-bool platform_default_b(const char *name, bool def)
-{
- return def;
-}
-
-int platform_default_i(const char *name, int def)
-{
- return def;
-}
diff --git a/WINDOWS/WINDLG.C b/WINDOWS/WINDLG.C
deleted file mode 100644
index 9c5fdb76..00000000
--- a/WINDOWS/WINDLG.C
+++ /dev/null
@@ -1,1156 +0,0 @@
-/*
- * windlg.c - dialogs for PuTTY(tel), including the configuration dialog.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-#include <assert.h>
-#include <ctype.h>
-#include <time.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "win_res.h"
-#include "winseat.h"
-#include "storage.h"
-#include "dialog.h"
-#include "licence.h"
-
-#include <commctrl.h>
-#include <commdlg.h>
-#include <shellapi.h>
-
-#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;
-
-#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;
-}
-
-static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg,
- WPARAM wParam, LPARAM lParam)
-{
- char *str;
-
- switch (msg) {
- case WM_INITDIALOG: {
- str = dupprintf("About %s", appname);
- SetWindowText(hwnd, str);
- sfree(str);
- char *buildinfo_text = buildinfo("\r\n");
- char *text = dupprintf
- ("%s\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s",
- appname, ver, buildinfo_text,
- "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved.");
- sfree(buildinfo_text);
- SetDlgItemText(hwnd, IDA_TEXT, text);
- MakeDlgItemBorderless(hwnd, IDA_TEXT);
- sfree(text);
- return 1;
- }
- case WM_COMMAND:
- switch (LOWORD(wParam)) {
- case IDOK:
- case IDCANCEL:
- EndDialog(hwnd, true);
- return 0;
- case IDA_LICENCE:
- EnableWindow(hwnd, 0);
- DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCEBOX),
- hwnd, LicenceProc);
- EnableWindow(hwnd, 1);
- SetActiveWindow(hwnd);
- return 0;
-
- case IDA_WEB:
- /* Load web browser */
- ShellExecute(hwnd, "open",
- "https://www.chiark.greenend.org.uk/~sgtatham/putty/",
- 0, 0, SW_SHOWDEFAULT);
- return 0;
- }
- return 0;
- case WM_CLOSE:
- EndDialog(hwnd, true);
- return 0;
- }
- return 0;
-}
-
-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:
- 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,
- (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);
- }
-
- /*
- * 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);
- 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);
- DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
- EnableWindow(hwnd, 1);
- SetActiveWindow(hwnd);
-}
-
-void show_help(HWND hwnd)
-{
- launch_help(hwnd, NULL);
-}
-
-void defuse_showwindow(void)
-{
- /*
- * Work around the fact that the app's first call to ShowWindow
- * will ignore the default in favour of the shell-provided
- * setting.
- */
- {
- HWND hwnd;
- hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX),
- NULL, NullDlgProc);
- ShowWindow(hwnd, SW_HIDE);
- SetActiveWindow(hwnd);
- DestroyWindow(hwnd);
- }
-}
-
-bool do_config(Conf *conf)
-{
- bool ret;
-
- 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);
-
- 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);
-
- conf_free(backup_conf);
-
- return ret;
-}
-
-static void win_gui_eventlog(LogPolicy *lp, const char *string)
-{
- char timebuf[40];
- char **location;
- struct tm tm;
-
- tm=ltime();
- strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
-
- if (ninitial < LOGEVENT_INITIAL_MAX)
- location = &events_initial[ninitial];
- else
- location = &events_circular[(circular_first + ncircular) % LOGEVENT_CIRCULAR_MAX];
-
- if (*location)
- sfree(*location);
- *location = dupcat(timebuf, string);
- if (logbox) {
- int count;
- SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING,
- 0, (LPARAM) *location);
- count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0);
- SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0);
- }
- if (ninitial < LOGEVENT_INITIAL_MAX) {
- ninitial++;
- } else if (ncircular < LOGEVENT_CIRCULAR_MAX) {
- ncircular++;
- } else if (ncircular == LOGEVENT_CIRCULAR_MAX) {
- circular_first = (circular_first + 1) % LOGEVENT_CIRCULAR_MAX;
- sfree(events_circular[circular_first]);
- events_circular[circular_first] = dupstr("..");
- }
-}
-
-static void win_gui_logging_error(LogPolicy *lp, const char *event)
-{
- WinGuiSeat *wgs = container_of(lp, WinGuiSeat, logpolicy);
-
- /* Send 'can't open log file' errors to the terminal window.
- * (Marked as stderr, although terminal.c won't care.) */
- seat_stderr_pl(&wgs->seat, ptrlen_from_asciz(event));
- seat_stderr_pl(&wgs->seat, PTRLEN_LITERAL("\r\n"));
-}
-
-void showeventlog(HWND hwnd)
-{
- if (!logbox) {
- logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX),
- hwnd, LogProc);
- ShowWindow(logbox, SW_SHOWNORMAL);
- }
- SetActiveWindow(logbox);
-}
-
-void showabout(HWND hwnd)
-{
- DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
-}
-
-struct hostkey_dialog_ctx {
- 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);
- }
-
- 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/WINDOWS/WINDOW.C b/WINDOWS/WINDOW.C
index 82bbe8e6..979e2ed3 100644
--- a/WINDOWS/WINDOW.C
+++ b/WINDOWS/WINDOW.C
@@ -121,11 +121,51 @@ static int offset_width, offset_height;
static bool was_zoomed = false;
static int prev_rows, prev_cols;
+<<<<<<< HEAD
static void flash_window(WinGuiSeat *wgs, int mode);
static void sys_cursor_update(WinGuiSeat *wgs);
static bool get_fullscreen_rect(WinGuiSeat *wgs, RECT *ss);
static void conf_cache_data(WinGuiSeat *wgs);
+=======
+static 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 cmdline_get_passwd_input_state cmdline_get_passwd_state;
+
+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;
+
+static Conf *conf;
+static LogContext *logctx;
+static Terminal *term;
+
+static void conf_cache_data(void);
+static int cursor_type;
+static int vtmode;
+>>>>>>> tags/0.78
static struct sesslist sesslist; /* for saved-session menu */
@@ -139,6 +179,24 @@ DECL_WINDOWS_FUNCTION(static, HRESULT, AdjustWindowRectExForDpi, (LPRECT lpRect,
static HBITMAP caretbm;
+<<<<<<< HEAD
+=======
+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 wchar_t *window_name, *icon_name;
+
+static int compose_state = 0;
+
+>>>>>>> tags/0.78
static UINT wm_mousewheel = WM_MOUSEWHEEL;
struct WinGuiSeatListNode wgslisthead = {
@@ -289,7 +347,13 @@ static void start_backend(WinGuiSeat *wgs)
char *error, *realhost;
int i;
+<<<<<<< HEAD
wgs->cmdline_get_passwd_state = cmdline_get_passwd_input_state_new;
+=======
+ cmdline_get_passwd_state = cmdline_get_passwd_input_state_new;
+
+ vt = backend_vt_from_conf(conf);
+>>>>>>> tags/0.78
vt = backend_vt_from_conf(wgs->conf);
@@ -308,7 +372,11 @@ static void start_backend(WinGuiSeat *wgs)
msg = dupprintf("Unable to open terminal:\n%s", error);
} else {
msg = dupprintf("Unable to open connection to\n%s\n%s",
+<<<<<<< HEAD
conf_dest(wgs->conf), error);
+=======
+ conf_dest(conf), error);
+>>>>>>> tags/0.78
}
sfree(error);
MessageBox(NULL, msg, str, MB_ICONERROR | MB_OK);
@@ -351,8 +419,13 @@ static void close_session(void *vctx)
wgs->session_closed = true;
newtitle = dupprintf("%s (inactive)", appname);
+<<<<<<< HEAD
win_set_icon_title(&wgs->termwin, newtitle, DEFAULT_CODEPAGE);
win_set_title(&wgs->termwin, newtitle, DEFAULT_CODEPAGE);
+=======
+ win_set_icon_title(wintw, newtitle, DEFAULT_CODEPAGE);
+ win_set_title(wintw, newtitle, DEFAULT_CODEPAGE);
+>>>>>>> tags/0.78
sfree(newtitle);
if (wgs->ldisc) {
@@ -514,11 +587,19 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
/*
* Process the command line.
*/
+<<<<<<< HEAD
gui_term_process_cmdline(wgs->conf, cmdline);
memset(&wgs->ucsdata, 0, sizeof(wgs->ucsdata));
conf_cache_data(wgs);
+=======
+ gui_term_process_cmdline(conf, cmdline);
+
+ memset(&ucsdata, 0, sizeof(ucsdata));
+
+ conf_cache_data();
+>>>>>>> tags/0.78
/*
* Guess some defaults for the window size. This all gets
@@ -553,9 +634,15 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN)
resize_forbidden = true;
wchar_t *uappname = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname);
+<<<<<<< HEAD
wgs->window_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname);
wgs->icon_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname);
if (!conf_get_bool(wgs->conf, CONF_scrollbar))
+=======
+ window_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname);
+ icon_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname);
+ if (!conf_get_bool(conf, CONF_scrollbar))
+>>>>>>> tags/0.78
winmode &= ~(WS_VSCROLL);
if (conf_get_int(wgs->conf, CONF_resize_action) == RESIZE_DISABLED ||
resize_forbidden)
@@ -568,40 +655,65 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
#ifdef TEST_ANSI_WINDOW
/* For developer testing of ANSI window support, pretend
* CreateWindowExW failed */
+<<<<<<< HEAD
wgs->term_hwnd = NULL;
+=======
+ wgs.term_hwnd = NULL;
+>>>>>>> tags/0.78
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
#else
unicode_window = true;
sw_PeekMessage = PeekMessageW;
sw_DispatchMessage = DispatchMessageW;
sw_DefWindowProc = DefWindowProcW;
+<<<<<<< HEAD
wgs->term_hwnd = CreateWindowExW(
+=======
+ wgs.term_hwnd = CreateWindowExW(
+>>>>>>> tags/0.78
exwinmode, terminal_window_class_w(), uappname,
winmode, CW_USEDEFAULT, CW_USEDEFAULT,
guess_width, guess_height, NULL, NULL, inst, NULL);
#endif
#if defined LEGACY_WINDOWS || defined TEST_ANSI_WINDOW
+<<<<<<< HEAD
if (!wgs->term_hwnd && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) {
+=======
+ if (!wgs.term_hwnd && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) {
+>>>>>>> tags/0.78
/* Fall back to an ANSI window, swapping in all the ANSI
* window message handling functions */
unicode_window = false;
sw_PeekMessage = PeekMessageA;
sw_DispatchMessage = DispatchMessageA;
sw_DefWindowProc = DefWindowProcA;
+<<<<<<< HEAD
wgs->term_hwnd = CreateWindowExA(
+=======
+ wgs.term_hwnd = CreateWindowExA(
+>>>>>>> tags/0.78
exwinmode, terminal_window_class_a(), appname,
winmode, CW_USEDEFAULT, CW_USEDEFAULT,
guess_width, guess_height, NULL, NULL, inst, NULL);
}
#endif
+<<<<<<< HEAD
if (!wgs->term_hwnd) {
modalfatalbox("Unable to create terminal window: %s",
win_strerror(GetLastError()));
}
memset(&wgs->dpi_info, 0, sizeof(struct _dpi_info));
init_dpi_info(wgs);
+=======
+ if (!wgs.term_hwnd) {
+ modalfatalbox("Unable to create terminal window: %s",
+ win_strerror(GetLastError()));
+ }
+ memset(&dpi_info, 0, sizeof(struct _dpi_info));
+ init_dpi_info();
+>>>>>>> tags/0.78
sfree(uappname);
}
@@ -759,6 +871,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
gui_terminal_ready(wgs->term_hwnd, &wgs->seat, wgs->backend);
+ gui_terminal_ready(wgs.term_hwnd, &wgs.seat, backend);
+
while (1) {
int n;
DWORD timeout;
@@ -837,6 +951,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
return msg.wParam; /* ... but optimiser doesn't know */
}
+<<<<<<< HEAD
static void wgs_cleanup(WinGuiSeat *wgs)
{
deinit_fonts(wgs);
@@ -847,6 +962,8 @@ static void wgs_cleanup(WinGuiSeat *wgs)
sfree(wgs);
}
+=======
+>>>>>>> tags/0.78
char *handle_restrict_acl_cmdline_prefix(char *p)
{
/*
@@ -1345,11 +1462,19 @@ static int get_font_width(HDC hdc, const TEXTMETRIC *tm)
static void init_dpi_info(WinGuiSeat *wgs)
{
+<<<<<<< HEAD
if (wgs->dpi_info.cur_dpi.x == 0 || wgs->dpi_info.cur_dpi.y == 0) {
if (p_GetDpiForMonitor && p_MonitorFromWindow) {
UINT dpiX, dpiY;
HMONITOR currentMonitor = p_MonitorFromWindow(
wgs->term_hwnd, MONITOR_DEFAULTTOPRIMARY);
+=======
+ if (dpi_info.cur_dpi.x == 0 || dpi_info.cur_dpi.y == 0) {
+ if (p_GetDpiForMonitor && p_MonitorFromWindow) {
+ UINT dpiX, dpiY;
+ HMONITOR currentMonitor = p_MonitorFromWindow(
+ wgs.term_hwnd, MONITOR_DEFAULTTOPRIMARY);
+>>>>>>> tags/0.78
if (p_GetDpiForMonitor(currentMonitor, MDT_EFFECTIVE_DPI,
&dpiX, &dpiY) == S_OK) {
wgs->dpi_info.cur_dpi.x = (int)dpiX;
@@ -1472,12 +1597,21 @@ static void init_fonts(WinGuiSeat *wgs, int pick_width, int pick_height)
/* !!! Yes the next line is right */
if (cset == OEM_CHARSET)
+<<<<<<< HEAD
wgs->ucsdata.font_codepage = GetOEMCP();
else if (TranslateCharsetInfo ((DWORD *)(ULONG_PTR)cset,
&info, TCI_SRCCHARSET))
wgs->ucsdata.font_codepage = info.ciACP;
else
wgs->ucsdata.font_codepage = -1;
+=======
+ ucsdata.font_codepage = GetOEMCP();
+ else if (TranslateCharsetInfo ((DWORD *)(ULONG_PTR)cset,
+ &info, TCI_SRCCHARSET))
+ ucsdata.font_codepage = info.ciACP;
+ else
+ ucsdata.font_codepage = -1;
+>>>>>>> tags/0.78
GetCPInfo(wgs->ucsdata.font_codepage, &cpinfo);
wgs->ucsdata.dbcs_screenfont = (cpinfo.MaxCharSize > 1);
@@ -1658,9 +1792,15 @@ static void wintw_request_resize(TermWin *tw, int w, int h)
int width, height;
/* If the window is maximized suppress resizing attempts */
+<<<<<<< HEAD
if (IsZoomed(wgs->term_hwnd)) {
if (conf_get_int(wgs->conf, CONF_resize_action) == RESIZE_TERM) {
term_resize_request_completed(wgs->term);
+=======
+ if (IsZoomed(wgs.term_hwnd)) {
+ if (conf_get_int(conf, CONF_resize_action) == RESIZE_TERM) {
+ term_resize_request_completed(term);
+>>>>>>> tags/0.78
return;
}
}
@@ -1674,13 +1814,21 @@ static void wintw_request_resize(TermWin *tw, int w, int h)
/* Sanity checks ... */
{
RECT ss;
+<<<<<<< HEAD
if (get_fullscreen_rect(wgs, &ss)) {
+=======
+ if (get_fullscreen_rect(&ss)) {
+>>>>>>> tags/0.78
/* Make sure the values aren't too big */
width = (ss.right - ss.left - extra_width) / 4;
height = (ss.bottom - ss.top - extra_height) / 6;
if (w > width || h > height) {
+<<<<<<< HEAD
term_resize_request_completed(wgs->term);
+=======
+ term_resize_request_completed(term);
+>>>>>>> tags/0.78
return;
}
if (w < 15)
@@ -1690,12 +1838,21 @@ static void wintw_request_resize(TermWin *tw, int w, int h)
}
}
+<<<<<<< HEAD
if (conf_get_int(wgs->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,
+=======
+ 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,
+>>>>>>> tags/0.78
SWP_NOACTIVATE | SWP_NOCOPYBITS |
SWP_NOMOVE | SWP_NOZORDER);
} else {
@@ -1704,12 +1861,21 @@ static void wintw_request_resize(TermWin *tw, int w, int h)
* terminal the new size immediately, so that reset_window
* will know what to do.
*/
+<<<<<<< HEAD
term_size(wgs->term, h, w, conf_get_int(wgs->conf, CONF_savelines));
reset_window(wgs, 0);
}
term_resize_request_completed(wgs->term);
InvalidateRect(wgs->term_hwnd, NULL, true);
+=======
+ term_size(term, h, w, conf_get_int(conf, CONF_savelines));
+ reset_window(0);
+ }
+
+ term_resize_request_completed(term);
+ InvalidateRect(wgs.term_hwnd, NULL, true);
+>>>>>>> tags/0.78
}
static void recompute_window_offset(WinGuiSeat *wgs)
@@ -1837,6 +2003,7 @@ static void reset_window(WinGuiSeat *wgs, int reinit)
wgs->dpi_info.cur_dpi.x);
rect.right += (window_border * 2);
rect.bottom += (window_border * 2);
+<<<<<<< HEAD
OffsetRect(&wgs->dpi_info.new_wnd_rect,
((wgs->dpi_info.new_wnd_rect.right -
wgs->dpi_info.new_wnd_rect.left) -
@@ -1847,6 +2014,15 @@ static void reset_window(WinGuiSeat *wgs, int reinit)
SetWindowPos(wgs->term_hwnd, NULL,
wgs->dpi_info.new_wnd_rect.left,
wgs->dpi_info.new_wnd_rect.top,
+=======
+ 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,
+>>>>>>> tags/0.78
rect.right - rect.left, rect.bottom - rect.top,
SWP_NOZORDER);
@@ -1900,7 +2076,11 @@ static void reset_window(WinGuiSeat *wgs, int reinit)
static RECT ss;
int width, height;
+<<<<<<< HEAD
get_fullscreen_rect(wgs, &ss);
+=======
+ get_fullscreen_rect(&ss);
+>>>>>>> tags/0.78
width = (ss.right - ss.left - extra_width) / font_width;
height = (ss.bottom - ss.top - extra_height) / font_height;
@@ -2016,10 +2196,17 @@ static Mouse_Button translate_button(WinGuiSeat *wgs, Mouse_Button button)
if (button == MBT_LEFT)
return MBT_SELECT;
if (button == MBT_MIDDLE)
+<<<<<<< HEAD
return conf_get_int(wgs->conf, CONF_mouse_is_xterm) == 1 ?
MBT_PASTE : MBT_EXTEND;
if (button == MBT_RIGHT)
return conf_get_int(wgs->conf, CONF_mouse_is_xterm) == 1 ?
+=======
+ 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 ?
+>>>>>>> tags/0.78
MBT_EXTEND : MBT_PASTE;
return 0; /* shouldn't happen */
}
@@ -2059,6 +2246,11 @@ static bool is_alt_pressed(void)
return false;
}
+<<<<<<< HEAD
+=======
+static bool resizing;
+
+>>>>>>> tags/0.78
static void exit_callback(void *vctx)
{
WinGuiSeat *wgs = (WinGuiSeat *)vctx;
@@ -2088,6 +2280,14 @@ static void exit_callback(void *vctx)
}
static void win_seat_notify_remote_exit(Seat *seat)
+<<<<<<< HEAD
+=======
+{
+ queue_toplevel_callback(exit_callback, NULL);
+}
+
+void timer_change_notify(unsigned long next)
+>>>>>>> tags/0.78
{
WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat);
queue_toplevel_callback(exit_callback, wgs);
@@ -2256,7 +2456,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
int size;
serbuf = strbuf_new();
+<<<<<<< HEAD
conf_serialise(BinarySink_UPCAST(serbuf), wgs->conf);
+=======
+ conf_serialise(BinarySink_UPCAST(serbuf), conf);
+>>>>>>> tags/0.78
size = serbuf->len;
sa.nLength = sizeof(sa);
@@ -2348,8 +2552,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
{
/* Disable full-screen if resizing forbidden */
int i;
+<<<<<<< HEAD
for (i = 0; i < lenof(wgs->popup_menus); i++)
EnableMenuItem(wgs->popup_menus[i].menu, IDM_FULLSCREEN,
+=======
+ for (i = 0; i < lenof(popup_menus); i++)
+ EnableMenuItem(popup_menus[i].menu, IDM_FULLSCREEN,
+>>>>>>> tags/0.78
MF_BYCOMMAND |
(resize_action == RESIZE_DISABLED
? MF_GRAYED : MF_ENABLED));
@@ -2366,9 +2575,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
* Flush the line discipline's edit buffer in the
* case where local editing has just been disabled.
*/
+<<<<<<< HEAD
if (wgs->ldisc) {
ldisc_configure(wgs->ldisc, wgs->conf);
ldisc_echoedit_update(wgs->ldisc);
+=======
+ if (ldisc) {
+ ldisc_configure(ldisc, conf);
+ ldisc_echoedit_update(ldisc);
+>>>>>>> tags/0.78
}
if (conf_get_bool(wgs->conf, CONF_system_colour) !=
@@ -2412,9 +2627,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
GetWindowLongPtr(hwnd, GWL_EXSTYLE);
nexflag = exflag;
+<<<<<<< HEAD
if (conf_get_bool(wgs->conf, CONF_alwaysontop) !=
conf_get_bool(prev_conf, CONF_alwaysontop)) {
if (conf_get_bool(wgs->conf, CONF_alwaysontop)) {
+=======
+ if (conf_get_bool(conf, CONF_alwaysontop) !=
+ conf_get_bool(prev_conf, CONF_alwaysontop)) {
+ if (conf_get_bool(conf, CONF_alwaysontop)) {
+>>>>>>> tags/0.78
nexflag |= WS_EX_TOPMOST;
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE);
@@ -2424,13 +2645,21 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
SWP_NOMOVE | SWP_NOSIZE);
}
}
+<<<<<<< HEAD
if (conf_get_bool(wgs->conf, CONF_sunken_edge))
+=======
+ if (conf_get_bool(conf, CONF_sunken_edge))
+>>>>>>> tags/0.78
nexflag |= WS_EX_CLIENTEDGE;
else
nexflag &= ~(WS_EX_CLIENTEDGE);
nflg = flag;
+<<<<<<< HEAD
if (conf_get_bool(wgs->conf, is_full_screen(wgs) ?
+=======
+ if (conf_get_bool(conf, is_full_screen() ?
+>>>>>>> tags/0.78
CONF_scrollbar_in_fullscreen :
CONF_scrollbar))
nflg |= WS_VSCROLL;
@@ -2438,7 +2667,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
nflg &= ~WS_VSCROLL;
if (resize_action == RESIZE_DISABLED ||
+<<<<<<< HEAD
is_full_screen(wgs))
+=======
+ is_full_screen())
+>>>>>>> tags/0.78
nflg &= ~WS_THICKFRAME;
else
nflg |= WS_THICKFRAME;
@@ -2470,21 +2703,37 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
}
{
+<<<<<<< HEAD
FontSpec *font = conf_get_fontspec(wgs->conf, CONF_font);
+=======
+ FontSpec *font = conf_get_fontspec(conf, CONF_font);
+>>>>>>> tags/0.78
FontSpec *prev_font = conf_get_fontspec(prev_conf,
CONF_font);
if (!strcmp(font->name, prev_font->name) ||
+<<<<<<< HEAD
!strcmp(conf_get_str(wgs->conf, CONF_line_codepage),
+=======
+ !strcmp(conf_get_str(conf, CONF_line_codepage),
+>>>>>>> tags/0.78
conf_get_str(prev_conf, CONF_line_codepage)) ||
font->isbold != prev_font->isbold ||
font->height != prev_font->height ||
font->charset != prev_font->charset ||
+<<<<<<< HEAD
conf_get_int(wgs->conf, CONF_font_quality) !=
conf_get_int(prev_conf, CONF_font_quality) ||
conf_get_int(wgs->conf, CONF_vtmode) !=
conf_get_int(prev_conf, CONF_vtmode) ||
conf_get_int(wgs->conf, CONF_bold_style) !=
+=======
+ conf_get_int(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) !=
+>>>>>>> tags/0.78
conf_get_int(prev_conf, CONF_bold_style) ||
resize_action == RESIZE_DISABLED ||
resize_action == RESIZE_EITHER ||
@@ -2582,9 +2831,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
POINT cursorpos;
/* Just in case this happened in mid-select */
+<<<<<<< HEAD
term_cancel_selection_drag(wgs->term);
show_mouseptr(wgs, true); /* make sure pointer is visible */
+=======
+ term_cancel_selection_drag(term);
+
+ show_mouseptr(true); /* make sure pointer is visible */
+>>>>>>> tags/0.78
GetCursorPos(&cursorpos);
TrackPopupMenu(wgs->popup_menus[CTXMENU].menu,
TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON,
@@ -2664,7 +2919,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
if (pt.x == 0 && pt.y == 0) {
mouse_on_hotspot = true;
}
+<<<<<<< HEAD
if (is_full_screen(wgs) && press &&
+=======
+ if (is_full_screen() && press &&
+>>>>>>> tags/0.78
button == MBT_LEFT && mouse_on_hotspot) {
SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU,
MAKELPARAM(pt.x, pt.y));
@@ -2698,7 +2957,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
static LPARAM lp = 0;
if (wParam != wp || lParam != lp ||
last_mousemove != WM_MOUSEMOVE) {
+<<<<<<< HEAD
show_mouseptr(wgs, true);
+=======
+ show_mouseptr(true);
+>>>>>>> tags/0.78
wp = wParam; lp = lParam;
last_mousemove = WM_MOUSEMOVE;
}
@@ -2729,7 +2992,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
static LPARAM lp = 0;
if (wParam != wp || lParam != lp ||
last_mousemove != WM_NCMOUSEMOVE) {
+<<<<<<< HEAD
show_mouseptr(wgs, true);
+=======
+ show_mouseptr(true);
+>>>>>>> tags/0.78
wp = wParam; lp = lParam;
last_mousemove = WM_NCMOUSEMOVE;
}
@@ -2749,8 +3016,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
HideCaret(hwnd);
hdc = BeginPaint(hwnd, &p);
+<<<<<<< HEAD
if (wgs->pal) {
SelectPalette(hdc, wgs->pal, true);
+=======
+ if (pal) {
+ SelectPalette(hdc, pal, true);
+>>>>>>> tags/0.78
RealizePalette(hdc);
}
@@ -2805,10 +3077,17 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
HBRUSH fillcolour, oldbrush;
HPEN edge, oldpen;
fillcolour = CreateSolidBrush (
+<<<<<<< HEAD
wgs->colours[ATTR_DEFBG>>ATTR_BGSHIFT]);
oldbrush = SelectObject(hdc, fillcolour);
edge = CreatePen(PS_SOLID, 0,
wgs->colours[ATTR_DEFBG>>ATTR_BGSHIFT]);
+=======
+ colours[ATTR_DEFBG>>ATTR_BGSHIFT]);
+ oldbrush = SelectObject(hdc, fillcolour);
+ edge = CreatePen(PS_SOLID, 0,
+ colours[ATTR_DEFBG>>ATTR_BGSHIFT]);
+>>>>>>> tags/0.78
oldpen = SelectObject(hdc, edge);
/*
@@ -2824,8 +3103,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
ExcludeClipRect(hdc,
offset_width, offset_height,
+<<<<<<< HEAD
offset_width+font_width*wgs->term->cols,
offset_height+font_height*wgs->term->rows);
+=======
+ offset_width+font_width*term->cols,
+ offset_height+font_height*term->rows);
+>>>>>>> tags/0.78
Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
p.rcPaint.right, p.rcPaint.bottom);
@@ -3008,10 +3292,17 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
}
if (wParam == SIZE_MINIMIZED)
sw_SetWindowText(hwnd,
+<<<<<<< HEAD
conf_get_bool(wgs->conf, CONF_win_name_always) ?
wgs->window_name : wgs->icon_name);
if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
sw_SetWindowText(hwnd, wgs->window_name);
+=======
+ conf_get_bool(conf, CONF_win_name_always) ?
+ window_name : icon_name);
+ if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
+ sw_SetWindowText(hwnd, window_name);
+>>>>>>> tags/0.78
if (wParam == SIZE_RESTORED) {
processed_resize = false;
clear_full_screen(wgs);
@@ -3288,20 +3579,32 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
* instead we send the characters one by one.
*/
/* don't divide SURROGATE PAIR */
+<<<<<<< HEAD
if (wgs->ldisc) {
+=======
+ if (ldisc) {
+>>>>>>> tags/0.78
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(
+<<<<<<< HEAD
wgs->term, (unsigned short *)(buff+i), 2);
+=======
+ term, (unsigned short *)(buff+i), 2);
+>>>>>>> tags/0.78
i += 2;
continue;
}
}
term_keyinputw(
+<<<<<<< HEAD
wgs->term, (unsigned short *)(buff+i), 1);
+=======
+ term, (unsigned short *)(buff+i), 1);
+>>>>>>> tags/0.78
}
}
free(buff);
@@ -3347,9 +3650,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
}
} else {
char c = (unsigned char)wParam;
+<<<<<<< HEAD
term_seen_key_event(wgs->term);
if (wgs->ldisc)
term_keyinput(wgs->term, CP_ACP, &c, 1);
+=======
+ term_seen_key_event(term);
+ if (ldisc)
+ term_keyinput(term, CP_ACP, &c, 1);
+>>>>>>> tags/0.78
}
return 0;
case WM_SYSCOLORCHANGE:
@@ -4280,11 +4589,19 @@ static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam,
}
if (wParam == compose_keycode) {
+<<<<<<< HEAD
if (wgs->compose_state == 0 &&
(HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
wgs->compose_state = 1;
else if (wgs->compose_state == 1 && (HIWORD(lParam) & KF_UP))
wgs->compose_state = 2;
+=======
+ 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;
+>>>>>>> tags/0.78
else
wgs->compose_state = 0;
} else if (wgs->compose_state == 1 && wParam != VK_CONTROL)
@@ -4592,7 +4909,11 @@ static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam,
case VK_F20: fkey_number = 20; goto numbered_function_key;
numbered_function_key:
consumed_alt = false;
+<<<<<<< HEAD
p += format_function_key((char *)p, wgs->term, fkey_number,
+=======
+ p += format_function_key((char *)p, term, fkey_number,
+>>>>>>> tags/0.78
shift_state & 1, shift_state & 2,
left_alt, &consumed_alt);
if (consumed_alt)
@@ -4611,7 +4932,11 @@ static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam,
if (shift_state & 2)
break;
+<<<<<<< HEAD
p += format_small_keypad_key((char *)p, wgs->term, sk_key,
+=======
+ p += format_small_keypad_key((char *)p, term, sk_key,
+>>>>>>> tags/0.78
shift_state & 1, shift_state & 2,
left_alt, &consumed_alt);
if (consumed_alt)
@@ -4626,7 +4951,11 @@ static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam,
case VK_CLEAR: xkey = 'G'; goto arrow_key; /* close enough */
arrow_key:
consumed_alt = false;
+<<<<<<< HEAD
p += format_arrow_key((char *)p, wgs->term, xkey, shift_state & 1,
+=======
+ p += format_arrow_key((char *)p, term, xkey, shift_state & 1,
+>>>>>>> tags/0.78
shift_state & 2, left_alt, &consumed_alt);
if (consumed_alt)
left_alt = false; /* supersedes the usual prefixing of Esc */
@@ -4807,6 +5136,7 @@ static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam,
static void wintw_set_title(TermWin *tw, const char *title, int codepage)
{
+<<<<<<< HEAD
WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin);
wchar_t *new_window_name = dup_mb_to_wc(codepage, 0, title);
if (!wcscmp(new_window_name, wgs->window_name)) {
@@ -4818,10 +5148,22 @@ static void wintw_set_title(TermWin *tw, const char *title, int codepage)
if (conf_get_bool(wgs->conf, CONF_win_name_always) ||
!IsIconic(wgs->term_hwnd))
sw_SetWindowText(wgs->term_hwnd, wgs->window_name);
+=======
+ wchar_t *new_window_name = dup_mb_to_wc(codepage, 0, title);
+ if (!wcscmp(new_window_name, window_name)) {
+ sfree(new_window_name);
+ return;
+ }
+ sfree(window_name);
+ window_name = new_window_name;
+ if (conf_get_bool(conf, CONF_win_name_always) || !IsIconic(wgs.term_hwnd))
+ sw_SetWindowText(wgs.term_hwnd, window_name);
+>>>>>>> tags/0.78
}
static void wintw_set_icon_title(TermWin *tw, const char *title, int codepage)
{
+<<<<<<< HEAD
WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin);
wchar_t *new_icon_name = dup_mb_to_wc(codepage, 0, title);
if (!wcscmp(new_icon_name, wgs->icon_name)) {
@@ -4833,6 +5175,17 @@ static void wintw_set_icon_title(TermWin *tw, const char *title, int codepage)
if (!conf_get_bool(wgs->conf, CONF_win_name_always) &&
IsIconic(wgs->term_hwnd))
sw_SetWindowText(wgs->term_hwnd, wgs->icon_name);
+=======
+ wchar_t *new_icon_name = dup_mb_to_wc(codepage, 0, title);
+ if (!wcscmp(new_icon_name, icon_name)) {
+ sfree(new_icon_name);
+ return;
+ }
+ sfree(icon_name);
+ icon_name = new_icon_name;
+ if (!conf_get_bool(conf, CONF_win_name_always) && IsIconic(wgs.term_hwnd))
+ sw_SetWindowText(wgs.term_hwnd, icon_name);
+>>>>>>> tags/0.78
}
static void wintw_set_scrollbar(TermWin *tw, int total, int start, int page)
@@ -5134,7 +5487,11 @@ static void wintw_clip_write(
for (i = 0; i < OSC4_NCOLOURS; i++) {
if (palette[i] != 0) {
+<<<<<<< HEAD
const PALETTEENTRY *pe = &wgs->logpal->palPalEntry[i];
+=======
+ const PALETTEENTRY *pe = &logpal->palPalEntry[i];
+>>>>>>> tags/0.78
put_fmt(rtf, "\\red%d\\green%d\\blue%d;",
pe->peRed, pe->peGreen, pe->peBlue);
}
@@ -5684,7 +6041,11 @@ static void wintw_move(TermWin *tw, int x, int y)
int resize_action = conf_get_int(wgs->conf, CONF_resize_action);
if (resize_action == RESIZE_DISABLED ||
resize_action == RESIZE_FONT ||
+<<<<<<< HEAD
IsZoomed(wgs->term_hwnd))
+=======
+ IsZoomed(wgs.term_hwnd))
+>>>>>>> tags/0.78
return;
SetWindowPos(wgs->term_hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
@@ -5743,13 +6104,21 @@ static bool is_full_screen(WinGuiSeat *wgs)
/* 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. */
+<<<<<<< HEAD
static bool get_fullscreen_rect(WinGuiSeat *wgs, RECT *ss)
+=======
+static bool get_fullscreen_rect(RECT *ss)
+>>>>>>> tags/0.78
{
#if defined(MONITOR_DEFAULTTONEAREST) && !defined(NO_MULTIMON)
if (p_GetMonitorInfoA && p_MonitorFromWindow) {
HMONITOR mon;
MONITORINFO mi;
+<<<<<<< HEAD
mon = p_MonitorFromWindow(wgs->term_hwnd, MONITOR_DEFAULTTONEAREST);
+=======
+ mon = p_MonitorFromWindow(wgs.term_hwnd, MONITOR_DEFAULTTONEAREST);
+>>>>>>> tags/0.78
mi.cbSize = sizeof(mi);
p_GetMonitorInfoA(mon, &mi);
@@ -5778,7 +6147,11 @@ static void make_full_screen(WinGuiSeat *wgs)
assert(IsZoomed(wgs->term_hwnd));
+<<<<<<< HEAD
if (is_full_screen(wgs))
+=======
+ if (is_full_screen())
+>>>>>>> tags/0.78
return;
/* Remove the window furniture. */
@@ -5791,8 +6164,13 @@ static void make_full_screen(WinGuiSeat *wgs)
SetWindowLongPtr(wgs->term_hwnd, GWL_STYLE, style);
/* Resize ourselves to exactly cover the nearest monitor. */
+<<<<<<< HEAD
get_fullscreen_rect(wgs, &ss);
SetWindowPos(wgs->term_hwnd, HWND_TOP, ss.left, ss.top,
+=======
+ get_fullscreen_rect(&ss);
+ SetWindowPos(wgs.term_hwnd, HWND_TOP, ss.left, ss.top,
+>>>>>>> tags/0.78
ss.right - ss.left, ss.bottom - ss.top, SWP_FRAMECHANGED);
/* We may have changed size as a result */
@@ -5860,6 +6238,7 @@ static void flip_full_screen(WinGuiSeat *wgs)
static size_t win_seat_output(Seat *seat, SeatOutputType type,
const void *data, size_t len)
{
+<<<<<<< HEAD
WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat);
return term_data(wgs->term, data, len);
}
@@ -5869,6 +6248,15 @@ static void wintw_unthrottle(TermWin *tw, size_t bufsize)
WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin);
if (wgs->backend)
backend_unthrottle(wgs->backend, bufsize);
+=======
+ return term_data(term, data, len);
+}
+
+static void wintw_unthrottle(TermWin *win, size_t bufsize)
+{
+ if (backend)
+ backend_unthrottle(backend, bufsize);
+>>>>>>> tags/0.78
}
static bool win_seat_eof(Seat *seat)
@@ -5878,15 +6266,23 @@ static bool win_seat_eof(Seat *seat)
static SeatPromptResult win_seat_get_userpass_input(Seat *seat, prompts_t *p)
{
+<<<<<<< HEAD
WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat);
SeatPromptResult spr;
spr = cmdline_get_passwd_input(p, &wgs->cmdline_get_passwd_state, true);
if (spr.kind == SPRK_INCOMPLETE)
spr = term_get_userpass_input(wgs->term, p);
+=======
+ SeatPromptResult spr;
+ spr = cmdline_get_passwd_input(p, &cmdline_get_passwd_state, true);
+ if (spr.kind == SPRK_INCOMPLETE)
+ spr = term_get_userpass_input(term, p);
+>>>>>>> tags/0.78
return spr;
}
static void win_seat_set_trust_status(Seat *seat, bool trusted)
+<<<<<<< HEAD
{
WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat);
term_set_trust_status(wgs->term, trusted);
@@ -5894,6 +6290,14 @@ static void win_seat_set_trust_status(Seat *seat, bool trusted)
static bool win_seat_can_set_trust_status(Seat *seat)
{
+=======
+{
+ term_set_trust_status(term, trusted);
+}
+
+static bool win_seat_can_set_trust_status(Seat *seat)
+{
+>>>>>>> tags/0.78
return true;
}
diff --git a/WINDOWS/WINGSS.C b/WINDOWS/WINGSS.C
deleted file mode 100644
index 79c4921c..00000000
--- a/WINDOWS/WINGSS.C
+++ /dev/null
@@ -1,703 +0,0 @@
-#ifndef NO_GSSAPI
-
-#include <limits.h>
-#include "putty.h"
-
-#define SECURITY_WIN32
-#include <security.h>
-
-#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", &regkey)
- == 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/WINDOWS/WINHANDL.C b/WINDOWS/WINHANDL.C
deleted file mode 100644
index 82d2aded..00000000
--- a/WINDOWS/WINHANDL.C
+++ /dev/null
@@ -1,731 +0,0 @@
-/*
- * 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 <assert.h>
-
-#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/WINDOWS/WINHELP.C b/WINDOWS/WINHELP.C
deleted file mode 100644
index df6ac37b..00000000
--- a/WINDOWS/WINHELP.C
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * winhelp.c: centralised functions to launch Windows HTML Help files.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#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) { }
-
-#else
-
-#include <htmlhelp.h>
-
-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/WINDOWS/WINHELP.H b/WINDOWS/WINHELP.H
deleted file mode 100644
index 9011df45..00000000
--- a/WINDOWS/WINHELP.H
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * 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/WINDOWS/WINJUMP.C b/WINDOWS/WINJUMP.C
deleted file mode 100644
index 358504fd..00000000
--- a/WINDOWS/WINJUMP.C
+++ /dev/null
@@ -1,748 +0,0 @@
-/*
- * 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 <assert.h>
-
-#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 <shobjidl.h>, 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)
- {
- if (p_SetCurrentProcessExplicitAppUserModelID(L"SimonTatham.PuTTY") == S_OK)
- {
- return true;
- }
- return false;
- }
- /* Function doesn't exist, which is ok for Pre-7 systems */
-
- return true;
-
-}
diff --git a/WINDOWS/WINMISC.C b/WINDOWS/WINMISC.C
deleted file mode 100644
index 759df011..00000000
--- a/WINDOWS/WINMISC.C
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * winmisc.c: miscellaneous Windows-specific things
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-#include "putty.h"
-#ifndef SECURITY_WIN32
-#define SECURITY_WIN32
-#endif
-#include <security.h>
-
-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/WINDOWS/WINNET.C b/WINDOWS/WINNET.C
deleted file mode 100644
index 3b4da3cc..00000000
--- a/WINDOWS/WINNET.C
+++ /dev/null
@@ -1,1825 +0,0 @@
-/*
- * Windows networking abstraction.
- *
- * For the IPv6 code in here I am indebted to Jeroen Massar and
- * unfix.org.
- */
-
-#include <winsock2.h> /* need to put this first, for winelib builds */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#define NEED_DECLARATION_OF_SELECT /* in order to initialise it */
-
-#include "putty.h"
-#include "network.h"
-#include "tree234.h"
-#include "ssh.h"
-
-#include <ws2tcpip.h>
-
-#ifndef NO_IPV6
-#ifdef __clang__
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wmissing-braces"
-#endif
-const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
-const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
-#ifdef __clang__
-#pragma clang diagnostic pop
-#endif
-#endif
-
-#define ipv4_is_loopback(addr) \
- ((p_ntohl(addr.s_addr) & 0xFF000000L) == 0x7F000000L)
-
-/*
- * Mutable state that goes with a SockAddr: stores information
- * about where in the list of candidate IP(v*) addresses we've
- * currently got to.
- */
-typedef struct SockAddrStep_tag SockAddrStep;
-struct SockAddrStep_tag {
-#ifndef NO_IPV6
- struct addrinfo *ai; /* steps along addr->ais */
-#endif
- int curraddr;
-};
-
-typedef struct NetSocket NetSocket;
-struct NetSocket {
- const char *error;
- SOCKET s;
- Plug *plug;
- bufchain output_data;
- bool connected;
- bool writable;
- bool frozen; /* this causes readability notifications to be ignored */
- bool frozen_readable; /* this means we missed at least one readability
- * notification while we were frozen */
- bool localhost_only; /* for listening sockets */
- char oobdata[1];
- size_t sending_oob;
- bool oobinline, nodelay, keepalive, privport;
- enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
- SockAddr *addr;
- SockAddrStep step;
- int port;
- int pending_error; /* in case send() returns error */
- /*
- * We sometimes need pairs of Socket structures to be linked:
- * if we are listening on the same IPv6 and v4 port, for
- * example. So here we define `parent' and `child' pointers to
- * track this link.
- */
- NetSocket *parent, *child;
-
- Socket sock;
-};
-
-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;
-
-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);
-}
-
-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, "<unknown>", 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,
-};
-
-static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug)
-{
- DWORD err;
- const 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);
- 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);
- }
-
- {
- SockAddr thisaddr = sk_extractaddr_tmp(
- sock->addr, &sock->step);
- plug_log(sock->plug, PLUGLOG_CONNECT_TRYING,
- &thisaddr, sock->port, NULL, 0);
- }
-
- /*
- * Open socket.
- */
- family = SOCKADDR_FAMILY(sock->addr, sock->step);
-
- /*
- * Remove the socket from the tree before we overwrite its
- * internal socket id, because that forms part of the tree's
- * sorting criterion. We'll add it back before exiting this
- * function, whether we changed anything or not.
- */
- del234(sktree, sock);
-
- s = p_socket(family, SOCK_STREAM, 0);
- sock->s = s;
-
- if (s == INVALID_SOCKET) {
- err = p_WSAGetLastError();
- sock->error = winsock_error_string(err);
- goto ret;
- }
-
- SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0);
-
- if (sock->oobinline) {
- BOOL b = true;
- p_setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (void *) &b, sizeof(b));
- }
-
- if (sock->nodelay) {
- BOOL b = true;
- p_setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b));
- }
-
- if (sock->keepalive) {
- BOOL b = true;
- p_setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &b, sizeof(b));
- }
-
- /*
- * Bind to local address.
- */
- if (sock->privport)
- localport = 1023; /* count from 1023 downwards */
- else
- localport = 0; /* just use port 0 (ie winsock picks) */
-
- /* Loop round trying to bind */
- while (1) {
- int sockcode;
-
-#ifndef NO_IPV6
- if (family == AF_INET6) {
- memset(&a6, 0, sizeof(a6));
- a6.sin6_family = AF_INET6;
- /*a6.sin6_addr = in6addr_any; */ /* == 0 done by memset() */
- a6.sin6_port = p_htons(localport);
- } else
-#endif
- {
- a.sin_family = AF_INET;
- a.sin_addr.s_addr = p_htonl(INADDR_ANY);
- a.sin_port = p_htons(localport);
- }
-#ifndef NO_IPV6
- sockcode = p_bind(s, (family == AF_INET6 ?
- (struct sockaddr *) &a6 :
- (struct sockaddr *) &a),
- (family == AF_INET6 ? sizeof(a6) : sizeof(a)));
-#else
- sockcode = p_bind(s, (struct sockaddr *) &a, sizeof(a));
-#endif
- if (sockcode != SOCKET_ERROR) {
- err = 0;
- break; /* done */
- } else {
- err = p_WSAGetLastError();
- if (err != WSAEADDRINUSE) /* failed, for a bad reason */
- break;
- }
-
- if (localport == 0)
- break; /* we're only looping once */
- localport--;
- if (localport == 0)
- break; /* we might have got to the end */
- }
-
- if (err) {
- sock->error = winsock_error_string(err);
- goto ret;
- }
-
- /*
- * Connect to remote address.
- */
-#ifndef NO_IPV6
- if (sock->step.ai) {
- if (family == AF_INET6) {
- a6.sin6_family = AF_INET6;
- a6.sin6_port = p_htons((short) sock->port);
- a6.sin6_addr =
- ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_addr;
- a6.sin6_flowinfo = ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_flowinfo;
- a6.sin6_scope_id = ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_scope_id;
- } else {
- a.sin_family = AF_INET;
- a.sin_addr =
- ((struct sockaddr_in *) sock->step.ai->ai_addr)->sin_addr;
- a.sin_port = p_htons((short) sock->port);
- }
- } else
-#endif
- {
- assert(sock->addr->addresses && sock->step.curraddr < sock->addr->naddresses);
- a.sin_family = AF_INET;
- a.sin_addr.s_addr = p_htonl(sock->addr->addresses[sock->step.curraddr]);
- a.sin_port = p_htons((short) sock->port);
- }
-
- /* Set up a select mechanism. This could be an AsyncSelect on a
- * window, or an EventSelect on an event object. */
- errstr = do_select(s, true);
- if (errstr) {
- sock->error = errstr;
- err = 1;
- goto ret;
- }
-
- if ((
-#ifndef NO_IPV6
- p_connect(s,
- ((family == AF_INET6) ? (struct sockaddr *) &a6 :
- (struct sockaddr *) &a),
- (family == AF_INET6) ? sizeof(a6) : sizeof(a))
-#else
- p_connect(s, (struct sockaddr *) &a, sizeof(a))
-#endif
- ) == SOCKET_ERROR) {
- err = p_WSAGetLastError();
- /*
- * We expect a potential EWOULDBLOCK here, because the
- * chances are the front end has done a select for
- * FD_CONNECT, so that connect() will complete
- * asynchronously.
- */
- if ( err != WSAEWOULDBLOCK ) {
- sock->error = winsock_error_string(err);
- goto ret;
- }
- } else {
- /*
- * If we _don't_ get EWOULDBLOCK, the connect has completed
- * and we should set the socket as writable.
- */
- sock->writable = true;
- SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step);
- plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS,
- &thisaddr, sock->port, NULL, 0);
- }
-
- err = 0;
-
- ret:
-
- /*
- * No matter what happened, put the socket back in the tree.
- */
- add234(sktree, sock);
-
- if (err) {
- SockAddr thisaddr = sk_extractaddr_tmp(
- sock->addr, &sock->step);
- plug_log(sock->plug, PLUGLOG_CONNECT_FAILED,
- &thisaddr, sock->port, sock->error, err);
- }
- return err;
-}
-
-Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline,
- bool nodelay, bool keepalive, Plug *plug)
-{
- NetSocket *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 */
- 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);
- 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;
-}
-
-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);
-}
-
-/*
- * 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) {
- SockAddr thisaddr = sk_extractaddr_tmp(
- s->addr, &s->step);
- plug_log(s->plug, PLUGLOG_CONNECT_FAILED, &thisaddr, s->port,
- winsock_error_string(err), err);
- while (err && s->addr && sk_nextaddr(s->addr, &s->step)) {
- err = try_connect(s);
- }
- }
- if (err != 0)
- plug_closing(s->plug, winsock_error_string(err), err, 0);
- return;
- }
-
- noise_ultralight(NOISE_SOURCE_IOID, wParam);
-
- switch (WSAGETSELECTEVENT(lParam)) {
- case FD_CONNECT:
- s->connected = true;
- s->writable = true;
-
- /*
- * Once a socket is connected, we can stop falling back
- * through the candidate addresses to connect to. But first,
- * let the plug know we were successful.
- */
- if (s->addr) {
- SockAddr thisaddr = sk_extractaddr_tmp(
- s->addr, &s->step);
- plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS,
- &thisaddr, s->port, NULL, 0);
-
- sk_addr_free(s->addr);
- s->addr = NULL;
- }
- break;
- case FD_READ:
- /* In the case the socket is still frozen, we don't even bother */
- if (s->frozen) {
- s->frozen_readable = true;
- break;
- }
-
- /*
- * We have received data on the socket. For an oobinline
- * socket, this might be data _before_ an urgent pointer,
- * in which case we send it to the back end with type==1
- * (data prior to urgent).
- */
- if (s->oobinline) {
- u_long atmark_from_ioctl = 1;
- p_ioctlsocket(s->s, SIOCATMARK, &atmark_from_ioctl);
- /*
- * Avoid checking the return value from ioctlsocket(),
- * on the grounds that some WinSock wrappers don't
- * support it. If it does nothing, we get atmark==1,
- * which is equivalent to `no OOB pending', so the
- * effect will be to non-OOB-ify any OOB data.
- */
- atmark = atmark_from_ioctl;
- } else
- atmark = true;
-
- ret = p_recv(s->s, buf, sizeof(buf), 0);
- noise_ultralight(NOISE_SOURCE_IOLEN, ret);
- if (ret < 0) {
- err = p_WSAGetLastError();
- if (err == WSAEWOULDBLOCK) {
- break;
- }
- }
- if (ret < 0) {
- plug_closing(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);
- } else {
- plug_receive(s->plug, 2, buf, ret);
- }
- break;
- case FD_WRITE: {
- int bufsize_before, bufsize_after;
- s->writable = true;
- bufsize_before = s->sending_oob + bufchain_size(&s->output_data);
- try_send(s);
- bufsize_after = s->sending_oob + bufchain_size(&s->output_data);
- if (bufsize_after < bufsize_before)
- plug_sent(s->plug, bufsize_after);
- break;
- }
- case FD_CLOSE:
- /* Signal a close on the socket. First read any outstanding data. */
- do {
- ret = p_recv(s->s, buf, sizeof(buf), 0);
- if (ret < 0) {
- err = p_WSAGetLastError();
- if (err == WSAEWOULDBLOCK)
- break;
- plug_closing(s->plug, winsock_error_string(err), err, 0);
- } 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 */
- } else if (plug_accepting(s->plug, sk_net_accept, actx)) {
- p_closesocket(t); /* denied or error */
- }
- break;
- }
- }
-}
-
-/*
- * Special error values are returned from sk_namelookup and sk_new
- * if there's a problem. These functions extract an error message,
- * or return NULL if there's no problem.
- */
-const char *sk_addr_error(SockAddr *addr)
-{
- return addr->error;
-}
-static const char *sk_net_socket_error(Socket *sock)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
- return s->error;
-}
-
-static SocketPeerInfo *sk_net_peer_info(Socket *sock)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
-#ifdef NO_IPV6
- struct sockaddr_in addr;
-#else
- struct sockaddr_storage addr;
- 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/WINDOWS/WINNOJMP.C b/WINDOWS/WINNOJMP.C
deleted file mode 100644
index dd61dc69..00000000
--- a/WINDOWS/WINNOJMP.C
+++ /dev/null
@@ -1,8 +0,0 @@
-/*
- * 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/WINDOWS/WINPGEN.C b/WINDOWS/WINPGEN.C
deleted file mode 100644
index 56c6a8db..00000000
--- a/WINDOWS/WINPGEN.C
+++ /dev/null
@@ -1,2026 +0,0 @@
-/*
- * PuTTY key generation front end (Windows).
- */
-
-#include <time.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "sshkeygen.h"
-#include "licence.h"
-#include "winsecur.h"
-#include "puttygen-rc.h"
-
-#include <commctrl.h>
-
-#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;
-
-/*
- * Print a modal (Really Bad) message box and perform a fatal exit.
- */
-void modalfatalbox(const char *fmt, ...)
-{
- va_list ap;
- char *stuff;
-
- va_start(ap, fmt);
- stuff = dupvprintf(fmt, ap);
- va_end(ap);
- MessageBox(NULL, stuff, "PuTTYgen Fatal Error",
- MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
- sfree(stuff);
- exit(1);
-}
-
-/*
- * Print a non-fatal message box and do not exit.
- */
-void nonfatal(const char *fmt, ...)
-{
- va_list ap;
- char *stuff;
-
- va_start(ap, fmt);
- stuff = dupvprintf(fmt, ap);
- va_end(ap);
- MessageBox(NULL, stuff, "PuTTYgen Error",
- MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
- sfree(stuff);
-}
-
-/* ----------------------------------------------------------------------
- * ProgressReceiver implementation.
- */
-
-#define PROGRESSRANGE 65535
-
-struct progressphase {
- double startpoint, total;
- /* For exponential phases */
- double exp_probability, exp_current_value;
-};
-
-struct progress {
- size_t nphases, phasessize;
- struct progressphase *phases, *currphase;
-
- double scale;
- HWND progbar;
-
- ProgressReceiver rec;
-};
-
-static ProgressPhase win_progress_add_linear(
- ProgressReceiver *prog, double overall_cost) {
- struct progress *p = container_of(prog, struct progress, rec);
-
- sgrowarray(p->phases, p->phasessize, p->nphases);
- int phase = p->nphases++;
-
- p->phases[phase].total = overall_cost;
-
- ProgressPhase ph = { .n = phase };
- return ph;
-}
-
-static ProgressPhase win_progress_add_probabilistic(
- ProgressReceiver *prog, double cost_per_attempt, double probability) {
- struct progress *p = container_of(prog, struct progress, rec);
-
- sgrowarray(p->phases, p->phasessize, p->nphases);
- int phase = p->nphases++;
-
- p->phases[phase].exp_probability = 1.0 - probability;
- p->phases[phase].exp_current_value = 1.0;
- /* Expected number of attempts = 1 / probability of attempt succeeding */
- p->phases[phase].total = cost_per_attempt / probability;
-
- ProgressPhase ph = { .n = phase };
- return ph;
-}
-
-static void win_progress_ready(ProgressReceiver *prog)
-{
- struct progress *p = container_of(prog, struct progress, rec);
-
- double total = 0;
- for (int i = 0; i < p->nphases; i++) {
- p->phases[i].startpoint = total;
- total += p->phases[i].total;
- }
- p->scale = PROGRESSRANGE / total;
-
- SendMessage(p->progbar, PBM_SETRANGE, 0, MAKELPARAM(0, PROGRESSRANGE));
-}
-
-static void win_progress_start_phase(ProgressReceiver *prog,
- ProgressPhase phase)
-{
- struct progress *p = container_of(prog, struct progress, rec);
-
- assert(phase.n < p->nphases);
- p->currphase = &p->phases[phase.n];
-}
-
-static void win_progress_update(struct progress *p, double phasepos)
-{
- double position = (p->currphase->startpoint +
- p->currphase->total * phasepos);
- position *= p->scale;
- if (position < 0)
- position = 0;
- if (position > PROGRESSRANGE)
- position = PROGRESSRANGE;
-
- SendMessage(p->progbar, PBM_SETPOS, (WPARAM)position, 0);
-}
-
-static void win_progress_report(ProgressReceiver *prog, double progress)
-{
- struct progress *p = container_of(prog, struct progress, rec);
-
- win_progress_update(p, progress);
-}
-
-static void win_progress_report_attempt(ProgressReceiver *prog)
-{
- struct progress *p = container_of(prog, struct progress, rec);
-
- p->currphase->exp_current_value *= p->currphase->exp_probability;
- win_progress_update(p, 1.0 - p->currphase->exp_current_value);
-}
-
-static void win_progress_report_phase_complete(ProgressReceiver *prog)
-{
- struct progress *p = container_of(prog, struct progress, rec);
-
- win_progress_update(p, 1.0);
-}
-
-static const ProgressReceiverVtable win_progress_vt = {
- .add_linear = win_progress_add_linear,
- .add_probabilistic = win_progress_add_probabilistic,
- .ready = win_progress_ready,
- .start_phase = win_progress_start_phase,
- .report = win_progress_report,
- .report_attempt = win_progress_report_attempt,
- .report_phase_complete = win_progress_report_phase_complete,
-};
-
-static void win_progress_initialise(struct progress *p)
-{
- p->nphases = p->phasessize = 0;
- p->phases = p->currphase = NULL;
- p->rec.vt = &win_progress_vt;
-}
-
-static void win_progress_cleanup(struct progress *p)
-{
- sfree(p->phases);
-}
-
-struct PassphraseProcStruct {
- char **passphrase;
- char *comment;
-};
-
-/*
- * Dialog-box function for the passphrase box.
- */
-static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg,
- WPARAM wParam, LPARAM lParam)
-{
- static char **passphrase = NULL;
- struct PassphraseProcStruct *p;
-
- switch (msg) {
- case WM_INITDIALOG:
- SetForegroundWindow(hwnd);
- SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
- SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
-
- /*
- * Centre the window.
- */
- { /* centre the window */
- RECT rs, rd;
- HWND hw;
-
- hw = GetDesktopWindow();
- if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
- MoveWindow(hwnd,
- (rs.right + rs.left + rd.left - rd.right) / 2,
- (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
- rd.right - rd.left, rd.bottom - rd.top, true);
- }
-
- p = (struct PassphraseProcStruct *) lParam;
- passphrase = p->passphrase;
- if (p->comment)
- SetDlgItemText(hwnd, 101, p->comment);
- burnstr(*passphrase);
- *passphrase = dupstr("");
- SetDlgItemText(hwnd, 102, *passphrase);
- return 0;
- case WM_COMMAND:
- switch (LOWORD(wParam)) {
- case IDOK:
- if (*passphrase)
- EndDialog(hwnd, 1);
- else
- MessageBeep(0);
- return 0;
- case IDCANCEL:
- EndDialog(hwnd, 0);
- return 0;
- case 102: /* edit box */
- if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
- burnstr(*passphrase);
- *passphrase = GetDlgItemText_alloc(hwnd, 102);
- }
- return 0;
- }
- return 0;
- case WM_CLOSE:
- EndDialog(hwnd, 0);
- return 0;
- }
- return 0;
-}
-
-static void try_get_dlg_item_uint32(HWND hwnd, int id, uint32_t *out)
-{
- char buf[128];
- if (!GetDlgItemText(hwnd, id, buf, sizeof(buf)))
- return;
-
- if (!*buf)
- return;
-
- char *end;
- unsigned long val = strtoul(buf, &end, 10);
- if (*end)
- return;
-
- if ((val >> 16) >> 16)
- return;
-
- *out = val;
-}
-
-static ppk_save_parameters save_params;
-
-struct PPKParams {
- ppk_save_parameters params;
- uint32_t time_passes, time_ms;
-};
-
-/*
- * Dialog-box function for the passphrase box.
- */
-static INT_PTR CALLBACK PPKParamsProc(HWND hwnd, UINT msg,
- WPARAM wParam, LPARAM lParam)
-{
- struct PPKParams *pp;
- char *buf;
-
- if (msg == WM_INITDIALOG) {
- pp = (struct PPKParams *)lParam;
- SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pp);
- } else {
- pp = (struct PPKParams *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
- }
-
- switch (msg) {
- case WM_INITDIALOG:
- SetForegroundWindow(hwnd);
- SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
- SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
-
- if (has_help())
- SetWindowLongPtr(hwnd, GWL_EXSTYLE,
- GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
- WS_EX_CONTEXTHELP);
-
- /*
- * Centre the window.
- */
- { /* centre the window */
- RECT rs, rd;
- HWND hw;
-
- hw = GetDesktopWindow();
- if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
- MoveWindow(hwnd,
- (rs.right + rs.left + rd.left - rd.right) / 2,
- (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
- rd.right - rd.left, rd.bottom - rd.top, true);
- }
-
- CheckRadioButton(hwnd, IDC_PPKVER_2, IDC_PPKVER_3,
- IDC_PPKVER_2 + (pp->params.fmt_version - 2));
-
- CheckRadioButton(
- hwnd, IDC_KDF_ARGON2ID, IDC_KDF_ARGON2D,
- (pp->params.argon2_flavour == Argon2id ? IDC_KDF_ARGON2ID :
- pp->params.argon2_flavour == Argon2i ? IDC_KDF_ARGON2I :
- /* pp->params.argon2_flavour == Argon2d ? */ IDC_KDF_ARGON2D));
-
- buf = dupprintf("%"PRIu32, pp->params.argon2_mem);
- SetDlgItemText(hwnd, IDC_ARGON2_MEM, buf);
- sfree(buf);
-
- if (pp->params.argon2_passes_auto) {
- CheckRadioButton(hwnd, IDC_PPK_AUTO_YES, IDC_PPK_AUTO_NO,
- IDC_PPK_AUTO_YES);
- buf = dupprintf("%"PRIu32, pp->time_ms);
- SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf);
- sfree(buf);
- } else {
- CheckRadioButton(hwnd, IDC_PPK_AUTO_YES, IDC_PPK_AUTO_NO,
- IDC_PPK_AUTO_NO);
- buf = dupprintf("%"PRIu32, pp->time_passes);
- SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf);
- sfree(buf);
- }
-
- buf = dupprintf("%"PRIu32, pp->params.argon2_parallelism);
- SetDlgItemText(hwnd, IDC_ARGON2_PARALLEL, buf);
- sfree(buf);
-
- return 0;
- case WM_COMMAND:
- switch (LOWORD(wParam)) {
- case IDOK:
- EndDialog(hwnd, 1);
- return 0;
- case IDCANCEL:
- EndDialog(hwnd, 0);
- return 0;
- case IDC_PPKVER_2:
- pp->params.fmt_version = 2;
- return 0;
- case IDC_PPKVER_3:
- pp->params.fmt_version = 3;
- return 0;
- case IDC_KDF_ARGON2ID:
- pp->params.argon2_flavour = Argon2id;
- return 0;
- case IDC_KDF_ARGON2I:
- pp->params.argon2_flavour = Argon2i;
- return 0;
- case IDC_KDF_ARGON2D:
- pp->params.argon2_flavour = Argon2d;
- return 0;
- case IDC_ARGON2_MEM:
- try_get_dlg_item_uint32(hwnd, IDC_ARGON2_MEM,
- &pp->params.argon2_mem);
- return 0;
- case IDC_PPK_AUTO_YES:
- pp->params.argon2_passes_auto = true;
- buf = dupprintf("%"PRIu32, pp->time_ms);
- SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf);
- sfree(buf);
- return 0;
- case IDC_PPK_AUTO_NO:
- pp->params.argon2_passes_auto = false;
- buf = dupprintf("%"PRIu32, pp->time_passes);
- SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf);
- sfree(buf);
- return 0;
- case IDC_ARGON2_TIME:
- try_get_dlg_item_uint32(hwnd, IDC_ARGON2_TIME,
- pp->params.argon2_passes_auto ?
- &pp->time_ms : &pp->time_passes);
- return 0;
- case IDC_ARGON2_PARALLEL:
- try_get_dlg_item_uint32(hwnd, IDC_ARGON2_PARALLEL,
- &pp->params.argon2_parallelism);
- return 0;
- }
- return 0;
- case WM_HELP: {
- int id = ((LPHELPINFO)lParam)->iCtrlId;
- const char *topic = NULL;
- switch (id) {
- case IDC_PPKVER_STATIC:
- case IDC_PPKVER_2:
- case IDC_PPKVER_3:
- topic = WINHELP_CTX_puttygen_ppkver; break;
- case IDC_KDF_STATIC:
- case IDC_KDF_ARGON2ID:
- case IDC_KDF_ARGON2I:
- case IDC_KDF_ARGON2D:
- case IDC_ARGON2_MEM_STATIC:
- case IDC_ARGON2_MEM:
- case IDC_ARGON2_MEM_STATIC2:
- case IDC_ARGON2_TIME_STATIC:
- case IDC_ARGON2_TIME:
- case IDC_PPK_AUTO_YES:
- case IDC_PPK_AUTO_NO:
- case IDC_ARGON2_PARALLEL_STATIC:
- case IDC_ARGON2_PARALLEL:
- topic = WINHELP_CTX_puttygen_kdfparam; break;
- }
- if (topic) {
- launch_help(hwnd, topic);
- } else {
- MessageBeep(0);
- }
- break;
- }
- case WM_CLOSE:
- EndDialog(hwnd, 0);
- return 0;
- }
- return 0;
-}
-
-/*
- * Prompt for a key file. Assumes the filename buffer is of size
- * FILENAME_MAX.
- */
-static bool prompt_keyfile(HWND hwnd, char *dlgtitle,
- char *filename, bool save, bool ppk)
-{
- OPENFILENAME of;
- memset(&of, 0, sizeof(of));
- of.hwndOwner = hwnd;
- if (ppk) {
- of.lpstrFilter = "PuTTY Private Key Files (*.ppk)\0*.ppk\0"
- "All Files (*.*)\0*\0\0\0";
- of.lpstrDefExt = ".ppk";
- } else {
- of.lpstrFilter = "All Files (*.*)\0*\0\0\0";
- }
- of.lpstrCustomFilter = NULL;
- of.nFilterIndex = 1;
- of.lpstrFile = filename;
- *filename = '\0';
- of.nMaxFile = FILENAME_MAX;
- of.lpstrFileTitle = NULL;
- of.lpstrTitle = dlgtitle;
- of.Flags = 0;
- return request_file(NULL, &of, false, save);
-}
-
-/*
- * Dialog-box function for the Licence box.
- */
-static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg,
- WPARAM wParam, LPARAM lParam)
-{
- switch (msg) {
- case WM_INITDIALOG: {
- /*
- * Centre the window.
- */
- RECT rs, rd;
- HWND hw;
-
- hw = GetDesktopWindow();
- if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
- MoveWindow(hwnd,
- (rs.right + rs.left + rd.left - rd.right) / 2,
- (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
- rd.right - rd.left, rd.bottom - rd.top, true);
-
- SetDlgItemText(hwnd, 1000, LICENCE_TEXT("\r\n\r\n"));
- return 1;
- }
- case WM_COMMAND:
- switch (LOWORD(wParam)) {
- case IDOK:
- case IDCANCEL:
- EndDialog(hwnd, 1);
- return 0;
- }
- return 0;
- case WM_CLOSE:
- EndDialog(hwnd, 1);
- return 0;
- }
- return 0;
-}
-
-/*
- * Dialog-box function for the About box.
- */
-static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg,
- WPARAM wParam, LPARAM lParam)
-{
- switch (msg) {
- case WM_INITDIALOG:
- /*
- * Centre the window.
- */
- { /* centre the window */
- RECT rs, rd;
- HWND hw;
-
- hw = GetDesktopWindow();
- if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
- MoveWindow(hwnd,
- (rs.right + rs.left + rd.left - rd.right) / 2,
- (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
- rd.right - rd.left, rd.bottom - rd.top, true);
- }
-
- {
- char *buildinfo_text = buildinfo("\r\n");
- char *text = dupprintf
- ("PuTTYgen\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s",
- ver, buildinfo_text,
- "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved.");
- sfree(buildinfo_text);
- SetDlgItemText(hwnd, 1000, text);
- MakeDlgItemBorderless(hwnd, 1000);
- sfree(text);
- }
- return 1;
- case WM_COMMAND:
- switch (LOWORD(wParam)) {
- case IDOK:
- case IDCANCEL:
- EndDialog(hwnd, 1);
- return 0;
- case 101:
- EnableWindow(hwnd, 0);
- DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
- EnableWindow(hwnd, 1);
- SetActiveWindow(hwnd);
- return 0;
- case 102:
- /* Load web browser */
- ShellExecute(hwnd, "open",
- "https://www.chiark.greenend.org.uk/~sgtatham/putty/",
- 0, 0, SW_SHOWDEFAULT);
- return 0;
- }
- return 0;
- case WM_CLOSE:
- EndDialog(hwnd, 1);
- return 0;
- }
- return 0;
-}
-
-typedef enum {RSA, DSA, ECDSA, EDDSA} keytype;
-
-/*
- * Thread to generate a key.
- */
-struct rsa_key_thread_params {
- HWND progressbar; /* notify this with progress */
- HWND dialog; /* notify this on completion */
- int key_bits; /* bits in key modulus (RSA, DSA) */
- int curve_bits; /* bits in elliptic curve (ECDSA) */
- keytype keytype;
- const PrimeGenerationPolicy *primepolicy;
- bool rsa_strong;
- union {
- RSAKey *key;
- struct 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);
-}
-
-/*
- * 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);
-}
-
-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,
- MAKEINTRESOURCE(210),
- NULL, PassphraseProc,
- (LPARAM) &pps);
- if (!dlgret) {
- ret = -2;
- break;
- }
- assert(passphrase != NULL);
- } else
- passphrase = dupstr("");
- if (type == SSH_KEYTYPE_SSH1) {
- if (realtype == type)
- ret = rsa1_load_f(filename, &newkey1, passphrase, &errmsg);
- else
- ret = import_ssh1(filename, realtype, &newkey1,
- passphrase, &errmsg);
- } else {
- if (realtype == type)
- newkey2 = ppk_load_f(filename, passphrase, &errmsg);
- else
- newkey2 = import_ssh2(filename, realtype, passphrase, &errmsg);
- if (newkey2 == SSH2_WRONG_PASSPHRASE)
- ret = -1;
- else if (!newkey2)
- ret = 0;
- else
- ret = 1;
- }
- } while (ret == -1);
- if (comment)
- sfree(comment);
- if (ret == 0) {
- char *msg = dupprintf("Couldn't load private key (%s)", errmsg);
- message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
- 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.
- */
- }
- SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
- (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(200)));
-
- 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);
- DialogBox(hinst, MAKEINTRESOURCE(213), hwnd, AboutProc);
- EnableWindow(hwnd, 1);
- SetActiveWindow(hwnd);
- return 0;
- case IDC_GIVEHELP:
- if (HIWORD(wParam) == BN_CLICKED ||
- HIWORD(wParam) == BN_DOUBLECLICKED) {
- launch_help(hwnd, WINHELP_CTX_puttygen_general);
- }
- return 0;
- case IDC_GENERATE:
- if (HIWORD(wParam) != BN_CLICKED &&
- HIWORD(wParam) != BN_DOUBLECLICKED)
- break;
- state =
- (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
- if (!state->generation_thread_exists) {
- unsigned raw_entropy_required;
- unsigned char *raw_entropy_buf;
- BOOL ok;
-
- state->key_bits = GetDlgItemInt(hwnd, IDC_BITS, &ok, false);
- if (!ok)
- state->key_bits = DEFAULT_KEY_BITS;
- state->ssh2 = true;
-
- if (IsDlgButtonChecked(hwnd, IDC_KEYSSH1)) {
- state->ssh2 = false;
- state->keytype = RSA;
- } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2RSA)) {
- state->keytype = RSA;
- } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA)) {
- state->keytype = DSA;
- } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2ECDSA)) {
- state->keytype = ECDSA;
- int curveindex = SendDlgItemMessage(hwnd, IDC_ECCURVE,
- CB_GETCURSEL, 0, 0);
- assert(curveindex >= 0);
- assert(curveindex < n_ec_nist_curve_lengths);
- state->curve_bits = ec_nist_curve_lengths[curveindex];
- } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2EDDSA)) {
- state->keytype = EDDSA;
- int curveindex = SendDlgItemMessage(hwnd, IDC_EDCURVE,
- CB_GETCURSEL, 0, 0);
- assert(curveindex >= 0);
- assert(curveindex < n_ec_ed_curve_lengths);
- state->curve_bits = ec_ed_curve_lengths[curveindex];
- } else {
- /* Somehow, no button was checked */
- break;
- }
-
- if ((state->keytype == RSA || state->keytype == DSA) &&
- state->key_bits < 256) {
- char *message = dupprintf
- ("PuTTYgen will not generate a key smaller than 256"
- " bits.\nKey length reset to default %d. Continue?",
- DEFAULT_KEY_BITS);
- int ret = MessageBox(hwnd, message, "PuTTYgen Warning",
- MB_ICONWARNING | MB_OKCANCEL);
- sfree(message);
- if (ret != IDOK)
- break;
- state->key_bits = DEFAULT_KEY_BITS;
- SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, false);
- } else if ((state->keytype == RSA || state->keytype == DSA) &&
- state->key_bits < DEFAULT_KEY_BITS) {
- char *message = dupprintf
- ("Keys shorter than %d bits are not recommended. "
- "Really generate this key?", DEFAULT_KEY_BITS);
- int ret = MessageBox(hwnd, message, "PuTTYgen Warning",
- MB_ICONWARNING | MB_OKCANCEL);
- sfree(message);
- if (ret != IDOK)
- break;
- }
-
- if (state->keytype == RSA || state->keytype == DSA)
- raw_entropy_required = (state->key_bits / 2) * 2;
- else if (state->keytype == ECDSA || state->keytype == EDDSA)
- raw_entropy_required = (state->curve_bits / 2) * 2;
- else
- unreachable("we must have initialised keytype by now");
-
- /* Bound the entropy collection above by the amount of
- * data we can actually fit into the PRNG. Any more
- * than that and it's doing no more good. */
- if (raw_entropy_required > random_seed_bits())
- raw_entropy_required = random_seed_bits();
-
- raw_entropy_buf = snewn(raw_entropy_required, unsigned char);
- if (win_read_random(raw_entropy_buf, raw_entropy_required)) {
- /*
- * If we can get entropy from CryptGenRandom, use
- * it. But CryptGenRandom isn't a kernel-level
- * CPRNG (according to Wikipedia), and papers have
- * been published cryptanalysing it. So we'll
- * still do manual entropy collection; we'll just
- * do it _as well_ as this.
- */
- random_reseed(
- make_ptrlen(raw_entropy_buf, raw_entropy_required));
- }
-
- /*
- * Manual entropy input, by making the user wave the
- * mouse over the window a lot.
- *
- * My brief statistical tests on mouse movements
- * suggest that there are about 2.5 bits of randomness
- * in the x position, 2.5 in the y position, and 1.7
- * in the message time, making 5.7 bits of
- * unpredictability per mouse movement. However, other
- * people have told me it's far less than that, so I'm
- * going to be stupidly cautious and knock that down
- * to a nice round 2. With this method, we require two
- * words per mouse movement, so with 2 bits per mouse
- * movement we expect 2 bits every 2 words, i.e. the
- * number of _words_ of mouse data we want to collect
- * is just the same as the number of _bits_ of entropy
- * we want.
- */
- state->entropy_required = raw_entropy_required;
-
- ui_set_state(hwnd, state, 1);
- SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg);
- state->key_exists = false;
- state->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;
-}
-
-void cleanup_exit(int code)
-{
- shutdown_help();
- exit(code);
-}
-
-HINSTANCE hinst;
-
-int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
-{
- 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/WINDOWS/WINPGNT.C b/WINDOWS/WINPGNT.C
deleted file mode 100644
index d6e960b6..00000000
--- a/WINDOWS/WINPGNT.C
+++ /dev/null
@@ -1,1723 +0,0 @@
-/*
- * Pageant: the PuTTY Authentication Agent.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stddef.h>
-#include <ctype.h>
-#include <assert.h>
-#include <tchar.h>
-
-#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 <shellapi.h>
-
-#ifndef NO_SECURITY
-#include <aclapi.h>
-#ifdef DEBUG_IPC
-#define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */
-#include <sddl.h>
-#endif
-#endif
-
-#define WM_SYSTRAY (WM_APP + 6)
-#define WM_SYSTRAY2 (WM_APP + 7)
-
-#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
-
-#define APPNAME "Pageant"
-
-/* Titles and class names for invisible windows. IPCWINTITLE and
- * IPCCLASSNAME are critical to backwards compatibility: WM_COPYDATA
- * based Pageant clients will call FindWindow with those parameters
- * and expect to find the Pageant IPC receiver. */
-#define TRAYWINTITLE "Pageant"
-#define TRAYCLASSNAME "PageantSysTray"
-#define IPCWINTITLE "Pageant"
-#define IPCCLASSNAME "Pageant"
-
-static HWND traywindow;
-static HWND keylist;
-static HWND aboutbox;
-static HMENU systray_menu, session_menu;
-static bool already_running;
-static FingerprintType fptype = SSH_FPTYPE_DEFAULT;
-
-static char *putty_path;
-static bool restrict_putty_acl = false;
-
-/* CWD for "add key" file requester. */
-static filereq *keypath = NULL;
-
-/* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
- * wParam are used by Windows, and should be masked off, so we shouldn't
- * attempt to store information in them. Hence all these identifiers have
- * the low 4 bits clear. Also, identifiers should < 0xF000. */
-
-#define IDM_CLOSE 0x0010
-#define IDM_VIEWKEYS 0x0020
-#define IDM_ADDKEY 0x0030
-#define IDM_ADDKEY_ENCRYPTED 0x0040
-#define IDM_REMOVE_ALL 0x0050
-#define IDM_REENCRYPT_ALL 0x0060
-#define IDM_HELP 0x0070
-#define IDM_ABOUT 0x0080
-#define IDM_PUTTY 0x0090
-#define IDM_SESSIONS_BASE 0x1000
-#define IDM_SESSIONS_MAX 0x2000
-#define PUTTY_REGKEY "Software\\SimonTatham\\PuTTY\\Sessions"
-#define PUTTY_DEFAULT "Default%20Settings"
-static int initial_menuitems_count;
-
-/*
- * Print a modal (Really Bad) message box and perform a fatal exit.
- */
-void modalfatalbox(const char *fmt, ...)
-{
- va_list ap;
- char *buf;
-
- va_start(ap, fmt);
- buf = dupvprintf(fmt, ap);
- va_end(ap);
- MessageBox(traywindow, buf, "Pageant Fatal Error",
- MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
- sfree(buf);
- exit(1);
-}
-
-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;
-}
-
-/*
- * Warn about the obsolescent key file format.
- */
-void old_keyfile_warning(void)
-{
- static const char mbtitle[] = "PuTTY Key File Warning";
- static const char message[] =
- "You are loading an SSH-2 private key which has an\n"
- "old version of the file format. This means your key\n"
- "file is not fully tamperproof. Future versions of\n"
- "PuTTY may stop supporting this private key format,\n"
- "so we recommend you convert your key to the new\n"
- "format.\n"
- "\n"
- "You can perform this conversion by loading the key\n"
- "into PuTTYgen and then saving it again.";
-
- MessageBox(NULL, message, mbtitle, MB_OK);
-}
-
-struct keylist_update_ctx {
- 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;
- 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;
- tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
- strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
-
- res = Shell_NotifyIcon(NIM_ADD, &tnid);
-
- if (hicon) DestroyIcon(hicon);
-
- return res;
-}
-
-/* Update the saved-sessions menu. */
-static void update_sessions(void)
-{
- int num_entries;
- HKEY hkey;
- TCHAR buf[MAX_PATH + 1];
- MENUITEMINFO mii;
- strbuf *sb;
-
- int index_key, index_menu;
-
- if (!putty_path)
- return;
-
- if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
- return;
-
- for(num_entries = GetMenuItemCount(session_menu);
- num_entries > initial_menuitems_count;
- num_entries--)
- RemoveMenu(session_menu, 0, MF_BYPOSITION);
-
- index_key = 0;
- index_menu = 0;
-
- sb = strbuf_new();
- while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
- if(strcmp(buf, PUTTY_DEFAULT) != 0) {
- strbuf_clear(sb);
- unescape_registry_key(buf, sb);
-
- memset(&mii, 0, sizeof(mii));
- mii.cbSize = sizeof(mii);
- mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
- mii.fType = MFT_STRING;
- mii.fState = MFS_ENABLED;
- mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
- mii.dwTypeData = sb->s;
- InsertMenuItem(session_menu, index_menu, true, &mii);
- index_menu++;
- }
- index_key++;
- }
- strbuf_free(sb);
-
- RegCloseKey(hkey);
-
- if(index_menu == 0) {
- mii.cbSize = sizeof(mii);
- mii.fMask = MIIM_TYPE | MIIM_STATE;
- mii.fType = MFT_STRING;
- mii.fState = MFS_GRAYED;
- mii.dwTypeData = _T("(No sessions)");
- InsertMenuItem(session_menu, index_menu, true, &mii);
- }
-}
-
-#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);
- 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);
- }
-}
-
-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);
-}
-
-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;
-
-int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
-{
- 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;
-
- /*
- * 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);
- } else
- putty_path = NULL;
- }
-
- /*
- * 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;
- } 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;
- wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
- 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/WINDOWS/WINPGNTC.C b/WINDOWS/WINPGNTC.C
deleted file mode 100644
index 557dc532..00000000
--- a/WINDOWS/WINPGNTC.C
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Pageant client code.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#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/WINDOWS/WINPLINK.C b/WINDOWS/WINPLINK.C
deleted file mode 100644
index 58d43e6d..00000000
--- a/WINDOWS/WINPLINK.C
+++ /dev/null
@@ -1,535 +0,0 @@
-/*
- * PLink - a Windows command-line (stdin/stdout) variant of PuTTY.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <stdarg.h>
-
-#include "putty.h"
-#include "storage.h"
-#include "tree234.h"
-#include "winsecur.h"
-
-void cmdline_error(const char *fmt, ...)
-{
- va_list ap;
- va_start(ap, fmt);
- console_print_error_msg_fmt_v("plink", fmt, ap);
- va_end(ap);
- exit(1);
-}
-
-static HANDLE inhandle, outhandle, errhandle;
-static struct handle *stdin_handle, *stdout_handle, *stderr_handle;
-static handle_sink stdout_hs, stderr_hs;
-static StripCtrlChars *stdout_scc, *stderr_scc;
-static BinarySink *stdout_bs, *stderr_bs;
-static DWORD orig_console_mode;
-
-static Backend *backend;
-static LogContext *logctx;
-static Conf *conf;
-
-static void plink_echoedit_update(Seat *seat, bool echo, bool edit)
-{
- /* Update stdin read mode to reflect changes in line discipline. */
- DWORD mode;
-
- mode = ENABLE_PROCESSED_INPUT;
- if (echo)
- mode = mode | ENABLE_ECHO_INPUT;
- else
- mode = mode & ~ENABLE_ECHO_INPUT;
- if (edit)
- mode = mode | ENABLE_LINE_INPUT;
- else
- mode = mode & ~ENABLE_LINE_INPUT;
- SetConsoleMode(inhandle, mode);
-}
-
-static size_t plink_output(
- Seat *seat, 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");
- 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;
-}
-
-int main(int argc, char **argv)
-{
- int exitcode;
- bool errors;
- bool use_subsystem = false;
- bool just_test_share_exists = false;
- enum TriState sanitise_stdout = AUTO, sanitise_stderr = AUTO;
- const struct BackendVtable *vt;
-
- dll_hijacking_protection();
-
- /*
- * Initialise port and protocol to sensible defaults. (These
- * will be overridden by more or less anything.)
- */
- settings_set_default_protocol(PROT_SSH);
- settings_set_default_port(22);
-
- /*
- * Process the command line.
- */
- conf = conf_new();
- do_defaults(NULL, conf);
- settings_set_default_protocol(conf_get_int(conf, CONF_protocol));
- settings_set_default_port(conf_get_int(conf, CONF_port));
- errors = false;
- {
- /*
- * Override the default protocol if PLINK_PROTOCOL is set.
- */
- char *p = getenv("PLINK_PROTOCOL");
- if (p) {
- const struct BackendVtable *vt = backend_vt_from_name(p);
- if (vt) {
- settings_set_default_protocol(vt->protocol);
- settings_set_default_port(vt->default_port);
- conf_set_int(conf, CONF_protocol, vt->protocol);
- conf_set_int(conf, CONF_port, vt->default_port);
- }
- }
- }
- while (--argc) {
- char *p = *++argv;
- int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
- 1, conf);
- if (ret == -2) {
- fprintf(stderr,
- "plink: option \"%s\" requires an argument\n", p);
- errors = true;
- } else if (ret == 2) {
- --argc, ++argv;
- } else if (ret == 1) {
- continue;
- } else if (!strcmp(p, "-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;
- }
-
- if (restricted_acl()) {
- lp_eventlog(console_cli_logpolicy,
- "Running with restricted process ACL");
- }
-
- inhandle = GetStdHandle(STD_INPUT_HANDLE);
- outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
- errhandle = GetStdHandle(STD_ERROR_HANDLE);
-
- /*
- * Turn off ECHO and LINE input modes. We don't care if this
- * call fails, because we know we aren't necessarily running in
- * a console.
- */
- GetConsoleMode(inhandle, &orig_console_mode);
- SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
-
- /*
- * Pass the output handles to the handle-handling subsystem.
- * (The input one we leave until we're through the
- * authentication process.)
- */
- stdout_handle = handle_output_new(outhandle, stdouterr_sent, NULL, 0);
- stderr_handle = handle_output_new(errhandle, stdouterr_sent, NULL, 0);
- handle_sink_init(&stdout_hs, stdout_handle);
- handle_sink_init(&stderr_hs, stderr_handle);
- stdout_bs = BinarySink_UPCAST(&stdout_hs);
- stderr_bs = BinarySink_UPCAST(&stderr_hs);
-
- /*
- * Decide whether to sanitise control sequences out of standard
- * output and standard error.
- *
- * If we weren't given a command-line override, we do this if (a)
- * the fd in question is pointing at a console, and (b) we aren't
- * trying to allocate a terminal as part of the session.
- *
- * (Rationale: the risk of control sequences is that they cause
- * confusion when sent to a local console, so if there isn't one,
- * no problem. Also, if we allocate a remote terminal, then we
- * sent a terminal type, i.e. we told it what kind of escape
- * sequences we _like_, i.e. we were expecting to receive some.)
- */
- if (sanitise_stdout == FORCE_ON ||
- (sanitise_stdout == AUTO && is_console_handle(outhandle) &&
- conf_get_bool(conf, CONF_nopty))) {
- stdout_scc = stripctrl_new(stdout_bs, true, L'\0');
- stdout_bs = BinarySink_UPCAST(stdout_scc);
- }
- if (sanitise_stderr == FORCE_ON ||
- (sanitise_stderr == AUTO && is_console_handle(errhandle) &&
- conf_get_bool(conf, CONF_nopty))) {
- stderr_scc = stripctrl_new(stderr_bs, true, L'\0');
- stderr_bs = BinarySink_UPCAST(stderr_scc);
- }
-
- /*
- * Start up the connection.
- */
- winselcli_setup(); /* ensure event object exists */
- {
- char *error, *realhost;
- /* nodelay is only useful if stdin is a character device (console) */
- bool nodelay = conf_get_bool(conf, CONF_tcp_nodelay) &&
- (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);
-
- error = backend_init(vt, plink_seat, &backend, logctx, conf,
- conf_get_str(conf, CONF_host),
- conf_get_int(conf, CONF_port),
- &realhost, nodelay,
- conf_get_bool(conf, CONF_tcp_keepalives));
- if (error) {
- fprintf(stderr, "Unable to open connection:\n%s", error);
- sfree(error);
- return 1;
- }
- ldisc_create(conf, NULL, backend, plink_seat);
- sfree(realhost);
- }
-
- main_thread_id = GetCurrentThreadId();
-
- sending = false;
-
- cli_main_loop(plink_mainloop_pre, plink_mainloop_post, NULL);
-
- exitcode = backend_exitcode(backend);
- if (exitcode < 0) {
- fprintf(stderr, "Remote process exit code unavailable\n");
- exitcode = 1; /* this is an error condition */
- }
- cleanup_exit(exitcode);
- return 0; /* placate compiler warning */
-}
diff --git a/WINDOWS/WINPRINT.C b/WINDOWS/WINPRINT.C
deleted file mode 100644
index e6b3531d..00000000
--- a/WINDOWS/WINPRINT.C
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Printing interface for PuTTY.
- */
-
-#include "putty.h"
-#include <winspool.h>
-
-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/WINDOWS/WINPROXY.C b/WINDOWS/WINPROXY.C
deleted file mode 100644
index 94e31fcb..00000000
--- a/WINDOWS/WINPROXY.C
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * winproxy.c: Windows implementation of platform_new_connection(),
- * supporting an OpenSSH-like proxy command via the winhandl.c
- * mechanism.
- */
-
-#include <stdio.h>
-#include <assert.h>
-
-#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/WINDOWS/WINSER.C b/WINDOWS/WINSER.C
deleted file mode 100644
index 7f4bcf2e..00000000
--- a/WINDOWS/WINSER.C
+++ /dev/null
@@ -1,465 +0,0 @@
-/*
- * Serial back end (Windows-specific).
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-
-#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
- * <http://msdn2.microsoft.com/en-us/library/aa365247.aspx>
- * ("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 <http://msdn2.microsoft.com/en-us/library/aa363858.aspx>
- * (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/WINDOWS/WINSFTP.C b/WINDOWS/WINSFTP.C
deleted file mode 100644
index 0c695d2e..00000000
--- a/WINDOWS/WINSFTP.C
+++ /dev/null
@@ -1,650 +0,0 @@
-/*
- * winsftp.c: the Windows-specific parts of PSFTP and PSCP.
- */
-
-#include <winsock2.h> /* need to put this first, for winelib builds */
-#include <assert.h>
-
-#define NEED_DECLARATION_OF_SELECT
-
-#include "putty.h"
-#include "psftp.h"
-#include "ssh.h"
-#include "winsecur.h"
-
-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;
-
- dll_hijacking_protection();
-
- ret = psftp_main(argc, argv);
-
- return ret;
-}
diff --git a/WINDOWS/WINSTORE.C b/WINDOWS/WINSTORE.C
deleted file mode 100644
index 09e5c028..00000000
--- a/WINDOWS/WINSTORE.C
+++ /dev/null
@@ -1,873 +0,0 @@
-/*
- * winstore.c: Windows-specific implementation of the interface
- * defined in storage.h.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-#include <assert.h>
-#include "putty.h"
-#include "storage.h"
-
-#include <shlobj.h>
-#ifndef CSIDL_APPDATA
-#define CSIDL_APPDATA 0x001a
-#endif
-#ifndef CSIDL_LOCAL_APPDATA
-#define CSIDL_LOCAL_APPDATA 0x001c
-#endif
-
-static const char *const reg_jumplist_key = PUTTY_REG_POS "\\Jumplist";
-static const char *const reg_jumplist_value = "Recent sessions";
-static const char *const puttystr = PUTTY_REG_POS "\\Sessions";
-
-static bool tried_shgetfolderpath = false;
-static HMODULE shell32_module = NULL;
-DECL_WINDOWS_FUNCTION(static, HRESULT, SHGetFolderPathA,
- (HWND, int, HANDLE, DWORD, LPSTR));
-
-struct settings_w {
- HKEY sesskey;
-};
-
-settings_w *open_settings_w(const char *sessionname, char **errmsg)
-{
- HKEY subkey1, sesskey;
- int ret;
- strbuf *sb;
-
- *errmsg = NULL;
-
- if (!sessionname || !*sessionname)
- sessionname = "Default Settings";
-
- 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)
-{
- 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)
-{
- if (handle)
- RegSetValueEx(handle->sesskey, key, 0, REG_DWORD,
- (CONST BYTE *) &value, sizeof(value));
-}
-
-void close_settings_w(settings_w *handle)
-{
- RegCloseKey(handle->sesskey);
- sfree(handle);
-}
-
-struct settings_r {
- HKEY sesskey;
-};
-
-settings_r *open_settings_r(const char *sessionname)
-{
- HKEY subkey1, sesskey;
- strbuf *sb;
-
- if (!sessionname || !*sessionname)
- sessionname = "Default Settings";
-
- 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;
-
- /* 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);
-
- 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)
-{
- if (handle) {
- RegCloseKey(handle->sesskey);
- sfree(handle);
- }
-}
-
-void del_settings(const char *sessionname)
-{
- HKEY subkey1;
- strbuf *sb;
-
- 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;
-};
-
-settings_e *enum_settings_start(void)
-{
- settings_e *ret;
- HKEY key;
-
- if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS)
- return NULL;
-
- ret = snew(settings_e);
- if (ret) {
- ret->key = key;
- ret->i = 0;
- }
-
- return ret;
-}
-
-bool enum_settings_next(settings_e *e, strbuf *sb)
-{
- 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)
-{
- RegCloseKey(e->key);
- 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);
-
- 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);
-
- 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)
-{
- 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;
-
- 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';
- }
-
- /* 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. */
- 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. */
- RegCloseKey(pjumplist_key);
-
- if (ret != ERROR_SUCCESS) {
- return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE;
- } else {
- return JUMPLISTREG_OK;
- }
-}
-
-/* Adds a new entry to the jumplist entries in the registry. */
-int add_to_jumplist_registry(const char *item)
-{
- return transform_jumplist_registry(item, item, NULL);
-}
-
-/* Removes an item from the jumplist entries in the registry. */
-int remove_from_jumplist_registry(const char *item)
-{
- return transform_jumplist_registry(NULL, item, NULL);
-}
-
-/* Returns the jumplist entries from the registry. Caller must free
- * the returned pointer. */
-char *get_jumplist_registry_entries (void)
-{
- char *list_value;
-
- if (transform_jumplist_registry(NULL,NULL,&list_value) != JUMPLISTREG_OK) {
- list_value = snewn(2, char);
- *list_value = '\0';
- *(list_value + 1) = '\0';
- }
- return list_value;
-}
-
-/*
- * Recursively delete a registry key and everything under it.
- */
-static void registry_recursive_remove(HKEY key)
-{
- DWORD i;
- char name[MAX_PATH + 1];
- HKEY subkey;
-
- i = 0;
- while (RegEnumKey(key, i, name, sizeof(name)) == ERROR_SUCCESS) {
- if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS) {
- registry_recursive_remove(subkey);
- RegCloseKey(subkey);
- }
- RegDeleteKey(key, name);
- }
-}
-
-void cleanup_all(void)
-{
- HKEY key;
- int ret;
- char name[MAX_PATH + 1];
-
- /* ------------------------------------------------------------
- * Wipe out the random seed file, in all of its possible
- * locations.
- */
- access_random_seed(DEL);
-
- /* ------------------------------------------------------------
- * Ask Windows to delete any jump list information associated
- * with this installation of PuTTY.
- */
- clear_jumplist();
-
- /* ------------------------------------------------------------
- * Destroy all registry information associated with PuTTY.
- */
-
- /*
- * Open the main PuTTY registry key and remove everything in it.
- */
- if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &key) ==
- ERROR_SUCCESS) {
- registry_recursive_remove(key);
- RegCloseKey(key);
- }
- /*
- * Now open the parent key and remove the PuTTY main key. Once
- * we've done that, see if the parent key has any other
- * children.
- */
- if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_PARENT,
- &key) == ERROR_SUCCESS) {
- RegDeleteKey(key, PUTTY_REG_PARENT_CHILD);
- ret = RegEnumKey(key, 0, name, sizeof(name));
- RegCloseKey(key);
- /*
- * If the parent key had no other children, we must delete
- * it in its turn. That means opening the _grandparent_
- * key.
- */
- if (ret != ERROR_SUCCESS) {
- if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_GPARENT,
- &key) == ERROR_SUCCESS) {
- RegDeleteKey(key, PUTTY_REG_GPARENT_CHILD);
- RegCloseKey(key);
- }
- }
- }
- /*
- * Now we're done.
- */
-}
diff --git a/WINDOWS/WINSTUFF.H b/WINDOWS/WINSTUFF.H
deleted file mode 100644
index c0df5a31..00000000
--- a/WINDOWS/WINSTUFF.H
+++ /dev/null
@@ -1,704 +0,0 @@
-/*
- * winstuff.h: Windows-specific inter-module stuff.
- */
-
-#ifndef PUTTY_WINSTUFF_H
-#define PUTTY_WINSTUFF_H
-
-#ifndef AUTO_WINSOCK
-#include <winsock2.h>
-#endif
-#include <windows.h>
-#include <stdio.h> /* 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 <stddef.h>
-#if !defined _MSC_VER || _MSC_VER >= 1600 || defined __clang__
-#include <stdint.h>
-#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)
-
-#define PUTTY_REG_POS "Software\\SimonTatham\\PuTTY"
-#define PUTTY_REG_PARENT "Software\\SimonTatham"
-#define PUTTY_REG_PARENT_CHILD "PuTTY"
-#define PUTTY_REG_GPARENT "Software"
-#define PUTTY_REG_GPARENT_CHILD "SimonTatham"
-
-/* Result values for the jumplist registry functions. */
-#define JUMPLISTREG_OK 0
-#define JUMPLISTREG_ERROR_INVALID_PARAMETER 1
-#define JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE 2
-#define JUMPLISTREG_ERROR_VALUEREAD_FAILURE 3
-#define JUMPLISTREG_ERROR_VALUEWRITE_FAILURE 4
-#define JUMPLISTREG_ERROR_INVALID_VALUE 5
-
-#define PUTTY_CHM_FILE "putty.chm"
-
-#define GETTICKCOUNT GetTickCount
-#define CURSORBLINK GetCaretBlinkTime()
-#define TICKSPERSEC 1000 /* GetTickCount returns milliseconds */
-
-#define DEFAULT_CODEPAGE CP_ACP
-#define USES_VTLINE_HACK
-
-#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")
-
-/*
- * 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/WINDOWS/WINTIME.C b/WINDOWS/WINTIME.C
deleted file mode 100644
index 5fa3b0de..00000000
--- a/WINDOWS/WINTIME.C
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * wintime.c - Avoid trouble with time() returning (time_t)-1 on Windows.
- */
-
-#include "putty.h"
-#include <time.h>
-
-struct tm ltime(void)
-{
- SYSTEMTIME st;
- struct tm tm;
-
- memset(&tm, 0, sizeof(tm)); /* in case there are any other fields */
-
- GetLocalTime(&st);
- tm.tm_sec=st.wSecond;
- tm.tm_min=st.wMinute;
- tm.tm_hour=st.wHour;
- tm.tm_mday=st.wDay;
- tm.tm_mon=st.wMonth-1;
- tm.tm_year=(st.wYear>=1900?st.wYear-1900:0);
- tm.tm_wday=st.wDayOfWeek;
- tm.tm_yday=-1; /* GetLocalTime doesn't tell us */
- tm.tm_isdst=0; /* GetLocalTime doesn't tell us */
- return tm;
-}
diff --git a/WINDOWS/WINUCS.C b/WINDOWS/WINUCS.C
deleted file mode 100644
index 9ffff5e9..00000000
--- a/WINDOWS/WINUCS.C
+++ /dev/null
@@ -1,1213 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <time.h>
-#include <assert.h>
-
-#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 <http://www.unicode.org/Public/MAPPINGS/> -- 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&#0142;), 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/WINDOWS/WINUTILS.C b/WINDOWS/WINUTILS.C
deleted file mode 100644
index dec8984b..00000000
--- a/WINDOWS/WINUTILS.C
+++ /dev/null
@@ -1,788 +0,0 @@
-/*
- * winutils.c: miscellaneous Windows utilities for GUI apps
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-
-#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 <args>', 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 <string>', 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/WINDOWS/WINX11.C b/WINDOWS/WINX11.C
deleted file mode 100644
index 800d8509..00000000
--- a/WINDOWS/WINX11.C
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * winx11.c: fetch local auth data for X forwarding.
- */
-
-#include <ctype.h>
-#include <assert.h>
-#include <stdlib.h>
-
-#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/WINDOWS/WIN_RES.H b/WINDOWS/WIN_RES.H
deleted file mode 100644
index d34f6852..00000000
--- a/WINDOWS/WIN_RES.H
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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
-#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/WINDOWS/WIN_RES.RC2 b/WINDOWS/WIN_RES.RC2
deleted file mode 100644
index ccec3122..00000000
--- a/WINDOWS/WIN_RES.RC2
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Windows resources shared between PuTTY and PuTTYtel, to be #include'd
- * after defining appropriate macros.
- *
- * Note that many of these strings mention PuTTY. Due to restrictions in
- * VC's handling of string concatenation, this can't easily be fixed.
- * It's fixed up at runtime.
- *
- * This file has the more or less arbitrary extension '.rc2' to avoid
- * IDEs taking it to be a top-level resource script in its own right
- * (which has been known to happen if the extension was '.rc'), and
- * also to avoid the resource compiler ignoring everything included
- * from it (which happens if the extension is '.h').
- */
-
-#include "win_res.h"
-
-IDI_MAINICON ICON "putty.ico"
-
-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
-END
-
-/* Accelerators used: aco */
-IDD_MAINBOX DIALOG DISCARDABLE 0, 0, 300, 252
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "PuTTY Configuration"
-FONT 8, "MS Shell Dlg"
-CLASS "PuTTYConfigBox"
-BEGIN
-END
-
-/* Accelerators used: co */
-IDD_LOGBOX DIALOG DISCARDABLE 100, 20, 300, 119
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "PuTTY Event Log"
-FONT 8, "MS Shell Dlg"
-BEGIN
- DEFPUSHBUTTON "&Close", IDOK, 135, 102, 44, 14
- PUSHBUTTON "C&opy", IDN_COPY, 81, 102, 44, 14
- LISTBOX IDN_LIST, 3, 3, 294, 95, LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL | LBS_EXTENDEDSEL
-END
-
-/* No accelerators used */
-IDD_LICENCEBOX DIALOG DISCARDABLE 50, 50, 326, 239
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "PuTTY Licence"
-FONT 8, "MS Shell Dlg"
-BEGIN
- DEFPUSHBUTTON "OK", IDOK, 148, 219, 44, 14
-
- EDITTEXT IDA_TEXT, 10, 10, 306, 200, ES_READONLY | ES_MULTILINE | ES_LEFT, WS_EX_STATICEDGE
-END
-
-/* 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
-
-#include "version.rc2"
diff --git a/WINDOWS/installer.wxs b/WINDOWS/installer.wxs
index b221320e..65a7f379 100644
--- a/WINDOWS/installer.wxs
+++ b/WINDOWS/installer.wxs
@@ -91,6 +91,10 @@
<?define Desktop_Shortcut_Component_GUID = "8999BBE1-F99E-4301-B7A6-480C19DE13B9" ?>
<?endif ?>
+<?ifndef HelpFilePath ?>
+ <?define HelpFilePath = "../doc/putty.chm" ?>
+<?endif ?>
+
<?define ProgramName = "PuTTY$(var.Bitness)" ?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
@@ -117,12 +121,6 @@
Language="1033" Codepage="1252" Version="$(var.Winver)">
<!--
- We force the install scope to perMachine, largely because I
- don't really understand how to make it usefully switchable
- between the two. If anyone is a WiX expert and does want to
- install PuTTY locally in a user account, I hope they'll send a
- well explained patch!
-
$(var.Puttytextver) is again defined on the candle command
line, and describes the version of PuTTY in human-readable
form, e.g. "PuTTY 0.67" or "PuTTY development snapshot [foo]".
@@ -131,8 +129,7 @@
Description="$(var.Puttytextver) installer"
Manufacturer="Simon Tatham"
InstallerVersion="$(var.InstallerVersion)" Languages="1033"
- Compressed="yes" SummaryCodepage="1252"
- InstallScope="perMachine" />
+ Compressed="yes" SummaryCodepage="1252" />
<!--
Permit installing an arbitrary one of these PuTTY installers
@@ -238,7 +235,7 @@ https://msdn.microsoft.com/en-us/library/windows/desktop/dd391569(v=vs.85).aspx
<Component Id="HelpFile_Component"
Guid="$(var.HelpFile_Component_GUID)">
<File Id="HelpFile_File"
- Source="../doc/putty.chm" KeyPath="yes">
+ Source="$(var.HelpFilePath)" KeyPath="yes">
<Shortcut Id="startmenuManual" Directory="ProgramMenuDir"
Name="PuTTY Manual"
Advertise="no" />
diff --git a/WINDOWS/wincapi.c b/WINDOWS/wincapi.c
deleted file mode 100644
index de78988b..00000000
--- a/WINDOWS/wincapi.c
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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/WINDOWS/wincapi.h b/WINDOWS/wincapi.h
deleted file mode 100644
index 732412e2..00000000
--- a/WINDOWS/wincapi.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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
diff --git a/WINDOWS/wincliloop.c b/WINDOWS/wincliloop.c
deleted file mode 100644
index 26a4d3aa..00000000
--- a/WINDOWS/wincliloop.c
+++ /dev/null
@@ -1,136 +0,0 @@
-#include "putty.h"
-
-void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx)
-{
- SOCKET *sklist = NULL;
- size_t skcount = 0, sksize = 0;
- unsigned long now, next, then;
- now = GETTICKCOUNT();
-
- while (true) {
- 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/WINDOWS/winhelp.rc2 b/WINDOWS/winhelp.rc2
deleted file mode 100644
index 3499d25e..00000000
--- a/WINDOWS/winhelp.rc2
+++ /dev/null
@@ -1,8 +0,0 @@
-#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/WINDOWS/winhsock.c b/WINDOWS/winhsock.c
deleted file mode 100644
index 543b77b6..00000000
--- a/WINDOWS/winhsock.c
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * General mechanism for wrapping up reading/writing of Windows
- * HANDLEs into a PuTTY Socket abstraction.
- */
-
-#include <stdio.h>
-#include <assert.h>
-#include <limits.h>
-
-#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/WINDOWS/winmiscs.c b/WINDOWS/winmiscs.c
deleted file mode 100644
index 571a9122..00000000
--- a/WINDOWS/winmiscs.c
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * 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/WINDOWS/winnohlp.c b/WINDOWS/winnohlp.c
deleted file mode 100644
index 62ddc65c..00000000
--- a/WINDOWS/winnohlp.c
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * nohelp.c: implement the has_embedded_chm() function for
- * applications that have no help file at all, so that misc.c's
- * buildinfo string knows not to talk meaninglessly about whether the
- * nonexistent help file is present.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#include "putty.h"
-
-int has_embedded_chm(void) { return -1; }
diff --git a/WINDOWS/winnpc.c b/WINDOWS/winnpc.c
deleted file mode 100644
index eabfb4bc..00000000
--- a/WINDOWS/winnpc.c
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Windows support module which deals with being a named-pipe client.
- */
-
-#include <stdio.h>
-#include <assert.h>
-
-#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/WINDOWS/winnps.c b/WINDOWS/winnps.c
deleted file mode 100644
index 1757cdbb..00000000
--- a/WINDOWS/winnps.c
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * Windows support module which deals with being a named-pipe server.
- */
-
-#include <stdio.h>
-#include <assert.h>
-
-#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/WINDOWS/winseat.h b/WINDOWS/winseat.h
deleted file mode 100644
index c6b5fa96..00000000
--- a/WINDOWS/winseat.h
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Small implementation of Seat and LogPolicy shared between window.c
- * and windlg.c.
- */
-
-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/WINDOWS/winsecur.c b/WINDOWS/winsecur.c
deleted file mode 100644
index a1164af5..00000000
--- a/WINDOWS/winsecur.c
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * winsecur.c: implementation of winsecur.h.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#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/WINDOWS/winsecur.h b/WINDOWS/winsecur.h
deleted file mode 100644
index fdd39d81..00000000
--- a/WINDOWS/winsecur.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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 <aclapi.h>
-
-/*
- * 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/WINDOWS/winselcli.c b/WINDOWS/winselcli.c
deleted file mode 100644
index f19a0bbe..00000000
--- a/WINDOWS/winselcli.c
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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/WINDOWS/winselgui.c b/WINDOWS/winselgui.c
deleted file mode 100644
index 48a15212..00000000
--- a/WINDOWS/winselgui.c
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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/WINDOWS/winshare.c b/WINDOWS/winshare.c
deleted file mode 100644
index f0e409ac..00000000
--- a/WINDOWS/winshare.c
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Windows implementation of SSH connection-sharing IPC setup.
- */
-
-#include <stdio.h>
-#include <assert.h>
-
-#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/X11FWD.C b/X11FWD.C
deleted file mode 100644
index 86f85831..00000000
--- a/X11FWD.C
+++ /dev/null
@@ -1,1201 +0,0 @@
-/*
- * Platform-independent bits of X11 forwarding.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <time.h>
-
-#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;
-}
diff --git a/agentf.c b/agentf.c
deleted file mode 100644
index dc5bec01..00000000
--- a/agentf.c
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * SSH agent forwarding.
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#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/be_list.c b/be_list.c
new file mode 100644
index 00000000..09437a69
--- /dev/null
+++ b/be_list.c
@@ -0,0 +1,118 @@
+/*
+ * Source file that is rebuilt per application, and provides the list
+ * of backends, the default protocol, and the application name.
+ *
+ * This file expects the build system to provide some per-application
+ * definitions on the compiler command line. So you don't just add it
+ * directly to the sources list for an application. Instead you call
+ * the be_list() function defined in setup.cmake, e.g.
+ *
+ * be_list(target-name AppName [SSH] [SERIAL] [OTHERBACKENDS])
+ *
+ * This translates into the following command-line macro definitions
+ * used by the code below:
+ *
+ * - APPNAME should be defined to the name of the program, in
+ * user-facing capitalisation (e.g. PuTTY rather than putty).
+ * Unquoted: it's easier to stringify it in the preprocessor than
+ * to persuade cmake to put the right quotes on the command line on
+ * all build platforms.
+ *
+ * - The following macros should each be defined to 1 if a given set
+ * of backends should be added to the backends[] list, or 0 if they
+ * should not be:
+ *
+ * * SSH: the two SSH backends (SSH proper, and bare-ssh-connection)
+ *
+ * * SERIAL: the serial port backend
+ *
+ * * OTHERBACKENDS: the non-cryptographic network protocol backends
+ * (Telnet, Rlogin, SUPDUP, Raw)
+ */
+
+#include <stdio.h>
+#include "putty.h"
+
+const char *const appname = STR(APPNAME);
+
+/*
+ * Define the default protocol for the application. This is always a
+ * network backend (serial ports come second behind network, in every
+ * case). Applications that don't have either (such as pterm) don't
+ * need this variable anyway, so just set it to -1.
+ */
+#if SSH
+const int be_default_protocol = PROT_SSH;
+#elif OTHERBACKENDS
+const int be_default_protocol = PROT_TELNET;
+#else
+const int be_default_protocol = -1;
+#endif
+
+/*
+ * List all the configured backends, in the order they should appear
+ * in the config box.
+ */
+const struct BackendVtable *const backends[] = {
+ /*
+ * Start with the most-preferred network-remote-login protocol.
+ * That's SSH if present, otherwise Telnet if present.
+ */
+#if SSH
+ &ssh_backend,
+#elif OTHERBACKENDS
+ &telnet_backend, /* Telnet at the top if SSH is absent */
+#endif
+
+ /*
+ * Second on the list is the serial-port backend, if available.
+ */
+#if SERIAL
+ &serial_backend,
+#endif
+
+ /*
+ * After that come the remaining network protocols: Telnet if it
+ * hasn't already appeared above, and Rlogin, SUPDUP and Raw.
+ */
+#if OTHERBACKENDS && SSH
+ &telnet_backend, /* only if SSH displaced it at the top */
+#endif
+#if OTHERBACKENDS
+ &rlogin_backend,
+ &supdup_backend,
+ &raw_backend,
+#endif
+
+ /*
+ * Bare ssh-connection / PSUSAN is a niche protocol and goes well
+ * down the list.
+ */
+#if SSH
+ &sshconn_backend,
+#endif
+
+ /*
+ * Done. Null pointer to mark the end of the list.
+ */
+ NULL
+};
+
+/*
+ * Number of backends at the start of the above list that should have
+ * radio buttons in the config UI.
+ *
+ * The rule is: the most-preferred network backend, and Serial, each
+ * get a radio button if present.
+ *
+ * The rest will be relegated to a dropdown list.
+ */
+const size_t n_ui_backends =
+ 0
+#if SSH || OTHERBACKENDS
+ + 1
+#endif
+#if SERIAL
+ + 1
+#endif
+ ;
diff --git a/be_misc.c b/be_misc.c
deleted file mode 100644
index 7f50a643..00000000
--- a/be_misc.c
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * be_misc.c: helper functions shared between main network backends.
- */
-
-#include <assert.h>
-#include <string.h>
-
-#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/be_none.c b/be_none.c
deleted file mode 100644
index 588bb1d4..00000000
--- a/be_none.c
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Linking module for programs that do not support selection of backend
- * (such as pterm).
- */
-
-#include <stdio.h>
-#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/be_nos_s.c b/be_nos_s.c
deleted file mode 100644
index 097433aa..00000000
--- a/be_nos_s.c
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Linking module for PuTTYtel: list the available backends not
- * including ssh.
- */
-
-#include <stdio.h>
-#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,
- NULL
-};
-
-const size_t n_ui_backends = 2;
diff --git a/be_nossh.c b/be_nossh.c
deleted file mode 100644
index 1c94f3ae..00000000
--- a/be_nossh.c
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Linking module for PuTTYtel: list the available backends not
- * including ssh.
- */
-
-#include <stdio.h>
-#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,
- NULL
-};
-
-const size_t n_ui_backends = 1;
diff --git a/be_ssh.c b/be_ssh.c
deleted file mode 100644
index 81be62d8..00000000
--- a/be_ssh.c
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * 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 <stdio.h>
-#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/cgtest.c b/cgtest.c
index ba87892c..f0655b98 100644
--- a/cgtest.c
+++ b/cgtest.c
@@ -65,10 +65,10 @@ char *get_random_data_diagnostic(int len, const char *device)
static int nprompts, promptsgot;
static const char *prompts[3];
-int console_get_userpass_input_diagnostic(prompts_t *p)
+SeatPromptResult console_get_userpass_input_diagnostic(prompts_t *p)
{
size_t i;
- int ret = 1;
+ SeatPromptResult ret = SPR_OK;
for (i = 0; i < p->n_prompts; i++) {
if (promptsgot < nprompts) {
prompt_set_result(p->prompts[i], prompts[promptsgot++]);
@@ -77,7 +77,7 @@ int console_get_userpass_input_diagnostic(prompts_t *p)
p->prompts[i]->prompt, p->prompts[i]->result->s);
} else {
promptsgot++; /* track number of requests anyway */
- ret = 0;
+ ret = SPR_SW_ABORT("preloaded prompt unavailable in cgtest");
if (cgtest_verbose)
printf(" prompt \"%s\": no response preloaded\n",
p->prompts[i]->prompt);
diff --git a/charset/CMakeLists.txt b/charset/CMakeLists.txt
new file mode 100644
index 00000000..4ff5bb8a
--- /dev/null
+++ b/charset/CMakeLists.txt
@@ -0,0 +1,30 @@
+include(FindPerl)
+if(NOT PERL_EXECUTABLE)
+ message(FATAL_ERROR "Perl is required to autogenerate sbcsdat.c")
+endif()
+
+set(GENERATED_SBCSDAT_C ${GENERATED_SOURCES_DIR}/sbcsdat.c)
+add_custom_command(OUTPUT ${GENERATED_SBCSDAT_C}.tmp
+ COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/charset/sbcsgen.pl
+ -o ${GENERATED_SBCSDAT_C}.tmp
+ DEPENDS ${CMAKE_SOURCE_DIR}/charset/sbcsgen.pl
+ ${CMAKE_SOURCE_DIR}/charset/sbcs.dat)
+add_custom_target(generated_sbcsdat_c
+ BYPRODUCTS ${GENERATED_SBCSDAT_C}
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ ${GENERATED_SBCSDAT_C}.tmp ${GENERATED_SBCSDAT_C}
+ DEPENDS ${GENERATED_SBCSDAT_C}.tmp
+ COMMENT "Updating sbcsdat.c")
+
+add_library(charset STATIC
+ fromucs.c
+ localenc.c
+ macenc.c
+ mimeenc.c
+ sbcs.c
+ ${GENERATED_SBCSDAT_C}
+ slookup.c
+ toucs.c
+ utf8.c
+ xenc.c)
+add_dependencies(charset generated_sbcsdat_c)
diff --git a/charset/localenc.c b/charset/localenc.c
index 4aa4ae66..49719fbe 100644
--- a/charset/localenc.c
+++ b/charset/localenc.c
@@ -1,5 +1,5 @@
/*
- * local.c - translate our internal character set codes to and from
+ * localenc.c - translate our internal character set codes to and from
* our own set of plausibly legible character-set names. Also
* provides a canonical name for each encoding (useful for software
* announcing what character set it will be using), and a set of
@@ -103,7 +103,7 @@ int charset_from_localenc(const char *name)
p = name;
q = localencs[i].name;
while (*p || *q) {
- if (tolower((unsigned char)*p) != tolower((unsigned char)*q))
+ if (tolower((unsigned char)*p) != tolower((unsigned char)*q))
break;
p++; q++;
}
diff --git a/charset/mimeenc.c b/charset/mimeenc.c
index 2fec6d26..8a0203b3 100644
--- a/charset/mimeenc.c
+++ b/charset/mimeenc.c
@@ -207,7 +207,7 @@ int charset_from_mimeenc(const char *name)
p = name;
q = mimeencs[i].name;
while (*p || *q) {
- if (tolower((unsigned char)*p) != tolower((unsigned char)*q))
+ if (tolower((unsigned char)*p) != tolower((unsigned char)*q))
break;
p++; q++;
}
diff --git a/charset/sbcsgen.pl b/charset/sbcsgen.pl
index 078aebf7..6012c933 100644..100755
--- a/charset/sbcsgen.pl
+++ b/charset/sbcsgen.pl
@@ -1,11 +1,19 @@
-#!/usr/bin/env perl -w
+#!/usr/bin/env perl
# This script generates sbcsdat.c (the data for all the SBCSes) from its
# source form sbcs.dat.
-$infile = "sbcs.dat";
+use warnings;
+use Getopt::Long;
+use File::Basename;
+
+$infile = (dirname __FILE__) . "/sbcs.dat";
$outfile = "sbcsdat.c";
+my $usage = "usage: sbcsgen.pl [-o OUTFILE]\n";
+GetOptions("o|output=s" => \$outfile)
+ or die $usage;
+
open FOO, $infile;
open BAR, ">$outfile";
select BAR;
@@ -30,7 +38,7 @@ my @charsetnames = ();
my @sortpriority = ();
while (<FOO>) {
- chomp;
+ chomp; y/\r//d;
if (/^charset (.*)$/) {
$charsetname = $1;
@vals = ();
diff --git a/charset/xenc.c b/charset/xenc.c
index 964ca6ff..24592ad1 100644
--- a/charset/xenc.c
+++ b/charset/xenc.c
@@ -82,7 +82,7 @@ int charset_from_xenc(const char *name)
p = name;
q = xencs[i].name;
while (*p || *q) {
- if (tolower((unsigned char)*p) != tolower((unsigned char)*q))
+ if (tolower((unsigned char)*p) != tolower((unsigned char)*q))
break;
p++; q++;
}
diff --git a/cmake/cmake.h.in b/cmake/cmake.h.in
new file mode 100644
index 00000000..5ad32515
--- /dev/null
+++ b/cmake/cmake.h.in
@@ -0,0 +1,60 @@
+#cmakedefine NO_IPV6
+#cmakedefine NO_GSSAPI
+#cmakedefine STATIC_GSSAPI
+
+#cmakedefine NO_MULTIMON
+
+#cmakedefine01 HAVE_WINRESRC_H
+#cmakedefine01 HAVE_WINRES_H
+#cmakedefine01 HAVE_WIN_H
+#cmakedefine01 HAVE_NO_STDINT_H
+#cmakedefine01 HAVE_AFUNIX_H
+#cmakedefine01 HAVE_GCP_RESULTSW
+#cmakedefine01 HAVE_ADDDLLDIRECTORY
+#cmakedefine01 HAVE_GETNAMEDPIPECLIENTPROCESSID
+#cmakedefine01 HAVE_SETDEFAULTDLLDIRECTORIES
+#cmakedefine01 HAVE_STRTOUMAX
+#cmakedefine01 HAVE_DWMAPI_H
+
+#cmakedefine NOT_X_WINDOWS
+#cmakedefine OMIT_UTMP
+
+#cmakedefine01 HAVE_ASM_HWCAP_H
+#cmakedefine01 HAVE_SYS_AUXV_H
+#cmakedefine01 HAVE_SYS_SYSCTL_H
+#cmakedefine01 HAVE_SYS_TYPES_H
+#cmakedefine01 HAVE_GLOB_H
+#cmakedefine01 HAVE_UTMP_H
+#cmakedefine01 HAVE_FUTIMES
+#cmakedefine01 HAVE_GETADDRINFO
+#cmakedefine01 HAVE_POSIX_OPENPT
+#cmakedefine01 HAVE_PTSNAME
+#cmakedefine01 HAVE_SETRESUID
+#cmakedefine01 HAVE_SETRESGID
+#cmakedefine01 HAVE_STRSIGNAL
+#cmakedefine01 HAVE_UPDWTMPX
+#cmakedefine01 HAVE_FSTATAT
+#cmakedefine01 HAVE_DIRFD
+#cmakedefine01 HAVE_SETPWENT
+#cmakedefine01 HAVE_ENDPWENT
+#cmakedefine01 HAVE_GETAUXVAL
+#cmakedefine01 HAVE_ELF_AUX_INFO
+#cmakedefine01 HAVE_SYSCTLBYNAME
+#cmakedefine01 HAVE_CLOCK_MONOTONIC
+#cmakedefine01 HAVE_CLOCK_GETTIME
+#cmakedefine01 HAVE_SO_PEERCRED
+#cmakedefine01 HAVE_NULLARY_SETPGRP
+#cmakedefine01 HAVE_BINARY_SETPGRP
+#cmakedefine01 HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE
+#cmakedefine01 HAVE_PANGO_FONT_MAP_LIST_FAMILIES
+
+#cmakedefine01 HAVE_AES_NI
+#cmakedefine01 HAVE_SHA_NI
+#cmakedefine01 HAVE_SHAINTRIN_H
+#cmakedefine01 HAVE_CLMUL
+#cmakedefine01 HAVE_NEON_CRYPTO
+#cmakedefine01 HAVE_NEON_PMULL
+#cmakedefine01 HAVE_NEON_VADDQ_P128
+#cmakedefine01 HAVE_NEON_SHA512
+#cmakedefine01 HAVE_NEON_SHA512_INTRINSICS
+#cmakedefine01 USE_ARM64_NEON_H
diff --git a/cmake/gitcommit.cmake b/cmake/gitcommit.cmake
new file mode 100644
index 00000000..0aa8a095
--- /dev/null
+++ b/cmake/gitcommit.cmake
@@ -0,0 +1,63 @@
+# Pure cmake script to write out cmake_commit.c and cmake_version.but
+
+set(DEFAULT_COMMIT "unavailable")
+set(commit "${DEFAULT_COMMIT}")
+
+set(TOPLEVEL_SOURCE_DIR ${CMAKE_SOURCE_DIR})
+
+execute_process(
+ COMMAND ${GIT_EXECUTABLE} rev-parse --show-toplevel
+ OUTPUT_VARIABLE git_worktree
+ ERROR_VARIABLE stderr
+ RESULT_VARIABLE status)
+string(REGEX REPLACE "\n$" "" git_worktree "${git_worktree}")
+
+if(status EQUAL 0)
+ if(git_worktree STREQUAL CMAKE_SOURCE_DIR)
+ execute_process(
+ COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
+ OUTPUT_VARIABLE git_commit
+ ERROR_VARIABLE stderr
+ RESULT_VARIABLE status)
+ if(status EQUAL 0)
+ string(REGEX REPLACE "\n$" "" commit "${git_commit}")
+ else()
+ if(commit STREQUAL "unavailable")
+ message("Unable to determine git commit: 'git rev-parse HEAD' returned status ${status} and error output:\n${stderr}\n")
+ endif()
+ endif()
+ else()
+ if(commit STREQUAL "unavailable")
+ message("Unable to determine git commit: top-level source dir ${CMAKE_SOURCE_DIR} is not the root of a repository")
+ endif()
+ endif()
+else()
+ if(commit STREQUAL "unavailable")
+ message("Unable to determine git commit: 'git rev-parse --show-toplevel' returned status ${status} and error output:\n${stderr}\n")
+ endif()
+endif()
+
+if(OUTPUT_TYPE STREQUAL header)
+ file(WRITE "${OUTPUT_FILE}" "\
+/*
+ * cmake_commit.c - string literal giving the source git commit, if known.
+ *
+ * Generated by cmake/gitcommit.cmake.
+ */
+
+#include \"putty.h\"
+const char commitid[] = \"${commit}\";
+")
+elseif(OUTPUT_TYPE STREQUAL halibut)
+ if(commit STREQUAL "unavailable")
+ file(WRITE "${OUTPUT_FILE}" "\
+\\versionid no version information available
+")
+ else()
+ file(WRITE "${OUTPUT_FILE}" "\
+\\versionid built from git commit ${commit}
+")
+ endif()
+else()
+ message(FATAL_ERROR "Set OUTPUT_TYPE when running this script")
+endif()
diff --git a/cmake/gtk.cmake b/cmake/gtk.cmake
new file mode 100644
index 00000000..13ff7705
--- /dev/null
+++ b/cmake/gtk.cmake
@@ -0,0 +1,89 @@
+# Look for GTK, of any version.
+
+set(PUTTY_GTK_VERSION "ANY"
+ CACHE STRING "Which major version of GTK to build with")
+set_property(CACHE PUTTY_GTK_VERSION
+ PROPERTY STRINGS ANY 3 2 1 NONE)
+
+set(GTK_FOUND FALSE)
+
+macro(try_pkg_config_gtk VER PACKAGENAME)
+ if(NOT GTK_FOUND AND
+ (PUTTY_GTK_VERSION STREQUAL ANY OR PUTTY_GTK_VERSION STREQUAL ${VER}))
+ find_package(PkgConfig)
+ pkg_check_modules(GTK ${PACKAGENAME})
+ if(GTK_FOUND)
+ set(GTK_VERSION ${VER})
+ endif()
+ endif()
+endmacro()
+try_pkg_config_gtk(3 gtk+-3.0)
+try_pkg_config_gtk(2 gtk+-2.0)
+
+if(NOT GTK_FOUND AND
+ (PUTTY_GTK_VERSION STREQUAL ANY OR PUTTY_GTK_VERSION STREQUAL 1))
+ message("-- Checking for GTK1 (via gtk-config)")
+ find_program(GTK_CONFIG gtk-config)
+ if(GTK_CONFIG)
+ execute_process(COMMAND ${GTK_CONFIG} --cflags
+ OUTPUT_VARIABLE gtk_config_cflags
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ RESULT_VARIABLE gtk_config_cflags_result)
+ execute_process(COMMAND ${GTK_CONFIG} --libs
+ OUTPUT_VARIABLE gtk_config_libs
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ RESULT_VARIABLE gtk_config_libs_result)
+
+ if(gtk_config_cflags_result EQUAL 0 AND gtk_config_libs_result EQUAL 0)
+
+ set(GTK_INCLUDE_DIRS)
+ set(GTK_LIBRARY_DIRS)
+ set(GTK_LIBRARIES)
+
+ separate_arguments(gtk_config_cflags NATIVE_COMMAND
+ ${gtk_config_cflags})
+ foreach(opt ${gtk_config_cflags})
+ string(REGEX MATCH "^-I" ok ${opt})
+ if(ok)
+ string(REGEX REPLACE "^-I" "" optval ${opt})
+ list(APPEND GTK_INCLUDE_DIRS ${optval})
+ endif()
+ endforeach()
+
+ separate_arguments(gtk_config_libs NATIVE_COMMAND
+ ${gtk_config_libs})
+ foreach(opt ${gtk_config_libs})
+ string(REGEX MATCH "^-l" ok ${opt})
+ if(ok)
+ list(APPEND GTK_LIBRARIES ${opt})
+ endif()
+ string(REGEX MATCH "^-L" ok ${opt})
+ if(ok)
+ string(REGEX REPLACE "^-L" "" optval ${opt})
+ list(APPEND GTK_LIBRARY_DIRS ${optval})
+ endif()
+ endforeach()
+
+ message("-- Found GTK1")
+ set(GTK_FOUND TRUE)
+ endif()
+ endif()
+endif()
+
+if(GTK_FOUND)
+ # Check for some particular Pango functions.
+ function(pango_check_subscope)
+ set(CMAKE_REQUIRED_INCLUDES ${GTK_INCLUDE_DIRS})
+ set(CMAKE_REQUIRED_LINK_OPTIONS ${GTK_LDFLAGS})
+ set(CMAKE_REQUIRED_LIBRARIES ${GTK_LIBRARIES})
+ check_symbol_exists(pango_font_family_is_monospace "pango/pango.h"
+ HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE)
+ check_symbol_exists(pango_font_map_list_families "pango/pango.h"
+ HAVE_PANGO_FONT_MAP_LIST_FAMILIES)
+ set(HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE
+ ${HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE} PARENT_SCOPE)
+ set(HAVE_PANGO_FONT_MAP_LIST_FAMILIES
+ ${HAVE_PANGO_FONT_MAP_LIST_FAMILIES} PARENT_SCOPE)
+ endfunction()
+ pango_check_subscope()
+endif()
diff --git a/cmake/licence.cmake b/cmake/licence.cmake
new file mode 100644
index 00000000..e356ae93
--- /dev/null
+++ b/cmake/licence.cmake
@@ -0,0 +1,39 @@
+# Pure cmake script to generate licence.h from LICENCE
+
+file(READ "${LICENCE_FILE}" LICENCE_TEXT)
+
+function(c_string_escape outvar value)
+ string(REPLACE "\\" "\\\\" value "${value}")
+ string(REPLACE "\"" "\\\"" value "${value}")
+ set("${outvar}" "${value}" PARENT_SCOPE)
+endfunction()
+
+set(copyright_regex "PuTTY is copyright ([0-9]+-[0-9]+ [^\n]*[^\n.])\\.?\n")
+string(REGEX MATCH "${copyright_regex}" COPYRIGHT_NOTICE "${LICENCE_TEXT}")
+string(REGEX REPLACE "${copyright_regex}" "\\1"
+ COPYRIGHT_NOTICE "${COPYRIGHT_NOTICE}")
+c_string_escape(COPYRIGHT_NOTICE "${COPYRIGHT_NOTICE}")
+
+string(REGEX REPLACE "\n$" "" LICENCE_TEXT "${LICENCE_TEXT}")
+string(REPLACE "\r" "" LICENCE_TEXT "${LICENCE_TEXT}")
+string(REPLACE "\n\n" "\r" LICENCE_TEXT "${LICENCE_TEXT}")
+string(REPLACE "\n" " " LICENCE_TEXT "${LICENCE_TEXT}")
+string(REPLACE "\r" "\n" LICENCE_TEXT "${LICENCE_TEXT}")
+
+c_string_escape(LICENCE_TEXT "${LICENCE_TEXT}")
+string(REPLACE "\n" "\" \\\n parsep \\\n \""
+ LICENCE_TEXT "${LICENCE_TEXT}")
+
+file(WRITE "${OUTPUT_FILE}" "\
+/*
+ * licence.h - macro definitions for the PuTTY licence.
+ *
+ * Generated by cmake/licence.cmake from ./LICENCE.
+ * You should edit those files rather than editing this one.
+ */
+
+#define LICENCE_TEXT(parsep) \\
+ \"${LICENCE_TEXT}\"
+
+#define SHORT_COPYRIGHT_DETAILS \"${COPYRIGHT_NOTICE}\"
+")
diff --git a/cmake/platforms/unix.cmake b/cmake/platforms/unix.cmake
new file mode 100644
index 00000000..4d056d0a
--- /dev/null
+++ b/cmake/platforms/unix.cmake
@@ -0,0 +1,234 @@
+set(PUTTY_GSSAPI DYNAMIC
+ CACHE STRING "Build PuTTY with dynamically or statically linked \
+Kerberos / GSSAPI support, if possible")
+set_property(CACHE PUTTY_GSSAPI
+ PROPERTY STRINGS DYNAMIC STATIC OFF)
+
+include(CheckIncludeFile)
+include(CheckLibraryExists)
+include(CheckSymbolExists)
+include(CheckCSourceCompiles)
+include(GNUInstallDirs)
+
+set(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS}
+ -D_DEFAULT_SOURCE -D_GNU_SOURCE)
+
+check_include_file(sys/auxv.h HAVE_SYS_AUXV_H)
+check_include_file(asm/hwcap.h HAVE_ASM_HWCAP_H)
+check_include_file(sys/sysctl.h HAVE_SYS_SYSCTL_H)
+check_include_file(sys/types.h HAVE_SYS_TYPES_H)
+check_include_file(glob.h HAVE_GLOB_H)
+check_include_file(utmp.h HAVE_UTMP_H)
+check_include_file(utmpx.h HAVE_UTMPX_H)
+
+check_symbol_exists(futimes "sys/time.h" HAVE_FUTIMES)
+check_symbol_exists(getaddrinfo "sys/types.h;sys/socket.h;netdb.h"
+ HAVE_GETADDRINFO)
+check_symbol_exists(posix_openpt "stdlib.h;fcntl.h" HAVE_POSIX_OPENPT)
+check_symbol_exists(ptsname "stdlib.h" HAVE_PTSNAME)
+check_symbol_exists(setresuid "unistd.h" HAVE_SETRESUID)
+check_symbol_exists(setresgid "unistd.h" HAVE_SETRESGID)
+check_symbol_exists(strsignal "string.h" HAVE_STRSIGNAL)
+check_symbol_exists(updwtmpx "utmpx.h" HAVE_UPDWTMPX)
+check_symbol_exists(fstatat "sys/types.h;sys/stat.h;unistd.h" HAVE_FSTATAT)
+check_symbol_exists(dirfd "sys/types.h;dirent.h" HAVE_DIRFD)
+check_symbol_exists(setpwent "sys/types.h;pwd.h" HAVE_SETPWENT)
+check_symbol_exists(endpwent "sys/types.h;pwd.h" HAVE_ENDPWENT)
+check_symbol_exists(getauxval "sys/auxv.h" HAVE_GETAUXVAL)
+check_symbol_exists(elf_aux_info "sys/auxv.h" HAVE_ELF_AUX_INFO)
+check_symbol_exists(sysctlbyname "sys/types.h;sys/sysctl.h" HAVE_SYSCTLBYNAME)
+check_symbol_exists(CLOCK_MONOTONIC "time.h" HAVE_CLOCK_MONOTONIC)
+check_symbol_exists(clock_gettime "time.h" HAVE_CLOCK_GETTIME)
+
+check_c_source_compiles("
+#define _GNU_SOURCE
+#include <features.h>
+#include <sys/socket.h>
+int main(int argc, char **argv) {
+ struct ucred cr;
+ socklen_t crlen = sizeof(cr);
+ return getsockopt(0, SOL_SOCKET, SO_PEERCRED, &cr, &crlen) +
+ cr.pid + cr.uid + cr.gid;
+}" HAVE_SO_PEERCRED)
+
+check_c_source_compiles("
+#include <sys/types.h>
+#include <unistd.h>
+
+int main(int argc, char **argv) {
+ setpgrp();
+}" HAVE_NULLARY_SETPGRP)
+check_c_source_compiles("
+#include <sys/types.h>
+#include <unistd.h>
+
+int main(int argc, char **argv) {
+ setpgrp(0, 0);
+}" HAVE_BINARY_SETPGRP)
+
+if(HAVE_GETADDRINFO AND PUTTY_IPV6)
+ set(NO_IPV6 OFF)
+else()
+ set(NO_IPV6 ON)
+endif()
+
+if(HAVE_UTMPX_H)
+ set(OMIT_UTMP OFF)
+else()
+ set(OMIT_UTMP ON)
+endif()
+
+include(cmake/gtk.cmake)
+
+if(GTK_FOUND)
+ # See if we have X11 available. This requires libX11 itself, and also
+ # the GDK integration to X11.
+ find_package(X11)
+
+ function(check_x11)
+ list(APPEND CMAKE_REQUIRED_INCLUDES ${GTK_INCLUDE_DIRS})
+ check_include_file(gdk/gdkx.h HAVE_GDK_GDKX_H)
+
+ if(X11_FOUND AND HAVE_GDK_GDKX_H)
+ set(NOT_X_WINDOWS OFF PARENT_SCOPE)
+ else()
+ set(NOT_X_WINDOWS ON PARENT_SCOPE)
+ endif()
+ endfunction()
+ check_x11()
+else()
+ # If we didn't even have GTK, behave as if X11 is not available.
+ # (There's nothing useful we could do with it even if there was.)
+ set(NOT_X_WINDOWS ON)
+endif()
+
+include_directories(${CMAKE_SOURCE_DIR}/charset ${GTK_INCLUDE_DIRS} ${X11_INCLUDE_DIR})
+link_directories(${GTK_LIBRARY_DIRS})
+
+function(add_optional_system_lib library testfn)
+ check_library_exists(${library} ${testfn} "" HAVE_LIB${library})
+ if (HAVE_LIB${library})
+ set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES};-l${library})
+ link_libraries(-l${library})
+ endif()
+endfunction()
+
+add_optional_system_lib(m pow)
+add_optional_system_lib(rt clock_gettime)
+add_optional_system_lib(xnet socket)
+
+set(extra_dirs charset)
+
+if(PUTTY_GSSAPI STREQUAL DYNAMIC)
+ add_optional_system_lib(dl dlopen)
+ if(HAVE_NO_LIBdl)
+ message(WARNING
+ "Could not find libdl -- cannot provide dynamic GSSAPI support")
+ set(NO_GSSAPI ON)
+ endif()
+endif()
+
+if(PUTTY_GSSAPI STREQUAL STATIC)
+ set(KRB5_CFLAGS)
+ set(KRB5_LDFLAGS)
+
+ # First try using pkg-config
+ find_package(PkgConfig)
+ pkg_check_modules(KRB5 krb5-gssapi)
+
+ # Failing that, try the dedicated krb5-config
+ if(NOT KRB5_FOUND)
+ find_program(KRB5_CONFIG krb5-config)
+ if(KRB5_CONFIG)
+ execute_process(COMMAND ${KRB5_CONFIG} --cflags gssapi
+ OUTPUT_VARIABLE krb5_config_cflags
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ RESULT_VARIABLE krb5_config_cflags_result)
+ execute_process(COMMAND ${KRB5_CONFIG} --libs gssapi
+ OUTPUT_VARIABLE krb5_config_libs
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ RESULT_VARIABLE krb5_config_libs_result)
+
+ if(krb5_config_cflags_result EQUAL 0 AND krb5_config_libs_result EQUAL 0)
+ set(KRB5_INCLUDE_DIRS)
+ set(KRB5_LIBRARY_DIRS)
+ set(KRB5_LIBRARIES)
+
+ # We can safely put krb5-config's cflags directly into cmake's
+ # cflags, without bothering to extract the include directories.
+ set(KRB5_CFLAGS ${krb5_config_cflags})
+
+ # But krb5-config --libs isn't so simple. It will actually
+ # deliver a mix of libraries and other linker options. We have
+ # to separate them for cmake purposes, because if we pass the
+ # whole lot to add_link_options then they'll appear too early
+ # in the command line (so that by the time our own code refers
+ # to GSSAPI functions it'll be too late to search these
+ # libraries for them), and if we pass the whole lot to
+ # link_libraries then it'll get confused about options that
+ # aren't libraries.
+ separate_arguments(krb5_config_libs NATIVE_COMMAND
+ ${krb5_config_libs})
+ foreach(opt ${krb5_config_libs})
+ string(REGEX MATCH "^-l" ok ${opt})
+ if(ok)
+ list(APPEND KRB5_LIBRARIES ${opt})
+ continue()
+ endif()
+ string(REGEX MATCH "^-L" ok ${opt})
+ if(ok)
+ string(REGEX REPLACE "^-L" "" optval ${opt})
+ list(APPEND KRB5_LIBRARY_DIRS ${optval})
+ continue()
+ endif()
+ list(APPEND KRB5_LDFLAGS ${opt})
+ endforeach()
+
+ message(STATUS "Found Kerberos via krb5-config")
+ set(KRB5_FOUND YES)
+ endif()
+ endif()
+ endif()
+
+ if(KRB5_FOUND)
+ include_directories(${KRB5_INCLUDE_DIRS})
+ link_directories(${KRB5_LIBRARY_DIRS})
+ link_libraries(${KRB5_LIBRARIES})
+ add_compile_options(${KRB5_CFLAGS})
+ add_link_options(${KRB5_LDFLAGS})
+ set(STATIC_GSSAPI ON)
+ else()
+ message(WARNING
+ "Could not find krb5 via pkg-config or krb5-config -- \
+cannot provide static GSSAPI support")
+ set(NO_GSSAPI ON)
+ endif()
+endif()
+
+if(PUTTY_GSSAPI STREQUAL OFF)
+ set(NO_GSSAPI ON)
+endif()
+
+if(STRICT AND (CMAKE_C_COMPILER_ID MATCHES "GNU" OR
+ CMAKE_C_COMPILER_ID MATCHES "Clang"))
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wpointer-arith -Wvla")
+endif()
+
+function(installed_program target)
+ if(CMAKE_VERSION VERSION_LESS 3.14)
+ # CMake 3.13 and earlier required an explicit install destination.
+ install(TARGETS ${target} RUNTIME DESTINATION bin)
+ else()
+ # 3.14 and above selects a sensible default, which we should avoid
+ # overriding here so that end users can override it using
+ # CMAKE_INSTALL_BINDIR.
+ install(TARGETS ${target})
+ endif()
+
+ if(HAVE_MANPAGE_${target}_1)
+ install(FILES ${CMAKE_BINARY_DIR}/doc/${target}.1
+ DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
+ else()
+ message(WARNING "Could not build man page ${target}.1")
+ endif()
+endfunction()
diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake
new file mode 100644
index 00000000..a7ed7c7c
--- /dev/null
+++ b/cmake/platforms/windows.cmake
@@ -0,0 +1,202 @@
+set(PUTTY_MINEFIELD OFF
+ CACHE BOOL "Build PuTTY with its built-in memory debugger 'Minefield'")
+set(PUTTY_GSSAPI ON
+ CACHE BOOL "Build PuTTY with GSSAPI support")
+set(PUTTY_LINK_MAPS OFF
+ CACHE BOOL "Attempt to generate link maps")
+set(PUTTY_EMBEDDED_CHM_FILE ""
+ CACHE FILEPATH "Path to a .chm help file to embed in the binaries")
+
+if(PUTTY_SUBSYSTEM_VERSION)
+ string(REPLACE
+ "subsystem:windows" "subsystem:windows,${PUTTY_SUBSYSTEM_VERSION}"
+ CMAKE_C_CREATE_WIN32_EXE ${CMAKE_C_CREATE_WIN32_EXE})
+ string(REPLACE
+ "subsystem:console" "subsystem:console,${PUTTY_SUBSYSTEM_VERSION}"
+ CMAKE_C_CREATE_CONSOLE_EXE ${CMAKE_C_CREATE_CONSOLE_EXE})
+endif()
+
+function(define_negation newvar oldvar)
+ if(${oldvar})
+ set(${newvar} OFF PARENT_SCOPE)
+ else()
+ set(${newvar} ON PARENT_SCOPE)
+ endif()
+endfunction()
+
+include(CheckIncludeFiles)
+include(CheckSymbolExists)
+include(CheckCSourceCompiles)
+
+# Still needed for AArch32 Windows builds
+set(CMAKE_REQUIRED_DEFINITIONS -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE)
+
+check_include_files("windows.h;winresrc.h" HAVE_WINRESRC_H)
+if(NOT HAVE_WINRESRC_H)
+ # A couple of fallback names for the header file you can include in
+ # .rc files. We conditionalise even these checks, to save effort at
+ # cmake time.
+ check_include_files("windows.h;winres.h" HAVE_WINRES_H)
+ if(NOT HAVE_WINRES_H)
+ check_include_files("windows.h;win.h" HAVE_WIN_H)
+ endif()
+endif()
+check_include_files("stdint.h" HAVE_STDINT_H)
+define_negation(HAVE_NO_STDINT_H HAVE_STDINT_H)
+
+check_include_files("windows.h;multimon.h" HAVE_MULTIMON_H)
+define_negation(NO_MULTIMON HAVE_MULTIMON_H)
+
+check_include_files("windows.h;htmlhelp.h" HAVE_HTMLHELP_H)
+define_negation(NO_HTMLHELP HAVE_HTMLHELP_H)
+
+check_include_files("winsock2.h;afunix.h" HAVE_AFUNIX_H)
+
+check_symbol_exists(strtoumax "inttypes.h" HAVE_STRTOUMAX)
+check_symbol_exists(AddDllDirectory "windows.h" HAVE_ADDDLLDIRECTORY)
+check_symbol_exists(SetDefaultDllDirectories "windows.h"
+ HAVE_SETDEFAULTDLLDIRECTORIES)
+check_symbol_exists(GetNamedPipeClientProcessId "windows.h"
+ HAVE_GETNAMEDPIPECLIENTPROCESSID)
+check_symbol_exists(CreatePseudoConsole "windows.h" HAVE_CONPTY)
+
+check_c_source_compiles("
+#include <windows.h>
+GCP_RESULTSW gcpw;
+int main(void) { return 0; }
+" HAVE_GCP_RESULTSW)
+
+function(dwmapi_test_wrapper)
+ set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} dwmapi.lib)
+ check_c_source_compiles("
+#include <windows.h>
+#include <dwmapi.h>
+volatile HWND hwnd;
+int main(void) {
+ RECT r;
+ DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &r, sizeof(r));
+}
+" HAVE_DWMAPI_H)
+ set(HAVE_DWMAPI_H ${HAVE_DWMAPI_H} PARENT_SCOPE)
+endfunction()
+dwmapi_test_wrapper()
+
+set(NO_SECURITY ${PUTTY_NO_SECURITY})
+
+add_compile_definitions(
+ _WINDOWS
+ _CRT_SECURE_NO_WARNINGS
+ _WINSOCK_DEPRECATED_NO_WARNINGS
+ _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE)
+
+if(PUTTY_MINEFIELD)
+ add_compile_definitions(MINEFIELD)
+endif()
+if(NOT PUTTY_GSSAPI)
+ add_compile_definitions(NO_GSSAPI)
+endif()
+if(PUTTY_EMBEDDED_CHM_FILE)
+ add_compile_definitions("EMBEDDED_CHM_FILE=\"${PUTTY_EMBEDDED_CHM_FILE}\"")
+endif()
+
+if(WINELIB)
+ enable_language(RC)
+ set(LFLAG_MANIFEST_NO "")
+elseif(CMAKE_C_COMPILER_ID MATCHES "MSVC" OR
+ CMAKE_C_COMPILER_FRONTEND_VARIANT MATCHES "MSVC")
+ set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} /nologo /C1252")
+ set(LFLAG_MANIFEST_NO "/manifest:no")
+else()
+ set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -c1252")
+ set(LFLAG_MANIFEST_NO "")
+endif()
+
+if(STRICT)
+ if(CMAKE_C_COMPILER_ID MATCHES "GNU")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wpointer-arith -Wvla")
+ elseif(CMAKE_C_COMPILER_ID MATCHES "Clang")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wpointer-arith -Wvla")
+ endif()
+endif()
+
+if(CMAKE_C_COMPILER_ID MATCHES "Clang")
+ # Switch back from MSVC-style error message format
+ # "file.c(line,col)" to clang's native style "file.c:line:col:". I
+ # find the latter more convenient because it matches other Unixy
+ # tools like grep, and I have tooling to parse that format and jump
+ # to the sites of error messages.
+ set(CMAKE_C_FLAGS
+ "${CMAKE_C_FLAGS} -Xclang -fdiagnostics-format -Xclang clang")
+endif()
+
+if(CMAKE_C_COMPILER_ID MATCHES "MSVC")
+ # Turn off some warnings that I've just found too noisy.
+ #
+ # - 4244, 4267: "possible loss of data" when narrowing an integer
+ # type (separate warning numbers for initialisers and
+ # assignments). Every time I spot-check instances of this, they
+ # turn out to be sensible (e.g. something was already checked, or
+ # was assigned from a previous variable that must have been in
+ # range). I don't think putting a warning-suppression idiom at
+ # every one of these sites would improve code legibility.
+ #
+ # - 4018: "signed/unsigned mismatch" in integer comparison. Again,
+ # comes up a lot, and generally my spot checks make it look as if
+ # it's OK.
+ #
+ # - 4146: applying unary '-' to an unsigned type. We do that all
+ # the time in deliberate bit-twiddling code like mpint.c or
+ # crypto implementations.
+ #
+ # - 4293: warning about undefined behaviour if a shift count is too
+ # big. We often do this inside a ?: clause which doesn't evaluate
+ # the overlong shift unless the shift count _isn't_ too big. When
+ # the shift count is constant, MSVC spots the potential problem
+ # in one branch of the ?:, but doesn't also spot that that branch
+ # isn't ever taken, so it complains about a thing that's already
+ # guarded.
+ #
+ # - 4090: different 'const' qualifiers. It's a shame to suppress
+ # this one, because const mismatches really are a thing I'd
+ # normally like to be warned about. But MSVC (as of 2017 at
+ # least) seems to have a bug in which assigning a 'void *' into a
+ # 'const char **' thinks there's a const-qualifier mismatch.
+ # There isn't! Both are pointers to modifiable objects. The fact
+ # that in one case, the modifiable object is a pointer to
+ # something _else_ const should make no difference.
+
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} \
+/wd4244 /wd4267 /wd4018 /wd4146 /wd4293 /wd4090")
+endif()
+
+if(CMAKE_C_COMPILER_FRONTEND_VARIANT MATCHES "MSVC")
+ set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} /dynamicbase /nxcompat")
+endif()
+
+set(platform_libraries
+ advapi32.lib comdlg32.lib gdi32.lib imm32.lib
+ ole32.lib shell32.lib user32.lib ws2_32.lib kernel32.lib)
+
+# Generate link maps
+if(PUTTY_LINK_MAPS)
+ if(CMAKE_C_COMPILER_ID MATCHES "Clang" AND
+ "x${CMAKE_C_COMPILER_FRONTEND_VARIANT}" STREQUAL "xMSVC")
+ set(CMAKE_C_LINK_EXECUTABLE
+ "${CMAKE_C_LINK_EXECUTABLE} /lldmap:<TARGET>.map")
+ elseif(CMAKE_C_COMPILER_ID MATCHES "MSVC")
+ set(CMAKE_C_LINK_EXECUTABLE
+ "${CMAKE_C_LINK_EXECUTABLE} /map:<TARGET>.map")
+ else()
+ message(WARNING
+ "Don't know how to generate link maps on this toolchain")
+ endif()
+endif()
+
+# Write out a file in the cmake output directory listing the
+# executables that are 'official' enough to want to code-sign and
+# ship.
+file(WRITE ${CMAKE_BINARY_DIR}/shipped.txt "")
+function(installed_program target)
+ file(APPEND ${CMAKE_BINARY_DIR}/shipped.txt
+ "${target}${CMAKE_EXECUTABLE_SUFFIX}\n")
+endfunction()
diff --git a/cmake/setup.cmake b/cmake/setup.cmake
new file mode 100644
index 00000000..7a650771
--- /dev/null
+++ b/cmake/setup.cmake
@@ -0,0 +1,113 @@
+# Forcibly re-enable assertions, even if we're building in release
+# mode. This is a security project - assertions may be enforcing
+# security-critical constraints. A backstop #ifdef in defs.h should
+# give a #error if this manoeuvre doesn't do what it needs to.
+string(REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
+string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
+string(REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
+string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
+
+set(PUTTY_IPV6 ON
+ CACHE BOOL "Build PuTTY with IPv6 support if possible")
+set(PUTTY_DEBUG OFF
+ CACHE BOOL "Build PuTTY with debug() statements enabled")
+set(PUTTY_FUZZING OFF
+ CACHE BOOL "Build PuTTY binaries suitable for fuzzing, NOT FOR REAL USE")
+set(PUTTY_COVERAGE OFF
+ CACHE BOOL "Build PuTTY binaries suitable for code coverage analysis")
+
+set(STRICT OFF
+ CACHE BOOL "Enable extra compiler warnings and make them errors")
+
+include(FindGit)
+
+set(GENERATED_SOURCES_DIR ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY})
+
+set(GENERATED_LICENCE_H ${GENERATED_SOURCES_DIR}/licence.h)
+set(INTERMEDIATE_LICENCE_H ${GENERATED_LICENCE_H}.tmp)
+add_custom_command(OUTPUT ${INTERMEDIATE_LICENCE_H}
+ COMMAND ${CMAKE_COMMAND}
+ -DLICENCE_FILE=${CMAKE_SOURCE_DIR}/LICENCE
+ -DOUTPUT_FILE=${INTERMEDIATE_LICENCE_H}
+ -P ${CMAKE_SOURCE_DIR}/cmake/licence.cmake
+ DEPENDS ${CMAKE_SOURCE_DIR}/cmake/licence.cmake ${CMAKE_SOURCE_DIR}/LICENCE)
+add_custom_target(generated_licence_h
+ BYPRODUCTS ${GENERATED_LICENCE_H}
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ ${INTERMEDIATE_LICENCE_H} ${GENERATED_LICENCE_H}
+ DEPENDS ${INTERMEDIATE_LICENCE_H}
+ COMMENT "Updating licence.h")
+
+set(GENERATED_COMMIT_C ${GENERATED_SOURCES_DIR}/cmake_commit.c)
+set(INTERMEDIATE_COMMIT_C ${GENERATED_COMMIT_C}.tmp)
+add_custom_target(check_git_commit
+ BYPRODUCTS ${INTERMEDIATE_COMMIT_C}
+ COMMAND ${CMAKE_COMMAND}
+ -DGIT_EXECUTABLE=${GIT_EXECUTABLE}
+ -DOUTPUT_FILE=${INTERMEDIATE_COMMIT_C}
+ -DOUTPUT_TYPE=header
+ -P ${CMAKE_SOURCE_DIR}/cmake/gitcommit.cmake
+ DEPENDS ${CMAKE_SOURCE_DIR}/cmake/gitcommit.cmake
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ COMMENT "Checking current git commit")
+add_custom_target(cmake_commit_c
+ BYPRODUCTS ${GENERATED_COMMIT_C}
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ ${INTERMEDIATE_COMMIT_C} ${GENERATED_COMMIT_C}
+ DEPENDS check_git_commit ${INTERMEDIATE_COMMIT_C}
+ COMMENT "Updating cmake_commit.c")
+
+if(CMAKE_VERSION VERSION_LESS 3.12)
+ function(add_compile_definitions)
+ foreach(i ${ARGN})
+ add_compile_options(-D${i})
+ endforeach()
+ endfunction()
+endif()
+
+function(add_sources_from_current_dir target)
+ set(sources)
+ foreach(i ${ARGN})
+ set(sources ${sources} ${CMAKE_CURRENT_SOURCE_DIR}/${i})
+ endforeach()
+ target_sources(${target} PRIVATE ${sources})
+endfunction()
+
+set(extra_dirs)
+if(CMAKE_SYSTEM_NAME MATCHES "Windows" OR WINELIB)
+ set(platform windows)
+else()
+ set(platform unix)
+endif()
+
+function(be_list TARGET NAME)
+ cmake_parse_arguments(OPT "SSH;SERIAL;OTHERBACKENDS" "" "" "${ARGN}")
+ add_library(${TARGET}-be-list OBJECT ${CMAKE_SOURCE_DIR}/be_list.c)
+ foreach(setting SSH SERIAL OTHERBACKENDS)
+ if(OPT_${setting})
+ target_compile_definitions(${TARGET}-be-list PRIVATE ${setting}=1)
+ else()
+ target_compile_definitions(${TARGET}-be-list PRIVATE ${setting}=0)
+ endif()
+ endforeach()
+ target_compile_definitions(${TARGET}-be-list PRIVATE APPNAME=${NAME})
+ target_sources(${TARGET} PRIVATE $<TARGET_OBJECTS:${TARGET}-be-list>)
+endfunction()
+
+include(cmake/platforms/${platform}.cmake)
+
+include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${GENERATED_SOURCES_DIR}
+ ${platform}
+ ${extra_dirs})
+
+if(PUTTY_DEBUG)
+ add_compile_definitions(DEBUG)
+endif()
+if(PUTTY_FUZZING)
+ add_compile_definitions(FUZZING)
+endif()
+if(PUTTY_COVERAGE)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage -g ")
+endif()
diff --git a/cmake/toolchain-mingw.cmake b/cmake/toolchain-mingw.cmake
new file mode 100644
index 00000000..2e0bc669
--- /dev/null
+++ b/cmake/toolchain-mingw.cmake
@@ -0,0 +1,12 @@
+# Simple toolchain file for cross-building Windows PuTTY on Linux
+# using MinGW (tested on Ubuntu).
+
+set(CMAKE_SYSTEM_NAME Windows)
+set(CMAKE_SYSTEM_PROCESSOR x86_64)
+
+set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
+set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
+set(CMAKE_AR x86_64-w64-mingw32-ar)
+set(CMAKE_RANLIB x86_64-w64-mingw32-ranlib)
+
+add_compile_definitions(__USE_MINGW_ANSI_STDIO)
diff --git a/cmake/toolchain-winegcc.cmake b/cmake/toolchain-winegcc.cmake
new file mode 100644
index 00000000..73a9e53e
--- /dev/null
+++ b/cmake/toolchain-winegcc.cmake
@@ -0,0 +1,33 @@
+# Toolchain file for cross-building a Winelib version of Windows PuTTY
+# on Linux, using winegcc (tested on Ubuntu).
+
+# Winelib is weird because it's basically compiling ordinary Linux
+# objects and executables, but we want to pretend to be Windows for
+# purposes of (a) having resource files, and (b) selecting the Windows
+# platform subdirectory.
+#
+# So, do we tag this as a weird kind of Windows build, or a weird kind
+# of Linux build? Either way we have to do _something_ out of the
+# ordinary.
+#
+# After some experimentation, it seems to make more sense to treat
+# Winelib builds as basically Linux, and set a flag WINELIB that
+# PuTTY's main build scripts will detect and handle specially.
+# Specifically, that flag will cause cmake/setup.cmake to select the
+# Windows platform (overriding the usual check of CMAKE_SYSTEM_NAME),
+# and also trigger a call to enable_language(RC), which for some kind
+# of cmake re-entrancy reason we can't do in this toolchain file
+# itself.
+set(CMAKE_SYSTEM_NAME Linux)
+set(WINELIB ON)
+
+# We need a wrapper script around winegcc proper, because cmake's link
+# command lines will refer to system libraries as "-lkernel32.lib"
+# rather than the required "-lkernel32". The winegcc script alongside
+# this toolchain file bodges that command-line translation.
+set(CMAKE_C_COMPILER ${CMAKE_SOURCE_DIR}/cmake/winegcc)
+
+set(CMAKE_RC_COMPILER wrc)
+set(CMAKE_RC_OUTPUT_EXTENSION .res.o)
+set(CMAKE_RC_COMPILE_OBJECT
+ "<CMAKE_RC_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -o <OBJECT> <SOURCE>")
diff --git a/cmake/winegcc b/cmake/winegcc
new file mode 100755
index 00000000..fb298ad1
--- /dev/null
+++ b/cmake/winegcc
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+# Wrapper for winegcc that allows it to be used in a build generated
+# from PuTTY's CMakeLists.txt, by bodging around the command-line
+# options that CMake gets wrong.
+
+init=true
+for arg in init "$@"; do
+ if $init; then
+ set --
+ init=false
+ continue
+ fi
+
+ case "$arg" in
+ # The Windows build definition for PuTTY specifies all the
+ # system API libraries by names like kernel32.lib. When CMake
+ # reads that file and thinks it's compiling for Linux, it will
+ # generate link options such as -lkernel32.lib. But in fact
+ # winegcc expects -lkernel32, so we need to strip the ".lib"
+ # suffix.
+ -l*.lib) set -- "$@" "${arg%.lib}";;
+
+ # Anything else, we leave unchanged.
+ *) set -- "$@" "$arg";;
+ esac
+done
+
+exec winegcc "$@"
diff --git a/cmdgen.c b/cmdgen.c
index ef9c2e1c..b12758a1 100644
--- a/cmdgen.c
+++ b/cmdgen.c
@@ -130,13 +130,19 @@ void help(void)
" public RFC 4716 / ssh.com public key\n"
" public-openssh OpenSSH public key\n"
" fingerprint output the key fingerprint\n"
+ " cert-info print certificate information\n"
" text output the key components as "
"'name=0x####'\n"
" -o specify output file\n"
" -l equivalent to `-O fingerprint'\n"
" -L equivalent to `-O public-openssh'\n"
" -p equivalent to `-O public'\n"
+ " --cert-info equivalent to `-O cert-info'\n"
" --dump equivalent to `-O text'\n"
+ " -E fptype specify fingerprint output type:\n"
+ " sha256, md5, sha256-cert, md5-cert\n"
+ " --certificate file incorporate a certificate into the key\n"
+ " --remove-certificate remove any certificate from the key\n"
" --reencrypt load a key and save it with fresh "
"encryption\n"
" --old-passphrase file\n"
@@ -214,6 +220,15 @@ static char *readpassphrase(const char *filename)
#define DEFAULT_RSADSA_BITS 2048
+static void spr_error(SeatPromptResult spr)
+{
+ if (spr.kind == SPRK_SW_ABORT) {
+ char *err = spr_get_error_message(spr);
+ fprintf(stderr, "puttygen: unable to read passphrase: %s", err);
+ sfree(err);
+ }
+}
+
/* For Unix in particular, but harmless if this main() is reused elsewhere */
const bool buildinfo_gtk_relevant = false;
@@ -226,7 +241,7 @@ int main(int argc, char **argv)
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;
+ OPENSSH_NEW, SSHCOM, TEXT, CERTINFO } outtype = PRIVATE;
int bits = -1;
const char *comment = NULL;
char *origcomment = NULL;
@@ -241,6 +256,8 @@ int main(int argc, char **argv)
char *old_passphrase = NULL, *new_passphrase = NULL;
bool load_encrypted;
const char *random_device = NULL;
+ char *certfile = NULL;
+ bool remove_cert = false;
int exit_status = 0;
const PrimeGenerationPolicy *primegen = &primegen_probabilistic;
bool strong_rsa = false;
@@ -286,75 +303,79 @@ int main(int argc, char **argv)
while (*p && *p != '=')
p++; /* find end of option */
if (*p == '=') {
- *p++ = '\0';
- val = 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;
- }
+ 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;
- }
+ 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;
- }
+ 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)
+ 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)
+ 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;
- }
+ if (!val && argc > 1)
+ --argc, val = *++argv;
+ if (!val) {
+ errs = true;
+ fprintf(stderr, "puttygen: option `-%s'"
+ " expects an argument\n", opt);
+ } else {
+ random_device = val;
+ }
} else if (!strcmp(opt, "-dump")) {
outtype = TEXT;
+ } else if (!strcmp(opt, "-cert-info") ||
+ !strcmp(opt, "-certinfo") ||
+ !strcmp(opt, "-cert_info")) {
+ outtype = CERTINFO;
} else if (!strcmp(opt, "-primes")) {
if (!val && argc > 1)
--argc, val = *++argv;
@@ -383,6 +404,18 @@ int main(int argc, char **argv)
}
} else if (!strcmp(opt, "-strong-rsa")) {
strong_rsa = true;
+ } else if (!strcmp(opt, "-certificate")) {
+ if (!val && argc > 1)
+ --argc, val = *++argv;
+ if (!val) {
+ errs = true;
+ fprintf(stderr, "puttygen: option `-%s'"
+ " expects an argument\n", opt);
+ } else {
+ certfile = val;
+ }
+ } else if (!strcmp(opt, "-remove-certificate")) {
+ remove_cert = true;
} else if (!strcmp(opt, "-reencrypt")) {
reencrypt = true;
} else if (!strcmp(opt, "-ppk-param") ||
@@ -461,9 +494,9 @@ int main(int argc, char **argv)
}
}
} else {
- errs = true;
- fprintf(stderr,
- "puttygen: no such option `-%s'\n", opt);
+ errs = true;
+ fprintf(stderr,
+ "puttygen: no such option `-%s'\n", opt);
}
p = NULL;
break;
@@ -569,6 +602,8 @@ int main(int argc, char **argv)
outtype = SSHCOM, sshver = 2;
else if (!strcmp(p, "text"))
outtype = TEXT;
+ else if (!strcmp(p, "cert-info"))
+ outtype = CERTINFO;
else {
fprintf(stderr,
"puttygen: unknown output type `%s'\n", p);
@@ -583,6 +618,10 @@ int main(int argc, char **argv)
fptype = SSH_FPTYPE_MD5;
else if (!strcmp(p, "sha256"))
fptype = SSH_FPTYPE_SHA256;
+ else if (!strcmp(p, "md5-cert"))
+ fptype = SSH_FPTYPE_MD5_CERT;
+ else if (!strcmp(p, "sha256-cert"))
+ fptype = SSH_FPTYPE_SHA256_CERT;
else {
fprintf(stderr, "puttygen: unknown fingerprint "
"type `%s'\n", p);
@@ -790,7 +829,8 @@ int main(int argc, char **argv)
outfiletmp = dupcat(outfile, ".tmp");
}
- if (!change_passphrase && !comment && !reencrypt) {
+ if (!change_passphrase && !comment && !reencrypt && !certfile &&
+ !remove_cert) {
fprintf(stderr, "puttygen: this command would perform no useful"
" action\n");
RETURN(1);
@@ -840,6 +880,26 @@ int main(int argc, char **argv)
RETURN(1);
}
+ /*
+ * Check consistency properties relating to certificates.
+ */
+ if (certfile && !(sshver == 2 && intype_has_private &&
+ outtype_has_private && infile)) {
+ fprintf(stderr, "puttygen: certificates can only be added to "
+ "existing SSH-2 private key files\n");
+ RETURN(1);
+ }
+ if (remove_cert && !(sshver == 2 && infile)) {
+ fprintf(stderr, "puttygen: certificates can only be removed from "
+ "existing SSH-2 key files\n");
+ RETURN(1);
+ }
+ if (certfile && remove_cert) {
+ fprintf(stderr, "puttygen: cannot both add and remove a "
+ "certificate\n");
+ RETURN(1);
+ }
+
/* ------------------------------------------------------------------
* Now we're ready to actually do some stuff.
*/
@@ -878,10 +938,10 @@ int main(int argc, char **argv)
PrimeGenerationContext *pgc = primegen_new_context(primegen);
if (keytype == DSA) {
- struct dss_key *dsskey = snew(struct dss_key);
- dsa_generate(dsskey, bits, pgc, &cmdgen_progress);
+ struct dsa_key *dsakey = snew(struct dsa_key);
+ dsa_generate(dsakey, bits, pgc, &cmdgen_progress);
ssh2key = snew(ssh2_userkey);
- ssh2key->key = &dsskey->sshk;
+ ssh2key->key = &dsakey->sshk;
ssh1key = NULL;
} else if (keytype == ECDSA) {
struct ecdsa_key *ek = snew(struct ecdsa_key);
@@ -941,16 +1001,16 @@ int main(int argc, char **argv)
if (encrypted && load_encrypted) {
if (!old_passphrase) {
prompts_t *p = new_prompts();
- int ret;
+ SeatPromptResult spr;
p->to_server = false;
p->from_server = false;
p->name = dupstr("SSH key passphrase");
add_prompt(p, dupstr("Enter passphrase to load key: "), false);
- ret = console_get_userpass_input(p);
- assert(ret >= 0);
- if (!ret) {
+ spr = console_get_userpass_input(p);
+ assert(spr.kind != SPRK_INCOMPLETE);
+ if (spr_is_abort(spr)) {
free_prompts(p);
- perror("puttygen: unable to read passphrase");
+ spr_error(spr);
RETURN(1);
} else {
old_passphrase = prompt_get_result(p->prompts[0]);
@@ -1073,6 +1133,124 @@ int main(int argc, char **argv)
}
/*
+ * Swap out the public key for a different one, if asked to.
+ */
+ if (certfile) {
+ Filename *certfilename = filename_from_str(certfile);
+ LoadedFile *certfile_lf;
+ const char *error = NULL;
+
+ if (!strcmp(certfile, "-"))
+ certfile_lf = lf_load_keyfile_fp(stdin, &error);
+ else
+ certfile_lf = lf_load_keyfile(certfilename, &error);
+
+ filename_free(certfilename);
+
+ if (!certfile_lf) {
+ fprintf(stderr, "puttygen: unable to load certificate file `%s': "
+ "%s\n", certfile, error);
+ RETURN(1);
+ }
+
+ char *algname = NULL;
+ char *comment = NULL;
+ strbuf *pub = strbuf_new();
+ if (!ppk_loadpub_s(BinarySource_UPCAST(certfile_lf), &algname,
+ BinarySink_UPCAST(pub), &comment, &error)) {
+ fprintf(stderr, "puttygen: unable to load certificate file `%s': "
+ "%s\n", certfile, error);
+ strbuf_free(pub);
+ sfree(algname);
+ sfree(comment);
+ lf_free(certfile_lf);
+ RETURN(1);
+ }
+
+ lf_free(certfile_lf);
+ sfree(comment);
+
+ const ssh_keyalg *alg = find_pubkey_alg(algname);
+ if (!alg) {
+ fprintf(stderr, "puttygen: certificate file `%s' has unsupported "
+ "algorithm name `%s'\n", certfile, algname);
+ strbuf_free(pub);
+ sfree(algname);
+ RETURN(1);
+ }
+
+ sfree(algname);
+
+ /* Check the two public keys match apart from certificates */
+ strbuf *old_basepub = strbuf_new();
+ ssh_key_public_blob(ssh_key_base_key(ssh2key->key),
+ BinarySink_UPCAST(old_basepub));
+
+ ssh_key *new_pubkey = ssh_key_new_pub(alg, ptrlen_from_strbuf(pub));
+ strbuf *new_basepub = strbuf_new();
+ ssh_key_public_blob(ssh_key_base_key(new_pubkey),
+ BinarySink_UPCAST(new_basepub));
+ ssh_key_free(new_pubkey);
+
+ bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(old_basepub),
+ ptrlen_from_strbuf(new_basepub));
+ strbuf_free(old_basepub);
+ strbuf_free(new_basepub);
+
+ if (!match) {
+ fprintf(stderr, "puttygen: certificate in `%s' does not match "
+ "public key in `%s'\n", certfile, infile);
+ strbuf_free(pub);
+ RETURN(1);
+ }
+
+ strbuf *priv = strbuf_new_nm();
+ ssh_key_private_blob(ssh2key->key, BinarySink_UPCAST(priv));
+ ssh_key *newkey = ssh_key_new_priv(
+ alg, ptrlen_from_strbuf(pub), ptrlen_from_strbuf(priv));
+ strbuf_free(pub);
+ strbuf_free(priv);
+
+ if (!newkey) {
+ fprintf(stderr, "puttygen: unable to combine certificate in `%s' "
+ "with private key\n", certfile);
+ RETURN(1);
+ }
+
+ ssh_key_free(ssh2key->key);
+ ssh2key->key = newkey;
+ } else if (remove_cert) {
+ /*
+ * Removing a certificate can be meaningfully done to a pure
+ * public key blob, as well as a full key pair.
+ */
+ if (ssh2key) {
+ ssh_key *newkey = ssh_key_clone(ssh_key_base_key(ssh2key->key));
+ ssh_key_free(ssh2key->key);
+ ssh2key->key = newkey;
+ } else if (ssh2blob) {
+ ptrlen algname = pubkey_blob_to_alg_name(
+ ptrlen_from_strbuf(ssh2blob));
+
+ const ssh_keyalg *alg = find_pubkey_alg_len(algname);
+
+ if (!alg) {
+ fprintf(stderr, "puttygen: input file `%s' has unsupported "
+ "algorithm name `%.*s'\n", infile,
+ PTRLEN_PRINTF(algname));
+ RETURN(1);
+ }
+
+ ssh_key *tmpkey = ssh_key_new_pub(
+ alg, ptrlen_from_strbuf(ssh2blob));
+ strbuf_clear(ssh2blob);
+ ssh_key_public_blob(ssh_key_base_key(tmpkey),
+ BinarySink_UPCAST(ssh2blob));
+ ssh_key_free(tmpkey);
+ }
+ }
+
+ /*
* Unless we're changing the passphrase, the old one (if any) is a
* reasonable default.
*/
@@ -1090,18 +1268,18 @@ int main(int argc, char **argv)
if (!new_passphrase && (change_passphrase ||
(keytype != NOKEYGEN && outtype != TEXT))) {
prompts_t *p = new_prompts();
- int ret;
+ SeatPromptResult spr;
p->to_server = false;
p->from_server = false;
p->name = dupstr("New SSH key passphrase");
add_prompt(p, dupstr("Enter passphrase to save key: "), false);
add_prompt(p, dupstr("Re-enter passphrase to verify: "), false);
- ret = console_get_userpass_input(p);
- assert(ret >= 0);
- if (!ret) {
+ spr = console_get_userpass_input(p);
+ assert(spr.kind != SPRK_INCOMPLETE);
+ if (spr_is_abort(spr)) {
free_prompts(p);
- perror("puttygen: unable to read new passphrase");
+ spr_error(spr);
RETURN(1);
} else {
if (strcmp(prompt_get_result_ref(p->prompts[0]),
@@ -1164,29 +1342,29 @@ int main(int argc, char **argv)
FILE *fp;
if (outfile) {
- fp = f_open(outfilename, "w", false);
- if (!fp) {
- fprintf(stderr, "unable to open output file\n");
- exit(1);
- }
+ fp = f_open(outfilename, "w", false);
+ if (!fp) {
+ fprintf(stderr, "unable to open output file\n");
+ exit(1);
+ }
} else {
- fp = stdout;
+ fp = stdout;
}
if (sshver == 1) {
- ssh1_write_pubkey(fp, ssh1key);
+ 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 (!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)
@@ -1200,26 +1378,26 @@ int main(int argc, char **argv)
char *fingerprint;
if (sshver == 1) {
- assert(ssh1key);
- fingerprint = rsa_ssh1_fingerprint(ssh1key);
+ 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 (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);
- }
+ fp = f_open(outfilename, "w", false);
+ if (!fp) {
+ fprintf(stderr, "unable to open output file\n");
+ exit(1);
+ }
} else {
- fp = stdout;
+ fp = stdout;
}
fprintf(fp, "%s\n", fingerprint);
if (outfile)
@@ -1271,9 +1449,8 @@ int main(int argc, char **argv)
} else {
assert(ssh2blob);
- BinarySource src[1];
- BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(ssh2blob));
- ptrlen algname = get_string(src);
+ ptrlen algname = pubkey_blob_to_alg_name(
+ ptrlen_from_strbuf(ssh2blob));
const ssh_keyalg *alg = find_pubkey_alg_len(algname);
if (!alg) {
fprintf(stderr, "puttygen: cannot extract key components "
@@ -1304,16 +1481,55 @@ int main(int argc, char **argv)
}
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);
+ key_component *comp = &kc->components[i];
+ fprintf(fp, "%s=", comp->name);
+ switch (comp->type) {
+ case KCT_MPINT: {
+ char *hex = mp_get_hex(comp->mp);
+ fprintf(fp, "0x%s\n", hex);
smemclr(hex, strlen(hex));
sfree(hex);
- } else {
- fprintf(fp, "%s=\"", kc->components[i].name);
- write_c_string_literal(fp, ptrlen_from_asciz(
- kc->components[i].text));
+ break;
+ }
+ case KCT_TEXT:
+ fputs("\"", fp);
+ write_c_string_literal(fp, ptrlen_from_strbuf(comp->str));
fputs("\"\n", fp);
+ break;
+ case KCT_BINARY: {
+ /*
+ * Display format for binary key components is to show
+ * them as base64, with a wrapper so that the actual
+ * printed string is along the lines of
+ * 'b64("aGVsbG8sIHdvcmxkCg==")'.
+ *
+ * That's a compromise between not being too verbose
+ * for a human reader, and still being reasonably
+ * friendly to people pasting the output of this
+ * 'puttygen --dump' option into Python code (which
+ * the format is designed to permit in general).
+ *
+ * Python users pasting a dump containing one of these
+ * will have to define a function 'b64' in advance
+ * which takes a string, which you can do most easily
+ * using this import statement, as seen in
+ * cryptsuite.py:
+ *
+ * from base64 import b64decode as b64
+ */
+ fputs("b64(\"", fp);
+ char b64[4];
+ for (size_t j = 0; j < comp->str->len; j += 3) {
+ size_t len = comp->str->len - j;
+ if (len > 3) len = 3;
+ base64_encode_atom(comp->str->u + j, len, b64);
+ fwrite(b64, 1, 4, fp);
+ }
+ fputs("\")\n", fp);
+ break;
+ }
+ default:
+ unreachable("bad key component type");
}
}
@@ -1322,6 +1538,83 @@ int main(int argc, char **argv)
key_components_free(kc);
break;
}
+
+ case CERTINFO: {
+ if (sshver == 1) {
+ fprintf(stderr, "puttygen: SSH-1 keys cannot contain "
+ "certificates\n");
+ RETURN(1);
+ }
+
+ const ssh_keyalg *alg;
+ ssh_key *sk;
+ bool sk_allocated = false;
+
+ if (ssh2key) {
+ sk = ssh2key->key;
+ alg = ssh_key_alg(sk);
+ } else {
+ assert(ssh2blob);
+ ptrlen algname = pubkey_blob_to_alg_name(
+ ptrlen_from_strbuf(ssh2blob));
+ alg = find_pubkey_alg_len(algname);
+ if (!alg) {
+ fprintf(stderr, "puttygen: cannot extract certificate info "
+ "from public key of unknown type '%.*s'\n",
+ PTRLEN_PRINTF(algname));
+ RETURN(1);
+ }
+ sk = ssh_key_new_pub(alg, ptrlen_from_strbuf(ssh2blob));
+ if (!sk) {
+ fprintf(stderr, "puttygen: unable to decode public key\n");
+ RETURN(1);
+ }
+ sk_allocated = true;
+ }
+
+ if (!alg->is_certificate) {
+ fprintf(stderr, "puttygen: key is not a certificate\n");
+ } else {
+ SeatDialogText *text = ssh_key_cert_info(sk);
+
+ FILE *fp;
+ if (outfile) {
+ fp = f_open(outfilename, "w", false);
+ if (!fp) {
+ fprintf(stderr, "unable to open output file\n");
+ exit(1);
+ }
+ } else {
+ fp = stdout;
+ }
+
+ for (SeatDialogTextItem *item = text->items,
+ *end = item+text->nitems; item < end; item++) {
+ switch (item->type) {
+ case SDT_MORE_INFO_KEY:
+ fprintf(fp, "%s", item->text);
+ break;
+ case SDT_MORE_INFO_VALUE_SHORT:
+ fprintf(fp, ": %s\n", item->text);
+ break;
+ case SDT_MORE_INFO_VALUE_BLOB:
+ fprintf(fp, ":\n%s\n", item->text);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (outfile)
+ fclose(fp);
+
+ seat_dialog_text_free(text);
+ }
+
+ if (sk_allocated)
+ ssh_key_free(sk);
+ break;
+ }
}
out:
diff --git a/cmdline.c b/cmdline.c
index 57b6a9f5..b5a36eb0 100644
--- a/cmdline.c
+++ b/cmdline.c
@@ -78,35 +78,56 @@ void cmdline_cleanup(void)
/*
* 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.
+ * SPR(K)_INCOMPLETE 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)
+SeatPromptResult cmdline_get_passwd_input(
+ prompts_t *p, cmdline_get_passwd_input_state *state, bool restartable)
{
- 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.
+ * that comes in a prompt-set on its own. Also, we don't use a
+ * command-line password for any kind of prompt which is destined
+ * for local use rather than to be sent to the server: the idea is
+ * to pre-fill _passwords_, not private-key passphrases (for which
+ * there are better alternatives available).
*/
- if (!cmdline_password || p->n_prompts != 1 || p->prompts[0]->echo) {
- return -1;
+ if (p->n_prompts != 1 || p->prompts[0]->echo || !p->to_server) {
+ return SPR_INCOMPLETE;
}
/*
* If we've tried once, return utter failure (no more passwords left
* to try).
*/
- if (tried_once)
- return 0;
+ if (state->tried)
+ return SPR_SW_ABORT("Configured password was not accepted");
+
+ /*
+ * If we never had a password available in the first place, we
+ * can't do anything in any case. (But we delay this test until
+ * after trying once, so that even if we free cmdline_password
+ * below, we'll still remember that we _used_ to have one.)
+ */
+ if (!cmdline_password)
+ return SPR_INCOMPLETE;
prompt_set_result(p->prompts[0], cmdline_password);
- smemclr(cmdline_password, strlen(cmdline_password));
- sfree(cmdline_password);
- cmdline_password = NULL;
- tried_once = true;
- return 1;
+ state->tried = true;
+
+ if (!restartable) {
+ /*
+ * If there's no possibility of needing to do this again after
+ * a 'Restart Session' event, then wipe our copy of the
+ * password out of memory.
+ */
+ smemclr(cmdline_password, strlen(cmdline_password));
+ sfree(cmdline_password);
+ cmdline_password = NULL;
+ }
+
+ return SPR_OK;
}
static bool cmdline_check_unavailable(int flag, const char *p)
@@ -584,6 +605,11 @@ int cmdline_process_param(const char *p, char *value,
cmdline_error("the -pw option can only be used with the "
"SSH protocol");
else {
+ if (cmdline_password) {
+ smemclr(cmdline_password, strlen(cmdline_password));
+ sfree(cmdline_password);
+ }
+
cmdline_password = dupstr(value);
/* Assuming that `value' is directly from argv, make a good faith
* attempt to trample it, to stop it showing up in `ps' output
@@ -592,6 +618,37 @@ int cmdline_process_param(const char *p, char *value,
}
}
+ if (!strcmp(p, "-pwfile")) {
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(1);
+ /* We delay evaluating this until after the protocol is decided,
+ * so that we can warn if it's of no use with the selected protocol */
+ if (conf_get_int(conf, CONF_protocol) != PROT_SSH)
+ cmdline_error("the -pwfile option can only be used with the "
+ "SSH protocol");
+ else {
+ Filename *fn = filename_from_str(value);
+ FILE *fp = f_open(fn, "r", false);
+ if (!fp) {
+ cmdline_error("unable to open password file '%s'", value);
+ } else {
+ if (cmdline_password) {
+ smemclr(cmdline_password, strlen(cmdline_password));
+ sfree(cmdline_password);
+ }
+
+ cmdline_password = chomp(fgetline(fp));
+ if (!cmdline_password) {
+ cmdline_error("unable to read a password from file '%s'",
+ value);
+ }
+ fclose(fp);
+ }
+ filename_free(fn);
+ }
+ }
+
if (!strcmp(p, "-agent") || !strcmp(p, "-pagent") ||
!strcmp(p, "-pageant")) {
RETURN(1);
@@ -702,13 +759,25 @@ int cmdline_process_param(const char *p, char *value,
filename_free(fn);
}
+ if (!strcmp(p, "-cert")) {
+ Filename *fn;
+ RETURN(2);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
+ SAVEABLE(0);
+ fn = filename_from_str(value);
+ conf_set_filename(conf, CONF_detached_cert, fn);
+ filename_free(fn);
+ }
+
if (!strcmp(p, "-4") || !strcmp(p, "-ipv4")) {
RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
SAVEABLE(1);
conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV4);
}
if (!strcmp(p, "-6") || !strcmp(p, "-ipv6")) {
RETURN(1);
+ UNAVAILABLE_IN(TOOLTYPE_NONNETWORK);
SAVEABLE(1);
conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV6);
}
diff --git a/conf.c b/conf.c
deleted file mode 100644
index ecd26cd0..00000000
--- a/conf.c
+++ /dev/null
@@ -1,593 +0,0 @@
-/*
- * conf.c: implementation of the internal storage format used for
- * the configuration of a PuTTY session.
- */
-
-#include <stdio.h>
-#include <stddef.h>
-#include <assert.h>
-
-#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;
-
- 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;
-
- 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);
-
- 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);
-
- 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/config.c b/config.c
index b8348530..8cdeee24 100644
--- a/config.c
+++ b/config.c
@@ -9,13 +9,14 @@
#include "putty.h"
#include "dialog.h"
#include "storage.h"
+#include "tree234.h"
#define PRINTER_DISABLED_STRING "None (printing disabled)"
#define HOST_BOX_TITLE "Host Name (or IP address)"
#define PORT_BOX_TITLE "Port"
-void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg,
+void conf_radiobutton_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
int button;
@@ -28,7 +29,7 @@ void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg,
* is the one selected.
*/
if (event == EVENT_REFRESH) {
- int val = conf_get_int(conf, ctrl->radio.context.i);
+ int val = conf_get_int(conf, ctrl->context.i);
for (button = 0; button < ctrl->radio.nbuttons; button++)
if (val == ctrl->radio.buttondata[button].i)
break;
@@ -38,12 +39,12 @@ void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg,
} 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,
+ conf_set_int(conf, ctrl->context.i,
ctrl->radio.buttondata[button].i);
}
}
-void conf_radiobutton_bool_handler(union control *ctrl, dlgparam *dlg,
+void conf_radiobutton_bool_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
int button;
@@ -55,7 +56,7 @@ void conf_radiobutton_bool_handler(union control *ctrl, dlgparam *dlg,
* config option.
*/
if (event == EVENT_REFRESH) {
- int val = conf_get_bool(conf, ctrl->radio.context.i);
+ int val = conf_get_bool(conf, ctrl->context.i);
for (button = 0; button < ctrl->radio.nbuttons; button++)
if (val == ctrl->radio.buttondata[button].i)
break;
@@ -65,13 +66,13 @@ void conf_radiobutton_bool_handler(union control *ctrl, dlgparam *dlg,
} 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,
+ conf_set_bool(conf, ctrl->context.i,
ctrl->radio.buttondata[button].i);
}
}
#define CHECKBOX_INVERT (1<<30)
-void conf_checkbox_handler(union control *ctrl, dlgparam *dlg,
+void conf_checkbox_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
int key;
@@ -82,7 +83,7 @@ void conf_checkbox_handler(union control *ctrl, dlgparam *dlg,
* For a standard checkbox, the context parameter gives the
* primary key (CONF_foo), optionally ORed with CHECKBOX_INVERT.
*/
- key = ctrl->checkbox.context.i;
+ key = ctrl->context.i;
if (key & CHECKBOX_INVERT) {
key &= ~CHECKBOX_INVERT;
invert = true;
@@ -103,27 +104,23 @@ void conf_checkbox_handler(union control *ctrl, dlgparam *dlg,
}
}
-void conf_editbox_handler(union control *ctrl, dlgparam *dlg,
+const struct conf_editbox_handler_type conf_editbox_str = {.type = EDIT_STR};
+const struct conf_editbox_handler_type conf_editbox_int = {.type = EDIT_INT};
+
+void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
/*
- * The standard edit-box handler expects the main `context'
- * field to contain the primary key. The secondary `context2'
- * field 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.)
+ * The standard edit-box handler expects the main `context' field
+ * to contain the primary key. The secondary `context2' field is a
+ * pointer to the struct conf_editbox_handler_type defined in
+ * putty.h.
*/
- int key = ctrl->editbox.context.i;
- int length = ctrl->editbox.context2.i;
+ int key = ctrl->context.i;
+ const struct conf_editbox_handler_type *type = ctrl->context2.cp;
Conf *conf = (Conf *)data;
- if (length > 0) {
+ if (type->type == EDIT_STR) {
if (event == EVENT_REFRESH) {
char *field = conf_get_str(conf, key);
dlg_editbox_set(ctrl, dlg, field);
@@ -132,30 +129,30 @@ void conf_editbox_handler(union control *ctrl, dlgparam *dlg,
conf_set_str(conf, key, field);
sfree(field);
}
- } else if (length < 0) {
+ } else {
if (event == EVENT_REFRESH) {
char str[80];
int value = conf_get_int(conf, key);
- if (length == -1)
+ if (type->type == EDIT_INT)
sprintf(str, "%d", value);
else
- sprintf(str, "%g", (double)value / (double)(-length));
+ sprintf(str, "%g", (double)value / type->denominator);
dlg_editbox_set(ctrl, dlg, str);
} else if (event == EVENT_VALCHANGE) {
char *str = dlg_editbox_get(ctrl, dlg);
- if (length == -1)
+ if (type->type == EDIT_INT)
conf_set_int(conf, key, atoi(str));
else
- conf_set_int(conf, key, (int)((-length) * atof(str)));
+ conf_set_int(conf, key, (int)(type->denominator * atof(str)));
sfree(str);
}
}
}
-void conf_filesel_handler(union control *ctrl, dlgparam *dlg,
+void conf_filesel_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
- int key = ctrl->fileselect.context.i;
+ int key = ctrl->context.i;
Conf *conf = (Conf *)data;
if (event == EVENT_REFRESH) {
@@ -168,10 +165,10 @@ void conf_filesel_handler(union control *ctrl, dlgparam *dlg,
}
}
-void conf_fontsel_handler(union control *ctrl, dlgparam *dlg,
+void conf_fontsel_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
- int key = ctrl->fontselect.context.i;
+ int key = ctrl->context.i;
Conf *conf = (Conf *)data;
if (event == EVENT_REFRESH) {
@@ -184,7 +181,7 @@ void conf_fontsel_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void config_host_handler(union control *ctrl, dlgparam *dlg,
+static void config_host_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
@@ -216,7 +213,7 @@ static void config_host_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void config_port_handler(union control *ctrl, dlgparam *dlg,
+static void config_port_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
@@ -257,7 +254,7 @@ static void config_port_handler(union control *ctrl, dlgparam *dlg,
}
struct hostport {
- union control *host, *port, *protradio, *protlist;
+ dlgcontrol *host, *port, *protradio, *protlist;
bool mid_refresh;
};
@@ -268,12 +265,12 @@ struct hostport {
* and refreshes both host and port boxes when switching to/from the
* serial backend.
*/
-static void config_protocols_handler(union control *ctrl, dlgparam *dlg,
+static void config_protocols_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
int curproto = conf_get_int(conf, CONF_protocol);
- struct hostport *hp = (struct hostport *)ctrl->generic.context.p;
+ struct hostport *hp = (struct hostport *)ctrl->context.p;
if (event == EVENT_REFRESH) {
/*
@@ -312,7 +309,7 @@ static void config_protocols_handler(union control *ctrl, dlgparam *dlg,
for (size_t i = n_ui_backends;
i < PROTOCOL_LIMIT && backends[i]; i++) {
dlg_listbox_addwithid(ctrl, dlg,
- backends[i]->displayname,
+ backends[i]->displayname_tc,
backends[i]->protocol);
if (backends[i]->protocol == curproto)
curentry = i - n_ui_backends;
@@ -421,7 +418,7 @@ static void config_protocols_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void loggingbuttons_handler(union control *ctrl, dlgparam *dlg,
+static void loggingbuttons_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
int button;
@@ -450,7 +447,7 @@ static void loggingbuttons_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void numeric_keypad_handler(union control *ctrl, dlgparam *dlg,
+static void numeric_keypad_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
int button;
@@ -481,7 +478,7 @@ static void numeric_keypad_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void cipherlist_handler(union control *ctrl, dlgparam *dlg,
+static void cipherlist_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
@@ -490,6 +487,7 @@ static void cipherlist_handler(union control *ctrl, dlgparam *dlg,
static const struct { const char *s; int c; } ciphers[] = {
{ "ChaCha20 (SSH-2 only)", CIPHER_CHACHA20 },
+ { "AES-GCM (SSH-2 only)", CIPHER_AESGCM },
{ "3DES", CIPHER_3DES },
{ "Blowfish", CIPHER_BLOWFISH },
{ "DES", CIPHER_DES },
@@ -527,7 +525,7 @@ static void cipherlist_handler(union control *ctrl, dlgparam *dlg,
}
#ifndef NO_GSSAPI
-static void gsslist_handler(union control *ctrl, dlgparam *dlg,
+static void gsslist_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
@@ -554,7 +552,7 @@ static void gsslist_handler(union control *ctrl, dlgparam *dlg,
}
#endif
-static void kexlist_handler(union control *ctrl, dlgparam *dlg,
+static void kexlist_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
@@ -562,12 +560,17 @@ static void kexlist_handler(union control *ctrl, dlgparam *dlg,
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 }
+ { "Diffie-Hellman group 1 (1024-bit)", KEX_DHGROUP1 },
+ { "Diffie-Hellman group 14 (2048-bit)", KEX_DHGROUP14 },
+ { "Diffie-Hellman group 15 (3072-bit)", KEX_DHGROUP15 },
+ { "Diffie-Hellman group 16 (4096-bit)", KEX_DHGROUP16 },
+ { "Diffie-Hellman group 17 (6144-bit)", KEX_DHGROUP17 },
+ { "Diffie-Hellman group 18 (8192-bit)", KEX_DHGROUP18 },
+ { "Diffie-Hellman group exchange", KEX_DHGEX },
+ { "RSA-based key exchange", KEX_RSA },
+ { "ECDH key exchange", KEX_ECDH },
+ { "NTRU Prime / Curve25519 hybrid kex", KEX_NTRU_HYBRID },
+ { "-- warn below here --", KEX_WARN }
};
/* Set up the "kex preference" box. */
@@ -598,8 +601,8 @@ static void kexlist_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void hklist_handler(union control *ctrl, dlgparam *dlg,
- void *data, int event)
+static void hklist_handler(dlgcontrol *ctrl, dlgparam *dlg,
+ void *data, int event)
{
Conf *conf = (Conf *)data;
if (event == EVENT_REFRESH) {
@@ -642,7 +645,7 @@ static void hklist_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void printerbox_handler(union control *ctrl, dlgparam *dlg,
+static void printerbox_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
@@ -678,7 +681,7 @@ static void printerbox_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void codepage_handler(union control *ctrl, dlgparam *dlg,
+static void codepage_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
@@ -702,7 +705,7 @@ static void codepage_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void sshbug_handler(union control *ctrl, dlgparam *dlg,
+static void sshbug_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
@@ -713,7 +716,7 @@ static void sshbug_handler(union control *ctrl, dlgparam *dlg,
* 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);
+ int oldconf = conf_get_int(conf, ctrl->context.i);
dlg_update_start(ctrl, dlg);
dlg_listbox_clear(ctrl, dlg);
dlg_listbox_addwithid(ctrl, dlg, "Auto", AUTO);
@@ -731,13 +734,44 @@ static void sshbug_handler(union control *ctrl, dlgparam *dlg,
i = AUTO;
else
i = dlg_listbox_getid(ctrl, dlg, i);
- conf_set_int(conf, ctrl->listbox.context.i, i);
+ conf_set_int(conf, ctrl->context.i, i);
+ }
+}
+
+static void sshbug_handler_manual_only(dlgcontrol *ctrl, dlgparam *dlg,
+ void *data, int event)
+{
+ /*
+ * This is just like sshbug_handler, except that there's no 'Auto'
+ * option. Used for bug workaround flags that can't be
+ * autodetected, and have to be manually enabled if they're to be
+ * used at all.
+ */
+ Conf *conf = (Conf *)data;
+ if (event == EVENT_REFRESH) {
+ int oldconf = conf_get_int(conf, ctrl->context.i);
+ dlg_update_start(ctrl, dlg);
+ dlg_listbox_clear(ctrl, dlg);
+ dlg_listbox_addwithid(ctrl, dlg, "Off", FORCE_OFF);
+ dlg_listbox_addwithid(ctrl, dlg, "On", FORCE_ON);
+ switch (oldconf) {
+ case FORCE_OFF: dlg_listbox_select(ctrl, dlg, 0); break;
+ case FORCE_ON: dlg_listbox_select(ctrl, dlg, 1); break;
+ }
+ dlg_update_done(ctrl, dlg);
+ } else if (event == EVENT_SELCHANGE) {
+ int i = dlg_listbox_index(ctrl, dlg);
+ if (i < 0)
+ i = FORCE_OFF;
+ else
+ i = dlg_listbox_getid(ctrl, dlg, i);
+ conf_set_int(conf, ctrl->context.i, i);
}
}
struct sessionsaver_data {
- union control *editbox, *listbox, *loadbutton, *savebutton, *delbutton;
- union control *okbutton, *cancelbutton;
+ dlgcontrol *editbox, *listbox, *loadbutton, *savebutton, *delbutton;
+ dlgcontrol *okbutton, *cancelbutton;
struct sesslist sesslist;
bool midsession;
char *savedsession; /* the current contents of ssd->editbox */
@@ -779,12 +813,12 @@ static bool load_selected_session(
return true;
}
-static void sessionsaver_handler(union control *ctrl, dlgparam *dlg,
+static void sessionsaver_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
struct sessionsaver_data *ssd =
- (struct sessionsaver_data *)ctrl->generic.context.p;
+ (struct sessionsaver_data *)ctrl->context.p;
if (event == EVENT_REFRESH) {
if (ctrl == ssd->editbox) {
@@ -917,15 +951,15 @@ static void sessionsaver_handler(union control *ctrl, dlgparam *dlg,
}
struct charclass_data {
- union control *listbox, *editbox, *button;
+ dlgcontrol *listbox, *editbox, *button;
};
-static void charclass_handler(union control *ctrl, dlgparam *dlg,
+static void charclass_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
struct charclass_data *ccd =
- (struct charclass_data *)ctrl->generic.context.p;
+ (struct charclass_data *)ctrl->context.p;
if (event == EVENT_REFRESH) {
if (ctrl == ccd->listbox) {
@@ -958,7 +992,7 @@ static void charclass_handler(union control *ctrl, dlgparam *dlg,
}
struct colour_data {
- union control *listbox, *redit, *gedit, *bedit, *button;
+ dlgcontrol *listbox, *redit, *gedit, *bedit, *button;
};
/* Array of the user-visible colour names defined in the list macro in
@@ -969,12 +1003,12 @@ static const char *const colours[] = {
#undef CONF_COLOUR_NAME_DECL
};
-static void colour_handler(union control *ctrl, dlgparam *dlg,
+static void colour_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
struct colour_data *cd =
- (struct colour_data *)ctrl->generic.context.p;
+ (struct colour_data *)ctrl->context.p;
bool update = false, clear = false;
int r, g, b;
@@ -1075,15 +1109,15 @@ static void colour_handler(union control *ctrl, dlgparam *dlg,
}
struct ttymodes_data {
- union control *valradio, *valbox, *setbutton, *listbox;
+ dlgcontrol *valradio, *valbox, *setbutton, *listbox;
};
-static void ttymodes_handler(union control *ctrl, dlgparam *dlg,
+static void ttymodes_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
struct ttymodes_data *td =
- (struct ttymodes_data *)ctrl->generic.context.p;
+ (struct ttymodes_data *)ctrl->context.p;
if (event == EVENT_REFRESH) {
if (ctrl == td->listbox) {
@@ -1160,15 +1194,15 @@ static void ttymodes_handler(union control *ctrl, dlgparam *dlg,
}
struct environ_data {
- union control *varbox, *valbox, *addbutton, *rembutton, *listbox;
+ dlgcontrol *varbox, *valbox, *addbutton, *rembutton, *listbox;
};
-static void environ_handler(union control *ctrl, dlgparam *dlg,
+static void environ_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
struct environ_data *ed =
- (struct environ_data *)ctrl->generic.context.p;
+ (struct environ_data *)ctrl->context.p;
if (event == EVENT_REFRESH) {
if (ctrl == ed->listbox) {
@@ -1232,19 +1266,19 @@ static void environ_handler(union control *ctrl, dlgparam *dlg,
}
struct portfwd_data {
- union control *addbutton, *rembutton, *listbox;
- union control *sourcebox, *destbox, *direction;
+ dlgcontrol *addbutton, *rembutton, *listbox;
+ dlgcontrol *sourcebox, *destbox, *direction;
#ifndef NO_IPV6
- union control *addressfamily;
+ dlgcontrol *addressfamily;
#endif
};
-static void portfwd_handler(union control *ctrl, dlgparam *dlg,
+static void portfwd_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
struct portfwd_data *pfd =
- (struct portfwd_data *)ctrl->generic.context.p;
+ (struct portfwd_data *)ctrl->context.p;
if (event == EVENT_REFRESH) {
if (ctrl == pfd->listbox) {
@@ -1400,15 +1434,15 @@ static void portfwd_handler(union control *ctrl, dlgparam *dlg,
}
struct manual_hostkey_data {
- union control *addbutton, *rembutton, *listbox, *keybox;
+ dlgcontrol *addbutton, *rembutton, *listbox, *keybox;
};
-static void manual_hostkey_handler(union control *ctrl, dlgparam *dlg,
+static void manual_hostkey_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
struct manual_hostkey_data *mh =
- (struct manual_hostkey_data *)ctrl->generic.context.p;
+ (struct manual_hostkey_data *)ctrl->context.p;
if (event == EVENT_REFRESH) {
if (ctrl == mh->listbox) {
@@ -1466,13 +1500,13 @@ static void manual_hostkey_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void clipboard_selector_handler(union control *ctrl, dlgparam *dlg,
+static void clipboard_selector_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
Conf *conf = (Conf *)data;
- int setting = ctrl->generic.context.i;
+ int setting = ctrl->context.i;
#ifdef NAMED_CLIPBOARDS
- int strsetting = ctrl->editbox.context2.i;
+ int strsetting = ctrl->context2.i;
#endif
static const struct {
@@ -1554,7 +1588,7 @@ static void clipboard_selector_handler(union control *ctrl, dlgparam *dlg,
}
static void clipboard_control(struct controlset *s, const char *label,
- char shortcut, int percentage, intorptr helpctx,
+ char shortcut, int percentage, HelpCtx helpctx,
int setting, int strsetting)
{
#ifdef NAMED_CLIPBOARDS
@@ -1567,7 +1601,7 @@ static void clipboard_control(struct controlset *s, const char *label,
#endif
}
-static void serial_parity_handler(union control *ctrl, dlgparam *dlg,
+static void serial_parity_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
static const struct {
@@ -1580,7 +1614,7 @@ static void serial_parity_handler(union control *ctrl, dlgparam *dlg,
{"Mark", SER_PAR_MARK},
{"Space", SER_PAR_SPACE},
};
- int mask = ctrl->listbox.context.i;
+ int mask = ctrl->context.i;
int i, j;
Conf *conf = (Conf *)data;
@@ -1622,7 +1656,7 @@ static void serial_parity_handler(union control *ctrl, dlgparam *dlg,
}
}
-static void serial_flow_handler(union control *ctrl, dlgparam *dlg,
+static void serial_flow_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event)
{
static const struct {
@@ -1634,7 +1668,7 @@ static void serial_flow_handler(union control *ctrl, dlgparam *dlg,
{"RTS/CTS", SER_FLOW_RTSCTS},
{"DSR/DTR", SER_FLOW_DSRDTR},
};
- int mask = ctrl->listbox.context.i;
+ int mask = ctrl->context.i;
int i, j;
Conf *conf = (Conf *)data;
@@ -1675,6 +1709,67 @@ static void serial_flow_handler(union control *ctrl, dlgparam *dlg,
}
}
+void proxy_type_handler(dlgcontrol *ctrl, dlgparam *dlg,
+ void *data, int event)
+{
+ Conf *conf = (Conf *)data;
+ if (event == EVENT_REFRESH) {
+ /*
+ * We must fetch the previously configured value from the Conf
+ * before we start modifying the drop-down list, otherwise the
+ * spurious SELCHANGE we trigger in the process will overwrite
+ * the value we wanted to keep.
+ */
+ int proxy_type = conf_get_int(conf, CONF_proxy_type);
+
+ dlg_update_start(ctrl, dlg);
+ dlg_listbox_clear(ctrl, dlg);
+
+ int index_to_select = 0, current_index = 0;
+
+#define ADD(id, title) do { \
+ dlg_listbox_addwithid(ctrl, dlg, title, id); \
+ if (id == proxy_type) \
+ index_to_select = current_index; \
+ current_index++; \
+ } while (0)
+
+ ADD(PROXY_NONE, "None");
+ ADD(PROXY_SOCKS5, "SOCKS 5");
+ ADD(PROXY_SOCKS4, "SOCKS 4");
+ ADD(PROXY_HTTP, "HTTP CONNECT");
+ if (ssh_proxy_supported) {
+ ADD(PROXY_SSH_TCPIP, "SSH to proxy and use port forwarding");
+ ADD(PROXY_SSH_EXEC, "SSH to proxy and execute a command");
+ ADD(PROXY_SSH_SUBSYSTEM, "SSH to proxy and invoke a subsystem");
+ }
+ if (ctrl->context.i & PROXY_UI_FLAG_LOCAL) {
+ ADD(PROXY_CMD, "Local (run a subprogram to connect)");
+ }
+ ADD(PROXY_TELNET, "'Telnet' (send an ad-hoc command)");
+
+#undef ADD
+
+ dlg_listbox_select(ctrl, dlg, index_to_select);
+
+ dlg_update_done(ctrl, dlg);
+ } else if (event == EVENT_SELCHANGE) {
+ int i = dlg_listbox_index(ctrl, dlg);
+ if (i < 0)
+ i = AUTO;
+ else
+ i = dlg_listbox_getid(ctrl, dlg, i);
+ conf_set_int(conf, CONF_proxy_type, i);
+ }
+}
+
+static void host_ca_button_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ if (event == EVENT_ACTION)
+ show_ca_config_box(dp);
+}
+
void setup_config_box(struct controlbox *b, bool midsession,
int protocol, int protcfginfo)
{
@@ -1687,7 +1782,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
struct environ_data *ed;
struct portfwd_data *pfd;
struct manual_hostkey_data *mh;
- union control *c;
+ dlgcontrol *c;
bool resize_forbidden = false;
char *str;
@@ -1710,11 +1805,11 @@ void setup_config_box(struct controlbox *b, bool midsession,
HELPCTX(no_help),
sessionsaver_handler, P(ssd));
ssd->okbutton->button.isdefault = true;
- ssd->okbutton->generic.column = 3;
+ ssd->okbutton->column = 3;
ssd->cancelbutton = ctrl_pushbutton(s, "Cancel", 'c', HELPCTX(no_help),
sessionsaver_handler, P(ssd));
ssd->cancelbutton->button.iscancel = true;
- ssd->cancelbutton->generic.column = 4;
+ ssd->cancelbutton->column = 4;
/* We carefully don't close the 5-column part, so that platform-
* specific add-ons can put extra buttons alongside Open and Cancel. */
@@ -1736,12 +1831,12 @@ void setup_config_box(struct controlbox *b, bool midsession,
c = ctrl_editbox(s, HOST_BOX_TITLE, 'n', 100,
HELPCTX(session_hostname),
config_host_handler, I(0), I(0));
- c->generic.column = 0;
+ c->column = 0;
hp->host = c;
c = ctrl_editbox(s, PORT_BOX_TITLE, 'p', 100,
HELPCTX(session_hostname),
config_port_handler, I(0), I(0));
- c->generic.column = 1;
+ c->column = 1;
hp->port = c;
ctrl_columns(s, 1, 100);
@@ -1749,8 +1844,8 @@ void setup_config_box(struct controlbox *b, bool midsession,
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;
+ config_protocols_handler, P(hp));
+ c->column = 0;
hp->protradio = c;
c->radio.buttons = sresize(c->radio.buttons, PROTOCOL_LIMIT, char *);
c->radio.shortcuts = sresize(c->radio.shortcuts, PROTOCOL_LIMIT, char);
@@ -1762,7 +1857,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
for (size_t i = 0; i < n_ui_backends; i++) {
assert(backends[i]);
c->radio.buttons[c->radio.nbuttons] =
- dupstr(backends[i]->displayname);
+ dupstr(backends[i]->displayname_tc);
c->radio.shortcuts[c->radio.nbuttons] =
(backends[i]->protocol == PROT_SSH ? 's' :
backends[i]->protocol == PROT_SERIAL ? 'r' :
@@ -1785,10 +1880,10 @@ void setup_config_box(struct controlbox *b, bool midsession,
config_protocols_handler, P(hp));
hp->protlist = c;
/* droplist is populated in config_protocols_handler */
- c->generic.column = 1;
+ c->column = 1;
/* Vertically centre the two protocol controls w.r.t. each other */
- hp->protlist->generic.align_next_to = hp->protradio;
+ hp->protlist->align_next_to = hp->protradio;
ctrl_columns(s, 1, 100);
}
@@ -1804,7 +1899,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
ssd->editbox = ctrl_editbox(s, "Saved Sessions", 'e', 100,
HELPCTX(session_saved),
sessionsaver_handler, P(ssd), P(NULL));
- ssd->editbox->generic.column = 0;
+ ssd->editbox->column = 0;
/* Reset columns so that the buttons are alongside the list, rather
* than alongside that edit box. */
ctrl_columns(s, 1, 100);
@@ -1812,13 +1907,13 @@ void setup_config_box(struct controlbox *b, bool midsession,
ssd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
HELPCTX(session_saved),
sessionsaver_handler, P(ssd));
- ssd->listbox->generic.column = 0;
+ ssd->listbox->column = 0;
ssd->listbox->listbox.height = 7;
if (!midsession) {
ssd->loadbutton = ctrl_pushbutton(s, "Load", 'l',
HELPCTX(session_saved),
sessionsaver_handler, P(ssd));
- ssd->loadbutton->generic.column = 1;
+ ssd->loadbutton->column = 1;
} else {
/* We can't offer the Load button mid-session, as it would allow the
* user to load and subsequently save settings they can't see. (And
@@ -1830,12 +1925,12 @@ void setup_config_box(struct controlbox *b, bool midsession,
ssd->savebutton = ctrl_pushbutton(s, "Save", 'v',
HELPCTX(session_saved),
sessionsaver_handler, P(ssd));
- ssd->savebutton->generic.column = 1;
+ ssd->savebutton->column = 1;
if (!midsession) {
ssd->delbutton = ctrl_pushbutton(s, "Delete", 'd',
HELPCTX(session_saved),
sessionsaver_handler, P(ssd));
- ssd->delbutton->generic.column = 1;
+ ssd->delbutton->column = 1;
} else {
/* Disable the Delete button mid-session too, for UI consistency. */
ssd->delbutton = NULL;
@@ -1849,7 +1944,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
I(CONF_close_on_exit),
"Always", I(FORCE_ON),
"Never", I(FORCE_OFF),
- "Only on clean exit", I(AUTO), NULL);
+ "Only on clean exit", I(AUTO));
/*
* The Session/Logging panel.
@@ -1879,8 +1974,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
"Printable output", 'p', I(LGTYP_ASCII),
"All session output", 'l', I(LGTYP_DEBUG),
sshlogname, 's', I(LGTYP_PACKETS),
- sshrawlogname, 'r', I(LGTYP_SSHRAW),
- NULL);
+ sshrawlogname, 'r', I(LGTYP_SSHRAW));
}
ctrl_filesel(s, "Log file name:", 'f',
NULL, true, "Select session log file name",
@@ -1894,13 +1988,13 @@ void setup_config_box(struct controlbox *b, bool midsession,
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);
+ "Ask the user every time", I(LGXF_ASK));
ctrl_checkbox(s, "Flush log file frequently", 'u',
- HELPCTX(logging_flush),
- conf_checkbox_handler, I(CONF_logflush));
+ HELPCTX(logging_flush),
+ conf_checkbox_handler, I(CONF_logflush));
ctrl_checkbox(s, "Include header", 'i',
- HELPCTX(logging_header),
- conf_checkbox_handler, I(CONF_logheader));
+ HELPCTX(logging_header),
+ conf_checkbox_handler, I(CONF_logheader));
if ((midsession && protocol == PROT_SSH) ||
(!midsession && backend_vt_from_proto(PROT_SSH))) {
@@ -1940,7 +2034,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
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));
+ conf_editbox_handler, I(CONF_answerback), ED_STR);
s = ctrl_getset(b, "Terminal", "ldisc", "Line discipline options");
ctrl_radiobuttons(s, "Local echo:", 'l', 3,
@@ -1948,13 +2042,13 @@ void setup_config_box(struct controlbox *b, bool midsession,
conf_radiobutton_handler,I(CONF_localecho),
"Auto", I(AUTO),
"Force on", I(FORCE_ON),
- "Force off", I(FORCE_OFF), NULL);
+ "Force off", I(FORCE_OFF));
ctrl_radiobuttons(s, "Local line editing:", 't', 3,
HELPCTX(terminal_localedit),
conf_radiobutton_handler,I(CONF_localedit),
"Auto", I(AUTO),
"Force on", I(FORCE_ON),
- "Force off", I(FORCE_OFF), NULL);
+ "Force off", I(FORCE_OFF));
s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing");
ctrl_combobox(s, "Printer to send ANSI printer output to:", 'p', 100,
@@ -1973,18 +2067,29 @@ void setup_config_box(struct controlbox *b, bool midsession,
HELPCTX(keyboard_backspace),
conf_radiobutton_bool_handler,
I(CONF_bksp_is_delete),
- "Control-H", I(0), "Control-? (127)", I(1), NULL);
+ "Control-H", I(0), "Control-? (127)", I(1));
ctrl_radiobuttons(s, "The Home and End keys", 'e', 2,
HELPCTX(keyboard_homeend),
conf_radiobutton_bool_handler,
I(CONF_rxvt_homeend),
- "Standard", I(false), "rxvt", I(true), NULL);
- ctrl_radiobuttons(s, "The Function keys and keypad", 'f', 3,
+ "Standard", I(false), "rxvt", I(true));
+ ctrl_radiobuttons(s, "The Function keys and keypad", 'f', 4,
HELPCTX(keyboard_funkeys),
conf_radiobutton_handler,
I(CONF_funky_type),
- "ESC[n~", I(0), "Linux", I(1), "Xterm R6", I(2),
- "VT400", I(3), "VT100+", I(4), "SCO", I(5), NULL);
+ "ESC[n~", I(FUNKY_TILDE),
+ "Linux", I(FUNKY_LINUX),
+ "Xterm R6", I(FUNKY_XTERM),
+ "VT400", I(FUNKY_VT400),
+ "VT100+", I(FUNKY_VT100P),
+ "SCO", I(FUNKY_SCO),
+ "Xterm 216+", I(FUNKY_XTERM_216));
+ ctrl_radiobuttons(s, "Shift/Ctrl/Alt with the arrow keys", 'w', 2,
+ HELPCTX(keyboard_sharrow),
+ conf_radiobutton_handler,
+ I(CONF_sharrow_type),
+ "Ctrl toggles app mode", I(SHARROW_APPLICATION),
+ "xterm-style bitmap", I(SHARROW_BITMAP));
s = ctrl_getset(b, "Terminal/Keyboard", "appkeypad",
"Application keypad settings:");
@@ -1992,12 +2097,11 @@ void setup_config_box(struct controlbox *b, bool midsession,
HELPCTX(keyboard_appcursor),
conf_radiobutton_bool_handler,
I(CONF_app_cursor),
- "Normal", I(0), "Application", I(1), NULL);
+ "Normal", I(0), "Application", I(1));
ctrl_radiobuttons(s, "Initial state of numeric keypad:", 'n', 3,
HELPCTX(keyboard_appkeypad),
numeric_keypad_handler, P(NULL),
- "Normal", I(0), "Application", I(1), "NetHack", I(2),
- NULL);
+ "Normal", I(0), "Application", I(1), "NetHack", I(2));
/*
* The Terminal/Bell panel.
@@ -2011,7 +2115,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
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);
+ "Visual bell (flash window)", I(BELL_VISUAL));
s = ctrl_getset(b, "Terminal/Bell", "overload",
"Control the bell overload behaviour");
@@ -2020,17 +2124,21 @@ void setup_config_box(struct controlbox *b, bool midsession,
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));
+ conf_editbox_handler, I(CONF_bellovl_n), ED_INT);
+
+ static const struct conf_editbox_handler_type conf_editbox_tickspersec = {
+ .type = EDIT_FIXEDPOINT, .denominator = TICKSPERSEC};
+
ctrl_editbox(s, "... in this many seconds", 't', 20,
HELPCTX(bell_overload),
conf_editbox_handler, I(CONF_bellovl_t),
- I(-TICKSPERSEC));
+ CP(&conf_editbox_tickspersec));
ctrl_text(s, "The bell is re-enabled after a few seconds of silence.",
HELPCTX(bell_overload));
ctrl_editbox(s, "Seconds of silence required", 's', 20,
HELPCTX(bell_overload),
conf_editbox_handler, I(CONF_bellovl_s),
- I(-TICKSPERSEC));
+ CP(&conf_editbox_tickspersec));
/*
* The Terminal/Features panel.
@@ -2065,7 +2173,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
I(CONF_remote_qtitle_action),
"None", I(TITLE_NONE),
"Empty string", I(TITLE_EMPTY),
- "Window title", I(TITLE_REAL), NULL);
+ "Window title", I(TITLE_REAL));
ctrl_checkbox(s, "Disable remote-controlled clearing of scrollback", 'e',
HELPCTX(features_clearscroll),
conf_checkbox_handler,
@@ -2099,12 +2207,12 @@ void setup_config_box(struct controlbox *b, bool midsession,
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;
+ conf_editbox_handler, I(CONF_width), ED_INT);
+ c->column = 0;
c = ctrl_editbox(s, "Rows", 'r', 100,
HELPCTX(window_size),
- conf_editbox_handler, I(CONF_height),I(-1));
- c->generic.column = 1;
+ conf_editbox_handler, I(CONF_height),ED_INT);
+ c->column = 1;
ctrl_columns(s, 1, 100);
}
@@ -2112,7 +2220,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
"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));
+ conf_editbox_handler, I(CONF_savelines), ED_INT);
ctrl_checkbox(s, "Display scrollbar", 'd',
HELPCTX(window_scrollback),
conf_checkbox_handler, I(CONF_scrollbar));
@@ -2142,7 +2250,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
I(CONF_cursor_type),
"Block", 'l', I(0),
"Underline", 'u', I(1),
- "Vertical line", 'v', I(2), NULL);
+ "Vertical line", 'v', I(2));
ctrl_checkbox(s, "Cursor blinks", 'b',
HELPCTX(appearance_cursor),
conf_checkbox_handler, I(CONF_blink_cur));
@@ -2164,7 +2272,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
ctrl_editbox(s, "Gap between text and window edge:", 'e', 20,
HELPCTX(appearance_border),
conf_editbox_handler,
- I(CONF_window_border), I(-1));
+ I(CONF_window_border), ED_INT);
/*
* The Window/Behaviour panel.
@@ -2177,7 +2285,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
"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));
+ conf_editbox_handler, I(CONF_wintitle), ED_STR);
ctrl_checkbox(s, "Separate window and icon titles", 'i',
HELPCTX(appearance_title),
conf_checkbox_handler,
@@ -2208,13 +2316,12 @@ void setup_config_box(struct controlbox *b, bool midsession,
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_radiobuttons(
+ s, "Handling of line drawing characters:", NO_SHORTCUT,1,
+ HELPCTX(translation_linedraw),
+ conf_radiobutton_handler, I(CONF_vtmode),
+ "Use Unicode line drawing code points",'u',I(VT_UNICODE),
+ "Poor man's line drawing (+, - and |)",'p',I(VT_POORMAN));
ctrl_checkbox(s, "Copy and paste line drawing characters as lqqqk",'d',
HELPCTX(selection_linedraw),
conf_checkbox_handler, I(CONF_rawcnp));
@@ -2239,7 +2346,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
conf_radiobutton_bool_handler,
I(CONF_rect_select),
"Normal", 'n', I(false),
- "Rectangular block", 'r', I(true), NULL);
+ "Rectangular block", 'r', I(true));
s = ctrl_getset(b, "Window/Selection", "clipboards",
"Assign copy/paste actions to clipboards");
@@ -2287,11 +2394,11 @@ void setup_config_box(struct controlbox *b, bool midsession,
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->editbox->column = 0;
ccd->button = ctrl_pushbutton(s, "Set", 's',
HELPCTX(copy_charclasses),
charclass_handler, P(ccd));
- ccd->button->generic.column = 1;
+ ccd->button->column = 1;
ctrl_columns(s, 1, 100);
/*
@@ -2315,8 +2422,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
conf_radiobutton_handler, I(CONF_bold_style),
"The font", I(1),
"The colour", I(2),
- "Both", I(3),
- NULL);
+ "Both", I(3));
str = dupprintf("Adjust the precise colours %s displays", appname);
s = ctrl_getset(b, "Window/Colours", "adjust", str);
@@ -2328,22 +2434,22 @@ void setup_config_box(struct controlbox *b, bool midsession,
cd = (struct colour_data *)ctrl_alloc(b, sizeof(struct colour_data));
cd->listbox = ctrl_listbox(s, "Select a colour to adjust:", 'u',
HELPCTX(colours_config), colour_handler, P(cd));
- cd->listbox->generic.column = 0;
+ cd->listbox->column = 0;
cd->listbox->listbox.height = 7;
c = ctrl_text(s, "RGB value:", HELPCTX(colours_config));
- c->generic.column = 1;
+ c->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->redit->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->gedit->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->bedit->column = 1;
cd->button = ctrl_pushbutton(s, "Modify", 'm', HELPCTX(colours_config),
colour_handler, P(cd));
- cd->button->generic.column = 1;
+ cd->button->column = 1;
ctrl_columns(s, 1, 100);
/*
@@ -2358,8 +2464,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
"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));
+ conf_editbox_handler, I(CONF_ping_interval), ED_INT);
if (!midsession) {
s = ctrl_getset(b, "Connection", "tcp",
@@ -2374,15 +2479,14 @@ void setup_config_box(struct controlbox *b, bool midsession,
I(CONF_tcp_keepalives));
#ifndef NO_IPV6
s = ctrl_getset(b, "Connection", "ipversion",
- "Internet protocol version");
+ "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);
+ HELPCTX(connection_ipversion),
+ conf_radiobutton_handler,
+ I(CONF_addressfamily),
+ "Auto", 'u', I(ADDRTYPE_UNSPEC),
+ "IPv4", '4', I(ADDRTYPE_IPV4),
+ "IPv6", '6', I(ADDRTYPE_IPV6));
#endif
{
@@ -2393,7 +2497,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
"Logical name of remote host");
ctrl_editbox(s, label, 'm', 100,
HELPCTX(connection_loghost),
- conf_editbox_handler, I(CONF_loghost), I(1));
+ conf_editbox_handler, I(CONF_loghost), ED_STR);
}
}
@@ -2408,7 +2512,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
"Login details");
ctrl_editbox(s, "Auto-login username", 'u', 50,
HELPCTX(connection_username),
- conf_editbox_handler, I(CONF_username), I(1));
+ conf_editbox_handler, I(CONF_username), ED_STR);
{
/* We assume the local username is sufficiently stable
* to include on the dialog box. */
@@ -2421,8 +2525,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
conf_radiobutton_bool_handler,
I(CONF_username_from_env),
"Prompt", I(false),
- userlabel, I(true),
- NULL);
+ userlabel, I(true));
sfree(userlabel);
}
@@ -2430,10 +2533,10 @@ void setup_config_box(struct controlbox *b, bool midsession,
"Terminal details");
ctrl_editbox(s, "Terminal-type string", 't', 50,
HELPCTX(connection_termtype),
- conf_editbox_handler, I(CONF_termtype), I(1));
+ conf_editbox_handler, I(CONF_termtype), ED_STR);
ctrl_editbox(s, "Terminal speeds", 's', 50,
HELPCTX(connection_termspeed),
- conf_editbox_handler, I(CONF_termspeed), I(1));
+ conf_editbox_handler, I(CONF_termspeed), ED_STR);
s = ctrl_getset(b, "Connection/Data", "env",
"Environment variables");
@@ -2443,19 +2546,19 @@ void setup_config_box(struct controlbox *b, bool midsession,
ed->varbox = ctrl_editbox(s, "Variable", 'v', 60,
HELPCTX(telnet_environ),
environ_handler, P(ed), P(NULL));
- ed->varbox->generic.column = 0;
+ ed->varbox->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->valbox->column = 0;
ed->addbutton = ctrl_pushbutton(s, "Add", 'd',
HELPCTX(telnet_environ),
environ_handler, P(ed));
- ed->addbutton->generic.column = 1;
+ ed->addbutton->column = 1;
ed->rembutton = ctrl_pushbutton(s, "Remove", 'r',
HELPCTX(telnet_environ),
environ_handler, P(ed));
- ed->rembutton->generic.column = 1;
+ ed->rembutton->column = 1;
ctrl_columns(s, 1, 100);
ed->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
HELPCTX(telnet_environ),
@@ -2477,33 +2580,25 @@ void setup_config_box(struct controlbox *b, bool midsession,
"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);
+ c = ctrl_droplist(s, "Proxy type:", 't', 70,
+ HELPCTX(proxy_type), proxy_type_handler, I(0));
ctrl_columns(s, 2, 80, 20);
c = ctrl_editbox(s, "Proxy hostname", 'y', 100,
HELPCTX(proxy_main),
conf_editbox_handler,
- I(CONF_proxy_host), I(1));
- c->generic.column = 0;
+ I(CONF_proxy_host), ED_STR);
+ c->column = 0;
c = ctrl_editbox(s, "Port", 'p', 100,
HELPCTX(proxy_main),
conf_editbox_handler,
I(CONF_proxy_port),
- I(-1));
- c->generic.column = 1;
+ ED_INT);
+ c->column = 1;
ctrl_columns(s, 1, 100);
ctrl_editbox(s, "Exclude Hosts/IPs", 'e', 100,
HELPCTX(proxy_exclude),
conf_editbox_handler,
- I(CONF_proxy_exclude_list), I(1));
+ I(CONF_proxy_exclude_list), ED_STR);
ctrl_checkbox(s, "Consider proxying local host connections", 'x',
HELPCTX(proxy_exclude),
conf_checkbox_handler,
@@ -2514,20 +2609,20 @@ void setup_config_box(struct controlbox *b, bool midsession,
I(CONF_proxy_dns),
"No", I(FORCE_OFF),
"Auto", I(AUTO),
- "Yes", I(FORCE_ON), NULL);
+ "Yes", I(FORCE_ON));
ctrl_editbox(s, "Username", 'u', 60,
HELPCTX(proxy_auth),
conf_editbox_handler,
- I(CONF_proxy_username), I(1));
+ I(CONF_proxy_username), ED_STR);
c = ctrl_editbox(s, "Password", 'w', 60,
HELPCTX(proxy_auth),
conf_editbox_handler,
- I(CONF_proxy_password), I(1));
+ I(CONF_proxy_password), ED_STR);
c->editbox.password = true;
- ctrl_editbox(s, "Telnet command", 'm', 100,
+ ctrl_editbox(s, "Command to send to proxy (for some types)", 'm', 100,
HELPCTX(proxy_command),
conf_editbox_handler,
- I(CONF_proxy_telnet_command), I(1));
+ I(CONF_proxy_telnet_command), ED_STR);
ctrl_radiobuttons(s, "Print proxy diagnostics "
"in the terminal window", 'r', 5,
@@ -2536,7 +2631,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
I(CONF_proxy_log_to_term),
"No", I(FORCE_OFF),
"Yes", I(FORCE_ON),
- "Only until session starts", I(AUTO), NULL);
+ "Only until session starts", I(AUTO));
}
/*
@@ -2577,7 +2672,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
"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));
+ conf_editbox_handler, I(CONF_remote_cmd), ED_STR);
s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options");
ctrl_checkbox(s, "Don't start a shell or command at all", 'n',
@@ -2623,7 +2718,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
conf_radiobutton_handler,
I(CONF_sshprot),
"2", '2', I(3),
- "1 (INSECURE)", '1', I(0), NULL);
+ "1 (INSECURE)", '1', I(0));
}
/*
@@ -2656,19 +2751,19 @@ void setup_config_box(struct controlbox *b, bool midsession,
HELPCTX(ssh_kex_repeat),
conf_editbox_handler,
I(CONF_ssh_rekey_time),
- I(-1));
+ ED_INT);
#ifndef NO_GSSAPI
ctrl_editbox(s, "Minutes between GSS checks (0 for never)", NO_SHORTCUT, 20,
HELPCTX(ssh_kex_repeat),
conf_editbox_handler,
I(CONF_gssapirekey),
- I(-1));
+ ED_INT);
#endif
ctrl_editbox(s, "Max data before rekey (0 for no limit)", 'x', 20,
HELPCTX(ssh_kex_repeat),
conf_editbox_handler,
I(CONF_ssh_rekey_data),
- I(16));
+ ED_STR);
ctrl_text(s, "(Use 1M for 1 megabyte, 1G for 1 gigabyte etc)",
HELPCTX(ssh_kex_repeat));
}
@@ -2704,7 +2799,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
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;
+ c->column = 0;
/* You want to select from the list, _then_ hit Remove. So
* tab order should be that way round. */
mh = (struct manual_hostkey_data *)
@@ -2712,8 +2807,8 @@ void setup_config_box(struct controlbox *b, bool midsession,
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->rembutton->column = 1;
+ mh->rembutton->delay_taborder = true;
mh->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
HELPCTX(ssh_kex_manual_hostkeys),
manual_hostkey_handler, P(mh));
@@ -2727,14 +2822,25 @@ void setup_config_box(struct controlbox *b, bool midsession,
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->keybox->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;
+ mh->addbutton->column = 1;
ctrl_columns(s, 1, 100);
}
+ /*
+ * But there's no reason not to forbid access to the host CA
+ * configuration box, which is common across sessions in any
+ * case.
+ */
+ s = ctrl_getset(b, "Connection/SSH/Host keys", "ca",
+ "Configure trusted certification authorities");
+ c = ctrl_pushbutton(s, "Configure host CAs", NO_SHORTCUT,
+ HELPCTX(ssh_kex_cert),
+ host_ca_button_handler, I(0));
+
if (!midsession || !(protcfginfo == 1 || protcfginfo == -1)) {
/*
* The Connection/SSH/Cipher panel.
@@ -2792,8 +2898,8 @@ void setup_config_box(struct controlbox *b, bool midsession,
conf_checkbox_handler,
I(CONF_try_ki_auth));
- s = ctrl_getset(b, "Connection/SSH/Auth", "params",
- "Authentication parameters");
+ s = ctrl_getset(b, "Connection/SSH/Auth", "aux",
+ "Other authentication-related options");
ctrl_checkbox(s, "Allow agent forwarding", 'f',
HELPCTX(ssh_auth_agentfwd),
conf_checkbox_handler, I(CONF_agentfwd));
@@ -2801,11 +2907,26 @@ void setup_config_box(struct controlbox *b, bool midsession,
HELPCTX(ssh_auth_changeuser),
conf_checkbox_handler,
I(CONF_change_username));
+
+ ctrl_settitle(b, "Connection/SSH/Auth/Credentials",
+ "Credentials to authenticate with");
+
+ s = ctrl_getset(b, "Connection/SSH/Auth/Credentials", "publickey",
+ "Public-key authentication");
ctrl_filesel(s, "Private key file for authentication:", 'k',
FILTER_KEY_FILES, false, "Select private key file",
HELPCTX(ssh_auth_privkey),
conf_filesel_handler, I(CONF_keyfile));
-
+ ctrl_filesel(s, "Certificate to use with the private key:", 'e',
+ NULL, false, "Select certificate file",
+ HELPCTX(ssh_auth_cert),
+ conf_filesel_handler, I(CONF_detached_cert));
+
+ s = ctrl_getset(b, "Connection/SSH/Auth/Credentials", "plugin",
+ "Plugin to provide authentication responses");
+ ctrl_editbox(s, "Plugin command to run", NO_SHORTCUT, 100,
+ HELPCTX(ssh_auth_plugin),
+ conf_editbox_handler, I(CONF_auth_plugin), ED_STR);
#ifndef NO_GSSAPI
/*
* Connection/SSH/Auth/GSSAPI, which sadly won't fit on
@@ -2894,12 +3015,12 @@ void setup_config_box(struct controlbox *b, bool midsession,
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;
+ c->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;
+ td->setbutton->column = 1;
+ td->setbutton->delay_taborder = true;
ctrl_columns(s, 1, 100); /* column break */
/* Bit of a hack to get the value radio buttons and
* edit-box on the same row. */
@@ -2909,14 +3030,13 @@ void setup_config_box(struct controlbox *b, bool midsession,
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;
+ "This:", NO_SHORTCUT, P(NULL));
+ td->valradio->column = 0;
td->valbox = ctrl_editbox(s, NULL, NO_SHORTCUT, 100,
HELPCTX(ssh_ttymodes),
ttymodes_handler, P(td), P(NULL));
- td->valbox->generic.column = 1;
- td->valbox->generic.align_next_to = td->valradio;
+ td->valbox->column = 1;
+ td->valbox->align_next_to = td->valradio;
ctrl_tabdelay(s, td->setbutton);
}
@@ -2933,13 +3053,13 @@ void setup_config_box(struct controlbox *b, bool midsession,
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));
+ conf_editbox_handler, I(CONF_x11_display), ED_STR);
ctrl_radiobuttons(s, "Remote X11 authentication protocol", 'u', 2,
HELPCTX(ssh_tunnels_x11auth),
conf_radiobutton_handler,
I(CONF_x11_auth),
"MIT-Magic-Cookie-1", I(X11_MIT),
- "XDM-Authorization-1", I(X11_XDM), NULL);
+ "XDM-Authorization-1", I(X11_XDM));
}
/*
@@ -2961,15 +3081,15 @@ void setup_config_box(struct controlbox *b, bool midsession,
ctrl_columns(s, 3, 55, 20, 25);
c = ctrl_text(s, "Forwarded ports:", HELPCTX(ssh_tunnels_portfwd));
- c->generic.column = COLUMN_FIELD(0,2);
+ c->column = COLUMN_FIELD(0,2);
/* You want to select from the list, _then_ hit Remove. So tab order
* should be that way round. */
pfd = (struct portfwd_data *)ctrl_alloc(b,sizeof(struct portfwd_data));
pfd->rembutton = ctrl_pushbutton(s, "Remove", 'r',
HELPCTX(ssh_tunnels_portfwd),
portfwd_handler, P(pfd));
- pfd->rembutton->generic.column = 2;
- pfd->rembutton->generic.tabdelay = true;
+ pfd->rembutton->column = 2;
+ pfd->rembutton->delay_taborder = true;
pfd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
HELPCTX(ssh_tunnels_portfwd),
portfwd_handler, P(pfd));
@@ -2985,12 +3105,12 @@ void setup_config_box(struct controlbox *b, bool midsession,
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->addbutton->column = 2;
+ pfd->addbutton->delay_taborder = true;
pfd->sourcebox = ctrl_editbox(s, "Source port", 's', 40,
HELPCTX(ssh_tunnels_portfwd),
portfwd_handler, P(pfd), P(NULL));
- pfd->sourcebox->generic.column = 0;
+ pfd->sourcebox->column = 0;
pfd->destbox = ctrl_editbox(s, "Destination", 'i', 67,
HELPCTX(ssh_tunnels_portfwd),
portfwd_handler, P(pfd), P(NULL));
@@ -2999,8 +3119,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
portfwd_handler, P(pfd),
"Local", 'l', P(NULL),
"Remote", 'm', P(NULL),
- "Dynamic", 'y', P(NULL),
- NULL);
+ "Dynamic", 'y', P(NULL));
#ifndef NO_IPV6
pfd->addressfamily =
ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3,
@@ -3008,8 +3127,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
portfwd_handler, P(pfd),
"Auto", 'u', I(ADDRTYPE_UNSPEC),
"IPv4", '4', I(ADDRTYPE_IPV4),
- "IPv6", '6', I(ADDRTYPE_IPV6),
- NULL);
+ "IPv6", '6', I(ADDRTYPE_IPV6));
#endif
ctrl_tabdelay(s, pfd->addbutton);
ctrl_columns(s, 1, 100);
@@ -3039,6 +3157,17 @@ void setup_config_box(struct controlbox *b, bool midsession,
HELPCTX(ssh_bugs_maxpkt2),
sshbug_handler, I(CONF_sshbug_maxpkt2));
+ s = ctrl_getset(b, "Connection/SSH/Bugs", "manual",
+ "Manually enabled workarounds");
+ ctrl_droplist(s, "Discards data sent before its greeting", 'd', 20,
+ HELPCTX(ssh_bugs_dropstart),
+ sshbug_handler_manual_only,
+ I(CONF_sshbug_dropstart));
+ ctrl_droplist(s, "Chokes on PuTTY's full KEXINIT", 'p', 20,
+ HELPCTX(ssh_bugs_filter_kexinit),
+ sshbug_handler_manual_only,
+ I(CONF_sshbug_filter_kexinit));
+
ctrl_settitle(b, "Connection/SSH/More bugs",
"Further workarounds for SSH server bugs");
@@ -3090,22 +3219,26 @@ void setup_config_box(struct controlbox *b, bool midsession,
"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));
+ conf_editbox_handler, I(CONF_serline), ED_STR);
}
s = ctrl_getset(b, "Connection/Serial", "sercfg", "Configure the serial line");
ctrl_editbox(s, "Speed (baud)", 's', 40,
HELPCTX(serial_speed),
- conf_editbox_handler, I(CONF_serspeed), I(-1));
+ conf_editbox_handler, I(CONF_serspeed), ED_INT);
ctrl_editbox(s, "Data bits", 'b', 40,
HELPCTX(serial_databits),
- conf_editbox_handler, I(CONF_serdatabits), I(-1));
+ conf_editbox_handler, I(CONF_serdatabits), ED_INT);
/*
* Stop bits come in units of one half.
*/
+ static const struct conf_editbox_handler_type conf_editbox_stopbits = {
+ .type = EDIT_FIXEDPOINT, .denominator = 2};
+
ctrl_editbox(s, "Stop bits", 't', 40,
HELPCTX(serial_stopbits),
- conf_editbox_handler, I(CONF_serstopbits), I(-2));
+ conf_editbox_handler, I(CONF_serstopbits),
+ CP(&conf_editbox_stopbits));
ctrl_droplist(s, "Parity", 'p', 40,
HELPCTX(serial_parity), serial_parity_handler,
I(ser_vt->serial_parity_mask));
@@ -3131,12 +3264,12 @@ void setup_config_box(struct controlbox *b, bool midsession,
conf_radiobutton_bool_handler,
I(CONF_rfc_environ),
"BSD (commonplace)", 'b', I(false),
- "RFC 1408 (unusual)", 'f', I(true), NULL);
+ "RFC 1408 (unusual)", 'f', I(true));
ctrl_radiobuttons(s, "Telnet negotiation mode:", 't', 2,
HELPCTX(telnet_passive),
conf_radiobutton_bool_handler,
I(CONF_passive_telnet),
- "Passive", I(true), "Active", I(false), NULL);
+ "Passive", I(true), "Active", I(false));
}
ctrl_checkbox(s, "Keyboard sends Telnet special commands", 'k',
HELPCTX(telnet_specialkeys),
@@ -3159,7 +3292,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
"Data to send to the server");
ctrl_editbox(s, "Local username:", 'l', 50,
HELPCTX(rlogin_localuser),
- conf_editbox_handler, I(CONF_localusername), I(1));
+ conf_editbox_handler, I(CONF_localusername), ED_STR);
}
@@ -3175,7 +3308,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
ctrl_editbox(s, "Location string", 'l', 70,
HELPCTX(supdup_location),
conf_editbox_handler, I(CONF_supdup_location),
- I(1));
+ ED_STR);
ctrl_radiobuttons(s, "Extended ASCII Character set:", 'e', 4,
HELPCTX(supdup_ascii),
@@ -3183,7 +3316,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
I(CONF_supdup_ascii_set),
"None", I(SUPDUP_CHARSET_ASCII),
"ITS", I(SUPDUP_CHARSET_ITS),
- "WAITS", I(SUPDUP_CHARSET_WAITS), NULL);
+ "WAITS", I(SUPDUP_CHARSET_WAITS));
ctrl_checkbox(s, "**MORE** processing", 'm',
HELPCTX(supdup_more),
diff --git a/configure.ac b/configure.ac
deleted file mode 100644
index 51ea3691..00000000
--- a/configure.ac
+++ /dev/null
@@ -1,264 +0,0 @@
-# To compile this into a configure script, you need:
-# * Autoconf 2.59c or newer
-# * Gtk (for $prefix/share/aclocal/gtk.m4)
-# * Automake (for aclocal)
-# If you've got them, running "autoreconf" should work.
-
-# Version number is substituted by Buildscr for releases, snapshots
-# and custom builds out of svn; X.XX shows up in ad-hoc developer
-# builds, which shouldn't matter
-AC_INIT(putty, X.XX)
-AC_CONFIG_FILES([Makefile])
-AC_CONFIG_HEADERS([uxconfig.h:uxconfig.in])
-AM_INIT_AUTOMAKE([-Wall foreign])
-
-AC_PROG_INSTALL
-AC_PROG_RANLIB
-ifdef([AM_PROG_AR],[AM_PROG_AR])
-AM_PROG_CC_C_O
-
-AC_PROG_CC_C99
-
-# Mild abuse of the '--enable' option format to allow manual
-# specification of setuid or setgid setup in pterm.
-setidtype=none
-AC_ARG_ENABLE([setuid],
- [AS_HELP_STRING([--enable-setuid=USER],
- [make pterm setuid to a given user])],
- [case "$enableval" in
- no) setidtype=none;;
- *) setidtype=setuid; setidval="$enableval";;
- esac])
-AC_ARG_ENABLE([setgid],
- [AS_HELP_STRING([--enable-setgid=GROUP],
- [make pterm setgid to a given group])],
- [case "$enableval" in
- no) setidtype=none;;
- *) setidtype=setgid; setidval="$enableval";;
- esac])
-AM_CONDITIONAL(HAVE_SETID_CMD, [test "$setidtype" != "none"])
-AS_IF([test "x$setidtype" = "xsetuid"],
- [SETID_CMD="chown $setidval"; SETID_MODE="4755"])
-AS_IF([test "x$setidtype" = "xsetgid"],
- [SETID_CMD="chgrp $setidval"; SETID_MODE="2755"])
-AC_SUBST(SETID_CMD)
-AC_SUBST(SETID_MODE)
-
-AC_ARG_ENABLE([git-commit],
- [AS_HELP_STRING([--disable-git-commit],
- [disable embedding current git HEAD in binaries])],
- [],
- [if test -d "$srcdir/.git"; then
- enable_git_commit=yes; else enable_git_commit=no; fi])
-
-if test "x$enable_git_commit" = "xyes" -a ! -d "$srcdir/.git"; then
- AC_ERROR([Cannot --enable-git-commit when source tree is not a git checkout])
-fi
-AM_CONDITIONAL(AUTO_GIT_COMMIT, [test "x$enable_git_commit" = "xyes"])
-
-AC_ARG_WITH([gssapi],
- [AS_HELP_STRING([--without-gssapi],
- [disable GSSAPI support])],
- [],
- [with_gssapi=yes])
-
-AC_ARG_WITH([quartz],
- [AS_HELP_STRING([--with-quartz],
- [build for the MacOS Quartz GTK back end])],
- [AC_DEFINE([OSX_GTK], [1], [Define if building with GTK for MacOS.])
- with_quartz=yes],
- [with_quartz=no])
-
-AM_CONDITIONAL([HAVE_QUARTZ],[test "x$with_quartz" = "xyes"])
-
-WITH_GSSAPI=
-AS_IF([test "x$with_gssapi" != xno],
- [AC_DEFINE([WITH_GSSAPI], [1], [Define if building with GSSAPI support.])])
-
-AC_ARG_WITH([gtk],
- [AS_HELP_STRING([--with-gtk=VER],
- [specify GTK version to use (`1', `2' or `3')])
-AS_HELP_STRING([--without-gtk],
- [do not use GTK (build command-line tools only)])],
- [gtk_version_desired="$withval"],
- [gtk_version_desired="any"])
-
-case "$gtk_version_desired" in
- 1 | 2 | 3 | any | no) ;;
- yes) gtk_version_desired="any" ;;
- *) AC_ERROR([Invalid GTK version specified])
-esac
-
-AC_CHECK_HEADERS([utmpx.h],,,[
-#include <sys/types.h>
-#include <utmp.h>])
-
-# Look for GTK 3, GTK 2 and GTK 1, in descending order of preference.
-# If we can't find any, have the makefile only build the CLI programs.
-
-gtk=none
-
-case "$gtk_version_desired:$gtk" in
- 3:none | any:none)
- ifdef([AM_PATH_GTK_3_0],[AM_PATH_GTK_3_0([3.0.0], [gtk=3], [])],
- [AC_WARNING([generating configure script without GTK 3 autodetection])])
- ;;
-esac
-
-case "$gtk_version_desired:$gtk" in
- 2:none | any:none)
- ifdef([AM_PATH_GTK_2_0],[AM_PATH_GTK_2_0([2.0.0], [gtk=2], [])],
- [AC_WARNING([generating configure script without GTK 2 autodetection])])
- ;;
-esac
-
-case "$gtk_version_desired:$gtk" in
- 1:none | any:none)
- ifdef([AM_PATH_GTK],[AM_PATH_GTK([1.2.0], [gtk=1], [])],[
- # manual check for gtk1
- AC_PATH_PROG(GTK1_CONFIG, gtk-config, absent)
- if test "$GTK1_CONFIG" != "absent"; then
- GTK_CFLAGS="`"$GTK1_CONFIG" --cflags`"
- GTK_LIBS=`"$GTK1_CONFIG" --libs`
- AC_SUBST(GTK_CFLAGS)
- AC_SUBST(GTK_LIBS)
- gtk=1
- fi
- ])
- ;;
-esac
-
-case "$gtk" in
- 1)
- # Add some manual #defines to make the GTK 1 headers work when
- # compiling in C99 mode. Left to themselves, they'll expect the
- # old-style pre-C99 GNU semantics of 'inline' and 'extern inline',
- # with the effect that they'll end up defining out-of-line
- # versions of the inlined functions in more than one translation
- # unit and cause a link failure. Override them to 'static inline',
- # which is safe.
- GTK_CFLAGS="$GTK_CFLAGS -DG_INLINE_FUNC='static inline' -DG_CAN_INLINE=1"
-esac
-
-AM_CONDITIONAL(HAVE_GTK, [test "$gtk" != "none"])
-
-if test "$gtk" = "2" -o "$gtk" = "3"; then
- ac_save_CFLAGS="$CFLAGS"
- ac_save_LIBS="$LIBS"
- CFLAGS="$CFLAGS $GTK_CFLAGS"
- LIBS="$GTK_LIBS $LIBS"
- AC_CHECK_FUNCS([pango_font_family_is_monospace pango_font_map_list_families])
- CFLAGS="$ac_save_CFLAGS"
- LIBS="$ac_save_LIBS"
-fi
-
-AC_SEARCH_LIBS([socket], [xnet])
-
-AS_IF([test "x$with_gssapi" != xno],
- [AC_SEARCH_LIBS(
- [dlopen],[dl],
- [],
- [AC_DEFINE([NO_LIBDL], [1], [Define if we could not find libdl.])
- AC_CHECK_HEADERS([gssapi/gssapi.h])
- AC_SEARCH_LIBS(
- [gss_init_sec_context],[gssapi gssapi_krb5 gss],
- [],
- [AC_DEFINE([NO_GSSAPI_LIB], [1], [Define if we could not find a gssapi library])])])])
-
-AC_CHECK_LIB(X11, XOpenDisplay,
- [GTK_LIBS="-lX11 $GTK_LIBS"
- AC_DEFINE([HAVE_LIBX11],[],[Define if libX11.a is available])])
-
-AC_CHECK_FUNCS([getaddrinfo posix_openpt ptsname setresuid strsignal updwtmpx fstatat dirfd futimes setpwent endpwent getauxval elf_aux_info sysctlbyname])
-AC_CHECK_DECLS([CLOCK_MONOTONIC], [], [], [[#include <time.h>]])
-AC_CHECK_HEADERS([sys/auxv.h asm/hwcap.h sys/sysctl.h sys/types.h glob.h])
-AC_SEARCH_LIBS([clock_gettime], [rt], [AC_DEFINE([HAVE_CLOCK_GETTIME],[],[Define if clock_gettime() is available])])
-
-AC_CACHE_CHECK([for SO_PEERCRED and dependencies], [x_cv_linux_so_peercred], [
- AC_COMPILE_IFELSE([
- AC_LANG_PROGRAM([[
- #define _GNU_SOURCE
- #include <features.h>
- #include <sys/socket.h>
- ]],[[
- struct ucred cr;
- socklen_t crlen = sizeof(cr);
- return getsockopt(0, SOL_SOCKET, SO_PEERCRED, &cr, &crlen) +
- cr.pid + cr.uid + cr.gid;
- ]]
- )],
- AS_VAR_SET(x_cv_linux_so_peercred, yes),
- AS_VAR_SET(x_cv_linux_so_peercred, no)
- )
-])
-AS_IF([test AS_VAR_GET(x_cv_linux_so_peercred) = yes],
- [AC_DEFINE([HAVE_SO_PEERCRED], [1],
- [Define if SO_PEERCRED works in the Linux fashion.])]
-)
-
-if test "x$GCC" = "xyes"; then
- :
- AC_SUBST(WARNINGOPTS, ['-Wall -Wpointer-arith -Wvla'])
-else
- :
- AC_SUBST(WARNINGOPTS, [])
-fi
-
-AC_SEARCH_LIBS([pow], [m])
-
-AC_OUTPUT
-
-if test "$gtk_version_desired" = "no"; then cat <<EOF
-
-'configure' was instructed not to build using GTK. Therefore, PuTTY
-itself and the other GUI utilities will not be built by the generated
-Makefile: only the command-line tools such as puttygen, plink and
-psftp will be built.
-
-EOF
-elif test "$gtk" = "none"; then cat <<EOF
-
-'configure' was unable to find any version of the GTK libraries on
-your system. Therefore, PuTTY itself and the other GUI utilities will
-not be built by the generated Makefile: only the command-line tools
-such as puttygen, plink and psftp will be built.
-
-EOF
-fi
-
-AH_BOTTOM([
-/* Convert autoconf definitions to ones that PuTTY wants. */
-
-#ifndef HAVE_GETADDRINFO
-# define NO_IPV6
-#endif
-#ifndef HAVE_SETRESUID
-# define HAVE_NO_SETRESUID
-#endif
-#ifndef HAVE_STRSIGNAL
-# define HAVE_NO_STRSIGNAL
-#endif
-#if !defined(HAVE_UTMPX_H) || !defined(HAVE_UPDWTMPX)
-# define OMIT_UTMP
-#endif
-#ifndef HAVE_PTSNAME
-# define BSD_PTYS
-#endif
-#ifndef HAVE_SYS_SELECT_H
-# define HAVE_NO_SYS_SELECT_H
-#endif
-#ifndef HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE
-# define PANGO_PRE_1POINT4
-#endif
-#ifndef HAVE_PANGO_FONT_MAP_LIST_FAMILIES
-# define PANGO_PRE_1POINT6
-#endif
-#if !defined(WITH_GSSAPI)
-# define NO_GSSAPI
-#endif
-#if !defined(NO_GSSAPI) && defined(NO_LIBDL)
-# if !defined(HAVE_GSSAPI_GSSAPI_H) || defined(NO_GSSAPI_LIB)
-# define NO_GSSAPI
-# endif
-#endif
-])
diff --git a/console.c b/console.c
index 7155b9f0..62c64aff 100644
--- a/console.c
+++ b/console.c
@@ -9,42 +9,6 @@
#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";
@@ -59,6 +23,17 @@ const char weakhk_msg_common_fmt[] =
const char console_continue_prompt[] = "Continue with connection? (y/n) ";
const char console_abandoned_msg[] = "Connection abandoned.\n";
+const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat)
+{
+ static const SeatDialogPromptDescriptions descs = {
+ .hk_accept_action = "enter \"y\"",
+ .hk_connect_once_action = "enter \"n\"",
+ .hk_cancel_action = "press Return",
+ .hk_cancel_action_Participle = "Pressing Return",
+ };
+ return &descs;
+}
+
bool console_batch_mode = false;
/*
diff --git a/console.h b/console.h
index a8b22466..f6222e6a 100644
--- a/console.h
+++ b/console.h
@@ -2,10 +2,13 @@
* Common pieces between the platform console frontend modules.
*/
-extern const char hk_absentmsg_common_fmt[];
+char *hk_absentmsg_common(const char *host, int port,
+ const char *keytype, const char *fingerprint);
extern const char hk_absentmsg_interactive_intro[];
extern const char hk_absentmsg_interactive_prompt[];
-extern const char hk_wrongmsg_common_fmt[];
+
+char *hk_wrongmsg_common(const char *host, int port,
+ const char *keytype, const char *fingerprint);
extern const char hk_wrongmsg_interactive_intro[];
extern const char hk_wrongmsg_interactive_prompt[];
diff --git a/contrib/authplugin-example.py b/contrib/authplugin-example.py
new file mode 100755
index 00000000..395bd2c8
--- /dev/null
+++ b/contrib/authplugin-example.py
@@ -0,0 +1,287 @@
+#!/usr/bin/env python3
+
+# This is a demonstration example of how to write a
+# keyboard-interactive authentication helper plugin using PuTTY's
+# protocol for involving it in SSH connection setup.
+
+import io
+import os
+import struct
+import sys
+
+# Exception class we'll use to get a clean exit on EOF.
+class PluginEOF(Exception): pass
+
+# ----------------------------------------------------------------------
+#
+# Marshalling and unmarshalling routines to write and read the
+# necessary SSH data types to/from a binary file handle (which can
+# include an io.BytesIO if you need to encode/decode in-process).
+#
+# Error handling is a totally ad-hoc mixture of 'assert' and just
+# assuming things will have the right type, or be the right length of
+# tuple, or be valid UTF-8. So it should be _robust_, in the sense
+# that you'll get a Python exception if anything fails. But no
+# sensible error reporting or recovery is implemented.
+#
+# That should be good enough, because PuTTY will log the plugin's
+# standard error in its Event Log, so if the plugin crashes, you'll be
+# able to retrieve the traceback.
+
+def wr_byte(fh, b):
+ assert 0 <= b < 0x100
+ fh.write(bytes([b]))
+
+def wr_boolean(fh, b):
+ wr_byte(fh, 1 if b else 0)
+
+def wr_uint32(fh, u):
+ assert 0 <= u < 0x100000000
+ fh.write(struct.pack(">I", u))
+
+def wr_string(fh, s):
+ wr_uint32(fh, len(s))
+ fh.write(s)
+
+def wr_string_utf8(fh, s):
+ wr_string(fh, s.encode("UTF-8"))
+
+def rd_n(fh, n):
+ data = fh.read(n)
+ if len(data) < n:
+ raise PluginEOF()
+ return data
+
+def rd_byte(fh):
+ return rd_n(fh, 1)[0]
+
+def rd_boolean(fh):
+ return rd_byte(fh) != 0
+
+def rd_uint32(fh):
+ return struct.unpack(">I", rd_n(fh, 4))[0]
+
+def rd_string(fh):
+ length = rd_uint32(fh)
+ return rd_n(fh, length)
+
+def rd_string_utf8(fh):
+ return rd_string(fh).decode("UTF-8")
+
+# ----------------------------------------------------------------------
+#
+# Protocol definitions.
+
+our_max_version = 2
+
+PLUGIN_INIT = 1
+PLUGIN_INIT_RESPONSE = 2
+PLUGIN_PROTOCOL = 3
+PLUGIN_PROTOCOL_ACCEPT = 4
+PLUGIN_PROTOCOL_REJECT = 5
+PLUGIN_AUTH_SUCCESS = 6
+PLUGIN_AUTH_FAILURE = 7
+PLUGIN_INIT_FAILURE = 8
+PLUGIN_KI_SERVER_REQUEST = 20
+PLUGIN_KI_SERVER_RESPONSE = 21
+PLUGIN_KI_USER_REQUEST = 22
+PLUGIN_KI_USER_RESPONSE = 23
+
+# ----------------------------------------------------------------------
+#
+# Classes to make it easy to construct and receive messages.
+#
+# OutMessage is constructed with the message type; then you use the
+# wr_foo() routines to add fields to it, and finally call its send()
+# method.
+#
+# InMessage is constructed via the expect() class method, to which you
+# give a list of message types you expect to see one of at this stage.
+# Once you've got one, you can rd_foo() fields from it.
+
+class OutMessage:
+ def __init__(self, msgtype):
+ self.buf = io.BytesIO()
+ wr_byte(self.buf, msgtype)
+ self.write = self.buf.write
+
+ def send(self, fh=sys.stdout.buffer):
+ wr_string(fh, self.buf.getvalue())
+ fh.flush()
+
+class InMessage:
+ @classmethod
+ def expect(cls, expected_types, fh=sys.stdin.buffer):
+ self = cls()
+ self.buf = io.BytesIO(rd_string(fh))
+ self.msgtype = rd_byte(self.buf)
+ self.read = self.buf.read
+
+ if self.msgtype not in expected_types:
+ raise ValueError("received packet type {:d}, expected {}".format(
+ self.msgtype, ",".join(map("{:d}".format,
+ sorted(expected_types)))))
+ return self
+
+# ----------------------------------------------------------------------
+#
+# The main implementation of the protocol.
+
+def protocol():
+ # Start by expecting PLUGIN_INIT.
+ msg = InMessage.expect({PLUGIN_INIT})
+ their_version = rd_uint32(msg)
+ hostname = rd_string_utf8(msg)
+ port = rd_uint32(msg)
+ username = rd_string_utf8(msg)
+ print(f"Got hostname {hostname!r}, port {port!r}", file=sys.stderr)
+
+ # Decide which protocol version we're speaking.
+ version = min(their_version, our_max_version)
+ assert version != 0, "Protocol version 0 does not exist"
+
+ if "TESTPLUGIN_INIT_FAIL" in os.environ:
+ # Test the plugin failing at startup time.
+ msg = OutMessage(PLUGIN_INIT_FAILURE)
+ wr_string_utf8(msg, os.environ["TESTPLUGIN_INIT_FAIL"])
+ msg.send()
+ return
+
+ # Send INIT_RESPONSE, with our protocol version and an overridden
+ # username.
+ #
+ # By default this test plugin doesn't override the username, but
+ # you can make it do so by setting TESTPLUGIN_USERNAME in the
+ # environment.
+ msg = OutMessage(PLUGIN_INIT_RESPONSE)
+ wr_uint32(msg, version)
+ wr_string_utf8(msg, os.environ.get("TESTPLUGIN_USERNAME", ""))
+ msg.send()
+
+ # Outer loop run once per authentication protocol.
+ while True:
+ # Expect a message telling us what the protocol is.
+ msg = InMessage.expect({PLUGIN_PROTOCOL})
+ method = rd_string(msg)
+
+ if "TESTPLUGIN_PROTO_REJECT" in os.environ:
+ # Test the plugin failing at PLUGIN_PROTOCOL time.
+ msg = OutMessage(PLUGIN_PROTOCOL_REJECT)
+ wr_string_utf8(msg, os.environ["TESTPLUGIN_PROTO_REJECT"])
+ msg.send()
+ continue
+
+ # We only support keyboard-interactive. If we supported other
+ # auth methods, this would be the place to add further clauses
+ # to this if statement for them.
+ if method == b"keyboard-interactive":
+ msg = OutMessage(PLUGIN_PROTOCOL_ACCEPT)
+ msg.send()
+
+ # Inner loop run once per keyboard-interactive exchange
+ # with the SSH server.
+ while True:
+ # Expect a set of prompts from the server, or
+ # terminate the loop on SUCCESS or FAILURE.
+ #
+ # (We could also respond to SUCCESS or FAILURE by
+ # updating caches of our own, if we had any that were
+ # useful.)
+ msg = InMessage.expect({PLUGIN_KI_SERVER_REQUEST,
+ PLUGIN_AUTH_SUCCESS,
+ PLUGIN_AUTH_FAILURE})
+ if (msg.msgtype == PLUGIN_AUTH_SUCCESS or
+ msg.msgtype == PLUGIN_AUTH_FAILURE):
+ break
+
+ # If we didn't just break, we're sitting on a
+ # PLUGIN_KI_SERVER_REQUEST message. Get all its bits
+ # and pieces out.
+ name = rd_string_utf8(msg)
+ instructions = rd_string_utf8(msg)
+ language = rd_string(msg)
+ nprompts = rd_uint32(msg)
+ prompts = []
+ for i in range(nprompts):
+ prompt = rd_string_utf8(msg)
+ echo = rd_boolean(msg)
+ prompts.append((prompt, echo))
+
+ # Actually make up some answers for the prompts. This
+ # is the part that a non-example implementation would
+ # do very differently, of course!
+ #
+ # Here, we answer "foo" to every prompt, except that
+ # if there are exactly two prompts in the packet then
+ # we answer "stoat" to the first and "weasel" to the
+ # second.
+ #
+ # (These answers are consistent with the ones required
+ # by PuTTY's test SSH server Uppity in its own
+ # keyboard-interactive test implementation: that
+ # presents a two-prompt packet and expects
+ # "stoat","weasel" as the answers, and then presents a
+ # zero-prompt packet. So this test plugin will get you
+ # through Uppity's k-i in a one-touch manner. The
+ # "foo" in this code isn't used by Uppity at all; I
+ # just include it because I had to have _some_
+ # handling for the else clause.)
+ #
+ # If TESTPLUGIN_PROMPTS is set in the environment, we
+ # ask the user questions of our own by sending them
+ # back to PuTTY as USER_REQUEST messages.
+ if nprompts == 2:
+ if "TESTPLUGIN_PROMPTS" in os.environ:
+ for i in range(2):
+ # Make up some questions to ask.
+ msg = OutMessage(PLUGIN_KI_USER_REQUEST)
+ wr_string_utf8(
+ msg, "Plugin request #{:d} (name)".format(i))
+ wr_string_utf8(
+ msg, "Plugin request #{:d} (instructions)"
+ .format(i))
+ wr_string(msg, b"")
+ wr_uint32(msg, 2)
+ wr_string_utf8(msg, "Prompt 1 of 2 (echo): ")
+ wr_boolean(msg, True)
+ wr_string_utf8(msg, "Prompt 2 of 2 (no echo): ")
+ wr_boolean(msg, False)
+ msg.send()
+
+ # Expect the answers.
+ msg = InMessage.expect({PLUGIN_KI_USER_RESPONSE})
+ user_nprompts = rd_uint32(msg)
+ assert user_nprompts == 2, (
+ "Should match what we just sent")
+ for i in range(nprompts):
+ user_response = rd_string_utf8(msg)
+ # We don't actually check these
+ # responses for anything.
+
+ answers = ["stoat", "weasel"]
+
+ else:
+ answers = ["foo"] * nprompts
+
+ # Send the answers to the SSH server's questions.
+ msg = OutMessage(PLUGIN_KI_SERVER_RESPONSE)
+ wr_uint32(msg, len(answers))
+ for answer in answers:
+ wr_string_utf8(msg, answer)
+ msg.send()
+
+ else:
+ # Default handler if we don't speak the offered protocol
+ # at all.
+ msg = OutMessage(PLUGIN_PROTOCOL_REJECT)
+ wr_string_utf8(msg, "")
+ msg.send()
+
+# Demonstration write to stderr, to prove that it shows up in PuTTY's
+# Event Log.
+print("Hello from test plugin's stderr", file=sys.stderr)
+
+try:
+ protocol()
+except PluginEOF:
+ pass
diff --git a/contrib/gdb.py b/contrib/gdb.py
index 34bbb0ec..fb7413ec 100644
--- a/contrib/gdb.py
+++ b/contrib/gdb.py
@@ -30,12 +30,29 @@ class PuTTYMpintPrettyPrinter(gdb.printing.PrettyPrinter):
return "mp_int(NULL)".format(address)
return "mp_int(invalid @ {:#x})".format(address)
+class PuTTYPtrlenPrettyPrinter(gdb.printing.PrettyPrinter):
+ "Pretty-print strings in PuTTY's ptrlen type."
+ name = "ptrlen"
+
+ def __init__(self, val):
+ super(PuTTYPtrlenPrettyPrinter, self).__init__(self.name)
+ self.val = val
+
+ def to_string(self):
+ length = int(self.val["len"])
+ char_array_ptr_type = gdb.lookup_type(
+ "char").const().array(length).pointer()
+ line = self.val["ptr"].cast(char_array_ptr_type).dereference()
+ return repr(bytes(int(line[i]) for i in range(length))).lstrip('b')
+
class PuTTYPrinterSelector(gdb.printing.PrettyPrinter):
def __init__(self):
super(PuTTYPrinterSelector, self).__init__("PuTTY")
def __call__(self, val):
if str(val.type) == "mp_int *":
return PuTTYMpintPrettyPrinter(val)
+ if str(val.type) == "ptrlen":
+ return PuTTYPtrlenPrettyPrinter(val)
return None
gdb.printing.register_pretty_printer(None, PuTTYPrinterSelector())
@@ -203,7 +220,7 @@ class List234(gdb.Function):
Arguments are a tree234, and optionally a value type. If no value
type is given, the result is a list of the raw void * pointers
- stored in the tree. Othewise, each one is cast to a pointer to the
+ stored in the tree. Otherwise, each one is cast to a pointer to the
value type and dereferenced.
Due to limitations of GDB's convenience function syntax, the value
diff --git a/contrib/proveprime.py b/contrib/proveprime.py
new file mode 100755
index 00000000..655e68ea
--- /dev/null
+++ b/contrib/proveprime.py
@@ -0,0 +1,162 @@
+#!/usr/bin/env python3
+
+import argparse
+import functools
+import math
+import os
+import re
+import subprocess
+import sys
+import itertools
+
+def gen_names():
+ for i in itertools.count():
+ name = "p{:d}".format(i)
+ if name not in nameset:
+ yield name
+nameset=set()
+names = gen_names()
+
+class YafuError(Exception):
+ pass
+
+verbose = False
+def diag(*args):
+ if verbose:
+ print(*args, file=sys.stderr)
+
+factorcache = set()
+factorcachefile = None
+def cache_factor(f):
+ if f not in factorcache:
+ factorcache.add(f)
+ if factorcachefile is not None:
+ factorcachefile.write("{:d}\n".format(f))
+ factorcachefile.flush()
+
+yafu = None
+yafu_pattern = re.compile(rb"^P\d+ = (\d+)$")
+def call_yafu(n):
+ n_orig = n
+ diag("starting yafu", n_orig)
+ p = subprocess.Popen([yafu, "-v", "-v"], stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ p.stdin.write("{:d}\n".format(n).encode("ASCII"))
+ p.stdin.close()
+ factors = []
+ for line in iter(p.stdout.readline, b''):
+ line = line.rstrip(b"\r\n")
+ diag("yafu output:", line.decode())
+ m = yafu_pattern.match(line)
+ if m is not None:
+ f = int(m.group(1))
+ if n % f != 0:
+ raise YafuError("bad yafu factor {:d}".format(f))
+ factors.append(f)
+ if f >> 64:
+ cache_factor(f)
+ n //= f
+ p.wait()
+ diag("done yafu", n_orig)
+ return factors, n
+
+def factorise(n):
+ allfactors = []
+ for f in factorcache:
+ if n % f == 0:
+ n //= f
+ allfactors.append(f)
+ while n > 1:
+ factors, n = call_yafu(n)
+ allfactors.extend(factors)
+ return sorted(allfactors)
+
+def product(ns):
+ return functools.reduce(lambda a,b: a*b, ns, 1)
+
+smallprimes = set()
+commands = {}
+
+def proveprime(p, name=None):
+ if p >> 32 == 0:
+ smallprimes.add(p)
+ return "{:d}".format(p)
+
+ if name is None:
+ name = next(names)
+ print("{} = {:d}".format(name, p))
+
+ fs = factorise(p-1)
+ fs.reverse()
+ prod = product(fs)
+ qs = []
+ for q in fs:
+ newprod = prod // q
+ if newprod * newprod * newprod > p:
+ prod = newprod
+ else:
+ qs.append(q)
+ assert prod == product(qs)
+ assert prod * prod * prod > p
+ qset = set(qs)
+ qnamedict = {q: proveprime(q) for q in qset}
+ qnames = [qnamedict[q] for q in qs]
+ for w in itertools.count(2):
+ assert pow(w, p-1, p) == 1, "{}={:d} is not prime!".format(name, p)
+ diag("trying witness", w, "for", p)
+ for q in qset:
+ wpower = pow(w, (p-1) // q, p) - 1
+ if math.gcd(wpower, p) != 1:
+ break
+ else:
+ diag("found witness", w, "for", p)
+ break
+ commands[p]= (name, w, qnames)
+ return name
+
+def main():
+ parser = argparse.ArgumentParser(description='')
+ parser.add_argument("prime", nargs="+",
+ help="Number to prove prime. Can be prefixed by a "
+ "variable name and '=', e.g. 'x=9999999967'.")
+ parser.add_argument("--cryptsuite", action="store_true",
+ help="Generate abbreviated Pockle calls suitable "
+ "for the tests in cryptsuite.py.")
+ parser.add_argument("--yafu", default="yafu",
+ help="yafu binary to help with factoring.")
+ parser.add_argument("-v", "--verbose", action="store_true",
+ help="Write diagnostics to standard error.")
+ parser.add_argument("--cache", help="Cache of useful factors of things.")
+ args = parser.parse_args()
+
+ global verbose, yafu
+ verbose = args.verbose
+ yafu = args.yafu
+
+ if args.cache is not None:
+ with open(args.cache, "r") as fh:
+ for line in iter(fh.readline, ""):
+ factorcache.add(int(line.rstrip("\r\n")))
+ global factorcachefile
+ factorcachefile = open(args.cache, "a")
+
+ for ps in args.prime:
+ name, value = (ps.split("=", 1) if "=" in ps
+ else (None, ps))
+ proveprime(int(value, 0), name)
+
+ print("po = pockle_new()")
+ if len(smallprimes) > 0:
+ if args.cryptsuite:
+ print("add_small(po, {})".format(
+ ", ".join("{:d}".format(q) for q in sorted(smallprimes))))
+ else:
+ for q in sorted(smallprimes):
+ print("pockle_add_small_prime(po, {:d})".format(q))
+ for p, (name, w, qnames) in sorted(commands.items()):
+ print("{cmd}(po, {name}, [{qs}], {w:d})".format(
+ cmd = "add" if args.cryptsuite else "pockle_add_prime",
+ name=name, w=w, qs=", ".join(qnames)))
+
+if __name__ == '__main__':
+ main()
diff --git a/cproxy.c b/cproxy.c
deleted file mode 100644
index e1d788b6..00000000
--- a/cproxy.c
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Routines to do cryptographic interaction with proxies in PuTTY.
- * This is in a separate module from proxy.c, so that it can be
- * conveniently removed in PuTTYtel by replacing this module with
- * the stub version nocproxy.c.
- */
-
-#include <assert.h>
-#include <ctype.h>
-#include <string.h>
-
-#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/crypto/CMakeLists.txt b/crypto/CMakeLists.txt
new file mode 100644
index 00000000..4b0aa907
--- /dev/null
+++ b/crypto/CMakeLists.txt
@@ -0,0 +1,242 @@
+add_sources_from_current_dir(crypto
+ aes-common.c
+ aes-select.c
+ aes-sw.c
+ aesgcm-common.c
+ aesgcm-select.c
+ aesgcm-sw.c
+ aesgcm-ref-poly.c
+ arcfour.c
+ argon2.c
+ bcrypt.c
+ blake2.c
+ blowfish.c
+ chacha20-poly1305.c
+ crc32.c
+ des.c
+ diffie-hellman.c
+ dsa.c
+ ecc-arithmetic.c
+ ecc-ssh.c
+ hash_simple.c
+ hmac.c
+ mac.c
+ mac_simple.c
+ md5.c
+ mpint.c
+ ntru.c
+ openssh-certs.c
+ prng.c
+ pubkey-pem.c
+ pubkey-ppk.c
+ pubkey-ssh1.c
+ rsa.c
+ sha256-common.c
+ sha256-select.c
+ sha256-sw.c
+ sha512-common.c
+ sha512-select.c
+ sha512-sw.c
+ sha3.c
+ sha1-common.c
+ sha1-select.c
+ sha1-sw.c
+ xdmauth.c)
+
+include(CheckCSourceCompiles)
+
+function(test_compile_with_flags outvar)
+ cmake_parse_arguments(OPT "" ""
+ "GNU_FLAGS;MSVC_FLAGS;ADD_SOURCES_IF_SUCCESSFUL;TEST_SOURCE" "${ARGN}")
+
+ # Figure out what flags are applicable to this compiler.
+ set(flags)
+ if(CMAKE_C_COMPILER_ID MATCHES "GNU" OR
+ CMAKE_C_COMPILER_ID MATCHES "Clang")
+ set(flags ${OPT_GNU_FLAGS})
+ endif()
+ if(CMAKE_C_COMPILER_ID MATCHES "MSVC")
+ set(flags ${OPT_MSVC_FLAGS})
+ endif()
+
+ # See if we can compile the provided test program.
+ foreach(i ${flags})
+ set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${i}")
+ endforeach()
+ check_c_source_compiles("${OPT_TEST_SOURCE}" "${outvar}")
+
+ if(${outvar} AND OPT_ADD_SOURCES_IF_SUCCESSFUL)
+ # Make an object library that compiles the implementation with the
+ # necessary flags, and add the resulting objects to the crypto
+ # library.
+ set(libname object_lib_${outvar})
+ add_library(${libname} OBJECT ${OPT_ADD_SOURCES_IF_SUCCESSFUL})
+ target_compile_options(${libname} PRIVATE ${flags})
+ target_sources(crypto PRIVATE $<TARGET_OBJECTS:${libname}>)
+ endif()
+
+ # Export the output to the caller's scope, so that further tests can
+ # be based on it.
+ set(${outvar} ${${outvar}} PARENT_SCOPE)
+endfunction()
+
+# ----------------------------------------------------------------------
+# Try to enable x86 intrinsics-based crypto implementations.
+
+test_compile_with_flags(HAVE_WMMINTRIN_H
+ GNU_FLAGS -msse4.1
+ TEST_SOURCE "
+ #include <wmmintrin.h>
+ #include <smmintrin.h>
+ volatile __m128i r, a, b;
+ int main(void) { r = _mm_xor_si128(a, b); }")
+if(HAVE_WMMINTRIN_H)
+ test_compile_with_flags(HAVE_AES_NI
+ GNU_FLAGS -msse4.1 -maes
+ TEST_SOURCE "
+ #include <wmmintrin.h>
+ #include <smmintrin.h>
+ volatile __m128i r, a, b;
+ int main(void) { r = _mm_aesenc_si128(a, b); }"
+ ADD_SOURCES_IF_SUCCESSFUL aes-ni aes-ni.c)
+
+ # shaintrin.h doesn't exist on all compilers; sometimes it's folded
+ # into the other headers
+ test_compile_with_flags(HAVE_SHAINTRIN_H
+ GNU_FLAGS -msse4.1 -msha
+ TEST_SOURCE "
+ #include <wmmintrin.h>
+ #include <smmintrin.h>
+ #include <immintrin.h>
+ #include <shaintrin.h>
+ volatile __m128i r, a, b;
+ int main(void) { r = _mm_xor_si128(a, b); }")
+ if(HAVE_SHAINTRIN_H)
+ set(include_shaintrin "#include <shaintrin.h>")
+ else()
+ set(include_shaintrin "")
+ endif()
+
+ test_compile_with_flags(HAVE_SHA_NI
+ GNU_FLAGS -msse4.1 -msha
+ TEST_SOURCE "
+ #include <wmmintrin.h>
+ #include <smmintrin.h>
+ #include <immintrin.h>
+ ${include_shaintrin}
+ volatile __m128i r, a, b, c;
+ int main(void) { r = _mm_sha256rnds2_epu32(a, b, c); }"
+ ADD_SOURCES_IF_SUCCESSFUL sha256-ni.c sha1-ni.c)
+
+ test_compile_with_flags(HAVE_CLMUL
+ GNU_FLAGS -msse4.1 -mpclmul
+ TEST_SOURCE "
+ #include <wmmintrin.h>
+ #include <tmmintrin.h>
+ volatile __m128i r, a, b;
+ int main(void) { r = _mm_clmulepi64_si128(a, b, 5);
+ r = _mm_shuffle_epi8(r, a); }"
+ ADD_SOURCES_IF_SUCCESSFUL aesgcm-clmul.c)
+endif()
+
+# ----------------------------------------------------------------------
+# Try to enable Arm Neon intrinsics-based crypto implementations.
+
+# Start by checking which header file we need. ACLE specifies that it
+# ought to be <arm_neon.h>, on both 32- and 64-bit Arm, but Visual
+# Studio for some reason renamed the header to <arm64_neon.h> in
+# 64-bit, and gives an error if you use the standard name. (However,
+# clang-cl does let you use the standard name.)
+test_compile_with_flags(HAVE_ARM_NEON_H
+ MSVC_FLAGS -D_ARM_USE_NEW_NEON_INTRINSICS
+ TEST_SOURCE "
+ #include <arm_neon.h>
+ volatile uint8x16_t r, a, b;
+ int main(void) { r = veorq_u8(a, b); }")
+if(HAVE_ARM_NEON_H)
+ set(neon ON)
+ set(neon_header "arm_neon.h")
+else()
+ test_compile_with_flags(HAVE_ARM64_NEON_H TEST_SOURCE "
+ #include <arm64_neon.h>
+ volatile uint8x16_t r, a, b;
+ int main(void) { r = veorq_u8(a, b); }")
+ if(HAVE_ARM64_NEON_H)
+ set(neon ON)
+ set(neon_header "arm64_neon.h")
+ set(USE_ARM64_NEON_H ON)
+ endif()
+endif()
+
+if(neon)
+ # If we have _some_ NEON header, look for the individual things we
+ # can enable with it.
+
+ # The 'crypto' architecture extension includes support for AES,
+ # SHA-1, and SHA-256.
+ test_compile_with_flags(HAVE_NEON_CRYPTO
+ GNU_FLAGS -march=armv8-a+crypto
+ MSVC_FLAGS -D_ARM_USE_NEW_NEON_INTRINSICS
+ TEST_SOURCE "
+ #include <${neon_header}>
+ volatile uint8x16_t r, a, b;
+ volatile uint32x4_t s, x, y, z;
+ int main(void) { r = vaeseq_u8(a, b); s = vsha256hq_u32(x, y, z); }"
+ ADD_SOURCES_IF_SUCCESSFUL aes-neon.c sha256-neon.c sha1-neon.c)
+
+ test_compile_with_flags(HAVE_NEON_PMULL
+ GNU_FLAGS -march=armv8-a+crypto
+ MSVC_FLAGS -D_ARM_USE_NEW_NEON_INTRINSICS
+ TEST_SOURCE "
+ #include <${neon_header}>
+ volatile poly128_t r;
+ volatile poly64_t a, b;
+ volatile poly64x2_t u, v;
+ int main(void) { r = vmull_p64(a, b); r = vmull_high_p64(u, v); }"
+ ADD_SOURCES_IF_SUCCESSFUL aesgcm-neon.c)
+
+ test_compile_with_flags(HAVE_NEON_VADDQ_P128
+ GNU_FLAGS -march=armv8-a+crypto
+ MSVC_FLAGS -D_ARM_USE_NEW_NEON_INTRINSICS
+ TEST_SOURCE "
+ #include <${neon_header}>
+ volatile poly128_t r;
+ int main(void) { r = vaddq_p128(r, r); }")
+
+ # The 'sha3' architecture extension, despite the name, includes
+ # support for SHA-512 (from the SHA-2 standard) as well as SHA-3
+ # proper.
+ #
+ # Versions of clang up to and including clang 12 support this
+ # extension in assembly language, but not the ACLE intrinsics for
+ # it. So we check both.
+ test_compile_with_flags(HAVE_NEON_SHA512_INTRINSICS
+ GNU_FLAGS -march=armv8.2-a+crypto+sha3
+ TEST_SOURCE "
+ #include <${neon_header}>
+ volatile uint64x2_t r, a, b;
+ int main(void) { r = vsha512su0q_u64(a, b); }"
+ ADD_SOURCES_IF_SUCCESSFUL sha512-neon.c)
+ if(HAVE_NEON_SHA512_INTRINSICS)
+ set(HAVE_NEON_SHA512 ON)
+ else()
+ test_compile_with_flags(HAVE_NEON_SHA512_ASM
+ GNU_FLAGS -march=armv8.2-a+crypto+sha3
+ TEST_SOURCE "
+ #include <${neon_header}>
+ volatile uint64x2_t r, a;
+ int main(void) { __asm__(\"sha512su0 %0.2D,%1.2D\" : \"+w\" (r) : \"w\" (a)); }"
+ ADD_SOURCES_IF_SUCCESSFUL sha512-neon.c)
+ if(HAVE_NEON_SHA512_ASM)
+ set(HAVE_NEON_SHA512 ON)
+ endif()
+ endif()
+endif()
+
+set(HAVE_AES_NI ${HAVE_AES_NI} PARENT_SCOPE)
+set(HAVE_SHA_NI ${HAVE_SHA_NI} PARENT_SCOPE)
+set(HAVE_SHAINTRIN_H ${HAVE_SHAINTRIN_H} PARENT_SCOPE)
+set(HAVE_NEON_CRYPTO ${HAVE_NEON_CRYPTO} PARENT_SCOPE)
+set(HAVE_NEON_SHA512 ${HAVE_NEON_SHA512} PARENT_SCOPE)
+set(HAVE_NEON_SHA512_INTRINSICS ${HAVE_NEON_SHA512_INTRINSICS} PARENT_SCOPE)
+set(USE_ARM64_NEON_H ${USE_ARM64_NEON_H} PARENT_SCOPE)
diff --git a/crypto/aes-common.c b/crypto/aes-common.c
new file mode 100644
index 00000000..3bed2af1
--- /dev/null
+++ b/crypto/aes-common.c
@@ -0,0 +1,20 @@
+/*
+ * Common variable definitions across all the AES implementations.
+ */
+
+#include "ssh.h"
+#include "aes.h"
+
+const uint8_t aes_key_setup_round_constants[10] = {
+ /* The first few powers of X in GF(2^8), used during key setup.
+ * This can safely be a lookup table without side channel risks,
+ * because key setup iterates through it once in a standard way
+ * regardless of the key. */
+ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36,
+};
+
+void aesgcm_cipher_crypt_length(
+ ssh_cipher *cipher, void *blk, int len, unsigned long seq)
+{
+ /* Do nothing: lengths are sent in clear for this cipher. */
+}
diff --git a/crypto/aes-neon.c b/crypto/aes-neon.c
new file mode 100644
index 00000000..5cd9f2d1
--- /dev/null
+++ b/crypto/aes-neon.c
@@ -0,0 +1,359 @@
+/* ----------------------------------------------------------------------
+ * Hardware-accelerated implementation of AES using Arm NEON.
+ */
+
+#include "ssh.h"
+#include "aes.h"
+
+#if USE_ARM64_NEON_H
+#include <arm64_neon.h>
+#else
+#include <arm_neon.h>
+#endif
+
+static bool aes_neon_available(void)
+{
+ /*
+ * For Arm, we delegate to a per-platform AES detection function,
+ * because it has to be implemented by asking the operating system
+ * rather than directly querying the CPU.
+ *
+ * That's because Arm systems commonly have multiple cores that
+ * are not all alike, so any method of querying whether NEON
+ * crypto instructions work on the _current_ CPU - even one as
+ * crude as just trying one and catching the SIGILL - wouldn't
+ * give an answer that you could still rely on the first time the
+ * OS migrated your process to another CPU.
+ */
+ return platform_aes_neon_available();
+}
+
+/*
+ * Core NEON encrypt/decrypt functions, one per length and direction.
+ */
+
+#define NEON_CIPHER(len, repmacro) \
+ static inline uint8x16_t aes_neon_##len##_e( \
+ uint8x16_t v, const uint8x16_t *keysched) \
+ { \
+ repmacro(v = vaesmcq_u8(vaeseq_u8(v, *keysched++));); \
+ v = vaeseq_u8(v, *keysched++); \
+ return veorq_u8(v, *keysched); \
+ } \
+ static inline uint8x16_t aes_neon_##len##_d( \
+ uint8x16_t v, const uint8x16_t *keysched) \
+ { \
+ repmacro(v = vaesimcq_u8(vaesdq_u8(v, *keysched++));); \
+ v = vaesdq_u8(v, *keysched++); \
+ return veorq_u8(v, *keysched); \
+ }
+
+NEON_CIPHER(128, REP9)
+NEON_CIPHER(192, REP11)
+NEON_CIPHER(256, REP13)
+
+/*
+ * The main key expansion.
+ */
+static void aes_neon_key_expand(
+ const unsigned char *key, size_t key_words,
+ uint8x16_t *keysched_e, uint8x16_t *keysched_d)
+{
+ size_t rounds = key_words + 6;
+ size_t sched_words = (rounds + 1) * 4;
+
+ /*
+ * Store the key schedule as 32-bit integers during expansion, so
+ * that it's easy to refer back to individual previous words. We
+ * collect them into the final uint8x16_t form at the end.
+ */
+ uint32_t sched[MAXROUNDKEYS * 4];
+
+ unsigned rconpos = 0;
+
+ for (size_t i = 0; i < sched_words; i++) {
+ if (i < key_words) {
+ sched[i] = GET_32BIT_LSB_FIRST(key + 4 * i);
+ } else {
+ uint32_t temp = sched[i - 1];
+
+ bool rotate_and_round_constant = (i % key_words == 0);
+ bool sub = rotate_and_round_constant ||
+ (key_words == 8 && i % 8 == 4);
+
+ if (rotate_and_round_constant)
+ temp = (temp << 24) | (temp >> 8);
+
+ if (sub) {
+ uint32x4_t v32 = vdupq_n_u32(temp);
+ uint8x16_t v8 = vreinterpretq_u8_u32(v32);
+ v8 = vaeseq_u8(v8, vdupq_n_u8(0));
+ v32 = vreinterpretq_u32_u8(v8);
+ temp = vget_lane_u32(vget_low_u32(v32), 0);
+ }
+
+ if (rotate_and_round_constant) {
+ assert(rconpos < lenof(aes_key_setup_round_constants));
+ temp ^= aes_key_setup_round_constants[rconpos++];
+ }
+
+ sched[i] = sched[i - key_words] ^ temp;
+ }
+ }
+
+ /*
+ * Combine the key schedule words into uint8x16_t vectors and
+ * store them in the output context.
+ */
+ for (size_t round = 0; round <= rounds; round++)
+ keysched_e[round] = vreinterpretq_u8_u32(vld1q_u32(sched + 4*round));
+
+ smemclr(sched, sizeof(sched));
+
+ /*
+ * Now prepare the modified keys for the inverse cipher.
+ */
+ for (size_t eround = 0; eround <= rounds; eround++) {
+ size_t dround = rounds - eround;
+ uint8x16_t rkey = keysched_e[eround];
+ if (eround && dround) /* neither first nor last */
+ rkey = vaesimcq_u8(rkey);
+ keysched_d[dround] = rkey;
+ }
+}
+
+/*
+ * Auxiliary routine to reverse the byte order of a vector, so that
+ * the SDCTR IV can be made big-endian for feeding to the cipher.
+ *
+ * In fact we don't need to reverse the vector _all_ the way; we leave
+ * the two lanes in MSW,LSW order, because that makes no difference to
+ * the efficiency of the increment. That way we only have to reverse
+ * bytes within each lane in this function.
+ */
+static inline uint8x16_t aes_neon_sdctr_reverse(uint8x16_t v)
+{
+ return vrev64q_u8(v);
+}
+
+/*
+ * Auxiliary routine to increment the 128-bit counter used in SDCTR
+ * mode. There's no instruction to treat a 128-bit vector as a single
+ * long integer, so instead we have to increment the bottom half
+ * unconditionally, and the top half if the bottom half started off as
+ * all 1s (in which case there was about to be a carry).
+ */
+static inline uint8x16_t aes_neon_sdctr_increment(uint8x16_t in)
+{
+#ifdef __aarch64__
+ /* There will be a carry if the low 64 bits are all 1s. */
+ uint64x1_t all1 = vcreate_u64(0xFFFFFFFFFFFFFFFF);
+ uint64x1_t carry = vceq_u64(vget_high_u64(vreinterpretq_u64_u8(in)), all1);
+
+ /* Make a word whose bottom half is unconditionally all 1s, and
+ * the top half is 'carry', i.e. all 0s most of the time but all
+ * 1s if we need to increment the top half. Then that word is what
+ * we need to _subtract_ from the input counter. */
+ uint64x2_t subtrahend = vcombine_u64(carry, all1);
+#else
+ /* AArch32 doesn't have comparisons that operate on a 64-bit lane,
+ * so we start by comparing each 32-bit half of the low 64 bits
+ * _separately_ to all-1s. */
+ uint32x2_t all1 = vdup_n_u32(0xFFFFFFFF);
+ uint32x2_t carry = vceq_u32(
+ vget_high_u32(vreinterpretq_u32_u8(in)), all1);
+
+ /* Swap the 32-bit words of the compare output, and AND with the
+ * unswapped version. Now carry is all 1s iff the bottom half of
+ * the input counter was all 1s, and all 0s otherwise. */
+ carry = vand_u32(carry, vrev64_u32(carry));
+
+ /* Now make the vector to subtract in the same way as above. */
+ uint64x2_t subtrahend = vreinterpretq_u64_u32(vcombine_u32(carry, all1));
+#endif
+
+ return vreinterpretq_u8_u64(
+ vsubq_u64(vreinterpretq_u64_u8(in), subtrahend));
+}
+
+/*
+ * Much simpler auxiliary routine to increment the counter for GCM
+ * mode. This only has to increment the low word.
+ */
+static inline uint8x16_t aes_neon_gcm_increment(uint8x16_t in)
+{
+ uint32x4_t inw = vreinterpretq_u32_u8(in);
+ uint32x4_t ONE = vcombine_u32(vcreate_u32(0), vcreate_u32(1));
+ inw = vaddq_u32(inw, ONE);
+ return vreinterpretq_u8_u32(inw);
+}
+
+/*
+ * The SSH interface and the cipher modes.
+ */
+
+typedef struct aes_neon_context aes_neon_context;
+struct aes_neon_context {
+ uint8x16_t keysched_e[MAXROUNDKEYS], keysched_d[MAXROUNDKEYS], iv;
+
+ ssh_cipher ciph;
+};
+
+static ssh_cipher *aes_neon_new(const ssh_cipheralg *alg)
+{
+ const struct aes_extra *extra = (const struct aes_extra *)alg->extra;
+ if (!check_availability(extra))
+ return NULL;
+
+ aes_neon_context *ctx = snew(aes_neon_context);
+ ctx->ciph.vt = alg;
+ return &ctx->ciph;
+}
+
+static void aes_neon_free(ssh_cipher *ciph)
+{
+ aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph);
+ smemclr(ctx, sizeof(*ctx));
+ sfree(ctx);
+}
+
+static void aes_neon_setkey(ssh_cipher *ciph, const void *vkey)
+{
+ aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph);
+ const unsigned char *key = (const unsigned char *)vkey;
+
+ aes_neon_key_expand(key, ctx->ciph.vt->real_keybits / 32,
+ ctx->keysched_e, ctx->keysched_d);
+}
+
+static void aes_neon_setiv_cbc(ssh_cipher *ciph, const void *iv)
+{
+ aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph);
+ ctx->iv = vld1q_u8(iv);
+}
+
+static void aes_neon_setiv_sdctr(ssh_cipher *ciph, const void *iv)
+{
+ aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph);
+ uint8x16_t counter = vld1q_u8(iv);
+ ctx->iv = aes_neon_sdctr_reverse(counter);
+}
+
+static void aes_neon_setiv_gcm(ssh_cipher *ciph, const void *iv)
+{
+ aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph);
+ uint8x16_t counter = vld1q_u8(iv);
+ ctx->iv = aes_neon_sdctr_reverse(counter);
+ ctx->iv = vreinterpretq_u8_u32(vsetq_lane_u32(
+ 1, vreinterpretq_u32_u8(ctx->iv), 2));
+}
+
+static void aes_neon_next_message_gcm(ssh_cipher *ciph)
+{
+ aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph);
+ uint32x4_t iv = vreinterpretq_u32_u8(ctx->iv);
+ uint64_t msg_counter = vgetq_lane_u32(iv, 0);
+ msg_counter = (msg_counter << 32) | vgetq_lane_u32(iv, 3);
+ msg_counter++;
+ iv = vsetq_lane_u32(msg_counter >> 32, iv, 0);
+ iv = vsetq_lane_u32(msg_counter, iv, 3);
+ iv = vsetq_lane_u32(1, iv, 2);
+ ctx->iv = vreinterpretq_u8_u32(iv);
+}
+
+typedef uint8x16_t (*aes_neon_fn)(uint8x16_t v, const uint8x16_t *keysched);
+
+static inline void aes_cbc_neon_encrypt(
+ ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn encrypt)
+{
+ aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph);
+
+ for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+ blk < finish; blk += 16) {
+ uint8x16_t plaintext = vld1q_u8(blk);
+ uint8x16_t cipher_input = veorq_u8(plaintext, ctx->iv);
+ uint8x16_t ciphertext = encrypt(cipher_input, ctx->keysched_e);
+ vst1q_u8(blk, ciphertext);
+ ctx->iv = ciphertext;
+ }
+}
+
+static inline void aes_cbc_neon_decrypt(
+ ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn decrypt)
+{
+ aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph);
+
+ for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+ blk < finish; blk += 16) {
+ uint8x16_t ciphertext = vld1q_u8(blk);
+ uint8x16_t decrypted = decrypt(ciphertext, ctx->keysched_d);
+ uint8x16_t plaintext = veorq_u8(decrypted, ctx->iv);
+ vst1q_u8(blk, plaintext);
+ ctx->iv = ciphertext;
+ }
+}
+
+static inline void aes_sdctr_neon(
+ ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn encrypt)
+{
+ aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph);
+
+ for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+ blk < finish; blk += 16) {
+ uint8x16_t counter = aes_neon_sdctr_reverse(ctx->iv);
+ uint8x16_t keystream = encrypt(counter, ctx->keysched_e);
+ uint8x16_t input = vld1q_u8(blk);
+ uint8x16_t output = veorq_u8(input, keystream);
+ vst1q_u8(blk, output);
+ ctx->iv = aes_neon_sdctr_increment(ctx->iv);
+ }
+}
+
+static inline void aes_encrypt_ecb_block_neon(
+ ssh_cipher *ciph, void *blk, aes_neon_fn encrypt)
+{
+ aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph);
+ uint8x16_t plaintext = vld1q_u8(blk);
+ uint8x16_t ciphertext = encrypt(plaintext, ctx->keysched_e);
+ vst1q_u8(blk, ciphertext);
+}
+
+static inline void aes_gcm_neon(
+ ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn encrypt)
+{
+ aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph);
+
+ for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+ blk < finish; blk += 16) {
+ uint8x16_t counter = aes_neon_sdctr_reverse(ctx->iv);
+ uint8x16_t keystream = encrypt(counter, ctx->keysched_e);
+ uint8x16_t input = vld1q_u8(blk);
+ uint8x16_t output = veorq_u8(input, keystream);
+ vst1q_u8(blk, output);
+ ctx->iv = aes_neon_gcm_increment(ctx->iv);
+ }
+}
+
+#define NEON_ENC_DEC(len) \
+ static void aes##len##_neon_cbc_encrypt( \
+ ssh_cipher *ciph, void *vblk, int blklen) \
+ { aes_cbc_neon_encrypt(ciph, vblk, blklen, aes_neon_##len##_e); } \
+ static void aes##len##_neon_cbc_decrypt( \
+ ssh_cipher *ciph, void *vblk, int blklen) \
+ { aes_cbc_neon_decrypt(ciph, vblk, blklen, aes_neon_##len##_d); } \
+ static void aes##len##_neon_sdctr( \
+ ssh_cipher *ciph, void *vblk, int blklen) \
+ { aes_sdctr_neon(ciph, vblk, blklen, aes_neon_##len##_e); } \
+ static void aes##len##_neon_gcm( \
+ ssh_cipher *ciph, void *vblk, int blklen) \
+ { aes_gcm_neon(ciph, vblk, blklen, aes_neon_##len##_e); } \
+ static void aes##len##_neon_encrypt_ecb_block( \
+ ssh_cipher *ciph, void *vblk) \
+ { aes_encrypt_ecb_block_neon(ciph, vblk, aes_neon_##len##_e); }
+
+NEON_ENC_DEC(128)
+NEON_ENC_DEC(192)
+NEON_ENC_DEC(256)
+
+AES_EXTRA(_neon);
+AES_ALL_VTABLES(_neon, "NEON accelerated");
diff --git a/crypto/aes-ni.c b/crypto/aes-ni.c
new file mode 100644
index 00000000..67d82b86
--- /dev/null
+++ b/crypto/aes-ni.c
@@ -0,0 +1,341 @@
+/*
+ * Hardware-accelerated implementation of AES using x86 AES-NI.
+ */
+
+#include "ssh.h"
+#include "aes.h"
+
+#include <wmmintrin.h>
+#include <smmintrin.h>
+
+#if defined(__clang__) || defined(__GNUC__)
+#include <cpuid.h>
+#define GET_CPU_ID(out) __cpuid(1, (out)[0], (out)[1], (out)[2], (out)[3])
+#else
+#define GET_CPU_ID(out) __cpuid(out, 1)
+#endif
+
+static bool aes_ni_available(void)
+{
+ /*
+ * Determine if AES is available on this CPU, by checking that
+ * both AES itself and SSE4.1 are supported.
+ */
+ unsigned int CPUInfo[4];
+ GET_CPU_ID(CPUInfo);
+ return (CPUInfo[2] & (1 << 25)) && (CPUInfo[2] & (1 << 19));
+}
+
+/*
+ * Core AES-NI encrypt/decrypt functions, one per length and direction.
+ */
+
+#define NI_CIPHER(len, dir, dirlong, repmacro) \
+ static inline __m128i aes_ni_##len##_##dir( \
+ __m128i v, const __m128i *keysched) \
+ { \
+ v = _mm_xor_si128(v, *keysched++); \
+ repmacro(v = _mm_aes##dirlong##_si128(v, *keysched++);); \
+ return _mm_aes##dirlong##last_si128(v, *keysched); \
+ }
+
+NI_CIPHER(128, e, enc, REP9)
+NI_CIPHER(128, d, dec, REP9)
+NI_CIPHER(192, e, enc, REP11)
+NI_CIPHER(192, d, dec, REP11)
+NI_CIPHER(256, e, enc, REP13)
+NI_CIPHER(256, d, dec, REP13)
+
+/*
+ * The main key expansion.
+ */
+static void aes_ni_key_expand(
+ const unsigned char *key, size_t key_words,
+ __m128i *keysched_e, __m128i *keysched_d)
+{
+ size_t rounds = key_words + 6;
+ size_t sched_words = (rounds + 1) * 4;
+
+ /*
+ * Store the key schedule as 32-bit integers during expansion, so
+ * that it's easy to refer back to individual previous words. We
+ * collect them into the final __m128i form at the end.
+ */
+ uint32_t sched[MAXROUNDKEYS * 4];
+
+ unsigned rconpos = 0;
+
+ for (size_t i = 0; i < sched_words; i++) {
+ if (i < key_words) {
+ sched[i] = GET_32BIT_LSB_FIRST(key + 4 * i);
+ } else {
+ uint32_t temp = sched[i - 1];
+
+ bool rotate_and_round_constant = (i % key_words == 0);
+ bool only_sub = (key_words == 8 && i % 8 == 4);
+
+ if (rotate_and_round_constant) {
+ __m128i v = _mm_setr_epi32(0,temp,0,0);
+ v = _mm_aeskeygenassist_si128(v, 0);
+ temp = _mm_extract_epi32(v, 1);
+
+ assert(rconpos < lenof(aes_key_setup_round_constants));
+ temp ^= aes_key_setup_round_constants[rconpos++];
+ } else if (only_sub) {
+ __m128i v = _mm_setr_epi32(0,temp,0,0);
+ v = _mm_aeskeygenassist_si128(v, 0);
+ temp = _mm_extract_epi32(v, 0);
+ }
+
+ sched[i] = sched[i - key_words] ^ temp;
+ }
+ }
+
+ /*
+ * Combine the key schedule words into __m128i vectors and store
+ * them in the output context.
+ */
+ for (size_t round = 0; round <= rounds; round++)
+ keysched_e[round] = _mm_setr_epi32(
+ sched[4*round ], sched[4*round+1],
+ sched[4*round+2], sched[4*round+3]);
+
+ smemclr(sched, sizeof(sched));
+
+ /*
+ * Now prepare the modified keys for the inverse cipher.
+ */
+ for (size_t eround = 0; eround <= rounds; eround++) {
+ size_t dround = rounds - eround;
+ __m128i rkey = keysched_e[eround];
+ if (eround && dround) /* neither first nor last */
+ rkey = _mm_aesimc_si128(rkey);
+ keysched_d[dround] = rkey;
+ }
+}
+
+/*
+ * Auxiliary routine to increment the 128-bit counter used in SDCTR
+ * mode.
+ */
+static inline __m128i aes_ni_sdctr_increment(__m128i v)
+{
+ const __m128i ONE = _mm_setr_epi32(1,0,0,0);
+ const __m128i ZERO = _mm_setzero_si128();
+
+ /* Increment the low-order 64 bits of v */
+ v = _mm_add_epi64(v, ONE);
+ /* Check if they've become zero */
+ __m128i cmp = _mm_cmpeq_epi64(v, ZERO);
+ /* If so, the low half of cmp is all 1s. Pack that into the high
+ * half of addend with zero in the low half. */
+ __m128i addend = _mm_unpacklo_epi64(ZERO, cmp);
+ /* And subtract that from v, which increments the high 64 bits iff
+ * the low 64 wrapped round. */
+ v = _mm_sub_epi64(v, addend);
+
+ return v;
+}
+
+/*
+ * Much simpler auxiliary routine to increment the counter for GCM
+ * mode. This only has to increment the low word.
+ */
+static inline __m128i aes_ni_gcm_increment(__m128i v)
+{
+ const __m128i ONE = _mm_setr_epi32(1,0,0,0);
+ return _mm_add_epi32(v, ONE);
+}
+
+/*
+ * Auxiliary routine to reverse the byte order of a vector, so that
+ * the SDCTR IV can be made big-endian for feeding to the cipher.
+ */
+static inline __m128i aes_ni_sdctr_reverse(__m128i v)
+{
+ v = _mm_shuffle_epi8(
+ v, _mm_setr_epi8(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0));
+ return v;
+}
+
+/*
+ * The SSH interface and the cipher modes.
+ */
+
+typedef struct aes_ni_context aes_ni_context;
+struct aes_ni_context {
+ __m128i keysched_e[MAXROUNDKEYS], keysched_d[MAXROUNDKEYS], iv;
+
+ void *pointer_to_free;
+ ssh_cipher ciph;
+};
+
+static ssh_cipher *aes_ni_new(const ssh_cipheralg *alg)
+{
+ const struct aes_extra *extra = (const struct aes_extra *)alg->extra;
+ if (!check_availability(extra))
+ return NULL;
+
+ /*
+ * The __m128i variables in the context structure need to be
+ * 16-byte aligned, but not all malloc implementations that this
+ * code has to work with will guarantee to return a 16-byte
+ * aligned pointer. So we over-allocate, manually realign the
+ * pointer ourselves, and store the original one inside the
+ * context so we know how to free it later.
+ */
+ void *allocation = smalloc(sizeof(aes_ni_context) + 15);
+ uintptr_t alloc_address = (uintptr_t)allocation;
+ uintptr_t aligned_address = (alloc_address + 15) & ~15;
+ aes_ni_context *ctx = (aes_ni_context *)aligned_address;
+
+ ctx->ciph.vt = alg;
+ ctx->pointer_to_free = allocation;
+ return &ctx->ciph;
+}
+
+static void aes_ni_free(ssh_cipher *ciph)
+{
+ aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+ void *allocation = ctx->pointer_to_free;
+ smemclr(ctx, sizeof(*ctx));
+ sfree(allocation);
+}
+
+static void aes_ni_setkey(ssh_cipher *ciph, const void *vkey)
+{
+ aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+ const unsigned char *key = (const unsigned char *)vkey;
+
+ aes_ni_key_expand(key, ctx->ciph.vt->real_keybits / 32,
+ ctx->keysched_e, ctx->keysched_d);
+}
+
+static void aes_ni_setiv_cbc(ssh_cipher *ciph, const void *iv)
+{
+ aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+ ctx->iv = _mm_loadu_si128(iv);
+}
+
+static void aes_ni_setiv_sdctr(ssh_cipher *ciph, const void *iv)
+{
+ aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+ __m128i counter = _mm_loadu_si128(iv);
+ ctx->iv = aes_ni_sdctr_reverse(counter);
+}
+
+static void aes_ni_setiv_gcm(ssh_cipher *ciph, const void *iv)
+{
+ aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+ __m128i counter = _mm_loadu_si128(iv);
+ ctx->iv = aes_ni_sdctr_reverse(counter);
+ ctx->iv = _mm_insert_epi32(ctx->iv, 1, 0);
+}
+
+static void aes_ni_next_message_gcm(ssh_cipher *ciph)
+{
+ aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+ uint32_t fixed = _mm_extract_epi32(ctx->iv, 3);
+ uint64_t msg_counter = _mm_extract_epi32(ctx->iv, 2);
+ msg_counter <<= 32;
+ msg_counter |= (uint32_t)_mm_extract_epi32(ctx->iv, 1);
+ msg_counter++;
+ ctx->iv = _mm_set_epi32(fixed, msg_counter >> 32, msg_counter, 1);
+}
+
+typedef __m128i (*aes_ni_fn)(__m128i v, const __m128i *keysched);
+
+static inline void aes_cbc_ni_encrypt(
+ ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt)
+{
+ aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+
+ for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+ blk < finish; blk += 16) {
+ __m128i plaintext = _mm_loadu_si128((const __m128i *)blk);
+ __m128i cipher_input = _mm_xor_si128(plaintext, ctx->iv);
+ __m128i ciphertext = encrypt(cipher_input, ctx->keysched_e);
+ _mm_storeu_si128((__m128i *)blk, ciphertext);
+ ctx->iv = ciphertext;
+ }
+}
+
+static inline void aes_cbc_ni_decrypt(
+ ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn decrypt)
+{
+ aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+
+ for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+ blk < finish; blk += 16) {
+ __m128i ciphertext = _mm_loadu_si128((const __m128i *)blk);
+ __m128i decrypted = decrypt(ciphertext, ctx->keysched_d);
+ __m128i plaintext = _mm_xor_si128(decrypted, ctx->iv);
+ _mm_storeu_si128((__m128i *)blk, plaintext);
+ ctx->iv = ciphertext;
+ }
+}
+
+static inline void aes_sdctr_ni(
+ ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt)
+{
+ aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+
+ for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+ blk < finish; blk += 16) {
+ __m128i counter = aes_ni_sdctr_reverse(ctx->iv);
+ __m128i keystream = encrypt(counter, ctx->keysched_e);
+ __m128i input = _mm_loadu_si128((const __m128i *)blk);
+ __m128i output = _mm_xor_si128(input, keystream);
+ _mm_storeu_si128((__m128i *)blk, output);
+ ctx->iv = aes_ni_sdctr_increment(ctx->iv);
+ }
+}
+
+static inline void aes_encrypt_ecb_block_ni(
+ ssh_cipher *ciph, void *blk, aes_ni_fn encrypt)
+{
+ aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+ __m128i plaintext = _mm_loadu_si128(blk);
+ __m128i ciphertext = encrypt(plaintext, ctx->keysched_e);
+ _mm_storeu_si128(blk, ciphertext);
+}
+
+static inline void aes_gcm_ni(
+ ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt)
+{
+ aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph);
+
+ for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+ blk < finish; blk += 16) {
+ __m128i counter = aes_ni_sdctr_reverse(ctx->iv);
+ __m128i keystream = encrypt(counter, ctx->keysched_e);
+ __m128i input = _mm_loadu_si128((const __m128i *)blk);
+ __m128i output = _mm_xor_si128(input, keystream);
+ _mm_storeu_si128((__m128i *)blk, output);
+ ctx->iv = aes_ni_gcm_increment(ctx->iv);
+ }
+}
+
+#define NI_ENC_DEC(len) \
+ static void aes##len##_ni_cbc_encrypt( \
+ ssh_cipher *ciph, void *vblk, int blklen) \
+ { aes_cbc_ni_encrypt(ciph, vblk, blklen, aes_ni_##len##_e); } \
+ static void aes##len##_ni_cbc_decrypt( \
+ ssh_cipher *ciph, void *vblk, int blklen) \
+ { aes_cbc_ni_decrypt(ciph, vblk, blklen, aes_ni_##len##_d); } \
+ static void aes##len##_ni_sdctr( \
+ ssh_cipher *ciph, void *vblk, int blklen) \
+ { aes_sdctr_ni(ciph, vblk, blklen, aes_ni_##len##_e); } \
+ static void aes##len##_ni_gcm( \
+ ssh_cipher *ciph, void *vblk, int blklen) \
+ { aes_gcm_ni(ciph, vblk, blklen, aes_ni_##len##_e); } \
+ static void aes##len##_ni_encrypt_ecb_block( \
+ ssh_cipher *ciph, void *vblk) \
+ { aes_encrypt_ecb_block_ni(ciph, vblk, aes_ni_##len##_e); }
+
+NI_ENC_DEC(128)
+NI_ENC_DEC(192)
+NI_ENC_DEC(256)
+
+AES_EXTRA(_ni);
+AES_ALL_VTABLES(_ni, "AES-NI accelerated");
diff --git a/crypto/aes-select.c b/crypto/aes-select.c
new file mode 100644
index 00000000..62b4ab01
--- /dev/null
+++ b/crypto/aes-select.c
@@ -0,0 +1,110 @@
+/*
+ * Top-level vtables to select an AES implementation.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "aes.h"
+
+static ssh_cipher *aes_select(const ssh_cipheralg *alg)
+{
+ const ssh_cipheralg *const *real_algs = (const ssh_cipheralg **)alg->extra;
+
+ for (size_t i = 0; real_algs[i]; i++) {
+ const ssh_cipheralg *alg = real_algs[i];
+ const struct aes_extra *alg_extra =
+ (const struct aes_extra *)alg->extra;
+ if (check_availability(alg_extra))
+ return ssh_cipher_new(alg);
+ }
+
+ /* We should never reach the NULL at the end of the list, because
+ * the last non-NULL entry should be software-only AES, which is
+ * always available. */
+ unreachable("aes_select ran off the end of its list");
+}
+
+#if HAVE_AES_NI
+#define IF_NI(...) __VA_ARGS__
+#else
+#define IF_NI(...)
+#endif
+
+#if HAVE_NEON_CRYPTO
+#define IF_NEON(...) __VA_ARGS__
+#else
+#define IF_NEON(...)
+#endif
+
+#define AES_SELECTOR_VTABLE(mode_c, id, mode_display, bits, ...) \
+ static const ssh_cipheralg * \
+ ssh_aes ## bits ## _ ## mode_c ## _impls[] = { \
+ IF_NI(&ssh_aes ## bits ## _ ## mode_c ## _ni,) \
+ IF_NEON(&ssh_aes ## bits ## _ ## mode_c ## _neon,) \
+ &ssh_aes ## bits ## _ ## mode_c ## _sw, \
+ NULL, \
+ }; \
+ const ssh_cipheralg ssh_aes ## bits ## _ ## mode_c = { \
+ .new = aes_select, \
+ .ssh2_id = id, \
+ .blksize = 16, \
+ .real_keybits = bits, \
+ .padded_keybytes = bits/8, \
+ .text_name = "AES-" #bits " " mode_display \
+ " (dummy selector vtable)", \
+ .extra = ssh_aes ## bits ## _ ## mode_c ## _impls, \
+ __VA_ARGS__ \
+ }
+
+AES_SELECTOR_VTABLE(cbc, "aes128-cbc", "CBC", 128, );
+AES_SELECTOR_VTABLE(cbc, "aes192-cbc", "CBC", 192, );
+AES_SELECTOR_VTABLE(cbc, "aes256-cbc", "CBC", 256, );
+AES_SELECTOR_VTABLE(sdctr, "aes128-ctr", "SDCTR", 128, );
+AES_SELECTOR_VTABLE(sdctr, "aes192-ctr", "SDCTR", 192, );
+AES_SELECTOR_VTABLE(sdctr, "aes256-ctr", "SDCTR", 256, );
+AES_SELECTOR_VTABLE(gcm, "aes128-gcm@openssh.com", "GCM", 128,
+ .required_mac = &ssh2_aesgcm_mac);
+AES_SELECTOR_VTABLE(gcm, "aes256-gcm@openssh.com", "GCM", 256,
+ .required_mac = &ssh2_aesgcm_mac);
+
+/* 192-bit AES-GCM is included only so that testcrypt can run standard
+ * test vectors against it. OpenSSH doesn't define a protocol id for
+ * it. Hence setting its ssh2_id to NULL here, and more importantly,
+ * leaving it out of aesgcm_list[] below. */
+AES_SELECTOR_VTABLE(gcm, NULL, "GCM", 192,
+ .required_mac = &ssh2_aesgcm_mac);
+
+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,
+ .text_name = "AES-256 CBC (dummy selector vtable)",
+ .extra = ssh_aes256_cbc_impls,
+};
+
+static const ssh_cipheralg *const aes_list[] = {
+ &ssh_aes256_sdctr,
+ &ssh_aes256_cbc,
+ &ssh_rijndael_lysator,
+ &ssh_aes192_sdctr,
+ &ssh_aes192_cbc,
+ &ssh_aes128_sdctr,
+ &ssh_aes128_cbc,
+};
+
+const ssh2_ciphers ssh2_aes = { lenof(aes_list), aes_list };
+
+static const ssh_cipheralg *const aesgcm_list[] = {
+ /* OpenSSH only defines protocol ids for 128- and 256-bit AES-GCM,
+ * not 192-bit. */
+ &ssh_aes128_gcm,
+ &ssh_aes256_gcm,
+};
+
+const ssh2_ciphers ssh2_aesgcm = { lenof(aesgcm_list), aesgcm_list };
diff --git a/crypto/aes-sw.c b/crypto/aes-sw.c
new file mode 100644
index 00000000..aaa3c475
--- /dev/null
+++ b/crypto/aes-sw.c
@@ -0,0 +1,1133 @@
+/*
+ * Software implementation of AES.
+ *
+ * This implementation uses a bit-sliced representation. Instead of
+ * the obvious approach of storing the cipher state so that each byte
+ * (or field element, or entry in the cipher matrix) occupies 8
+ * contiguous bits in a machine integer somewhere, we organise the
+ * cipher state as an array of 8 integers, in such a way that each
+ * logical byte of the cipher state occupies one bit in each integer,
+ * all at the same position. This allows us to do parallel logic on
+ * all bytes of the state by doing bitwise operations between the 8
+ * integers; in particular, the S-box (SubBytes) lookup is done this
+ * way, which takes about 110 operations - but for those 110 bitwise
+ * ops you get 64 S-box lookups, not just one.
+ */
+
+#include "ssh.h"
+#include "aes.h"
+#include "mpint_i.h" /* we reuse the BignumInt system */
+
+static bool aes_sw_available(void)
+{
+ /* Software AES is always available */
+ return true;
+}
+
+#define SLICE_PARALLELISM (BIGNUM_INT_BYTES / 2)
+
+#ifdef BITSLICED_DEBUG
+/* Dump function that undoes the bitslicing transform, so you can see
+ * the logical data represented by a set of slice words. */
+static inline void dumpslices_uint16_t(
+ const char *prefix, const uint16_t slices[8])
+{
+ printf("%-30s", prefix);
+ for (unsigned byte = 0; byte < 16; byte++) {
+ unsigned byteval = 0;
+ for (unsigned bit = 0; bit < 8; bit++)
+ byteval |= (1 & (slices[bit] >> byte)) << bit;
+ printf("%02x", byteval);
+ }
+ printf("\n");
+}
+
+static inline void dumpslices_BignumInt(
+ const char *prefix, const BignumInt slices[8])
+{
+ printf("%-30s", prefix);
+ for (unsigned iter = 0; iter < SLICE_PARALLELISM; iter++) {
+ for (unsigned byte = 0; byte < 16; byte++) {
+ unsigned byteval = 0;
+ for (unsigned bit = 0; bit < 8; bit++)
+ byteval |= (1 & (slices[bit] >> (iter*16+byte))) << bit;
+ printf("%02x", byteval);
+ }
+ if (iter+1 < SLICE_PARALLELISM)
+ printf(" ");
+ }
+ printf("\n");
+}
+#else
+#define dumpslices_uintN_t(prefix, slices) ((void)0)
+#define dumpslices_BignumInt(prefix, slices) ((void)0)
+#endif
+
+/* -----
+ * Bit-slicing transformation: convert between an array of 16 uint8_t
+ * and an array of 8 uint16_t, so as to interchange the bit index
+ * within each element and the element index within the array. (That
+ * is, bit j of input[i] == bit i of output[j].
+ */
+
+#define SWAPWORDS(shift) do \
+ { \
+ uint64_t mask = ~(uint64_t)0 / ((1ULL << shift) + 1); \
+ uint64_t diff = ((i0 >> shift) ^ i1) & mask; \
+ i0 ^= diff << shift; \
+ i1 ^= diff; \
+ } while (0)
+
+#define SWAPINWORD(i, bigshift, smallshift) do \
+ { \
+ uint64_t mask = ~(uint64_t)0; \
+ mask /= ((1ULL << bigshift) + 1); \
+ mask /= ((1ULL << smallshift) + 1); \
+ mask <<= smallshift; \
+ unsigned shift = bigshift - smallshift; \
+ uint64_t diff = ((i >> shift) ^ i) & mask; \
+ i ^= diff ^ (diff << shift); \
+ } while (0)
+
+#define TO_BITSLICES(slices, bytes, uintN_t, assign_op, shift) do \
+ { \
+ uint64_t i0 = GET_64BIT_LSB_FIRST(bytes); \
+ uint64_t i1 = GET_64BIT_LSB_FIRST(bytes + 8); \
+ SWAPINWORD(i0, 8, 1); \
+ SWAPINWORD(i1, 8, 1); \
+ SWAPINWORD(i0, 16, 2); \
+ SWAPINWORD(i1, 16, 2); \
+ SWAPINWORD(i0, 32, 4); \
+ SWAPINWORD(i1, 32, 4); \
+ SWAPWORDS(8); \
+ slices[0] assign_op (uintN_t)((i0 >> 0) & 0xFFFF) << (shift); \
+ slices[2] assign_op (uintN_t)((i0 >> 16) & 0xFFFF) << (shift); \
+ slices[4] assign_op (uintN_t)((i0 >> 32) & 0xFFFF) << (shift); \
+ slices[6] assign_op (uintN_t)((i0 >> 48) & 0xFFFF) << (shift); \
+ slices[1] assign_op (uintN_t)((i1 >> 0) & 0xFFFF) << (shift); \
+ slices[3] assign_op (uintN_t)((i1 >> 16) & 0xFFFF) << (shift); \
+ slices[5] assign_op (uintN_t)((i1 >> 32) & 0xFFFF) << (shift); \
+ slices[7] assign_op (uintN_t)((i1 >> 48) & 0xFFFF) << (shift); \
+ } while (0)
+
+#define FROM_BITSLICES(bytes, slices, shift) do \
+ { \
+ uint64_t i1 = ((slices[7] >> (shift)) & 0xFFFF); \
+ i1 = (i1 << 16) | ((slices[5] >> (shift)) & 0xFFFF); \
+ i1 = (i1 << 16) | ((slices[3] >> (shift)) & 0xFFFF); \
+ i1 = (i1 << 16) | ((slices[1] >> (shift)) & 0xFFFF); \
+ uint64_t i0 = ((slices[6] >> (shift)) & 0xFFFF); \
+ i0 = (i0 << 16) | ((slices[4] >> (shift)) & 0xFFFF); \
+ i0 = (i0 << 16) | ((slices[2] >> (shift)) & 0xFFFF); \
+ i0 = (i0 << 16) | ((slices[0] >> (shift)) & 0xFFFF); \
+ SWAPWORDS(8); \
+ SWAPINWORD(i0, 32, 4); \
+ SWAPINWORD(i1, 32, 4); \
+ SWAPINWORD(i0, 16, 2); \
+ SWAPINWORD(i1, 16, 2); \
+ SWAPINWORD(i0, 8, 1); \
+ SWAPINWORD(i1, 8, 1); \
+ PUT_64BIT_LSB_FIRST(bytes, i0); \
+ PUT_64BIT_LSB_FIRST((bytes) + 8, i1); \
+ } while (0)
+
+/* -----
+ * Some macros that will be useful repeatedly.
+ */
+
+/* Iterate a unary transformation over all 8 slices. */
+#define ITERATE(MACRO, output, input, uintN_t) do \
+ { \
+ MACRO(output[0], input[0], uintN_t); \
+ MACRO(output[1], input[1], uintN_t); \
+ MACRO(output[2], input[2], uintN_t); \
+ MACRO(output[3], input[3], uintN_t); \
+ MACRO(output[4], input[4], uintN_t); \
+ MACRO(output[5], input[5], uintN_t); \
+ MACRO(output[6], input[6], uintN_t); \
+ MACRO(output[7], input[7], uintN_t); \
+ } while (0)
+
+/* Simply add (i.e. XOR) two whole sets of slices together. */
+#define BITSLICED_ADD(output, lhs, rhs) do \
+ { \
+ output[0] = lhs[0] ^ rhs[0]; \
+ output[1] = lhs[1] ^ rhs[1]; \
+ output[2] = lhs[2] ^ rhs[2]; \
+ output[3] = lhs[3] ^ rhs[3]; \
+ output[4] = lhs[4] ^ rhs[4]; \
+ output[5] = lhs[5] ^ rhs[5]; \
+ output[6] = lhs[6] ^ rhs[6]; \
+ output[7] = lhs[7] ^ rhs[7]; \
+ } while (0)
+
+/* -----
+ * The AES S-box, in pure bitwise logic so that it can be run in
+ * parallel on whole words full of bit-sliced field elements.
+ *
+ * Source: 'A new combinational logic minimization technique with
+ * applications to cryptology', https://eprint.iacr.org/2009/191
+ *
+ * As a minor speed optimisation, I use a modified version of the
+ * S-box which omits the additive constant 0x63, i.e. this S-box
+ * consists of only the field inversion and linear map components.
+ * Instead, the addition of the constant is deferred until after the
+ * subsequent ShiftRows and MixColumns stages, so that it happens at
+ * the same time as adding the next round key - and then we just make
+ * it _part_ of the round key, so it doesn't cost any extra
+ * instructions to add.
+ *
+ * (Obviously adding a constant to each byte commutes with ShiftRows,
+ * which only permutes the bytes. It also commutes with MixColumns:
+ * that's not quite so obvious, but since the effect of MixColumns is
+ * to multiply a constant polynomial M into each column, it is obvious
+ * that adding some polynomial K and then multiplying by M is
+ * equivalent to multiplying by M and then adding the product KM. And
+ * in fact, since the coefficients of M happen to sum to 1, it turns
+ * out that KM = K, so we don't even have to change the constant when
+ * we move it to the far side of MixColumns.)
+ *
+ * Of course, one knock-on effect of this is that the use of the S-box
+ * *during* key setup has to be corrected by manually adding on the
+ * constant afterwards!
+ */
+
+/* Initial linear transformation for the forward S-box, from Fig 2 of
+ * the paper. */
+#define SBOX_FORWARD_TOP_TRANSFORM(input, uintN_t) \
+ uintN_t y14 = input[4] ^ input[2]; \
+ uintN_t y13 = input[7] ^ input[1]; \
+ uintN_t y9 = input[7] ^ input[4]; \
+ uintN_t y8 = input[7] ^ input[2]; \
+ uintN_t t0 = input[6] ^ input[5]; \
+ uintN_t y1 = t0 ^ input[0]; \
+ uintN_t y4 = y1 ^ input[4]; \
+ uintN_t y12 = y13 ^ y14; \
+ uintN_t y2 = y1 ^ input[7]; \
+ uintN_t y5 = y1 ^ input[1]; \
+ uintN_t y3 = y5 ^ y8; \
+ uintN_t t1 = input[3] ^ y12; \
+ uintN_t y15 = t1 ^ input[2]; \
+ uintN_t y20 = t1 ^ input[6]; \
+ uintN_t y6 = y15 ^ input[0]; \
+ uintN_t y10 = y15 ^ t0; \
+ uintN_t y11 = y20 ^ y9; \
+ uintN_t y7 = input[0] ^ y11; \
+ uintN_t y17 = y10 ^ y11; \
+ uintN_t y19 = y10 ^ y8; \
+ uintN_t y16 = t0 ^ y11; \
+ uintN_t y21 = y13 ^ y16; \
+ uintN_t y18 = input[7] ^ y16; \
+ /* Make a copy of input[0] under a new name, because the core
+ * will refer to it, and in the inverse version of the S-box
+ * the corresponding value will be one of the calculated ones
+ * and not in input[0] itself. */ \
+ uintN_t i0 = input[0]; \
+ /* end */
+
+/* Core nonlinear component, from Fig 3 of the paper. */
+#define SBOX_CORE(uintN_t) \
+ uintN_t t2 = y12 & y15; \
+ uintN_t t3 = y3 & y6; \
+ uintN_t t4 = t3 ^ t2; \
+ uintN_t t5 = y4 & i0; \
+ uintN_t t6 = t5 ^ t2; \
+ uintN_t t7 = y13 & y16; \
+ uintN_t t8 = y5 & y1; \
+ uintN_t t9 = t8 ^ t7; \
+ uintN_t t10 = y2 & y7; \
+ uintN_t t11 = t10 ^ t7; \
+ uintN_t t12 = y9 & y11; \
+ uintN_t t13 = y14 & y17; \
+ uintN_t t14 = t13 ^ t12; \
+ uintN_t t15 = y8 & y10; \
+ uintN_t t16 = t15 ^ t12; \
+ uintN_t t17 = t4 ^ t14; \
+ uintN_t t18 = t6 ^ t16; \
+ uintN_t t19 = t9 ^ t14; \
+ uintN_t t20 = t11 ^ t16; \
+ uintN_t t21 = t17 ^ y20; \
+ uintN_t t22 = t18 ^ y19; \
+ uintN_t t23 = t19 ^ y21; \
+ uintN_t t24 = t20 ^ y18; \
+ uintN_t t25 = t21 ^ t22; \
+ uintN_t t26 = t21 & t23; \
+ uintN_t t27 = t24 ^ t26; \
+ uintN_t t28 = t25 & t27; \
+ uintN_t t29 = t28 ^ t22; \
+ uintN_t t30 = t23 ^ t24; \
+ uintN_t t31 = t22 ^ t26; \
+ uintN_t t32 = t31 & t30; \
+ uintN_t t33 = t32 ^ t24; \
+ uintN_t t34 = t23 ^ t33; \
+ uintN_t t35 = t27 ^ t33; \
+ uintN_t t36 = t24 & t35; \
+ uintN_t t37 = t36 ^ t34; \
+ uintN_t t38 = t27 ^ t36; \
+ uintN_t t39 = t29 & t38; \
+ uintN_t t40 = t25 ^ t39; \
+ uintN_t t41 = t40 ^ t37; \
+ uintN_t t42 = t29 ^ t33; \
+ uintN_t t43 = t29 ^ t40; \
+ uintN_t t44 = t33 ^ t37; \
+ uintN_t t45 = t42 ^ t41; \
+ uintN_t z0 = t44 & y15; \
+ uintN_t z1 = t37 & y6; \
+ uintN_t z2 = t33 & i0; \
+ uintN_t z3 = t43 & y16; \
+ uintN_t z4 = t40 & y1; \
+ uintN_t z5 = t29 & y7; \
+ uintN_t z6 = t42 & y11; \
+ uintN_t z7 = t45 & y17; \
+ uintN_t z8 = t41 & y10; \
+ uintN_t z9 = t44 & y12; \
+ uintN_t z10 = t37 & y3; \
+ uintN_t z11 = t33 & y4; \
+ uintN_t z12 = t43 & y13; \
+ uintN_t z13 = t40 & y5; \
+ uintN_t z14 = t29 & y2; \
+ uintN_t z15 = t42 & y9; \
+ uintN_t z16 = t45 & y14; \
+ uintN_t z17 = t41 & y8; \
+ /* end */
+
+/* Final linear transformation for the forward S-box, from Fig 4 of
+ * the paper. */
+#define SBOX_FORWARD_BOTTOM_TRANSFORM(output, uintN_t) \
+ uintN_t t46 = z15 ^ z16; \
+ uintN_t t47 = z10 ^ z11; \
+ uintN_t t48 = z5 ^ z13; \
+ uintN_t t49 = z9 ^ z10; \
+ uintN_t t50 = z2 ^ z12; \
+ uintN_t t51 = z2 ^ z5; \
+ uintN_t t52 = z7 ^ z8; \
+ uintN_t t53 = z0 ^ z3; \
+ uintN_t t54 = z6 ^ z7; \
+ uintN_t t55 = z16 ^ z17; \
+ uintN_t t56 = z12 ^ t48; \
+ uintN_t t57 = t50 ^ t53; \
+ uintN_t t58 = z4 ^ t46; \
+ uintN_t t59 = z3 ^ t54; \
+ uintN_t t60 = t46 ^ t57; \
+ uintN_t t61 = z14 ^ t57; \
+ uintN_t t62 = t52 ^ t58; \
+ uintN_t t63 = t49 ^ t58; \
+ uintN_t t64 = z4 ^ t59; \
+ uintN_t t65 = t61 ^ t62; \
+ uintN_t t66 = z1 ^ t63; \
+ output[7] = t59 ^ t63; \
+ output[1] = t56 ^ t62; \
+ output[0] = t48 ^ t60; \
+ uintN_t t67 = t64 ^ t65; \
+ output[4] = t53 ^ t66; \
+ output[3] = t51 ^ t66; \
+ output[2] = t47 ^ t65; \
+ output[6] = t64 ^ output[4]; \
+ output[5] = t55 ^ t67; \
+ /* end */
+
+#define BITSLICED_SUBBYTES(output, input, uintN_t) do { \
+ SBOX_FORWARD_TOP_TRANSFORM(input, uintN_t); \
+ SBOX_CORE(uintN_t); \
+ SBOX_FORWARD_BOTTOM_TRANSFORM(output, uintN_t); \
+ } while (0)
+
+/*
+ * Initial and final linear transformations for the backward S-box. I
+ * generated these myself, by implementing the linear-transform
+ * optimisation algorithm in the paper, and applying it to the
+ * matrices calculated by _their_ top and bottom transformations, pre-
+ * and post-multiplied as appropriate by the linear map in the inverse
+ * S_box.
+ */
+#define SBOX_BACKWARD_TOP_TRANSFORM(input, uintN_t) \
+ uintN_t y5 = input[4] ^ input[6]; \
+ uintN_t y19 = input[3] ^ input[0]; \
+ uintN_t itmp8 = y5 ^ input[0]; \
+ uintN_t y4 = itmp8 ^ input[1]; \
+ uintN_t y9 = input[4] ^ input[3]; \
+ uintN_t y2 = y9 ^ y4; \
+ uintN_t itmp9 = y2 ^ input[7]; \
+ uintN_t y1 = y9 ^ input[0]; \
+ uintN_t y6 = y5 ^ input[7]; \
+ uintN_t y18 = y9 ^ input[5]; \
+ uintN_t y7 = y18 ^ y2; \
+ uintN_t y16 = y7 ^ y1; \
+ uintN_t y21 = y7 ^ input[1]; \
+ uintN_t y3 = input[4] ^ input[7]; \
+ uintN_t y13 = y16 ^ y21; \
+ uintN_t y8 = input[4] ^ y6; \
+ uintN_t y10 = y8 ^ y19; \
+ uintN_t y14 = y8 ^ y9; \
+ uintN_t y20 = itmp9 ^ input[2]; \
+ uintN_t y11 = y9 ^ y20; \
+ uintN_t i0 = y11 ^ y7; \
+ uintN_t y15 = i0 ^ y6; \
+ uintN_t y17 = y16 ^ y15; \
+ uintN_t y12 = itmp9 ^ input[3]; \
+ /* end */
+#define SBOX_BACKWARD_BOTTOM_TRANSFORM(output, uintN_t) \
+ uintN_t otmp18 = z15 ^ z6; \
+ uintN_t otmp19 = z13 ^ otmp18; \
+ uintN_t otmp20 = z12 ^ otmp19; \
+ uintN_t otmp21 = z16 ^ otmp20; \
+ uintN_t otmp22 = z8 ^ otmp21; \
+ uintN_t otmp23 = z0 ^ otmp22; \
+ uintN_t otmp24 = otmp22 ^ z3; \
+ uintN_t otmp25 = otmp24 ^ z4; \
+ uintN_t otmp26 = otmp25 ^ z2; \
+ uintN_t otmp27 = z1 ^ otmp26; \
+ uintN_t otmp28 = z14 ^ otmp27; \
+ uintN_t otmp29 = otmp28 ^ z10; \
+ output[4] = z2 ^ otmp23; \
+ output[7] = z5 ^ otmp24; \
+ uintN_t otmp30 = z11 ^ otmp29; \
+ output[5] = z13 ^ otmp30; \
+ uintN_t otmp31 = otmp25 ^ z8; \
+ output[1] = z7 ^ otmp31; \
+ uintN_t otmp32 = z11 ^ z9; \
+ uintN_t otmp33 = z17 ^ otmp32; \
+ uintN_t otmp34 = otmp30 ^ otmp33; \
+ output[0] = z15 ^ otmp33; \
+ uintN_t otmp35 = z12 ^ otmp34; \
+ output[6] = otmp35 ^ z16; \
+ uintN_t otmp36 = z1 ^ otmp23; \
+ uintN_t otmp37 = z5 ^ otmp36; \
+ output[2] = z4 ^ otmp37; \
+ uintN_t otmp38 = z11 ^ output[1]; \
+ uintN_t otmp39 = z2 ^ otmp38; \
+ uintN_t otmp40 = z17 ^ otmp39; \
+ uintN_t otmp41 = z0 ^ otmp40; \
+ uintN_t otmp42 = z5 ^ otmp41; \
+ uintN_t otmp43 = otmp42 ^ z10; \
+ uintN_t otmp44 = otmp43 ^ z3; \
+ output[3] = otmp44 ^ z16; \
+ /* end */
+
+#define BITSLICED_INVSUBBYTES(output, input, uintN_t) do { \
+ SBOX_BACKWARD_TOP_TRANSFORM(input, uintN_t); \
+ SBOX_CORE(uintN_t); \
+ SBOX_BACKWARD_BOTTOM_TRANSFORM(output, uintN_t); \
+ } while (0)
+
+
+/* -----
+ * The ShiftRows transformation. This operates independently on each
+ * bit slice.
+ */
+
+#define SINGLE_BITSLICE_SHIFTROWS(output, input, uintN_t) do \
+ { \
+ uintN_t mask, mask2, mask3, diff, x = (input); \
+ /* Rotate rows 2 and 3 by 16 bits */ \
+ mask = 0x00CC * (((uintN_t)~(uintN_t)0) / 0xFFFF); \
+ diff = ((x >> 8) ^ x) & mask; \
+ x ^= diff ^ (diff << 8); \
+ /* Rotate rows 1 and 3 by 8 bits */ \
+ mask = 0x0AAA * (((uintN_t)~(uintN_t)0) / 0xFFFF); \
+ mask2 = 0xA000 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \
+ mask3 = 0x5555 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \
+ x = ((x >> 4) & mask) | ((x << 12) & mask2) | (x & mask3); \
+ /* Write output */ \
+ (output) = x; \
+ } while (0)
+
+#define SINGLE_BITSLICE_INVSHIFTROWS(output, input, uintN_t) do \
+ { \
+ uintN_t mask, mask2, mask3, diff, x = (input); \
+ /* Rotate rows 2 and 3 by 16 bits */ \
+ mask = 0x00CC * (((uintN_t)~(uintN_t)0) / 0xFFFF); \
+ diff = ((x >> 8) ^ x) & mask; \
+ x ^= diff ^ (diff << 8); \
+ /* Rotate rows 1 and 3 by 8 bits, the opposite way to ShiftRows */ \
+ mask = 0x000A * (((uintN_t)~(uintN_t)0) / 0xFFFF); \
+ mask2 = 0xAAA0 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \
+ mask3 = 0x5555 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \
+ x = ((x >> 12) & mask) | ((x << 4) & mask2) | (x & mask3); \
+ /* Write output */ \
+ (output) = x; \
+ } while (0)
+
+#define BITSLICED_SHIFTROWS(output, input, uintN_t) do \
+ { \
+ ITERATE(SINGLE_BITSLICE_SHIFTROWS, output, input, uintN_t); \
+ } while (0)
+
+#define BITSLICED_INVSHIFTROWS(output, input, uintN_t) do \
+ { \
+ ITERATE(SINGLE_BITSLICE_INVSHIFTROWS, output, input, uintN_t); \
+ } while (0)
+
+/* -----
+ * The MixColumns transformation. This has to operate on all eight bit
+ * slices at once, and also passes data back and forth between the
+ * bits in an adjacent group of 4 within each slice.
+ *
+ * Notation: let F = GF(2)[X]/<X^8+X^4+X^3+X+1> be the finite field
+ * used in AES, and let R = F[Y]/<Y^4+1> be the ring whose elements
+ * represent the possible contents of a column of the matrix. I use X
+ * and Y below in those senses, i.e. X is the value in F that
+ * represents the byte 0x02, and Y is the value in R that cycles the
+ * four bytes around by one if you multiply by it.
+ */
+
+/* Multiply every column by Y^3, i.e. cycle it round one place to the
+ * right. Operates on one bit slice at a time; you have to wrap it in
+ * ITERATE to affect all the data at once. */
+#define BITSLICED_MUL_BY_Y3(output, input, uintN_t) do \
+ { \
+ uintN_t mask, mask2, x; \
+ mask = 0x8 * (((uintN_t)~(uintN_t)0) / 0xF); \
+ mask2 = 0x7 * (((uintN_t)~(uintN_t)0) / 0xF); \
+ x = input; \
+ output = ((x << 3) & mask) ^ ((x >> 1) & mask2); \
+ } while (0)
+
+/* Multiply every column by Y^2. */
+#define BITSLICED_MUL_BY_Y2(output, input, uintN_t) do \
+ { \
+ uintN_t mask, mask2, x; \
+ mask = 0xC * (((uintN_t)~(uintN_t)0) / 0xF); \
+ mask2 = 0x3 * (((uintN_t)~(uintN_t)0) / 0xF); \
+ x = input; \
+ output = ((x << 2) & mask) ^ ((x >> 2) & mask2); \
+ } while (0)
+
+#define BITSLICED_MUL_BY_1_Y3(output, input, uintN_t) do \
+ { \
+ uintN_t tmp = input; \
+ BITSLICED_MUL_BY_Y3(tmp, input, uintN_t); \
+ output = input ^ tmp; \
+ } while (0)
+
+/* Multiply every column by 1+Y^2. */
+#define BITSLICED_MUL_BY_1_Y2(output, input, uintN_t) do \
+ { \
+ uintN_t tmp = input; \
+ BITSLICED_MUL_BY_Y2(tmp, input, uintN_t); \
+ output = input ^ tmp; \
+ } while (0)
+
+/* Multiply every field element by X. This has to feed data between
+ * slices, so it does the whole job in one go without needing ITERATE. */
+#define BITSLICED_MUL_BY_X(output, input, uintN_t) do \
+ { \
+ uintN_t bit7 = input[7]; \
+ output[7] = input[6]; \
+ output[6] = input[5]; \
+ output[5] = input[4]; \
+ output[4] = input[3] ^ bit7; \
+ output[3] = input[2] ^ bit7; \
+ output[2] = input[1]; \
+ output[1] = input[0] ^ bit7; \
+ output[0] = bit7; \
+ } while (0)
+
+/*
+ * The MixColumns constant is
+ * M = X + Y + Y^2 + (X+1)Y^3
+ * which we construct by rearranging it into
+ * M = 1 + (1+Y^3) [ X + (1+Y^2) ]
+ */
+#define BITSLICED_MIXCOLUMNS(output, input, uintN_t) do \
+ { \
+ uintN_t a[8], aX[8], b[8]; \
+ /* a = input * (1+Y^3) */ \
+ ITERATE(BITSLICED_MUL_BY_1_Y3, a, input, uintN_t); \
+ /* aX = a * X */ \
+ BITSLICED_MUL_BY_X(aX, a, uintN_t); \
+ /* b = a * (1+Y^2) = input * (1+Y+Y^2+Y^3) */ \
+ ITERATE(BITSLICED_MUL_BY_1_Y2, b, a, uintN_t); \
+ /* output = input + aX + b (reusing a as a temp */ \
+ BITSLICED_ADD(a, aX, b); \
+ BITSLICED_ADD(output, input, a); \
+ } while (0)
+
+/*
+ * The InvMixColumns constant, written out longhand, is
+ * I = (X^3+X^2+X) + (X^3+1)Y + (X^3+X^2+1)Y^2 + (X^3+X+1)Y^3
+ * We represent this as
+ * I = (X^3+X^2+X+1)(Y^3+Y^2+Y+1) + 1 + X(Y+Y^2) + X^2(Y+Y^3)
+ */
+#define BITSLICED_INVMIXCOLUMNS(output, input, uintN_t) do \
+ { \
+ /* We need input * X^i for i=1,...,3 */ \
+ uintN_t X[8], X2[8], X3[8]; \
+ BITSLICED_MUL_BY_X(X, input, uintN_t); \
+ BITSLICED_MUL_BY_X(X2, X, uintN_t); \
+ BITSLICED_MUL_BY_X(X3, X2, uintN_t); \
+ /* Sum them all and multiply by 1+Y+Y^2+Y^3. */ \
+ uintN_t S[8]; \
+ BITSLICED_ADD(S, input, X); \
+ BITSLICED_ADD(S, S, X2); \
+ BITSLICED_ADD(S, S, X3); \
+ ITERATE(BITSLICED_MUL_BY_1_Y3, S, S, uintN_t); \
+ ITERATE(BITSLICED_MUL_BY_1_Y2, S, S, uintN_t); \
+ /* Compute the X(Y+Y^2) term. */ \
+ uintN_t A[8]; \
+ ITERATE(BITSLICED_MUL_BY_1_Y3, A, X, uintN_t); \
+ ITERATE(BITSLICED_MUL_BY_Y2, A, A, uintN_t); \
+ /* Compute the X^2(Y+Y^3) term. */ \
+ uintN_t B[8]; \
+ ITERATE(BITSLICED_MUL_BY_1_Y2, B, X2, uintN_t); \
+ ITERATE(BITSLICED_MUL_BY_Y3, B, B, uintN_t); \
+ /* And add all the pieces together. */ \
+ BITSLICED_ADD(S, S, input); \
+ BITSLICED_ADD(S, S, A); \
+ BITSLICED_ADD(output, S, B); \
+ } while (0)
+
+/* -----
+ * Put it all together into a cipher round.
+ */
+
+/* Dummy macro to get rid of the MixColumns in the final round. */
+#define NO_MIXCOLUMNS(out, in, uintN_t) do {} while (0)
+
+#define ENCRYPT_ROUND_FN(suffix, uintN_t, mixcol_macro) \
+ static void aes_sliced_round_e_##suffix( \
+ uintN_t output[8], const uintN_t input[8], const uintN_t roundkey[8]) \
+ { \
+ BITSLICED_SUBBYTES(output, input, uintN_t); \
+ BITSLICED_SHIFTROWS(output, output, uintN_t); \
+ mixcol_macro(output, output, uintN_t); \
+ BITSLICED_ADD(output, output, roundkey); \
+ }
+
+ENCRYPT_ROUND_FN(serial, uint16_t, BITSLICED_MIXCOLUMNS)
+ENCRYPT_ROUND_FN(serial_last, uint16_t, NO_MIXCOLUMNS)
+ENCRYPT_ROUND_FN(parallel, BignumInt, BITSLICED_MIXCOLUMNS)
+ENCRYPT_ROUND_FN(parallel_last, BignumInt, NO_MIXCOLUMNS)
+
+#define DECRYPT_ROUND_FN(suffix, uintN_t, mixcol_macro) \
+ static void aes_sliced_round_d_##suffix( \
+ uintN_t output[8], const uintN_t input[8], const uintN_t roundkey[8]) \
+ { \
+ BITSLICED_ADD(output, input, roundkey); \
+ mixcol_macro(output, output, uintN_t); \
+ BITSLICED_INVSUBBYTES(output, output, uintN_t); \
+ BITSLICED_INVSHIFTROWS(output, output, uintN_t); \
+ }
+
+#if 0 /* no cipher mode we support requires serial decryption */
+DECRYPT_ROUND_FN(serial, uint16_t, BITSLICED_INVMIXCOLUMNS)
+DECRYPT_ROUND_FN(serial_first, uint16_t, NO_MIXCOLUMNS)
+#endif
+DECRYPT_ROUND_FN(parallel, BignumInt, BITSLICED_INVMIXCOLUMNS)
+DECRYPT_ROUND_FN(parallel_first, BignumInt, NO_MIXCOLUMNS)
+
+/* -----
+ * Key setup function.
+ */
+
+typedef struct aes_sliced_key aes_sliced_key;
+struct aes_sliced_key {
+ BignumInt roundkeys_parallel[MAXROUNDKEYS * 8];
+ uint16_t roundkeys_serial[MAXROUNDKEYS * 8];
+ unsigned rounds;
+};
+
+static void aes_sliced_key_setup(
+ aes_sliced_key *sk, const void *vkey, size_t keybits)
+{
+ const unsigned char *key = (const unsigned char *)vkey;
+
+ size_t key_words = keybits / 32;
+ sk->rounds = key_words + 6;
+ size_t sched_words = (sk->rounds + 1) * 4;
+
+ unsigned rconpos = 0;
+
+ uint16_t *outslices = sk->roundkeys_serial;
+ unsigned outshift = 0;
+
+ memset(sk->roundkeys_serial, 0, sizeof(sk->roundkeys_serial));
+
+ uint8_t inblk[16];
+ memset(inblk, 0, 16);
+ uint16_t slices[8];
+
+ for (size_t i = 0; i < sched_words; i++) {
+ /*
+ * Prepare a word of round key in the low 4 bits of each
+ * integer in slices[].
+ */
+ if (i < key_words) {
+ memcpy(inblk, key + 4*i, 4);
+ TO_BITSLICES(slices, inblk, uint16_t, =, 0);
+ } else {
+ unsigned wordindex, bitshift;
+ uint16_t *prevslices;
+
+ /* Fetch the (i-1)th key word */
+ wordindex = i-1;
+ bitshift = 4 * (wordindex & 3);
+ prevslices = sk->roundkeys_serial + 8 * (wordindex >> 2);
+ for (size_t i = 0; i < 8; i++)
+ slices[i] = prevslices[i] >> bitshift;
+
+ /* Decide what we're doing in this expansion stage */
+ bool rotate_and_round_constant = (i % key_words == 0);
+ bool sub = rotate_and_round_constant ||
+ (key_words == 8 && i % 8 == 4);
+
+ if (rotate_and_round_constant) {
+ for (size_t i = 0; i < 8; i++)
+ slices[i] = ((slices[i] << 3) | (slices[i] >> 1)) & 0xF;
+ }
+
+ if (sub) {
+ /* Apply the SubBytes transform to the key word. But
+ * here we need to apply the _full_ SubBytes from the
+ * spec, including the constant which our S-box leaves
+ * out. */
+ BITSLICED_SUBBYTES(slices, slices, uint16_t);
+ slices[0] ^= 0xFFFF;
+ slices[1] ^= 0xFFFF;
+ slices[5] ^= 0xFFFF;
+ slices[6] ^= 0xFFFF;
+ }
+
+ if (rotate_and_round_constant) {
+ assert(rconpos < lenof(aes_key_setup_round_constants));
+ uint8_t rcon = aes_key_setup_round_constants[rconpos++];
+ for (size_t i = 0; i < 8; i++)
+ slices[i] ^= 1 & (rcon >> i);
+ }
+
+ /* Combine with the (i-Nk)th key word */
+ wordindex = i - key_words;
+ bitshift = 4 * (wordindex & 3);
+ prevslices = sk->roundkeys_serial + 8 * (wordindex >> 2);
+ for (size_t i = 0; i < 8; i++)
+ slices[i] ^= prevslices[i] >> bitshift;
+ }
+
+ /*
+ * Now copy it into sk.
+ */
+ for (unsigned b = 0; b < 8; b++)
+ outslices[b] |= (slices[b] & 0xF) << outshift;
+ outshift += 4;
+ if (outshift == 16) {
+ outshift = 0;
+ outslices += 8;
+ }
+ }
+
+ smemclr(inblk, sizeof(inblk));
+ smemclr(slices, sizeof(slices));
+
+ /*
+ * Add the S-box constant to every round key after the first one,
+ * compensating for it being left out in the main cipher.
+ */
+ for (size_t i = 8; i < 8 * (sched_words/4); i += 8) {
+ sk->roundkeys_serial[i+0] ^= 0xFFFF;
+ sk->roundkeys_serial[i+1] ^= 0xFFFF;
+ sk->roundkeys_serial[i+5] ^= 0xFFFF;
+ sk->roundkeys_serial[i+6] ^= 0xFFFF;
+ }
+
+ /*
+ * Replicate that set of round keys into larger integers for the
+ * parallel versions of the cipher.
+ */
+ for (size_t i = 0; i < 8 * (sched_words / 4); i++) {
+ sk->roundkeys_parallel[i] = sk->roundkeys_serial[i] *
+ ((BignumInt)~(BignumInt)0 / 0xFFFF);
+ }
+}
+
+/* -----
+ * The full cipher primitive, including transforming the input and
+ * output to/from bit-sliced form.
+ */
+
+#define ENCRYPT_FN(suffix, uintN_t, nblocks) \
+ static void aes_sliced_e_##suffix( \
+ uint8_t *output, const uint8_t *input, const aes_sliced_key *sk) \
+ { \
+ uintN_t state[8]; \
+ TO_BITSLICES(state, input, uintN_t, =, 0); \
+ for (unsigned i = 1; i < nblocks; i++) { \
+ input += 16; \
+ TO_BITSLICES(state, input, uintN_t, |=, i*16); \
+ } \
+ const uintN_t *keys = sk->roundkeys_##suffix; \
+ BITSLICED_ADD(state, state, keys); \
+ keys += 8; \
+ for (unsigned i = 0; i < sk->rounds-1; i++) { \
+ aes_sliced_round_e_##suffix(state, state, keys); \
+ keys += 8; \
+ } \
+ aes_sliced_round_e_##suffix##_last(state, state, keys); \
+ for (unsigned i = 0; i < nblocks; i++) { \
+ FROM_BITSLICES(output, state, i*16); \
+ output += 16; \
+ } \
+ }
+
+#define DECRYPT_FN(suffix, uintN_t, nblocks) \
+ static void aes_sliced_d_##suffix( \
+ uint8_t *output, const uint8_t *input, const aes_sliced_key *sk) \
+ { \
+ uintN_t state[8]; \
+ TO_BITSLICES(state, input, uintN_t, =, 0); \
+ for (unsigned i = 1; i < nblocks; i++) { \
+ input += 16; \
+ TO_BITSLICES(state, input, uintN_t, |=, i*16); \
+ } \
+ const uintN_t *keys = sk->roundkeys_##suffix + 8*sk->rounds; \
+ aes_sliced_round_d_##suffix##_first(state, state, keys); \
+ keys -= 8; \
+ for (unsigned i = 0; i < sk->rounds-1; i++) { \
+ aes_sliced_round_d_##suffix(state, state, keys); \
+ keys -= 8; \
+ } \
+ BITSLICED_ADD(state, state, keys); \
+ for (unsigned i = 0; i < nblocks; i++) { \
+ FROM_BITSLICES(output, state, i*16); \
+ output += 16; \
+ } \
+ }
+
+ENCRYPT_FN(serial, uint16_t, 1)
+#if 0 /* no cipher mode we support requires serial decryption */
+DECRYPT_FN(serial, uint16_t, 1)
+#endif
+ENCRYPT_FN(parallel, BignumInt, SLICE_PARALLELISM)
+DECRYPT_FN(parallel, BignumInt, SLICE_PARALLELISM)
+
+/* -----
+ * The SSH interface and the cipher modes.
+ */
+
+#define SDCTR_WORDS (16 / BIGNUM_INT_BYTES)
+
+typedef struct aes_sw_context aes_sw_context;
+struct aes_sw_context {
+ aes_sliced_key sk;
+ union {
+ struct {
+ /* In CBC mode, the IV is just a copy of the last seen
+ * cipher block. */
+ uint8_t prevblk[16];
+ } cbc;
+ struct {
+ /* In SDCTR mode, we keep the counter itself in a form
+ * that's easy to increment. We also use the parallel
+ * version of the core AES function, so we'll encrypt
+ * multiple counter values in one go. That won't align
+ * nicely with the sizes of data we're asked to encrypt,
+ * so we must also store a cache of the last set of
+ * keystream blocks we generated, and our current position
+ * within that cache. */
+ BignumInt counter[SDCTR_WORDS];
+ uint8_t keystream[SLICE_PARALLELISM * 16];
+ uint8_t *keystream_pos;
+ } sdctr;
+ struct {
+ /* In GCM mode, the cipher preimage consists of three
+ * sections: one fixed, one that increments per message
+ * sent and MACed, and one that increments per cipher
+ * block. */
+ uint64_t msg_counter;
+ uint32_t fixed_iv, block_counter;
+ /* But we keep the precomputed keystream chunks just like
+ * SDCTR mode. */
+ uint8_t keystream[SLICE_PARALLELISM * 16];
+ uint8_t *keystream_pos;
+ } gcm;
+ } iv;
+ ssh_cipher ciph;
+};
+
+static ssh_cipher *aes_sw_new(const ssh_cipheralg *alg)
+{
+ aes_sw_context *ctx = snew(aes_sw_context);
+ ctx->ciph.vt = alg;
+ return &ctx->ciph;
+}
+
+static void aes_sw_free(ssh_cipher *ciph)
+{
+ aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+ smemclr(ctx, sizeof(*ctx));
+ sfree(ctx);
+}
+
+static void aes_sw_setkey(ssh_cipher *ciph, const void *vkey)
+{
+ aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+ aes_sliced_key_setup(&ctx->sk, vkey, ctx->ciph.vt->real_keybits);
+}
+
+static void aes_sw_setiv_cbc(ssh_cipher *ciph, const void *iv)
+{
+ aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+ memcpy(ctx->iv.cbc.prevblk, iv, 16);
+}
+
+static void aes_sw_setiv_sdctr(ssh_cipher *ciph, const void *viv)
+{
+ aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+ const uint8_t *iv = (const uint8_t *)viv;
+
+ /* Import the initial counter value into the internal representation */
+ for (unsigned i = 0; i < SDCTR_WORDS; i++)
+ ctx->iv.sdctr.counter[i] =
+ GET_BIGNUMINT_MSB_FIRST(
+ iv + 16 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES);
+
+ /* Set keystream_pos to indicate that the keystream cache is
+ * currently empty */
+ ctx->iv.sdctr.keystream_pos =
+ ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream);
+}
+
+static void aes_sw_setiv_gcm(ssh_cipher *ciph, const void *viv)
+{
+ aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+ const uint8_t *iv = (const uint8_t *)viv;
+
+ ctx->iv.gcm.fixed_iv = GET_32BIT_MSB_FIRST(iv);
+ ctx->iv.gcm.msg_counter = GET_64BIT_MSB_FIRST(iv + 4);
+ ctx->iv.gcm.block_counter = 1;
+
+ /* Set keystream_pos to indicate that the keystream cache is
+ * currently empty */
+ ctx->iv.gcm.keystream_pos =
+ ctx->iv.gcm.keystream + sizeof(ctx->iv.gcm.keystream);
+}
+
+static void aes_sw_next_message_gcm(ssh_cipher *ciph)
+{
+ aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+
+ ctx->iv.gcm.msg_counter++;
+ ctx->iv.gcm.block_counter = 1;
+ ctx->iv.gcm.keystream_pos =
+ ctx->iv.gcm.keystream + sizeof(ctx->iv.gcm.keystream);
+}
+
+typedef void (*aes_sw_fn)(uint32_t v[4], const uint32_t *keysched);
+
+static inline void memxor16(void *vout, const void *vlhs, const void *vrhs)
+{
+ uint8_t *out = (uint8_t *)vout;
+ const uint8_t *lhs = (const uint8_t *)vlhs, *rhs = (const uint8_t *)vrhs;
+ uint64_t w;
+
+ w = GET_64BIT_LSB_FIRST(lhs);
+ w ^= GET_64BIT_LSB_FIRST(rhs);
+ PUT_64BIT_LSB_FIRST(out, w);
+ w = GET_64BIT_LSB_FIRST(lhs + 8);
+ w ^= GET_64BIT_LSB_FIRST(rhs + 8);
+ PUT_64BIT_LSB_FIRST(out + 8, w);
+}
+
+static inline void aes_cbc_sw_encrypt(
+ ssh_cipher *ciph, void *vblk, int blklen)
+{
+ aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+
+ /*
+ * CBC encryption has to be done serially, because the input to
+ * each run of the cipher includes the output from the previous
+ * run.
+ */
+
+ for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+ blk < finish; blk += 16) {
+ /*
+ * We use the IV array itself as the location for the
+ * encryption, because there's no reason not to.
+ */
+
+ /* XOR the new plaintext block into the previous cipher block */
+ memxor16(ctx->iv.cbc.prevblk, ctx->iv.cbc.prevblk, blk);
+
+ /* Run the cipher over the result, which leaves it
+ * conveniently already stored in ctx->iv */
+ aes_sliced_e_serial(
+ ctx->iv.cbc.prevblk, ctx->iv.cbc.prevblk, &ctx->sk);
+
+ /* Copy it to the output location */
+ memcpy(blk, ctx->iv.cbc.prevblk, 16);
+ }
+}
+
+static inline void aes_cbc_sw_decrypt(
+ ssh_cipher *ciph, void *vblk, int blklen)
+{
+ aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+ uint8_t *blk = (uint8_t *)vblk;
+
+ /*
+ * CBC decryption can run in parallel, because all the
+ * _ciphertext_ blocks are already available.
+ */
+
+ size_t blocks_remaining = blklen / 16;
+
+ uint8_t data[SLICE_PARALLELISM * 16];
+ /* Zeroing the data array is probably overcautious, but it avoids
+ * technically undefined behaviour from leaving it uninitialised
+ * if our very first iteration doesn't include enough cipher
+ * blocks to populate it fully */
+ memset(data, 0, sizeof(data));
+
+ while (blocks_remaining > 0) {
+ /* Number of blocks we'll handle in this iteration. If we're
+ * dealing with fewer than the maximum, it doesn't matter -
+ * it's harmless to run the full parallel cipher function
+ * anyway. */
+ size_t blocks = (blocks_remaining < SLICE_PARALLELISM ?
+ blocks_remaining : SLICE_PARALLELISM);
+
+ /* Parallel-decrypt the input, in a separate array so we still
+ * have the cipher stream available for XORing. */
+ memcpy(data, blk, 16 * blocks);
+ aes_sliced_d_parallel(data, data, &ctx->sk);
+
+ /* Write the output and update the IV */
+ for (size_t i = 0; i < blocks; i++) {
+ uint8_t *decrypted = data + 16*i;
+ uint8_t *output = blk + 16*i;
+
+ memxor16(decrypted, decrypted, ctx->iv.cbc.prevblk);
+ memcpy(ctx->iv.cbc.prevblk, output, 16);
+ memcpy(output, decrypted, 16);
+ }
+
+ /* Advance the input pointer. */
+ blk += 16 * blocks;
+ blocks_remaining -= blocks;
+ }
+
+ smemclr(data, sizeof(data));
+}
+
+static inline void aes_sdctr_sw(
+ ssh_cipher *ciph, void *vblk, int blklen)
+{
+ aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+
+ /*
+ * SDCTR encrypt/decrypt loops round one block at a time XORing
+ * the keystream into the user's data, and periodically has to run
+ * a parallel encryption operation to get more keystream.
+ */
+
+ uint8_t *keystream_end =
+ ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream);
+
+ for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+ blk < finish; blk += 16) {
+
+ if (ctx->iv.sdctr.keystream_pos == keystream_end) {
+ /*
+ * Generate some keystream.
+ */
+ for (uint8_t *block = ctx->iv.sdctr.keystream;
+ block < keystream_end; block += 16) {
+ /* Format the counter value into the buffer. */
+ for (unsigned i = 0; i < SDCTR_WORDS; i++)
+ PUT_BIGNUMINT_MSB_FIRST(
+ block + 16 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES,
+ ctx->iv.sdctr.counter[i]);
+
+ /* Increment the counter. */
+ BignumCarry carry = 1;
+ for (unsigned i = 0; i < SDCTR_WORDS; i++)
+ BignumADC(ctx->iv.sdctr.counter[i], carry,
+ ctx->iv.sdctr.counter[i], 0, carry);
+ }
+
+ /* Encrypt all those counter blocks. */
+ aes_sliced_e_parallel(ctx->iv.sdctr.keystream,
+ ctx->iv.sdctr.keystream, &ctx->sk);
+
+ /* Reset keystream_pos to the start of the buffer. */
+ ctx->iv.sdctr.keystream_pos = ctx->iv.sdctr.keystream;
+ }
+
+ memxor16(blk, blk, ctx->iv.sdctr.keystream_pos);
+ ctx->iv.sdctr.keystream_pos += 16;
+ }
+}
+
+static inline void aes_encrypt_ecb_block_sw(ssh_cipher *ciph, void *blk)
+{
+ aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+ aes_sliced_e_serial(blk, blk, &ctx->sk);
+}
+
+static inline void aes_gcm_sw(
+ ssh_cipher *ciph, void *vblk, int blklen)
+{
+ aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph);
+
+ /*
+ * GCM encrypt/decrypt looks just like SDCTR, except that the
+ * method of generating more keystream varies slightly.
+ */
+
+ uint8_t *keystream_end =
+ ctx->iv.gcm.keystream + sizeof(ctx->iv.gcm.keystream);
+
+ for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen;
+ blk < finish; blk += 16) {
+
+ if (ctx->iv.gcm.keystream_pos == keystream_end) {
+ /*
+ * Generate some keystream.
+ */
+ for (uint8_t *block = ctx->iv.gcm.keystream;
+ block < keystream_end; block += 16) {
+ /* Format the counter value into the buffer. */
+ PUT_32BIT_MSB_FIRST(block, ctx->iv.gcm.fixed_iv);
+ PUT_64BIT_MSB_FIRST(block + 4, ctx->iv.gcm.msg_counter);
+ PUT_32BIT_MSB_FIRST(block + 12, ctx->iv.gcm.block_counter);
+
+ /* Increment the counter. */
+ ctx->iv.gcm.block_counter++;
+ }
+
+ /* Encrypt all those counter blocks. */
+ aes_sliced_e_parallel(ctx->iv.gcm.keystream,
+ ctx->iv.gcm.keystream, &ctx->sk);
+
+ /* Reset keystream_pos to the start of the buffer. */
+ ctx->iv.gcm.keystream_pos = ctx->iv.gcm.keystream;
+ }
+
+ memxor16(blk, blk, ctx->iv.gcm.keystream_pos);
+ ctx->iv.gcm.keystream_pos += 16;
+ }
+}
+
+#define SW_ENC_DEC(len) \
+ static void aes##len##_sw_cbc_encrypt( \
+ ssh_cipher *ciph, void *vblk, int blklen) \
+ { aes_cbc_sw_encrypt(ciph, vblk, blklen); } \
+ static void aes##len##_sw_cbc_decrypt( \
+ ssh_cipher *ciph, void *vblk, int blklen) \
+ { aes_cbc_sw_decrypt(ciph, vblk, blklen); } \
+ static void aes##len##_sw_sdctr( \
+ ssh_cipher *ciph, void *vblk, int blklen) \
+ { aes_sdctr_sw(ciph, vblk, blklen); } \
+ static void aes##len##_sw_gcm( \
+ ssh_cipher *ciph, void *vblk, int blklen) \
+ { aes_gcm_sw(ciph, vblk, blklen); } \
+ static void aes##len##_sw_encrypt_ecb_block( \
+ ssh_cipher *ciph, void *vblk) \
+ { aes_encrypt_ecb_block_sw(ciph, vblk); }
+
+SW_ENC_DEC(128)
+SW_ENC_DEC(192)
+SW_ENC_DEC(256)
+
+AES_EXTRA(_sw);
+AES_ALL_VTABLES(_sw, "unaccelerated");
diff --git a/crypto/aes.h b/crypto/aes.h
new file mode 100644
index 00000000..cab5b989
--- /dev/null
+++ b/crypto/aes.h
@@ -0,0 +1,160 @@
+/*
+ * Definitions likely to be helpful to multiple AES implementations.
+ */
+
+/*
+ * The 'extra' structure used by AES implementations is used to
+ * include information about how to check if a given implementation is
+ * available at run time, and whether we've already checked.
+ */
+struct aes_extra_mutable;
+struct aes_extra {
+ /* Function to check availability. Might be expensive, so we don't
+ * want to call it more than once. */
+ bool (*check_available)(void);
+
+ /* Point to a writable substructure. */
+ struct aes_extra_mutable *mut;
+
+ /* Extra API function specific to AES, to encrypt a single block
+ * in ECB mode without touching the IV. Used by AES-GCM MAC
+ * setup. */
+ void (*encrypt_ecb_block)(ssh_cipher *, void *);
+};
+struct aes_extra_mutable {
+ bool checked_availability;
+ bool is_available;
+};
+static inline bool check_availability(const struct aes_extra *extra)
+{
+ if (!extra->mut->checked_availability) {
+ extra->mut->is_available = extra->check_available();
+ extra->mut->checked_availability = true;
+ }
+
+ return extra->mut->is_available;
+}
+
+/* Shared stub function for all the AES-GCM vtables. */
+void aesgcm_cipher_crypt_length(
+ ssh_cipher *cipher, void *blk, int len, unsigned long seq);
+
+/* External entry point for the encrypt_ecb_block function. */
+static inline void aes_encrypt_ecb_block(ssh_cipher *ciph, void *blk)
+{
+ const struct aes_extra *extra = ciph->vt->extra;
+ extra->encrypt_ecb_block(ciph, blk);
+}
+
+/*
+ * Macros to define vtables for AES variants. There are a lot of
+ * these, because of the cross product between cipher modes, key
+ * sizes, and assorted HW/SW implementations, so it's worth spending
+ * some effort here to reduce the boilerplate in the sub-files.
+ */
+
+#define AES_EXTRA_BITS(impl_c, bits) \
+ static struct aes_extra_mutable aes ## impl_c ## _extra_mut; \
+ static const struct aes_extra aes ## bits ## impl_c ## _extra = { \
+ .check_available = aes ## impl_c ## _available, \
+ .mut = &aes ## impl_c ## _extra_mut, \
+ .encrypt_ecb_block = &aes ## bits ## impl_c ## _encrypt_ecb_block, \
+ }
+
+#define AES_EXTRA(impl_c) \
+ AES_EXTRA_BITS(impl_c, 128); \
+ AES_EXTRA_BITS(impl_c, 192); \
+ AES_EXTRA_BITS(impl_c, 256)
+
+#define AES_CBC_VTABLE(impl_c, impl_display, bits) \
+ const ssh_cipheralg ssh_aes ## bits ## _cbc ## impl_c = { \
+ .new = aes ## impl_c ## _new, \
+ .free = aes ## impl_c ## _free, \
+ .setiv = aes ## impl_c ## _setiv_cbc, \
+ .setkey = aes ## impl_c ## _setkey, \
+ .encrypt = aes ## bits ## impl_c ## _cbc_encrypt, \
+ .decrypt = aes ## bits ## impl_c ## _cbc_decrypt, \
+ .next_message = nullcipher_next_message, \
+ .ssh2_id = "aes" #bits "-cbc", \
+ .blksize = 16, \
+ .real_keybits = bits, \
+ .padded_keybytes = bits/8, \
+ .flags = SSH_CIPHER_IS_CBC, \
+ .text_name = "AES-" #bits " CBC (" impl_display ")", \
+ .extra = &aes ## bits ## impl_c ## _extra, \
+ }
+
+#define AES_SDCTR_VTABLE(impl_c, impl_display, bits) \
+ const ssh_cipheralg ssh_aes ## bits ## _sdctr ## impl_c = { \
+ .new = aes ## impl_c ## _new, \
+ .free = aes ## impl_c ## _free, \
+ .setiv = aes ## impl_c ## _setiv_sdctr, \
+ .setkey = aes ## impl_c ## _setkey, \
+ .encrypt = aes ## bits ## impl_c ## _sdctr, \
+ .decrypt = aes ## bits ## impl_c ## _sdctr, \
+ .next_message = nullcipher_next_message, \
+ .ssh2_id = "aes" #bits "-ctr", \
+ .blksize = 16, \
+ .real_keybits = bits, \
+ .padded_keybytes = bits/8, \
+ .flags = 0, \
+ .text_name = "AES-" #bits " SDCTR (" impl_display ")", \
+ .extra = &aes ## bits ## impl_c ## _extra, \
+ }
+
+#define AES_GCM_VTABLE(impl_c, impl_display, bits) \
+ const ssh_cipheralg ssh_aes ## bits ## _gcm ## impl_c = { \
+ .new = aes ## impl_c ## _new, \
+ .free = aes ## impl_c ## _free, \
+ .setiv = aes ## impl_c ## _setiv_gcm, \
+ .setkey = aes ## impl_c ## _setkey, \
+ .encrypt = aes ## bits ## impl_c ## _gcm, \
+ .decrypt = aes ## bits ## impl_c ## _gcm, \
+ .encrypt_length = aesgcm_cipher_crypt_length, \
+ .decrypt_length = aesgcm_cipher_crypt_length, \
+ .next_message = aes ## impl_c ## _next_message_gcm, \
+ /* 192-bit AES-GCM is included only so that testcrypt can run \
+ * standard test vectors against it. OpenSSH doesn't define a \
+ * protocol id for it. So we set its ssh2_id to NULL. */ \
+ .ssh2_id = bits==192 ? NULL : "aes" #bits "-gcm@openssh.com", \
+ .blksize = 16, \
+ .real_keybits = bits, \
+ .padded_keybytes = bits/8, \
+ .flags = SSH_CIPHER_SEPARATE_LENGTH, \
+ .text_name = "AES-" #bits " GCM (" impl_display ")", \
+ .required_mac = &ssh2_aesgcm_mac, \
+ .extra = &aes ## bits ## impl_c ## _extra, \
+ }
+
+#define AES_ALL_VTABLES(impl_c, impl_display) \
+ AES_CBC_VTABLE(impl_c, impl_display, 128); \
+ AES_CBC_VTABLE(impl_c, impl_display, 192); \
+ AES_CBC_VTABLE(impl_c, impl_display, 256); \
+ AES_SDCTR_VTABLE(impl_c, impl_display, 128); \
+ AES_SDCTR_VTABLE(impl_c, impl_display, 192); \
+ AES_SDCTR_VTABLE(impl_c, impl_display, 256); \
+ AES_GCM_VTABLE(impl_c, impl_display, 128); \
+ AES_GCM_VTABLE(impl_c, impl_display, 192); \
+ AES_GCM_VTABLE(impl_c, impl_display, 256)
+
+/*
+ * Macros to repeat a piece of code particular numbers of times that
+ * correspond to 1 fewer than the number of AES rounds. (Because the
+ * last round is different.)
+ */
+#define REP2(x) x x
+#define REP4(x) REP2(REP2(x))
+#define REP8(x) REP2(REP4(x))
+#define REP9(x) REP8(x) x
+#define REP11(x) REP8(x) REP2(x) x
+#define REP13(x) REP8(x) REP4(x) x
+
+/*
+ * The round constants used in key schedule expansion.
+ */
+extern const uint8_t aes_key_setup_round_constants[10];
+
+/*
+ * The largest number of round keys ever needed.
+ */
+#define MAXROUNDKEYS 15
diff --git a/crypto/aesgcm-clmul.c b/crypto/aesgcm-clmul.c
new file mode 100644
index 00000000..cfb72e26
--- /dev/null
+++ b/crypto/aesgcm-clmul.c
@@ -0,0 +1,180 @@
+/*
+ * Implementation of the GCM polynomial hash using the x86 CLMUL
+ * extension, which provides 64x64->128 polynomial multiplication (or
+ * 'carry-less', which is what the CL stands for).
+ *
+ * Follows the reference implementation in aesgcm-ref-poly.c; see
+ * there for comments on the underlying technique. Here the comments
+ * just discuss the x86-specific details.
+ */
+
+#include <wmmintrin.h>
+#include <tmmintrin.h>
+
+#if defined(__clang__) || defined(__GNUC__)
+#include <cpuid.h>
+#define GET_CPU_ID(out) __cpuid(1, (out)[0], (out)[1], (out)[2], (out)[3])
+#else
+#define GET_CPU_ID(out) __cpuid(out, 1)
+#endif
+
+#include "ssh.h"
+#include "aesgcm.h"
+
+typedef struct aesgcm_clmul {
+ AESGCM_COMMON_FIELDS;
+ __m128i var, acc, mask;
+ void *ptr_to_free;
+} aesgcm_clmul;
+
+static bool aesgcm_clmul_available(void)
+{
+ /*
+ * Determine if CLMUL is available on this CPU.
+ */
+ unsigned int CPUInfo[4];
+ GET_CPU_ID(CPUInfo);
+ return (CPUInfo[2] & (1 << 1));
+}
+
+/*
+ * __m128i has to be aligned to 16 bytes, and x86 mallocs may not
+ * guarantee that, so we must over-allocate to make sure a large
+ * enough 16-byte region can be found, and ensure the aesgcm_clmul
+ * struct pointer is at least that well aligned.
+ */
+#define SPECIAL_ALLOC
+static aesgcm_clmul *aesgcm_clmul_alloc(void)
+{
+ char *p = smalloc(sizeof(aesgcm_clmul) + 15);
+ uintptr_t ip = (uintptr_t)p;
+ ip = (ip + 15) & ~15;
+ aesgcm_clmul *ctx = (aesgcm_clmul *)ip;
+ memset(ctx, 0, sizeof(aesgcm_clmul));
+ ctx->ptr_to_free = p;
+ return ctx;
+}
+
+#define SPECIAL_FREE
+static void aesgcm_clmul_free(aesgcm_clmul *ctx)
+{
+ void *ptf = ctx->ptr_to_free;
+ smemclr(ctx, sizeof(*ctx));
+ sfree(ptf);
+}
+
+/* Helper function to reverse the 16 bytes in a 128-bit vector */
+static inline __m128i mm_byteswap(__m128i vec)
+{
+ const __m128i reverse = _mm_set_epi64x(
+ 0x0001020304050607ULL, 0x08090a0b0c0d0e0fULL);
+ return _mm_shuffle_epi8(vec, reverse);
+}
+
+/* Helper function to swap the two 64-bit words in a 128-bit vector */
+static inline __m128i mm_wordswap(__m128i vec)
+{
+ return _mm_shuffle_epi32(vec, 0x4E);
+}
+
+/* Load and store a 128-bit vector in big-endian fashion */
+static inline __m128i mm_load_be(const void *p)
+{
+ return mm_byteswap(_mm_loadu_si128(p));
+}
+static inline void mm_store_be(void *p, __m128i vec)
+{
+ _mm_storeu_si128(p, mm_byteswap(vec));
+}
+
+/*
+ * Key setup is just like in aesgcm-ref-poly.c. There's no point using
+ * vector registers to accelerate this, because it happens rarely.
+ */
+static void aesgcm_clmul_setkey_impl(aesgcm_clmul *ctx,
+ const unsigned char *var)
+{
+ uint64_t hi = GET_64BIT_MSB_FIRST(var);
+ uint64_t lo = GET_64BIT_MSB_FIRST(var + 8);
+
+ uint64_t bit = 1 & (hi >> 63);
+ hi = (hi << 1) ^ (lo >> 63);
+ lo = (lo << 1) ^ bit;
+ hi ^= 0xC200000000000000 & -bit;
+
+ ctx->var = _mm_set_epi64x(hi, lo);
+}
+
+static inline void aesgcm_clmul_setup(aesgcm_clmul *ctx,
+ const unsigned char *mask)
+{
+ ctx->mask = mm_load_be(mask);
+ ctx->acc = _mm_set_epi64x(0, 0);
+}
+
+/*
+ * Folding a coefficient into the accumulator is done by essentially
+ * the algorithm in aesgcm-ref-poly.c. I don't speak these intrinsics
+ * all that well, so in the parts where I needed to XOR half of one
+ * vector into half of another, I did a lot of faffing about with
+ * masks like 0xFFFFFFFFFFFFFFFF0000000000000000. Very likely this can
+ * be streamlined by a better x86-speaker than me. Patches welcome.
+ */
+static inline void aesgcm_clmul_coeff(aesgcm_clmul *ctx,
+ const unsigned char *coeff)
+{
+ ctx->acc = _mm_xor_si128(ctx->acc, mm_load_be(coeff));
+
+ /* Compute ah^al and bh^bl by word-swapping each of a and b and
+ * XORing with the original. That does more work than necessary -
+ * you end up with each of the desired values repeated twice -
+ * but I don't know of a neater way. */
+ __m128i aswap = mm_wordswap(ctx->acc);
+ __m128i vswap = mm_wordswap(ctx->var);
+ aswap = _mm_xor_si128(ctx->acc, aswap);
+ vswap = _mm_xor_si128(ctx->var, vswap);
+
+ /* Do the three multiplications required by Karatsuba */
+ __m128i md = _mm_clmulepi64_si128(aswap, vswap, 0x00);
+ __m128i lo = _mm_clmulepi64_si128(ctx->acc, ctx->var, 0x00);
+ __m128i hi = _mm_clmulepi64_si128(ctx->acc, ctx->var, 0x11);
+ /* Combine lo and hi into md */
+ md = _mm_xor_si128(md, lo);
+ md = _mm_xor_si128(md, hi);
+
+ /* Now we must XOR the high half of md into the low half of hi,
+ * and the low half of md into the high half of hi. Simplest thing
+ * is to swap the words of md (so that each one lines up with the
+ * register it's going to end up in), and then mask one off in
+ * each case. */
+ md = mm_wordswap(md);
+ lo = _mm_xor_si128(lo, _mm_and_si128(md, _mm_set_epi64x(~0ULL, 0ULL)));
+ hi = _mm_xor_si128(hi, _mm_and_si128(md, _mm_set_epi64x(0ULL, ~0ULL)));
+
+ /* The reduction stage is transformed similarly from the version
+ * in aesgcm-ref-poly.c. */
+ __m128i r1 = _mm_clmulepi64_si128(_mm_set_epi64x(0, 0xC200000000000000),
+ lo, 0x00);
+ r1 = mm_wordswap(r1);
+ r1 = _mm_xor_si128(r1, lo);
+ hi = _mm_xor_si128(hi, _mm_and_si128(r1, _mm_set_epi64x(~0ULL, 0ULL)));
+
+ __m128i r2 = _mm_clmulepi64_si128(_mm_set_epi64x(0, 0xC200000000000000),
+ r1, 0x10);
+ hi = _mm_xor_si128(hi, r2);
+ hi = _mm_xor_si128(hi, _mm_and_si128(r1, _mm_set_epi64x(0ULL, ~0ULL)));
+
+ ctx->acc = hi;
+}
+
+static inline void aesgcm_clmul_output(aesgcm_clmul *ctx,
+ unsigned char *output)
+{
+ mm_store_be(output, _mm_xor_si128(ctx->acc, ctx->mask));
+ smemclr(&ctx->acc, 16);
+ smemclr(&ctx->mask, 16);
+}
+
+#define AESGCM_FLAVOUR clmul
+#define AESGCM_NAME "CLMUL accelerated"
+#include "aesgcm-footer.h"
diff --git a/crypto/aesgcm-common.c b/crypto/aesgcm-common.c
new file mode 100644
index 00000000..1e20c87b
--- /dev/null
+++ b/crypto/aesgcm-common.c
@@ -0,0 +1,8 @@
+#include "ssh.h"
+#include "aesgcm.h"
+
+void aesgcm_set_prefix_lengths(ssh2_mac *mac, size_t skip, size_t aad)
+{
+ const struct aesgcm_extra *extra = mac->vt->extra;
+ extra->set_prefix_lengths(mac, skip, aad);
+}
diff --git a/crypto/aesgcm-footer.h b/crypto/aesgcm-footer.h
new file mode 100644
index 00000000..981905da
--- /dev/null
+++ b/crypto/aesgcm-footer.h
@@ -0,0 +1,368 @@
+/*
+ * Common footer included by every implementation of the AES-GCM MAC.
+ *
+ * The difficult part of AES-GCM, which is done differently depending
+ * on what hardware acceleration is available, is the actual
+ * evaluation of a polynomial over GF(2^128) whose coefficients are
+ * 128-bit chunks of data. But preparing those chunks in the first
+ * place (out of the ciphertext, associated data, and an
+ * administrative block containing the lengths of both) is done in the
+ * same way no matter what technique is used for the evaluation, so
+ * that's centralised into this file, along with as much of the other
+ * functionality as posible.
+ *
+ * This footer file is #included by each implementation, but each one
+ * will define its own struct type for the state, so that each alloc
+ * function will test sizeof() a different structure, and similarly
+ * for free when it zeroes out the state on cleanup.
+ *
+ * The functions in the source file may be defined as 'inline' so that
+ * the functions in here can inline them. The 'coeff' function in
+ * particular probably should be, because that's called once per
+ * 16-byte block, so eliminating function call overheads is especially
+ * useful there.
+ *
+ * This footer has the following expectations from the source file
+ * that #includes it:
+ *
+ * - define AESGCM_FLAVOUR to be a fragment of a C identifier that
+ * will be included in all the function names (both the ones
+ * defined in the implementation source file and those in here).
+ * For example purposes below I'll suppose that this is 'foo'.
+ *
+ * - define AESGCM_NAME to be a string literal that will be included
+ * in the display name of the implementation.
+ *
+ * - define a typedef 'aesgcm_foo' to be the state structure for the
+ * implementation, and inside that structure, expand the macro
+ * AESGCM_COMMON_FIELDS defined in aesgcm.h
+ *
+ * - define the following functions:
+ *
+ * // Determine whether this implementation is available at run time
+ * static bool aesgcm_foo_available(void);
+ *
+ * // Set up the 'key' of the polynomial part of the MAC, that is,
+ * // the value at which the polynomial will be evaluated. 'var' is
+ * // a 16-byte data block in the byte order it comes out of AES.
+ * static void aesgcm_foo_setkey_impl(aesgcm_foo *ctx,
+ * const unsigned char *var);
+ *
+ * // Set up at the start of evaluating an individual polynomial.
+ * // 'mask' is the 16-byte data block that will be XORed into the
+ * // output value of the polynomial, also in AES byte order. This
+ * // function should store 'mask' in whatever form is most
+ * // convenient, and initialise an accumulator to zero.
+ * static void aesgcm_foo_setup(aesgcm_foo *ctx,
+ * const unsigned char *mask);
+ *
+ * // Fold in a coefficient of the polynomial, by means of XORing
+ * // it into the accumulator and then multiplying the accumulator
+ * // by the variable passed to setkey_impl() above.
+ * //
+ * // 'coeff' points to the 16-byte block of data that the
+ * // polynomial coefficient will be made out of.
+ * //
+ * // You probably want to mark this function 'inline'.
+ * static void aesgcm_foo_coeff(aesgcm_foo *ctx,
+ * const unsigned char *coeff);
+ *
+ * // Generate the output MAC, by XORing the accumulator's final
+ * // value with the mask passed to setup() above.
+ * //
+ * // 'output' points to a 16-byte region of memory to write the
+ * // result to.
+ * static void aesgcm_foo_output(aesgcm_foo *ctx,
+ * unsigned char *output);
+ *
+ * - if allocation of the state structure must be done in a
+ * non-standard way (e.g. x86 needs this to force greater alignment
+ * than standard malloc provides), then #define SPECIAL_ALLOC and
+ * define this additional function:
+ *
+ * // Allocate a state structure, zero out its contents, and return it.
+ * static aesgcm_foo *aesgcm_foo_alloc(void);
+ *
+ * - if freeing must also be done in an unusual way, #define
+ * SPECIAL_FREE and define this function:
+ *
+ * // Zero out the state structure to avoid information leaks if the
+ * // memory is reused, and then free it.
+ * static void aesgcm_foo_free(aesgcm_foo *ctx);
+ */
+
+#ifndef AESGCM_FLAVOUR
+#error AESGCM_FLAVOUR must be defined by any module including this footer
+#endif
+#ifndef AESGCM_NAME
+#error AESGCM_NAME must be defined by any module including this footer
+#endif
+
+#define CONTEXT CAT(aesgcm_, AESGCM_FLAVOUR)
+#define PREFIX(name) CAT(CAT(aesgcm_, AESGCM_FLAVOUR), CAT(_, name))
+
+#include "aes.h" // for aes_encrypt_ecb_block
+
+static const char *PREFIX(mac_text_name)(ssh2_mac *mac)
+{
+ return "AES-GCM (" AESGCM_NAME ")";
+}
+
+static void PREFIX(mac_next_message)(ssh2_mac *mac)
+{
+ CONTEXT *ctx = container_of(mac, CONTEXT, mac);
+
+ /*
+ * Make the mask value for a single MAC instance, by encrypting
+ * the all-zeroes word using the associated AES instance in its
+ * ordinary GCM fashion. This consumes the first block of
+ * keystream (with per-block counter equal to 1), leaving the
+ * second block of keystream ready to be used on the first block
+ * of plaintext.
+ */
+ unsigned char buf[16];
+ memset(buf, 0, 16);
+ ssh_cipher_encrypt(ctx->cipher, buf, 16);
+ PREFIX(setup)(ctx, buf); /* give it to the implementation to store */
+ smemclr(buf, sizeof(buf));
+}
+
+static void PREFIX(mac_setkey)(ssh2_mac *mac, ptrlen key)
+{
+ CONTEXT *ctx = container_of(mac, CONTEXT, mac);
+
+ /*
+ * Make the value of the polynomial variable, by encrypting the
+ * all-zeroes word using the associated AES instance in the
+ * special ECB mode. This is done via the special AES-specific API
+ * function encrypt_ecb_block, which doesn't touch the counter
+ * state at all.
+ */
+ unsigned char var[16];
+ memset(var, 0, 16);
+ aes_encrypt_ecb_block(ctx->cipher, var);
+ PREFIX(setkey_impl)(ctx, var);
+ smemclr(var, sizeof(var));
+
+ PREFIX(mac_next_message)(mac); /* set up mask */
+}
+
+static void PREFIX(mac_start)(ssh2_mac *mac)
+{
+ CONTEXT *ctx = container_of(mac, CONTEXT, mac);
+
+ ctx->skipgot = ctx->aadgot = ctx->ciphertextlen = ctx->partlen = 0;
+}
+
+/*
+ * Handle receiving data via the BinarySink API and turning it into a
+ * collection of 16-byte blocks to use as polynomial coefficients.
+ *
+ * This code is written in a fully general way, which is able to
+ * handle an arbitrary number of bytes at the start of the data to
+ * ignore completely (necessary for PuTTY integration), and an
+ * arbitrary number to treat as associated data, and the rest will be
+ * regarded as ciphertext. The stream can be interrupted at any byte
+ * position and resumed later; a partial block will be stored as
+ * necessary.
+ *
+ * At the time of writing this comment, in live use most of that
+ * generality isn't required: the full data is passed to this function
+ * in just one call. But there's no guarantee of that staying true in
+ * future, so we do the full deal here just in case, and the test
+ * vectors in cryptsuite.py will test it. (And they'll use
+ * set_prefix_lengths to set up different configurations from the SSH
+ * usage.)
+ */
+static void PREFIX(mac_BinarySink_write)(
+ BinarySink *bs, const void *blkv, size_t len)
+{
+ CONTEXT *ctx = BinarySink_DOWNCAST(bs, CONTEXT);
+ const unsigned char *blk = (const unsigned char *)blkv;
+
+ /*
+ * Skip the prefix sequence number used as implicit extra data in
+ * SSH MACs. This is not included in the associated data field for
+ * GCM, because the IV incrementation policy provides its own
+ * sequence numbering.
+ */
+ if (ctx->skipgot < ctx->skiplen) {
+ size_t n = ctx->skiplen - ctx->skipgot;
+ if (n > len)
+ n = len;
+ blk += n;
+ len -= n;
+ ctx->skipgot += n;
+
+ if (len == 0)
+ return;
+ }
+
+ /*
+ * Read additional authenticated data and fold it in to the MAC.
+ */
+ while (ctx->aadgot < ctx->aadlen) {
+ size_t n = ctx->aadlen - ctx->aadgot;
+ if (n > len)
+ n = len;
+
+ if (ctx->partlen || n < 16) {
+ /*
+ * Fold data into the partial block.
+ */
+ if (n > 16 - ctx->partlen)
+ n = 16 - ctx->partlen;
+ memcpy(ctx->partblk + ctx->partlen, blk, n);
+ ctx->partlen += n;
+ } else if (n >= 16) {
+ /*
+ * Consume a whole block of AAD.
+ */
+ PREFIX(coeff)(ctx, blk);
+ n = 16;
+ }
+ blk += n;
+ len -= n;
+ ctx->aadgot += n;
+
+ if (ctx->partlen == 16) {
+ PREFIX(coeff)(ctx, ctx->partblk);
+ ctx->partlen = 0;
+ }
+
+ if (ctx->aadgot == ctx->aadlen && ctx->partlen) {
+ memset(ctx->partblk + ctx->partlen, 0, 16 - ctx->partlen);
+ PREFIX(coeff)(ctx, ctx->partblk);
+ ctx->partlen = 0;
+ }
+
+ if (len == 0)
+ return;
+ }
+
+ /*
+ * Read the main ciphertext and fold it in to the MAC.
+ */
+ while (len > 0) {
+ size_t n = len;
+
+ if (ctx->partlen || n < 16) {
+ /*
+ * Fold data into the partial block.
+ */
+ if (n > 16 - ctx->partlen)
+ n = 16 - ctx->partlen;
+ memcpy(ctx->partblk + ctx->partlen, blk, n);
+ ctx->partlen += n;
+ } else if (n >= 16) {
+ /*
+ * Consume a whole block of ciphertext.
+ */
+ PREFIX(coeff)(ctx, blk);
+ n = 16;
+ }
+ blk += n;
+ len -= n;
+ ctx->ciphertextlen += n;
+
+ if (ctx->partlen == 16) {
+ PREFIX(coeff)(ctx, ctx->partblk);
+ ctx->partlen = 0;
+ }
+ }
+}
+
+static void PREFIX(mac_genresult)(ssh2_mac *mac, unsigned char *output)
+{
+ CONTEXT *ctx = container_of(mac, CONTEXT, mac);
+
+ /*
+ * Consume any partial block of ciphertext remaining.
+ */
+ if (ctx->partlen) {
+ memset(ctx->partblk + ctx->partlen, 0, 16 - ctx->partlen);
+ PREFIX(coeff)(ctx, ctx->partblk);
+ }
+
+ /*
+ * Consume the final block giving the lengths of the AAD and ciphertext.
+ */
+ unsigned char blk[16];
+ memset(blk, 0, 16);
+ PUT_64BIT_MSB_FIRST(blk, ctx->aadlen * 8);
+ PUT_64BIT_MSB_FIRST(blk + 8, ctx->ciphertextlen * 8);
+ PREFIX(coeff)(ctx, blk);
+
+ /*
+ * And call the implementation's output function.
+ */
+ PREFIX(output)(ctx, output);
+
+ smemclr(blk, sizeof(blk));
+ smemclr(ctx->partblk, 16);
+}
+
+static ssh2_mac *PREFIX(mac_new)(const ssh2_macalg *alg, ssh_cipher *cipher)
+{
+ const struct aesgcm_extra *extra = alg->extra;
+ if (!check_aesgcm_availability(extra))
+ return NULL;
+
+#ifdef SPECIAL_ALLOC
+ CONTEXT *ctx = PREFIX(alloc)();
+#else
+ CONTEXT *ctx = snew(CONTEXT);
+ memset(ctx, 0, sizeof(CONTEXT));
+#endif
+
+ ctx->mac.vt = alg;
+ ctx->cipher = cipher;
+ /* Default values for SSH-2, overridable by set_prefix_lengths for
+ * testcrypt purposes */
+ ctx->skiplen = 4;
+ ctx->aadlen = 4;
+ BinarySink_INIT(ctx, PREFIX(mac_BinarySink_write));
+ BinarySink_DELEGATE_INIT(&ctx->mac, ctx);
+ return &ctx->mac;
+}
+
+static void PREFIX(set_prefix_lengths)(ssh2_mac *mac, size_t skip, size_t aad)
+{
+ CONTEXT *ctx = container_of(mac, CONTEXT, mac);
+ ctx->skiplen = skip;
+ ctx->aadlen = aad;
+}
+
+static void PREFIX(mac_free)(ssh2_mac *mac)
+{
+ CONTEXT *ctx = container_of(mac, CONTEXT, mac);
+#ifdef SPECIAL_FREE
+ PREFIX(free)(ctx);
+#else
+ smemclr(ctx, sizeof(*ctx));
+ sfree(ctx);
+#endif
+}
+
+static struct aesgcm_extra_mutable PREFIX(extra_mut);
+
+static const struct aesgcm_extra PREFIX(extra) = {
+ .check_available = PREFIX(available),
+ .mut = &PREFIX(extra_mut),
+ .set_prefix_lengths = PREFIX(set_prefix_lengths),
+};
+
+const ssh2_macalg CAT(ssh2_aesgcm_mac_, AESGCM_FLAVOUR) = {
+ .new = PREFIX(mac_new),
+ .free = PREFIX(mac_free),
+ .setkey = PREFIX(mac_setkey),
+ .start = PREFIX(mac_start),
+ .genresult = PREFIX(mac_genresult),
+ .next_message = PREFIX(mac_next_message),
+ .text_name = PREFIX(mac_text_name),
+ .name = "",
+ .etm_name = "", /* Not selectable independently */
+ .len = 16,
+ .keylen = 0,
+ .extra = &PREFIX(extra),
+};
diff --git a/crypto/aesgcm-neon.c b/crypto/aesgcm-neon.c
new file mode 100644
index 00000000..64bc8349
--- /dev/null
+++ b/crypto/aesgcm-neon.c
@@ -0,0 +1,164 @@
+/*
+ * Implementation of the GCM polynomial hash using Arm NEON vector
+ * intrinsics, in particular the multiplication operation for
+ * polynomials over GF(2).
+ *
+ * Follows the reference implementation in aesgcm-ref-poly.c; see
+ * there for comments on the underlying technique. Here the comments
+ * just discuss the NEON-specific details.
+ */
+
+#include "ssh.h"
+#include "aesgcm.h"
+
+#if USE_ARM64_NEON_H
+#include <arm64_neon.h>
+#else
+#include <arm_neon.h>
+#endif
+
+typedef struct aesgcm_neon {
+ AESGCM_COMMON_FIELDS;
+ poly128_t var, acc, mask;
+} aesgcm_neon;
+
+static bool aesgcm_neon_available(void)
+{
+ return platform_pmull_neon_available();
+}
+
+/*
+ * The NEON types involved are:
+ *
+ * 'poly128_t' is a type that lives in a 128-bit vector register and
+ * represents a 128-bit polynomial over GF(2)
+ *
+ * 'poly64x2_t' is a type that lives in a 128-bit vector register and
+ * represents a vector of two 64-bit polynomials. These appear as
+ * intermediate results in some of the helper functions below, but we
+ * never need to actually have a variable of that type.
+ *
+ * 'poly64x1_t' is a type that lives in a 128-bit vector register and
+ * represents a vector of one 64-bit polynomial.
+ *
+ * That is distinct from 'poly64_t', which is a type that lives in
+ * ordinary scalar registers and is a typedef for an integer type.
+ *
+ * Generally here we try to work in terms of poly128_t and 64-bit
+ * integer types, and let everything else be handled as internal
+ * details of these helper functions.
+ */
+
+/* Make a poly128_t from two halves */
+static inline poly128_t create_p128(poly64_t hi, poly64_t lo)
+{
+ return vreinterpretq_p128_p64(
+ vcombine_p64(vcreate_p64(lo), vcreate_p64(hi)));
+}
+
+/* Retrieve the high and low halves of a poly128_t */
+static inline poly64_t hi_half(poly128_t v)
+{
+ return vgetq_lane_p64(vreinterpretq_p64_p128(v), 1);
+}
+static inline poly64_t lo_half(poly128_t v)
+{
+ return vgetq_lane_p64(vreinterpretq_p64_p128(v), 0);
+}
+
+/* 64x64 -> 128 bit polynomial multiplication, the largest we can do
+ * in one CPU operation */
+static inline poly128_t pmul(poly64_t v, poly64_t w)
+{
+ return vmull_p64(v, w);
+}
+
+/* Load and store a poly128_t in the form of big-endian bytes. This
+ * involves separately swapping the halves of the register and
+ * reversing the bytes within each half. */
+static inline poly128_t load_p128_be(const void *p)
+{
+ poly128_t swapped = vreinterpretq_p128_u8(vrev64q_u8(vld1q_u8(p)));
+ return create_p128(lo_half(swapped), hi_half(swapped));
+}
+static inline void store_p128_be(void *p, poly128_t v)
+{
+ poly128_t swapped = create_p128(lo_half(v), hi_half(v));
+ vst1q_u8(p, vrev64q_u8(vreinterpretq_u8_p128(swapped)));
+}
+
+#if !HAVE_NEON_VADDQ_P128
+static inline poly128_t vaddq_p128(poly128_t a, poly128_t b)
+{
+ return vreinterpretq_p128_u32(veorq_u32(
+ vreinterpretq_u32_p128(a), vreinterpretq_u32_p128(b)));
+}
+#endif
+
+/*
+ * Key setup is just like in aesgcm-ref-poly.c. There's no point using
+ * vector registers to accelerate this, because it happens rarely.
+ */
+static void aesgcm_neon_setkey_impl(aesgcm_neon *ctx, const unsigned char *var)
+{
+ uint64_t hi = GET_64BIT_MSB_FIRST(var);
+ uint64_t lo = GET_64BIT_MSB_FIRST(var + 8);
+
+ uint64_t bit = 1 & (hi >> 63);
+ hi = (hi << 1) ^ (lo >> 63);
+ lo = (lo << 1) ^ bit;
+ hi ^= 0xC200000000000000 & -bit;
+
+ ctx->var = create_p128(hi, lo);
+}
+
+static inline void aesgcm_neon_setup(aesgcm_neon *ctx,
+ const unsigned char *mask)
+{
+ ctx->mask = load_p128_be(mask);
+ ctx->acc = create_p128(0, 0);
+}
+
+/*
+ * Folding a coefficient into the accumulator is done by exactly the
+ * algorithm in aesgcm-ref-poly.c, translated line by line.
+ *
+ * It's possible that this could be improved by some clever manoeuvres
+ * that avoid having to break vectors in half and put them together
+ * again. Patches welcome if anyone has better ideas.
+ */
+static inline void aesgcm_neon_coeff(aesgcm_neon *ctx,
+ const unsigned char *coeff)
+{
+ ctx->acc = vaddq_p128(ctx->acc, load_p128_be(coeff));
+
+ poly64_t ah = hi_half(ctx->acc), al = lo_half(ctx->acc);
+ poly64_t bh = hi_half(ctx->var), bl = lo_half(ctx->var);
+ poly128_t md = pmul(ah ^ al, bh ^ bl);
+ poly128_t lo = pmul(al, bl);
+ poly128_t hi = pmul(ah, bh);
+ md = vaddq_p128(md, vaddq_p128(hi, lo));
+ hi = create_p128(hi_half(hi), lo_half(hi) ^ hi_half(md));
+ lo = create_p128(hi_half(lo) ^ lo_half(md), lo_half(lo));
+
+ poly128_t r1 = pmul((poly64_t)0xC200000000000000, lo_half(lo));
+ hi = create_p128(hi_half(hi), lo_half(hi) ^ lo_half(lo) ^ hi_half(r1));
+ lo = create_p128(hi_half(lo) ^ lo_half(r1), lo_half(lo));
+
+ poly128_t r2 = pmul((poly64_t)0xC200000000000000, hi_half(lo));
+ hi = vaddq_p128(hi, r2);
+ hi = create_p128(hi_half(hi) ^ hi_half(lo), lo_half(hi));
+
+ ctx->acc = hi;
+}
+
+static inline void aesgcm_neon_output(aesgcm_neon *ctx, unsigned char *output)
+{
+ store_p128_be(output, vaddq_p128(ctx->acc, ctx->mask));
+ ctx->acc = create_p128(0, 0);
+ ctx->mask = create_p128(0, 0);
+}
+
+#define AESGCM_FLAVOUR neon
+#define AESGCM_NAME "NEON accelerated"
+#include "aesgcm-footer.h"
diff --git a/crypto/aesgcm-ref-poly.c b/crypto/aesgcm-ref-poly.c
new file mode 100644
index 00000000..f6ca0fa5
--- /dev/null
+++ b/crypto/aesgcm-ref-poly.c
@@ -0,0 +1,364 @@
+/*
+ * Implementation of the GCM polynomial hash in pure software, but
+ * based on a primitive that performs 64x64->128 bit polynomial
+ * multiplication over GF(2).
+ *
+ * This implementation is technically correct (should even be
+ * side-channel safe as far as I can see), but it's hopelessly slow,
+ * so no live SSH connection should ever use it. Therefore, it's
+ * deliberately not included in the lists in aesgcm-select.c. For pure
+ * software GCM in live use, you want aesgcm-sw.c, and that's what the
+ * selection system will choose.
+ *
+ * However, this implementation _is_ made available to testcrypt, so
+ * all the GCM tests in cryptsuite.py are run over this as well as the
+ * other implementations.
+ *
+ * The reason why this code exists at all is to act as a reference for
+ * GCM implementations that use a CPU-specific polynomial multiply
+ * intrinsic or asm statement. This version will run on whatever
+ * platform you're trying to port to, and will generate all the same
+ * intermediate results you expect the CPU-specific one to go through.
+ * So you can insert parallel diagnostics in this version and in your
+ * new version, to see where the two diverge.
+ *
+ * Also, this version is a good place to put long comments explaining
+ * the entire strategy of this implementation and its rationale. That
+ * avoids those comments having to be duplicated in the multiple
+ * platform-specific implementations, which can focus on commenting
+ * the way the local platform's idioms match up to this version, and
+ * refer to this file for the explanation of the underlying technique.
+ */
+
+#include "ssh.h"
+#include "aesgcm.h"
+
+/*
+ * Store a 128-bit value in the most convenient form standard C will
+ * let us, namely two uint64_t giving its most and least significant
+ * halves.
+ */
+typedef struct {
+ uint64_t hi, lo;
+} value128_t;
+
+typedef struct aesgcm_ref_poly {
+ AESGCM_COMMON_FIELDS;
+
+ /*
+ * The state of our GCM implementation is represented entirely by
+ * three 128-bit values:
+ */
+
+ /*
+ * The value at which we're evaluating the polynomial. The GCM
+ * spec calls this 'H'. It's defined once at GCM key setup time,
+ * by encrypting the all-zeroes value with our block cipher.
+ */
+ value128_t var;
+
+ /*
+ * Accumulator containing the result of evaluating the polynomial
+ * so far.
+ */
+ value128_t acc;
+
+ /*
+ * The mask value that is XORed into the final value of 'acc' to
+ * produce the output MAC. This is different for every MAC
+ * generated, because its purpose is to ensure that no information
+ * gathered from a legal MAC can be used to help the forgery of
+ * another one, and that comparing two legal MACs gives you no
+ * useful information about the text they cover, because in each
+ * case, the masks are different and pseudorandom.
+ */
+ value128_t mask;
+} aesgcm_ref_poly;
+
+static bool aesgcm_ref_poly_available(void)
+{
+ return true; /* pure software implementation, always available */
+}
+
+/*
+ * Primitive function that takes two uint64_t values representing
+ * polynomials, multiplies them, and returns a value128_t struct
+ * containing the full product.
+ *
+ * Because the input polynomials have maximum degree 63, the output
+ * has max degree 63+63 = 127, not 128. As a result, the topmost bit
+ * of the output is always zero.
+ *
+ * The inside of this function is implemented in the simplest way,
+ * with no attention paid to performance. The important feature of
+ * this implementation is not what's _inside_ this function, but
+ * what's _outside_ it: aesgcm_ref_poly_coeff() tries to minimise the
+ * number of these operations.
+ */
+static value128_t pmul(uint64_t x, uint64_t y)
+{
+ value128_t r;
+ r.hi = r.lo = 0;
+
+ uint64_t bit = 1 & y;
+ r.lo ^= x & -bit;
+
+ for (unsigned i = 1; i < 64; i++) {
+ bit = 1 & (y >> i);
+ uint64_t z = x & -bit;
+ r.lo ^= z << i;
+ r.hi ^= z >> (64-i);
+ }
+
+ return r;
+}
+
+/*
+ * OK, I promised a long comment explaining what's going on in this
+ * implementation, and now it's time.
+ *
+ * The way AES-GCM _itself_ is defined by its own spec, its finite
+ * field consists of polynomials over GF(2), constrained to be 128
+ * bits long by reducing them modulo P = x^128 + x^7 + x^2 + x + 1.
+ * Using the usual binary representation in which bit i is the
+ * coefficient of x^i, that's 0x100000000000000000000000000000087.
+ *
+ * That is, whenever you multiply two polynomials and find a term
+ * x^128, you can replace it with x^7+x^2+x+1. More generally,
+ * x^(128+n) can be replaced with x^(7+n)+x^(2+n)+x^(1+n)+x^n. In
+ * binary terms, a 1 bit at the 128th position or above is replaced by
+ * 0x87 exactly 128 bits further down.
+ *
+ * So you'd think that multiplying two 128-bit polynomials mod P would
+ * be a matter of generating their full 256-bit product in the form of
+ * four words HI:HU:LU:LO, and then reducing it mod P by a two-stage
+ * process of computing HI * 0x87 and XORing it into HU:LU, then
+ * computing HU * 0x87 and XORing it into LU:LO.
+ *
+ * But it's not!
+ *
+ * The reason why not is because when AES-GCM is applied to SSH,
+ * somehow the _bit_ order got reversed. A 16-byte block of data in
+ * memory is converted into a polynomial by regarding bit 7 of the
+ * first byte as the constant term, bit 0 of the first byte as the x^7
+ * coefficient, ..., bit 0 of the last byte as the x^127 coefficient.
+ * So if we load that 16-byte block as a big-endian 128-bit integer,
+ * we end up with it representing a polynomial back to front, with the
+ * constant term at the top and the x^127 bit at the bottom.
+ *
+ * Well, that _shouldn't_ be a problem, right? The nice thing about
+ * polynomial multiplication is that it's essentially reversible. If
+ * you reverse the order of the coefficients of two polynomials, then
+ * the product of the reversed polys is exactly the reversal of the
+ * product of the original ones. So we bit-reverse our modulo
+ * polynomial to get 0x1c2000000000000000000000000000001, and we just
+ * pretend we're working mod that instead.
+ *
+ * And that is basically what we're about to do. But there's one
+ * complication, that arises from the semantics of the polynomial
+ * multiplication function we're using as our primitive operation.
+ *
+ * That function multiplies two polynomials of degree at most 63, to
+ * give one with degree at most 127. So it returns a 128-bit integer
+ * whose low bit is the constant term, and its very highest bit is 0,
+ * and its _next_ highest bit is the product of the high bits of the
+ * two inputs.
+ *
+ * That operation is _not_ symmetric in bit-reversal. If you give it
+ * the 64-bit-wise reversals of two polynomials P,Q, then its output
+ * is not the 128-bit-wise reversal of their product PQ, because that
+ * would require the constant term of PQ to appear in bit 127 of the
+ * output, and in fact it appears in bit 126. So in fact, what we get
+ * is offset by one bit from where we'd like it: it's the bit-reversal
+ * of PQx, not of PQ.
+ *
+ * There's more than one way we could fix this. One approach would be
+ * to work around this off-by-one error by taking the 128-bit output
+ * of pmul() and shifting it left by a bit. Then it _is_ the bitwise
+ * reversal of the 128-bit value we'd have wanted to get, and we could
+ * implement the exact algorithm described above, in fully
+ * bit-reversed form.
+ *
+ * But a 128-bit left shift is not a trivial operation in the vector
+ * architectures that this code is acting as a reference for. So we'd
+ * prefer to find a fix that doesn't need compensation during the
+ * actual per-block multiplication step.
+ *
+ * If we did the obvious thing anyway - compute the unshifted 128-bit
+ * product representing the bit-reversal of PQx, and reduce it mod
+ * 0x1c2000000000000000000000000000001 - then we'd get a result which
+ * is exactly what we want, except that it's got a factor of x in it
+ * that we need to get rid of. The obvious answer is to divide by x
+ * (which is legal and safe, since mod P, x is invertible).
+ *
+ * Dividing a 128-bit polynomial by x is easy in principle. Shift left
+ * (because we're still bit-reversed), and if that shifted a 1 bit off
+ * the top, XOR 0xc2000000000000000000000000000001 into the remaining
+ * 128 bits.
+ *
+ * But we're back to having that expensive left shift. What can we do
+ * about that?
+ *
+ * Happily, one of the two input values to our per-block multiply
+ * operation is fixed! It's given to us at key setup stage, and never
+ * changed until the next rekey. So if at key setup time we do this
+ * left shift business _once_, replacing the logical value Q with Q/x,
+ * then that exactly cancels out the unwanted factor of x that shows
+ * up in our multiply operation. And now it doesn't matter that it's
+ * expensive (in the sense of 'a few more instructions than you'd
+ * like'), because it only happens once per SSH key exchange, not once
+ * per 16 bytes of data transferred.
+ */
+
+static void aesgcm_ref_poly_setkey_impl(aesgcm_ref_poly *ctx,
+ const unsigned char *var)
+{
+ /*
+ * Key setup function. We copy the provided 16-byte 'var'
+ * value into our polynomial. But, as discussed above, we also
+ * need to divide it by x.
+ */
+
+ ctx->var.hi = GET_64BIT_MSB_FIRST(var);
+ ctx->var.lo = GET_64BIT_MSB_FIRST(var + 8);
+
+ uint64_t bit = 1 & (ctx->var.hi >> 63);
+ ctx->var.hi = (ctx->var.hi << 1) ^ (ctx->var.lo >> 63);
+ ctx->var.lo = (ctx->var.lo << 1) ^ bit;
+ ctx->var.hi ^= 0xC200000000000000 & -bit;
+}
+
+static inline void aesgcm_ref_poly_setup(aesgcm_ref_poly *ctx,
+ const unsigned char *mask)
+{
+ /*
+ * Set up to start evaluating a particular MAC. Copy in the mask
+ * value for this packet, and initialise acc to zero.
+ */
+
+ ctx->mask.hi = GET_64BIT_MSB_FIRST(mask);
+ ctx->mask.lo = GET_64BIT_MSB_FIRST(mask + 8);
+ ctx->acc.hi = ctx->acc.lo = 0;
+}
+
+static inline void aesgcm_ref_poly_coeff(aesgcm_ref_poly *ctx,
+ const unsigned char *coeff)
+{
+ /*
+ * One step of Horner's-rule polynomial evaluation (with each
+ * coefficient of the polynomial being an element of GF(2^128),
+ * itself composed of polynomials over GF(2) mod P).
+ *
+ * We take our accumulator value, add the incoming coefficient
+ * (which means XOR, by GF(2) rules), and multiply by x (that is,
+ * 'var').
+ */
+
+ /*
+ * The addition first, which is easy.
+ */
+ ctx->acc.hi ^= GET_64BIT_MSB_FIRST(coeff);
+ ctx->acc.lo ^= GET_64BIT_MSB_FIRST(coeff + 8);
+
+ /*
+ * First, create the 256-bit product of the two 128-bit
+ * polynomials over GF(2) stored in ctx->acc and ctx->var.
+ *
+ * The obvious way to do this is by four smaller multiplications
+ * of 64x64 -> 128 bits. But we can do better using a single
+ * iteration of the Karatsuba technique, which is actually more
+ * convenient in polynomials over GF(2) than it is in integers,
+ * because there aren't all those awkward carries complicating
+ * things.
+ *
+ * Letting B denote x^64, and imagining our two inputs are split
+ * up into 64-bit chunks ah,al,bh,bl, the product we want is
+ *
+ * (ah B + al) (bh B + bl)
+ * = (ah bh) B^2 + (al bh + ah bl) B + (al bl)
+ *
+ * which looks like four smaller multiplications of each of ah,al
+ * with each of bh,bl. But Karatsuba's trick is to first compute
+ *
+ * (ah + al) (bh + bl)
+ * = ah bh + al bh + ah bl + al bl
+ *
+ * and then subtract the terms (ah bh) and (al bl), which we had
+ * to compute anyway, to get the middle two terms (al bh + ah bl)
+ * which are our coefficient of B.
+ *
+ * This involves more bookkeeping instructions like XORs, but with
+ * any luck those are faster than the main multiplication.
+ */
+ uint64_t ah = ctx->acc.hi, al = ctx->acc.lo;
+ uint64_t bh = ctx->var.hi, bl = ctx->var.lo;
+ /* Compute the outer two terms */
+ value128_t lo = pmul(al, bl);
+ value128_t hi = pmul(ah, bh);
+ /* Compute the trick product (ah+al)(bh+bl) */
+ value128_t md = pmul(ah ^ al, bh ^ bl);
+ /* Subtract off the outer two terms to get md = al bh + ah bl */
+ md.hi ^= lo.hi ^ hi.hi;
+ md.lo ^= lo.lo ^ hi.lo;
+ /* And add that into the 256-bit value given by hi * x^128 + lo */
+ lo.hi ^= md.lo;
+ hi.lo ^= md.hi;
+
+ /*
+ * OK. Now hi and lo together make up the 256-bit full product.
+ * Now reduce it mod the reversal of the GCM modulus polynomial.
+ * As discussed above, that's 0x1c2000000000000000000000000000001.
+ *
+ * We want the _topmost_ 128 bits of this, because we're working
+ * in a bit-reversed world. So what we fundamentally want to do is
+ * to take our 256-bit product, and add to it the product of its
+ * low 128 bits with 0x1c2000000000000000000000000000001. Then the
+ * top 128 bits will be the output we want.
+ *
+ * Since there's no carrying in this arithmetic, it's enough to
+ * discard the 1 bit at the bottom of that, because it won't
+ * affect anything in the half we're keeping. So it's enough to
+ * add 0x1c2000000000000000000000000000000 * lo to (hi:lo).
+ *
+ * We can only work with 64 bits at a time, so the first thing we
+ * do is to break that up:
+ *
+ * - add 0x1c200000000000000 * lo.lo to (hi.lo : lo.hi)
+ * - add 0x1c200000000000000 * lo.hi to (hi.hi : hi.lo)
+ *
+ * But there's still a problem: 0x1c200000000000000 is just too
+ * _big_ to fit in 64 bits. So we have to break it up into the low
+ * 64 bits 0xc200000000000000, and its leading 1. So each of those
+ * steps of the form 'add 0x1c200000000000000 * x to y:z' becomes
+ * 'add 0xc200000000000000 * x to y:z' followed by 'add x to y',
+ * the latter step dealing with the leading 1.
+ */
+
+ /* First step, adding to the middle two words of our number. After
+ * this the lowest word (in lo.lo) is ignored. */
+ value128_t r1 = pmul(0xC200000000000000, lo.lo);
+ hi.lo ^= r1.hi ^ lo.lo;
+ lo.hi ^= r1.lo;
+
+ /* Second of those steps, adding to the top two words, and
+ * discarding lo.hi. */
+ value128_t r2 = pmul(0xC200000000000000, lo.hi);
+ hi.hi ^= r2.hi ^ lo.hi;
+ hi.lo ^= r2.lo;
+
+ /* Now 'hi' is precisely what we have left. */
+ ctx->acc = hi;
+}
+
+static inline void aesgcm_ref_poly_output(aesgcm_ref_poly *ctx,
+ unsigned char *output)
+{
+ PUT_64BIT_MSB_FIRST(output, ctx->acc.hi ^ ctx->mask.hi);
+ PUT_64BIT_MSB_FIRST(output + 8, ctx->acc.lo ^ ctx->mask.lo);
+ smemclr(&ctx->acc, 16);
+ smemclr(&ctx->mask, 16);
+}
+
+#define AESGCM_FLAVOUR ref_poly
+#define AESGCM_NAME "reference polynomial-based implementation"
+#include "aesgcm-footer.h"
diff --git a/crypto/aesgcm-select.c b/crypto/aesgcm-select.c
new file mode 100644
index 00000000..eefe7148
--- /dev/null
+++ b/crypto/aesgcm-select.c
@@ -0,0 +1,38 @@
+#include "ssh.h"
+#include "aesgcm.h"
+
+static ssh2_mac *aesgcm_mac_selector_new(const ssh2_macalg *alg,
+ ssh_cipher *cipher)
+{
+ static const ssh2_macalg *const real_algs[] = {
+#if HAVE_CLMUL
+ &ssh2_aesgcm_mac_clmul,
+#endif
+#if HAVE_NEON_PMULL
+ &ssh2_aesgcm_mac_neon,
+#endif
+ &ssh2_aesgcm_mac_sw,
+ NULL,
+ };
+
+ for (size_t i = 0; real_algs[i]; i++) {
+ const ssh2_macalg *alg = real_algs[i];
+ const struct aesgcm_extra *alg_extra =
+ (const struct aesgcm_extra *)alg->extra;
+ if (check_aesgcm_availability(alg_extra))
+ return ssh2_mac_new(alg, cipher);
+ }
+
+ /* We should never reach the NULL at the end of the list, because
+ * the last non-NULL entry should be software-only GCM, which is
+ * always available. */
+ unreachable("aesgcm_select ran off the end of its list");
+}
+
+const ssh2_macalg ssh2_aesgcm_mac = {
+ .new = aesgcm_mac_selector_new,
+ .name = "",
+ .etm_name = "", /* Not selectable independently */
+ .len = 16,
+ .keylen = 0,
+};
diff --git a/crypto/aesgcm-sw.c b/crypto/aesgcm-sw.c
new file mode 100644
index 00000000..f322ae30
--- /dev/null
+++ b/crypto/aesgcm-sw.c
@@ -0,0 +1,145 @@
+/*
+ * Implementation of the GCM polynomial hash in pure software.
+ *
+ * I don't know of a faster way to do this in a side-channel safe
+ * manner than by precomputing a giant table and iterating over the
+ * whole thing.
+ *
+ * The original GCM reference suggests that you precompute the effects
+ * of multiplying a 128-bit value by the fixed key, in the form of a
+ * table indexed by some number of bits of the input value, so that
+ * you end up computing something of the form
+ *
+ * table1[x & 0xFF] ^ table2[(x>>8) & 0xFF] ^ ... ^ table15[(x>>120) & 0xFF]
+ *
+ * But that was obviously written before cache and timing leaks were
+ * known about. What's a time-safe approach?
+ *
+ * Well, the above technique isn't fixed to 8 bits of input per table.
+ * You could trade off the number of tables against the size of each
+ * table. At one extreme of this tradeoff, you have 128 tables each
+ * indexed by a single input bit - which is to say, you have 128
+ * values, each 128 bits wide, and you XOR together the subset of
+ * those values corresponding to the input bits, which you can do by
+ * making a bitmask out of each input bit using standard constant-
+ * time-coding bit twiddling techniques.
+ *
+ * That's pretty unpleasant when GCM is supposed to be a fast
+ * algorithm, but I don't know of a better approach that meets current
+ * security standards! Suggestions welcome, if they can get through
+ * testsc.
+ */
+
+#include "ssh.h"
+#include "aesgcm.h"
+
+/*
+ * Store a 128-bit value in the most convenient form standard C will
+ * let us, namely two uint64_t giving its most and least significant
+ * halves.
+ */
+typedef struct {
+ uint64_t hi, lo;
+} value128_t;
+
+typedef struct aesgcm_sw {
+ AESGCM_COMMON_FIELDS;
+
+ /* Accumulator for the current evaluation, and mask that will be
+ * XORed in at the end. High */
+ value128_t acc, mask;
+
+ /*
+ * Table of values to XOR in for each bit, representing the effect
+ * of multiplying by the fixed key. The key itself doesn't need to
+ * be stored separately, because it's never used. (However, it is
+ * also the first entry in the table, so if you _do_ need it,
+ * there it is.)
+ *
+ * Table is indexed from the low bit of the input upwards.
+ */
+ value128_t table[128];
+} aesgcm_sw;
+
+static bool aesgcm_sw_available(void)
+{
+ return true; /* pure software implementation, always available */
+}
+
+static void aesgcm_sw_setkey_impl(aesgcm_sw *gcm, const unsigned char *var)
+{
+ value128_t v;
+ v.hi = GET_64BIT_MSB_FIRST(var);
+ v.lo = GET_64BIT_MSB_FIRST(var + 8);
+
+ /*
+ * Prepare the table. This has to be done in reverse order, so
+ * that the original value of the variable corresponds to
+ * table[127], because AES-GCM works in the bit-reversal of its
+ * logical specification so that's where the logical constant term
+ * lives. (See more detailed comment in aesgcm-ref-poly.c.)
+ */
+ for (size_t i = 0; i < 128; i++) {
+ gcm->table[127 - i] = v;
+
+ /* Multiply v by x, which means shifting right (bit reversal
+ * again) and then adding 0xE1 at the top if we shifted a 1 out. */
+ uint64_t lobit = v.lo & 1;
+ v.lo = (v.lo >> 1) ^ (v.hi << 63);
+ v.hi = (v.hi >> 1) ^ (0xE100000000000000ULL & -lobit);
+ }
+}
+
+static inline void aesgcm_sw_setup(aesgcm_sw *gcm, const unsigned char *mask)
+{
+ gcm->mask.hi = GET_64BIT_MSB_FIRST(mask);
+ gcm->mask.lo = GET_64BIT_MSB_FIRST(mask + 8);
+ gcm->acc.hi = gcm->acc.lo = 0;
+}
+
+static inline void aesgcm_sw_coeff(aesgcm_sw *gcm, const unsigned char *coeff)
+{
+ /* XOR in the new coefficient */
+ gcm->acc.hi ^= GET_64BIT_MSB_FIRST(coeff);
+ gcm->acc.lo ^= GET_64BIT_MSB_FIRST(coeff + 8);
+
+ /* And now just loop over the bits of acc, making up a new value
+ * by XORing together the entries of 'table' corresponding to set
+ * bits. */
+
+ value128_t out;
+ out.lo = out.hi = 0;
+
+ const value128_t *tableptr = gcm->table;
+
+ for (size_t i = 0; i < 64; i++) {
+ uint64_t bit = 1 & gcm->acc.lo;
+ gcm->acc.lo >>= 1;
+ uint64_t mask = -bit;
+ out.hi ^= mask & tableptr->hi;
+ out.lo ^= mask & tableptr->lo;
+ tableptr++;
+ }
+ for (size_t i = 0; i < 64; i++) {
+ uint64_t bit = 1 & gcm->acc.hi;
+ gcm->acc.hi >>= 1;
+ uint64_t mask = -bit;
+ out.hi ^= mask & tableptr->hi;
+ out.lo ^= mask & tableptr->lo;
+ tableptr++;
+ }
+
+ gcm->acc = out;
+}
+
+static inline void aesgcm_sw_output(aesgcm_sw *gcm, unsigned char *output)
+{
+ PUT_64BIT_MSB_FIRST(output, gcm->acc.hi ^ gcm->mask.hi);
+ PUT_64BIT_MSB_FIRST(output + 8, gcm->acc.lo ^ gcm->mask.lo);
+ smemclr(&gcm->acc, 16);
+ smemclr(&gcm->mask, 16);
+}
+
+#define AESGCM_FLAVOUR sw
+#define AESGCM_NAME "unaccelerated"
+#include "aesgcm-footer.h"
diff --git a/crypto/aesgcm.h b/crypto/aesgcm.h
new file mode 100644
index 00000000..48077004
--- /dev/null
+++ b/crypto/aesgcm.h
@@ -0,0 +1,44 @@
+/*
+ * Common parts of the state structure for AESGCM MAC implementations.
+ */
+#define AESGCM_COMMON_FIELDS \
+ ssh_cipher *cipher; \
+ unsigned char partblk[16]; \
+ size_t skiplen, aadlen, ciphertextlen; \
+ size_t skipgot, aadgot, partlen; \
+ BinarySink_IMPLEMENTATION; \
+ ssh2_mac mac
+
+/*
+ * The 'extra' structure is used to include information about how to
+ * check if a given implementation is available at run time, and
+ * whether we've already checked.
+ */
+struct aesgcm_extra_mutable;
+struct aesgcm_extra {
+ /* Function to check availability. Might be expensive, so we don't
+ * want to call it more than once. */
+ bool (*check_available)(void);
+
+ /* Point to a writable substructure. */
+ struct aesgcm_extra_mutable *mut;
+
+ /*
+ * Extra API function specific to this MAC type that allows
+ * testcrypt to set more general lengths for skiplen and aadlen.
+ */
+ void (*set_prefix_lengths)(ssh2_mac *mac, size_t skip, size_t aad);
+};
+struct aesgcm_extra_mutable {
+ bool checked_availability;
+ bool is_available;
+};
+static inline bool check_aesgcm_availability(const struct aesgcm_extra *extra)
+{
+ if (!extra->mut->checked_availability) {
+ extra->mut->is_available = extra->check_available();
+ extra->mut->checked_availability = true;
+ }
+
+ return extra->mut->is_available;
+}
diff --git a/crypto/arcfour.c b/crypto/arcfour.c
new file mode 100644
index 00000000..87d59022
--- /dev/null
+++ b/crypto/arcfour.c
@@ -0,0 +1,143 @@
+/*
+ * Arcfour (RC4) implementation for PuTTY.
+ *
+ * Coded from Schneier.
+ */
+
+#include <assert.h>
+#include "ssh.h"
+
+typedef struct {
+ unsigned char i, j, s[256];
+ ssh_cipher ciph;
+} ArcfourContext;
+
+static void arcfour_block(void *handle, void *vblk, int len)
+{
+ unsigned char *blk = (unsigned char *)vblk;
+ ArcfourContext *ctx = (ArcfourContext *)handle;
+ unsigned k;
+ unsigned char tmp, i, j, *s;
+
+ s = ctx->s;
+ i = ctx->i; j = ctx->j;
+ for (k = 0; (int)k < len; k++) {
+ i = (i + 1) & 0xff;
+ j = (j + s[i]) & 0xff;
+ tmp = s[i]; s[i] = s[j]; s[j] = tmp;
+ blk[k] ^= s[(s[i]+s[j]) & 0xff];
+ }
+ ctx->i = i; ctx->j = j;
+}
+
+static void arcfour_setkey(ArcfourContext *ctx, unsigned char const *key,
+ unsigned keybytes)
+{
+ unsigned char tmp, k[256], *s;
+ unsigned i, j;
+
+ s = ctx->s;
+ assert(keybytes <= 256);
+ ctx->i = ctx->j = 0;
+ for (i = 0; i < 256; i++) {
+ s[i] = i;
+ k[i] = key[i % keybytes];
+ }
+ j = 0;
+ for (i = 0; i < 256; i++) {
+ j = (j + s[i] + k[i]) & 0xff;
+ tmp = s[i]; s[i] = s[j]; s[j] = tmp;
+ }
+}
+
+/* -- Interface with PuTTY -- */
+
+/*
+ * We don't implement Arcfour in SSH-1 because it's utterly insecure in
+ * several ways. See CERT Vulnerability Notes VU#25309, VU#665372,
+ * and VU#565052.
+ *
+ * We don't implement the "arcfour" algorithm in SSH-2 because it doesn't
+ * stir the cipher state before emitting keystream, and hence is likely
+ * to leak data about the key.
+ */
+
+static ssh_cipher *arcfour_new(const ssh_cipheralg *alg)
+{
+ ArcfourContext *ctx = snew(ArcfourContext);
+ ctx->ciph.vt = alg;
+ return &ctx->ciph;
+}
+
+static void arcfour_free(ssh_cipher *cipher)
+{
+ ArcfourContext *ctx = container_of(cipher, ArcfourContext, ciph);
+ smemclr(ctx, sizeof(*ctx));
+ sfree(ctx);
+}
+
+static void arcfour_stir(ArcfourContext *ctx)
+{
+ unsigned char *junk = snewn(1536, unsigned char);
+ memset(junk, 0, 1536);
+ arcfour_block(ctx, junk, 1536);
+ smemclr(junk, 1536);
+ sfree(junk);
+}
+
+static void arcfour_ssh2_setiv(ssh_cipher *cipher, const void *key)
+{
+ /* As a pure stream cipher, Arcfour has no IV separate from the key */
+}
+
+static void arcfour_ssh2_setkey(ssh_cipher *cipher, const void *key)
+{
+ ArcfourContext *ctx = container_of(cipher, ArcfourContext, ciph);
+ arcfour_setkey(ctx, key, ctx->ciph.vt->padded_keybytes);
+ arcfour_stir(ctx);
+}
+
+static void arcfour_ssh2_block(ssh_cipher *cipher, void *blk, int len)
+{
+ ArcfourContext *ctx = container_of(cipher, ArcfourContext, ciph);
+ arcfour_block(ctx, blk, len);
+}
+
+const ssh_cipheralg ssh_arcfour128_ssh2 = {
+ .new = arcfour_new,
+ .free = arcfour_free,
+ .setiv = arcfour_ssh2_setiv,
+ .setkey = arcfour_ssh2_setkey,
+ .encrypt = arcfour_ssh2_block,
+ .decrypt = arcfour_ssh2_block,
+ .next_message = nullcipher_next_message,
+ .ssh2_id = "arcfour128",
+ .blksize = 1,
+ .real_keybits = 128,
+ .padded_keybytes = 16,
+ .flags = 0,
+ .text_name = "Arcfour-128",
+};
+
+const ssh_cipheralg ssh_arcfour256_ssh2 = {
+ .new = arcfour_new,
+ .free = arcfour_free,
+ .setiv = arcfour_ssh2_setiv,
+ .setkey = arcfour_ssh2_setkey,
+ .encrypt = arcfour_ssh2_block,
+ .decrypt = arcfour_ssh2_block,
+ .next_message = nullcipher_next_message,
+ .ssh2_id = "arcfour256",
+ .blksize = 1,
+ .real_keybits = 256,
+ .padded_keybytes = 32,
+ .flags = 0,
+ .text_name = "Arcfour-256",
+};
+
+static const ssh_cipheralg *const arcfour_list[] = {
+ &ssh_arcfour256_ssh2,
+ &ssh_arcfour128_ssh2,
+};
+
+const ssh2_ciphers ssh2_arcfour = { lenof(arcfour_list), arcfour_list };
diff --git a/crypto/argon2.c b/crypto/argon2.c
new file mode 100644
index 00000000..99bcfa17
--- /dev/null
+++ b/crypto/argon2.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 <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "marshal.h"
+
+/* ----------------------------------------------------------------------
+ * Argon2 uses data marshalling rules similar to SSH but with 32-bit integers
+ * stored little-endian. Start with some local BinarySink routines for storing
+ * a uint32 and a string in that fashion.
+ */
+
+static void BinarySink_put_uint32_le(BinarySink *bs, unsigned long val)
+{
+ unsigned char data[4];
+ PUT_32BIT_LSB_FIRST(data, val);
+ bs->write(bs, data, sizeof(data));
+}
+
+static void BinarySink_put_stringpl_le(BinarySink *bs, ptrlen pl)
+{
+ /* Check that the string length fits in a uint32, without doing a
+ * potentially implementation-defined shift of more than 31 bits */
+ assert((pl.len >> 31) < 2);
+
+ BinarySink_put_uint32_le(bs, pl.len);
+ bs->write(bs, pl.ptr, pl.len);
+}
+
+#define put_uint32_le(bs, val) \
+ BinarySink_put_uint32_le(BinarySink_UPCAST(bs), val)
+#define put_stringpl_le(bs, val) \
+ BinarySink_put_stringpl_le(BinarySink_UPCAST(bs), val)
+
+/* ----------------------------------------------------------------------
+ * Argon2 defines a hash-function family that's an extension of BLAKE2b to
+ * generate longer output digests, by repeatedly outputting half of a BLAKE2
+ * hash output and then re-hashing the whole thing until there are 64 or fewer
+ * bytes left to output. The spec calls this H' (a variant of the original
+ * hash it calls H, which is the unmodified BLAKE2b).
+ */
+
+static ssh_hash *hprime_new(unsigned length)
+{
+ ssh_hash *h = blake2b_new_general(length > 64 ? 64 : length);
+ put_uint32_le(h, length);
+ return h;
+}
+
+static void hprime_final(ssh_hash *h, unsigned length, void *vout)
+{
+ uint8_t *out = (uint8_t *)vout;
+
+ while (length > 64) {
+ uint8_t hashbuf[64];
+ ssh_hash_final(h, hashbuf);
+
+ memcpy(out, hashbuf, 32);
+ out += 32;
+ length -= 32;
+
+ h = blake2b_new_general(length > 64 ? 64 : length);
+ put_data(h, hashbuf, 64);
+
+ smemclr(hashbuf, sizeof(hashbuf));
+ }
+
+ ssh_hash_final(h, out);
+}
+
+/* Externally visible entry point for the long hash function. This is only
+ * used by testcrypt, so it would be overkill to set it up like a proper
+ * ssh_hash. */
+strbuf *argon2_long_hash(unsigned length, ptrlen data)
+{
+ ssh_hash *h = hprime_new(length);
+ put_datapl(h, data);
+ strbuf *out = strbuf_new();
+ hprime_final(h, length, strbuf_append(out, length));
+ return out;
+}
+
+/* ----------------------------------------------------------------------
+ * Argon2's own mixing function G, which operates on 1Kb blocks of data.
+ *
+ * The definition of G in the spec takes two 1Kb blocks as input and produces
+ * a 1Kb output block. The first thing that happens to the input blocks is
+ * that they get XORed together, and then only the XOR output is used, so you
+ * could perfectly well regard G as a 1Kb->1Kb function.
+ */
+
+static inline uint64_t ror(uint64_t x, unsigned rotation)
+{
+ unsigned lshift = 63 & -rotation, rshift = 63 & rotation;
+ return (x << lshift) | (x >> rshift);
+}
+
+static inline uint64_t trunc32(uint64_t x)
+{
+ return x & 0xFFFFFFFF;
+}
+
+/* Internal function similar to the BLAKE2b round, which mixes up four 64-bit
+ * words */
+static inline void GB(uint64_t *a, uint64_t *b, uint64_t *c, uint64_t *d)
+{
+ *a += *b + 2 * trunc32(*a) * trunc32(*b);
+ *d = ror(*d ^ *a, 32);
+ *c += *d + 2 * trunc32(*c) * trunc32(*d);
+ *b = ror(*b ^ *c, 24);
+ *a += *b + 2 * trunc32(*a) * trunc32(*b);
+ *d = ror(*d ^ *a, 16);
+ *c += *d + 2 * trunc32(*c) * trunc32(*d);
+ *b = ror(*b ^ *c, 63);
+}
+
+/* Higher-level internal function which mixes up sixteen 64-bit words. This is
+ * applied to different subsets of the 128 words in a kilobyte block, and the
+ * API here is designed to make it easy to apply in the circumstances the spec
+ * requires. In every call, the sixteen words form eight pairs adjacent in
+ * memory, whose addresses are in arithmetic progression. So the 16 input
+ * words are in[0], in[1], in[instep], in[instep+1], ..., in[7*instep],
+ * in[7*instep+1], and the 16 output words similarly. */
+static inline void P(uint64_t *out, unsigned outstep,
+ uint64_t *in, unsigned instep)
+{
+ for (unsigned i = 0; i < 8; i++) {
+ out[i*outstep] = in[i*instep];
+ out[i*outstep+1] = in[i*instep+1];
+ }
+
+ GB(out+0*outstep+0, out+2*outstep+0, out+4*outstep+0, out+6*outstep+0);
+ GB(out+0*outstep+1, out+2*outstep+1, out+4*outstep+1, out+6*outstep+1);
+ GB(out+1*outstep+0, out+3*outstep+0, out+5*outstep+0, out+7*outstep+0);
+ GB(out+1*outstep+1, out+3*outstep+1, out+5*outstep+1, out+7*outstep+1);
+
+ GB(out+0*outstep+0, out+2*outstep+1, out+5*outstep+0, out+7*outstep+1);
+ GB(out+0*outstep+1, out+3*outstep+0, out+5*outstep+1, out+6*outstep+0);
+ GB(out+1*outstep+0, out+3*outstep+1, out+4*outstep+0, out+6*outstep+1);
+ GB(out+1*outstep+1, out+2*outstep+0, out+4*outstep+1, out+7*outstep+0);
+}
+
+/* The full G function, taking input blocks X and Y. The result of G is most
+ * often XORed into an existing output block, so this API is designed with
+ * that in mind: the mixing function's output is always XORed into whatever
+ * 1Kb of data is already at 'out'. */
+static void G_xor(uint8_t *out, const uint8_t *X, const uint8_t *Y)
+{
+ uint64_t R[128], Q[128], Z[128];
+
+ for (unsigned i = 0; i < 128; i++)
+ R[i] = GET_64BIT_LSB_FIRST(X + 8*i) ^ GET_64BIT_LSB_FIRST(Y + 8*i);
+
+ for (unsigned i = 0; i < 8; i++)
+ P(Q+16*i, 2, R+16*i, 2);
+
+ for (unsigned i = 0; i < 8; i++)
+ P(Z+2*i, 16, Q+2*i, 16);
+
+ for (unsigned i = 0; i < 128; i++)
+ PUT_64BIT_LSB_FIRST(out + 8*i,
+ GET_64BIT_LSB_FIRST(out + 8*i) ^ R[i] ^ Z[i]);
+
+ smemclr(R, sizeof(R));
+ smemclr(Q, sizeof(Q));
+ smemclr(Z, sizeof(Z));
+}
+
+/* ----------------------------------------------------------------------
+ * The main Argon2 function.
+ */
+
+static void argon2_internal(uint32_t p, uint32_t T, uint32_t m, uint32_t t,
+ uint32_t y, ptrlen P, ptrlen S, ptrlen K, ptrlen X,
+ uint8_t *out)
+{
+ /*
+ * Start by hashing all the input data together: the four string arguments
+ * (password P, salt S, optional secret key K, optional associated data
+ * X), plus all the parameters for the function's memory and time usage.
+ *
+ * The output of this hash is the sole input to the subsequent mixing
+ * step: Argon2 does not preserve any more entropy from the inputs, it
+ * just makes it extra painful to get the final answer.
+ */
+ uint8_t h0[64];
+ {
+ ssh_hash *h = blake2b_new_general(64);
+ put_uint32_le(h, p);
+ put_uint32_le(h, T);
+ put_uint32_le(h, m);
+ put_uint32_le(h, t);
+ put_uint32_le(h, 0x13); /* hash function version number */
+ put_uint32_le(h, y);
+ put_stringpl_le(h, P);
+ put_stringpl_le(h, S);
+ put_stringpl_le(h, K);
+ put_stringpl_le(h, X);
+ ssh_hash_final(h, h0);
+ }
+
+ struct blk { uint8_t data[1024]; };
+
+ /*
+ * Array of 1Kb blocks. The total size is (approximately) m, the
+ * caller-specified parameter for how much memory to use; the blocks are
+ * regarded as a rectangular array of p rows ('lanes') by q columns, where
+ * p is the 'parallelism' input parameter (the lanes can be processed
+ * concurrently up to a point) and q is whatever makes the product pq come
+ * to m.
+ *
+ * Additionally, each row is divided into four equal 'segments', which are
+ * important to the way the algorithm decides which blocks to use as input
+ * to each step of the function.
+ *
+ * The term 'slice' refers to a whole set of vertically aligned segments,
+ * i.e. slice 0 is the whole left quarter of the array, and slice 3 the
+ * whole right quarter.
+ */
+ size_t SL = m / (4*p); /* segment length: # of 1Kb blocks in a segment */
+ size_t q = 4 * SL; /* width of the array: 4 segments times SL */
+ size_t mprime = q * p; /* total size of the array, approximately m */
+
+ /* Allocate the memory. */
+ struct blk *B = snewn(mprime, struct blk);
+ memset(B, 0, mprime * sizeof(struct blk));
+
+ /*
+ * Initial setup: fill the first two full columns of the array with data
+ * expanded from the starting hash h0. Each block is the result of using
+ * the long-output hash function H' to hash h0 itself plus the block's
+ * coordinates in the array.
+ */
+ for (size_t i = 0; i < p; i++) {
+ ssh_hash *h = hprime_new(1024);
+ put_data(h, h0, 64);
+ put_uint32_le(h, 0);
+ put_uint32_le(h, i);
+ hprime_final(h, 1024, B[i].data);
+ }
+ for (size_t i = 0; i < p; i++) {
+ ssh_hash *h = hprime_new(1024);
+ put_data(h, h0, 64);
+ put_uint32_le(h, 1);
+ put_uint32_le(h, i);
+ hprime_final(h, 1024, B[i+p].data);
+ }
+
+ /*
+ * Declarations for the main loop.
+ *
+ * The basic structure of the main loop is going to involve processing the
+ * array one whole slice (vertically divided quarter) at a time. Usually
+ * we'll write a new value into every single block in the slice, except
+ * that in the initial slice on the first pass, we've already written
+ * values into the first two columns during the initial setup above. So
+ * 'jstart' indicates the starting index in each segment we process; it
+ * starts off as 2 so that we don't overwrite the initial setup, and then
+ * after the first slice is done, we set it to 0, and it stays there.
+ *
+ * d_mode indicates whether we're being data-dependent (true) or
+ * data-independent (false). In the hybrid Argon2id mode, we start off
+ * independent, and then once we've mixed things up enough, switch over to
+ * dependent mode to force long serial chains of computation.
+ */
+ size_t jstart = 2;
+ bool d_mode = (y == 0);
+ struct blk out2i, tmp2i, in2i;
+
+ /* Outermost loop: t whole passes from left to right over the array */
+ for (size_t pass = 0; pass < t; pass++) {
+
+ /* Within that, we process the array in its four main slices */
+ for (unsigned slice = 0; slice < 4; slice++) {
+
+ /* In Argon2id mode, if we're half way through the first pass,
+ * this is the moment to switch d_mode from false to true */
+ if (pass == 0 && slice == 2 && y == 2)
+ d_mode = true;
+
+ /* Loop over every segment in the slice (i.e. every row). So i is
+ * the y-coordinate of each block we process. */
+ for (size_t i = 0; i < p; i++) {
+
+ /* And within that segment, process the blocks from left to
+ * right, starting at 'jstart' (usually 0, but 2 in the first
+ * slice). */
+ for (size_t jpre = jstart; jpre < SL; jpre++) {
+
+ /* j is the x-coordinate of each block we process, made up
+ * of the slice number and the index 'jpre' within the
+ * segment. */
+ size_t j = slice * SL + jpre;
+
+ /* jm1 is j-1 (mod q) */
+ uint32_t jm1 = (j == 0 ? q-1 : j-1);
+
+ /*
+ * Construct two 32-bit pseudorandom integers J1 and J2.
+ * This is the part of the algorithm that varies between
+ * the data-dependent and independent modes.
+ */
+ uint32_t J1, J2;
+ if (d_mode) {
+ /*
+ * Data-dependent: grab the first 64 bits of the block
+ * to the left of this one.
+ */
+ J1 = GET_32BIT_LSB_FIRST(B[i + p * jm1].data);
+ J2 = GET_32BIT_LSB_FIRST(B[i + p * jm1].data + 4);
+ } else {
+ /*
+ * Data-independent: generate pseudorandom data by
+ * hashing a sequence of preimage blocks that include
+ * all our input parameters, plus the coordinates of
+ * this point in the algorithm (array position and
+ * pass number) to make all the hash outputs distinct.
+ *
+ * The hash we use is G itself, applied twice. So we
+ * generate 1Kb of data at a time, which is enough for
+ * 128 (J1,J2) pairs. Hence we only need to do the
+ * hashing if our index within the segment is a
+ * multiple of 128, or if we're at the very start of
+ * the algorithm (in which case we started at 2 rather
+ * than 0). After that we can just keep picking data
+ * out of our most recent hash output.
+ */
+ if (jpre == jstart || jpre % 128 == 0) {
+ /*
+ * Hash preimage is mostly zeroes, with a
+ * collection of assorted integer values we had
+ * anyway.
+ */
+ memset(in2i.data, 0, sizeof(in2i.data));
+ PUT_64BIT_LSB_FIRST(in2i.data + 0, pass);
+ PUT_64BIT_LSB_FIRST(in2i.data + 8, i);
+ PUT_64BIT_LSB_FIRST(in2i.data + 16, slice);
+ PUT_64BIT_LSB_FIRST(in2i.data + 24, mprime);
+ PUT_64BIT_LSB_FIRST(in2i.data + 32, t);
+ PUT_64BIT_LSB_FIRST(in2i.data + 40, y);
+ PUT_64BIT_LSB_FIRST(in2i.data + 48, jpre / 128 + 1);
+
+ /*
+ * Now apply G twice to generate the hash output
+ * in out2i.
+ */
+ memset(tmp2i.data, 0, sizeof(tmp2i.data));
+ G_xor(tmp2i.data, tmp2i.data, in2i.data);
+ memset(out2i.data, 0, sizeof(out2i.data));
+ G_xor(out2i.data, out2i.data, tmp2i.data);
+ }
+
+ /*
+ * Extract J1 and J2 from the most recent hash output
+ * (whether we've just computed it or not).
+ */
+ J1 = GET_32BIT_LSB_FIRST(
+ out2i.data + 8 * (jpre % 128));
+ J2 = GET_32BIT_LSB_FIRST(
+ out2i.data + 8 * (jpre % 128) + 4);
+ }
+
+ /*
+ * Now convert J1 and J2 into the index of an existing
+ * block of the array to use as input to this step. This
+ * is fairly fiddly.
+ *
+ * The easy part: the y-coordinate of the input block is
+ * obtained by reducing J2 mod p, except that at the very
+ * start of the algorithm (processing the first slice on
+ * the first pass) we simply use the same y-coordinate as
+ * our output block.
+ *
+ * Note that it's safe to use the ordinary % operator
+ * here, without any concern for timing side channels: in
+ * data-independent mode J2 is not correlated to any
+ * secrets, and in data-dependent mode we're going to be
+ * giving away side-channel data _anyway_ when we use it
+ * as an array index (and by assumption we don't care,
+ * because it's already massively randomised from the real
+ * inputs).
+ */
+ uint32_t index_l = (pass == 0 && slice == 0) ? i : J2 % p;
+
+ /*
+ * The hard part: which block in this array row do we use?
+ *
+ * First, we decide what the possible candidates are. This
+ * requires some case analysis, and depends on whether the
+ * array row is the same one we're writing into or not.
+ *
+ * If it's not the same row: we can't use any block from
+ * the current slice (because the segments within a slice
+ * have to be processable in parallel, so in a concurrent
+ * implementation those blocks are potentially in the
+ * process of being overwritten by other threads). But the
+ * other three slices are fair game, except that in the
+ * first pass, slices to the right of us won't have had
+ * any values written into them yet at all.
+ *
+ * If it is the same row, we _are_ allowed to use blocks
+ * from the current slice, but only the ones before our
+ * current position.
+ *
+ * In both cases, we also exclude the individual _column_
+ * just to the left of the current one. (The block
+ * immediately to our left is going to be the _other_
+ * input to G, but the spec also says that we avoid that
+ * column even in a different row.)
+ *
+ * All of this means that we end up choosing from a
+ * cyclically contiguous interval of blocks within this
+ * lane, but the start and end points require some thought
+ * to get them right.
+ */
+
+ /* Start position is the beginning of the _next_ slice
+ * (containing data from the previous pass), unless we're
+ * on pass 0, where the start position has to be 0. */
+ uint32_t Wstart = (pass == 0 ? 0 : (slice + 1) % 4 * SL);
+
+ /* End position splits up by cases. */
+ uint32_t Wend;
+ if (index_l == i) {
+ /* Same lane as output: we can use anything up to (but
+ * not including) the block immediately left of us. */
+ Wend = jm1;
+ } else {
+ /* Different lane from output: we can use anything up
+ * to the previous slice boundary, or one less than
+ * that if we're at the very left edge of our slice
+ * right now. */
+ Wend = SL * slice;
+ if (jpre == 0)
+ Wend = (Wend + q-1) % q;
+ }
+
+ /* Total number of blocks available to choose from */
+ uint32_t Wsize = (Wend + q - Wstart) % q;
+
+ /* Fiddly computation from the spec that chooses from the
+ * available blocks, in a deliberately non-uniform
+ * fashion, using J1 as pseudorandom input data. Output is
+ * zz which is the index within our contiguous interval. */
+ uint32_t x = ((uint64_t)J1 * J1) >> 32;
+ uint32_t y = ((uint64_t)Wsize * x) >> 32;
+ uint32_t zz = Wsize - 1 - y;
+
+ /* And index_z is the actual x coordinate of the block we
+ * want. */
+ uint32_t index_z = (Wstart + zz) % q;
+
+ /* Phew! Combine that block with the one immediately to
+ * our left, and XOR over the top of whatever is already
+ * in our current output block. */
+ G_xor(B[i + p * j].data, B[i + p * jm1].data,
+ B[index_l + p * index_z].data);
+ }
+ }
+
+ /* We've finished processing a slice. Reset jstart to 0. It will
+ * onily _not_ have been 0 if this was pass 0 slice 0, in which
+ * case it still had its initial value of 2 to avoid the starting
+ * data. */
+ jstart = 0;
+ }
+ }
+
+ /*
+ * The main output is all done. Final output works by taking the XOR of
+ * all the blocks in the rightmost column of the array, and then using
+ * that as input to our long hash H'. The output of _that_ is what we
+ * deliver to the caller.
+ */
+
+ struct blk C = B[p * (q-1)];
+ for (size_t i = 1; i < p; i++)
+ memxor(C.data, C.data, B[i + p * (q-1)].data, 1024);
+
+ {
+ ssh_hash *h = hprime_new(T);
+ put_data(h, C.data, 1024);
+ hprime_final(h, T, out);
+ }
+
+ /*
+ * Clean up.
+ */
+ smemclr(out2i.data, sizeof(out2i.data));
+ smemclr(tmp2i.data, sizeof(tmp2i.data));
+ smemclr(in2i.data, sizeof(in2i.data));
+ smemclr(C.data, sizeof(C.data));
+ smemclr(B, mprime * sizeof(struct blk));
+ sfree(B);
+}
+
+/*
+ * Wrapper function that appends to a strbuf (which sshpubk.c will want).
+ */
+void argon2(Argon2Flavour flavour, uint32_t mem, uint32_t passes,
+ uint32_t parallel, uint32_t taglen,
+ ptrlen P, ptrlen S, ptrlen K, ptrlen X, strbuf *out)
+{
+ argon2_internal(parallel, taglen, mem, passes, flavour,
+ P, S, K, X, strbuf_append(out, taglen));
+}
+
+/*
+ * Wrapper function which dynamically chooses the number of passes to run in
+ * order to hit an approximate total amount of CPU time. Writes the result
+ * into 'passes'.
+ */
+void argon2_choose_passes(
+ Argon2Flavour flavour, uint32_t mem,
+ uint32_t milliseconds, uint32_t *passes,
+ uint32_t parallel, uint32_t taglen,
+ ptrlen P, ptrlen S, ptrlen K, ptrlen X,
+ strbuf *out)
+{
+ unsigned long desired_time = (TICKSPERSEC * milliseconds) / 1000;
+
+ /*
+ * We only need the time taken to be approximately right, so we
+ * scale up the number of passes geometrically, which avoids
+ * taking O(t^2) time to find a pass count taking time t.
+ *
+ * Using the Fibonacci numbers is slightly nicer than the obvious
+ * approach of powers of 2, because it's still very easy to
+ * compute, and grows less fast (powers of 1.6 instead of 2), so
+ * you get just a touch more precision.
+ */
+ uint32_t a = 1, b = 1;
+
+ while (true) {
+ unsigned long start_time = GETTICKCOUNT();
+ argon2(flavour, mem, b, parallel, taglen, P, S, K, X, out);
+ unsigned long ticks = GETTICKCOUNT() - start_time;
+
+ /* But just in case computers get _too_ fast, we have to cap
+ * the growth before it gets past the uint32_t upper bound! So
+ * if computing a+b would overflow, stop here. */
+
+ if (ticks >= desired_time || a > (uint32_t)~b) {
+ *passes = b;
+ return;
+ } else {
+ strbuf_clear(out);
+
+ /* Next Fibonacci number: replace (a, b) with (b, a+b) */
+ b += a;
+ a = b - a;
+ }
+ }
+}
diff --git a/crypto/bcrypt.c b/crypto/bcrypt.c
new file mode 100644
index 00000000..1ccd4756
--- /dev/null
+++ b/crypto/bcrypt.c
@@ -0,0 +1,118 @@
+/*
+ * '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 <stddef.h>
+#include <string.h>
+#include "ssh.h"
+#include "blowfish.h"
+
+static BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes,
+ const unsigned char *salt, int saltbytes)
+{
+ int i;
+ BlowfishContext *ctx;
+
+ ctx = blowfish_make_context();
+ blowfish_initkey(ctx);
+ blowfish_expandkey(ctx, key, keybytes, salt, saltbytes);
+
+ /* Original bcrypt replaces this fixed loop count with the
+ * variable cost. OpenSSH instead iterates the whole thing more
+ * than once if it wants extra rounds. */
+ for (i = 0; i < 64; i++) {
+ blowfish_expandkey(ctx, salt, saltbytes, NULL, 0);
+ blowfish_expandkey(ctx, key, keybytes, NULL, 0);
+ }
+
+ return ctx;
+}
+
+static void bcrypt_hash(const unsigned char *key, int keybytes,
+ const unsigned char *salt, int saltbytes,
+ unsigned char output[32])
+{
+ BlowfishContext *ctx;
+ int i;
+
+ ctx = bcrypt_setup(key, keybytes, salt, saltbytes);
+ /* This was quite a nice starting string until it ran into
+ * little-endian Blowfish :-/ */
+ memcpy(output, "cyxOmorhcitawolBhsiftawSanyDetim", 32);
+ for (i = 0; i < 64; i++) {
+ blowfish_lsb_encrypt_ecb(output, 32, ctx);
+ }
+ blowfish_free_context(ctx);
+}
+
+static void bcrypt_genblock(int counter,
+ const unsigned char hashed_passphrase[64],
+ const unsigned char *salt, int saltbytes,
+ unsigned char output[32])
+{
+ unsigned char hashed_salt[64];
+
+ /* Hash the input salt with the counter value optionally suffixed
+ * to get our real 32-byte salt */
+ ssh_hash *h = ssh_hash_new(&ssh_sha512);
+ put_data(h, salt, saltbytes);
+ if (counter)
+ put_uint32(h, counter);
+ ssh_hash_final(h, hashed_salt);
+
+ bcrypt_hash(hashed_passphrase, 64, hashed_salt, 64, output);
+
+ smemclr(&hashed_salt, sizeof(hashed_salt));
+}
+
+void openssh_bcrypt(ptrlen passphrase, ptrlen salt,
+ int rounds, unsigned char *out, int outbytes)
+{
+ unsigned char hashed_passphrase[64];
+ unsigned char block[32], outblock[32];
+ const unsigned char *thissalt;
+ int thissaltbytes;
+ int modulus, residue, i, j, round;
+
+ /* Hash the passphrase to get the bcrypt key material */
+ hash_simple(&ssh_sha512, passphrase, hashed_passphrase);
+
+ /* We output key bytes in a scattered fashion to meld all output
+ * key blocks into all parts of the output. To do this, we pick a
+ * modulus, and we output the key bytes to indices of out[] in the
+ * following order: first the indices that are multiples of the
+ * modulus, then the ones congruent to 1 mod modulus, etc. Each of
+ * those passes consumes exactly one block output from
+ * bcrypt_genblock, so we must pick a modulus large enough that at
+ * most 32 bytes are used in the pass. */
+ modulus = (outbytes + 31) / 32;
+
+ for (residue = 0; residue < modulus; residue++) {
+ /* Our output block of data is the XOR of all blocks generated
+ * by bcrypt in the following loop */
+ memset(outblock, 0, sizeof(outblock));
+
+ thissalt = salt.ptr;
+ thissaltbytes = salt.len;
+ for (round = 0; round < rounds; round++) {
+ bcrypt_genblock(round == 0 ? residue+1 : 0,
+ hashed_passphrase,
+ thissalt, thissaltbytes, block);
+ /* Each subsequent bcrypt call reuses the previous one's
+ * output as its salt */
+ thissalt = block;
+ thissaltbytes = 32;
+
+ for (i = 0; i < 32; i++)
+ outblock[i] ^= block[i];
+ }
+
+ for (i = residue, j = 0; i < outbytes; i += modulus, j++)
+ out[i] = outblock[j];
+ }
+ smemclr(&hashed_passphrase, sizeof(hashed_passphrase));
+}
diff --git a/sshblake2.c b/crypto/blake2.c
index a4d42f21..a4d42f21 100644
--- a/sshblake2.c
+++ b/crypto/blake2.c
diff --git a/crypto/blowfish.c b/crypto/blowfish.c
new file mode 100644
index 00000000..a4e44652
--- /dev/null
+++ b/crypto/blowfish.c
@@ -0,0 +1,702 @@
+/*
+ * Blowfish implementation for PuTTY.
+ *
+ * Coded from scratch from the algorithm description.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include "ssh.h"
+#include "blowfish.h"
+
+struct BlowfishContext {
+ uint32_t S0[256], S1[256], S2[256], S3[256], P[18];
+ uint32_t iv0, iv1; /* for CBC mode */
+};
+
+/*
+ * The Blowfish init data: hex digits of the fractional part of pi.
+ * (ie pi as a hex fraction is 3.243F6A8885A308D3...)
+ *
+ * If you have Simon Tatham's 'spigot' exact real calculator
+ * available, or any other method of generating 8336 fractional hex
+ * digits of pi on standard output, you can regenerate these tables
+ * exactly as below using the following Perl script (adjusting the
+ * first line or two if your pi-generator is not spigot).
+
+open my $spig, "spigot -n -B16 -d8336 pi |";
+read $spig, $ignore, 2; # throw away the leading "3."
+for my $name ("parray", "sbox0".."sbox3") {
+ print "static const uint32_t ${name}[] = {\n";
+ my $len = $name eq "parray" ? 18 : 256;
+ for my $i (1..$len) {
+ read $spig, $word, 8;
+ printf "%s0x%s,", ($i%6==1 ? " " : " "), uc $word;
+ print "\n" if ($i == $len || $i%6 == 0);
+ }
+ print "};\n\n";
+}
+close $spig;
+
+ */
+static const uint32_t parray[] = {
+ 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0,
+ 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C,
+ 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, 0x9216D5D9, 0x8979FB1B,
+};
+
+static const uint32_t sbox0[] = {
+ 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96,
+ 0xBA7C9045, 0xF12C7F99, 0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16,
+ 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, 0x728EB658,
+ 0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013,
+ 0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF, 0x8E79DCB0, 0x603A180E,
+ 0x6C9E0E8B, 0xB01E8A3E, 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60,
+ 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 0x55CA396A, 0x2AAB10B6,
+ 0xB4CC5C34, 0x1141E8CE, 0xA15486AF, 0x7C72E993, 0xB3EE1411, 0x636FBC2A,
+ 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, 0xAFD6BA33, 0x6C24CF5C,
+ 0x7A325381, 0x28958677, 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193,
+ 0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032, 0xEF845D5D, 0xE98575B1,
+ 0xDC262302, 0xEB651B88, 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239,
+ 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 0x21C66842, 0xF6E96C9A,
+ 0x670C9C61, 0xABD388F0, 0x6A51A0D2, 0xD8542F68, 0x960FA728, 0xAB5133A3,
+ 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 0xA1F1651D, 0x39AF0176,
+ 0x66CA593E, 0x82430E88, 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE,
+ 0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6, 0x4ED3AA62, 0x363F7706,
+ 0x1BFEDF72, 0x429B023D, 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B,
+ 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 0xE3FE501A, 0xB6794C3B,
+ 0x976CE0BD, 0x04C006BA, 0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2, 0x196A2463,
+ 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 0x6DFC511F, 0x9B30952C,
+ 0xCC814544, 0xAF5EBD09, 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3,
+ 0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB, 0x5579C0BD, 0x1A60320A,
+ 0xD6A100C6, 0x402C7279, 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8,
+ 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 0x323DB5FA, 0xFD238760,
+ 0x53317B48, 0x3E00DF82, 0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB,
+ 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 0x695B27B0, 0xBBCA58C8,
+ 0xE1FFA35D, 0xB8F011A0, 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B,
+ 0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790, 0xE1DDF2DA, 0xA4CB7E33,
+ 0x62FB1341, 0xCEE4C6E8, 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4,
+ 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 0xD08ED1D0, 0xAFC725E0,
+ 0x8E3C5B2F, 0x8E7594B7, 0x8FF6E2FB, 0xF2122B64, 0x8888B812, 0x900DF01C,
+ 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 0x2F2F2218, 0xBE0E1777,
+ 0xEA752DFE, 0x8B021FA1, 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299,
+ 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9, 0x165FA266, 0x80957705,
+ 0x93CC7314, 0x211A1477, 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF,
+ 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 0x00250E2D, 0x2071B35E,
+ 0x226800BB, 0x57B8E0AF, 0x2464369B, 0xF009B91E, 0x5563911D, 0x59DFA6AA,
+ 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 0x83260376, 0x6295CFA9,
+ 0x11C81968, 0x4E734A41, 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915,
+ 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400, 0x08BA6FB5, 0x571BE91F,
+ 0xF296EC6B, 0x2A0DD915, 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664,
+ 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A,
+};
+
+static const uint32_t sbox1[] = {
+ 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D,
+ 0x9CEE60B8, 0x8FEDB266, 0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1,
+ 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65,
+ 0x6B8FE4D6, 0x99F73FD6, 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1,
+ 0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E, 0x09686B3F, 0x3EBAEFC9,
+ 0x3C971814, 0x6B6A70A1, 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737,
+ 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 0xB03ADA37, 0xF0500C0D,
+ 0xF01C1F04, 0x0200B3FF, 0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD,
+ 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 0x3AE5E581, 0x37C2DADC,
+ 0xC8B57634, 0x9AF3DDA7, 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41,
+ 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331, 0x4E548B38, 0x4F6DB908,
+ 0x6F420D03, 0xF60A04BF, 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF,
+ 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 0x5512721F, 0x2E6B7124,
+ 0x501ADDE6, 0x9F84CD87, 0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C,
+ 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 0xEF1C1847, 0x3215D908,
+ 0xDD433B37, 0x24C2BA16, 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD,
+ 0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B, 0x043556F1, 0xD7A3C76B,
+ 0x3C11183B, 0x5924A509, 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E,
+ 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 0x771FE71C, 0x4E3D06FA,
+ 0x2965DCB9, 0x99E71D0F, 0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A,
+ 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 0xF2F74EA7, 0x361D2B3D,
+ 0x1939260F, 0x19C27960, 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66,
+ 0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28, 0xC332DDEF, 0xBE6C5AA5,
+ 0x65582185, 0x68AB9802, 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84,
+ 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 0x13CCA830, 0xEB61BD96,
+ 0x0334FE1E, 0xAA0363CF, 0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14,
+ 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 0x648B1EAF, 0x19BDF0CA,
+ 0xA02369B9, 0x655ABB50, 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7,
+ 0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8, 0xF837889A, 0x97E32D77,
+ 0x11ED935F, 0x16681281, 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99,
+ 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 0xCDB30AEB, 0x532E3054,
+ 0x8FD948E4, 0x6DBC3128, 0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73,
+ 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 0x45EEE2B6, 0xA3AAABEA,
+ 0xDB6C4F15, 0xFACB4FD0, 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105,
+ 0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250, 0xCF62A1F2, 0x5B8D2646,
+ 0xFC8883A0, 0xC1C7B6A3, 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285,
+ 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 0x58428D2A, 0x0C55F5EA,
+ 0x1DADF43E, 0x233F7061, 0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB,
+ 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 0xA6078084, 0x19F8509E,
+ 0xE8EFD855, 0x61D99735, 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC,
+ 0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9, 0xDB73DBD3, 0x105588CD,
+ 0x675FDA79, 0xE3674340, 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20,
+ 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7,
+};
+
+static const uint32_t sbox2[] = {
+ 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7,
+ 0xBCF46B2E, 0xD4A20068, 0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF,
+ 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF,
+ 0x70F4DDD3, 0x66A02F45, 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504,
+ 0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A, 0x28507825, 0x530429F4,
+ 0x0A2C86DA, 0xE9B66DFB, 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE,
+ 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 0xAACE1E7C, 0xD3375FEC,
+ 0xCE78A399, 0x406B2A42, 0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B,
+ 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 0x3A6EFA74, 0xDD5B4332,
+ 0x6841E7F7, 0xCA7820FB, 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527,
+ 0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B, 0x55A867BC, 0xA1159A58,
+ 0xCCA92963, 0x99E1DB33, 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C,
+ 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 0x95C11548, 0xE4C66D22,
+ 0x48C1133F, 0xC70F86DC, 0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17,
+ 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 0x257B7834, 0x602A9C60,
+ 0xDFF8E8A3, 0x1F636C1B, 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115,
+ 0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922, 0x85B2A20E, 0xE6BA0D99,
+ 0xDE720C8C, 0x2DA2F728, 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0,
+ 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 0x0A476341, 0x992EFF74,
+ 0x3A6F6EAB, 0xF4F8FD37, 0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D,
+ 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 0xF1290DC7, 0xCC00FFA3,
+ 0xB5390F92, 0x690FED0B, 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3,
+ 0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB, 0x37392EB3, 0xCC115979,
+ 0x8026E297, 0xF42E312D, 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C,
+ 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 0x1A6B1018, 0x11CAEDFA,
+ 0x3D25BDD8, 0xE2E1C3C9, 0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A,
+ 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 0x9DBC8057, 0xF0F7C086,
+ 0x60787BF8, 0x6003604D, 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC,
+ 0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F, 0x77A057BE, 0xBDE8AE24,
+ 0x55464299, 0xBF582E61, 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2,
+ 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 0x7AEB2661, 0x8B1DDF84,
+ 0x846A0E79, 0x915F95E2, 0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C,
+ 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 0xB77F19B6, 0xE0A9DC09,
+ 0x662D09A1, 0xC4324633, 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10,
+ 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169, 0xDCB7DA83, 0x573906FE,
+ 0xA1E2CE9B, 0x4FCD7F52, 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027,
+ 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 0xF0177A28, 0xC0F586E0,
+ 0x006058AA, 0x30DC7D62, 0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634,
+ 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 0x6F05E409, 0x4B7C0188,
+ 0x39720A3D, 0x7C927C24, 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC,
+ 0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4, 0x1E50EF5E, 0xB161E6F8,
+ 0xA28514D9, 0x6C51133C, 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837,
+ 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0,
+};
+
+static const uint32_t sbox3[] = {
+ 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742,
+ 0xD3822740, 0x99BC9BBE, 0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B,
+ 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79,
+ 0xC6A376D2, 0x6549C2C8, 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6,
+ 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304, 0xA1FAD5F0, 0x6A2D519A,
+ 0x63EF8CE2, 0x9A86EE22, 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4,
+ 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 0x2826A2F9, 0xA73A3AE1,
+ 0x4BA99586, 0xEF5562E9, 0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59,
+ 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 0xE990FD5A, 0x9E34D797,
+ 0x2CF0B7D9, 0x022B8B51, 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28,
+ 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C, 0xE029AC71, 0xE019A5E6,
+ 0x47B0ACFD, 0xED93FA9B, 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28,
+ 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 0x15056DD4, 0x88F46DBA,
+ 0x03A16125, 0x0564F0BD, 0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A,
+ 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 0x7533D928, 0xB155FDF5,
+ 0x03563482, 0x8ABA3CBB, 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F,
+ 0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991, 0xEA7A90C2, 0xFB3E7BCE,
+ 0x5121CE64, 0x774FBE32, 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680,
+ 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 0xB39A460A, 0x6445C0DD,
+ 0x586CDECF, 0x1C20C8AE, 0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB,
+ 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 0x72EACEA8, 0xFA6484BB,
+ 0x8D6612AE, 0xBF3C6F47, 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370,
+ 0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D, 0x4040CB08, 0x4EB4E2CC,
+ 0x34D2466A, 0x0115AF84, 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048,
+ 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 0x611560B1, 0xE7933FDC,
+ 0xBB3A792B, 0x344525BD, 0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9,
+ 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 0x1A908749, 0xD44FBD9A,
+ 0xD0DADECB, 0xD50ADA38, 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F,
+ 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C, 0xBF97222C, 0x15E6FC2A,
+ 0x0F91FC71, 0x9B941525, 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1,
+ 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 0xE0EC6E0E, 0x1698DB3B,
+ 0x4C98A0BE, 0x3278E964, 0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E,
+ 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 0xDF359F8D, 0x9B992F2E,
+ 0xE60B6F47, 0x0FE3F11D, 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F,
+ 0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299, 0xF523F357, 0xA6327623,
+ 0x93A83531, 0x56CCCD02, 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC,
+ 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 0xE6C6C7BD, 0x327A140A,
+ 0x45E1D006, 0xC3F27B9A, 0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6,
+ 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 0x53113EC0, 0x1640E3D3,
+ 0x38ABBD60, 0x2547ADF0, 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060,
+ 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E, 0x1948C25C, 0x02FB8A8C,
+ 0x01C36AE4, 0xD6EBE1F9, 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F,
+ 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6,
+};
+
+#define Fprime(a,b,c,d) ( ( (S0[a] + S1[b]) ^ S2[c] ) + S3[d] )
+#define F(x) Fprime( ((x>>24)&0xFF), ((x>>16)&0xFF), ((x>>8)&0xFF), (x&0xFF) )
+#define ROUND(n) ( xL ^= P[n], t = xL, xL = F(xL) ^ xR, xR = t )
+
+static void blowfish_encrypt(uint32_t xL, uint32_t xR, uint32_t *output,
+ BlowfishContext *ctx)
+{
+ uint32_t *S0 = ctx->S0;
+ uint32_t *S1 = ctx->S1;
+ uint32_t *S2 = ctx->S2;
+ uint32_t *S3 = ctx->S3;
+ uint32_t *P = ctx->P;
+ uint32_t t;
+
+ ROUND(0);
+ ROUND(1);
+ ROUND(2);
+ ROUND(3);
+ ROUND(4);
+ ROUND(5);
+ ROUND(6);
+ ROUND(7);
+ ROUND(8);
+ ROUND(9);
+ ROUND(10);
+ ROUND(11);
+ ROUND(12);
+ ROUND(13);
+ ROUND(14);
+ ROUND(15);
+ xL ^= P[16];
+ xR ^= P[17];
+
+ output[0] = xR;
+ output[1] = xL;
+}
+
+static void blowfish_decrypt(uint32_t xL, uint32_t xR, uint32_t *output,
+ BlowfishContext *ctx)
+{
+ uint32_t *S0 = ctx->S0;
+ uint32_t *S1 = ctx->S1;
+ uint32_t *S2 = ctx->S2;
+ uint32_t *S3 = ctx->S3;
+ uint32_t *P = ctx->P;
+ uint32_t t;
+
+ ROUND(17);
+ ROUND(16);
+ ROUND(15);
+ ROUND(14);
+ ROUND(13);
+ ROUND(12);
+ ROUND(11);
+ ROUND(10);
+ ROUND(9);
+ ROUND(8);
+ ROUND(7);
+ ROUND(6);
+ ROUND(5);
+ ROUND(4);
+ ROUND(3);
+ ROUND(2);
+ xL ^= P[1];
+ xR ^= P[0];
+
+ output[0] = xR;
+ output[1] = xL;
+}
+
+static void blowfish_lsb_encrypt_cbc(unsigned char *blk, int len,
+ BlowfishContext *ctx)
+{
+ uint32_t xL, xR, out[2], iv0, iv1;
+
+ assert((len & 7) == 0);
+
+ iv0 = ctx->iv0;
+ iv1 = ctx->iv1;
+
+ while (len > 0) {
+ xL = GET_32BIT_LSB_FIRST(blk);
+ xR = GET_32BIT_LSB_FIRST(blk + 4);
+ iv0 ^= xL;
+ iv1 ^= xR;
+ blowfish_encrypt(iv0, iv1, out, ctx);
+ iv0 = out[0];
+ iv1 = out[1];
+ PUT_32BIT_LSB_FIRST(blk, iv0);
+ PUT_32BIT_LSB_FIRST(blk + 4, iv1);
+ blk += 8;
+ len -= 8;
+ }
+
+ ctx->iv0 = iv0;
+ ctx->iv1 = iv1;
+}
+
+void blowfish_lsb_encrypt_ecb(void *vblk, int len, BlowfishContext *ctx)
+{
+ unsigned char *blk = (unsigned char *)vblk;
+ uint32_t xL, xR, out[2];
+
+ assert((len & 7) == 0);
+
+ while (len > 0) {
+ xL = GET_32BIT_LSB_FIRST(blk);
+ xR = GET_32BIT_LSB_FIRST(blk + 4);
+ blowfish_encrypt(xL, xR, out, ctx);
+ PUT_32BIT_LSB_FIRST(blk, out[0]);
+ PUT_32BIT_LSB_FIRST(blk + 4, out[1]);
+ blk += 8;
+ len -= 8;
+ }
+}
+
+static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len,
+ BlowfishContext *ctx)
+{
+ uint32_t xL, xR, out[2], iv0, iv1;
+
+ assert((len & 7) == 0);
+
+ iv0 = ctx->iv0;
+ iv1 = ctx->iv1;
+
+ while (len > 0) {
+ xL = GET_32BIT_LSB_FIRST(blk);
+ xR = GET_32BIT_LSB_FIRST(blk + 4);
+ blowfish_decrypt(xL, xR, out, ctx);
+ iv0 ^= out[0];
+ iv1 ^= out[1];
+ PUT_32BIT_LSB_FIRST(blk, iv0);
+ PUT_32BIT_LSB_FIRST(blk + 4, iv1);
+ iv0 = xL;
+ iv1 = xR;
+ blk += 8;
+ len -= 8;
+ }
+
+ ctx->iv0 = iv0;
+ ctx->iv1 = iv1;
+}
+
+static void blowfish_msb_encrypt_cbc(unsigned char *blk, int len,
+ BlowfishContext *ctx)
+{
+ uint32_t xL, xR, out[2], iv0, iv1;
+
+ assert((len & 7) == 0);
+
+ iv0 = ctx->iv0;
+ iv1 = ctx->iv1;
+
+ while (len > 0) {
+ xL = GET_32BIT_MSB_FIRST(blk);
+ xR = GET_32BIT_MSB_FIRST(blk + 4);
+ iv0 ^= xL;
+ iv1 ^= xR;
+ blowfish_encrypt(iv0, iv1, out, ctx);
+ iv0 = out[0];
+ iv1 = out[1];
+ PUT_32BIT_MSB_FIRST(blk, iv0);
+ PUT_32BIT_MSB_FIRST(blk + 4, iv1);
+ blk += 8;
+ len -= 8;
+ }
+
+ ctx->iv0 = iv0;
+ ctx->iv1 = iv1;
+}
+
+static void blowfish_msb_decrypt_cbc(unsigned char *blk, int len,
+ BlowfishContext *ctx)
+{
+ uint32_t xL, xR, out[2], iv0, iv1;
+
+ assert((len & 7) == 0);
+
+ iv0 = ctx->iv0;
+ iv1 = ctx->iv1;
+
+ while (len > 0) {
+ xL = GET_32BIT_MSB_FIRST(blk);
+ xR = GET_32BIT_MSB_FIRST(blk + 4);
+ blowfish_decrypt(xL, xR, out, ctx);
+ iv0 ^= out[0];
+ iv1 ^= out[1];
+ PUT_32BIT_MSB_FIRST(blk, iv0);
+ PUT_32BIT_MSB_FIRST(blk + 4, iv1);
+ iv0 = xL;
+ iv1 = xR;
+ blk += 8;
+ len -= 8;
+ }
+
+ ctx->iv0 = iv0;
+ ctx->iv1 = iv1;
+}
+
+static void blowfish_msb_sdctr(unsigned char *blk, int len,
+ BlowfishContext *ctx)
+{
+ uint32_t b[2], iv0, iv1, tmp;
+
+ assert((len & 7) == 0);
+
+ iv0 = ctx->iv0;
+ iv1 = ctx->iv1;
+
+ while (len > 0) {
+ blowfish_encrypt(iv0, iv1, b, ctx);
+ tmp = GET_32BIT_MSB_FIRST(blk);
+ PUT_32BIT_MSB_FIRST(blk, tmp ^ b[0]);
+ tmp = GET_32BIT_MSB_FIRST(blk + 4);
+ PUT_32BIT_MSB_FIRST(blk + 4, tmp ^ b[1]);
+ if ((iv1 = (iv1 + 1) & 0xffffffff) == 0)
+ iv0 = (iv0 + 1) & 0xffffffff;
+ blk += 8;
+ len -= 8;
+ }
+
+ ctx->iv0 = iv0;
+ ctx->iv1 = iv1;
+}
+
+void blowfish_initkey(BlowfishContext *ctx)
+{
+ int i;
+
+ for (i = 0; i < 18; i++) {
+ ctx->P[i] = parray[i];
+ }
+
+ for (i = 0; i < 256; i++) {
+ ctx->S0[i] = sbox0[i];
+ ctx->S1[i] = sbox1[i];
+ ctx->S2[i] = sbox2[i];
+ ctx->S3[i] = sbox3[i];
+ }
+}
+
+void blowfish_expandkey(BlowfishContext *ctx,
+ const void *vkey, short keybytes,
+ const void *vsalt, short saltbytes)
+{
+ const unsigned char *key = (const unsigned char *)vkey;
+ const unsigned char *salt = (const unsigned char *)vsalt;
+ uint32_t *S0 = ctx->S0;
+ uint32_t *S1 = ctx->S1;
+ uint32_t *S2 = ctx->S2;
+ uint32_t *S3 = ctx->S3;
+ uint32_t *P = ctx->P;
+ uint32_t str[2];
+ int i, j;
+ int saltpos;
+ unsigned char dummysalt[1];
+
+ saltpos = 0;
+ if (!salt) {
+ saltbytes = 1;
+ salt = dummysalt;
+ dummysalt[0] = 0;
+ }
+
+ for (i = 0; i < 18; i++) {
+ P[i] ^=
+ ((uint32_t) (unsigned char) (key[(i * 4 + 0) % keybytes])) << 24;
+ P[i] ^=
+ ((uint32_t) (unsigned char) (key[(i * 4 + 1) % keybytes])) << 16;
+ P[i] ^=
+ ((uint32_t) (unsigned char) (key[(i * 4 + 2) % keybytes])) << 8;
+ P[i] ^= ((uint32_t) (unsigned char) (key[(i * 4 + 3) % keybytes]));
+ }
+
+ str[0] = str[1] = 0;
+
+ for (i = 0; i < 18; i += 2) {
+ for (j = 0; j < 8; j++)
+ str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4));
+
+ blowfish_encrypt(str[0], str[1], str, ctx);
+ P[i] = str[0];
+ P[i + 1] = str[1];
+ }
+
+ for (i = 0; i < 256; i += 2) {
+ for (j = 0; j < 8; j++)
+ str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4));
+ blowfish_encrypt(str[0], str[1], str, ctx);
+ S0[i] = str[0];
+ S0[i + 1] = str[1];
+ }
+ for (i = 0; i < 256; i += 2) {
+ for (j = 0; j < 8; j++)
+ str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4));
+ blowfish_encrypt(str[0], str[1], str, ctx);
+ S1[i] = str[0];
+ S1[i + 1] = str[1];
+ }
+ for (i = 0; i < 256; i += 2) {
+ for (j = 0; j < 8; j++)
+ str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4));
+ blowfish_encrypt(str[0], str[1], str, ctx);
+ S2[i] = str[0];
+ S2[i + 1] = str[1];
+ }
+ for (i = 0; i < 256; i += 2) {
+ for (j = 0; j < 8; j++)
+ str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4));
+ blowfish_encrypt(str[0], str[1], str, ctx);
+ S3[i] = str[0];
+ S3[i + 1] = str[1];
+ }
+}
+
+static void blowfish_setkey(BlowfishContext *ctx,
+ const unsigned char *key, short keybytes)
+{
+ blowfish_initkey(ctx);
+ blowfish_expandkey(ctx, key, keybytes, NULL, 0);
+}
+
+/* -- Interface with PuTTY -- */
+
+#define SSH1_SESSION_KEY_LENGTH 32
+
+BlowfishContext *blowfish_make_context(void)
+{
+ return snew(BlowfishContext);
+}
+
+void blowfish_free_context(BlowfishContext *ctx)
+{
+ sfree(ctx);
+}
+
+static void blowfish_iv_be(BlowfishContext *ctx, const void *viv)
+{
+ const unsigned char *iv = (const unsigned char *)viv;
+ ctx->iv0 = GET_32BIT_MSB_FIRST(iv);
+ ctx->iv1 = GET_32BIT_MSB_FIRST(iv + 4);
+}
+
+static void blowfish_iv_le(BlowfishContext *ctx, const void *viv)
+{
+ const unsigned char *iv = (const unsigned char *)viv;
+ ctx->iv0 = GET_32BIT_LSB_FIRST(iv);
+ ctx->iv1 = GET_32BIT_LSB_FIRST(iv + 4);
+}
+
+struct blowfish_ctx {
+ BlowfishContext context;
+ ssh_cipher ciph;
+};
+
+static ssh_cipher *blowfish_new(const ssh_cipheralg *alg)
+{
+ struct blowfish_ctx *ctx = snew(struct blowfish_ctx);
+ ctx->ciph.vt = alg;
+ return &ctx->ciph;
+}
+
+static void blowfish_free(ssh_cipher *cipher)
+{
+ struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+ smemclr(ctx, sizeof(*ctx));
+ sfree(ctx);
+}
+
+static void blowfish_ssh_setkey(ssh_cipher *cipher, const void *key)
+{
+ struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+ blowfish_setkey(&ctx->context, key, ctx->ciph.vt->padded_keybytes);
+}
+
+static void blowfish_ssh1_setiv(ssh_cipher *cipher, const void *iv)
+{
+ struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+ blowfish_iv_le(&ctx->context, iv);
+}
+
+static void blowfish_ssh2_setiv(ssh_cipher *cipher, const void *iv)
+{
+ struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+ blowfish_iv_be(&ctx->context, iv);
+}
+
+static void blowfish_ssh1_encrypt_blk(ssh_cipher *cipher, void *blk, int len)
+{
+ struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+ blowfish_lsb_encrypt_cbc(blk, len, &ctx->context);
+}
+
+static void blowfish_ssh1_decrypt_blk(ssh_cipher *cipher, void *blk, int len)
+{
+ struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+ blowfish_lsb_decrypt_cbc(blk, len, &ctx->context);
+}
+
+static void blowfish_ssh2_encrypt_blk(ssh_cipher *cipher, void *blk, int len)
+{
+ struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+ blowfish_msb_encrypt_cbc(blk, len, &ctx->context);
+}
+
+static void blowfish_ssh2_decrypt_blk(ssh_cipher *cipher, void *blk, int len)
+{
+ struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+ blowfish_msb_decrypt_cbc(blk, len, &ctx->context);
+}
+
+static void blowfish_ssh2_sdctr(ssh_cipher *cipher, void *blk, int len)
+{
+ struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph);
+ blowfish_msb_sdctr(blk, len, &ctx->context);
+}
+
+const ssh_cipheralg ssh_blowfish_ssh1 = {
+ .new = blowfish_new,
+ .free = blowfish_free,
+ .setiv = blowfish_ssh1_setiv,
+ .setkey = blowfish_ssh_setkey,
+ .encrypt = blowfish_ssh1_encrypt_blk,
+ .decrypt = blowfish_ssh1_decrypt_blk,
+ .next_message = nullcipher_next_message,
+ .blksize = 8,
+ .real_keybits = 128,
+ .padded_keybytes = SSH1_SESSION_KEY_LENGTH,
+ .flags = SSH_CIPHER_IS_CBC,
+ .text_name = "Blowfish-256 CBC",
+};
+
+const ssh_cipheralg ssh_blowfish_ssh2 = {
+ .new = blowfish_new,
+ .free = blowfish_free,
+ .setiv = blowfish_ssh2_setiv,
+ .setkey = blowfish_ssh_setkey,
+ .encrypt = blowfish_ssh2_encrypt_blk,
+ .decrypt = blowfish_ssh2_decrypt_blk,
+ .next_message = nullcipher_next_message,
+ .ssh2_id = "blowfish-cbc",
+ .blksize = 8,
+ .real_keybits = 128,
+ .padded_keybytes = 16,
+ .flags = SSH_CIPHER_IS_CBC,
+ .text_name = "Blowfish-128 CBC",
+};
+
+const ssh_cipheralg ssh_blowfish_ssh2_ctr = {
+ .new = blowfish_new,
+ .free = blowfish_free,
+ .setiv = blowfish_ssh2_setiv,
+ .setkey = blowfish_ssh_setkey,
+ .encrypt = blowfish_ssh2_sdctr,
+ .decrypt = blowfish_ssh2_sdctr,
+ .next_message = nullcipher_next_message,
+ .ssh2_id = "blowfish-ctr",
+ .blksize = 8,
+ .real_keybits = 256,
+ .padded_keybytes = 32,
+ .flags = 0,
+ .text_name = "Blowfish-256 SDCTR",
+};
+
+static const ssh_cipheralg *const blowfish_list[] = {
+ &ssh_blowfish_ssh2_ctr,
+ &ssh_blowfish_ssh2
+};
+
+const ssh2_ciphers ssh2_blowfish = { lenof(blowfish_list), blowfish_list };
diff --git a/crypto/blowfish.h b/crypto/blowfish.h
new file mode 100644
index 00000000..54158922
--- /dev/null
+++ b/crypto/blowfish.h
@@ -0,0 +1,14 @@
+/*
+ * Header file shared between blowfish.c and bcrypt.c. Exposes the
+ * internal Blowfish routines needed by bcrypt.
+ */
+
+typedef struct BlowfishContext BlowfishContext;
+
+BlowfishContext *blowfish_make_context(void);
+void blowfish_free_context(BlowfishContext *ctx);
+void blowfish_initkey(BlowfishContext *ctx);
+void blowfish_expandkey(BlowfishContext *ctx,
+ const void *key, short keybytes,
+ const void *salt, short saltbytes);
+void blowfish_lsb_encrypt_ecb(void *blk, int len, BlowfishContext *ctx);
diff --git a/crypto/chacha20-poly1305.c b/crypto/chacha20-poly1305.c
new file mode 100644
index 00000000..4216a64d
--- /dev/null
+++ b/crypto/chacha20-poly1305.c
@@ -0,0 +1,1079 @@
+/*
+ * ChaCha20-Poly1305 Implementation for SSH-2
+ *
+ * Protocol spec:
+ * http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?rev=1.2&content-type=text/x-cvsweb-markup
+ *
+ * ChaCha20 spec:
+ * http://cr.yp.to/chacha/chacha-20080128.pdf
+ *
+ * Salsa20 spec:
+ * http://cr.yp.to/snuffle/spec.pdf
+ *
+ * Poly1305-AES spec:
+ * http://cr.yp.to/mac/poly1305-20050329.pdf
+ *
+ * The nonce for the Poly1305 is the second part of the key output
+ * from the first round of ChaCha20. This removes the AES requirement.
+ * This is undocumented!
+ *
+ * This has an intricate link between the cipher and the MAC. The
+ * keying of both is done in by the cipher and setting of the IV is
+ * done by the MAC. One cannot operate without the other. The
+ * configuration of the ssh_cipheralg structure ensures that the MAC is
+ * set (and others ignored) if this cipher is chosen.
+ *
+ * This cipher also encrypts the length using a different
+ * instantiation of the cipher using a different key and IV made from
+ * the sequence number which is passed in addition when calling
+ * encrypt/decrypt on it.
+ */
+
+#include "ssh.h"
+#include "mpint_i.h"
+
+#ifndef INLINE
+#define INLINE
+#endif
+
+/* ChaCha20 implementation, only supporting 256-bit keys */
+
+/* State for each ChaCha20 instance */
+struct chacha20 {
+ /* Current context, usually with the count incremented
+ * 0-3 are the static constant
+ * 4-11 are the key
+ * 12-13 are the counter
+ * 14-15 are the IV */
+ uint32_t state[16];
+ /* The output of the state above ready to xor */
+ unsigned char current[64];
+ /* The index of the above currently used to allow a true streaming cipher */
+ int currentIndex;
+};
+
+static INLINE void chacha20_round(struct chacha20 *ctx)
+{
+ int i;
+ uint32_t copy[16];
+
+ /* Take a copy */
+ memcpy(copy, ctx->state, sizeof(copy));
+
+ /* A circular rotation for a 32bit number */
+#define rotl(x, shift) x = ((x << shift) | (x >> (32 - shift)))
+
+ /* What to do for each quarter round operation */
+#define qrop(a, b, c, d) \
+ copy[a] += copy[b]; \
+ copy[c] ^= copy[a]; \
+ rotl(copy[c], d)
+
+ /* A quarter round */
+#define quarter(a, b, c, d) \
+ qrop(a, b, d, 16); \
+ qrop(c, d, b, 12); \
+ qrop(a, b, d, 8); \
+ qrop(c, d, b, 7)
+
+ /* Do 20 rounds, in pairs because every other is different */
+ for (i = 0; i < 20; i += 2) {
+ /* A round */
+ quarter(0, 4, 8, 12);
+ quarter(1, 5, 9, 13);
+ quarter(2, 6, 10, 14);
+ quarter(3, 7, 11, 15);
+ /* Another slightly different round */
+ quarter(0, 5, 10, 15);
+ quarter(1, 6, 11, 12);
+ quarter(2, 7, 8, 13);
+ quarter(3, 4, 9, 14);
+ }
+
+ /* Dump the macros, don't need them littering */
+#undef rotl
+#undef qrop
+#undef quarter
+
+ /* Add the initial state */
+ for (i = 0; i < 16; ++i) {
+ copy[i] += ctx->state[i];
+ }
+
+ /* Update the content of the xor buffer */
+ for (i = 0; i < 16; ++i) {
+ ctx->current[i * 4 + 0] = copy[i] >> 0;
+ ctx->current[i * 4 + 1] = copy[i] >> 8;
+ ctx->current[i * 4 + 2] = copy[i] >> 16;
+ ctx->current[i * 4 + 3] = copy[i] >> 24;
+ }
+ /* State full, reset pointer to beginning */
+ ctx->currentIndex = 0;
+ smemclr(copy, sizeof(copy));
+
+ /* Increment round counter */
+ ++ctx->state[12];
+ /* Check for overflow, not done in one line so the 32 bits are chopped by the type */
+ if (!(uint32_t)(ctx->state[12])) {
+ ++ctx->state[13];
+ }
+}
+
+/* Initialise context with 256bit key */
+static void chacha20_key(struct chacha20 *ctx, const unsigned char *key)
+{
+ static const char constant[16] = "expand 32-byte k";
+
+ /* Add the fixed string to the start of the state */
+ ctx->state[0] = GET_32BIT_LSB_FIRST(constant + 0);
+ ctx->state[1] = GET_32BIT_LSB_FIRST(constant + 4);
+ ctx->state[2] = GET_32BIT_LSB_FIRST(constant + 8);
+ ctx->state[3] = GET_32BIT_LSB_FIRST(constant + 12);
+
+ /* Add the key */
+ ctx->state[4] = GET_32BIT_LSB_FIRST(key + 0);
+ ctx->state[5] = GET_32BIT_LSB_FIRST(key + 4);
+ ctx->state[6] = GET_32BIT_LSB_FIRST(key + 8);
+ ctx->state[7] = GET_32BIT_LSB_FIRST(key + 12);
+ ctx->state[8] = GET_32BIT_LSB_FIRST(key + 16);
+ ctx->state[9] = GET_32BIT_LSB_FIRST(key + 20);
+ ctx->state[10] = GET_32BIT_LSB_FIRST(key + 24);
+ ctx->state[11] = GET_32BIT_LSB_FIRST(key + 28);
+
+ /* New key, dump context */
+ ctx->currentIndex = 64;
+}
+
+static void chacha20_iv(struct chacha20 *ctx, const unsigned char *iv)
+{
+ ctx->state[12] = 0;
+ ctx->state[13] = 0;
+ ctx->state[14] = GET_32BIT_MSB_FIRST(iv);
+ ctx->state[15] = GET_32BIT_MSB_FIRST(iv + 4);
+
+ /* New IV, dump context */
+ ctx->currentIndex = 64;
+}
+
+static void chacha20_encrypt(struct chacha20 *ctx, unsigned char *blk, int len)
+{
+ while (len) {
+ /* If we don't have any state left, then cycle to the next */
+ if (ctx->currentIndex >= 64) {
+ chacha20_round(ctx);
+ }
+
+ /* Do the xor while there's some state left and some plaintext left */
+ while (ctx->currentIndex < 64 && len) {
+ *blk++ ^= ctx->current[ctx->currentIndex++];
+ --len;
+ }
+ }
+}
+
+/* Decrypt is encrypt... It's xor against a PRNG... */
+static INLINE void chacha20_decrypt(struct chacha20 *ctx,
+ unsigned char *blk, int len)
+{
+ chacha20_encrypt(ctx, blk, len);
+}
+
+/* Poly1305 implementation (no AES, nonce is not encrypted) */
+
+#define NWORDS ((130 + BIGNUM_INT_BITS-1) / BIGNUM_INT_BITS)
+typedef struct bigval {
+ BignumInt w[NWORDS];
+} bigval;
+
+static void bigval_clear(bigval *r)
+{
+ int i;
+ for (i = 0; i < NWORDS; i++)
+ r->w[i] = 0;
+}
+
+static void bigval_import_le(bigval *r, const void *vdata, int len)
+{
+ const unsigned char *data = (const unsigned char *)vdata;
+ int i;
+ bigval_clear(r);
+ for (i = 0; i < len; i++)
+ r->w[i / BIGNUM_INT_BYTES] |=
+ (BignumInt)data[i] << (8 * (i % BIGNUM_INT_BYTES));
+}
+
+static void bigval_export_le(const bigval *r, void *vdata, int len)
+{
+ unsigned char *data = (unsigned char *)vdata;
+ int i;
+ for (i = 0; i < len; i++)
+ data[i] = r->w[i / BIGNUM_INT_BYTES] >> (8 * (i % BIGNUM_INT_BYTES));
+}
+
+/*
+ * Core functions to do arithmetic mod p = 2^130-5. The whole
+ * collection of these, up to and including the surrounding #if, are
+ * generated automatically for various sizes of BignumInt by
+ * contrib/make1305.py.
+ */
+
+#if BIGNUM_INT_BITS == 16
+
+static void bigval_add(bigval *r, const bigval *a, const bigval *b)
+{
+ BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
+ BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26;
+ BignumCarry carry;
+
+ v0 = a->w[0];
+ v1 = a->w[1];
+ v2 = a->w[2];
+ v3 = a->w[3];
+ v4 = a->w[4];
+ v5 = a->w[5];
+ v6 = a->w[6];
+ v7 = a->w[7];
+ v8 = a->w[8];
+ v9 = b->w[0];
+ v10 = b->w[1];
+ v11 = b->w[2];
+ v12 = b->w[3];
+ v13 = b->w[4];
+ v14 = b->w[5];
+ v15 = b->w[6];
+ v16 = b->w[7];
+ v17 = b->w[8];
+ BignumADC(v18, carry, v0, v9, 0);
+ BignumADC(v19, carry, v1, v10, carry);
+ BignumADC(v20, carry, v2, v11, carry);
+ BignumADC(v21, carry, v3, v12, carry);
+ BignumADC(v22, carry, v4, v13, carry);
+ BignumADC(v23, carry, v5, v14, carry);
+ BignumADC(v24, carry, v6, v15, carry);
+ BignumADC(v25, carry, v7, v16, carry);
+ v26 = v8 + v17 + carry;
+ r->w[0] = v18;
+ r->w[1] = v19;
+ r->w[2] = v20;
+ r->w[3] = v21;
+ r->w[4] = v22;
+ r->w[5] = v23;
+ r->w[6] = v24;
+ r->w[7] = v25;
+ r->w[8] = v26;
+}
+
+static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b)
+{
+ BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
+ BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27;
+ BignumInt v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40;
+ BignumInt v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53;
+ BignumInt v54, v55, v56, v57, v58, v59, v60, v61, v62, v63, v64, v65, v66;
+ BignumInt v67, v68, v69, v70, v71, v72, v73, v74, v75, v76, v77, v78, v79;
+ BignumInt v80, v81, v82, v83, v84, v85, v86, v87, v88, v89, v90, v91, v92;
+ BignumInt v93, v94, v95, v96, v97, v98, v99, v100, v101, v102, v103, v104;
+ BignumInt v105, v106, v107, v108, v109, v110, v111, v112, v113, v114;
+ BignumInt v115, v116, v117, v118, v119, v120, v121, v122, v123, v124;
+ BignumInt v125, v126, v127, v128, v129, v130, v131, v132, v133, v134;
+ BignumInt v135, v136, v137, v138, v139, v140, v141, v142, v143, v144;
+ BignumInt v145, v146, v147, v148, v149, v150, v151, v152, v153, v154;
+ BignumInt v155, v156, v157, v158, v159, v160, v161, v162, v163, v164;
+ BignumInt v165, v166, v167, v168, v169, v170, v171, v172, v173, v174;
+ BignumInt v175, v176, v177, v178, v180, v181, v182, v183, v184, v185;
+ BignumInt v186, v187, v188, v189, v190, v191, v192, v193, v194, v195;
+ BignumInt v196, v197, v198, v199, v200, v201, v202, v203, v204, v205;
+ BignumInt v206, v207, v208, v210, v212, v213, v214, v215, v216, v217;
+ BignumInt v218, v219, v220, v221, v222, v223, v224, v225, v226, v227;
+ BignumInt v228, v229;
+ BignumCarry carry;
+
+ v0 = a->w[0];
+ v1 = a->w[1];
+ v2 = a->w[2];
+ v3 = a->w[3];
+ v4 = a->w[4];
+ v5 = a->w[5];
+ v6 = a->w[6];
+ v7 = a->w[7];
+ v8 = a->w[8];
+ v9 = b->w[0];
+ v10 = b->w[1];
+ v11 = b->w[2];
+ v12 = b->w[3];
+ v13 = b->w[4];
+ v14 = b->w[5];
+ v15 = b->w[6];
+ v16 = b->w[7];
+ v17 = b->w[8];
+ BignumMUL(v19, v18, v0, v9);
+ BignumMULADD(v21, v20, v0, v10, v19);
+ BignumMULADD(v23, v22, v0, v11, v21);
+ BignumMULADD(v25, v24, v0, v12, v23);
+ BignumMULADD(v27, v26, v0, v13, v25);
+ BignumMULADD(v29, v28, v0, v14, v27);
+ BignumMULADD(v31, v30, v0, v15, v29);
+ BignumMULADD(v33, v32, v0, v16, v31);
+ BignumMULADD(v35, v34, v0, v17, v33);
+ BignumMULADD(v37, v36, v1, v9, v20);
+ BignumMULADD2(v39, v38, v1, v10, v22, v37);
+ BignumMULADD2(v41, v40, v1, v11, v24, v39);
+ BignumMULADD2(v43, v42, v1, v12, v26, v41);
+ BignumMULADD2(v45, v44, v1, v13, v28, v43);
+ BignumMULADD2(v47, v46, v1, v14, v30, v45);
+ BignumMULADD2(v49, v48, v1, v15, v32, v47);
+ BignumMULADD2(v51, v50, v1, v16, v34, v49);
+ BignumMULADD2(v53, v52, v1, v17, v35, v51);
+ BignumMULADD(v55, v54, v2, v9, v38);
+ BignumMULADD2(v57, v56, v2, v10, v40, v55);
+ BignumMULADD2(v59, v58, v2, v11, v42, v57);
+ BignumMULADD2(v61, v60, v2, v12, v44, v59);
+ BignumMULADD2(v63, v62, v2, v13, v46, v61);
+ BignumMULADD2(v65, v64, v2, v14, v48, v63);
+ BignumMULADD2(v67, v66, v2, v15, v50, v65);
+ BignumMULADD2(v69, v68, v2, v16, v52, v67);
+ BignumMULADD2(v71, v70, v2, v17, v53, v69);
+ BignumMULADD(v73, v72, v3, v9, v56);
+ BignumMULADD2(v75, v74, v3, v10, v58, v73);
+ BignumMULADD2(v77, v76, v3, v11, v60, v75);
+ BignumMULADD2(v79, v78, v3, v12, v62, v77);
+ BignumMULADD2(v81, v80, v3, v13, v64, v79);
+ BignumMULADD2(v83, v82, v3, v14, v66, v81);
+ BignumMULADD2(v85, v84, v3, v15, v68, v83);
+ BignumMULADD2(v87, v86, v3, v16, v70, v85);
+ BignumMULADD2(v89, v88, v3, v17, v71, v87);
+ BignumMULADD(v91, v90, v4, v9, v74);
+ BignumMULADD2(v93, v92, v4, v10, v76, v91);
+ BignumMULADD2(v95, v94, v4, v11, v78, v93);
+ BignumMULADD2(v97, v96, v4, v12, v80, v95);
+ BignumMULADD2(v99, v98, v4, v13, v82, v97);
+ BignumMULADD2(v101, v100, v4, v14, v84, v99);
+ BignumMULADD2(v103, v102, v4, v15, v86, v101);
+ BignumMULADD2(v105, v104, v4, v16, v88, v103);
+ BignumMULADD2(v107, v106, v4, v17, v89, v105);
+ BignumMULADD(v109, v108, v5, v9, v92);
+ BignumMULADD2(v111, v110, v5, v10, v94, v109);
+ BignumMULADD2(v113, v112, v5, v11, v96, v111);
+ BignumMULADD2(v115, v114, v5, v12, v98, v113);
+ BignumMULADD2(v117, v116, v5, v13, v100, v115);
+ BignumMULADD2(v119, v118, v5, v14, v102, v117);
+ BignumMULADD2(v121, v120, v5, v15, v104, v119);
+ BignumMULADD2(v123, v122, v5, v16, v106, v121);
+ BignumMULADD2(v125, v124, v5, v17, v107, v123);
+ BignumMULADD(v127, v126, v6, v9, v110);
+ BignumMULADD2(v129, v128, v6, v10, v112, v127);
+ BignumMULADD2(v131, v130, v6, v11, v114, v129);
+ BignumMULADD2(v133, v132, v6, v12, v116, v131);
+ BignumMULADD2(v135, v134, v6, v13, v118, v133);
+ BignumMULADD2(v137, v136, v6, v14, v120, v135);
+ BignumMULADD2(v139, v138, v6, v15, v122, v137);
+ BignumMULADD2(v141, v140, v6, v16, v124, v139);
+ BignumMULADD2(v143, v142, v6, v17, v125, v141);
+ BignumMULADD(v145, v144, v7, v9, v128);
+ BignumMULADD2(v147, v146, v7, v10, v130, v145);
+ BignumMULADD2(v149, v148, v7, v11, v132, v147);
+ BignumMULADD2(v151, v150, v7, v12, v134, v149);
+ BignumMULADD2(v153, v152, v7, v13, v136, v151);
+ BignumMULADD2(v155, v154, v7, v14, v138, v153);
+ BignumMULADD2(v157, v156, v7, v15, v140, v155);
+ BignumMULADD2(v159, v158, v7, v16, v142, v157);
+ BignumMULADD2(v161, v160, v7, v17, v143, v159);
+ BignumMULADD(v163, v162, v8, v9, v146);
+ BignumMULADD2(v165, v164, v8, v10, v148, v163);
+ BignumMULADD2(v167, v166, v8, v11, v150, v165);
+ BignumMULADD2(v169, v168, v8, v12, v152, v167);
+ BignumMULADD2(v171, v170, v8, v13, v154, v169);
+ BignumMULADD2(v173, v172, v8, v14, v156, v171);
+ BignumMULADD2(v175, v174, v8, v15, v158, v173);
+ BignumMULADD2(v177, v176, v8, v16, v160, v175);
+ v178 = v8 * v17 + v161 + v177;
+ v180 = (v162) & ((((BignumInt)1) << 2)-1);
+ v181 = ((v162) >> 2) | ((v164) << 14);
+ v182 = ((v164) >> 2) | ((v166) << 14);
+ v183 = ((v166) >> 2) | ((v168) << 14);
+ v184 = ((v168) >> 2) | ((v170) << 14);
+ v185 = ((v170) >> 2) | ((v172) << 14);
+ v186 = ((v172) >> 2) | ((v174) << 14);
+ v187 = ((v174) >> 2) | ((v176) << 14);
+ v188 = ((v176) >> 2) | ((v178) << 14);
+ v189 = (v178) >> 2;
+ v190 = (v189) & ((((BignumInt)1) << 2)-1);
+ v191 = (v178) >> 4;
+ BignumMUL(v193, v192, 5, v181);
+ BignumMULADD(v195, v194, 5, v182, v193);
+ BignumMULADD(v197, v196, 5, v183, v195);
+ BignumMULADD(v199, v198, 5, v184, v197);
+ BignumMULADD(v201, v200, 5, v185, v199);
+ BignumMULADD(v203, v202, 5, v186, v201);
+ BignumMULADD(v205, v204, 5, v187, v203);
+ BignumMULADD(v207, v206, 5, v188, v205);
+ v208 = 5 * v190 + v207;
+ v210 = 25 * v191;
+ BignumADC(v212, carry, v18, v192, 0);
+ BignumADC(v213, carry, v36, v194, carry);
+ BignumADC(v214, carry, v54, v196, carry);
+ BignumADC(v215, carry, v72, v198, carry);
+ BignumADC(v216, carry, v90, v200, carry);
+ BignumADC(v217, carry, v108, v202, carry);
+ BignumADC(v218, carry, v126, v204, carry);
+ BignumADC(v219, carry, v144, v206, carry);
+ v220 = v180 + v208 + carry;
+ BignumADC(v221, carry, v212, v210, 0);
+ BignumADC(v222, carry, v213, 0, carry);
+ BignumADC(v223, carry, v214, 0, carry);
+ BignumADC(v224, carry, v215, 0, carry);
+ BignumADC(v225, carry, v216, 0, carry);
+ BignumADC(v226, carry, v217, 0, carry);
+ BignumADC(v227, carry, v218, 0, carry);
+ BignumADC(v228, carry, v219, 0, carry);
+ v229 = v220 + 0 + carry;
+ r->w[0] = v221;
+ r->w[1] = v222;
+ r->w[2] = v223;
+ r->w[3] = v224;
+ r->w[4] = v225;
+ r->w[5] = v226;
+ r->w[6] = v227;
+ r->w[7] = v228;
+ r->w[8] = v229;
+}
+
+static void bigval_final_reduce(bigval *n)
+{
+ BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v13, v14, v15;
+ BignumInt v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28;
+ BignumInt v29, v30, v31, v32, v34, v35, v36, v37, v38, v39, v40, v41, v42;
+ BignumInt v43;
+ BignumCarry carry;
+
+ v0 = n->w[0];
+ v1 = n->w[1];
+ v2 = n->w[2];
+ v3 = n->w[3];
+ v4 = n->w[4];
+ v5 = n->w[5];
+ v6 = n->w[6];
+ v7 = n->w[7];
+ v8 = n->w[8];
+ v9 = (v8) >> 2;
+ v10 = (v8) & ((((BignumInt)1) << 2)-1);
+ v11 = 5 * v9;
+ BignumADC(v13, carry, v0, v11, 0);
+ BignumADC(v14, carry, v1, 0, carry);
+ BignumADC(v15, carry, v2, 0, carry);
+ BignumADC(v16, carry, v3, 0, carry);
+ BignumADC(v17, carry, v4, 0, carry);
+ BignumADC(v18, carry, v5, 0, carry);
+ BignumADC(v19, carry, v6, 0, carry);
+ BignumADC(v20, carry, v7, 0, carry);
+ v21 = v10 + 0 + carry;
+ BignumADC(v22, carry, v13, 5, 0);
+ (void)v22;
+ BignumADC(v23, carry, v14, 0, carry);
+ (void)v23;
+ BignumADC(v24, carry, v15, 0, carry);
+ (void)v24;
+ BignumADC(v25, carry, v16, 0, carry);
+ (void)v25;
+ BignumADC(v26, carry, v17, 0, carry);
+ (void)v26;
+ BignumADC(v27, carry, v18, 0, carry);
+ (void)v27;
+ BignumADC(v28, carry, v19, 0, carry);
+ (void)v28;
+ BignumADC(v29, carry, v20, 0, carry);
+ (void)v29;
+ v30 = v21 + 0 + carry;
+ v31 = (v30) >> 2;
+ v32 = 5 * v31;
+ BignumADC(v34, carry, v13, v32, 0);
+ BignumADC(v35, carry, v14, 0, carry);
+ BignumADC(v36, carry, v15, 0, carry);
+ BignumADC(v37, carry, v16, 0, carry);
+ BignumADC(v38, carry, v17, 0, carry);
+ BignumADC(v39, carry, v18, 0, carry);
+ BignumADC(v40, carry, v19, 0, carry);
+ BignumADC(v41, carry, v20, 0, carry);
+ v42 = v21 + 0 + carry;
+ v43 = (v42) & ((((BignumInt)1) << 2)-1);
+ n->w[0] = v34;
+ n->w[1] = v35;
+ n->w[2] = v36;
+ n->w[3] = v37;
+ n->w[4] = v38;
+ n->w[5] = v39;
+ n->w[6] = v40;
+ n->w[7] = v41;
+ n->w[8] = v43;
+}
+
+#elif BIGNUM_INT_BITS == 32
+
+static void bigval_add(bigval *r, const bigval *a, const bigval *b)
+{
+ BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
+ BignumCarry carry;
+
+ v0 = a->w[0];
+ v1 = a->w[1];
+ v2 = a->w[2];
+ v3 = a->w[3];
+ v4 = a->w[4];
+ v5 = b->w[0];
+ v6 = b->w[1];
+ v7 = b->w[2];
+ v8 = b->w[3];
+ v9 = b->w[4];
+ BignumADC(v10, carry, v0, v5, 0);
+ BignumADC(v11, carry, v1, v6, carry);
+ BignumADC(v12, carry, v2, v7, carry);
+ BignumADC(v13, carry, v3, v8, carry);
+ v14 = v4 + v9 + carry;
+ r->w[0] = v10;
+ r->w[1] = v11;
+ r->w[2] = v12;
+ r->w[3] = v13;
+ r->w[4] = v14;
+}
+
+static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b)
+{
+ BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
+ BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27;
+ BignumInt v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40;
+ BignumInt v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53;
+ BignumInt v54, v55, v56, v57, v58, v60, v61, v62, v63, v64, v65, v66, v67;
+ BignumInt v68, v69, v70, v71, v72, v73, v74, v75, v76, v78, v80, v81, v82;
+ BignumInt v83, v84, v85, v86, v87, v88, v89;
+ BignumCarry carry;
+
+ v0 = a->w[0];
+ v1 = a->w[1];
+ v2 = a->w[2];
+ v3 = a->w[3];
+ v4 = a->w[4];
+ v5 = b->w[0];
+ v6 = b->w[1];
+ v7 = b->w[2];
+ v8 = b->w[3];
+ v9 = b->w[4];
+ BignumMUL(v11, v10, v0, v5);
+ BignumMULADD(v13, v12, v0, v6, v11);
+ BignumMULADD(v15, v14, v0, v7, v13);
+ BignumMULADD(v17, v16, v0, v8, v15);
+ BignumMULADD(v19, v18, v0, v9, v17);
+ BignumMULADD(v21, v20, v1, v5, v12);
+ BignumMULADD2(v23, v22, v1, v6, v14, v21);
+ BignumMULADD2(v25, v24, v1, v7, v16, v23);
+ BignumMULADD2(v27, v26, v1, v8, v18, v25);
+ BignumMULADD2(v29, v28, v1, v9, v19, v27);
+ BignumMULADD(v31, v30, v2, v5, v22);
+ BignumMULADD2(v33, v32, v2, v6, v24, v31);
+ BignumMULADD2(v35, v34, v2, v7, v26, v33);
+ BignumMULADD2(v37, v36, v2, v8, v28, v35);
+ BignumMULADD2(v39, v38, v2, v9, v29, v37);
+ BignumMULADD(v41, v40, v3, v5, v32);
+ BignumMULADD2(v43, v42, v3, v6, v34, v41);
+ BignumMULADD2(v45, v44, v3, v7, v36, v43);
+ BignumMULADD2(v47, v46, v3, v8, v38, v45);
+ BignumMULADD2(v49, v48, v3, v9, v39, v47);
+ BignumMULADD(v51, v50, v4, v5, v42);
+ BignumMULADD2(v53, v52, v4, v6, v44, v51);
+ BignumMULADD2(v55, v54, v4, v7, v46, v53);
+ BignumMULADD2(v57, v56, v4, v8, v48, v55);
+ v58 = v4 * v9 + v49 + v57;
+ v60 = (v50) & ((((BignumInt)1) << 2)-1);
+ v61 = ((v50) >> 2) | ((v52) << 30);
+ v62 = ((v52) >> 2) | ((v54) << 30);
+ v63 = ((v54) >> 2) | ((v56) << 30);
+ v64 = ((v56) >> 2) | ((v58) << 30);
+ v65 = (v58) >> 2;
+ v66 = (v65) & ((((BignumInt)1) << 2)-1);
+ v67 = (v58) >> 4;
+ BignumMUL(v69, v68, 5, v61);
+ BignumMULADD(v71, v70, 5, v62, v69);
+ BignumMULADD(v73, v72, 5, v63, v71);
+ BignumMULADD(v75, v74, 5, v64, v73);
+ v76 = 5 * v66 + v75;
+ v78 = 25 * v67;
+ BignumADC(v80, carry, v10, v68, 0);
+ BignumADC(v81, carry, v20, v70, carry);
+ BignumADC(v82, carry, v30, v72, carry);
+ BignumADC(v83, carry, v40, v74, carry);
+ v84 = v60 + v76 + carry;
+ BignumADC(v85, carry, v80, v78, 0);
+ BignumADC(v86, carry, v81, 0, carry);
+ BignumADC(v87, carry, v82, 0, carry);
+ BignumADC(v88, carry, v83, 0, carry);
+ v89 = v84 + 0 + carry;
+ r->w[0] = v85;
+ r->w[1] = v86;
+ r->w[2] = v87;
+ r->w[3] = v88;
+ r->w[4] = v89;
+}
+
+static void bigval_final_reduce(bigval *n)
+{
+ BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v9, v10, v11, v12, v13, v14;
+ BignumInt v15, v16, v17, v18, v19, v20, v22, v23, v24, v25, v26, v27;
+ BignumCarry carry;
+
+ v0 = n->w[0];
+ v1 = n->w[1];
+ v2 = n->w[2];
+ v3 = n->w[3];
+ v4 = n->w[4];
+ v5 = (v4) >> 2;
+ v6 = (v4) & ((((BignumInt)1) << 2)-1);
+ v7 = 5 * v5;
+ BignumADC(v9, carry, v0, v7, 0);
+ BignumADC(v10, carry, v1, 0, carry);
+ BignumADC(v11, carry, v2, 0, carry);
+ BignumADC(v12, carry, v3, 0, carry);
+ v13 = v6 + 0 + carry;
+ BignumADC(v14, carry, v9, 5, 0);
+ (void)v14;
+ BignumADC(v15, carry, v10, 0, carry);
+ (void)v15;
+ BignumADC(v16, carry, v11, 0, carry);
+ (void)v16;
+ BignumADC(v17, carry, v12, 0, carry);
+ (void)v17;
+ v18 = v13 + 0 + carry;
+ v19 = (v18) >> 2;
+ v20 = 5 * v19;
+ BignumADC(v22, carry, v9, v20, 0);
+ BignumADC(v23, carry, v10, 0, carry);
+ BignumADC(v24, carry, v11, 0, carry);
+ BignumADC(v25, carry, v12, 0, carry);
+ v26 = v13 + 0 + carry;
+ v27 = (v26) & ((((BignumInt)1) << 2)-1);
+ n->w[0] = v22;
+ n->w[1] = v23;
+ n->w[2] = v24;
+ n->w[3] = v25;
+ n->w[4] = v27;
+}
+
+#elif BIGNUM_INT_BITS == 64
+
+static void bigval_add(bigval *r, const bigval *a, const bigval *b)
+{
+ BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8;
+ BignumCarry carry;
+
+ v0 = a->w[0];
+ v1 = a->w[1];
+ v2 = a->w[2];
+ v3 = b->w[0];
+ v4 = b->w[1];
+ v5 = b->w[2];
+ BignumADC(v6, carry, v0, v3, 0);
+ BignumADC(v7, carry, v1, v4, carry);
+ v8 = v2 + v5 + carry;
+ r->w[0] = v6;
+ r->w[1] = v7;
+ r->w[2] = v8;
+}
+
+static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b)
+{
+ BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
+ BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v24, v25, v26, v27, v28;
+ BignumInt v29, v30, v31, v32, v33, v34, v36, v38, v39, v40, v41, v42, v43;
+ BignumCarry carry;
+
+ v0 = a->w[0];
+ v1 = a->w[1];
+ v2 = a->w[2];
+ v3 = b->w[0];
+ v4 = b->w[1];
+ v5 = b->w[2];
+ BignumMUL(v7, v6, v0, v3);
+ BignumMULADD(v9, v8, v0, v4, v7);
+ BignumMULADD(v11, v10, v0, v5, v9);
+ BignumMULADD(v13, v12, v1, v3, v8);
+ BignumMULADD2(v15, v14, v1, v4, v10, v13);
+ BignumMULADD2(v17, v16, v1, v5, v11, v15);
+ BignumMULADD(v19, v18, v2, v3, v14);
+ BignumMULADD2(v21, v20, v2, v4, v16, v19);
+ v22 = v2 * v5 + v17 + v21;
+ v24 = (v18) & ((((BignumInt)1) << 2)-1);
+ v25 = ((v18) >> 2) | ((v20) << 62);
+ v26 = ((v20) >> 2) | ((v22) << 62);
+ v27 = (v22) >> 2;
+ v28 = (v27) & ((((BignumInt)1) << 2)-1);
+ v29 = (v22) >> 4;
+ BignumMUL(v31, v30, 5, v25);
+ BignumMULADD(v33, v32, 5, v26, v31);
+ v34 = 5 * v28 + v33;
+ v36 = 25 * v29;
+ BignumADC(v38, carry, v6, v30, 0);
+ BignumADC(v39, carry, v12, v32, carry);
+ v40 = v24 + v34 + carry;
+ BignumADC(v41, carry, v38, v36, 0);
+ BignumADC(v42, carry, v39, 0, carry);
+ v43 = v40 + 0 + carry;
+ r->w[0] = v41;
+ r->w[1] = v42;
+ r->w[2] = v43;
+}
+
+static void bigval_final_reduce(bigval *n)
+{
+ BignumInt v0, v1, v2, v3, v4, v5, v7, v8, v9, v10, v11, v12, v13, v14;
+ BignumInt v16, v17, v18, v19;
+ BignumCarry carry;
+
+ v0 = n->w[0];
+ v1 = n->w[1];
+ v2 = n->w[2];
+ v3 = (v2) >> 2;
+ v4 = (v2) & ((((BignumInt)1) << 2)-1);
+ v5 = 5 * v3;
+ BignumADC(v7, carry, v0, v5, 0);
+ BignumADC(v8, carry, v1, 0, carry);
+ v9 = v4 + 0 + carry;
+ BignumADC(v10, carry, v7, 5, 0);
+ (void)v10;
+ BignumADC(v11, carry, v8, 0, carry);
+ (void)v11;
+ v12 = v9 + 0 + carry;
+ v13 = (v12) >> 2;
+ v14 = 5 * v13;
+ BignumADC(v16, carry, v7, v14, 0);
+ BignumADC(v17, carry, v8, 0, carry);
+ v18 = v9 + 0 + carry;
+ v19 = (v18) & ((((BignumInt)1) << 2)-1);
+ n->w[0] = v16;
+ n->w[1] = v17;
+ n->w[2] = v19;
+}
+
+#else
+#error Add another bit count to contrib/make1305.py and rerun it
+#endif
+
+struct poly1305 {
+ unsigned char nonce[16];
+ bigval r;
+ bigval h;
+
+ /* Buffer in case we get less that a multiple of 16 bytes */
+ unsigned char buffer[16];
+ int bufferIndex;
+};
+
+static void poly1305_init(struct poly1305 *ctx)
+{
+ memset(ctx->nonce, 0, 16);
+ ctx->bufferIndex = 0;
+ bigval_clear(&ctx->h);
+}
+
+static void poly1305_key(struct poly1305 *ctx, ptrlen key)
+{
+ assert(key.len == 32); /* Takes a 256 bit key */
+
+ unsigned char key_copy[16];
+ memcpy(key_copy, key.ptr, 16);
+
+ /* Key the MAC itself
+ * bytes 4, 8, 12 and 16 are required to have their top four bits clear */
+ key_copy[3] &= 0x0f;
+ key_copy[7] &= 0x0f;
+ key_copy[11] &= 0x0f;
+ key_copy[15] &= 0x0f;
+ /* bytes 5, 9 and 13 are required to have their bottom two bits clear */
+ key_copy[4] &= 0xfc;
+ key_copy[8] &= 0xfc;
+ key_copy[12] &= 0xfc;
+ bigval_import_le(&ctx->r, key_copy, 16);
+ smemclr(key_copy, sizeof(key_copy));
+
+ /* Use second 128 bits as the nonce */
+ memcpy(ctx->nonce, (const char *)key.ptr + 16, 16);
+}
+
+/* Feed up to 16 bytes (should only be less for the last chunk) */
+static void poly1305_feed_chunk(struct poly1305 *ctx,
+ const unsigned char *chunk, int len)
+{
+ bigval c;
+ bigval_import_le(&c, chunk, len);
+ c.w[len / BIGNUM_INT_BYTES] |=
+ (BignumInt)1 << (8 * (len % BIGNUM_INT_BYTES));
+ bigval_add(&c, &c, &ctx->h);
+ bigval_mul_mod_p(&ctx->h, &c, &ctx->r);
+}
+
+static void poly1305_feed(struct poly1305 *ctx,
+ const unsigned char *buf, int len)
+{
+ /* Check for stuff left in the buffer from last time */
+ if (ctx->bufferIndex) {
+ /* Try to fill up to 16 */
+ while (ctx->bufferIndex < 16 && len) {
+ ctx->buffer[ctx->bufferIndex++] = *buf++;
+ --len;
+ }
+ if (ctx->bufferIndex == 16) {
+ poly1305_feed_chunk(ctx, ctx->buffer, 16);
+ ctx->bufferIndex = 0;
+ }
+ }
+
+ /* Process 16 byte whole chunks */
+ while (len >= 16) {
+ poly1305_feed_chunk(ctx, buf, 16);
+ len -= 16;
+ buf += 16;
+ }
+
+ /* Cache stuff that's left over */
+ if (len) {
+ memcpy(ctx->buffer, buf, len);
+ ctx->bufferIndex = len;
+ }
+}
+
+/* Finalise and populate buffer with 16 byte with MAC */
+static void poly1305_finalise(struct poly1305 *ctx, unsigned char *mac)
+{
+ bigval tmp;
+
+ if (ctx->bufferIndex) {
+ poly1305_feed_chunk(ctx, ctx->buffer, ctx->bufferIndex);
+ }
+
+ bigval_import_le(&tmp, ctx->nonce, 16);
+ bigval_final_reduce(&ctx->h);
+ bigval_add(&tmp, &tmp, &ctx->h);
+ bigval_export_le(&tmp, mac, 16);
+}
+
+/* SSH-2 wrapper */
+
+struct ccp_context {
+ struct chacha20 a_cipher; /* Used for length */
+ struct chacha20 b_cipher; /* Used for content */
+
+ /* Cache of the first 4 bytes because they are the sequence number */
+ /* Kept in 8 bytes with the top as zero to allow easy passing to setiv */
+ int mac_initialised; /* Where we have got to in filling mac_iv */
+ unsigned char mac_iv[8];
+
+ struct poly1305 mac;
+
+ BinarySink_IMPLEMENTATION;
+ ssh_cipher ciph;
+ ssh2_mac mac_if;
+ bool ciph_allocated, mac_allocated;
+};
+
+static ssh2_mac *poly_ssh2_new(
+ const ssh2_macalg *alg, ssh_cipher *cipher)
+{
+ struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
+ ctx->mac_if.vt = alg;
+ ctx->mac_allocated = true;
+ BinarySink_DELEGATE_INIT(&ctx->mac_if, ctx);
+ return &ctx->mac_if;
+}
+
+static void ccp_common_free(struct ccp_context *ctx)
+{
+ if (ctx->ciph_allocated || ctx->mac_allocated)
+ return;
+
+ smemclr(&ctx->a_cipher, sizeof(ctx->a_cipher));
+ smemclr(&ctx->b_cipher, sizeof(ctx->b_cipher));
+ smemclr(&ctx->mac, sizeof(ctx->mac));
+ sfree(ctx);
+}
+
+static void poly_ssh2_free(ssh2_mac *mac)
+{
+ struct ccp_context *ctx = container_of(mac, struct ccp_context, mac_if);
+ ctx->mac_allocated = false;
+ ccp_common_free(ctx);
+}
+
+static void poly_setkey(ssh2_mac *mac, ptrlen key)
+{
+ /* Uses the same context as ChaCha20, so ignore */
+}
+
+static void poly_start(ssh2_mac *mac)
+{
+ struct ccp_context *ctx = container_of(mac, struct ccp_context, mac_if);
+
+ ctx->mac_initialised = 0;
+ memset(ctx->mac_iv, 0, 8);
+ poly1305_init(&ctx->mac);
+}
+
+static void poly_BinarySink_write(BinarySink *bs, const void *blkv, size_t len)
+{
+ struct ccp_context *ctx = BinarySink_DOWNCAST(bs, struct ccp_context);
+ const unsigned char *blk = (const unsigned char *)blkv;
+
+ /* First 4 bytes are the IV */
+ while (ctx->mac_initialised < 4 && len) {
+ ctx->mac_iv[7 - ctx->mac_initialised] = *blk++;
+ ++ctx->mac_initialised;
+ --len;
+ }
+
+ /* Initialise the IV if needed */
+ if (ctx->mac_initialised == 4) {
+ chacha20_iv(&ctx->b_cipher, ctx->mac_iv);
+ ++ctx->mac_initialised; /* Don't do it again */
+
+ /* Do first rotation */
+ chacha20_round(&ctx->b_cipher);
+
+ /* Set the poly key */
+ poly1305_key(&ctx->mac, make_ptrlen(ctx->b_cipher.current, 32));
+
+ /* Set the first round as used */
+ ctx->b_cipher.currentIndex = 64;
+ }
+
+ /* Update the MAC with anything left */
+ if (len) {
+ poly1305_feed(&ctx->mac, blk, len);
+ }
+}
+
+static void poly_genresult(ssh2_mac *mac, unsigned char *blk)
+{
+ struct ccp_context *ctx = container_of(mac, struct ccp_context, mac_if);
+ poly1305_finalise(&ctx->mac, blk);
+}
+
+static const char *poly_text_name(ssh2_mac *mac)
+{
+ return "Poly1305";
+}
+
+const ssh2_macalg ssh2_poly1305 = {
+ .new = poly_ssh2_new,
+ .free = poly_ssh2_free,
+ .setkey = poly_setkey,
+ .start = poly_start,
+ .genresult = poly_genresult,
+ .next_message = nullmac_next_message,
+ .text_name = poly_text_name,
+ .name = "",
+ .etm_name = "", /* Not selectable individually, just part of
+ * ChaCha20-Poly1305 */
+ .len = 16,
+ .keylen = 0,
+};
+
+static ssh_cipher *ccp_new(const ssh_cipheralg *alg)
+{
+ struct ccp_context *ctx = snew(struct ccp_context);
+ BinarySink_INIT(ctx, poly_BinarySink_write);
+ poly1305_init(&ctx->mac);
+ ctx->ciph.vt = alg;
+ ctx->ciph_allocated = true;
+ ctx->mac_allocated = false;
+ return &ctx->ciph;
+}
+
+static void ccp_free(ssh_cipher *cipher)
+{
+ struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
+ ctx->ciph_allocated = false;
+ ccp_common_free(ctx);
+}
+
+static void ccp_iv(ssh_cipher *cipher, const void *iv)
+{
+ /* struct ccp_context *ctx =
+ container_of(cipher, struct ccp_context, ciph); */
+ /* IV is set based on the sequence number */
+}
+
+static void ccp_key(ssh_cipher *cipher, const void *vkey)
+{
+ const unsigned char *key = (const unsigned char *)vkey;
+ struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
+ /* Initialise the a_cipher (for decrypting lengths) with the first 256 bits */
+ chacha20_key(&ctx->a_cipher, key + 32);
+ /* Initialise the b_cipher (for content and MAC) with the second 256 bits */
+ chacha20_key(&ctx->b_cipher, key);
+}
+
+static void ccp_encrypt(ssh_cipher *cipher, void *blk, int len)
+{
+ struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
+ chacha20_encrypt(&ctx->b_cipher, blk, len);
+}
+
+static void ccp_decrypt(ssh_cipher *cipher, void *blk, int len)
+{
+ struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
+ chacha20_decrypt(&ctx->b_cipher, blk, len);
+}
+
+static void ccp_length_op(struct ccp_context *ctx, void *blk, int len,
+ unsigned long seq)
+{
+ unsigned char iv[8];
+ /*
+ * According to RFC 4253 (section 6.4), the packet sequence number wraps
+ * at 2^32, so its 32 high-order bits will always be zero.
+ */
+ PUT_32BIT_LSB_FIRST(iv, 0);
+ PUT_32BIT_LSB_FIRST(iv + 4, seq);
+ chacha20_iv(&ctx->a_cipher, iv);
+ chacha20_iv(&ctx->b_cipher, iv);
+ /* Reset content block count to 1, as the first is the key for Poly1305 */
+ ++ctx->b_cipher.state[12];
+ smemclr(iv, sizeof(iv));
+}
+
+static void ccp_encrypt_length(ssh_cipher *cipher, void *blk, int len,
+ unsigned long seq)
+{
+ struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
+ ccp_length_op(ctx, blk, len, seq);
+ chacha20_encrypt(&ctx->a_cipher, blk, len);
+}
+
+static void ccp_decrypt_length(ssh_cipher *cipher, void *blk, int len,
+ unsigned long seq)
+{
+ struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph);
+ ccp_length_op(ctx, blk, len, seq);
+ chacha20_decrypt(&ctx->a_cipher, blk, len);
+}
+
+const ssh_cipheralg ssh2_chacha20_poly1305 = {
+ .new = ccp_new,
+ .free = ccp_free,
+ .setiv = ccp_iv,
+ .setkey = ccp_key,
+ .encrypt = ccp_encrypt,
+ .decrypt = ccp_decrypt,
+ .encrypt_length = ccp_encrypt_length,
+ .decrypt_length = ccp_decrypt_length,
+ .next_message = nullcipher_next_message,
+ .ssh2_id = "chacha20-poly1305@openssh.com",
+ .blksize = 1,
+ .real_keybits = 512,
+ .padded_keybytes = 64,
+ .flags = SSH_CIPHER_SEPARATE_LENGTH,
+ .text_name = "ChaCha20",
+ .required_mac = &ssh2_poly1305,
+};
+
+static const ssh_cipheralg *const ccp_list[] = {
+ &ssh2_chacha20_poly1305
+};
+
+const ssh2_ciphers ssh2_ccp = { lenof(ccp_list), ccp_list };
diff --git a/crypto/crc32.c b/crypto/crc32.c
new file mode 100644
index 00000000..bd874433
--- /dev/null
+++ b/crypto/crc32.c
@@ -0,0 +1,113 @@
+/*
+ * CRC32 implementation, as used in SSH-1.
+ *
+ * (This is not, of course, a cryptographic function! It lives in the
+ * 'crypto' directory because SSH-1 uses it _as if_ it was crypto: it
+ * handles sensitive data, and we implement it with care for side
+ * channels.)
+ *
+ * This particular form of the CRC uses the polynomial
+ * P(x) = x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+1
+ * and represents polynomials in bit-reversed form, so that the x^0
+ * coefficient (constant term) appears in the bit with place value
+ * 2^31, and the x^31 coefficient in the bit with place value 2^0. In
+ * this representation, (x^32 mod P) = 0xEDB88320, so multiplying the
+ * current state by x is done by shifting right by one bit, and XORing
+ * that constant into the result if the bit shifted out was 1.
+ *
+ * There's a bewildering array of subtly different variants of CRC out
+ * there, using different polynomials, both bit orders, and varying
+ * the start and end conditions. There are catalogue websites such as
+ * http://reveng.sourceforge.net/crc-catalogue/ , which generally seem
+ * to have the convention of indexing CRCs by their 'check value',
+ * defined as whatever you get if you hash the 9-byte test string
+ * "123456789".
+ *
+ * The crc32_rfc1662() function below, which starts off the CRC state
+ * at 0xFFFFFFFF and complements it after feeding all the data, gives
+ * the check value 0xCBF43926, and matches the hash function that the
+ * above catalogue refers to as "CRC-32/ISO-HDLC"; among other things,
+ * it's also the "FCS-32" checksum described in RFC 1662 section C.3
+ * (hence the name I've given it here).
+ *
+ * The crc32_ssh1() function implements the variant form used by
+ * SSH-1, which uses the same update function, but starts the state at
+ * zero and doesn't complement it at the end of the computation. The
+ * check value for that version is 0x2DFD2D88, which that CRC
+ * catalogue doesn't list at all.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "ssh.h"
+
+/*
+ * Multiply a CRC value by x^4. This implementation strategy avoids
+ * using a lookup table (which would be a side-channel hazard, since
+ * SSH-1 applies this CRC to decrypted session data).
+ *
+ * The basic idea is that you'd like to "multiply" the shifted-out 4
+ * bits by the CRC polynomial value 0xEDB88320, or rather by that
+ * value shifted right 3 bits (since you want the _last_ bit shifted
+ * out, i.e. the one originally at the 2^3 position, to generate
+ * 0xEDB88320 itself). But the scare-quoted "multiply" would have to
+ * be a multiplication of polynomials over GF(2), which differs from
+ * integer multiplication in that you don't have any carries. In other
+ * words, you make a copy of one input shifted left by the index of
+ * each set bit in the other, so that adding them all together would
+ * give you the ordinary integer product, and then you XOR them
+ * together instead.
+ *
+ * With a 4-bit multiplier, the two kinds of multiplication coincide
+ * provided the multiplicand has no two set bits at positions
+ * differing by less than 4, because then no two copies of the
+ * multiplier can overlap to generate a carry. So I break up the
+ * intended multiplicand K = 0xEDB88320 >> 3 into three sub-constants
+ * a,b,c with that property, such that a^b^c = K. Then I can multiply
+ * m by each of them separately, and XOR together the results.
+ */
+static inline uint32_t crc32_shift_4(uint32_t v)
+{
+ const uint32_t a = 0x11111044, b = 0x08840020, c = 0x04220000;
+ uint32_t m = v & 0xF;
+ return (v >> 4) ^ (a*m) ^ (b*m) ^ (c*m);
+}
+
+/*
+ * The 8-bit shift you need every time you absorb an input byte,
+ * implemented simply by iterating the 4-bit shift twice.
+ */
+static inline uint32_t crc32_shift_8(uint32_t v)
+{
+ return crc32_shift_4(crc32_shift_4(v));
+}
+
+/*
+ * Update an existing hash value with extra bytes of data.
+ */
+uint32_t crc32_update(uint32_t crc, ptrlen data)
+{
+ const uint8_t *p = (const uint8_t *)data.ptr;
+ for (size_t len = data.len; len-- > 0 ;)
+ crc = crc32_shift_8(crc ^ *p++);
+ return crc;
+}
+
+/*
+ * The SSH-1 variant of CRC-32.
+ */
+uint32_t crc32_ssh1(ptrlen data)
+{
+ return crc32_update(0, data);
+}
+
+/*
+ * The official version of CRC-32. Nothing in PuTTY proper uses this,
+ * but it's useful to expose it to testcrypt so that we can implement
+ * standard test vectors.
+ */
+uint32_t crc32_rfc1662(ptrlen data)
+{
+ return crc32_update(0xFFFFFFFF, data) ^ 0xFFFFFFFF;
+}
diff --git a/crypto/des.c b/crypto/des.c
new file mode 100644
index 00000000..1cbec8fa
--- /dev/null
+++ b/crypto/des.c
@@ -0,0 +1,1053 @@
+/*
+ * 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 <stdio.h>
+
+#include "ssh.h"
+#include "mpint_i.h" /* we reuse the BignumInt system */
+
+/* If you compile with -DDES_DIAGNOSTICS, intermediate results will be
+ * sent to debug() (so you also need to compile with -DDEBUG).
+ * Otherwise this ifdef will condition away all the debug() calls. */
+#ifndef DES_DIAGNOSTICS
+#undef debug
+#define debug(...) ((void)0)
+#endif
+
+/*
+ * General utility functions.
+ */
+static inline uint32_t rol(uint32_t x, unsigned c)
+{
+ return (x << (31 & c)) | (x >> (31 & -c));
+}
+static inline uint32_t ror(uint32_t x, unsigned c)
+{
+ return rol(x, -c);
+}
+
+/*
+ * The hard part of doing DES in constant time is the S-box lookup.
+ *
+ * My strategy is to iterate over the whole lookup table! That's slow,
+ * but I don't see any way to avoid _something_ along those lines: in
+ * every round, every entry in every S-box is potentially needed, and
+ * if you can't change your memory access pattern based on the input
+ * data, it follows that you have to read a quantity of information
+ * equal to the size of all the S-boxes. (Unless they were to turn out
+ * to be significantly compressible, but I for one couldn't show them
+ * to be.)
+ *
+ * In more detail, I construct a sort of counter-based 'selection
+ * gadget', which is 15 bits wide and starts off with the top bit
+ * zero, the next eight bits all 1, and the bottom six set to the
+ * input S-box index:
+ *
+ * 011111111xxxxxx
+ *
+ * Now if you add 1 in the lowest bit position, then either it carries
+ * into the top section (resetting it to 100000000), or it doesn't do
+ * that yet. If you do that 64 times, then it will _guarantee_ to have
+ * ticked over into 100000000. In between those increments, the eight
+ * bits that started off as 11111111 will have stayed that way for
+ * some number of iterations and then become 00000000, and exactly how
+ * many iterations depends on the input index.
+ *
+ * The purpose of the 0 bit at the top is to absorb the carry when the
+ * switch happens, which means you can pack more than one gadget into
+ * the same machine word and have them all work in parallel without
+ * each one intefering with the next.
+ *
+ * The next step is to use each of those 8-bit segments as a bit mask:
+ * each one is ANDed with a lookup table entry, and all the results
+ * are XORed together. So you end up with the bitwise XOR of some
+ * initial segment of the table entries. And the stored S-box tables
+ * are transformed in such a way that the real S-box values are given
+ * not by the individual entries, but by the cumulative XORs
+ * constructed in this way.
+ *
+ * A refinement is that I increment each gadget by 2 rather than 1
+ * each time, so I only iterate 32 times instead of 64. That's why
+ * there are 8 selection bits instead of 4: each gadget selects enough
+ * bits to reconstruct _two_ S-box entries, for a pair of indices
+ * (2n,2n+1), and then finally I use the low bit of the index to do a
+ * parallel selection between each of those pairs.
+ *
+ * The selection gadget is not quite 16 bits wide. So you can fit four
+ * of them across a 64-bit word at 16-bit intervals, which is also
+ * convenient because the place the S-box indices are coming from also
+ * has pairs of them separated by 16-bit distances, so it's easy to
+ * copy them into the gadgets in the first place.
+ */
+
+/*
+ * The S-box data. Each pair of nonzero columns here describes one of
+ * the S-boxes, corresponding to the SGTDES tables in test/desref.py,
+ * under the following transformation.
+ *
+ * Take S-box #3 as an example. Its values in successive rows of this
+ * table are eb,e8,54,3d, ... So the cumulative XORs of initial
+ * sequences of those values are eb,(eb^e8),(eb^e8^54), ... which
+ * comes to eb,03,57,... Of _those_ values, the top nibble (e,0,5,...)
+ * gives the even-numbered entries in the S-box, in _reverse_ order
+ * (because a lower input index selects the XOR of a longer
+ * subsequence). The odd-numbered entries are given by XORing the two
+ * digits together: (e^b),(0^3),(5^7),... = 5,3,2,... And indeed, if
+ * you check SGTDES.sboxes[3] you find it ends ... 52 03 e5.
+ */
+#define SBOX_ITERATION(X) \
+ /* 66 22 44 00 77 33 55 11 */ \
+ X(0xf600970083008500, 0x0e00eb007b002e00) \
+ X(0xda00e4009000e000, 0xad00e800a700b400) \
+ X(0x1a009d003f003600, 0xf60054004300cd00) \
+ X(0xaf00c500e900a900, 0x63003d00f2005900) \
+ X(0xf300750079001400, 0x80005000a2008900) \
+ X(0xa100d400d6007b00, 0xd3009000d300e100) \
+ X(0x450087002600ac00, 0xae003c0031009c00) \
+ X(0xd000b100b6003600, 0x3e006f0092005900) \
+ X(0x4d008a0026001000, 0x89007a00b8004a00) \
+ X(0xca00f5003f00ac00, 0x6f00f0003c009400) \
+ X(0x92008d0090001000, 0x8c00c600ce004a00) \
+ X(0xe2005900e9006d00, 0x790078007800fa00) \
+ X(0x1300b10090008d00, 0xa300170027001800) \
+ X(0xc70058005f006a00, 0x9c00c100e0006300) \
+ X(0x9b002000f000f000, 0xf70057001600f900) \
+ X(0xeb00b0009000af00, 0xa9006300b0005800) \
+ X(0xa2001d00cf000000, 0x3800b00066000000) \
+ X(0xf100da007900d000, 0xbc00790094007900) \
+ X(0x570015001900ad00, 0x6f00ef005100cb00) \
+ X(0xc3006100e9006d00, 0xc000b700f800f200) \
+ X(0x1d005800b600d000, 0x67004d00cd002c00) \
+ X(0xf400b800d600e000, 0x5e00a900b000e700) \
+ X(0x5400d1003f009c00, 0xc90069002c005300) \
+ X(0xe200e50060005900, 0x6a00b800c500f200) \
+ X(0xdf0047007900d500, 0x7000ec004c00ea00) \
+ X(0x7100d10060009c00, 0x3f00b10095005e00) \
+ X(0x82008200f0002000, 0x87001d00cd008000) \
+ X(0xd0007000af00c000, 0xe200be006100f200) \
+ X(0x8000930060001000, 0x36006e0081001200) \
+ X(0x6500a300d600ac00, 0xcf003d007d00c000) \
+ X(0x9000700060009800, 0x62008100ad009200) \
+ X(0xe000e4003f00f400, 0x5a00ed009000f200) \
+ /* end of list */
+
+/*
+ * The S-box mapping function. Expects two 32-bit input words: si6420
+ * contains the table indices for S-boxes 0,2,4,6 with their low bits
+ * starting at position 2 (for S-box 0) and going up in steps of 8.
+ * si7531 has indices 1,3,5,7 in the same bit positions.
+ */
+static inline uint32_t des_S(uint32_t si6420, uint32_t si7531)
+{
+ debug("sindices: %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ 0x3F & (si6420 >> 2), 0x3F & (si7531 >> 2),
+ 0x3F & (si6420 >> 10), 0x3F & (si7531 >> 10),
+ 0x3F & (si6420 >> 18), 0x3F & (si7531 >> 18),
+ 0x3F & (si6420 >> 26), 0x3F & (si7531 >> 26));
+
+#ifdef SIXTY_FOUR_BIT
+ /*
+ * On 64-bit machines, we store the table in exactly the form
+ * shown above, and make two 64-bit words containing four
+ * selection gadgets each.
+ */
+
+ /* Set up the gadgets. The 'cNNNN' variables will be gradually
+ * incremented, and the bits in positions FF00FF00FF00FF00 will
+ * act as selectors for the words in the table.
+ *
+ * A side effect of moving the input indices further apart is that
+ * they change order, because it's easier to keep a pair that were
+ * originally 16 bits apart still 16 bits apart, which now makes
+ * them adjacent instead of separated by one. So the fact that
+ * si6420 turns into c6240 (with the 2,4 reversed) is not a typo!
+ * This will all be undone when we rebuild the output word later.
+ */
+ uint64_t c6240 = ((si6420 | ((uint64_t)si6420 << 24))
+ & 0x00FC00FC00FC00FC) | 0xFF00FF00FF00FF00;
+ uint64_t c7351 = ((si7531 | ((uint64_t)si7531 << 24))
+ & 0x00FC00FC00FC00FC) | 0xFF00FF00FF00FF00;
+ debug("S in: c6240=%016"PRIx64" c7351=%016"PRIx64"\n", c6240, c7351);
+
+ /* Iterate over the table. The 'sNNNN' variables accumulate the
+ * XOR of all the table entries not masked out. */
+ static const struct tbl { uint64_t t6240, t7351; } tbl[32] = {
+#define TABLE64(a, b) { a, b },
+ SBOX_ITERATION(TABLE64)
+#undef TABLE64
+ };
+ uint64_t s6240 = 0, s7351 = 0;
+ for (const struct tbl *t = tbl, *limit = tbl + 32; t < limit; t++) {
+ s6240 ^= c6240 & t->t6240; c6240 += 0x0008000800080008;
+ s7351 ^= c7351 & t->t7351; c7351 += 0x0008000800080008;
+ }
+ debug("S out: s6240=%016"PRIx64" s7351=%016"PRIx64"\n", s6240, s7351);
+
+ /* Final selection between each even/odd pair: mask off the low
+ * bits of all the input indices (which haven't changed throughout
+ * the iteration), and multiply by a bit mask that will turn each
+ * set bit into a mask covering the upper nibble of the selected
+ * pair. Then use those masks to control which set of lower
+ * nibbles is XORed into the upper nibbles. */
+ s6240 ^= (s6240 << 4) & ((0xf000/0x004) * (c6240 & 0x0004000400040004));
+ s7351 ^= (s7351 << 4) & ((0xf000/0x004) * (c7351 & 0x0004000400040004));
+
+ /* Now the eight final S-box outputs are in the upper nibble of
+ * each selection position. Mask away the rest of the clutter. */
+ s6240 &= 0xf000f000f000f000;
+ s7351 &= 0xf000f000f000f000;
+ debug("s0=%x s1=%x s2=%x s3=%x s4=%x s5=%x s6=%x s7=%x\n",
+ (unsigned)(0xF & (s6240 >> 12)),
+ (unsigned)(0xF & (s7351 >> 12)),
+ (unsigned)(0xF & (s6240 >> 44)),
+ (unsigned)(0xF & (s7351 >> 44)),
+ (unsigned)(0xF & (s6240 >> 28)),
+ (unsigned)(0xF & (s7351 >> 28)),
+ (unsigned)(0xF & (s6240 >> 60)),
+ (unsigned)(0xF & (s7351 >> 60)));
+
+ /* Combine them all into a single 32-bit output word, which will
+ * come out in the order 76543210. */
+ uint64_t combined = (s6240 >> 12) | (s7351 >> 8);
+ return combined | (combined >> 24);
+
+#else /* SIXTY_FOUR_BIT */
+ /*
+ * For 32-bit platforms, we do the same thing but in four 32-bit
+ * words instead of two 64-bit ones, so the CPU doesn't have to
+ * waste time propagating carries or shifted bits between the two
+ * halves of a uint64 that weren't needed anyway.
+ */
+
+ /* Set up the gadgets */
+ uint32_t c40 = ((si6420 ) & 0x00FC00FC) | 0xFF00FF00;
+ uint32_t c62 = ((si6420 >> 8) & 0x00FC00FC) | 0xFF00FF00;
+ uint32_t c51 = ((si7531 ) & 0x00FC00FC) | 0xFF00FF00;
+ uint32_t c73 = ((si7531 >> 8) & 0x00FC00FC) | 0xFF00FF00;
+ debug("S in: c40=%08"PRIx32" c62=%08"PRIx32
+ " c51=%08"PRIx32" c73=%08"PRIx32"\n", c40, c62, c51, c73);
+
+ /* Iterate over the table */
+ static const struct tbl { uint32_t t40, t62, t51, t73; } tbl[32] = {
+#define TABLE32(a, b) { ((uint32_t)a), (a>>32), ((uint32_t)b), (b>>32) },
+ SBOX_ITERATION(TABLE32)
+#undef TABLE32
+ };
+ uint32_t s40 = 0, s62 = 0, s51 = 0, s73 = 0;
+ for (const struct tbl *t = tbl, *limit = tbl + 32; t < limit; t++) {
+ s40 ^= c40 & t->t40; c40 += 0x00080008;
+ s62 ^= c62 & t->t62; c62 += 0x00080008;
+ s51 ^= c51 & t->t51; c51 += 0x00080008;
+ s73 ^= c73 & t->t73; c73 += 0x00080008;
+ }
+ debug("S out: s40=%08"PRIx32" s62=%08"PRIx32
+ " s51=%08"PRIx32" s73=%08"PRIx32"\n", s40, s62, s51, s73);
+
+ /* Final selection within each pair */
+ s40 ^= (s40 << 4) & ((0xf000/0x004) * (c40 & 0x00040004));
+ s62 ^= (s62 << 4) & ((0xf000/0x004) * (c62 & 0x00040004));
+ s51 ^= (s51 << 4) & ((0xf000/0x004) * (c51 & 0x00040004));
+ s73 ^= (s73 << 4) & ((0xf000/0x004) * (c73 & 0x00040004));
+
+ /* Clean up the clutter */
+ s40 &= 0xf000f000;
+ s62 &= 0xf000f000;
+ s51 &= 0xf000f000;
+ s73 &= 0xf000f000;
+ debug("s0=%x s1=%x s2=%x s3=%x s4=%x s5=%x s6=%x s7=%x\n",
+ (unsigned)(0xF & (s40 >> 12)),
+ (unsigned)(0xF & (s51 >> 12)),
+ (unsigned)(0xF & (s62 >> 12)),
+ (unsigned)(0xF & (s73 >> 12)),
+ (unsigned)(0xF & (s40 >> 28)),
+ (unsigned)(0xF & (s51 >> 28)),
+ (unsigned)(0xF & (s62 >> 28)),
+ (unsigned)(0xF & (s73 >> 28)));
+
+ /* Recombine and return */
+ return (s40 >> 12) | (s62 >> 4) | (s51 >> 8) | (s73);
+
+#endif /* SIXTY_FOUR_BIT */
+
+}
+
+/*
+ * Now for the permutation P. The basic strategy here is to use a
+ * Benes network: in each stage, the bit at position i is allowed to
+ * either stay where it is or swap with i ^ D, where D is a power of 2
+ * that varies with each phase. (So when D=1, pairs of the form
+ * {2n,2n+1} can swap; when D=2, the pairs are {4n+j,4n+j+2} for
+ * j={0,1}, and so on.)
+ *
+ * You can recursively construct a Benes network for an arbitrary
+ * permutation, in which the values of D iterate across all the powers
+ * of 2 less than the permutation size and then go back again. For
+ * example, the typical presentation for 32 bits would have D iterate
+ * over 16,8,4,2,1,2,4,8,16, and there's an easy algorithm that can
+ * express any permutation in that form by deciding which pairs of
+ * bits to swap in the outer pair of stages and then recursing to do
+ * all the stages in between.
+ *
+ * Actually implementing the swaps is easy when they're all between
+ * bits at the same separation: make the value x ^ (x >> D), mask out
+ * just the bits in the low position of a pair that needs to swap, and
+ * then use the resulting value y to make x ^ y ^ (y << D) which is
+ * the swapped version.
+ *
+ * In this particular case, I processed the bit indices in the other
+ * order (going 1,2,4,8,16,8,4,2,1), which makes no significant
+ * difference to the construction algorithm (it's just a relabelling),
+ * but it now means that the first two steps only permute entries
+ * within the output of each S-box - and therefore we can leave them
+ * completely out, in favour of just defining the S-boxes so that
+ * those permutation steps are already applied. Furthermore, by
+ * exhaustive search over the rest of the possible bit-orders for each
+ * S-box, I was able to find a version of P which could be represented
+ * in such a way that two further phases had all their control bits
+ * zero and could be skipped. So the number of swap stages is reduced
+ * to 5 from the 9 that might have been needed.
+ */
+
+static inline uint32_t des_benes_step(uint32_t v, unsigned D, uint32_t mask)
+{
+ uint32_t diff = (v ^ (v >> D)) & mask;
+ return v ^ diff ^ (diff << D);
+}
+
+static inline uint32_t des_P(uint32_t v_orig)
+{
+ uint32_t v = v_orig;
+
+ /* initial stages with distance 1,2 are part of the S-box data table */
+ v = des_benes_step(v, 4, 0x07030702);
+ v = des_benes_step(v, 8, 0x004E009E);
+ v = des_benes_step(v, 16, 0x0000D9D3);
+/* v = des_benes_step(v, 8, 0x00000000); no-op, so we can skip it */
+ v = des_benes_step(v, 4, 0x05040004);
+/* v = des_benes_step(v, 2, 0x00000000); no-op, so we can skip it */
+ v = des_benes_step(v, 1, 0x04045015);
+
+ debug("P(%08"PRIx32") = %08"PRIx32"\n", v_orig, v);
+
+ return v;
+}
+
+/*
+ * Putting the S and P functions together, and adding in the round key
+ * as well, gives us the full mixing function f.
+ */
+
+static inline uint32_t des_f(uint32_t R, uint32_t K7531, uint32_t K6420)
+{
+ uint32_t s7531 = R ^ K7531, s6420 = rol(R, 4) ^ K6420;
+ return des_P(des_S(s6420, s7531));
+}
+
+/*
+ * The key schedule, and the function to set it up.
+ */
+
+typedef struct des_keysched des_keysched;
+struct des_keysched {
+ uint32_t k7531[16], k6420[16];
+};
+
+/*
+ * Simplistic function to select an arbitrary sequence of bits from
+ * one value and glue them together into another value. bitnums[]
+ * gives the sequence of bit indices of the input, from the highest
+ * output bit downwards. An index of -1 means that output bit is left
+ * at zero.
+ *
+ * This function is only used during key setup, so it doesn't need to
+ * be highly optimised.
+ */
+static inline uint64_t bitsel(
+ uint64_t input, const int8_t *bitnums, size_t size)
+{
+ uint64_t ret = 0;
+ while (size-- > 0) {
+ int bitpos = *bitnums++;
+ ret <<= 1;
+ if (bitpos >= 0)
+ ret |= 1 & (input >> bitpos);
+ }
+ return ret;
+}
+
+static void des_key_setup(uint64_t key, des_keysched *sched)
+{
+ static const int8_t PC1[] = {
+ 7, 15, 23, 31, 39, 47, 55, 63, 6, 14, 22, 30, 38, 46,
+ 54, 62, 5, 13, 21, 29, 37, 45, 53, 61, 4, 12, 20, 28,
+ -1, -1, -1, -1,
+ 1, 9, 17, 25, 33, 41, 49, 57, 2, 10, 18, 26, 34, 42,
+ 50, 58, 3, 11, 19, 27, 35, 43, 51, 59, 36, 44, 52, 60,
+ };
+ static const int8_t PC2_7531[] = {
+ 46, 43, 49, 36, 59, 55, -1, -1, /* index into S-box 7 */
+ 37, 41, 48, 56, 34, 52, -1, -1, /* index into S-box 5 */
+ 15, 4, 25, 19, 9, 1, -1, -1, /* index into S-box 3 */
+ 12, 7, 17, 0, 22, 3, -1, -1, /* index into S-box 1 */
+ };
+ static const int8_t PC2_6420[] = {
+ 57, 32, 45, 54, 39, 50, -1, -1, /* index into S-box 6 */
+ 44, 53, 33, 40, 47, 58, -1, -1, /* index into S-box 4 */
+ 26, 16, 5, 11, 23, 8, -1, -1, /* index into S-box 2 */
+ 10, 14, 6, 20, 27, 24, -1, -1, /* index into S-box 0 */
+ };
+ static const int leftshifts[] = {1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1};
+
+ /* Select 56 bits from the 64-bit input key integer (the low bit
+ * of each input byte is unused), into a word consisting of two
+ * 28-bit integers starting at bits 0 and 32. */
+ uint64_t CD = bitsel(key, PC1, lenof(PC1));
+
+ for (size_t i = 0; i < 16; i++) {
+ /* Rotate each 28-bit half of CD left by 1 or 2 bits (varying
+ * between rounds) */
+ CD <<= leftshifts[i];
+ CD = (CD & 0x0FFFFFFF0FFFFFFF) | ((CD & 0xF0000000F0000000) >> 28);
+
+ /* Select key bits from the rotated word to use during the
+ * actual cipher */
+ sched->k7531[i] = bitsel(CD, PC2_7531, lenof(PC2_7531));
+ sched->k6420[i] = bitsel(CD, PC2_6420, lenof(PC2_6420));
+ }
+}
+
+/*
+ * Helper routines for dealing with 64-bit blocks in the form of an L
+ * and R word.
+ */
+
+typedef struct LR LR;
+struct LR { uint32_t L, R; };
+
+static inline LR des_load_lr(const void *vp)
+{
+ const uint8_t *p = (const uint8_t *)vp;
+ LR out;
+ out.L = GET_32BIT_MSB_FIRST(p);
+ out.R = GET_32BIT_MSB_FIRST(p+4);
+ return out;
+}
+
+static inline void des_store_lr(void *vp, LR lr)
+{
+ uint8_t *p = (uint8_t *)vp;
+ PUT_32BIT_MSB_FIRST(p, lr.L);
+ PUT_32BIT_MSB_FIRST(p+4, lr.R);
+}
+
+static inline LR des_xor_lr(LR a, LR b)
+{
+ a.L ^= b.L;
+ a.R ^= b.R;
+ return a;
+}
+
+static inline LR des_swap_lr(LR in)
+{
+ LR out;
+ out.L = in.R;
+ out.R = in.L;
+ return out;
+}
+
+/*
+ * The initial and final permutations of official DES are in a
+ * restricted form, in which the 'before' and 'after' positions of a
+ * given data bit are derived from each other by permuting the bits of
+ * the _index_ and flipping some of them. This allows the permutation
+ * to be performed effectively by a method that looks rather like
+ * _half_ of a general Benes network, because the restricted form
+ * means only half of it is actually needed.
+ *
+ * _Our_ initial and final permutations include a rotation by 1 bit,
+ * but it's still easier to just suffix that to the standard IP/FP
+ * than to regenerate everything using a more general method.
+ *
+ * Because we're permuting 64 bits in this case, between two 32-bit
+ * words, there's a separate helper function for this code that
+ * doesn't look quite like des_benes_step() above.
+ */
+
+static inline void des_bitswap_IP_FP(uint32_t *L, uint32_t *R,
+ unsigned D, uint32_t mask)
+{
+ uint32_t diff = mask & ((*R >> D) ^ *L);
+ *R ^= diff << D;
+ *L ^= diff;
+}
+
+static inline LR des_IP(LR lr)
+{
+ des_bitswap_IP_FP(&lr.R, &lr.L, 4, 0x0F0F0F0F);
+ des_bitswap_IP_FP(&lr.R, &lr.L, 16, 0x0000FFFF);
+ des_bitswap_IP_FP(&lr.L, &lr.R, 2, 0x33333333);
+ des_bitswap_IP_FP(&lr.L, &lr.R, 8, 0x00FF00FF);
+ des_bitswap_IP_FP(&lr.R, &lr.L, 1, 0x55555555);
+
+ lr.L = ror(lr.L, 1);
+ lr.R = ror(lr.R, 1);
+
+ return lr;
+}
+
+static inline LR des_FP(LR lr)
+{
+ lr.L = rol(lr.L, 1);
+ lr.R = rol(lr.R, 1);
+
+ des_bitswap_IP_FP(&lr.R, &lr.L, 1, 0x55555555);
+ des_bitswap_IP_FP(&lr.L, &lr.R, 8, 0x00FF00FF);
+ des_bitswap_IP_FP(&lr.L, &lr.R, 2, 0x33333333);
+ des_bitswap_IP_FP(&lr.R, &lr.L, 16, 0x0000FFFF);
+ des_bitswap_IP_FP(&lr.R, &lr.L, 4, 0x0F0F0F0F);
+
+ return lr;
+}
+
+/*
+ * The main cipher functions, which are identical except that they use
+ * the key schedule in opposite orders.
+ *
+ * We provide a version without the initial and final permutations,
+ * for use in triple-DES mode (no sense undoing and redoing it in
+ * between the phases).
+ */
+
+static inline LR des_round(LR in, const des_keysched *sched, size_t round)
+{
+ LR out;
+ out.L = in.R;
+ out.R = in.L ^ des_f(in.R, sched->k7531[round], sched->k6420[round]);
+ return out;
+}
+
+static inline LR des_inner_cipher(LR lr, const des_keysched *sched,
+ size_t start, size_t step)
+{
+ lr = des_round(lr, sched, start+0x0*step);
+ lr = des_round(lr, sched, start+0x1*step);
+ lr = des_round(lr, sched, start+0x2*step);
+ lr = des_round(lr, sched, start+0x3*step);
+ lr = des_round(lr, sched, start+0x4*step);
+ lr = des_round(lr, sched, start+0x5*step);
+ lr = des_round(lr, sched, start+0x6*step);
+ lr = des_round(lr, sched, start+0x7*step);
+ lr = des_round(lr, sched, start+0x8*step);
+ lr = des_round(lr, sched, start+0x9*step);
+ lr = des_round(lr, sched, start+0xa*step);
+ lr = des_round(lr, sched, start+0xb*step);
+ lr = des_round(lr, sched, start+0xc*step);
+ lr = des_round(lr, sched, start+0xd*step);
+ lr = des_round(lr, sched, start+0xe*step);
+ lr = des_round(lr, sched, start+0xf*step);
+ return des_swap_lr(lr);
+}
+
+static inline LR des_full_cipher(LR lr, const des_keysched *sched,
+ size_t start, size_t step)
+{
+ lr = des_IP(lr);
+ lr = des_inner_cipher(lr, sched, start, step);
+ lr = des_FP(lr);
+ return lr;
+}
+
+/*
+ * Parameter pairs for the start,step arguments to the cipher routines
+ * above, causing them to use the same key schedule in opposite orders.
+ */
+#define ENCIPHER 0, 1 /* for encryption */
+#define DECIPHER 15, -1 /* for decryption */
+
+/* ----------------------------------------------------------------------
+ * Single-DES
+ */
+
+struct des_cbc_ctx {
+ des_keysched sched;
+ LR iv;
+ ssh_cipher ciph;
+};
+
+static ssh_cipher *des_cbc_new(const ssh_cipheralg *alg)
+{
+ struct des_cbc_ctx *ctx = snew(struct des_cbc_ctx);
+ ctx->ciph.vt = alg;
+ return &ctx->ciph;
+}
+
+static void des_cbc_free(ssh_cipher *ciph)
+{
+ struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph);
+ smemclr(ctx, sizeof(*ctx));
+ sfree(ctx);
+}
+
+static void des_cbc_setkey(ssh_cipher *ciph, const void *vkey)
+{
+ struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph);
+ const uint8_t *key = (const uint8_t *)vkey;
+ des_key_setup(GET_64BIT_MSB_FIRST(key), &ctx->sched);
+}
+
+static void des_cbc_setiv(ssh_cipher *ciph, const void *iv)
+{
+ struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph);
+ ctx->iv = des_load_lr(iv);
+}
+
+static void des_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len)
+{
+ struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph);
+ uint8_t *data = (uint8_t *)vdata;
+ for (; len > 0; len -= 8, data += 8) {
+ LR plaintext = des_load_lr(data);
+ LR cipher_in = des_xor_lr(plaintext, ctx->iv);
+ LR ciphertext = des_full_cipher(cipher_in, &ctx->sched, ENCIPHER);
+ des_store_lr(data, ciphertext);
+ ctx->iv = ciphertext;
+ }
+}
+
+static void des_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len)
+{
+ struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph);
+ uint8_t *data = (uint8_t *)vdata;
+ for (; len > 0; len -= 8, data += 8) {
+ LR ciphertext = des_load_lr(data);
+ LR cipher_out = des_full_cipher(ciphertext, &ctx->sched, DECIPHER);
+ LR plaintext = des_xor_lr(cipher_out, ctx->iv);
+ des_store_lr(data, plaintext);
+ ctx->iv = ciphertext;
+ }
+}
+
+const ssh_cipheralg ssh_des = {
+ .new = des_cbc_new,
+ .free = des_cbc_free,
+ .setiv = des_cbc_setiv,
+ .setkey = des_cbc_setkey,
+ .encrypt = des_cbc_encrypt,
+ .decrypt = des_cbc_decrypt,
+ .next_message = nullcipher_next_message,
+ .ssh2_id = "des-cbc",
+ .blksize = 8,
+ .real_keybits = 56,
+ .padded_keybytes = 8,
+ .flags = SSH_CIPHER_IS_CBC,
+ .text_name = "single-DES CBC",
+};
+
+const ssh_cipheralg ssh_des_sshcom_ssh2 = {
+ /* Same as ssh_des_cbc, but with a different SSH-2 ID */
+ .new = des_cbc_new,
+ .free = des_cbc_free,
+ .setiv = des_cbc_setiv,
+ .setkey = des_cbc_setkey,
+ .encrypt = des_cbc_encrypt,
+ .decrypt = des_cbc_decrypt,
+ .next_message = nullcipher_next_message,
+ .ssh2_id = "des-cbc@ssh.com",
+ .blksize = 8,
+ .real_keybits = 56,
+ .padded_keybytes = 8,
+ .flags = SSH_CIPHER_IS_CBC,
+ .text_name = "single-DES CBC",
+};
+
+static const ssh_cipheralg *const des_list[] = {
+ &ssh_des,
+ &ssh_des_sshcom_ssh2
+};
+
+const ssh2_ciphers ssh2_des = { lenof(des_list), des_list };
+
+/* ----------------------------------------------------------------------
+ * Triple-DES CBC, SSH-2 style. The CBC mode treats the three
+ * invocations of DES as a single unified cipher, and surrounds it
+ * with just one layer of CBC, so only one IV is needed.
+ */
+
+struct des3_cbc1_ctx {
+ des_keysched sched[3];
+ LR iv;
+ ssh_cipher ciph;
+};
+
+static ssh_cipher *des3_cbc1_new(const ssh_cipheralg *alg)
+{
+ struct des3_cbc1_ctx *ctx = snew(struct des3_cbc1_ctx);
+ ctx->ciph.vt = alg;
+ return &ctx->ciph;
+}
+
+static void des3_cbc1_free(ssh_cipher *ciph)
+{
+ struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph);
+ smemclr(ctx, sizeof(*ctx));
+ sfree(ctx);
+}
+
+static void des3_cbc1_setkey(ssh_cipher *ciph, const void *vkey)
+{
+ struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph);
+ const uint8_t *key = (const uint8_t *)vkey;
+ for (size_t i = 0; i < 3; i++)
+ des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]);
+}
+
+static void des3_cbc1_setiv(ssh_cipher *ciph, const void *iv)
+{
+ struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph);
+ ctx->iv = des_load_lr(iv);
+}
+
+static void des3_cbc1_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len)
+{
+ struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph);
+ uint8_t *data = (uint8_t *)vdata;
+ for (; len > 0; len -= 8, data += 8) {
+ LR plaintext = des_load_lr(data);
+ LR cipher_in = des_xor_lr(plaintext, ctx->iv);
+
+ /* Run three copies of the cipher, without undoing and redoing
+ * IP/FP in between. */
+ LR lr = des_IP(cipher_in);
+ lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER);
+ lr = des_inner_cipher(lr, &ctx->sched[1], DECIPHER);
+ lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER);
+ LR ciphertext = des_FP(lr);
+
+ des_store_lr(data, ciphertext);
+ ctx->iv = ciphertext;
+ }
+}
+
+static void des3_cbc1_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len)
+{
+ struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph);
+ uint8_t *data = (uint8_t *)vdata;
+ for (; len > 0; len -= 8, data += 8) {
+ LR ciphertext = des_load_lr(data);
+
+ /* Similarly to encryption, but with the order reversed. */
+ LR lr = des_IP(ciphertext);
+ lr = des_inner_cipher(lr, &ctx->sched[2], DECIPHER);
+ lr = des_inner_cipher(lr, &ctx->sched[1], ENCIPHER);
+ lr = des_inner_cipher(lr, &ctx->sched[0], DECIPHER);
+ LR cipher_out = des_FP(lr);
+
+ LR plaintext = des_xor_lr(cipher_out, ctx->iv);
+ des_store_lr(data, plaintext);
+ ctx->iv = ciphertext;
+ }
+}
+
+const ssh_cipheralg ssh_3des_ssh2 = {
+ .new = des3_cbc1_new,
+ .free = des3_cbc1_free,
+ .setiv = des3_cbc1_setiv,
+ .setkey = des3_cbc1_setkey,
+ .encrypt = des3_cbc1_cbc_encrypt,
+ .decrypt = des3_cbc1_cbc_decrypt,
+ .next_message = nullcipher_next_message,
+ .ssh2_id = "3des-cbc",
+ .blksize = 8,
+ .real_keybits = 168,
+ .padded_keybytes = 24,
+ .flags = SSH_CIPHER_IS_CBC,
+ .text_name = "triple-DES CBC",
+};
+
+/* ----------------------------------------------------------------------
+ * Triple-DES in SDCTR mode. Again, the three DES instances are
+ * treated as one big cipher, with a single counter encrypted through
+ * all three.
+ */
+
+#define SDCTR_WORDS (8 / BIGNUM_INT_BYTES)
+
+struct des3_sdctr_ctx {
+ des_keysched sched[3];
+ BignumInt counter[SDCTR_WORDS];
+ ssh_cipher ciph;
+};
+
+static ssh_cipher *des3_sdctr_new(const ssh_cipheralg *alg)
+{
+ struct des3_sdctr_ctx *ctx = snew(struct des3_sdctr_ctx);
+ ctx->ciph.vt = alg;
+ return &ctx->ciph;
+}
+
+static void des3_sdctr_free(ssh_cipher *ciph)
+{
+ struct des3_sdctr_ctx *ctx = container_of(
+ ciph, struct des3_sdctr_ctx, ciph);
+ smemclr(ctx, sizeof(*ctx));
+ sfree(ctx);
+}
+
+static void des3_sdctr_setkey(ssh_cipher *ciph, const void *vkey)
+{
+ struct des3_sdctr_ctx *ctx = container_of(
+ ciph, struct des3_sdctr_ctx, ciph);
+ const uint8_t *key = (const uint8_t *)vkey;
+ for (size_t i = 0; i < 3; i++)
+ des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]);
+}
+
+static void des3_sdctr_setiv(ssh_cipher *ciph, const void *viv)
+{
+ struct des3_sdctr_ctx *ctx = container_of(
+ ciph, struct des3_sdctr_ctx, ciph);
+ const uint8_t *iv = (const uint8_t *)viv;
+
+ /* Import the initial counter value into the internal representation */
+ for (unsigned i = 0; i < SDCTR_WORDS; i++)
+ ctx->counter[i] = GET_BIGNUMINT_MSB_FIRST(
+ iv + 8 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES);
+}
+
+static void des3_sdctr_encrypt_decrypt(ssh_cipher *ciph, void *vdata, int len)
+{
+ struct des3_sdctr_ctx *ctx = container_of(
+ ciph, struct des3_sdctr_ctx, ciph);
+ uint8_t *data = (uint8_t *)vdata;
+ uint8_t iv_buf[8];
+ for (; len > 0; len -= 8, data += 8) {
+ /* Format the counter value into the buffer. */
+ for (unsigned i = 0; i < SDCTR_WORDS; i++)
+ PUT_BIGNUMINT_MSB_FIRST(
+ iv_buf + 8 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES,
+ ctx->counter[i]);
+
+ /* Increment the counter. */
+ BignumCarry carry = 1;
+ for (unsigned i = 0; i < SDCTR_WORDS; i++)
+ BignumADC(ctx->counter[i], carry, ctx->counter[i], 0, carry);
+
+ /* Triple-encrypt the counter value from the IV. */
+ LR lr = des_IP(des_load_lr(iv_buf));
+ lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER);
+ lr = des_inner_cipher(lr, &ctx->sched[1], DECIPHER);
+ lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER);
+ LR keystream = des_FP(lr);
+
+ LR input = des_load_lr(data);
+ LR output = des_xor_lr(input, keystream);
+ des_store_lr(data, output);
+ }
+ smemclr(iv_buf, sizeof(iv_buf));
+}
+
+const ssh_cipheralg ssh_3des_ssh2_ctr = {
+ .new = des3_sdctr_new,
+ .free = des3_sdctr_free,
+ .setiv = des3_sdctr_setiv,
+ .setkey = des3_sdctr_setkey,
+ .encrypt = des3_sdctr_encrypt_decrypt,
+ .decrypt = des3_sdctr_encrypt_decrypt,
+ .next_message = nullcipher_next_message,
+ .ssh2_id = "3des-ctr",
+ .blksize = 8,
+ .real_keybits = 168,
+ .padded_keybytes = 24,
+ .flags = 0,
+ .text_name = "triple-DES SDCTR",
+};
+
+static const ssh_cipheralg *const des3_list[] = {
+ &ssh_3des_ssh2_ctr,
+ &ssh_3des_ssh2
+};
+
+const ssh2_ciphers ssh2_3des = { lenof(des3_list), des3_list };
+
+/* ----------------------------------------------------------------------
+ * Triple-DES, SSH-1 style. SSH-1 replicated the whole CBC structure
+ * three times, so there have to be three separate IVs, one in each
+ * layer.
+ */
+
+struct des3_cbc3_ctx {
+ des_keysched sched[3];
+ LR iv[3];
+ ssh_cipher ciph;
+};
+
+static ssh_cipher *des3_cbc3_new(const ssh_cipheralg *alg)
+{
+ struct des3_cbc3_ctx *ctx = snew(struct des3_cbc3_ctx);
+ ctx->ciph.vt = alg;
+ return &ctx->ciph;
+}
+
+static void des3_cbc3_free(ssh_cipher *ciph)
+{
+ struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph);
+ smemclr(ctx, sizeof(*ctx));
+ sfree(ctx);
+}
+
+static void des3_cbc3_setkey(ssh_cipher *ciph, const void *vkey)
+{
+ struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph);
+ const uint8_t *key = (const uint8_t *)vkey;
+ for (size_t i = 0; i < 3; i++)
+ des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]);
+}
+
+static void des3_cbc3_setiv(ssh_cipher *ciph, const void *viv)
+{
+ struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph);
+
+ /*
+ * In principle, we ought to provide an interface for the user to
+ * input 24 instead of 8 bytes of IV. But that would make this an
+ * ugly exception to the otherwise universal rule that IV size =
+ * cipher block size, and there's really no need to violate that
+ * rule given that this is a historical one-off oddity and SSH-1
+ * always initialises all three IVs to zero anyway. So we fudge it
+ * by just setting all the IVs to the same value.
+ */
+
+ LR iv = des_load_lr(viv);
+
+ /* But we store the IVs in permuted form, so that we can handle
+ * all three CBC layers without having to do IP/FP in between. */
+ iv = des_IP(iv);
+ for (size_t i = 0; i < 3; i++)
+ ctx->iv[i] = iv;
+}
+
+static void des3_cbc3_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len)
+{
+ struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph);
+ uint8_t *data = (uint8_t *)vdata;
+ for (; len > 0; len -= 8, data += 8) {
+ /* Load and IP the input. */
+ LR plaintext = des_IP(des_load_lr(data));
+ LR lr = plaintext;
+
+ /* Do three passes of CBC, with the middle one inverted. */
+
+ lr = des_xor_lr(lr, ctx->iv[0]);
+ lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER);
+ ctx->iv[0] = lr;
+
+ LR ciphertext = lr;
+ lr = des_inner_cipher(ciphertext, &ctx->sched[1], DECIPHER);
+ lr = des_xor_lr(lr, ctx->iv[1]);
+ ctx->iv[1] = ciphertext;
+
+ lr = des_xor_lr(lr, ctx->iv[2]);
+ lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER);
+ ctx->iv[2] = lr;
+
+ des_store_lr(data, des_FP(lr));
+ }
+}
+
+static void des3_cbc3_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len)
+{
+ struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph);
+ uint8_t *data = (uint8_t *)vdata;
+ for (; len > 0; len -= 8, data += 8) {
+ /* Load and IP the input */
+ LR lr = des_IP(des_load_lr(data));
+ LR ciphertext;
+
+ /* Do three passes of CBC, with the middle one inverted. */
+ ciphertext = lr;
+ lr = des_inner_cipher(ciphertext, &ctx->sched[2], DECIPHER);
+ lr = des_xor_lr(lr, ctx->iv[2]);
+ ctx->iv[2] = ciphertext;
+
+ lr = des_xor_lr(lr, ctx->iv[1]);
+ lr = des_inner_cipher(lr, &ctx->sched[1], ENCIPHER);
+ ctx->iv[1] = lr;
+
+ ciphertext = lr;
+ lr = des_inner_cipher(ciphertext, &ctx->sched[0], DECIPHER);
+ lr = des_xor_lr(lr, ctx->iv[0]);
+ ctx->iv[0] = ciphertext;
+
+ des_store_lr(data, des_FP(lr));
+ }
+}
+
+const ssh_cipheralg ssh_3des_ssh1 = {
+ .new = des3_cbc3_new,
+ .free = des3_cbc3_free,
+ .setiv = des3_cbc3_setiv,
+ .setkey = des3_cbc3_setkey,
+ .encrypt = des3_cbc3_cbc_encrypt,
+ .decrypt = des3_cbc3_cbc_decrypt,
+ .next_message = nullcipher_next_message,
+ .blksize = 8,
+ .real_keybits = 168,
+ .padded_keybytes = 24,
+ .flags = SSH_CIPHER_IS_CBC,
+ .text_name = "triple-DES inner-CBC",
+};
diff --git a/crypto/diffie-hellman.c b/crypto/diffie-hellman.c
new file mode 100644
index 00000000..4da2d471
--- /dev/null
+++ b/crypto/diffie-hellman.c
@@ -0,0 +1,439 @@
+/*
+ * Diffie-Hellman implementation for PuTTY.
+ */
+
+#include <assert.h>
+
+#include "ssh.h"
+#include "misc.h"
+#include "mpint.h"
+
+struct dh_ctx {
+ mp_int *x, *e, *p, *q, *g;
+};
+
+struct dh_extra {
+ bool gex;
+ void (*construct)(dh_ctx *ctx);
+};
+
+static void dh_group1_construct(dh_ctx *ctx)
+{
+ /* Command to recompute, from the expression in RFC 2412 section E.2:
+spigot -B16 '2^1024 - 2^960 - 1 + 2^64 * ( floor(2^894 pi) + 129093 )'
+ */
+ ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF);
+ ctx->g = mp_from_integer(2);
+}
+
+static void dh_group14_construct(dh_ctx *ctx)
+{
+ /* Command to recompute, from the expression in RFC 3526 section 3:
+spigot -B16 '2^2048 - 2^1984 - 1 + 2^64 * ( floor(2^1918 pi) + 124476 )'
+ */
+ ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF);
+ ctx->g = mp_from_integer(2);
+}
+
+static void dh_group15_construct(dh_ctx *ctx)
+{
+ /* Command to recompute, from the expression in RFC 3526 section 4:
+spigot -B16 '2^3072 - 2^3008 - 1 + 2^64 * ( floor(2^2942 pi) + 1690314 )'
+ */
+ ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF);
+ ctx->g = mp_from_integer(2);
+}
+
+static void dh_group16_construct(dh_ctx *ctx)
+{
+ /* Command to recompute, from the expression in RFC 3526 section 5:
+spigot -B16 '2^4096 - 2^4032 - 1 + 2^64 * ( floor(2^3966 pi) + 240904 )'
+ */
+ ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF);
+ ctx->g = mp_from_integer(2);
+}
+
+static void dh_group17_construct(dh_ctx *ctx)
+{
+ /* Command to recompute, from the expression in RFC 3526 section 6:
+spigot -B16 '2^6144 - 2^6080 - 1 + 2^64 * ( floor(2^6014 pi) + 929484 )'
+ */
+ ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF);
+ ctx->g = mp_from_integer(2);
+}
+
+static void dh_group18_construct(dh_ctx *ctx)
+{
+ /* Command to recompute, from the expression in RFC 3526 section 7:
+spigot -B16 '2^8192 - 2^8128 - 1 + 2^64 * ( floor(2^8062 pi) + 4743158 )'
+ */
+ ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF);
+ ctx->g = mp_from_integer(2);
+}
+
+static const struct dh_extra extra_group1 = {
+ false, dh_group1_construct,
+};
+
+const ssh_kex ssh_diffiehellman_group1_sha1 = {
+ .name = "diffie-hellman-group1-sha1",
+ .groupname = "group1",
+ .main_type = KEXTYPE_DH,
+ .hash = &ssh_sha1,
+ .extra = &extra_group1,
+};
+
+static const ssh_kex *const group1_list[] = {
+ &ssh_diffiehellman_group1_sha1
+};
+
+const ssh_kexes ssh_diffiehellman_group1 = { lenof(group1_list), group1_list };
+
+static const struct dh_extra extra_group18 = {
+ false, dh_group18_construct,
+};
+
+const ssh_kex ssh_diffiehellman_group18_sha512 = {
+ .name = "diffie-hellman-group18-sha512",
+ .groupname = "group18",
+ .main_type = KEXTYPE_DH,
+ .hash = &ssh_sha512,
+ .extra = &extra_group18,
+};
+
+static const ssh_kex *const group18_list[] = {
+ &ssh_diffiehellman_group18_sha512,
+};
+
+const ssh_kexes ssh_diffiehellman_group18 = {
+ lenof(group18_list), group18_list
+};
+
+static const struct dh_extra extra_group17 = {
+ false, dh_group17_construct,
+};
+
+const ssh_kex ssh_diffiehellman_group17_sha512 = {
+ .name = "diffie-hellman-group17-sha512",
+ .groupname = "group17",
+ .main_type = KEXTYPE_DH,
+ .hash = &ssh_sha512,
+ .extra = &extra_group17,
+};
+
+static const ssh_kex *const group17_list[] = {
+ &ssh_diffiehellman_group17_sha512,
+};
+
+const ssh_kexes ssh_diffiehellman_group17 = {
+ lenof(group17_list), group17_list
+};
+
+static const struct dh_extra extra_group16 = {
+ false, dh_group16_construct,
+};
+
+const ssh_kex ssh_diffiehellman_group16_sha512 = {
+ .name = "diffie-hellman-group16-sha512",
+ .groupname = "group16",
+ .main_type = KEXTYPE_DH,
+ .hash = &ssh_sha512,
+ .extra = &extra_group16,
+};
+
+static const ssh_kex *const group16_list[] = {
+ &ssh_diffiehellman_group16_sha512,
+};
+
+const ssh_kexes ssh_diffiehellman_group16 = {
+ lenof(group16_list), group16_list
+};
+
+static const struct dh_extra extra_group15 = {
+ false, dh_group15_construct,
+};
+
+const ssh_kex ssh_diffiehellman_group15_sha512 = {
+ .name = "diffie-hellman-group15-sha512",
+ .groupname = "group15",
+ .main_type = KEXTYPE_DH,
+ .hash = &ssh_sha512,
+ .extra = &extra_group15,
+};
+
+static const ssh_kex *const group15_list[] = {
+ &ssh_diffiehellman_group15_sha512,
+};
+
+const ssh_kexes ssh_diffiehellman_group15 = {
+ lenof(group15_list), group15_list
+};
+
+static const struct dh_extra extra_group14 = {
+ false, dh_group14_construct,
+};
+
+const ssh_kex ssh_diffiehellman_group14_sha256 = {
+ .name = "diffie-hellman-group14-sha256",
+ .groupname = "group14",
+ .main_type = KEXTYPE_DH,
+ .hash = &ssh_sha256,
+ .extra = &extra_group14,
+};
+
+const ssh_kex ssh_diffiehellman_group14_sha1 = {
+ .name = "diffie-hellman-group14-sha1",
+ .groupname = "group14",
+ .main_type = KEXTYPE_DH,
+ .hash = &ssh_sha1,
+ .extra = &extra_group14,
+};
+
+static const ssh_kex *const group14_list[] = {
+ &ssh_diffiehellman_group14_sha256,
+ &ssh_diffiehellman_group14_sha1
+};
+
+const ssh_kexes ssh_diffiehellman_group14 = {
+ lenof(group14_list), group14_list
+};
+
+static const struct dh_extra extra_gex = { true };
+
+static const ssh_kex ssh_diffiehellman_gex_sha256 = {
+ .name = "diffie-hellman-group-exchange-sha256",
+ .groupname = NULL,
+ .main_type = KEXTYPE_DH,
+ .hash = &ssh_sha256,
+ .extra = &extra_gex,
+};
+
+static const ssh_kex ssh_diffiehellman_gex_sha1 = {
+ .name = "diffie-hellman-group-exchange-sha1",
+ .groupname = NULL,
+ .main_type = KEXTYPE_DH,
+ .hash = &ssh_sha1,
+ .extra = &extra_gex,
+};
+
+static const ssh_kex *const gex_list[] = {
+ &ssh_diffiehellman_gex_sha256,
+ &ssh_diffiehellman_gex_sha1
+};
+
+const ssh_kexes ssh_diffiehellman_gex = { lenof(gex_list), gex_list };
+
+static const ssh_kex ssh_gssk5_diffiehellman_gex_sha1 = {
+ .name = "gss-gex-sha1-" GSS_KRB5_OID_HASH,
+ .groupname = NULL,
+ .main_type = KEXTYPE_GSS,
+ .hash = &ssh_sha1,
+ .extra = &extra_gex,
+};
+
+static const ssh_kex ssh_gssk5_diffiehellman_group18_sha512 = {
+ .name = "gss-group18-sha512-" GSS_KRB5_OID_HASH,
+ .groupname = "group18",
+ .main_type = KEXTYPE_GSS,
+ .hash = &ssh_sha512,
+ .extra = &extra_group18,
+};
+
+static const ssh_kex ssh_gssk5_diffiehellman_group17_sha512 = {
+ .name = "gss-group17-sha512-" GSS_KRB5_OID_HASH,
+ .groupname = "group17",
+ .main_type = KEXTYPE_GSS,
+ .hash = &ssh_sha512,
+ .extra = &extra_group17,
+};
+
+static const ssh_kex ssh_gssk5_diffiehellman_group16_sha512 = {
+ .name = "gss-group16-sha512-" GSS_KRB5_OID_HASH,
+ .groupname = "group16",
+ .main_type = KEXTYPE_GSS,
+ .hash = &ssh_sha512,
+ .extra = &extra_group16,
+};
+
+static const ssh_kex ssh_gssk5_diffiehellman_group15_sha512 = {
+ .name = "gss-group15-sha512-" GSS_KRB5_OID_HASH,
+ .groupname = "group15",
+ .main_type = KEXTYPE_GSS,
+ .hash = &ssh_sha512,
+ .extra = &extra_group15,
+};
+
+static const ssh_kex ssh_gssk5_diffiehellman_group14_sha256 = {
+ .name = "gss-group14-sha256-" GSS_KRB5_OID_HASH,
+ .groupname = "group14",
+ .main_type = KEXTYPE_GSS,
+ .hash = &ssh_sha256,
+ .extra = &extra_group14,
+};
+
+static const ssh_kex *const gssk5_sha2_kex_list[] = {
+ &ssh_gssk5_diffiehellman_group16_sha512,
+ &ssh_gssk5_diffiehellman_group17_sha512,
+ &ssh_gssk5_diffiehellman_group18_sha512,
+ &ssh_gssk5_diffiehellman_group15_sha512,
+ &ssh_gssk5_diffiehellman_group14_sha256,
+};
+
+const ssh_kexes ssh_gssk5_sha2_kex = {
+ lenof(gssk5_sha2_kex_list), gssk5_sha2_kex_list
+};
+
+static const ssh_kex ssh_gssk5_diffiehellman_group14_sha1 = {
+ .name = "gss-group14-sha1-" GSS_KRB5_OID_HASH,
+ .groupname = "group14",
+ .main_type = KEXTYPE_GSS,
+ .hash = &ssh_sha1,
+ .extra = &extra_group14,
+};
+
+static const ssh_kex ssh_gssk5_diffiehellman_group1_sha1 = {
+ .name = "gss-group1-sha1-" GSS_KRB5_OID_HASH,
+ .groupname = "group1",
+ .main_type = KEXTYPE_GSS,
+ .hash = &ssh_sha1,
+ .extra = &extra_group1,
+};
+
+static const ssh_kex *const gssk5_sha1_kex_list[] = {
+ &ssh_gssk5_diffiehellman_gex_sha1,
+ &ssh_gssk5_diffiehellman_group14_sha1,
+ &ssh_gssk5_diffiehellman_group1_sha1
+};
+
+const ssh_kexes ssh_gssk5_sha1_kex = {
+ lenof(gssk5_sha1_kex_list), gssk5_sha1_kex_list
+};
+
+/*
+ * Common DH initialisation.
+ */
+static void dh_init(dh_ctx *ctx)
+{
+ ctx->q = mp_rshift_fixed(ctx->p, 1);
+ ctx->x = ctx->e = NULL;
+}
+
+bool dh_is_gex(const ssh_kex *kex)
+{
+ const struct dh_extra *extra = (const struct dh_extra *)kex->extra;
+ return extra->gex;
+}
+
+/*
+ * Initialise DH for a standard group.
+ */
+dh_ctx *dh_setup_group(const ssh_kex *kex)
+{
+ const struct dh_extra *extra = (const struct dh_extra *)kex->extra;
+ assert(!extra->gex);
+ dh_ctx *ctx = snew(dh_ctx);
+ extra->construct(ctx);
+ dh_init(ctx);
+ return ctx;
+}
+
+/*
+ * Initialise DH for a server-supplied group.
+ */
+dh_ctx *dh_setup_gex(mp_int *pval, mp_int *gval)
+{
+ dh_ctx *ctx = snew(dh_ctx);
+ ctx->p = mp_copy(pval);
+ ctx->g = mp_copy(gval);
+ dh_init(ctx);
+ return ctx;
+}
+
+/*
+ * Return size of DH modulus p.
+ */
+int dh_modulus_bit_size(const dh_ctx *ctx)
+{
+ return mp_get_nbits(ctx->p);
+}
+
+/*
+ * Clean up and free a context.
+ */
+void dh_cleanup(dh_ctx *ctx)
+{
+ if (ctx->x)
+ mp_free(ctx->x);
+ if (ctx->e)
+ mp_free(ctx->e);
+ if (ctx->p)
+ mp_free(ctx->p);
+ if (ctx->g)
+ mp_free(ctx->g);
+ if (ctx->q)
+ mp_free(ctx->q);
+ sfree(ctx);
+}
+
+/*
+ * DH stage 1: invent a number x between 1 and q, and compute e =
+ * g^x mod p. Return e.
+ */
+mp_int *dh_create_e(dh_ctx *ctx)
+{
+ /*
+ * Lower limit is just 2.
+ */
+ mp_int *lo = mp_from_integer(2);
+
+ /*
+ * Upper limit.
+ */
+ mp_int *hi = mp_copy(ctx->q);
+ mp_sub_integer_into(hi, hi, 1);
+
+ /*
+ * Make a random number in that range.
+ */
+ ctx->x = mp_random_in_range(lo, hi);
+ mp_free(lo);
+ mp_free(hi);
+
+ /*
+ * Now compute e = g^x mod p.
+ */
+ ctx->e = mp_modpow(ctx->g, ctx->x, ctx->p);
+
+ return ctx->e;
+}
+
+/*
+ * DH stage 2-epsilon: given a number f, validate it to ensure it's in
+ * range. (RFC 4253 section 8: "Values of 'e' or 'f' that are not in
+ * the range [1, p-1] MUST NOT be sent or accepted by either side."
+ * Also, we rule out 1 and p-1 too, since that's easy to do and since
+ * they lead to obviously weak keys that even a passive eavesdropper
+ * can figure out.)
+ */
+const char *dh_validate_f(dh_ctx *ctx, mp_int *f)
+{
+ if (!mp_hs_integer(f, 2)) {
+ return "f value received is too small";
+ } else {
+ mp_int *pm1 = mp_copy(ctx->p);
+ mp_sub_integer_into(pm1, pm1, 1);
+ unsigned cmp = mp_cmp_hs(f, pm1);
+ mp_free(pm1);
+ if (cmp)
+ return "f value received is too large";
+ }
+ return NULL;
+}
+
+/*
+ * DH stage 2: given a number f, compute K = f^x mod p.
+ */
+mp_int *dh_find_K(dh_ctx *ctx, mp_int *f)
+{
+ return mp_modpow(f, ctx->x, ctx->p);
+}
diff --git a/crypto/dsa.c b/crypto/dsa.c
new file mode 100644
index 00000000..71fcd94a
--- /dev/null
+++ b/crypto/dsa.c
@@ -0,0 +1,517 @@
+/*
+ * Digital Signature Algorithm implementation for PuTTY.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "ssh.h"
+#include "mpint.h"
+#include "misc.h"
+
+static void dsa_freekey(ssh_key *key); /* forward reference */
+
+static ssh_key *dsa_new_pub(const ssh_keyalg *self, ptrlen data)
+{
+ BinarySource src[1];
+ struct dsa_key *dsa;
+
+ BinarySource_BARE_INIT_PL(src, data);
+ if (!ptrlen_eq_string(get_string(src), "ssh-dss"))
+ return NULL;
+
+ dsa = snew(struct dsa_key);
+ dsa->sshk.vt = &ssh_dsa;
+ dsa->p = get_mp_ssh2(src);
+ dsa->q = get_mp_ssh2(src);
+ dsa->g = get_mp_ssh2(src);
+ dsa->y = get_mp_ssh2(src);
+ dsa->x = NULL;
+
+ if (get_err(src) ||
+ mp_eq_integer(dsa->p, 0) || mp_eq_integer(dsa->q, 0)) {
+ /* Invalid key. */
+ dsa_freekey(&dsa->sshk);
+ return NULL;
+ }
+
+ return &dsa->sshk;
+}
+
+static void dsa_freekey(ssh_key *key)
+{
+ struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+ if (dsa->p)
+ mp_free(dsa->p);
+ if (dsa->q)
+ mp_free(dsa->q);
+ if (dsa->g)
+ mp_free(dsa->g);
+ if (dsa->y)
+ mp_free(dsa->y);
+ if (dsa->x)
+ mp_free(dsa->x);
+ sfree(dsa);
+}
+
+static void append_hex_to_strbuf(strbuf *sb, mp_int *x)
+{
+ if (sb->len > 0)
+ put_byte(sb, ',');
+ put_data(sb, "0x", 2);
+ char *hex = mp_get_hex(x);
+ size_t hexlen = strlen(hex);
+ put_data(sb, hex, hexlen);
+ smemclr(hex, hexlen);
+ sfree(hex);
+}
+
+static char *dsa_cache_str(ssh_key *key)
+{
+ struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+ strbuf *sb = strbuf_new();
+
+ if (!dsa->p) {
+ strbuf_free(sb);
+ return NULL;
+ }
+
+ append_hex_to_strbuf(sb, dsa->p);
+ append_hex_to_strbuf(sb, dsa->q);
+ append_hex_to_strbuf(sb, dsa->g);
+ append_hex_to_strbuf(sb, dsa->y);
+
+ return strbuf_to_str(sb);
+}
+
+static key_components *dsa_components(ssh_key *key)
+{
+ struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+ key_components *kc = key_components_new();
+
+ key_components_add_text(kc, "key_type", "DSA");
+ assert(dsa->p);
+ key_components_add_mp(kc, "p", dsa->p);
+ key_components_add_mp(kc, "q", dsa->q);
+ key_components_add_mp(kc, "g", dsa->g);
+ key_components_add_mp(kc, "public_y", dsa->y);
+ if (dsa->x)
+ key_components_add_mp(kc, "private_x", dsa->x);
+
+ return kc;
+}
+
+static char *dsa_invalid(ssh_key *key, unsigned flags)
+{
+ /* No validity criterion will stop us from using a DSA key at all */
+ return NULL;
+}
+
+static bool dsa_verify(ssh_key *key, ptrlen sig, ptrlen data)
+{
+ struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+ BinarySource src[1];
+ unsigned char hash[20];
+ bool toret;
+
+ if (!dsa->p)
+ return false;
+
+ BinarySource_BARE_INIT_PL(src, sig);
+
+ /*
+ * Commercial SSH (2.0.13) and OpenSSH disagree over the format
+ * of a DSA signature. OpenSSH is in line with RFC 4253:
+ * it uses a string "ssh-dss", followed by a 40-byte string
+ * containing two 160-bit integers end-to-end. Commercial SSH
+ * can't be bothered with the header bit, and considers a DSA
+ * signature blob to be _just_ the 40-byte string containing
+ * the two 160-bit integers. We tell them apart by measuring
+ * the length: length 40 means the commercial-SSH bug, anything
+ * else is assumed to be RFC-compliant.
+ */
+ if (sig.len != 40) { /* bug not present; read admin fields */
+ ptrlen type = get_string(src);
+ sig = get_string(src);
+
+ if (get_err(src) || !ptrlen_eq_string(type, "ssh-dss") ||
+ sig.len != 40)
+ return false;
+ }
+
+ /* Now we're sitting on a 40-byte string for sure. */
+ mp_int *r = mp_from_bytes_be(make_ptrlen(sig.ptr, 20));
+ mp_int *s = mp_from_bytes_be(make_ptrlen((const char *)sig.ptr + 20, 20));
+ if (!r || !s) {
+ if (r)
+ mp_free(r);
+ if (s)
+ mp_free(s);
+ return false;
+ }
+
+ /* Basic sanity checks: 0 < r,s < q */
+ unsigned invalid = 0;
+ invalid |= mp_eq_integer(r, 0);
+ invalid |= mp_eq_integer(s, 0);
+ invalid |= mp_cmp_hs(r, dsa->q);
+ invalid |= mp_cmp_hs(s, dsa->q);
+ if (invalid) {
+ mp_free(r);
+ mp_free(s);
+ return false;
+ }
+
+ /*
+ * Step 1. w <- s^-1 mod q.
+ */
+ mp_int *w = mp_invert(s, dsa->q);
+ if (!w) {
+ mp_free(r);
+ mp_free(s);
+ return false;
+ }
+
+ /*
+ * Step 2. u1 <- SHA(message) * w mod q.
+ */
+ hash_simple(&ssh_sha1, data, hash);
+ mp_int *sha = mp_from_bytes_be(make_ptrlen(hash, 20));
+ mp_int *u1 = mp_modmul(sha, w, dsa->q);
+
+ /*
+ * Step 3. u2 <- r * w mod q.
+ */
+ mp_int *u2 = mp_modmul(r, w, dsa->q);
+
+ /*
+ * Step 4. v <- (g^u1 * y^u2 mod p) mod q.
+ */
+ mp_int *gu1p = mp_modpow(dsa->g, u1, dsa->p);
+ mp_int *yu2p = mp_modpow(dsa->y, u2, dsa->p);
+ mp_int *gu1yu2p = mp_modmul(gu1p, yu2p, dsa->p);
+ mp_int *v = mp_mod(gu1yu2p, dsa->q);
+
+ /*
+ * Step 5. v should now be equal to r.
+ */
+
+ toret = mp_cmp_eq(v, r);
+
+ mp_free(w);
+ mp_free(sha);
+ mp_free(u1);
+ mp_free(u2);
+ mp_free(gu1p);
+ mp_free(yu2p);
+ mp_free(gu1yu2p);
+ mp_free(v);
+ mp_free(r);
+ mp_free(s);
+
+ return toret;
+}
+
+static void dsa_public_blob(ssh_key *key, BinarySink *bs)
+{
+ struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+
+ put_stringz(bs, "ssh-dss");
+ put_mp_ssh2(bs, dsa->p);
+ put_mp_ssh2(bs, dsa->q);
+ put_mp_ssh2(bs, dsa->g);
+ put_mp_ssh2(bs, dsa->y);
+}
+
+static void dsa_private_blob(ssh_key *key, BinarySink *bs)
+{
+ struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+
+ put_mp_ssh2(bs, dsa->x);
+}
+
+static ssh_key *dsa_new_priv(const ssh_keyalg *self, ptrlen pub, ptrlen priv)
+{
+ BinarySource src[1];
+ ssh_key *sshk;
+ struct dsa_key *dsa;
+ ptrlen hash;
+ unsigned char digest[20];
+ mp_int *ytest;
+
+ sshk = dsa_new_pub(self, pub);
+ if (!sshk)
+ return NULL;
+
+ dsa = container_of(sshk, struct dsa_key, sshk);
+ BinarySource_BARE_INIT_PL(src, priv);
+ dsa->x = get_mp_ssh2(src);
+ if (get_err(src)) {
+ dsa_freekey(&dsa->sshk);
+ return NULL;
+ }
+
+ /*
+ * Check the obsolete hash in the old DSA key format.
+ */
+ hash = get_string(src);
+ if (hash.len == 20) {
+ ssh_hash *h = ssh_hash_new(&ssh_sha1);
+ put_mp_ssh2(h, dsa->p);
+ put_mp_ssh2(h, dsa->q);
+ put_mp_ssh2(h, dsa->g);
+ ssh_hash_final(h, digest);
+ if (!smemeq(hash.ptr, digest, 20)) {
+ dsa_freekey(&dsa->sshk);
+ return NULL;
+ }
+ }
+
+ /*
+ * Now ensure g^x mod p really is y.
+ */
+ ytest = mp_modpow(dsa->g, dsa->x, dsa->p);
+ if (!mp_cmp_eq(ytest, dsa->y)) {
+ mp_free(ytest);
+ dsa_freekey(&dsa->sshk);
+ return NULL;
+ }
+ mp_free(ytest);
+
+ return &dsa->sshk;
+}
+
+static ssh_key *dsa_new_priv_openssh(const ssh_keyalg *self,
+ BinarySource *src)
+{
+ struct dsa_key *dsa;
+
+ dsa = snew(struct dsa_key);
+ dsa->sshk.vt = &ssh_dsa;
+
+ dsa->p = get_mp_ssh2(src);
+ dsa->q = get_mp_ssh2(src);
+ dsa->g = get_mp_ssh2(src);
+ dsa->y = get_mp_ssh2(src);
+ dsa->x = get_mp_ssh2(src);
+
+ if (get_err(src) ||
+ mp_eq_integer(dsa->q, 0) || mp_eq_integer(dsa->p, 0)) {
+ /* Invalid key. */
+ dsa_freekey(&dsa->sshk);
+ return NULL;
+ }
+
+ return &dsa->sshk;
+}
+
+static void dsa_openssh_blob(ssh_key *key, BinarySink *bs)
+{
+ struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+
+ put_mp_ssh2(bs, dsa->p);
+ put_mp_ssh2(bs, dsa->q);
+ put_mp_ssh2(bs, dsa->g);
+ put_mp_ssh2(bs, dsa->y);
+ put_mp_ssh2(bs, dsa->x);
+}
+
+static bool dsa_has_private(ssh_key *key)
+{
+ struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+ return dsa->x != NULL;
+}
+
+static int dsa_pubkey_bits(const ssh_keyalg *self, ptrlen pub)
+{
+ ssh_key *sshk;
+ struct dsa_key *dsa;
+ int ret;
+
+ sshk = dsa_new_pub(self, pub);
+ if (!sshk)
+ return -1;
+
+ dsa = container_of(sshk, struct dsa_key, sshk);
+ ret = mp_get_nbits(dsa->p);
+ dsa_freekey(&dsa->sshk);
+
+ return ret;
+}
+
+mp_int *dsa_gen_k(const char *id_string, mp_int *modulus,
+ mp_int *private_key,
+ unsigned char *digest, int digest_len)
+{
+ /*
+ * The basic DSA signing algorithm is:
+ *
+ * - invent a random k between 1 and q-1 (exclusive).
+ * - Compute r = (g^k mod p) mod q.
+ * - Compute s = k^-1 * (hash + x*r) mod q.
+ *
+ * This has the dangerous properties that:
+ *
+ * - if an attacker in possession of the public key _and_ the
+ * signature (for example, the host you just authenticated
+ * to) can guess your k, he can reverse the computation of s
+ * and work out x = r^-1 * (s*k - hash) mod q. That is, he
+ * can deduce the private half of your key, and masquerade
+ * as you for as long as the key is still valid.
+ *
+ * - since r is a function purely of k and the public key, if
+ * the attacker only has a _range of possibilities_ for k
+ * it's easy for him to work through them all and check each
+ * one against r; he'll never be unsure of whether he's got
+ * the right one.
+ *
+ * - if you ever sign two different hashes with the same k, it
+ * will be immediately obvious because the two signatures
+ * will have the same r, and moreover an attacker in
+ * possession of both signatures (and the public key of
+ * course) can compute k = (hash1-hash2) * (s1-s2)^-1 mod q,
+ * and from there deduce x as before.
+ *
+ * - the Bleichenbacher attack on DSA makes use of methods of
+ * generating k which are significantly non-uniformly
+ * distributed; in particular, generating a 160-bit random
+ * number and reducing it mod q is right out.
+ *
+ * For this reason we must be pretty careful about how we
+ * generate our k. Since this code runs on Windows, with no
+ * particularly good system entropy sources, we can't trust our
+ * RNG itself to produce properly unpredictable data. Hence, we
+ * use a totally different scheme instead.
+ *
+ * What we do is to take a SHA-512 (_big_) hash of the private
+ * key x, and then feed this into another SHA-512 hash that
+ * also includes the message hash being signed. That is:
+ *
+ * proto_k = SHA512 ( SHA512(x) || SHA160(message) )
+ *
+ * This number is 512 bits long, so reducing it mod q won't be
+ * noticeably non-uniform. So
+ *
+ * k = proto_k mod q
+ *
+ * This has the interesting property that it's _deterministic_:
+ * signing the same hash twice with the same key yields the
+ * same signature.
+ *
+ * Despite this determinism, it's still not predictable to an
+ * attacker, because in order to repeat the SHA-512
+ * construction that created it, the attacker would have to
+ * know the private key value x - and by assumption he doesn't,
+ * because if he knew that he wouldn't be attacking k!
+ *
+ * (This trick doesn't, _per se_, protect against reuse of k.
+ * Reuse of k is left to chance; all it does is prevent
+ * _excessively high_ chances of reuse of k due to entropy
+ * problems.)
+ *
+ * Thanks to Colin Plumb for the general idea of using x to
+ * ensure k is hard to guess, and to the Cambridge University
+ * Computer Security Group for helping to argue out all the
+ * fine details.
+ */
+ ssh_hash *h;
+ unsigned char digest512[64];
+
+ /*
+ * Hash some identifying text plus x.
+ */
+ h = ssh_hash_new(&ssh_sha512);
+ put_asciz(h, id_string);
+ put_mp_ssh2(h, private_key);
+ ssh_hash_digest(h, digest512);
+
+ /*
+ * Now hash that digest plus the message hash.
+ */
+ ssh_hash_reset(h);
+ put_data(h, digest512, sizeof(digest512));
+ put_data(h, digest, digest_len);
+ ssh_hash_final(h, digest512);
+
+ /*
+ * Now convert the result into a bignum, and coerce it to the
+ * range [2,q), which we do by reducing it mod q-2 and adding 2.
+ */
+ mp_int *modminus2 = mp_copy(modulus);
+ mp_sub_integer_into(modminus2, modminus2, 2);
+ mp_int *proto_k = mp_from_bytes_be(make_ptrlen(digest512, 64));
+ mp_int *k = mp_mod(proto_k, modminus2);
+ mp_free(proto_k);
+ mp_free(modminus2);
+ mp_add_integer_into(k, k, 2);
+
+ smemclr(digest512, sizeof(digest512));
+
+ return k;
+}
+
+static void dsa_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs)
+{
+ struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
+ unsigned char digest[20];
+ int i;
+
+ hash_simple(&ssh_sha1, data, digest);
+
+ mp_int *k = dsa_gen_k("DSA deterministic k generator", dsa->q, dsa->x,
+ digest, sizeof(digest));
+ mp_int *kinv = mp_invert(k, dsa->q); /* k^-1 mod q */
+
+ /*
+ * Now we have k, so just go ahead and compute the signature.
+ */
+ mp_int *gkp = mp_modpow(dsa->g, k, dsa->p); /* g^k mod p */
+ mp_int *r = mp_mod(gkp, dsa->q); /* r = (g^k mod p) mod q */
+ mp_free(gkp);
+
+ mp_int *hash = mp_from_bytes_be(make_ptrlen(digest, 20));
+ mp_int *xr = mp_mul(dsa->x, r);
+ mp_int *hxr = mp_add(xr, hash); /* hash + x*r */
+ mp_int *s = mp_modmul(kinv, hxr, dsa->q); /* s = k^-1 * (hash+x*r) mod q */
+ mp_free(hxr);
+ mp_free(xr);
+ mp_free(kinv);
+ mp_free(k);
+ mp_free(hash);
+
+ put_stringz(bs, "ssh-dss");
+ put_uint32(bs, 40);
+ for (i = 0; i < 20; i++)
+ put_byte(bs, mp_get_byte(r, 19 - i));
+ for (i = 0; i < 20; i++)
+ put_byte(bs, mp_get_byte(s, 19 - i));
+ mp_free(r);
+ mp_free(s);
+}
+
+static char *dsa_alg_desc(const ssh_keyalg *self) { return dupstr("DSA"); }
+
+const ssh_keyalg ssh_dsa = {
+ .new_pub = dsa_new_pub,
+ .new_priv = dsa_new_priv,
+ .new_priv_openssh = dsa_new_priv_openssh,
+ .freekey = dsa_freekey,
+ .invalid = dsa_invalid,
+ .sign = dsa_sign,
+ .verify = dsa_verify,
+ .public_blob = dsa_public_blob,
+ .private_blob = dsa_private_blob,
+ .openssh_blob = dsa_openssh_blob,
+ .has_private = dsa_has_private,
+ .cache_str = dsa_cache_str,
+ .components = dsa_components,
+ .base_key = nullkey_base_key,
+ .pubkey_bits = dsa_pubkey_bits,
+ .supported_flags = nullkey_supported_flags,
+ .alternate_ssh_id = nullkey_alternate_ssh_id,
+ .alg_desc = dsa_alg_desc,
+ .variable_size = nullkey_variable_size_yes,
+ .ssh_id = "ssh-dss",
+ .cache_id = "dss",
+};
diff --git a/crypto/ecc-arithmetic.c b/crypto/ecc-arithmetic.c
new file mode 100644
index 00000000..6a896f92
--- /dev/null
+++ b/crypto/ecc-arithmetic.c
@@ -0,0 +1,1171 @@
+/*
+ * Basic arithmetic for elliptic curves, implementing ecc.h.
+ */
+
+#include <assert.h>
+
+#include "ssh.h"
+#include "mpint.h"
+#include "ecc.h"
+
+/* ----------------------------------------------------------------------
+ * Weierstrass curves.
+ */
+
+struct WeierstrassPoint {
+ /*
+ * Internally, we represent a point using 'Jacobian coordinates',
+ * which are three values X,Y,Z whose relation to the affine
+ * coordinates x,y is that x = X/Z^2 and y = Y/Z^3.
+ *
+ * This allows us to do most of our calculations without having to
+ * take an inverse mod p: every time the obvious affine formulae
+ * would need you to divide by something, you instead multiply it
+ * into the 'denominator' coordinate Z. You only have to actually
+ * take the inverse of Z when you need to get the affine
+ * coordinates back out, which means you do it once after your
+ * entire computation instead of at every intermediate step.
+ *
+ * The point at infinity is represented by setting all three
+ * coordinates to zero.
+ *
+ * These values are also stored in the Montgomery-multiplication
+ * transformed representation.
+ */
+ mp_int *X, *Y, *Z;
+
+ WeierstrassCurve *wc;
+};
+
+struct WeierstrassCurve {
+ /* Prime modulus of the finite field. */
+ mp_int *p;
+
+ /* Persistent Montgomery context for doing arithmetic mod p. */
+ MontyContext *mc;
+
+ /* Modsqrt context for point decompression. NULL if this curve was
+ * constructed without providing nonsquare_mod_p. */
+ ModsqrtContext *sc;
+
+ /* Parameters of the curve, in Montgomery-multiplication
+ * transformed form. */
+ mp_int *a, *b;
+};
+
+WeierstrassCurve *ecc_weierstrass_curve(
+ mp_int *p, mp_int *a, mp_int *b, mp_int *nonsquare_mod_p)
+{
+ WeierstrassCurve *wc = snew(WeierstrassCurve);
+ wc->p = mp_copy(p);
+ wc->mc = monty_new(p);
+ wc->a = monty_import(wc->mc, a);
+ wc->b = monty_import(wc->mc, b);
+
+ if (nonsquare_mod_p)
+ wc->sc = modsqrt_new(p, nonsquare_mod_p);
+ else
+ wc->sc = NULL;
+
+ return wc;
+}
+
+void ecc_weierstrass_curve_free(WeierstrassCurve *wc)
+{
+ mp_free(wc->p);
+ mp_free(wc->a);
+ mp_free(wc->b);
+ monty_free(wc->mc);
+ if (wc->sc)
+ modsqrt_free(wc->sc);
+ sfree(wc);
+}
+
+static WeierstrassPoint *ecc_weierstrass_point_new_empty(WeierstrassCurve *wc)
+{
+ WeierstrassPoint *wp = snew(WeierstrassPoint);
+ wp->wc = wc;
+ wp->X = wp->Y = wp->Z = NULL;
+ return wp;
+}
+
+static WeierstrassPoint *ecc_weierstrass_point_new_imported(
+ WeierstrassCurve *wc, mp_int *monty_x, mp_int *monty_y)
+{
+ WeierstrassPoint *wp = ecc_weierstrass_point_new_empty(wc);
+ wp->X = monty_x;
+ wp->Y = monty_y;
+ wp->Z = mp_copy(monty_identity(wc->mc));
+ return wp;
+}
+
+WeierstrassPoint *ecc_weierstrass_point_new(
+ WeierstrassCurve *wc, mp_int *x, mp_int *y)
+{
+ return ecc_weierstrass_point_new_imported(
+ wc, monty_import(wc->mc, x), monty_import(wc->mc, y));
+}
+
+WeierstrassPoint *ecc_weierstrass_point_new_identity(WeierstrassCurve *wc)
+{
+ WeierstrassPoint *wp = ecc_weierstrass_point_new_empty(wc);
+ size_t bits = mp_max_bits(wc->p);
+ wp->X = mp_new(bits);
+ wp->Y = mp_new(bits);
+ wp->Z = mp_new(bits);
+ return wp;
+}
+
+void ecc_weierstrass_point_copy_into(
+ WeierstrassPoint *dest, WeierstrassPoint *src)
+{
+ mp_copy_into(dest->X, src->X);
+ mp_copy_into(dest->Y, src->Y);
+ mp_copy_into(dest->Z, src->Z);
+}
+
+WeierstrassPoint *ecc_weierstrass_point_copy(WeierstrassPoint *orig)
+{
+ WeierstrassPoint *wp = ecc_weierstrass_point_new_empty(orig->wc);
+ wp->X = mp_copy(orig->X);
+ wp->Y = mp_copy(orig->Y);
+ wp->Z = mp_copy(orig->Z);
+ return wp;
+}
+
+void ecc_weierstrass_point_free(WeierstrassPoint *wp)
+{
+ mp_free(wp->X);
+ mp_free(wp->Y);
+ mp_free(wp->Z);
+ smemclr(wp, sizeof(*wp));
+ sfree(wp);
+}
+
+WeierstrassPoint *ecc_weierstrass_point_new_from_x(
+ WeierstrassCurve *wc, mp_int *xorig, unsigned desired_y_parity)
+{
+ assert(wc->sc);
+
+ /*
+ * The curve equation is y^2 = x^3 + ax + b, which is already
+ * conveniently in a form where we can compute the RHS and take
+ * the square root of it to get y.
+ */
+ unsigned success;
+
+ mp_int *x = monty_import(wc->mc, xorig);
+
+ /*
+ * Compute the RHS of the curve equation. We don't need to take
+ * account of z here, because we're constructing the point from
+ * scratch. So it really is just x^3 + ax + b.
+ */
+ mp_int *x2 = monty_mul(wc->mc, x, x);
+ mp_int *x2_plus_a = monty_add(wc->mc, x2, wc->a);
+ mp_int *x3_plus_ax = monty_mul(wc->mc, x2_plus_a, x);
+ mp_int *rhs = monty_add(wc->mc, x3_plus_ax, wc->b);
+ mp_free(x2);
+ mp_free(x2_plus_a);
+ mp_free(x3_plus_ax);
+
+ mp_int *y = monty_modsqrt(wc->sc, rhs, &success);
+ mp_free(rhs);
+
+ if (!success) {
+ /* Failure! x^3+ax+b worked out to be a number that has no
+ * square root mod p. In this situation there's no point in
+ * trying to be time-constant, since the protocol sequence is
+ * going to diverge anyway when we complain to whoever gave us
+ * this bogus value. */
+ mp_free(x);
+ mp_free(y);
+ return NULL;
+ }
+
+ /*
+ * Choose whichever of y and p-y has the specified parity (of its
+ * lowest positive residue mod p).
+ */
+ mp_int *tmp = monty_export(wc->mc, y);
+ unsigned flip = (mp_get_bit(tmp, 0) ^ desired_y_parity) & 1;
+ mp_sub_into(tmp, wc->p, y);
+ mp_select_into(y, y, tmp, flip);
+ mp_free(tmp);
+
+ return ecc_weierstrass_point_new_imported(wc, x, y);
+}
+
+static void ecc_weierstrass_cond_overwrite(
+ WeierstrassPoint *dest, WeierstrassPoint *src, unsigned overwrite)
+{
+ mp_select_into(dest->X, dest->X, src->X, overwrite);
+ mp_select_into(dest->Y, dest->Y, src->Y, overwrite);
+ mp_select_into(dest->Z, dest->Z, src->Z, overwrite);
+}
+
+static void ecc_weierstrass_cond_swap(
+ WeierstrassPoint *P, WeierstrassPoint *Q, unsigned swap)
+{
+ mp_cond_swap(P->X, Q->X, swap);
+ mp_cond_swap(P->Y, Q->Y, swap);
+ mp_cond_swap(P->Z, Q->Z, swap);
+}
+
+/*
+ * Shared code between all three of the basic arithmetic functions:
+ * once we've determined the slope of the line that we're intersecting
+ * the curve with, this takes care of finding the coordinates of the
+ * third intersection point (given the two input x-coordinates and one
+ * of the y-coords) and negating it to generate the output.
+ */
+static inline void ecc_weierstrass_epilogue(
+ mp_int *Px, mp_int *Qx, mp_int *Py, mp_int *common_Z,
+ mp_int *lambda_n, mp_int *lambda_d, WeierstrassPoint *out)
+{
+ WeierstrassCurve *wc = out->wc;
+
+ /* Powers of the numerator and denominator of the slope lambda */
+ mp_int *lambda_n2 = monty_mul(wc->mc, lambda_n, lambda_n);
+ mp_int *lambda_d2 = monty_mul(wc->mc, lambda_d, lambda_d);
+ mp_int *lambda_d3 = monty_mul(wc->mc, lambda_d, lambda_d2);
+
+ /* Make the output x-coordinate */
+ mp_int *xsum = monty_add(wc->mc, Px, Qx);
+ mp_int *lambda_d2_xsum = monty_mul(wc->mc, lambda_d2, xsum);
+ out->X = monty_sub(wc->mc, lambda_n2, lambda_d2_xsum);
+
+ /* Make the output y-coordinate */
+ mp_int *lambda_d2_Px = monty_mul(wc->mc, lambda_d2, Px);
+ mp_int *xdiff = monty_sub(wc->mc, lambda_d2_Px, out->X);
+ mp_int *lambda_n_xdiff = monty_mul(wc->mc, lambda_n, xdiff);
+ mp_int *lambda_d3_Py = monty_mul(wc->mc, lambda_d3, Py);
+ out->Y = monty_sub(wc->mc, lambda_n_xdiff, lambda_d3_Py);
+
+ /* Make the output z-coordinate */
+ out->Z = monty_mul(wc->mc, common_Z, lambda_d);
+
+ mp_free(lambda_n2);
+ mp_free(lambda_d2);
+ mp_free(lambda_d3);
+ mp_free(xsum);
+ mp_free(xdiff);
+ mp_free(lambda_d2_xsum);
+ mp_free(lambda_n_xdiff);
+ mp_free(lambda_d2_Px);
+ mp_free(lambda_d3_Py);
+}
+
+/*
+ * Shared code between add and add_general: put the two input points
+ * over a common denominator, and determine the slope lambda of the
+ * line through both of them. If the points have the same
+ * x-coordinate, then the slope will be returned with a zero
+ * denominator.
+ */
+static inline void ecc_weierstrass_add_prologue(
+ WeierstrassPoint *P, WeierstrassPoint *Q,
+ mp_int **Px, mp_int **Py, mp_int **Qx, mp_int **denom,
+ mp_int **lambda_n, mp_int **lambda_d)
+{
+ WeierstrassCurve *wc = P->wc;
+
+ /* Powers of the points' denominators */
+ mp_int *Pz2 = monty_mul(wc->mc, P->Z, P->Z);
+ mp_int *Pz3 = monty_mul(wc->mc, Pz2, P->Z);
+ mp_int *Qz2 = monty_mul(wc->mc, Q->Z, Q->Z);
+ mp_int *Qz3 = monty_mul(wc->mc, Qz2, Q->Z);
+
+ /* Points' x,y coordinates scaled by the other one's denominator
+ * (raised to the appropriate power) */
+ *Px = monty_mul(wc->mc, P->X, Qz2);
+ *Py = monty_mul(wc->mc, P->Y, Qz3);
+ *Qx = monty_mul(wc->mc, Q->X, Pz2);
+ mp_int *Qy = monty_mul(wc->mc, Q->Y, Pz3);
+
+ /* Common denominator */
+ *denom = monty_mul(wc->mc, P->Z, Q->Z);
+
+ /* Slope of the line through the two points, if P != Q */
+ *lambda_n = monty_sub(wc->mc, Qy, *Py);
+ *lambda_d = monty_sub(wc->mc, *Qx, *Px);
+
+ mp_free(Pz2);
+ mp_free(Pz3);
+ mp_free(Qz2);
+ mp_free(Qz3);
+ mp_free(Qy);
+}
+
+WeierstrassPoint *ecc_weierstrass_add(WeierstrassPoint *P, WeierstrassPoint *Q)
+{
+ WeierstrassCurve *wc = P->wc;
+ assert(Q->wc == wc);
+
+ WeierstrassPoint *S = ecc_weierstrass_point_new_empty(wc);
+
+ mp_int *Px, *Py, *Qx, *denom, *lambda_n, *lambda_d;
+ ecc_weierstrass_add_prologue(
+ P, Q, &Px, &Py, &Qx, &denom, &lambda_n, &lambda_d);
+
+ /* Never expect to have received two mutually inverse inputs, or
+ * two identical ones (which would make this a doubling). In other
+ * words, the two input x-coordinates (after putting over a common
+ * denominator) should never have been equal. */
+ assert(!mp_eq_integer(lambda_n, 0));
+
+ /* Now go to the common epilogue code. */
+ ecc_weierstrass_epilogue(Px, Qx, Py, denom, lambda_n, lambda_d, S);
+
+ mp_free(Px);
+ mp_free(Py);
+ mp_free(Qx);
+ mp_free(denom);
+ mp_free(lambda_n);
+ mp_free(lambda_d);
+
+ return S;
+}
+
+/*
+ * Code to determine the slope of the line you need to intersect with
+ * the curve in the case where you're adding a point to itself. In
+ * this situation you can't just say "the line through both input
+ * points" because that's under-determined; instead, you have to take
+ * the _tangent_ to the curve at the given point, by differentiating
+ * the curve equation y^2=x^3+ax+b to get 2y dy/dx = 3x^2+a.
+ */
+static inline void ecc_weierstrass_tangent_slope(
+ WeierstrassPoint *P, mp_int **lambda_n, mp_int **lambda_d)
+{
+ WeierstrassCurve *wc = P->wc;
+
+ mp_int *X2 = monty_mul(wc->mc, P->X, P->X);
+ mp_int *twoX2 = monty_add(wc->mc, X2, X2);
+ mp_int *threeX2 = monty_add(wc->mc, twoX2, X2);
+ mp_int *Z2 = monty_mul(wc->mc, P->Z, P->Z);
+ mp_int *Z4 = monty_mul(wc->mc, Z2, Z2);
+ mp_int *aZ4 = monty_mul(wc->mc, wc->a, Z4);
+
+ *lambda_n = monty_add(wc->mc, threeX2, aZ4);
+ *lambda_d = monty_add(wc->mc, P->Y, P->Y);
+
+ mp_free(X2);
+ mp_free(twoX2);
+ mp_free(threeX2);
+ mp_free(Z2);
+ mp_free(Z4);
+ mp_free(aZ4);
+}
+
+WeierstrassPoint *ecc_weierstrass_double(WeierstrassPoint *P)
+{
+ WeierstrassCurve *wc = P->wc;
+ WeierstrassPoint *D = ecc_weierstrass_point_new_empty(wc);
+
+ mp_int *lambda_n, *lambda_d;
+ ecc_weierstrass_tangent_slope(P, &lambda_n, &lambda_d);
+ ecc_weierstrass_epilogue(P->X, P->X, P->Y, P->Z, lambda_n, lambda_d, D);
+ mp_free(lambda_n);
+ mp_free(lambda_d);
+
+ return D;
+}
+
+static inline void ecc_weierstrass_select_into(
+ WeierstrassPoint *dest, WeierstrassPoint *P, WeierstrassPoint *Q,
+ unsigned choose_Q)
+{
+ mp_select_into(dest->X, P->X, Q->X, choose_Q);
+ mp_select_into(dest->Y, P->Y, Q->Y, choose_Q);
+ mp_select_into(dest->Z, P->Z, Q->Z, choose_Q);
+}
+
+WeierstrassPoint *ecc_weierstrass_add_general(
+ WeierstrassPoint *P, WeierstrassPoint *Q)
+{
+ WeierstrassCurve *wc = P->wc;
+ assert(Q->wc == wc);
+
+ WeierstrassPoint *S = ecc_weierstrass_point_new_empty(wc);
+
+ /* Parameters for the epilogue, and slope of the line if P != Q */
+ mp_int *Px, *Py, *Qx, *denom, *lambda_n, *lambda_d;
+ ecc_weierstrass_add_prologue(
+ P, Q, &Px, &Py, &Qx, &denom, &lambda_n, &lambda_d);
+
+ /* Slope if P == Q */
+ mp_int *lambda_n_tangent, *lambda_d_tangent;
+ ecc_weierstrass_tangent_slope(P, &lambda_n_tangent, &lambda_d_tangent);
+
+ /* Select between those slopes depending on whether P == Q */
+ unsigned same_x_coord = mp_eq_integer(lambda_d, 0);
+ unsigned same_y_coord = mp_eq_integer(lambda_n, 0);
+ unsigned equality = same_x_coord & same_y_coord;
+ mp_select_into(lambda_n, lambda_n, lambda_n_tangent, equality);
+ mp_select_into(lambda_d, lambda_d, lambda_d_tangent, equality);
+
+ /* Now go to the common code between addition and doubling */
+ ecc_weierstrass_epilogue(Px, Qx, Py, denom, lambda_n, lambda_d, S);
+
+ /* Check for the input identity cases, and overwrite the output if
+ * necessary. */
+ ecc_weierstrass_select_into(S, S, Q, mp_eq_integer(P->Z, 0));
+ ecc_weierstrass_select_into(S, S, P, mp_eq_integer(Q->Z, 0));
+
+ /*
+ * In the case where P == -Q and so the output is the identity,
+ * we'll have calculated lambda_d = 0 and so the output will have
+ * z==0 already. Detect that and use it to normalise the other two
+ * coordinates to zero.
+ */
+ unsigned output_id = mp_eq_integer(S->Z, 0);
+ mp_cond_clear(S->X, output_id);
+ mp_cond_clear(S->Y, output_id);
+
+ mp_free(Px);
+ mp_free(Py);
+ mp_free(Qx);
+ mp_free(denom);
+ mp_free(lambda_n);
+ mp_free(lambda_d);
+ mp_free(lambda_n_tangent);
+ mp_free(lambda_d_tangent);
+
+ return S;
+}
+
+WeierstrassPoint *ecc_weierstrass_multiply(WeierstrassPoint *B, mp_int *n)
+{
+ WeierstrassPoint *two_B = ecc_weierstrass_double(B);
+ WeierstrassPoint *k_B = ecc_weierstrass_point_copy(B);
+ WeierstrassPoint *kplus1_B = ecc_weierstrass_point_copy(two_B);
+
+ /*
+ * This multiply routine more or less follows the shape of the
+ * 'Montgomery ladder' technique that you have to use under the
+ * extra constraint on addition in Montgomery curves, because it
+ * was fresh in my mind and easier to just do it the same way. See
+ * the comment in ecc_montgomery_multiply.
+ */
+
+ unsigned not_started_yet = 1;
+ for (size_t bitindex = mp_max_bits(n); bitindex-- > 0 ;) {
+ unsigned nbit = mp_get_bit(n, bitindex);
+
+ WeierstrassPoint *sum = ecc_weierstrass_add(k_B, kplus1_B);
+ ecc_weierstrass_cond_swap(k_B, kplus1_B, nbit);
+ WeierstrassPoint *other = ecc_weierstrass_double(k_B);
+ ecc_weierstrass_point_free(k_B);
+ ecc_weierstrass_point_free(kplus1_B);
+ k_B = other;
+ kplus1_B = sum;
+ ecc_weierstrass_cond_swap(k_B, kplus1_B, nbit);
+
+ ecc_weierstrass_cond_overwrite(k_B, B, not_started_yet);
+ ecc_weierstrass_cond_overwrite(kplus1_B, two_B, not_started_yet);
+ not_started_yet &= ~nbit;
+ }
+
+ ecc_weierstrass_point_free(two_B);
+ ecc_weierstrass_point_free(kplus1_B);
+ return k_B;
+}
+
+unsigned ecc_weierstrass_is_identity(WeierstrassPoint *wp)
+{
+ return mp_eq_integer(wp->Z, 0);
+}
+
+/*
+ * Normalise a point by scaling its Jacobian coordinates so that Z=1.
+ * This doesn't change what point is represented by the triple, but it
+ * means the affine x,y can now be easily recovered from X and Y.
+ */
+static void ecc_weierstrass_normalise(WeierstrassPoint *wp)
+{
+ WeierstrassCurve *wc = wp->wc;
+ mp_int *zinv = monty_invert(wc->mc, wp->Z);
+ mp_int *zinv2 = monty_mul(wc->mc, zinv, zinv);
+ mp_int *zinv3 = monty_mul(wc->mc, zinv2, zinv);
+ monty_mul_into(wc->mc, wp->X, wp->X, zinv2);
+ monty_mul_into(wc->mc, wp->Y, wp->Y, zinv3);
+ monty_mul_into(wc->mc, wp->Z, wp->Z, zinv);
+ mp_free(zinv);
+ mp_free(zinv2);
+ mp_free(zinv3);
+}
+
+void ecc_weierstrass_get_affine(
+ WeierstrassPoint *wp, mp_int **x, mp_int **y)
+{
+ WeierstrassCurve *wc = wp->wc;
+
+ ecc_weierstrass_normalise(wp);
+
+ if (x)
+ *x = monty_export(wc->mc, wp->X);
+ if (y)
+ *y = monty_export(wc->mc, wp->Y);
+}
+
+unsigned ecc_weierstrass_point_valid(WeierstrassPoint *P)
+{
+ WeierstrassCurve *wc = P->wc;
+
+ /*
+ * The projective version of the curve equation is
+ * Y^2 = X^3 + a X Z^4 + b Z^6
+ */
+ mp_int *lhs = monty_mul(P->wc->mc, P->Y, P->Y);
+ mp_int *x2 = monty_mul(wc->mc, P->X, P->X);
+ mp_int *x3 = monty_mul(wc->mc, x2, P->X);
+ mp_int *z2 = monty_mul(wc->mc, P->Z, P->Z);
+ mp_int *z4 = monty_mul(wc->mc, z2, z2);
+ mp_int *az4 = monty_mul(wc->mc, wc->a, z4);
+ mp_int *axz4 = monty_mul(wc->mc, az4, P->X);
+ mp_int *x3_plus_axz4 = monty_add(wc->mc, x3, axz4);
+ mp_int *z6 = monty_mul(wc->mc, z2, z4);
+ mp_int *bz6 = monty_mul(wc->mc, wc->b, z6);
+ mp_int *rhs = monty_add(wc->mc, x3_plus_axz4, bz6);
+
+ unsigned valid = mp_cmp_eq(lhs, rhs);
+
+ mp_free(lhs);
+ mp_free(x2);
+ mp_free(x3);
+ mp_free(z2);
+ mp_free(z4);
+ mp_free(az4);
+ mp_free(axz4);
+ mp_free(x3_plus_axz4);
+ mp_free(z6);
+ mp_free(bz6);
+ mp_free(rhs);
+
+ return valid;
+}
+
+/* ----------------------------------------------------------------------
+ * Montgomery curves.
+ */
+
+struct MontgomeryPoint {
+ /* XZ coordinates. These represent the affine x coordinate by the
+ * relationship x = X/Z. */
+ mp_int *X, *Z;
+
+ MontgomeryCurve *mc;
+};
+
+struct MontgomeryCurve {
+ /* Prime modulus of the finite field. */
+ mp_int *p;
+
+ /* Montgomery context for arithmetic mod p. */
+ MontyContext *mc;
+
+ /* Parameters of the curve, in Montgomery-multiplication
+ * transformed form. */
+ mp_int *a, *b;
+
+ /* (a+2)/4, also in Montgomery-multiplication form. */
+ mp_int *aplus2over4;
+};
+
+MontgomeryCurve *ecc_montgomery_curve(
+ mp_int *p, mp_int *a, mp_int *b)
+{
+ MontgomeryCurve *mc = snew(MontgomeryCurve);
+ mc->p = mp_copy(p);
+ mc->mc = monty_new(p);
+ mc->a = monty_import(mc->mc, a);
+ mc->b = monty_import(mc->mc, b);
+
+ mp_int *four = mp_from_integer(4);
+ mp_int *fourinverse = mp_invert(four, mc->p);
+ mp_int *aplus2 = mp_copy(a);
+ mp_add_integer_into(aplus2, aplus2, 2);
+ mp_int *aplus2over4 = mp_modmul(aplus2, fourinverse, mc->p);
+ mc->aplus2over4 = monty_import(mc->mc, aplus2over4);
+ mp_free(four);
+ mp_free(fourinverse);
+ mp_free(aplus2);
+ mp_free(aplus2over4);
+
+ return mc;
+}
+
+void ecc_montgomery_curve_free(MontgomeryCurve *mc)
+{
+ mp_free(mc->p);
+ mp_free(mc->a);
+ mp_free(mc->b);
+ mp_free(mc->aplus2over4);
+ monty_free(mc->mc);
+ sfree(mc);
+}
+
+static MontgomeryPoint *ecc_montgomery_point_new_empty(MontgomeryCurve *mc)
+{
+ MontgomeryPoint *mp = snew(MontgomeryPoint);
+ mp->mc = mc;
+ mp->X = mp->Z = NULL;
+ return mp;
+}
+
+MontgomeryPoint *ecc_montgomery_point_new(MontgomeryCurve *mc, mp_int *x)
+{
+ MontgomeryPoint *mp = ecc_montgomery_point_new_empty(mc);
+ mp->X = monty_import(mc->mc, x);
+ mp->Z = mp_copy(monty_identity(mc->mc));
+ return mp;
+}
+
+void ecc_montgomery_point_copy_into(
+ MontgomeryPoint *dest, MontgomeryPoint *src)
+{
+ mp_copy_into(dest->X, src->X);
+ mp_copy_into(dest->Z, src->Z);
+}
+
+MontgomeryPoint *ecc_montgomery_point_copy(MontgomeryPoint *orig)
+{
+ MontgomeryPoint *mp = ecc_montgomery_point_new_empty(orig->mc);
+ mp->X = mp_copy(orig->X);
+ mp->Z = mp_copy(orig->Z);
+ return mp;
+}
+
+void ecc_montgomery_point_free(MontgomeryPoint *mp)
+{
+ mp_free(mp->X);
+ mp_free(mp->Z);
+ smemclr(mp, sizeof(*mp));
+ sfree(mp);
+}
+
+static void ecc_montgomery_cond_overwrite(
+ MontgomeryPoint *dest, MontgomeryPoint *src, unsigned overwrite)
+{
+ mp_select_into(dest->X, dest->X, src->X, overwrite);
+ mp_select_into(dest->Z, dest->Z, src->Z, overwrite);
+}
+
+static void ecc_montgomery_cond_swap(
+ MontgomeryPoint *P, MontgomeryPoint *Q, unsigned swap)
+{
+ mp_cond_swap(P->X, Q->X, swap);
+ mp_cond_swap(P->Z, Q->Z, swap);
+}
+
+MontgomeryPoint *ecc_montgomery_diff_add(
+ MontgomeryPoint *P, MontgomeryPoint *Q, MontgomeryPoint *PminusQ)
+{
+ MontgomeryCurve *mc = P->mc;
+ assert(Q->mc == mc);
+ assert(PminusQ->mc == mc);
+
+ /*
+ * Differential addition is achieved using the following formula
+ * that relates the affine x-coordinates of P, Q, P+Q and P-Q:
+ *
+ * x(P+Q) x(P-Q) (x(Q)-x(P))^2 = (x(P)x(Q) - 1)^2
+ *
+ * As with the Weierstrass coordinates, the code below transforms
+ * that affine relation into a projective one to avoid having to
+ * do a division during the main arithmetic.
+ */
+
+ MontgomeryPoint *S = ecc_montgomery_point_new_empty(mc);
+
+ mp_int *Px_m_Pz = monty_sub(mc->mc, P->X, P->Z);
+ mp_int *Px_p_Pz = monty_add(mc->mc, P->X, P->Z);
+ mp_int *Qx_m_Qz = monty_sub(mc->mc, Q->X, Q->Z);
+ mp_int *Qx_p_Qz = monty_add(mc->mc, Q->X, Q->Z);
+ mp_int *PmQp = monty_mul(mc->mc, Px_m_Pz, Qx_p_Qz);
+ mp_int *PpQm = monty_mul(mc->mc, Px_p_Pz, Qx_m_Qz);
+ mp_int *Xpre = monty_add(mc->mc, PmQp, PpQm);
+ mp_int *Zpre = monty_sub(mc->mc, PmQp, PpQm);
+ mp_int *Xpre2 = monty_mul(mc->mc, Xpre, Xpre);
+ mp_int *Zpre2 = monty_mul(mc->mc, Zpre, Zpre);
+ S->X = monty_mul(mc->mc, Xpre2, PminusQ->Z);
+ S->Z = monty_mul(mc->mc, Zpre2, PminusQ->X);
+
+ mp_free(Px_m_Pz);
+ mp_free(Px_p_Pz);
+ mp_free(Qx_m_Qz);
+ mp_free(Qx_p_Qz);
+ mp_free(PmQp);
+ mp_free(PpQm);
+ mp_free(Xpre);
+ mp_free(Zpre);
+ mp_free(Xpre2);
+ mp_free(Zpre2);
+
+ return S;
+}
+
+MontgomeryPoint *ecc_montgomery_double(MontgomeryPoint *P)
+{
+ MontgomeryCurve *mc = P->mc;
+ MontgomeryPoint *D = ecc_montgomery_point_new_empty(mc);
+
+ /*
+ * To double a point in affine coordinates, in principle you can
+ * use the same technique as for Weierstrass: differentiate the
+ * curve equation to get the tangent line at the input point, use
+ * that to get an expression for y which you substitute back into
+ * the curve equation, and subtract the known two roots (in this
+ * case both the same) from the x^2 coefficient of the resulting
+ * cubic.
+ *
+ * In this case, we don't have an input y-coordinate, so you have
+ * to do a bit of extra transformation to find a formula that can
+ * work without it. The tangent formula is (3x^2 + 2ax + 1)/(2y),
+ * and when that appears in the final formula it will be squared -
+ * so we can substitute the y^2 in the denominator for the RHS of
+ * the curve equation. Put together, that gives
+ *
+ * x_out = (x+1)^2 (x-1)^2 / 4(x^3+ax^2+x)
+ *
+ * and, as usual, the code below transforms that into projective
+ * form to avoid the division.
+ */
+
+ mp_int *Px_m_Pz = monty_sub(mc->mc, P->X, P->Z);
+ mp_int *Px_p_Pz = monty_add(mc->mc, P->X, P->Z);
+ mp_int *Px_m_Pz_2 = monty_mul(mc->mc, Px_m_Pz, Px_m_Pz);
+ mp_int *Px_p_Pz_2 = monty_mul(mc->mc, Px_p_Pz, Px_p_Pz);
+ D->X = monty_mul(mc->mc, Px_m_Pz_2, Px_p_Pz_2);
+ mp_int *XZ = monty_mul(mc->mc, P->X, P->Z);
+ mp_int *twoXZ = monty_add(mc->mc, XZ, XZ);
+ mp_int *fourXZ = monty_add(mc->mc, twoXZ, twoXZ);
+ mp_int *fourXZ_scaled = monty_mul(mc->mc, fourXZ, mc->aplus2over4);
+ mp_int *Zpre = monty_add(mc->mc, Px_m_Pz_2, fourXZ_scaled);
+ D->Z = monty_mul(mc->mc, fourXZ, Zpre);
+
+ mp_free(Px_m_Pz);
+ mp_free(Px_p_Pz);
+ mp_free(Px_m_Pz_2);
+ mp_free(Px_p_Pz_2);
+ mp_free(XZ);
+ mp_free(twoXZ);
+ mp_free(fourXZ);
+ mp_free(fourXZ_scaled);
+ mp_free(Zpre);
+
+ return D;
+}
+
+static void ecc_montgomery_normalise(MontgomeryPoint *mp)
+{
+ MontgomeryCurve *mc = mp->mc;
+ mp_int *zinv = monty_invert(mc->mc, mp->Z);
+ monty_mul_into(mc->mc, mp->X, mp->X, zinv);
+ monty_mul_into(mc->mc, mp->Z, mp->Z, zinv);
+ mp_free(zinv);
+}
+
+MontgomeryPoint *ecc_montgomery_multiply(MontgomeryPoint *B, mp_int *n)
+{
+ /*
+ * 'Montgomery ladder' technique, to compute an arbitrary integer
+ * multiple of B under the constraint that you can only add two
+ * unequal points if you also know their difference.
+ *
+ * The setup is that you maintain two curve points one of which is
+ * always the other one plus B. Call them kB and (k+1)B, where k
+ * is some integer that evolves as we go along. We begin by
+ * doubling the input B, to initialise those points to B and 2B,
+ * so that k=1.
+ *
+ * At each stage, we add kB and (k+1)B together - which we can do
+ * under the differential-addition constraint because we know
+ * their difference is always just B - to give us (2k+1)B. Then we
+ * double one of kB or (k+1)B, and depending on which one we
+ * choose, we end up with (2k)B or (2k+2)B. Either way, that
+ * differs by B from the other value we've just computed. So in
+ * each iteration, we do one diff-add and one doubling, plus a
+ * couple of conditional swaps to choose which value we double and
+ * which way round we put the output points, and the effect is to
+ * replace k with either 2k or 2k+1, which we choose based on the
+ * appropriate bit of the desired exponent.
+ *
+ * This routine doesn't assume we know the exact location of the
+ * topmost set bit of the exponent. So to maintain constant time
+ * it does an iteration for every _potential_ bit, starting from
+ * the top downwards; after each iteration in which we haven't
+ * seen a set exponent bit yet, we just overwrite the two points
+ * with B and 2B again,
+ */
+
+ MontgomeryPoint *two_B = ecc_montgomery_double(B);
+ MontgomeryPoint *k_B = ecc_montgomery_point_copy(B);
+ MontgomeryPoint *kplus1_B = ecc_montgomery_point_copy(two_B);
+
+ unsigned not_started_yet = 1;
+ for (size_t bitindex = mp_max_bits(n); bitindex-- > 0 ;) {
+ unsigned nbit = mp_get_bit(n, bitindex);
+
+ MontgomeryPoint *sum = ecc_montgomery_diff_add(k_B, kplus1_B, B);
+ ecc_montgomery_cond_swap(k_B, kplus1_B, nbit);
+ MontgomeryPoint *other = ecc_montgomery_double(k_B);
+ ecc_montgomery_point_free(k_B);
+ ecc_montgomery_point_free(kplus1_B);
+ k_B = other;
+ kplus1_B = sum;
+ ecc_montgomery_cond_swap(k_B, kplus1_B, nbit);
+
+ ecc_montgomery_cond_overwrite(k_B, B, not_started_yet);
+ ecc_montgomery_cond_overwrite(kplus1_B, two_B, not_started_yet);
+ not_started_yet &= ~nbit;
+ }
+
+ ecc_montgomery_point_free(two_B);
+ ecc_montgomery_point_free(kplus1_B);
+ return k_B;
+}
+
+void ecc_montgomery_get_affine(MontgomeryPoint *mp, mp_int **x)
+{
+ MontgomeryCurve *mc = mp->mc;
+
+ ecc_montgomery_normalise(mp);
+
+ if (x)
+ *x = monty_export(mc->mc, mp->X);
+}
+
+unsigned ecc_montgomery_is_identity(MontgomeryPoint *mp)
+{
+ return mp_eq_integer(mp->Z, 0);
+}
+
+/* ----------------------------------------------------------------------
+ * Twisted Edwards curves.
+ */
+
+struct EdwardsPoint {
+ /*
+ * We represent an Edwards curve point in 'extended coordinates'.
+ * There's more than one coordinate system going by that name,
+ * unfortunately. These ones have the semantics that X,Y,Z are
+ * ordinary projective coordinates (so x=X/Z and y=Y/Z), but also,
+ * we store the extra value T = xyZ = XY/Z.
+ */
+ mp_int *X, *Y, *Z, *T;
+
+ EdwardsCurve *ec;
+};
+
+struct EdwardsCurve {
+ /* Prime modulus of the finite field. */
+ mp_int *p;
+
+ /* Montgomery context for arithmetic mod p. */
+ MontyContext *mc;
+
+ /* Modsqrt context for point decompression. */
+ ModsqrtContext *sc;
+
+ /* Parameters of the curve, in Montgomery-multiplication
+ * transformed form. */
+ mp_int *d, *a;
+};
+
+EdwardsCurve *ecc_edwards_curve(mp_int *p, mp_int *d, mp_int *a,
+ mp_int *nonsquare_mod_p)
+{
+ EdwardsCurve *ec = snew(EdwardsCurve);
+ ec->p = mp_copy(p);
+ ec->mc = monty_new(p);
+ ec->d = monty_import(ec->mc, d);
+ ec->a = monty_import(ec->mc, a);
+
+ if (nonsquare_mod_p)
+ ec->sc = modsqrt_new(p, nonsquare_mod_p);
+ else
+ ec->sc = NULL;
+
+ return ec;
+}
+
+void ecc_edwards_curve_free(EdwardsCurve *ec)
+{
+ mp_free(ec->p);
+ mp_free(ec->d);
+ mp_free(ec->a);
+ monty_free(ec->mc);
+ if (ec->sc)
+ modsqrt_free(ec->sc);
+ sfree(ec);
+}
+
+static EdwardsPoint *ecc_edwards_point_new_empty(EdwardsCurve *ec)
+{
+ EdwardsPoint *ep = snew(EdwardsPoint);
+ ep->ec = ec;
+ ep->X = ep->Y = ep->Z = ep->T = NULL;
+ return ep;
+}
+
+static EdwardsPoint *ecc_edwards_point_new_imported(
+ EdwardsCurve *ec, mp_int *monty_x, mp_int *monty_y)
+{
+ EdwardsPoint *ep = ecc_edwards_point_new_empty(ec);
+ ep->X = monty_x;
+ ep->Y = monty_y;
+ ep->T = monty_mul(ec->mc, ep->X, ep->Y);
+ ep->Z = mp_copy(monty_identity(ec->mc));
+ return ep;
+}
+
+EdwardsPoint *ecc_edwards_point_new(
+ EdwardsCurve *ec, mp_int *x, mp_int *y)
+{
+ return ecc_edwards_point_new_imported(
+ ec, monty_import(ec->mc, x), monty_import(ec->mc, y));
+}
+
+void ecc_edwards_point_copy_into(EdwardsPoint *dest, EdwardsPoint *src)
+{
+ mp_copy_into(dest->X, src->X);
+ mp_copy_into(dest->Y, src->Y);
+ mp_copy_into(dest->Z, src->Z);
+ mp_copy_into(dest->T, src->T);
+}
+
+EdwardsPoint *ecc_edwards_point_copy(EdwardsPoint *orig)
+{
+ EdwardsPoint *ep = ecc_edwards_point_new_empty(orig->ec);
+ ep->X = mp_copy(orig->X);
+ ep->Y = mp_copy(orig->Y);
+ ep->Z = mp_copy(orig->Z);
+ ep->T = mp_copy(orig->T);
+ return ep;
+}
+
+void ecc_edwards_point_free(EdwardsPoint *ep)
+{
+ mp_free(ep->X);
+ mp_free(ep->Y);
+ mp_free(ep->Z);
+ mp_free(ep->T);
+ smemclr(ep, sizeof(*ep));
+ sfree(ep);
+}
+
+EdwardsPoint *ecc_edwards_point_new_from_y(
+ EdwardsCurve *ec, mp_int *yorig, unsigned desired_x_parity)
+{
+ assert(ec->sc);
+
+ /*
+ * The curve equation is ax^2 + y^2 = 1 + dx^2y^2, which
+ * rearranges to x^2(dy^2-a) = y^2-1. So we compute
+ * (y^2-1)/(dy^2-a) and take its square root.
+ */
+ unsigned success;
+
+ mp_int *y = monty_import(ec->mc, yorig);
+ mp_int *y2 = monty_mul(ec->mc, y, y);
+ mp_int *dy2 = monty_mul(ec->mc, ec->d, y2);
+ mp_int *dy2ma = monty_sub(ec->mc, dy2, ec->a);
+ mp_int *y2m1 = monty_sub(ec->mc, y2, monty_identity(ec->mc));
+ mp_int *recip_denominator = monty_invert(ec->mc, dy2ma);
+ mp_int *radicand = monty_mul(ec->mc, y2m1, recip_denominator);
+ mp_int *x = monty_modsqrt(ec->sc, radicand, &success);
+ mp_free(y2);
+ mp_free(dy2);
+ mp_free(dy2ma);
+ mp_free(y2m1);
+ mp_free(recip_denominator);
+ mp_free(radicand);
+
+ if (!success) {
+ /* Failure! x^2 worked out to be a number that has no square
+ * root mod p. In this situation there's no point in trying to
+ * be time-constant, since the protocol sequence is going to
+ * diverge anyway when we complain to whoever gave us this
+ * bogus value. */
+ mp_free(x);
+ mp_free(y);
+ return NULL;
+ }
+
+ /*
+ * Choose whichever of x and p-x has the specified parity (of its
+ * lowest positive residue mod p).
+ */
+ mp_int *tmp = monty_export(ec->mc, x);
+ unsigned flip = (mp_get_bit(tmp, 0) ^ desired_x_parity) & 1;
+ mp_sub_into(tmp, ec->p, x);
+ mp_select_into(x, x, tmp, flip);
+ mp_free(tmp);
+
+ return ecc_edwards_point_new_imported(ec, x, y);
+}
+
+static void ecc_edwards_cond_overwrite(
+ EdwardsPoint *dest, EdwardsPoint *src, unsigned overwrite)
+{
+ mp_select_into(dest->X, dest->X, src->X, overwrite);
+ mp_select_into(dest->Y, dest->Y, src->Y, overwrite);
+ mp_select_into(dest->Z, dest->Z, src->Z, overwrite);
+ mp_select_into(dest->T, dest->T, src->T, overwrite);
+}
+
+static void ecc_edwards_cond_swap(
+ EdwardsPoint *P, EdwardsPoint *Q, unsigned swap)
+{
+ mp_cond_swap(P->X, Q->X, swap);
+ mp_cond_swap(P->Y, Q->Y, swap);
+ mp_cond_swap(P->Z, Q->Z, swap);
+ mp_cond_swap(P->T, Q->T, swap);
+}
+
+EdwardsPoint *ecc_edwards_add(EdwardsPoint *P, EdwardsPoint *Q)
+{
+ EdwardsCurve *ec = P->ec;
+ assert(Q->ec == ec);
+
+ EdwardsPoint *S = ecc_edwards_point_new_empty(ec);
+
+ /*
+ * The affine rule for Edwards addition of (x1,y1) and (x2,y2) is
+ *
+ * x_out = (x1 y2 + y1 x2) / (1 + d x1 x2 y1 y2)
+ * y_out = (y1 y2 - a x1 x2) / (1 - d x1 x2 y1 y2)
+ *
+ * The formulae below are listed as 'add-2008-hwcd' in
+ * https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html
+ *
+ * and if you undo the careful optimisation to find out what
+ * they're actually computing, it comes out to
+ *
+ * X_out = (X1 Y2 + Y1 X2) (Z1 Z2 - d T1 T2)
+ * Y_out = (Y1 Y2 - a X1 X2) (Z1 Z2 + d T1 T2)
+ * Z_out = (Z1 Z2 - d T1 T2) (Z1 Z2 + d T1 T2)
+ * T_out = (X1 Y2 + Y1 X2) (Y1 Y2 - a X1 X2)
+ */
+ mp_int *PxQx = monty_mul(ec->mc, P->X, Q->X);
+ mp_int *PyQy = monty_mul(ec->mc, P->Y, Q->Y);
+ mp_int *PtQt = monty_mul(ec->mc, P->T, Q->T);
+ mp_int *PzQz = monty_mul(ec->mc, P->Z, Q->Z);
+ mp_int *Psum = monty_add(ec->mc, P->X, P->Y);
+ mp_int *Qsum = monty_add(ec->mc, Q->X, Q->Y);
+ mp_int *aPxQx = monty_mul(ec->mc, ec->a, PxQx);
+ mp_int *dPtQt = monty_mul(ec->mc, ec->d, PtQt);
+ mp_int *sumprod = monty_mul(ec->mc, Psum, Qsum);
+ mp_int *xx_p_yy = monty_add(ec->mc, PxQx, PyQy);
+ mp_int *E = monty_sub(ec->mc, sumprod, xx_p_yy);
+ mp_int *F = monty_sub(ec->mc, PzQz, dPtQt);
+ mp_int *G = monty_add(ec->mc, PzQz, dPtQt);
+ mp_int *H = monty_sub(ec->mc, PyQy, aPxQx);
+ S->X = monty_mul(ec->mc, E, F);
+ S->Z = monty_mul(ec->mc, F, G);
+ S->Y = monty_mul(ec->mc, G, H);
+ S->T = monty_mul(ec->mc, H, E);
+
+ mp_free(PxQx);
+ mp_free(PyQy);
+ mp_free(PtQt);
+ mp_free(PzQz);
+ mp_free(Psum);
+ mp_free(Qsum);
+ mp_free(aPxQx);
+ mp_free(dPtQt);
+ mp_free(sumprod);
+ mp_free(xx_p_yy);
+ mp_free(E);
+ mp_free(F);
+ mp_free(G);
+ mp_free(H);
+
+ return S;
+}
+
+static void ecc_edwards_normalise(EdwardsPoint *ep)
+{
+ EdwardsCurve *ec = ep->ec;
+ mp_int *zinv = monty_invert(ec->mc, ep->Z);
+ monty_mul_into(ec->mc, ep->X, ep->X, zinv);
+ monty_mul_into(ec->mc, ep->Y, ep->Y, zinv);
+ monty_mul_into(ec->mc, ep->Z, ep->Z, zinv);
+ mp_free(zinv);
+ monty_mul_into(ec->mc, ep->T, ep->X, ep->Y);
+}
+
+EdwardsPoint *ecc_edwards_multiply(EdwardsPoint *B, mp_int *n)
+{
+ EdwardsPoint *two_B = ecc_edwards_add(B, B);
+ EdwardsPoint *k_B = ecc_edwards_point_copy(B);
+ EdwardsPoint *kplus1_B = ecc_edwards_point_copy(two_B);
+
+ /*
+ * Another copy of the same exponentiation routine following the
+ * pattern of the Montgomery ladder, because it works as well as
+ * any other technique and this way I didn't have to debug two of
+ * them.
+ */
+
+ unsigned not_started_yet = 1;
+ for (size_t bitindex = mp_max_bits(n); bitindex-- > 0 ;) {
+ unsigned nbit = mp_get_bit(n, bitindex);
+
+ EdwardsPoint *sum = ecc_edwards_add(k_B, kplus1_B);
+ ecc_edwards_cond_swap(k_B, kplus1_B, nbit);
+ EdwardsPoint *other = ecc_edwards_add(k_B, k_B);
+ ecc_edwards_point_free(k_B);
+ ecc_edwards_point_free(kplus1_B);
+ k_B = other;
+ kplus1_B = sum;
+ ecc_edwards_cond_swap(k_B, kplus1_B, nbit);
+
+ ecc_edwards_cond_overwrite(k_B, B, not_started_yet);
+ ecc_edwards_cond_overwrite(kplus1_B, two_B, not_started_yet);
+ not_started_yet &= ~nbit;
+ }
+
+ ecc_edwards_point_free(two_B);
+ ecc_edwards_point_free(kplus1_B);
+ return k_B;
+}
+
+/*
+ * Helper routine to determine whether two values each given as a pair
+ * of projective coordinates represent the same affine value.
+ */
+static inline unsigned projective_eq(
+ MontyContext *mc, mp_int *An, mp_int *Ad,
+ mp_int *Bn, mp_int *Bd)
+{
+ mp_int *AnBd = monty_mul(mc, An, Bd);
+ mp_int *BnAd = monty_mul(mc, Bn, Ad);
+ unsigned toret = mp_cmp_eq(AnBd, BnAd);
+ mp_free(AnBd);
+ mp_free(BnAd);
+ return toret;
+}
+
+unsigned ecc_edwards_eq(EdwardsPoint *P, EdwardsPoint *Q)
+{
+ EdwardsCurve *ec = P->ec;
+ assert(Q->ec == ec);
+
+ return (projective_eq(ec->mc, P->X, P->Z, Q->X, Q->Z) &
+ projective_eq(ec->mc, P->Y, P->Z, Q->Y, Q->Z));
+}
+
+void ecc_edwards_get_affine(EdwardsPoint *ep, mp_int **x, mp_int **y)
+{
+ EdwardsCurve *ec = ep->ec;
+
+ ecc_edwards_normalise(ep);
+
+ if (x)
+ *x = monty_export(ec->mc, ep->X);
+ if (y)
+ *y = monty_export(ec->mc, ep->Y);
+}
diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c
new file mode 100644
index 00000000..d3197866
--- /dev/null
+++ b/crypto/ecc-ssh.c
@@ -0,0 +1,1809 @@
+/*
+ * Elliptic-curve signing and key exchange for PuTTY's SSH layer.
+ */
+
+/*
+ * References:
+ *
+ * Elliptic curves in SSH are specified in RFC 5656:
+ * https://www.rfc-editor.org/rfc/rfc5656
+ *
+ * That specification delegates details of public key formatting and a
+ * lot of underlying mechanism to SEC 1:
+ * http://www.secg.org/sec1-v2.pdf
+ *
+ * Montgomery maths from:
+ * Handbook of elliptic and hyperelliptic curve cryptography, Chapter 13
+ * http://cs.ucsb.edu/~koc/ccs130h/2013/EllipticHyperelliptic-CohenFrey.pdf
+ *
+ * Curve25519 spec from libssh (with reference to other things in the
+ * libssh code):
+ * https://git.libssh.org/users/aris/libssh.git/tree/doc/curve25519-sha256@libssh.org.txt
+ *
+ * Edwards DSA:
+ * http://ed25519.cr.yp.to/ed25519-20110926.pdf
+ */
+
+#include <stdlib.h>
+#include <assert.h>
+
+#include "ssh.h"
+#include "mpint.h"
+#include "ecc.h"
+
+/* ----------------------------------------------------------------------
+ * Elliptic curve definitions
+ */
+
+static void initialise_common(
+ struct ec_curve *curve, EllipticCurveType type, mp_int *p,
+ unsigned extrabits)
+{
+ curve->type = type;
+ curve->p = mp_copy(p);
+ curve->fieldBits = mp_get_nbits(p);
+ curve->fieldBytes = (curve->fieldBits + extrabits + 7) / 8;
+}
+
+static void initialise_wcurve(
+ struct ec_curve *curve, mp_int *p, mp_int *a, mp_int *b,
+ mp_int *nonsquare, mp_int *G_x, mp_int *G_y, mp_int *G_order)
+{
+ initialise_common(curve, EC_WEIERSTRASS, p, 0);
+
+ curve->w.wc = ecc_weierstrass_curve(p, a, b, nonsquare);
+
+ curve->w.G = ecc_weierstrass_point_new(curve->w.wc, G_x, G_y);
+ curve->w.G_order = mp_copy(G_order);
+}
+
+static void initialise_mcurve(
+ struct ec_curve *curve, mp_int *p, mp_int *a, mp_int *b,
+ mp_int *G_x, unsigned log2_cofactor)
+{
+ initialise_common(curve, EC_MONTGOMERY, p, 0);
+
+ curve->m.mc = ecc_montgomery_curve(p, a, b);
+ curve->m.log2_cofactor = log2_cofactor;
+
+ curve->m.G = ecc_montgomery_point_new(curve->m.mc, G_x);
+}
+
+static void initialise_ecurve(
+ struct ec_curve *curve, mp_int *p, mp_int *d, mp_int *a,
+ mp_int *nonsquare, mp_int *G_x, mp_int *G_y, mp_int *G_order,
+ unsigned log2_cofactor)
+{
+ /* Ensure curve->fieldBytes is long enough to store an extra bit
+ * for a compressed point */
+ initialise_common(curve, EC_EDWARDS, p, 1);
+
+ curve->e.ec = ecc_edwards_curve(p, d, a, nonsquare);
+ curve->e.log2_cofactor = log2_cofactor;
+
+ curve->e.G = ecc_edwards_point_new(curve->e.ec, G_x, G_y);
+ curve->e.G_order = mp_copy(G_order);
+}
+
+static struct ec_curve *ec_p256(void)
+{
+ static struct ec_curve curve = { 0 };
+ static bool initialised = false;
+
+ if (!initialised)
+ {
+ mp_int *p = MP_LITERAL(0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff);
+ mp_int *a = MP_LITERAL(0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc);
+ mp_int *b = MP_LITERAL(0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b);
+ mp_int *G_x = MP_LITERAL(0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296);
+ mp_int *G_y = MP_LITERAL(0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5);
+ mp_int *G_order = MP_LITERAL(0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551);
+ mp_int *nonsquare_mod_p = mp_from_integer(3);
+ initialise_wcurve(&curve, p, a, b, nonsquare_mod_p, G_x, G_y, G_order);
+ mp_free(p);
+ mp_free(a);
+ mp_free(b);
+ mp_free(G_x);
+ mp_free(G_y);
+ mp_free(G_order);
+ mp_free(nonsquare_mod_p);
+
+ curve.textname = curve.name = "nistp256";
+
+ /* Now initialised, no need to do it again */
+ initialised = true;
+ }
+
+ return &curve;
+}
+
+static struct ec_curve *ec_p384(void)
+{
+ static struct ec_curve curve = { 0 };
+ static bool initialised = false;
+
+ if (!initialised)
+ {
+ mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff);
+ mp_int *a = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc);
+ mp_int *b = MP_LITERAL(0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef);
+ mp_int *G_x = MP_LITERAL(0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7);
+ mp_int *G_y = MP_LITERAL(0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f);
+ mp_int *G_order = MP_LITERAL(0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973);
+ mp_int *nonsquare_mod_p = mp_from_integer(19);
+ initialise_wcurve(&curve, p, a, b, nonsquare_mod_p, G_x, G_y, G_order);
+ mp_free(p);
+ mp_free(a);
+ mp_free(b);
+ mp_free(G_x);
+ mp_free(G_y);
+ mp_free(G_order);
+ mp_free(nonsquare_mod_p);
+
+ curve.textname = curve.name = "nistp384";
+
+ /* Now initialised, no need to do it again */
+ initialised = true;
+ }
+
+ return &curve;
+}
+
+static struct ec_curve *ec_p521(void)
+{
+ static struct ec_curve curve = { 0 };
+ static bool initialised = false;
+
+ if (!initialised)
+ {
+ mp_int *p = MP_LITERAL(0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
+ mp_int *a = MP_LITERAL(0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc);
+ mp_int *b = MP_LITERAL(0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00);
+ mp_int *G_x = MP_LITERAL(0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66);
+ mp_int *G_y = MP_LITERAL(0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650);
+ mp_int *G_order = MP_LITERAL(0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409);
+ mp_int *nonsquare_mod_p = mp_from_integer(3);
+ initialise_wcurve(&curve, p, a, b, nonsquare_mod_p, G_x, G_y, G_order);
+ mp_free(p);
+ mp_free(a);
+ mp_free(b);
+ mp_free(G_x);
+ mp_free(G_y);
+ mp_free(G_order);
+ mp_free(nonsquare_mod_p);
+
+ curve.textname = curve.name = "nistp521";
+
+ /* Now initialised, no need to do it again */
+ initialised = true;
+ }
+
+ return &curve;
+}
+
+static struct ec_curve *ec_curve25519(void)
+{
+ static struct ec_curve curve = { 0 };
+ static bool initialised = false;
+
+ if (!initialised)
+ {
+ mp_int *p = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed);
+ mp_int *a = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000076d06);
+ mp_int *b = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000001);
+ mp_int *G_x = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000009);
+ initialise_mcurve(&curve, p, a, b, G_x, 3);
+ mp_free(p);
+ mp_free(a);
+ mp_free(b);
+ mp_free(G_x);
+
+ /* This curve doesn't need a name, because it's never used in
+ * any format that embeds the curve name */
+ curve.name = NULL;
+ curve.textname = "Curve25519";
+
+ /* Now initialised, no need to do it again */
+ initialised = true;
+ }
+
+ return &curve;
+}
+
+static struct ec_curve *ec_curve448(void)
+{
+ static struct ec_curve curve = { 0 };
+ static bool initialised = false;
+
+ if (!initialised)
+ {
+ mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
+ mp_int *a = MP_LITERAL(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000262a6);
+ mp_int *b = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001);
+ mp_int *G_x = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005);
+ initialise_mcurve(&curve, p, a, b, G_x, 2);
+ mp_free(p);
+ mp_free(a);
+ mp_free(b);
+ mp_free(G_x);
+
+ /* This curve doesn't need a name, because it's never used in
+ * any format that embeds the curve name */
+ curve.name = NULL;
+ curve.textname = "Curve448";
+
+ /* Now initialised, no need to do it again */
+ initialised = true;
+ }
+
+ return &curve;
+}
+
+static struct ec_curve *ec_ed25519(void)
+{
+ static struct ec_curve curve = { 0 };
+ static bool initialised = false;
+
+ if (!initialised)
+ {
+ mp_int *p = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed);
+ mp_int *d = MP_LITERAL(0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3);
+ mp_int *a = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec); /* == p-1 */
+ mp_int *G_x = MP_LITERAL(0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a);
+ mp_int *G_y = MP_LITERAL(0x6666666666666666666666666666666666666666666666666666666666666658);
+ mp_int *G_order = MP_LITERAL(0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed);
+ mp_int *nonsquare_mod_p = mp_from_integer(2);
+ initialise_ecurve(&curve, p, d, a, nonsquare_mod_p,
+ G_x, G_y, G_order, 3);
+ mp_free(p);
+ mp_free(d);
+ mp_free(a);
+ mp_free(G_x);
+ mp_free(G_y);
+ mp_free(G_order);
+ mp_free(nonsquare_mod_p);
+
+ /* This curve doesn't need a name, because it's never used in
+ * any format that embeds the curve name */
+ curve.name = NULL;
+
+ curve.textname = "Ed25519";
+
+ /* Now initialised, no need to do it again */
+ initialised = true;
+ }
+
+ return &curve;
+}
+
+static struct ec_curve *ec_ed448(void)
+{
+ static struct ec_curve curve = { 0 };
+ static bool initialised = false;
+
+ if (!initialised)
+ {
+ mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
+ mp_int *d = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffff6756); /* = p - 39081 */
+ mp_int *a = MP_LITERAL(0x1);
+ mp_int *G_x = MP_LITERAL(0x4f1970c66bed0ded221d15a622bf36da9e146570470f1767ea6de324a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e);
+ mp_int *G_y = MP_LITERAL(0x693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e05a0c2d73ad3ff1ce67c39c4fdbd132c4ed7c8ad9808795bf230fa14);
+ mp_int *G_order = MP_LITERAL(0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3);
+ mp_int *nonsquare_mod_p = mp_from_integer(7);
+ initialise_ecurve(&curve, p, d, a, nonsquare_mod_p,
+ G_x, G_y, G_order, 2);
+ mp_free(p);
+ mp_free(d);
+ mp_free(a);
+ mp_free(G_x);
+ mp_free(G_y);
+ mp_free(G_order);
+ mp_free(nonsquare_mod_p);
+
+ /* This curve doesn't need a name, because it's never used in
+ * any format that embeds the curve name */
+ curve.name = NULL;
+
+ curve.textname = "Ed448";
+
+ /* Now initialised, no need to do it again */
+ initialised = true;
+ }
+
+ return &curve;
+}
+
+/* ----------------------------------------------------------------------
+ * Public point from private
+ */
+
+struct ecsign_extra {
+ struct ec_curve *(*curve)(void);
+ const ssh_hashalg *hash;
+
+ /* These fields are used by the OpenSSH PEM format importer/exporter */
+ const unsigned char *oid;
+ int oidlen;
+
+ /* Human-readable algorithm description */
+ const char *alg_desc;
+
+ /* Some EdDSA instances prefix a string to all hash preimages, to
+ * disambiguate which signature variant they're being used with */
+ ptrlen hash_prefix;
+};
+
+WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg)
+{
+ const struct ecsign_extra *extra =
+ (const struct ecsign_extra *)alg->extra;
+ struct ec_curve *curve = extra->curve();
+ assert(curve->type == EC_WEIERSTRASS);
+
+ mp_int *priv_reduced = mp_mod(private_key, curve->p);
+ WeierstrassPoint *toret = ecc_weierstrass_multiply(
+ curve->w.G, priv_reduced);
+ mp_free(priv_reduced);
+ return toret;
+}
+
+static mp_int *eddsa_exponent_from_hash(
+ ptrlen hash, const struct ec_curve *curve)
+{
+ /*
+ * Make an integer out of the hash data, little-endian.
+ */
+ assert(hash.len >= curve->fieldBytes);
+ mp_int *e = mp_from_bytes_le(make_ptrlen(hash.ptr, curve->fieldBytes));
+
+ /*
+ * Set the highest bit that fits in the modulus, and clear any
+ * above that.
+ */
+ mp_set_bit(e, curve->fieldBits - 1, 1);
+ mp_reduce_mod_2to(e, curve->fieldBits);
+
+ /*
+ * Clear a curve-specific number of low bits.
+ */
+ for (unsigned bit = 0; bit < curve->e.log2_cofactor; bit++)
+ mp_set_bit(e, bit, 0);
+
+ return e;
+}
+
+EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg)
+{
+ const struct ecsign_extra *extra =
+ (const struct ecsign_extra *)alg->extra;
+ struct ec_curve *curve = extra->curve();
+ assert(curve->type == EC_EDWARDS);
+
+ ssh_hash *h = ssh_hash_new(extra->hash);
+ for (size_t i = 0; i < curve->fieldBytes; ++i)
+ put_byte(h, mp_get_byte(private_key, i));
+
+ unsigned char hash[MAX_HASH_LEN];
+ ssh_hash_final(h, hash);
+
+ mp_int *exponent = eddsa_exponent_from_hash(
+ make_ptrlen(hash, extra->hash->hlen), curve);
+
+ EdwardsPoint *toret = ecc_edwards_multiply(curve->e.G, exponent);
+ mp_free(exponent);
+
+ return toret;
+}
+
+/* ----------------------------------------------------------------------
+ * Marshalling and unmarshalling functions
+ */
+
+static mp_int *BinarySource_get_mp_le(BinarySource *src)
+{
+ return mp_from_bytes_le(get_string(src));
+}
+#define get_mp_le(src) BinarySource_get_mp_le(BinarySource_UPCAST(src))
+
+static void BinarySink_put_mp_le_fixedlen(BinarySink *bs, mp_int *x,
+ size_t bytes)
+{
+ put_uint32(bs, bytes);
+ for (size_t i = 0; i < bytes; ++i)
+ put_byte(bs, mp_get_byte(x, i));
+}
+#define put_mp_le_fixedlen(bs, x, bytes) \
+ BinarySink_put_mp_le_fixedlen(BinarySink_UPCAST(bs), x, bytes)
+
+static WeierstrassPoint *ecdsa_decode(
+ ptrlen encoded, const struct ec_curve *curve)
+{
+ assert(curve->type == EC_WEIERSTRASS);
+ BinarySource src[1];
+
+ BinarySource_BARE_INIT_PL(src, encoded);
+ unsigned char format_type = get_byte(src);
+
+ WeierstrassPoint *P;
+
+ size_t len = get_avail(src);
+ mp_int *x;
+ mp_int *y;
+
+ switch (format_type) {
+ case 0:
+ /* The identity. */
+ P = ecc_weierstrass_point_new_identity(curve->w.wc);
+ break;
+ case 2:
+ case 3:
+ /* A compressed point, in which the x-coordinate is stored in
+ * full, and y is deduced from that and a single bit
+ * indicating its parity (stored in the format type byte). */
+ x = mp_from_bytes_be(get_data(src, len));
+ P = ecc_weierstrass_point_new_from_x(curve->w.wc, x, format_type & 1);
+ mp_free(x);
+ if (!P) /* this can fail if the input is invalid */
+ return NULL;
+ break;
+ case 4:
+ /* An uncompressed point: the x,y coordinates are stored in
+ * full. We expect the rest of the string to have even length,
+ * and be divided half and half between the two values. */
+ if (len % 2 != 0)
+ return NULL;
+ len /= 2;
+ x = mp_from_bytes_be(get_data(src, len));
+ y = mp_from_bytes_be(get_data(src, len));
+ P = ecc_weierstrass_point_new(curve->w.wc, x, y);
+ mp_free(x);
+ mp_free(y);
+ break;
+ default:
+ /* An unrecognised type byte. */
+ return NULL;
+ }
+
+ /* Verify the point is on the curve */
+ if (!ecc_weierstrass_point_valid(P)) {
+ ecc_weierstrass_point_free(P);
+ return NULL;
+ }
+
+ return P;
+}
+
+static WeierstrassPoint *BinarySource_get_wpoint(
+ BinarySource *src, const struct ec_curve *curve)
+{
+ ptrlen str = get_string(src);
+ if (get_err(src))
+ return NULL;
+ return ecdsa_decode(str, curve);
+}
+#define get_wpoint(src, curve) \
+ BinarySource_get_wpoint(BinarySource_UPCAST(src), curve)
+
+static void BinarySink_put_wpoint(
+ BinarySink *bs, WeierstrassPoint *point, const struct ec_curve *curve,
+ bool bare)
+{
+ strbuf *sb;
+ BinarySink *bs_inner;
+
+ if (!bare) {
+ /*
+ * Encapsulate the raw data inside an outermost string layer.
+ */
+ sb = strbuf_new();
+ bs_inner = BinarySink_UPCAST(sb);
+ } else {
+ /*
+ * Just write the data directly to the output.
+ */
+ bs_inner = bs;
+ }
+
+ if (ecc_weierstrass_is_identity(point)) {
+ put_byte(bs_inner, 0);
+ } else {
+ mp_int *x, *y;
+ ecc_weierstrass_get_affine(point, &x, &y);
+
+ /*
+ * For ECDSA, we only ever output uncompressed points.
+ */
+ put_byte(bs_inner, 0x04);
+ for (size_t i = curve->fieldBytes; i--;)
+ put_byte(bs_inner, mp_get_byte(x, i));
+ for (size_t i = curve->fieldBytes; i--;)
+ put_byte(bs_inner, mp_get_byte(y, i));
+
+ mp_free(x);
+ mp_free(y);
+ }
+
+ if (!bare)
+ put_stringsb(bs, sb);
+}
+#define put_wpoint(bs, point, curve, bare) \
+ BinarySink_put_wpoint(BinarySink_UPCAST(bs), point, curve, bare)
+
+static EdwardsPoint *eddsa_decode(ptrlen encoded, const struct ec_curve *curve)
+{
+ assert(curve->type == EC_EDWARDS);
+
+ mp_int *y = mp_from_bytes_le(encoded);
+
+ /* The topmost bit of the encoding isn't part of y, so it stores
+ * the bottom bit of x. Extract it, and zero that bit in y. */
+ unsigned desired_x_parity = mp_get_bit(y, curve->fieldBytes * 8 - 1);
+ mp_set_bit(y, curve->fieldBytes * 8 - 1, 0);
+
+ /* What's left should now be within the range of the curve's modulus */
+ if (mp_cmp_hs(y, curve->p)) {
+ mp_free(y);
+ return NULL;
+ }
+
+ EdwardsPoint *P = ecc_edwards_point_new_from_y(
+ curve->e.ec, y, desired_x_parity);
+ mp_free(y);
+
+ /* A point constructed in this way will always satisfy the curve
+ * equation, unless ecc-arithmetic.c wasn't able to construct one
+ * at all, in which case P is now NULL. Either way, return it. */
+ return P;
+}
+
+static EdwardsPoint *BinarySource_get_epoint(
+ BinarySource *src, const struct ec_curve *curve)
+{
+ ptrlen str = get_string(src);
+ if (get_err(src))
+ return NULL;
+ return eddsa_decode(str, curve);
+}
+#define get_epoint(src, curve) \
+ BinarySource_get_epoint(BinarySource_UPCAST(src), curve)
+
+static void BinarySink_put_epoint(
+ BinarySink *bs, EdwardsPoint *point, const struct ec_curve *curve,
+ bool bare)
+{
+ mp_int *x, *y;
+ ecc_edwards_get_affine(point, &x, &y);
+
+ assert(curve->fieldBytes >= 2);
+
+ /*
+ * EdDSA requires point compression. We store a single integer,
+ * with bytes in little-endian order, which mostly contains y but
+ * in which the topmost bit is the low bit of x.
+ */
+ if (!bare)
+ put_uint32(bs, curve->fieldBytes); /* string length field */
+ for (size_t i = 0; i < curve->fieldBytes - 1; i++)
+ put_byte(bs, mp_get_byte(y, i));
+ put_byte(bs, (mp_get_byte(y, curve->fieldBytes - 1) & 0x7F) |
+ (mp_get_bit(x, 0) << 7));
+
+ mp_free(x);
+ mp_free(y);
+}
+#define put_epoint(bs, point, curve, bare) \
+ BinarySink_put_epoint(BinarySink_UPCAST(bs), point, curve, bare)
+
+/* ----------------------------------------------------------------------
+ * Exposed ECDSA interface
+ */
+
+static void ecdsa_freekey(ssh_key *key)
+{
+ struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+
+ if (ek->publicKey)
+ ecc_weierstrass_point_free(ek->publicKey);
+ if (ek->privateKey)
+ mp_free(ek->privateKey);
+ sfree(ek);
+}
+
+static void eddsa_freekey(ssh_key *key)
+{
+ struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+
+ if (ek->publicKey)
+ ecc_edwards_point_free(ek->publicKey);
+ if (ek->privateKey)
+ mp_free(ek->privateKey);
+ sfree(ek);
+}
+
+static char *ec_signkey_invalid(ssh_key *key, unsigned flags)
+{
+ /* All validity criteria for both ECDSA and EdDSA were checked
+ * when we loaded the key in the first place */
+ return NULL;
+}
+
+static ssh_key *ecdsa_new_pub(const ssh_keyalg *alg, ptrlen data)
+{
+ const struct ecsign_extra *extra =
+ (const struct ecsign_extra *)alg->extra;
+ struct ec_curve *curve = extra->curve();
+ assert(curve->type == EC_WEIERSTRASS);
+
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, data);
+ get_string(src);
+
+ /* Curve name is duplicated for Weierstrass form */
+ if (!ptrlen_eq_string(get_string(src), curve->name))
+ return NULL;
+
+ struct ecdsa_key *ek = snew(struct ecdsa_key);
+ ek->sshk.vt = alg;
+ ek->curve = curve;
+ ek->privateKey = NULL;
+
+ ek->publicKey = get_wpoint(src, curve);
+ if (!ek->publicKey) {
+ ecdsa_freekey(&ek->sshk);
+ return NULL;
+ }
+
+ return &ek->sshk;
+}
+
+static ssh_key *eddsa_new_pub(const ssh_keyalg *alg, ptrlen data)
+{
+ const struct ecsign_extra *extra =
+ (const struct ecsign_extra *)alg->extra;
+ struct ec_curve *curve = extra->curve();
+ assert(curve->type == EC_EDWARDS);
+
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, data);
+ get_string(src);
+
+ struct eddsa_key *ek = snew(struct eddsa_key);
+ ek->sshk.vt = alg;
+ ek->curve = curve;
+ ek->privateKey = NULL;
+
+ ek->publicKey = get_epoint(src, curve);
+ if (!ek->publicKey) {
+ eddsa_freekey(&ek->sshk);
+ return NULL;
+ }
+
+ return &ek->sshk;
+}
+
+static char *ecc_cache_str_shared(
+ const char *curve_name, mp_int *x, mp_int *y)
+{
+ strbuf *sb = strbuf_new();
+
+ if (curve_name)
+ put_fmt(sb, "%s,", curve_name);
+
+ char *hx = mp_get_hex(x);
+ char *hy = mp_get_hex(y);
+ put_fmt(sb, "0x%s,0x%s", hx, hy);
+ sfree(hx);
+ sfree(hy);
+
+ return strbuf_to_str(sb);
+}
+
+static char *ecdsa_cache_str(ssh_key *key)
+{
+ struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+ mp_int *x, *y;
+
+ ecc_weierstrass_get_affine(ek->publicKey, &x, &y);
+ char *toret = ecc_cache_str_shared(ek->curve->name, x, y);
+ mp_free(x);
+ mp_free(y);
+ return toret;
+}
+
+static key_components *ecdsa_components(ssh_key *key)
+{
+ struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+ key_components *kc = key_components_new();
+
+ key_components_add_text(kc, "key_type", "ECDSA");
+ key_components_add_text(kc, "curve_name", ek->curve->textname);
+
+ mp_int *x, *y;
+ ecc_weierstrass_get_affine(ek->publicKey, &x, &y);
+ key_components_add_mp(kc, "public_affine_x", x);
+ key_components_add_mp(kc, "public_affine_y", y);
+ mp_free(x);
+ mp_free(y);
+
+ if (ek->privateKey)
+ key_components_add_mp(kc, "private_exponent", ek->privateKey);
+
+ return kc;
+}
+
+static char *eddsa_cache_str(ssh_key *key)
+{
+ struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+ mp_int *x, *y;
+
+ ecc_edwards_get_affine(ek->publicKey, &x, &y);
+ char *toret = ecc_cache_str_shared(ek->curve->name, x, y);
+ mp_free(x);
+ mp_free(y);
+ return toret;
+}
+
+static key_components *eddsa_components(ssh_key *key)
+{
+ struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+ key_components *kc = key_components_new();
+
+ key_components_add_text(kc, "key_type", "EdDSA");
+ key_components_add_text(kc, "curve_name", ek->curve->textname);
+
+ mp_int *x, *y;
+ ecc_edwards_get_affine(ek->publicKey, &x, &y);
+ key_components_add_mp(kc, "public_affine_x", x);
+ key_components_add_mp(kc, "public_affine_y", y);
+ mp_free(x);
+ mp_free(y);
+
+ if (ek->privateKey)
+ key_components_add_mp(kc, "private_exponent", ek->privateKey);
+
+ return kc;
+}
+
+static void ecdsa_public_blob(ssh_key *key, BinarySink *bs)
+{
+ struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+
+ put_stringz(bs, ek->sshk.vt->ssh_id);
+ put_stringz(bs, ek->curve->name);
+ put_wpoint(bs, ek->publicKey, ek->curve, false);
+}
+
+static void eddsa_public_blob(ssh_key *key, BinarySink *bs)
+{
+ struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+
+ put_stringz(bs, ek->sshk.vt->ssh_id);
+ put_epoint(bs, ek->publicKey, ek->curve, false);
+}
+
+static void ecdsa_private_blob(ssh_key *key, BinarySink *bs)
+{
+ struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+
+ /* ECDSA uses ordinary SSH-2 mpint format to store the private key */
+ assert(ek->privateKey);
+ put_mp_ssh2(bs, ek->privateKey);
+}
+
+static bool ecdsa_has_private(ssh_key *key)
+{
+ struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+ return ek->privateKey != NULL;
+}
+
+static void eddsa_private_blob(ssh_key *key, BinarySink *bs)
+{
+ struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+
+ /* EdDSA stores the private key integer little-endian and unsigned */
+ assert(ek->privateKey);
+ put_mp_le_fixedlen(bs, ek->privateKey, ek->curve->fieldBytes);
+}
+
+static bool eddsa_has_private(ssh_key *key)
+{
+ struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+ return ek->privateKey != NULL;
+}
+
+static ssh_key *ecdsa_new_priv(const ssh_keyalg *alg, ptrlen pub, ptrlen priv)
+{
+ ssh_key *sshk = ecdsa_new_pub(alg, pub);
+ if (!sshk)
+ return NULL;
+ struct ecdsa_key *ek = container_of(sshk, struct ecdsa_key, sshk);
+
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, priv);
+ ek->privateKey = get_mp_ssh2(src);
+
+ return &ek->sshk;
+}
+
+static ssh_key *eddsa_new_priv(const ssh_keyalg *alg, ptrlen pub, ptrlen priv)
+{
+ ssh_key *sshk = eddsa_new_pub(alg, pub);
+ if (!sshk)
+ return NULL;
+ struct eddsa_key *ek = container_of(sshk, struct eddsa_key, sshk);
+
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, priv);
+ ek->privateKey = get_mp_le(src);
+
+ return &ek->sshk;
+}
+
+static ssh_key *eddsa_new_priv_openssh(
+ const ssh_keyalg *alg, BinarySource *src)
+{
+ const struct ecsign_extra *extra =
+ (const struct ecsign_extra *)alg->extra;
+ struct ec_curve *curve = extra->curve();
+ assert(curve->type == EC_EDWARDS);
+
+ ptrlen pubkey_pl = get_string(src);
+ ptrlen privkey_extended_pl = get_string(src);
+ if (get_err(src) || pubkey_pl.len != curve->fieldBytes)
+ return NULL;
+
+ /*
+ * The OpenSSH format for ed25519 private keys also for some
+ * reason encodes an extra copy of the public key in the second
+ * half of the secret-key string. Check that that's present and
+ * correct as well, otherwise the key we think we've imported
+ * won't behave identically to the way OpenSSH would have treated
+ * it.
+ *
+ * We assume that Ed448 will work the same way, as and when
+ * OpenSSH implements it, which at the time of writing this they
+ * had not.
+ */
+ BinarySource subsrc[1];
+ BinarySource_BARE_INIT_PL(subsrc, privkey_extended_pl);
+ ptrlen privkey_pl = get_data(subsrc, curve->fieldBytes);
+ ptrlen pubkey_copy_pl = get_data(subsrc, curve->fieldBytes);
+ if (get_err(subsrc) || get_avail(subsrc))
+ return NULL;
+ if (!ptrlen_eq_ptrlen(pubkey_pl, pubkey_copy_pl))
+ return NULL;
+
+ struct eddsa_key *ek = snew(struct eddsa_key);
+ ek->sshk.vt = alg;
+ ek->curve = curve;
+ ek->privateKey = NULL;
+
+ ek->publicKey = eddsa_decode(pubkey_pl, curve);
+ if (!ek->publicKey) {
+ eddsa_freekey(&ek->sshk);
+ return NULL;
+ }
+
+ ek->privateKey = mp_from_bytes_le(privkey_pl);
+
+ return &ek->sshk;
+}
+
+static void eddsa_openssh_blob(ssh_key *key, BinarySink *bs)
+{
+ struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+ assert(ek->curve->type == EC_EDWARDS);
+
+ /* Encode the public and private points as strings */
+ strbuf *pub_sb = strbuf_new();
+ put_epoint(pub_sb, ek->publicKey, ek->curve, false);
+ ptrlen pub = make_ptrlen(pub_sb->s + 4, pub_sb->len - 4);
+
+ strbuf *priv_sb = strbuf_new_nm();
+ put_mp_le_fixedlen(priv_sb, ek->privateKey, ek->curve->fieldBytes);
+ ptrlen priv = make_ptrlen(priv_sb->s + 4, priv_sb->len - 4);
+
+ put_stringpl(bs, pub);
+
+ /* Encode the private key as the concatenation of the
+ * little-endian key integer and the public key again */
+ put_uint32(bs, priv.len + pub.len);
+ put_datapl(bs, priv);
+ put_datapl(bs, pub);
+
+ strbuf_free(pub_sb);
+ strbuf_free(priv_sb);
+}
+
+static ssh_key *ecdsa_new_priv_openssh(
+ const ssh_keyalg *alg, BinarySource *src)
+{
+ const struct ecsign_extra *extra =
+ (const struct ecsign_extra *)alg->extra;
+ struct ec_curve *curve = extra->curve();
+ assert(curve->type == EC_WEIERSTRASS);
+
+ get_string(src);
+
+ struct ecdsa_key *ek = snew(struct ecdsa_key);
+ ek->sshk.vt = alg;
+ ek->curve = curve;
+ ek->privateKey = NULL;
+
+ ek->publicKey = get_wpoint(src, curve);
+ if (!ek->publicKey) {
+ ecdsa_freekey(&ek->sshk);
+ return NULL;
+ }
+
+ ek->privateKey = get_mp_ssh2(src);
+
+ return &ek->sshk;
+}
+
+static void ecdsa_openssh_blob(ssh_key *key, BinarySink *bs)
+{
+ struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+ put_stringz(bs, ek->curve->name);
+ put_wpoint(bs, ek->publicKey, ek->curve, false);
+ put_mp_ssh2(bs, ek->privateKey);
+}
+
+static int ec_shared_pubkey_bits(const ssh_keyalg *alg, ptrlen blob)
+{
+ const struct ecsign_extra *extra =
+ (const struct ecsign_extra *)alg->extra;
+ struct ec_curve *curve = extra->curve();
+ return curve->fieldBits;
+}
+
+static mp_int *ecdsa_signing_exponent_from_data(
+ const struct ec_curve *curve, const struct ecsign_extra *extra,
+ ptrlen data)
+{
+ /* Hash the data being signed. */
+ unsigned char hash[MAX_HASH_LEN];
+ ssh_hash *h = ssh_hash_new(extra->hash);
+ put_datapl(h, data);
+ ssh_hash_final(h, hash);
+
+ /*
+ * Take the leftmost b bits of the hash of the signed data (where
+ * b is the number of bits in order(G)), interpreted big-endian.
+ */
+ mp_int *z = mp_from_bytes_be(make_ptrlen(hash, extra->hash->hlen));
+ size_t zbits = mp_get_nbits(z);
+ size_t nbits = mp_get_nbits(curve->w.G_order);
+ size_t shift = zbits - nbits;
+ /* Bound the shift count below at 0, using bit twiddling to avoid
+ * a conditional branch */
+ shift &= ~-(shift >> (CHAR_BIT * sizeof(size_t) - 1));
+ mp_int *toret = mp_rshift_safe(z, shift);
+ mp_free(z);
+
+ return toret;
+}
+
+static bool ecdsa_verify(ssh_key *key, ptrlen sig, ptrlen data)
+{
+ struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+ const struct ecsign_extra *extra =
+ (const struct ecsign_extra *)ek->sshk.vt->extra;
+
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, sig);
+
+ /* Check the signature starts with the algorithm name */
+ if (!ptrlen_eq_string(get_string(src), ek->sshk.vt->ssh_id))
+ return false;
+
+ /* Everything else is nested inside a sub-string. Descend into that. */
+ ptrlen sigstr = get_string(src);
+ if (get_err(src))
+ return false;
+ BinarySource_BARE_INIT_PL(src, sigstr);
+
+ /* Extract the signature integers r,s */
+ mp_int *r = get_mp_ssh2(src);
+ mp_int *s = get_mp_ssh2(src);
+ if (get_err(src)) {
+ mp_free(r);
+ mp_free(s);
+ return false;
+ }
+
+ /* Basic sanity checks: 0 < r,s < order(G) */
+ unsigned invalid = 0;
+ invalid |= mp_eq_integer(r, 0);
+ invalid |= mp_eq_integer(s, 0);
+ invalid |= mp_cmp_hs(r, ek->curve->w.G_order);
+ invalid |= mp_cmp_hs(s, ek->curve->w.G_order);
+
+ /* Get the hash of the signed data, converted to an integer */
+ mp_int *z = ecdsa_signing_exponent_from_data(ek->curve, extra, data);
+
+ /* Verify the signature integers against the hash */
+ mp_int *w = mp_invert(s, ek->curve->w.G_order);
+ mp_int *u1 = mp_modmul(z, w, ek->curve->w.G_order);
+ mp_free(z);
+ mp_int *u2 = mp_modmul(r, w, ek->curve->w.G_order);
+ mp_free(w);
+ WeierstrassPoint *u1G = ecc_weierstrass_multiply(ek->curve->w.G, u1);
+ mp_free(u1);
+ WeierstrassPoint *u2P = ecc_weierstrass_multiply(ek->publicKey, u2);
+ mp_free(u2);
+ WeierstrassPoint *sum = ecc_weierstrass_add_general(u1G, u2P);
+ ecc_weierstrass_point_free(u1G);
+ ecc_weierstrass_point_free(u2P);
+
+ mp_int *x;
+ ecc_weierstrass_get_affine(sum, &x, NULL);
+ ecc_weierstrass_point_free(sum);
+
+ mp_divmod_into(x, ek->curve->w.G_order, NULL, x);
+ invalid |= (1 ^ mp_cmp_eq(r, x));
+ mp_free(x);
+
+ mp_free(r);
+ mp_free(s);
+
+ return !invalid;
+}
+
+static mp_int *eddsa_signing_exponent_from_data(
+ struct eddsa_key *ek, const struct ecsign_extra *extra,
+ ptrlen r_encoded, ptrlen data)
+{
+ /* Hash (r || public key || message) */
+ unsigned char hash[MAX_HASH_LEN];
+ ssh_hash *h = ssh_hash_new(extra->hash);
+ put_datapl(h, extra->hash_prefix);
+ put_datapl(h, r_encoded);
+ put_epoint(h, ek->publicKey, ek->curve, true); /* omit string header */
+ put_datapl(h, data);
+ ssh_hash_final(h, hash);
+
+ /* Convert to an integer */
+ mp_int *toret = mp_from_bytes_le(make_ptrlen(hash, extra->hash->hlen));
+
+ smemclr(hash, extra->hash->hlen);
+ return toret;
+}
+
+static bool eddsa_verify(ssh_key *key, ptrlen sig, ptrlen data)
+{
+ struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+ const struct ecsign_extra *extra =
+ (const struct ecsign_extra *)ek->sshk.vt->extra;
+
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, sig);
+
+ /* Check the signature starts with the algorithm name */
+ if (!ptrlen_eq_string(get_string(src), ek->sshk.vt->ssh_id))
+ return false;
+
+ /* Now expect a single string which is the concatenation of an
+ * encoded curve point r and an integer s. */
+ ptrlen sigstr = get_string(src);
+ if (get_err(src))
+ return false;
+ BinarySource_BARE_INIT_PL(src, sigstr);
+ ptrlen rstr = get_data(src, ek->curve->fieldBytes);
+ ptrlen sstr = get_data(src, ek->curve->fieldBytes);
+ if (get_err(src) || get_avail(src))
+ return false;
+
+ EdwardsPoint *r = eddsa_decode(rstr, ek->curve);
+ if (!r)
+ return false;
+ mp_int *s = mp_from_bytes_le(sstr);
+
+ mp_int *H = eddsa_signing_exponent_from_data(ek, extra, rstr, data);
+
+ /* Verify that s*G == r + H*publicKey */
+ EdwardsPoint *lhs = ecc_edwards_multiply(ek->curve->e.G, s);
+ mp_free(s);
+ EdwardsPoint *hpk = ecc_edwards_multiply(ek->publicKey, H);
+ mp_free(H);
+ EdwardsPoint *rhs = ecc_edwards_add(r, hpk);
+ ecc_edwards_point_free(hpk);
+ unsigned valid = ecc_edwards_eq(lhs, rhs);
+ ecc_edwards_point_free(lhs);
+ ecc_edwards_point_free(rhs);
+ ecc_edwards_point_free(r);
+
+ return valid;
+}
+
+static void ecdsa_sign(ssh_key *key, ptrlen data,
+ unsigned flags, BinarySink *bs)
+{
+ struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk);
+ const struct ecsign_extra *extra =
+ (const struct ecsign_extra *)ek->sshk.vt->extra;
+ assert(ek->privateKey);
+
+ mp_int *z = ecdsa_signing_exponent_from_data(ek->curve, extra, data);
+
+ /* Generate k between 1 and curve->n, using the same deterministic
+ * k generation system we use for conventional DSA. */
+ mp_int *k;
+ {
+ unsigned char digest[20];
+ hash_simple(&ssh_sha1, data, digest);
+ k = dsa_gen_k(
+ "ECDSA deterministic k generator", ek->curve->w.G_order,
+ ek->privateKey, digest, sizeof(digest));
+ }
+
+ WeierstrassPoint *kG = ecc_weierstrass_multiply(ek->curve->w.G, k);
+ mp_int *x;
+ ecc_weierstrass_get_affine(kG, &x, NULL);
+ ecc_weierstrass_point_free(kG);
+
+ /* r = kG.x mod order(G) */
+ mp_int *r = mp_mod(x, ek->curve->w.G_order);
+ mp_free(x);
+
+ /* s = (z + r * priv)/k mod n */
+ mp_int *rPriv = mp_modmul(r, ek->privateKey, ek->curve->w.G_order);
+ mp_int *numerator = mp_modadd(z, rPriv, ek->curve->w.G_order);
+ mp_free(z);
+ mp_free(rPriv);
+ mp_int *kInv = mp_invert(k, ek->curve->w.G_order);
+ mp_free(k);
+ mp_int *s = mp_modmul(numerator, kInv, ek->curve->w.G_order);
+ mp_free(numerator);
+ mp_free(kInv);
+
+ /* Format the output */
+ put_stringz(bs, ek->sshk.vt->ssh_id);
+
+ strbuf *substr = strbuf_new();
+ put_mp_ssh2(substr, r);
+ put_mp_ssh2(substr, s);
+ put_stringsb(bs, substr);
+
+ mp_free(r);
+ mp_free(s);
+}
+
+static void eddsa_sign(ssh_key *key, ptrlen data,
+ unsigned flags, BinarySink *bs)
+{
+ struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk);
+ const struct ecsign_extra *extra =
+ (const struct ecsign_extra *)ek->sshk.vt->extra;
+ assert(ek->privateKey);
+
+ /*
+ * EdDSA prescribes a specific method of generating the random
+ * nonce integer for the signature. (A verifier can't tell
+ * whether you followed that method, but it's important to
+ * follow it anyway, because test vectors will want a specific
+ * signature for a given message, and because this preserves
+ * determinism of signatures even if the same signature were
+ * made twice by different software.)
+ */
+
+ /*
+ * First, we hash the private key integer (bare, little-endian)
+ * into a hash generating 2*fieldBytes of output.
+ */
+ unsigned char hash[MAX_HASH_LEN];
+ ssh_hash *h = ssh_hash_new(extra->hash);
+ for (size_t i = 0; i < ek->curve->fieldBytes; ++i)
+ put_byte(h, mp_get_byte(ek->privateKey, i));
+ ssh_hash_final(h, hash);
+
+ /*
+ * The first half of the output hash is converted into an
+ * integer a, by the standard EdDSA transformation.
+ */
+ mp_int *a = eddsa_exponent_from_hash(
+ make_ptrlen(hash, ek->curve->fieldBytes), ek->curve);
+
+ /*
+ * The second half of the hash of the private key is hashed again
+ * with the message to be signed, and used as an exponent to
+ * generate the signature point r.
+ */
+ h = ssh_hash_new(extra->hash);
+ put_datapl(h, extra->hash_prefix);
+ put_data(h, hash + ek->curve->fieldBytes,
+ extra->hash->hlen - ek->curve->fieldBytes);
+ put_datapl(h, data);
+ ssh_hash_final(h, hash);
+ mp_int *log_r_unreduced = mp_from_bytes_le(
+ make_ptrlen(hash, extra->hash->hlen));
+ mp_int *log_r = mp_mod(log_r_unreduced, ek->curve->e.G_order);
+ mp_free(log_r_unreduced);
+ EdwardsPoint *r = ecc_edwards_multiply(ek->curve->e.G, log_r);
+
+ /*
+ * Encode r now, because we'll need its encoding for the next
+ * hashing step as well as to write into the actual signature.
+ */
+ strbuf *r_enc = strbuf_new();
+ put_epoint(r_enc, r, ek->curve, true); /* omit string header */
+ ecc_edwards_point_free(r);
+
+ /*
+ * Compute the hash of (r || public key || message) just as
+ * eddsa_verify does.
+ */
+ mp_int *H = eddsa_signing_exponent_from_data(
+ ek, extra, ptrlen_from_strbuf(r_enc), data);
+
+ /* And then s = (log(r) + H*a) mod order(G). */
+ mp_int *Ha = mp_modmul(H, a, ek->curve->e.G_order);
+ mp_int *s = mp_modadd(log_r, Ha, ek->curve->e.G_order);
+ mp_free(H);
+ mp_free(a);
+ mp_free(Ha);
+ mp_free(log_r);
+
+ /* Format the output */
+ put_stringz(bs, ek->sshk.vt->ssh_id);
+ put_uint32(bs, r_enc->len + ek->curve->fieldBytes);
+ put_data(bs, r_enc->u, r_enc->len);
+ strbuf_free(r_enc);
+ for (size_t i = 0; i < ek->curve->fieldBytes; ++i)
+ put_byte(bs, mp_get_byte(s, i));
+ mp_free(s);
+}
+
+static char *ec_alg_desc(const ssh_keyalg *self)
+{
+ const struct ecsign_extra *extra =
+ (const struct ecsign_extra *)self->extra;
+ return dupstr(extra->alg_desc);
+}
+
+static const struct ecsign_extra sign_extra_ed25519 = {
+ ec_ed25519, &ssh_sha512,
+ NULL, 0, "Ed25519", PTRLEN_DECL_LITERAL(""),
+};
+const ssh_keyalg ssh_ecdsa_ed25519 = {
+ .new_pub = eddsa_new_pub,
+ .new_priv = eddsa_new_priv,
+ .new_priv_openssh = eddsa_new_priv_openssh,
+ .freekey = eddsa_freekey,
+ .invalid = ec_signkey_invalid,
+ .sign = eddsa_sign,
+ .verify = eddsa_verify,
+ .public_blob = eddsa_public_blob,
+ .private_blob = eddsa_private_blob,
+ .openssh_blob = eddsa_openssh_blob,
+ .has_private = eddsa_has_private,
+ .cache_str = eddsa_cache_str,
+ .components = eddsa_components,
+ .base_key = nullkey_base_key,
+ .pubkey_bits = ec_shared_pubkey_bits,
+ .supported_flags = nullkey_supported_flags,
+ .alternate_ssh_id = nullkey_alternate_ssh_id,
+ .alg_desc = ec_alg_desc,
+ .variable_size = nullkey_variable_size_no,
+ .ssh_id = "ssh-ed25519",
+ .cache_id = "ssh-ed25519",
+ .extra = &sign_extra_ed25519,
+};
+
+static const struct ecsign_extra sign_extra_ed448 = {
+ ec_ed448, &ssh_shake256_114bytes,
+ NULL, 0, "Ed448", PTRLEN_DECL_LITERAL("SigEd448\0\0"),
+};
+const ssh_keyalg ssh_ecdsa_ed448 = {
+ .new_pub = eddsa_new_pub,
+ .new_priv = eddsa_new_priv,
+ .new_priv_openssh = eddsa_new_priv_openssh,
+ .freekey = eddsa_freekey,
+ .invalid = ec_signkey_invalid,
+ .sign = eddsa_sign,
+ .verify = eddsa_verify,
+ .public_blob = eddsa_public_blob,
+ .private_blob = eddsa_private_blob,
+ .openssh_blob = eddsa_openssh_blob,
+ .has_private = eddsa_has_private,
+ .cache_str = eddsa_cache_str,
+ .components = eddsa_components,
+ .base_key = nullkey_base_key,
+ .pubkey_bits = ec_shared_pubkey_bits,
+ .supported_flags = nullkey_supported_flags,
+ .alternate_ssh_id = nullkey_alternate_ssh_id,
+ .alg_desc = ec_alg_desc,
+ .variable_size = nullkey_variable_size_no,
+ .ssh_id = "ssh-ed448",
+ .cache_id = "ssh-ed448",
+ .extra = &sign_extra_ed448,
+};
+
+/* OID: 1.2.840.10045.3.1.7 (ansiX9p256r1) */
+static const unsigned char nistp256_oid[] = {
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07
+};
+static const struct ecsign_extra sign_extra_nistp256 = {
+ ec_p256, &ssh_sha256,
+ nistp256_oid, lenof(nistp256_oid), "NIST p256",
+};
+const ssh_keyalg ssh_ecdsa_nistp256 = {
+ .new_pub = ecdsa_new_pub,
+ .new_priv = ecdsa_new_priv,
+ .new_priv_openssh = ecdsa_new_priv_openssh,
+ .freekey = ecdsa_freekey,
+ .invalid = ec_signkey_invalid,
+ .sign = ecdsa_sign,
+ .verify = ecdsa_verify,
+ .public_blob = ecdsa_public_blob,
+ .private_blob = ecdsa_private_blob,
+ .openssh_blob = ecdsa_openssh_blob,
+ .has_private = ecdsa_has_private,
+ .cache_str = ecdsa_cache_str,
+ .components = ecdsa_components,
+ .base_key = nullkey_base_key,
+ .pubkey_bits = ec_shared_pubkey_bits,
+ .supported_flags = nullkey_supported_flags,
+ .alternate_ssh_id = nullkey_alternate_ssh_id,
+ .alg_desc = ec_alg_desc,
+ .variable_size = nullkey_variable_size_no,
+ .ssh_id = "ecdsa-sha2-nistp256",
+ .cache_id = "ecdsa-sha2-nistp256",
+ .extra = &sign_extra_nistp256,
+};
+
+/* OID: 1.3.132.0.34 (secp384r1) */
+static const unsigned char nistp384_oid[] = {
+ 0x2b, 0x81, 0x04, 0x00, 0x22
+};
+static const struct ecsign_extra sign_extra_nistp384 = {
+ ec_p384, &ssh_sha384,
+ nistp384_oid, lenof(nistp384_oid), "NIST p384",
+};
+const ssh_keyalg ssh_ecdsa_nistp384 = {
+ .new_pub = ecdsa_new_pub,
+ .new_priv = ecdsa_new_priv,
+ .new_priv_openssh = ecdsa_new_priv_openssh,
+ .freekey = ecdsa_freekey,
+ .invalid = ec_signkey_invalid,
+ .sign = ecdsa_sign,
+ .verify = ecdsa_verify,
+ .public_blob = ecdsa_public_blob,
+ .private_blob = ecdsa_private_blob,
+ .openssh_blob = ecdsa_openssh_blob,
+ .has_private = ecdsa_has_private,
+ .cache_str = ecdsa_cache_str,
+ .components = ecdsa_components,
+ .base_key = nullkey_base_key,
+ .pubkey_bits = ec_shared_pubkey_bits,
+ .supported_flags = nullkey_supported_flags,
+ .alternate_ssh_id = nullkey_alternate_ssh_id,
+ .alg_desc = ec_alg_desc,
+ .variable_size = nullkey_variable_size_no,
+ .ssh_id = "ecdsa-sha2-nistp384",
+ .cache_id = "ecdsa-sha2-nistp384",
+ .extra = &sign_extra_nistp384,
+};
+
+/* OID: 1.3.132.0.35 (secp521r1) */
+static const unsigned char nistp521_oid[] = {
+ 0x2b, 0x81, 0x04, 0x00, 0x23
+};
+static const struct ecsign_extra sign_extra_nistp521 = {
+ ec_p521, &ssh_sha512,
+ nistp521_oid, lenof(nistp521_oid), "NIST p521",
+};
+const ssh_keyalg ssh_ecdsa_nistp521 = {
+ .new_pub = ecdsa_new_pub,
+ .new_priv = ecdsa_new_priv,
+ .new_priv_openssh = ecdsa_new_priv_openssh,
+ .freekey = ecdsa_freekey,
+ .invalid = ec_signkey_invalid,
+ .sign = ecdsa_sign,
+ .verify = ecdsa_verify,
+ .public_blob = ecdsa_public_blob,
+ .private_blob = ecdsa_private_blob,
+ .openssh_blob = ecdsa_openssh_blob,
+ .has_private = ecdsa_has_private,
+ .cache_str = ecdsa_cache_str,
+ .components = ecdsa_components,
+ .base_key = nullkey_base_key,
+ .pubkey_bits = ec_shared_pubkey_bits,
+ .supported_flags = nullkey_supported_flags,
+ .alternate_ssh_id = nullkey_alternate_ssh_id,
+ .alg_desc = ec_alg_desc,
+ .variable_size = nullkey_variable_size_no,
+ .ssh_id = "ecdsa-sha2-nistp521",
+ .cache_id = "ecdsa-sha2-nistp521",
+ .extra = &sign_extra_nistp521,
+};
+
+/* ----------------------------------------------------------------------
+ * Exposed ECDH interfaces
+ */
+
+struct eckex_extra {
+ struct ec_curve *(*curve)(void);
+};
+
+typedef struct ecdh_key_w {
+ const struct eckex_extra *extra;
+ const struct ec_curve *curve;
+ mp_int *private;
+ WeierstrassPoint *w_public;
+
+ ecdh_key ek;
+} ecdh_key_w;
+
+typedef struct ecdh_key_m {
+ const struct eckex_extra *extra;
+ const struct ec_curve *curve;
+ mp_int *private;
+ MontgomeryPoint *m_public;
+
+ ecdh_key ek;
+} ecdh_key_m;
+
+static ecdh_key *ssh_ecdhkex_w_new(const ssh_kex *kex, bool is_server)
+{
+ const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra;
+ const struct ec_curve *curve = extra->curve();
+
+ ecdh_key_w *dhw = snew(ecdh_key_w);
+ dhw->ek.vt = kex->ecdh_vt;
+ dhw->extra = extra;
+ dhw->curve = curve;
+
+ mp_int *one = mp_from_integer(1);
+ dhw->private = mp_random_in_range(one, dhw->curve->w.G_order);
+ mp_free(one);
+
+ dhw->w_public = ecc_weierstrass_multiply(dhw->curve->w.G, dhw->private);
+
+ return &dhw->ek;
+}
+
+static ecdh_key *ssh_ecdhkex_m_new(const ssh_kex *kex, bool is_server)
+{
+ const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra;
+ const struct ec_curve *curve = extra->curve();
+
+ ecdh_key_m *dhm = snew(ecdh_key_m);
+ dhm->ek.vt = kex->ecdh_vt;
+ dhm->extra = extra;
+ dhm->curve = curve;
+
+ strbuf *bytes = strbuf_new_nm();
+ random_read(strbuf_append(bytes, dhm->curve->fieldBytes),
+ dhm->curve->fieldBytes);
+
+ dhm->private = mp_from_bytes_le(ptrlen_from_strbuf(bytes));
+
+ /* Ensure the private key has the highest valid bit set, and no
+ * bits _above_ the highest valid one */
+ mp_reduce_mod_2to(dhm->private, dhm->curve->fieldBits);
+ mp_set_bit(dhm->private, dhm->curve->fieldBits - 1, 1);
+
+ /* Clear a curve-specific number of low bits */
+ for (unsigned bit = 0; bit < dhm->curve->m.log2_cofactor; bit++)
+ mp_set_bit(dhm->private, bit, 0);
+
+ strbuf_free(bytes);
+
+ dhm->m_public = ecc_montgomery_multiply(dhm->curve->m.G, dhm->private);
+
+ return &dhm->ek;
+}
+
+static void ssh_ecdhkex_w_getpublic(ecdh_key *dh, BinarySink *bs)
+{
+ ecdh_key_w *dhw = container_of(dh, ecdh_key_w, ek);
+ put_wpoint(bs, dhw->w_public, dhw->curve, true);
+}
+
+static void ssh_ecdhkex_m_getpublic(ecdh_key *dh, BinarySink *bs)
+{
+ ecdh_key_m *dhm = container_of(dh, ecdh_key_m, ek);
+ mp_int *x;
+ ecc_montgomery_get_affine(dhm->m_public, &x);
+ for (size_t i = 0; i < dhm->curve->fieldBytes; ++i)
+ put_byte(bs, mp_get_byte(x, i));
+ mp_free(x);
+}
+
+static bool ssh_ecdhkex_w_getkey(ecdh_key *dh, ptrlen remoteKey,
+ BinarySink *bs)
+{
+ ecdh_key_w *dhw = container_of(dh, ecdh_key_w, ek);
+
+ WeierstrassPoint *remote_p = ecdsa_decode(remoteKey, dhw->curve);
+ if (!remote_p)
+ return false;
+
+ if (ecc_weierstrass_is_identity(remote_p)) {
+ /* Not a sensible Diffie-Hellman input value */
+ ecc_weierstrass_point_free(remote_p);
+ return false;
+ }
+
+ WeierstrassPoint *p = ecc_weierstrass_multiply(remote_p, dhw->private);
+
+ mp_int *x;
+ ecc_weierstrass_get_affine(p, &x, NULL);
+ put_mp_ssh2(bs, x);
+ mp_free(x);
+
+ ecc_weierstrass_point_free(remote_p);
+ ecc_weierstrass_point_free(p);
+
+ return true;
+}
+
+static bool ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey,
+ BinarySink *bs)
+{
+ ecdh_key_m *dhm = container_of(dh, ecdh_key_m, ek);
+
+ mp_int *remote_x = mp_from_bytes_le(remoteKey);
+
+ /* Per RFC 7748 section 5, discard any set bits of the other
+ * side's public value beyond the minimum number of bits required
+ * to represent all valid values. However, an overlarge value that
+ * still fits into the remaining number of bits is accepted, and
+ * will be reduced mod p. */
+ mp_reduce_mod_2to(remote_x, dhm->curve->fieldBits);
+
+ MontgomeryPoint *remote_p = ecc_montgomery_point_new(
+ dhm->curve->m.mc, remote_x);
+ mp_free(remote_x);
+
+ MontgomeryPoint *p = ecc_montgomery_multiply(remote_p, dhm->private);
+
+ if (ecc_montgomery_is_identity(p)) {
+ ecc_montgomery_point_free(remote_p);
+ ecc_montgomery_point_free(p);
+ return false;
+ }
+
+ mp_int *x;
+ ecc_montgomery_get_affine(p, &x);
+
+ ecc_montgomery_point_free(remote_p);
+ ecc_montgomery_point_free(p);
+
+ /*
+ * Endianness-swap. The Curve25519 algorithm definition assumes
+ * you were doing your computation in arrays of 32 little-endian
+ * bytes, and now specifies that you take your final one of those
+ * and convert it into a bignum in _network_ byte order, i.e.
+ * big-endian.
+ *
+ * In particular, the spec says, you convert the _whole_ 32 bytes
+ * into a bignum. That is, on the rare occasions that x has come
+ * out with the most significant 8 bits zero, we have to imagine
+ * that being represented by a 32-byte string with the last byte
+ * being zero, so that has to be converted into an SSH-2 bignum
+ * with the _low_ byte zero, i.e. a multiple of 256.
+ */
+ strbuf *sb = strbuf_new();
+ for (size_t i = 0; i < dhm->curve->fieldBytes; ++i)
+ put_byte(sb, mp_get_byte(x, i));
+ mp_free(x);
+ x = mp_from_bytes_be(ptrlen_from_strbuf(sb));
+ strbuf_free(sb);
+ put_mp_ssh2(bs, x);
+ mp_free(x);
+
+ return true;
+}
+
+static void ssh_ecdhkex_w_free(ecdh_key *dh)
+{
+ ecdh_key_w *dhw = container_of(dh, ecdh_key_w, ek);
+ mp_free(dhw->private);
+ ecc_weierstrass_point_free(dhw->w_public);
+ sfree(dhw);
+}
+
+static void ssh_ecdhkex_m_free(ecdh_key *dh)
+{
+ ecdh_key_m *dhm = container_of(dh, ecdh_key_m, ek);
+ mp_free(dhm->private);
+ ecc_montgomery_point_free(dhm->m_public);
+ sfree(dhm);
+}
+
+static char *ssh_ecdhkex_description(const ssh_kex *kex)
+{
+ const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra;
+ const struct ec_curve *curve = extra->curve();
+ return dupprintf("ECDH key exchange with curve %s", curve->textname);
+}
+
+static const struct eckex_extra kex_extra_curve25519 = { ec_curve25519 };
+
+static const ecdh_keyalg ssh_ecdhkex_m_alg = {
+ .new = ssh_ecdhkex_m_new,
+ .free = ssh_ecdhkex_m_free,
+ .getpublic = ssh_ecdhkex_m_getpublic,
+ .getkey = ssh_ecdhkex_m_getkey,
+ .description = ssh_ecdhkex_description,
+};
+const ssh_kex ssh_ec_kex_curve25519 = {
+ .name = "curve25519-sha256",
+ .main_type = KEXTYPE_ECDH,
+ .hash = &ssh_sha256,
+ .ecdh_vt = &ssh_ecdhkex_m_alg,
+ .extra = &kex_extra_curve25519,
+};
+/* Pre-RFC alias */
+static const ssh_kex ssh_ec_kex_curve25519_libssh = {
+ .name = "curve25519-sha256@libssh.org",
+ .main_type = KEXTYPE_ECDH,
+ .hash = &ssh_sha256,
+ .ecdh_vt = &ssh_ecdhkex_m_alg,
+ .extra = &kex_extra_curve25519,
+};
+/* GSSAPI variant */
+static const ssh_kex ssh_ec_kex_curve25519_gss = {
+ .name = "gss-curve25519-sha256-" GSS_KRB5_OID_HASH,
+ .main_type = KEXTYPE_GSS_ECDH,
+ .hash = &ssh_sha256,
+ .ecdh_vt = &ssh_ecdhkex_m_alg,
+ .extra = &kex_extra_curve25519,
+};
+
+static const struct eckex_extra kex_extra_curve448 = { ec_curve448 };
+const ssh_kex ssh_ec_kex_curve448 = {
+ .name = "curve448-sha512",
+ .main_type = KEXTYPE_ECDH,
+ .hash = &ssh_sha512,
+ .ecdh_vt = &ssh_ecdhkex_m_alg,
+ .extra = &kex_extra_curve448,
+};
+
+static const ecdh_keyalg ssh_ecdhkex_w_alg = {
+ .new = ssh_ecdhkex_w_new,
+ .free = ssh_ecdhkex_w_free,
+ .getpublic = ssh_ecdhkex_w_getpublic,
+ .getkey = ssh_ecdhkex_w_getkey,
+ .description = ssh_ecdhkex_description,
+};
+static const struct eckex_extra kex_extra_nistp256 = { ec_p256 };
+const ssh_kex ssh_ec_kex_nistp256 = {
+ .name = "ecdh-sha2-nistp256",
+ .main_type = KEXTYPE_ECDH,
+ .hash = &ssh_sha256,
+ .ecdh_vt = &ssh_ecdhkex_w_alg,
+ .extra = &kex_extra_nistp256,
+};
+/* GSSAPI variant */
+static const ssh_kex ssh_ec_kex_nistp256_gss = {
+ .name = "gss-nistp256-sha256-" GSS_KRB5_OID_HASH,
+ .main_type = KEXTYPE_GSS_ECDH,
+ .hash = &ssh_sha256,
+ .ecdh_vt = &ssh_ecdhkex_w_alg,
+ .extra = &kex_extra_nistp256,
+};
+
+static const struct eckex_extra kex_extra_nistp384 = { ec_p384 };
+const ssh_kex ssh_ec_kex_nistp384 = {
+ .name = "ecdh-sha2-nistp384",
+ .main_type = KEXTYPE_ECDH,
+ .hash = &ssh_sha384,
+ .ecdh_vt = &ssh_ecdhkex_w_alg,
+ .extra = &kex_extra_nistp384,
+};
+/* GSSAPI variant */
+static const ssh_kex ssh_ec_kex_nistp384_gss = {
+ .name = "gss-nistp384-sha384-" GSS_KRB5_OID_HASH,
+ .main_type = KEXTYPE_GSS_ECDH,
+ .hash = &ssh_sha384,
+ .ecdh_vt = &ssh_ecdhkex_w_alg,
+ .extra = &kex_extra_nistp384,
+};
+
+static const struct eckex_extra kex_extra_nistp521 = { ec_p521 };
+const ssh_kex ssh_ec_kex_nistp521 = {
+ .name = "ecdh-sha2-nistp521",
+ .main_type = KEXTYPE_ECDH,
+ .hash = &ssh_sha512,
+ .ecdh_vt = &ssh_ecdhkex_w_alg,
+ .extra = &kex_extra_nistp521,
+};
+/* GSSAPI variant */
+static const ssh_kex ssh_ec_kex_nistp521_gss = {
+ .name = "gss-nistp521-sha512-" GSS_KRB5_OID_HASH,
+ .main_type = KEXTYPE_GSS_ECDH,
+ .hash = &ssh_sha512,
+ .ecdh_vt = &ssh_ecdhkex_w_alg,
+ .extra = &kex_extra_nistp521,
+};
+
+static const ssh_kex *const ec_kex_list[] = {
+ &ssh_ec_kex_curve448,
+ &ssh_ec_kex_curve25519,
+ &ssh_ec_kex_curve25519_libssh,
+ &ssh_ec_kex_nistp256,
+ &ssh_ec_kex_nistp384,
+ &ssh_ec_kex_nistp521,
+};
+
+const ssh_kexes ssh_ecdh_kex = { lenof(ec_kex_list), ec_kex_list };
+
+static const ssh_kex *const ec_gss_kex_list[] = {
+ &ssh_ec_kex_curve25519_gss,
+ &ssh_ec_kex_nistp521_gss,
+ &ssh_ec_kex_nistp384_gss,
+ &ssh_ec_kex_nistp256_gss,
+};
+
+const ssh_kexes ssh_gssk5_ecdh_kex = {
+ lenof(ec_gss_kex_list), ec_gss_kex_list
+};
+
+/* ----------------------------------------------------------------------
+ * Helper functions for finding key algorithms and returning auxiliary
+ * data.
+ */
+
+const ssh_keyalg *ec_alg_by_oid(int len, const void *oid,
+ const struct ec_curve **curve)
+{
+ static const ssh_keyalg *algs_with_oid[] = {
+ &ssh_ecdsa_nistp256,
+ &ssh_ecdsa_nistp384,
+ &ssh_ecdsa_nistp521,
+ };
+ int i;
+
+ for (i = 0; i < lenof(algs_with_oid); i++) {
+ const ssh_keyalg *alg = algs_with_oid[i];
+ const struct ecsign_extra *extra =
+ (const struct ecsign_extra *)alg->extra;
+ if (len == extra->oidlen && !memcmp(oid, extra->oid, len)) {
+ *curve = extra->curve();
+ return alg;
+ }
+ }
+ return NULL;
+}
+
+const unsigned char *ec_alg_oid(const ssh_keyalg *alg,
+ int *oidlen)
+{
+ const struct ecsign_extra *extra = (const struct ecsign_extra *)alg->extra;
+ *oidlen = extra->oidlen;
+ return extra->oid;
+}
+
+const int ec_nist_curve_lengths[] = { 256, 384, 521 };
+const int n_ec_nist_curve_lengths = lenof(ec_nist_curve_lengths);
+
+const int ec_ed_curve_lengths[] = { 255, 448 };
+const int n_ec_ed_curve_lengths = lenof(ec_ed_curve_lengths);
+
+bool ec_nist_alg_and_curve_by_bits(
+ int bits, const struct ec_curve **curve, const ssh_keyalg **alg)
+{
+ switch (bits) {
+ case 256: *alg = &ssh_ecdsa_nistp256; break;
+ case 384: *alg = &ssh_ecdsa_nistp384; break;
+ case 521: *alg = &ssh_ecdsa_nistp521; break;
+ default: return false;
+ }
+ *curve = ((struct ecsign_extra *)(*alg)->extra)->curve();
+ return true;
+}
+
+bool ec_ed_alg_and_curve_by_bits(
+ int bits, const struct ec_curve **curve, const ssh_keyalg **alg)
+{
+ switch (bits) {
+ case 255: case 256: *alg = &ssh_ecdsa_ed25519; break;
+ case 448: *alg = &ssh_ecdsa_ed448; break;
+ default: return false;
+ }
+ *curve = ((struct ecsign_extra *)(*alg)->extra)->curve();
+ return true;
+}
diff --git a/crypto/ecc.h b/crypto/ecc.h
new file mode 100644
index 00000000..a6115329
--- /dev/null
+++ b/crypto/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 *orig);
+void ecc_weierstrass_point_free(WeierstrassPoint *point);
+
+/* Check whether a point is actually on the curve. */
+unsigned ecc_weierstrass_point_valid(WeierstrassPoint *);
+
+/*
+ * Add two points and return their sum. This function is fully
+ * general: it should do the right thing if the two inputs are the
+ * same, or if either (or both) of the input points is the identity,
+ * or if the two input points are inverses so the output is the
+ * identity. However, it pays for that generality by being slower than
+ * the special-purpose functions below..
+ */
+WeierstrassPoint *ecc_weierstrass_add_general(
+ WeierstrassPoint *, WeierstrassPoint *);
+
+/*
+ * Fast but less general arithmetic functions: add two points on the
+ * condition that they are not equal and neither is the identity, and
+ * add a point to itself.
+ */
+WeierstrassPoint *ecc_weierstrass_add(WeierstrassPoint *, WeierstrassPoint *);
+WeierstrassPoint *ecc_weierstrass_double(WeierstrassPoint *);
+
+/*
+ * Compute an integer multiple of a point. Not guaranteed to work
+ * unless the integer argument is less than the order of the point in
+ * the group (because it won't cope if an identity element shows up in
+ * any intermediate product).
+ */
+WeierstrassPoint *ecc_weierstrass_multiply(WeierstrassPoint *, mp_int *);
+
+/*
+ * Query functions to get the value of a point back out. is_identity
+ * tells you whether the point is the identity; if it isn't, then
+ * get_affine will retrieve one or both of its affine coordinates.
+ * (You can pass NULL as either output pointer, if you don't need that
+ * coordinate as output.)
+ */
+unsigned ecc_weierstrass_is_identity(WeierstrassPoint *wp);
+void ecc_weierstrass_get_affine(WeierstrassPoint *wp, mp_int **x, mp_int **y);
+
+/* ----------------------------------------------------------------------
+ * Montgomery curves.
+ *
+ * A curve in this form is defined by two parameters a,b, and the
+ * curve equation is by^2 = x^3 + ax^2 + x.
+ *
+ * As with Weierstrass curves, there's an additional point at infinity
+ * that is the identity element, and the inverse of (x,y) is (x,-y).
+ *
+ * However, we don't actually work with full (x,y) pairs. We just
+ * store the x-coordinate (so what we're really representing is not a
+ * specific point on the curve but a two-point set {P,-P}). This means
+ * you can't quite do point addition, because if you're given {P,-P}
+ * and {Q,-Q} as input, you can work out a pair of x-coordinates that
+ * are those of P-Q and P+Q, but you don't know which is which.
+ *
+ * Instead, the basic operation is 'differential addition', in which
+ * you are given three parameters P, Q and P-Q and you return P+Q. (As
+ * well as disambiguating which of the possible answers you want, that
+ * extra input also enables a fast formulae for computing it. This
+ * fast formula is more or less why Montgomery curves are useful in
+ * the first place.)
+ *
+ * Doubling a point is still possible to do unambiguously, so you can
+ * still compute an integer multiple of P if you start by making 2P
+ * and then doing a series of differential additions.
+ */
+
+/*
+ * Create and destroy Montgomery curve data structures.
+ */
+MontgomeryCurve *ecc_montgomery_curve(mp_int *p, mp_int *a, mp_int *b);
+void ecc_montgomery_curve_free(MontgomeryCurve *);
+
+/*
+ * Create, copy and free points on the curve. We don't need to
+ * explicitly represent the identity for this application.
+ */
+MontgomeryPoint *ecc_montgomery_point_new(MontgomeryCurve *mc, mp_int *x);
+void ecc_montgomery_point_copy_into(
+ MontgomeryPoint *dest, MontgomeryPoint *src);
+MontgomeryPoint *ecc_montgomery_point_copy(MontgomeryPoint *orig);
+void ecc_montgomery_point_free(MontgomeryPoint *mp);
+
+/*
+ * Basic arithmetic routines: differential addition and point-
+ * doubling. Each of these assumes that no special cases come up - no
+ * input or output point should be the identity, and in diff_add, P
+ * and Q shouldn't be the same.
+ */
+MontgomeryPoint *ecc_montgomery_diff_add(
+ MontgomeryPoint *P, MontgomeryPoint *Q, MontgomeryPoint *PminusQ);
+MontgomeryPoint *ecc_montgomery_double(MontgomeryPoint *P);
+
+/*
+ * Compute an integer multiple of a point.
+ */
+MontgomeryPoint *ecc_montgomery_multiply(MontgomeryPoint *, mp_int *);
+
+/*
+ * Return the affine x-coordinate of a point.
+ */
+void ecc_montgomery_get_affine(MontgomeryPoint *mp, mp_int **x);
+
+/*
+ * Test whether a point is the curve identity.
+ */
+unsigned ecc_montgomery_is_identity(MontgomeryPoint *mp);
+
+/* ----------------------------------------------------------------------
+ * Twisted Edwards curves.
+ *
+ * A curve in this form is defined by two parameters d,a, and the
+ * curve equation is a x^2 + y^2 = 1 + d x^2 y^2.
+ *
+ * Apparently if you ask a proper algebraic geometer they'll tell you
+ * that this is technically not an actual elliptic curve. Certainly it
+ * doesn't work quite the same way as the other kinds: in this form,
+ * there is no need for a point at infinity, because the identity
+ * element is represented by the affine coordinates (0,1). And you
+ * invert a point by negating its x rather than y coordinate: the
+ * inverse of (x,y) is (-x,y).
+ *
+ * The usefulness of this representation is that the addition formula
+ * is 'strongly unified', meaning that the same formula works for any
+ * input and output points, without needing special cases for the
+ * identity or for doubling.
+ */
+
+/*
+ * Create and destroy Edwards curve data structures.
+ *
+ * Similarly to ecc_weierstrass_curve, you don't have to provide
+ * nonsquare_mod_p if you don't need ecc_edwards_point_new_from_y.
+ */
+EdwardsCurve *ecc_edwards_curve(
+ mp_int *p, mp_int *d, mp_int *a, mp_int *nonsquare_mod_p);
+void ecc_edwards_curve_free(EdwardsCurve *);
+
+/*
+ * Create points.
+ *
+ * There's no need to have a separate function to create the identity
+ * point, because you can just pass x=0 and y=1 to the usual function.
+ *
+ * Similarly to the Weierstrass curve, ecc_edwards_point_new_from_y
+ * creates a point given only its y-coordinate and the desired parity
+ * of its x-coordinate, and you can only call it if you provided the
+ * optional nonsquare_mod_p argument when creating the curve.
+ */
+EdwardsPoint *ecc_edwards_point_new(
+ EdwardsCurve *curve, mp_int *x, mp_int *y);
+EdwardsPoint *ecc_edwards_point_new_from_y(
+ EdwardsCurve *curve, mp_int *y, unsigned desired_x_parity);
+
+/* Copy and free points. */
+void ecc_edwards_point_copy_into(EdwardsPoint *dest, EdwardsPoint *src);
+EdwardsPoint *ecc_edwards_point_copy(EdwardsPoint *orig);
+void ecc_edwards_point_free(EdwardsPoint *point);
+
+/*
+ * Arithmetic: add two points, and calculate an integer multiple of a
+ * point.
+ */
+EdwardsPoint *ecc_edwards_add(EdwardsPoint *, EdwardsPoint *);
+EdwardsPoint *ecc_edwards_multiply(EdwardsPoint *, mp_int *);
+
+/*
+ * Query functions: compare two points for equality, and return the
+ * affine coordinates of a point.
+ */
+unsigned ecc_edwards_eq(EdwardsPoint *, EdwardsPoint *);
+void ecc_edwards_get_affine(EdwardsPoint *wp, mp_int **x, mp_int **y);
+
+#endif /* PUTTY_ECC_H */
diff --git a/crypto/hash_simple.c b/crypto/hash_simple.c
new file mode 100644
index 00000000..0115b920
--- /dev/null
+++ b/crypto/hash_simple.c
@@ -0,0 +1,13 @@
+/*
+ * Convenience function to hash a single piece of data, wrapping up
+ * the faff of making and freeing an ssh_hash.
+ */
+
+#include "ssh.h"
+
+void hash_simple(const ssh_hashalg *alg, ptrlen data, void *output)
+{
+ ssh_hash *hash = ssh_hash_new(alg);
+ put_datapl(hash, data);
+ ssh_hash_final(hash, output);
+}
diff --git a/crypto/hmac.c b/crypto/hmac.c
new file mode 100644
index 00000000..adeccd29
--- /dev/null
+++ b/crypto/hmac.c
@@ -0,0 +1,263 @@
+/*
+ * Implementation of HMAC (RFC 2104) for PuTTY, in a general form that
+ * can wrap any underlying hash function.
+ */
+
+#include "ssh.h"
+
+struct hmac {
+ const ssh_hashalg *hashalg;
+ ssh_hash *h_outer, *h_inner, *h_live;
+ uint8_t *digest;
+ strbuf *text_name;
+ ssh2_mac mac;
+};
+
+struct hmac_extra {
+ const ssh_hashalg *hashalg_base;
+ const char *suffix, *annotation;
+};
+
+static ssh2_mac *hmac_new(const ssh2_macalg *alg, ssh_cipher *cipher)
+{
+ struct hmac *ctx = snew(struct hmac);
+ const struct hmac_extra *extra = (const struct hmac_extra *)alg->extra;
+
+ ctx->h_outer = ssh_hash_new(extra->hashalg_base);
+ /* In case that hashalg was a selector vtable, we'll now switch to
+ * using whatever real one it selected, for all future purposes. */
+ ctx->hashalg = ssh_hash_alg(ctx->h_outer);
+ ctx->h_inner = ssh_hash_new(ctx->hashalg);
+ ctx->h_live = ssh_hash_new(ctx->hashalg);
+
+ /*
+ * HMAC is not well defined as a wrapper on an absolutely general
+ * hash function; it expects that the function it's wrapping will
+ * consume data in fixed-size blocks, and it's partially defined
+ * in terms of that block size. So we insist that the hash we're
+ * given must have defined a meaningful block size.
+ */
+ assert(ctx->hashalg->blocklen);
+
+ ctx->digest = snewn(ctx->hashalg->hlen, uint8_t);
+
+ ctx->text_name = strbuf_new();
+ put_fmt(ctx->text_name, "HMAC-%s%s",
+ ctx->hashalg->text_basename, extra->suffix);
+ if (extra->annotation || ctx->hashalg->annotation) {
+ put_fmt(ctx->text_name, " (");
+ const char *sep = "";
+ if (extra->annotation) {
+ put_fmt(ctx->text_name, "%s%s", sep, extra->annotation);
+ sep = ", ";
+ }
+ if (ctx->hashalg->annotation) {
+ put_fmt(ctx->text_name, "%s%s", sep, ctx->hashalg->annotation);
+ sep = ", ";
+ }
+ put_fmt(ctx->text_name, ")");
+ }
+
+ ctx->mac.vt = alg;
+ BinarySink_DELEGATE_INIT(&ctx->mac, ctx->h_live);
+
+ return &ctx->mac;
+}
+
+static void hmac_free(ssh2_mac *mac)
+{
+ struct hmac *ctx = container_of(mac, struct hmac, mac);
+
+ ssh_hash_free(ctx->h_outer);
+ ssh_hash_free(ctx->h_inner);
+ ssh_hash_free(ctx->h_live);
+ smemclr(ctx->digest, ctx->hashalg->hlen);
+ sfree(ctx->digest);
+ strbuf_free(ctx->text_name);
+
+ smemclr(ctx, sizeof(*ctx));
+ sfree(ctx);
+}
+
+#define PAD_OUTER 0x5C
+#define PAD_INNER 0x36
+
+static void hmac_key(ssh2_mac *mac, ptrlen key)
+{
+ struct hmac *ctx = container_of(mac, struct hmac, mac);
+
+ const uint8_t *kp;
+ size_t klen;
+ strbuf *sb = NULL;
+
+ if (key.len > ctx->hashalg->blocklen) {
+ /*
+ * RFC 2104 section 2: if the key exceeds the block length of
+ * the underlying hash, then we start by hashing the key, and
+ * use that hash as the 'true' key for the HMAC construction.
+ */
+ sb = strbuf_new_nm();
+ strbuf_append(sb, ctx->hashalg->hlen);
+ hash_simple(ctx->hashalg, key, sb->u);
+ kp = sb->u;
+ klen = sb->len;
+ } else {
+ /*
+ * A short enough key is used as is.
+ */
+ kp = (const uint8_t *)key.ptr;
+ klen = key.len;
+ }
+
+ ssh_hash_reset(ctx->h_outer);
+ for (size_t i = 0; i < klen; i++)
+ put_byte(ctx->h_outer, PAD_OUTER ^ kp[i]);
+ for (size_t i = klen; i < ctx->hashalg->blocklen; i++)
+ put_byte(ctx->h_outer, PAD_OUTER);
+
+ ssh_hash_reset(ctx->h_inner);
+ for (size_t i = 0; i < klen; i++)
+ put_byte(ctx->h_inner, PAD_INNER ^ kp[i]);
+ for (size_t i = klen; i < ctx->hashalg->blocklen; i++)
+ put_byte(ctx->h_inner, PAD_INNER);
+
+ if (sb)
+ strbuf_free(sb);
+}
+
+static void hmac_start(ssh2_mac *mac)
+{
+ struct hmac *ctx = container_of(mac, struct hmac, mac);
+ ssh_hash_copyfrom(ctx->h_live, ctx->h_inner);
+}
+
+static void hmac_genresult(ssh2_mac *mac, unsigned char *output)
+{
+ struct hmac *ctx = container_of(mac, struct hmac, mac);
+ ssh_hash *htmp;
+
+ /* Leave h_live and h_outer in place, so that the SSH-2 BPP can
+ * continue regenerating test results from different-length
+ * prefixes of the packet */
+ ssh_hash_digest_nondestructive(ctx->h_live, ctx->digest);
+
+ htmp = ssh_hash_copy(ctx->h_outer);
+ put_data(htmp, ctx->digest, ctx->hashalg->hlen);
+ ssh_hash_final(htmp, ctx->digest);
+
+ /*
+ * Some instances of HMAC truncate the output hash, so instead of
+ * writing it directly to 'output' we wrote it to our own
+ * full-length buffer, and now we copy the required amount.
+ */
+ memcpy(output, ctx->digest, mac->vt->len);
+ smemclr(ctx->digest, ctx->hashalg->hlen);
+}
+
+static const char *hmac_text_name(ssh2_mac *mac)
+{
+ struct hmac *ctx = container_of(mac, struct hmac, mac);
+ return ctx->text_name->s;
+}
+
+static const struct hmac_extra ssh_hmac_sha256_extra = { &ssh_sha256, "" };
+const ssh2_macalg ssh_hmac_sha256 = {
+ .new = hmac_new,
+ .free = hmac_free,
+ .setkey = hmac_key,
+ .start = hmac_start,
+ .genresult = hmac_genresult,
+ .next_message = nullmac_next_message,
+ .text_name = hmac_text_name,
+ .name = "hmac-sha2-256",
+ .etm_name = "hmac-sha2-256-etm@openssh.com",
+ .len = 32,
+ .keylen = 32,
+ .extra = &ssh_hmac_sha256_extra,
+};
+
+static const struct hmac_extra ssh_hmac_md5_extra = { &ssh_md5, "" };
+const ssh2_macalg ssh_hmac_md5 = {
+ .new = hmac_new,
+ .free = hmac_free,
+ .setkey = hmac_key,
+ .start = hmac_start,
+ .genresult = hmac_genresult,
+ .next_message = nullmac_next_message,
+ .text_name = hmac_text_name,
+ .name = "hmac-md5",
+ .etm_name = "hmac-md5-etm@openssh.com",
+ .len = 16,
+ .keylen = 16,
+ .extra = &ssh_hmac_md5_extra,
+};
+
+static const struct hmac_extra ssh_hmac_sha1_extra = { &ssh_sha1, "" };
+
+const ssh2_macalg ssh_hmac_sha1 = {
+ .new = hmac_new,
+ .free = hmac_free,
+ .setkey = hmac_key,
+ .start = hmac_start,
+ .genresult = hmac_genresult,
+ .next_message = nullmac_next_message,
+ .text_name = hmac_text_name,
+ .name = "hmac-sha1",
+ .etm_name = "hmac-sha1-etm@openssh.com",
+ .len = 20,
+ .keylen = 20,
+ .extra = &ssh_hmac_sha1_extra,
+};
+
+static const struct hmac_extra ssh_hmac_sha1_96_extra = { &ssh_sha1, "-96" };
+
+const ssh2_macalg ssh_hmac_sha1_96 = {
+ .new = hmac_new,
+ .free = hmac_free,
+ .setkey = hmac_key,
+ .start = hmac_start,
+ .genresult = hmac_genresult,
+ .next_message = nullmac_next_message,
+ .text_name = hmac_text_name,
+ .name = "hmac-sha1-96",
+ .etm_name = "hmac-sha1-96-etm@openssh.com",
+ .len = 12,
+ .keylen = 20,
+ .extra = &ssh_hmac_sha1_96_extra,
+};
+
+static const struct hmac_extra ssh_hmac_sha1_buggy_extra = {
+ &ssh_sha1, "", "bug-compatible"
+};
+
+const ssh2_macalg ssh_hmac_sha1_buggy = {
+ .new = hmac_new,
+ .free = hmac_free,
+ .setkey = hmac_key,
+ .start = hmac_start,
+ .genresult = hmac_genresult,
+ .next_message = nullmac_next_message,
+ .text_name = hmac_text_name,
+ .name = "hmac-sha1",
+ .len = 20,
+ .keylen = 16,
+ .extra = &ssh_hmac_sha1_buggy_extra,
+};
+
+static const struct hmac_extra ssh_hmac_sha1_96_buggy_extra = {
+ &ssh_sha1, "-96", "bug-compatible"
+};
+
+const ssh2_macalg ssh_hmac_sha1_96_buggy = {
+ .new = hmac_new,
+ .free = hmac_free,
+ .setkey = hmac_key,
+ .start = hmac_start,
+ .genresult = hmac_genresult,
+ .next_message = nullmac_next_message,
+ .text_name = hmac_text_name,
+ .name = "hmac-sha1-96",
+ .len = 12,
+ .keylen = 16,
+ .extra = &ssh_hmac_sha1_96_buggy_extra,
+};
diff --git a/sshmac.c b/crypto/mac.c
index c117d90b..c117d90b 100644
--- a/sshmac.c
+++ b/crypto/mac.c
diff --git a/crypto/mac_simple.c b/crypto/mac_simple.c
new file mode 100644
index 00000000..c705fd88
--- /dev/null
+++ b/crypto/mac_simple.c
@@ -0,0 +1,16 @@
+/*
+ * Convenience function to MAC a single piece of data, wrapping up
+ * the faff of making and freeing an ssh_mac.
+ */
+
+#include "ssh.h"
+
+void mac_simple(const ssh2_macalg *alg, ptrlen key, ptrlen data, void *output)
+{
+ ssh2_mac *mac = ssh2_mac_new(alg, NULL);
+ ssh2_mac_setkey(mac, key);
+ ssh2_mac_start(mac);
+ put_datapl(mac, data);
+ ssh2_mac_genresult(mac, output);
+ ssh2_mac_free(mac);
+}
diff --git a/SSHMD5.C b/crypto/md5.c
index 9155c99e..9155c99e 100644
--- a/SSHMD5.C
+++ b/crypto/md5.c
diff --git a/crypto/mpint.c b/crypto/mpint.c
new file mode 100644
index 00000000..437c7e8c
--- /dev/null
+++ b/crypto/mpint.c
@@ -0,0 +1,2810 @@
+/*
+ * Multiprecision integer arithmetic, implementing mpint.h.
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include "defs.h"
+#include "misc.h"
+#include "puttymem.h"
+
+#include "mpint.h"
+#include "mpint_i.h"
+
+#define SIZE_T_BITS (CHAR_BIT * sizeof(size_t))
+
+/*
+ * Inline helpers to take min and max of size_t values, used
+ * throughout this code.
+ */
+static inline size_t size_t_min(size_t a, size_t b)
+{
+ return a < b ? a : b;
+}
+static inline size_t size_t_max(size_t a, size_t b)
+{
+ return a > b ? a : b;
+}
+
+/*
+ * Helper to fetch a word of data from x with array overflow checking.
+ * If x is too short to have that word, 0 is returned.
+ */
+static inline BignumInt mp_word(mp_int *x, size_t i)
+{
+ return i < x->nw ? x->w[i] : 0;
+}
+
+/*
+ * Shift an ordinary C integer by BIGNUM_INT_BITS, in a way that
+ * avoids writing a shift operator whose RHS is greater or equal to
+ * the size of the type, because that's undefined behaviour in C.
+ *
+ * In fact we must avoid even writing it in a definitely-untaken
+ * branch of an if, because compilers will sometimes warn about
+ * that. So you can't just write 'shift too big ? 0 : n >> shift',
+ * because even if 'shift too big' is a constant-expression
+ * evaluating to false, you can still get complaints about the
+ * else clause of the ?:.
+ *
+ * So we have to re-check _inside_ that clause, so that the shift
+ * count is reset to something nonsensical but safe in the case
+ * where the clause wasn't going to be taken anyway.
+ */
+static uintmax_t shift_right_by_one_word(uintmax_t n)
+{
+ bool shift_too_big = BIGNUM_INT_BYTES >= sizeof(n);
+ return shift_too_big ? 0 :
+ n >> (shift_too_big ? 0 : BIGNUM_INT_BITS);
+}
+static uintmax_t shift_left_by_one_word(uintmax_t n)
+{
+ bool shift_too_big = BIGNUM_INT_BYTES >= sizeof(n);
+ return shift_too_big ? 0 :
+ n << (shift_too_big ? 0 : BIGNUM_INT_BITS);
+}
+
+mp_int *mp_make_sized(size_t nw)
+{
+ mp_int *x = snew_plus(mp_int, nw * sizeof(BignumInt));
+ assert(nw); /* we outlaw the zero-word mp_int */
+ x->nw = nw;
+ x->w = snew_plus_get_aux(x);
+ mp_clear(x);
+ return x;
+}
+
+mp_int *mp_new(size_t maxbits)
+{
+ size_t words = (maxbits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS;
+ return mp_make_sized(words);
+}
+
+mp_int *mp_resize(mp_int *mp, size_t newmaxbits)
+{
+ mp_int *copy = mp_new(newmaxbits);
+ mp_copy_into(copy, mp);
+ mp_free(mp);
+ return copy;
+}
+
+mp_int *mp_from_integer(uintmax_t n)
+{
+ mp_int *x = mp_make_sized(
+ (sizeof(n) + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES);
+ for (size_t i = 0; i < x->nw; i++)
+ x->w[i] = n >> (i * BIGNUM_INT_BITS);
+ return x;
+}
+
+size_t mp_max_bytes(mp_int *x)
+{
+ return x->nw * BIGNUM_INT_BYTES;
+}
+
+size_t mp_max_bits(mp_int *x)
+{
+ return x->nw * BIGNUM_INT_BITS;
+}
+
+void mp_free(mp_int *x)
+{
+ mp_clear(x);
+ smemclr(x, sizeof(*x));
+ sfree(x);
+}
+
+void mp_dump(FILE *fp, const char *prefix, mp_int *x, const char *suffix)
+{
+ fprintf(fp, "%s0x", prefix);
+ for (size_t i = mp_max_bytes(x); i-- > 0 ;)
+ fprintf(fp, "%02X", mp_get_byte(x, i));
+ fputs(suffix, fp);
+}
+
+void mp_copy_into(mp_int *dest, mp_int *src)
+{
+ size_t copy_nw = size_t_min(dest->nw, src->nw);
+ memmove(dest->w, src->w, copy_nw * sizeof(BignumInt));
+ smemclr(dest->w + copy_nw, (dest->nw - copy_nw) * sizeof(BignumInt));
+}
+
+void mp_copy_integer_into(mp_int *r, uintmax_t n)
+{
+ for (size_t i = 0; i < r->nw; i++) {
+ r->w[i] = n;
+ n = shift_right_by_one_word(n);
+ }
+}
+
+/*
+ * Conditional selection is done by negating 'which', to give a mask
+ * word which is all 1s if which==1 and all 0s if which==0. Then you
+ * can select between two inputs a,b without data-dependent control
+ * flow by XORing them to get their difference; ANDing with the mask
+ * word to replace that difference with 0 if which==0; and XORing that
+ * into a, which will either turn it into b or leave it alone.
+ *
+ * This trick will be used throughout this code and taken as read the
+ * rest of the time (or else I'd be here all week typing comments),
+ * but I felt I ought to explain it in words _once_.
+ */
+void mp_select_into(mp_int *dest, mp_int *src0, mp_int *src1,
+ unsigned which)
+{
+ BignumInt mask = -(BignumInt)(1 & which);
+ for (size_t i = 0; i < dest->nw; i++) {
+ BignumInt srcword0 = mp_word(src0, i), srcword1 = mp_word(src1, i);
+ dest->w[i] = srcword0 ^ ((srcword1 ^ srcword0) & mask);
+ }
+}
+
+void mp_cond_swap(mp_int *x0, mp_int *x1, unsigned swap)
+{
+ assert(x0->nw == x1->nw);
+ volatile BignumInt mask = -(BignumInt)(1 & swap);
+ for (size_t i = 0; i < x0->nw; i++) {
+ BignumInt diff = (x0->w[i] ^ x1->w[i]) & mask;
+ x0->w[i] ^= diff;
+ x1->w[i] ^= diff;
+ }
+}
+
+void mp_clear(mp_int *x)
+{
+ smemclr(x->w, x->nw * sizeof(BignumInt));
+}
+
+void mp_cond_clear(mp_int *x, unsigned clear)
+{
+ BignumInt mask = ~-(BignumInt)(1 & clear);
+ for (size_t i = 0; i < x->nw; i++)
+ x->w[i] &= mask;
+}
+
+/*
+ * Common code between mp_from_bytes_{le,be} which reads bytes in an
+ * arbitrary arithmetic progression.
+ */
+static mp_int *mp_from_bytes_int(ptrlen bytes, size_t m, size_t c)
+{
+ size_t nw = (bytes.len + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES;
+ nw = size_t_max(nw, 1);
+ mp_int *n = mp_make_sized(nw);
+ for (size_t i = 0; i < bytes.len; i++)
+ n->w[i / BIGNUM_INT_BYTES] |=
+ (BignumInt)(((const unsigned char *)bytes.ptr)[m*i+c]) <<
+ (8 * (i % BIGNUM_INT_BYTES));
+ return n;
+}
+
+mp_int *mp_from_bytes_le(ptrlen bytes)
+{
+ return mp_from_bytes_int(bytes, 1, 0);
+}
+
+mp_int *mp_from_bytes_be(ptrlen bytes)
+{
+ return mp_from_bytes_int(bytes, -1, bytes.len - 1);
+}
+
+static mp_int *mp_from_words(size_t nw, const BignumInt *w)
+{
+ mp_int *x = mp_make_sized(nw);
+ memcpy(x->w, w, x->nw * sizeof(BignumInt));
+ return x;
+}
+
+/*
+ * Decimal-to-binary conversion: just go through the input string
+ * adding on the decimal value of each digit, and then multiplying the
+ * number so far by 10.
+ */
+mp_int *mp_from_decimal_pl(ptrlen decimal)
+{
+ /* 196/59 is an upper bound (and also a continued-fraction
+ * convergent) for log2(10), so this conservatively estimates the
+ * number of bits that will be needed to store any number that can
+ * be written in this many decimal digits. */
+ assert(decimal.len < (~(size_t)0) / 196);
+ size_t bits = 196 * decimal.len / 59;
+
+ /* Now round that up to words. */
+ size_t words = bits / BIGNUM_INT_BITS + 1;
+
+ mp_int *x = mp_make_sized(words);
+ for (size_t i = 0; i < decimal.len; i++) {
+ mp_add_integer_into(x, x, ((const char *)decimal.ptr)[i] - '0');
+
+ if (i+1 == decimal.len)
+ break;
+
+ mp_mul_integer_into(x, x, 10);
+ }
+ return x;
+}
+
+mp_int *mp_from_decimal(const char *decimal)
+{
+ return mp_from_decimal_pl(ptrlen_from_asciz(decimal));
+}
+
+/*
+ * Hex-to-binary conversion: _algorithmically_ simpler than decimal
+ * (none of those multiplications by 10), but there's some fiddly
+ * bit-twiddling needed to process each hex digit without diverging
+ * control flow depending on whether it's a letter or a number.
+ */
+mp_int *mp_from_hex_pl(ptrlen hex)
+{
+ assert(hex.len <= (~(size_t)0) / 4);
+ size_t bits = hex.len * 4;
+ size_t words = (bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS;
+ words = size_t_max(words, 1);
+ mp_int *x = mp_make_sized(words);
+ for (size_t nibble = 0; nibble < hex.len; nibble++) {
+ BignumInt digit = ((const char *)hex.ptr)[hex.len-1 - nibble];
+
+ BignumInt lmask = ~-((BignumInt)((digit-'a')|('f'-digit))
+ >> (BIGNUM_INT_BITS-1));
+ BignumInt umask = ~-((BignumInt)((digit-'A')|('F'-digit))
+ >> (BIGNUM_INT_BITS-1));
+
+ BignumInt digitval = digit - '0';
+ digitval ^= (digitval ^ (digit - 'a' + 10)) & lmask;
+ digitval ^= (digitval ^ (digit - 'A' + 10)) & umask;
+ digitval &= 0xF; /* at least be _slightly_ nice about weird input */
+
+ size_t word_idx = nibble / (BIGNUM_INT_BYTES*2);
+ size_t nibble_within_word = nibble % (BIGNUM_INT_BYTES*2);
+ x->w[word_idx] |= digitval << (nibble_within_word * 4);
+ }
+ return x;
+}
+
+mp_int *mp_from_hex(const char *hex)
+{
+ return mp_from_hex_pl(ptrlen_from_asciz(hex));
+}
+
+mp_int *mp_copy(mp_int *x)
+{
+ return mp_from_words(x->nw, x->w);
+}
+
+uint8_t mp_get_byte(mp_int *x, size_t byte)
+{
+ return 0xFF & (mp_word(x, byte / BIGNUM_INT_BYTES) >>
+ (8 * (byte % BIGNUM_INT_BYTES)));
+}
+
+unsigned mp_get_bit(mp_int *x, size_t bit)
+{
+ return 1 & (mp_word(x, bit / BIGNUM_INT_BITS) >>
+ (bit % BIGNUM_INT_BITS));
+}
+
+uintmax_t mp_get_integer(mp_int *x)
+{
+ uintmax_t toret = 0;
+ for (size_t i = x->nw; i-- > 0 ;)
+ toret = shift_left_by_one_word(toret) | x->w[i];
+ return toret;
+}
+
+void mp_set_bit(mp_int *x, size_t bit, unsigned val)
+{
+ size_t word = bit / BIGNUM_INT_BITS;
+ assert(word < x->nw);
+
+ unsigned shift = (bit % BIGNUM_INT_BITS);
+
+ x->w[word] &= ~((BignumInt)1 << shift);
+ x->w[word] |= (BignumInt)(val & 1) << shift;
+}
+
+/*
+ * Helper function used here and there to normalise any nonzero input
+ * value to 1.
+ */
+static inline unsigned normalise_to_1(BignumInt n)
+{
+ n = (n >> 1) | (n & 1); /* ensure top bit is clear */
+ n = (BignumInt)(-n) >> (BIGNUM_INT_BITS - 1); /* normalise to 0 or 1 */
+ return n;
+}
+static inline unsigned normalise_to_1_u64(uint64_t n)
+{
+ n = (n >> 1) | (n & 1); /* ensure top bit is clear */
+ n = (-n) >> 63; /* normalise to 0 or 1 */
+ return n;
+}
+
+/*
+ * Find the highest nonzero word in a number. Returns the index of the
+ * word in x->w, and also a pair of output uint64_t in which that word
+ * appears in the high one shifted left by 'shift_wanted' bits, the
+ * words immediately below it occupy the space to the right, and the
+ * words below _that_ fill up the low one.
+ *
+ * If there is no nonzero word at all, the passed-by-reference output
+ * variables retain their original values.
+ */
+static inline void mp_find_highest_nonzero_word_pair(
+ mp_int *x, size_t shift_wanted, size_t *index,
+ uint64_t *hi, uint64_t *lo)
+{
+ uint64_t curr_hi = 0, curr_lo = 0;
+
+ for (size_t curr_index = 0; curr_index < x->nw; curr_index++) {
+ BignumInt curr_word = x->w[curr_index];
+ unsigned indicator = normalise_to_1(curr_word);
+
+ curr_lo = (BIGNUM_INT_BITS < 64 ? (curr_lo >> BIGNUM_INT_BITS) : 0) |
+ (curr_hi << (64 - BIGNUM_INT_BITS));
+ curr_hi = (BIGNUM_INT_BITS < 64 ? (curr_hi >> BIGNUM_INT_BITS) : 0) |
+ ((uint64_t)curr_word << shift_wanted);
+
+ if (hi) *hi ^= (curr_hi ^ *hi ) & -(uint64_t)indicator;
+ if (lo) *lo ^= (curr_lo ^ *lo ) & -(uint64_t)indicator;
+ if (index) *index ^= (curr_index ^ *index) & -(size_t) indicator;
+ }
+}
+
+size_t mp_get_nbits(mp_int *x)
+{
+ /* Sentinel values in case there are no bits set at all: we
+ * imagine that there's a word at position -1 (i.e. the topmost
+ * fraction word) which is all 1s, because that way, we handle a
+ * zero input by considering its highest set bit to be the top one
+ * of that word, i.e. just below the units digit, i.e. at bit
+ * index -1, i.e. so we'll return 0 on output. */
+ size_t hiword_index = -(size_t)1;
+ uint64_t hiword64 = ~(BignumInt)0;
+
+ /*
+ * Find the highest nonzero word and its index.
+ */
+ mp_find_highest_nonzero_word_pair(x, 0, &hiword_index, &hiword64, NULL);
+ BignumInt hiword = hiword64; /* in case BignumInt is a narrower type */
+
+ /*
+ * Find the index of the highest set bit within hiword.
+ */
+ BignumInt hibit_index = 0;
+ for (size_t i = (1 << (BIGNUM_INT_BITS_BITS-1)); i != 0; i >>= 1) {
+ BignumInt shifted_word = hiword >> i;
+ BignumInt indicator =
+ (BignumInt)(-shifted_word) >> (BIGNUM_INT_BITS-1);
+ hiword ^= (shifted_word ^ hiword ) & -indicator;
+ hibit_index += i & -(size_t)indicator;
+ }
+
+ /*
+ * Put together the result.
+ */
+ return (hiword_index << BIGNUM_INT_BITS_BITS) + hibit_index + 1;
+}
+
+/*
+ * Shared code between the hex and decimal output functions to get rid
+ * of leading zeroes on the output string. The idea is that we wrote
+ * out a fixed number of digits and a trailing \0 byte into 'buf', and
+ * now we want to shift it all left so that the first nonzero digit
+ * moves to buf[0] (or, if there are no nonzero digits at all, we move
+ * up by 'maxtrim', so that we return 0 as "0" instead of "").
+ */
+static void trim_leading_zeroes(char *buf, size_t bufsize, size_t maxtrim)
+{
+ size_t trim = maxtrim;
+
+ /*
+ * Look for the first character not equal to '0', to find the
+ * shift count.
+ */
+ if (trim > 0) {
+ for (size_t pos = trim; pos-- > 0 ;) {
+ uint8_t diff = buf[pos] ^ '0';
+ size_t mask = -((((size_t)diff) - 1) >> (SIZE_T_BITS - 1));
+ trim ^= (trim ^ pos) & ~mask;
+ }
+ }
+
+ /*
+ * Now do the shift, in log n passes each of which does a
+ * conditional shift by 2^i bytes if bit i is set in the shift
+ * count.
+ */
+ uint8_t *ubuf = (uint8_t *)buf;
+ for (size_t logd = 0; bufsize >> logd; logd++) {
+ uint8_t mask = -(uint8_t)((trim >> logd) & 1);
+ size_t d = (size_t)1 << logd;
+ for (size_t i = 0; i+d < bufsize; i++) {
+ uint8_t diff = mask & (ubuf[i] ^ ubuf[i+d]);
+ ubuf[i] ^= diff;
+ ubuf[i+d] ^= diff;
+ }
+ }
+}
+
+/*
+ * Binary to decimal conversion. Our strategy here is to extract each
+ * decimal digit by finding the input number's residue mod 10, then
+ * subtract that off to give an exact multiple of 10, which then means
+ * you can safely divide by 10 by means of shifting right one bit and
+ * then multiplying by the inverse of 5 mod 2^n.
+ */
+char *mp_get_decimal(mp_int *x_orig)
+{
+ mp_int *x = mp_copy(x_orig), *y = mp_make_sized(x->nw);
+
+ /*
+ * The inverse of 5 mod 2^lots is 0xccccccccccccccccccccd, for an
+ * appropriate number of 'c's. Manually construct an integer the
+ * right size.
+ */
+ mp_int *inv5 = mp_make_sized(x->nw);
+ assert(BIGNUM_INT_BITS % 8 == 0);
+ for (size_t i = 0; i < inv5->nw; i++)
+ inv5->w[i] = BIGNUM_INT_MASK / 5 * 4;
+ inv5->w[0]++;
+
+ /*
+ * 146/485 is an upper bound (and also a continued-fraction
+ * convergent) of log10(2), so this is a conservative estimate of
+ * the number of decimal digits needed to store a value that fits
+ * in this many binary bits.
+ */
+ assert(x->nw < (~(size_t)1) / (146 * BIGNUM_INT_BITS));
+ size_t bufsize = size_t_max(x->nw * (146 * BIGNUM_INT_BITS) / 485, 1) + 2;
+ char *outbuf = snewn(bufsize, char);
+ outbuf[bufsize - 1] = '\0';
+
+ /*
+ * Loop over the number generating digits from the least
+ * significant upwards, so that we write to outbuf in reverse
+ * order.
+ */
+ for (size_t pos = bufsize - 1; pos-- > 0 ;) {
+ /*
+ * Find the current residue mod 10. We do this by first
+ * summing the bytes of the number, with all but the lowest
+ * one multiplied by 6 (because 256^i == 6 mod 10 for all
+ * i>0). That gives us a single word congruent mod 10 to the
+ * input number, and then we reduce it further by manual
+ * multiplication and shifting, just in case the compiler
+ * target implements the C division operator in a way that has
+ * input-dependent timing.
+ */
+ uint32_t low_digit = 0, maxval = 0, mult = 1;
+ for (size_t i = 0; i < x->nw; i++) {
+ for (unsigned j = 0; j < BIGNUM_INT_BYTES; j++) {
+ low_digit += mult * (0xFF & (x->w[i] >> (8*j)));
+ maxval += mult * 0xFF;
+ mult = 6;
+ }
+ /*
+ * For _really_ big numbers, prevent overflow of t by
+ * periodically folding the top half of the accumulator
+ * into the bottom half, using the same rule 'multiply by
+ * 6 when shifting down by one or more whole bytes'.
+ */
+ if (maxval > UINT32_MAX - (6 * 0xFF * BIGNUM_INT_BYTES)) {
+ low_digit = (low_digit & 0xFFFF) + 6 * (low_digit >> 16);
+ maxval = (maxval & 0xFFFF) + 6 * (maxval >> 16);
+ }
+ }
+
+ /*
+ * Final reduction of low_digit. We multiply by 2^32 / 10
+ * (that's the constant 0x19999999) to get a 64-bit value
+ * whose top 32 bits are the approximate quotient
+ * low_digit/10; then we subtract off 10 times that; and
+ * finally we do one last trial subtraction of 10 by adding 6
+ * (which sets bit 4 if the number was just over 10) and then
+ * testing bit 4.
+ */
+ low_digit -= 10 * ((0x19999999ULL * low_digit) >> 32);
+ low_digit -= 10 * ((low_digit + 6) >> 4);
+
+ assert(low_digit < 10); /* make sure we did reduce fully */
+ outbuf[pos] = '0' + low_digit;
+
+ /*
+ * Now subtract off that digit, divide by 2 (using a right
+ * shift) and by 5 (using the modular inverse), to get the
+ * next output digit into the units position.
+ */
+ mp_sub_integer_into(x, x, low_digit);
+ mp_rshift_fixed_into(y, x, 1);
+ mp_mul_into(x, y, inv5);
+ }
+
+ mp_free(x);
+ mp_free(y);
+ mp_free(inv5);
+
+ trim_leading_zeroes(outbuf, bufsize, bufsize - 2);
+ return outbuf;
+}
+
+/*
+ * Binary to hex conversion. Reasonably simple (only a spot of bit
+ * twiddling to choose whether to output a digit or a letter for each
+ * nibble).
+ */
+static char *mp_get_hex_internal(mp_int *x, uint8_t letter_offset)
+{
+ size_t nibbles = x->nw * BIGNUM_INT_BYTES * 2;
+ size_t bufsize = nibbles + 1;
+ char *outbuf = snewn(bufsize, char);
+ outbuf[nibbles] = '\0';
+
+ for (size_t nibble = 0; nibble < nibbles; nibble++) {
+ size_t word_idx = nibble / (BIGNUM_INT_BYTES*2);
+ size_t nibble_within_word = nibble % (BIGNUM_INT_BYTES*2);
+ uint8_t digitval = 0xF & (x->w[word_idx] >> (nibble_within_word * 4));
+
+ uint8_t mask = -((digitval + 6) >> 4);
+ char digit = digitval + '0' + (letter_offset & mask);
+ outbuf[nibbles-1 - nibble] = digit;
+ }
+
+ trim_leading_zeroes(outbuf, bufsize, nibbles - 1);
+ return outbuf;
+}
+
+char *mp_get_hex(mp_int *x)
+{
+ return mp_get_hex_internal(x, 'a' - ('0'+10));
+}
+
+char *mp_get_hex_uppercase(mp_int *x)
+{
+ return mp_get_hex_internal(x, 'A' - ('0'+10));
+}
+
+/*
+ * Routines for reading and writing the SSH-1 and SSH-2 wire formats
+ * for multiprecision integers, declared in marshal.h.
+ *
+ * These can't avoid having control flow dependent on the true bit
+ * size of the number, because the wire format requires the number of
+ * output bytes to depend on that.
+ */
+void BinarySink_put_mp_ssh1(BinarySink *bs, mp_int *x)
+{
+ size_t bits = mp_get_nbits(x);
+ size_t bytes = (bits + 7) / 8;
+
+ assert(bits < 0x10000);
+ put_uint16(bs, bits);
+ for (size_t i = bytes; i-- > 0 ;)
+ put_byte(bs, mp_get_byte(x, i));
+}
+
+void BinarySink_put_mp_ssh2(BinarySink *bs, mp_int *x)
+{
+ size_t bytes = (mp_get_nbits(x) + 8) / 8;
+
+ put_uint32(bs, bytes);
+ for (size_t i = bytes; i-- > 0 ;)
+ put_byte(bs, mp_get_byte(x, i));
+}
+
+mp_int *BinarySource_get_mp_ssh1(BinarySource *src)
+{
+ unsigned bitc = get_uint16(src);
+ ptrlen bytes = get_data(src, (bitc + 7) / 8);
+ if (get_err(src)) {
+ return mp_from_integer(0);
+ } else {
+ mp_int *toret = mp_from_bytes_be(bytes);
+ /* SSH-1.5 spec says that it's OK for the prefix uint16 to be
+ * _greater_ than the actual number of bits */
+ if (mp_get_nbits(toret) > bitc) {
+ src->err = BSE_INVALID;
+ mp_free(toret);
+ toret = mp_from_integer(0);
+ }
+ return toret;
+ }
+}
+
+mp_int *BinarySource_get_mp_ssh2(BinarySource *src)
+{
+ ptrlen bytes = get_string(src);
+ if (get_err(src)) {
+ return mp_from_integer(0);
+ } else {
+ const unsigned char *p = bytes.ptr;
+ if ((bytes.len > 0 &&
+ ((p[0] & 0x80) ||
+ (p[0] == 0 && (bytes.len <= 1 || !(p[1] & 0x80)))))) {
+ src->err = BSE_INVALID;
+ return mp_from_integer(0);
+ }
+ return mp_from_bytes_be(bytes);
+ }
+}
+
+/*
+ * Make an mp_int structure whose words array aliases a subinterval of
+ * some other mp_int. This makes it easy to read or write just the low
+ * or high words of a number, e.g. to add a number starting from a
+ * high bit position, or to reduce mod 2^{n*BIGNUM_INT_BITS}.
+ *
+ * The convention throughout this code is that when we store an mp_int
+ * directly by value, we always expect it to be an alias of some kind,
+ * so its words array won't ever need freeing. Whereas an 'mp_int *'
+ * has an owner, who knows whether it needs freeing or whether it was
+ * created by address-taking an alias.
+ */
+static mp_int mp_make_alias(mp_int *in, size_t offset, size_t len)
+{
+ /*
+ * Bounds-check the offset and length so that we always return
+ * something valid, even if it's not necessarily the length the
+ * caller asked for.
+ */
+ if (offset > in->nw)
+ offset = in->nw;
+ if (len > in->nw - offset)
+ len = in->nw - offset;
+
+ mp_int toret;
+ toret.nw = len;
+ toret.w = in->w + offset;
+ return toret;
+}
+
+/*
+ * A special case of mp_make_alias: in some cases we preallocate a
+ * large mp_int to use as scratch space (to avoid pointless
+ * malloc/free churn in recursive or iterative work).
+ *
+ * mp_alloc_from_scratch creates an alias of size 'len' to part of
+ * 'pool', and adjusts 'pool' itself so that further allocations won't
+ * overwrite that space.
+ *
+ * There's no free function to go with this. Typically you just copy
+ * the pool mp_int by value, allocate from the copy, and when you're
+ * done with those allocations, throw the copy away and go back to the
+ * original value of pool. (A mark/release system.)
+ */
+static mp_int mp_alloc_from_scratch(mp_int *pool, size_t len)
+{
+ assert(len <= pool->nw);
+ mp_int toret = mp_make_alias(pool, 0, len);
+ *pool = mp_make_alias(pool, len, pool->nw);
+ return toret;
+}
+
+/*
+ * Internal component common to lots of assorted add/subtract code.
+ * Reads words from a,b; writes into w_out (which might be NULL if the
+ * output isn't even needed). Takes an input carry flag in 'carry',
+ * and returns the output carry. Each word read from b is ANDed with
+ * b_and and then XORed with b_xor.
+ *
+ * So you can implement addition by setting b_and to all 1s and b_xor
+ * to 0; you can subtract by making b_xor all 1s too (effectively
+ * bit-flipping b) and also passing 1 as the input carry (to turn
+ * one's complement into two's complement). And you can do conditional
+ * add/subtract by choosing b_and to be all 1s or all 0s based on a
+ * condition, because the value of b will be totally ignored if b_and
+ * == 0.
+ */
+static BignumCarry mp_add_masked_into(
+ BignumInt *w_out, size_t rw, mp_int *a, mp_int *b,
+ BignumInt b_and, BignumInt b_xor, BignumCarry carry)
+{
+ for (size_t i = 0; i < rw; i++) {
+ BignumInt aword = mp_word(a, i), bword = mp_word(b, i), out;
+ bword = (bword & b_and) ^ b_xor;
+ BignumADC(out, carry, aword, bword, carry);
+ if (w_out)
+ w_out[i] = out;
+ }
+ return carry;
+}
+
+/*
+ * Like the public mp_add_into except that it returns the output carry.
+ */
+static inline BignumCarry mp_add_into_internal(mp_int *r, mp_int *a, mp_int *b)
+{
+ return mp_add_masked_into(r->w, r->nw, a, b, ~(BignumInt)0, 0, 0);
+}
+
+void mp_add_into(mp_int *r, mp_int *a, mp_int *b)
+{
+ mp_add_into_internal(r, a, b);
+}
+
+void mp_sub_into(mp_int *r, mp_int *a, mp_int *b)
+{
+ mp_add_masked_into(r->w, r->nw, a, b, ~(BignumInt)0, ~(BignumInt)0, 1);
+}
+
+void mp_and_into(mp_int *r, mp_int *a, mp_int *b)
+{
+ for (size_t i = 0; i < r->nw; i++) {
+ BignumInt aword = mp_word(a, i), bword = mp_word(b, i);
+ r->w[i] = aword & bword;
+ }
+}
+
+void mp_or_into(mp_int *r, mp_int *a, mp_int *b)
+{
+ for (size_t i = 0; i < r->nw; i++) {
+ BignumInt aword = mp_word(a, i), bword = mp_word(b, i);
+ r->w[i] = aword | bword;
+ }
+}
+
+void mp_xor_into(mp_int *r, mp_int *a, mp_int *b)
+{
+ for (size_t i = 0; i < r->nw; i++) {
+ BignumInt aword = mp_word(a, i), bword = mp_word(b, i);
+ r->w[i] = aword ^ bword;
+ }
+}
+
+void mp_bic_into(mp_int *r, mp_int *a, mp_int *b)
+{
+ for (size_t i = 0; i < r->nw; i++) {
+ BignumInt aword = mp_word(a, i), bword = mp_word(b, i);
+ r->w[i] = aword & ~bword;
+ }
+}
+
+static void mp_cond_negate(mp_int *r, mp_int *x, unsigned yes)
+{
+ BignumCarry carry = yes;
+ BignumInt flip = -(BignumInt)yes;
+ for (size_t i = 0; i < r->nw; i++) {
+ BignumInt xword = mp_word(x, i);
+ xword ^= flip;
+ BignumADC(r->w[i], carry, 0, xword, carry);
+ }
+}
+
+/*
+ * Similar to mp_add_masked_into, but takes a C integer instead of an
+ * mp_int as the masked operand.
+ */
+static BignumCarry mp_add_masked_integer_into(
+ BignumInt *w_out, size_t rw, mp_int *a, uintmax_t b,
+ BignumInt b_and, BignumInt b_xor, BignumCarry carry)
+{
+ for (size_t i = 0; i < rw; i++) {
+ BignumInt aword = mp_word(a, i);
+ BignumInt bword = b;
+ b = shift_right_by_one_word(b);
+ BignumInt out;
+ bword = (bword ^ b_xor) & b_and;
+ BignumADC(out, carry, aword, bword, carry);
+ if (w_out)
+ w_out[i] = out;
+ }
+ return carry;
+}
+
+void mp_add_integer_into(mp_int *r, mp_int *a, uintmax_t n)
+{
+ mp_add_masked_integer_into(r->w, r->nw, a, n, ~(BignumInt)0, 0, 0);
+}
+
+void mp_sub_integer_into(mp_int *r, mp_int *a, uintmax_t n)
+{
+ mp_add_masked_integer_into(r->w, r->nw, a, n,
+ ~(BignumInt)0, ~(BignumInt)0, 1);
+}
+
+/*
+ * Sets r to a + n << (word_index * BIGNUM_INT_BITS), treating
+ * word_index as secret data.
+ */
+static void mp_add_integer_into_shifted_by_words(
+ mp_int *r, mp_int *a, uintmax_t n, size_t word_index)
+{
+ unsigned indicator = 0;
+ BignumCarry carry = 0;
+
+ for (size_t i = 0; i < r->nw; i++) {
+ /* indicator becomes 1 when we reach the index that the least
+ * significant bits of n want to be placed at, and it stays 1
+ * thereafter. */
+ indicator |= 1 ^ normalise_to_1(i ^ word_index);
+
+ /* If indicator is 1, we add the low bits of n into r, and
+ * shift n down. If it's 0, we add zero bits into r, and
+ * leave n alone. */
+ BignumInt bword = n & -(BignumInt)indicator;
+ uintmax_t new_n = shift_right_by_one_word(n);
+ n ^= (n ^ new_n) & -(uintmax_t)indicator;
+
+ BignumInt aword = mp_word(a, i);
+ BignumInt out;
+ BignumADC(out, carry, aword, bword, carry);
+ r->w[i] = out;
+ }
+}
+
+void mp_mul_integer_into(mp_int *r, mp_int *a, uint16_t n)
+{
+ BignumInt carry = 0, mult = n;
+ for (size_t i = 0; i < r->nw; i++) {
+ BignumInt aword = mp_word(a, i);
+ BignumMULADD(carry, r->w[i], aword, mult, carry);
+ }
+ assert(!carry);
+}
+
+void mp_cond_add_into(mp_int *r, mp_int *a, mp_int *b, unsigned yes)
+{
+ BignumInt mask = -(BignumInt)(yes & 1);
+ mp_add_masked_into(r->w, r->nw, a, b, mask, 0, 0);
+}
+
+void mp_cond_sub_into(mp_int *r, mp_int *a, mp_int *b, unsigned yes)
+{
+ BignumInt mask = -(BignumInt)(yes & 1);
+ mp_add_masked_into(r->w, r->nw, a, b, mask, mask, 1 & mask);
+}
+
+/*
+ * Ordered comparison between unsigned numbers is done by subtracting
+ * one from the other and looking at the output carry.
+ */
+unsigned mp_cmp_hs(mp_int *a, mp_int *b)
+{
+ size_t rw = size_t_max(a->nw, b->nw);
+ return mp_add_masked_into(NULL, rw, a, b, ~(BignumInt)0, ~(BignumInt)0, 1);
+}
+
+unsigned mp_hs_integer(mp_int *x, uintmax_t n)
+{
+ BignumInt carry = 1;
+ size_t nwords = sizeof(n)/BIGNUM_INT_BYTES;
+ for (size_t i = 0, e = size_t_max(x->nw, nwords); i < e; i++) {
+ BignumInt nword = n;
+ n = shift_right_by_one_word(n);
+ BignumInt dummy_out;
+ BignumADC(dummy_out, carry, mp_word(x, i), ~nword, carry);
+ (void)dummy_out;
+ }
+ return carry;
+}
+
+/*
+ * Equality comparison is done by bitwise XOR of the input numbers,
+ * ORing together all the output words, and normalising the result
+ * using our careful normalise_to_1 helper function.
+ */
+unsigned mp_cmp_eq(mp_int *a, mp_int *b)
+{
+ BignumInt diff = 0;
+ for (size_t i = 0, limit = size_t_max(a->nw, b->nw); i < limit; i++)
+ diff |= mp_word(a, i) ^ mp_word(b, i);
+ return 1 ^ normalise_to_1(diff); /* return 1 if diff _is_ zero */
+}
+
+unsigned mp_eq_integer(mp_int *x, uintmax_t n)
+{
+ BignumInt diff = 0;
+ size_t nwords = sizeof(n)/BIGNUM_INT_BYTES;
+ for (size_t i = 0, e = size_t_max(x->nw, nwords); i < e; i++) {
+ BignumInt nword = n;
+ n = shift_right_by_one_word(n);
+ diff |= mp_word(x, i) ^ nword;
+ }
+ return 1 ^ normalise_to_1(diff); /* return 1 if diff _is_ zero */
+}
+
+static void mp_neg_into(mp_int *r, mp_int *a)
+{
+ mp_int zero;
+ zero.nw = 0;
+ mp_sub_into(r, &zero, a);
+}
+
+mp_int *mp_add(mp_int *x, mp_int *y)
+{
+ mp_int *r = mp_make_sized(size_t_max(x->nw, y->nw) + 1);
+ mp_add_into(r, x, y);
+ return r;
+}
+
+mp_int *mp_sub(mp_int *x, mp_int *y)
+{
+ mp_int *r = mp_make_sized(size_t_max(x->nw, y->nw));
+ mp_sub_into(r, x, y);
+ return r;
+}
+
+/*
+ * Internal routine: multiply and accumulate in the trivial O(N^2)
+ * way. Sets r <- r + a*b.
+ */
+static void mp_mul_add_simple(mp_int *r, mp_int *a, mp_int *b)
+{
+ BignumInt *aend = a->w + a->nw, *bend = b->w + b->nw, *rend = r->w + r->nw;
+
+ for (BignumInt *ap = a->w, *rp = r->w;
+ ap < aend && rp < rend; ap++, rp++) {
+
+ BignumInt adata = *ap, carry = 0, *rq = rp;
+
+ for (BignumInt *bp = b->w; bp < bend && rq < rend; bp++, rq++) {
+ BignumInt bdata = bp < bend ? *bp : 0;
+ BignumMULADD2(carry, *rq, adata, bdata, *rq, carry);
+ }
+
+ for (; rq < rend; rq++)
+ BignumADC(*rq, carry, carry, *rq, 0);
+ }
+}
+
+#ifndef KARATSUBA_THRESHOLD /* allow redefinition via -D for testing */
+#define KARATSUBA_THRESHOLD 24
+#endif
+
+static inline size_t mp_mul_scratchspace_unary(size_t n)
+{
+ /*
+ * Simplistic and overcautious bound on the amount of scratch
+ * space that the recursive multiply function will need.
+ *
+ * The rationale is: on the main Karatsuba branch of
+ * mp_mul_internal, which is the most space-intensive one, we
+ * allocate space for (a0+a1) and (b0+b1) (each just over half the
+ * input length n) and their product (the sum of those sizes, i.e.
+ * just over n itself). Then in order to actually compute the
+ * product, we do a recursive multiplication of size just over n.
+ *
+ * If all those 'just over' weren't there, and everything was
+ * _exactly_ half the length, you'd get the amount of space for a
+ * size-n multiply defined by the recurrence M(n) = 2n + M(n/2),
+ * which is satisfied by M(n) = 4n. But instead it's (2n plus a
+ * word or two) and M(n/2 plus a word or two). On the assumption
+ * that there's still some constant k such that M(n) <= kn, this
+ * gives us kn = 2n + w + k(n/2 + w), where w is a small constant
+ * (one or two words). That simplifies to kn/2 = 2n + (k+1)w, and
+ * since we don't even _start_ needing scratch space until n is at
+ * least 50, we can bound 2n + (k+1)w above by 3n, giving k=6.
+ *
+ * So I claim that 6n words of scratch space will suffice, and I
+ * check that by assertion at every stage of the recursion.
+ */
+ return n * 6;
+}
+
+static size_t mp_mul_scratchspace(size_t rw, size_t aw, size_t bw)
+{
+ size_t inlen = size_t_min(rw, size_t_max(aw, bw));
+ return mp_mul_scratchspace_unary(inlen);
+}
+
+static void mp_mul_internal(mp_int *r, mp_int *a, mp_int *b, mp_int scratch)
+{
+ size_t inlen = size_t_min(r->nw, size_t_max(a->nw, b->nw));
+ assert(scratch.nw >= mp_mul_scratchspace_unary(inlen));
+
+ mp_clear(r);
+
+ if (inlen < KARATSUBA_THRESHOLD || a->nw == 0 || b->nw == 0) {
+ /*
+ * The input numbers are too small to bother optimising. Go
+ * straight to the simple primitive approach.
+ */
+ mp_mul_add_simple(r, a, b);
+ return;
+ }
+
+ /*
+ * Karatsuba divide-and-conquer algorithm. We cut each input in
+ * half, so that it's expressed as two big 'digits' in a giant
+ * base D:
+ *
+ * a = a_1 D + a_0
+ * b = b_1 D + b_0
+ *
+ * Then the product is of course
+ *
+ * ab = a_1 b_1 D^2 + (a_1 b_0 + a_0 b_1) D + a_0 b_0
+ *
+ * and we compute the three coefficients by recursively calling
+ * ourself to do half-length multiplications.
+ *
+ * The clever bit that makes this worth doing is that we only need
+ * _one_ half-length multiplication for the central coefficient
+ * rather than the two that it obviouly looks like, because we can
+ * use a single multiplication to compute
+ *
+ * (a_1 + a_0) (b_1 + b_0) = a_1 b_1 + a_1 b_0 + a_0 b_1 + a_0 b_0
+ *
+ * and then we subtract the other two coefficients (a_1 b_1 and
+ * a_0 b_0) which we were computing anyway.
+ *
+ * Hence we get to multiply two numbers of length N in about three
+ * times as much work as it takes to multiply numbers of length
+ * N/2, which is obviously better than the four times as much work
+ * it would take if we just did a long conventional multiply.
+ */
+
+ /* Break up the input as botlen + toplen, with botlen >= toplen.
+ * The 'base' D is equal to 2^{botlen * BIGNUM_INT_BITS}. */
+ size_t toplen = inlen / 2;
+ size_t botlen = inlen - toplen;
+
+ /* Alias bignums that address the two halves of a,b, and useful
+ * pieces of r. */
+ mp_int a0 = mp_make_alias(a, 0, botlen);
+ mp_int b0 = mp_make_alias(b, 0, botlen);
+ mp_int a1 = mp_make_alias(a, botlen, toplen);
+ mp_int b1 = mp_make_alias(b, botlen, toplen);
+ mp_int r0 = mp_make_alias(r, 0, botlen*2);
+ mp_int r1 = mp_make_alias(r, botlen, r->nw);
+ mp_int r2 = mp_make_alias(r, botlen*2, r->nw);
+
+ /* Recurse to compute a0*b0 and a1*b1, in their correct positions
+ * in the output bignum. They can't overlap. */
+ mp_mul_internal(&r0, &a0, &b0, scratch);
+ mp_mul_internal(&r2, &a1, &b1, scratch);
+
+ if (r->nw < inlen*2) {
+ /*
+ * The output buffer isn't large enough to require the whole
+ * product, so some of a1*b1 won't have been stored. In that
+ * case we won't try to do the full Karatsuba optimisation;
+ * we'll just recurse again to compute a0*b1 and a1*b0 - or at
+ * least as much of them as the output buffer size requires -
+ * and add each one in.
+ */
+ mp_int s = mp_alloc_from_scratch(
+ &scratch, size_t_min(botlen+toplen, r1.nw));
+
+ mp_mul_internal(&s, &a0, &b1, scratch);
+ mp_add_into(&r1, &r1, &s);
+ mp_mul_internal(&s, &a1, &b0, scratch);
+ mp_add_into(&r1, &r1, &s);
+ return;
+ }
+
+ /* a0+a1 and b0+b1 */
+ mp_int asum = mp_alloc_from_scratch(&scratch, botlen+1);
+ mp_int bsum = mp_alloc_from_scratch(&scratch, botlen+1);
+ mp_add_into(&asum, &a0, &a1);
+ mp_add_into(&bsum, &b0, &b1);
+
+ /* Their product */
+ mp_int product = mp_alloc_from_scratch(&scratch, botlen*2+1);
+ mp_mul_internal(&product, &asum, &bsum, scratch);
+
+ /* Subtract off the outer terms we already have */
+ mp_sub_into(&product, &product, &r0);
+ mp_sub_into(&product, &product, &r2);
+
+ /* And add it in with the right offset. */
+ mp_add_into(&r1, &r1, &product);
+}
+
+void mp_mul_into(mp_int *r, mp_int *a, mp_int *b)
+{
+ mp_int *scratch = mp_make_sized(mp_mul_scratchspace(r->nw, a->nw, b->nw));
+ mp_mul_internal(r, a, b, *scratch);
+ mp_free(scratch);
+}
+
+mp_int *mp_mul(mp_int *x, mp_int *y)
+{
+ mp_int *r = mp_make_sized(x->nw + y->nw);
+ mp_mul_into(r, x, y);
+ return r;
+}
+
+void mp_lshift_fixed_into(mp_int *r, mp_int *a, size_t bits)
+{
+ size_t words = bits / BIGNUM_INT_BITS;
+ size_t bitoff = bits % BIGNUM_INT_BITS;
+
+ for (size_t i = r->nw; i-- > 0 ;) {
+ if (i < words) {
+ r->w[i] = 0;
+ } else {
+ r->w[i] = mp_word(a, i - words);
+ if (bitoff != 0) {
+ r->w[i] <<= bitoff;
+ if (i > words)
+ r->w[i] |= mp_word(a, i - words - 1) >>
+ (BIGNUM_INT_BITS - bitoff);
+ }
+ }
+ }
+}
+
+void mp_rshift_fixed_into(mp_int *r, mp_int *a, size_t bits)
+{
+ size_t words = bits / BIGNUM_INT_BITS;
+ size_t bitoff = bits % BIGNUM_INT_BITS;
+
+ for (size_t i = 0; i < r->nw; i++) {
+ r->w[i] = mp_word(a, i + words);
+ if (bitoff != 0) {
+ r->w[i] >>= bitoff;
+ r->w[i] |= mp_word(a, i + words + 1) << (BIGNUM_INT_BITS - bitoff);
+ }
+ }
+}
+
+mp_int *mp_lshift_fixed(mp_int *x, size_t bits)
+{
+ size_t words = (bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS;
+ mp_int *r = mp_make_sized(x->nw + words);
+ mp_lshift_fixed_into(r, x, bits);
+ return r;
+}
+
+mp_int *mp_rshift_fixed(mp_int *x, size_t bits)
+{
+ size_t words = bits / BIGNUM_INT_BITS;
+ size_t nw = x->nw - size_t_min(x->nw, words);
+ mp_int *r = mp_make_sized(size_t_max(nw, 1));
+ mp_rshift_fixed_into(r, x, bits);
+ return r;
+}
+
+/*
+ * Safe right shift is done using the same technique as
+ * trim_leading_zeroes above: you make an n-word left shift by
+ * composing an appropriate subset of power-of-2-sized shifts, so it
+ * takes log_2(n) loop iterations each of which does a different shift
+ * by a power of 2 words, using the usual bit twiddling to make the
+ * whole shift conditional on the appropriate bit of n.
+ */
+static void mp_rshift_safe_in_place(mp_int *r, size_t bits)
+{
+ size_t wordshift = bits / BIGNUM_INT_BITS;
+ size_t bitshift = bits % BIGNUM_INT_BITS;
+
+ unsigned clear = (r->nw - wordshift) >> (CHAR_BIT * sizeof(size_t) - 1);
+ mp_cond_clear(r, clear);
+
+ for (unsigned bit = 0; r->nw >> bit; bit++) {
+ size_t word_offset = (size_t)1 << bit;
+ BignumInt mask = -(BignumInt)((wordshift >> bit) & 1);
+ for (size_t i = 0; i < r->nw; i++) {
+ BignumInt w = mp_word(r, i + word_offset);
+ r->w[i] ^= (r->w[i] ^ w) & mask;
+ }
+ }
+
+ /*
+ * That's done the shifting by words; now we do the shifting by
+ * bits.
+ */
+ for (unsigned bit = 0; bit < BIGNUM_INT_BITS_BITS; bit++) {
+ unsigned shift = 1 << bit, upshift = BIGNUM_INT_BITS - shift;
+ BignumInt mask = -(BignumInt)((bitshift >> bit) & 1);
+ for (size_t i = 0; i < r->nw; i++) {
+ BignumInt w = ((r->w[i] >> shift) | (mp_word(r, i+1) << upshift));
+ r->w[i] ^= (r->w[i] ^ w) & mask;
+ }
+ }
+}
+
+mp_int *mp_rshift_safe(mp_int *x, size_t bits)
+{
+ mp_int *r = mp_copy(x);
+ mp_rshift_safe_in_place(r, bits);
+ return r;
+}
+
+void mp_rshift_safe_into(mp_int *r, mp_int *x, size_t bits)
+{
+ mp_copy_into(r, x);
+ mp_rshift_safe_in_place(r, bits);
+}
+
+static void mp_lshift_safe_in_place(mp_int *r, size_t bits)
+{
+ size_t wordshift = bits / BIGNUM_INT_BITS;
+ size_t bitshift = bits % BIGNUM_INT_BITS;
+
+ /*
+ * Same strategy as mp_rshift_safe_in_place, but of course the
+ * other way up.
+ */
+
+ unsigned clear = (r->nw - wordshift) >> (CHAR_BIT * sizeof(size_t) - 1);
+ mp_cond_clear(r, clear);
+
+ for (unsigned bit = 0; r->nw >> bit; bit++) {
+ size_t word_offset = (size_t)1 << bit;
+ BignumInt mask = -(BignumInt)((wordshift >> bit) & 1);
+ for (size_t i = r->nw; i-- > 0 ;) {
+ BignumInt w = mp_word(r, i - word_offset);
+ r->w[i] ^= (r->w[i] ^ w) & mask;
+ }
+ }
+
+ size_t downshift = BIGNUM_INT_BITS - bitshift;
+ size_t no_shift = (downshift >> BIGNUM_INT_BITS_BITS);
+ downshift &= ~-(size_t)no_shift;
+ BignumInt downshifted_mask = ~-(BignumInt)no_shift;
+
+ for (size_t i = r->nw; i-- > 0 ;) {
+ r->w[i] = (r->w[i] << bitshift) |
+ ((mp_word(r, i-1) >> downshift) & downshifted_mask);
+ }
+}
+
+void mp_lshift_safe_into(mp_int *r, mp_int *x, size_t bits)
+{
+ mp_copy_into(r, x);
+ mp_lshift_safe_in_place(r, bits);
+}
+
+void mp_reduce_mod_2to(mp_int *x, size_t p)
+{
+ size_t word = p / BIGNUM_INT_BITS;
+ size_t mask = ((size_t)1 << (p % BIGNUM_INT_BITS)) - 1;
+ for (; word < x->nw; word++) {
+ x->w[word] &= mask;
+ mask = 0;
+ }
+}
+
+/*
+ * Inverse mod 2^n is computed by an iterative technique which doubles
+ * the number of bits at each step.
+ */
+mp_int *mp_invert_mod_2to(mp_int *x, size_t p)
+{
+ /* Input checks: x must be coprime to the modulus, i.e. odd, and p
+ * can't be zero */
+ assert(x->nw > 0);
+ assert(x->w[0] & 1);
+ assert(p > 0);
+
+ size_t rw = (p + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS;
+ rw = size_t_max(rw, 1);
+ mp_int *r = mp_make_sized(rw);
+
+ size_t mul_scratchsize = mp_mul_scratchspace(2*rw, rw, rw);
+ mp_int *scratch_orig = mp_make_sized(6 * rw + mul_scratchsize);
+ mp_int scratch_per_iter = *scratch_orig;
+ mp_int mul_scratch = mp_alloc_from_scratch(
+ &scratch_per_iter, mul_scratchsize);
+
+ r->w[0] = 1;
+
+ for (size_t b = 1; b < p; b <<= 1) {
+ /*
+ * In each step of this iteration, we have the inverse of x
+ * mod 2^b, and we want the inverse of x mod 2^{2b}.
+ *
+ * Write B = 2^b for convenience, so we want x^{-1} mod B^2.
+ * Let x = x_0 + B x_1 + k B^2, with 0 <= x_0,x_1 < B.
+ *
+ * We want to find r_0 and r_1 such that
+ * (r_1 B + r_0) (x_1 B + x_0) == 1 (mod B^2)
+ *
+ * To begin with, we know r_0 must be the inverse mod B of
+ * x_0, i.e. of x, i.e. it is the inverse we computed in the
+ * previous iteration. So now all we need is r_1.
+ *
+ * Multiplying out, neglecting multiples of B^2, and writing
+ * x_0 r_0 = K B + 1, we have
+ *
+ * r_1 x_0 B + r_0 x_1 B + K B == 0 (mod B^2)
+ * => r_1 x_0 B == - r_0 x_1 B - K B (mod B^2)
+ * => r_1 x_0 == - r_0 x_1 - K (mod B)
+ * => r_1 == r_0 (- r_0 x_1 - K) (mod B)
+ *
+ * (the last step because we multiply through by the inverse
+ * of x_0, which we already know is r_0).
+ */
+
+ mp_int scratch_this_iter = scratch_per_iter;
+ size_t Bw = (b + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS;
+ size_t B2w = (2*b + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS;
+
+ /* Start by finding K: multiply x_0 by r_0, and shift down. */
+ mp_int x0 = mp_alloc_from_scratch(&scratch_this_iter, Bw);
+ mp_copy_into(&x0, x);
+ mp_reduce_mod_2to(&x0, b);
+ mp_int r0 = mp_make_alias(r, 0, Bw);
+ mp_int Kshift = mp_alloc_from_scratch(&scratch_this_iter, B2w);
+ mp_mul_internal(&Kshift, &x0, &r0, mul_scratch);
+ mp_int K = mp_alloc_from_scratch(&scratch_this_iter, Bw);
+ mp_rshift_fixed_into(&K, &Kshift, b);
+
+ /* Now compute the product r_0 x_1, reusing the space of Kshift. */
+ mp_int x1 = mp_alloc_from_scratch(&scratch_this_iter, Bw);
+ mp_rshift_fixed_into(&x1, x, b);
+ mp_reduce_mod_2to(&x1, b);
+ mp_int r0x1 = mp_make_alias(&Kshift, 0, Bw);
+ mp_mul_internal(&r0x1, &r0, &x1, mul_scratch);
+
+ /* Add K to that. */
+ mp_add_into(&r0x1, &r0x1, &K);
+
+ /* Negate it. */
+ mp_neg_into(&r0x1, &r0x1);
+
+ /* Multiply by r_0. */
+ mp_int r1 = mp_alloc_from_scratch(&scratch_this_iter, Bw);
+ mp_mul_internal(&r1, &r0, &r0x1, mul_scratch);
+ mp_reduce_mod_2to(&r1, b);
+
+ /* That's our r_1, so add it on to r_0 to get the full inverse
+ * output from this iteration. */
+ mp_lshift_fixed_into(&K, &r1, (b % BIGNUM_INT_BITS));
+ size_t Bpos = b / BIGNUM_INT_BITS;
+ mp_int r1_position = mp_make_alias(r, Bpos, B2w-Bpos);
+ mp_add_into(&r1_position, &r1_position, &K);
+ }
+
+ /* Finally, reduce mod the precise desired number of bits. */
+ mp_reduce_mod_2to(r, p);
+
+ mp_free(scratch_orig);
+ return r;
+}
+
+static size_t monty_scratch_size(MontyContext *mc)
+{
+ return 3*mc->rw + mc->pw + mp_mul_scratchspace(mc->pw, mc->rw, mc->rw);
+}
+
+MontyContext *monty_new(mp_int *modulus)
+{
+ MontyContext *mc = snew(MontyContext);
+
+ mc->rw = modulus->nw;
+ mc->rbits = mc->rw * BIGNUM_INT_BITS;
+ mc->pw = mc->rw * 2 + 1;
+
+ mc->m = mp_copy(modulus);
+
+ mc->minus_minv_mod_r = mp_invert_mod_2to(mc->m, mc->rbits);
+ mp_neg_into(mc->minus_minv_mod_r, mc->minus_minv_mod_r);
+
+ mp_int *r = mp_make_sized(mc->rw + 1);
+ r->w[mc->rw] = 1;
+ mc->powers_of_r_mod_m[0] = mp_mod(r, mc->m);
+ mp_free(r);
+
+ for (size_t j = 1; j < lenof(mc->powers_of_r_mod_m); j++)
+ mc->powers_of_r_mod_m[j] = mp_modmul(
+ mc->powers_of_r_mod_m[0], mc->powers_of_r_mod_m[j-1], mc->m);
+
+ mc->scratch = mp_make_sized(monty_scratch_size(mc));
+
+ return mc;
+}
+
+void monty_free(MontyContext *mc)
+{
+ mp_free(mc->m);
+ for (size_t j = 0; j < 3; j++)
+ mp_free(mc->powers_of_r_mod_m[j]);
+ mp_free(mc->minus_minv_mod_r);
+ mp_free(mc->scratch);
+ smemclr(mc, sizeof(*mc));
+ sfree(mc);
+}
+
+/*
+ * The main Montgomery reduction step.
+ */
+static mp_int monty_reduce_internal(MontyContext *mc, mp_int *x, mp_int scratch)
+{
+ /*
+ * The trick with Montgomery reduction is that on the one hand we
+ * want to reduce the size of the input by a factor of about r,
+ * and on the other hand, the two numbers we just multiplied were
+ * both stored with an extra factor of r multiplied in. So we
+ * computed ar*br = ab r^2, but we want to return abr, so we need
+ * to divide by r - and if we can do that by _actually dividing_
+ * by r then this also reduces the size of the number.
+ *
+ * But we can only do that if the number we're dividing by r is a
+ * multiple of r. So first we must add an adjustment to it which
+ * clears its bottom 'rbits' bits. That adjustment must be a
+ * multiple of m in order to leave the residue mod n unchanged, so
+ * the question is, what multiple of m can we add to x to make it
+ * congruent to 0 mod r? And the answer is, x * (-m)^{-1} mod r.
+ */
+
+ /* x mod r */
+ mp_int x_lo = mp_make_alias(x, 0, mc->rbits);
+
+ /* x * (-m)^{-1}, i.e. the number we want to multiply by m */
+ mp_int k = mp_alloc_from_scratch(&scratch, mc->rw);
+ mp_mul_internal(&k, &x_lo, mc->minus_minv_mod_r, scratch);
+
+ /* m times that, i.e. the number we want to add to x */
+ mp_int mk = mp_alloc_from_scratch(&scratch, mc->pw);
+ mp_mul_internal(&mk, mc->m, &k, scratch);
+
+ /* Add it to x */
+ mp_add_into(&mk, x, &mk);
+
+ /* Reduce mod r, by simply making an alias to the upper words of x */
+ mp_int toret = mp_make_alias(&mk, mc->rw, mk.nw - mc->rw);
+
+ /*
+ * We'll generally be doing this after a multiplication of two
+ * fully reduced values. So our input could be anything up to m^2,
+ * and then we added up to rm to it. Hence, the maximum value is
+ * rm+m^2, and after dividing by r, that becomes r + m(m/r) < 2r.
+ * So a single trial-subtraction will finish reducing to the
+ * interval [0,m).
+ */
+ mp_cond_sub_into(&toret, &toret, mc->m, mp_cmp_hs(&toret, mc->m));
+ return toret;
+}
+
+void monty_mul_into(MontyContext *mc, mp_int *r, mp_int *x, mp_int *y)
+{
+ assert(x->nw <= mc->rw);
+ assert(y->nw <= mc->rw);
+
+ mp_int scratch = *mc->scratch;
+ mp_int tmp = mp_alloc_from_scratch(&scratch, 2*mc->rw);
+ mp_mul_into(&tmp, x, y);
+ mp_int reduced = monty_reduce_internal(mc, &tmp, scratch);
+ mp_copy_into(r, &reduced);
+ mp_clear(mc->scratch);
+}
+
+mp_int *monty_mul(MontyContext *mc, mp_int *x, mp_int *y)
+{
+ mp_int *toret = mp_make_sized(mc->rw);
+ monty_mul_into(mc, toret, x, y);
+ return toret;
+}
+
+mp_int *monty_modulus(MontyContext *mc)
+{
+ return mc->m;
+}
+
+mp_int *monty_identity(MontyContext *mc)
+{
+ return mc->powers_of_r_mod_m[0];
+}
+
+mp_int *monty_invert(MontyContext *mc, mp_int *x)
+{
+ /* Given xr, we want to return x^{-1}r = (xr)^{-1} r^2 =
+ * monty_reduce((xr)^{-1} r^3) */
+ mp_int *tmp = mp_invert(x, mc->m);
+ mp_int *toret = monty_mul(mc, tmp, mc->powers_of_r_mod_m[2]);
+ mp_free(tmp);
+ return toret;
+}
+
+/*
+ * Importing a number into Montgomery representation involves
+ * multiplying it by r and reducing mod m. We use the general-purpose
+ * mp_modmul for this, in case the input number is out of range.
+ */
+mp_int *monty_import(MontyContext *mc, mp_int *x)
+{
+ return mp_modmul(x, mc->powers_of_r_mod_m[0], mc->m);
+}
+
+void monty_import_into(MontyContext *mc, mp_int *r, mp_int *x)
+{
+ mp_int *imported = monty_import(mc, x);
+ mp_copy_into(r, imported);
+ mp_free(imported);
+}
+
+/*
+ * Exporting a number means multiplying it by r^{-1}, which is exactly
+ * what monty_reduce does anyway, so we just do that.
+ */
+void monty_export_into(MontyContext *mc, mp_int *r, mp_int *x)
+{
+ assert(x->nw <= 2*mc->rw);
+ mp_int reduced = monty_reduce_internal(mc, x, *mc->scratch);
+ mp_copy_into(r, &reduced);
+ mp_clear(mc->scratch);
+}
+
+mp_int *monty_export(MontyContext *mc, mp_int *x)
+{
+ mp_int *toret = mp_make_sized(mc->rw);
+ monty_export_into(mc, toret, x);
+ return toret;
+}
+
+#define MODPOW_LOG2_WINDOW_SIZE 5
+#define MODPOW_WINDOW_SIZE (1 << MODPOW_LOG2_WINDOW_SIZE)
+mp_int *monty_pow(MontyContext *mc, mp_int *base, mp_int *exponent)
+{
+ /*
+ * Modular exponentiation is done from the top down, using a
+ * fixed-window technique.
+ *
+ * We have a table storing every power of the base from base^0 up
+ * to base^{w-1}, where w is a small power of 2, say 2^k. (k is
+ * defined above as MODPOW_LOG2_WINDOW_SIZE, and w = 2^k is
+ * defined as MODPOW_WINDOW_SIZE.)
+ *
+ * We break the exponent up into k-bit chunks, from the bottom up,
+ * that is
+ *
+ * exponent = c_0 + 2^k c_1 + 2^{2k} c_2 + ... + 2^{nk} c_n
+ *
+ * and we compute base^exponent by computing in turn
+ *
+ * base^{c_n}
+ * base^{2^k c_n + c_{n-1}}
+ * base^{2^{2k} c_n + 2^k c_{n-1} + c_{n-2}}
+ * ...
+ *
+ * where each line is obtained by raising the previous line to the
+ * power 2^k (i.e. squaring it k times) and then multiplying in
+ * a value base^{c_i}, which we can look up in our table.
+ *
+ * Side-channel considerations: the exponent is secret, so
+ * actually doing a single table lookup by using a chunk of
+ * exponent bits as an array index would be an obvious leak of
+ * secret information into the cache. So instead, in each
+ * iteration, we read _all_ the table entries, and do a sequence
+ * of mp_select operations to leave just the one we wanted in the
+ * variable that will go into the multiplication. In other
+ * contexts (like software AES) that technique is so prohibitively
+ * slow that it makes you choose a strategy that doesn't use table
+ * lookups at all (we do bitslicing in preference); but here, this
+ * iteration through 2^k table elements is replacing k-1 bignum
+ * _multiplications_ that you'd have to use instead if you did
+ * simple square-and-multiply, and that makes it still a win.
+ */
+
+ /* Table that holds base^0, ..., base^{w-1} */
+ mp_int *table[MODPOW_WINDOW_SIZE];
+ table[0] = mp_copy(monty_identity(mc));
+ for (size_t i = 1; i < MODPOW_WINDOW_SIZE; i++)
+ table[i] = monty_mul(mc, table[i-1], base);
+
+ /* out accumulates the output value */
+ mp_int *out = mp_make_sized(mc->rw);
+ mp_copy_into(out, monty_identity(mc));
+
+ /* table_entry will hold each value we get out of the table */
+ mp_int *table_entry = mp_make_sized(mc->rw);
+
+ /* Bit index of the chunk of bits we're working on. Start with the
+ * highest multiple of k strictly less than the size of our
+ * bignum, i.e. the highest-index chunk of bits that might
+ * conceivably contain any nonzero bit. */
+ size_t i = (exponent->nw * BIGNUM_INT_BITS) - 1;
+ i -= i % MODPOW_LOG2_WINDOW_SIZE;
+
+ bool first_iteration = true;
+
+ while (true) {
+ /* Construct the table index */
+ unsigned table_index = 0;
+ for (size_t j = 0; j < MODPOW_LOG2_WINDOW_SIZE; j++)
+ table_index |= mp_get_bit(exponent, i+j) << j;
+
+ /* Iterate through the table to do a side-channel-safe lookup,
+ * ending up with table_entry = table[table_index] */
+ mp_copy_into(table_entry, table[0]);
+ for (size_t j = 1; j < MODPOW_WINDOW_SIZE; j++) {
+ unsigned not_this_one =
+ ((table_index ^ j) + MODPOW_WINDOW_SIZE - 1)
+ >> MODPOW_LOG2_WINDOW_SIZE;
+ mp_select_into(table_entry, table[j], table_entry, not_this_one);
+ }
+
+ if (!first_iteration) {
+ /* Multiply into the output */
+ monty_mul_into(mc, out, out, table_entry);
+ } else {
+ /* On the first iteration, we can save one multiplication
+ * by just copying */
+ mp_copy_into(out, table_entry);
+ first_iteration = false;
+ }
+
+ /* If that was the bottommost chunk of bits, we're done */
+ if (i == 0)
+ break;
+
+ /* Otherwise, square k times and go round again. */
+ for (size_t j = 0; j < MODPOW_LOG2_WINDOW_SIZE; j++)
+ monty_mul_into(mc, out, out, out);
+
+ i-= MODPOW_LOG2_WINDOW_SIZE;
+ }
+
+ for (size_t i = 0; i < MODPOW_WINDOW_SIZE; i++)
+ mp_free(table[i]);
+ mp_free(table_entry);
+ mp_clear(mc->scratch);
+ return out;
+}
+
+mp_int *mp_modpow(mp_int *base, mp_int *exponent, mp_int *modulus)
+{
+ assert(modulus->nw > 0);
+ assert(modulus->w[0] & 1);
+
+ MontyContext *mc = monty_new(modulus);
+ mp_int *m_base = monty_import(mc, base);
+ mp_int *m_out = monty_pow(mc, m_base, exponent);
+ mp_int *out = monty_export(mc, m_out);
+ mp_free(m_base);
+ mp_free(m_out);
+ monty_free(mc);
+ return out;
+}
+
+/*
+ * Given two input integers a,b which are not both even, computes d =
+ * gcd(a,b) and also two integers A,B such that A*a - B*b = d. A,B
+ * will be the minimal non-negative pair satisfying that criterion,
+ * which is equivalent to saying that 0 <= A < b/d and 0 <= B < a/d.
+ *
+ * This algorithm is an adapted form of Stein's algorithm, which
+ * computes gcd(a,b) using only addition and bit shifts (i.e. without
+ * needing general division), using the following rules:
+ *
+ * - if both of a,b are even, divide off a common factor of 2
+ * - if one of a,b (WLOG a) is even, then gcd(a,b) = gcd(a/2,b), so
+ * just divide a by 2
+ * - if both of a,b are odd, then WLOG a>b, and gcd(a,b) =
+ * gcd(b,(a-b)/2).
+ *
+ * Sometimes this function is used for modular inversion, in which
+ * case we already know we expect the two inputs to be coprime, so to
+ * save time the 'both even' initial case is assumed not to arise (or
+ * to have been handled already by the caller). So this function just
+ * performs a sequence of reductions in the following form:
+ *
+ * - if a,b are both odd, sort them so that a > b, and replace a with
+ * b-a; otherwise sort them so that a is the even one
+ * - either way, now a is even and b is odd, so divide a by 2.
+ *
+ * The big change to Stein's algorithm is that we need the Bezout
+ * coefficients as output, not just the gcd. So we need to know how to
+ * generate those in each case, based on the coefficients from the
+ * reduced pair of numbers:
+ *
+ * - If a is even, and u,v are such that u*(a/2) + v*b = d:
+ * + if u is also even, then this is just (u/2)*a + v*b = d
+ * + otherwise, (u+b)*(a/2) + (v-a/2)*b is also equal to d, and
+ * since u and b are both odd, (u+b)/2 is an integer, so we have
+ * ((u+b)/2)*a + (v-a/2)*b = d.
+ *
+ * - If a,b are both odd, and u,v are such that u*b + v*(a-b) = d,
+ * then v*a + (u-v)*b = d.
+ *
+ * In the case where we passed from (a,b) to (b,(a-b)/2), we regard it
+ * as having first subtracted b from a and then halved a, so both of
+ * these transformations must be done in sequence.
+ *
+ * The code below transforms this from a recursive to an iterative
+ * algorithm. We first reduce a,b to 0,1, recording at each stage
+ * whether we did the initial subtraction, and whether we had to swap
+ * the two values; then we iterate backwards over that record of what
+ * we did, applying the above rules for building up the Bezout
+ * coefficients as we go. Of course, all the case analysis is done by
+ * the usual bit-twiddling conditionalisation to avoid data-dependent
+ * control flow.
+ *
+ * Also, since these mp_ints are generally treated as unsigned, we
+ * store the coefficients by absolute value, with the semantics that
+ * they always have opposite sign, and in the unwinding loop we keep a
+ * bit indicating whether Aa-Bb is currently expected to be +d or -d,
+ * so that we can do one final conditional adjustment if it's -d.
+ *
+ * Once the reduction rules have managed to reduce the input numbers
+ * to (0,d), then they are stable (the next reduction will always
+ * divide the even one by 2, which maps 0 to 0). So it doesn't matter
+ * if we do more steps of the algorithm than necessary; hence, for
+ * constant time, we just need to find the maximum number we could
+ * _possibly_ require, and do that many.
+ *
+ * If a,b < 2^n, at most 2n iterations are required. Proof: consider
+ * the quantity Q = log_2(a) + log_2(b). Every step halves one of the
+ * numbers (and may also reduce one of them further by doing a
+ * subtraction beforehand, but in the worst case, not by much or not
+ * at all). So Q reduces by at least 1 per iteration, and it starts
+ * off with a value at most 2n.
+ *
+ * The worst case inputs (I think) are where x=2^{n-1} and y=2^n-1
+ * (i.e. x is a power of 2 and y is all 1s). In that situation, the
+ * first n-1 steps repeatedly halve x until it's 1, and then there are
+ * n further steps each of which subtracts 1 from y and halves it.
+ */
+static void mp_bezout_into(mp_int *a_coeff_out, mp_int *b_coeff_out,
+ mp_int *gcd_out, mp_int *a_in, mp_int *b_in)
+{
+ size_t nw = size_t_max(1, size_t_max(a_in->nw, b_in->nw));
+
+ /* Make mutable copies of the input numbers */
+ mp_int *a = mp_make_sized(nw), *b = mp_make_sized(nw);
+ mp_copy_into(a, a_in);
+ mp_copy_into(b, b_in);
+
+ /* Space to build up the output coefficients, with an extra word
+ * so that intermediate values can overflow off the top and still
+ * right-shift back down to the correct value */
+ mp_int *ac = mp_make_sized(nw + 1), *bc = mp_make_sized(nw + 1);
+
+ /* And a general-purpose temp register */
+ mp_int *tmp = mp_make_sized(nw);
+
+ /* Space to record the sequence of reduction steps to unwind. We
+ * make it a BignumInt for no particular reason except that (a)
+ * mp_make_sized conveniently zeroes the allocation and mp_free
+ * wipes it, and (b) this way I can use mp_dump() if I have to
+ * debug this code. */
+ size_t steps = 2 * nw * BIGNUM_INT_BITS;
+ mp_int *record = mp_make_sized(
+ (steps*2 + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS);
+
+ for (size_t step = 0; step < steps; step++) {
+ /*
+ * If a and b are both odd, we want to sort them so that a is
+ * larger. But if one is even, we want to sort them so that a
+ * is the even one.
+ */
+ unsigned swap_if_both_odd = mp_cmp_hs(b, a);
+ unsigned swap_if_one_even = a->w[0] & 1;
+ unsigned both_odd = a->w[0] & b->w[0] & 1;
+ unsigned swap = swap_if_one_even ^ (
+ (swap_if_both_odd ^ swap_if_one_even) & both_odd);
+
+ mp_cond_swap(a, b, swap);
+
+ /*
+ * If a,b are both odd, then a is the larger number, so
+ * subtract the smaller one from it.
+ */
+ mp_cond_sub_into(a, a, b, both_odd);
+
+ /*
+ * Now a is even, so divide it by two.
+ */
+ mp_rshift_fixed_into(a, a, 1);
+
+ /*
+ * Record the two 1-bit values both_odd and swap.
+ */
+ mp_set_bit(record, step*2, both_odd);
+ mp_set_bit(record, step*2+1, swap);
+ }
+
+ /*
+ * Now we expect to have reduced the two numbers to 0 and d,
+ * although we don't know which way round. (But we avoid checking
+ * this by assertion; sometimes we'll need to do this computation
+ * without giving away that we already know the inputs were bogus.
+ * So we'd prefer to just press on and return nonsense.)
+ */
+
+ if (gcd_out) {
+ /*
+ * At this point we can return the actual gcd. Since one of
+ * a,b is it and the other is zero, the easiest way to get it
+ * is to add them together.
+ */
+ mp_add_into(gcd_out, a, b);
+ }
+
+ /*
+ * If the caller _only_ wanted the gcd, and neither Bezout
+ * coefficient is even required, we can skip the entire unwind
+ * stage.
+ */
+ if (a_coeff_out || b_coeff_out) {
+
+ /*
+ * The Bezout coefficients of a,b at this point are simply 0
+ * for whichever of a,b is zero, and 1 for whichever is
+ * nonzero. The nonzero number equals gcd(a,b), which by
+ * assumption is odd, so we can do this by just taking the low
+ * bit of each one.
+ */
+ ac->w[0] = mp_get_bit(a, 0);
+ bc->w[0] = mp_get_bit(b, 0);
+
+ /*
+ * Overwrite a,b themselves with those same numbers. This has
+ * the effect of dividing both of them by d, which will
+ * arrange that during the unwind stage we generate the
+ * minimal coefficients instead of a larger pair.
+ */
+ mp_copy_into(a, ac);
+ mp_copy_into(b, bc);
+
+ /*
+ * We'll maintain the invariant as we unwind that ac * a - bc
+ * * b is either +d or -d (or rather, +1/-1 after scaling by
+ * d), and we'll remember which. (We _could_ keep it at +d the
+ * whole time, but it would cost more work every time round
+ * the loop, so it's cheaper to fix that up once at the end.)
+ *
+ * Initially, the result is +d if a was the nonzero value after
+ * reduction, and -d if b was.
+ */
+ unsigned minus_d = b->w[0];
+
+ for (size_t step = steps; step-- > 0 ;) {
+ /*
+ * Recover the data from the step we're unwinding.
+ */
+ unsigned both_odd = mp_get_bit(record, step*2);
+ unsigned swap = mp_get_bit(record, step*2+1);
+
+ /*
+ * Unwind the division: if our coefficient of a is odd, we
+ * adjust the coefficients by +b and +a respectively.
+ */
+ unsigned adjust = ac->w[0] & 1;
+ mp_cond_add_into(ac, ac, b, adjust);
+ mp_cond_add_into(bc, bc, a, adjust);
+
+ /*
+ * Now ac is definitely even, so we divide it by two.
+ */
+ mp_rshift_fixed_into(ac, ac, 1);
+
+ /*
+ * Now unwind the subtraction, if there was one, by adding
+ * ac to bc.
+ */
+ mp_cond_add_into(bc, bc, ac, both_odd);
+
+ /*
+ * Undo the transformation of the input numbers, by
+ * multiplying a by 2 and then adding b to a (the latter
+ * only if both_odd).
+ */
+ mp_lshift_fixed_into(a, a, 1);
+ mp_cond_add_into(a, a, b, both_odd);
+
+ /*
+ * Finally, undo the swap. If we do swap, this also
+ * reverses the sign of the current result ac*a+bc*b.
+ */
+ mp_cond_swap(a, b, swap);
+ mp_cond_swap(ac, bc, swap);
+ minus_d ^= swap;
+ }
+
+ /*
+ * Now we expect to have recovered the input a,b (or rather,
+ * the versions of them divided by d). But we might find that
+ * our current result is -d instead of +d, that is, we have
+ * A',B' such that A'a - B'b = -d.
+ *
+ * In that situation, we set A = b-A' and B = a-B', giving us
+ * Aa-Bb = ab - A'a - ab + B'b = +1.
+ */
+ mp_sub_into(tmp, b, ac);
+ mp_select_into(ac, ac, tmp, minus_d);
+ mp_sub_into(tmp, a, bc);
+ mp_select_into(bc, bc, tmp, minus_d);
+
+ /*
+ * Now we really are done. Return the outputs.
+ */
+ if (a_coeff_out)
+ mp_copy_into(a_coeff_out, ac);
+ if (b_coeff_out)
+ mp_copy_into(b_coeff_out, bc);
+
+ }
+
+ mp_free(a);
+ mp_free(b);
+ mp_free(ac);
+ mp_free(bc);
+ mp_free(tmp);
+ mp_free(record);
+}
+
+mp_int *mp_invert(mp_int *x, mp_int *m)
+{
+ mp_int *result = mp_make_sized(m->nw);
+ mp_bezout_into(result, NULL, NULL, x, m);
+ return result;
+}
+
+void mp_gcd_into(mp_int *a, mp_int *b, mp_int *gcd, mp_int *A, mp_int *B)
+{
+ /*
+ * Identify shared factors of 2. To do this we OR the two numbers
+ * to get something whose lowest set bit is in the right place,
+ * remove all higher bits by ANDing it with its own negation, and
+ * use mp_get_nbits to find the location of the single remaining
+ * set bit.
+ */
+ mp_int *tmp = mp_make_sized(size_t_max(a->nw, b->nw));
+ for (size_t i = 0; i < tmp->nw; i++)
+ tmp->w[i] = mp_word(a, i) | mp_word(b, i);
+ BignumCarry carry = 1;
+ for (size_t i = 0; i < tmp->nw; i++) {
+ BignumInt negw;
+ BignumADC(negw, carry, 0, ~tmp->w[i], carry);
+ tmp->w[i] &= negw;
+ }
+ size_t shift = mp_get_nbits(tmp) - 1;
+ mp_free(tmp);
+
+ /*
+ * Make copies of a,b with those shared factors of 2 divided off,
+ * so that at least one is odd (which is the precondition for
+ * mp_bezout_into). Compute the gcd of those.
+ */
+ mp_int *as = mp_rshift_safe(a, shift);
+ mp_int *bs = mp_rshift_safe(b, shift);
+ mp_bezout_into(A, B, gcd, as, bs);
+ mp_free(as);
+ mp_free(bs);
+
+ /*
+ * And finally shift the gcd back up (unless the caller didn't
+ * even ask for it), to put the shared factors of 2 back in.
+ */
+ if (gcd)
+ mp_lshift_safe_in_place(gcd, shift);
+}
+
+mp_int *mp_gcd(mp_int *a, mp_int *b)
+{
+ mp_int *gcd = mp_make_sized(size_t_min(a->nw, b->nw));
+ mp_gcd_into(a, b, gcd, NULL, NULL);
+ return gcd;
+}
+
+unsigned mp_coprime(mp_int *a, mp_int *b)
+{
+ mp_int *gcd = mp_gcd(a, b);
+ unsigned toret = mp_eq_integer(gcd, 1);
+ mp_free(gcd);
+ return toret;
+}
+
+static uint32_t recip_approx_32(uint32_t x)
+{
+ /*
+ * Given an input x in [2^31,2^32), i.e. a uint32_t with its high
+ * bit set, this function returns an approximation to 2^63/x,
+ * computed using only multiplications and bit shifts just in case
+ * the C divide operator has non-constant time (either because the
+ * underlying machine instruction does, or because the operator
+ * expands to a library function on a CPU without hardware
+ * division).
+ *
+ * The coefficients are derived from those of the degree-9
+ * polynomial which is the minimax-optimal approximation to that
+ * function on the given interval (generated using the Remez
+ * algorithm), converted into integer arithmetic with shifts used
+ * to maximise the number of significant bits at every state. (A
+ * sort of 'static floating point' - the exponent is statically
+ * known at every point in the code, so it never needs to be
+ * stored at run time or to influence runtime decisions.)
+ *
+ * Exhaustive iteration over the whole input space shows the
+ * largest possible error to be 1686.54. (The input value
+ * attaining that bound is 4226800006 == 0xfbefd986, whose true
+ * reciprocal is 2182116973.540... == 0x8210766d.8a6..., whereas
+ * this function returns 2182115287 == 0x82106fd7.)
+ */
+ uint64_t r = 0x92db03d6ULL;
+ r = 0xf63e71eaULL - ((r*x) >> 34);
+ r = 0xb63721e8ULL - ((r*x) >> 34);
+ r = 0x9c2da00eULL - ((r*x) >> 33);
+ r = 0xaada0bb8ULL - ((r*x) >> 32);
+ r = 0xf75cd403ULL - ((r*x) >> 31);
+ r = 0xecf97a41ULL - ((r*x) >> 31);
+ r = 0x90d876cdULL - ((r*x) >> 31);
+ r = 0x6682799a0ULL - ((r*x) >> 26);
+ return r;
+}
+
+void mp_divmod_into(mp_int *n, mp_int *d, mp_int *q_out, mp_int *r_out)
+{
+ assert(!mp_eq_integer(d, 0));
+
+ /*
+ * We do division by using Newton-Raphson iteration to converge to
+ * the reciprocal of d (or rather, R/d for R a sufficiently large
+ * power of 2); then we multiply that reciprocal by n; and we
+ * finish up with conditional subtraction.
+ *
+ * But we have to do it in a fixed number of N-R iterations, so we
+ * need some error analysis to know how many we might need.
+ *
+ * The iteration is derived by defining f(r) = d - R/r.
+ * Differentiating gives f'(r) = R/r^2, and the Newton-Raphson
+ * formula applied to those functions gives
+ *
+ * r_{i+1} = r_i - f(r_i) / f'(r_i)
+ * = r_i - (d - R/r_i) r_i^2 / R
+ * = r_i (2 R - d r_i) / R
+ *
+ * Now let e_i be the error in a given iteration, in the sense
+ * that
+ *
+ * d r_i = R + e_i
+ * i.e. e_i/R = (r_i - r_true) / r_true
+ *
+ * so e_i is the _relative_ error in r_i.
+ *
+ * We must also introduce a rounding-error term, because the
+ * division by R always gives an integer. This might make the
+ * output off by up to 1 (in the negative direction, because
+ * right-shifting gives floor of the true quotient). So when we
+ * divide by R, we must imagine adding some f in [0,1). Then we
+ * have
+ *
+ * d r_{i+1} = d r_i (2 R - d r_i) / R - d f
+ * = (R + e_i) (R - e_i) / R - d f
+ * = (R^2 - e_i^2) / R - d f
+ * = R - (e_i^2 / R + d f)
+ * => e_{i+1} = - (e_i^2 / R + d f)
+ *
+ * The sum of two positive quantities is bounded above by twice
+ * their max, and max |f| = 1, so we can bound this as follows:
+ *
+ * |e_{i+1}| <= 2 max (e_i^2/R, d)
+ * |e_{i+1}/R| <= 2 max ((e_i/R)^2, d/R)
+ * log2 |R/e_{i+1}| <= min (2 log2 |R/e_i|, log2 |R/d|) - 1
+ *
+ * which tells us that the number of 'good' bits - i.e.
+ * log2(R/e_i) - very nearly doubles at every iteration (apart
+ * from that subtraction of 1), until it gets to the same size as
+ * log2(R/d). In other words, the size of R in bits has to be the
+ * size of denominator we're putting in, _plus_ the amount of
+ * precision we want to get back out.
+ *
+ * So when we multiply n (the input numerator) by our final
+ * reciprocal approximation r, but actually r differs from R/d by
+ * up to 2, then it follows that
+ *
+ * n/d - nr/R = n/d - [ n (R/d + e) ] / R
+ * = n/d - [ (n/d) R + n e ] / R
+ * = -ne/R
+ * => 0 <= n/d - nr/R < 2n/R
+ *
+ * so our computed quotient can differ from the true n/d by up to
+ * 2n/R. Hence, as long as we also choose R large enough that 2n/R
+ * is bounded above by a constant, we can guarantee a bounded
+ * number of final conditional-subtraction steps.
+ */
+
+ /*
+ * Get at least 32 of the most significant bits of the input
+ * number.
+ */
+ size_t hiword_index = 0;
+ uint64_t hibits = 0, lobits = 0;
+ mp_find_highest_nonzero_word_pair(d, 64 - BIGNUM_INT_BITS,
+ &hiword_index, &hibits, &lobits);
+
+ /*
+ * Make a shifted combination of those two words which puts the
+ * topmost bit of the number at bit 63.
+ */
+ size_t shift_up = 0;
+ for (size_t i = BIGNUM_INT_BITS_BITS; i-- > 0;) {
+ size_t sl = (size_t)1 << i; /* left shift count */
+ size_t sr = 64 - sl; /* complementary right-shift count */
+
+ /* Should we shift up? */
+ unsigned indicator = 1 ^ normalise_to_1_u64(hibits >> sr);
+
+ /* If we do, what will we get? */
+ uint64_t new_hibits = (hibits << sl) | (lobits >> sr);
+ uint64_t new_lobits = lobits << sl;
+ size_t new_shift_up = shift_up + sl;
+
+ /* Conditionally swap those values in. */
+ hibits ^= (hibits ^ new_hibits ) & -(uint64_t)indicator;
+ lobits ^= (lobits ^ new_lobits ) & -(uint64_t)indicator;
+ shift_up ^= (shift_up ^ new_shift_up ) & -(size_t) indicator;
+ }
+
+ /*
+ * So now we know the most significant 32 bits of d are at the top
+ * of hibits. Approximate the reciprocal of those bits.
+ */
+ lobits = (uint64_t)recip_approx_32(hibits >> 32) << 32;
+ hibits = 0;
+
+ /*
+ * And shift that up by as many bits as the input was shifted up
+ * just now, so that the product of this approximation and the
+ * actual input will be close to a fixed power of two regardless
+ * of where the MSB was.
+ *
+ * I do this in another log n individual passes, partly in case
+ * the CPU's register-controlled shift operation isn't
+ * time-constant, and also in case the compiler code-generates
+ * uint64_t shifts out of a variable number of smaller-word shift
+ * instructions, e.g. by splitting up into cases.
+ */
+ for (size_t i = BIGNUM_INT_BITS_BITS; i-- > 0;) {
+ size_t sl = (size_t)1 << i; /* left shift count */
+ size_t sr = 64 - sl; /* complementary right-shift count */
+
+ /* Should we shift up? */
+ unsigned indicator = 1 & (shift_up >> i);
+
+ /* If we do, what will we get? */
+ uint64_t new_hibits = (hibits << sl) | (lobits >> sr);
+ uint64_t new_lobits = lobits << sl;
+
+ /* Conditionally swap those values in. */
+ hibits ^= (hibits ^ new_hibits ) & -(uint64_t)indicator;
+ lobits ^= (lobits ^ new_lobits ) & -(uint64_t)indicator;
+ }
+
+ /*
+ * The product of the 128-bit value now in hibits:lobits with the
+ * 128-bit value we originally retrieved in the same variables
+ * will be in the vicinity of 2^191. So we'll take log2(R) to be
+ * 191, plus a multiple of BIGNUM_INT_BITS large enough to allow R
+ * to hold the combined sizes of n and d.
+ */
+ size_t log2_R;
+ {
+ size_t max_log2_n = (n->nw + d->nw) * BIGNUM_INT_BITS;
+ log2_R = max_log2_n + 3;
+ log2_R -= size_t_min(191, log2_R);
+ log2_R = (log2_R + BIGNUM_INT_BITS - 1) & ~(BIGNUM_INT_BITS - 1);
+ log2_R += 191;
+ }
+
+ /* Number of words in a bignum capable of holding numbers the size
+ * of twice R. */
+ size_t rw = ((log2_R+2) + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS;
+
+ /*
+ * Now construct our full-sized starting reciprocal approximation.
+ */
+ mp_int *r_approx = mp_make_sized(rw);
+ size_t output_bit_index;
+ {
+ /* Where in the input number did the input 128-bit value come from? */
+ size_t input_bit_index =
+ (hiword_index * BIGNUM_INT_BITS) - (128 - BIGNUM_INT_BITS);
+
+ /* So how far do we need to shift our 64-bit output, if the
+ * product of those two fixed-size values is 2^191 and we want
+ * to make it 2^log2_R instead? */
+ output_bit_index = log2_R - 191 - input_bit_index;
+
+ /* If we've done all that right, it should be a whole number
+ * of words. */
+ assert(output_bit_index % BIGNUM_INT_BITS == 0);
+ size_t output_word_index = output_bit_index / BIGNUM_INT_BITS;
+
+ mp_add_integer_into_shifted_by_words(
+ r_approx, r_approx, lobits, output_word_index);
+ mp_add_integer_into_shifted_by_words(
+ r_approx, r_approx, hibits,
+ output_word_index + 64 / BIGNUM_INT_BITS);
+ }
+
+ /*
+ * Make the constant 2*R, which we'll need in the iteration.
+ */
+ mp_int *two_R = mp_make_sized(rw);
+ BignumInt top_word = (BignumInt)1 << ((log2_R+1) % BIGNUM_INT_BITS);
+ mp_add_integer_into_shifted_by_words(
+ two_R, two_R, top_word, (log2_R+1) / BIGNUM_INT_BITS);
+
+ /*
+ * Scratch space.
+ */
+ mp_int *dr = mp_make_sized(rw + d->nw);
+ mp_int *diff = mp_make_sized(size_t_max(rw, dr->nw));
+ mp_int *product = mp_make_sized(rw + diff->nw);
+ size_t scratchsize = size_t_max(
+ mp_mul_scratchspace(dr->nw, r_approx->nw, d->nw),
+ mp_mul_scratchspace(product->nw, r_approx->nw, diff->nw));
+ mp_int *scratch = mp_make_sized(scratchsize);
+ mp_int product_shifted = mp_make_alias(
+ product, log2_R / BIGNUM_INT_BITS, product->nw);
+
+ /*
+ * Initial error estimate: the 32-bit output of recip_approx_32
+ * differs by less than 2048 (== 2^11) from the true top 32 bits
+ * of the reciprocal, so the relative error is at most 2^11
+ * divided by the 32-bit reciprocal, which at worst is 2^11/2^31 =
+ * 2^-20. So even in the worst case, we have 20 good bits of
+ * reciprocal to start with.
+ */
+ size_t good_bits = 31 - 11;
+ size_t good_bits_needed = BIGNUM_INT_BITS * n->nw + 4; /* add a few */
+
+ /*
+ * Now do Newton-Raphson iterations until we have reason to think
+ * they're not converging any more.
+ */
+ while (good_bits < good_bits_needed) {
+ /*
+ * Compute the next iterate.
+ */
+ mp_mul_internal(dr, r_approx, d, *scratch);
+ mp_sub_into(diff, two_R, dr);
+ mp_mul_internal(product, r_approx, diff, *scratch);
+ mp_rshift_fixed_into(r_approx, &product_shifted,
+ log2_R % BIGNUM_INT_BITS);
+
+ /*
+ * Adjust the error estimate.
+ */
+ good_bits = good_bits * 2 - 1;
+ }
+
+ mp_free(dr);
+ mp_free(diff);
+ mp_free(product);
+ mp_free(scratch);
+
+ /*
+ * Now we've got our reciprocal, we can compute the quotient, by
+ * multiplying in n and then shifting down by log2_R bits.
+ */
+ mp_int *quotient_full = mp_mul(r_approx, n);
+ mp_int quotient_alias = mp_make_alias(
+ quotient_full, log2_R / BIGNUM_INT_BITS, quotient_full->nw);
+ mp_int *quotient = mp_make_sized(n->nw);
+ mp_rshift_fixed_into(quotient, &quotient_alias, log2_R % BIGNUM_INT_BITS);
+
+ /*
+ * Next, compute the remainder.
+ */
+ mp_int *remainder = mp_make_sized(d->nw);
+ mp_mul_into(remainder, quotient, d);
+ mp_sub_into(remainder, n, remainder);
+
+ /*
+ * Finally, two conditional subtractions to fix up any remaining
+ * rounding error. (I _think_ one should be enough, but this
+ * routine isn't time-critical enough to take chances.)
+ */
+ unsigned q_correction = 0;
+ for (unsigned iter = 0; iter < 2; iter++) {
+ unsigned need_correction = mp_cmp_hs(remainder, d);
+ mp_cond_sub_into(remainder, remainder, d, need_correction);
+ q_correction += need_correction;
+ }
+ mp_add_integer_into(quotient, quotient, q_correction);
+
+ /*
+ * Now we should have a perfect answer, i.e. 0 <= r < d.
+ */
+ assert(!mp_cmp_hs(remainder, d));
+
+ if (q_out)
+ mp_copy_into(q_out, quotient);
+ if (r_out)
+ mp_copy_into(r_out, remainder);
+
+ mp_free(r_approx);
+ mp_free(two_R);
+ mp_free(quotient_full);
+ mp_free(quotient);
+ mp_free(remainder);
+}
+
+mp_int *mp_div(mp_int *n, mp_int *d)
+{
+ mp_int *q = mp_make_sized(n->nw);
+ mp_divmod_into(n, d, q, NULL);
+ return q;
+}
+
+mp_int *mp_mod(mp_int *n, mp_int *d)
+{
+ mp_int *r = mp_make_sized(d->nw);
+ mp_divmod_into(n, d, NULL, r);
+ return r;
+}
+
+uint32_t mp_mod_known_integer(mp_int *x, uint32_t m)
+{
+ uint64_t reciprocal = ((uint64_t)1 << 48) / m;
+ uint64_t accumulator = 0;
+ for (size_t i = mp_max_bytes(x); i-- > 0 ;) {
+ accumulator = 0x100 * accumulator + mp_get_byte(x, i);
+ /*
+ * Let A be the value in 'accumulator' at this point, and let
+ * R be the value it will have after we subtract quot*m below.
+ *
+ * Lemma 1: if A < 2^48, then R < 2m.
+ *
+ * Proof:
+ *
+ * By construction, we have 2^48/m - 1 < reciprocal <= 2^48/m.
+ * Multiplying that by the accumulator gives
+ *
+ * A/m * 2^48 - A < unshifted_quot <= A/m * 2^48
+ * i.e. 0 <= (A/m * 2^48) - unshifted_quot < A
+ * i.e. 0 <= A/m - unshifted_quot/2^48 < A/2^48
+ *
+ * So when we shift this quotient right by 48 bits, i.e. take
+ * the floor of (unshifted_quot/2^48), the value we take the
+ * floor of is at most A/2^48 less than the true rational
+ * value A/m that we _wanted_ to take the floor of.
+ *
+ * Provided A < 2^48, this is less than 1. So the quotient
+ * 'quot' that we've just produced is either the true quotient
+ * floor(A/m), or one less than it. Hence, the output value R
+ * is less than 2m. []
+ *
+ * Lemma 2: if A < 2^16 m, then the multiplication of
+ * accumulator*reciprocal does not overflow.
+ *
+ * Proof: as above, we have reciprocal <= 2^48/m. Multiplying
+ * by A gives unshifted_quot <= 2^48 * A / m < 2^48 * 2^16 =
+ * 2^64. []
+ */
+ uint64_t unshifted_quot = accumulator * reciprocal;
+ uint64_t quot = unshifted_quot >> 48;
+ accumulator -= quot * m;
+ }
+
+ /*
+ * Theorem 1: accumulator < 2m at the end of every iteration of
+ * this loop.
+ *
+ * Proof: induction on the above loop.
+ *
+ * Base case: at the start of the first loop iteration, the
+ * accumulator is 0, which is certainly < 2m.
+ *
+ * Inductive step: in each loop iteration, we take a value at most
+ * 2m-1, multiply it by 2^8, and add another byte less than 2^8 to
+ * generate the input value A to the reduction process above. So
+ * we have A < 2m * 2^8 - 1. We know m < 2^32 (because it was
+ * passed in as a uint32_t), so A < 2^41, which is enough to allow
+ * us to apply Lemma 1, showing that the value of 'accumulator' at
+ * the end of the loop is still < 2m. []
+ *
+ * Corollary: we need at most one final subtraction of m to
+ * produce the canonical residue of x mod m, i.e. in the range
+ * [0,m).
+ *
+ * Theorem 2: no multiplication in the inner loop overflows.
+ *
+ * Proof: in Theorem 1 we established A < 2m * 2^8 - 1 in every
+ * iteration. That is less than m * 2^16, so Lemma 2 applies.
+ *
+ * The other multiplication, of quot * m, cannot overflow because
+ * quot is at most A/m, so quot*m <= A < 2^64. []
+ */
+
+ uint32_t result = accumulator;
+ uint32_t reduced = result - m;
+ uint32_t select = -(reduced >> 31);
+ result = reduced ^ ((result ^ reduced) & select);
+ assert(result < m);
+ return result;
+}
+
+mp_int *mp_nthroot(mp_int *y, unsigned n, mp_int *remainder_out)
+{
+ /*
+ * Allocate scratch space.
+ */
+ mp_int **alloc, **powers, **newpowers, *scratch;
+ size_t nalloc = 2*(n+1)+1;
+ alloc = snewn(nalloc, mp_int *);
+ for (size_t i = 0; i < nalloc; i++)
+ alloc[i] = mp_make_sized(y->nw + 1);
+ powers = alloc;
+ newpowers = alloc + (n+1);
+ scratch = alloc[2*n+2];
+
+ /*
+ * We're computing the rounded-down nth root of y, i.e. the
+ * maximal x such that x^n <= y. We try to add 2^i to it for each
+ * possible value of i, starting from the largest one that might
+ * fit (i.e. such that 2^{n*i} fits in the size of y) downwards to
+ * i=0.
+ *
+ * We track all the smaller powers of x in the array 'powers'. In
+ * each iteration, if we update x, we update all of those values
+ * to match.
+ */
+ mp_copy_integer_into(powers[0], 1);
+ for (size_t s = mp_max_bits(y) / n + 1; s-- > 0 ;) {
+ /*
+ * Let b = 2^s. We need to compute the powers (x+b)^i for each
+ * i, starting from our recorded values of x^i.
+ */
+ for (size_t i = 0; i < n+1; i++) {
+ /*
+ * (x+b)^i = x^i
+ * + (i choose 1) x^{i-1} b
+ * + (i choose 2) x^{i-2} b^2
+ * + ...
+ * + b^i
+ */
+ uint16_t binom = 1; /* coefficient of b^i */
+ mp_copy_into(newpowers[i], powers[i]);
+ for (size_t j = 0; j < i; j++) {
+ /* newpowers[i] += binom * powers[j] * 2^{(i-j)*s} */
+ mp_mul_integer_into(scratch, powers[j], binom);
+ mp_lshift_fixed_into(scratch, scratch, (i-j) * s);
+ mp_add_into(newpowers[i], newpowers[i], scratch);
+
+ uint32_t binom_mul = binom;
+ binom_mul *= (i-j);
+ binom_mul /= (j+1);
+ assert(binom_mul < 0x10000);
+ binom = binom_mul;
+ }
+ }
+
+ /*
+ * Now, is the new value of x^n still <= y? If so, update.
+ */
+ unsigned newbit = mp_cmp_hs(y, newpowers[n]);
+ for (size_t i = 0; i < n+1; i++)
+ mp_select_into(powers[i], powers[i], newpowers[i], newbit);
+ }
+
+ if (remainder_out)
+ mp_sub_into(remainder_out, y, powers[n]);
+
+ mp_int *root = mp_new(mp_max_bits(y) / n);
+ mp_copy_into(root, powers[1]);
+
+ for (size_t i = 0; i < nalloc; i++)
+ mp_free(alloc[i]);
+ sfree(alloc);
+
+ return root;
+}
+
+mp_int *mp_modmul(mp_int *x, mp_int *y, mp_int *modulus)
+{
+ mp_int *product = mp_mul(x, y);
+ mp_int *reduced = mp_mod(product, modulus);
+ mp_free(product);
+ return reduced;
+}
+
+mp_int *mp_modadd(mp_int *x, mp_int *y, mp_int *modulus)
+{
+ mp_int *sum = mp_add(x, y);
+ mp_int *reduced = mp_mod(sum, modulus);
+ mp_free(sum);
+ return reduced;
+}
+
+mp_int *mp_modsub(mp_int *x, mp_int *y, mp_int *modulus)
+{
+ mp_int *diff = mp_make_sized(size_t_max(x->nw, y->nw));
+ mp_sub_into(diff, x, y);
+ unsigned negate = mp_cmp_hs(y, x);
+ mp_cond_negate(diff, diff, negate);
+ mp_int *residue = mp_mod(diff, modulus);
+ mp_cond_negate(residue, residue, negate);
+ /* If we've just negated the residue, then it will be < 0 and need
+ * the modulus adding to it to make it positive - *except* if the
+ * residue was zero when we negated it. */
+ unsigned make_positive = negate & ~mp_eq_integer(residue, 0);
+ mp_cond_add_into(residue, residue, modulus, make_positive);
+ mp_free(diff);
+ return residue;
+}
+
+static mp_int *mp_modadd_in_range(mp_int *x, mp_int *y, mp_int *modulus)
+{
+ mp_int *sum = mp_make_sized(modulus->nw);
+ unsigned carry = mp_add_into_internal(sum, x, y);
+ mp_cond_sub_into(sum, sum, modulus, carry | mp_cmp_hs(sum, modulus));
+ return sum;
+}
+
+static mp_int *mp_modsub_in_range(mp_int *x, mp_int *y, mp_int *modulus)
+{
+ mp_int *diff = mp_make_sized(modulus->nw);
+ mp_sub_into(diff, x, y);
+ mp_cond_add_into(diff, diff, modulus, 1 ^ mp_cmp_hs(x, y));
+ return diff;
+}
+
+mp_int *monty_add(MontyContext *mc, mp_int *x, mp_int *y)
+{
+ return mp_modadd_in_range(x, y, mc->m);
+}
+
+mp_int *monty_sub(MontyContext *mc, mp_int *x, mp_int *y)
+{
+ return mp_modsub_in_range(x, y, mc->m);
+}
+
+void mp_min_into(mp_int *r, mp_int *x, mp_int *y)
+{
+ mp_select_into(r, x, y, mp_cmp_hs(x, y));
+}
+
+void mp_max_into(mp_int *r, mp_int *x, mp_int *y)
+{
+ mp_select_into(r, y, x, mp_cmp_hs(x, y));
+}
+
+mp_int *mp_min(mp_int *x, mp_int *y)
+{
+ mp_int *r = mp_make_sized(size_t_min(x->nw, y->nw));
+ mp_min_into(r, x, y);
+ return r;
+}
+
+mp_int *mp_max(mp_int *x, mp_int *y)
+{
+ mp_int *r = mp_make_sized(size_t_max(x->nw, y->nw));
+ mp_max_into(r, x, y);
+ return r;
+}
+
+mp_int *mp_power_2(size_t power)
+{
+ mp_int *x = mp_new(power + 1);
+ mp_set_bit(x, power, 1);
+ return x;
+}
+
+struct ModsqrtContext {
+ mp_int *p; /* the prime */
+ MontyContext *mc; /* for doing arithmetic mod p */
+
+ /* Decompose p-1 as 2^e k, for positive integer e and odd k */
+ size_t e;
+ mp_int *k;
+ mp_int *km1o2; /* (k-1)/2 */
+
+ /* The user-provided value z which is not a quadratic residue mod
+ * p, and its kth power. Both in Montgomery form. */
+ mp_int *z, *zk;
+};
+
+ModsqrtContext *modsqrt_new(mp_int *p, mp_int *any_nonsquare_mod_p)
+{
+ ModsqrtContext *sc = snew(ModsqrtContext);
+ memset(sc, 0, sizeof(ModsqrtContext));
+
+ sc->p = mp_copy(p);
+ sc->mc = monty_new(sc->p);
+ sc->z = monty_import(sc->mc, any_nonsquare_mod_p);
+
+ /* Find the lowest set bit in p-1. Since this routine expects p to
+ * be non-secret (typically a well-known standard elliptic curve
+ * parameter), for once we don't need clever bit tricks. */
+ for (sc->e = 1; sc->e < BIGNUM_INT_BITS * p->nw; sc->e++)
+ if (mp_get_bit(p, sc->e))
+ break;
+
+ sc->k = mp_rshift_fixed(p, sc->e);
+ sc->km1o2 = mp_rshift_fixed(sc->k, 1);
+
+ /* Leave zk to be filled in lazily, since it's more expensive to
+ * compute. If this context turns out never to be needed, we can
+ * save the bulk of the setup time this way. */
+
+ return sc;
+}
+
+static void modsqrt_lazy_setup(ModsqrtContext *sc)
+{
+ if (!sc->zk)
+ sc->zk = monty_pow(sc->mc, sc->z, sc->k);
+}
+
+void modsqrt_free(ModsqrtContext *sc)
+{
+ monty_free(sc->mc);
+ mp_free(sc->p);
+ mp_free(sc->z);
+ mp_free(sc->k);
+ mp_free(sc->km1o2);
+
+ if (sc->zk)
+ mp_free(sc->zk);
+
+ sfree(sc);
+}
+
+mp_int *mp_modsqrt(ModsqrtContext *sc, mp_int *x, unsigned *success)
+{
+ mp_int *mx = monty_import(sc->mc, x);
+ mp_int *mroot = monty_modsqrt(sc, mx, success);
+ mp_free(mx);
+ mp_int *root = monty_export(sc->mc, mroot);
+ mp_free(mroot);
+ return root;
+}
+
+/*
+ * Modular square root, using an algorithm more or less similar to
+ * Tonelli-Shanks but adapted for constant time.
+ *
+ * The basic idea is to write p-1 = k 2^e, where k is odd and e > 0.
+ * Then the multiplicative group mod p (call it G) has a sequence of
+ * e+1 nested subgroups G = G_0 > G_1 > G_2 > ... > G_e, where each
+ * G_i is exactly half the size of G_{i-1} and consists of all the
+ * squares of elements in G_{i-1}. So the innermost group G_e has
+ * order k, which is odd, and hence within that group you can take a
+ * square root by raising to the power (k+1)/2.
+ *
+ * Our strategy is to iterate over these groups one by one and make
+ * sure the number x we're trying to take the square root of is inside
+ * each one, by adjusting it if it isn't.
+ *
+ * Suppose g is a primitive root of p, i.e. a generator of G_0. (We
+ * don't actually need to know what g _is_; we just imagine it for the
+ * sake of understanding.) Then G_i consists of precisely the (2^i)th
+ * powers of g, and hence, you can tell if a number is in G_i if
+ * raising it to the power k 2^{e-i} gives 1. So the conceptual
+ * algorithm goes: for each i, test whether x is in G_i by that
+ * method. If it isn't, then the previous iteration ensured it's in
+ * G_{i-1}, so it will be an odd power of g^{2^{i-1}}, and hence
+ * multiplying by any other odd power of g^{2^{i-1}} will give x' in
+ * G_i. And we have one of those, because our non-square z is an odd
+ * power of g, so z^{2^{i-1}} is an odd power of g^{2^{i-1}}.
+ *
+ * (There's a special case in the very first iteration, where we don't
+ * have a G_{i-1}. If it turns out that x is not even in G_1, that
+ * means it's not a square, so we set *success to 0. We still run the
+ * rest of the algorithm anyway, for the sake of constant time, but we
+ * don't give a hoot what it returns.)
+ *
+ * When we get to the end and have x in G_e, then we can take its
+ * square root by raising to (k+1)/2. But of course that's not the
+ * square root of the original input - it's only the square root of
+ * the adjusted version we produced during the algorithm. To get the
+ * true output answer we also have to multiply by a power of z,
+ * namely, z to the power of _half_ whatever we've been multiplying in
+ * as we go along. (The power of z we multiplied in must have been
+ * even, because the case in which we would have multiplied in an odd
+ * power of z is the i=0 case, in which we instead set the failure
+ * flag.)
+ *
+ * The code below is an optimised version of that basic idea, in which
+ * we _start_ by computing x^k so as to be able to test membership in
+ * G_i by only a few squarings rather than a full from-scratch modpow
+ * every time; we also start by computing our candidate output value
+ * x^{(k+1)/2}. So when the above description says 'adjust x by z^i'
+ * for some i, we have to adjust our running values of x^k and
+ * x^{(k+1)/2} by z^{ik} and z^{ik/2} respectively (the latter is safe
+ * because, as above, i is always even). And it turns out that we
+ * don't actually have to store the adjusted version of x itself at
+ * all - we _only_ keep those two powers of it.
+ */
+mp_int *monty_modsqrt(ModsqrtContext *sc, mp_int *x, unsigned *success)
+{
+ modsqrt_lazy_setup(sc);
+
+ mp_int *scratch_to_free = mp_make_sized(3 * sc->mc->rw);
+ mp_int scratch = *scratch_to_free;
+
+ /*
+ * Compute toret = x^{(k+1)/2}, our starting point for the output
+ * square root, and also xk = x^k which we'll use as we go along
+ * for knowing when to apply correction factors. We do this by
+ * first computing x^{(k-1)/2}, then multiplying it by x, then
+ * multiplying the two together.
+ */
+ mp_int *toret = monty_pow(sc->mc, x, sc->km1o2);
+ mp_int xk = mp_alloc_from_scratch(&scratch, sc->mc->rw);
+ mp_copy_into(&xk, toret);
+ monty_mul_into(sc->mc, toret, toret, x);
+ monty_mul_into(sc->mc, &xk, toret, &xk);
+
+ mp_int tmp = mp_alloc_from_scratch(&scratch, sc->mc->rw);
+
+ mp_int power_of_zk = mp_alloc_from_scratch(&scratch, sc->mc->rw);
+ mp_copy_into(&power_of_zk, sc->zk);
+
+ for (size_t i = 0; i < sc->e; i++) {
+ mp_copy_into(&tmp, &xk);
+ for (size_t j = i+1; j < sc->e; j++)
+ monty_mul_into(sc->mc, &tmp, &tmp, &tmp);
+ unsigned eq1 = mp_cmp_eq(&tmp, monty_identity(sc->mc));
+
+ if (i == 0) {
+ /* One special case: if x=0, then no power of x will ever
+ * equal 1, but we should still report success on the
+ * grounds that 0 does have a square root mod p. */
+ *success = eq1 | mp_eq_integer(x, 0);
+ } else {
+ monty_mul_into(sc->mc, &tmp, toret, &power_of_zk);
+ mp_select_into(toret, &tmp, toret, eq1);
+
+ monty_mul_into(sc->mc, &power_of_zk,
+ &power_of_zk, &power_of_zk);
+
+ monty_mul_into(sc->mc, &tmp, &xk, &power_of_zk);
+ mp_select_into(&xk, &tmp, &xk, eq1);
+ }
+ }
+
+ mp_free(scratch_to_free);
+
+ return toret;
+}
+
+mp_int *mp_random_bits_fn(size_t bits, random_read_fn_t random_read)
+{
+ size_t bytes = (bits + 7) / 8;
+ uint8_t *randbuf = snewn(bytes, uint8_t);
+ random_read(randbuf, bytes);
+ if (bytes)
+ randbuf[0] &= (2 << ((bits-1) & 7)) - 1;
+ mp_int *toret = mp_from_bytes_be(make_ptrlen(randbuf, bytes));
+ smemclr(randbuf, bytes);
+ sfree(randbuf);
+ return toret;
+}
+
+mp_int *mp_random_upto_fn(mp_int *limit, random_read_fn_t rf)
+{
+ /*
+ * It would be nice to generate our random numbers in such a way
+ * as to make every possible outcome literally equiprobable. But
+ * we can't do that in constant time, so we have to go for a very
+ * close approximation instead. I'm going to take the view that a
+ * factor of (1+2^-128) between the probabilities of two outcomes
+ * is acceptable on the grounds that you'd have to examine so many
+ * outputs to even detect it.
+ */
+ mp_int *unreduced = mp_random_bits_fn(mp_max_bits(limit) + 128, rf);
+ mp_int *reduced = mp_mod(unreduced, limit);
+ mp_free(unreduced);
+ return reduced;
+}
+
+mp_int *mp_random_in_range_fn(mp_int *lo, mp_int *hi, random_read_fn_t rf)
+{
+ mp_int *n_outcomes = mp_sub(hi, lo);
+ mp_int *addend = mp_random_upto_fn(n_outcomes, rf);
+ mp_int *result = mp_make_sized(hi->nw);
+ mp_add_into(result, addend, lo);
+ mp_free(addend);
+ mp_free(n_outcomes);
+ return result;
+}
diff --git a/crypto/mpint_i.h b/crypto/mpint_i.h
new file mode 100644
index 00000000..fb2b367c
--- /dev/null
+++ b/crypto/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 <intrin.h>
+ typedef unsigned char BignumCarry; /* the type _addcarry_u64 likes to use */
+ typedef unsigned __int64 BignumInt;
+ #define BIGNUM_INT_BITS_BITS 6
+ #define BignumADC(ret, retc, a, b, c) do \
+ { \
+ BignumInt ADC_tmp; \
+ (retc) = _addcarry_u64(c, a, b, &ADC_tmp); \
+ (ret) = ADC_tmp; \
+ } while (0)
+ #define BignumMUL(rh, rl, a, b) do \
+ { \
+ BignumInt MULADD_hi; \
+ (rl) = _umul128(a, b, &MULADD_hi); \
+ (rh) = MULADD_hi; \
+ } while (0)
+ #define BignumMULADD(rh, rl, a, b, addend) do \
+ { \
+ BignumInt MULADD_lo, MULADD_hi; \
+ MULADD_lo = _umul128(a, b, &MULADD_hi); \
+ MULADD_hi += _addcarry_u64(0, MULADD_lo, (addend), &(rl)); \
+ (rh) = MULADD_hi; \
+ } while (0)
+ #define BignumMULADD2(rh, rl, a, b, addend1, addend2) do \
+ { \
+ BignumInt MULADD_lo1, MULADD_lo2, MULADD_hi; \
+ MULADD_lo1 = _umul128(a, b, &MULADD_hi); \
+ MULADD_hi += _addcarry_u64(0, MULADD_lo1, (addend1), &MULADD_lo2); \
+ MULADD_hi += _addcarry_u64(0, MULADD_lo2, (addend2), &(rl)); \
+ (rh) = MULADD_hi; \
+ } while (0)
+
+#elif (defined __GNUC__ || defined _LLP64 || __STDC__ >= 199901L) && BB_OK(5)
+
+ /* 32-bit BignumInt, using C99 unsigned long long as BignumDblInt */
+
+ typedef unsigned int BignumInt;
+ #define BIGNUM_INT_BITS_BITS 5
+ #define DEFINE_BIGNUMDBLINT typedef unsigned long long BignumDblInt
+
+#elif defined _MSC_VER && BB_OK(5)
+
+ /* 32-bit BignumInt, using Visual Studio __int64 as BignumDblInt */
+
+ typedef unsigned int BignumInt;
+ #define BIGNUM_INT_BITS_BITS 5
+ #define DEFINE_BIGNUMDBLINT typedef unsigned __int64 BignumDblInt
+
+#elif defined _LP64 && BB_OK(5)
+
+ /*
+ * 32-bit BignumInt, using unsigned long itself as BignumDblInt.
+ *
+ * Only for platforms where long is 64 bits, of course.
+ */
+
+ typedef unsigned int BignumInt;
+ #define BIGNUM_INT_BITS_BITS 5
+ #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt
+
+#elif BB_OK(4)
+
+ /*
+ * 16-bit BignumInt, using unsigned long as BignumDblInt.
+ *
+ * This is the final fallback for real emergencies: C89 guarantees
+ * unsigned short/long to be at least the required sizes, so this
+ * should work on any C implementation at all. But it'll be
+ * noticeably slow, so if you find yourself in this case you
+ * probably want to move heaven and earth to find an alternative!
+ */
+
+ typedef unsigned short BignumInt;
+ #define BIGNUM_INT_BITS_BITS 4
+ #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt
+
+#else
+
+ /* Should only get here if BB_OK(4) evaluated false, i.e. the
+ * command line defined BIGNUM_OVERRIDE to an absurdly small
+ * value. */
+ #error Must define BIGNUM_OVERRIDE to at least 4
+
+#endif
+
+#undef BB_OK
+
+/*
+ * Common code across all branches of that ifdef: define all the
+ * easy constant macros in terms of BIGNUM_INT_BITS_BITS.
+ */
+#define BIGNUM_INT_BITS (1 << BIGNUM_INT_BITS_BITS)
+#define BIGNUM_INT_BYTES (BIGNUM_INT_BITS / 8)
+#define BIGNUM_TOP_BIT (((BignumInt)1) << (BIGNUM_INT_BITS-1))
+#define BIGNUM_INT_MASK (BIGNUM_TOP_BIT | (BIGNUM_TOP_BIT-1))
+
+/*
+ * Just occasionally, we might need a GET_nnBIT_xSB_FIRST macro to
+ * operate on whatever BignumInt is.
+ */
+#if BIGNUM_INT_BITS_BITS == 4
+#define GET_BIGNUMINT_MSB_FIRST GET_16BIT_MSB_FIRST
+#define GET_BIGNUMINT_LSB_FIRST GET_16BIT_LSB_FIRST
+#define PUT_BIGNUMINT_MSB_FIRST PUT_16BIT_MSB_FIRST
+#define PUT_BIGNUMINT_LSB_FIRST PUT_16BIT_LSB_FIRST
+#elif BIGNUM_INT_BITS_BITS == 5
+#define GET_BIGNUMINT_MSB_FIRST GET_32BIT_MSB_FIRST
+#define GET_BIGNUMINT_LSB_FIRST GET_32BIT_LSB_FIRST
+#define PUT_BIGNUMINT_MSB_FIRST PUT_32BIT_MSB_FIRST
+#define PUT_BIGNUMINT_LSB_FIRST PUT_32BIT_LSB_FIRST
+#elif BIGNUM_INT_BITS_BITS == 6
+#define GET_BIGNUMINT_MSB_FIRST GET_64BIT_MSB_FIRST
+#define GET_BIGNUMINT_LSB_FIRST GET_64BIT_LSB_FIRST
+#define PUT_BIGNUMINT_MSB_FIRST PUT_64BIT_MSB_FIRST
+#define PUT_BIGNUMINT_LSB_FIRST PUT_64BIT_LSB_FIRST
+#else
+ #error Ran out of options for GET_BIGNUMINT_xSB_FIRST
+#endif
+
+/*
+ * Common code across _most_ branches of the ifdef: define a set of
+ * statement macros in terms of the BignumDblInt type provided. In
+ * this case, we also define BignumCarry to be the same thing as
+ * BignumInt, for simplicity.
+ */
+#ifdef DEFINE_BIGNUMDBLINT
+
+ typedef BignumInt BignumCarry;
+ #define BignumADC(ret, retc, a, b, c) do \
+ { \
+ DEFINE_BIGNUMDBLINT; \
+ BignumDblInt ADC_temp = (BignumInt)(a); \
+ ADC_temp += (BignumInt)(b); \
+ ADC_temp += (c); \
+ (ret) = (BignumInt)ADC_temp; \
+ (retc) = (BignumCarry)(ADC_temp >> BIGNUM_INT_BITS); \
+ } while (0)
+
+ #define BignumMUL(rh, rl, a, b) do \
+ { \
+ DEFINE_BIGNUMDBLINT; \
+ BignumDblInt MUL_temp = (BignumInt)(a); \
+ MUL_temp *= (BignumInt)(b); \
+ (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \
+ (rl) = (BignumInt)(MUL_temp); \
+ } while (0)
+
+ #define BignumMULADD(rh, rl, a, b, addend) do \
+ { \
+ DEFINE_BIGNUMDBLINT; \
+ BignumDblInt MUL_temp = (BignumInt)(a); \
+ MUL_temp *= (BignumInt)(b); \
+ MUL_temp += (BignumInt)(addend); \
+ (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \
+ (rl) = (BignumInt)(MUL_temp); \
+ } while (0)
+
+ #define BignumMULADD2(rh, rl, a, b, addend1, addend2) do \
+ { \
+ DEFINE_BIGNUMDBLINT; \
+ BignumDblInt MUL_temp = (BignumInt)(a); \
+ MUL_temp *= (BignumInt)(b); \
+ MUL_temp += (BignumInt)(addend1); \
+ MUL_temp += (BignumInt)(addend2); \
+ (rh) = (BignumInt)(MUL_temp >> BIGNUM_INT_BITS); \
+ (rl) = (BignumInt)(MUL_temp); \
+ } while (0)
+
+#endif /* DEFINE_BIGNUMDBLINT */
+
+/* ----------------------------------------------------------------------
+ * Data structures used inside mpint.c.
+ */
+
+struct mp_int {
+ size_t nw;
+ BignumInt *w;
+};
+
+struct MontyContext {
+ /*
+ * The actual modulus.
+ */
+ mp_int *m;
+
+ /*
+ * Montgomery multiplication works by selecting a value r > m,
+ * coprime to m, which is really easy to divide by. In binary
+ * arithmetic, that means making it a power of 2; in fact we make
+ * it a whole number of BignumInt.
+ *
+ * We don't store r directly as an mp_int (there's no need). But
+ * its value is 2^rbits; we also store rw = rbits/BIGNUM_INT_BITS
+ * (the corresponding word offset within an mp_int).
+ *
+ * pw is the number of words needed to store an mp_int you're
+ * doing reduction on: it has to be big enough to hold the sum of
+ * an input value up to m^2 plus an extra addend up to m*r.
+ */
+ size_t rbits, rw, pw;
+
+ /*
+ * The key step in Montgomery reduction requires the inverse of -m
+ * mod r.
+ */
+ mp_int *minus_minv_mod_r;
+
+ /*
+ * r^1, r^2 and r^3 mod m, which are used for various purposes.
+ *
+ * (Annoyingly, this is one of the rare cases where it would have
+ * been nicer to have a Pascal-style 1-indexed array. I couldn't
+ * _quite_ bring myself to put a gratuitous zero element in here.
+ * So you just have to live with getting r^k by taking the [k-1]th
+ * element of this array.)
+ */
+ mp_int *powers_of_r_mod_m[3];
+
+ /*
+ * Persistent scratch space from which monty_* functions can
+ * allocate storage for intermediate values.
+ */
+ mp_int *scratch;
+};
+
+/* Functions shared between mpint.c and mpunsafe.c */
+mp_int *mp_make_sized(size_t nw);
diff --git a/crypto/ntru.c b/crypto/ntru.c
new file mode 100644
index 00000000..edc57a91
--- /dev/null
+++ b/crypto/ntru.c
@@ -0,0 +1,1889 @@
+/*
+ * Implementation of OpenSSH 9.x's hybrid key exchange protocol
+ * sntrup761x25519-sha512@openssh.com .
+ *
+ * This consists of the 'Streamlined NTRU Prime' quantum-resistant
+ * cryptosystem, run in parallel with ordinary Curve25519 to generate
+ * a shared secret combining the output of both systems.
+ *
+ * (Hence, even if you don't trust this newfangled NTRU Prime thing at
+ * all, it's at least no _less_ secure than the kex you were using
+ * already.)
+ *
+ * References for the NTRU Prime cryptosystem, up to and including
+ * binary encodings of public and private keys and the exact preimages
+ * of the hashes used in key exchange:
+ *
+ * https://ntruprime.cr.yp.to/
+ * https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf
+ *
+ * The SSH protocol layer is not documented anywhere I could find (as
+ * of 2022-04-15, not even in OpenSSH's PROTOCOL.* files). I had to
+ * read OpenSSH's source code to find out how it worked, and the
+ * answer is as follows:
+ *
+ * This hybrid kex method is treated for SSH purposes as a form of
+ * elliptic-curve Diffie-Hellman, and shares the same SSH message
+ * sequence: client sends SSH2_MSG_KEX_ECDH_INIT containing its public
+ * half, server responds with SSH2_MSG_KEX_ECDH_REPLY containing _its_
+ * public half plus the host key and signature on the shared secret.
+ *
+ * (This is a bit of a fudge, because unlike actual ECDH, this kex
+ * method is asymmetric: one side sends a public key, and the other
+ * side encrypts something with it and sends the ciphertext back. So
+ * while the normal ECDH implementations can compute the two sides
+ * independently in parallel, this system reusing the same messages
+ * has to be serial. But the order of the messages _is_ firmly
+ * specified in SSH ECDH, so it works anyway.)
+ *
+ * For this kex method, SSH2_MSG_KEX_ECDH_INIT still contains a single
+ * SSH 'string', which consists of the concatenation of a Streamlined
+ * NTRU Prime public key with the Curve25519 public value. (Both of
+ * these have fixed length in bytes, so there's no ambiguity in the
+ * concatenation.)
+ *
+ * SSH2_MSG_KEX_ECDH_REPLY is mostly the same as usual. The only
+ * string in the packet that varies is the second one, which would
+ * normally contain the server's public elliptic curve point. Instead,
+ * it now contains the concatenation of
+ *
+ * - a Streamlined NTRU Prime ciphertext
+ * - the 'confirmation hash' specified in ntruprime-20201007.pdf,
+ * hashing the plaintext of that ciphertext together with the
+ * public key
+ * - the Curve25519 public point as usual.
+ *
+ * Again, all three of those elements have fixed lengths.
+ *
+ * The client decrypts the ciphertext, checks the confirmation hash,
+ * and if successful, generates the 'session hash' specified in
+ * ntruprime-20201007.pdf, which is 32 bytes long and is the ultimate
+ * output of the Streamlined NTRU Prime key exchange.
+ *
+ * The output of the hybrid kex method as a whole is an SSH 'string'
+ * of length 64 containing the SHA-512 hash of the concatenatio of
+ *
+ * - the Streamlined NTRU Prime session hash (32 bytes)
+ * - the Curve25519 shared secret (32 bytes).
+ *
+ * That string is included directly into the SSH exchange hash and key
+ * derivation hashes, in place of the mpint that comes out of most
+ * other kex methods.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "mpint.h"
+#include "ntru.h"
+
+/* ----------------------------------------------------------------------
+ * Preliminaries: we're going to need to do modular arithmetic on
+ * small values (considerably smaller than 2^16), and we need to do it
+ * without using integer division which might not be time-safe.
+ *
+ * The strategy for this is the same as I used in
+ * mp_mod_known_integer: see there for the proofs. The basic idea is
+ * that we precompute the reciprocal of our modulus as a fixed-point
+ * number, and use that to get an approximate quotient which we
+ * subtract off. For these integer sizes, precomputing a fixed-point
+ * reciprocal of the form (2^48 / modulus) leaves us at most off by 1
+ * in the quotient, so there's a single (time-safe) trial subtraction
+ * at the end.
+ *
+ * (It's possible that some speed could be gained by not reducing
+ * fully at every step. But then you'd have to carefully identify all
+ * the places in the algorithm where things are compared to zero. This
+ * was the easiest way to get it all working in the first place.)
+ */
+
+/* Precompute the reciprocal */
+static uint64_t reciprocal_for_reduction(uint16_t q)
+{
+ return ((uint64_t)1 << 48) / q;
+}
+
+/* Reduce x mod q, assuming qrecip == reciprocal_for_reduction(q) */
+static uint16_t reduce(uint32_t x, uint16_t q, uint64_t qrecip)
+{
+ uint64_t unshifted_quot = x * qrecip;
+ uint64_t quot = unshifted_quot >> 48;
+ uint16_t reduced = x - quot * q;
+ reduced -= q * (1 & ((q-1 - reduced) >> 15));
+ return reduced;
+}
+
+/* Reduce x mod q as above, but also return the quotient */
+static uint16_t reduce_with_quot(uint32_t x, uint32_t *quot_out,
+ uint16_t q, uint64_t qrecip)
+{
+ uint64_t unshifted_quot = x * qrecip;
+ uint64_t quot = unshifted_quot >> 48;
+ uint16_t reduced = x - quot * q;
+ uint64_t extraquot = (1 & ((q-1 - reduced) >> 15));
+ reduced -= extraquot * q;
+ *quot_out = quot + extraquot;
+ return reduced;
+}
+
+/* Invert x mod q, assuming it's nonzero. (For time-safety, no check
+ * is made for zero; it just returns 0.) */
+static uint16_t invert(uint16_t x, uint16_t q, uint64_t qrecip)
+{
+ /* Fermat inversion: compute x^(q-2), since x^(q-1) == 1. */
+ uint32_t sq = x, bit = 1, acc = 1, exp = q-2;
+ while (1) {
+ if (exp & bit) {
+ acc = reduce(acc * sq, q, qrecip);
+ exp &= ~bit;
+ if (!exp)
+ return acc;
+ }
+ sq = reduce(sq * sq, q, qrecip);
+ bit <<= 1;
+ }
+}
+
+/* Check whether x == 0, time-safely, and return 1 if it is or 0 otherwise. */
+static unsigned iszero(uint16_t x)
+{
+ return 1 & ~((x + 0xFFFF) >> 16);
+}
+
+/*
+ * Handy macros to cut down on all those extra function parameters. In
+ * the common case where a function is working mod the same modulus
+ * throughout (and has called it q), you can just write 'SETUP;' at
+ * the top and then call REDUCE(...) and INVERT(...) without having to
+ * write out q and qrecip every time.
+ */
+#define SETUP uint64_t qrecip = reciprocal_for_reduction(q)
+#define REDUCE(x) reduce(x, q, qrecip)
+#define INVERT(x) invert(x, q, qrecip)
+
+/* ----------------------------------------------------------------------
+ * Quotient-ring functions.
+ *
+ * NTRU Prime works with two similar but different quotient rings:
+ *
+ * Z_q[x] / <x^p-x-1> where p,q are the prime parameters of the system
+ * Z_3[x] / <x^p-x-1> with the same p, but coefficients mod 3.
+ *
+ * The former is a field (every nonzero element is invertible),
+ * because the system parameters are chosen such that x^p-x-1 is
+ * invertible over Z_q. The latter is not a field (or not necessarily,
+ * and in particular, not for the value of p we use here).
+ *
+ * In these core functions, you pass in the modulus you want as the
+ * parameter q, which is either the 'real' q specified in the system
+ * parameters, or 3 if you're doing one of the mod-3 parts of the
+ * algorithm.
+ */
+
+/*
+ * Multiply two elements of a quotient ring.
+ *
+ * 'a' and 'b' are arrays of exactly p coefficients, with constant
+ * term first. 'out' is an array the same size to write the inverse
+ * into.
+ */
+void ntru_ring_multiply(uint16_t *out, const uint16_t *a, const uint16_t *b,
+ unsigned p, unsigned q)
+{
+ SETUP;
+
+ /*
+ * Strategy: just compute the full product with 2p coefficients,
+ * and then reduce it mod x^p-x-1 by working downwards from the
+ * top coefficient replacing x^{p+k} with (x+1)x^k for k = ...,1,0.
+ *
+ * Possibly some speed could be gained here by doing the recursive
+ * Karatsuba optimisation for the initial multiplication? But I
+ * haven't tried it.
+ */
+ uint32_t *unreduced = snewn(2*p, uint32_t);
+ for (unsigned i = 0; i < 2*p; i++)
+ unreduced[i] = 0;
+ for (unsigned i = 0; i < p; i++)
+ for (unsigned j = 0; j < p; j++)
+ unreduced[i+j] = REDUCE(unreduced[i+j] + a[i] * b[j]);
+
+ for (unsigned i = 2*p - 1; i >= p; i--) {
+ unreduced[i-p] += unreduced[i];
+ unreduced[i-p+1] += unreduced[i];
+ unreduced[i] = 0;
+ }
+
+ for (unsigned i = 0; i < p; i++)
+ out[i] = REDUCE(unreduced[i]);
+
+ smemclr(unreduced, 2*p * sizeof(*unreduced));
+ sfree(unreduced);
+}
+
+/*
+ * Invert an element of the quotient ring.
+ *
+ * 'in' is an array of exactly p coefficients, with constant term
+ * first. 'out' is an array the same size to write the inverse into.
+ *
+ * Method: essentially Stein's gcd algorithm, taking the gcd of the
+ * input (regarded as an element of Z_q[x] proper) and x^p-x-1. Given
+ * two polynomials over a field which are not both divisible by x, you
+ * can find their gcd by iterating the following procedure:
+ *
+ * - if one is divisible by x, divide off x
+ * - otherwise, subtract from the higher-degree one whatever scalar
+ * multiple of the lower-degree one will make it divisible by x,
+ * and _then_ divide off x
+ *
+ * Neither of these types of step changes the gcd of the two
+ * polynomials.
+ *
+ * Each step reduces the sum of the two polynomials' degree by at
+ * least one, as long as at least one of the degrees is positive.
+ * (Maybe more than one if all the stars align in the second case, if
+ * the subtraction cancels the leading term as well as the constant
+ * term.) So in at most deg A + deg B steps, we must have reached the
+ * situation where both polys are constants; in one more step after
+ * that, one of them will be zero; and in one step after _that_, the
+ * zero one will reliably be the one we're dividing by x. Or rather,
+ * that's what happens in the case where A,B are coprime; if not, then
+ * one hits zero while the other is still nonzero.
+ *
+ * In a normal gcd algorithm, you'd track a linear combination of the
+ * two original polynomials that yields each working value, and end up
+ * with a linear combination of the inputs that yields the gcd. In
+ * this algorithm, the 'divide off x' step makes that awkward - but we
+ * can solve that by instead multiplying by the inverse of x in the
+ * ring that we want our answer to be valid in! And since the modulus
+ * polynomial of the ring is x^p-x-1, the inverse of x is easy to
+ * calculate, because it's always just x^{p-1} - 1, which is also very
+ * easy to multiply by.
+ */
+unsigned ntru_ring_invert(uint16_t *out, const uint16_t *in,
+ unsigned p, unsigned q)
+{
+ SETUP;
+
+ /* Size of the polynomial arrays we'll work with */
+ const size_t SIZE = p+1;
+
+ /* Number of steps of the algorithm is the max possible value of
+ * deg A + deg B + 2, where deg A <= p-1 and deg B = p */
+ const size_t STEPS = 2*p + 1;
+
+ /* Our two working polynomials */
+ uint16_t *A = snewn(SIZE, uint16_t);
+ uint16_t *B = snewn(SIZE, uint16_t);
+
+ /* Coefficient of the input value in each one */
+ uint16_t *Ac = snewn(SIZE, uint16_t);
+ uint16_t *Bc = snewn(SIZE, uint16_t);
+
+ /* Initialise A to the input, and Ac correspondingly to 1 */
+ memcpy(A, in, p*sizeof(uint16_t));
+ A[p] = 0;
+ Ac[0] = 1;
+ for (size_t i = 1; i < SIZE; i++)
+ Ac[i] = 0;
+
+ /* Initialise B to the quotient polynomial of the ring, x^p-x-1
+ * And Bc = 0 */
+ B[0] = B[1] = q-1;
+ for (size_t i = 2; i < p; i++)
+ B[i] = 0;
+ B[p] = 1;
+ for (size_t i = 0; i < SIZE; i++)
+ Bc[i] = 0;
+
+ /* Run the gcd-finding algorithm. */
+ for (size_t i = 0; i < STEPS; i++) {
+ /*
+ * First swap round so that A is the one we'll be dividing by x.
+ *
+ * In the case where one of the two polys has a zero constant
+ * term, it's that one. In the other case, it's the one of
+ * smaller degree. We must compute both, and choose between
+ * them in a side-channel-safe way.
+ */
+ unsigned x_divides_A = iszero(A[0]);
+ unsigned x_divides_B = iszero(B[0]);
+ unsigned B_is_bigger = 0;
+ {
+ unsigned not_seen_top_term_of_A = 1, not_seen_top_term_of_B = 1;
+ for (size_t j = SIZE; j-- > 0 ;) {
+ not_seen_top_term_of_A &= iszero(A[j]);
+ not_seen_top_term_of_B &= iszero(B[j]);
+ B_is_bigger |= (~not_seen_top_term_of_B &
+ not_seen_top_term_of_A);
+ }
+ }
+ unsigned need_swap = x_divides_B | (~x_divides_A & B_is_bigger);
+ uint16_t swap_mask = -need_swap;
+ for (size_t j = 0; j < SIZE; j++) {
+ uint16_t diff = (A[j] ^ B[j]) & swap_mask;
+ A[j] ^= diff;
+ B[j] ^= diff;
+ }
+ for (size_t j = 0; j < SIZE; j++) {
+ uint16_t diff = (Ac[j] ^ Bc[j]) & swap_mask;
+ Ac[j] ^= diff;
+ Bc[j] ^= diff;
+ }
+
+ /*
+ * Replace A with a linear combination of both A and B that
+ * has constant term zero, which we do by calculating
+ *
+ * (constant term of B) * A - (constant term of A) * B
+ *
+ * In one of the two cases, A's constant term is already zero,
+ * so the coefficient of B will be zero too; hence, this will
+ * do nothing useful (it will merely scale A by some scalar
+ * value), but it will take the same length of time as doing
+ * something, which is just what we want.
+ */
+ uint16_t Amult = B[0], Bmult = q - A[0];
+ for (size_t j = 0; j < SIZE; j++)
+ A[j] = REDUCE(Amult * A[j] + Bmult * B[j]);
+ /* And do the same transformation to Ac */
+ for (size_t j = 0; j < SIZE; j++)
+ Ac[j] = REDUCE(Amult * Ac[j] + Bmult * Bc[j]);
+
+ /*
+ * Now divide A by x, and compensate by multiplying Ac by
+ * x^{p-1}-1 mod x^p-x-1.
+ *
+ * That multiplication is particularly easy, precisely because
+ * x^{p-1}-1 is the multiplicative inverse of x! Each x^n term
+ * for n>0 just moves down to the x^{n-1} term, and only the
+ * constant term has to be dealt with in an interesting way.
+ */
+ for (size_t j = 1; j < SIZE; j++)
+ A[j-1] = A[j];
+ A[SIZE-1] = 0;
+ uint16_t Ac0 = Ac[0];
+ for (size_t j = 1; j < p; j++)
+ Ac[j-1] = Ac[j];
+ Ac[p-1] = Ac0;
+ Ac[0] = REDUCE(Ac[0] + q - Ac0);
+ }
+
+ /*
+ * Now we expect that A is 0, and B is a constant. If so, then
+ * they are coprime, and we're going to return success. If not,
+ * they have a common factor.
+ */
+ unsigned success = iszero(A[0]) & (1 ^ iszero(B[0]));
+ for (size_t j = 1; j < SIZE; j++)
+ success &= iszero(A[j]) & iszero(B[j]);
+
+ /*
+ * So we're going to return Bc, but first, scale it by the
+ * multiplicative inverse of the constant we ended up with in
+ * B[0].
+ */
+ uint16_t scale = INVERT(B[0]);
+ for (size_t i = 0; i < p; i++)
+ out[i] = REDUCE(scale * Bc[i]);
+
+ smemclr(A, SIZE * sizeof(*A));
+ sfree(A);
+ smemclr(B, SIZE * sizeof(*B));
+ sfree(B);
+ smemclr(Ac, SIZE * sizeof(*Ac));
+ sfree(Ac);
+ smemclr(Bc, SIZE * sizeof(*Bc));
+ sfree(Bc);
+
+ return success;
+}
+
+/*
+ * Given an array of values mod q, convert each one to its
+ * minimum-absolute-value representative, and then reduce mod 3.
+ *
+ * Output values are 0, 1 and 0xFFFF, representing -1.
+ *
+ * (Normally our arrays of uint16_t are in 'minimal non-negative
+ * residue' form, so the output of this function is unusual. But it's
+ * useful to have it in this form so that it can be reused by
+ * ntru_round3. You can put it back to the usual representation using
+ * ntru_normalise, below.)
+ */
+void ntru_mod3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q)
+{
+ uint64_t qrecip = reciprocal_for_reduction(q);
+ uint64_t recip3 = reciprocal_for_reduction(3);
+
+ unsigned bias = q/2;
+ uint16_t adjust = 3 - reduce(bias-1, 3, recip3);
+
+ for (unsigned i = 0; i < p; i++) {
+ uint16_t val = reduce(in[i] + bias, q, qrecip);
+ uint16_t residue = reduce(val + adjust, 3, recip3);
+ out[i] = residue - 1;
+ }
+}
+
+/*
+ * Given an array of values mod q, round each one to the nearest
+ * multiple of 3 to its minimum-absolute-value representative.
+ *
+ * Output values are signed integers coerced to uint16_t, so again,
+ * use ntru_normalise afterwards to put them back to normal.
+ */
+void ntru_round3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q)
+{
+ SETUP;
+ unsigned bias = q/2;
+ ntru_mod3(out, in, p, q);
+ for (unsigned i = 0; i < p; i++)
+ out[i] = REDUCE(in[i] + bias) - bias - out[i];
+}
+
+/*
+ * Given an array of signed integers coerced to uint16_t in the range
+ * [-q/2,+q/2], normalise them back to mod q values.
+ */
+static void ntru_normalise(uint16_t *out, const uint16_t *in,
+ unsigned p, unsigned q)
+{
+ for (unsigned i = 0; i < p; i++)
+ out[i] = in[i] + q * (in[i] >> 15);
+}
+
+/*
+ * Given an array of values mod q, add a constant to each one.
+ */
+void ntru_bias(uint16_t *out, const uint16_t *in, unsigned bias,
+ unsigned p, unsigned q)
+{
+ SETUP;
+ for (unsigned i = 0; i < p; i++)
+ out[i] = REDUCE(in[i] + bias);
+}
+
+/*
+ * Given an array of values mod q, multiply each one by a constant.
+ */
+void ntru_scale(uint16_t *out, const uint16_t *in, uint16_t scale,
+ unsigned p, unsigned q)
+{
+ SETUP;
+ for (unsigned i = 0; i < p; i++)
+ out[i] = REDUCE(in[i] * scale);
+}
+
+/*
+ * Given an array of values mod 3, convert them to values mod q in a
+ * way that maps -1,0,+1 to -1,0,+1.
+ */
+static void ntru_expand(
+ uint16_t *out, const uint16_t *in, unsigned p, unsigned q)
+{
+ for (size_t i = 0; i < p; i++) {
+ uint16_t v = in[i];
+ /* Map 2 to q-1, and leave 0 and 1 unchanged */
+ v += (v >> 1) * (q-3);
+ out[i] = v;
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * Implement the binary encoding from ntruprime-20201007.pdf, which is
+ * used to encode public keys and ciphertexts (though not plaintexts,
+ * which are done in a much simpler way).
+ *
+ * The general idea is that your encoder takes as input a list of
+ * small non-negative integers (r_i), and a sequence of limits (m_i)
+ * such that 0 <= r_i < m_i, and emits a sequence of bytes that encode
+ * all of these as tightly as reasonably possible.
+ *
+ * That's more general than is really needed, because in both the
+ * actual uses of this encoding, the input m_i are all the same! But
+ * the array of (r_i,m_i) pairs evolves during encoding, so they don't
+ * _stay_ all the same, so you still have to have all the generality.
+ *
+ * The encoding process makes a number of passes along the list of
+ * inputs. In each step, pairs of adjacent numbers are combined into
+ * one larger one by turning (r_i,m_i) and (r_{i+1},m_{i+1}) into the
+ * pair (r_i + m_i r_{i+1}, m_i m_{i+1}), i.e. so that the original
+ * numbers could be recovered by taking the quotient and remaiinder of
+ * the new r value by m_i. Then, if the new m_i is at least 2^14, we
+ * emit the low 8 bits of r_i to the output stream and reduce r_i and
+ * its limit correspondingly. So at the end of the pass, we've got
+ * half as many numbers still to encode, they're all still not too
+ * big, and we've emitted some amount of data into the output. Then do
+ * another pass, keep going until there's only one number left, and
+ * emit it little-endian.
+ *
+ * That's all very well, but how do you decode it again? DJB exhibits
+ * a pair of recursive functions that are supposed to be mutually
+ * inverse, but I didn't have any confidence that I'd be able to debug
+ * them sensibly if they turned out not to be (or rather, if I
+ * implemented one of them wrong). So I came up with my own strategy
+ * instead.
+ *
+ * In my strategy, we start by processing just the (m_i) into an
+ * 'encoding schedule' consisting of a sequence of simple
+ * instructions. The instructions operate on a FIFO queue of numbers,
+ * initialised to the original (r_i). The three instruction types are:
+ *
+ * - 'COMBINE': consume two numbers a,b from the head of the queue,
+ * combine them by calculating a + m*b for some specified m, and
+ * push the result on the tail of the queue.
+ *
+ * - 'BYTE': divide the tail element of the queue by 2^8 and emit the
+ * low bits into the output stream.
+ *
+ * - 'COPY': pop a number from the head of the queue and push it
+ * straight back on the tail. (Used for handling the leftover
+ * element at the end of a pass if the input to the pass was a list
+ * of odd length.)
+ *
+ * So we effectively implement DJB's encoding process in simulation,
+ * and instead of actually processing a set of (r_i), we 'compile' the
+ * process into a sequence of instructions that can be handed just the
+ * (r_i) later and encode them in the right way. At the end of the
+ * instructions, the queue is expected to have been reduced to length
+ * 1 and contain the single integer 0.
+ *
+ * The nice thing about this system is that each of those three
+ * instructions is easy to reverse. So you can also use the same
+ * instructions for decoding: start with a queue containing 0, and
+ * process the instructions in reverse order and reverse sense. So
+ * BYTE means to _consume_ a byte from the encoded data (starting from
+ * the rightmost end) and use it to make a queue element bigger; and
+ * COMBINE run in reverse pops a single element from one end of the
+ * queue, divides it by m, and pushes the quotient and remainder on
+ * the other end.
+ *
+ * (So it's easy to debug, because the queue passes through the exact
+ * same sequence of states during decoding that it did during
+ * encoding, just in reverse order.)
+ *
+ * Also, the encoding schedule comes with information about the
+ * expected size of the encoded data, because you can find that out
+ * easily by just counting the BYTE commands.
+ */
+
+enum {
+ /*
+ * Command values appearing in the 'ops' array. ENC_COPY and
+ * ENC_BYTE are single values; values of the form
+ * (ENC_COMBINE_BASE + m) represent a COMBINE command with
+ * parameter m.
+ */
+ ENC_COPY, ENC_BYTE, ENC_COMBINE_BASE
+};
+struct NTRUEncodeSchedule {
+ /*
+ * Object representing a compiled set of encoding instructions.
+ *
+ * 'nvals' is the number of r_i we expect to encode. 'nops' is the
+ * number of encoding commands in the 'ops' list; 'opsize' is the
+ * physical size of the array, used during construction.
+ *
+ * 'endpos' is used to avoid a last-minute faff during decoding.
+ * We implement our FIFO of integers as a ring buffer of size
+ * 'nvals'. Encoding cycles round it some number of times, and the
+ * final 0 element ends up at some random location in the array.
+ * If we know _where_ the 0 ends up during encoding, we can put
+ * the initial 0 there at the start of decoding, and then when we
+ * finish reversing all the instructions, we'll end up with the
+ * output numbers already arranged at their correct positions, so
+ * that there's no need to rotate the array at the last minute.
+ */
+ size_t nvals, endpos, nops, opsize;
+ uint32_t *ops;
+};
+static inline void sched_append(NTRUEncodeSchedule *sched, uint16_t op)
+{
+ /* Helper function to append an operation to the schedule, and
+ * update endpos. */
+ sgrowarray(sched->ops, sched->opsize, sched->nops);
+ sched->ops[sched->nops++] = op;
+ if (op != ENC_BYTE)
+ sched->endpos = (sched->endpos + 1) % sched->nvals;
+}
+
+/*
+ * Take in the list of limit values (m_i) and compute the encoding
+ * schedule.
+ */
+NTRUEncodeSchedule *ntru_encode_schedule(const uint16_t *ms_in, size_t n)
+{
+ NTRUEncodeSchedule *sched = snew(NTRUEncodeSchedule);
+ sched->nvals = n;
+ sched->endpos = n-1;
+ sched->nops = sched->opsize = 0;
+ sched->ops = NULL;
+
+ assert(n != 0);
+
+ /*
+ * 'ms' is the list of (m_i) on input to the current pass.
+ * 'ms_new' is the list output from the current pass. After each
+ * pass we swap the arrays round.
+ */
+ uint32_t *ms = snewn(n, uint32_t);
+ uint32_t *msnew = snewn(n, uint32_t);
+ for (size_t i = 0; i < n; i++)
+ ms[i] = ms_in[i];
+
+ while (n > 1) {
+ size_t nnew = 0;
+ for (size_t i = 0; i < n; i += 2) {
+ if (i+1 == n) {
+ /*
+ * Odd element at the end of the input list: just copy
+ * it unchanged to the output.
+ */
+ sched_append(sched, ENC_COPY);
+ msnew[nnew++] = ms[i];
+ break;
+ }
+
+ /*
+ * Normal case: consume two elements from the input list
+ * and combine them.
+ */
+ uint32_t m1 = ms[i], m2 = ms[i+1], m = m1*m2;
+ sched_append(sched, ENC_COMBINE_BASE + m1);
+
+ /*
+ * And then, as long as the combined limit is big enough,
+ * emit an output byte from the bottom of it.
+ */
+ while (m >= (1<<14)) {
+ sched_append(sched, ENC_BYTE);
+ m = (m + 0xFF) >> 8;
+ }
+
+ /*
+ * Whatever is left after that, we emit into the output
+ * list and append to the fifo.
+ */
+ msnew[nnew++] = m;
+ }
+
+ /*
+ * End of pass. The output list of (m_i) now becomes the input
+ * list.
+ */
+ uint32_t *tmp = ms;
+ ms = msnew;
+ n = nnew;
+ msnew = tmp;
+ }
+
+ /*
+ * When that loop terminates, it's because there's exactly one
+ * number left to encode. (Or, technically, _at most_ one - but we
+ * don't support encoding a completely empty list in this
+ * implementation, because what would be the point?) That number
+ * is just emitted little-endian until its limit is 1 (meaning its
+ * only possible actual value is 0).
+ */
+ assert(n == 1);
+ uint32_t m = ms[0];
+ while (m > 1) {
+ sched_append(sched, ENC_BYTE);
+ m = (m + 0xFF) >> 8;
+ }
+
+ sfree(ms);
+ sfree(msnew);
+
+ return sched;
+}
+
+void ntru_encode_schedule_free(NTRUEncodeSchedule *sched)
+{
+ sfree(sched->ops);
+ sfree(sched);
+}
+
+/*
+ * Calculate the output length of the encoded data in bytes.
+ */
+size_t ntru_encode_schedule_length(NTRUEncodeSchedule *sched)
+{
+ size_t len = 0;
+ for (size_t i = 0; i < sched->nops; i++)
+ if (sched->ops[i] == ENC_BYTE)
+ len++;
+ return len;
+}
+
+/*
+ * Retrieve the number of items encoded. (Used by testcrypt.)
+ */
+size_t ntru_encode_schedule_nvals(NTRUEncodeSchedule *sched)
+{
+ return sched->nvals;
+}
+
+/*
+ * Actually encode a sequence of (r_i), emitting the output bytes to
+ * an arbitrary BinarySink.
+ */
+void ntru_encode(NTRUEncodeSchedule *sched, const uint16_t *rs_in,
+ BinarySink *bs)
+{
+ size_t n = sched->nvals;
+ uint32_t *rs = snewn(n, uint32_t);
+ for (size_t i = 0; i < n; i++)
+ rs[i] = rs_in[i];
+
+ /*
+ * The head and tail pointers of the queue are both 'full'. That
+ * is, rs[head] is the first element actually in the queue, and
+ * rs[tail] is the last element.
+ *
+ * So you append to the queue by first advancing 'tail' and then
+ * writing to rs[tail], whereas you consume from the queue by
+ * first reading rs[head] and _then_ advancing 'head'.
+ *
+ * The more normal thing would be to make 'tail' point to the
+ * first empty slot instead of the last full one. But then you'd
+ * have to faff about with modular arithmetic to find the last
+ * full slot for the BYTE command, so in this case, it's easier to
+ * do it the less usual way.
+ */
+ size_t head = 0, tail = n-1;
+
+ for (size_t i = 0; i < sched->nops; i++) {
+ uint16_t op = sched->ops[i];
+ switch (op) {
+ case ENC_BYTE:
+ put_byte(bs, rs[tail] & 0xFF);
+ rs[tail] >>= 8;
+ break;
+ case ENC_COPY: {
+ uint32_t r = rs[head];
+ head = (head + 1) % n;
+ tail = (tail + 1) % n;
+ rs[tail] = r;
+ break;
+ }
+ default: {
+ uint32_t r1 = rs[head];
+ head = (head + 1) % n;
+ uint32_t r2 = rs[head];
+ head = (head + 1) % n;
+ tail = (tail + 1) % n;
+ rs[tail] = r1 + (op - ENC_COMBINE_BASE) * r2;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Expect that we've ended up with a single zero in the queue, at
+ * exactly the position that the setup-time analysis predicted it.
+ */
+ assert(head == sched->endpos);
+ assert(tail == sched->endpos);
+ assert(rs[head] == 0);
+
+ smemclr(rs, n * sizeof(*rs));
+ sfree(rs);
+}
+
+/*
+ * Decode a ptrlen of binary data into a sequence of (r_i). The data
+ * is expected to be of exactly the right length (on pain of assertion
+ * failure).
+ */
+void ntru_decode(NTRUEncodeSchedule *sched, uint16_t *rs_out, ptrlen data)
+{
+ size_t n = sched->nvals;
+ const uint8_t *base = (const uint8_t *)data.ptr;
+ const uint8_t *pos = base + data.len;
+
+ /*
+ * Initialise the queue to a single zero, at the 'endpos' position
+ * that will mean the final output is correctly aligned.
+ *
+ * 'head' and 'tail' have the same meanings as in encoding. So
+ * 'tail' is the location that BYTE modifies and COPY and COMBINE
+ * consume from, and 'head' is the location that COPY and COMBINE
+ * push on to. As in encoding, they both point at the extremal
+ * full slots in the array.
+ */
+ uint32_t *rs = snewn(n, uint32_t);
+ size_t head = sched->endpos, tail = head;
+ rs[tail] = 0;
+
+ for (size_t i = sched->nops; i-- > 0 ;) {
+ uint16_t op = sched->ops[i];
+ switch (op) {
+ case ENC_BYTE: {
+ assert(pos > base);
+ uint8_t byte = *--pos;
+ rs[tail] = (rs[tail] << 8) | byte;
+ break;
+ }
+ case ENC_COPY: {
+ uint32_t r = rs[tail];
+ tail = (tail + n - 1) % n;
+ head = (head + n - 1) % n;
+ rs[head] = r;
+ break;
+ }
+ default: {
+ uint32_t r = rs[tail];
+ tail = (tail + n - 1) % n;
+
+ uint32_t m = op - ENC_COMBINE_BASE;
+ uint64_t mrecip = reciprocal_for_reduction(m);
+
+ uint32_t r1, r2;
+ r1 = reduce_with_quot(r, &r2, m, mrecip);
+
+ head = (head + n - 1) % n;
+ rs[head] = r2;
+ head = (head + n - 1) % n;
+ rs[head] = r1;
+ break;
+ }
+ }
+ }
+
+ assert(pos == base);
+ assert(head == 0);
+ assert(tail == n-1);
+
+ for (size_t i = 0; i < n; i++)
+ rs_out[i] = rs[i];
+ smemclr(rs, n * sizeof(*rs));
+ sfree(rs);
+}
+
+/* ----------------------------------------------------------------------
+ * The actual public-key cryptosystem.
+ */
+
+struct NTRUKeyPair {
+ unsigned p, q, w;
+ uint16_t *h; /* public key */
+ uint16_t *f3, *ginv; /* private key */
+ uint16_t *rho; /* for implicit rejection */
+};
+
+/* Helper function to free an array of uint16_t containing a ring
+ * element, clearing it on the way since some of them are sensitive. */
+static void ring_free(uint16_t *val, unsigned p)
+{
+ smemclr(val, p*sizeof(*val));
+ sfree(val);
+}
+
+void ntru_keypair_free(NTRUKeyPair *keypair)
+{
+ ring_free(keypair->h, keypair->p);
+ ring_free(keypair->f3, keypair->p);
+ ring_free(keypair->ginv, keypair->p);
+ ring_free(keypair->rho, keypair->p);
+ sfree(keypair);
+}
+
+/* Trivial accessors used by test programs. */
+unsigned ntru_keypair_p(NTRUKeyPair *keypair) { return keypair->p; }
+const uint16_t *ntru_pubkey(NTRUKeyPair *keypair) { return keypair->h; }
+
+/*
+ * Generate a value of the class DJB describes as 'Short': it consists
+ * of p terms that are all either 0 or +1 or -1, and exactly w of them
+ * are not zero.
+ *
+ * Values of this kind are used for several purposes: part of the
+ * private key, a plaintext, and the 'rho' fake-plaintext value used
+ * for deliberately returning a duff but non-revealing session hash if
+ * things go wrong.
+ *
+ * -1 is represented as 2 in the output array. So if you want these
+ * numbers mod 3, then they come out already in the right form.
+ * Otherwise, use ntru_expand.
+ */
+void ntru_gen_short(uint16_t *v, unsigned p, unsigned w)
+{
+ /*
+ * Get enough random data to generate a polynomial all of whose p
+ * terms are in {0,+1,-1}, and exactly w of them are nonzero.
+ * We'll do this by making up a completely random sequence of
+ * {+1,-1} and then setting a random subset of them to 0.
+ *
+ * So we'll need p random bits to choose the nonzero values, and
+ * then (doing it the simplest way) log2(p!) bits to shuffle them,
+ * plus say 128 bits to ensure any fluctuations in uniformity are
+ * negligible.
+ *
+ * log2(p!) is a pain to calculate, so we'll bound it above by
+ * p*log2(p), which we bound in turn by p*16.
+ */
+ size_t randbitpos = 17 * p + 128;
+ mp_int *randdata = mp_resize(mp_random_bits(randbitpos), randbitpos + 32);
+
+ /*
+ * Initial value before zeroing out some terms: p randomly chosen
+ * values in {1,2}.
+ */
+ for (size_t i = 0; i < p; i++)
+ v[i] = 1 + mp_get_bit(randdata, --randbitpos);
+
+ /*
+ * Hereafter we're going to extract random bits by multiplication,
+ * treating randdata as a large fixed-point number.
+ */
+ mp_reduce_mod_2to(randdata, randbitpos);
+
+ /*
+ * Zero out some terms, leaving a randomly selected w of them
+ * nonzero.
+ */
+ uint32_t nonzeros_left = w;
+ mp_int *x = mp_new(64);
+ for (size_t i = p; i-- > 0 ;) {
+ /*
+ * Pick a random number out of the number of terms remaning.
+ */
+ mp_mul_integer_into(randdata, randdata, i+1);
+ mp_rshift_fixed_into(x, randdata, randbitpos);
+ mp_reduce_mod_2to(randdata, randbitpos);
+ size_t j = mp_get_integer(x);
+
+ /*
+ * If that's less than nonzeros_left, then we're leaving this
+ * number nonzero. Otherwise we're zeroing it out.
+ */
+ uint32_t keep = (uint32_t)(j - nonzeros_left) >> 31;
+ v[i] &= -keep; /* clear this field if keep == 0 */
+ nonzeros_left -= keep; /* decrement counter if keep == 1 */
+ }
+
+ mp_free(x);
+ mp_free(randdata);
+}
+
+/*
+ * Make a single attempt at generating a key pair. This involves
+ * inventing random elements of both our quotient rings and hoping
+ * they're both invertible.
+ *
+ * They may not be, if you're unlucky. The element of Z_q/<x^p-x-1>
+ * will _almost_ certainly be invertible, because that is a field, so
+ * invertibility can only fail if you were so unlucky as to choose the
+ * all-0s element. But the element of Z_3/<x^p-x-1> may fail to be
+ * invertible because it has a common factor with x^p-x-1 (which, over
+ * Z_3, is not irreducible).
+ *
+ * So we can't guarantee to generate a key pair in constant time,
+ * because there's no predicting how many retries we'll need. However,
+ * this isn't a failure of side-channel safety, because we completely
+ * discard all the random numbers and state from each failed attempt.
+ * So if there were a side-channel leakage from a failure, the only
+ * thing it would give away would be a bunch of random numbers that
+ * turned out not to be used anyway.
+ *
+ * But a _successful_ call to this function should execute in a
+ * secret-independent manner, and this 'make a single attempt'
+ * function is exposed in the API so that 'testsc' can check that.
+ */
+NTRUKeyPair *ntru_keygen_attempt(unsigned p, unsigned q, unsigned w)
+{
+ /*
+ * First invent g, which is the one more likely to fail to invert.
+ * This is simply a uniformly random polynomial with p terms over
+ * Z_3. So we need p*log2(3) random bits for it, plus 128 for
+ * uniformity. It's easiest to bound log2(3) above by 2.
+ */
+ size_t randbitpos = 2 * p + 128;
+ mp_int *randdata = mp_resize(mp_random_bits(randbitpos), randbitpos + 32);
+
+ /*
+ * Select p random values from {0,1,2}.
+ */
+ uint16_t *g = snewn(p, uint16_t);
+ mp_int *x = mp_new(64);
+ for (size_t i = 0; i < p; i++) {
+ mp_mul_integer_into(randdata, randdata, 3);
+ mp_rshift_fixed_into(x, randdata, randbitpos);
+ mp_reduce_mod_2to(randdata, randbitpos);
+ g[i] = mp_get_integer(x);
+ }
+ mp_free(x);
+ mp_free(randdata);
+
+ /*
+ * Try to invert g over Z_3, and fail if it isn't invertible.
+ */
+ uint16_t *ginv = snewn(p, uint16_t);
+ if (!ntru_ring_invert(ginv, g, p, 3)) {
+ ring_free(g, p);
+ ring_free(ginv, p);
+ return NULL;
+ }
+
+ /*
+ * Fine; we have g. Now make up an f, and convert it to a
+ * polynomial over q.
+ */
+ uint16_t *f = snewn(p, uint16_t);
+ ntru_gen_short(f, p, w);
+ ntru_expand(f, f, p, q);
+
+ /*
+ * Multiply f by 3.
+ */
+ uint16_t *f3 = snewn(p, uint16_t);
+ ntru_scale(f3, f, 3, p, q);
+
+ /*
+ * Try to invert 3*f over Z_q. This should be _almost_ guaranteed
+ * to succeed, since Z_q/<x^p-x-1> is a field, so the only
+ * non-invertible value is 0. Even so, there _is_ one, so check
+ * the return value!
+ */
+ uint16_t *f3inv = snewn(p, uint16_t);
+ if (!ntru_ring_invert(f3inv, f3, p, q)) {
+ ring_free(f, p);
+ ring_free(f3, p);
+ ring_free(f3inv, p);
+ ring_free(g, p);
+ ring_free(ginv, p);
+ return NULL;
+ }
+
+ /*
+ * Make the public key, by converting g to a polynomial over q and
+ * then multiplying by f3inv.
+ */
+ uint16_t *g_q = snewn(p, uint16_t);
+ ntru_expand(g_q, g, p, q);
+ uint16_t *h = snewn(p, uint16_t);
+ ntru_ring_multiply(h, g_q, f3inv, p, q);
+
+ /*
+ * Make up rho, used to substitute for the plaintext in the
+ * session hash in case of confirmation failure.
+ */
+ uint16_t *rho = snewn(p, uint16_t);
+ ntru_gen_short(rho, p, w);
+
+ /*
+ * And we're done! Free everything except the pieces we're
+ * returning.
+ */
+ NTRUKeyPair *keypair = snew(NTRUKeyPair);
+ keypair->p = p;
+ keypair->q = q;
+ keypair->w = w;
+ keypair->h = h;
+ keypair->f3 = f3;
+ keypair->ginv = ginv;
+ keypair->rho = rho;
+ ring_free(f, p);
+ ring_free(f3inv, p);
+ ring_free(g, p);
+ ring_free(g_q, p);
+ return keypair;
+}
+
+/*
+ * The top-level key generation function for real use (as opposed to
+ * testsc): keep trying to make a key until you succeed.
+ */
+NTRUKeyPair *ntru_keygen(unsigned p, unsigned q, unsigned w)
+{
+ while (1) {
+ NTRUKeyPair *keypair = ntru_keygen_attempt(p, q, w);
+ if (keypair)
+ return keypair;
+ }
+}
+
+/*
+ * Public-key encryption.
+ */
+void ntru_encrypt(uint16_t *ciphertext, const uint16_t *plaintext,
+ uint16_t *pubkey, unsigned p, unsigned q)
+{
+ uint16_t *r_q = snewn(p, uint16_t);
+ ntru_expand(r_q, plaintext, p, q);
+
+ uint16_t *unrounded = snewn(p, uint16_t);
+ ntru_ring_multiply(unrounded, r_q, pubkey, p, q);
+
+ ntru_round3(ciphertext, unrounded, p, q);
+ ntru_normalise(ciphertext, ciphertext, p, q);
+
+ ring_free(r_q, p);
+ ring_free(unrounded, p);
+}
+
+/*
+ * Public-key decryption.
+ */
+void ntru_decrypt(uint16_t *plaintext, const uint16_t *ciphertext,
+ NTRUKeyPair *keypair)
+{
+ unsigned p = keypair->p, q = keypair->q, w = keypair->w;
+ uint16_t *tmp = snewn(p, uint16_t);
+
+ ntru_ring_multiply(tmp, ciphertext, keypair->f3, p, q);
+
+ ntru_mod3(tmp, tmp, p, q);
+ ntru_normalise(tmp, tmp, p, 3);
+
+ ntru_ring_multiply(plaintext, tmp, keypair->ginv, p, 3);
+ ring_free(tmp, p);
+
+ /*
+ * With luck, this should have recovered exactly the original
+ * plaintext. But, as per the spec, we check whether it has
+ * exactly w nonzero coefficients, and if not, then something has
+ * gone wrong - and in that situation we time-safely substitute a
+ * different output.
+ *
+ * (I don't know exactly why we do this, but I assume it's because
+ * otherwise the mis-decoded output could be made to disgorge a
+ * secret about the private key in some way.)
+ */
+
+ unsigned weight = p;
+ for (size_t i = 0; i < p; i++)
+ weight -= iszero(plaintext[i]);
+ unsigned ok = iszero(weight ^ w);
+
+ /*
+ * The default failure return value consists of w 1s followed by
+ * 0s.
+ */
+ unsigned mask = ok - 1;
+ for (size_t i = 0; i < w; i++) {
+ uint16_t diff = (1 ^ plaintext[i]) & mask;
+ plaintext[i] ^= diff;
+ }
+ for (size_t i = w; i < p; i++) {
+ uint16_t diff = (0 ^ plaintext[i]) & mask;
+ plaintext[i] ^= diff;
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * Encode and decode public keys, ciphertexts and plaintexts.
+ *
+ * Public keys and ciphertexts use the complicated binary encoding
+ * system implemented above. In both cases, the inputs are regarded as
+ * symmetric about zero, and are first biased to map their most
+ * negative permitted value to 0, so that they become non-negative and
+ * hence suitable as inputs to the encoding system. In the case of a
+ * ciphertext, where the input coefficients have also been coerced to
+ * be multiples of 3, we divide by 3 as well, saving space by reducing
+ * the upper bounds (m_i) on all the encoded numbers.
+ */
+
+/*
+ * Compute the encoding schedule for a public key.
+ */
+static NTRUEncodeSchedule *ntru_encode_pubkey_schedule(unsigned p, unsigned q)
+{
+ uint16_t *ms = snewn(p, uint16_t);
+ for (size_t i = 0; i < p; i++)
+ ms[i] = q;
+ NTRUEncodeSchedule *sched = ntru_encode_schedule(ms, p);
+ sfree(ms);
+ return sched;
+}
+
+/*
+ * Encode a public key.
+ */
+void ntru_encode_pubkey(const uint16_t *pubkey, unsigned p, unsigned q,
+ BinarySink *bs)
+{
+ /* Compute the biased version for encoding */
+ uint16_t *biased_pubkey = snewn(p, uint16_t);
+ ntru_bias(biased_pubkey, pubkey, q / 2, p, q);
+
+ /* Encode it */
+ NTRUEncodeSchedule *sched = ntru_encode_pubkey_schedule(p, q);
+ ntru_encode(sched, biased_pubkey, bs);
+ ntru_encode_schedule_free(sched);
+
+ ring_free(biased_pubkey, p);
+}
+
+/*
+ * Decode a public key and write it into 'pubkey'. We also return a
+ * ptrlen pointing at the chunk of data we removed from the
+ * BinarySource.
+ */
+ptrlen ntru_decode_pubkey(uint16_t *pubkey, unsigned p, unsigned q,
+ BinarySource *src)
+{
+ NTRUEncodeSchedule *sched = ntru_encode_pubkey_schedule(p, q);
+
+ /* Retrieve the right number of bytes from the source */
+ size_t len = ntru_encode_schedule_length(sched);
+ ptrlen encoded = get_data(src, len);
+ if (get_err(src)) {
+ /* If there wasn't enough data, give up and return all-zeroes
+ * purely for determinism. But that value should never be
+ * used, because the caller will also check get_err(src). */
+ memset(pubkey, 0, p*sizeof(*pubkey));
+ } else {
+ /* Do the decoding */
+ ntru_decode(sched, pubkey, encoded);
+
+ /* Unbias the coefficients */
+ ntru_bias(pubkey, pubkey, q-q/2, p, q);
+ }
+
+ ntru_encode_schedule_free(sched);
+ return encoded;
+}
+
+/*
+ * For ciphertext biasing: work out the largest absolute value a
+ * ciphertext element can take, which is given by taking q/2 and
+ * rounding it to the nearest multiple of 3.
+ */
+static inline unsigned ciphertext_bias(unsigned q)
+{
+ return (q/2+1) / 3;
+}
+
+/*
+ * The number of possible values of a ciphertext coefficient (for use
+ * as the m_i in encoding) ranges from +ciphertext_bias(q) to
+ * -ciphertext_bias(q) inclusive.
+ */
+static inline unsigned ciphertext_m(unsigned q)
+{
+ return 1 + 2 * ciphertext_bias(q);
+}
+
+/*
+ * Compute the encoding schedule for a ciphertext.
+ */
+static NTRUEncodeSchedule *ntru_encode_ciphertext_schedule(
+ unsigned p, unsigned q)
+{
+ unsigned m = ciphertext_m(q);
+ uint16_t *ms = snewn(p, uint16_t);
+ for (size_t i = 0; i < p; i++)
+ ms[i] = m;
+ NTRUEncodeSchedule *sched = ntru_encode_schedule(ms, p);
+ sfree(ms);
+ return sched;
+}
+
+/*
+ * Encode a ciphertext.
+ */
+void ntru_encode_ciphertext(const uint16_t *ciphertext, unsigned p, unsigned q,
+ BinarySink *bs)
+{
+ SETUP;
+
+ /*
+ * Bias the ciphertext, and scale down by 1/3, which we do by
+ * modular multiplication by the inverse of 3 mod q. (That only
+ * works if we know the inputs are all _exact_ multiples of 3
+ * - but we do!)
+ */
+ uint16_t *biased_ciphertext = snewn(p, uint16_t);
+ ntru_bias(biased_ciphertext, ciphertext, 3 * ciphertext_bias(q), p, q);
+ ntru_scale(biased_ciphertext, biased_ciphertext, INVERT(3), p, q);
+
+ /* Encode. */
+ NTRUEncodeSchedule *sched = ntru_encode_ciphertext_schedule(p, q);
+ ntru_encode(sched, biased_ciphertext, bs);
+ ntru_encode_schedule_free(sched);
+
+ ring_free(biased_ciphertext, p);
+}
+
+ptrlen ntru_decode_ciphertext(uint16_t *ct, NTRUKeyPair *keypair,
+ BinarySource *src)
+{
+ unsigned p = keypair->p, q = keypair->q;
+
+ NTRUEncodeSchedule *sched = ntru_encode_ciphertext_schedule(p, q);
+
+ /* Retrieve the right number of bytes from the source */
+ size_t len = ntru_encode_schedule_length(sched);
+ ptrlen encoded = get_data(src, len);
+ if (get_err(src)) {
+ /* As above, return deterministic nonsense on failure */
+ memset(ct, 0, p*sizeof(*ct));
+ } else {
+ /* Do the decoding */
+ ntru_decode(sched, ct, encoded);
+
+ /* Undo the scaling and bias */
+ ntru_scale(ct, ct, 3, p, q);
+ ntru_bias(ct, ct, q - 3 * ciphertext_bias(q), p, q);
+ }
+
+ ntru_encode_schedule_free(sched);
+ return encoded; /* also useful to the caller, optionally */
+}
+
+/*
+ * Encode a plaintext.
+ *
+ * This is a much simpler encoding than the NTRUEncodeSchedule system:
+ * since elements of a plaintext are mod 3, we just encode each one in
+ * 2 bits, applying the usual bias so that {-1,0,+1} map to {0,1,2}
+ * respectively.
+ *
+ * There's no corresponding decode function, because plaintexts are
+ * never transmitted on the wire (the whole point is that they're too
+ * secret!). Plaintexts are only encoded in order to put them into
+ * hash preimages.
+ */
+void ntru_encode_plaintext(const uint16_t *plaintext, unsigned p,
+ BinarySink *bs)
+{
+ unsigned byte = 0, bitpos = 0;
+ for (size_t i = 0; i < p; i++) {
+ unsigned encoding = (plaintext[i] + 1) * iszero(plaintext[i] >> 1);
+ byte |= encoding << bitpos;
+ bitpos += 2;
+ if (bitpos == 8 || i+1 == p) {
+ put_byte(bs, byte);
+ byte = 0;
+ bitpos = 0;
+ }
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * Compute the hashes required by the key exchange layer of NTRU Prime.
+ *
+ * There are two of these. The 'confirmation hash' is sent by the
+ * server along with the ciphertext, and the client can recalculate it
+ * to check whether the ciphertext was decrypted correctly. Then, the
+ * 'session hash' is the actual output of key exchange, and if the
+ * confirmation hash doesn't match, it gets deliberately corrupted.
+ */
+
+/*
+ * Make the confirmation hash, whose inputs are the plaintext and the
+ * public key.
+ *
+ * This is defined as H(2 || H(3 || r) || H(4 || K)), where r is the
+ * plaintext and K is the public key (as encoded by the above
+ * functions), and the constants 2,3,4 are single bytes. The choice of
+ * hash function (H itself) is SHA-512 truncated to 256 bits.
+ *
+ * (To be clear: that is _not_ the thing that FIPS 180-4 6.7 defines
+ * as "SHA-512/256", which varies the initialisation vector of the
+ * SHA-512 algorithm as well as truncating the output. _This_
+ * algorithm uses the standard SHA-512 IV, and _just_ truncates the
+ * output, in the manner suggested by FIPS 180-4 section 7.)
+ *
+ * 'out' should therefore expect to receive 32 bytes of data.
+ */
+static void ntru_confirmation_hash(
+ uint8_t *out, const uint16_t *plaintext,
+ const uint16_t *pubkey, unsigned p, unsigned q)
+{
+ /* The outer hash object */
+ ssh_hash *hconfirm = ssh_hash_new(&ssh_sha512);
+ put_byte(hconfirm, 2); /* initial byte 2 */
+
+ uint8_t hashdata[64];
+
+ /* Compute H(3 || r) and add it to the main hash */
+ ssh_hash *h3r = ssh_hash_new(&ssh_sha512);
+ put_byte(h3r, 3);
+ ntru_encode_plaintext(plaintext, p, BinarySink_UPCAST(h3r));
+ ssh_hash_final(h3r, hashdata);
+ put_data(hconfirm, hashdata, 32);
+
+ /* Compute H(4 || K) and add it to the main hash */
+ ssh_hash *h4K = ssh_hash_new(&ssh_sha512);
+ put_byte(h4K, 4);
+ ntru_encode_pubkey(pubkey, p, q, BinarySink_UPCAST(h4K));
+ ssh_hash_final(h4K, hashdata);
+ put_data(hconfirm, hashdata, 32);
+
+ /* Compute the full output of the main SHA-512 hash */
+ ssh_hash_final(hconfirm, hashdata);
+
+ /* And copy the first 32 bytes into the caller's output array */
+ memcpy(out, hashdata, 32);
+ smemclr(hashdata, sizeof(hashdata));
+}
+
+/*
+ * Make the session hash, whose inputs are the plaintext, the
+ * ciphertext, and the confirmation hash (hence, transitively, a
+ * dependence on the public key as well).
+ *
+ * As computed by the server, and by the client if the confirmation
+ * hash matched, this is defined as
+ *
+ * H(1 || H(3 || r) || ciphertext || confirmation hash)
+ *
+ * but if the confirmation hash _didn't_ match, then the plaintext r
+ * is replaced with the dummy plaintext-shaped value 'rho' we invented
+ * during key generation (presumably to avoid leaking any information
+ * about our secrets), and the initial byte 1 is replaced with 0 (to
+ * ensure that the resulting hash preimage can't match any legitimate
+ * preimage). So in that case, you instead get
+ *
+ * H(0 || H(3 || rho) || ciphertext || confirmation hash)
+ *
+ * The inputs to this function include 'ok', which is the value to use
+ * as the initial byte (1 on success, 0 on failure), and 'plaintext'
+ * which should already have been substituted with rho in case of
+ * failure.
+ *
+ * The ciphertext is provided in already-encoded form.
+ */
+static void ntru_session_hash(
+ uint8_t *out, unsigned ok, const uint16_t *plaintext,
+ unsigned p, ptrlen ciphertext, ptrlen confirmation_hash)
+{
+ /* The outer hash object */
+ ssh_hash *hsession = ssh_hash_new(&ssh_sha512);
+ put_byte(hsession, ok); /* initial byte 1 or 0 */
+
+ uint8_t hashdata[64];
+
+ /* Compute H(3 || r), or maybe H(3 || rho), and add it to the main hash */
+ ssh_hash *h3r = ssh_hash_new(&ssh_sha512);
+ put_byte(h3r, 3);
+ ntru_encode_plaintext(plaintext, p, BinarySink_UPCAST(h3r));
+ ssh_hash_final(h3r, hashdata);
+ put_data(hsession, hashdata, 32);
+
+ /* Put the ciphertext and confirmation hash in */
+ put_datapl(hsession, ciphertext);
+ put_datapl(hsession, confirmation_hash);
+
+ /* Compute the full output of the main SHA-512 hash */
+ ssh_hash_final(hsession, hashdata);
+
+ /* And copy the first 32 bytes into the caller's output array */
+ memcpy(out, hashdata, 32);
+ smemclr(hashdata, sizeof(hashdata));
+}
+
+/* ----------------------------------------------------------------------
+ * Top-level key exchange and SSH integration.
+ *
+ * Although this system borrows the ECDH packet structure, it's unlike
+ * true ECDH in that it is completely asymmetric between client and
+ * server. So we have two separate vtables of methods for the two
+ * sides of the system, and a third vtable containing only the class
+ * methods, in particular a constructor which chooses which one to
+ * instantiate.
+ */
+
+/*
+ * The parameters p,q,w for the system. There are other choices of
+ * these, but OpenSSH only specifies this set. (If that ever changes,
+ * we'll need to turn these into elements of the state structures.)
+ */
+#define p_LIVE 761
+#define q_LIVE 4591
+#define w_LIVE 286
+
+static char *ssh_ntru_description(const ssh_kex *kex)
+{
+ return dupprintf("NTRU Prime / Curve25519 hybrid key exchange");
+}
+
+/*
+ * State structure for the client, which takes the role of inventing a
+ * key pair and decrypting a secret plaintext sent to it by the server.
+ */
+typedef struct ntru_client_key {
+ NTRUKeyPair *keypair;
+ ecdh_key *curve25519;
+
+ ecdh_key ek;
+} ntru_client_key;
+
+static void ssh_ntru_client_free(ecdh_key *dh);
+static void ssh_ntru_client_getpublic(ecdh_key *dh, BinarySink *bs);
+static bool ssh_ntru_client_getkey(ecdh_key *dh, ptrlen remoteKey,
+ BinarySink *bs);
+
+static const ecdh_keyalg ssh_ntru_client_vt = {
+ /* This vtable has no 'new' method, because it's constructed via
+ * the selector vt below */
+ .free = ssh_ntru_client_free,
+ .getpublic = ssh_ntru_client_getpublic,
+ .getkey = ssh_ntru_client_getkey,
+ .description = ssh_ntru_description,
+};
+
+static ecdh_key *ssh_ntru_client_new(void)
+{
+ ntru_client_key *nk = snew(ntru_client_key);
+ nk->ek.vt = &ssh_ntru_client_vt;
+
+ nk->keypair = ntru_keygen(p_LIVE, q_LIVE, w_LIVE);
+ nk->curve25519 = ecdh_key_new(&ssh_ec_kex_curve25519, false);
+
+ return &nk->ek;
+}
+
+static void ssh_ntru_client_free(ecdh_key *dh)
+{
+ ntru_client_key *nk = container_of(dh, ntru_client_key, ek);
+ ntru_keypair_free(nk->keypair);
+ ecdh_key_free(nk->curve25519);
+ sfree(nk);
+}
+
+static void ssh_ntru_client_getpublic(ecdh_key *dh, BinarySink *bs)
+{
+ ntru_client_key *nk = container_of(dh, ntru_client_key, ek);
+
+ /*
+ * The client's public information is a single SSH string
+ * containing the NTRU public key and the Curve25519 public point
+ * concatenated. So write both of those into the output
+ * BinarySink.
+ */
+ ntru_encode_pubkey(nk->keypair->h, p_LIVE, q_LIVE, bs);
+ ecdh_key_getpublic(nk->curve25519, bs);
+}
+
+static bool ssh_ntru_client_getkey(ecdh_key *dh, ptrlen remoteKey,
+ BinarySink *bs)
+{
+ ntru_client_key *nk = container_of(dh, ntru_client_key, ek);
+
+ /*
+ * We expect the server to have sent us a string containing a
+ * ciphertext, a confirmation hash, and a Curve25519 public point.
+ * Extract all three.
+ */
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, remoteKey);
+
+ uint16_t *ciphertext = snewn(p_LIVE, uint16_t);
+ ptrlen ciphertext_encoded = ntru_decode_ciphertext(
+ ciphertext, nk->keypair, src);
+ ptrlen confirmation_hash = get_data(src, 32);
+ ptrlen curve25519_remoteKey = get_data(src, 32);
+
+ if (get_err(src) || get_avail(src)) {
+ /* Hard-fail if the input wasn't exactly the right length */
+ ring_free(ciphertext, p_LIVE);
+ return false;
+ }
+
+ /*
+ * Main hash object which will combine the NTRU and Curve25519
+ * outputs.
+ */
+ ssh_hash *h = ssh_hash_new(&ssh_sha512);
+
+ /* Reusable buffer for storing various hash outputs. */
+ uint8_t hashdata[64];
+
+ /*
+ * NTRU side.
+ */
+ {
+ /* Decrypt the ciphertext to recover the server's plaintext */
+ uint16_t *plaintext = snewn(p_LIVE, uint16_t);
+ ntru_decrypt(plaintext, ciphertext, nk->keypair);
+
+ /* Make the confirmation hash */
+ ntru_confirmation_hash(hashdata, plaintext, nk->keypair->h,
+ p_LIVE, q_LIVE);
+
+ /* Check it matches the one the server sent */
+ unsigned ok = smemeq(hashdata, confirmation_hash.ptr, 32);
+
+ /* If not, substitute in rho for the plaintext in the session hash */
+ unsigned mask = ok-1;
+ for (size_t i = 0; i < p_LIVE; i++)
+ plaintext[i] ^= mask & (plaintext[i] ^ nk->keypair->rho[i]);
+
+ /* Compute the session hash, whether or not we did that */
+ ntru_session_hash(hashdata, ok, plaintext, p_LIVE, ciphertext_encoded,
+ confirmation_hash);
+
+ /* Free temporary values */
+ ring_free(plaintext, p_LIVE);
+ ring_free(ciphertext, p_LIVE);
+
+ /* And put the NTRU session hash into the main hash object. */
+ put_data(h, hashdata, 32);
+ }
+
+ /*
+ * Curve25519 side.
+ */
+ {
+ strbuf *otherkey = strbuf_new_nm();
+
+ /* Call out to Curve25519 to compute the shared secret from that
+ * kex method */
+ bool ok = ecdh_key_getkey(nk->curve25519, curve25519_remoteKey,
+ BinarySink_UPCAST(otherkey));
+
+ /* If that failed (which only happens if the other end does
+ * something wrong, like sending a low-order curve point
+ * outside the subgroup it's supposed to), we might as well
+ * just abort and return failure. That's what we'd have done
+ * in standalone Curve25519. */
+ if (!ok) {
+ ssh_hash_free(h);
+ smemclr(hashdata, sizeof(hashdata));
+ strbuf_free(otherkey);
+ return false;
+ }
+
+ /*
+ * ecdh_key_getkey will have returned us a chunk of data
+ * containing an encoded mpint, which is how the Curve25519
+ * output normally goes into the exchange hash. But in this
+ * context we want to treat it as a fixed big-endian 32 bytes,
+ * so extract it from its encoding and put it into the main
+ * hash object in the new format.
+ */
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(otherkey));
+ mp_int *curvekey = get_mp_ssh2(src);
+
+ for (unsigned i = 32; i-- > 0 ;)
+ put_byte(h, mp_get_byte(curvekey, i));
+
+ mp_free(curvekey);
+ strbuf_free(otherkey);
+ }
+
+ /*
+ * Finish up: compute the final output hash (full 64 bytes of
+ * SHA-512 this time), and return it encoded as a string.
+ */
+ ssh_hash_final(h, hashdata);
+ put_stringpl(bs, make_ptrlen(hashdata, sizeof(hashdata)));
+ smemclr(hashdata, sizeof(hashdata));
+
+ return true;
+}
+
+/*
+ * State structure for the server, which takes the role of inventing a
+ * secret plaintext and sending it to the client encrypted with the
+ * public key the client sent.
+ */
+typedef struct ntru_server_key {
+ uint16_t *plaintext;
+ strbuf *ciphertext_encoded, *confirmation_hash;
+ ecdh_key *curve25519;
+
+ ecdh_key ek;
+} ntru_server_key;
+
+static void ssh_ntru_server_free(ecdh_key *dh);
+static void ssh_ntru_server_getpublic(ecdh_key *dh, BinarySink *bs);
+static bool ssh_ntru_server_getkey(ecdh_key *dh, ptrlen remoteKey,
+ BinarySink *bs);
+
+static const ecdh_keyalg ssh_ntru_server_vt = {
+ /* This vtable has no 'new' method, because it's constructed via
+ * the selector vt below */
+ .free = ssh_ntru_server_free,
+ .getpublic = ssh_ntru_server_getpublic,
+ .getkey = ssh_ntru_server_getkey,
+ .description = ssh_ntru_description,
+};
+
+static ecdh_key *ssh_ntru_server_new(void)
+{
+ ntru_server_key *nk = snew(ntru_server_key);
+ nk->ek.vt = &ssh_ntru_server_vt;
+
+ nk->plaintext = snewn(p_LIVE, uint16_t);
+ nk->ciphertext_encoded = strbuf_new_nm();
+ nk->confirmation_hash = strbuf_new_nm();
+ ntru_gen_short(nk->plaintext, p_LIVE, w_LIVE);
+
+ nk->curve25519 = ecdh_key_new(&ssh_ec_kex_curve25519, false);
+
+ return &nk->ek;
+}
+
+static void ssh_ntru_server_free(ecdh_key *dh)
+{
+ ntru_server_key *nk = container_of(dh, ntru_server_key, ek);
+ ring_free(nk->plaintext, p_LIVE);
+ strbuf_free(nk->ciphertext_encoded);
+ strbuf_free(nk->confirmation_hash);
+ ecdh_key_free(nk->curve25519);
+ sfree(nk);
+}
+
+static bool ssh_ntru_server_getkey(ecdh_key *dh, ptrlen remoteKey,
+ BinarySink *bs)
+{
+ ntru_server_key *nk = container_of(dh, ntru_server_key, ek);
+
+ /*
+ * In the server, getkey is called first, with the public
+ * information received from the client. We expect the client to
+ * have sent us a string containing a public key and a Curve25519
+ * public point.
+ */
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, remoteKey);
+
+ uint16_t *pubkey = snewn(p_LIVE, uint16_t);
+ ntru_decode_pubkey(pubkey, p_LIVE, q_LIVE, src);
+ ptrlen curve25519_remoteKey = get_data(src, 32);
+
+ if (get_err(src) || get_avail(src)) {
+ /* Hard-fail if the input wasn't exactly the right length */
+ ring_free(pubkey, p_LIVE);
+ return false;
+ }
+
+ /*
+ * Main hash object which will combine the NTRU and Curve25519
+ * outputs.
+ */
+ ssh_hash *h = ssh_hash_new(&ssh_sha512);
+
+ /* Reusable buffer for storing various hash outputs. */
+ uint8_t hashdata[64];
+
+ /*
+ * NTRU side.
+ */
+ {
+ /* Encrypt the plaintext we generated at construction time,
+ * and encode the ciphertext into a strbuf so we can reuse it
+ * for both the session hash and sending to the client. */
+ uint16_t *ciphertext = snewn(p_LIVE, uint16_t);
+ ntru_encrypt(ciphertext, nk->plaintext, pubkey, p_LIVE, q_LIVE);
+ ntru_encode_ciphertext(ciphertext, p_LIVE, q_LIVE,
+ BinarySink_UPCAST(nk->ciphertext_encoded));
+ ring_free(ciphertext, p_LIVE);
+
+ /* Compute the confirmation hash, and write it into another
+ * strbuf. */
+ ntru_confirmation_hash(hashdata, nk->plaintext, pubkey,
+ p_LIVE, q_LIVE);
+ put_data(nk->confirmation_hash, hashdata, 32);
+
+ /* Compute the session hash (which is easy on the server side,
+ * requiring no conditional substitution). */
+ ntru_session_hash(hashdata, 1, nk->plaintext, p_LIVE,
+ ptrlen_from_strbuf(nk->ciphertext_encoded),
+ ptrlen_from_strbuf(nk->confirmation_hash));
+
+ /* And put the NTRU session hash into the main hash object. */
+ put_data(h, hashdata, 32);
+
+ /* Now we can free the public key */
+ ring_free(pubkey, p_LIVE);
+ }
+
+ /*
+ * Curve25519 side.
+ */
+ {
+ strbuf *otherkey = strbuf_new_nm();
+
+ /* Call out to Curve25519 to compute the shared secret from that
+ * kex method */
+ bool ok = ecdh_key_getkey(nk->curve25519, curve25519_remoteKey,
+ BinarySink_UPCAST(otherkey));
+ /* As on the client side, abort if Curve25519 reported failure */
+ if (!ok) {
+ ssh_hash_free(h);
+ smemclr(hashdata, sizeof(hashdata));
+ strbuf_free(otherkey);
+ return false;
+ }
+
+ /* As on the client side, decode Curve25519's mpint so we can
+ * re-encode it appropriately for our hash preimage */
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(otherkey));
+ mp_int *curvekey = get_mp_ssh2(src);
+
+ for (unsigned i = 32; i-- > 0 ;)
+ put_byte(h, mp_get_byte(curvekey, i));
+
+ mp_free(curvekey);
+ strbuf_free(otherkey);
+ }
+
+ /*
+ * Finish up: compute the final output hash (full 64 bytes of
+ * SHA-512 this time), and return it encoded as a string.
+ */
+ ssh_hash_final(h, hashdata);
+ put_stringpl(bs, make_ptrlen(hashdata, sizeof(hashdata)));
+ smemclr(hashdata, sizeof(hashdata));
+
+ return true;
+}
+
+static void ssh_ntru_server_getpublic(ecdh_key *dh, BinarySink *bs)
+{
+ ntru_server_key *nk = container_of(dh, ntru_server_key, ek);
+
+ /*
+ * In the server, this function is called after getkey, so we
+ * already have all our pieces prepared. Just concatenate them all
+ * into the 'server's public data' string to go in ECDH_REPLY.
+ */
+ put_datapl(bs, ptrlen_from_strbuf(nk->ciphertext_encoded));
+ put_datapl(bs, ptrlen_from_strbuf(nk->confirmation_hash));
+ ecdh_key_getpublic(nk->curve25519, bs);
+}
+
+/* ----------------------------------------------------------------------
+ * Selector vtable that instantiates the appropriate one of the above,
+ * depending on is_server.
+ */
+static ecdh_key *ssh_ntru_new(const ssh_kex *kex, bool is_server)
+{
+ if (is_server)
+ return ssh_ntru_server_new();
+ else
+ return ssh_ntru_client_new();
+}
+
+static const ecdh_keyalg ssh_ntru_selector_vt = {
+ /* This is a never-instantiated vtable which only implements the
+ * functions that don't require an instance. */
+ .new = ssh_ntru_new,
+ .description = ssh_ntru_description,
+};
+
+static const ssh_kex ssh_ntru_curve25519 = {
+ .name = "sntrup761x25519-sha512@openssh.com",
+ .main_type = KEXTYPE_ECDH,
+ .hash = &ssh_sha512,
+ .ecdh_vt = &ssh_ntru_selector_vt,
+};
+
+static const ssh_kex *const hybrid_list[] = {
+ &ssh_ntru_curve25519,
+};
+
+const ssh_kexes ssh_ntru_hybrid_kex = { lenof(hybrid_list), hybrid_list };
diff --git a/crypto/ntru.h b/crypto/ntru.h
new file mode 100644
index 00000000..4789491b
--- /dev/null
+++ b/crypto/ntru.h
@@ -0,0 +1,53 @@
+/*
+ * Internal functions for the NTRU cryptosystem, exposed in a header
+ * that is expected to be included only by ntru.c and test programs.
+ */
+
+#ifndef PUTTY_CRYPTO_NTRU_H
+#define PUTTY_CRYPTO_NTRU_H
+
+unsigned ntru_ring_invert(uint16_t *out, const uint16_t *in,
+ unsigned p, unsigned q);
+void ntru_ring_multiply(uint16_t *out, const uint16_t *a, const uint16_t *b,
+ unsigned p, unsigned q);
+void ntru_mod3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q);
+void ntru_round3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q);
+void ntru_bias(uint16_t *out, const uint16_t *in, unsigned bias,
+ unsigned p, unsigned q);
+void ntru_scale(uint16_t *out, const uint16_t *in, uint16_t scale,
+ unsigned p, unsigned q);
+
+NTRUEncodeSchedule *ntru_encode_schedule(const uint16_t *ms_in, size_t n);
+void ntru_encode_schedule_free(NTRUEncodeSchedule *sched);
+size_t ntru_encode_schedule_length(NTRUEncodeSchedule *sched);
+size_t ntru_encode_schedule_nvals(NTRUEncodeSchedule *sched);
+void ntru_encode(NTRUEncodeSchedule *sched, const uint16_t *rs_in,
+ BinarySink *bs);
+void ntru_decode(NTRUEncodeSchedule *sched, uint16_t *rs_out, ptrlen data);
+
+void ntru_gen_short(uint16_t *v, unsigned p, unsigned w);
+
+NTRUKeyPair *ntru_keygen_attempt(unsigned p, unsigned q, unsigned w);
+NTRUKeyPair *ntru_keygen(unsigned p, unsigned q, unsigned w);
+void ntru_keypair_free(NTRUKeyPair *keypair);
+
+void ntru_encrypt(uint16_t *ciphertext, const uint16_t *plaintext,
+ uint16_t *pubkey, unsigned p, unsigned q);
+void ntru_decrypt(uint16_t *plaintext, const uint16_t *ciphertext,
+ NTRUKeyPair *keypair);
+
+void ntru_encode_pubkey(const uint16_t *pubkey, unsigned p, unsigned q,
+ BinarySink *bs);
+ptrlen ntru_decode_pubkey(uint16_t *pubkey, unsigned p, unsigned q,
+ BinarySource *src);
+void ntru_encode_ciphertext(const uint16_t *ciphertext, unsigned p, unsigned q,
+ BinarySink *bs);
+ptrlen ntru_decode_ciphertext(uint16_t *ct, NTRUKeyPair *keypair,
+ BinarySource *src);
+void ntru_encode_plaintext(const uint16_t *plaintext, unsigned p,
+ BinarySink *bs);
+
+unsigned ntru_keypair_p(NTRUKeyPair *keypair);
+const uint16_t *ntru_pubkey(NTRUKeyPair *keypair);
+
+#endif /* PUTTY_CRYPTO_NTRU_H */
diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c
new file mode 100644
index 00000000..cf0c2af3
--- /dev/null
+++ b/crypto/openssh-certs.c
@@ -0,0 +1,1160 @@
+/*
+ * Public key type for OpenSSH certificates.
+ */
+
+#include "ssh.h"
+#include "putty.h"
+
+enum {
+ SSH_CERT_TYPE_USER = 1,
+ SSH_CERT_TYPE_HOST = 2,
+};
+
+typedef struct opensshcert_key {
+ strbuf *nonce;
+ uint64_t serial;
+ uint32_t type;
+ strbuf *key_id;
+ strbuf *valid_principals;
+ uint64_t valid_after, valid_before;
+ strbuf *critical_options;
+ strbuf *extensions;
+ strbuf *reserved;
+ strbuf *signature_key;
+ strbuf *signature;
+
+ ssh_key *basekey;
+
+ ssh_key sshk;
+} opensshcert_key;
+
+typedef struct blob_fmt {
+ const unsigned *fmt;
+ size_t len;
+} blob_fmt;
+
+typedef struct opensshcert_extra {
+ /*
+ * OpenSSH certificate formats aren't completely consistent about
+ * the relationship between the public+private blob uploaded to
+ * the agent for the certified key type, and the one for the base
+ * key type. Here we specify the mapping.
+ *
+ * Each of these foo_fmt strings indicates the layout of a
+ * particular version of the key, in the form of an array of
+ * integers together with a length, with each integer describing
+ * one of the components of the key. The integers are defined by
+ * enums, so that they're tightly packed; the general idea is that
+ * if you're converting from one form to another, then you use the
+ * format list for the source format to read out a succession of
+ * SSH strings from the source data and put them in an array
+ * indexed by the integer ids, and then use the list for the
+ * destination format to write the strings out to the destination
+ * in the right (maybe different) order.
+ *
+ * pub_fmt describes the format of the public-key blob for the
+ * base key type, not counting the initial string giving the key
+ * type identifier itself. As far as I know, this always matches
+ * the format of the public-key data appearing in the middle of
+ * the certificate.
+ *
+ * base_ossh_fmt describes the format of the full OpenSSH blob
+ * appearing in the ssh-agent protocol for the base key,
+ * containing the public and private key data.
+ *
+ * cert_ossh_fmt describes the format of the OpenSSH blob for the
+ * certificate key format, beginning just after the certificate
+ * string itself.
+ */
+ blob_fmt pub_fmt, base_ossh_fmt, cert_ossh_fmt;
+
+ /*
+ * The RSA-SHA2 algorithm names have their SSH id set to names
+ * like "rsa-sha2-512-cert-...", which is what will be received in
+ * the KEXINIT algorithm list if a host key in one of those
+ * algorithms is presented. But the _key_ type id that will appear
+ * in the public key blob is "ssh-rsa-cert-...". So we need a
+ * separate field to indicate the key type id we expect to see in
+ * certified public keys, and also the one we want to put back
+ * into the artificial public blob we make to pass to the
+ * constructor for the underlying key.
+ *
+ * (In rsa.c this is managed much more simply, because everything
+ * sharing the same vtable wants the same key type id.)
+ */
+ const char *cert_key_ssh_id, *base_key_ssh_id;
+} opensshcert_extra;
+
+/*
+ * The actual integer arrays defining the per-key blob formats.
+ */
+
+/* DSA is the most orthodox: only the obviously necessary public key
+ * info appears at all, it's in the same order everywhere, and none of
+ * it is repeated unnecessarily */
+enum { DSA_p, DSA_q, DSA_g, DSA_y, DSA_x };
+static const unsigned dsa_pub_fmt[] = { DSA_p, DSA_q, DSA_g, DSA_y };
+static const unsigned dsa_base_ossh_fmt[] = {
+ DSA_p, DSA_q, DSA_g, DSA_y, DSA_x };
+static const unsigned dsa_cert_ossh_fmt[] = { DSA_x };
+
+/* ECDSA is almost as nice, except that it pointlessly mentions the
+ * curve name in the public data, which shouldn't be necessary given
+ * that the SSH key id has already implied it. But at least that's
+ * consistent everywhere. */
+enum { ECDSA_curve, ECDSA_point, ECDSA_exp };
+static const unsigned ecdsa_pub_fmt[] = { ECDSA_curve, ECDSA_point };
+static const unsigned ecdsa_base_ossh_fmt[] = {
+ ECDSA_curve, ECDSA_point, ECDSA_exp };
+static const unsigned ecdsa_cert_ossh_fmt[] = { ECDSA_exp };
+
+/* Ed25519 has the oddity that the private data following the
+ * certificate in the OpenSSH blob is preceded by an extra copy of the
+ * public data, for no obviously necessary reason since that doesn't
+ * happen in any of the rest of these formats */
+enum { EDDSA_point, EDDSA_exp };
+static const unsigned eddsa_pub_fmt[] = { EDDSA_point };
+static const unsigned eddsa_base_ossh_fmt[] = { EDDSA_point, EDDSA_exp };
+static const unsigned eddsa_cert_ossh_fmt[] = { EDDSA_point, EDDSA_exp };
+
+/* And RSA has the quirk that the modulus and exponent are reversed in
+ * the base key type's OpenSSH blob! */
+enum { RSA_e, RSA_n, RSA_d, RSA_p, RSA_q, RSA_iqmp };
+static const unsigned rsa_pub_fmt[] = { RSA_e, RSA_n };
+static const unsigned rsa_base_ossh_fmt[] = {
+ RSA_n, RSA_e, RSA_d, RSA_p, RSA_q, RSA_iqmp };
+static const unsigned rsa_cert_ossh_fmt[] = { RSA_d, RSA_p, RSA_q, RSA_iqmp };
+
+/*
+ * Routines to transform one kind of blob into another based on those
+ * foo_fmt integer arrays.
+ */
+typedef struct BlobTransformer {
+ ptrlen *parts;
+ size_t nparts;
+} BlobTransformer;
+
+#define BLOBTRANS_DECLARE(bt) BlobTransformer bt[1] = { { NULL, 0 } }
+
+static inline void blobtrans_clear(BlobTransformer *bt)
+{
+ sfree(bt->parts);
+ bt->parts = NULL;
+ bt->nparts = 0;
+}
+
+static inline bool blobtrans_read(BlobTransformer *bt, BinarySource *src,
+ blob_fmt blob)
+{
+ size_t nparts = bt->nparts;
+ for (size_t i = 0; i < blob.len; i++)
+ if (nparts < blob.fmt[i]+1)
+ nparts = blob.fmt[i]+1;
+
+ if (nparts > bt->nparts) {
+ bt->parts = sresize(bt->parts, nparts, ptrlen);
+ while (bt->nparts < nparts)
+ bt->parts[bt->nparts++] = make_ptrlen(NULL, 0);
+ }
+
+ for (size_t i = 0; i < blob.len; i++) {
+ size_t j = blob.fmt[i];
+ ptrlen part = get_string(src);
+ if (bt->parts[j].ptr) {
+ /*
+ * If the same string appears in both the public blob and
+ * the private data, check they match. (This happens in
+ * Ed25519: an extra copy of the public point string
+ * appears in the certified OpenSSH data after the
+ * certificate and before the private key.)
+ */
+ if (!ptrlen_eq_ptrlen(bt->parts[j], part))
+ return false;
+ }
+ bt->parts[j] = part;
+ }
+
+ return true;
+}
+
+static inline void blobtrans_write(BlobTransformer *bt, BinarySink *bs,
+ blob_fmt blob)
+{
+ for (size_t i = 0; i < blob.len; i++) {
+ assert(i < bt->nparts);
+ ptrlen part = bt->parts[blob.fmt[i]];
+ assert(part.ptr);
+ put_stringpl(bs, part);
+ }
+}
+
+/*
+ * Forward declarations.
+ */
+static ssh_key *opensshcert_new_pub(const ssh_keyalg *self, ptrlen pub);
+static ssh_key *opensshcert_new_priv(
+ const ssh_keyalg *self, ptrlen pub, ptrlen priv);
+static ssh_key *opensshcert_new_priv_openssh(
+ const ssh_keyalg *self, BinarySource *src);
+static void opensshcert_freekey(ssh_key *key);
+static char *opensshcert_invalid(ssh_key *key, unsigned flags);
+static void opensshcert_sign(ssh_key *key, ptrlen data, unsigned flags,
+ BinarySink *bs);
+static bool opensshcert_verify(ssh_key *key, ptrlen sig, ptrlen data);
+static void opensshcert_public_blob(ssh_key *key, BinarySink *bs);
+static void opensshcert_private_blob(ssh_key *key, BinarySink *bs);
+static void opensshcert_openssh_blob(ssh_key *key, BinarySink *bs);
+static void opensshcert_ca_public_blob(ssh_key *key, BinarySink *bs);
+static void opensshcert_cert_id_string(ssh_key *key, BinarySink *bs);
+static SeatDialogText *opensshcert_cert_info(ssh_key *key);
+static bool opensshcert_has_private(ssh_key *key);
+static char *opensshcert_cache_str(ssh_key *key);
+static key_components *opensshcert_components(ssh_key *key);
+static ssh_key *opensshcert_base_key(ssh_key *key);
+static bool opensshcert_check_cert(
+ ssh_key *key, bool host, ptrlen principal, uint64_t time,
+ const ca_options *opts, BinarySink *error);
+static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob);
+static unsigned opensshcert_supported_flags(const ssh_keyalg *self);
+static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self,
+ unsigned flags);
+static char *opensshcert_alg_desc(const ssh_keyalg *self);
+static bool opensshcert_variable_size(const ssh_keyalg *self);
+static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self,
+ const ssh_keyalg *base);
+
+/*
+ * Top-level vtables for the certified key formats, defined via a list
+ * macro so I can also make an array of them all.
+ */
+
+#define KEYALG_LIST(X) \
+ X(ssh_dsa, "ssh-dss", "ssh-dss", dsa) \
+ X(ssh_rsa, "ssh-rsa", "ssh-rsa", rsa) \
+ X(ssh_rsa_sha256, "rsa-sha2-256", "ssh-rsa", rsa) \
+ X(ssh_rsa_sha512, "rsa-sha2-512", "ssh-rsa", rsa) \
+ X(ssh_ecdsa_ed25519, "ssh-ed25519", "ssh-ed25519", eddsa) \
+ X(ssh_ecdsa_nistp256, "ecdsa-sha2-nistp256","ecdsa-sha2-nistp256", ecdsa) \
+ X(ssh_ecdsa_nistp384, "ecdsa-sha2-nistp384","ecdsa-sha2-nistp384", ecdsa) \
+ X(ssh_ecdsa_nistp521, "ecdsa-sha2-nistp521","ecdsa-sha2-nistp521", ecdsa) \
+ /* end of list */
+
+#define KEYALG_DEF(name, ssh_alg_id_prefix, ssh_key_id_prefix, fmt_prefix) \
+ static const struct opensshcert_extra opensshcert_##name##_extra = { \
+ .pub_fmt = { .fmt = fmt_prefix ## _pub_fmt, \
+ .len = lenof(fmt_prefix ## _pub_fmt) }, \
+ .base_ossh_fmt = { .fmt = fmt_prefix ## _base_ossh_fmt, \
+ .len = lenof(fmt_prefix ## _base_ossh_fmt) }, \
+ .cert_ossh_fmt = { .fmt = fmt_prefix ## _cert_ossh_fmt, \
+ .len = lenof(fmt_prefix ## _cert_ossh_fmt) }, \
+ .cert_key_ssh_id = ssh_key_id_prefix "-cert-v01@openssh.com", \
+ .base_key_ssh_id = ssh_key_id_prefix, \
+ }; \
+ \
+ const ssh_keyalg opensshcert_##name = { \
+ .new_pub = opensshcert_new_pub, \
+ .new_priv = opensshcert_new_priv, \
+ .new_priv_openssh = opensshcert_new_priv_openssh, \
+ .freekey = opensshcert_freekey, \
+ .invalid = opensshcert_invalid, \
+ .sign = opensshcert_sign, \
+ .verify = opensshcert_verify, \
+ .public_blob = opensshcert_public_blob, \
+ .private_blob = opensshcert_private_blob, \
+ .openssh_blob = opensshcert_openssh_blob, \
+ .has_private = opensshcert_has_private, \
+ .cache_str = opensshcert_cache_str, \
+ .components = opensshcert_components, \
+ .base_key = opensshcert_base_key, \
+ .ca_public_blob = opensshcert_ca_public_blob, \
+ .check_cert = opensshcert_check_cert, \
+ .cert_id_string = opensshcert_cert_id_string, \
+ .cert_info = opensshcert_cert_info, \
+ .pubkey_bits = opensshcert_pubkey_bits, \
+ .supported_flags = opensshcert_supported_flags, \
+ .alternate_ssh_id = opensshcert_alternate_ssh_id, \
+ .alg_desc = opensshcert_alg_desc, \
+ .variable_size = opensshcert_variable_size, \
+ .related_alg = opensshcert_related_alg, \
+ .ssh_id = ssh_alg_id_prefix "-cert-v01@openssh.com", \
+ .cache_id = "opensshcert-" ssh_key_id_prefix, \
+ .extra = &opensshcert_##name##_extra, \
+ .is_certificate = true, \
+ .base_alg = &name, \
+ };
+KEYALG_LIST(KEYALG_DEF)
+#undef KEYALG_DEF
+
+#define KEYALG_LIST_ENTRY(name, algid, keyid, fmt) &opensshcert_##name,
+static const ssh_keyalg *const opensshcert_all_keyalgs[] = {
+ KEYALG_LIST(KEYALG_LIST_ENTRY)
+};
+#undef KEYALG_LIST_ENTRY
+
+static strbuf *get_base_public_blob(BinarySource *src,
+ const opensshcert_extra *extra)
+{
+ strbuf *basepub = strbuf_new();
+ put_stringz(basepub, extra->base_key_ssh_id);
+
+ /* Make the base public key blob out of the public key
+ * material in the certificate. This invocation of the
+ * blobtrans system doesn't do any format translation, but it
+ * does ensure that the right amount of data is copied so that
+ * src ends up in the right position to read the remaining
+ * certificate fields. */
+ BLOBTRANS_DECLARE(bt);
+ blobtrans_read(bt, src, extra->pub_fmt);
+ blobtrans_write(bt, BinarySink_UPCAST(basepub), extra->pub_fmt);
+ blobtrans_clear(bt);
+
+ return basepub;
+}
+
+static opensshcert_key *opensshcert_new_shared(
+ const ssh_keyalg *self, ptrlen blob, strbuf **basepub_out)
+{
+ const opensshcert_extra *extra = self->extra;
+
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, blob);
+
+ /* Check the initial key-type string */
+ if (!ptrlen_eq_string(get_string(src), extra->cert_key_ssh_id))
+ return NULL;
+
+ opensshcert_key *ck = snew(opensshcert_key);
+ memset(ck, 0, sizeof(*ck));
+ ck->sshk.vt = self;
+
+ ck->nonce = strbuf_dup(get_string(src));
+ strbuf *basepub = get_base_public_blob(src, extra);
+ ck->serial = get_uint64(src);
+ ck->type = get_uint32(src);
+ ck->key_id = strbuf_dup(get_string(src));
+ ck->valid_principals = strbuf_dup(get_string(src));
+ ck->valid_after = get_uint64(src);
+ ck->valid_before = get_uint64(src);
+ ck->critical_options = strbuf_dup(get_string(src));
+ ck->extensions = strbuf_dup(get_string(src));
+ ck->reserved = strbuf_dup(get_string(src));
+ ck->signature_key = strbuf_dup(get_string(src));
+ ck->signature = strbuf_dup(get_string(src));
+
+
+ if (get_err(src)) {
+ ssh_key_free(&ck->sshk);
+ strbuf_free(basepub);
+ return NULL;
+ }
+
+ *basepub_out = basepub;
+ return ck;
+}
+
+static ssh_key *opensshcert_new_pub(const ssh_keyalg *self, ptrlen pub)
+{
+ strbuf *basepub;
+ opensshcert_key *ck = opensshcert_new_shared(self, pub, &basepub);
+ if (!ck)
+ return NULL;
+
+ ck->basekey = ssh_key_new_pub(self->base_alg, ptrlen_from_strbuf(basepub));
+ strbuf_free(basepub);
+
+ if (!ck->basekey) {
+ ssh_key_free(&ck->sshk);
+ return NULL;
+ }
+
+ return &ck->sshk;
+}
+
+static ssh_key *opensshcert_new_priv(
+ const ssh_keyalg *self, ptrlen pub, ptrlen priv)
+{
+ strbuf *basepub;
+ opensshcert_key *ck = opensshcert_new_shared(self, pub, &basepub);
+ if (!ck)
+ return NULL;
+
+ ck->basekey = ssh_key_new_priv(self->base_alg,
+ ptrlen_from_strbuf(basepub), priv);
+ strbuf_free(basepub);
+
+ if (!ck->basekey) {
+ ssh_key_free(&ck->sshk);
+ return NULL;
+ }
+
+ return &ck->sshk;
+}
+
+static ssh_key *opensshcert_new_priv_openssh(
+ const ssh_keyalg *self, BinarySource *src)
+{
+ const opensshcert_extra *extra = self->extra;
+
+ ptrlen cert = get_string(src);
+
+ strbuf *basepub;
+ opensshcert_key *ck = opensshcert_new_shared(self, cert, &basepub);
+ if (!ck)
+ return NULL;
+
+ strbuf *baseossh = strbuf_new();
+
+ /* Make the base OpenSSH key blob out of the public key blob
+ * returned from opensshcert_new_shared, and the trailing
+ * private data following the certificate */
+ BLOBTRANS_DECLARE(bt);
+
+ BinarySource pubsrc[1];
+ BinarySource_BARE_INIT_PL(pubsrc, ptrlen_from_strbuf(basepub));
+ get_string(pubsrc); /* skip key type id */
+
+ /* blobtrans_read might fail in this case, because we're reading
+ * from two sources and they might fail to match */
+ bool success = blobtrans_read(bt, pubsrc, extra->pub_fmt) &&
+ blobtrans_read(bt, src, extra->cert_ossh_fmt);
+
+ blobtrans_write(bt, BinarySink_UPCAST(baseossh), extra->base_ossh_fmt);
+ blobtrans_clear(bt);
+
+ if (!success) {
+ ssh_key_free(&ck->sshk);
+ strbuf_free(basepub);
+ strbuf_free(baseossh);
+ return NULL;
+ }
+
+ strbuf_free(basepub);
+
+ BinarySource osshsrc[1];
+ BinarySource_BARE_INIT_PL(osshsrc, ptrlen_from_strbuf(baseossh));
+ ck->basekey = ssh_key_new_priv_openssh(self->base_alg, osshsrc);
+ strbuf_free(baseossh);
+
+ if (!ck->basekey) {
+ ssh_key_free(&ck->sshk);
+ return NULL;
+ }
+
+ return &ck->sshk;
+}
+
+static void opensshcert_freekey(ssh_key *key)
+{
+ opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+
+ /* If this function is called from one of the above constructors
+ * because it failed part way through, we might not have managed
+ * to construct ck->basekey, so it might be NULL. */
+ if (ck->basekey)
+ ssh_key_free(ck->basekey);
+
+ strbuf_free(ck->nonce);
+ strbuf_free(ck->key_id);
+ strbuf_free(ck->valid_principals);
+ strbuf_free(ck->critical_options);
+ strbuf_free(ck->extensions);
+ strbuf_free(ck->reserved);
+ strbuf_free(ck->signature_key);
+ strbuf_free(ck->signature);
+
+ sfree(ck);
+}
+
+static ssh_key *opensshcert_base_key(ssh_key *key)
+{
+ opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+ return ck->basekey;
+}
+
+/*
+ * Make a public key object from the CA public blob, potentially
+ * taking into account that the signature might override the algorithm
+ * name
+ */
+static ssh_key *opensshcert_ca_pub_key(
+ opensshcert_key *ck, ptrlen sig, ptrlen *algname)
+{
+ ptrlen ca_keyblob = ptrlen_from_strbuf(ck->signature_key);
+
+ ptrlen alg_source = sig.ptr ? sig : ca_keyblob;
+ if (algname)
+ *algname = pubkey_blob_to_alg_name(alg_source);
+
+ const ssh_keyalg *ca_alg = pubkey_blob_to_alg(alg_source);
+ if (!ca_alg)
+ return NULL; /* don't even recognise the certifying key type */
+
+ return ssh_key_new_pub(ca_alg, ca_keyblob);
+}
+
+static void opensshcert_signature_preimage(opensshcert_key *ck, BinarySink *bs)
+{
+ const opensshcert_extra *extra = ck->sshk.vt->extra;
+ put_stringz(bs, extra->cert_key_ssh_id);
+ put_stringpl(bs, ptrlen_from_strbuf(ck->nonce));
+
+ strbuf *basepub = strbuf_new();
+ ssh_key_public_blob(ck->basekey, BinarySink_UPCAST(basepub));
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(basepub));
+ get_string(src); /* skip initial key type string */
+ put_data(bs, get_ptr(src), get_avail(src));
+ strbuf_free(basepub);
+
+ put_uint64(bs, ck->serial);
+ put_uint32(bs, ck->type);
+ put_stringpl(bs, ptrlen_from_strbuf(ck->key_id));
+ put_stringpl(bs, ptrlen_from_strbuf(ck->valid_principals));
+ put_uint64(bs, ck->valid_after);
+ put_uint64(bs, ck->valid_before);
+ put_stringpl(bs, ptrlen_from_strbuf(ck->critical_options));
+ put_stringpl(bs, ptrlen_from_strbuf(ck->extensions));
+ put_stringpl(bs, ptrlen_from_strbuf(ck->reserved));
+ put_stringpl(bs, ptrlen_from_strbuf(ck->signature_key));
+}
+
+static void opensshcert_public_blob(ssh_key *key, BinarySink *bs)
+{
+ opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+
+ opensshcert_signature_preimage(ck, bs);
+ put_stringpl(bs, ptrlen_from_strbuf(ck->signature));
+}
+
+static void opensshcert_private_blob(ssh_key *key, BinarySink *bs)
+{
+ opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+ ssh_key_private_blob(ck->basekey, bs);
+}
+
+static void opensshcert_openssh_blob(ssh_key *key, BinarySink *bs)
+{
+ opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+ const opensshcert_extra *extra = key->vt->extra;
+
+ strbuf *cert = strbuf_new();
+ ssh_key_public_blob(key, BinarySink_UPCAST(cert));
+ put_stringsb(bs, cert);
+
+ strbuf *baseossh = strbuf_new_nm();
+ ssh_key_openssh_blob(ck->basekey, BinarySink_UPCAST(baseossh));
+ BinarySource basesrc[1];
+ BinarySource_BARE_INIT_PL(basesrc, ptrlen_from_strbuf(baseossh));
+
+ BLOBTRANS_DECLARE(bt);
+ blobtrans_read(bt, basesrc, extra->base_ossh_fmt);
+ blobtrans_write(bt, bs, extra->cert_ossh_fmt);
+ blobtrans_clear(bt);
+
+ strbuf_free(baseossh);
+}
+
+static void opensshcert_ca_public_blob(ssh_key *key, BinarySink *bs)
+{
+ opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+ put_datapl(bs, ptrlen_from_strbuf(ck->signature_key));
+}
+
+static void opensshcert_cert_id_string(ssh_key *key, BinarySink *bs)
+{
+ opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+ put_datapl(bs, ptrlen_from_strbuf(ck->key_id));
+}
+
+static bool opensshcert_has_private(ssh_key *key)
+{
+ opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+ return ssh_key_has_private(ck->basekey);
+}
+
+static char *opensshcert_cache_str(ssh_key *key)
+{
+ opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+ return ssh_key_cache_str(ck->basekey);
+}
+
+static void opensshcert_time_to_iso8601(BinarySink *bs, uint64_t time)
+{
+ time_t t = time;
+ char buf[256];
+ put_data(bs, buf, strftime(buf, sizeof(buf),
+ "%Y-%m-%d %H:%M:%S UTC", gmtime(&t)));
+}
+
+static void opensshcert_string_list_key_components(
+ key_components *kc, strbuf *input, const char *title, const char *title2)
+{
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(input));
+
+ const char *titles[2] = { title, title2 };
+ size_t ntitles = (title2 ? 2 : 1);
+
+ unsigned index = 0;
+ while (get_avail(src)) {
+ for (size_t ti = 0; ti < ntitles; ti++) {
+ ptrlen value = get_string(src);
+ if (get_err(src))
+ break;
+ char *name = dupprintf("%s_%u", titles[ti], index);
+ key_components_add_text_pl(kc, name, value);
+ sfree(name);
+ }
+ index++;
+ }
+}
+
+static key_components *opensshcert_components(ssh_key *key)
+{
+ opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+ key_components *kc = ssh_key_components(ck->basekey);
+ key_components_add_binary(kc, "cert_nonce", ptrlen_from_strbuf(ck->nonce));
+ key_components_add_uint(kc, "cert_serial", ck->serial);
+ switch (ck->type) {
+ case SSH_CERT_TYPE_HOST:
+ key_components_add_text(kc, "cert_type", "host");
+ break;
+ case SSH_CERT_TYPE_USER:
+ key_components_add_text(kc, "cert_type", "user");
+ break;
+ default:
+ key_components_add_uint(kc, "cert_type", ck->type);
+ break;
+ }
+ key_components_add_text(kc, "cert_key_id", ck->key_id->s);
+ opensshcert_string_list_key_components(kc, ck->valid_principals,
+ "cert_valid_principal", NULL);
+ key_components_add_uint(kc, "cert_valid_after", ck->valid_after);
+ key_components_add_uint(kc, "cert_valid_before", ck->valid_before);
+ /* Translate the validity period into human-legible dates, but
+ * only if they're not the min/max integer. Rationale: if you see
+ * "584554051223-11-09 07:00:15 UTC" as the expiry time you'll be
+ * as likely to think it's a weird buffer overflow as half a
+ * trillion years in the future! */
+ if (ck->valid_after != 0) {
+ strbuf *date = strbuf_new();
+ opensshcert_time_to_iso8601(BinarySink_UPCAST(date), ck->valid_after);
+ key_components_add_text_pl(kc, "cert_valid_after_date",
+ ptrlen_from_strbuf(date));
+ strbuf_free(date);
+ }
+ if (ck->valid_before != 0xFFFFFFFFFFFFFFFF) {
+ strbuf *date = strbuf_new();
+ opensshcert_time_to_iso8601(BinarySink_UPCAST(date), ck->valid_before);
+ key_components_add_text_pl(kc, "cert_valid_before_date",
+ ptrlen_from_strbuf(date));
+ strbuf_free(date);
+ }
+ opensshcert_string_list_key_components(kc, ck->critical_options,
+ "cert_critical_option",
+ "cert_critical_option_data");
+ opensshcert_string_list_key_components(kc, ck->extensions,
+ "cert_extension",
+ "cert_extension_data");
+ key_components_add_binary(kc, "cert_ca_key", ptrlen_from_strbuf(
+ ck->signature_key));
+
+ ptrlen ca_algname;
+ ssh_key *ca_key = opensshcert_ca_pub_key(ck, make_ptrlen(NULL, 0),
+ &ca_algname);
+ key_components_add_text_pl(kc, "cert_ca_key_algorithm_id", ca_algname);
+
+ if (ca_key) {
+ key_components *kc_ca_key = ssh_key_components(ca_key);
+ for (size_t i = 0; i < kc_ca_key->ncomponents; i++) {
+ key_component *comp = &kc_ca_key->components[i];
+ char *subname = dupcat("cert_ca_key_", comp->name);
+ key_components_add_copy(kc, subname, comp);
+ sfree(subname);
+ }
+ key_components_free(kc_ca_key);
+ ssh_key_free(ca_key);
+ }
+
+ key_components_add_binary(kc, "cert_ca_sig", ptrlen_from_strbuf(
+ ck->signature));
+ return kc;
+}
+
+static SeatDialogText *opensshcert_cert_info(ssh_key *key)
+{
+ opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+ SeatDialogText *text = seat_dialog_text_new();
+ strbuf *tmp = strbuf_new();
+
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "Certificate type");
+ switch (ck->type) {
+ case SSH_CERT_TYPE_HOST:
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
+ "host key");
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "Valid host names");
+ break;
+ case SSH_CERT_TYPE_USER:
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
+ "user authentication key");
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "Valid user names");
+ break;
+ default:
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
+ "unknown type %" PRIu32, ck->type);
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "Valid principals");
+ break;
+ }
+
+ {
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
+ ck->valid_principals));
+ const char *sep = "";
+ strbuf_clear(tmp);
+ while (get_avail(src)) {
+ ptrlen principal = get_string(src);
+ if (get_err(src))
+ break;
+ put_dataz(tmp, sep);
+ sep = ",";
+ put_datapl(tmp, principal);
+ }
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
+ "%s", tmp->s);
+ }
+
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "Validity period");
+ strbuf_clear(tmp);
+ if (ck->valid_after == 0) {
+ if (ck->valid_before == 0xFFFFFFFFFFFFFFFF) {
+ put_dataz(tmp, "forever");
+ } else {
+ put_dataz(tmp, "until ");
+ opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp),
+ ck->valid_before);
+ }
+ } else {
+ if (ck->valid_before == 0xFFFFFFFFFFFFFFFF) {
+ put_dataz(tmp, "after ");
+ opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp),
+ ck->valid_after);
+ } else {
+ opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp),
+ ck->valid_after);
+ put_dataz(tmp, " - ");
+ opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp),
+ ck->valid_before);
+ }
+ }
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", tmp->s);
+
+ /*
+ * List critical options we know about. (This is everything listed
+ * in PROTOCOL.certkeys that isn't specific to U2F/FIDO key types
+ * that PuTTY doesn't currently support.)
+ */
+ {
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
+ ck->critical_options));
+ strbuf_clear(tmp);
+ while (get_avail(src)) {
+ ptrlen key = get_string(src);
+ ptrlen value = get_string(src);
+ if (get_err(src))
+ break;
+ if (ck->type == SSH_CERT_TYPE_USER &&
+ ptrlen_eq_string(key, "source-address")) {
+ BinarySource src2[1];
+ BinarySource_BARE_INIT_PL(src2, value);
+ ptrlen addresslist = get_string(src2);
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "Permitted client IP addresses");
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
+ "%.*s", PTRLEN_PRINTF(addresslist));
+ } else if (ck->type == SSH_CERT_TYPE_USER &&
+ ptrlen_eq_string(key, "force-command")) {
+ BinarySource src2[1];
+ BinarySource_BARE_INIT_PL(src2, value);
+ ptrlen command = get_string(src2);
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "Forced remote command");
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
+ "%.*s", PTRLEN_PRINTF(command));
+ }
+ }
+ }
+
+ /*
+ * List certificate extensions. Again, we go through everything in
+ * PROTOCOL.certkeys that isn't specific to U2F/FIDO key types.
+ * But we also flip the sense round for user-readability: I think
+ * it's more likely that the typical key will permit all these
+ * things, so we emit no output in that case, and only mention the
+ * things that _aren't_ enabled.
+ */
+
+ bool x11_ok = false, agent_ok = false, portfwd_ok = false;
+ bool pty_ok = false, user_rc_ok = false;
+
+ {
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
+ ck->extensions));
+ while (get_avail(src)) {
+ ptrlen key = get_string(src);
+ /* ptrlen value = */ get_string(src); // nothing needs this yet
+ if (get_err(src))
+ break;
+ if (ptrlen_eq_string(key, "permit-X11-forwarding")) {
+ x11_ok = true;
+ } else if (ptrlen_eq_string(key, "permit-agent-forwarding")) {
+ agent_ok = true;
+ } else if (ptrlen_eq_string(key, "permit-port-forwarding")) {
+ portfwd_ok = true;
+ } else if (ptrlen_eq_string(key, "permit-pty")) {
+ pty_ok = true;
+ } else if (ptrlen_eq_string(key, "permit-user-rc")) {
+ user_rc_ok = true;
+ }
+ }
+ }
+
+ if (ck->type == SSH_CERT_TYPE_USER) {
+ if (!x11_ok) {
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "X11 forwarding permitted");
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
+ }
+ if (!agent_ok) {
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "Agent forwarding permitted");
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
+ }
+ if (!portfwd_ok) {
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "Port forwarding permitted");
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
+ }
+ if (!pty_ok) {
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "PTY allocation permitted");
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
+ }
+ if (!user_rc_ok) {
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "Running user ~/.ssh.rc permitted");
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no");
+ }
+ }
+
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "Certificate ID string");
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
+ "%s", ck->key_id->s);
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "Certificate serial number");
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT,
+ "%" PRIu64, ck->serial);
+
+ char *fp = ssh2_fingerprint_blob(ptrlen_from_strbuf(ck->signature_key),
+ SSH_FPTYPE_DEFAULT);
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "Fingerprint of signing CA key");
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp);
+ sfree(fp);
+
+ fp = ssh2_fingerprint(key, ssh_fptype_to_cert(SSH_FPTYPE_DEFAULT));
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "Fingerprint including certificate");
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp);
+ sfree(fp);
+
+ strbuf_free(tmp);
+ return text;
+}
+
+static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob)
+{
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, blob);
+
+ get_string(src); /* key type */
+ get_string(src); /* nonce */
+ strbuf *basepub = get_base_public_blob(src, self->extra);
+ int bits = ssh_key_public_bits(
+ self->base_alg, ptrlen_from_strbuf(basepub));
+ strbuf_free(basepub);
+ return bits;
+}
+
+static unsigned opensshcert_supported_flags(const ssh_keyalg *self)
+{
+ return ssh_keyalg_supported_flags(self->base_alg);
+}
+
+static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self,
+ unsigned flags)
+{
+ const char *base_id = ssh_keyalg_alternate_ssh_id(self->base_alg, flags);
+
+ for (size_t i = 0; i < lenof(opensshcert_all_keyalgs); i++) {
+ const ssh_keyalg *alg_i = opensshcert_all_keyalgs[i];
+ if (!strcmp(base_id, alg_i->base_alg->ssh_id))
+ return alg_i->ssh_id;
+ }
+
+ return self->ssh_id;
+}
+
+static char *opensshcert_alg_desc(const ssh_keyalg *self)
+{
+ char *base_desc = ssh_keyalg_desc(self->base_alg);
+ char *our_desc = dupcat(base_desc, " cert");
+ sfree(base_desc);
+ return our_desc;
+}
+
+static bool opensshcert_variable_size(const ssh_keyalg *self)
+{
+ return ssh_keyalg_variable_size(self->base_alg);
+}
+
+static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self,
+ const ssh_keyalg *base)
+{
+ for (size_t i = 0; i < lenof(opensshcert_all_keyalgs); i++) {
+ const ssh_keyalg *alg_i = opensshcert_all_keyalgs[i];
+ if (base == alg_i->base_alg)
+ return alg_i;
+ }
+
+ return self;
+}
+
+static char *opensshcert_invalid(ssh_key *key, unsigned flags)
+{
+ opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+ return ssh_key_invalid(ck->basekey, flags);
+}
+
+static bool opensshcert_check_cert(
+ ssh_key *key, bool host, ptrlen principal, uint64_t time,
+ const ca_options *opts, BinarySink *error)
+{
+ opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+ bool result = false;
+ ssh_key *ca_key = NULL;
+ strbuf *preimage = strbuf_new();
+ BinarySource src[1];
+
+ ptrlen signature = ptrlen_from_strbuf(ck->signature);
+
+ /*
+ * The OpenSSH certificate spec is one-layer only: it explicitly
+ * forbids using a certified key in turn as the CA.
+ *
+ * If it did not, then we'd also have to recursively verify
+ * everything up the CA chain until we reached the ultimate root,
+ * and then make sure _that_ was something we trusted. (Not to
+ * mention that there'd probably be an additional SSH_CERT_TYPE_CA
+ * or some such, and certificate options saying what kinds of
+ * certificate a CA was trusted to sign for, and ...)
+ */
+ ca_key = opensshcert_ca_pub_key(ck, make_ptrlen(NULL, 0), NULL);
+ if (!ca_key) {
+ put_fmt(error, "Certificate's signing key is invalid");
+ goto out;
+ }
+ if (ssh_key_alg(ca_key)->is_certificate) {
+ put_fmt(error, "Certificate is signed with a certified key "
+ "(forbidden by OpenSSH certificate specification)");
+ goto out;
+ }
+
+ /*
+ * Now re-instantiate the key in a way that matches the signature
+ * (i.e. so that if the key is an RSA one we get the right subtype
+ * of RSA).
+ */
+ ssh_key_free(ca_key);
+ ca_key = opensshcert_ca_pub_key(ck, signature, NULL);
+ if (!ca_key) {
+ put_fmt(error, "Certificate's signing key does not match "
+ "signature type");
+ goto out;
+ }
+
+ /* Check which signature algorithm is actually in use, because
+ * that might be a reason to reject the certificate (e.g. ssh-rsa
+ * when we wanted rsa-sha2-*). */
+ const ssh_keyalg *sig_alg = ssh_key_alg(ca_key);
+ if ((sig_alg == &ssh_rsa && !opts->permit_rsa_sha1) ||
+ (sig_alg == &ssh_rsa_sha256 && !opts->permit_rsa_sha256) ||
+ (sig_alg == &ssh_rsa_sha512 && !opts->permit_rsa_sha512)) {
+ put_fmt(error, "Certificate signature uses '%s' signature type "
+ "(forbidden by user configuration)", sig_alg->ssh_id);
+ goto out;
+ }
+
+ opensshcert_signature_preimage(ck, BinarySink_UPCAST(preimage));
+
+ if (!ssh_key_verify(ca_key, signature, ptrlen_from_strbuf(preimage))) {
+ put_fmt(error, "Certificate's signature is invalid");
+ goto out;
+ }
+
+ uint32_t expected_type = host ? SSH_CERT_TYPE_HOST : SSH_CERT_TYPE_USER;
+ if (ck->type != expected_type) {
+ put_fmt(error, "Certificate type is ");
+ switch (ck->type) {
+ case SSH_CERT_TYPE_HOST:
+ put_fmt(error, "host");
+ break;
+ case SSH_CERT_TYPE_USER:
+ put_fmt(error, "user");
+ break;
+ default:
+ put_fmt(error, "unknown value %" PRIu32, ck->type);
+ break;
+ }
+ put_fmt(error, "; expected %s", host ? "host" : "user");
+ goto out;
+ }
+
+ /*
+ * Check the time bounds on the certificate.
+ */
+ if (time < ck->valid_after) {
+ put_fmt(error, "Certificate is not valid until ");
+ opensshcert_time_to_iso8601(BinarySink_UPCAST(error), time);
+ goto out;
+ }
+ if (time >= ck->valid_before) {
+ put_fmt(error, "Certificate expired at ");
+ opensshcert_time_to_iso8601(BinarySink_UPCAST(error), time);
+ goto out;
+ }
+
+ /*
+ * Check that this certificate is for the right thing.
+ *
+ * If valid_principals is a zero-length string then this is
+ * specified to be a carte-blanche certificate valid for any
+ * principal (at least, provided you trust the CA that issued it).
+ */
+ if (ck->valid_principals->len != 0) {
+ BinarySource_BARE_INIT_PL(
+ src, ptrlen_from_strbuf(ck->valid_principals));
+
+ while (get_avail(src)) {
+ ptrlen valid_principal = get_string(src);
+ if (get_err(src)) {
+ put_fmt(error, "Certificate's valid principals list is "
+ "incorrectly formatted");
+ goto out;
+ }
+ if (ptrlen_eq_ptrlen(valid_principal, principal))
+ goto principal_ok;
+ }
+
+ /*
+ * No valid principal matched. Now go through the list a
+ * second time writing the cert contents into the error
+ * message, so that the user can see at a glance what went
+ * wrong.
+ *
+ * (If you've typed the wrong spelling of the host name, you
+ * really need to see "This cert is for 'foo.example.com' and
+ * I was trying to match it against 'foo'", rather than just
+ * "Computer says no".)
+ */
+ put_fmt(error, "Certificate's %s list [",
+ host ? "hostname" : "username");
+ BinarySource_BARE_INIT_PL(
+ src, ptrlen_from_strbuf(ck->valid_principals));
+ const char *sep = "";
+ while (get_avail(src)) {
+ ptrlen valid_principal = get_string(src);
+ put_fmt(error, "%s\"", sep);
+ put_c_string_literal(error, valid_principal);
+ put_fmt(error, "\"");
+ sep = ", ";
+ }
+ put_fmt(error, "] does not contain expected %s \"",
+ host ? "hostname" : "username");
+ put_c_string_literal(error, principal);
+ put_fmt(error, "\"");
+ goto out;
+ principal_ok:;
+ }
+
+ /*
+ * Check for critical options.
+ */
+ {
+ BinarySource_BARE_INIT_PL(
+ src, ptrlen_from_strbuf(ck->critical_options));
+
+ while (get_avail(src)) {
+ ptrlen option = get_string(src);
+ ptrlen data = get_string(src);
+ if (get_err(src)) {
+ put_fmt(error, "Certificate's critical options list is "
+ "incorrectly formatted");
+ goto out;
+ }
+
+ /*
+ * If we ever do support any options, this will be where
+ * we insert code to recognise and validate them.
+ *
+ * At present, we implement no critical options at all.
+ * (For host certs, as of 2022-04-20, OpenSSH hasn't
+ * defined any. For user certs, the only SSH server using
+ * this is Uppity, which doesn't support key restrictions
+ * in general.)
+ */
+ (void)data; /* no options supported => no use made of the data */
+
+ /*
+ * Report an unrecognised literal.
+ */
+ put_fmt(error, "Certificate specifies an unsupported critical "
+ "option \"");
+ put_c_string_literal(error, option);
+ put_fmt(error, "\"");
+ goto out;
+ }
+ }
+
+ /* If we get here without failing any check, accept the certificate! */
+ result = true;
+
+ out:
+ if (ca_key)
+ ssh_key_free(ca_key);
+ strbuf_free(preimage);
+ return result;
+}
+
+static bool opensshcert_verify(ssh_key *key, ptrlen sig, ptrlen data)
+{
+ /* This method is pure *signature* verification; checking the
+ * certificate is done elsewhere. */
+ opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+ return ssh_key_verify(ck->basekey, sig, data);
+}
+
+static void opensshcert_sign(ssh_key *key, ptrlen data, unsigned flags,
+ BinarySink *bs)
+{
+ opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
+ ssh_key_sign(ck->basekey, data, flags, bs);
+}
diff --git a/crypto/prng.c b/crypto/prng.c
new file mode 100644
index 00000000..247d1dcf
--- /dev/null
+++ b/crypto/prng.c
@@ -0,0 +1,287 @@
+/*
+ * PuTTY's cryptographic pseudorandom number generator.
+ *
+ * This module just defines the PRNG object type and its methods. The
+ * usual global instance of it is managed by sshrand.c.
+ */
+
+#include "putty.h"
+#include "ssh.h"
+#include "mpint_i.h"
+
+#ifdef PRNG_DIAGNOSTICS
+#define prngdebug debug
+#else
+#define prngdebug(...) ((void)0)
+#endif
+
+/*
+ * This random number generator is based on the 'Fortuna' design by
+ * Niels Ferguson and Bruce Schneier. The biggest difference is that I
+ * use SHA-256 in place of a block cipher: the generator side of the
+ * system works by computing HASH(key || counter) instead of
+ * ENCRYPT(counter, key).
+ *
+ * Rationale: the Fortuna description itself suggests that using
+ * SHA-256 would be nice but people wouldn't accept it because it's
+ * too slow - but PuTTY isn't a heavy enough user of random numbers to
+ * make that a serious worry. In fact even with SHA-256 this generator
+ * is faster than the one we previously used. Also the Fortuna
+ * description worries about periodic rekeying to avoid the barely
+ * detectable pattern of never repeating a cipher block - but with
+ * SHA-256, even that shouldn't be a worry, because the output
+ * 'blocks' are twice the size, and also SHA-256 has no guarantee of
+ * bijectivity, so it surely _could_ be possible to generate the same
+ * block from two counter values. Thirdly, Fortuna has to have a hash
+ * function anyway, for reseeding and entropy collection, so reusing
+ * the same one means it only depends on one underlying primitive and
+ * can be easily reinstantiated with a larger hash function if you
+ * decide you'd like to do that on a particular occasion.
+ */
+
+#define NCOLLECTORS 32
+#define RESEED_DATA_SIZE 64
+
+typedef struct prng_impl prng_impl;
+struct prng_impl {
+ prng Prng;
+
+ const ssh_hashalg *hashalg;
+
+ /*
+ * Generation side:
+ *
+ * 'generator' is a hash object with the current key preloaded
+ * into it. The counter-mode generation is achieved by copying
+ * that hash object, appending the counter value to the copy, and
+ * calling ssh_hash_final.
+ */
+ ssh_hash *generator;
+ BignumInt counter[128 / BIGNUM_INT_BITS];
+
+ /*
+ * When re-seeding the generator, you call prng_seed_begin(),
+ * which sets up a hash object in 'keymaker'. You write your new
+ * seed data into it (which you can do by calling put_data on the
+ * PRNG object itself) and then call prng_seed_finish(), which
+ * finalises this hash and uses the output to set up the new
+ * generator.
+ *
+ * The keymaker hash preimage includes the previous key, so if you
+ * just want to change keys for the sake of not keeping the same
+ * one for too long, you don't have to put any extra seed data in
+ * at all.
+ */
+ ssh_hash *keymaker;
+
+ /*
+ * Collection side:
+ *
+ * There are NCOLLECTORS hash objects collecting entropy. Each
+ * separately numbered entropy source puts its output into those
+ * hash objects in the order 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,...,
+ * that is to say, each entropy source has a separate counter
+ * which is incremented every time that source generates an event,
+ * and the event data is added to the collector corresponding to
+ * the index of the lowest set bit in the current counter value.
+ *
+ * Whenever collector #0 has at least RESEED_DATA_SIZE bytes (and
+ * it's not at least 100ms since the last reseed), the PRNG is
+ * reseeded, with seed data on reseed #n taken from the first j
+ * collectors, where j is one more than the number of factors of 2
+ * in n. That is, collector #0 is used in every reseed; #1 in
+ * every other one, #2 in every fourth, etc.
+ *
+ * 'until_reseed' counts the amount of data that still needs to be
+ * added to collector #0 before a reseed will be triggered.
+ */
+ uint32_t source_counters[NOISE_MAX_SOURCES];
+ ssh_hash *collectors[NCOLLECTORS];
+ size_t until_reseed;
+ uint32_t reseeds;
+ uint64_t last_reseed_time;
+};
+
+static void prng_seed_BinarySink_write(
+ BinarySink *bs, const void *data, size_t len);
+
+prng *prng_new(const ssh_hashalg *hashalg)
+{
+ prng_impl *pi = snew(prng_impl);
+
+ memset(pi, 0, sizeof(prng_impl));
+ pi->hashalg = hashalg;
+ pi->keymaker = NULL;
+ pi->generator = NULL;
+ memset(pi->counter, 0, sizeof(pi->counter));
+ for (size_t i = 0; i < NCOLLECTORS; i++)
+ pi->collectors[i] = ssh_hash_new(pi->hashalg);
+ pi->until_reseed = 0;
+ BinarySink_INIT(&pi->Prng, prng_seed_BinarySink_write);
+
+ pi->Prng.savesize = pi->hashalg->hlen * 4;
+
+ return &pi->Prng;
+}
+
+void prng_free(prng *pr)
+{
+ prng_impl *pi = container_of(pr, prng_impl, Prng);
+
+ smemclr(pi->counter, sizeof(pi->counter));
+ for (size_t i = 0; i < NCOLLECTORS; i++)
+ ssh_hash_free(pi->collectors[i]);
+ if (pi->generator)
+ ssh_hash_free(pi->generator);
+ if (pi->keymaker)
+ ssh_hash_free(pi->keymaker);
+ smemclr(pi, sizeof(*pi));
+ sfree(pi);
+}
+
+void prng_seed_begin(prng *pr)
+{
+ prng_impl *pi = container_of(pr, prng_impl, Prng);
+
+ assert(!pi->keymaker);
+
+ prngdebug("prng: reseed begin\n");
+
+ /*
+ * Make a hash instance that will generate the key for the new one.
+ */
+ if (pi->generator) {
+ pi->keymaker = pi->generator;
+ pi->generator = NULL;
+ } else {
+ pi->keymaker = ssh_hash_new(pi->hashalg);
+ }
+
+ put_byte(pi->keymaker, 'R');
+}
+
+static void prng_seed_BinarySink_write(
+ BinarySink *bs, const void *data, size_t len)
+{
+ prng *pr = BinarySink_DOWNCAST(bs, prng);
+ prng_impl *pi = container_of(pr, prng_impl, Prng);
+ assert(pi->keymaker);
+ prngdebug("prng: got %"SIZEu" bytes of seed\n", len);
+ put_data(pi->keymaker, data, len);
+}
+
+void prng_seed_finish(prng *pr)
+{
+ prng_impl *pi = container_of(pr, prng_impl, Prng);
+ unsigned char buf[MAX_HASH_LEN];
+
+ assert(pi->keymaker);
+
+ prngdebug("prng: reseed finish\n");
+
+ /*
+ * Actually generate the key.
+ */
+ ssh_hash_final(pi->keymaker, buf);
+ pi->keymaker = NULL;
+
+ /*
+ * Load that key into a fresh hash instance, which will become the
+ * new generator.
+ */
+ assert(!pi->generator);
+ pi->generator = ssh_hash_new(pi->hashalg);
+ put_data(pi->generator, buf, pi->hashalg->hlen);
+
+ pi->until_reseed = RESEED_DATA_SIZE;
+ pi->last_reseed_time = prng_reseed_time_ms();
+
+ smemclr(buf, sizeof(buf));
+}
+
+static inline void prng_generate(prng_impl *pi, void *outbuf)
+{
+ ssh_hash *h = ssh_hash_copy(pi->generator);
+
+ prngdebug("prng_generate\n");
+ put_byte(h, 'G');
+ for (unsigned i = 0; i < 128; i += 8)
+ put_byte(h, pi->counter[i/BIGNUM_INT_BITS] >> (i%BIGNUM_INT_BITS));
+ BignumCarry c = 1;
+ for (unsigned i = 0; i < lenof(pi->counter); i++)
+ BignumADC(pi->counter[i], c, pi->counter[i], 0, c);
+ ssh_hash_final(h, outbuf);
+}
+
+void prng_read(prng *pr, void *vout, size_t size)
+{
+ prng_impl *pi = container_of(pr, prng_impl, Prng);
+ unsigned char buf[MAX_HASH_LEN];
+
+ assert(!pi->keymaker);
+
+ prngdebug("prng_read %"SIZEu"\n", size);
+
+ uint8_t *out = (uint8_t *)vout;
+ while (size > 0) {
+ prng_generate(pi, buf);
+ size_t to_use = size > pi->hashalg->hlen ? pi->hashalg->hlen : size;
+ memcpy(out, buf, to_use);
+ out += to_use;
+ size -= to_use;
+ }
+
+ smemclr(buf, sizeof(buf));
+
+ prng_seed_begin(&pi->Prng);
+ prng_seed_finish(&pi->Prng);
+}
+
+void prng_add_entropy(prng *pr, unsigned source_id, ptrlen data)
+{
+ prng_impl *pi = container_of(pr, prng_impl, Prng);
+
+ assert(source_id < NOISE_MAX_SOURCES);
+ uint32_t counter = ++pi->source_counters[source_id];
+
+ size_t index = 0;
+ while (index+1 < NCOLLECTORS && !(counter & 1)) {
+ counter >>= 1;
+ index++;
+ }
+
+ prngdebug("prng_add_entropy source=%u size=%"SIZEu" -> collector %zi\n",
+ source_id, data.len, index);
+
+ put_datapl(pi->collectors[index], data);
+
+ if (index == 0)
+ pi->until_reseed = (pi->until_reseed < data.len ? 0 :
+ pi->until_reseed - data.len);
+
+ if (pi->until_reseed == 0 &&
+ prng_reseed_time_ms() - pi->last_reseed_time >= 100) {
+ prng_seed_begin(&pi->Prng);
+
+ unsigned char buf[MAX_HASH_LEN];
+ uint32_t reseed_index = ++pi->reseeds;
+ prngdebug("prng entropy reseed #%"PRIu32"\n", reseed_index);
+ for (size_t i = 0; i < NCOLLECTORS; i++) {
+ prngdebug("emptying collector %"SIZEu"\n", i);
+ ssh_hash_digest(pi->collectors[i], buf);
+ put_data(&pi->Prng, buf, pi->hashalg->hlen);
+ ssh_hash_reset(pi->collectors[i]);
+ if (reseed_index & 1)
+ break;
+ reseed_index >>= 1;
+ }
+ smemclr(buf, sizeof(buf));
+ prng_seed_finish(&pi->Prng);
+ }
+}
+
+size_t prng_seed_bits(prng *pr)
+{
+ prng_impl *pi = container_of(pr, prng_impl, Prng);
+ return pi->hashalg->hlen * 8;
+}
diff --git a/crypto/pubkey-pem.c b/crypto/pubkey-pem.c
new file mode 100644
index 00000000..3eaa16aa
--- /dev/null
+++ b/crypto/pubkey-pem.c
@@ -0,0 +1,32 @@
+/*
+ * Convenience functions to encrypt and decrypt OpenSSH PEM format for
+ * SSH-2 private key files. This uses triple-DES in SSH-2 style (one
+ * CBC layer), with three distinct keys, and an IV also generated from
+ * the passphrase.
+ */
+
+#include "ssh.h"
+
+static ssh_cipher *des3_pubkey_ossh_cipher(const void *vkey, const void *viv)
+{
+ ssh_cipher *c = ssh_cipher_new(&ssh_3des_ssh2);
+ ssh_cipher_setkey(c, vkey);
+ ssh_cipher_setiv(c, viv);
+ return c;
+}
+
+void des3_decrypt_pubkey_ossh(const void *vkey, const void *viv,
+ void *vblk, int len)
+{
+ ssh_cipher *c = des3_pubkey_ossh_cipher(vkey, viv);
+ ssh_cipher_decrypt(c, vblk, len);
+ ssh_cipher_free(c);
+}
+
+void des3_encrypt_pubkey_ossh(const void *vkey, const void *viv,
+ void *vblk, int len)
+{
+ ssh_cipher *c = des3_pubkey_ossh_cipher(vkey, viv);
+ ssh_cipher_encrypt(c, vblk, len);
+ ssh_cipher_free(c);
+}
diff --git a/crypto/pubkey-ppk.c b/crypto/pubkey-ppk.c
new file mode 100644
index 00000000..7ffb570a
--- /dev/null
+++ b/crypto/pubkey-ppk.c
@@ -0,0 +1,29 @@
+/*
+ * Convenience functions to encrypt and decrypt PuTTY's own .PPK
+ * format for SSH-2 private key files, which uses 256-bit AES in CBC
+ * mode.
+ */
+
+#include "ssh.h"
+
+static ssh_cipher *aes256_pubkey_cipher(const void *key, const void *iv)
+{
+ ssh_cipher *cipher = ssh_cipher_new(&ssh_aes256_cbc);
+ ssh_cipher_setkey(cipher, key);
+ ssh_cipher_setiv(cipher, iv);
+ return cipher;
+}
+
+void aes256_encrypt_pubkey(const void *key, const void *iv, void *blk, int len)
+{
+ ssh_cipher *c = aes256_pubkey_cipher(key, iv);
+ ssh_cipher_encrypt(c, blk, len);
+ ssh_cipher_free(c);
+}
+
+void aes256_decrypt_pubkey(const void *key, const void *iv, void *blk, int len)
+{
+ ssh_cipher *c = aes256_pubkey_cipher(key, iv);
+ ssh_cipher_decrypt(c, blk, len);
+ ssh_cipher_free(c);
+}
diff --git a/crypto/pubkey-ssh1.c b/crypto/pubkey-ssh1.c
new file mode 100644
index 00000000..b3129e29
--- /dev/null
+++ b/crypto/pubkey-ssh1.c
@@ -0,0 +1,38 @@
+/*
+ * Convenience functions to encrypt and decrypt the standard format
+ * for SSH-1 private key files. This uses triple-DES in SSH-1 style
+ * (three separate CBC layers), but the same key is used for the first
+ * and third layers.CBC mode.
+ */
+
+#include "ssh.h"
+
+static ssh_cipher *des3_pubkey_cipher(const void *vkey)
+{
+ ssh_cipher *c = ssh_cipher_new(&ssh_3des_ssh1);
+ uint8_t keys3[24], iv[8];
+
+ memcpy(keys3, vkey, 16);
+ memcpy(keys3 + 16, vkey, 8);
+ ssh_cipher_setkey(c, keys3);
+ smemclr(keys3, sizeof(keys3));
+
+ memset(iv, 0, 8);
+ ssh_cipher_setiv(c, iv);
+
+ return c;
+}
+
+void des3_decrypt_pubkey(const void *vkey, void *vblk, int len)
+{
+ ssh_cipher *c = des3_pubkey_cipher(vkey);
+ ssh_cipher_decrypt(c, vblk, len);
+ ssh_cipher_free(c);
+}
+
+void des3_encrypt_pubkey(const void *vkey, void *vblk, int len)
+{
+ ssh_cipher *c = des3_pubkey_cipher(vkey);
+ ssh_cipher_encrypt(c, vblk, len);
+ ssh_cipher_free(c);
+}
diff --git a/crypto/rsa.c b/crypto/rsa.c
new file mode 100644
index 00000000..aa0e08a6
--- /dev/null
+++ b/crypto/rsa.c
@@ -0,0 +1,1158 @@
+/*
+ * RSA implementation for PuTTY.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "ssh.h"
+#include "mpint.h"
+#include "misc.h"
+
+void BinarySource_get_rsa_ssh1_pub(
+ BinarySource *src, RSAKey *rsa, RsaSsh1Order order)
+{
+ unsigned bits;
+ mp_int *e, *m;
+
+ bits = get_uint32(src);
+ if (order == RSA_SSH1_EXPONENT_FIRST) {
+ e = get_mp_ssh1(src);
+ m = get_mp_ssh1(src);
+ } else {
+ m = get_mp_ssh1(src);
+ e = get_mp_ssh1(src);
+ }
+
+ if (rsa) {
+ rsa->bits = bits;
+ rsa->exponent = e;
+ rsa->modulus = m;
+ rsa->bytes = (mp_get_nbits(m) + 7) / 8;
+ } else {
+ mp_free(e);
+ mp_free(m);
+ }
+}
+
+void BinarySource_get_rsa_ssh1_priv(
+ BinarySource *src, RSAKey *rsa)
+{
+ rsa->private_exponent = get_mp_ssh1(src);
+}
+
+key_components *rsa_components(RSAKey *rsa)
+{
+ key_components *kc = key_components_new();
+ key_components_add_text(kc, "key_type", "RSA");
+ key_components_add_mp(kc, "public_modulus", rsa->modulus);
+ key_components_add_mp(kc, "public_exponent", rsa->exponent);
+ if (rsa->private_exponent) {
+ key_components_add_mp(kc, "private_exponent", rsa->private_exponent);
+ key_components_add_mp(kc, "private_p", rsa->p);
+ key_components_add_mp(kc, "private_q", rsa->q);
+ key_components_add_mp(kc, "private_inverse_q_mod_p", rsa->iqmp);
+ }
+ return kc;
+}
+
+RSAKey *BinarySource_get_rsa_ssh1_priv_agent(BinarySource *src)
+{
+ RSAKey *rsa = snew(RSAKey);
+ memset(rsa, 0, sizeof(RSAKey));
+
+ get_rsa_ssh1_pub(src, rsa, RSA_SSH1_MODULUS_FIRST);
+ get_rsa_ssh1_priv(src, rsa);
+
+ /* SSH-1 names p and q the other way round, i.e. we have the
+ * inverse of p mod q and not of q mod p. We swap the names,
+ * because our internal RSA wants iqmp. */
+ rsa->iqmp = get_mp_ssh1(src);
+ rsa->q = get_mp_ssh1(src);
+ rsa->p = get_mp_ssh1(src);
+
+ return rsa;
+}
+
+void duprsakey(RSAKey *dst, const RSAKey *src)
+{
+ dst->bits = src->bits;
+ dst->bytes = src->bytes;
+ dst->modulus = mp_copy(src->modulus);
+ dst->exponent = mp_copy(src->exponent);
+ dst->private_exponent = src->private_exponent ?
+ mp_copy(src->private_exponent) : NULL;
+ dst->p = mp_copy(src->p);
+ dst->q = mp_copy(src->q);
+ dst->iqmp = mp_copy(src->iqmp);
+ dst->comment = src->comment ? dupstr(src->comment) : NULL;
+ dst->sshk.vt = src->sshk.vt;
+}
+
+bool rsa_ssh1_encrypt(unsigned char *data, int length, RSAKey *key)
+{
+ mp_int *b1, *b2;
+ int i;
+ unsigned char *p;
+
+ if (key->bytes < length + 4)
+ return false; /* RSA key too short! */
+
+ memmove(data + key->bytes - length, data, length);
+ data[0] = 0;
+ data[1] = 2;
+
+ size_t npad = key->bytes - length - 3;
+ /*
+ * Generate a sequence of nonzero padding bytes. We do this in a
+ * reasonably uniform way and without having to loop round
+ * retrying the random number generation, by first generating an
+ * integer in [0,2^n) for an appropriately large n; then we
+ * repeatedly multiply by 255 to give an integer in [0,255*2^n),
+ * extract the top 8 bits to give an integer in [0,255), and mask
+ * those bits off before multiplying up again for the next digit.
+ * This gives us a sequence of numbers in [0,255), and of course
+ * adding 1 to each of them gives numbers in [1,256) as we wanted.
+ *
+ * (You could imagine this being a sort of fixed-point operation:
+ * given a uniformly random binary _fraction_, multiplying it by k
+ * and subtracting off the integer part will yield you a sequence
+ * of integers each in [0,k). I'm just doing that scaled up by a
+ * power of 2 to avoid the fractions.)
+ */
+ size_t random_bits = (npad + 16) * 8;
+ mp_int *randval = mp_new(random_bits + 8);
+ mp_int *tmp = mp_random_bits(random_bits);
+ mp_copy_into(randval, tmp);
+ mp_free(tmp);
+ for (i = 2; i < key->bytes - length - 1; i++) {
+ mp_mul_integer_into(randval, randval, 255);
+ uint8_t byte = mp_get_byte(randval, random_bits / 8);
+ assert(byte != 255);
+ data[i] = byte + 1;
+ mp_reduce_mod_2to(randval, random_bits);
+ }
+ mp_free(randval);
+ data[key->bytes - length - 1] = 0;
+
+ b1 = mp_from_bytes_be(make_ptrlen(data, key->bytes));
+
+ b2 = mp_modpow(b1, key->exponent, key->modulus);
+
+ p = data;
+ for (i = key->bytes; i--;) {
+ *p++ = mp_get_byte(b2, i);
+ }
+
+ mp_free(b1);
+ mp_free(b2);
+
+ return true;
+}
+
+/*
+ * Compute (base ^ exp) % mod, provided mod == p * q, with p,q
+ * distinct primes, and iqmp is the multiplicative inverse of q mod p.
+ * Uses Chinese Remainder Theorem to speed computation up over the
+ * obvious implementation of a single big modpow.
+ */
+static mp_int *crt_modpow(mp_int *base, mp_int *exp, mp_int *mod,
+ mp_int *p, mp_int *q, mp_int *iqmp)
+{
+ mp_int *pm1, *qm1, *pexp, *qexp, *presult, *qresult;
+ mp_int *diff, *multiplier, *ret0, *ret;
+
+ /*
+ * Reduce the exponent mod phi(p) and phi(q), to save time when
+ * exponentiating mod p and mod q respectively. Of course, since p
+ * and q are prime, phi(p) == p-1 and similarly for q.
+ */
+ pm1 = mp_copy(p);
+ mp_sub_integer_into(pm1, pm1, 1);
+ qm1 = mp_copy(q);
+ mp_sub_integer_into(qm1, qm1, 1);
+ pexp = mp_mod(exp, pm1);
+ qexp = mp_mod(exp, qm1);
+
+ /*
+ * Do the two modpows.
+ */
+ mp_int *base_mod_p = mp_mod(base, p);
+ presult = mp_modpow(base_mod_p, pexp, p);
+ mp_free(base_mod_p);
+ mp_int *base_mod_q = mp_mod(base, q);
+ qresult = mp_modpow(base_mod_q, qexp, q);
+ mp_free(base_mod_q);
+
+ /*
+ * Recombine the results. We want a value which is congruent to
+ * qresult mod q, and to presult mod p.
+ *
+ * We know that iqmp * q is congruent to 1 * mod p (by definition
+ * of iqmp) and to 0 mod q (obviously). So we start with qresult
+ * (which is congruent to qresult mod both primes), and add on
+ * (presult-qresult) * (iqmp * q) which adjusts it to be congruent
+ * to presult mod p without affecting its value mod q.
+ *
+ * (If presult-qresult < 0, we add p to it to keep it positive.)
+ */
+ unsigned presult_too_small = mp_cmp_hs(qresult, presult);
+ mp_cond_add_into(presult, presult, p, presult_too_small);
+
+ diff = mp_sub(presult, qresult);
+ multiplier = mp_mul(iqmp, q);
+ ret0 = mp_mul(multiplier, diff);
+ mp_add_into(ret0, ret0, qresult);
+
+ /*
+ * Finally, reduce the result mod n.
+ */
+ ret = mp_mod(ret0, mod);
+
+ /*
+ * Free all the intermediate results before returning.
+ */
+ mp_free(pm1);
+ mp_free(qm1);
+ mp_free(pexp);
+ mp_free(qexp);
+ mp_free(presult);
+ mp_free(qresult);
+ mp_free(diff);
+ mp_free(multiplier);
+ mp_free(ret0);
+
+ return ret;
+}
+
+/*
+ * Wrapper on crt_modpow that looks up all the right values from an
+ * RSAKey.
+ */
+static mp_int *rsa_privkey_op(mp_int *input, RSAKey *key)
+{
+ return crt_modpow(input, key->private_exponent,
+ key->modulus, key->p, key->q, key->iqmp);
+}
+
+mp_int *rsa_ssh1_decrypt(mp_int *input, RSAKey *key)
+{
+ return rsa_privkey_op(input, key);
+}
+
+bool rsa_ssh1_decrypt_pkcs1(mp_int *input, RSAKey *key,
+ strbuf *outbuf)
+{
+ strbuf *data = strbuf_new_nm();
+ bool success = false;
+ BinarySource src[1];
+
+ {
+ mp_int *b = rsa_ssh1_decrypt(input, key);
+ for (size_t i = (mp_get_nbits(key->modulus) + 7) / 8; i-- > 0 ;) {
+ put_byte(data, mp_get_byte(b, i));
+ }
+ mp_free(b);
+ }
+
+ BinarySource_BARE_INIT(src, data->u, data->len);
+
+ /* Check PKCS#1 formatting prefix */
+ if (get_byte(src) != 0) goto out;
+ if (get_byte(src) != 2) goto out;
+ while (1) {
+ unsigned char byte = get_byte(src);
+ if (get_err(src)) goto out;
+ if (byte == 0)
+ break;
+ }
+
+ /* Everything else is the payload */
+ success = true;
+ put_data(outbuf, get_ptr(src), get_avail(src));
+
+ out:
+ strbuf_free(data);
+ return success;
+}
+
+static void append_hex_to_strbuf(strbuf *sb, mp_int *x)
+{
+ if (sb->len > 0)
+ put_byte(sb, ',');
+ put_data(sb, "0x", 2);
+ char *hex = mp_get_hex(x);
+ size_t hexlen = strlen(hex);
+ put_data(sb, hex, hexlen);
+ smemclr(hex, hexlen);
+ sfree(hex);
+}
+
+char *rsastr_fmt(RSAKey *key)
+{
+ strbuf *sb = strbuf_new();
+
+ append_hex_to_strbuf(sb, key->exponent);
+ append_hex_to_strbuf(sb, key->modulus);
+
+ return strbuf_to_str(sb);
+}
+
+/*
+ * Generate a fingerprint string for the key. Compatible with the
+ * OpenSSH fingerprint code.
+ */
+char *rsa_ssh1_fingerprint(RSAKey *key)
+{
+ unsigned char digest[16];
+ strbuf *out;
+ int i;
+
+ /*
+ * The hash preimage for SSH-1 key fingerprinting consists of the
+ * modulus and exponent _without_ any preceding length field -
+ * just the minimum number of bytes to represent each integer,
+ * stored big-endian, concatenated with no marker at the division
+ * between them.
+ */
+
+ ssh_hash *hash = ssh_hash_new(&ssh_md5);
+ for (size_t i = (mp_get_nbits(key->modulus) + 7) / 8; i-- > 0 ;)
+ put_byte(hash, mp_get_byte(key->modulus, i));
+ for (size_t i = (mp_get_nbits(key->exponent) + 7) / 8; i-- > 0 ;)
+ put_byte(hash, mp_get_byte(key->exponent, i));
+ ssh_hash_final(hash, digest);
+
+ out = strbuf_new();
+ put_fmt(out, "%"SIZEu" ", mp_get_nbits(key->modulus));
+ for (i = 0; i < 16; i++)
+ put_fmt(out, "%s%02x", i ? ":" : "", digest[i]);
+ if (key->comment)
+ put_fmt(out, " %s", key->comment);
+ return strbuf_to_str(out);
+}
+
+/*
+ * Wrap the output of rsa_ssh1_fingerprint up into the same kind of
+ * structure that comes from ssh2_all_fingerprints.
+ */
+char **rsa_ssh1_fake_all_fingerprints(RSAKey *key)
+{
+ char **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 bool rsa2_has_private(ssh_key *key)
+{
+ RSAKey *rsa = container_of(key, RSAKey, sshk);
+ return rsa->private_exponent != NULL;
+}
+
+static void rsa2_public_blob(ssh_key *key, BinarySink *bs)
+{
+ RSAKey *rsa = container_of(key, RSAKey, sshk);
+
+ put_stringz(bs, "ssh-rsa");
+ put_mp_ssh2(bs, rsa->exponent);
+ put_mp_ssh2(bs, rsa->modulus);
+}
+
+static void rsa2_private_blob(ssh_key *key, BinarySink *bs)
+{
+ RSAKey *rsa = container_of(key, RSAKey, sshk);
+
+ put_mp_ssh2(bs, rsa->private_exponent);
+ put_mp_ssh2(bs, rsa->p);
+ put_mp_ssh2(bs, rsa->q);
+ put_mp_ssh2(bs, rsa->iqmp);
+}
+
+static ssh_key *rsa2_new_priv(const ssh_keyalg *self,
+ ptrlen pub, ptrlen priv)
+{
+ BinarySource src[1];
+ ssh_key *sshk;
+ RSAKey *rsa;
+
+ sshk = rsa2_new_pub(self, pub);
+ if (!sshk)
+ return NULL;
+
+ rsa = container_of(sshk, RSAKey, sshk);
+ BinarySource_BARE_INIT_PL(src, priv);
+ rsa->private_exponent = get_mp_ssh2(src);
+ rsa->p = get_mp_ssh2(src);
+ rsa->q = get_mp_ssh2(src);
+ rsa->iqmp = get_mp_ssh2(src);
+
+ if (get_err(src) || !rsa_verify(rsa)) {
+ rsa2_freekey(&rsa->sshk);
+ return NULL;
+ }
+
+ return &rsa->sshk;
+}
+
+static ssh_key *rsa2_new_priv_openssh(const ssh_keyalg *self,
+ BinarySource *src)
+{
+ RSAKey *rsa;
+
+ rsa = snew(RSAKey);
+ rsa->sshk.vt = &ssh_rsa;
+ rsa->comment = NULL;
+
+ rsa->modulus = get_mp_ssh2(src);
+ rsa->exponent = get_mp_ssh2(src);
+ rsa->private_exponent = get_mp_ssh2(src);
+ rsa->iqmp = get_mp_ssh2(src);
+ rsa->p = get_mp_ssh2(src);
+ rsa->q = get_mp_ssh2(src);
+
+ if (get_err(src) || !rsa_verify(rsa)) {
+ rsa2_freekey(&rsa->sshk);
+ return NULL;
+ }
+
+ return &rsa->sshk;
+}
+
+static void rsa2_openssh_blob(ssh_key *key, BinarySink *bs)
+{
+ RSAKey *rsa = container_of(key, RSAKey, sshk);
+
+ put_mp_ssh2(bs, rsa->modulus);
+ put_mp_ssh2(bs, rsa->exponent);
+ put_mp_ssh2(bs, rsa->private_exponent);
+ put_mp_ssh2(bs, rsa->iqmp);
+ put_mp_ssh2(bs, rsa->p);
+ put_mp_ssh2(bs, rsa->q);
+}
+
+static int rsa2_pubkey_bits(const ssh_keyalg *self, ptrlen pub)
+{
+ ssh_key *sshk;
+ RSAKey *rsa;
+ int ret;
+
+ sshk = rsa2_new_pub(self, pub);
+ if (!sshk)
+ return -1;
+
+ rsa = container_of(sshk, RSAKey, sshk);
+ ret = mp_get_nbits(rsa->modulus);
+ rsa2_freekey(&rsa->sshk);
+
+ return ret;
+}
+
+static inline const ssh_hashalg *rsa2_hash_alg_for_flags(
+ unsigned flags, const char **protocol_id_out)
+{
+ const ssh_hashalg *halg;
+ const char *protocol_id;
+
+ if (flags & SSH_AGENT_RSA_SHA2_256) {
+ halg = &ssh_sha256;
+ protocol_id = "rsa-sha2-256";
+ } else if (flags & SSH_AGENT_RSA_SHA2_512) {
+ halg = &ssh_sha512;
+ protocol_id = "rsa-sha2-512";
+ } else {
+ halg = &ssh_sha1;
+ protocol_id = "ssh-rsa";
+ }
+
+ if (protocol_id_out)
+ *protocol_id_out = protocol_id;
+
+ return halg;
+}
+
+static inline ptrlen rsa_pkcs1_prefix_for_hash(const ssh_hashalg *halg)
+{
+ if (halg == &ssh_sha1) {
+ /*
+ * This is the magic ASN.1/DER prefix that goes in the decoded
+ * signature, between the string of FFs and the actual SHA-1
+ * hash value. The meaning of it is:
+ *
+ * 00 -- this marks the end of the FFs; not part of the ASN.1
+ * bit itself
+ *
+ * 30 21 -- a constructed SEQUENCE of length 0x21
+ * 30 09 -- a constructed sub-SEQUENCE of length 9
+ * 06 05 -- an object identifier, length 5
+ * 2B 0E 03 02 1A -- object id { 1 3 14 3 2 26 }
+ * (the 1,3 comes from 0x2B = 43 = 40*1+3)
+ * 05 00 -- NULL
+ * 04 14 -- a primitive OCTET STRING of length 0x14
+ * [0x14 bytes of hash data follows]
+ *
+ * The object id in the middle there is listed as `id-sha1' in
+ * ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1d2.asn
+ * (the ASN module for PKCS #1) and its expanded form is as
+ * follows:
+ *
+ * id-sha1 OBJECT IDENTIFIER ::= {
+ * iso(1) identified-organization(3) oiw(14) secsig(3)
+ * algorithms(2) 26 }
+ */
+ static const unsigned char sha1_asn1_prefix[] = {
+ 0x00, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B,
+ 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14,
+ };
+ return PTRLEN_FROM_CONST_BYTES(sha1_asn1_prefix);
+ }
+
+ if (halg == &ssh_sha256) {
+ /*
+ * A similar piece of ASN.1 used for signatures using SHA-256,
+ * in the same format but differing only in various length
+ * fields and OID.
+ */
+ static const unsigned char sha256_asn1_prefix[] = {
+ 0x00, 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60,
+ 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
+ 0x05, 0x00, 0x04, 0x20,
+ };
+ return PTRLEN_FROM_CONST_BYTES(sha256_asn1_prefix);
+ }
+
+ if (halg == &ssh_sha512) {
+ /*
+ * And one more for SHA-512.
+ */
+ static const unsigned char sha512_asn1_prefix[] = {
+ 0x00, 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60,
+ 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03,
+ 0x05, 0x00, 0x04, 0x40,
+ };
+ return PTRLEN_FROM_CONST_BYTES(sha512_asn1_prefix);
+ }
+
+ unreachable("bad hash algorithm for RSA PKCS#1");
+}
+
+static inline size_t rsa_pkcs1_length_of_fixed_parts(const ssh_hashalg *halg)
+{
+ ptrlen asn1_prefix = rsa_pkcs1_prefix_for_hash(halg);
+ return halg->hlen + asn1_prefix.len + 2;
+}
+
+static unsigned char *rsa_pkcs1_signature_string(
+ size_t nbytes, const ssh_hashalg *halg, ptrlen data)
+{
+ size_t fixed_parts = rsa_pkcs1_length_of_fixed_parts(halg);
+ assert(nbytes >= fixed_parts);
+ size_t padding = nbytes - fixed_parts;
+
+ ptrlen asn1_prefix = rsa_pkcs1_prefix_for_hash(halg);
+
+ unsigned char *bytes = snewn(nbytes, unsigned char);
+
+ bytes[0] = 0;
+ bytes[1] = 1;
+
+ memset(bytes + 2, 0xFF, padding);
+
+ memcpy(bytes + 2 + padding, asn1_prefix.ptr, asn1_prefix.len);
+
+ ssh_hash *h = ssh_hash_new(halg);
+ put_datapl(h, data);
+ ssh_hash_final(h, bytes + 2 + padding + asn1_prefix.len);
+
+ return bytes;
+}
+
+static bool rsa2_verify(ssh_key *key, ptrlen sig, ptrlen data)
+{
+ RSAKey *rsa = container_of(key, RSAKey, sshk);
+ BinarySource src[1];
+ ptrlen type, in_pl;
+ mp_int *in, *out;
+
+ const struct ssh2_rsa_extra *extra =
+ (const struct ssh2_rsa_extra *)key->vt->extra;
+
+ const ssh_hashalg *halg = rsa2_hash_alg_for_flags(extra->signflags, NULL);
+
+ /* Start by making sure the key is even long enough to encode a
+ * signature. If not, everything fails to verify. */
+ size_t nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8;
+ if (nbytes < rsa_pkcs1_length_of_fixed_parts(halg))
+ return false;
+
+ BinarySource_BARE_INIT_PL(src, sig);
+ type = get_string(src);
+ /*
+ * RFC 4253 section 6.6: the signature integer in an ssh-rsa
+ * signature is 'without lengths or padding'. That is, we _don't_
+ * expect the usual leading zero byte if the topmost bit of the
+ * first byte is set. (However, because of the possibility of
+ * BUG_SSH2_RSA_PADDING at the other end, we tolerate it if it's
+ * there.) So we can't use get_mp_ssh2, which enforces that
+ * leading-byte scheme; instead we use get_string and
+ * mp_from_bytes_be, which will tolerate anything.
+ */
+ in_pl = get_string(src);
+ if (get_err(src) || !ptrlen_eq_string(type, key->vt->ssh_id))
+ return false;
+
+ in = mp_from_bytes_be(in_pl);
+ out = mp_modpow(in, rsa->exponent, rsa->modulus);
+ mp_free(in);
+
+ unsigned diff = 0;
+
+ unsigned char *bytes = rsa_pkcs1_signature_string(nbytes, halg, data);
+ for (size_t i = 0; i < nbytes; i++)
+ diff |= bytes[nbytes-1 - i] ^ mp_get_byte(out, i);
+ smemclr(bytes, nbytes);
+ sfree(bytes);
+ mp_free(out);
+
+ return diff == 0;
+}
+
+static void rsa2_sign(ssh_key *key, ptrlen data,
+ unsigned flags, BinarySink *bs)
+{
+ RSAKey *rsa = container_of(key, RSAKey, sshk);
+ unsigned char *bytes;
+ size_t nbytes;
+ mp_int *in, *out;
+ const ssh_hashalg *halg;
+ const char *sign_alg_name;
+
+ const struct ssh2_rsa_extra *extra =
+ (const struct ssh2_rsa_extra *)key->vt->extra;
+ flags |= extra->signflags;
+
+ halg = rsa2_hash_alg_for_flags(flags, &sign_alg_name);
+
+ nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8;
+
+ bytes = rsa_pkcs1_signature_string(nbytes, halg, data);
+ in = mp_from_bytes_be(make_ptrlen(bytes, nbytes));
+ smemclr(bytes, nbytes);
+ sfree(bytes);
+
+ out = rsa_privkey_op(in, rsa);
+ mp_free(in);
+
+ put_stringz(bs, sign_alg_name);
+ nbytes = (mp_get_nbits(out) + 7) / 8;
+ put_uint32(bs, nbytes);
+ for (size_t i = 0; i < nbytes; i++)
+ put_byte(bs, mp_get_byte(out, nbytes - 1 - i));
+
+ mp_free(out);
+}
+
+static char *rsa2_invalid(ssh_key *key, unsigned flags)
+{
+ RSAKey *rsa = container_of(key, RSAKey, sshk);
+ size_t bits = mp_get_nbits(rsa->modulus), nbytes = (bits + 7) / 8;
+ const char *sign_alg_name;
+ const ssh_hashalg *halg = rsa2_hash_alg_for_flags(flags, &sign_alg_name);
+ if (nbytes < rsa_pkcs1_length_of_fixed_parts(halg)) {
+ return dupprintf(
+ "%"SIZEu"-bit RSA key is too short to generate %s signatures",
+ bits, sign_alg_name);
+ }
+
+ return NULL;
+}
+
+static unsigned ssh_rsa_supported_flags(const ssh_keyalg *self)
+{
+ return SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512;
+}
+
+static const char *ssh_rsa_alternate_ssh_id(
+ const ssh_keyalg *self, unsigned flags)
+{
+ if (flags & SSH_AGENT_RSA_SHA2_512)
+ return ssh_rsa_sha512.ssh_id;
+ if (flags & SSH_AGENT_RSA_SHA2_256)
+ return ssh_rsa_sha256.ssh_id;
+ return self->ssh_id;
+}
+
+static char *rsa2_alg_desc(const ssh_keyalg *self) { return dupstr("RSA"); }
+
+static const struct ssh2_rsa_extra
+ rsa_extra = { 0 },
+ rsa_sha256_extra = { SSH_AGENT_RSA_SHA2_256 },
+ rsa_sha512_extra = { SSH_AGENT_RSA_SHA2_512 };
+
+#define COMMON_KEYALG_FIELDS \
+ .new_pub = rsa2_new_pub, \
+ .new_priv = rsa2_new_priv, \
+ .new_priv_openssh = rsa2_new_priv_openssh, \
+ .freekey = rsa2_freekey, \
+ .invalid = rsa2_invalid, \
+ .sign = rsa2_sign, \
+ .verify = rsa2_verify, \
+ .public_blob = rsa2_public_blob, \
+ .private_blob = rsa2_private_blob, \
+ .openssh_blob = rsa2_openssh_blob, \
+ .has_private = rsa2_has_private, \
+ .cache_str = rsa2_cache_str, \
+ .components = rsa2_components, \
+ .base_key = nullkey_base_key, \
+ .pubkey_bits = rsa2_pubkey_bits, \
+ .alg_desc = rsa2_alg_desc, \
+ .variable_size = nullkey_variable_size_yes, \
+ .cache_id = "rsa2"
+
+const ssh_keyalg ssh_rsa = {
+ COMMON_KEYALG_FIELDS,
+ .ssh_id = "ssh-rsa",
+ .supported_flags = ssh_rsa_supported_flags,
+ .alternate_ssh_id = ssh_rsa_alternate_ssh_id,
+ .extra = &rsa_extra,
+};
+
+const ssh_keyalg ssh_rsa_sha256 = {
+ COMMON_KEYALG_FIELDS,
+ .ssh_id = "rsa-sha2-256",
+ .supported_flags = nullkey_supported_flags,
+ .alternate_ssh_id = nullkey_alternate_ssh_id,
+ .extra = &rsa_sha256_extra,
+};
+
+const ssh_keyalg ssh_rsa_sha512 = {
+ COMMON_KEYALG_FIELDS,
+ .ssh_id = "rsa-sha2-512",
+ .supported_flags = nullkey_supported_flags,
+ .alternate_ssh_id = nullkey_alternate_ssh_id,
+ .extra = &rsa_sha512_extra,
+};
+
+RSAKey *ssh_rsakex_newkey(ptrlen data)
+{
+ ssh_key *sshk = rsa2_new_pub(&ssh_rsa, data);
+ if (!sshk)
+ return NULL;
+ return container_of(sshk, RSAKey, sshk);
+}
+
+void ssh_rsakex_freekey(RSAKey *key)
+{
+ rsa2_freekey(&key->sshk);
+}
+
+int ssh_rsakex_klen(RSAKey *rsa)
+{
+ return mp_get_nbits(rsa->modulus);
+}
+
+static void oaep_mask(const ssh_hashalg *h, void *seed, int seedlen,
+ void *vdata, int datalen)
+{
+ unsigned char *data = (unsigned char *)vdata;
+ unsigned count = 0;
+
+ ssh_hash *s = ssh_hash_new(h);
+
+ while (datalen > 0) {
+ int i, max = (datalen > h->hlen ? h->hlen : datalen);
+ unsigned char hash[MAX_HASH_LEN];
+
+ ssh_hash_reset(s);
+ assert(h->hlen <= MAX_HASH_LEN);
+ put_data(s, seed, seedlen);
+ put_uint32(s, count);
+ ssh_hash_digest(s, hash);
+ count++;
+
+ for (i = 0; i < max; i++)
+ data[i] ^= hash[i];
+
+ data += max;
+ datalen -= max;
+ }
+
+ ssh_hash_free(s);
+}
+
+strbuf *ssh_rsakex_encrypt(RSAKey *rsa, const ssh_hashalg *h, ptrlen in)
+{
+ mp_int *b1, *b2;
+ int k, i;
+ char *p;
+ const int HLEN = h->hlen;
+
+ /*
+ * Here we encrypt using RSAES-OAEP. Essentially this means:
+ *
+ * - we have a SHA-based `mask generation function' which
+ * creates a pseudo-random stream of mask data
+ * deterministically from an input chunk of data.
+ *
+ * - we have a random chunk of data called a seed.
+ *
+ * - we use the seed to generate a mask which we XOR with our
+ * plaintext.
+ *
+ * - then we use _the masked plaintext_ to generate a mask
+ * which we XOR with the seed.
+ *
+ * - then we concatenate the masked seed and the masked
+ * plaintext, and RSA-encrypt that lot.
+ *
+ * The result is that the data input to the encryption function
+ * is random-looking and (hopefully) contains no exploitable
+ * structure such as PKCS1-v1_5 does.
+ *
+ * For a precise specification, see RFC 3447, section 7.1.1.
+ * Some of the variable names below are derived from that, so
+ * it'd probably help to read it anyway.
+ */
+
+ /* k denotes the length in octets of the RSA modulus. */
+ k = (7 + mp_get_nbits(rsa->modulus)) / 8;
+
+ /* The length of the input data must be at most k - 2hLen - 2. */
+ assert(in.len > 0 && in.len <= k - 2*HLEN - 2);
+
+ /* The length of the output data wants to be precisely k. */
+ strbuf *toret = strbuf_new_nm();
+ int outlen = k;
+ unsigned char *out = strbuf_append(toret, outlen);
+
+ /*
+ * Now perform EME-OAEP encoding. First set up all the unmasked
+ * output data.
+ */
+ /* Leading byte zero. */
+ out[0] = 0;
+ /* At position 1, the seed: HLEN bytes of random data. */
+ random_read(out + 1, HLEN);
+ /* At position 1+HLEN, the data block DB, consisting of: */
+ /* The hash of the label (we only support an empty label here) */
+ hash_simple(h, PTRLEN_LITERAL(""), out + HLEN + 1);
+ /* A bunch of zero octets */
+ memset(out + 2*HLEN + 1, 0, outlen - (2*HLEN + 1));
+ /* A single 1 octet, followed by the input message data. */
+ out[outlen - in.len - 1] = 1;
+ memcpy(out + outlen - in.len, in.ptr, in.len);
+
+ /*
+ * Now use the seed data to mask the block DB.
+ */
+ oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1);
+
+ /*
+ * And now use the masked DB to mask the seed itself.
+ */
+ oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN);
+
+ /*
+ * Now `out' contains precisely the data we want to
+ * RSA-encrypt.
+ */
+ b1 = mp_from_bytes_be(make_ptrlen(out, outlen));
+ b2 = mp_modpow(b1, rsa->exponent, rsa->modulus);
+ p = (char *)out;
+ for (i = outlen; i--;) {
+ *p++ = mp_get_byte(b2, i);
+ }
+ mp_free(b1);
+ mp_free(b2);
+
+ /*
+ * And we're done.
+ */
+ return toret;
+}
+
+mp_int *ssh_rsakex_decrypt(
+ RSAKey *rsa, const ssh_hashalg *h, ptrlen ciphertext)
+{
+ mp_int *b1, *b2;
+ int outlen, i;
+ unsigned char *out;
+ unsigned char labelhash[64];
+ BinarySource src[1];
+ const int HLEN = h->hlen;
+
+ /*
+ * Decryption side of the RSA key exchange operation.
+ */
+
+ /* The length of the encrypted data should be exactly the length
+ * in octets of the RSA modulus.. */
+ outlen = (7 + mp_get_nbits(rsa->modulus)) / 8;
+ if (ciphertext.len != outlen)
+ return NULL;
+
+ /* Do the RSA decryption, and extract the result into a byte array. */
+ b1 = mp_from_bytes_be(ciphertext);
+ b2 = rsa_privkey_op(b1, rsa);
+ out = snewn(outlen, unsigned char);
+ for (i = 0; i < outlen; i++)
+ out[i] = mp_get_byte(b2, outlen-1-i);
+ mp_free(b1);
+ mp_free(b2);
+
+ /* Do the OAEP masking operations, in the reverse order from encryption */
+ oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN);
+ oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1);
+
+ /* Check the leading byte is zero. */
+ if (out[0] != 0) {
+ sfree(out);
+ return NULL;
+ }
+ /* Check the label hash at position 1+HLEN */
+ assert(HLEN <= lenof(labelhash));
+ hash_simple(h, PTRLEN_LITERAL(""), labelhash);
+ if (memcmp(out + HLEN + 1, labelhash, HLEN)) {
+ sfree(out);
+ return NULL;
+ }
+ /* Expect zero bytes followed by a 1 byte */
+ for (i = 1 + 2 * HLEN; i < outlen; i++) {
+ if (out[i] == 1) {
+ i++; /* skip over the 1 byte */
+ break;
+ } else if (out[i] != 0) {
+ sfree(out);
+ return NULL;
+ }
+ }
+ /* And what's left is the input message data, which should be
+ * encoded as an ordinary SSH-2 mpint. */
+ BinarySource_BARE_INIT(src, out + i, outlen - i);
+ b1 = get_mp_ssh2(src);
+ sfree(out);
+ if (get_err(src) || get_avail(src) != 0) {
+ mp_free(b1);
+ return NULL;
+ }
+
+ /* Success! */
+ return b1;
+}
+
+static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha1 = { 1024 };
+static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha256 = { 2048 };
+
+static const ssh_kex ssh_rsa_kex_sha1 = {
+ .name = "rsa1024-sha1",
+ .main_type = KEXTYPE_RSA,
+ .hash = &ssh_sha1,
+ .extra = &ssh_rsa_kex_extra_sha1,
+};
+
+static const ssh_kex ssh_rsa_kex_sha256 = {
+ .name = "rsa2048-sha256",
+ .main_type = KEXTYPE_RSA,
+ .hash = &ssh_sha256,
+ .extra = &ssh_rsa_kex_extra_sha256,
+};
+
+static const ssh_kex *const rsa_kex_list[] = {
+ &ssh_rsa_kex_sha256,
+ &ssh_rsa_kex_sha1
+};
+
+const ssh_kexes ssh_rsa_kex = { lenof(rsa_kex_list), rsa_kex_list };
diff --git a/crypto/sha1-common.c b/crypto/sha1-common.c
new file mode 100644
index 00000000..bf1db67a
--- /dev/null
+++ b/crypto/sha1-common.c
@@ -0,0 +1,10 @@
+/*
+ * Common variable definitions across all the SHA-1 implementations.
+ */
+
+#include "ssh.h"
+#include "sha1.h"
+
+const uint32_t sha1_initial_state[5] = {
+ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0,
+};
diff --git a/crypto/sha1-neon.c b/crypto/sha1-neon.c
new file mode 100644
index 00000000..99045714
--- /dev/null
+++ b/crypto/sha1-neon.c
@@ -0,0 +1,190 @@
+/*
+ * Hardware-accelerated implementation of SHA-1 using Arm NEON.
+ */
+
+#include "ssh.h"
+#include "sha1.h"
+
+#if USE_ARM64_NEON_H
+#include <arm64_neon.h>
+#else
+#include <arm_neon.h>
+#endif
+
+static bool sha1_neon_available(void)
+{
+ /*
+ * For Arm, we delegate to a per-platform detection function (see
+ * explanation in aes-neon.c).
+ */
+ return platform_sha1_neon_available();
+}
+
+typedef struct sha1_neon_core sha1_neon_core;
+struct sha1_neon_core {
+ uint32x4_t abcd;
+ uint32_t e;
+};
+
+static inline uint32x4_t sha1_neon_load_input(const uint8_t *p)
+{
+ return vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(p)));
+}
+
+static inline uint32x4_t sha1_neon_schedule_update(
+ uint32x4_t m4, uint32x4_t m3, uint32x4_t m2, uint32x4_t m1)
+{
+ return vsha1su1q_u32(vsha1su0q_u32(m4, m3, m2), m1);
+}
+
+/*
+ * SHA-1 has three different kinds of round, differing in whether they
+ * use the Ch, Maj or Par functions defined above. Each one uses a
+ * separate NEON instruction, so we define three inline functions for
+ * the different round types using this macro.
+ *
+ * The two batches of Par-type rounds also use a different constant,
+ * but that's passed in as an operand, so we don't need a fourth
+ * inline function just for that.
+ */
+#define SHA1_NEON_ROUND_FN(type) \
+ static inline sha1_neon_core sha1_neon_round4_##type( \
+ sha1_neon_core old, uint32x4_t sched, uint32x4_t constant) \
+ { \
+ sha1_neon_core new; \
+ uint32x4_t round_input = vaddq_u32(sched, constant); \
+ new.abcd = vsha1##type##q_u32(old.abcd, old.e, round_input); \
+ new.e = vsha1h_u32(vget_lane_u32(vget_low_u32(old.abcd), 0)); \
+ return new; \
+ }
+SHA1_NEON_ROUND_FN(c)
+SHA1_NEON_ROUND_FN(p)
+SHA1_NEON_ROUND_FN(m)
+
+static inline void sha1_neon_block(sha1_neon_core *core, const uint8_t *p)
+{
+ uint32x4_t constant, s0, s1, s2, s3;
+ sha1_neon_core cr = *core;
+
+ constant = vdupq_n_u32(SHA1_STAGE0_CONSTANT);
+ s0 = sha1_neon_load_input(p);
+ cr = sha1_neon_round4_c(cr, s0, constant);
+ s1 = sha1_neon_load_input(p + 16);
+ cr = sha1_neon_round4_c(cr, s1, constant);
+ s2 = sha1_neon_load_input(p + 32);
+ cr = sha1_neon_round4_c(cr, s2, constant);
+ s3 = sha1_neon_load_input(p + 48);
+ cr = sha1_neon_round4_c(cr, s3, constant);
+ s0 = sha1_neon_schedule_update(s0, s1, s2, s3);
+ cr = sha1_neon_round4_c(cr, s0, constant);
+
+ constant = vdupq_n_u32(SHA1_STAGE1_CONSTANT);
+ s1 = sha1_neon_schedule_update(s1, s2, s3, s0);
+ cr = sha1_neon_round4_p(cr, s1, constant);
+ s2 = sha1_neon_schedule_update(s2, s3, s0, s1);
+ cr = sha1_neon_round4_p(cr, s2, constant);
+ s3 = sha1_neon_schedule_update(s3, s0, s1, s2);
+ cr = sha1_neon_round4_p(cr, s3, constant);
+ s0 = sha1_neon_schedule_update(s0, s1, s2, s3);
+ cr = sha1_neon_round4_p(cr, s0, constant);
+ s1 = sha1_neon_schedule_update(s1, s2, s3, s0);
+ cr = sha1_neon_round4_p(cr, s1, constant);
+
+ constant = vdupq_n_u32(SHA1_STAGE2_CONSTANT);
+ s2 = sha1_neon_schedule_update(s2, s3, s0, s1);
+ cr = sha1_neon_round4_m(cr, s2, constant);
+ s3 = sha1_neon_schedule_update(s3, s0, s1, s2);
+ cr = sha1_neon_round4_m(cr, s3, constant);
+ s0 = sha1_neon_schedule_update(s0, s1, s2, s3);
+ cr = sha1_neon_round4_m(cr, s0, constant);
+ s1 = sha1_neon_schedule_update(s1, s2, s3, s0);
+ cr = sha1_neon_round4_m(cr, s1, constant);
+ s2 = sha1_neon_schedule_update(s2, s3, s0, s1);
+ cr = sha1_neon_round4_m(cr, s2, constant);
+
+ constant = vdupq_n_u32(SHA1_STAGE3_CONSTANT);
+ s3 = sha1_neon_schedule_update(s3, s0, s1, s2);
+ cr = sha1_neon_round4_p(cr, s3, constant);
+ s0 = sha1_neon_schedule_update(s0, s1, s2, s3);
+ cr = sha1_neon_round4_p(cr, s0, constant);
+ s1 = sha1_neon_schedule_update(s1, s2, s3, s0);
+ cr = sha1_neon_round4_p(cr, s1, constant);
+ s2 = sha1_neon_schedule_update(s2, s3, s0, s1);
+ cr = sha1_neon_round4_p(cr, s2, constant);
+ s3 = sha1_neon_schedule_update(s3, s0, s1, s2);
+ cr = sha1_neon_round4_p(cr, s3, constant);
+
+ core->abcd = vaddq_u32(core->abcd, cr.abcd);
+ core->e += cr.e;
+}
+
+typedef struct sha1_neon {
+ sha1_neon_core core;
+ sha1_block blk;
+ BinarySink_IMPLEMENTATION;
+ ssh_hash hash;
+} sha1_neon;
+
+static void sha1_neon_write(BinarySink *bs, const void *vp, size_t len);
+
+static ssh_hash *sha1_neon_new(const ssh_hashalg *alg)
+{
+ const struct sha1_extra *extra = (const struct sha1_extra *)alg->extra;
+ if (!check_availability(extra))
+ return NULL;
+
+ sha1_neon *s = snew(sha1_neon);
+
+ s->hash.vt = alg;
+ BinarySink_INIT(s, sha1_neon_write);
+ BinarySink_DELEGATE_INIT(&s->hash, s);
+ return &s->hash;
+}
+
+static void sha1_neon_reset(ssh_hash *hash)
+{
+ sha1_neon *s = container_of(hash, sha1_neon, hash);
+
+ s->core.abcd = vld1q_u32(sha1_initial_state);
+ s->core.e = sha1_initial_state[4];
+
+ sha1_block_setup(&s->blk);
+}
+
+static void sha1_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+ sha1_neon *copy = container_of(hcopy, sha1_neon, hash);
+ sha1_neon *orig = container_of(horig, sha1_neon, hash);
+
+ *copy = *orig; /* structure copy */
+
+ BinarySink_COPIED(copy);
+ BinarySink_DELEGATE_INIT(&copy->hash, copy);
+}
+
+static void sha1_neon_free(ssh_hash *hash)
+{
+ sha1_neon *s = container_of(hash, sha1_neon, hash);
+ smemclr(s, sizeof(*s));
+ sfree(s);
+}
+
+static void sha1_neon_write(BinarySink *bs, const void *vp, size_t len)
+{
+ sha1_neon *s = BinarySink_DOWNCAST(bs, sha1_neon);
+
+ while (len > 0)
+ if (sha1_block_write(&s->blk, &vp, &len))
+ sha1_neon_block(&s->core, s->blk.block);
+}
+
+static void sha1_neon_digest(ssh_hash *hash, uint8_t *digest)
+{
+ sha1_neon *s = container_of(hash, sha1_neon, hash);
+
+ sha1_block_pad(&s->blk, BinarySink_UPCAST(s));
+ vst1q_u8(digest, vrev32q_u8(vreinterpretq_u8_u32(s->core.abcd)));
+ PUT_32BIT_MSB_FIRST(digest + 16, s->core.e);
+}
+
+SHA1_VTABLE(neon, "NEON accelerated");
diff --git a/crypto/sha1-ni.c b/crypto/sha1-ni.c
new file mode 100644
index 00000000..04e6386b
--- /dev/null
+++ b/crypto/sha1-ni.c
@@ -0,0 +1,325 @@
+/*
+ * Hardware-accelerated implementation of SHA-1 using x86 SHA-NI.
+ */
+
+#include "ssh.h"
+#include "sha1.h"
+
+#include <wmmintrin.h>
+#include <smmintrin.h>
+#include <immintrin.h>
+#if HAVE_SHAINTRIN_H
+#include <shaintrin.h>
+#endif
+
+#if defined(__clang__) || defined(__GNUC__)
+#include <cpuid.h>
+#define GET_CPU_ID_0(out) \
+ __cpuid(0, (out)[0], (out)[1], (out)[2], (out)[3])
+#define GET_CPU_ID_7(out) \
+ __cpuid_count(7, 0, (out)[0], (out)[1], (out)[2], (out)[3])
+#else
+#define GET_CPU_ID_0(out) __cpuid(out, 0)
+#define GET_CPU_ID_7(out) __cpuidex(out, 7, 0)
+#endif
+
+static bool sha1_ni_available(void)
+{
+ unsigned int CPUInfo[4];
+ GET_CPU_ID_0(CPUInfo);
+ if (CPUInfo[0] < 7)
+ return false;
+
+ GET_CPU_ID_7(CPUInfo);
+ return CPUInfo[1] & (1 << 29); /* Check SHA */
+}
+
+/* SHA1 implementation using new instructions
+ The code is based on Jeffrey Walton's SHA1 implementation:
+ https://github.com/noloader/SHA-Intrinsics
+*/
+static inline void sha1_ni_block(__m128i *core, const uint8_t *p)
+{
+ __m128i ABCD, E0, E1, MSG0, MSG1, MSG2, MSG3;
+ const __m128i MASK = _mm_set_epi64x(
+ 0x0001020304050607ULL, 0x08090a0b0c0d0e0fULL);
+
+ const __m128i *block = (const __m128i *)p;
+
+ /* Load initial values */
+ ABCD = core[0];
+ E0 = core[1];
+
+ /* Rounds 0-3 */
+ MSG0 = _mm_loadu_si128(block);
+ MSG0 = _mm_shuffle_epi8(MSG0, MASK);
+ E0 = _mm_add_epi32(E0, MSG0);
+ E1 = ABCD;
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0);
+
+ /* Rounds 4-7 */
+ MSG1 = _mm_loadu_si128(block + 1);
+ MSG1 = _mm_shuffle_epi8(MSG1, MASK);
+ E1 = _mm_sha1nexte_epu32(E1, MSG1);
+ E0 = ABCD;
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 0);
+ MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1);
+
+ /* Rounds 8-11 */
+ MSG2 = _mm_loadu_si128(block + 2);
+ MSG2 = _mm_shuffle_epi8(MSG2, MASK);
+ E0 = _mm_sha1nexte_epu32(E0, MSG2);
+ E1 = ABCD;
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0);
+ MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2);
+ MSG0 = _mm_xor_si128(MSG0, MSG2);
+
+ /* Rounds 12-15 */
+ MSG3 = _mm_loadu_si128(block + 3);
+ MSG3 = _mm_shuffle_epi8(MSG3, MASK);
+ E1 = _mm_sha1nexte_epu32(E1, MSG3);
+ E0 = ABCD;
+ MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3);
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 0);
+ MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3);
+ MSG1 = _mm_xor_si128(MSG1, MSG3);
+
+ /* Rounds 16-19 */
+ E0 = _mm_sha1nexte_epu32(E0, MSG0);
+ E1 = ABCD;
+ MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0);
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0);
+ MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0);
+ MSG2 = _mm_xor_si128(MSG2, MSG0);
+
+ /* Rounds 20-23 */
+ E1 = _mm_sha1nexte_epu32(E1, MSG1);
+ E0 = ABCD;
+ MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1);
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1);
+ MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1);
+ MSG3 = _mm_xor_si128(MSG3, MSG1);
+
+ /* Rounds 24-27 */
+ E0 = _mm_sha1nexte_epu32(E0, MSG2);
+ E1 = ABCD;
+ MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2);
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 1);
+ MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2);
+ MSG0 = _mm_xor_si128(MSG0, MSG2);
+
+ /* Rounds 28-31 */
+ E1 = _mm_sha1nexte_epu32(E1, MSG3);
+ E0 = ABCD;
+ MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3);
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1);
+ MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3);
+ MSG1 = _mm_xor_si128(MSG1, MSG3);
+
+ /* Rounds 32-35 */
+ E0 = _mm_sha1nexte_epu32(E0, MSG0);
+ E1 = ABCD;
+ MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0);
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 1);
+ MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0);
+ MSG2 = _mm_xor_si128(MSG2, MSG0);
+
+ /* Rounds 36-39 */
+ E1 = _mm_sha1nexte_epu32(E1, MSG1);
+ E0 = ABCD;
+ MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1);
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1);
+ MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1);
+ MSG3 = _mm_xor_si128(MSG3, MSG1);
+
+ /* Rounds 40-43 */
+ E0 = _mm_sha1nexte_epu32(E0, MSG2);
+ E1 = ABCD;
+ MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2);
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2);
+ MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2);
+ MSG0 = _mm_xor_si128(MSG0, MSG2);
+
+ /* Rounds 44-47 */
+ E1 = _mm_sha1nexte_epu32(E1, MSG3);
+ E0 = ABCD;
+ MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3);
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 2);
+ MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3);
+ MSG1 = _mm_xor_si128(MSG1, MSG3);
+
+ /* Rounds 48-51 */
+ E0 = _mm_sha1nexte_epu32(E0, MSG0);
+ E1 = ABCD;
+ MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0);
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2);
+ MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0);
+ MSG2 = _mm_xor_si128(MSG2, MSG0);
+
+ /* Rounds 52-55 */
+ E1 = _mm_sha1nexte_epu32(E1, MSG1);
+ E0 = ABCD;
+ MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1);
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 2);
+ MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1);
+ MSG3 = _mm_xor_si128(MSG3, MSG1);
+
+ /* Rounds 56-59 */
+ E0 = _mm_sha1nexte_epu32(E0, MSG2);
+ E1 = ABCD;
+ MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2);
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2);
+ MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2);
+ MSG0 = _mm_xor_si128(MSG0, MSG2);
+
+ /* Rounds 60-63 */
+ E1 = _mm_sha1nexte_epu32(E1, MSG3);
+ E0 = ABCD;
+ MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3);
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3);
+ MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3);
+ MSG1 = _mm_xor_si128(MSG1, MSG3);
+
+ /* Rounds 64-67 */
+ E0 = _mm_sha1nexte_epu32(E0, MSG0);
+ E1 = ABCD;
+ MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0);
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 3);
+ MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0);
+ MSG2 = _mm_xor_si128(MSG2, MSG0);
+
+ /* Rounds 68-71 */
+ E1 = _mm_sha1nexte_epu32(E1, MSG1);
+ E0 = ABCD;
+ MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1);
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3);
+ MSG3 = _mm_xor_si128(MSG3, MSG1);
+
+ /* Rounds 72-75 */
+ E0 = _mm_sha1nexte_epu32(E0, MSG2);
+ E1 = ABCD;
+ MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2);
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 3);
+
+ /* Rounds 76-79 */
+ E1 = _mm_sha1nexte_epu32(E1, MSG3);
+ E0 = ABCD;
+ ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3);
+
+ /* Combine state */
+ core[0] = _mm_add_epi32(ABCD, core[0]);
+ core[1] = _mm_sha1nexte_epu32(E0, core[1]);
+}
+
+typedef struct sha1_ni {
+ /*
+ * core[0] stores the first four words of the SHA-1 state. core[1]
+ * stores just the fifth word, in the vector lane at the highest
+ * address.
+ */
+ __m128i core[2];
+ sha1_block blk;
+ void *pointer_to_free;
+ BinarySink_IMPLEMENTATION;
+ ssh_hash hash;
+} sha1_ni;
+
+static void sha1_ni_write(BinarySink *bs, const void *vp, size_t len);
+
+static sha1_ni *sha1_ni_alloc(void)
+{
+ /*
+ * The __m128i variables in the context structure need to be
+ * 16-byte aligned, but not all malloc implementations that this
+ * code has to work with will guarantee to return a 16-byte
+ * aligned pointer. So we over-allocate, manually realign the
+ * pointer ourselves, and store the original one inside the
+ * context so we know how to free it later.
+ */
+ void *allocation = smalloc(sizeof(sha1_ni) + 15);
+ uintptr_t alloc_address = (uintptr_t)allocation;
+ uintptr_t aligned_address = (alloc_address + 15) & ~15;
+ sha1_ni *s = (sha1_ni *)aligned_address;
+ s->pointer_to_free = allocation;
+ return s;
+}
+
+static ssh_hash *sha1_ni_new(const ssh_hashalg *alg)
+{
+ const struct sha1_extra *extra = (const struct sha1_extra *)alg->extra;
+ if (!check_availability(extra))
+ return NULL;
+
+ sha1_ni *s = sha1_ni_alloc();
+
+ s->hash.vt = alg;
+ BinarySink_INIT(s, sha1_ni_write);
+ BinarySink_DELEGATE_INIT(&s->hash, s);
+ return &s->hash;
+}
+
+static void sha1_ni_reset(ssh_hash *hash)
+{
+ sha1_ni *s = container_of(hash, sha1_ni, hash);
+
+ /* Initialise the core vectors in their storage order */
+ s->core[0] = _mm_set_epi64x(
+ 0x67452301efcdab89ULL, 0x98badcfe10325476ULL);
+ s->core[1] = _mm_set_epi32(0xc3d2e1f0, 0, 0, 0);
+
+ sha1_block_setup(&s->blk);
+}
+
+static void sha1_ni_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+ sha1_ni *copy = container_of(hcopy, sha1_ni, hash);
+ sha1_ni *orig = container_of(horig, sha1_ni, hash);
+
+ void *ptf_save = copy->pointer_to_free;
+ *copy = *orig; /* structure copy */
+ copy->pointer_to_free = ptf_save;
+
+ BinarySink_COPIED(copy);
+ BinarySink_DELEGATE_INIT(&copy->hash, copy);
+}
+
+static void sha1_ni_free(ssh_hash *hash)
+{
+ sha1_ni *s = container_of(hash, sha1_ni, hash);
+
+ void *ptf = s->pointer_to_free;
+ smemclr(s, sizeof(*s));
+ sfree(ptf);
+}
+
+static void sha1_ni_write(BinarySink *bs, const void *vp, size_t len)
+{
+ sha1_ni *s = BinarySink_DOWNCAST(bs, sha1_ni);
+
+ while (len > 0)
+ if (sha1_block_write(&s->blk, &vp, &len))
+ sha1_ni_block(s->core, s->blk.block);
+}
+
+static void sha1_ni_digest(ssh_hash *hash, uint8_t *digest)
+{
+ sha1_ni *s = container_of(hash, sha1_ni, hash);
+
+ sha1_block_pad(&s->blk, BinarySink_UPCAST(s));
+
+ /* Rearrange the first vector into its output order */
+ __m128i abcd = _mm_shuffle_epi32(s->core[0], 0x1B);
+
+ /* Byte-swap it into the output endianness */
+ const __m128i mask = _mm_setr_epi8(3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12);
+ abcd = _mm_shuffle_epi8(abcd, mask);
+
+ /* And store it */
+ _mm_storeu_si128((__m128i *)digest, abcd);
+
+ /* Finally, store the leftover word */
+ uint32_t e = _mm_extract_epi32(s->core[1], 3);
+ PUT_32BIT_MSB_FIRST(digest + 16, e);
+}
+
+SHA1_VTABLE(ni, "SHA-NI accelerated");
diff --git a/crypto/sha1-select.c b/crypto/sha1-select.c
new file mode 100644
index 00000000..1e8a6ce9
--- /dev/null
+++ b/crypto/sha1-select.c
@@ -0,0 +1,44 @@
+/*
+ * Top-level vtables to select a SHA-1 implementation.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sha1.h"
+
+static ssh_hash *sha1_select(const ssh_hashalg *alg)
+{
+ static const ssh_hashalg *const real_algs[] = {
+#if HAVE_SHA_NI
+ &ssh_sha1_ni,
+#endif
+#if HAVE_NEON_CRYPTO
+ &ssh_sha1_neon,
+#endif
+ &ssh_sha1_sw,
+ NULL,
+ };
+
+ for (size_t i = 0; real_algs[i]; i++) {
+ const ssh_hashalg *alg = real_algs[i];
+ const struct sha1_extra *alg_extra =
+ (const struct sha1_extra *)alg->extra;
+ if (check_availability(alg_extra))
+ return ssh_hash_new(alg);
+ }
+
+ /* We should never reach the NULL at the end of the list, because
+ * the last non-NULL entry should be software-only SHA-1, which
+ * is always available. */
+ unreachable("sha1_select ran off the end of its list");
+}
+
+const ssh_hashalg ssh_sha1 = {
+ .new = sha1_select,
+ .hlen = 20,
+ .blocklen = 64,
+ HASHALG_NAMES_ANNOTATED("SHA-1", "dummy selector vtable"),
+};
diff --git a/crypto/sha1-sw.c b/crypto/sha1-sw.c
new file mode 100644
index 00000000..905d97f3
--- /dev/null
+++ b/crypto/sha1-sw.c
@@ -0,0 +1,155 @@
+/*
+ * Software implementation of SHA-1.
+ */
+
+#include "ssh.h"
+#include "sha1.h"
+
+static bool sha1_sw_available(void)
+{
+ /* Software SHA-1 is always available */
+ return true;
+}
+
+static inline uint32_t rol(uint32_t x, unsigned y)
+{
+ return (x << (31 & y)) | (x >> (31 & -y));
+}
+
+static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0)
+{
+ return if0 ^ (ctrl & (if1 ^ if0));
+}
+
+static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z)
+{
+ return (x & y) | (z & (x | y));
+}
+
+static inline uint32_t Par(uint32_t x, uint32_t y, uint32_t z)
+{
+ return (x ^ y ^ z);
+}
+
+static inline void sha1_sw_round(
+ unsigned round_index, const uint32_t *schedule,
+ uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e,
+ uint32_t f, uint32_t constant)
+{
+ *e = rol(*a, 5) + f + *e + schedule[round_index] + constant;
+ *b = rol(*b, 30);
+}
+
+static void sha1_sw_block(uint32_t *core, const uint8_t *block)
+{
+ uint32_t w[SHA1_ROUNDS];
+ uint32_t a,b,c,d,e;
+
+ for (size_t t = 0; t < 16; t++)
+ w[t] = GET_32BIT_MSB_FIRST(block + 4*t);
+
+ for (size_t t = 16; t < SHA1_ROUNDS; t++)
+ w[t] = rol(w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16], 1);
+
+ a = core[0]; b = core[1]; c = core[2]; d = core[3];
+ e = core[4];
+
+ size_t t = 0;
+ for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) {
+ sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Ch(b,c,d), SHA1_STAGE0_CONSTANT);
+ sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Ch(a,b,c), SHA1_STAGE0_CONSTANT);
+ sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Ch(e,a,b), SHA1_STAGE0_CONSTANT);
+ sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Ch(d,e,a), SHA1_STAGE0_CONSTANT);
+ sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Ch(c,d,e), SHA1_STAGE0_CONSTANT);
+ }
+ for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) {
+ sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Par(b,c,d), SHA1_STAGE1_CONSTANT);
+ sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Par(a,b,c), SHA1_STAGE1_CONSTANT);
+ sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Par(e,a,b), SHA1_STAGE1_CONSTANT);
+ sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Par(d,e,a), SHA1_STAGE1_CONSTANT);
+ sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Par(c,d,e), SHA1_STAGE1_CONSTANT);
+ }
+ for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) {
+ sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Maj(b,c,d), SHA1_STAGE2_CONSTANT);
+ sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Maj(a,b,c), SHA1_STAGE2_CONSTANT);
+ sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Maj(e,a,b), SHA1_STAGE2_CONSTANT);
+ sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Maj(d,e,a), SHA1_STAGE2_CONSTANT);
+ sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Maj(c,d,e), SHA1_STAGE2_CONSTANT);
+ }
+ for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) {
+ sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Par(b,c,d), SHA1_STAGE3_CONSTANT);
+ sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Par(a,b,c), SHA1_STAGE3_CONSTANT);
+ sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Par(e,a,b), SHA1_STAGE3_CONSTANT);
+ sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Par(d,e,a), SHA1_STAGE3_CONSTANT);
+ sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Par(c,d,e), SHA1_STAGE3_CONSTANT);
+ }
+
+ core[0] += a; core[1] += b; core[2] += c; core[3] += d; core[4] += e;
+
+ smemclr(w, sizeof(w));
+}
+
+typedef struct sha1_sw {
+ uint32_t core[5];
+ sha1_block blk;
+ BinarySink_IMPLEMENTATION;
+ ssh_hash hash;
+} sha1_sw;
+
+static void sha1_sw_write(BinarySink *bs, const void *vp, size_t len);
+
+static ssh_hash *sha1_sw_new(const ssh_hashalg *alg)
+{
+ sha1_sw *s = snew(sha1_sw);
+
+ s->hash.vt = alg;
+ BinarySink_INIT(s, sha1_sw_write);
+ BinarySink_DELEGATE_INIT(&s->hash, s);
+ return &s->hash;
+}
+
+static void sha1_sw_reset(ssh_hash *hash)
+{
+ sha1_sw *s = container_of(hash, sha1_sw, hash);
+
+ memcpy(s->core, sha1_initial_state, sizeof(s->core));
+ sha1_block_setup(&s->blk);
+}
+
+static void sha1_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+ sha1_sw *copy = container_of(hcopy, sha1_sw, hash);
+ sha1_sw *orig = container_of(horig, sha1_sw, hash);
+
+ memcpy(copy, orig, sizeof(*copy));
+ BinarySink_COPIED(copy);
+ BinarySink_DELEGATE_INIT(&copy->hash, copy);
+}
+
+static void sha1_sw_free(ssh_hash *hash)
+{
+ sha1_sw *s = container_of(hash, sha1_sw, hash);
+
+ smemclr(s, sizeof(*s));
+ sfree(s);
+}
+
+static void sha1_sw_write(BinarySink *bs, const void *vp, size_t len)
+{
+ sha1_sw *s = BinarySink_DOWNCAST(bs, sha1_sw);
+
+ while (len > 0)
+ if (sha1_block_write(&s->blk, &vp, &len))
+ sha1_sw_block(s->core, s->blk.block);
+}
+
+static void sha1_sw_digest(ssh_hash *hash, uint8_t *digest)
+{
+ sha1_sw *s = container_of(hash, sha1_sw, hash);
+
+ sha1_block_pad(&s->blk, BinarySink_UPCAST(s));
+ for (size_t i = 0; i < 5; i++)
+ PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]);
+}
+
+SHA1_VTABLE(sw, "unaccelerated");
diff --git a/crypto/sha1.h b/crypto/sha1.h
new file mode 100644
index 00000000..2cdba0d4
--- /dev/null
+++ b/crypto/sha1.h
@@ -0,0 +1,109 @@
+/*
+ * Definitions likely to be helpful to multiple SHA-1 implementations.
+ */
+
+/*
+ * The 'extra' structure used by SHA-1 implementations is used to
+ * include information about how to check if a given implementation is
+ * available at run time, and whether we've already checked.
+ */
+struct sha1_extra_mutable;
+struct sha1_extra {
+ /* Function to check availability. Might be expensive, so we don't
+ * want to call it more than once. */
+ bool (*check_available)(void);
+
+ /* Point to a writable substructure. */
+ struct sha1_extra_mutable *mut;
+};
+struct sha1_extra_mutable {
+ bool checked_availability;
+ bool is_available;
+};
+static inline bool check_availability(const struct sha1_extra *extra)
+{
+ if (!extra->mut->checked_availability) {
+ extra->mut->is_available = extra->check_available();
+ extra->mut->checked_availability = true;
+ }
+
+ return extra->mut->is_available;
+}
+
+/*
+ * Macro to define a SHA-1 vtable together with its 'extra'
+ * structure.
+ */
+#define SHA1_VTABLE(impl_c, impl_display) \
+ static struct sha1_extra_mutable sha1_ ## impl_c ## _extra_mut; \
+ static const struct sha1_extra sha1_ ## impl_c ## _extra = { \
+ .check_available = sha1_ ## impl_c ## _available, \
+ .mut = &sha1_ ## impl_c ## _extra_mut, \
+ }; \
+ const ssh_hashalg ssh_sha1_ ## impl_c = { \
+ .new = sha1_ ## impl_c ## _new, \
+ .reset = sha1_ ## impl_c ## _reset, \
+ .copyfrom = sha1_ ## impl_c ## _copyfrom, \
+ .digest = sha1_ ## impl_c ## _digest, \
+ .free = sha1_ ## impl_c ## _free, \
+ .hlen = 20, \
+ .blocklen = 64, \
+ HASHALG_NAMES_ANNOTATED("SHA-1", impl_display), \
+ .extra = &sha1_ ## impl_c ## _extra, \
+ }
+
+extern const uint32_t sha1_initial_state[5];
+
+#define SHA1_ROUNDS_PER_STAGE 20
+#define SHA1_STAGE0_CONSTANT 0x5a827999
+#define SHA1_STAGE1_CONSTANT 0x6ed9eba1
+#define SHA1_STAGE2_CONSTANT 0x8f1bbcdc
+#define SHA1_STAGE3_CONSTANT 0xca62c1d6
+#define SHA1_ROUNDS (4 * SHA1_ROUNDS_PER_STAGE)
+
+typedef struct sha1_block sha1_block;
+struct sha1_block {
+ uint8_t block[64];
+ size_t used;
+ uint64_t len;
+};
+
+static inline void sha1_block_setup(sha1_block *blk)
+{
+ blk->used = 0;
+ blk->len = 0;
+}
+
+static inline bool sha1_block_write(
+ sha1_block *blk, const void **vdata, size_t *len)
+{
+ size_t blkleft = sizeof(blk->block) - blk->used;
+ size_t chunk = *len < blkleft ? *len : blkleft;
+
+ const uint8_t *p = *vdata;
+ memcpy(blk->block + blk->used, p, chunk);
+ *vdata = p + chunk;
+ *len -= chunk;
+ blk->used += chunk;
+ blk->len += chunk;
+
+ if (blk->used == sizeof(blk->block)) {
+ blk->used = 0;
+ return true;
+ }
+
+ return false;
+}
+
+static inline void sha1_block_pad(sha1_block *blk, BinarySink *bs)
+{
+ uint64_t final_len = blk->len << 3;
+ size_t pad = 1 + (63 & (55 - blk->used));
+
+ put_byte(bs, 0x80);
+ for (size_t i = 1; i < pad; i++)
+ put_byte(bs, 0);
+ put_uint64(bs, final_len);
+
+ assert(blk->used == 0 && "Should have exactly hit a block boundary");
+}
diff --git a/crypto/sha256-common.c b/crypto/sha256-common.c
new file mode 100644
index 00000000..52904c08
--- /dev/null
+++ b/crypto/sha256-common.c
@@ -0,0 +1,30 @@
+/*
+ * Common variable definitions across all the SHA-256 implementations.
+ */
+
+#include "ssh.h"
+#include "sha256.h"
+
+const uint32_t sha256_initial_state[8] = {
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
+};
+
+const uint32_t sha256_round_constants[64] = {
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+ 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+ 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+ 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
+};
diff --git a/crypto/sha256-neon.c b/crypto/sha256-neon.c
new file mode 100644
index 00000000..87d24d0c
--- /dev/null
+++ b/crypto/sha256-neon.c
@@ -0,0 +1,162 @@
+/*
+ * Hardware-accelerated implementation of SHA-256 using Arm NEON.
+ */
+
+#include "ssh.h"
+#include "sha256.h"
+
+#if USE_ARM64_NEON_H
+#include <arm64_neon.h>
+#else
+#include <arm_neon.h>
+#endif
+
+static bool sha256_neon_available(void)
+{
+ /*
+ * For Arm, we delegate to a per-platform detection function (see
+ * explanation in aes-neon.c).
+ */
+ return platform_sha256_neon_available();
+}
+
+typedef struct sha256_neon_core sha256_neon_core;
+struct sha256_neon_core {
+ uint32x4_t abcd, efgh;
+};
+
+static inline uint32x4_t sha256_neon_load_input(const uint8_t *p)
+{
+ return vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(p)));
+}
+
+static inline uint32x4_t sha256_neon_schedule_update(
+ uint32x4_t m4, uint32x4_t m3, uint32x4_t m2, uint32x4_t m1)
+{
+ return vsha256su1q_u32(vsha256su0q_u32(m4, m3), m2, m1);
+}
+
+static inline sha256_neon_core sha256_neon_round4(
+ sha256_neon_core old, uint32x4_t sched, unsigned round)
+{
+ sha256_neon_core new;
+
+ uint32x4_t round_input = vaddq_u32(
+ sched, vld1q_u32(sha256_round_constants + round));
+ new.abcd = vsha256hq_u32 (old.abcd, old.efgh, round_input);
+ new.efgh = vsha256h2q_u32(old.efgh, old.abcd, round_input);
+ return new;
+}
+
+static inline void sha256_neon_block(sha256_neon_core *core, const uint8_t *p)
+{
+ uint32x4_t s0, s1, s2, s3;
+ sha256_neon_core cr = *core;
+
+ s0 = sha256_neon_load_input(p);
+ cr = sha256_neon_round4(cr, s0, 0);
+ s1 = sha256_neon_load_input(p+16);
+ cr = sha256_neon_round4(cr, s1, 4);
+ s2 = sha256_neon_load_input(p+32);
+ cr = sha256_neon_round4(cr, s2, 8);
+ s3 = sha256_neon_load_input(p+48);
+ cr = sha256_neon_round4(cr, s3, 12);
+ s0 = sha256_neon_schedule_update(s0, s1, s2, s3);
+ cr = sha256_neon_round4(cr, s0, 16);
+ s1 = sha256_neon_schedule_update(s1, s2, s3, s0);
+ cr = sha256_neon_round4(cr, s1, 20);
+ s2 = sha256_neon_schedule_update(s2, s3, s0, s1);
+ cr = sha256_neon_round4(cr, s2, 24);
+ s3 = sha256_neon_schedule_update(s3, s0, s1, s2);
+ cr = sha256_neon_round4(cr, s3, 28);
+ s0 = sha256_neon_schedule_update(s0, s1, s2, s3);
+ cr = sha256_neon_round4(cr, s0, 32);
+ s1 = sha256_neon_schedule_update(s1, s2, s3, s0);
+ cr = sha256_neon_round4(cr, s1, 36);
+ s2 = sha256_neon_schedule_update(s2, s3, s0, s1);
+ cr = sha256_neon_round4(cr, s2, 40);
+ s3 = sha256_neon_schedule_update(s3, s0, s1, s2);
+ cr = sha256_neon_round4(cr, s3, 44);
+ s0 = sha256_neon_schedule_update(s0, s1, s2, s3);
+ cr = sha256_neon_round4(cr, s0, 48);
+ s1 = sha256_neon_schedule_update(s1, s2, s3, s0);
+ cr = sha256_neon_round4(cr, s1, 52);
+ s2 = sha256_neon_schedule_update(s2, s3, s0, s1);
+ cr = sha256_neon_round4(cr, s2, 56);
+ s3 = sha256_neon_schedule_update(s3, s0, s1, s2);
+ cr = sha256_neon_round4(cr, s3, 60);
+
+ core->abcd = vaddq_u32(core->abcd, cr.abcd);
+ core->efgh = vaddq_u32(core->efgh, cr.efgh);
+}
+
+typedef struct sha256_neon {
+ sha256_neon_core core;
+ sha256_block blk;
+ BinarySink_IMPLEMENTATION;
+ ssh_hash hash;
+} sha256_neon;
+
+static void sha256_neon_write(BinarySink *bs, const void *vp, size_t len);
+
+static ssh_hash *sha256_neon_new(const ssh_hashalg *alg)
+{
+ const struct sha256_extra *extra = (const struct sha256_extra *)alg->extra;
+ if (!check_availability(extra))
+ return NULL;
+
+ sha256_neon *s = snew(sha256_neon);
+
+ s->hash.vt = alg;
+ BinarySink_INIT(s, sha256_neon_write);
+ BinarySink_DELEGATE_INIT(&s->hash, s);
+ return &s->hash;
+}
+
+static void sha256_neon_reset(ssh_hash *hash)
+{
+ sha256_neon *s = container_of(hash, sha256_neon, hash);
+
+ s->core.abcd = vld1q_u32(sha256_initial_state);
+ s->core.efgh = vld1q_u32(sha256_initial_state + 4);
+
+ sha256_block_setup(&s->blk);
+}
+
+static void sha256_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+ sha256_neon *copy = container_of(hcopy, sha256_neon, hash);
+ sha256_neon *orig = container_of(horig, sha256_neon, hash);
+
+ *copy = *orig; /* structure copy */
+
+ BinarySink_COPIED(copy);
+ BinarySink_DELEGATE_INIT(&copy->hash, copy);
+}
+
+static void sha256_neon_free(ssh_hash *hash)
+{
+ sha256_neon *s = container_of(hash, sha256_neon, hash);
+ smemclr(s, sizeof(*s));
+ sfree(s);
+}
+
+static void sha256_neon_write(BinarySink *bs, const void *vp, size_t len)
+{
+ sha256_neon *s = BinarySink_DOWNCAST(bs, sha256_neon);
+
+ while (len > 0)
+ if (sha256_block_write(&s->blk, &vp, &len))
+ sha256_neon_block(&s->core, s->blk.block);
+}
+
+static void sha256_neon_digest(ssh_hash *hash, uint8_t *digest)
+{
+ sha256_neon *s = container_of(hash, sha256_neon, hash);
+
+ sha256_block_pad(&s->blk, BinarySink_UPCAST(s));
+ vst1q_u8(digest, vrev32q_u8(vreinterpretq_u8_u32(s->core.abcd)));
+ vst1q_u8(digest + 16, vrev32q_u8(vreinterpretq_u8_u32(s->core.efgh)));
+}
+
+SHA256_VTABLE(neon, "NEON accelerated");
diff --git a/crypto/sha256-ni.c b/crypto/sha256-ni.c
new file mode 100644
index 00000000..530fa433
--- /dev/null
+++ b/crypto/sha256-ni.c
@@ -0,0 +1,342 @@
+/*
+ * Hardware-accelerated implementation of SHA-256 using x86 SHA-NI.
+ */
+
+#include "ssh.h"
+#include "sha256.h"
+
+#include <wmmintrin.h>
+#include <smmintrin.h>
+#include <immintrin.h>
+#if HAVE_SHAINTRIN_H
+#include <shaintrin.h>
+#endif
+
+#if defined(__clang__) || defined(__GNUC__)
+#include <cpuid.h>
+#define GET_CPU_ID_0(out) \
+ __cpuid(0, (out)[0], (out)[1], (out)[2], (out)[3])
+#define GET_CPU_ID_7(out) \
+ __cpuid_count(7, 0, (out)[0], (out)[1], (out)[2], (out)[3])
+#else
+#define GET_CPU_ID_0(out) __cpuid(out, 0)
+#define GET_CPU_ID_7(out) __cpuidex(out, 7, 0)
+#endif
+
+static bool sha256_ni_available(void)
+{
+ unsigned int CPUInfo[4];
+ GET_CPU_ID_0(CPUInfo);
+ if (CPUInfo[0] < 7)
+ return false;
+
+ GET_CPU_ID_7(CPUInfo);
+ return CPUInfo[1] & (1 << 29); /* Check SHA */
+}
+
+/* SHA256 implementation using new instructions
+ The code is based on Jeffrey Walton's SHA256 implementation:
+ https://github.com/noloader/SHA-Intrinsics
+*/
+static inline void sha256_ni_block(__m128i *core, const uint8_t *p)
+{
+ __m128i STATE0, STATE1;
+ __m128i MSG, TMP;
+ __m128i MSG0, MSG1, MSG2, MSG3;
+ const __m128i *block = (const __m128i *)p;
+ const __m128i MASK = _mm_set_epi64x(
+ 0x0c0d0e0f08090a0bULL, 0x0405060700010203ULL);
+
+ /* Load initial values */
+ STATE0 = core[0];
+ STATE1 = core[1];
+
+ /* Rounds 0-3 */
+ MSG = _mm_loadu_si128(block);
+ MSG0 = _mm_shuffle_epi8(MSG, MASK);
+ MSG = _mm_add_epi32(MSG0, _mm_set_epi64x(
+ 0xE9B5DBA5B5C0FBCFULL, 0x71374491428A2F98ULL));
+ STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
+ MSG = _mm_shuffle_epi32(MSG, 0x0E);
+ STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG);
+
+ /* Rounds 4-7 */
+ MSG1 = _mm_loadu_si128(block + 1);
+ MSG1 = _mm_shuffle_epi8(MSG1, MASK);
+ MSG = _mm_add_epi32(MSG1, _mm_set_epi64x(
+ 0xAB1C5ED5923F82A4ULL, 0x59F111F13956C25BULL));
+ STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
+ MSG = _mm_shuffle_epi32(MSG, 0x0E);
+ STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG);
+ MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1);
+
+ /* Rounds 8-11 */
+ MSG2 = _mm_loadu_si128(block + 2);
+ MSG2 = _mm_shuffle_epi8(MSG2, MASK);
+ MSG = _mm_add_epi32(MSG2, _mm_set_epi64x(
+ 0x550C7DC3243185BEULL, 0x12835B01D807AA98ULL));
+ STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
+ MSG = _mm_shuffle_epi32(MSG, 0x0E);
+ STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG);
+ MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2);
+
+ /* Rounds 12-15 */
+ MSG3 = _mm_loadu_si128(block + 3);
+ MSG3 = _mm_shuffle_epi8(MSG3, MASK);
+ MSG = _mm_add_epi32(MSG3, _mm_set_epi64x(
+ 0xC19BF1749BDC06A7ULL, 0x80DEB1FE72BE5D74ULL));
+ STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
+ TMP = _mm_alignr_epi8(MSG3, MSG2, 4);
+ MSG0 = _mm_add_epi32(MSG0, TMP);
+ MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3);
+ MSG = _mm_shuffle_epi32(MSG, 0x0E);
+ STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG);
+ MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3);
+
+ /* Rounds 16-19 */
+ MSG = _mm_add_epi32(MSG0, _mm_set_epi64x(
+ 0x240CA1CC0FC19DC6ULL, 0xEFBE4786E49B69C1ULL));
+ STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
+ TMP = _mm_alignr_epi8(MSG0, MSG3, 4);
+ MSG1 = _mm_add_epi32(MSG1, TMP);
+ MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0);
+ MSG = _mm_shuffle_epi32(MSG, 0x0E);
+ STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG);
+ MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0);
+
+ /* Rounds 20-23 */
+ MSG = _mm_add_epi32(MSG1, _mm_set_epi64x(
+ 0x76F988DA5CB0A9DCULL, 0x4A7484AA2DE92C6FULL));
+ STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
+ TMP = _mm_alignr_epi8(MSG1, MSG0, 4);
+ MSG2 = _mm_add_epi32(MSG2, TMP);
+ MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1);
+ MSG = _mm_shuffle_epi32(MSG, 0x0E);
+ STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG);
+ MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1);
+
+ /* Rounds 24-27 */
+ MSG = _mm_add_epi32(MSG2, _mm_set_epi64x(
+ 0xBF597FC7B00327C8ULL, 0xA831C66D983E5152ULL));
+ STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
+ TMP = _mm_alignr_epi8(MSG2, MSG1, 4);
+ MSG3 = _mm_add_epi32(MSG3, TMP);
+ MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2);
+ MSG = _mm_shuffle_epi32(MSG, 0x0E);
+ STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG);
+ MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2);
+
+ /* Rounds 28-31 */
+ MSG = _mm_add_epi32(MSG3, _mm_set_epi64x(
+ 0x1429296706CA6351ULL, 0xD5A79147C6E00BF3ULL));
+ STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
+ TMP = _mm_alignr_epi8(MSG3, MSG2, 4);
+ MSG0 = _mm_add_epi32(MSG0, TMP);
+ MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3);
+ MSG = _mm_shuffle_epi32(MSG, 0x0E);
+ STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG);
+ MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3);
+
+ /* Rounds 32-35 */
+ MSG = _mm_add_epi32(MSG0, _mm_set_epi64x(
+ 0x53380D134D2C6DFCULL, 0x2E1B213827B70A85ULL));
+ STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
+ TMP = _mm_alignr_epi8(MSG0, MSG3, 4);
+ MSG1 = _mm_add_epi32(MSG1, TMP);
+ MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0);
+ MSG = _mm_shuffle_epi32(MSG, 0x0E);
+ STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG);
+ MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0);
+
+ /* Rounds 36-39 */
+ MSG = _mm_add_epi32(MSG1, _mm_set_epi64x(
+ 0x92722C8581C2C92EULL, 0x766A0ABB650A7354ULL));
+ STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
+ TMP = _mm_alignr_epi8(MSG1, MSG0, 4);
+ MSG2 = _mm_add_epi32(MSG2, TMP);
+ MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1);
+ MSG = _mm_shuffle_epi32(MSG, 0x0E);
+ STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG);
+ MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1);
+
+ /* Rounds 40-43 */
+ MSG = _mm_add_epi32(MSG2, _mm_set_epi64x(
+ 0xC76C51A3C24B8B70ULL, 0xA81A664BA2BFE8A1ULL));
+ STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
+ TMP = _mm_alignr_epi8(MSG2, MSG1, 4);
+ MSG3 = _mm_add_epi32(MSG3, TMP);
+ MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2);
+ MSG = _mm_shuffle_epi32(MSG, 0x0E);
+ STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG);
+ MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2);
+
+ /* Rounds 44-47 */
+ MSG = _mm_add_epi32(MSG3, _mm_set_epi64x(
+ 0x106AA070F40E3585ULL, 0xD6990624D192E819ULL));
+ STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
+ TMP = _mm_alignr_epi8(MSG3, MSG2, 4);
+ MSG0 = _mm_add_epi32(MSG0, TMP);
+ MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3);
+ MSG = _mm_shuffle_epi32(MSG, 0x0E);
+ STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG);
+ MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3);
+
+ /* Rounds 48-51 */
+ MSG = _mm_add_epi32(MSG0, _mm_set_epi64x(
+ 0x34B0BCB52748774CULL, 0x1E376C0819A4C116ULL));
+ STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
+ TMP = _mm_alignr_epi8(MSG0, MSG3, 4);
+ MSG1 = _mm_add_epi32(MSG1, TMP);
+ MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0);
+ MSG = _mm_shuffle_epi32(MSG, 0x0E);
+ STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG);
+ MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0);
+
+ /* Rounds 52-55 */
+ MSG = _mm_add_epi32(MSG1, _mm_set_epi64x(
+ 0x682E6FF35B9CCA4FULL, 0x4ED8AA4A391C0CB3ULL));
+ STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
+ TMP = _mm_alignr_epi8(MSG1, MSG0, 4);
+ MSG2 = _mm_add_epi32(MSG2, TMP);
+ MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1);
+ MSG = _mm_shuffle_epi32(MSG, 0x0E);
+ STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG);
+
+ /* Rounds 56-59 */
+ MSG = _mm_add_epi32(MSG2, _mm_set_epi64x(
+ 0x8CC7020884C87814ULL, 0x78A5636F748F82EEULL));
+ STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
+ TMP = _mm_alignr_epi8(MSG2, MSG1, 4);
+ MSG3 = _mm_add_epi32(MSG3, TMP);
+ MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2);
+ MSG = _mm_shuffle_epi32(MSG, 0x0E);
+ STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG);
+
+ /* Rounds 60-63 */
+ MSG = _mm_add_epi32(MSG3, _mm_set_epi64x(
+ 0xC67178F2BEF9A3F7ULL, 0xA4506CEB90BEFFFAULL));
+ STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
+ MSG = _mm_shuffle_epi32(MSG, 0x0E);
+ STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG);
+
+ /* Combine state */
+ core[0] = _mm_add_epi32(STATE0, core[0]);
+ core[1] = _mm_add_epi32(STATE1, core[1]);
+}
+
+typedef struct sha256_ni {
+ /*
+ * These two vectors store the 8 words of the SHA-256 state, but
+ * not in the same order they appear in the spec: the first word
+ * holds A,B,E,F and the second word C,D,G,H.
+ */
+ __m128i core[2];
+ sha256_block blk;
+ void *pointer_to_free;
+ BinarySink_IMPLEMENTATION;
+ ssh_hash hash;
+} sha256_ni;
+
+static void sha256_ni_write(BinarySink *bs, const void *vp, size_t len);
+
+static sha256_ni *sha256_ni_alloc(void)
+{
+ /*
+ * The __m128i variables in the context structure need to be
+ * 16-byte aligned, but not all malloc implementations that this
+ * code has to work with will guarantee to return a 16-byte
+ * aligned pointer. So we over-allocate, manually realign the
+ * pointer ourselves, and store the original one inside the
+ * context so we know how to free it later.
+ */
+ void *allocation = smalloc(sizeof(sha256_ni) + 15);
+ uintptr_t alloc_address = (uintptr_t)allocation;
+ uintptr_t aligned_address = (alloc_address + 15) & ~15;
+ sha256_ni *s = (sha256_ni *)aligned_address;
+ s->pointer_to_free = allocation;
+ return s;
+}
+
+static ssh_hash *sha256_ni_new(const ssh_hashalg *alg)
+{
+ const struct sha256_extra *extra = (const struct sha256_extra *)alg->extra;
+ if (!check_availability(extra))
+ return NULL;
+
+ sha256_ni *s = sha256_ni_alloc();
+
+ s->hash.vt = alg;
+ BinarySink_INIT(s, sha256_ni_write);
+ BinarySink_DELEGATE_INIT(&s->hash, s);
+
+ return &s->hash;
+}
+
+static void sha256_ni_reset(ssh_hash *hash)
+{
+ sha256_ni *s = container_of(hash, sha256_ni, hash);
+
+ /* Initialise the core vectors in their storage order */
+ s->core[0] = _mm_set_epi64x(
+ 0x6a09e667bb67ae85ULL, 0x510e527f9b05688cULL);
+ s->core[1] = _mm_set_epi64x(
+ 0x3c6ef372a54ff53aULL, 0x1f83d9ab5be0cd19ULL);
+
+ sha256_block_setup(&s->blk);
+}
+
+static void sha256_ni_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+ sha256_ni *copy = container_of(hcopy, sha256_ni, hash);
+ sha256_ni *orig = container_of(horig, sha256_ni, hash);
+
+ void *ptf_save = copy->pointer_to_free;
+ *copy = *orig; /* structure copy */
+ copy->pointer_to_free = ptf_save;
+
+ BinarySink_COPIED(copy);
+ BinarySink_DELEGATE_INIT(&copy->hash, copy);
+}
+
+static void sha256_ni_free(ssh_hash *hash)
+{
+ sha256_ni *s = container_of(hash, sha256_ni, hash);
+
+ void *ptf = s->pointer_to_free;
+ smemclr(s, sizeof(*s));
+ sfree(ptf);
+}
+
+static void sha256_ni_write(BinarySink *bs, const void *vp, size_t len)
+{
+ sha256_ni *s = BinarySink_DOWNCAST(bs, sha256_ni);
+
+ while (len > 0)
+ if (sha256_block_write(&s->blk, &vp, &len))
+ sha256_ni_block(s->core, s->blk.block);
+}
+
+static void sha256_ni_digest(ssh_hash *hash, uint8_t *digest)
+{
+ sha256_ni *s = container_of(hash, sha256_ni, hash);
+
+ sha256_block_pad(&s->blk, BinarySink_UPCAST(s));
+
+ /* Rearrange the words into the output order */
+ __m128i feba = _mm_shuffle_epi32(s->core[0], 0x1B);
+ __m128i dchg = _mm_shuffle_epi32(s->core[1], 0xB1);
+ __m128i dcba = _mm_blend_epi16(feba, dchg, 0xF0);
+ __m128i hgfe = _mm_alignr_epi8(dchg, feba, 8);
+
+ /* Byte-swap them into the output endianness */
+ const __m128i mask = _mm_setr_epi8(3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12);
+ dcba = _mm_shuffle_epi8(dcba, mask);
+ hgfe = _mm_shuffle_epi8(hgfe, mask);
+
+ /* And store them */
+ __m128i *output = (__m128i *)digest;
+ _mm_storeu_si128(output, dcba);
+ _mm_storeu_si128(output+1, hgfe);
+}
+
+SHA256_VTABLE(ni, "SHA-NI accelerated");
diff --git a/crypto/sha256-select.c b/crypto/sha256-select.c
new file mode 100644
index 00000000..78e5b7e4
--- /dev/null
+++ b/crypto/sha256-select.c
@@ -0,0 +1,44 @@
+/*
+ * Top-level vtables to select a SHA-256 implementation.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sha256.h"
+
+static ssh_hash *sha256_select(const ssh_hashalg *alg)
+{
+ static const ssh_hashalg *const real_algs[] = {
+#if HAVE_SHA_NI
+ &ssh_sha256_ni,
+#endif
+#if HAVE_NEON_CRYPTO
+ &ssh_sha256_neon,
+#endif
+ &ssh_sha256_sw,
+ NULL,
+ };
+
+ for (size_t i = 0; real_algs[i]; i++) {
+ const ssh_hashalg *alg = real_algs[i];
+ const struct sha256_extra *alg_extra =
+ (const struct sha256_extra *)alg->extra;
+ if (check_availability(alg_extra))
+ return ssh_hash_new(alg);
+ }
+
+ /* We should never reach the NULL at the end of the list, because
+ * the last non-NULL entry should be software-only SHA-256, which
+ * is always available. */
+ unreachable("sha256_select ran off the end of its list");
+}
+
+const ssh_hashalg ssh_sha256 = {
+ .new = sha256_select,
+ .hlen = 32,
+ .blocklen = 64,
+ HASHALG_NAMES_ANNOTATED("SHA-256", "dummy selector vtable"),
+};
diff --git a/crypto/sha256-sw.c b/crypto/sha256-sw.c
new file mode 100644
index 00000000..82a116c6
--- /dev/null
+++ b/crypto/sha256-sw.c
@@ -0,0 +1,157 @@
+/*
+ * Software implementation of SHA-256.
+ */
+
+#include "ssh.h"
+#include "sha256.h"
+
+static bool sha256_sw_available(void)
+{
+ /* Software SHA-256 is always available */
+ return true;
+}
+
+static inline uint32_t ror(uint32_t x, unsigned y)
+{
+ return (x << (31 & -y)) | (x >> (31 & y));
+}
+
+static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0)
+{
+ return if0 ^ (ctrl & (if1 ^ if0));
+}
+
+static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z)
+{
+ return (x & y) | (z & (x | y));
+}
+
+static inline uint32_t Sigma_0(uint32_t x)
+{
+ return ror(x,2) ^ ror(x,13) ^ ror(x,22);
+}
+
+static inline uint32_t Sigma_1(uint32_t x)
+{
+ return ror(x,6) ^ ror(x,11) ^ ror(x,25);
+}
+
+static inline uint32_t sigma_0(uint32_t x)
+{
+ return ror(x,7) ^ ror(x,18) ^ (x >> 3);
+}
+
+static inline uint32_t sigma_1(uint32_t x)
+{
+ return ror(x,17) ^ ror(x,19) ^ (x >> 10);
+}
+
+static inline void sha256_sw_round(
+ unsigned round_index, const uint32_t *schedule,
+ uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d,
+ uint32_t *e, uint32_t *f, uint32_t *g, uint32_t *h)
+{
+ uint32_t t1 = *h + Sigma_1(*e) + Ch(*e,*f,*g) +
+ sha256_round_constants[round_index] + schedule[round_index];
+
+ uint32_t t2 = Sigma_0(*a) + Maj(*a,*b,*c);
+
+ *d += t1;
+ *h = t1 + t2;
+}
+
+static void sha256_sw_block(uint32_t *core, const uint8_t *block)
+{
+ uint32_t w[SHA256_ROUNDS];
+ uint32_t a,b,c,d,e,f,g,h;
+
+ for (size_t t = 0; t < 16; t++)
+ w[t] = GET_32BIT_MSB_FIRST(block + 4*t);
+
+ for (size_t t = 16; t < SHA256_ROUNDS; t++)
+ w[t] = sigma_1(w[t-2]) + w[t-7] + sigma_0(w[t-15]) + w[t-16];
+
+ a = core[0]; b = core[1]; c = core[2]; d = core[3];
+ e = core[4]; f = core[5]; g = core[6]; h = core[7];
+
+ for (size_t t = 0; t < SHA256_ROUNDS; t += 8) {
+ sha256_sw_round(t+0, w, &a,&b,&c,&d,&e,&f,&g,&h);
+ sha256_sw_round(t+1, w, &h,&a,&b,&c,&d,&e,&f,&g);
+ sha256_sw_round(t+2, w, &g,&h,&a,&b,&c,&d,&e,&f);
+ sha256_sw_round(t+3, w, &f,&g,&h,&a,&b,&c,&d,&e);
+ sha256_sw_round(t+4, w, &e,&f,&g,&h,&a,&b,&c,&d);
+ sha256_sw_round(t+5, w, &d,&e,&f,&g,&h,&a,&b,&c);
+ sha256_sw_round(t+6, w, &c,&d,&e,&f,&g,&h,&a,&b);
+ sha256_sw_round(t+7, w, &b,&c,&d,&e,&f,&g,&h,&a);
+ }
+
+ core[0] += a; core[1] += b; core[2] += c; core[3] += d;
+ core[4] += e; core[5] += f; core[6] += g; core[7] += h;
+
+ smemclr(w, sizeof(w));
+}
+
+typedef struct sha256_sw {
+ uint32_t core[8];
+ sha256_block blk;
+ BinarySink_IMPLEMENTATION;
+ ssh_hash hash;
+} sha256_sw;
+
+static void sha256_sw_write(BinarySink *bs, const void *vp, size_t len);
+
+static ssh_hash *sha256_sw_new(const ssh_hashalg *alg)
+{
+ sha256_sw *s = snew(sha256_sw);
+
+ s->hash.vt = alg;
+ BinarySink_INIT(s, sha256_sw_write);
+ BinarySink_DELEGATE_INIT(&s->hash, s);
+ return &s->hash;
+}
+
+static void sha256_sw_reset(ssh_hash *hash)
+{
+ sha256_sw *s = container_of(hash, sha256_sw, hash);
+
+ memcpy(s->core, sha256_initial_state, sizeof(s->core));
+ sha256_block_setup(&s->blk);
+}
+
+static void sha256_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+ sha256_sw *copy = container_of(hcopy, sha256_sw, hash);
+ sha256_sw *orig = container_of(horig, sha256_sw, hash);
+
+ memcpy(copy, orig, sizeof(*copy));
+ BinarySink_COPIED(copy);
+ BinarySink_DELEGATE_INIT(&copy->hash, copy);
+}
+
+static void sha256_sw_free(ssh_hash *hash)
+{
+ sha256_sw *s = container_of(hash, sha256_sw, hash);
+
+ smemclr(s, sizeof(*s));
+ sfree(s);
+}
+
+static void sha256_sw_write(BinarySink *bs, const void *vp, size_t len)
+{
+ sha256_sw *s = BinarySink_DOWNCAST(bs, sha256_sw);
+
+ while (len > 0)
+ if (sha256_block_write(&s->blk, &vp, &len))
+ sha256_sw_block(s->core, s->blk.block);
+}
+
+static void sha256_sw_digest(ssh_hash *hash, uint8_t *digest)
+{
+ sha256_sw *s = container_of(hash, sha256_sw, hash);
+
+ sha256_block_pad(&s->blk, BinarySink_UPCAST(s));
+ for (size_t i = 0; i < 8; i++)
+ PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]);
+}
+
+SHA256_VTABLE(sw, "unaccelerated");
diff --git a/crypto/sha256.h b/crypto/sha256.h
new file mode 100644
index 00000000..e6ca7564
--- /dev/null
+++ b/crypto/sha256.h
@@ -0,0 +1,105 @@
+/*
+ * Definitions likely to be helpful to multiple SHA-256 implementations.
+ */
+
+/*
+ * The 'extra' structure used by SHA-256 implementations is used to
+ * include information about how to check if a given implementation is
+ * available at run time, and whether we've already checked.
+ */
+struct sha256_extra_mutable;
+struct sha256_extra {
+ /* Function to check availability. Might be expensive, so we don't
+ * want to call it more than once. */
+ bool (*check_available)(void);
+
+ /* Point to a writable substructure. */
+ struct sha256_extra_mutable *mut;
+};
+struct sha256_extra_mutable {
+ bool checked_availability;
+ bool is_available;
+};
+static inline bool check_availability(const struct sha256_extra *extra)
+{
+ if (!extra->mut->checked_availability) {
+ extra->mut->is_available = extra->check_available();
+ extra->mut->checked_availability = true;
+ }
+
+ return extra->mut->is_available;
+}
+
+/*
+ * Macro to define a SHA-256 vtable together with its 'extra'
+ * structure.
+ */
+#define SHA256_VTABLE(impl_c, impl_display) \
+ static struct sha256_extra_mutable sha256_ ## impl_c ## _extra_mut; \
+ static const struct sha256_extra sha256_ ## impl_c ## _extra = { \
+ .check_available = sha256_ ## impl_c ## _available, \
+ .mut = &sha256_ ## impl_c ## _extra_mut, \
+ }; \
+ const ssh_hashalg ssh_sha256_ ## impl_c = { \
+ .new = sha256_ ## impl_c ## _new, \
+ .reset = sha256_ ## impl_c ## _reset, \
+ .copyfrom = sha256_ ## impl_c ## _copyfrom, \
+ .digest = sha256_ ## impl_c ## _digest, \
+ .free = sha256_ ## impl_c ## _free, \
+ .hlen = 32, \
+ .blocklen = 64, \
+ HASHALG_NAMES_ANNOTATED("SHA-256", impl_display), \
+ .extra = &sha256_ ## impl_c ## _extra, \
+ }
+
+extern const uint32_t sha256_initial_state[8];
+extern const uint32_t sha256_round_constants[64];
+
+#define SHA256_ROUNDS 64
+
+typedef struct sha256_block sha256_block;
+struct sha256_block {
+ uint8_t block[64];
+ size_t used;
+ uint64_t len;
+};
+
+static inline void sha256_block_setup(sha256_block *blk)
+{
+ blk->used = 0;
+ blk->len = 0;
+}
+
+static inline bool sha256_block_write(
+ sha256_block *blk, const void **vdata, size_t *len)
+{
+ size_t blkleft = sizeof(blk->block) - blk->used;
+ size_t chunk = *len < blkleft ? *len : blkleft;
+
+ const uint8_t *p = *vdata;
+ memcpy(blk->block + blk->used, p, chunk);
+ *vdata = p + chunk;
+ *len -= chunk;
+ blk->used += chunk;
+ blk->len += chunk;
+
+ if (blk->used == sizeof(blk->block)) {
+ blk->used = 0;
+ return true;
+ }
+
+ return false;
+}
+
+static inline void sha256_block_pad(sha256_block *blk, BinarySink *bs)
+{
+ uint64_t final_len = blk->len << 3;
+ size_t pad = 1 + (63 & (55 - blk->used));
+
+ put_byte(bs, 0x80);
+ for (size_t i = 1; i < pad; i++)
+ put_byte(bs, 0);
+ put_uint64(bs, final_len);
+
+ assert(blk->used == 0 && "Should have exactly hit a block boundary");
+}
diff --git a/sshsha3.c b/crypto/sha3.c
index 83d136bf..83d136bf 100644
--- a/sshsha3.c
+++ b/crypto/sha3.c
diff --git a/crypto/sha512-common.c b/crypto/sha512-common.c
new file mode 100644
index 00000000..89ac136c
--- /dev/null
+++ b/crypto/sha512-common.c
@@ -0,0 +1,71 @@
+/*
+ * Common variable definitions across all the SHA-512 implementations.
+ */
+
+#include "ssh.h"
+#include "sha512.h"
+
+const uint64_t sha512_initial_state[8] = {
+ 0x6a09e667f3bcc908ULL,
+ 0xbb67ae8584caa73bULL,
+ 0x3c6ef372fe94f82bULL,
+ 0xa54ff53a5f1d36f1ULL,
+ 0x510e527fade682d1ULL,
+ 0x9b05688c2b3e6c1fULL,
+ 0x1f83d9abfb41bd6bULL,
+ 0x5be0cd19137e2179ULL,
+};
+
+const uint64_t sha384_initial_state[8] = {
+ 0xcbbb9d5dc1059ed8ULL,
+ 0x629a292a367cd507ULL,
+ 0x9159015a3070dd17ULL,
+ 0x152fecd8f70e5939ULL,
+ 0x67332667ffc00b31ULL,
+ 0x8eb44a8768581511ULL,
+ 0xdb0c2e0d64f98fa7ULL,
+ 0x47b5481dbefa4fa4ULL,
+};
+
+const uint64_t sha512_round_constants[80] = {
+ 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL,
+ 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL,
+ 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL,
+ 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL,
+ 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL,
+ 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
+ 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL,
+ 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL,
+ 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL,
+ 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL,
+ 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL,
+ 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
+ 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL,
+ 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL,
+ 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL,
+ 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL,
+ 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL,
+ 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
+ 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL,
+ 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL,
+ 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL,
+ 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL,
+ 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL,
+ 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
+ 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL,
+ 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL,
+ 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL,
+ 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL,
+ 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL,
+ 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
+ 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL,
+ 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL,
+ 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL,
+ 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL,
+ 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL,
+ 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
+ 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL,
+ 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL,
+ 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL,
+ 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL,
+};
diff --git a/crypto/sha512-neon.c b/crypto/sha512-neon.c
new file mode 100644
index 00000000..849a79d7
--- /dev/null
+++ b/crypto/sha512-neon.c
@@ -0,0 +1,329 @@
+/*
+ * Hardware-accelerated implementation of SHA-512 using Arm NEON.
+ */
+
+#include "ssh.h"
+#include "sha512.h"
+
+#if USE_ARM64_NEON_H
+#include <arm64_neon.h>
+#else
+#include <arm_neon.h>
+#endif
+
+static bool sha512_neon_available(void)
+{
+ /*
+ * For Arm, we delegate to a per-platform detection function (see
+ * explanation in aes-neon.c).
+ */
+ return platform_sha512_neon_available();
+}
+
+#if !HAVE_NEON_SHA512_INTRINSICS
+/*
+ * clang 12 and before do not provide the SHA-512 NEON intrinsics, but
+ * do provide assembler support for the underlying instructions. So I
+ * define the intrinsic functions myself, using inline assembler.
+ */
+static inline uint64x2_t vsha512su0q_u64(uint64x2_t x, uint64x2_t y)
+{
+ __asm__("sha512su0 %0.2D,%1.2D" : "+w" (x) : "w" (y));
+ return x;
+}
+static inline uint64x2_t vsha512su1q_u64(uint64x2_t x, uint64x2_t y,
+ uint64x2_t z)
+{
+ __asm__("sha512su1 %0.2D,%1.2D,%2.2D" : "+w" (x) : "w" (y), "w" (z));
+ return x;
+}
+static inline uint64x2_t vsha512hq_u64(uint64x2_t x, uint64x2_t y,
+ uint64x2_t z)
+{
+ __asm__("sha512h %0,%1,%2.2D" : "+w" (x) : "w" (y), "w" (z));
+ return x;
+}
+static inline uint64x2_t vsha512h2q_u64(uint64x2_t x, uint64x2_t y,
+ uint64x2_t z)
+{
+ __asm__("sha512h2 %0,%1,%2.2D" : "+w" (x) : "w" (y), "w" (z));
+ return x;
+}
+#endif /* HAVE_NEON_SHA512_INTRINSICS */
+
+typedef struct sha512_neon_core sha512_neon_core;
+struct sha512_neon_core {
+ uint64x2_t ab, cd, ef, gh;
+};
+
+static inline uint64x2_t sha512_neon_load_input(const uint8_t *p)
+{
+ return vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p)));
+}
+
+static inline uint64x2_t sha512_neon_schedule_update(
+ uint64x2_t m8, uint64x2_t m7, uint64x2_t m4, uint64x2_t m3, uint64x2_t m1)
+{
+ /*
+ * vsha512su0q_u64() takes words from a long way back in the
+ * schedule and performs the sigma_0 half of the computation of
+ * the next two 64-bit message-schedule words.
+ *
+ * vsha512su1q_u64() combines the result of that with the sigma_1
+ * steps, to output the finished version of those two words. The
+ * total amount of input data it requires fits nicely into three
+ * 128-bit vector registers, but one of those registers is
+ * misaligned compared to the 128-bit chunks that the message
+ * schedule is stored in. So we use vextq_u64 to make one of its
+ * input words out of the second half of m4 and the first half of
+ * m3.
+ */
+ return vsha512su1q_u64(vsha512su0q_u64(m8, m7), m1, vextq_u64(m4, m3, 1));
+}
+
+static inline void sha512_neon_round2(
+ unsigned round_index, uint64x2_t schedule_words,
+ uint64x2_t *ab, uint64x2_t *cd, uint64x2_t *ef, uint64x2_t *gh)
+{
+ /*
+ * vsha512hq_u64 performs the Sigma_1 and Ch half of the
+ * computation of two rounds of SHA-512 (including feeding back
+ * one of the outputs from the first of those half-rounds into the
+ * second one).
+ *
+ * vsha512h2q_u64 combines the result of that with the Sigma_0 and
+ * Maj steps, and outputs one 128-bit vector that replaces the gh
+ * piece of the input hash state, and a second that updates cd by
+ * addition.
+ *
+ * Similarly to vsha512su1q_u64 above, some of the input registers
+ * expected by these instructions are misaligned by 64 bits
+ * relative to the chunks we've divided the hash state into, so we
+ * have to start by making 'de' and 'fg' words out of our input
+ * cd,ef,gh, using vextq_u64.
+ *
+ * Also, one of the inputs to vsha512hq_u64 is expected to contain
+ * the results of summing gh + two round constants + two words of
+ * message schedule, but the two words of the message schedule
+ * have to be the opposite way round in the vector register from
+ * the way that vsha512su1q_u64 output them. Hence, there's
+ * another vextq_u64 in here that swaps the two halves of the
+ * initial_sum vector register.
+ *
+ * (This also means that I don't have to prepare a specially
+ * reordered version of the sha512_round_constants[] array: as
+ * long as I'm unavoidably doing a swap at run time _anyway_, I
+ * can load from the normally ordered version of that array, and
+ * just take care to fold in that data _before_ the swap rather
+ * than after.)
+ */
+
+ /* Load two round constants, with the first one in the low half */
+ uint64x2_t round_constants = vld1q_u64(
+ sha512_round_constants + round_index);
+
+ /* Add schedule words to round constants */
+ uint64x2_t initial_sum = vaddq_u64(schedule_words, round_constants);
+
+ /* Swap that sum around so the word used in the first of the two
+ * rounds is in the _high_ half of the vector, matching where h
+ * lives in the gh vector */
+ uint64x2_t swapped_initial_sum = vextq_u64(initial_sum, initial_sum, 1);
+
+ /* Add gh to that, now that they're matching ways round */
+ uint64x2_t sum = vaddq_u64(swapped_initial_sum, *gh);
+
+ /* Make the misaligned de and fg words */
+ uint64x2_t de = vextq_u64(*cd, *ef, 1);
+ uint64x2_t fg = vextq_u64(*ef, *gh, 1);
+
+ /* Now we're ready to put all the pieces together. The output from
+ * vsha512h2q_u64 can be used directly as the new gh, and the
+ * output from vsha512hq_u64 is simultaneously the intermediate
+ * value passed to h2 and the thing you have to add on to cd. */
+ uint64x2_t intermed = vsha512hq_u64(sum, fg, de);
+ *gh = vsha512h2q_u64(intermed, *cd, *ab);
+ *cd = vaddq_u64(*cd, intermed);
+}
+
+static inline void sha512_neon_block(sha512_neon_core *core, const uint8_t *p)
+{
+ uint64x2_t s0, s1, s2, s3, s4, s5, s6, s7;
+
+ uint64x2_t ab = core->ab, cd = core->cd, ef = core->ef, gh = core->gh;
+
+ s0 = sha512_neon_load_input(p + 16*0);
+ sha512_neon_round2(0, s0, &ab, &cd, &ef, &gh);
+ s1 = sha512_neon_load_input(p + 16*1);
+ sha512_neon_round2(2, s1, &gh, &ab, &cd, &ef);
+ s2 = sha512_neon_load_input(p + 16*2);
+ sha512_neon_round2(4, s2, &ef, &gh, &ab, &cd);
+ s3 = sha512_neon_load_input(p + 16*3);
+ sha512_neon_round2(6, s3, &cd, &ef, &gh, &ab);
+ s4 = sha512_neon_load_input(p + 16*4);
+ sha512_neon_round2(8, s4, &ab, &cd, &ef, &gh);
+ s5 = sha512_neon_load_input(p + 16*5);
+ sha512_neon_round2(10, s5, &gh, &ab, &cd, &ef);
+ s6 = sha512_neon_load_input(p + 16*6);
+ sha512_neon_round2(12, s6, &ef, &gh, &ab, &cd);
+ s7 = sha512_neon_load_input(p + 16*7);
+ sha512_neon_round2(14, s7, &cd, &ef, &gh, &ab);
+ s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7);
+ sha512_neon_round2(16, s0, &ab, &cd, &ef, &gh);
+ s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0);
+ sha512_neon_round2(18, s1, &gh, &ab, &cd, &ef);
+ s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1);
+ sha512_neon_round2(20, s2, &ef, &gh, &ab, &cd);
+ s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2);
+ sha512_neon_round2(22, s3, &cd, &ef, &gh, &ab);
+ s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3);
+ sha512_neon_round2(24, s4, &ab, &cd, &ef, &gh);
+ s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4);
+ sha512_neon_round2(26, s5, &gh, &ab, &cd, &ef);
+ s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5);
+ sha512_neon_round2(28, s6, &ef, &gh, &ab, &cd);
+ s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6);
+ sha512_neon_round2(30, s7, &cd, &ef, &gh, &ab);
+ s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7);
+ sha512_neon_round2(32, s0, &ab, &cd, &ef, &gh);
+ s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0);
+ sha512_neon_round2(34, s1, &gh, &ab, &cd, &ef);
+ s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1);
+ sha512_neon_round2(36, s2, &ef, &gh, &ab, &cd);
+ s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2);
+ sha512_neon_round2(38, s3, &cd, &ef, &gh, &ab);
+ s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3);
+ sha512_neon_round2(40, s4, &ab, &cd, &ef, &gh);
+ s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4);
+ sha512_neon_round2(42, s5, &gh, &ab, &cd, &ef);
+ s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5);
+ sha512_neon_round2(44, s6, &ef, &gh, &ab, &cd);
+ s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6);
+ sha512_neon_round2(46, s7, &cd, &ef, &gh, &ab);
+ s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7);
+ sha512_neon_round2(48, s0, &ab, &cd, &ef, &gh);
+ s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0);
+ sha512_neon_round2(50, s1, &gh, &ab, &cd, &ef);
+ s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1);
+ sha512_neon_round2(52, s2, &ef, &gh, &ab, &cd);
+ s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2);
+ sha512_neon_round2(54, s3, &cd, &ef, &gh, &ab);
+ s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3);
+ sha512_neon_round2(56, s4, &ab, &cd, &ef, &gh);
+ s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4);
+ sha512_neon_round2(58, s5, &gh, &ab, &cd, &ef);
+ s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5);
+ sha512_neon_round2(60, s6, &ef, &gh, &ab, &cd);
+ s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6);
+ sha512_neon_round2(62, s7, &cd, &ef, &gh, &ab);
+ s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7);
+ sha512_neon_round2(64, s0, &ab, &cd, &ef, &gh);
+ s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0);
+ sha512_neon_round2(66, s1, &gh, &ab, &cd, &ef);
+ s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1);
+ sha512_neon_round2(68, s2, &ef, &gh, &ab, &cd);
+ s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2);
+ sha512_neon_round2(70, s3, &cd, &ef, &gh, &ab);
+ s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3);
+ sha512_neon_round2(72, s4, &ab, &cd, &ef, &gh);
+ s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4);
+ sha512_neon_round2(74, s5, &gh, &ab, &cd, &ef);
+ s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5);
+ sha512_neon_round2(76, s6, &ef, &gh, &ab, &cd);
+ s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6);
+ sha512_neon_round2(78, s7, &cd, &ef, &gh, &ab);
+
+ core->ab = vaddq_u64(core->ab, ab);
+ core->cd = vaddq_u64(core->cd, cd);
+ core->ef = vaddq_u64(core->ef, ef);
+ core->gh = vaddq_u64(core->gh, gh);
+}
+
+typedef struct sha512_neon {
+ sha512_neon_core core;
+ sha512_block blk;
+ BinarySink_IMPLEMENTATION;
+ ssh_hash hash;
+} sha512_neon;
+
+static void sha512_neon_write(BinarySink *bs, const void *vp, size_t len);
+
+static ssh_hash *sha512_neon_new(const ssh_hashalg *alg)
+{
+ const struct sha512_extra *extra = (const struct sha512_extra *)alg->extra;
+ if (!check_availability(extra))
+ return NULL;
+
+ sha512_neon *s = snew(sha512_neon);
+
+ s->hash.vt = alg;
+ BinarySink_INIT(s, sha512_neon_write);
+ BinarySink_DELEGATE_INIT(&s->hash, s);
+ return &s->hash;
+}
+
+static void sha512_neon_reset(ssh_hash *hash)
+{
+ sha512_neon *s = container_of(hash, sha512_neon, hash);
+ const struct sha512_extra *extra =
+ (const struct sha512_extra *)hash->vt->extra;
+
+ s->core.ab = vld1q_u64(extra->initial_state);
+ s->core.cd = vld1q_u64(extra->initial_state+2);
+ s->core.ef = vld1q_u64(extra->initial_state+4);
+ s->core.gh = vld1q_u64(extra->initial_state+6);
+
+ sha512_block_setup(&s->blk);
+}
+
+static void sha512_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+ sha512_neon *copy = container_of(hcopy, sha512_neon, hash);
+ sha512_neon *orig = container_of(horig, sha512_neon, hash);
+
+ *copy = *orig; /* structure copy */
+
+ BinarySink_COPIED(copy);
+ BinarySink_DELEGATE_INIT(&copy->hash, copy);
+}
+
+static void sha512_neon_free(ssh_hash *hash)
+{
+ sha512_neon *s = container_of(hash, sha512_neon, hash);
+ smemclr(s, sizeof(*s));
+ sfree(s);
+}
+
+static void sha512_neon_write(BinarySink *bs, const void *vp, size_t len)
+{
+ sha512_neon *s = BinarySink_DOWNCAST(bs, sha512_neon);
+
+ while (len > 0)
+ if (sha512_block_write(&s->blk, &vp, &len))
+ sha512_neon_block(&s->core, s->blk.block);
+}
+
+static void sha512_neon_digest(ssh_hash *hash, uint8_t *digest)
+{
+ sha512_neon *s = container_of(hash, sha512_neon, hash);
+
+ sha512_block_pad(&s->blk, BinarySink_UPCAST(s));
+
+ vst1q_u8(digest, vrev64q_u8(vreinterpretq_u8_u64(s->core.ab)));
+ vst1q_u8(digest+16, vrev64q_u8(vreinterpretq_u8_u64(s->core.cd)));
+ vst1q_u8(digest+32, vrev64q_u8(vreinterpretq_u8_u64(s->core.ef)));
+ vst1q_u8(digest+48, vrev64q_u8(vreinterpretq_u8_u64(s->core.gh)));
+}
+
+static void sha384_neon_digest(ssh_hash *hash, uint8_t *digest)
+{
+ sha512_neon *s = container_of(hash, sha512_neon, hash);
+
+ sha512_block_pad(&s->blk, BinarySink_UPCAST(s));
+
+ vst1q_u8(digest, vrev64q_u8(vreinterpretq_u8_u64(s->core.ab)));
+ vst1q_u8(digest+16, vrev64q_u8(vreinterpretq_u8_u64(s->core.cd)));
+ vst1q_u8(digest+32, vrev64q_u8(vreinterpretq_u8_u64(s->core.ef)));
+}
+
+SHA512_VTABLES(neon, "NEON accelerated");
diff --git a/crypto/sha512-select.c b/crypto/sha512-select.c
new file mode 100644
index 00000000..ecd567bd
--- /dev/null
+++ b/crypto/sha512-select.c
@@ -0,0 +1,61 @@
+/*
+ * Top-level vtables to select a SHA-512 implementation.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sha512.h"
+
+static const ssh_hashalg *const real_sha512_algs[] = {
+#if HAVE_NEON_SHA512
+ &ssh_sha512_neon,
+#endif
+ &ssh_sha512_sw,
+ NULL,
+};
+
+static const ssh_hashalg *const real_sha384_algs[] = {
+#if HAVE_NEON_SHA512
+ &ssh_sha384_neon,
+#endif
+ &ssh_sha384_sw,
+ NULL,
+};
+
+static ssh_hash *sha512_select(const ssh_hashalg *alg)
+{
+ const ssh_hashalg *const *real_algs =
+ (const ssh_hashalg *const *)alg->extra;
+
+ for (size_t i = 0; real_algs[i]; i++) {
+ const ssh_hashalg *alg = real_algs[i];
+ const struct sha512_extra *alg_extra =
+ (const struct sha512_extra *)alg->extra;
+ if (check_availability(alg_extra))
+ return ssh_hash_new(alg);
+ }
+
+ /* We should never reach the NULL at the end of the list, because
+ * the last non-NULL entry should be software-only SHA-512, which
+ * is always available. */
+ unreachable("sha512_select ran off the end of its list");
+}
+
+const ssh_hashalg ssh_sha512 = {
+ .new = sha512_select,
+ .hlen = 64,
+ .blocklen = 128,
+ HASHALG_NAMES_ANNOTATED("SHA-512", "dummy selector vtable"),
+ .extra = real_sha512_algs,
+};
+
+const ssh_hashalg ssh_sha384 = {
+ .new = sha512_select,
+ .hlen = 48,
+ .blocklen = 128,
+ HASHALG_NAMES_ANNOTATED("SHA-384", "dummy selector vtable"),
+ .extra = real_sha384_algs,
+};
diff --git a/crypto/sha512-sw.c b/crypto/sha512-sw.c
new file mode 100644
index 00000000..9e47bbb9
--- /dev/null
+++ b/crypto/sha512-sw.c
@@ -0,0 +1,168 @@
+/*
+ * Software implementation of SHA-512.
+ */
+
+#include "ssh.h"
+#include "sha512.h"
+
+static bool sha512_sw_available(void)
+{
+ /* Software SHA-512 is always available */
+ return true;
+}
+
+static inline uint64_t ror(uint64_t x, unsigned y)
+{
+ return (x << (63 & -y)) | (x >> (63 & y));
+}
+
+static inline uint64_t Ch(uint64_t ctrl, uint64_t if1, uint64_t if0)
+{
+ return if0 ^ (ctrl & (if1 ^ if0));
+}
+
+static inline uint64_t Maj(uint64_t x, uint64_t y, uint64_t z)
+{
+ return (x & y) | (z & (x | y));
+}
+
+static inline uint64_t Sigma_0(uint64_t x)
+{
+ return ror(x,28) ^ ror(x,34) ^ ror(x,39);
+}
+
+static inline uint64_t Sigma_1(uint64_t x)
+{
+ return ror(x,14) ^ ror(x,18) ^ ror(x,41);
+}
+
+static inline uint64_t sigma_0(uint64_t x)
+{
+ return ror(x,1) ^ ror(x,8) ^ (x >> 7);
+}
+
+static inline uint64_t sigma_1(uint64_t x)
+{
+ return ror(x,19) ^ ror(x,61) ^ (x >> 6);
+}
+
+static inline void sha512_sw_round(
+ unsigned round_index, const uint64_t *schedule,
+ uint64_t *a, uint64_t *b, uint64_t *c, uint64_t *d,
+ uint64_t *e, uint64_t *f, uint64_t *g, uint64_t *h)
+{
+ uint64_t t1 = *h + Sigma_1(*e) + Ch(*e,*f,*g) +
+ sha512_round_constants[round_index] + schedule[round_index];
+
+ uint64_t t2 = Sigma_0(*a) + Maj(*a,*b,*c);
+
+ *d += t1;
+ *h = t1 + t2;
+}
+
+static void sha512_sw_block(uint64_t *core, const uint8_t *block)
+{
+ uint64_t w[SHA512_ROUNDS];
+ uint64_t a,b,c,d,e,f,g,h;
+
+ int t;
+
+ for (t = 0; t < 16; t++)
+ w[t] = GET_64BIT_MSB_FIRST(block + 8*t);
+
+ for (t = 16; t < SHA512_ROUNDS; t++)
+ w[t] = w[t-16] + w[t-7] + sigma_0(w[t-15]) + sigma_1(w[t-2]);
+
+ a = core[0]; b = core[1]; c = core[2]; d = core[3];
+ e = core[4]; f = core[5]; g = core[6]; h = core[7];
+
+ for (t = 0; t < SHA512_ROUNDS; t+=8) {
+ sha512_sw_round(t+0, w, &a,&b,&c,&d,&e,&f,&g,&h);
+ sha512_sw_round(t+1, w, &h,&a,&b,&c,&d,&e,&f,&g);
+ sha512_sw_round(t+2, w, &g,&h,&a,&b,&c,&d,&e,&f);
+ sha512_sw_round(t+3, w, &f,&g,&h,&a,&b,&c,&d,&e);
+ sha512_sw_round(t+4, w, &e,&f,&g,&h,&a,&b,&c,&d);
+ sha512_sw_round(t+5, w, &d,&e,&f,&g,&h,&a,&b,&c);
+ sha512_sw_round(t+6, w, &c,&d,&e,&f,&g,&h,&a,&b);
+ sha512_sw_round(t+7, w, &b,&c,&d,&e,&f,&g,&h,&a);
+ }
+
+ core[0] += a; core[1] += b; core[2] += c; core[3] += d;
+ core[4] += e; core[5] += f; core[6] += g; core[7] += h;
+
+ smemclr(w, sizeof(w));
+}
+
+typedef struct sha512_sw {
+ uint64_t core[8];
+ sha512_block blk;
+ BinarySink_IMPLEMENTATION;
+ ssh_hash hash;
+} sha512_sw;
+
+static void sha512_sw_write(BinarySink *bs, const void *vp, size_t len);
+
+static ssh_hash *sha512_sw_new(const ssh_hashalg *alg)
+{
+ sha512_sw *s = snew(sha512_sw);
+
+ s->hash.vt = alg;
+ BinarySink_INIT(s, sha512_sw_write);
+ BinarySink_DELEGATE_INIT(&s->hash, s);
+ return &s->hash;
+}
+
+static void sha512_sw_reset(ssh_hash *hash)
+{
+ sha512_sw *s = container_of(hash, sha512_sw, hash);
+ const struct sha512_extra *extra =
+ (const struct sha512_extra *)hash->vt->extra;
+
+ memcpy(s->core, extra->initial_state, sizeof(s->core));
+ sha512_block_setup(&s->blk);
+}
+
+static void sha512_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig)
+{
+ sha512_sw *copy = container_of(hcopy, sha512_sw, hash);
+ sha512_sw *orig = container_of(horig, sha512_sw, hash);
+
+ memcpy(copy, orig, sizeof(*copy));
+ BinarySink_COPIED(copy);
+ BinarySink_DELEGATE_INIT(&copy->hash, copy);
+}
+
+static void sha512_sw_free(ssh_hash *hash)
+{
+ sha512_sw *s = container_of(hash, sha512_sw, hash);
+
+ smemclr(s, sizeof(*s));
+ sfree(s);
+}
+
+static void sha512_sw_write(BinarySink *bs, const void *vp, size_t len)
+{
+ sha512_sw *s = BinarySink_DOWNCAST(bs, sha512_sw);
+
+ while (len > 0)
+ if (sha512_block_write(&s->blk, &vp, &len))
+ sha512_sw_block(s->core, s->blk.block);
+}
+
+static void sha512_sw_digest(ssh_hash *hash, uint8_t *digest)
+{
+ sha512_sw *s = container_of(hash, sha512_sw, hash);
+
+ sha512_block_pad(&s->blk, BinarySink_UPCAST(s));
+ for (size_t i = 0; i < hash->vt->hlen / 8; i++)
+ PUT_64BIT_MSB_FIRST(digest + 8*i, s->core[i]);
+}
+
+/*
+ * This implementation doesn't need separate digest methods for
+ * SHA-384 and SHA-512, because the above implementation reads the
+ * hash length out of the vtable.
+ */
+#define sha384_sw_digest sha512_sw_digest
+
+SHA512_VTABLES(sw, "unaccelerated");
diff --git a/crypto/sha512.h b/crypto/sha512.h
new file mode 100644
index 00000000..98145558
--- /dev/null
+++ b/crypto/sha512.h
@@ -0,0 +1,131 @@
+/*
+ * Definitions likely to be helpful to multiple SHA-512 implementations.
+ */
+
+/*
+ * The 'extra' structure used by SHA-512 implementations is used to
+ * include information about how to check if a given implementation is
+ * available at run time, and whether we've already checked.
+ */
+struct sha512_extra_mutable;
+struct sha512_extra {
+ /* Pointer to the initial state (distinguishes SHA-384 from -512) */
+ const uint64_t *initial_state;
+
+ /* Function to check availability. Might be expensive, so we don't
+ * want to call it more than once. */
+ bool (*check_available)(void);
+
+ /* Point to a writable substructure. */
+ struct sha512_extra_mutable *mut;
+};
+struct sha512_extra_mutable {
+ bool checked_availability;
+ bool is_available;
+};
+static inline bool check_availability(const struct sha512_extra *extra)
+{
+ if (!extra->mut->checked_availability) {
+ extra->mut->is_available = extra->check_available();
+ extra->mut->checked_availability = true;
+ }
+
+ return extra->mut->is_available;
+}
+
+/*
+ * Macro to define a pair of SHA-{384,512} vtables together with their
+ * 'extra' structure.
+ */
+#define SHA512_VTABLES(impl_c, impl_display) \
+ static struct sha512_extra_mutable sha512_ ## impl_c ## _extra_mut; \
+ static const struct sha512_extra sha384_ ## impl_c ## _extra = { \
+ .initial_state = sha384_initial_state, \
+ .check_available = sha512_ ## impl_c ## _available, \
+ .mut = &sha512_ ## impl_c ## _extra_mut, \
+ }; \
+ static const struct sha512_extra sha512_ ## impl_c ## _extra = { \
+ .initial_state = sha512_initial_state, \
+ .check_available = sha512_ ## impl_c ## _available, \
+ .mut = &sha512_ ## impl_c ## _extra_mut, \
+ }; \
+ const ssh_hashalg ssh_sha384_ ## impl_c = { \
+ .new = sha512_ ## impl_c ## _new, \
+ .reset = sha512_ ## impl_c ## _reset, \
+ .copyfrom = sha512_ ## impl_c ## _copyfrom, \
+ .digest = sha384_ ## impl_c ## _digest, \
+ .free = sha512_ ## impl_c ## _free, \
+ .hlen = 48, \
+ .blocklen = 128, \
+ HASHALG_NAMES_ANNOTATED("SHA-384", impl_display), \
+ .extra = &sha384_ ## impl_c ## _extra, \
+ }; \
+ const ssh_hashalg ssh_sha512_ ## impl_c = { \
+ .new = sha512_ ## impl_c ## _new, \
+ .reset = sha512_ ## impl_c ## _reset, \
+ .copyfrom = sha512_ ## impl_c ## _copyfrom, \
+ .digest = sha512_ ## impl_c ## _digest, \
+ .free = sha512_ ## impl_c ## _free, \
+ .hlen = 64, \
+ .blocklen = 128, \
+ HASHALG_NAMES_ANNOTATED("SHA-512", impl_display), \
+ .extra = &sha512_ ## impl_c ## _extra, \
+ }
+
+extern const uint64_t sha512_initial_state[8];
+extern const uint64_t sha384_initial_state[8];
+extern const uint64_t sha512_round_constants[80];
+
+#define SHA512_ROUNDS 80
+
+typedef struct sha512_block sha512_block;
+struct sha512_block {
+ uint8_t block[128];
+ size_t used;
+ uint64_t lenhi, lenlo;
+};
+
+static inline void sha512_block_setup(sha512_block *blk)
+{
+ blk->used = 0;
+ blk->lenhi = blk->lenlo = 0;
+}
+
+static inline bool sha512_block_write(
+ sha512_block *blk, const void **vdata, size_t *len)
+{
+ size_t blkleft = sizeof(blk->block) - blk->used;
+ size_t chunk = *len < blkleft ? *len : blkleft;
+
+ const uint8_t *p = *vdata;
+ memcpy(blk->block + blk->used, p, chunk);
+ *vdata = p + chunk;
+ *len -= chunk;
+ blk->used += chunk;
+
+ size_t chunkbits = chunk << 3;
+
+ blk->lenlo += chunkbits;
+ blk->lenhi += (blk->lenlo < chunkbits);
+
+ if (blk->used == sizeof(blk->block)) {
+ blk->used = 0;
+ return true;
+ }
+
+ return false;
+}
+
+static inline void sha512_block_pad(sha512_block *blk, BinarySink *bs)
+{
+ uint64_t final_lenhi = blk->lenhi;
+ uint64_t final_lenlo = blk->lenlo;
+ size_t pad = 127 & (111 - blk->used);
+
+ put_byte(bs, 0x80);
+ put_padding(bs, pad, 0);
+ put_uint64(bs, final_lenhi);
+ put_uint64(bs, final_lenlo);
+
+ assert(blk->used == 0 && "Should have exactly hit a block boundary");
+}
diff --git a/crypto/xdmauth.c b/crypto/xdmauth.c
new file mode 100644
index 00000000..86339b85
--- /dev/null
+++ b/crypto/xdmauth.c
@@ -0,0 +1,53 @@
+/*
+ * Convenience functions to encrypt and decrypt the cookies used in
+ * XDM-AUTHORIZATION-1.
+ */
+
+#include "ssh.h"
+
+static ssh_cipher *des_xdmauth_cipher(const void *vkeydata)
+{
+ /*
+ * XDM-AUTHORIZATION-1 uses single-DES, but packs the key into 7
+ * bytes, so here we have to repack it manually into the canonical
+ * form where it occupies 8 bytes each with the low bit unused.
+ */
+ const unsigned char *keydata = (const unsigned char *)vkeydata;
+ unsigned char key[8];
+ int i, nbits, j;
+ unsigned int bits;
+
+ bits = 0;
+ nbits = 0;
+ j = 0;
+ for (i = 0; i < 8; i++) {
+ if (nbits < 7) {
+ bits = (bits << 8) | keydata[j];
+ nbits += 8;
+ j++;
+ }
+ key[i] = (bits >> (nbits - 7)) << 1;
+ bits &= ~(0x7F << (nbits - 7));
+ nbits -= 7;
+ }
+
+ ssh_cipher *c = ssh_cipher_new(&ssh_des);
+ ssh_cipher_setkey(c, key);
+ smemclr(key, sizeof(key));
+ ssh_cipher_setiv(c, key);
+ return c;
+}
+
+void des_encrypt_xdmauth(const void *keydata, void *blk, int len)
+{
+ ssh_cipher *c = des_xdmauth_cipher(keydata);
+ ssh_cipher_encrypt(c, blk, len);
+ ssh_cipher_free(c);
+}
+
+void des_decrypt_xdmauth(const void *keydata, void *blk, int len)
+{
+ ssh_cipher *c = des_xdmauth_cipher(keydata);
+ ssh_cipher_decrypt(c, blk, len);
+ ssh_cipher_free(c);
+}
diff --git a/defs.h b/defs.h
index 3ba43c4f..286e0c96 100644
--- a/defs.h
+++ b/defs.h
@@ -11,6 +11,21 @@
#ifndef PUTTY_DEFS_H
#define PUTTY_DEFS_H
+#ifdef NDEBUG
+/*
+ * PuTTY is a security project, so assertions are important - if an
+ * assumption is violated, proceeding anyway may have far worse
+ * consequences than simple program termination. This check and #error
+ * should arrange that we don't ever accidentally compile assertions
+ * out.
+ */
+#error Do not compile this code base with NDEBUG defined!
+#endif
+
+#if HAVE_CMAKE_H
+#include "cmake.h"
+#endif
+
#include <stddef.h>
#include <stdint.h>
#include <stdio.h> /* for __MINGW_PRINTF_FORMAT */
@@ -27,6 +42,8 @@
#define SIZEx "Ix"
#define SIZEu "Iu"
uintmax_t strtoumax(const char *nptr, char **endptr, int base);
+/* Also, define a LEGACY_WINDOWS flag to enable other workarounds */
+#define LEGACY_WINDOWS
#else
#include <inttypes.h>
/* Because we still support older MSVC libraries which don't recognise the
@@ -83,9 +100,14 @@ typedef struct SockAddr SockAddr;
typedef struct Socket Socket;
typedef struct Plug Plug;
typedef struct SocketPeerInfo SocketPeerInfo;
+typedef struct DeferredSocketOpener DeferredSocketOpener;
+typedef struct DeferredSocketOpenerVtable DeferredSocketOpenerVtable;
typedef struct Backend Backend;
typedef struct BackendVtable BackendVtable;
+typedef struct Interactor Interactor;
+typedef struct InteractorVtable InteractorVtable;
+typedef struct InteractionReadySeat InteractionReadySeat;
typedef struct Ldisc_tag Ldisc;
typedef struct LogContext LogContext;
@@ -94,6 +116,12 @@ typedef struct LogPolicyVtable LogPolicyVtable;
typedef struct Seat Seat;
typedef struct SeatVtable SeatVtable;
+typedef struct SeatDialogText SeatDialogText;
+typedef struct SeatDialogTextItem SeatDialogTextItem;
+typedef struct SeatDialogPromptDescriptions SeatDialogPromptDescriptions;
+typedef struct SeatPromptResult SeatPromptResult;
+
+typedef struct cmdline_get_passwd_input_state cmdline_get_passwd_input_state;
typedef struct TermWin TermWin;
typedef struct TermWinVtable TermWinVtable;
@@ -118,6 +146,8 @@ typedef struct Channel Channel;
typedef struct SshChannel SshChannel;
typedef struct mainchan mainchan;
+typedef struct CertExprBuilder CertExprBuilder;
+
typedef struct ssh_sharing_state ssh_sharing_state;
typedef struct ssh_sharing_connstate ssh_sharing_connstate;
typedef struct share_channel share_channel;
@@ -144,17 +174,26 @@ typedef struct ssh_cipher ssh_cipher;
typedef struct ssh2_ciphers ssh2_ciphers;
typedef struct dh_ctx dh_ctx;
typedef struct ecdh_key ecdh_key;
+typedef struct ecdh_keyalg ecdh_keyalg;
+typedef struct NTRUKeyPair NTRUKeyPair;
+typedef struct NTRUEncodeSchedule NTRUEncodeSchedule;
typedef struct dlgparam dlgparam;
+typedef struct dlgcontrol dlgcontrol;
typedef struct settings_w settings_w;
typedef struct settings_r settings_r;
typedef struct settings_e settings_e;
+typedef struct ca_options ca_options;
+typedef struct host_ca host_ca;
+typedef struct host_ca_enum host_ca_enum;
typedef struct SessionSpecial SessionSpecial;
typedef struct StripCtrlChars StripCtrlChars;
+typedef struct BidiContext BidiContext;
+
/*
* A small structure wrapping up a (pointer, length) pair so that it
* can be conveniently passed to or from a function.
@@ -169,6 +208,8 @@ typedef struct logblank_t logblank_t;
typedef struct BinaryPacketProtocol BinaryPacketProtocol;
typedef struct PacketProtocolLayer PacketProtocolLayer;
+struct unicode_data;
+
/* Do a compile-time type-check of 'to_check' (without evaluating it),
* as a side effect of returning the value 'to_return'. Note that
* although this macro double-*expands* to_return, it always
@@ -190,32 +231,38 @@ typedef struct PacketProtocolLayer PacketProtocolLayer;
#define NORETURN
#endif
-/* ----------------------------------------------------------------------
- * Platform-specific definitions.
+/*
+ * Standard macro definitions. STR() behaves like the preprocessor
+ * stringification # operator, and CAT() behaves like the token paste
+ * ## operator, except that each one macro-expands its argument(s)
+ * first, unlike the raw version. E.g.
+ *
+ * #__LINE__ -> "__LINE__"
+ * STR(__LINE__) -> "1234" (or whatever)
+ *
+ * and similarly,
+ *
+ * foo ## __LINE__ -> foo__LINE__
+ * CAT(foo, __LINE__) -> foo1234 (or whatever)
*
- * 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.
+ * The expansion is achieved by having each macro pass its arguments
+ * to a secondary inner macro, because parameter lists of a macro call
+ * get expanded before the called macro is invoked. So STR(__LINE__)
+ * -> STR_INNER(1234) -> #1234 -> "1234", and similarly for CAT.
*/
+#define STR_INNER(x) #x
+#define STR(x) STR_INNER(x)
+#define CAT_INNER(x,y) x ## y
+#define CAT(x,y) CAT_INNER(x,y)
-/* 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
+/*
+ * Structure shared between ssh.h and storage.h, giving strictness
+ * options relating to checking of an OpenSSH certificate. It's a bit
+ * cheaty to put something so specific in here, but more painful to
+ * put it in putty.h.
+ */
+struct ca_options {
+ bool permit_rsa_sha1, permit_rsa_sha256, permit_rsa_sha512;
+};
#endif /* PUTTY_DEFS_H */
diff --git a/dialog.c b/dialog.c
index 7409daaa..b9306982 100644
--- a/dialog.c
+++ b/dialog.c
@@ -204,31 +204,31 @@ 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)
+static dlgcontrol *ctrl_new(struct controlset *s, int type,
+ HelpCtx helpctx, handler_fn handler,
+ intorptr context)
{
- union control *c = snew(union control);
+ dlgcontrol *c = snew(dlgcontrol);
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;
+ c->type = type;
+ c->delay_taborder = false;
+ c->column = COLUMN_FIELD(0, s->ncolumns);
+ c->helpctx = helpctx;
+ c->handler = handler;
+ c->context = context;
+ c->label = NULL;
+ c->align_next_to = NULL;
return c;
}
/* `ncolumns' is followed by that many percentages, as integers. */
-union control *ctrl_columns(struct controlset *s, int ncolumns, ...)
+dlgcontrol *ctrl_columns(struct controlset *s, int ncolumns, ...)
{
- union control *c = ctrl_new(s, CTRL_COLUMNS, P(NULL), NULL, P(NULL));
+ dlgcontrol *c = ctrl_new(s, CTRL_COLUMNS, NULL_HELPCTX, NULL, P(NULL));
assert(s->ncolumns == 1 || ncolumns == 1);
c->columns.ncols = ncolumns;
s->ncolumns = ncolumns;
@@ -246,33 +246,33 @@ union control *ctrl_columns(struct controlset *s, int ncolumns, ...)
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)
+dlgcontrol *ctrl_editbox(struct controlset *s, const char *label,
+ char shortcut, int percentage,
+ HelpCtx 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;
+ dlgcontrol *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->editbox.shortcut = shortcut;
c->editbox.percentwidth = percentage;
c->editbox.password = false;
c->editbox.has_list = false;
- c->editbox.context2 = context2;
+ c->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)
+dlgcontrol *ctrl_combobox(struct controlset *s, const char *label,
+ char shortcut, int percentage,
+ HelpCtx 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;
+ dlgcontrol *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->editbox.shortcut = shortcut;
c->editbox.percentwidth = percentage;
c->editbox.password = false;
c->editbox.has_list = true;
- c->editbox.context2 = context2;
+ c->context2 = context2;
return c;
}
@@ -282,14 +282,14 @@ union control *ctrl_combobox(struct controlset *s, const char *label,
* 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,
+dlgcontrol *ctrl_radiobuttons_fn(struct controlset *s, const char *label,
+ char shortcut, int ncolumns, HelpCtx helpctx,
handler_fn handler, intorptr context, ...)
{
va_list ap;
int i;
- union control *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context);
- c->radio.label = label ? dupstr(label) : NULL;
+ dlgcontrol *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->radio.shortcut = shortcut;
c->radio.ncolumns = ncolumns;
/*
@@ -328,24 +328,24 @@ union control *ctrl_radiobuttons(struct controlset *s, const char *label,
return c;
}
-union control *ctrl_pushbutton(struct controlset *s, const char *label,
- char shortcut, intorptr helpctx,
- handler_fn handler, intorptr context)
+dlgcontrol *ctrl_pushbutton(struct controlset *s, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context)
{
- union control *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context);
- c->button.label = label ? dupstr(label) : NULL;
+ dlgcontrol *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->button.shortcut = shortcut;
c->button.isdefault = false;
c->button.iscancel = false;
return c;
}
-union control *ctrl_listbox(struct controlset *s, const char *label,
- char shortcut, intorptr helpctx,
- handler_fn handler, intorptr context)
+dlgcontrol *ctrl_listbox(struct controlset *s, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context)
{
- union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
- c->listbox.label = label ? dupstr(label) : NULL;
+ dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->listbox.shortcut = shortcut;
c->listbox.height = 5; /* *shrug* a plausible default */
c->listbox.draglist = false;
@@ -357,12 +357,12 @@ union control *ctrl_listbox(struct controlset *s, const char *label,
return c;
}
-union control *ctrl_droplist(struct controlset *s, const char *label,
- char shortcut, int percentage, intorptr helpctx,
- handler_fn handler, intorptr context)
+dlgcontrol *ctrl_droplist(struct controlset *s, const char *label,
+ char shortcut, int percentage, HelpCtx helpctx,
+ handler_fn handler, intorptr context)
{
- union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
- c->listbox.label = label ? dupstr(label) : NULL;
+ dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->listbox.shortcut = shortcut;
c->listbox.height = 0; /* means it's a drop-down list */
c->listbox.draglist = false;
@@ -374,12 +374,12 @@ union control *ctrl_droplist(struct controlset *s, const char *label,
return c;
}
-union control *ctrl_draglist(struct controlset *s, const char *label,
- char shortcut, intorptr helpctx,
- handler_fn handler, intorptr context)
+dlgcontrol *ctrl_draglist(struct controlset *s, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context)
{
- union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
- c->listbox.label = label ? dupstr(label) : NULL;
+ dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->listbox.shortcut = shortcut;
c->listbox.height = 5; /* *shrug* a plausible default */
c->listbox.draglist = true;
@@ -391,61 +391,63 @@ union control *ctrl_draglist(struct controlset *s, const char *label,
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)
+dlgcontrol *ctrl_filesel(struct controlset *s, const char *label,
+ char shortcut, const char *filter, bool write,
+ const char *title, HelpCtx helpctx,
+ handler_fn handler, intorptr context)
{
- union control *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context);
- c->fileselect.label = label ? dupstr(label) : NULL;
+ dlgcontrol *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->fileselect.shortcut = shortcut;
c->fileselect.filter = filter;
c->fileselect.for_writing = write;
c->fileselect.title = dupstr(title);
+ c->fileselect.just_button = false;
return c;
}
-union control *ctrl_fontsel(struct controlset *s, const char *label,
- char shortcut, intorptr helpctx,
- handler_fn handler, intorptr context)
+dlgcontrol *ctrl_fontsel(struct controlset *s, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context)
{
- union control *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context);
- c->fontselect.label = label ? dupstr(label) : NULL;
+ dlgcontrol *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->fontselect.shortcut = shortcut;
return c;
}
-union control *ctrl_tabdelay(struct controlset *s, union control *ctrl)
+dlgcontrol *ctrl_tabdelay(struct controlset *s, dlgcontrol *ctrl)
{
- union control *c = ctrl_new(s, CTRL_TABDELAY, P(NULL), NULL, P(NULL));
+ dlgcontrol *c = ctrl_new(s, CTRL_TABDELAY, NULL_HELPCTX, NULL, P(NULL));
c->tabdelay.ctrl = ctrl;
return c;
}
-union control *ctrl_text(struct controlset *s, const char *text,
- intorptr helpctx)
+dlgcontrol *ctrl_text(struct controlset *s, const char *text,
+ HelpCtx helpctx)
{
- union control *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL));
- c->text.label = dupstr(text);
+ dlgcontrol *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL));
+ c->label = dupstr(text);
+ c->text.wrap = true;
return c;
}
-union control *ctrl_checkbox(struct controlset *s, const char *label,
- char shortcut, intorptr helpctx,
- handler_fn handler, intorptr context)
+dlgcontrol *ctrl_checkbox(struct controlset *s, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context)
{
- union control *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context);
- c->checkbox.label = label ? dupstr(label) : NULL;
+ dlgcontrol *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context);
+ c->label = label ? dupstr(label) : NULL;
c->checkbox.shortcut = shortcut;
return c;
}
-void ctrl_free(union control *ctrl)
+void ctrl_free(dlgcontrol *ctrl)
{
int i;
- sfree(ctrl->generic.label);
- switch (ctrl->generic.type) {
+ sfree(ctrl->label);
+ switch (ctrl->type) {
case CTRL_RADIO:
for (i = 0; i < ctrl->radio.nbuttons; i++)
sfree(ctrl->radio.buttons[i]);
diff --git a/dialog.h b/dialog.h
index 86ebfc20..ef9a9dfe 100644
--- a/dialog.h
+++ b/dialog.h
@@ -43,11 +43,12 @@ enum {
* included with DEFINE_INTORPTR_FNS defined. This is a total pain,
* but such is life.
*/
-typedef union { void *p; int i; } intorptr;
+typedef union { void *p; const void *cp; int i; } intorptr;
#ifndef INLINE
intorptr I(int i);
intorptr P(void *p);
+intorptr CP(const void *p);
#endif
#if defined DEFINE_INTORPTR_FNS || defined INLINE
@@ -58,6 +59,7 @@ intorptr P(void *p);
#endif
PREFIX intorptr I(int i) { intorptr ret; ret.i = i; return ret; }
PREFIX intorptr P(void *p) { intorptr ret; ret.p = p; return ret; }
+PREFIX intorptr CP(const void *p) { intorptr ret; ret.cp = p; return ret; }
#undef PREFIX
#endif
@@ -73,8 +75,6 @@ PREFIX intorptr P(void *p) { intorptr ret; ret.p = p; return ret; }
#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
@@ -103,326 +103,346 @@ enum {
EVENT_SELCHANGE,
EVENT_CALLBACK
};
-typedef void (*handler_fn)(union control *ctrl, dlgparam *dp,
+typedef void (*handler_fn)(dlgcontrol *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
+struct dlgcontrol {
+ /*
+ * Generic fields shared by all the control types.
+ */
+ int type;
+ /*
+ * Every control except CTRL_COLUMNS has _some_ sort of label. By
+ * putting it in the `generic' union as well as everywhere else,
+ * we avoid having to have an irritating switch statement when we
+ * go through and deallocate all the memory in a config-box
+ * structure.
+ *
+ * Yes, this does mean that any non-NULL value in this field is
+ * expected to be dynamically allocated and freeable.
+ *
+ * For CTRL_COLUMNS, this field MUST be NULL.
+ */
+ char *label;
+ /*
+ * If `delay_taborder' is true, it indicates that this particular
+ * control should not yet appear in the tab order. A subsequent
+ * CTRL_TABDELAY entry will place it.
+ */
+ bool delay_taborder;
+ /*
+ * Indicate which column(s) this control occupies. This can be
+ * unpacked into starting column and column span by the COLUMN
+ * macros above.
+ */
+ int column;
+ /*
+ * Most controls need to provide a function which gets called when
+ * that control's setting is changed, or when the control's
+ * setting needs initialising.
+ *
+ * The `data' parameter points to the writable data being modified
+ * as a result of the configuration activity; for example, the
+ * PuTTY `Conf' structure, although not necessarily.
+ *
+ * The `dlg' parameter is passed back to the platform- specific
+ * routines to read and write the actual control state.
+ */
+ handler_fn handler;
+ /*
+ * Almost all of the above functions will find it useful to be
+ * able to store one or two pieces of `void *' or `int' data.
+ */
+ intorptr context, context2;
+ /*
+ * For any control, we also allow the storage of a piece of data
+ * for use by context-sensitive help. For example, on Windows you
+ * can click the magic question mark and then click a control, and
+ * help for that control should spring up. Hence, here is a slot
+ * in which to store per-control data that a particular
+ * platform-specific driver can use to ensure it brings up the
+ * right piece of help text.
+ */
+ HelpCtx helpctx;
+ /*
+ * Setting this to non-NULL coerces two or more controls to have
+ * their y-coordinates adjusted so that they can sit alongside
+ * each other and look nicely aligned, even if they're different
+ * heights.
+ *
+ * Set this field on later controls (in terms of order in the data
+ * structure), pointing back to earlier ones, so that when each
+ * control is instantiated, the referred-to one is already there
+ * to be referred to.
+ *
+ * Don't expect this to change the position of the _first_
+ * control. Currently, the layout is done one control at a time,
+ * so that once the first control has been placed, the second one
+ * can't cause the first one to be retrospectively moved.
+ */
+ dlgcontrol *align_next_to;
-union 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.
+ * Union of further fields specific to each control type.
*/
- 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;
+ union {
+ struct { /* for CTRL_TABDELAY */
+ dlgcontrol *ctrl;
+ } tabdelay;
+ struct { /* for CTRL_EDITBOX */
+ char shortcut; /* keyboard shortcut */
+ /*
+ * Percentage of the dialog-box width used by the edit
+ * box. If this is set to 100, the label is on its own
+ * line; otherwise the label is on the same line as the
+ * box itself.
+ */
+ int percentwidth;
+ bool password; /* details of input are hidden */
+ /*
+ * A special case of the edit box is the combo box, which
+ * has a drop-down list built in. (Note that a _non_-
+ * editable drop-down list is done as a special case of a
+ * list box.)
+ *
+ * Don't try setting has_list and password on the same
+ * control; front ends are not required to support that
+ * combination.
+ */
+ bool has_list;
+ } editbox;
+ struct { /* for CTRL_RADIO */
+ /*
+ * `shortcut' here is a single keyboard shortcut which is
+ * expected to select the whole group of radio buttons. It
+ * can be NO_SHORTCUT if required, and there is also a way
+ * to place individual shortcuts on each button; see
+ * below.
+ */
+ char shortcut;
+ /*
+ * There are separate fields for `ncolumns' and `nbuttons'
+ * for several reasons.
+ *
+ * Firstly, we sometimes want the last of a set of buttons
+ * to have a longer label than the rest; we achieve this
+ * by setting `ncolumns' higher than `nbuttons', and the
+ * layout code is expected to understand that the final
+ * button should be given all the remaining space on the
+ * line. This sounds like a ludicrously specific special
+ * case (if we're doing this sort of thing, why not have
+ * the general ability to have a particular button span
+ * more than one column whether it's the last one or not?)
+ * but actually it's reasonably common for the sort of
+ * three-way control you get a lot of in PuTTY: `yes'
+ * versus `no' versus `some more complex way to decide'.
+ *
+ * Secondly, setting `nbuttons' higher than `ncolumns'
+ * lets us have more than one line of radio buttons for a
+ * single setting. A very important special case of this
+ * is setting `ncolumns' to 1, so that each button is on
+ * its own line.
+ */
+ int ncolumns;
+ int nbuttons;
+ /*
+ * This points to a dynamically allocated array of `char *'
+ * pointers, each of which points to a dynamically
+ * allocated string.
+ */
+ char **buttons; /* `nbuttons' button labels */
+ /*
+ * This points to a dynamically allocated array of `char'
+ * giving the individual keyboard shortcuts for each radio
+ * button. The array may be NULL if none are required.
+ */
+ char *shortcuts; /* `nbuttons' shortcuts; may be NULL */
+ /*
+ * This points to a dynamically allocated array of
+ * intorptr, giving helpful data for each button.
+ */
+ intorptr *buttondata; /* `nbuttons' entries; may be NULL */
+ } radio;
+ struct { /* for CTRL_CHECKBOX */
+ char shortcut;
+ } checkbox;
+ struct { /* for CTRL_BUTTON */
+ char shortcut;
+ /*
+ * At least Windows has the concept of a `default push
+ * button', which gets implicitly pressed when you hit
+ * Return even if it doesn't have the input focus.
+ */
+ bool isdefault;
+ /*
+ * Also, the reverse of this: a default cancel-type
+ * button, which is implicitly pressed when you hit
+ * Escape.
+ */
+ bool iscancel;
+ } button;
+ struct { /* for CTRL_LISTBOX */
+ char shortcut; /* keyboard shortcut */
+ /*
+ * Height of the list box, in approximate number of lines.
+ * If this is zero, the list is a drop-down list.
+ */
+ int height; /* height in lines */
+ /*
+ * If this is set, the list elements can be reordered by
+ * the user (by drag-and-drop or by Up and Down buttons,
+ * whatever the per-platform implementation feels
+ * comfortable with). This is not guaranteed to work on a
+ * drop-down list, so don't try it!
+ */
+ bool draglist;
+ /*
+ * If this is non-zero, the list can have more than one
+ * element selected at a time. This is not guaranteed to
+ * work on a drop-down list, so don't try it!
+ *
+ * Different non-zero values request slightly different
+ * types of multi-selection (this may well be meaningful
+ * only in GTK, so everyone else can ignore it if they
+ * want). 1 means the list box expects to have individual
+ * items selected, whereas 2 means it expects the user to
+ * want to select a large contiguous range at a time.
+ */
+ int multisel;
+ /*
+ * Percentage of the dialog-box width used by the list
+ * box. If this is set to 100, the label is on its own
+ * line; otherwise the label is on the same line as the
+ * box itself. Setting this to anything other than 100 is
+ * not guaranteed to work on a _non_-drop-down list, so
+ * don't try it!
+ */
+ int percentwidth;
+ /*
+ * Some list boxes contain strings that contain tab
+ * characters. If `ncols' is greater than 0, then
+ * `percentages' is expected to be non-zero and to contain
+ * the respective widths of `ncols' columns, which
+ * together will exactly fit the width of the list box.
+ * Otherwise `percentages' must be NULL.
+ *
+ * There should never be more than one column in a
+ * drop-down list (one with height==0), because front ends
+ * may have to implement it as a special case of an
+ * editable combo box.
+ */
+ int ncols; /* number of columns */
+ int *percentages; /* % width of each column */
+ /*
+ * Flag which can be set to false to suppress the
+ * horizontal scroll bar if a list box entry goes off the
+ * right-hand side.
+ */
+ bool hscroll;
+ } listbox;
+ struct { /* for CTRL_FILESELECT */
+ char shortcut;
+ /*
+ * `filter' dictates what type of files will be selected
+ * by default; for example, when selecting private key
+ * files the file selector would do well to only show .PPK
+ * files (on those systems where this is the chosen
+ * extension).
+ *
+ * The precise contents of `filter' are platform-defined,
+ * unfortunately. The special value NULL means `all files'
+ * and is always a valid fallback.
+ *
+ * Unlike almost all strings in this structure, this value
+ * is NOT expected to require freeing (although of course
+ * you can always use ctrl_alloc if you do need to create
+ * one on the fly). This is because the likely mode of use
+ * is to define string constants in a platform-specific
+ * header file, and directly reference those. Or worse, a
+ * particular platform might choose to cast integers into
+ * this pointer type...
+ */
+ char const *filter;
+ /*
+ * Some systems like to know whether a file selector is
+ * choosing a file to read or one to write (and possibly
+ * create).
+ */
+ bool for_writing;
+ /*
+ * On at least some platforms, the file selector is a
+ * separate dialog box, and contains a user-settable
+ * title.
+ *
+ * This value _is_ expected to require freeing.
+ */
+ char *title;
+ /*
+ * Reduce the file selector to just a single browse
+ * button.
+ *
+ * Normally, a file selector is used to set a config
+ * option that consists of a file name, so that that file
+ * will be read or written at run time. In that situation,
+ * it makes sense to have an edit box showing the
+ * currently selected file name, and a button to change it
+ * interactively.
+ *
+ * But occasionally a file selector is used to load a file
+ * _during_ configuration. For example, host CA public
+ * keys are entered directly into the configuration as
+ * strings, not stored by reference to a filename; but if
+ * you have one in a file, you want to be able to load it
+ * during the lifetime of the CA config box rather than
+ * awkwardly copy-pasting it. So in that case you just
+ * want a 'pop up a file chooser' button, and when that
+ * delivers a file name, you'll deal with it there and
+ * then and write some other thing (like the file's
+ * contents) into a nearby edit box.
+ *
+ * If you set this flag, then you may not call
+ * dlg_filesel_set on the file selector at all, because it
+ * doesn't store a filename. And you can only call
+ * dlg_filesel_get on it in the handler for EVENT_ACTION,
+ * which is what will be sent to you when the user has
+ * used it to choose a filename.
+ */
+ bool just_button;
+ } fileselect;
+ struct { /* for CTRL_COLUMNS */
+ /* In this variant, `label' MUST be NULL. */
+ int ncols; /* number of columns */
+ int *percentages; /* % width of each column */
+ /*
+ * Every time this control type appears, exactly one of
+ * `ncols' and the previous number of columns MUST be one.
+ * Attempting to allow a seamless transition from a four-
+ * to a five-column layout, for example, would be way more
+ * trouble than it was worth. If you must lay things out
+ * like that, define eight unevenly sized columns and use
+ * column-spanning a lot. But better still, just don't.
+ *
+ * `percentages' may be NULL if ncols==1, to save space.
+ */
+ } columns;
+ struct { /* for CTRL_FONTSELECT */
+ char shortcut;
+ } fontselect;
+ struct { /* for CTRL_TEXT */
+ /*
+ * If this is true (the default), the text will wrap on to
+ * multiple lines. If false, it will stay on the same
+ * line, with a horizontal scrollbar if necessary.
+ */
+ bool wrap;
+ } text;
+ };
};
#undef STANDARD_PREFIX
/*
- * `controlset' is a container holding an array of `union control'
+ * `controlset' is a container holding an array of `dlgcontrol'
* structures, together with a panel name and a title for the whole
* set. In Windows and any similar-looking GUI, each `controlset'
* in the config will be a container box within a panel.
@@ -435,9 +455,9 @@ struct controlset {
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 ncontrols; /* number of `dlgcontrol' in array */
size_t ctrlsize; /* allocated size of array */
- union control **ctrls; /* actual array */
+ dlgcontrol **ctrls; /* actual array */
};
typedef void (*ctrl_freefn_t)(void *); /* used by ctrl_alloc_with_free */
@@ -471,7 +491,7 @@ 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 *);
+void ctrl_free(dlgcontrol *);
/*
* This function works like `malloc', but the memory it returns
@@ -490,73 +510,77 @@ 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.
+ * Individual routines to create `dlgcontrol' structures in a controlset.
*
* Most of these routines allow the most common fields to be set
* directly, and put default values in the rest. Each one returns a
- * pointer to the `union control' it created, so that final tweaks
+ * pointer to the `dlgcontrol' 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);
+dlgcontrol *ctrl_columns(struct controlset *, int ncolumns, ...);
+dlgcontrol *ctrl_editbox(struct controlset *, const char *label,
+ char shortcut, int percentage, HelpCtx helpctx,
+ handler_fn handler,
+ intorptr context, intorptr context2);
+dlgcontrol *ctrl_combobox(struct controlset *, const char *label,
+ char shortcut, int percentage, HelpCtx helpctx,
+ handler_fn handler,
+ intorptr context, intorptr context2);
/*
* `ncolumns' is followed by (alternately) radio button titles and
* intorptrs, until a NULL in place of a title string is seen. Each
* title is expected to be followed by a shortcut _iff_ `shortcut'
* is NO_SHORTCUT.
*/
-union control *ctrl_radiobuttons(struct controlset *, const char *label,
- char shortcut, int ncolumns, intorptr helpctx,
+dlgcontrol *ctrl_radiobuttons_fn(struct controlset *, const char *label,
+ char shortcut, int ncolumns, HelpCtx 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,
+#define ctrl_radiobuttons(...) \
+ ctrl_radiobuttons_fn(__VA_ARGS__, (const char *)NULL)
+dlgcontrol *ctrl_pushbutton(struct controlset *, const char *label,
+ char shortcut, HelpCtx helpctx,
handler_fn handler, intorptr context);
-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 *);
+dlgcontrol *ctrl_listbox(struct controlset *, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context);
+dlgcontrol *ctrl_droplist(struct controlset *, const char *label,
+ char shortcut, int percentage, HelpCtx helpctx,
+ handler_fn handler, intorptr context);
+dlgcontrol *ctrl_draglist(struct controlset *, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context);
+dlgcontrol *ctrl_filesel(struct controlset *, const char *label,
+ char shortcut, const char *filter, bool write,
+ const char *title, HelpCtx helpctx,
+ handler_fn handler, intorptr context);
+dlgcontrol *ctrl_fontsel(struct controlset *, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context);
+dlgcontrol *ctrl_text(struct controlset *, const char *text,
+ HelpCtx helpctx);
+dlgcontrol *ctrl_checkbox(struct controlset *, const char *label,
+ char shortcut, HelpCtx helpctx,
+ handler_fn handler, intorptr context);
+dlgcontrol *ctrl_tabdelay(struct controlset *, dlgcontrol *);
/*
* Routines the platform-independent dialog code can call to read
* and write the values of controls.
*/
-void dlg_radiobutton_set(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 */
+void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int whichbutton);
+int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp);
+void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked);
+bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp);
+void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text);
+char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp); /* result must be freed by caller */
+void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp,
+ size_t start, size_t len);
/* The `listbox' functions can also apply to combo boxes. */
-void dlg_listbox_clear(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_clear(dlgcontrol *ctrl, dlgparam *dp);
+void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index);
+void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text);
/*
* Each listbox entry may have a numeric id associated with it.
* Note that some front ends only permit a string to be stored at
@@ -564,44 +588,44 @@ void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text);
* 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,
+void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp,
char const *text, int id);
-int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index);
+int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index);
/* dlg_listbox_index returns <0 if no single element is selected. */
-int dlg_listbox_index(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);
+int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp);
+bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index);
+void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index);
+void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text);
+void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn);
+Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp);
+void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fn);
+FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp);
/*
* Bracketing a large set of updates in these two functions will
* cause the front end (if possible) to delay updating the screen
* until it's all complete, thus avoiding flicker.
*/
-void dlg_update_start(union control *ctrl, dlgparam *dp);
-void dlg_update_done(union control *ctrl, dlgparam *dp);
+void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp);
+void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp);
/*
* Set input focus into a particular control.
*/
-void dlg_set_focus(union control *ctrl, dlgparam *dp);
+void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp);
/*
* Change the label text on a control.
*/
-void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text);
+void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text);
/*
* Return the `ctrl' structure for the most recent control that had
* the input focus apart from the one mentioned. This is NOT
* GUARANTEED to work on all platforms, so don't base any critical
* functionality on it!
*/
-union control *dlg_last_focused(union control *ctrl, dlgparam *dp);
+dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp);
/*
* Find out whether a particular control is currently visible.
*/
-bool dlg_is_visible(union control *ctrl, dlgparam *dp);
+bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp);
/*
* During event processing, you might well want to give an error
* indication to the user. dlg_beep() is a quick and easy generic
@@ -629,9 +653,9 @@ void dlg_end(dlgparam *dp, int value);
* 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,
+void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp,
int r, int g, int b);
-bool dlg_coloursel_results(union control *ctrl, dlgparam *dp,
+bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp,
int *r, int *g, int *b);
/*
@@ -643,7 +667,7 @@ bool dlg_coloursel_results(union control *ctrl, dlgparam *dp,
* 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);
+void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp);
/*
* Standard helper functions for reading a controlbox structure.
@@ -663,3 +687,9 @@ int ctrl_path_elements(const char *path);
/* Return the number of matching path elements at the starts of p1 and p2,
* or INT_MAX if the paths are identical. */
int ctrl_path_compare(const char *p1, const char *p2);
+
+/*
+ * Normalise the align_next_to fields in a controlset so that they
+ * form a backwards linked list.
+ */
+void ctrlset_normalise_aligns(struct controlset *s);
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
new file mode 100644
index 00000000..79b1ba1f
--- /dev/null
+++ b/doc/CMakeLists.txt
@@ -0,0 +1,182 @@
+cmake_minimum_required(VERSION 3.7)
+project(putty-documentation LANGUAGES)
+
+# This build script can be run standalone, or included as a
+# subdirectory of the main PuTTY cmake build system. If the latter, a
+# couple of things change: it has to set variables telling the rest of
+# the build system what manpages are available to be installed, and it
+# will change whether the 'make doc' target is included in 'make all'.
+
+include(FindGit)
+include(FindPerl)
+find_program(HALIBUT halibut)
+
+set(doc_outputs)
+set(manpage_outputs)
+
+if(HALIBUT AND PERL_EXECUTABLE)
+ # Build the main manual, which requires not only Halibut, but also
+ # Perl to run licence.pl to generate the copyright and licence
+ # sections from the master data outside this directory.
+
+ # If this is a source archive in which a fixed version.but was
+ # provided, use that. Otherwise, infer one from the git checkout (if
+ # possible).
+
+ set(manual_dependencies) # extra target names to depend on
+
+ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/version.but)
+ set(VERSION_BUT ${CMAKE_CURRENT_SOURCE_DIR}/version.but)
+ else()
+ set(VERSION_BUT ${CMAKE_CURRENT_BINARY_DIR}/cmake_version.but)
+ set(INTERMEDIATE_VERSION_BUT ${VERSION_BUT}.tmp)
+ add_custom_target(check_git_commit_for_doc
+ BYPRODUCTS ${INTERMEDIATE_VERSION_BUT}
+ COMMAND ${CMAKE_COMMAND}
+ -DGIT_EXECUTABLE=${GIT_EXECUTABLE}
+ -DOUTPUT_FILE=${INTERMEDIATE_VERSION_BUT}
+ -DOUTPUT_TYPE=halibut
+ -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/gitcommit.cmake
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/gitcommit.cmake
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..
+ COMMENT "Checking current git commit")
+ add_custom_target(cmake_version_but
+ BYPRODUCTS ${VERSION_BUT}
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ ${INTERMEDIATE_VERSION_BUT} ${VERSION_BUT}
+ DEPENDS check_git_commit_for_doc ${INTERMEDIATE_VERSION_BUT}
+ COMMENT "Updating cmake_version.but")
+ set(manual_dependencies ${manual_dependencies} cmake_version_but)
+ endif()
+
+ add_custom_target(copy_but
+ BYPRODUCTS copy.but
+ COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/../licence.pl
+ --copyrightdoc -o copy.but
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../licence.pl ${CMAKE_CURRENT_SOURCE_DIR}/../LICENCE)
+ add_custom_target(licence_but
+ BYPRODUCTS licence.but
+ COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/../licence.pl
+ --licencedoc -o licence.but
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../licence.pl ${CMAKE_CURRENT_SOURCE_DIR}/../LICENCE)
+ set(manual_dependencies ${manual_dependencies} copy_but licence_but)
+
+ set(manual_sources
+ ${CMAKE_CURRENT_BINARY_DIR}/copy.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/blurb.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/intro.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/gs.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/using.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/config.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/pscp.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/psftp.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/plink.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/pubkey.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/pageant.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/errors.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/faq.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/feedback.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/pubkeyfmt.but
+ ${CMAKE_CURRENT_BINARY_DIR}/licence.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/udp.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/pgpkeys.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/sshnames.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/authplugin.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/index.but
+ ${VERSION_BUT})
+
+ # The HTML manual goes in a subdirectory, for convenience.
+ set(html_dir ${CMAKE_CURRENT_BINARY_DIR}/html)
+ file(MAKE_DIRECTORY ${html_dir})
+ add_custom_command(OUTPUT ${html_dir}/index.html
+ COMMAND ${HALIBUT} --html ${manual_sources}
+ WORKING_DIRECTORY ${html_dir}
+ DEPENDS ${manual_sources} ${manual_dependencies})
+ list(APPEND doc_outputs ${html_dir}/index.html)
+
+ # Windows help.
+ file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/chmextra.but
+ "\\cfg{chm-extra-file}{${CMAKE_CURRENT_SOURCE_DIR}/chm.css}{chm.css}\n")
+ add_custom_command(OUTPUT putty.chm
+ COMMAND ${HALIBUT} --chm chmextra.but ${manual_sources}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ DEPENDS ${manual_sources} ${manual_dependencies})
+ list(APPEND doc_outputs putty.chm)
+
+ # Plain text.
+ add_custom_command(OUTPUT puttydoc.txt
+ COMMAND ${HALIBUT} --text ${manual_sources}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ DEPENDS ${manual_sources} ${manual_dependencies})
+ list(APPEND doc_outputs puttydoc.txt)
+endif()
+
+macro(register_manpage title section)
+ list(APPEND manpage_outputs ${title}.${section})
+ if(NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
+ # Only set this variable if there _is_ a parent scope.
+ set(HAVE_MANPAGE_${title}_${section} ON PARENT_SCOPE)
+ endif()
+endmacro()
+
+if(NOT HALIBUT)
+ # If we don't have Halibut available to rebuild the man pages from
+ # source, we must check whether the build and source directories
+ # correspond, so as to suppress the build rules that copy them from
+ # the source dir to the build dir. (Otherwise, someone unpacking
+ # putty-src.zip and building on a system without Halibut will find
+ # that there's a circular dependency in the makefile, which at least
+ # Ninja complains about.)
+ get_filename_component(DOCBUILDDIR ${CMAKE_CURRENT_BINARY_DIR} REALPATH)
+ get_filename_component(DOCSRCDIR ${CMAKE_CURRENT_SOURCE_DIR} REALPATH)
+endif()
+
+macro(manpage title section)
+ if(HALIBUT)
+ add_custom_command(OUTPUT ${title}.${section}
+ COMMAND ${HALIBUT} --man=${title}.${section}
+ ${CMAKE_CURRENT_SOURCE_DIR}/mancfg.but
+ ${CMAKE_CURRENT_SOURCE_DIR}/man-${title}.but
+ DEPENDS
+ mancfg.but man-${title}.but)
+ register_manpage(${title} ${section})
+ elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section})
+ # Our tarballs include prebuilt man pages in the source tree, so
+ # they can be installed from there even if Halibut isn't available.
+ if(NOT (DOCBUILDDIR STREQUAL DOCSRCDIR))
+ # Iff the build tree isn't the source tree, they'll need copying
+ # to the build tree first.
+ add_custom_command(OUTPUT ${title}.${section}
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section} ${title}.${section}
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${title}.${section})
+ endif()
+ register_manpage(${title} ${section})
+ endif()
+endmacro()
+
+manpage(putty 1)
+manpage(puttygen 1)
+manpage(plink 1)
+manpage(pscp 1)
+manpage(psftp 1)
+manpage(puttytel 1)
+manpage(pterm 1)
+manpage(pageant 1)
+manpage(psocks 1)
+manpage(psusan 1)
+
+add_custom_target(manpages ALL DEPENDS ${manpage_outputs})
+add_custom_target(doc DEPENDS ${doc_outputs} manpages)
+
+if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
+ # If we're doing a cmake from just the doc subdir, we expect the
+ # user to want to make all the documentation, including HTML and so
+ # forth. (What else would be the point?)
+ #
+ # But if we're included from the main makefile, then by default we
+ # only make the man pages (which are necessary for 'make install'),
+ # and we leave everything else to a separate 'make doc' target which
+ # the user can invoke if they need to.
+ add_custom_target(doc-default ALL DEPENDS doc)
+endif()
diff --git a/doc/Makefile b/doc/Makefile
deleted file mode 100644
index 5ff25d54..00000000
--- a/doc/Makefile
+++ /dev/null
@@ -1,80 +0,0 @@
-all: man index.html
-
-# Decide on the versionid policy.
-#
-# If the user has passed in $(VERSION) on the command line (`make
-# VERSION="Release 0.56"'), we use that as an explicit version string.
-# Otherwise, we use `svnversion' to examine the checked-out
-# documentation source, and if that returns a single revision number
-# then we invent a version string reflecting just that number. Failing
-# _that_, we resort to versionids.but which gives 'version
-# unavailable'.
-#
-# So here, we define VERSION using svnversion if it isn't already
-# defined ...
-ifndef VERSION
-SVNVERSION=$(shell test -d .svn && svnversion .)
-BADCHARS=$(findstring :,$(SVNVERSION))$(findstring S,$(SVNVERSION))
-ifeq ($(BADCHARS),)
-ifneq ($(SVNVERSION),)
-ifneq ($(SVNVERSION),exported)
-VERSION=Built from revision $(patsubst M,,$(SVNVERSION))
-endif
-endif
-endif
-endif
-# ... and now, we condition our build behaviour on whether or not
-# VERSION _is_ defined.
-ifdef VERSION
-VERSIONIDS=vstr
-vstr.but: FORCE
- printf '\\versionid $(VERSION)\n' > vstr.but
-FORCE:;
-else
-VERSIONIDS=vids
-endif
-
-CHAPTERS := $(SITE) copy blurb intro gs using config pscp psftp plink
-CHAPTERS += pubkey pageant errors faq feedback pubkeyfmt licence udp
-CHAPTERS += pgpkeys sshnames
-CHAPTERS += index $(VERSIONIDS)
-
-INPUTS = $(patsubst %,%.but,$(CHAPTERS))
-
-# This is temporary. Hack it locally or something.
-HALIBUT = halibut
-
-index.html: $(INPUTS)
- $(HALIBUT) --text --html --chm $(INPUTS)
-
-# During formal builds it's useful to be able to build this one alone.
-putty.chm: $(INPUTS)
- $(HALIBUT) --chm $(INPUTS)
-
-# We don't ship this any more.
-putty.hlp: $(INPUTS)
- $(HALIBUT) --winhelp $(INPUTS)
-
-putty.info: $(INPUTS)
- $(HALIBUT) --info $(INPUTS)
-
-MKMAN = $(HALIBUT) --man=$@ mancfg.but $<
-MANPAGES = putty.1 puttygen.1 plink.1 pscp.1 psftp.1 puttytel.1 pterm.1 \
- pageant.1 psocks.1 psusan.1
-man: $(MANPAGES)
-
-putty.1: man-putty.but mancfg.but; $(MKMAN)
-puttygen.1: man-puttygen.but mancfg.but; $(MKMAN)
-plink.1: man-plink.but mancfg.but; $(MKMAN)
-pscp.1: man-pscp.but mancfg.but; $(MKMAN)
-psftp.1: man-psftp.but mancfg.but; $(MKMAN)
-puttytel.1: man-puttytel.but mancfg.but; $(MKMAN)
-pterm.1: man-pterm.but mancfg.but; $(MKMAN)
-pageant.1: man-pageant.but mancfg.but; $(MKMAN)
-psocks.1: man-psocks.but mancfg.but; $(MKMAN)
-psusan.1: man-psusan.but mancfg.but; $(MKMAN)
-
-mostlyclean:
- rm -f *.html *.txt *.hlp *.cnt *.1 *.info vstr.but *.hh[pck]
-clean: mostlyclean
- rm -f *.chm
diff --git a/doc/authplugin.but b/doc/authplugin.but
new file mode 100644
index 00000000..41bc0daa
--- /dev/null
+++ b/doc/authplugin.but
@@ -0,0 +1,519 @@
+\A{authplugin} PuTTY authentication plugin protocol
+
+This appendix contains the specification for the protocol spoken over
+local IPC between PuTTY and an authentication helper plugin.
+
+If you already have an authentication plugin and want to configure
+PuTTY to use it, see \k{config-ssh-authplugin} for how to do that.
+This appendix is for people writing new authentication plugins.
+
+\H{authplugin-req} Requirements
+
+The following requirements informed the specification of this protocol.
+
+\s{Automate keyboard-interactive authentication.} We're motivated in
+the first place by the observation that the general SSH userauth
+method \cq{keyboard-interactive} (defined in \k{authplugin-ref-ki})
+can be used for many kinds of challenge/response or one-time-password
+styles of authentication, and in more than one of those, the necessary
+responses might be obtained from an auxiliary network connection, such
+as an HTTPS transaction. So it's useful if a user doesn't have to
+manually copy-type or copy-paste from their web browser into their SSH
+client, but instead, the process can be automated.
+
+\s{Be able to pass prompts on to the user.} On the other hand, some
+userauth methods can be only \e{partially} automated; some of the
+server's prompts might still require human input. Also, the plugin
+automating the authentication might need to ask its own questions that
+are not provided by the SSH server. (For example, \q{please enter the
+master key that the real response will be generated by hashing}.) So
+after the plugin intercepts the server's questions, it needs to be
+able to ask its own questions of the user, which may or may not be the
+same questions sent by the server.
+
+\s{Allow automatic generation of the username.} Sometimes, the
+authentication method comes with a mechanism for discovering the
+username to be used in the SSH login. So the plugin has to start up
+early enough that the client hasn't committed to a username yet.
+
+\s{Future expansion route to other SSH userauth flavours.} The initial
+motivation for this protocol is specific to keyboard-interactive. But
+other SSH authentication methods exist, and they may also benefit from
+automation in future. We're making no attempt here to predict what
+those methods might be or how they might be automated, but we do need
+to leave a space where they can be slotted in later if necessary.
+
+\s{Minimal information loss.} Keyboard-interactive prompts and replies
+should be passed to and from the plugin in a form as close as possible
+to the way they look on the wire in SSH itself. Therefore, the
+protocol resembles SSH in its data formats and marshalling (instead
+of, for example, translating from SSH binary packet style to another
+well-known format such as JSON, which would introduce edge cases in
+character encoding).
+
+\s{Half-duplex.} Simultaneously trying to read one I/O stream and
+write another adds a lot of complexity to software. It becomes
+necessary to have an organised event loop containing \cw{select} or
+\cw{WaitForMultipleObjects} or similar, which can invoke the handler
+for whichever event happens soonest. There's no need to add that
+complexity in an application like this, which isn't transferring large
+amounts of bulk data or multiplexing unrelated activities. So, to keep
+life simple for plugin authors, we set the ground rule that it must
+always be 100% clear which side is supposed to be sending a message
+next. That way, the plugin can be written as sequential code
+progressing through the protocol, making simple read and write calls
+to receive or send each message.
+
+\s{Communicate success/failure, to facilitate caching in the plugin.}
+A plugin might want to cache recently used data for next time, but
+only in the case where authentication using that data was actually
+successful. So the client has to tell the plugin what the outcome was,
+if it's known. (But this is best-effort only. Obviously the plugin
+cannot \e{depend} on hearing the answer, because any IPC protocol at
+all carries the risk that the other end might crash or be killed by
+things outside its control.)
+
+\H{authplugin-transport} Transport and configuration
+
+Plugins are executable programs on the client platform.
+
+The SSH client must be manually configured to use a plugin for a
+particular connection. The configuration takes the form of a command
+line, including the location of the plugin executable, and optionally
+command-line arguments that are meaningful to the particular plugin.
+
+The client invokes the plugin as a subprocess, passing it a pair of
+8-bit-clean pipes as its standard input and output. On those pipes,
+the client and plugin will communicate via the protocol specified
+below.
+
+\H{authplugin-formats} Data formats and marshalling
+
+This protocol borrows the low-level data formatting from SSH itself,
+in particular the following wire encodings from
+\k{authplugin-ref-arch} section 5:
+
+\dt \s{byte}
+
+\dd An integer between 0 and 0xFF inclusive, transmitted as a single
+byte of binary data.
+
+\dt \s{boolean}
+
+\dd The values \q{true} or \q{false}, transmitted as the bytes 1 and 0
+respectively.
+
+\dt \s{uint32}
+
+\dd An integer between 0 and 0xFFFFFFFF inclusive, transmitted as 4
+bytes of binary data, in big-endian (\q{network}) byte order.
+
+\dt \s{string}
+
+\dd A sequence of bytes, preceded by a \s{uint32} giving the number of
+bytes in the sequence. The length field does not include itself. For
+example, the empty string is represented by four zero bytes (the
+\s{uint32} encoding of 0); the string "AB" is represented by the six
+bytes 0,0,0,2,'A','B'.
+
+Unlike SSH itself, the protocol spoken between the client and the
+plugin is unencrypted, because local inter-process pipes are assumed
+to be secured by the OS kernel. So the binary packet protocol is much
+simpler than SSH proper, and is similar to SFTP and the OpenSSH agent
+protocol.
+
+The data sent in each direction of the conversation consists of a
+sequence of \s{messages} exchanged between the SSH client and the
+plugin. Each message is encoded as a \s{string}. The contents of the
+string begin with a \s{byte} giving the message type, which determines
+the format of the rest of the message.
+
+\H{authplugin-version} Protocol versioning
+
+This protocol itself is versioned. At connection setup, the client
+states the highest version number it knows how to speak, and then the
+plugin responds by choosing the version number that will actually be
+spoken (which may not be higher than the client's value).
+
+Including a version number makes it possible to make breaking changes
+to the protocol later.
+
+Even version numbers represent released versions of this spec. Odd
+numbers represent drafts or development versions in between releases.
+A client and plugin negotiating an odd version number are not
+guaranteed to interoperate; the developer testing the combination is
+responsible for ensuring the two are compatible.
+
+This document describes version 2 of the protocol, the first released
+version. (The initial drafts had version 1.)
+
+\H{authplugin-overview} Overview and sequence of events
+
+At the very beginning of the user authentication phase of SSH, the
+client launches the plugin subprocess, if one is configured. It
+immediately sends the \cw{PLUGIN_INIT} message, telling the plugin
+some initial information about where the SSH connection is to.
+
+The plugin responds with \cw{PLUGIN_INIT_RESPONSE}, which may
+optionally tell the SSH client what username to use.
+
+The client begins trying to authenticate with the SSH server in the
+usual way, using the username provided by the plugin (if any) or
+alternatively one obtained via its normal (non-plugin) policy.
+
+The client follows its normal policy for selecting authentication
+methods to attempt. If it chooses a method that this protocol does not
+cover, then the client will perform that method in its own way without
+consulting the plugin.
+
+However, if the client and server decide to attempt a method that this
+protocol \e{does} cover, then the client sends \cw{PLUGIN_PROTOCOL}
+specifying the SSH protocol id for the authentication method being
+used. The plugin responds with \cw{PLUGIN_PROTOCOL_ACCEPT} if it's
+willing to assist with this auth method, or
+\cw{PLUGIN_PROTOCOL_REJECT} if it isn't.
+
+If the plugin sends \cw{PLUGIN_PROTOCOL_REJECT}, then the client will
+proceed as if the plugin were not present. Later, if another auth
+method is negotiated (either because this one failed, or because it
+succeeded but the server wants multiple auth methods), the client may
+send a further \cw{PLUGIN_PROTOCOL} and try again.
+
+If the plugin sends \cw{PLUGIN_PROTOCOL_ACCEPT}, then a protocol
+segment begins that is specific to that auth method, terminating in
+either \cw{PLUGIN_AUTH_SUCCESS} or \cw{PLUGIN_AUTH_FAILURE}. After
+that, again, the client may send a further \cw{PLUGIN_PROTOCOL}.
+
+Currently the only supported method is \cq{keyboard-interactive},
+defined in \k{authplugin-ref-ki}. Once the client has announced this
+to the server, the followup protocol is as follows:
+
+Each time the server sends an \cw{SSH_MSG_USERAUTH_INFO_REQUEST}
+message requesting authentication responses from the user, the SSH
+client translates the message into \cw{PLUGIN_KI_SERVER_REQUEST} and
+passes it on to the plugin.
+
+At this point, the plugin may optionally send back
+\cw{PLUGIN_KI_USER_REQUEST} containing prompts to be presented to the
+actual user. The client will reply with a matching
+\cw{PLUGIN_KI_USER_RESPONSE} after asking the user to reply to the
+question(s) in the request message. The plugin can repeat this cycle
+multiple times.
+
+Once the plugin has all the information it needs to respond to the
+server's authentication prompts, it sends \cw{PLUGIN_KI_SERVER_RESPONSE}
+back to the client, which translates it into
+\cw{SSH_MSG_USERAUTH_INFO_RESPONSE} to send on to the server.
+
+After that, as described in \k{authplugin-ref-ki}, the server is free
+to accept authentication, reject it, or send another
+\cw{SSH_MSG_USERAUTH_INFO_REQUEST}. Each
+\cw{SSH_MSG_USERAUTH_INFO_REQUEST} is dealt with in the same way as
+above.
+
+If the server terminates keyboard-interactive authentication with
+\cw{SSH_MSG_USERAUTH_SUCCESS} or \cw{SSH_MSG_USERAUTH_FAILURE}, the
+client informs the plugin by sending either \cw{PLUGIN_AUTH_SUCCESS}
+or \cw{PLUGIN_AUTH_FAILURE}. \cw{PLUGIN_AUTH_SUCCESS} is sent when
+\e{that particular authentication method} was successful, regardless
+of whether the SSH server chooses to request further authentication
+afterwards: in particular, \cw{SSH_MSG_USERAUTH_FAILURE} with the
+\q{partial success} flag (see \k{authplugin-ref-userauth} section 5.1) translates
+into \cw{PLUGIN_AUTH_SUCCESS}.
+
+The plugin's standard input will close when the client no longer
+requires the plugin's services, for any reason. This could be because
+authentication is complete (with overall success or overall failure),
+or because the user has manually aborted the session in
+mid-authentication, or because the client crashed.
+
+\H{authplugin-messages} Message formats
+
+This section describes the format of every message in the protocol.
+
+As described in \k{authplugin-formats}, every message starts with the same two
+fields:
+
+\b \s{uint32}: overall length of the message
+
+\b \s{byte}: message type.
+
+The length field does not include itself, but does include the type
+code.
+
+The following subsections each give the format of the remainder of the
+message, after the type code.
+
+The type codes themselves are defined here:
+
+\c #define PLUGIN_INIT 1
+\c #define PLUGIN_INIT_RESPONSE 2
+\c #define PLUGIN_PROTOCOL 3
+\c #define PLUGIN_PROTOCOL_ACCEPT 4
+\c #define PLUGIN_PROTOCOL_REJECT 5
+\c #define PLUGIN_AUTH_SUCCESS 6
+\c #define PLUGIN_AUTH_FAILURE 7
+\c #define PLUGIN_INIT_FAILURE 8
+\c
+\c #define PLUGIN_KI_SERVER_REQUEST 20
+\c #define PLUGIN_KI_SERVER_RESPONSE 21
+\c #define PLUGIN_KI_USER_REQUEST 22
+\c #define PLUGIN_KI_USER_RESPONSE 23
+
+If this protocol is extended to be able to assist with further auth
+methods, their message type codes will also begin from 20, overlapping
+the codes for keyboard-interactive.
+
+\S{PLUGIN_INIT} \cw{PLUGIN_INIT}
+
+\s{Direction}: client to plugin
+
+\s{When}: the first message sent at connection startup
+
+\s{What happens next}: the plugin will send \cw{PLUGIN_INIT_RESPONSE}
+or \cw{PLUGIN_INIT_FAILURE}
+
+\s{Message contents after the type code}:
+
+\b \s{uint32}: the highest version number of this protocol that the
+client knows how to speak.
+
+\b \s{string}: the hostname of the server. This will be the \e{logical}
+hostname, in cases where it differs from the physical destination of
+the network connection. Whatever name would be used by the SSH client
+to cache the server's host key, that's the same name passed in this
+message.
+
+\b \s{uint32}: the port number on the server. (Together with the host
+name, this forms a primary key identifying a particular server. Port
+numbers may be vital because a single host can run two unrelated SSH
+servers with completely different authentication requirements, e.g.
+system sshd on port 22 and Gerrit on port 29418.)
+
+\b \s{string}: the username that the client will use to log in, if the
+plugin chooses not to override it. An empty string means that the
+client has no opinion about this (and might, for example, prompt the
+user).
+
+\S{PLUGIN_INIT_RESPONSE} \cw{PLUGIN_INIT_RESPONSE}
+
+\s{Direction}: plugin to client
+
+\s{When}: response to \cw{PLUGIN_INIT}
+
+\s{What happens next}: the client will send \cw{PLUGIN_PROTOCOL}, or
+perhaps terminate the session (if no auth method is ever negotiated
+that the plugin can help with)
+
+\s{Message contents after the type code}:
+
+\b \s{uint32}: the version number of this protocol that the connection
+will use. Must be no greater than the max version number sent by the
+client in \cw{PLUGIN_INIT}.
+
+\b \s{string}: the username that the plugin suggests the client use. An
+empty string means that the plugin has no opinion and the client
+should stick with the username it already had (or prompt the user, if
+it had none).
+
+\S{PLUGIN_INIT_FAILURE} \cw{PLUGIN_INIT_FAILURE}
+
+\s{Direction}: plugin to client
+
+\s{When}: response to \cw{PLUGIN_INIT}
+
+\s{What happens next}: the session is over
+
+\s{Message contents after the type code}:
+
+\b \s{string}: an error message to present to the user indicating why
+the plugin was unable to start up.
+
+\S{PLUGIN_PROTOCOL} \cw{PLUGIN_PROTOCOL}
+
+\s{Direction}: client to plugin
+
+\s{When}: sent after \cw{PLUGIN_INIT_RESPONSE}, or after a previous
+auth phase terminates with \cw{PLUGIN_AUTH_SUCCESS} or
+\cw{PLUGIN_AUTH_FAILURE}
+
+\s{What happens next}: the plugin will send
+\cw{PLUGIN_PROTOCOL_ACCEPT} or \cw{PLUGIN_PROTOCOL_REJECT}
+
+\s{Message contents after the type code}:
+
+\b \s{string}: the SSH protocol id of the auth method the client
+intends to attempt. Currently the only method specified for use in
+this protocol is \cq{keyboard-interactive}.
+
+\S{PLUGIN_PROTOCOL_REJECT} \cw{PLUGIN_PROTOCOL_REJECT}
+
+\s{Direction}: plugin to client
+
+\s{When}: sent after \cw{PLUGIN_PROTOCOL}
+
+\s{What happens next}: the client will either send another
+\cw{PLUGIN_PROTOCOL} or terminate the session
+
+\s{Message contents after the type code}:
+
+\b \s{string}: an error message to present to the user, explaining why
+the plugin cannot help with this authentication protocol.
+
+\lcont{
+
+An example might be \q{unable to open <config file>: <OS error
+message>}, if the plugin depends on some configuration that the user
+has not set up.
+
+If the plugin does not support this this particular authentication
+protocol at all, this string should be left blank, so that no message
+will be presented to the user at all.
+
+}
+
+\S{PLUGIN_PROTOCOL_ACCEPT} \cw{PLUGIN_PROTOCOL_ACCEPT}
+
+\s{Direction}: plugin to client
+
+\s{When}: sent after \cw{PLUGIN_PROTOCOL}
+
+\s{What happens next}: depends on the auth protocol agreed on. For
+keyboard-interactive, the client will send
+\cw{PLUGIN_KI_SERVER_REQUEST} or \cw{PLUGIN_AUTH_SUCCESS} or
+\cw{PLUGIN_AUTH_FAILURE}. No other method is specified.
+
+\s{Message contents after the type code}: none.
+
+\S{PLUGIN_KI_SERVER_REQUEST} \cw{PLUGIN_KI_SERVER_REQUEST}
+
+\s{Direction}: client to plugin
+
+\s{When}: sent after \cw{PLUGIN_PROTOCOL}, or after a previous
+\cw{PLUGIN_KI_SERVER_RESPONSE}, when the SSH server has sent
+\cw{SSH_MSG_USERAUTH_INFO_REQUEST}
+
+\s{What happens next}: the plugin will send either
+\cw{PLUGIN_KI_USER_REQUEST} or \cw{PLUGIN_KI_SERVER_RESPONSE}
+
+\s{Message contents after the type code}: the exact contents of the
+\cw{SSH_MSG_USERAUTH_INFO_REQUEST} just sent by the server. See
+\k{authplugin-ref-ki} section 3.2 for details. The summary:
+
+\b \s{string}: name of this prompt collection (e.g. to use as a
+dialog-box title)
+
+\b \s{string}: instructions to be displayed before this prompt
+collection
+
+\b \s{string}: language tag (deprecated)
+
+\b \s{uint32}: number of prompts in this collection
+
+\b That many copies of:
+
+\lcont{
+
+\b \s{string}: prompt (in UTF-8)
+
+\b \s{boolean}: whether the response to this prompt is safe to echo to
+the screen
+
+}
+
+\S{PLUGIN_KI_SERVER_RESPONSE} \cw{PLUGIN_KI_SERVER_RESPONSE}
+
+\s{Direction}: plugin to client
+
+\s{When}: response to \cw{PLUGIN_KI_SERVER_REQUEST}, perhaps after one
+or more intervening pairs of \cw{PLUGIN_KI_USER_REQUEST} and
+\cw{PLUGIN_KI_USER_RESPONSE}
+
+\s{What happens next}: the client will send a further
+\cw{PLUGIN_KI_SERVER_REQUEST}, or \cw{PLUGIN_AUTH_SUCCESS} or
+\cw{PLUGIN_AUTH_FAILURE}
+
+\s{Message contents after the type code}: the exact contents of the
+\cw{SSH_MSG_USERAUTH_INFO_RESPONSE} that the client should send back
+to the server. See \k{authplugin-ref-ki} section 3.4 for details. The
+summary:
+
+\b \s{uint32}: number of responses (must match the \q{number of
+prompts} field from the corresponding server request)
+
+\b That many copies of:
+
+\lcont{
+
+\b \s{string}: response to the \e{n}th prompt (in UTF-8)
+
+}
+
+\S{PLUGIN_KI_USER_REQUEST} \cw{PLUGIN_KI_USER_REQUEST}
+
+\s{Direction}: plugin to client
+
+\s{When}: response to \cw{PLUGIN_KI_SERVER_REQUEST}, if the plugin
+cannot answer the server's auth prompts without presenting prompts of
+its own to the user
+
+\s{What happens next}: the client will send \cw{PLUGIN_KI_USER_RESPONSE}
+
+\s{Message contents after the type code}: exactly the same as in
+\cw{PLUGIN_KI_SERVER_REQUEST} (see \k{PLUGIN_KI_SERVER_REQUEST}).
+
+\S{PLUGIN_KI_USER_RESPONSE} \cw{PLUGIN_KI_USER_RESPONSE}
+
+\s{Direction}: client to plugin
+
+\s{When}: response to \cw{PLUGIN_KI_USER_REQUEST}
+
+\s{What happens next}: the plugin will send
+\cw{PLUGIN_KI_SERVER_RESPONSE}, or another \cw{PLUGIN_KI_USER_REQUEST}
+
+\s{Message contents after the type code}: exactly the same as in
+\cw{PLUGIN_KI_SERVER_RESPONSE} (see \k{PLUGIN_KI_SERVER_RESPONSE}).
+
+\S{PLUGIN_AUTH_SUCCESS} \cw{PLUGIN_AUTH_SUCCESS}
+
+\s{Direction}: client to plugin
+
+\s{When}: sent after \cw{PLUGIN_KI_SERVER_RESPONSE}, or (in unusual
+cases) after \cw{PLUGIN_PROTOCOL_ACCEPT}
+
+\s{What happens next}: the client will either send another
+\cw{PLUGIN_PROTOCOL} or terminate the session
+
+\s{Message contents after the type code}: none
+
+\S{PLUGIN_AUTH_FAILURE} \cw{PLUGIN_AUTH_FAILURE}
+
+\s{Direction}: client to plugin
+
+\s{When}: sent after \cw{PLUGIN_KI_SERVER_RESPONSE}, or (in unusual
+cases) after \cw{PLUGIN_PROTOCOL_ACCEPT}
+
+\s{What happens next}: the client will either send another
+\cw{PLUGIN_PROTOCOL} or terminate the session
+
+\s{Message contents after the type code}: none
+
+\H{authplugin-refs} References
+
+\B{authplugin-ref-arch} \W{https://www.rfc-editor.org/rfc/rfc4251}{RFC 4251}, \q{The Secure Shell (SSH) Protocol
+Architecture}.
+
+\B{authplugin-ref-userauth} \W{https://www.rfc-editor.org/rfc/rfc4252}{RFC
+4252}, \q{The Secure Shell (SSH) Authentication Protocol}.
+
+\B{authplugin-ref-ki}
+\W{https://www.rfc-editor.org/rfc/rfc4256}{RFC 4256},
+\q{Generic Message Exchange Authentication for the Secure Shell
+Protocol (SSH)} (better known by its wire id
+\q{keyboard-interactive}).
+
+\BR{authplugin-ref-arch} [RFC4251]
+
+\BR{authplugin-ref-userauth} [RFC4252]
+
+\BR{authplugin-ref-ki} [RFC4256]
diff --git a/doc/blurb.but b/doc/blurb.but
index f980e9f1..c68a6262 100644
--- a/doc/blurb.but
+++ b/doc/blurb.but
@@ -17,7 +17,6 @@ page</a>.</p>}
\cfg{chm-contents-filename}{index.html}
\cfg{chm-template-filename}{%k.html}
\cfg{chm-head-end}{<link rel="stylesheet" type="text/css" href="chm.css">}
-\cfg{chm-extra-file}{chm.css}
\cfg{xhtml-contents-filename}{index.html}
\cfg{text-filename}{puttydoc.txt}
diff --git a/doc/chmextra.but b/doc/chmextra.but
new file mode 100644
index 00000000..8b8780c9
--- /dev/null
+++ b/doc/chmextra.but
@@ -0,0 +1,6 @@
+\# If you want to do a Halibut build of the CHM file by hand, without
+\# the help of the CMake edifice, then include this file which will
+\# refer to chm.css. The CMake edifice builds its own with a different
+\# pathname in it, for the sake of out-of-tree builds.
+
+\cfg{chm-extra-file}{chm.css}
diff --git a/doc/config.but b/doc/config.but
index 77313282..f258a356 100644
--- a/doc/config.but
+++ b/doc/config.but
@@ -596,6 +596,33 @@ through to \c{ESC [j}. With control they generate \c{ESC [k} through
to \c{ESC [v}, and with shift and control together they generate
\c{ESC [w} through to \c{ESC [\{}.
+\b In \I{xterm}Xterm 216 mode, the unshifted function keys behave the
+same as Xterm R6 mode. But pressing a function key together with Shift
+or Alt or Ctrl generates a different sequence containing an extra
+numeric parameter of the form (1 for Shift) + (2 for Alt) + (4 for
+Ctrl) + 1. For F1-F4, the basic sequences like \c{ESC OP} become
+\cw{ESC [1;}\e{bitmap}\cw{P} and similar; for F5 and above,
+\cw{ESC[}\e{index}\cw{~} becomes
+\cw{ESC[}\e{index}\cw{;}\e{bitmap}\cw{~}.
+
+If you don't know what any of this means, you probably don't need to
+fiddle with it.
+
+\S{config-sharrow} Changing the action of the \i{shifted arrow keys}
+
+This option affects the arrow keys, if you press one with any of the
+modifier keys Shift, Ctrl or Alt held down.
+
+\b In the default mode, labelled \c{Ctrl toggles app mode}, the Ctrl
+key toggles between the default arrow-key sequences like \c{ESC [A} and
+\c{ESC [B}, and the sequences Digital's terminals generate in
+\q{application cursor keys} mode, i.e. \c{ESC O A} and so on. Shift
+and Alt have no effect.
+
+\b In the \q{xterm-style bitmap} mode, Shift, Ctrl and Alt all
+generate different sequences, with a number indicating which set of
+modifiers is active.
+
If you don't know what any of this means, you probably don't need to
fiddle with it.
@@ -1342,7 +1369,7 @@ which one of the options is \q{Paste}). (This context menu is always
available by holding down Ctrl and right-clicking, regardless of the
setting of this option.)
-(When PuTTY iself is running on Unix, it follows the X Window System
+(When PuTTY itself is running on Unix, it follows the X Window System
convention.)
\S{config-mouseshift} \q{Shift overrides application's use of mouse}
@@ -1916,23 +1943,50 @@ it must always be explicitly configured.
\S{config-proxy-type} Setting the proxy type
-The \q{Proxy type} radio buttons allow you to configure what type of
+The \q{Proxy type} drop-down allows you to configure what type of
proxy you want PuTTY to use for its network connections. The default
setting is \q{None}; in this mode no proxy is used for any
connection.
-\b Selecting \I{HTTP proxy}\q{HTTP} allows you to proxy your connections
-through a web server supporting the HTTP \cw{CONNECT} command, as documented
-in \W{http://www.ietf.org/rfc/rfc2817.txt}{RFC 2817}.
+\b Selecting \I{HTTP proxy}\q{HTTP CONNECT} allows you to proxy your
+connections through a web server supporting the HTTP \cw{CONNECT} command,
+as documented in \W{https://www.rfc-editor.org/rfc/rfc2817}{RFC 2817}.
\b Selecting \q{SOCKS 4} or \q{SOCKS 5} allows you to proxy your
connections through a \i{SOCKS server}.
\b Many firewalls implement a less formal type of proxy in which a
-user can make a Telnet connection directly to the firewall machine
+user can make a Telnet or TCP connection directly to the firewall machine
and enter a command such as \c{connect myhost.com 22} to connect
through to an external host. Selecting \I{Telnet proxy}\q{Telnet}
-allows you to tell PuTTY to use this type of proxy.
+allows you to tell PuTTY to use this type of proxy, with the precise
+command specified as described in \k{config-proxy-command}.
+
+\b There are several ways to use a SSH server as a proxy. All of
+these cause PuTTY to make a secondary SSH connection to the proxy host
+(sometimes called a \q{\i{jump host}} in this context).
+
+\lcont{
+The \q{Proxy hostname} field will be interpreted as the name of a
+PuTTY saved session if one exists, or a hostname if not. This
+allows multi-hop jump paths, if the referenced saved session is
+itself configured to use an SSH proxy; and it allows combining SSH
+and non-SSH proxying.
+
+\b \q{SSH to proxy and use port forwarding} causes PuTTY to use the
+secondary SSH connection to open a port-forwarding channel to the
+final destination host (similar to OpenSSH's \cw{-J} option).
+
+\b \q{SSH to proxy and execute a command} causes PuTTY to run an
+arbitrary remote command on the proxy SSH server and use that
+command's standard input and output streams to run the primary
+connection over. The remote command line is specified as described in
+\k{config-proxy-command}.
+
+\b \q{SSH to proxy and invoke a subsystem} is similar but causes PuTTY
+to start an SSH \q{\i{subsystem}} rather than an ordinary command line.
+This might be useful with a specially set up SSH proxy server.
+}
\b Selecting \I{Local proxy}\q{Local} allows you to specify an arbitrary
command on the local machine to act as a proxy. When the session is
@@ -1945,11 +1999,6 @@ This could be used, for instance, to talk to some kind of network proxy
that PuTTY does not natively support; or you could tunnel a connection
over something other than TCP/IP entirely.
-If you want your local proxy command to make a secondary SSH
-connection to a proxy host and then tunnel the primary connection
-over that, you might well want the \c{-nc} command-line option in
-Plink. See \k{using-cmdline-ncmode} for more information.
-
You can also enable this mode on the command line; see
\k{using-cmdline-proxycmd}.
}
@@ -2007,9 +2056,9 @@ set it to \q{Yes}, PuTTY will always pass host names straight to the
proxy without trying to look them up first.
If you set this option to \q{Auto} (the default), PuTTY will do
-something it considers appropriate for each type of proxy. Telnet,
-HTTP, and SOCKS5 proxies will have host names passed straight to
-them; SOCKS4 proxies will not.
+something it considers appropriate for each type of proxy. Most
+types of proxy (HTTP, SOCK5, SSH, Telnet, and local) will have host
+names passed straight to them; SOCKS4 proxies will not.
Note that if you are doing DNS at the proxy, you should make sure
that your proxy exclusion settings (see \k{config-proxy-exclude}) do
@@ -2022,15 +2071,30 @@ is a protocol extension (SOCKS 4A) which does support it, but not
all SOCKS 4 servers provide this extension. If you enable proxy DNS
and your SOCKS 4 server cannot deal with it, this might be why.
+If you want to avoid PuTTY making \e{any} DNS query related to your
+destination host name (for example, because your local DNS resolver is
+very slow to return a negative response in that situation), then as
+well as setting this control to \q{Yes}, you may also need to turn off
+GSSAPI authentication and GSSAPI key exchange in SSH (see
+\k{config-ssh-auth-gssapi} and \k{config-ssh-gssapi-kex}
+respectively). This is because GSSAPI setup also involves a DNS query
+for the destination host name, and that query is performed by the
+separate GSSAPI library, so PuTTY can't override or reconfigure it.
+
\S{config-proxy-auth} \I{proxy username}Username and \I{proxy password}password
-If your proxy requires \I{proxy authentication}authentication, you can
-enter a username and a password in the \q{Username} and \q{Password} boxes.
+You can enter a username and a password in the \q{Username} and
+\q{Password} boxes, which will be used if your proxy requires
+\I{proxy authentication}authentication.
\I{security hazard}Note that if you save your session, the proxy
password will be saved in plain text, so anyone who can access your PuTTY
configuration data will be able to discover it.
+If PuTTY discovers that it needs a proxy username or password and you
+have not specified one here, PuTTY will prompt for it interactively in
+the terminal window.
+
Authentication is not fully supported for all forms of proxy:
\b Username and password authentication is supported for HTTP
@@ -2042,28 +2106,44 @@ proxies and SOCKS 5 proxies.
supports it (this is not supported in \i{PuTTYtel}); otherwise the
password is sent to the proxy in \I{plaintext password}plain text.
-\b With HTTP proxying, the only currently supported authentication
-method is \I{HTTP basic}\q{basic}, where the password is sent to the proxy
-in \I{plaintext password}plain text.
+\b With HTTP proxying, authentication is via \q{\i{HTTP Digest}} if
+possible (again, not supported in PuTTYtel), or \q{\i{HTTP Basic}}. In
+the latter case, the password is sent to the proxy in \I{plaintext
+password}plain text.
}
\b SOCKS 4 can use the \q{Username} field, but does not support
passwords.
+\b SSH proxying can use all the same forms of SSH authentication
+supported by PuTTY for its main connection. If the SSH server requests
+password authentication, any configured proxy password will be used,
+but other authentication methods such as public keys and GSSAPI will
+be tried first, just as for a primary SSH connection, and if they
+require credentials such as a key passphrase, PuTTY will interactively
+prompt for these.
+
\b You can specify a way to include a username and password in the
-Telnet/Local proxy command (see \k{config-proxy-command}).
+Telnet/Local proxy command (see \k{config-proxy-command}). If you do
+so, and don't also specify the actual username and/or password in the
+configuration, PuTTY will interactively prompt for them.
-\S{config-proxy-command} Specifying the Telnet or Local proxy command
+\S{config-proxy-command} Specifying the Telnet, SSH, or Local proxy command
If you are using the \i{Telnet proxy} type, the usual command required
by the firewall's Telnet server is \c{connect}, followed by a host
name and a port number. If your proxy needs a different command,
-you can enter an alternative here.
+you can enter an alternative in the \q{Command to send to proxy} box.
If you are using the \i{Local proxy} type, the local command to run
is specified here.
+If you are using the \q{SSH to proxy and execute a command} type, the
+command to run on the SSH proxy server is specified here. Similarly, if
+you are using \q{SSH to proxy and invoke a subsystem}, the subsystem
+name is constructed as specified here.
+
In this string, you can use \c{\\n} to represent a new-line, \c{\\r}
to represent a carriage return, \c{\\t} to represent a tab
character, and \c{\\x} followed by two hex digits to represent any
@@ -2071,12 +2151,15 @@ other character. \c{\\\\} is used to encode the \c{\\} character
itself.
Also, the special strings \c{%host} and \c{%port} will be replaced
-by the host name and port number you want to connect to. The strings
-\c{%user} and \c{%pass} will be replaced by the proxy username and
-password you specify. The strings \c{%proxyhost} and \c{%proxyport}
+by the host name and port number you want to connect to. For Telnet
+and Local proxy types, the strings \c{%user} and \c{%pass} will be
+replaced by the proxy username and password (which, if not specified
+in the configuration, will be prompted for) \dash this does not happen
+with SSH proxy types (because the proxy username/password are used
+for SSH authentication). The strings \c{%proxyhost} and \c{%proxyport}
will be replaced by the host details specified on the \e{Proxy} panel,
-if any (this is most likely to be useful for the Local proxy type).
-To get a literal \c{%} sign, enter \c{%%}.
+if any (this is most likely to be useful for proxy types using a
+local or remote command). To get a literal \c{%} sign, enter \c{%%}.
If a Telnet proxy server prompts for a username and password
before commands can be sent, you can use a command such as:
@@ -2086,8 +2169,8 @@ before commands can be sent, you can use a command such as:
This will send your username and password as the first two lines to
the proxy, followed by a command to connect to the desired host and
port. Note that if you do not include the \c{%user} or \c{%pass}
-tokens in the Telnet command, then the \q{Username} and \q{Password}
-configuration fields will be ignored.
+tokens in the Telnet command, then anything specified in \q{Username}
+and \q{Password} configuration fields will be ignored.
\S{config-proxy-logging} Controlling \i{proxy logging}
@@ -2264,24 +2347,51 @@ cipher selection (see \k{config-ssh-encryption}).
PuTTY currently supports the following key exchange methods:
-\b \q{ECDH}: \i{elliptic curve} \i{Diffie-Hellman key exchange}.
+\b \q{NTRU Prime / Curve25519 hybrid}: \q{\i{Streamlined NTRU Prime}}
+is a lattice-based algorithm intended to resist \i{quantum attacks}.
+In this key exchange method, it is run in parallel with a conventional
+Curve25519-based method (one of those included in \q{ECDH}), in such
+a way that it should be no \e{less} secure than that commonly-used
+method, and hopefully also resistant to a new class of attacks.
-\b \q{Group 14}: Diffie-Hellman key exchange with a well-known
-2048-bit group.
+\b \q{\i{ECDH}}: elliptic curve Diffie-Hellman key exchange,
+with a variety of standard curves and hash algorithms.
-\b \q{Group 1}: Diffie-Hellman key exchange with a well-known
-1024-bit group. We no longer recommend using this method, and it's
-not used by default in new installations; however, it may be the
-only method supported by very old server software.
+\b The original form of \i{Diffie-Hellman key exchange}, with a
+variety of well-known groups and hashes:
-\b \q{\ii{Group exchange}}: with this method, instead of using a fixed
-group, PuTTY requests that the server suggest a group to use for key
-exchange; the server can avoid groups known to be weak, and possibly
-invent new ones over time, without any changes required to PuTTY's
-configuration. We recommend use of this method instead of the
-well-known groups, if possible.
+\lcont{
+\b \q{Group 18}, a well-known 8192-bit group, used with the SHA-512
+hash function.
-\b \q{\i{RSA key exchange}}: this requires much less computational
+\b \q{Group 17}, a well-known 6144-bit group, used with the SHA-512
+hash function.
+
+\b \q{Group 16}, a well-known 4096-bit group, used with the SHA-512
+hash function.
+
+\b \q{Group 15}, a well-known 3072-bit group, used with the SHA-512
+hash function.
+
+\b \q{Group 14}: a well-known 2048-bit group, used with the SHA-256
+hash function or, if the server doesn't support that, SHA-1.
+
+\b \q{Group 1}: a well-known 1024-bit group, used with the SHA-1
+hash function. Neither we nor current SSH standards recommend using
+this method any longer, and it's not used by default in new
+installations; however, it may be the only method supported by very
+old server software.
+}
+
+\b \q{Diffie-Hellman \i{group exchange}}: with this method, instead
+of using a fixed group, PuTTY requests that the server suggest a group
+to use for a subsequent Diffie-Hellman key exchange; the server can
+avoid groups known to be weak, and possibly invent new ones over time,
+without any changes required to PuTTY's configuration. This key
+exchange method uses the SHA-256 hash or, if the server doesn't
+support that, SHA-1.
+
+\b \q{\i{RSA-based key exchange}}: this requires much less computational
effort on the part of the client, and somewhat less on the part of
the server, than Diffie-Hellman key exchange.
@@ -2303,6 +2413,10 @@ when using Kerberos V5, and not other GSSAPI mechanisms. If the user
running PuTTY has current Kerberos V5 credentials, then PuTTY will
select the GSSAPI key exchange methods in preference to any of the
ordinary SSH key exchange methods configured in the preference list.
+There's a GSSAPI-based equivalent to most of the ordinary methods
+listed in \k{config-ssh-kex-order}; server support determines which
+one will be used. (PuTTY's preference order for GSSAPI-authenticated
+key exchange methods is fixed, not controlled by the preference list.)
The advantage of doing GSSAPI authentication as part of the SSH key
exchange is apparent when you are using credential delegation (see
@@ -2317,7 +2431,8 @@ support GSSAPI in the SSH user authentication phase. This will still
let you log in using your Kerberos credentials, but will only allow
you to delegate the credentials that are active at the beginning of
the session; they can't be refreshed automatically later, in a
-long-running session.
+long-running session. See \k{config-ssh-auth-gssapi} for how to
+control GSSAPI user authentication in PuTTY.
Another effect of GSSAPI key exchange is that it replaces the usual
SSH mechanism of permanent host keys described in \k{gs-hostkey}.
@@ -2403,7 +2518,7 @@ protection than SSH-2 without rekeys.
\H{config-ssh-hostkey} The Host Keys panel
-The Host Keys panel allows you to configure options related to SSH-2
+The Host Keys panel allows you to configure options related to
\i{host key management}.
Host keys are used to prove the server's identity, and assure you that
@@ -2411,8 +2526,8 @@ the server is not being spoofed (either by a man-in-the-middle attack
or by completely replacing it on the network). See \k{gs-hostkey} for
a basic introduction to host keys.
-This entire panel is only relevant to SSH protocol version 2; none of
-these settings affect SSH-1 at all.
+Much of this panel is only relevant to SSH protocol version 2; SSH-1
+only supports one type of host key.
\S{config-ssh-hostkey-order} \ii{Host key type} selection
@@ -2431,7 +2546,7 @@ larger elliptic curve with a 448-bit instead of 255-bit modulus (so it
has a higher security level than Ed25519).
\b \q{ECDSA}: \i{elliptic curve} \i{DSA} using one of the
-NIST-standardised elliptic curves.
+\i{NIST}-standardised elliptic curves.
\b \q{DSA}: straightforward \i{DSA} using modular exponentiation.
@@ -2452,7 +2567,7 @@ to that for cipher selection (see \k{config-ssh-encryption}).
\S{config-ssh-prefer-known-hostkeys} Preferring known host keys
-By default, PuTTY will adjust the preference order for host key
+By default, PuTTY will adjust the preference order for SSH-2 host key
algorithms so that any host keys it already knows are moved to the top
of the list.
@@ -2527,6 +2642,146 @@ neither read \e{nor written}, unless you explicitly do so.
If the box is empty (as it usually is), then PuTTY's automated host
key management will work as normal.
+\S{config-ssh-kex-cert} Configuring PuTTY to accept host \i{certificates}
+
+In some environments, the SSH host keys for a lot of servers will all
+be signed in turn by a central \q{certification authority} (\q{CA} for
+short). This simplifies host key configuration for users, because if
+they configure their SSH client to accept host keys certified by that
+CA, then they don't need to individually confirm each host key the
+first time they connect to that server.
+
+In order to do this, press the \q{Configure host CAs} button in the
+\q{Host keys} configuration panel. This will launch a secondary
+configuration dialog box where you can configure what CAs PuTTY will
+accept signatures from.
+
+\s{Note that this configuration is common to all saved sessions}.
+Everything in the main PuTTY configuration is specific to one saved
+session, and you can prepare a separate session with all the
+configuration different. But there's only one copy of the host CA
+configuration, and it applies to all sessions PuTTY runs, whether
+saved or not.
+
+(Otherwise, it would be useless \dash configuring a CA by hand for
+each new host wouldn't be any more convenient than pressing the
+\q{confirm} button for each new host's host key.)
+
+To set up a new CA using this config box:
+
+First, load the CA's public key from a file, or paste it directly into
+the \q{Public key of certification authority} edit box. If your
+organisation signs its host keys in this way, they will publish the
+public key of their CA so that SSH users can include it in their
+configuration.
+
+Next, in the \q{Valid hosts this key is trusted to certify} box,
+configure at least one hostname wildcard to say what servers PuTTY
+should trust this CA to speak for. For example, suppose you work for
+Example Corporation (\cw{example.com}), and the Example Corporation IT
+department has advertised a CA that signs all the Example internal
+machines' host keys. Then probably you want to trust that CA to sign
+host keys for machines in the domain \cw{example.com}, but not for
+anything else. So you might enter \cq{*.example.com} into the \q{Valid
+hosts} box.
+
+\s{It's important to limit what the CA key is allowed to sign}. Don't
+just enter \cq{*} in that box! If you do that, you're saying that
+Example Corporation IT department is authorised to sign a host key for
+\e{anything at all} you might decide to connect to \dash even if
+you're connecting out of the company network to a machine somewhere
+else, such as your own personal server. So that configuration would
+enable the Example IT department to act as a \q{man-in-the-middle}
+between your PuTTY process and your server, and listen in to your
+communications \dash exactly the thing SSH is supposed to avoid.
+
+So, if the CA was provided to you by the sysadmins responsible for
+\cw{example.com} (or whatever), make sure PuTTY will \e{only} trust it
+for machines in the \cw{example.com} domain.
+
+For the full syntax of the \q{Valid hosts} expression, see
+\k{config-ssh-cert-valid-expr}.
+
+Finally, choose an identifying name for this CA; enter that name in
+the \q{Name for this CA} edit box at the top of the window, and press
+\q{Save} to record the CA in your configuration. The name you chose
+will appear in the list of saved CAs to the left of the \q{Save}
+button.
+
+The identifying name can be anything you like. It's there so that if
+you store multiple certificates you can tell which is which later when
+you want to edit or delete them. It also appears in the PuTTY Event
+Log when a server presents a certificate signed by that CA.
+
+To reload an existing CA configuration, select it in the list box and
+press \q{Load}. Then you can make changes, and save it again.
+
+To remove a CA from your configuration completely, select it in the
+list and press \q{Delete}.
+
+\S2{config-ssh-cert-valid-expr} Expressions you can enter in \q{Valid
+hosts}
+
+The simplest thing you can enter in the \q{Valid hosts this key is
+trusted to certify} edit box is just a hostname wildcard such as
+\cq{*.example.com}. This matches any host in any subdomain, so
+both \cq{ssh.example.com} and \cq{login.dept.example.com} would
+match, but \cq{prod.example.net} would not.
+
+But you can also enter multiple host name wildcards, and port number
+ranges, and make complicated Boolean expressions out of them using the
+operators \cq{&&} for \q{and}, \cq{||} for \q{or}, \cq{!} for \q{not},
+and parentheses.
+
+For example, here are some other things you could enter.
+
+\b \cq{*.foo.example.com || *.bar.example.com}. This means the CA is
+trusted to sign the host key for a connection if the host name matches
+\q{*.foo.example.com} \e{or} it matches \q{*.bar.example.com}. In
+other words, the CA has authority over those two particular subdomains
+of \cw{example.com}, but not for anything else, like
+\cw{www.example.com}.
+
+\b \cq{*.example.com && ! *.extrasecure.example.com}. This means the
+CA is trusted to sign the host key for a connection if the host name
+matches \q{*.example.com} \e{but does not} match
+\q{*.extrasecure.example.com}. (Imagine if there was one top-secret
+set of servers in your company that the main IT department didn't have
+security clearance to administer.)
+
+\b \cq{*.example.com && port:22}. This means the CA is trusted to sign
+the host key for a connection if the host name matches
+\q{*.example.com} \e{and} the port number is 22. SSH servers running
+on other ports would not be covered.
+
+\b \cq{(*.foo.example.com || *.bar.example.com) && port:0-1023}. This
+matches two subdomains of \cw{example.com}, as before, but \e{also}
+restricts the port number to the range 0-1023.
+
+A certificate configuration expression consists of one or more
+individual requirements which can each be a hostname wildcard, a
+single port number, or a port number range, combined together with
+these Boolean operators.
+
+Unlike other languages such as C, there is no implied priority between
+\cq{&&} and \cq{||}. If you write \cq{A && B || C} (where \cw{A},
+\cw{B} and \cw{C} are some particular requirements), then PuTTY will
+report a syntax error, because you haven't said which of the \cq{&&}
+and \cq{||} takes priority tightly. You will have to write either
+\cq{(A && B) || C}, meaning \q{both of \cw{A} and \cw{B}, or
+alternatively just \cw{C}}, or \cq{A && (B || C)} (\q{\cw{A}, and also
+at least one of \cw{B} and \cw{C}}), to make it clear.
+
+\S2{config-ssh-cert-rsa-hash} RSA signature types in certificates
+
+RSA keys can be used to generate signatures with a choice of secure
+hash function. Typically, any version of OpenSSH new enough to support
+certificates at all will also be new enough to avoid using SHA-1, so
+the default settings of accepting the more modern SHA-256 and SHA-512
+should be suitable for nearly all cases. For completeness, however,
+you can configure which types of RSA signature PuTTY will accept in a
+certificate from a CA using an RSA key.
+
\H{config-ssh-encryption} The Cipher panel
PuTTY supports a variety of different \i{encryption algorithm}s, and
@@ -2541,7 +2796,8 @@ PuTTY currently supports the following algorithms:
\b \i{ChaCha20-Poly1305}, a combined cipher and \i{MAC} (SSH-2 only)
-\b \i{AES} (Rijndael) - 256, 192, or 128-bit SDCTR or CBC (SSH-2 only)
+\b \i{AES} (Rijndael) - 256, 192, or 128-bit SDCTR or CBC, or
+256 or 128-bit GCM (SSH-2 only)
\b \i{Arcfour} (RC4) - 256 or 128-bit stream cipher (SSH-2 only)
@@ -2745,6 +3001,12 @@ username more than once, in case the server complains. If you know
your server can cope with it, you can enable the \q{Allow attempted
changes of username} option to modify PuTTY's behaviour.
+\H{config-ssh-auth-creds} The Credentials panel
+
+This subpane of the Auth panel contains configuration options that
+specify actual \e{credentials} to present to the server: key files and
+certificates.
+
\S{config-ssh-privkey} \q{\ii{Private key} file for authentication}
This box is where you enter the name of your private key file if you
@@ -2766,6 +3028,54 @@ in this case (in RFC 4716 or OpenSSH format), as that's sufficient to
identify the key to Pageant, but of course if Pageant isn't present
PuTTY can't fall back to using this file itself.
+\S{config-ssh-cert} \q{\ii{Certificate} to use with the private key}
+
+In some environments, user authentication keys can be signed in turn
+by a \q{certifying authority} (\q{CA} for short), and user accounts on
+an SSH server can be configured to automatically trust any key that's
+certified by the right signature.
+
+This can be a convenient setup if you have a very large number of
+servers. When you change your key pair, you might otherwise have to
+edit the \cw{authorized_keys} file on every server individually, to
+make them all accept the new key. But if instead you configure all
+those servers \e{once} to accept keys signed as yours by a CA, then
+when you change your public key, all you have to do is to get the new
+key certified by the same CA as before, and then all your servers will
+automatically accept it without needing individual reconfiguration.
+
+One way to use a certificate is to incorporate it into your private
+key file. \K{puttygen-cert} explains how to do that using PuTTYgen.
+But another approach is to tell PuTTY itself where to find the public
+certificate file, and then it will automatically present that
+certificate when authenticating with the corresponding private key.
+
+To do this, enter the pathname of the certificate file into the
+\q{Certificate to use with the private key} file selector.
+
+When this setting is configured, PuTTY will honour it no matter
+whether the private key is found in a file, or loaded into Pageant.
+
+\S{config-ssh-authplugin} \q{\ii{Plugin} to provide authentication responses}
+
+An SSH server can use the \q{keyboard-interactive} protocol to present
+a series of arbitrary questions and answers. Sometimes this is used
+for ordinary passwords, but sometimes the server will use the same
+mechanism for something more complicated, such as a one-time password
+system.
+
+Some of these systems can be automated. For this purpose, PuTTY allows
+you to provide a separate program to act as a \q{plugin} which will
+take over the authentication and send answers to the questions on your
+behalf.
+
+If you have been provided with a plugin of this type, you can
+configure it here, by entering a full command line in the \q{Plugin
+command to run} box.
+
+(If you want to \e{write} a plugin of this type, see \k{authplugin}
+for the full specification of how the plugin is expected to behave.)
+
\H{config-ssh-auth-gssapi} The \i{GSSAPI} panel
The \q{GSSAPI} subpanel of the \q{Auth} panel controls the use of
@@ -3206,7 +3516,10 @@ three states:
\b \q{On}: PuTTY will assume the server \e{does} have the bug.
\b \q{Auto}: PuTTY will use the server's version number announcement
-to try to guess whether or not the server has the bug.
+to try to guess whether or not the server has the bug. (This option is
+not available for bugs that \e{cannot} be detected from the server
+version, e.g. because they must be acted on before the server version
+is known.)
\S{config-ssh-bug-ignore2} \q{Chokes on SSH-2 \i{ignore message}s}
@@ -3299,6 +3612,55 @@ send an over-sized packet. If this bug is enabled when talking to a
correct server, the session will work correctly, but download
performance will be less than it could be.
+\S{config-ssh-bug-dropstart} \q{Discards data sent before its greeting}
+
+Just occasionally, an SSH connection can be established over some
+channel that will accidentally discard outgoing data very early in the
+connection.
+
+This is not typically seen as a bug in an actual SSH server, but it
+can sometimes occur in situations involving a complicated proxy
+process. An example is
+\W{https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991958}{Debian
+bug #991958}, in which a connection going over the console of a User
+Mode Linux kernel can lose outgoing data before the kernel has fully
+booted.
+
+You can work around this problem by manually enabling this bug flag,
+which will cause PuTTY to wait to send its initial SSH greeting until
+after it sees the greeting from the server.
+
+Note that this bug flag can never be automatically detected, since
+auto-detection relies on the version string in the server's greeting,
+and PuTTY has to decide whether to expect this bug \e{before} it sees
+the server's greeting. So this is a manual workaround only.
+
+\S{config-ssh-bug-filter-kexinit} \q{Chokes on PuTTY's full \cw{KEXINIT}}
+
+At the start of an SSH connection, the client and server exchange long
+messages of type \cw{SSH_MSG_KEXINIT}, containing lists of all the
+cryptographic algorithms they're prepared to use. This is used to
+negotiate a set of algorithms that both ends can speak.
+
+Occasionally, a badly written server might have a length limit on the
+list it's prepared to receive, and refuse to make a connection simply
+because PuTTY is giving it too many choices.
+
+A workaround is to enable this flag, which will make PuTTY wait to
+send \cw{KEXINIT} until after it receives the one from the server, and
+then filter its own \cw{KEXINIT} to leave out any algorithm the server
+doesn't also announce support for. This will generally make PuTTY's
+\cw{KEXINIT} at most the size of the server's, and will otherwise make
+no difference to the algorithm negotiation.
+
+This flag is a minor violation of the SSH protocol, because both sides
+are supposed to send \cw{KEXINIT} proactively. It still works provided
+\e{one} side sends its \cw{KEXINIT} without waiting, but if both
+client and server waited for the other one to speak first, the
+connection would deadlock. We don't know of any servers that do this,
+but if there is one, then this flag will make PuTTY unable to speak to
+them at all.
+
\S{config-ssh-bug-sig} \q{Requires padding on SSH-2 \i{RSA} \i{signatures}}
Versions below 3.3 of \i{OpenSSH} require SSH-2 RSA signatures to be
diff --git a/doc/errors.but b/doc/errors.but
index 36a42d94..a35a7256 100644
--- a/doc/errors.but
+++ b/doc/errors.but
@@ -10,8 +10,8 @@ self-explanatory. If you get an error message which is not listed in
this chapter and which you don't understand, report it to us as a
bug (see \k{feedback}) and we will add documentation for it.
-\H{errors-hostkey-absent} \q{The server's host key is not cached in
-the registry}
+\H{errors-hostkey-absent} \q{The host key is not cached for this
+server}
This error message occurs when PuTTY connects to a new SSH server.
Every server identifies itself by means of a host key; once PuTTY
@@ -35,10 +35,13 @@ See \k{gs-hostkey} for more information on host keys.
\H{errors-hostkey-wrong} \q{WARNING - POTENTIAL SECURITY BREACH!}
This message, followed by \q{The server's host key does not match
-the one PuTTY has cached in the registry}, means that PuTTY has
+the one PuTTY has cached for this server}, means that PuTTY has
connected to the SSH server before, knows what its host key
\e{should} be, but has found a different one.
+(If the message instead talks about a \q{certified host key}, see
+instead \k{errors-cert-mismatch}.)
+
This may mean that a malicious attacker has replaced your server
with a different one, or has redirected your network connection to
their own machine. On the other hand, it may simply mean that the
@@ -52,6 +55,41 @@ in the same way as you would if it was new.
See \k{gs-hostkey} for more information on host keys.
+\H{errors-cert-mismatch} \q{This server presented a certified host key
+which was signed by a different certification authority ...}
+
+If you've configured PuTTY to trust at least one
+\I{certificate}certification authority for signing host keys (see
+\k{config-ssh-kex-cert}), then it will ask the SSH server to send it
+any available certified host keys. If the server sends back a
+certified key signed by a \e{different} certification authority, PuTTY
+will present this variant of the host key prompt, preceded by
+\q{WARNING - POTENTIAL SECURITY BREACH!}
+
+One reason why this can happen is a deliberate attack. Just like an
+ordinary man-in-the-middle attack which substitutes a wrong host key,
+a particularly ambitious attacker might substitute an entire wrong
+certification authority, and hope that you connect anyway.
+
+But it's also possible in some situations that this error might arise
+legitimately. For example, if your organisation's IT department has
+just rolled out a new CA key which you haven't yet entered in PuTTY's
+configuration, or if your CA configuration involves two overlapping
+domains, or something similar.
+
+So, unfortunately, you'll have to work out what to do about it
+yourself: make an exception for this specific case, or abandon this
+connection and install a new CA key before trying again (if you're
+really sure you trust the CA), or edit your configuration in some
+other way, or just stop trying to use this server.
+
+If you're convinced that this particular server is legitimate even
+though the CA is not one you trust, PuTTY will let you cache the
+certified host key, treating it in the same way as an uncertified one.
+Then that particular certificate will be accepted for future
+connections to this specific server, even though other certificates
+signed by the same CA will still be rejected.
+
\H{errors-ssh-protocol} \q{SSH protocol version 2 required by our
configuration but remote only provides (old, insecure) SSH-1}
diff --git a/doc/faq.but b/doc/faq.but
index 474b984a..9a8deea2 100644
--- a/doc/faq.but
+++ b/doc/faq.but
@@ -150,7 +150,7 @@ data at the server end; it's your guarantee that it hasn't been
removed and replaced somewhere on the way. Host key checking makes
the attacker's job \e{astronomically} hard, compared to packet
sniffing, and even compared to subverting a router. Instead of
-applying a little intelligence and keeping an eye on Bugtraq, the
+applying a little intelligence and keeping an eye on oss-security, the
attacker must now perform a brute-force attack against at least one
military-strength cipher. That insignificant host key prompt really
does make \e{that} much difference.
@@ -220,7 +220,7 @@ Currently, release versions of PuTTY tools only run on Windows
systems and Unix.
As of 0.68, the supplied PuTTY executables run on versions of Windows
-from XP onwards, up to and including Windows 10; and we know of no
+from XP onwards, up to and including Windows 11; and we know of no
reason why PuTTY should not continue to work on future versions of
Windows. We provide 32-bit and 64-bit Windows executables for the
common x86 processor family; see \k{faq-32bit-64bit} for discussion
@@ -250,8 +250,7 @@ There are Unix ports of most of the traditional PuTTY tools, and also
one entirely new application.
If you look at the source release, you should find a \c{unix}
-subdirectory. There are a couple of ways of building it,
-including the usual \c{configure}/\c{make}; see the file \c{README}
+subdirectory. You need \c{cmake} to build it; see the file \c{README}
in the source distribution. This should build you:
\b Unix ports of PuTTY, Plink, PSCP, and PSFTP, which work pretty much
@@ -325,7 +324,8 @@ unfinished.
If any OS X and/or GTK programming experts are keen to have a finished
version of this, we urge them to help out with some of the remaining
-problems! See the TODO list in \c{unix/gtkapp.c} in the source code.
+problems! See the TODO list in \c{unix/main-gtk-application.c} in the
+source code.
\S{faq-epoc}{Question} Will there be a port to EPOC?
@@ -584,7 +584,7 @@ You can also paste by pressing Shift-Ins.
keys, proxying, cipher selection, etc.) in PSCP, PSFTP and Plink?
Most major features (e.g., public keys, port forwarding) are available
-through command line options. See the documentation.
+through command line options. See \k{using-general-opts}.
Not all features are accessible from the command line yet, although
we'd like to fix this. In the meantime, you can use most of
@@ -606,9 +606,16 @@ To use PSCP properly, run it from a Command Prompt window. See
\S{faq-pscp-spaces}{Question} \I{spaces in filenames}How do I use
PSCP to copy a file whose name has spaces in?
-If PSCP is using the traditional SCP protocol, this is confusing. If
-you're specifying a file at the local end, you just use one set of
-quotes as you would normally do:
+If PSCP is using the newer SFTP protocol (which is usual with most
+modern servers), this is straightforward; all filenames with spaces
+in are specified using a single pair of quotes in the obvious way:
+
+\c pscp "local file" user@host:
+\c pscp user@host:"remote file" .
+
+However, if PSCP is using the older SCP protocol for some reason,
+things are more confusing. If you're specifying a file at the local
+end, you just use one set of quotes as you would normally do:
\c pscp "local filename with spaces" user@host:
\c pscp user@host:myfile "local filename with spaces"
@@ -632,13 +639,6 @@ Instead, you need to specify the local file name in full:
\c c:\>pscp user@host:"\"oo er\"" "oo er"
-If PSCP is using the newer SFTP protocol, none of this is a problem,
-and all filenames with spaces in are specified using a single pair
-of quotes in the obvious way:
-
-\c pscp "local file" user@host:
-\c pscp user@host:"remote file" .
-
\S{faq-32bit-64bit}{Question} Should I run the 32-bit or the
64-bit version?
@@ -1152,6 +1152,22 @@ running, but it doesn't stop the process's memory as a whole from
being swapped completely out to disk when the process is long-term
inactive. And Pageant spends most of its time inactive.
+\S{faq-windowsstore}{Question} Is the version of PuTTY in the
+\i{Microsoft Store} legit?
+
+The free-of-charge \q{PuTTY} application at
+\W{https://apps.microsoft.com/store/detail/putty/XPFNZKSKLBP7RJ}{this link}
+is published and maintained by us. The copy there is the latest
+release, usually updated within a few days of us publishing it on our
+own website.
+
+There have been other copies of PuTTY on the store, some looking quite
+similar, and some charging money. Those were uploaded by other people,
+and we can't guarantee anything about them.
+
+The first version we published to the Microsoft Store was 0.76 (some
+time after its initial release on our website).
+
\H{faq-admin} Administrative questions
\S{faq-putty-org}{Question} Is \cw{putty.org} your website?
@@ -1286,7 +1302,7 @@ Small donations (tens of dollars or tens of euros) will probably be
spent on beer or curry, which helps motivate our volunteer team to
continue doing this for the world. Larger donations will be spent on
something that actually helps development, if we can find anything
-(perhaps new hardware, or a copy of Windows XP), but if we can't
+(perhaps new hardware, or a new version of Windows), but if we can't
find anything then we'll just distribute the money among the
developers. If you want to be sure your donation is going towards
something worthwhile, ask us first. If you don't like these terms,
diff --git a/doc/gs.but b/doc/gs.but
index e6a84923..8b915dbf 100644
--- a/doc/gs.but
+++ b/doc/gs.but
@@ -50,8 +50,9 @@ section.
If you are using SSH to connect to a server for the first time, you
will probably see a message looking something like this:
-\c The server's host key is not cached in the registry. You have no
-\c guarantee that the server is the computer you think it is.
+\c The host key is not cached for this server:
+\c ssh.example.com (port 22)
+\c You have no guarantee that the server is the computer you think it is.
\c The server's ssh-ed25519 key fingerprint is:
\c ssh-ed25519 255 SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w
\c If you trust this host, press "Accept" to add the key to PuTTY's
@@ -79,10 +80,10 @@ PuTTY \I{host key cache}records the host key for each server you
connect to, in the Windows \i{Registry}. Every time you connect to a
server, it checks that the host key presented by the server is the
same host key as it was the last time you connected. If it is not,
-you will see a warning, and you will have the chance to abandon your
-connection before you type any private information (such as a
-password) into it. (See \k{errors-hostkey-wrong} for what that looks
-like.)
+you will see a stronger warning, and you will have the chance to
+abandon your connection before you type any private information (such
+as a password) into it. (See \k{errors-hostkey-wrong} for what that
+looks like.)
However, when you connect to a server you have not connected to
before, PuTTY has no way of telling whether the host key is the
diff --git a/doc/index.but b/doc/index.but
index f7e7145a..187f5a1e 100644
--- a/doc/index.but
+++ b/doc/index.but
@@ -245,6 +245,7 @@ saved sessions from
\IM{-m} \c{-m} command-line option
\IM{-P-upper} \c{-P} command-line option
\IM{-pw} \c{-pw} command-line option
+\IM{-pwfile} \c{-pwfile} command-line option
\IM{-A-upper} \c{-A} command-line option
\IM{-a} \c{-a} command-line option
\IM{-X-upper} \c{-X} command-line option
@@ -607,8 +608,11 @@ saved sessions from
\IM{proxy authentication} proxy authentication
\IM{proxy authentication} authentication, to proxy
-\IM{HTTP basic} HTTP \q{basic} authentication
-\IM{HTTP basic} \q{basic} authentication (HTTP)
+\IM{HTTP Basic} HTTP Basic authentication
+\IM{HTTP Basic} \q{basic} authentication (HTTP)
+
+\IM{HTTP Digest} HTTP Digest authentication
+\IM{HTTP Digest} \q{digest} authentication (HTTP)
\IM{plaintext password} plain text password
\IM{plaintext password} password, plain text
@@ -684,6 +688,16 @@ saved sessions from
\IM{group exchange} Diffie-Hellman group exchange
\IM{group exchange} group exchange, Diffie-Hellman
+\IM{ECDH} \q{ECDH} (elliptic-curve Diffie-Hellman)
+\IM{ECDH} elliptic-curve Diffie-Hellman key exchange
+\IM{ECDH} key exchange, elliptic-curve Diffie-Hellman
+\IM{ECDH} Diffie-Hellman key exchange, with elliptic curves
+
+\IM{Streamlined NTRU Prime} Streamlined NTRU Prime
+\IM{Streamlined NTRU Prime} NTRU Prime
+
+\IM{quantum attacks} quantum attacks, resistance to
+
\IM{repeat key exchange} repeat key exchange
\IM{repeat key exchange} key exchange, repeat
@@ -815,6 +829,12 @@ saved sessions from
\IM{DSA} DSA
\IM{DSA} Digital Signature Standard
+\IM{ECDSA} ECDSA
+\IM{ECDSA} elliptic-curve DSA
+
+\IM{NIST} NIST-standardised elliptic curves
+\IM{NIST} elliptic curves, NIST-standardised
+
\IM{EdDSA} EdDSA
\IM{EdDSA} Edwards-curve DSA
@@ -863,6 +883,11 @@ saved sessions from
\IM{authentication agent} agent, authentication
\IM{-c-pageant} \c{-c} Pageant command-line option
+\IM{--keylist} \c{--keylist} Pageant command-line option
+\IM{--openssh-config} \c{--openssh-config} Pageant command-line option
+
+\IM{Windows OpenSSH} Windows OpenSSH
+\IM{Windows OpenSSH} OpenSSH, on Windows
\IM{FAQ} FAQ
\IM{FAQ} Frequently Asked Questions
@@ -930,3 +955,14 @@ saved sessions from
\IM{system tray} system tray, Windows
\IM{system tray} notification area, Windows (aka system tray)
\IM{system tray} taskbar notification area, Windows (aka system tray)
+
+\IM{shifted arrow keys} arrow keys, shifted
+\IM{shifted arrow keys} shifted arrow keys
+
+\IM{certificate}{certificates} certificates, SSH
+\IM{certificate}{certificates} SSH certificates
+\IM{certificate}{certificates} OpenSSH certificates
+\IM{certificate}{certificates} CA (certification authority)
+
+\IM{Microsoft Store} Microsoft Store
+\IM{Microsoft Store} Windows Store
diff --git a/doc/man-pageant.but b/doc/man-pageant.but
index 358f3a08..d202f166 100644
--- a/doc/man-pageant.but
+++ b/doc/man-pageant.but
@@ -41,7 +41,7 @@ extract their public half.
The agent protocol used by \c{pageant} is compatible with the PuTTY
tools and also with other implementations such as OpenSSH's SSH client
-and \e{ssh-agent(1)}. Some \c{pageant} features are implemented with
+and \cw{ssh-agent}(\e{1}). Some \c{pageant} features are implemented with
protocol extensions, so will only work if \c{pageant} is on both ends.
To run \c{pageant} as an agent, you must provide an option to tell it
@@ -256,6 +256,12 @@ be matched
\dd to indicate that it is a fingerprint of a specific format
+\dt \cq{sha256-cert:} or \cq{md5-cert:}
+
+\dd to indicate that it is a fingerprint of a specific format, and
+specifically matches the fingerprint of the public key \e{including} a
+certificate if any
+
}
\dt \cw{--public-openssh} \e{key-identifiers}, \cw{-L} \e{key-identifiers}
@@ -317,15 +323,15 @@ by the SSH agent protocol.
\dt \cw{--askpass} \e{prompt}
-\dd With this option, \c{pageant} acts as an \e{ssh-askpass(1)}
+\dd With this option, \c{pageant} acts as an \cw{ssh-askpass}(\e{1})
replacement, rather than performing any SSH agent functionality. This
may be useful if you prefer Pageant's GUI prompt style, which
minimises information leakage about your passphrase length in its
-visual feedback, compared to other \e{ssh-askpass(1)} implementations.
+visual feedback, compared to other \cw{ssh-askpass}(\e{1}) implementations.
\lcont{
-\c{pageant --askpass} implements the standard \e{ssh-askpass(1)}
+\c{pageant --askpass} implements the standard \cw{ssh-askpass}(\e{1})
interface: it can be passed a prompt to display (as a single argument)
and, if successful, prints the passphrase on standard output and
returns a zero exit status. Typically you would use the environment
diff --git a/doc/man-plink.but b/doc/man-plink.but
index 26e65f71..2a3b36c7 100644
--- a/doc/man-plink.but
+++ b/doc/man-plink.but
@@ -59,9 +59,9 @@ to aid in verifying new files released by the PuTTY team.
\dt \cw{-ssh-connection}
\dd Force use of the \q{bare \cw{ssh-connection}} protocol. This is
-only likely to be useful when connecting to a \e{psusan(1)} server,
-most likely with an absolute path to a Unix-domain socket in place
-of \e{host}.
+only likely to be useful when connecting to a \cw{psusan}(\e{1})
+server, most likely with an absolute path to a Unix-domain socket in
+place of \e{host}.
\dt \cw{\-proxycmd} \e{command}
@@ -114,11 +114,16 @@ sequences. These options override Plink's default behaviour to enable
or disabling such filtering on the standard error and standard output
channels.
+\dt \cw{-pwfile} \e{filename}
+
+\dd Open the specified file, and use the first line of text read from
+it as the remote password.
+
\dt \cw{-pw} \e{password}
\dd Set remote password to \e{password}. \e{CAUTION:} this will likely
make the password visible to other users of the local machine (via
-commands such as \q{\c{w}}).
+commands such as \q{\c{ps}} or \q{\c{w}}). Use \cw{-pwfile} instead.
\dt \cw{\-L} \cw{[}\e{srcaddr}\cw{:]}\e{srcport}\cw{:}\e{desthost}\cw{:}\e{destport}
diff --git a/doc/man-pscp.but b/doc/man-pscp.but
index 60ce4f5e..544d3a40 100644
--- a/doc/man-pscp.but
+++ b/doc/man-pscp.but
@@ -101,11 +101,16 @@ channel from the server, to prevent remote processes sending confusing
escape sequences. This option forces the standard error channel to not be
filtered.
+\dt \cw{-pwfile} \e{filename}
+
+\dd Open the specified file, and use the first line of text read from
+it as the remote password.
+
\dt \cw{-pw} \e{password}
\dd Set remote password to \e{password}. \e{CAUTION:} this will likely
make the password visible to other users of the local machine (via
-commands such as \q{\c{w}}).
+commands such as \q{\c{ps}} or \q{\c{w}}). Use \cw{-pwfile} instead.
\dt \cw{-1}
@@ -118,9 +123,9 @@ commands such as \q{\c{w}}).
\dt \cw{-ssh-connection}
\dd Force use of the \q{bare \cw{ssh-connection}} protocol. This is
-only likely to be useful when connecting to a \e{psusan(1)} server,
-most likely with an absolute path to a Unix-domain socket in place
-of \e{host}.
+only likely to be useful when connecting to a \cw{psusan}(\e{1})
+server, most likely with an absolute path to a Unix-domain socket in
+place of \e{host}.
\dt \cw{-ssh}
diff --git a/doc/man-psftp.but b/doc/man-psftp.but
index 52617291..e0b48602 100644
--- a/doc/man-psftp.but
+++ b/doc/man-psftp.but
@@ -89,11 +89,16 @@ channel from the server, to prevent remote processes sending confusing
escape sequences. This option forces the standard error channel to not be
filtered.
+\dt \cw{-pwfile} \e{filename}
+
+\dd Open the specified file, and use the first line of text read from
+it as the remote password.
+
\dt \cw{-pw} \e{password}
\dd Set remote password to \e{password}. \e{CAUTION:} this will likely
make the password visible to other users of the local machine (via
-commands such as \q{\c{w}}).
+commands such as \q{\c{ps}} or \q{\c{w}}). Use \cw{-pwfile} instead.
\dt \cw{-1}
@@ -106,9 +111,9 @@ commands such as \q{\c{w}}).
\dt \cw{-ssh-connection}
\dd Force use of the \q{bare \cw{ssh-connection}} protocol. This is
-only likely to be useful when connecting to a \e{psusan(1)} server,
-most likely with an absolute path to a Unix-domain socket in place
-of \e{host}.
+only likely to be useful when connecting to a \cw{psusan}(\e{1})
+server, most likely with an absolute path to a Unix-domain socket in
+place of \e{host}.
\dt \cw{-ssh}
diff --git a/doc/man-psocks.but b/doc/man-psocks.but
index a9792e44..eb075a6e 100644
--- a/doc/man-psocks.but
+++ b/doc/man-psocks.but
@@ -18,8 +18,8 @@ IPv4 and IPv6 connections. It does not support requiring
authentication of its clients.
\cw{psocks} can be used together with an SSH client such as
-\cw{putty(1)} to implement a reverse dynamic SSH tunnel. It can also
-be used for network protocol debugging, as it can record all the
+\cw{putty}(\e{1}) to implement a reverse dynamic SSH tunnel. It can
+also be used for network protocol debugging, as it can record all the
traffic passing through it in various ways.
By default, \cw{psocks} listens to connections from localhost only,
@@ -84,8 +84,8 @@ have the connection's traffic piped into it, similar to \cw{-f}.
\S{psocks-manpage-examples} EXAMPLES
-In combination with the \e{plink(1)} SSH client, to set up a reverse
-dynamic SSH tunnel, in which the remote listening port 1080 on
+In combination with the \cw{plink}(\e{1}) SSH client, to set up a
+reverse dynamic SSH tunnel, in which the remote listening port 1080 on
remote host \cw{myhost} acts as a SOCKS server giving access to your
local network:
diff --git a/doc/man-psusan.but b/doc/man-psusan.but
index fa986e88..64d3a030 100644
--- a/doc/man-psusan.but
+++ b/doc/man-psusan.but
@@ -191,15 +191,36 @@ And the setup script \cw{uml-psusan.sh} might look like this:
\c # Choose what shell you want to run inside psusan
\e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
\c export SHELL=/bin/bash
+\c # Set up a default path
+\e iiiiiiiiiiiiiiiiiiiiiii
+\c export PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
\c # And now run psusan over the serial port
\e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
\c exec /home/simon/src/putty/misc/psusan
-Now set up a PuTTY saved session as in the Docker example above, using
-that \cw{linux} command as the local proxy command, and you'll have a
-PuTTY session that starts up a clean UML instance when you run it, and
-(if you enabled connection sharing) further instances of the same
-session will connect to the same instance again.
+Now set up a PuTTY saved session as in the Docker example above.
+Basically you'll want to use the above \cw{linux} command as the local
+proxy command. However, it's worth wrapping it in \cw{setsid}(\e{1}),
+because when UML terminates, it kills its entire process group. So
+it's better that PuTTY should not be part of that group, and should
+have the opportunity to shut down cleanly by itself. So probably you
+end up setting the proxy command to be something more like:
+
+\c setsid linux mem=512M rootfstype=hostfs rootflags=/ rw \
+\c con=fd:2,fd:2 ssl0=fd:0,fd:1 init=/some/path/to/uml-psusan.sh
+\e iiiiiiiiiiiiiiiiiiiiiiiiiii
+
+You may also find that you have to enable the bug workaround that
+indicates that the server \q{Discards data sent before its greeting},
+because otherwise PuTTY's outgoing protocol greeting can be
+accidentally lost during UML startup. (See
+\W{https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991958}{Debian
+bug #991958}.)
+
+Once you've done that, you'll have a PuTTY session that starts up a
+clean UML instance when you run it, and (if you enabled connection
+sharing) further instances of the same session will connect to the
+same instance again.
\S2{psusan-manpage-examples-wsl} Windows Subsystem for Linux
@@ -231,6 +252,39 @@ ports in and out of the WSL environment (e.g. expose a WSL2 network
service through the hypervisor's internal NAT), forward Pageant into
it, and so on.
+\S2{psusan-manpage-examples-cygwin} Cygwin
+
+Another Unix-like environment on Windows is Cygwin. That comes with
+its own GUI terminal application, \cw{mintty} (as it happens, a
+derivative of PuTTY); but if you'd prefer to use PuTTY itself to talk
+to your Cygwin terminal sessions, \cw{psusan} can help.
+
+To do this, you'll first need to build the Unix PuTTY tools inside
+Cygwin (via the usual \cw{cmake} method). Then, copy the resulting
+\cw{psusan.exe} into Cygwin's \cw{/bin} directory. (It has to be
+in that directory for non-Cygwin programs to run it; otherwise it
+won't be able to find the Cygwin DLL at startup.)
+
+Then set up your PuTTY saved session like this:
+
+\b set the local proxy command to run \cw{psusan.exe} via its real
+Windows path. You might also want to add the \cw{--sessiondir} option
+so that shell sessions start up in your Cygwin home directory. For
+example, you might use the command \cq{c:\\cygwin64\\bin\\psusan.exe
+--sessiondir /home/simon} (changing the pathname and username to match
+your setup).
+
+\b enter anything you like in the host name box; \cq{Cygwin} is
+probably a good choice
+
+\b set the protocol to \q{Bare ssh-connection}, as usual.
+
+Port forwarding is probably not particularly useful in this case,
+since Cygwin shares the same network port space as the host machine.
+But turning on agent forwarding is useful, because then the Cygwin
+command-line SSH client can talk to Pageant without any further
+configuration.
+
\S2{psusan-manpage-examples-schroot} \cw{schroot}
Another example of a container-like environment is the alternative
diff --git a/doc/man-pterm.but b/doc/man-pterm.but
index fec97f11..d3d1d96a 100644
--- a/doc/man-pterm.but
+++ b/doc/man-pterm.but
@@ -76,7 +76,7 @@ will be ignored unless the \cw{BoldAsColour} resource is set to 0 or 2.
\dt \cw{\-geometry} \e{geometry}
\dd Specify the size of the terminal, in rows and columns of text. See
-\e{X(7)} for more information on the syntax of geometry
+\cw{X}(\e{7}) for more information on the syntax of geometry
specifications.
\dt \cw{\-sl} \e{lines}
diff --git a/doc/man-putty.but b/doc/man-putty.but
index 858ec0b0..a85b4505 100644
--- a/doc/man-putty.but
+++ b/doc/man-putty.but
@@ -55,7 +55,7 @@ will be ignored unless the \cw{BoldAsColour} resource is set to 0 or 2.
\dt \cw{\-geometry} \e{geometry}
\dd Specify the size of the terminal, in rows and columns of text.
-See \e{X(7)} for more information on the syntax of geometry
+See \cw{X}(\e{7}) for more information on the syntax of geometry
specifications.
\dt \cw{\-sl} \e{lines}
diff --git a/doc/man-puttygen.but b/doc/man-puttygen.but
index 021af205..e6a2c990 100644
--- a/doc/man-puttygen.but
+++ b/doc/man-puttygen.but
@@ -12,10 +12,12 @@
\e bbbbbbbb iiiiiii bb iiiiiii bb iiii bbbbbbbb iiiiii bb
\c [ -C new-comment ] [ -P ] [ --reencrypt ]
\e bb iiiiiiiiiii bb bbbbbbbbbbb
-\c [ -O output-type | -l | -L | -p | --dump ] [ -E fptype ]
-\e bb iiiiiiiiiii bb bb bb bbbbbb bb iiiiii
-\c [ --ppk-param key=value,... ]
-\e bbbbbbbbbbb iiibiiiiib
+\c [ --certificate cert-file | --remove-certificate ]
+\e bbbbbbbbbbbbb iiiiiiiii bbbbbbbbbbbbbbbbbbbb
+\c [ -O output-type | -l | -L | -p | --dump | --cert-info ]
+\e bb iiiiiiiiiii bb bb bb bbbbbb bbbbbbbbbbb
+\c [ --ppk-param key=value,... | -E fptype ]
+\e bbbbbbbbbbb iiibiiiiib bb iiiiii
\c [ -o output-file ]
\e bb iiiiiiiiiii
@@ -58,8 +60,9 @@ ssh.com's implementation.
You can also specify a file containing only a \e{public} key here.
The operations you can do are limited to outputting another public
-key format or a fingerprint. Public keys can be in RFC 4716 or
-OpenSSH format, or the standard SSH-1 format.
+key format (possibly removing an attached certificate first), or a
+fingerprint. Public keys can be in RFC 4716 or OpenSSH format, or
+the standard SSH-1 format.
}
@@ -143,6 +146,19 @@ to type).
automatic when you are generating a new key, but not when you are
modifying an existing key.
+\dt \cw{\-\-certificate} \e{certificate-file}
+
+\dd Adds an OpenSSH-style certificate to the public half of the key,
+so that the output file contains a certified public key with the same
+private key. If the input file already contained a certificate, it
+will be replaced with the new one. (Use \cq{-} to read a certificate
+from standard input.)
+
+\dt \cw{\-\-remove\-certificate}
+
+\dd Removes any certificate that was part of the key, to recover the
+uncertified version of the underlying key.
+
\dt \cw{\-\-reencrypt}
\dd For an existing private key saved with a passphrase, refresh the
@@ -260,6 +276,13 @@ newer format even for RSA, DSA, and ECDSA keys.
\dd Save an SSH-2 private key in ssh.com's format. This option is not
permitted for SSH-1 keys.
+\dt \cw{cert-info}
+
+\dd Save a textual dump of information about the certificate on the
+key, if any: whether it's a host or a user certificate, what host(s)
+or user(s) it's certified to be, its validity period, ID and serial
+number, and the fingerprint of the signing CA.
+
\dt \cw{text}
\dd Save a textual dump of the numeric components comprising the key
@@ -269,8 +292,9 @@ SSH.
\lcont{
The output consists of a series of \cw{name=value} lines, where each
-\c{value} is either a C-like string literal in double quotes, or a
-hexadecimal number starting with \cw{0x...}
+\c{value} is either a C-like string literal in double quotes, a
+hexadecimal number starting with \cw{0x...}, or a binary blob
+encoded with base64, denoted by \cw{b64("...")}.
}
If no output type is specified, the default is \c{private}.
@@ -283,8 +307,9 @@ If no output type is specified, the default is \c{private}.
this option is not specified, \c{puttygen} will assume you want to
overwrite the original file if the input and output file types are
the same (changing a comment or passphrase), and will assume you
-want to output to stdout if you are asking for a public key or
-fingerprint. Otherwise, the \c{\-o} option is required.
+want to output to stdout if you are asking for a public key,
+fingerprint, or one of the textual dump types. Otherwise, the
+\c{\-o} option is required.
\dt \cw{\-l}
@@ -298,6 +323,10 @@ fingerprint. Otherwise, the \c{\-o} option is required.
\dd Synonym for \q{\cw{-O public}}.
+\dt \cw{\-\-cert\-info}
+
+\dd Synonym for \q{\cw{-O cert-info}}.
+
\dt \cw{\-\-dump}
\dd Synonym for \q{\cw{-O text}}.
@@ -305,7 +334,18 @@ fingerprint. Otherwise, the \c{\-o} option is required.
\dt \cw{-E} \e{fptype}
\dd Specify the algorithm to use if generating a fingerprint. The
-options are \cw{sha256} (the default) and \cw{md5}.
+available algorithms are are \cw{sha256} (the default) and \cw{md5}.
+
+\lcont{
+
+By default, when showing the fingerprint of a public key that includes
+a certificate, \c{puttygen} will not include the certificate, so that
+the fingerprint shown will be the same as the underlying public key.
+If you want the fingerprint including the certificate (for example, so
+as to tell two certified keys apart), you can specify \cw{sha256-cert}
+or \cw{md5-cert} as the fingerprint type.
+
+}
\dt \cw{\-\-new\-passphrase} \e{file}
diff --git a/doc/man-puttytel.but b/doc/man-puttytel.but
index bf852ddb..075eeea7 100644
--- a/doc/man-puttytel.but
+++ b/doc/man-puttytel.but
@@ -56,7 +56,7 @@ will be ignored unless the \cw{BoldAsColour} resource is set to 0 or 2.
\dt \cw{\-geometry} \e{geometry}
\dd Specify the size of the terminal, in rows and columns of text. See
-\e{X(7)} for more information on the syntax of geometry
+\cw{X}(\e{7}) for more information on the syntax of geometry
specifications.
\dt \cw{\-sl} \e{lines}
diff --git a/doc/pageant.but b/doc/pageant.but
index 8abb5cdf..de6d4cb8 100644
--- a/doc/pageant.but
+++ b/doc/pageant.but
@@ -64,21 +64,24 @@ The large list box in the Pageant main window lists the private keys
that are currently loaded into Pageant. The list might look
something like this:
-\c ssh-ed25519 SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w
-\c ssh-rsa 2048 SHA256:8DFtyHm3kQihgy52nzX96qMcEVOq7/yJmmwQQhBWYFg
+\c Ed25519 SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w
+\c RSA 2048 SHA256:8DFtyHm3kQihgy52nzX96qMcEVOq7/yJmmwQQhBWYFg
For each key, the list box will tell you:
\b The type of the key. Currently, this can be
-\c{ssh-rsa} (an RSA key for use with the SSH-2 protocol),
-\c{ssh-dss} (a DSA key for use with the SSH-2 protocol),
-\c{ecdsa-sha2-*} (an ECDSA key for use with the SSH-2 protocol),
-\c{ssh-ed25519} (an Ed25519 key for use with the SSH-2 protocol),
-\c{ssh-ed448} (an Ed448 key for use with the SSH-2 protocol),
-or \c{ssh1} (an RSA key for use with the old SSH-1 protocol).
+\q{RSA} (an RSA key for use with the SSH-2 protocol),
+\q{DSA} (a DSA key for use with the SSH-2 protocol),
+\q{\i{NIST}} (an ECDSA key for use with the SSH-2 protocol),
+\q{Ed25519} (an Ed25519 key for use with the SSH-2 protocol),
+\q{Ed448} (an Ed448 key for use with the SSH-2 protocol),
+or \q{SSH-1} (an RSA key for use with the old SSH-1 protocol).
+(If the key has an associated certificate, this is shown here with a
+\q{cert} suffix.)
\b The size (in bits) of the key, for key types that come in different
-sizes.
+sizes. (For ECDSA \q{NIST} keys, this is indicated as \q{p256} or
+\q{p384} or \q{p521}.)
\b The \I{key fingerprint}fingerprint for the public key. This should be
the same fingerprint given by PuTTYgen, and (hopefully) also the same
@@ -86,10 +89,20 @@ fingerprint shown by remote utilities such as \i\c{ssh-keygen} when
applied to your \c{authorized_keys} file.
\lcont{
-By default this is shown in the \q{SHA256} format. You can change to the
-older \q{MD5} format (which looks like \c{aa:bb:cc:...}) with the
-\q{Fingerprint type} drop-down, but bear in mind that this format is
-less secure and should be avoided for comparison purposes where possible.
+For SSH-2 keys, by default this is shown in the \q{SHA256} format. You
+can change to the older \q{MD5} format (which looks like \c{aa:bb:cc:...})
+with the \q{Fingerprint type} drop-down, but bear in mind that this
+format is less secure and should be avoided for comparison purposes
+where possible.
+
+If some of the keys loaded into Pageant have certificates attached,
+then Pageant will default to showing the fingerprint of the underlying
+key. This way, a certified and uncertified version of the same key
+will have the same fingerprint, so you can see that they match. You
+can instead use the \q{Fingerprint type} drop-down to ask for a
+different fingerprint to be shown for certified keys, which includes
+the certificate as part of the fingerprinted data. That way you can
+tell two certificates apart.
}
\b The comment attached to the key.
@@ -170,6 +183,92 @@ by the command, like this:
\c C:\PuTTY\pageant.exe d:\main.ppk -c C:\PuTTY\putty.exe
+\S{pageant-cmdline-openssh} Integrating with \i{Windows OpenSSH}
+
+Windows's own port of OpenSSH uses the same mechanism as Pageant to
+talk to its SSH agent (Windows named pipes). This means that Windows
+OpenSSH can talk directly to Pageant, if it knows where to find
+Pageant's named pipe.
+
+When Pageant starts up, it can optionally write out a file containing
+an OpenSSH configuration directive that tells the Windows \c{ssh.exe}
+where to find Pageant. If you include this file from your Windows SSH
+configuration, then \c{ssh.exe} should automatically use Pageant as
+its agent, so that you can keep your keys in one place and have both
+SSH clients able to use them.
+
+The option is \i\c{--openssh-config}, and you follow it with a filename.
+
+To refer to this file from your main OpenSSH configuration, you can
+use the \cq{Include} directive. For example, you might run Pageant
+like this (with your own username substituted, of course):
+
+\c pageant --openssh-config C:\Users\Simon\.ssh\pageant.conf
+
+and then add a directive like this to your main \cq{.ssh\\config} file
+(assuming that lives in the same directory that you just put
+\cw{pageant.conf}):
+
+\c Include pageant.conf
+
+\s{Note}: this technique only works with \e{Windows's} port of
+OpenSSH, which lives at \cw{C:\\Windows\\System32\\OpenSSH\\ssh.exe}
+if you have it installed. (If not, it can be installed as a Windows
+optional feature, e.g., via Settings > Apps & features > Optional
+features > Add a feature > OpenSSH Client.)
+
+There are other versions of OpenSSH for Windows, notably the one that
+comes with Windows \cw{git}. Those will likely not work with the same
+configuration, because they tend to depend on Unix emulation layers
+like MinGW or MSys, so they won't speak Windows native pathname syntax
+or understand named pipes. The above instructions will only work with
+Windows's own version of OpenSSH.
+
+So, if you want to use Windows \cw{git} with an SSH key held in
+Pageant, you'll have to set the environment variable \cw{GIT_SSH}, to
+point at a different program. You could point it at
+\cw{c:\\Windows\\System32\\OpenSSH\\ssh.exe} once you've done this
+setup \dash but it's just as easy to point it at Plink!
+
+\S{pageant-cmdline-unix} Unix-domain sockets: integrating with WSL 1
+
+Pageant can listen on the WinSock implementation of \q{Unix-domain
+sockets}. These interoperate with the Unix-domain sockets found in the
+original Windows Subsystem for Linux (now known as WSL 1). So if you
+ask Pageant to listen on one of these, then your WSL 1 processes can
+talk directly to Pageant.
+
+To configure this, run Pageant with the option \c{--unix}, followed
+with a pathname. Then, in WSL 1, set the environment variable
+\cw{SSH_AUTH_SOCK} to point at the WSL translation of that pathname.
+
+For example, you might run
+
+\c pageant --unix C:\Users\Simon\.ssh\agent.sock
+
+and in WSL 1, set the environment variable
+
+\c SSH_AUTH_SOCK=/mnt/c/Users/Simon/.ssh/agent.sock
+
+Alternatively, you can add a line to your \cw{.ssh/config} file inside
+WSL that says
+
+\c IdentityAgent /mnt/c/Users/Simon/.ssh/agent.sock
+
+although doing it like that may mean that \cw{ssh-add} commands won't
+find the agent, even though \cw{ssh} itself will.
+
+\s{Security note}: Unix-domain sockets are protected against access by
+other users by the file protections on their containing directory. So
+if your Windows machine is multiuser, make sure you create the socket
+inside a directory that other users can't access at all. (In fact,
+that's a good idea on general principles.)
+
+\s{Compatibility note}: WSL 2 processes cannot talk to Pageant by this
+mechanism, because WSL 2's Unix-domain sockets are managed by a
+separate Linux kernel, and not by the same kernel that WinSock talks
+to.
+
\S{pageant-cmdline-keylist} Starting with the key list visible
Start Pageant with the \i\c{--keylist} option to show the main window
diff --git a/doc/pgpkeys.but b/doc/pgpkeys.but
index 8fab6153..7dc62f89 100644
--- a/doc/pgpkeys.but
+++ b/doc/pgpkeys.but
@@ -56,25 +56,25 @@ The current issue of those keys are available for download from the
PuTTY website, and are also available on PGP keyservers using the key
IDs listed below.
-\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2018.asc}{\s{Master Key} (2018)}
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2021.asc}{\s{Master Key} (2021)}
-\dd RSA, 4096-bit. Key ID: \cw{76BC7FE4EBFD2D9E}. Fingerprint:
-\cw{24E1\_B1C5\_75EA\_3C9F\_F752\_\_A922\_76BC\_7FE4\_EBFD\_2D9E}
+\dd RSA, 3072-bit. Key ID: \cw{DD4355EAAC1119DE}. Fingerprint:
+\cw{A872\_D42F\_1660\_890F\_0E05\_223E\_DD43\_55EA\_AC11\_19DE}
-\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2018.asc}{\s{Release Key} (2018)}
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2021.asc}{\s{Release Key} (2021)}
-\dd RSA, 3072-bit. Key ID: \cw{6289A25F4AE8DA82}. Fingerprint:
-\cw{E273\_94AC\_A3F9\_D904\_9522\_\_E054\_6289\_A25F\_4AE8\_DA82}
+\dd RSA, 3072-bit. Key ID: \cw{E4F83EA2AA4915EC}. Fingerprint:
+\cw{2CF6\_134B\_D3F7\_7A65\_88EB\_D668\_E4F8\_3EA2\_AA49\_15EC}
-\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2018.asc}{\s{Snapshot Key} (2018)}
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2021.asc}{\s{Snapshot Key} (2021)}
-\dd RSA, 3072-bit. Key ID: \cw{38BA7229B7588FD1}. Fingerprint:
-\cw{C92B\_52E9\_9AB6\_1DDA\_33DB\_\_2B7A\_38BA\_7229\_B758\_8FD1}
+\dd RSA, 3072-bit. Key ID: \cw{B43979F89F446CFD}. Fingerprint:
+\cw{1FD3\_BCAC\_E532\_FBE0\_6A8C\_09E2\_B439\_79F8\_9F44\_6CFD}
-\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2018.asc}{\s{Secure Contact Key} (2018)}
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2021.asc}{\s{Secure Contact Key} (2021)}
-\dd RSA, 3072-bit. Key ID: \cw{657D487977F95C98}. Fingerprint:
-\cw{A680\_0082\_2998\_6E46\_22CA\_\_0E43\_657D\_4879\_77F9\_5C98}
+\dd RSA, 3072-bit. Key ID: \cw{012C59D4211BD62A}. Fingerprint:
+\cw{E30F\_1354\_2A04\_BE0E\_56F0\_5801\_012C\_59D4\_211B\_D62A}
\H{pgpkeys-security} Security details
@@ -169,6 +169,28 @@ generated keys.
The details of all previous keys are given here.
+\s{Keys generated in the 2018 rollover}
+
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2018.asc}{\s{Master Key} (2018)}
+
+\dd RSA, 4096-bit. Key ID: \cw{76BC7FE4EBFD2D9E}. Fingerprint:
+\cw{24E1\_B1C5\_75EA\_3C9F\_F752\_\_A922\_76BC\_7FE4\_EBFD\_2D9E}
+
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2018.asc}{\s{Release Key} (2018)}
+
+\dd RSA, 3072-bit. Key ID: \cw{6289A25F4AE8DA82}. Fingerprint:
+\cw{E273\_94AC\_A3F9\_D904\_9522\_\_E054\_6289\_A25F\_4AE8\_DA82}
+
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2018.asc}{\s{Snapshot Key} (2018)}
+
+\dd RSA, 3072-bit. Key ID: \cw{38BA7229B7588FD1}. Fingerprint:
+\cw{C92B\_52E9\_9AB6\_1DDA\_33DB\_\_2B7A\_38BA\_7229\_B758\_8FD1}
+
+\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2018.asc}{\s{Secure Contact Key} (2018)}
+
+\dd RSA, 3072-bit. Key ID: \cw{657D487977F95C98}. Fingerprint:
+\cw{A680\_0082\_2998\_6E46\_22CA\_\_0E43\_657D\_4879\_77F9\_5C98}
+
\s{Key generated in 2016} (when we first introduced the Secure Contact Key)
\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2016.asc}{\s{Secure Contact Key} (2016)}
diff --git a/doc/plink.but b/doc/plink.but
index 30dcead1..39b14f2b 100644
--- a/doc/plink.but
+++ b/doc/plink.but
@@ -41,7 +41,7 @@ use Plink:
\c C:\>plink
\c Plink: command-line connection utility
-\c Release 0.76
+\c Release 0.78
\c Usage: plink [options] [user@]host [command]
\c ("host" can also be a PuTTY saved session name)
\c Options:
@@ -61,7 +61,7 @@ use Plink:
\c -sercfg configuration-string (e.g. 19200,8,n,1,X)
\c Specify the serial configuration (serial only)
\c The following options only apply to SSH connections:
-\c -pw passw login with specified password
+\c -pwfile file login with password read from specified file
\c -D [listen-IP:]listen-port
\c Dynamic SOCKS-based port forwarding
\c -L [listen-IP:]listen-port:host:port
diff --git a/doc/pscp.but b/doc/pscp.but
index e816f3e5..96a2d4b9 100644
--- a/doc/pscp.but
+++ b/doc/pscp.but
@@ -39,7 +39,7 @@ use PSCP:
\c C:\>pscp
\c PuTTY Secure Copy client
-\c Release 0.76
+\c Release 0.78
\c Usage: pscp [options] [user@]host:source target
\c pscp [options] source [source...] [user@]host:target
\c pscp [options] -ls [user@]host:filespec
@@ -53,7 +53,7 @@ use PSCP:
\c -load sessname Load settings from saved session
\c -P port connect to specified port
\c -l user connect with specified username
-\c -pw passw login with specified password
+\c -pwfile file login with password read from specified file
\c -1 -2 force use of particular SSH protocol version
\c -ssh -ssh-connection
\c force use of particular SSH protocol variant
diff --git a/doc/pubkey.but b/doc/pubkey.but
index f40c9526..5ac59390 100644
--- a/doc/pubkey.but
+++ b/doc/pubkey.but
@@ -56,15 +56,15 @@ and convenience. See \k{pageant} for further details.
There is more than one \i{public-key algorithm} available. The most
common are \i{RSA} and \i{ECDSA}, but others exist, notably \i{DSA}
-(otherwise known as DSS), the USA's federal Digital Signature Standard.
+(otherwise known as \i{DSS}), the USA's federal Digital Signature Standard.
The key types supported by PuTTY are described in \k{puttygen-keytype}.
\H{pubkey-puttygen} Using \i{PuTTYgen}, the PuTTY key generator
PuTTYgen is a key generator. It \I{generating keys}generates pairs of
-public and private keys to be used with PuTTY, PSCP, and Plink, as well
-as the PuTTY authentication agent, Pageant (see \k{pageant}). PuTTYgen
-generates RSA, DSA, ECDSA, and EdDSA keys.
+public and private keys to be used with PuTTY, PSCP, PSFTP, and Plink,
+as well as the PuTTY authentication agent, Pageant (see \k{pageant}).
+PuTTYgen generates RSA, DSA, ECDSA, and EdDSA keys.
When you run PuTTYgen you will see a window where you have two main
choices: \q{Generate}, to generate a new public/private key pair, or
@@ -132,10 +132,13 @@ The \q{Number of bits} input box allows you to choose the strength
of the key PuTTYgen will generate.
\b For RSA and DSA, 2048 bits should currently be sufficient for most
-purposes.
+purposes. (Smaller keys of these types are no longer considered
+secure, and PuTTYgen will warn if you try to generate them.)
-\b For ECDSA, only 256, 384, and 521 bits are supported. (ECDSA offers
-equivalent security to RSA with smaller key sizes.)
+\b For ECDSA, only 256, 384, and 521 bits are supported, corresponding
+to \i{NIST}-standardised elliptic curves. (Elliptic-curve keys do not
+need as many bits as RSA keys for equivalent security, so these numbers
+are smaller than the RSA recommendations.)
\b For EdDSA, the only valid sizes are 255 bits (these keys are also
known as \q{\i{Ed25519}} and are commonly used) and 448 bits
@@ -145,6 +148,9 @@ the same as 255.)
\S{puttygen-primes} Selecting the \i{prime generation method}
+(This is entirely optional. Unless you know better, it's entirely
+sensible to skip this and use the default settings.)
+
On the \q{Key} menu, you can also optionally change the method for
generating the prime numbers used in the generated key. This is used
for RSA and DSA keys only. (The other key types don't require
@@ -154,9 +160,6 @@ The prime-generation method does not affect compatibility: a key
generated with any of these methods will still work with all the same
SSH servers.
-If you don't care about this, it's entirely sensible to leave it on the
-default setting.
-
The available methods are:
\b Use \i{probable primes} (fast)
@@ -177,6 +180,15 @@ are prime, because it generates the output number together with a
proof of its primality. This takes more effort, but it eliminates that
theoretical risk in the probabilistic method.
+There in one way in which PuTTYgen's \q{proven primes} method is not
+strictly better than its \q{probable primes} method. If you use
+PuTTYgen to generate an RSA key on a computer that is potentially
+susceptible to timing- or cache-based \i{side-channel attacks}, such
+as a shared computer, the \q{probable primes} method is designed to
+resist such attacks, whereas the \q{proven primes} methods are not.
+(This is only a concern for RSA keys; for other key types, primes
+are either not secret or not involved.)
+
You might choose to switch from probable to proven primes if you have
a local security standard that demands it, or if you don't trust the
probabilistic argument for the safety of the usual method.
@@ -230,9 +242,9 @@ a particular fingerprint. So some utilities, such as the Pageant key
list box (see \k{pageant-mainwin-keylist}) and the Unix \c{ssh-add}
utility, will list key fingerprints rather than the whole public key.
-By default, PuTTYgen will display fingerprints in the \q{SHA256}
-format. If you need to see the fingerprint in the older \q{MD5} format
-(which looks like \c{aa:bb:cc:...}), you can choose
+By default, PuTTYgen will display SSH-2 key fingerprints in the
+\q{SHA256} format. If you need to see the fingerprint in the older
+\q{MD5} format (which looks like \c{aa:bb:cc:...}), you can choose
\q{Show fingerprint as MD5} from the \q{Key} menu, but bear in mind
that this is less cryptographically secure; it may be feasible for
an attacker to create a key with the same fingerprint as yours.
@@ -298,6 +310,48 @@ a result.
\e{Do not forget your passphrase}. There is no way to recover it.
+\S{puttygen-cert} Adding a \i{certificate} to your key
+
+In some environments, user authentication keys can be signed in turn
+by a \q{certifying authority} (\q{CA} for short), and user accounts on
+an SSH server can be configured to automatically trust any key that's
+certified by the right signature.
+
+This can be a convenient setup if you have a very large number of
+servers. When you change your key pair, you might otherwise have to
+edit the \cw{authorized_keys} file on every server individually, to
+make them all accept the new key. But if instead you configure all
+those servers \e{once} to accept keys signed as yours by a CA, then
+when you change your public key, all you have to do is to get the new
+key certified by the same CA as before, and then all your servers will
+automatically accept it without needing individual reconfiguration.
+
+To get your key signed by a CA, you'll probably send the CA the new
+\e{public} key (not the private half), and get back a modified version
+of the public key with the certificate included.
+
+If you want to incorporate the certificate into your PPK file for
+convenience, you can use the \q{Add certificate to key} menu option in
+PuTTYgen's \q{Key} menu. This will give you a single file containing
+your private key and the certificate, which is everything you need to
+authenticate to a server prepared to accept that certificate.
+
+To remove the certificate again and restore the uncertified PPK file,
+there's also a \q{Remove certificate from key} option.
+
+(However, you don't \e{have} to incorporate the certificate into your
+PPK file. You can equally well use it separately, via the
+\q{Certificate to use with the private key} option in PuTTY itself.
+See \k{config-ssh-cert}. It's up to you which you find more
+convenient.)
+
+When the currently loaded key in PuTTYgen contains a certificate, the
+large \q{Public key for pasting} edit box (see \k{puttygen-pastekey})
+is replaced by a button that brings up an information box telling you
+about the certificate, such as who it certifies your key as belonging
+to, when it expires (if ever), and the fingerprint of the CA key that
+signed it in turn.
+
\S{puttygen-savepriv} Saving your private key to a disk file
Once you have generated a key, set a comment field and set a
@@ -389,8 +443,8 @@ These options only affect PPK version 3.
\dt Key derivation function
\dd The variant of the \i{Argon2} key derivation function to use.
-You might change this if you consider your exposure to side-channel
-attacks to be different to the norm.
+You might change this if you consider your exposure to \i{side-channel
+attacks} to be different to the norm.
\dt Memory to use for passphrase hash
@@ -469,6 +523,83 @@ you have generated an SSH-1 private key using OpenSSH or
Hence, the export options are not available if you have generated an
SSH-1 key.
+\S{puttygen-cli} PuTTYgen command-line configuration
+
+PuTTYgen supports a set of command-line options to configure many of
+the same settings you can select in the GUI. This allows you to start
+it up with your own preferences ready-selected, which might be useful
+if you generate a lot of keys. (For example, you could make a Windows
+shortcut that runs PuTTYgen with some command line options, or a batch
+file or Powershell script that you could distribute to a whole
+organisation containing your local standards.)
+
+The options supported on the command line are:
+
+\dt \cw{\-t} \e{keytype}
+
+\dd Type of key to generate. You can select \c{rsa}, \c{dsa},
+\c{ecdsa}, \c{eddsa}, \c{ed25519}, \c{ed448}, or \c{rsa1}.
+See \k{puttygen-keytype}.
+
+\dt \cw{\-b} \e{bits}
+
+\dd Size of the key to generate, in bits. See \k{puttygen-strength}.
+
+\dt \cw{\-\-primes} \e{method}
+
+\dd Method for generating prime numbers. You can select \c{probable},
+\c{proven}, and \c{proven-even}. See \k{puttygen-primes}.
+
+\dt \cw{\-\-strong-rsa}
+
+\dd When generating an RSA key, make sure the prime factors of the key
+modulus are \q{strong primes}. See \k{puttygen-primes}.
+
+\dt \cw{\-\-ppk-param} \e{key}\cw{=}\e{value}\cw{,}...
+
+\dd Allows setting all the same details of the PPK save file format
+described in \k{puttygen-save-params}.
+
+\lcont{
+
+Aspects to change are specified as a series of \e{key}\cw{=}\e{value} pairs
+separated by commas. The \e{key}s are:
+
+\dt \cw{version}
+
+\dd The PPK format version: either \cw{3} or \cw{2}.
+
+\dt \cw{kdf}
+
+\dd The variant of Argon2 to use: \cw{argon2id}, \cw{argon2i}, and
+\cw{argon2d}.
+
+\dt \cw{memory}
+
+\dd The amount of memory needed to decrypt the key, in Kbyte.
+
+\dt \cw{time}
+
+\dd Specifies how much time is required to attempt decrypting the key,
+in milliseconds.
+
+\dt \cw{passes}
+
+\dd Alternative to \cw{time}: specifies the number of hash passes
+required to attempt decrypting the key.
+
+\dt \cw{parallelism}
+
+\dd Number of parallelisable threads that can be used to decrypt the
+key.
+
+}
+
+\dt \cw{\-E} \e{fptype}
+
+\dd Algorithm to use when displaying key fingerprints. You can
+select \c{sha256} or \c{md5}. See \k{puttygen-fingerprint}.
+
\H{pubkey-gettingready} Getting ready for public key authentication
Connect to your SSH server using PuTTY with the SSH protocol. When the
diff --git a/doc/pubkeyfmt.but b/doc/pubkeyfmt.but
index 78da4885..836ed527 100644
--- a/doc/pubkeyfmt.but
+++ b/doc/pubkeyfmt.but
@@ -7,7 +7,7 @@ In this appendix, binary data structures are described using data type
representations such as \cq{uint32}, \cq{string} and \cq{mpint} as
used in the SSH protocol standards themselves. These are defined
authoritatively by
-\W{https://tools.ietf.org/html/rfc4251#section-5}{RFC 4251 section 5},
+\W{https://www.rfc-editor.org/rfc/rfc4251#section-5}{RFC 4251 section 5},
\q{Data Type Representations Used in the SSH Protocols}.
\H{ppk-overview} Overview
@@ -86,7 +86,7 @@ can contain any byte values other than 13 and 10 (CR and LF).
The next part of the file gives the public key. This is stored
unencrypted but base64-encoded
-(\W{https://tools.ietf.org/html/rfc4648}{RFC 4648}), and is preceded
+(\W{https://www.rfc-editor.org/rfc/rfc4648}{RFC 4648}), and is preceded
by a header line saying how many lines of base64 data are shown,
looking like this:
@@ -241,7 +241,7 @@ of \e{y} in the group generated by \e{g} mod \e{p}.
\S{ppk-privkey-ecdsa} NIST elliptic-curve keys
-NIST elliptic-curve keys are stored using one of the following
+\i{NIST} elliptic-curve keys are stored using one of the following
\s{algorithm-name} values, each corresponding to a different elliptic
curve and key size:
diff --git a/doc/sshnames.but b/doc/sshnames.but
index 6cf82f73..a00ac678 100644
--- a/doc/sshnames.but
+++ b/doc/sshnames.but
@@ -70,7 +70,7 @@ They have been superseded by \cw{arcfour128} and \cw{arcfour256}.
The SSH agent protocol, which is only specified in an Internet-Draft
at the time of writing
-(\W{https://tools.ietf.org/html/draft-miller-ssh-agent}\cw{draft-miller-ssh-agent}),
+(\W{https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent}\cw{draft-miller-ssh-agent}),
defines an extension mechanism. These names can be sent in an
\cw{SSH_AGENTC_EXTENSION} message.
diff --git a/doc/udp.but b/doc/udp.but
index b3570d5d..12b3392b 100644
--- a/doc/udp.but
+++ b/doc/udp.but
@@ -28,9 +28,9 @@ platform-generic modules. The Unix-specific modules are all in the
\c{unix} subdirectory; the Windows-specific modules are in the
\c{windows} subdirectory.
-All the modules in the main source directory - notably \e{all} of
-the code for the various back ends - are platform-generic. We want
-to keep them that way.
+All the modules in the main source directory and other
+subdirectories - notably \e{all} of the code for the various back
+ends - are platform-generic. We want to keep them that way.
This also means you should stick to the C semantics guaranteed by the
C standard: try not to make assumptions about the precise size of
@@ -171,9 +171,7 @@ to. C++ friendliness is really a side benefit.)
We want PuTTY to continue being pure C, at least in the
platform-independent parts and the currently existing ports. Patches
which switch the Makefiles to compile it as C++ and start using
-classes will not be accepted. Also, in particular, we disapprove of
-\cw{//} comments, at least for the moment. (Perhaps once C99 becomes
-genuinely widespread we might be more lenient.)
+classes will not be accepted.
The one exception: a port to a new platform may use languages other
than C if they are necessary to code on that platform. If your
@@ -278,16 +276,17 @@ should be aware that you might be re-entered if a network event
comes in and is passed on to our window procedure by the
\cw{MessageBox()} message loop.
-Also, the front ends (in particular Windows Plink) can use multiple
-threads if they like. However, Windows Plink keeps \e{very} tight
-control of its auxiliary threads, and uses them pretty much
-exclusively as a form of \cw{select()}. Pretty much all the code
-outside \cw{windows/winplink.c} is \e{only} ever called from the one
-primary thread; the others just loop round blocking on file handles
-and send messages to the main thread when some real work needs
-doing. This is not considered a portability hazard because that bit
-of \cw{windows/winplink.c} will need rewriting on other platforms in
-any case.
+Also, the front ends can use multiple threads if they like. For
+example, the Windows front-end code spawns subthreads to deal with
+bidirectional blocking I/O on non-network streams such as Windows
+pipes. However, it keeps tight control of its auxiliary threads, and
+uses them only for that one purpose, as a form of \cw{select()}.
+Pretty much all the code outside \cw{windows/handle-io.c} is \e{only}
+ever called from the one primary thread; the others just loop round
+blocking on file handles, and signal the main thread (via Windows
+event objects) when some real work needs doing. This is not considered
+a portability hazard because that code is already Windows-specific and
+needs rewriting on other platforms.
One important consequence of this: PuTTY has only one thread in
which to do everything. That \q{everything} may include managing
@@ -333,51 +332,11 @@ on a 640\u00D7{x}480 display. If you're adding controls to either of
these boxes and you find yourself wanting to increase the size of
the whole box, \e{don't}. Split it into more panels instead.
-\H{udp-makefiles-auto} Automatically generated \cw{Makefile}s
-
-PuTTY is intended to compile on multiple platforms, and with
-multiple compilers. It would be horrifying to try to maintain a
-single \cw{Makefile} which handled all possible situations, and just
-as painful to try to directly maintain a set of matching
-\cw{Makefile}s for each different compilation environment.
-
-Therefore, we have moved the problem up by one level. In the PuTTY
-source archive is a file called \c{Recipe}, which lists which source
-files combine to produce which binaries; and there is also a script
-called \cw{mkfiles.pl}, which reads \c{Recipe} and writes out the
-real \cw{Makefile}s. (The script also reads all the source files and
-analyses their dependencies on header files, so we get an extra
-benefit from doing it this way, which is that we can supply correct
-dependency information even in environments where it's difficult to
-set up an automated \c{make depend} phase.)
-
-You should \e{never} edit any of the PuTTY \cw{Makefile}s directly.
-They are not stored in our source repository at all. They are
-automatically generated by \cw{mkfiles.pl} from the file \c{Recipe}.
-
-If you need to add a new object file to a particular binary, the
-right thing to do is to edit \c{Recipe} and re-run \cw{mkfiles.pl}.
-This will cause the new object file to be added in every tool that
-requires it, on every platform where it matters, in every
-\cw{Makefile} to which it is relevant, \e{and} to get all the
-dependency data right.
-
-If you send us a patch that modifies one of the \cw{Makefile}s, you
-just waste our time, because we will have to convert it into a
-change to \c{Recipe}. If you send us a patch that modifies \e{all}
-of the \cw{Makefile}s, you will have wasted a lot of \e{your} time
-as well!
-
-(There is a comment at the top of every \cw{Makefile} in the PuTTY
-source archive saying this, but many people don't seem to read it,
-so it's worth repeating here.)
-
-\H{udp-ssh-coroutines} Coroutines in the SSH code
-
-Large parts of the code in the various SSH modules (in fact most of
-the protocol layers) are structured using a set of macros that
-implement (something close to) Donald Knuth's \q{coroutines} concept
-in C.
+\H{udp-ssh-coroutines} Coroutines in protocol code
+
+Large parts of the code in modules implementing wire protocols
+(mainly SSH) are structured using a set of macros that implement
+(something close to) Donald Knuth's \q{coroutines} concept in C.
Essentially, the purpose of these macros are to arrange that a
function can call \cw{crReturn()} to return to its caller, and the
@@ -388,7 +347,7 @@ This means that any local (automatic) variables declared in such a
function will be corrupted every time you call \cw{crReturn}. If you
need a variable to persist for longer than that, you \e{must} make it
a field in some appropriate structure containing the persistent state
-of the coroutine \dash typically the main state structure for an SSH
+of the coroutine \dash typically the main state structure for a
protocol layer.
See
@@ -544,13 +503,13 @@ call sites. Instead, what we generally do in this code base is to
write a set of \cw{static inline} wrapper functions in the same header
file that defined the \cw{MyAbstraction} structure types, like this:
-\c static MyAbstraction *myabs_new(const MyAbstractionVtable *vt)
+\c static inline MyAbstraction *myabs_new(const MyAbstractionVtable *vt)
\c { return vt->new(vt); }
-\c static void myabs_free(MyAbstraction *myabs)
+\c static inline void myabs_free(MyAbstraction *myabs)
\c { myabs->vt->free(myabs); }
-\c static void myimpl_modify(MyAbstraction *myabs, unsigned param)
+\c static inline void myimpl_modify(MyAbstraction *myabs, unsigned param)
\c { myabs->vt->modify(myabs, param); }
-\c static unsigned myimpl_query(MyAbstraction *myabs, unsigned param)
+\c static inline unsigned myimpl_query(MyAbstraction *myabs, unsigned param)
\c { return myabs->vt->query(myabs, param); }
And now call sites can use those reasonably clean-looking wrapper
@@ -598,8 +557,8 @@ based on the offset within that structure of the field called
This system is flexible enough to permit \q{multiple inheritance}, or
rather, multiple \e{implementation}: having one object type implement
-more than one trait. For example, the \cw{Proxy} type implements both
-the \cw{Socket} trait and the \cw{Plug} trait that connects to it,
+more than one trait. For example, the \cw{ProxySocket} type implements
+both the \cw{Socket} trait and the \cw{Plug} trait that connects to it,
because it has to act as an adapter between another instance of each
of those types.
@@ -791,46 +750,6 @@ other two full implementation vtables.
}
-\H{udp-compile-once} Single compilation of each source file
-
-The PuTTY build system for any given platform works on the following
-very simple model:
-
-\b Each source file is compiled precisely once, to produce a single
-object file.
-
-\b Each binary is created by linking together some combination of
-those object files.
-
-Therefore, if you need to introduce functionality to a particular
-module which is only available in some of the tool binaries (for
-example, a cryptographic proxy authentication mechanism which needs
-to be left out of PuTTYtel to maintain its usability in
-crypto-hostile jurisdictions), the \e{wrong} way to do it is by
-adding \cw{#ifdef}s in (say) \cw{proxy.c}. This would require
-separate compilation of \cw{proxy.c} for PuTTY and PuTTYtel, which
-means that the entire \cw{Makefile}-generation architecture (see
-\k{udp-makefiles-auto}) would have to be significantly redesigned.
-Unless you are prepared to do that redesign yourself, \e{and}
-guarantee that it will still port to any future platforms we might
-decide to run on, you should not attempt this!
-
-The \e{right} way to introduce a feature like this is to put the new
-code in a separate source file, and (if necessary) introduce a
-second new source file defining the same set of functions, but
-defining them as stubs which don't provide the feature. Then the
-module whose behaviour needs to vary (\cw{proxy.c} in this example)
-can call the functions defined in these two modules, and it will
-either provide the new feature or not provide it according to which
-of your new modules it is linked with.
-
-Of course, object files are never shared \e{between} platforms; so
-it is allowable to use \cw{#ifdef} to select between platforms. This
-happens in \cw{puttyps.h} (choosing which of the platform-specific
-include files to use), and also in \cw{misc.c} (the Windows-specific
-\q{Minefield} memory diagnostic system). It should be used
-sparingly, though, if at all.
-
\H{udp-perfection} Do as we say, not as we do
The current PuTTY code probably does not conform strictly to \e{all}
diff --git a/doc/using.but b/doc/using.but
index 02a67808..5865ac95 100644
--- a/doc/using.but
+++ b/doc/using.but
@@ -838,17 +838,23 @@ any case.)
This option is equivalent to the port number control in the Session
panel of the PuTTY configuration box (see \k{config-hostname}).
-\S2{using-cmdline-pw} \i\c{-pw}: specify a \i{password}
+\S2{using-cmdline-pw} \i\c{-pwfile} and \i\c{-pw}: specify a \i{password}
A simple way to automate a remote login is to supply your password
-on the command line. This is \e{not recommended} for reasons of
-security. If you possibly can, we recommend you set up public-key
-authentication instead. See \k{pubkey} for details.
+on the command line.
-Note that the \c{-pw} option only works when you are using the SSH
-protocol. Due to fundamental limitations of Telnet, Rlogin, and
-SUPDUP, these protocols do not support automated password
-authentication.
+The \c{-pwfile} option takes a file name as an argument. The first
+line of text in that file will be used as your password.
+
+The \c{-pw} option takes the password itself as an argument. This is
+\s{NOT SECURE} if anybody else uses the same computer, because the
+whole command line (including the password) is likely to show up if
+another user lists the running processes. \c{-pw} is retained for
+backwards compatibility only; you should use \c{-pwfile} instead.
+
+Note that these options only work when you are using the SSH protocol.
+Due to fundamental limitations of Telnet, Rlogin, and SUPDUP, these
+protocols do not support automated password authentication.
\S2{using-cmdline-agentauth} \i\c{-agent} and \i\c{-noagent}:
control use of Pageant for authentication
@@ -941,15 +947,19 @@ this:
\c plink host1.example.com -nc host2.example.com:1234
-You might want to use this feature if you needed to make an SSH
-connection to a target host which you can only reach by going
-through a proxy host, and rather than using port forwarding you
-prefer to use the local proxy feature (see \k{config-proxy-type} for
-more about local proxies). In this situation you might select
-\q{Local} proxy type, set your local proxy command to be \cq{plink
-%proxyhost -nc %host:%port}, enter the target host name on the
-Session panel, and enter the directly reachable proxy host name on
-the Proxy panel.
+This can be useful if you're trying to make a connection to a target
+host which you can only reach by SSH forwarding through a proxy host.
+One way to do this would be to have an existing SSH connection to the
+proxy host, with a port forwarding, but if you prefer to have the
+connection started on demand as needed, then this approach can also
+work.
+
+However, this does depend on the program \e{using} the proxy being
+able to run a subprocess in place of making a network connection.
+PuTTY itself can do this using the \q{Local} proxy type, but there's a
+built-in more flexible way using the \q{SSH} proxy type. (See
+\k{config-proxy-type} for a description of both.) So this feature is
+probably most useful with another client program as the end user.
This feature is only available in SSH protocol version 2 (since the
version 1 protocol assumes you will always want to run a shell). It
@@ -1014,6 +1024,19 @@ This option is equivalent to the \q{Private key file for
authentication} box in the Auth panel of the PuTTY configuration box
(see \k{config-ssh-privkey}).
+\S2{using-cmdline-cert} \i\c{-cert}: specify an SSH \i{certificate}
+
+The \c{-cert} option allows you to specify the name of a certificate
+file containing a signed version of your public key. If you specify
+this option, PuTTY will present that certificate in place of the plain
+public key, whenever it tries to authenticate with a key that matches.
+(This applies whether the key is stored in Pageant or loaded directly
+from a file by PuTTY.)
+
+This option is equivalent to the \q{Certificate to use with the
+private key} box in the Auth panel of the PuTTY configuration box (see
+\k{config-ssh-cert}).
+
\S2{using-cmdline-no-trivial-auth} \i\c{-no-trivial-auth}: disconnect
if SSH authentication succeeds trivially
@@ -1152,3 +1175,12 @@ the extra protection), so it's reasonable to want to run Pageant but
not PuTTY with the ACL restrictions. You can force Pageant to start
subsidiary PuTTY processes with a restricted ACL if you also pass the
\i\c{-restrict-putty-acl} option.
+
+\S2{using-cmdline-host-ca} \i{\c{-host-ca}}: launch the
+\I{certificate}host CA configuration
+
+If you start PuTTY with the \c{-host-ca} option, it will not launch a
+session at all. Instead, it will just display the configuration dialog
+box for host certification authorities, as described in
+\k{config-ssh-kex-cert}. When you dismiss that dialog box, PuTTY will
+terminate.
diff --git a/ecc.c b/ecc.c
deleted file mode 100644
index 72f9bfe5..00000000
--- a/ecc.c
+++ /dev/null
@@ -1,1167 +0,0 @@
-#include <assert.h>
-
-#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/ecc.h b/ecc.h
deleted file mode 100644
index 96eebdf0..00000000
--- a/ecc.h
+++ /dev/null
@@ -1,243 +0,0 @@
-#ifndef PUTTY_ECC_H
-#define PUTTY_ECC_H
-
-/*
- * Arithmetic functions for the various kinds of elliptic curves used
- * by PuTTY's public-key cryptography.
- *
- * All of these elliptic curves are over the finite field whose order
- * is a large prime p. (Elliptic curves over a field of order 2^n are
- * also known, but PuTTY currently has no need of them.)
- */
-
-/* ----------------------------------------------------------------------
- * Weierstrass curves (or rather, 'short form' Weierstrass curves).
- *
- * A curve in this form is defined by two parameters a,b, and the
- * non-identity points on the curve are represented by (x,y) (the
- * 'affine coordinates') such that y^2 = x^3 + ax + b.
- *
- * The identity element of the curve's group is an additional 'point
- * at infinity', which is considered to be the third point on the
- * intersection of the curve with any vertical line. Hence, the
- * inverse of the point (x,y) is (x,-y).
- */
-
-/*
- * Create and destroy Weierstrass curve data structures. The mandatory
- * parameters to the constructor are the prime modulus p, and the
- * curve parameters a,b.
- *
- * 'nonsquare_mod_p' is an optional extra parameter, only needed by
- * ecc_edwards_point_new_from_y which has to take a modular square
- * root. You can pass it as NULL if you don't need that function.
- */
-WeierstrassCurve *ecc_weierstrass_curve(
- mp_int *p, mp_int *a, mp_int *b, mp_int *nonsquare_mod_p);
-void ecc_weierstrass_curve_free(WeierstrassCurve *);
-
-/*
- * Create points on a Weierstrass curve, given the curve.
- *
- * point_new_identity returns the special identity point.
- * point_new(x,y) returns the non-identity point with the given affine
- * coordinates.
- *
- * point_new_from_x constructs a non-identity point given only the
- * x-coordinate, by using the curve equation to work out what y has to
- * be. Of course the equation only tells you y^2, so it only
- * determines y up to sign; the parameter desired_y_parity controls
- * which of the two values of y you get, by saying whether you'd like
- * its minimal non-negative residue mod p to be even or odd. (Of
- * course, since p itself is odd, exactly one of y and p-y is odd.)
- * This function has to take a modular square root, so it will only
- * work if you passed in a non-square mod p when constructing the
- * curve.
- */
-WeierstrassPoint *ecc_weierstrass_point_new_identity(WeierstrassCurve *curve);
-WeierstrassPoint *ecc_weierstrass_point_new(
- WeierstrassCurve *curve, mp_int *x, mp_int *y);
-WeierstrassPoint *ecc_weierstrass_point_new_from_x(
- WeierstrassCurve *curve, mp_int *x, unsigned desired_y_parity);
-
-/* Memory management: copy and free points. */
-void ecc_weierstrass_point_copy_into(
- WeierstrassPoint *dest, WeierstrassPoint *src);
-WeierstrassPoint *ecc_weierstrass_point_copy(WeierstrassPoint *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/fuzzterm.c b/fuzzterm.c
deleted file mode 100644
index f53c0a9c..00000000
--- a/fuzzterm.c
+++ /dev/null
@@ -1,215 +0,0 @@
-#include <stddef.h>
-#include <stdlib.h>
-#include <stdio.h>
-
-#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/icons/Makefile b/icons/Makefile
index 3bdba19c..3e3ea456 100644
--- a/icons/Makefile
+++ b/icons/Makefile
@@ -13,18 +13,21 @@ PNGS = $(patsubst %.pam,%.png,$(PAMS))
MONOPNGS = $(patsubst %.pam,%.png,$(MONOPAMS))
TRUEPNGS = $(patsubst %.pam,%.png,$(TRUEPAMS))
+SVGS = $(patsubst %,%.svg,$(ICONS))
+
ICOS = putty.ico puttygen.ico pscp.ico pageant.ico pageants.ico puttycfg.ico \
- puttyins.ico
+ puttyins.ico pterm.ico ptermcfg.ico
ICNS = PuTTY.icns Pterm.icns
CICONS = xpmputty.c xpmpucfg.c xpmpterm.c xpmptcfg.c
base: icos cicons
-all: pngs monopngs base icns # truepngs currently disabled by default
+all: pngs monopngs base icns svgs # truepngs currently disabled by default
pngs: $(PNGS)
monopngs: $(MONOPNGS)
truepngs: $(TRUEPNGS)
+svgs: $(SVGS)
icos: $(ICOS)
icns: $(ICNS)
@@ -46,6 +49,9 @@ $(MONOPAMS): %.pam: mkicon.py
$(TRUEPAMS): %.pam: mkicon.py
./mkicon.py -T $(MODE) $(join $(subst -, ,$(subst -true,,$(basename $@))),_icon) $@
+$(SVGS): %.svg: mksvg.py
+ ./mksvg.py $(patsubst %.svg,%_icon,$@) -o $@
+
putty.ico: putty-16.png putty-32.png putty-48.png \
putty-16-mono.png putty-32-mono.png putty-48-mono.png
./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@
@@ -69,6 +75,14 @@ pscp.ico: pscp-16.png pscp-32.png pscp-48.png \
pscp-16-mono.png pscp-32-mono.png pscp-48-mono.png
./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@
+pterm.ico: pterm-16.png pterm-32.png pterm-48.png \
+ pterm-16-mono.png pterm-32-mono.png pterm-48-mono.png
+ ./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@
+
+ptermcfg.ico: ptermcfg-16.png ptermcfg-32.png ptermcfg-48.png \
+ ptermcfg-16-mono.png ptermcfg-32-mono.png ptermcfg-48-mono.png
+ ./icon.pl -4 $(filter-out %-mono.png, $^) -1 $(filter %-mono.png, $^) > $@
+
# Because the installer icon makes heavy use of brown when drawing
# the cardboard box, it's worth having 8-bit versions of it in
# addition to the 4- and 1-bit ones.
diff --git a/icons/mksvg.py b/icons/mksvg.py
new file mode 100755
index 00000000..f29ff25b
--- /dev/null
+++ b/icons/mksvg.py
@@ -0,0 +1,938 @@
+#!/usr/bin/env python3
+
+import argparse
+import itertools
+import math
+import os
+import sys
+from fractions import Fraction
+
+import xml.etree.cElementTree as ET
+
+# Python code which draws the PuTTY icon components in SVG.
+
+def makegroup(*objects):
+ if len(objects) == 1:
+ return objects[0]
+ g = ET.Element("g")
+ for obj in objects:
+ g.append(obj)
+ return g
+
+class Container:
+ "Empty class for keeping things in."
+ pass
+
+class SVGthing(object):
+ def __init__(self):
+ self.fillc = "none"
+ self.strokec = "none"
+ self.strokewidth = 0
+ self.strokebehind = False
+ self.clipobj = None
+ self.props = Container()
+ def fmt_colour(self, rgb):
+ return "#{0:02x}{1:02x}{2:02x}".format(*rgb)
+ def fill(self, colour):
+ self.fillc = self.fmt_colour(colour)
+ def stroke(self, colour, width=1, behind=False):
+ self.strokec = self.fmt_colour(colour)
+ self.strokewidth = width
+ self.strokebehind = behind
+ def clip(self, obj):
+ self.clipobj = obj
+ def styles(self, elt, styles):
+ elt.attrib["style"] = ";".join("{}:{}".format(k,v)
+ for k,v in sorted(styles.items()))
+ def add_clip_paths(self, container, idents, X, Y):
+ if self.clipobj:
+ self.clipobj.identifier = next(idents)
+ clipelt = self.clipobj.render_thing(X, Y)
+ clippath = ET.Element("clipPath")
+ clippath.attrib["id"] = self.clipobj.identifier
+ clippath.append(clipelt)
+ container.append(clippath)
+ return True
+ return False
+ def render(self, X, Y, with_styles=True):
+ elt = self.render_thing(X, Y)
+ if self.clipobj:
+ elt.attrib["clip-path"] = "url(#{})".format(
+ self.clipobj.identifier)
+ estyles = {"fill": self.fillc}
+ sstyles = {"stroke": self.strokec}
+ if self.strokewidth:
+ sstyles["stroke-width"] = "{:g}".format(self.strokewidth)
+ sstyles["stroke-linecap"] = "round"
+ sstyles["stroke-linejoin"] = "round"
+ if not self.strokebehind:
+ estyles.update(sstyles)
+ if with_styles:
+ self.styles(elt, estyles)
+ if not self.strokebehind:
+ return elt
+ selt = self.render_thing(X, Y)
+ if with_styles:
+ self.styles(selt, sstyles)
+ return makegroup(selt, elt)
+ def bbox(self):
+ it = self.bb_iter()
+ xmin, ymin = xmax, ymax = next(it)
+ for x, y in it:
+ xmin = min(x, xmin)
+ xmax = max(x, xmax)
+ ymin = min(y, ymin)
+ ymax = max(y, ymax)
+ r = self.strokewidth / 2.0
+ xmin -= r
+ ymin -= r
+ xmax += r
+ ymax += r
+ if self.clipobj:
+ x0, y0, x1, y1 = self.clipobj.bbox()
+ xmin = max(x0, xmin)
+ xmax = min(x1, xmax)
+ ymin = max(y0, ymin)
+ ymax = min(y1, ymax)
+ return xmin, ymin, xmax, ymax
+
+class SVGpath(SVGthing):
+ def __init__(self, pointlists, closed=True):
+ super().__init__()
+ self.pointlists = pointlists
+ self.closed = closed
+ def bb_iter(self):
+ for points in self.pointlists:
+ for x,y,on in points:
+ yield x,y
+ def render_thing(self, X, Y):
+ pathcmds = []
+
+ for points in self.pointlists:
+ while not points[-1][2]:
+ points = points[1:] + [points[0]]
+
+ piter = iter(points)
+
+ if self.closed:
+ xp, yp, _ = points[-1]
+ pathcmds.extend(["M", X+xp, Y-yp])
+ else:
+ xp, yp, on = next(piter)
+ assert on, "Open paths must start with an on-curve point"
+ pathcmds.extend(["M", X+xp, Y-yp])
+
+ for x, y, on in piter:
+ if isinstance(on, type(())):
+ assert on[0] == "arc"
+ _, rx, ry, rotation, large, sweep = on
+ pathcmds.extend(["a",
+ rx, ry, rotation,
+ 1 if large else 0,
+ 1 if sweep else 0,
+ x-xp, -(y-yp)])
+ elif not on:
+ x0, y0 = x, y
+ x1, y1, on = next(piter)
+ assert not on
+ x, y, on = next(piter)
+ assert on
+ pathcmds.extend(["c", x0-xp, -(y0-yp),
+ ",", x1-xp, -(y1-yp),
+ ",", x-xp, -(y-yp)])
+ elif x == xp:
+ pathcmds.extend(["v", -(y-yp)])
+ elif x == xp:
+ pathcmds.extend(["h", x-xp])
+ else:
+ pathcmds.extend(["l", x-xp, -(y-yp)])
+
+ xp, yp = x, y
+
+ if self.closed:
+ pathcmds.append("z")
+
+ path = ET.Element("path")
+ path.attrib["d"] = " ".join(str(cmd) for cmd in pathcmds)
+ return path
+
+class SVGrect(SVGthing):
+ def __init__(self, x0, y0, x1, y1):
+ super().__init__()
+ self.points = x0, y0, x1, y1
+ def bb_iter(self):
+ x0, y0, x1, y1 = self.points
+ return iter([(x0,y0), (x1,y1)])
+ def render_thing(self, X, Y):
+ x0, y0, x1, y1 = self.points
+ rect = ET.Element("rect")
+ rect.attrib["x"] = "{:g}".format(min(X+x0,X+x1))
+ rect.attrib["y"] = "{:g}".format(min(Y-y0,Y-y1))
+ rect.attrib["width"] = "{:g}".format(abs(x0-x1))
+ rect.attrib["height"] = "{:g}".format(abs(y0-y1))
+ return rect
+
+class SVGpoly(SVGthing):
+ def __init__(self, points):
+ super().__init__()
+ self.points = points
+ def bb_iter(self):
+ return iter(self.points)
+ def render_thing(self, X, Y):
+ poly = ET.Element("polygon")
+ poly.attrib["points"] = " ".join("{:g},{:g}".format(X+x,Y-y)
+ for x,y in self.points)
+ return poly
+
+class SVGgroup(object):
+ def __init__(self, objects, translations=[]):
+ translations = translations + (
+ [(0,0)] * (len(objects)-len(translations)))
+ self.contents = list(zip(objects, translations))
+ self.props = Container()
+ def render(self, X, Y):
+ return makegroup(*[obj.render(X+x, Y-y)
+ for obj, (x,y) in self.contents])
+ def add_clip_paths(self, container, idents, X, Y):
+ toret = False
+ for obj, (x,y) in self.contents:
+ if obj.add_clip_paths(container, idents, X+x, Y-y):
+ toret = True
+ return toret
+ def bbox(self):
+ it = ((x,y) + obj.bbox() for obj, (x,y) in self.contents)
+ x, y, xmin, ymin, xmax, ymax = next(it)
+ xmin = x+xmin
+ ymin = y+ymin
+ xmax = x+xmax
+ ymax = y+ymax
+ for x, y, x0, y0, x1, y1 in it:
+ xmin = min(x+x0, xmin)
+ xmax = max(x+x1, xmax)
+ ymin = min(y+y0, ymin)
+ ymax = max(y+y1, ymax)
+ return (xmin, ymin, xmax, ymax)
+
+class SVGtranslate(object):
+ def __init__(self, obj, translation):
+ self.obj = obj
+ self.tx, self.ty = translation
+ def render(self, X, Y):
+ return self.obj.render(X+self.tx, Y+self.ty)
+ def add_clip_paths(self, container, idents, X, Y):
+ return self.obj.add_clip_paths(container, idents, X+self.tx, Y-self.ty)
+ def bbox(self):
+ xmin, ymin, xmax, ymax = self.obj.bbox()
+ return xmin+self.tx, ymin+self.ty, xmax+self.tx, ymax+self.ty
+
+# Code to actually draw pieces of icon. These don't generally worry
+# about positioning within a rectangle; they just draw at a standard
+# location, return some useful coordinates, and leave composition
+# to other pieces of code.
+
+def sysbox(size):
+ # The system box of the computer.
+
+ height = 3.6*size
+ width = 16.51*size
+ depth = 2*size
+ highlight = 1*size
+
+ floppystart = 19*size # measured in half-pixels
+ floppyend = 29*size # measured in half-pixels
+ floppybottom = highlight
+ floppyrheight = 0.7 * size
+ floppyheight = floppyrheight
+ if floppyheight < 1:
+ floppyheight = 1
+ floppytop = floppybottom + floppyheight
+
+ background_coords = [
+ (0,0), (width,0), (width+depth,depth),
+ (width+depth,height+depth), (depth,height+depth), (0,height)]
+ background = SVGpoly(background_coords)
+ background.fill(greypix(0.75))
+
+ hl_dark = SVGpoly([
+ (highlight,0), (highlight,highlight), (width-highlight,highlight),
+ (width-highlight,height-highlight), (width+depth,height+depth),
+ (width+depth,depth), (width,0)])
+ hl_dark.fill(greypix(0.5))
+
+ hl_light = SVGpoly([
+ (0,highlight), (highlight,highlight), (highlight,height-highlight),
+ (width-highlight,height-highlight), (width+depth,height+depth),
+ (width+depth-highlight,height+depth), (width-highlight,height),
+ (0,height)])
+ hl_light.fill(cW)
+
+ floppy = SVGrect(floppystart/2.0, floppybottom,
+ floppyend/2.0, floppytop)
+ floppy.fill(cK)
+
+ outline = SVGpoly(background_coords)
+ outline.stroke(cK, width=0.5)
+
+ toret = SVGgroup([background, hl_dark, hl_light, floppy, outline])
+ toret.props.sysboxheight = height
+ toret.props.borderthickness = 1 # FIXME
+ return toret
+
+def monitor(size):
+ # The computer's monitor.
+
+ height = 9.5*size
+ width = 11.5*size
+ surround = 1*size
+ botsurround = 2*size
+ sheight = height - surround - botsurround
+ swidth = width - 2*surround
+ depth = 2*size
+ highlight = surround/2
+ shadow = 0.5*size
+
+ background_coords = [
+ (0,0), (width,0), (width+depth,depth),
+ (width+depth,height+depth), (depth,height+depth), (0,height)]
+ background = SVGpoly(background_coords)
+ background.fill(greypix(0.75))
+
+ hl0_dark = SVGpoly([
+ (0,0), (highlight,highlight), (width-highlight,highlight),
+ (width-highlight,height-highlight), (width+depth,height+depth),
+ (width+depth,depth), (width,0)])
+ hl0_dark.fill(greypix(0.5))
+
+ hl0_light = SVGpoly([
+ (0,0), (highlight,highlight), (highlight,height-highlight),
+ (width-highlight,height-highlight), (width,height), (0,height)])
+ hl0_light.fill(greypix(1))
+
+ hl1_dark = SVGpoly([
+ (surround-highlight,botsurround-highlight), (surround,botsurround),
+ (surround,height-surround), (width-surround,height-surround),
+ (width-surround+highlight,height-surround+highlight),
+ (surround-highlight,height-surround+highlight)])
+ hl1_dark.fill(greypix(0.5))
+
+ hl1_light = SVGpoly([
+ (surround-highlight,botsurround-highlight), (surround,botsurround),
+ (width-surround,botsurround), (width-surround,height-surround),
+ (width-surround+highlight,height-surround+highlight),
+ (width-surround+highlight,botsurround-highlight)])
+ hl1_light.fill(greypix(1))
+
+ screen = SVGrect(surround, botsurround, width-surround, height-surround)
+ screen.fill(bluepix(1))
+
+ screenshadow = SVGpoly([
+ (surround,botsurround), (surround+shadow,botsurround),
+ (surround+shadow,height-surround-shadow),
+ (width-surround,height-surround-shadow),
+ (width-surround,height-surround), (surround,height-surround)])
+ screenshadow.fill(bluepix(0.5))
+
+ outline = SVGpoly(background_coords)
+ outline.stroke(cK, width=0.5)
+
+ toret = SVGgroup([background, hl0_dark, hl0_light, hl1_dark, hl1_light,
+ screen, screenshadow, outline])
+ # Give the centre of the screen (for lightning-bolt positioning purposes)
+ # as the centre of the _light_ area of the screen, not counting the
+ # shadow on the top and left. I think that looks very slightly nicer.
+ sbb = (surround+shadow, botsurround, width-surround, height-surround-shadow)
+ toret.props.screencentre = ((sbb[0]+sbb[2])/2, (sbb[1]+sbb[3])/2)
+ return toret
+
+def computer(size):
+ # Monitor plus sysbox.
+ m = monitor(size)
+ s = sysbox(size)
+ x = (2+size/(size+1))*size
+ y = int(s.props.sysboxheight + s.props.borderthickness)
+ mb = m.bbox()
+ sb = s.bbox()
+ xoff = mb[0] - sb[0] + x
+ yoff = mb[1] - sb[1] + y
+ toret = SVGgroup([s, m], [(0,0), (xoff,yoff)])
+ toret.props.screencentre = (m.props.screencentre[0]+xoff,
+ m.props.screencentre[1]+yoff)
+ return toret
+
+def lightning(size):
+ # The lightning bolt motif.
+
+ # Compute the right size of a lightning bolt to exactly connect
+ # the centres of the two screens in the main PuTTY icon. We'll use
+ # that size of bolt for all the other icons too, for consistency.
+ iconw = iconh = 32 * size
+ cbb = computer(size).bbox()
+ assert cbb[2]-cbb[0] <= iconw and cbb[3]-cbb[1] <= iconh
+ width, height = iconw-(cbb[2]-cbb[0]), iconh-(cbb[3]-cbb[1])
+
+ degree = math.pi/180
+
+ centrethickness = 2*size # top-to-bottom thickness of centre bar
+ innerangle = 46 * degree # slope of the inner slanting line
+ outerangle = 39 * degree # slope of the outer one
+
+ innery = (height - centrethickness) / 2
+ outery = (height + centrethickness) / 2
+ innerx = innery / math.tan(innerangle)
+ outerx = outery / math.tan(outerangle)
+
+ points = [(innerx, innery), (0,0), (outerx, outery)]
+ points.extend([(width-x, height-y) for x,y in points])
+
+ # Fill and stroke the lightning bolt.
+ #
+ # Most of the filled-and-stroked objects in these icons are filled
+ # first, and then stroked with width 0.5, so that the edge of the
+ # filled area runs down the centre line of the stroke. Put another
+ # way, half the stroke covers what would have been the filled
+ # area, and the other half covers the background. This seems like
+ # the normal way to fill-and-stroke a shape of a given size, and
+ # SVG makes it easy by allowing us to specify the polygon just
+ # once with both 'fill' and 'stroke' CSS properties.
+ #
+ # But if we did that in this case, then the tips of the lightning
+ # bolt wouldn't have lightning-colour anywhere near them, because
+ # the two edges are so close together in angle that the point
+ # where the strokes would first _not_ overlap would be miles away
+ # from the logical endpoint.
+ #
+ # So, for this one case, we stroke the polygon first at double the
+ # width, and then fill it on top of that, requiring two copies of
+ # it in the SVG (though my construction class here hides that
+ # detail). The effect is that we still get a stroke of visible
+ # width 0.5, but it's entirely outside the filled area of the
+ # polygon, so the tips of the yellow interior of the lightning
+ # bolt are exactly at the logical endpoints.
+ poly = SVGpoly(points)
+ poly.fill(cY)
+ poly.stroke(cK, width=1, behind=True)
+ poly.props.end1 = (0,0)
+ poly.props.end2 = (width,height)
+ return poly
+
+def document(size):
+ # The document used in the PSCP/PSFTP icon.
+
+ width = 13*size
+ height = 16*size
+
+ lineht = 0.875*size
+ linespc = 1.125*size
+ nlines = int((height-linespc)/(lineht+linespc))
+ height = nlines*(lineht+linespc)+linespc # round this so it fits better
+
+ paper = SVGrect(0, 0, width, height)
+ paper.fill(cW)
+ paper.stroke(cK, width=0.5)
+
+ objs = [paper]
+
+ # Now draw lines of text.
+ for line in range(nlines):
+ # Decide where this line of text begins.
+ if line == 0:
+ start = 4*size
+ elif line < 5*nlines/7:
+ start = (line * 4/5) * size
+ else:
+ start = 1*size
+ # Decide where it ends.
+ endpoints = [10, 8, 11, 6, 5, 7, 5]
+ ey = line * 6.0 / (nlines-1)
+ eyf = math.floor(ey)
+ eyc = math.ceil(ey)
+ exf = endpoints[int(eyf)]
+ exc = endpoints[int(eyc)]
+ if eyf == eyc:
+ end = exf
+ else:
+ end = exf * (eyc-ey) + exc * (ey-eyf)
+ end = end * size
+
+ liney = (lineht+linespc) * (line+1)
+ line = SVGrect(start, liney-lineht, end, liney)
+ line.fill(cK)
+ objs.append(line)
+
+ return SVGgroup(objs)
+
+def hat(size):
+ # The secret-agent hat in the Pageant icon.
+
+ leftend = (0, -6*size)
+ rightend = (28*size, -12*size)
+ dx = rightend[0]-leftend[0]
+ dy = rightend[1]-leftend[1]
+ tcentre = (leftend[0] + 0.5*dx - 0.3*dy, leftend[1] + 0.5*dy + 0.3*dx)
+
+ hatpoints = [leftend + (True,),
+ (7.5*size, -6*size, True),
+ (12*size, 0, True),
+ (14*size, 3*size, False),
+ (tcentre[0] - 0.1*dx, tcentre[1] - 0.1*dy, False),
+ tcentre + (True,)]
+ for x, y, on in list(reversed(hatpoints))[1:]:
+ vx, vy = x-tcentre[0], y-tcentre[1]
+ coeff = float(vx*dx + vy*dy) / float(dx*dx + dy*dy)
+ rx, ry = x - 2*coeff*dx, y - 2*coeff*dy
+ hatpoints.append((rx, ry, on))
+
+ mainhat = SVGpath([hatpoints])
+ mainhat.fill(cK)
+
+ band = SVGpoly([
+ (leftend[0] - 0.1*dy, leftend[1] + 0.1*dx),
+ (rightend[0] - 0.1*dy, rightend[1] + 0.1*dx),
+ (rightend[0] - 0.15*dy, rightend[1] + 0.15*dx),
+ (leftend[0] - 0.15*dy, leftend[1] + 0.15*dx)])
+ band.fill(cW)
+ band.clip(SVGpath([hatpoints]))
+
+ outline = SVGpath([hatpoints])
+ outline.stroke(cK, width=1)
+
+ return SVGgroup([mainhat, band, outline])
+
+def key(size):
+ # The key in the PuTTYgen icon.
+
+ keyheadw = 9.5*size
+ keyheadh = 12*size
+ keyholed = 4*size
+ keyholeoff = 2*size
+ # Ensure keyheadh and keyshafth have the same parity.
+ keyshafth = (2*size - (int(keyheadh)&1)) / 2 * 2 + (int(keyheadh)&1)
+ keyshaftw = 18.5*size
+ keyheaddetail = [x*size for x in [12,11,8,10,9,8,11,12]]
+
+ squarepix = []
+
+ keyheadcx = keyshaftw + keyheadw / 2.0
+ keyheadcy = keyheadh / 2.0
+ keyshafttop = keyheadcy + keyshafth / 2.0
+ keyshaftbot = keyheadcy - keyshafth / 2.0
+
+ keyhead = [(0, keyshafttop, True), (keyshaftw, keyshafttop, True),
+ (keyshaftw, keyshaftbot,
+ ("arc", keyheadw/2.0, keyheadh/2.0, 0, True, True)),
+ (len(keyheaddetail)*size, keyshaftbot, True)]
+ for i, h in reversed(list(enumerate(keyheaddetail))):
+ keyhead.append(((i+1)*size, keyheadh-h, True))
+ keyhead.append(((i)*size, keyheadh-h, True))
+
+ keyholecx = keyheadcx + keyholeoff
+ keyholecy = keyheadcy
+ keyholer = keyholed / 2.0
+
+ keyhole = [(keyholecx + keyholer, keyholecy,
+ ("arc", keyholer, keyholer, 0, False, False)),
+ (keyholecx - keyholer, keyholecy,
+ ("arc", keyholer, keyholer, 0, False, False))]
+
+ outline = SVGpath([keyhead, keyhole])
+ outline.fill(cy)
+ outline.stroke(cK, width=0.5)
+ return outline
+
+def linedist(x1,y1, x2,y2, x,y):
+ # Compute the distance from the point x,y to the line segment
+ # joining x1,y1 to x2,y2. Returns the distance vector, measured
+ # with x,y at the origin.
+
+ vectors = []
+
+ # Special case: if x1,y1 and x2,y2 are the same point, we
+ # don't attempt to extrapolate it into a line at all.
+ if x1 != x2 or y1 != y2:
+ # First, find the nearest point to x,y on the infinite
+ # projection of the line segment. So we construct a vector
+ # n perpendicular to that segment...
+ nx = y2-y1
+ ny = x1-x2
+ # ... compute the dot product of (x1,y1)-(x,y) with that
+ # vector...
+ nd = (x1-x)*nx + (y1-y)*ny
+ # ... multiply by the vector we first thought of...
+ ndx = nd * nx
+ ndy = nd * ny
+ # ... and divide twice by the length of n.
+ ndx = ndx / (nx*nx+ny*ny)
+ ndy = ndy / (nx*nx+ny*ny)
+ # That gives us a displacement vector from x,y to the
+ # nearest point. See if it's within the range of the line
+ # segment.
+ cx = x + ndx
+ cy = y + ndy
+ if cx >= min(x1,x2) and cx <= max(x1,x2) and \
+ cy >= min(y1,y2) and cy <= max(y1,y2):
+ vectors.append((ndx,ndy))
+
+ # Now we have up to three candidate result vectors: (ndx,ndy)
+ # as computed just above, and the two vectors to the ends of
+ # the line segment, (x1-x,y1-y) and (x2-x,y2-y). Pick the
+ # shortest.
+ vectors = vectors + [(x1-x,y1-y), (x2-x,y2-y)]
+ bestlen, best = None, None
+ for v in vectors:
+ vlen = v[0]*v[0]+v[1]*v[1]
+ if bestlen == None or bestlen > vlen:
+ bestlen = vlen
+ best = v
+ return best
+
+def spanner(size):
+ # The spanner in the config box icon.
+
+ # Coordinate definitions.
+ headcentre = 0.5 + 4*size
+ headradius = headcentre + 0.1
+ headhighlight = 1.5*size
+ holecentre = 0.5 + 3*size
+ holeradius = 2*size
+ holehighlight = 1.5*size
+ shaftend = 0.5 + 25*size
+ shaftwidth = 2*size
+ shafthighlight = 1.5*size
+ cmax = shaftend + shaftwidth
+
+ # The spanner head is a circle centred at headcentre*(1,1) with
+ # radius headradius, minus a circle at holecentre*(1,1) with
+ # radius holeradius, and also minus every translate of that circle
+ # by a negative real multiple of (1,1).
+ #
+ # The spanner handle is a diagonally oriented rectangle, of width
+ # shaftwidth, with the centre of the far end at shaftend*(1,1),
+ # and the near end terminating somewhere inside the spanner head
+ # (doesn't really matter exactly where).
+ #
+ # Hence, in SVG we can represent the shape using a path of
+ # straight lines and circular arcs. But first we need to calculate
+ # the points where the straight lines meet the spanner head circle.
+ headpt = lambda a, on=True: (headcentre+headradius*math.cos(a),
+ -headcentre+headradius*math.sin(a), on)
+ holept = lambda a, on=True: (holecentre+holeradius*math.cos(a),
+ -holecentre+holeradius*math.sin(a), on)
+
+ # Now we can specify the path.
+ spannercoords = [[
+ holept(math.pi*5/4),
+ holept(math.pi*1/4, ("arc", holeradius,holeradius,0, False, False)),
+ headpt(math.pi*3/4 - math.asin(holeradius/headradius)),
+ headpt(math.pi*7/4 + math.asin(shaftwidth/headradius),
+ ("arc", headradius,headradius,0, False, True)),
+ (shaftend+math.sqrt(0.5)*shaftwidth,
+ -shaftend+math.sqrt(0.5)*shaftwidth, True),
+ (shaftend-math.sqrt(0.5)*shaftwidth,
+ -shaftend-math.sqrt(0.5)*shaftwidth, True),
+ headpt(math.pi*7/4 - math.asin(shaftwidth/headradius)),
+ headpt(math.pi*3/4 + math.asin(holeradius/headradius),
+ ("arc", headradius,headradius,0, False, True)),
+ ]]
+
+ base = SVGpath(spannercoords)
+ base.fill(cY)
+
+ shadowthickness = 2*size
+ sx, sy, _ = holept(math.pi*5/4)
+ sx += math.sqrt(0.5) * shadowthickness/2
+ sy += math.sqrt(0.5) * shadowthickness/2
+ sr = holeradius - shadowthickness/2
+
+ shadow = SVGpath([
+ [(sx, sy, sr),
+ holept(math.pi*1/4, ("arc", sr, sr, 0, False, False)),
+ headpt(math.pi*3/4 - math.asin(holeradius/headradius))],
+ [(shaftend-math.sqrt(0.5)*shaftwidth,
+ -shaftend-math.sqrt(0.5)*shaftwidth, True),
+ headpt(math.pi*7/4 - math.asin(shaftwidth/headradius)),
+ headpt(math.pi*3/4 + math.asin(holeradius/headradius),
+ ("arc", headradius,headradius,0, False, True))],
+ ], closed=False)
+ shadow.clip(SVGpath(spannercoords))
+ shadow.stroke(cy, width=shadowthickness)
+
+ outline = SVGpath(spannercoords)
+ outline.stroke(cK, width=0.5)
+
+ return SVGgroup([base, shadow, outline])
+
+def box(size, wantback):
+ # The back side of the cardboard box in the installer icon.
+
+ boxwidth = 15 * size
+ boxheight = 12 * size
+ boxdepth = 4 * size
+ boxfrontflapheight = 5 * size
+ boxrightflapheight = 3 * size
+
+ # Three shades of basically acceptable brown, all achieved by
+ # halftoning between two of the Windows-16 colours. I'm quite
+ # pleased that was feasible at all!
+ dark = halftone(cr, cK)
+ med = halftone(cr, cy)
+ light = halftone(cr, cY)
+ # We define our halftoning parity in such a way that the black
+ # pixels along the RHS of the visible part of the box back
+ # match up with the one-pixel black outline around the
+ # right-hand side of the box. In other words, we want the pixel
+ # at (-1, boxwidth-1) to be black, and hence the one at (0,
+ # boxwidth) too.
+ parityadjust = int(boxwidth) % 2
+
+ # The back of the box.
+ if wantback:
+ back = SVGpoly([
+ (0,0), (boxwidth,0), (boxwidth+boxdepth,boxdepth),
+ (boxwidth+boxdepth,boxheight+boxdepth),
+ (boxdepth,boxheight+boxdepth), (0,boxheight)])
+ back.fill(dark)
+ back.stroke(cK, width=0.5)
+ return back
+
+ # The front face of the box.
+ front = SVGrect(0, 0, boxwidth, boxheight)
+ front.fill(med)
+ front.stroke(cK, width=0.5)
+ # The right face of the box.
+ right = SVGpoly([
+ (boxwidth,0), (boxwidth+boxdepth,boxdepth),
+ (boxwidth+boxdepth,boxheight+boxdepth), (boxwidth,boxheight)])
+ right.fill(dark)
+ right.stroke(cK, width=0.5)
+ frontflap = SVGpoly([
+ (0,boxheight), (boxwidth,boxheight),
+ (boxwidth-boxfrontflapheight/2, boxheight-boxfrontflapheight),
+ (-boxfrontflapheight/2, boxheight-boxfrontflapheight)])
+ frontflap.stroke(cK, width=0.5)
+ frontflap.fill(light)
+ rightflap = SVGpoly([
+ (boxwidth,boxheight), (boxwidth+boxdepth,boxheight+boxdepth),
+ (boxwidth+boxdepth+boxrightflapheight,
+ boxheight+boxdepth-boxrightflapheight),
+ (boxwidth+boxrightflapheight,boxheight-boxrightflapheight)])
+ rightflap.stroke(cK, width=0.5)
+ rightflap.fill(med)
+
+ return SVGgroup([front, right, frontflap, rightflap])
+
+def boxback(size):
+ return box(size, 1)
+def boxfront(size):
+ return box(size, 0)
+
+# Functions to draw entire icons by composing the above components.
+
+def xybolt(c1, c2, size, boltoffx=0, boltoffy=0, c1bb=None, c2bb=None):
+ # Two unspecified objects and a lightning bolt.
+
+ w = h = 32 * size
+
+ bolt = lightning(size)
+
+ objs = [c2, c1, bolt]
+ origins = [None] * 3
+
+ # Position c2 against the top right of the icon.
+ bb = c2bb if c2bb is not None else c2.bbox()
+ assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
+ origins[0] = w-bb[2], h-bb[3]
+ # Position c1 against the bottom left of the icon.
+ bb = c1bb if c1bb is not None else c1.bbox()
+ assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
+ origins[1] = 0-bb[0], 0-bb[1]
+
+ # Place the lightning bolt so that it ends precisely at the centre
+ # of the monitor, in whichever of the two sub-pictures has one.
+ # (In the case of the PuTTY icon proper, in which _both_
+ # sub-pictures are computers, it should line up correctly for both.)
+ origin1 = origin2 = None
+ if hasattr(c1.props, "screencentre"):
+ origin1 = (
+ c1.props.screencentre[0] + origins[1][0] - bolt.props.end1[0],
+ c1.props.screencentre[1] + origins[1][1] - bolt.props.end1[1])
+ if hasattr(c2.props, "screencentre"):
+ origin2 = (
+ c2.props.screencentre[0] + origins[0][0] - bolt.props.end2[0],
+ c2.props.screencentre[1] + origins[0][1] - bolt.props.end2[1])
+ if origin1 is not None and origin2 is not None:
+ assert math.hypot(origin1[0]-origin2[0],origin1[1]-origin2[1]<1e-5), (
+ "Lightning bolt didn't line up! Off by {}*size".format(
+ ((origin1[0]-origin2[0])/size,
+ (origin1[1]-origin2[1])/size)))
+ origins[2] = origin1 if origin1 is not None else origin2
+ assert origins[2] is not None, "Need at least one computer to line up bolt"
+
+ toret = SVGgroup(objs, origins)
+ toret.props.c1pos = origins[1]
+ toret.props.c2pos = origins[0]
+ return toret
+
+def putty_icon(size):
+ return xybolt(computer(size), computer(size), size)
+
+def puttycfg_icon(size):
+ w = h = 32 * size
+ s = spanner(size)
+ b = putty_icon(size)
+ bb = s.bbox()
+ return SVGgroup([b, s], [(0,0), ((w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2)])
+
+def puttygen_icon(size):
+ k = key(size)
+ # Manually move the key around, by pretending to xybolt that its
+ # bounding box is offset from where it really is.
+ kbb = SVGtranslate(k,(2*size,5*size)).bbox()
+ return xybolt(computer(size), k, size, boltoffx=2, c2bb=kbb)
+
+def pscp_icon(size):
+ return xybolt(document(size), computer(size), size)
+
+def puttyins_icon(size):
+ boxfront = box(size, False)
+ boxback = box(size, True)
+ # The box back goes behind the lightning bolt.
+ most = xybolt(boxback, computer(size), size, c1bb=boxfront.bbox(),
+ boltoffx=-2, boltoffy=+1)
+ # But the box front goes over the top, so that the lightning
+ # bolt appears to come _out_ of the box. Here it's useful to
+ # know the exact coordinates where xybolt placed the box back,
+ # so we can overlay the box front exactly on top of it.
+ c1x, c1y = most.props.c1pos
+ return SVGgroup([most, boxfront], [(0,0), most.props.c1pos])
+
+def pterm_icon(size):
+ # Just a really big computer.
+
+ w = h = 32 * size
+
+ c = computer(size * 1.4)
+
+ # Centre c in the output rectangle.
+ bb = c.bbox()
+ assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
+
+ return SVGgroup([c], [((w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2)])
+
+def ptermcfg_icon(size):
+ w = h = 32 * size
+ s = spanner(size)
+ b = pterm_icon(size)
+ bb = s.bbox()
+ return SVGgroup([b, s], [(0,0), ((w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2)])
+
+def pageant_icon(size):
+ # A biggish computer, in a hat.
+
+ w = h = 32 * size
+
+ c = computer(size * 1.2)
+ ht = hat(size)
+
+ cbb = c.bbox()
+ hbb = ht.bbox()
+
+ # Determine the relative coordinates of the computer and hat. We
+ # do this by first centring one on the other, then adjusting by
+ # hand.
+ xrel = (cbb[0]+cbb[2]-hbb[0]-hbb[2])/2 + 2*size
+ yrel = (cbb[1]+cbb[3]-hbb[1]-hbb[3])/2 + 12*size
+
+ both = SVGgroup([c, ht], [(0,0), (xrel,yrel)])
+
+ # Mostly-centre the result in the output rectangle. We want
+ # everything to fit in frame, but we also want to make it look as
+ # if the computer is more x-centred than the hat.
+
+ # Coordinates that would centre the whole group.
+ bb = both.bbox()
+ assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h
+ grx, gry = (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2
+
+ # Coords that would centre just the computer.
+ bb = c.bbox()
+ crx, cry = (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2
+
+ # Use gry unchanged, but linear-combine grx with crx.
+ return SVGgroup([both], [(grx+0.6*(crx-grx), gry)])
+
+# Test and output functions.
+
+cK = (0x00, 0x00, 0x00, 0xFF)
+cr = (0x80, 0x00, 0x00, 0xFF)
+cg = (0x00, 0x80, 0x00, 0xFF)
+cy = (0x80, 0x80, 0x00, 0xFF)
+cb = (0x00, 0x00, 0x80, 0xFF)
+cm = (0x80, 0x00, 0x80, 0xFF)
+cc = (0x00, 0x80, 0x80, 0xFF)
+cP = (0xC0, 0xC0, 0xC0, 0xFF)
+cw = (0x80, 0x80, 0x80, 0xFF)
+cR = (0xFF, 0x00, 0x00, 0xFF)
+cG = (0x00, 0xFF, 0x00, 0xFF)
+cY = (0xFF, 0xFF, 0x00, 0xFF)
+cB = (0x00, 0x00, 0xFF, 0xFF)
+cM = (0xFF, 0x00, 0xFF, 0xFF)
+cC = (0x00, 0xFF, 0xFF, 0xFF)
+cW = (0xFF, 0xFF, 0xFF, 0xFF)
+cD = (0x00, 0x00, 0x00, 0x80)
+cT = (0x00, 0x00, 0x00, 0x00)
+def greypix(value):
+ value = max(min(value, 1), 0)
+ return (int(round(0xFF*value)),) * 3 + (0xFF,)
+def yellowpix(value):
+ value = max(min(value, 1), 0)
+ return (int(round(0xFF*value)),) * 2 + (0, 0xFF)
+def bluepix(value):
+ value = max(min(value, 1), 0)
+ return (0, 0, int(round(0xFF*value)), 0xFF)
+def dark(value):
+ value = max(min(value, 1), 0)
+ return (0, 0, 0, int(round(0xFF*value)))
+def blend(col1, col2):
+ r1,g1,b1,a1 = col1
+ r2,g2,b2,a2 = col2
+ r = int(round((r1*a1 + r2*(0xFF-a1)) / 255.0))
+ g = int(round((g1*a1 + g2*(0xFF-a1)) / 255.0))
+ b = int(round((b1*a1 + b2*(0xFF-a1)) / 255.0))
+ a = int(round((255*a1 + a2*(0xFF-a1)) / 255.0))
+ return r, g, b, a
+def halftone(col1, col2):
+ r1,g1,b1,a1 = col1
+ r2,g2,b2,a2 = col2
+ return ((r1+r2)//2, (g1+g2)//2, (b1+b2)//2, (a1+a2)//2)
+
+def drawicon(func, width, fname):
+ icon = func(width / 32.0)
+ minx, miny, maxx, maxy = icon.bbox()
+ #assert minx >= 0 and miny >= 0 and maxx <= width and maxy <= width
+
+ svgroot = ET.Element("svg")
+ svgroot.attrib["xmlns"] = "http://www.w3.org/2000/svg"
+ svgroot.attrib["viewBox"] = "0 0 {w:d} {w:d}".format(w=width)
+
+ defs = ET.Element("defs")
+ idents = ("iconid{:d}".format(n) for n in itertools.count())
+ if icon.add_clip_paths(defs, idents, 0, width):
+ svgroot.append(defs)
+
+ svgroot.append(icon.render(0,width))
+
+ ET.ElementTree(svgroot).write(fname)
+
+def main():
+ parser = argparse.ArgumentParser(description='Generate PuTTY SVG icons.')
+ parser.add_argument("icon", help="Which icon to generate.")
+ parser.add_argument("-s", "--size", type=int, default=48,
+ help="Notional pixel size to base the SVG on.")
+ parser.add_argument("-o", "--output", required=True,
+ help="Output file name.")
+ args = parser.parse_args()
+
+ drawicon(eval(args.icon), args.size, args.output)
+
+if __name__ == '__main__':
+ main()
diff --git a/import.c b/import.c
index 553fa750..918de50e 100644
--- a/import.c
+++ b/import.c
@@ -174,17 +174,6 @@ bool export_ssh2(const Filename *filename, int type,
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.)
*/
@@ -328,7 +317,7 @@ struct openssh_pem_key {
strbuf *keyblob;
};
-void BinarySink_put_mp_ssh2_from_string(BinarySink *bs, ptrlen str)
+static void BinarySink_put_mp_ssh2_from_string(BinarySink *bs, ptrlen str)
{
const unsigned char *bytes = (const unsigned char *)str.ptr;
size_t nbytes = str.len;
@@ -498,7 +487,7 @@ static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src,
if (errmsg_p) *errmsg_p = NULL;
return ret;
- error:
+ error:
if (line) {
smemclr(line, strlen(line));
sfree(line);
@@ -775,7 +764,7 @@ static ssh2_userkey *openssh_pem_read(
*/
assert(privptr > 0); /* should have bombed by now if not */
retkey = snew(ssh2_userkey);
- alg = (key->keytype == OP_RSA ? &ssh_rsa : &ssh_dss);
+ alg = (key->keytype == OP_RSA ? &ssh_rsa : &ssh_dsa);
retkey->key = ssh_key_new_priv(
alg, make_ptrlen(blob->u, privptr),
make_ptrlen(blob->u+privptr, blob->len-privptr));
@@ -801,7 +790,7 @@ static ssh2_userkey *openssh_pem_read(
errmsg = NULL; /* no error */
retval = retkey;
- error:
+ error:
strbuf_free(blob);
strbuf_free(key->keyblob);
smemclr(key, sizeof(*key));
@@ -811,7 +800,7 @@ static ssh2_userkey *openssh_pem_read(
}
static bool openssh_pem_write(
- const Filename *filename, ssh2_userkey *key, const char *passphrase)
+ const Filename *filename, ssh2_userkey *ukey, const char *passphrase)
{
strbuf *pubblob, *privblob, *outblob;
unsigned char *spareblob;
@@ -825,13 +814,17 @@ static bool openssh_pem_write(
FILE *fp;
BinarySource src[1];
+ /* OpenSSH's private key files never contain a certificate, so
+ * revert to the underlying base key if necessary */
+ ssh_key *key = ssh_key_base_key(ukey->key);
+
/*
* Fetch the key blobs.
*/
pubblob = strbuf_new();
- ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob));
+ ssh_key_public_blob(key, BinarySink_UPCAST(pubblob));
privblob = strbuf_new_nm();
- ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob));
+ ssh_key_private_blob(key, BinarySink_UPCAST(privblob));
spareblob = NULL;
outblob = strbuf_new_nm();
@@ -840,18 +833,18 @@ static bool openssh_pem_write(
* 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) {
+ if (ssh_key_alg(key) == &ssh_rsa ||
+ ssh_key_alg(key) == &ssh_dsa) {
strbuf *seq;
/*
- * The RSA and DSS handlers share some code because the two
+ * The RSA and DSA handlers share some code because the two
* key types have very similar ASN.1 representations, as a
* plain SEQUENCE of big integers. So we set up a list of
* bignums per key type and then construct the actual blob in
* common code after that.
*/
- if (ssh_key_alg(key->key) == &ssh_rsa) {
+ if (ssh_key_alg(key) == &ssh_rsa) {
ptrlen n, e, d, p, q, iqmp, dmp1, dmq1;
mp_int *bd, *bp, *bq, *bdmp1, *bdmq1;
@@ -947,11 +940,11 @@ static bool openssh_pem_write(
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) {
+ } else if (ssh_key_alg(key) == &ssh_ecdsa_nistp256 ||
+ ssh_key_alg(key) == &ssh_ecdsa_nistp384 ||
+ ssh_key_alg(key) == &ssh_ecdsa_nistp521) {
const unsigned char *oid;
- struct ecdsa_key *ec = container_of(key->key, struct ecdsa_key, sshk);
+ struct ecdsa_key *ec = container_of(key, struct ecdsa_key, sshk);
int oidlen;
int pointlen;
strbuf *seq, *sub;
@@ -966,7 +959,7 @@ static bool openssh_pem_write(
* [1]
* BIT STRING (0x00 public key point)
*/
- oid = ec_alg_oid(ssh_key_alg(key->key), &oidlen);
+ oid = ec_alg_oid(ssh_key_alg(key), &oidlen);
pointlen = (ec->curve->fieldBits + 7) / 8 * 2;
seq = strbuf_new_nm();
@@ -998,7 +991,7 @@ static bool openssh_pem_write(
/* Append the BIT STRING to the sequence */
put_ber_id_len(seq, 1, sub->len,
- ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED);
+ ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED);
put_data(seq, sub->s, sub->len);
strbuf_free(sub);
@@ -1075,12 +1068,12 @@ static bool openssh_pem_write(
fprintf(fp, "%02X", iv[i]);
fprintf(fp, "\n\n");
}
- base64_encode(fp, outblob->u, outblob->len, 64);
+ base64_encode_fp(fp, ptrlen_from_strbuf(outblob), 64);
fputs(footer, fp);
fclose(fp);
ret = true;
- error:
+ error:
if (outblob)
strbuf_free(outblob);
if (spareblob) {
@@ -1245,8 +1238,8 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
ret->kdfopts.bcrypt.rounds = get_uint32(opts);
if (get_err(opts)) {
- errmsg = "failed to parse bcrypt options string";
- goto error;
+ errmsg = "failed to parse bcrypt options string";
+ goto error;
}
break;
}
@@ -1294,7 +1287,7 @@ static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc,
if (errmsg_p) *errmsg_p = NULL;
return ret;
- error:
+ error:
if (line) {
smemclr(line, strlen(line));
sfree(line);
@@ -1363,9 +1356,8 @@ static ssh2_userkey *openssh_new_read(
memset(keybuf, 0, keysize);
break;
case ON_K_BCRYPT:
- openssh_bcrypt(passphrase,
- key->kdfopts.bcrypt.salt.ptr,
- key->kdfopts.bcrypt.salt.len,
+ openssh_bcrypt(ptrlen_from_asciz(passphrase),
+ key->kdfopts.bcrypt.salt,
key->kdfopts.bcrypt.rounds,
keybuf, keysize);
break;
@@ -1485,7 +1477,7 @@ static ssh2_userkey *openssh_new_read(
retval = retkey;
retkey = NULL; /* prevent the free */
- error:
+ error:
if (retkey) {
sfree(retkey->comment);
if (retkey->key)
@@ -1500,7 +1492,7 @@ static ssh2_userkey *openssh_new_read(
}
static bool openssh_new_write(
- const Filename *filename, ssh2_userkey *key, const char *passphrase)
+ const Filename *filename, ssh2_userkey *ukey, const char *passphrase)
{
strbuf *pubblob, *privblob, *cblob;
int padvalue;
@@ -1510,13 +1502,17 @@ static bool openssh_new_write(
const int bcrypt_rounds = 16;
FILE *fp;
+ /* OpenSSH's private key files never contain a certificate, so
+ * revert to the underlying base key if necessary */
+ ssh_key *key = ssh_key_base_key(ukey->key);
+
/*
* Fetch the key blobs and find out the lengths of things.
*/
pubblob = strbuf_new();
- ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob));
+ ssh_key_public_blob(key, BinarySink_UPCAST(pubblob));
privblob = strbuf_new_nm();
- ssh_key_openssh_blob(key->key, BinarySink_UPCAST(privblob));
+ ssh_key_openssh_blob(key, BinarySink_UPCAST(privblob));
/*
* Construct the cleartext version of the blob.
@@ -1563,11 +1559,11 @@ static bool openssh_new_write(
/* Private key. The main private blob goes inline, with no string
* wrapper. */
- put_stringz(cpblob, ssh_key_ssh_id(key->key));
+ put_stringz(cpblob, ssh_key_ssh_id(key));
put_data(cpblob, privblob->s, privblob->len);
/* Comment. */
- put_stringz(cpblob, key->comment);
+ put_stringz(cpblob, ukey->comment);
/* Pad out the encrypted section. */
padvalue = 1;
@@ -1583,9 +1579,9 @@ static bool openssh_new_write(
unsigned char keybuf[48];
ssh_cipher *cipher;
- openssh_bcrypt(passphrase,
- bcrypt_salt, sizeof(bcrypt_salt), bcrypt_rounds,
- keybuf, sizeof(keybuf));
+ openssh_bcrypt(ptrlen_from_asciz(passphrase),
+ make_ptrlen(bcrypt_salt, sizeof(bcrypt_salt)),
+ bcrypt_rounds, keybuf, sizeof(keybuf));
cipher = ssh_cipher_new(&ssh_aes256_sdctr);
ssh_cipher_setkey(cipher, keybuf);
@@ -1607,12 +1603,12 @@ static bool openssh_new_write(
if (!fp)
goto error;
fputs("-----BEGIN OPENSSH PRIVATE KEY-----\n", fp);
- base64_encode(fp, cblob->u, cblob->len, 64);
+ base64_encode_fp(fp, ptrlen_from_strbuf(cblob), 64);
fputs("-----END OPENSSH PRIVATE KEY-----\n", fp);
fclose(fp);
ret = true;
- error:
+ error:
if (cblob)
strbuf_free(cblob);
if (privblob)
@@ -1634,11 +1630,12 @@ static bool openssh_auto_write(
* 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)
+ const ssh_keyalg *alg = ssh_key_alg(ssh_key_base_key(key->key));
+ if (alg == &ssh_dsa ||
+ alg == &ssh_rsa ||
+ alg == &ssh_ecdsa_nistp256 ||
+ alg == &ssh_ecdsa_nistp384 ||
+ alg == &ssh_ecdsa_nistp521)
return openssh_pem_write(filename, key, passphrase);
else
return openssh_new_write(filename, key, passphrase);
@@ -1846,7 +1843,7 @@ static struct sshcom_key *load_sshcom_key(BinarySource *src,
if (errmsg_p) *errmsg_p = NULL;
return ret;
- error:
+ error:
if (line) {
smemclr(line, strlen(line));
sfree(line);
@@ -1884,7 +1881,7 @@ static bool sshcom_encrypted(BinarySource *filesrc, char **comment)
if (!ptrlen_eq_string(str, "none"))
answer = true;
- done:
+ done:
if (key) {
*comment = dupstr(key->comment);
strbuf_free(key->keyblob);
@@ -1896,7 +1893,7 @@ static bool sshcom_encrypted(BinarySource *filesrc, char **comment)
return answer;
}
-void BinarySink_put_mp_sshcom_from_string(BinarySink *bs, ptrlen str)
+static void BinarySink_put_mp_sshcom_from_string(BinarySink *bs, ptrlen str)
{
const unsigned char *bytes = (const unsigned char *)str.ptr;
size_t nbytes = str.len;
@@ -1980,7 +1977,7 @@ static ssh2_userkey *sshcom_read(
!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)) {
+ !memcmp(str.ptr, prefix_dsa, sizeof(prefix_dsa) - 1)) {
type = DSA;
} else {
errmsg = "key is of unknown type";
@@ -2111,7 +2108,7 @@ static ssh2_userkey *sshcom_read(
goto error;
}
- alg = &ssh_dss;
+ alg = &ssh_dsa;
put_stringz(blob, "ssh-dss");
put_mp_ssh2_from_string(blob, p);
put_mp_ssh2_from_string(blob, q);
@@ -2135,7 +2132,7 @@ static ssh2_userkey *sshcom_read(
errmsg = NULL; /* no error */
ret = retkey;
- error:
+ error:
if (blob) {
strbuf_free(blob);
}
@@ -2202,7 +2199,7 @@ static bool sshcom_write(
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) {
+ } else if (ssh_key_alg(key->key) == &ssh_dsa) {
ptrlen p, q, g, y, x;
/*
@@ -2309,12 +2306,12 @@ static bool sshcom_write(
}
fprintf(fp, "%s\"\n", c);
}
- base64_encode(fp, outblob->u, outblob->len, 70);
+ base64_encode_fp(fp, ptrlen_from_strbuf(outblob), 70);
fputs("---- END SSH2 ENCRYPTED PRIVATE KEY ----\n", fp);
fclose(fp);
ret = true;
- error:
+ error:
if (outblob)
strbuf_free(outblob);
if (privblob)
diff --git a/keygen/CMakeLists.txt b/keygen/CMakeLists.txt
new file mode 100644
index 00000000..17eea2b1
--- /dev/null
+++ b/keygen/CMakeLists.txt
@@ -0,0 +1,10 @@
+add_sources_from_current_dir(keygen
+ dsa.c
+ ecdsa.c
+ millerrabin.c
+ mpunsafe.c
+ pockle.c
+ prime.c
+ primecandidate.c
+ rsa.c
+ smallprimes.c)
diff --git a/keygen/dsa.c b/keygen/dsa.c
new file mode 100644
index 00000000..a7ea4f53
--- /dev/null
+++ b/keygen/dsa.c
@@ -0,0 +1,103 @@
+/*
+ * DSA key generation.
+ */
+
+#include "misc.h"
+#include "ssh.h"
+#include "sshkeygen.h"
+#include "mpint.h"
+
+int dsa_generate(struct dsa_key *key, int bits, PrimeGenerationContext *pgc,
+ ProgressReceiver *prog)
+{
+ /*
+ * Progress-reporting setup.
+ *
+ * DSA generation involves three potentially long jobs: inventing
+ * the small prime q, the large prime p, and finding an order-q
+ * element of the multiplicative group of p.
+ *
+ * The latter is done by finding an element whose order is
+ * _divisible_ by q and raising it to the power of (p-1)/q. Every
+ * element whose order is not divisible by q is a qth power of q
+ * distinct elements whose order _is_ divisible by q, so the
+ * probability of not finding a suitable element on the first try
+ * is in the region of 1/q, i.e. at most 2^-159.
+ *
+ * (So the probability of success will end up indistinguishable
+ * from 1 in IEEE standard floating point! But what can you do.)
+ */
+ ProgressPhase phase_q = primegen_add_progress_phase(pgc, prog, 160);
+ ProgressPhase phase_p = primegen_add_progress_phase(pgc, prog, bits);
+ double g_failure_probability = 1.0
+ / (double)(1ULL << 53)
+ / (double)(1ULL << 53)
+ / (double)(1ULL << 53);
+ ProgressPhase phase_g = progress_add_probabilistic(
+ prog, estimate_modexp_cost(bits), 1.0 - g_failure_probability);
+ progress_ready(prog);
+
+ PrimeCandidateSource *pcs;
+
+ /*
+ * Generate q: a prime of length 160.
+ */
+ progress_start_phase(prog, phase_q);
+ pcs = pcs_new(160);
+ mp_int *q = primegen_generate(pgc, pcs, prog);
+ progress_report_phase_complete(prog);
+
+ /*
+ * Now generate p: a prime of length `bits', such that p-1 is
+ * divisible by q.
+ */
+ progress_start_phase(prog, phase_p);
+ pcs = pcs_new(bits);
+ pcs_require_residue_1_mod_prime(pcs, q);
+ mp_int *p = primegen_generate(pgc, pcs, prog);
+ progress_report_phase_complete(prog);
+
+ /*
+ * Next we need g. Raise 2 to the power (p-1)/q modulo p, and
+ * if that comes out to one then try 3, then 4 and so on. As
+ * soon as we hit a non-unit (and non-zero!) one, that'll do
+ * for g.
+ */
+ progress_start_phase(prog, phase_g);
+ mp_int *power = mp_div(p, q); /* this is floor(p/q) == (p-1)/q */
+ mp_int *h = mp_from_integer(2);
+ mp_int *g;
+ while (1) {
+ progress_report_attempt(prog);
+ g = mp_modpow(h, power, p);
+ if (mp_hs_integer(g, 2))
+ break; /* got one */
+ mp_free(g);
+ mp_add_integer_into(h, h, 1);
+ }
+ mp_free(h);
+ mp_free(power);
+ progress_report_phase_complete(prog);
+
+ /*
+ * Now we're nearly done. All we need now is our private key x,
+ * which should be a number between 1 and q-1 exclusive, and
+ * our public key y = g^x mod p.
+ */
+ mp_int *two = mp_from_integer(2);
+ mp_int *qm1 = mp_copy(q);
+ mp_sub_integer_into(qm1, qm1, 1);
+ mp_int *x = mp_random_in_range(two, qm1);
+ mp_free(two);
+ mp_free(qm1);
+
+ key->sshk.vt = &ssh_dsa;
+
+ key->p = p;
+ key->q = q;
+ key->g = g;
+ key->x = x;
+ key->y = mp_modpow(key->g, key->x, key->p);
+
+ return 1;
+}
diff --git a/sshecdsag.c b/keygen/ecdsa.c
index 28a723b2..28a723b2 100644
--- a/sshecdsag.c
+++ b/keygen/ecdsa.c
diff --git a/keygen/millerrabin.c b/keygen/millerrabin.c
new file mode 100644
index 00000000..24ee6193
--- /dev/null
+++ b/keygen/millerrabin.c
@@ -0,0 +1,288 @@
+/*
+ * millerrabin.c: Miller-Rabin probabilistic primality testing, as
+ * declared in sshkeygen.h.
+ */
+
+#include <assert.h>
+#include "ssh.h"
+#include "sshkeygen.h"
+#include "mpint.h"
+#include "mpunsafe.h"
+
+/*
+ * The Miller-Rabin primality test is an extension to the Fermat
+ * test. The Fermat test just checks that a^(p-1) == 1 mod p; this
+ * is vulnerable to Carmichael numbers. Miller-Rabin considers how
+ * that 1 is derived as well.
+ *
+ * Lemma: if a^2 == 1 (mod p), and p is prime, then either a == 1
+ * or a == -1 (mod p).
+ *
+ * Proof: p divides a^2-1, i.e. p divides (a+1)(a-1). Hence,
+ * since p is prime, either p divides (a+1) or p divides (a-1).
+ * But this is the same as saying that either a is congruent to
+ * -1 mod p or a is congruent to +1 mod p. []
+ *
+ * Comment: This fails when p is not prime. Consider p=mn, so
+ * that mn divides (a+1)(a-1). Now we could have m dividing (a+1)
+ * and n dividing (a-1), without the whole of mn dividing either.
+ * For example, consider a=10 and p=99. 99 = 9 * 11; 9 divides
+ * 10-1 and 11 divides 10+1, so a^2 is congruent to 1 mod p
+ * without a having to be congruent to either 1 or -1.
+ *
+ * So the Miller-Rabin test, as well as considering a^(p-1),
+ * considers a^((p-1)/2), a^((p-1)/4), and so on as far as it can
+ * go. In other words. we write p-1 as q * 2^k, with k as large as
+ * possible (i.e. q must be odd), and we consider the powers
+ *
+ * a^(q*2^0) a^(q*2^1) ... a^(q*2^(k-1)) a^(q*2^k)
+ * i.e. a^((n-1)/2^k) a^((n-1)/2^(k-1)) ... a^((n-1)/2) a^(n-1)
+ *
+ * If p is to be prime, the last of these must be 1. Therefore, by
+ * the above lemma, the one before it must be either 1 or -1. And
+ * _if_ it's 1, then the one before that must be either 1 or -1,
+ * and so on ... In other words, we expect to see a trailing chain
+ * of 1s preceded by a -1. (If we're unlucky, our trailing chain of
+ * 1s will be as long as the list so we'll never get to see what
+ * lies before it. This doesn't count as a test failure because it
+ * hasn't _proved_ that p is not prime.)
+ *
+ * For example, consider a=2 and p=1729. 1729 is a Carmichael
+ * number: although it's not prime, it satisfies a^(p-1) == 1 mod p
+ * for any a coprime to it. So the Fermat test wouldn't have a
+ * problem with it at all, unless we happened to stumble on an a
+ * which had a common factor.
+ *
+ * So. 1729 - 1 equals 27 * 2^6. So we look at
+ *
+ * 2^27 mod 1729 == 645
+ * 2^108 mod 1729 == 1065
+ * 2^216 mod 1729 == 1
+ * 2^432 mod 1729 == 1
+ * 2^864 mod 1729 == 1
+ * 2^1728 mod 1729 == 1
+ *
+ * We do have a trailing string of 1s, so the Fermat test would
+ * have been happy. But this trailing string of 1s is preceded by
+ * 1065; whereas if 1729 were prime, we'd expect to see it preceded
+ * by -1 (i.e. 1728.). Guards! Seize this impostor.
+ *
+ * (If we were unlucky, we might have tried a=16 instead of a=2;
+ * now 16^27 mod 1729 == 1, so we would have seen a long string of
+ * 1s and wouldn't have seen the thing _before_ the 1s. So, just
+ * like the Fermat test, for a given p there may well exist values
+ * of a which fail to show up its compositeness. So we try several,
+ * just like the Fermat test. The difference is that Miller-Rabin
+ * is not _in general_ fooled by Carmichael numbers.)
+ *
+ * Put simply, then, the Miller-Rabin test requires us to:
+ *
+ * 1. write p-1 as q * 2^k, with q odd
+ * 2. compute z = (a^q) mod p.
+ * 3. report success if z == 1 or z == -1.
+ * 4. square z at most k-1 times, and report success if it becomes
+ * -1 at any point.
+ * 5. report failure otherwise.
+ *
+ * (We expect z to become -1 after at most k-1 squarings, because
+ * if it became -1 after k squarings then a^(p-1) would fail to be
+ * 1. And we don't need to investigate what happens after we see a
+ * -1, because we _know_ that -1 squared is 1 modulo anything at
+ * all, so after we've seen a -1 we can be sure of seeing nothing
+ * but 1s.)
+ */
+
+struct MillerRabin {
+ MontyContext *mc;
+
+ mp_int *pm1, *m_pm1;
+ mp_int *lowbit, *two;
+};
+
+MillerRabin *miller_rabin_new(mp_int *p)
+{
+ MillerRabin *mr = snew(MillerRabin);
+
+ assert(mp_hs_integer(p, 2));
+ assert(mp_get_bit(p, 0) == 1);
+
+ mr->pm1 = mp_copy(p);
+ mp_sub_integer_into(mr->pm1, mr->pm1, 1);
+
+ /*
+ * Standard bit-twiddling trick for isolating the lowest set bit
+ * of a number: x & (-x)
+ */
+ mr->lowbit = mp_new(mp_max_bits(mr->pm1));
+ mp_sub_into(mr->lowbit, mr->lowbit, mr->pm1);
+ mp_and_into(mr->lowbit, mr->lowbit, mr->pm1);
+
+ mr->two = mp_from_integer(2);
+
+ mr->mc = monty_new(p);
+ mr->m_pm1 = monty_import(mr->mc, mr->pm1);
+
+ return mr;
+}
+
+void miller_rabin_free(MillerRabin *mr)
+{
+ mp_free(mr->pm1);
+ mp_free(mr->m_pm1);
+ mp_free(mr->lowbit);
+ mp_free(mr->two);
+ monty_free(mr->mc);
+ smemclr(mr, sizeof(*mr));
+ sfree(mr);
+}
+
+/*
+ * The main internal function that implements a single M-R test.
+ *
+ * Expects the witness integer to be in Montgomery representation.
+ * (Since in live use witnesses are invented at random, this imposes
+ * no extra cost on the callers, and saves effort in here.)
+ */
+static struct mr_result miller_rabin_test_inner(MillerRabin *mr, mp_int *mw)
+{
+ mp_int *acc = mp_copy(monty_identity(mr->mc));
+ mp_int *spare = mp_new(mp_max_bits(mr->pm1));
+ size_t bit = mp_max_bits(mr->pm1);
+
+ /*
+ * The obvious approach to Miller-Rabin would be to start by
+ * calling monty_pow to raise w to the power q, and then square it
+ * k times ourselves. But that introduces a timing leak that gives
+ * away the value of k, i.e., how many factors of 2 there are in
+ * p-1.
+ *
+ * Instead, we don't call monty_pow at all. We do a modular
+ * exponentiation ourselves to compute w^((p-1)/2), using the
+ * technique that works from the top bit of the exponent
+ * downwards. That is, in each iteration we compute
+ * w^floor(exponent/2^i) for i one less than the previous
+ * iteration, by squaring the value we previously had and then
+ * optionally multiplying in w if the next exponent bit is 1.
+ *
+ * At the end of that process, once i <= k, the division
+ * (exponent/2^i) yields an integer, so the values we're computing
+ * are not just w^(floor of that), but w^(exactly that). In other
+ * words, the last k intermediate values of this modexp are
+ * precisely the values M-R wants to check against +1 or -1.
+ *
+ * So we interleave those checks with the modexp loop itself, and
+ * to avoid a timing leak, we check _every_ intermediate result
+ * against (the Montgomery representations of) both +1 and -1. And
+ * then we do bitwise masking to arrange that only the sensible
+ * ones of those checks find their way into our final answer.
+ */
+
+ unsigned active = 0;
+
+ struct mr_result result;
+ result.passed = result.potential_primitive_root = 0;
+
+ while (bit-- > 1) {
+ /*
+ * In this iteration, we're computing w^(2e) or w^(2e+1),
+ * where we have w^e from the previous iteration. So we square
+ * the value we had already, and then optionally multiply in
+ * another copy of w depending on the next bit of the exponent.
+ */
+ monty_mul_into(mr->mc, acc, acc, acc);
+ monty_mul_into(mr->mc, spare, acc, mw);
+ mp_select_into(acc, acc, spare, mp_get_bit(mr->pm1, bit));
+
+ /*
+ * mr->lowbit is a number with only one bit set, corresponding
+ * to the lowest set bit in p-1. So when that's the bit of the
+ * exponent we've just processed, we'll detect it by setting
+ * first_iter to true. That's our indication that we're now
+ * generating intermediate results useful to M-R, so we also
+ * set 'active', which stays set from then on.
+ */
+ unsigned first_iter = mp_get_bit(mr->lowbit, bit);
+ active |= first_iter;
+
+ /*
+ * Check the intermediate result against both +1 and -1.
+ */
+ unsigned is_plus_1 = mp_cmp_eq(acc, monty_identity(mr->mc));
+ unsigned is_minus_1 = mp_cmp_eq(acc, mr->m_pm1);
+
+ /*
+ * M-R must report success iff either: the first of the useful
+ * intermediate results (which is w^q) is 1, or _any_ of them
+ * (from w^q all the way up to w^((p-1)/2)) is -1.
+ *
+ * So we want to pass the test if is_plus_1 is set on the
+ * first iteration, or if is_minus_1 is set on any iteration.
+ */
+ result.passed |= (first_iter & is_plus_1);
+ result.passed |= (active & is_minus_1);
+
+ /*
+ * In the final iteration, is_minus_1 is also used to set the
+ * 'potential primitive root' flag, because we haven't found
+ * any exponent smaller than p-1 for which w^(that) == 1.
+ */
+ if (bit == 1)
+ result.potential_primitive_root = is_minus_1;
+ }
+
+ mp_free(acc);
+ mp_free(spare);
+
+ return result;
+}
+
+/*
+ * Wrapper on miller_rabin_test_inner for the convenience of
+ * testcrypt. Expects the witness integer to be literal, so we
+ * monty_import it before running the real test.
+ */
+struct mr_result miller_rabin_test(MillerRabin *mr, mp_int *w)
+{
+ mp_int *mw = monty_import(mr->mc, w);
+ struct mr_result result = miller_rabin_test_inner(mr, mw);
+ mp_free(mw);
+ return result;
+}
+
+bool miller_rabin_test_random(MillerRabin *mr)
+{
+ mp_int *mw = mp_random_in_range(mr->two, mr->pm1);
+ struct mr_result result = miller_rabin_test_inner(mr, mw);
+ mp_free(mw);
+ return result.passed;
+}
+
+mp_int *miller_rabin_find_potential_primitive_root(MillerRabin *mr)
+{
+ while (true) {
+ mp_int *mw = mp_unsafe_shrink(mp_random_in_range(mr->two, mr->pm1));
+ struct mr_result result = miller_rabin_test_inner(mr, mw);
+
+ if (result.passed && result.potential_primitive_root) {
+ mp_int *pr = monty_export(mr->mc, mw);
+ mp_free(mw);
+ return pr;
+ }
+
+ mp_free(mw);
+
+ if (!result.passed) {
+ return NULL;
+ }
+ }
+}
+
+unsigned miller_rabin_checks_needed(unsigned bits)
+{
+ /* Table 4.4 from Handbook of Applied Cryptography */
+ return (bits >= 1300 ? 2 : bits >= 850 ? 3 : bits >= 650 ? 4 :
+ bits >= 550 ? 5 : bits >= 450 ? 6 : bits >= 400 ? 7 :
+ bits >= 350 ? 8 : bits >= 300 ? 9 : bits >= 250 ? 12 :
+ bits >= 200 ? 15 : bits >= 150 ? 18 : 27);
+}
+
diff --git a/keygen/mpunsafe.c b/keygen/mpunsafe.c
new file mode 100644
index 00000000..2cd7a37a
--- /dev/null
+++ b/keygen/mpunsafe.c
@@ -0,0 +1,48 @@
+#include <assert.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include "defs.h"
+#include "misc.h"
+#include "puttymem.h"
+
+#include "mpint.h"
+#include "mpunsafe.h"
+#include "crypto/mpint_i.h"
+
+/*
+ * This global symbol is also defined in ssh/kex2-client.c, to ensure
+ * that these unsafe non-constant-time mp_int functions can't end up
+ * accidentally linked in to any PuTTY tool that actually makes an SSH
+ * client connection.
+ *
+ * (Only _client_ connections, however. Uppity, being a test server
+ * only, is exempt.)
+ */
+const int deliberate_symbol_clash = 12345;
+
+static size_t mp_unsafe_words_needed(mp_int *x)
+{
+ size_t words = x->nw;
+ while (words > 1 && !x->w[words-1])
+ words--;
+ return words;
+}
+
+mp_int *mp_unsafe_shrink(mp_int *x)
+{
+ x->nw = mp_unsafe_words_needed(x);
+ /* This potentially leaves some allocated words between the new
+ * and old values of x->nw, which won't be wiped by mp_free now
+ * that x->nw doesn't mention that they exist. But we've just
+ * checked they're all zero, so we don't need to wipe them now
+ * either. */
+ return x;
+}
+
+mp_int *mp_unsafe_copy(mp_int *x)
+{
+ mp_int *copy = mp_make_sized(mp_unsafe_words_needed(x));
+ mp_copy_into(copy, x);
+ return copy;
+}
diff --git a/keygen/mpunsafe.h b/keygen/mpunsafe.h
new file mode 100644
index 00000000..07215372
--- /dev/null
+++ b/keygen/mpunsafe.h
@@ -0,0 +1,39 @@
+/*
+ * mpunsafe.h: functions that deal with mp_ints in ways that are *not*
+ * expected to be constant-time. Used during key generation, in which
+ * constant run time is a lost cause anyway.
+ *
+ * These functions are in a separate header, so that you can easily
+ * check that you're not calling them in the wrong context. They're
+ * also defined in a separate source file, which is only linked in to
+ * the key generation tools. Furthermore, that source file also
+ * defines a global symbol that intentionally conflicts with one
+ * defined in the SSH client code, so that any attempt to put these
+ * functions into the same binary as the live SSH client
+ * implementation will cause a link-time failure. They should only be
+ * linked into PuTTYgen and auxiliary test programs.
+ *
+ * Also, just in case those precautions aren't enough, all the unsafe
+ * functions have 'unsafe' in the name.
+ */
+
+#ifndef PUTTY_MPINT_UNSAFE_H
+#define PUTTY_MPINT_UNSAFE_H
+
+/*
+ * The most obvious unsafe thing you want to do with an mp_int is to
+ * get rid of leading zero words in its representation, so that its
+ * nominal size is as close as possible to its true size, and you
+ * don't waste any time processing it.
+ *
+ * mp_unsafe_shrink performs this operation in place, mutating the
+ * size field of the mp_int it's given. It returns the same pointer it
+ * was given.
+ *
+ * mp_unsafe_copy leaves the original mp_int alone and makes a new one
+ * with the minimal size.
+ */
+mp_int *mp_unsafe_shrink(mp_int *m);
+mp_int *mp_unsafe_copy(mp_int *m);
+
+#endif /* PUTTY_MPINT_UNSAFE_H */
diff --git a/keygen/pockle.c b/keygen/pockle.c
new file mode 100644
index 00000000..2a072f18
--- /dev/null
+++ b/keygen/pockle.c
@@ -0,0 +1,450 @@
+#include <assert.h>
+#include "ssh.h"
+#include "sshkeygen.h"
+#include "mpint.h"
+#include "mpunsafe.h"
+#include "tree234.h"
+
+typedef struct PocklePrimeRecord PocklePrimeRecord;
+
+struct Pockle {
+ tree234 *tree;
+
+ PocklePrimeRecord **list;
+ size_t nlist, listsize;
+};
+
+struct PocklePrimeRecord {
+ mp_int *prime;
+ PocklePrimeRecord **factors;
+ size_t nfactors;
+ mp_int *witness;
+
+ size_t index; /* index in pockle->list */
+};
+
+static int ppr_cmp(void *av, void *bv)
+{
+ PocklePrimeRecord *a = (PocklePrimeRecord *)av;
+ PocklePrimeRecord *b = (PocklePrimeRecord *)bv;
+ return mp_cmp_hs(a->prime, b->prime) - mp_cmp_hs(b->prime, a->prime);
+}
+
+static int ppr_find(void *av, void *bv)
+{
+ mp_int *a = (mp_int *)av;
+ PocklePrimeRecord *b = (PocklePrimeRecord *)bv;
+ return mp_cmp_hs(a, b->prime) - mp_cmp_hs(b->prime, a);
+}
+
+Pockle *pockle_new(void)
+{
+ Pockle *pockle = snew(Pockle);
+ pockle->tree = newtree234(ppr_cmp);
+ pockle->list = NULL;
+ pockle->nlist = pockle->listsize = 0;
+ return pockle;
+}
+
+void pockle_free(Pockle *pockle)
+{
+ pockle_release(pockle, 0);
+ assert(count234(pockle->tree) == 0);
+ freetree234(pockle->tree);
+ sfree(pockle->list);
+ sfree(pockle);
+}
+
+static PockleStatus pockle_insert(Pockle *pockle, mp_int *p, mp_int **factors,
+ size_t nfactors, mp_int *w)
+{
+ PocklePrimeRecord *pr = snew(PocklePrimeRecord);
+ pr->prime = mp_copy(p);
+
+ PocklePrimeRecord *found = add234(pockle->tree, pr);
+ if (pr != found) {
+ /* it was already in there */
+ mp_free(pr->prime);
+ sfree(pr);
+ return POCKLE_OK;
+ }
+
+ if (w) {
+ pr->factors = snewn(nfactors, PocklePrimeRecord *);
+ for (size_t i = 0; i < nfactors; i++) {
+ pr->factors[i] = find234(pockle->tree, factors[i], ppr_find);
+ assert(pr->factors[i]);
+ }
+ pr->nfactors = nfactors;
+ pr->witness = mp_copy(w);
+ } else {
+ pr->factors = NULL;
+ pr->nfactors = 0;
+ pr->witness = NULL;
+ }
+ pr->index = pockle->nlist;
+
+ sgrowarray(pockle->list, pockle->listsize, pockle->nlist);
+ pockle->list[pockle->nlist++] = pr;
+ return POCKLE_OK;
+}
+
+size_t pockle_mark(Pockle *pockle)
+{
+ return pockle->nlist;
+}
+
+void pockle_release(Pockle *pockle, size_t mark)
+{
+ while (pockle->nlist > mark) {
+ PocklePrimeRecord *pr = pockle->list[--pockle->nlist];
+ del234(pockle->tree, pr);
+ mp_free(pr->prime);
+ if (pr->witness)
+ mp_free(pr->witness);
+ sfree(pr->factors);
+ sfree(pr);
+ }
+}
+
+PockleStatus pockle_add_small_prime(Pockle *pockle, mp_int *p)
+{
+ if (mp_hs_integer(p, (1ULL << 32)))
+ return POCKLE_SMALL_PRIME_NOT_SMALL;
+
+ uint32_t val = mp_get_integer(p);
+
+ if (val < 2)
+ return POCKLE_PRIME_SMALLER_THAN_2;
+
+ init_smallprimes();
+ for (size_t i = 0; i < NSMALLPRIMES; i++) {
+ if (val == smallprimes[i])
+ break; /* success */
+ if (val % smallprimes[i] == 0)
+ return POCKLE_SMALL_PRIME_NOT_PRIME;
+ }
+
+ return pockle_insert(pockle, p, NULL, 0, NULL);
+}
+
+PockleStatus pockle_add_prime(Pockle *pockle, mp_int *p,
+ mp_int **factors, size_t nfactors,
+ mp_int *witness)
+{
+ MontyContext *mc = NULL;
+ mp_int *x = NULL, *f = NULL, *w = NULL;
+ PockleStatus status;
+
+ /*
+ * We're going to try to verify that p is prime by using
+ * Pocklington's theorem. The idea is that we're given w such that
+ * w^{p-1} == 1 (mod p) (1)
+ * and for a collection of primes q | p-1,
+ * w^{(p-1)/q} - 1 is coprime to p. (2)
+ *
+ * Suppose r is a prime factor of p itself. Consider the
+ * multiplicative order of w mod r. By (1), r | w^{p-1}-1. But by
+ * (2), r does not divide w^{(p-1)/q}-1. So the order of w mod r
+ * is a factor of p-1, but not a factor of (p-1)/q. Hence, the
+ * largest power of q that divides p-1 must also divide ord w.
+ *
+ * Repeating this reasoning for all q, we find that the product of
+ * all the q (which we'll denote f) must divide ord w, which in
+ * turn divides r-1. So f | r-1 for any r | p.
+ *
+ * In particular, this means f < r. That is, all primes r | p are
+ * bigger than f. So if f > sqrt(p), then we've shown p is prime,
+ * because otherwise it would have to be the product of at least
+ * two factors bigger than its own square root.
+ *
+ * With an extra check, we can also show p to be prime even if
+ * we're only given enough factors to make f > cbrt(p). See below
+ * for that part, when we come to it.
+ */
+
+ /*
+ * Start by checking p > 1. It certainly can't be prime otherwise!
+ * (And since we're going to prove it prime by showing all its
+ * prime factors are large, we do also have to know it _has_ at
+ * least one prime factor for that to tell us anything.)
+ */
+ if (!mp_hs_integer(p, 2))
+ return POCKLE_PRIME_SMALLER_THAN_2;
+
+ /*
+ * Check that all the factors we've been given really are primes
+ * (in the sense that we already had them in our index). Make the
+ * product f, and check it really does divide p-1.
+ */
+ x = mp_copy(p);
+ mp_sub_integer_into(x, x, 1);
+ f = mp_from_integer(1);
+ for (size_t i = 0; i < nfactors; i++) {
+ mp_int *q = factors[i];
+
+ if (!find234(pockle->tree, q, ppr_find)) {
+ status = POCKLE_FACTOR_NOT_KNOWN_PRIME;
+ goto out;
+ }
+
+ mp_int *quotient = mp_new(mp_max_bits(x));
+ mp_int *residue = mp_new(mp_max_bits(q));
+ mp_divmod_into(x, q, quotient, residue);
+
+ unsigned exact = mp_eq_integer(residue, 0);
+ mp_free(residue);
+
+ mp_free(x);
+ x = quotient;
+
+ if (!exact) {
+ status = POCKLE_FACTOR_NOT_A_FACTOR;
+ goto out;
+ }
+
+ mp_int *tmp = f;
+ f = mp_unsafe_shrink(mp_mul(tmp, q));
+ mp_free(tmp);
+ }
+
+ /*
+ * Check that f > cbrt(p).
+ */
+ mp_int *f2 = mp_mul(f, f);
+ mp_int *f3 = mp_mul(f2, f);
+ bool too_big = mp_cmp_hs(p, f3);
+ mp_free(f3);
+ mp_free(f2);
+ if (too_big) {
+ status = POCKLE_PRODUCT_OF_FACTORS_TOO_SMALL;
+ goto out;
+ }
+
+ /*
+ * Now do the extra check that allows us to get away with only
+ * having f > cbrt(p) instead of f > sqrt(p).
+ *
+ * If we can show that f | r-1 for any r | p, then we've ruled out
+ * p being a product of _more_ than two primes (because then it
+ * would be the product of at least three things bigger than its
+ * own cube root). But we still have to rule out it being a
+ * product of exactly two.
+ *
+ * Suppose for the sake of contradiction that p is the product of
+ * two prime factors. We know both of those factors would have to
+ * be congruent to 1 mod f. So we'd have to have
+ *
+ * p = (uf+1)(vf+1) = (uv)f^2 + (u+v)f + 1 (3)
+ *
+ * We can't have uv >= f, or else that expression would come to at
+ * least f^3, i.e. it would exceed p. So uv < f. Hence, u,v < f as
+ * well.
+ *
+ * Can we have u+v >= f? If we did, then we could write v >= f-u,
+ * and hence f > uv >= u(f-u). That can be rearranged to show that
+ * u^2 > (u-1)f; decrementing the LHS makes the inequality no
+ * longer necessarily strict, so we have u^2-1 >= (u-1)f, and
+ * dividing off u-1 gives u+1 >= f. But we know u < f, so the only
+ * way this could happen would be if u=f-1, which makes v=1. But
+ * _then_ (3) gives us p = (f-1)f^2 + f^2 + 1 = f^3+1. But that
+ * can't be true if f^3 > p. So we can't have u+v >= f either, by
+ * contradiction.
+ *
+ * After all that, what have we shown? We've shown that we can
+ * write p = (uv)f^2 + (u+v)f + 1, with both uv and u+v strictly
+ * less than f. In other words, if you write down p in base f, it
+ * has exactly three digits, and they are uv, u+v and 1.
+ *
+ * But that means we can _find_ u and v: we know p and f, so we
+ * can just extract those digits of p's base-f representation.
+ * Once we've done so, they give the sum and product of the
+ * potential u,v. And given the sum and product of two numbers,
+ * you can make a quadratic which has those numbers as roots.
+ *
+ * We don't actually have to _solve_ the quadratic: all we have to
+ * do is check if its discriminant is a perfect square. If not,
+ * we'll know that no integers u,v can match this description.
+ */
+ {
+ /* We already have x = (p-1)/f. So we just need to write x in
+ * the form aF + b, and then we have a=uv and b=u+v. */
+ mp_int *a = mp_new(mp_max_bits(x));
+ mp_int *b = mp_new(mp_max_bits(f));
+ mp_divmod_into(x, f, a, b);
+ assert(!mp_cmp_hs(a, f));
+ assert(!mp_cmp_hs(b, f));
+
+ /* If a=0, then that means p < f^2, so we don't need to do
+ * this check at all: the straightforward Pocklington theorem
+ * is all we need. */
+ if (!mp_eq_integer(a, 0)) {
+ unsigned perfect_square = 0;
+
+ mp_int *bsq = mp_mul(b, b);
+ mp_lshift_fixed_into(a, a, 2);
+
+ if (mp_cmp_hs(bsq, a)) {
+ /* b^2-4a is non-negative, so it might be a square.
+ * Check it. */
+ mp_int *discriminant = mp_sub(bsq, a);
+ mp_int *remainder = mp_new(mp_max_bits(discriminant));
+ mp_int *root = mp_nthroot(discriminant, 2, remainder);
+ perfect_square = mp_eq_integer(remainder, 0);
+ mp_free(discriminant);
+ mp_free(root);
+ mp_free(remainder);
+ }
+
+ mp_free(bsq);
+
+ if (perfect_square) {
+ mp_free(b);
+ mp_free(a);
+ status = POCKLE_DISCRIMINANT_IS_SQUARE;
+ goto out;
+ }
+ }
+ mp_free(b);
+ mp_free(a);
+ }
+
+ /*
+ * Now we've done all the checks that are cheaper than a modpow,
+ * so we've ruled out as many things as possible before having to
+ * do any hard work. But there's nothing for it now: make a
+ * MontyContext.
+ */
+ mc = monty_new(p);
+ w = monty_import(mc, witness);
+
+ /*
+ * The initial Fermat check: is w^{p-1} itself congruent to 1 mod
+ * p?
+ */
+ {
+ mp_int *pm1 = mp_copy(p);
+ mp_sub_integer_into(pm1, pm1, 1);
+ mp_int *power = monty_pow(mc, w, pm1);
+ unsigned fermat_pass = mp_cmp_eq(power, monty_identity(mc));
+ mp_free(power);
+ mp_free(pm1);
+
+ if (!fermat_pass) {
+ status = POCKLE_FERMAT_TEST_FAILED;
+ goto out;
+ }
+ }
+
+ /*
+ * And now, for each factor q, is w^{(p-1)/q}-1 coprime to p?
+ */
+ for (size_t i = 0; i < nfactors; i++) {
+ mp_int *q = factors[i];
+ mp_int *exponent = mp_unsafe_shrink(mp_div(p, q));
+ mp_int *power = monty_pow(mc, w, exponent);
+ mp_int *power_extracted = monty_export(mc, power);
+ mp_sub_integer_into(power_extracted, power_extracted, 1);
+
+ unsigned coprime = mp_coprime(power_extracted, p);
+ if (!coprime) {
+ /*
+ * If w^{(p-1)/q}-1 is not coprime to p, the test has
+ * failed. But it makes a difference why. If the power of
+ * w turned out to be 1, so that we took gcd(1-1,p) =
+ * gcd(0,p) = p, that's like an inconclusive Fermat or M-R
+ * test: it might just mean you picked a witness integer
+ * that wasn't a primitive root. But if the power is any
+ * _other_ value mod p that is not coprime to p, it means
+ * we've detected that the number is *actually not prime*!
+ */
+ if (mp_eq_integer(power_extracted, 0))
+ status = POCKLE_WITNESS_POWER_IS_1;
+ else
+ status = POCKLE_WITNESS_POWER_NOT_COPRIME;
+ }
+
+ mp_free(exponent);
+ mp_free(power);
+ mp_free(power_extracted);
+
+ if (!coprime)
+ goto out; /* with the status we set up above */
+ }
+
+ /*
+ * Success! p is prime. Insert it into our tree234 of known
+ * primes, so that future calls to this function can cite it in
+ * evidence of larger numbers' primality.
+ */
+ status = pockle_insert(pockle, p, factors, nfactors, witness);
+
+ out:
+ if (x)
+ mp_free(x);
+ if (f)
+ mp_free(f);
+ if (w)
+ mp_free(w);
+ if (mc)
+ monty_free(mc);
+ return status;
+}
+
+static void mp_write_decimal(strbuf *sb, mp_int *x)
+{
+ char *s = mp_get_decimal(x);
+ ptrlen pl = ptrlen_from_asciz(s);
+ put_datapl(sb, pl);
+ smemclr(s, pl.len);
+ sfree(s);
+}
+
+strbuf *pockle_mpu(Pockle *pockle, mp_int *p)
+{
+ strbuf *sb = strbuf_new_nm();
+ PocklePrimeRecord *pr = find234(pockle->tree, p, ppr_find);
+ assert(pr);
+
+ bool *needed = snewn(pockle->nlist, bool);
+ memset(needed, 0, pockle->nlist * sizeof(bool));
+ needed[pr->index] = true;
+
+ put_fmt(sb, "[MPU - Primality Certificate]\nVersion 1.0\nBase 10\n\n"
+ "Proof for:\nN ");
+ mp_write_decimal(sb, p);
+ put_fmt(sb, "\n");
+
+ for (size_t index = pockle->nlist; index-- > 0 ;) {
+ if (!needed[index])
+ continue;
+ pr = pockle->list[index];
+
+ if (mp_get_nbits(pr->prime) <= 64) {
+ put_fmt(sb, "\nType Small\nN ");
+ mp_write_decimal(sb, pr->prime);
+ put_fmt(sb, "\n");
+ } else {
+ assert(pr->witness);
+ put_fmt(sb, "\nType BLS5\nN ");
+ mp_write_decimal(sb, pr->prime);
+ put_fmt(sb, "\n");
+ for (size_t i = 0; i < pr->nfactors; i++) {
+ put_fmt(sb, "Q[%"SIZEu"] ", i+1);
+ mp_write_decimal(sb, pr->factors[i]->prime);
+ assert(pr->factors[i]->index < index);
+ needed[pr->factors[i]->index] = true;
+ put_fmt(sb, "\n");
+ }
+ for (size_t i = 0; i < pr->nfactors + 1; i++) {
+ put_fmt(sb, "A[%"SIZEu"] ", i);
+ mp_write_decimal(sb, pr->witness);
+ put_fmt(sb, "\n");
+ }
+ put_fmt(sb, "----\n");
+ }
+ }
+ sfree(needed);
+
+ return sb;
+}
diff --git a/SSHPRIME.C b/keygen/prime.c
index d9bdebba..d9bdebba 100644
--- a/SSHPRIME.C
+++ b/keygen/prime.c
diff --git a/keygen/primecandidate.c b/keygen/primecandidate.c
new file mode 100644
index 00000000..fca2b297
--- /dev/null
+++ b/keygen/primecandidate.c
@@ -0,0 +1,447 @@
+/*
+ * primecandidate.c: implementation of the PrimeCandidateSource
+ * abstraction declared in sshkeygen.h.
+ */
+
+#include <assert.h>
+#include "ssh.h"
+#include "mpint.h"
+#include "mpunsafe.h"
+#include "sshkeygen.h"
+
+struct avoid {
+ unsigned mod, res;
+};
+
+struct PrimeCandidateSource {
+ unsigned bits;
+ bool ready, try_sophie_germain;
+ bool one_shot, thrown_away_my_shot;
+
+ /* We'll start by making up a random number strictly less than this ... */
+ mp_int *limit;
+
+ /* ... then we'll multiply by 'factor', and add 'addend'. */
+ mp_int *factor, *addend;
+
+ /* Then we'll try to add a small multiple of 'factor' to it to
+ * avoid it being a multiple of any small prime. Also, for RSA, we
+ * may need to avoid it being _this_ multiple of _this_: */
+ unsigned avoid_residue, avoid_modulus;
+
+ /* Once we're actually running, this will be the complete list of
+ * (modulus, residue) pairs we want to avoid. */
+ struct avoid *avoids;
+ size_t navoids, avoidsize;
+
+ /* List of known primes that our number will be congruent to 1 modulo */
+ mp_int **kps;
+ size_t nkps, kpsize;
+};
+
+PrimeCandidateSource *pcs_new_with_firstbits(unsigned bits,
+ unsigned first, unsigned nfirst)
+{
+ PrimeCandidateSource *s = snew(PrimeCandidateSource);
+
+ assert(first >> (nfirst-1) == 1);
+
+ s->bits = bits;
+ s->ready = false;
+ s->try_sophie_germain = false;
+ s->one_shot = false;
+ s->thrown_away_my_shot = false;
+
+ s->kps = NULL;
+ s->nkps = s->kpsize = 0;
+
+ s->avoids = NULL;
+ s->navoids = s->avoidsize = 0;
+
+ /* Make the number that's the lower limit of our range */
+ mp_int *firstmp = mp_from_integer(first);
+ mp_int *base = mp_lshift_fixed(firstmp, bits - nfirst);
+ mp_free(firstmp);
+
+ /* Set the low bit of that, because all (nontrivial) primes are odd */
+ mp_set_bit(base, 0, 1);
+
+ /* That's our addend. Now initialise factor to 2, to ensure we
+ * only generate odd numbers */
+ s->factor = mp_from_integer(2);
+ s->addend = base;
+
+ /* And that means the limit of our random numbers must be one
+ * factor of two _less_ than the position of the low bit of
+ * 'first', because we'll be multiplying the random number by
+ * 2 immediately afterwards. */
+ s->limit = mp_power_2(bits - nfirst - 1);
+
+ /* avoid_modulus == 0 signals that there's no extra residue to avoid */
+ s->avoid_residue = 1;
+ s->avoid_modulus = 0;
+
+ return s;
+}
+
+PrimeCandidateSource *pcs_new(unsigned bits)
+{
+ return pcs_new_with_firstbits(bits, 1, 1);
+}
+
+void pcs_free(PrimeCandidateSource *s)
+{
+ mp_free(s->limit);
+ mp_free(s->factor);
+ mp_free(s->addend);
+ for (size_t i = 0; i < s->nkps; i++)
+ mp_free(s->kps[i]);
+ sfree(s->avoids);
+ sfree(s->kps);
+ sfree(s);
+}
+
+void pcs_try_sophie_germain(PrimeCandidateSource *s)
+{
+ s->try_sophie_germain = true;
+}
+
+void pcs_set_oneshot(PrimeCandidateSource *s)
+{
+ s->one_shot = true;
+}
+
+static void pcs_require_residue_inner(PrimeCandidateSource *s,
+ mp_int *mod, mp_int *res)
+{
+ /*
+ * We already have a factor and addend. Ensure this one doesn't
+ * contradict it.
+ */
+ mp_int *gcd = mp_gcd(mod, s->factor);
+ mp_int *test1 = mp_mod(s->addend, gcd);
+ mp_int *test2 = mp_mod(res, gcd);
+ assert(mp_cmp_eq(test1, test2));
+ mp_free(test1);
+ mp_free(test2);
+
+ /*
+ * Reduce our input factor and addend, which are constraints on
+ * the ultimate output number, so that they're constraints on the
+ * initial cofactor we're going to make up.
+ *
+ * If we're generating x and we want to ensure ax+b == r (mod m),
+ * how does that work? We've already checked that b == r modulo g
+ * = gcd(a,m), i.e. r-b is a multiple of g, and so are a and m. So
+ * let's write a=gA, m=gM, (r-b)=gR, and then we can start by
+ * dividing that off:
+ *
+ * ax == r-b (mod m )
+ * => gAx == gR (mod gM)
+ * => Ax == R (mod M)
+ *
+ * Now the moduli A,M are coprime, which makes things easier.
+ *
+ * We're going to need to generate the x in this equation by
+ * generating a new smaller value y, multiplying it by M, and
+ * adding some constant K. So we have x = My + K, and we need to
+ * work out what K will satisfy the above equation. In other
+ * words, we need A(My+K) == R (mod M), and the AMy term vanishes,
+ * so we just need AK == R (mod M). So our congruence is solved by
+ * setting K to be R * A^{-1} mod M.
+ */
+ mp_int *A = mp_div(s->factor, gcd);
+ mp_int *M = mp_div(mod, gcd);
+ mp_int *Rpre = mp_modsub(res, s->addend, mod);
+ mp_int *R = mp_div(Rpre, gcd);
+ mp_int *Ainv = mp_invert(A, M);
+ mp_int *K = mp_modmul(R, Ainv, M);
+
+ mp_free(gcd);
+ mp_free(Rpre);
+ mp_free(Ainv);
+ mp_free(A);
+ mp_free(R);
+
+ /*
+ * So we know we have to transform our existing (factor, addend)
+ * pair into (factor * M, addend * factor * K). Now we just need
+ * to work out what the limit should be on the random value we're
+ * generating.
+ *
+ * If we need My+K < old_limit, then y < (old_limit-K)/M. But the
+ * RHS is a fraction, so in integers, we need y < ceil of it.
+ */
+ assert(!mp_cmp_hs(K, s->limit));
+ mp_int *dividend = mp_add(s->limit, M);
+ mp_sub_integer_into(dividend, dividend, 1);
+ mp_sub_into(dividend, dividend, K);
+ mp_free(s->limit);
+ s->limit = mp_div(dividend, M);
+ mp_free(dividend);
+
+ /*
+ * Now just update the real factor and addend, and we're done.
+ */
+
+ mp_int *addend_old = s->addend;
+ mp_int *tmp = mp_mul(s->factor, K); /* use the _old_ value of factor */
+ s->addend = mp_add(s->addend, tmp);
+ mp_free(tmp);
+ mp_free(addend_old);
+
+ mp_int *factor_old = s->factor;
+ s->factor = mp_mul(s->factor, M);
+ mp_free(factor_old);
+
+ mp_free(M);
+ mp_free(K);
+ s->factor = mp_unsafe_shrink(s->factor);
+ s->addend = mp_unsafe_shrink(s->addend);
+ s->limit = mp_unsafe_shrink(s->limit);
+}
+
+void pcs_require_residue(PrimeCandidateSource *s,
+ mp_int *mod, mp_int *res_orig)
+{
+ /*
+ * Reduce the input residue to its least non-negative value, in
+ * case it was given as a larger equivalent value.
+ */
+ mp_int *res_reduced = mp_mod(res_orig, mod);
+ pcs_require_residue_inner(s, mod, res_reduced);
+ mp_free(res_reduced);
+}
+
+void pcs_require_residue_1(PrimeCandidateSource *s, mp_int *mod)
+{
+ mp_int *res = mp_from_integer(1);
+ pcs_require_residue(s, mod, res);
+ mp_free(res);
+}
+
+void pcs_require_residue_1_mod_prime(PrimeCandidateSource *s, mp_int *mod)
+{
+ pcs_require_residue_1(s, mod);
+
+ sgrowarray(s->kps, s->kpsize, s->nkps);
+ s->kps[s->nkps++] = mp_copy(mod);
+}
+
+void pcs_avoid_residue_small(PrimeCandidateSource *s,
+ unsigned mod, unsigned res)
+{
+ assert(!s->avoid_modulus); /* can't cope with more than one */
+ s->avoid_modulus = mod;
+ s->avoid_residue = res % mod; /* reduce, just in case */
+}
+
+static int avoid_cmp(const void *av, const void *bv)
+{
+ const struct avoid *a = (const struct avoid *)av;
+ const struct avoid *b = (const struct avoid *)bv;
+ return a->mod < b->mod ? -1 : a->mod > b->mod ? +1 : 0;
+}
+
+static uint64_t invert(uint64_t a, uint64_t m)
+{
+ int64_t v0 = a, i0 = 1;
+ int64_t v1 = m, i1 = 0;
+ while (v0) {
+ int64_t tmp, q = v1 / v0;
+ tmp = v0; v0 = v1 - q*v0; v1 = tmp;
+ tmp = i0; i0 = i1 - q*i0; i1 = tmp;
+ }
+ assert(v1 == 1 || v1 == -1);
+ return i1 * v1;
+}
+
+void pcs_ready(PrimeCandidateSource *s)
+{
+ /*
+ * List all the small (modulus, residue) pairs we want to avoid.
+ */
+
+ init_smallprimes();
+
+#define ADD_AVOID(newmod, newres) do { \
+ sgrowarray(s->avoids, s->avoidsize, s->navoids); \
+ s->avoids[s->navoids].mod = (newmod); \
+ s->avoids[s->navoids].res = (newres); \
+ s->navoids++; \
+ } while (0)
+
+ unsigned limit = (mp_hs_integer(s->addend, 65536) ? 65536 :
+ mp_get_integer(s->addend));
+
+ /*
+ * Don't be divisible by any small prime, or at least, any prime
+ * smaller than our output number might actually manage to be. (If
+ * asked to generate a really small prime, it would be
+ * embarrassing to rule out legitimate answers on the grounds that
+ * they were divisible by themselves.)
+ */
+ for (size_t i = 0; i < NSMALLPRIMES && smallprimes[i] < limit; i++)
+ ADD_AVOID(smallprimes[i], 0);
+
+ if (s->try_sophie_germain) {
+ /*
+ * If we're aiming to generate a Sophie Germain prime (i.e. p
+ * such that 2p+1 is also prime), then we also want to ensure
+ * 2p+1 is not congruent to 0 mod any small prime, because if
+ * it is, we'll waste a lot of time generating a p for which
+ * 2p+1 can't possibly work. So we have to avoid an extra
+ * residue mod each odd q.
+ *
+ * We can simplify: 2p+1 == 0 (mod q)
+ * => 2p == -1 (mod q)
+ * => p == -2^{-1} (mod q)
+ *
+ * There's no need to do Euclid's algorithm to compute those
+ * inverses, because for any odd q, the modular inverse of -2
+ * mod q is just (q-1)/2. (Proof: multiplying it by -2 gives
+ * 1-q, which is congruent to 1 mod q.)
+ */
+ for (size_t i = 0; i < NSMALLPRIMES && smallprimes[i] < limit; i++)
+ if (smallprimes[i] != 2)
+ ADD_AVOID(smallprimes[i], (smallprimes[i] - 1) / 2);
+ }
+
+ /*
+ * Finally, if there's a particular modulus and residue we've been
+ * told to avoid, put it on the list.
+ */
+ if (s->avoid_modulus)
+ ADD_AVOID(s->avoid_modulus, s->avoid_residue);
+
+#undef ADD_AVOID
+
+ /*
+ * Sort our to-avoid list by modulus. Partly this is so that we'll
+ * check the smaller moduli first during the live runs, which lets
+ * us spot most failing cases earlier rather than later. Also, it
+ * brings equal moduli together, so that we can reuse the residue
+ * we computed from a previous one.
+ */
+ qsort(s->avoids, s->navoids, sizeof(*s->avoids), avoid_cmp);
+
+ /*
+ * Next, adjust each of these moduli to take account of our factor
+ * and addend. If we want factor*x+addend to avoid being congruent
+ * to 'res' modulo 'mod', then x itself must avoid being congruent
+ * to (res - addend) * factor^{-1}.
+ *
+ * If factor == 0 modulo mod, then the answer will have a fixed
+ * residue anyway, so we can discard it from our list to test.
+ */
+ int64_t factor_m = 0, addend_m = 0, last_mod = 0;
+
+ size_t out = 0;
+ for (size_t i = 0; i < s->navoids; i++) {
+ int64_t mod = s->avoids[i].mod, res = s->avoids[i].res;
+ if (mod != last_mod) {
+ last_mod = mod;
+ addend_m = mp_mod_known_integer(s->addend, mod);
+ factor_m = mp_mod_known_integer(s->factor, mod);
+ }
+
+ if (factor_m == 0) {
+ assert(res != addend_m);
+ continue;
+ }
+
+ res = (res - addend_m) * invert(factor_m, mod);
+ res %= mod;
+ if (res < 0)
+ res += mod;
+
+ s->avoids[out].mod = mod;
+ s->avoids[out].res = res;
+ out++;
+ }
+
+ s->navoids = out;
+
+ s->ready = true;
+}
+
+mp_int *pcs_generate(PrimeCandidateSource *s)
+{
+ assert(s->ready);
+ if (s->one_shot) {
+ if (s->thrown_away_my_shot)
+ return NULL;
+ s->thrown_away_my_shot = true;
+ }
+
+ while (true) {
+ mp_int *x = mp_random_upto(s->limit);
+
+ int64_t x_res = 0, last_mod = 0;
+ bool ok = true;
+
+ for (size_t i = 0; i < s->navoids; i++) {
+ int64_t mod = s->avoids[i].mod, avoid_res = s->avoids[i].res;
+
+ if (mod != last_mod) {
+ last_mod = mod;
+ x_res = mp_mod_known_integer(x, mod);
+ }
+
+ if (x_res == avoid_res) {
+ ok = false;
+ break;
+ }
+ }
+
+ if (!ok) {
+ mp_free(x);
+ if (s->one_shot)
+ return NULL;
+ continue; /* try a new x */
+ }
+
+ /*
+ * We've found a viable x. Make the final output value.
+ */
+ mp_int *toret = mp_new(s->bits);
+ mp_mul_into(toret, x, s->factor);
+ mp_add_into(toret, toret, s->addend);
+ mp_free(x);
+ return toret;
+ }
+}
+
+void pcs_inspect(PrimeCandidateSource *pcs, mp_int **limit_out,
+ mp_int **factor_out, mp_int **addend_out)
+{
+ *limit_out = mp_copy(pcs->limit);
+ *factor_out = mp_copy(pcs->factor);
+ *addend_out = mp_copy(pcs->addend);
+}
+
+unsigned pcs_get_bits(PrimeCandidateSource *pcs)
+{
+ return pcs->bits;
+}
+
+unsigned pcs_get_bits_remaining(PrimeCandidateSource *pcs)
+{
+ return mp_get_nbits(pcs->limit);
+}
+
+mp_int *pcs_get_upper_bound(PrimeCandidateSource *pcs)
+{
+ /* Compute (limit-1) * factor + addend */
+ mp_int *tmp = mp_mul(pcs->limit, pcs->factor);
+ mp_int *bound = mp_add(tmp, pcs->addend);
+ mp_free(tmp);
+ mp_sub_into(bound, bound, pcs->factor);
+ return bound;
+}
+
+mp_int **pcs_get_known_prime_factors(PrimeCandidateSource *pcs, size_t *nout)
+{
+ *nout = pcs->nkps;
+ return pcs->kps;
+}
diff --git a/SSHRSAG.C b/keygen/rsa.c
index b9676e7a..b9676e7a 100644
--- a/SSHRSAG.C
+++ b/keygen/rsa.c
diff --git a/smallprimes.c b/keygen/smallprimes.c
index a43b0bde..a43b0bde 100644
--- a/smallprimes.c
+++ b/keygen/smallprimes.c
diff --git a/ldisc.c b/ldisc.c
index f097c040..caff52d0 100644
--- a/ldisc.c
+++ b/ldisc.c
@@ -11,7 +11,62 @@
#include "putty.h"
#include "terminal.h"
-#include "ldisc.h"
+
+struct Ldisc_tag {
+ Terminal *term;
+ Backend *backend;
+ Seat *seat;
+
+ /*
+ * When the backend is not reporting true from sendok(), terminal
+ * input that comes here is stored in this bufchain instead. When
+ * the backend later decides it wants session input, we empty the
+ * queue in ldisc_check_sendok_callback(), passing its contents on
+ * to the backend. Before then, we also provide data from this
+ * queue to term_get_userpass_input() via ldisc_get_input_token(),
+ * to be interpreted as user responses to username and password
+ * prompts during authentication.
+ *
+ * Unfortunately, the data stored in this queue is not all of the
+ * same type: our output to the backend consists of both raw bytes
+ * sent to backend_send(), and also session specials such as
+ * SS_EOL and SS_EC. So we have to encode our queued data in a way
+ * that can represent both.
+ *
+ * The encoding is private to this source file, so we can change
+ * it if necessary and only have to worry about the encode and
+ * decode functions here. Currently, it is:
+ *
+ * - Bytes other than 0xFF are stored literally.
+ * - The byte 0xFF itself is stored as 0xFF 0xFF.
+ * - A session special (code, arg) is stored as 0xFF, followed by
+ * a big-endian 4-byte integer containing code, followed by
+ * another big-endian 4-byte integer containing arg.
+ *
+ * (This representation relies on session special codes being at
+ * most 0xFEFFFFFF when represented in 32 bits, so that the first
+ * byte of the 'code' integer can't be confused with the 0xFF
+ * followup byte indicating a literal 0xFF, But since session
+ * special codes are defined by an enum counting up from zero, and
+ * there are only a couple of dozen of them, that shouldn't be a
+ * problem! Even so, just in case, an assertion checks that at
+ * encode time.)
+ */
+ bufchain input_queue;
+
+ IdempotentCallback input_queue_callback;
+ prompts_t *prompts;
+
+ /*
+ * Values cached out of conf.
+ */
+ bool telnet_keyboard, telnet_newline;
+ int protocol, localecho, localedit;
+
+ char *buf;
+ size_t buflen, bufsiz;
+ bool quotenext;
+};
#define ECHOING (ldisc->localecho == FORCE_ON || \
(ldisc->localecho == AUTO && \
@@ -72,6 +127,8 @@ static void bsb(Ldisc *ldisc, int n)
c_write(ldisc, "\010 \010", 3);
}
+static void ldisc_input_queue_callback(void *ctx);
+
#define CTRL(x) (x^'@')
#define KCTRL(x) ((x^'@') | 0x100)
@@ -88,6 +145,14 @@ Ldisc *ldisc_create(Conf *conf, Terminal *term, Backend *backend, Seat *seat)
ldisc->term = term;
ldisc->seat = seat;
+ bufchain_init(&ldisc->input_queue);
+
+ ldisc->prompts = NULL;
+ ldisc->input_queue_callback.fn = ldisc_input_queue_callback;
+ ldisc->input_queue_callback.ctx = ldisc;
+ ldisc->input_queue_callback.queued = false;
+ bufchain_set_callback(&ldisc->input_queue, &ldisc->input_queue_callback);
+
ldisc_configure(ldisc, conf);
/* Link ourselves into the backend and the terminal */
@@ -110,12 +175,16 @@ void ldisc_configure(Ldisc *ldisc, Conf *conf)
void ldisc_free(Ldisc *ldisc)
{
+ bufchain_clear(&ldisc->input_queue);
if (ldisc->term)
ldisc->term->ldisc = NULL;
if (ldisc->backend)
backend_provide_ldisc(ldisc->backend, NULL);
if (ldisc->buf)
sfree(ldisc->buf);
+ if (ldisc->prompts && ldisc->prompts->ldisc_ptr_to_us == &ldisc->prompts)
+ ldisc->prompts->ldisc_ptr_to_us = NULL;
+ delete_callbacks_for_context(ldisc);
sfree(ldisc);
}
@@ -124,6 +193,173 @@ void ldisc_echoedit_update(Ldisc *ldisc)
seat_echoedit_update(ldisc->seat, ECHOING, EDITING);
}
+void ldisc_enable_prompt_callback(Ldisc *ldisc, prompts_t *prompts)
+{
+ /*
+ * Called by the terminal to indicate that there's a prompts_t
+ * currently in flight, or to indicate that one has just finished
+ * (by passing NULL). When ldisc->prompts is not null, we notify
+ * the terminal whenever new data arrives in our input queue, so
+ * that it can continue the interactive prompting process.
+ */
+ ldisc->prompts = prompts;
+ if (prompts)
+ ldisc->prompts->ldisc_ptr_to_us = &ldisc->prompts;
+}
+
+static void ldisc_input_queue_callback(void *ctx)
+{
+ /*
+ * Toplevel callback that is triggered whenever the input queue
+ * lengthens. If we're currently processing an interactive prompt,
+ * we call back the Terminal to tell it to do some more stuff with
+ * that prompt based on the new input.
+ */
+ Ldisc *ldisc = (Ldisc *)ctx;
+ if (ldisc->term && ldisc->prompts) {
+ /*
+ * The integer return value from this call is discarded,
+ * because we have no channel to pass it on to the backend
+ * that originally wanted it. But that's OK, because if the
+ * return value is >= 0 (that is, the prompts are either
+ * completely filled in, or aborted by the user), then the
+ * terminal will notify the callback in the prompts_t, and
+ * when that calls term_get_userpass_input again, it will
+ * return the same answer again.
+ */
+ term_get_userpass_input(ldisc->term, ldisc->prompts);
+ }
+}
+
+static void ldisc_to_backend_raw(
+ Ldisc *ldisc, const void *vbuf, size_t len)
+{
+ if (backend_sendok(ldisc->backend)) {
+ backend_send(ldisc->backend, vbuf, len);
+ } else {
+ const char *buf = (const char *)vbuf;
+ while (len > 0) {
+ /*
+ * Encode raw data in input_queue, by storing large chunks
+ * as long as they don't include 0xFF, and pausing every
+ * time they do to escape it.
+ */
+ const char *ff = memchr(buf, '\xFF', len);
+ size_t this_len = ff ? ff - buf : len;
+ if (this_len > 0) {
+ bufchain_add(&ldisc->input_queue, buf, len);
+ } else {
+ bufchain_add(&ldisc->input_queue, "\xFF\xFF", 2);
+ this_len = 1;
+ }
+ buf += this_len;
+ len -= this_len;
+ }
+ }
+}
+
+static void ldisc_to_backend_special(
+ Ldisc *ldisc, SessionSpecialCode code, int arg)
+{
+ if (backend_sendok(ldisc->backend)) {
+ backend_special(ldisc->backend, code, arg);
+ } else {
+ /*
+ * Encode a session special in input_queue.
+ */
+ unsigned char data[9];
+ data[0] = 0xFF;
+ PUT_32BIT_MSB_FIRST(data+1, code);
+ PUT_32BIT_MSB_FIRST(data+5, arg);
+ assert(data[1] != 0xFF &&
+ "SessionSpecialCode encoding collides with FF FF escape");
+ bufchain_add(&ldisc->input_queue, data, 9);
+ }
+}
+
+bool ldisc_has_input_buffered(Ldisc *ldisc)
+{
+ return bufchain_size(&ldisc->input_queue) > 0;
+}
+
+LdiscInputToken ldisc_get_input_token(Ldisc *ldisc)
+{
+ assert(bufchain_size(&ldisc->input_queue) > 0 &&
+ "You're not supposed to call this unless there is buffered input!");
+
+ LdiscInputToken tok;
+
+ char c;
+ bufchain_fetch_consume(&ldisc->input_queue, &c, 1);
+ if (c != '\xFF') {
+ /* A literal non-FF byte */
+ tok.is_special = false;
+ tok.chr = c;
+ return tok;
+ } else {
+ char data[8];
+
+ /* See if the byte after the FF is also FF, indicating a literal FF */
+ bufchain_fetch_consume(&ldisc->input_queue, data, 1);
+ if (data[0] == '\xFF') {
+ tok.is_special = false;
+ tok.chr = '\xFF';
+ return tok;
+ }
+
+ /* If not, get the rest of an 8-byte chunk and decode a special */
+ bufchain_fetch_consume(&ldisc->input_queue, data+1, 7);
+ tok.is_special = true;
+ tok.code = GET_32BIT_MSB_FIRST(data);
+ tok.arg = toint(GET_32BIT_MSB_FIRST(data+4));
+ return tok;
+ }
+}
+
+static void ldisc_check_sendok_callback(void *ctx)
+{
+ Ldisc *ldisc = (Ldisc *)ctx;
+
+ if (!(ldisc->backend && backend_sendok(ldisc->backend)))
+ return;
+
+ /*
+ * Flush the ldisc input queue into the backend, which is now
+ * willing to receive the data.
+ */
+ while (bufchain_size(&ldisc->input_queue) > 0) {
+ /*
+ * Process either a chunk of non-special data, or an FF
+ * escape, depending on whether the first thing we see is an
+ * FF byte.
+ */
+ ptrlen data = bufchain_prefix(&ldisc->input_queue);
+ const char *ff = memchr(data.ptr, '\xFF', data.len);
+ if (ff != data.ptr) {
+ /* Send a maximal block of data not containing any
+ * difficult bytes. */
+ if (ff)
+ data.len = ff - (const char *)data.ptr;
+ backend_send(ldisc->backend, data.ptr, data.len);
+ bufchain_consume(&ldisc->input_queue, data.len);
+ } else {
+ /* Decode either a special or an escaped FF byte. The
+ * easiest way to do this is to reuse the decoding code
+ * already in ldisc_get_input_token. */
+ LdiscInputToken tok = ldisc_get_input_token(ldisc);
+ if (tok.is_special)
+ backend_special(ldisc->backend, tok.code, tok.arg);
+ else
+ backend_send(ldisc->backend, &tok.chr, 1);
+ }
+ }
+}
+
+void ldisc_check_sendok(Ldisc *ldisc)
+{
+ queue_toplevel_callback(ldisc_check_sendok_callback, ldisc);
+}
+
void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
{
const char *buf = (const char *)vbuf;
@@ -206,7 +442,7 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
ldisc->buflen--;
}
- backend_special(ldisc->backend, SS_EL, 0);
+ ldisc_to_backend_special(ldisc, SS_EL, 0);
/*
* We don't send IP, SUSP or ABORT if the user has
* configured telnet specials off! This breaks
@@ -215,11 +451,11 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
if (!ldisc->telnet_keyboard)
goto default_case;
if (c == CTRL('C'))
- backend_special(ldisc->backend, SS_IP, 0);
+ ldisc_to_backend_special(ldisc, SS_IP, 0);
if (c == CTRL('Z'))
- backend_special(ldisc->backend, SS_SUSP, 0);
+ ldisc_to_backend_special(ldisc, SS_SUSP, 0);
if (c == CTRL('\\'))
- backend_special(ldisc->backend, SS_ABORT, 0);
+ ldisc_to_backend_special(ldisc, SS_ABORT, 0);
break;
case CTRL('R'): /* redraw line */
if (ECHOING) {
@@ -234,9 +470,9 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
break;
case CTRL('D'): /* logout or send */
if (ldisc->buflen == 0) {
- backend_special(ldisc->backend, SS_EOF, 0);
+ ldisc_to_backend_special(ldisc, SS_EOF, 0);
} else {
- backend_send(ldisc->backend, ldisc->buf, ldisc->buflen);
+ ldisc_to_backend_raw(ldisc, ldisc->buf, ldisc->buflen);
ldisc->buflen = 0;
}
break;
@@ -272,14 +508,13 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
/* FALLTHROUGH */
case KCTRL('M'): /* send with newline */
if (ldisc->buflen > 0)
- backend_send(ldisc->backend,
- ldisc->buf, ldisc->buflen);
+ ldisc_to_backend_raw(ldisc, ldisc->buf, ldisc->buflen);
if (ldisc->protocol == PROT_RAW)
- backend_send(ldisc->backend, "\r\n", 2);
+ ldisc_to_backend_raw(ldisc, "\r\n", 2);
else if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline)
- backend_special(ldisc->backend, SS_EOL, 0);
+ ldisc_to_backend_special(ldisc, SS_EOL, 0);
else
- backend_send(ldisc->backend, "\r", 1);
+ ldisc_to_backend_raw(ldisc, "\r", 1);
if (ECHOING)
c_write(ldisc, "\r\n", 2);
ldisc->buflen = 0;
@@ -287,7 +522,7 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
}
/* FALLTHROUGH */
default: /* get to this label from ^V handler */
- default_case:
+ default_case:
sgrowarray(ldisc->buf, ldisc->bufsiz, ldisc->buflen);
ldisc->buf[ldisc->buflen++] = c;
if (ECHOING)
@@ -298,7 +533,7 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
}
} else {
if (ldisc->buflen != 0) {
- backend_send(ldisc->backend, ldisc->buf, ldisc->buflen);
+ ldisc_to_backend_raw(ldisc, ldisc->buf, ldisc->buflen);
while (ldisc->buflen > 0) {
bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1]));
ldisc->buflen--;
@@ -311,33 +546,33 @@ void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive)
switch (buf[0]) {
case CTRL('M'):
if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline)
- backend_special(ldisc->backend, SS_EOL, 0);
+ ldisc_to_backend_special(ldisc, SS_EOL, 0);
else
- backend_send(ldisc->backend, "\r", 1);
+ ldisc_to_backend_raw(ldisc, "\r", 1);
break;
case CTRL('?'):
case CTRL('H'):
if (ldisc->telnet_keyboard) {
- backend_special(ldisc->backend, SS_EC, 0);
+ ldisc_to_backend_special(ldisc, SS_EC, 0);
break;
}
case CTRL('C'):
if (ldisc->telnet_keyboard) {
- backend_special(ldisc->backend, SS_IP, 0);
+ ldisc_to_backend_special(ldisc, SS_IP, 0);
break;
}
case CTRL('Z'):
if (ldisc->telnet_keyboard) {
- backend_special(ldisc->backend, SS_SUSP, 0);
+ ldisc_to_backend_special(ldisc, SS_SUSP, 0);
break;
}
default:
- backend_send(ldisc->backend, buf, len);
+ ldisc_to_backend_raw(ldisc, buf, len);
break;
}
} else
- backend_send(ldisc->backend, buf, len);
+ ldisc_to_backend_raw(ldisc, buf, len);
}
}
}
diff --git a/ldisc.h b/ldisc.h
deleted file mode 100644
index 770b4b05..00000000
--- a/ldisc.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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/licence.pl b/licence.pl
index e1d3a51f..f927bcb2 100644..100755
--- a/licence.pl
+++ b/licence.pl
@@ -1,17 +1,31 @@
-#!/usr/bin/env perl -w
+#!/usr/bin/env perl
# This script generates licence.h (containing the PuTTY licence in the
# form of macros expanding to C string literals) from the LICENCE
# master file. It also regenerates the licence-related Halibut input
# files.
+use warnings;
use File::Basename;
-
-# Read the input file.
-$infile = "LICENCE";
+use Getopt::Long;
+
+my $usage = "usage: licence.pl (--header|--licencedoc|--copyrightdoc) " .
+ "[-o OUTFILE]\n";
+my $mode = undef;
+my $output = undef;
+GetOptions("--header" => sub {$mode = "header"},
+ "--licencedoc" => sub {$mode = "licencedoc"},
+ "--copyrightdoc" => sub {$mode = "copyrightdoc"},
+ "o|output=s" => \$output)
+ and defined $mode
+ or die $usage;
+
+# Read the input file. We expect to find that alongside this script.
+my $infile = (dirname __FILE__) . "/LICENCE";
open my $in, $infile or die "$infile: open: $!\n";
my @lines = ();
while (<$in>) {
+ y/\r//d;
chomp;
push @lines, $_;
}
@@ -36,76 +50,68 @@ die "bad format of first paragraph\n"
unless $paras[0] =~ m!copyright ([^\.]*)\.!i;
$shortdetails = $1;
-# Write out licence.h.
-
-$outfile = "licence.h";
-open my $out, ">", $outfile or die "$outfile: open: $!\n";
-select $out;
-
-print "/*\n";
-print " * $outfile - macro definitions for the PuTTY licence.\n";
-print " *\n";
-print " * Generated by @{[basename __FILE__]} from $infile.\n";
-print " * You should edit those files rather than editing this one.\n";
-print " */\n";
-print "\n";
-
-print "#define LICENCE_TEXT(parsep) \\\n";
-for my $i (0..$#paras) {
- my $lit = &stringlit($paras[$i]);
- print " parsep \\\n" if $i > 0;
- print " \"$lit\"";
- print " \\" if $i < $#paras;
- print "\n";
-}
-print "\n";
-
-printf "#define SHORT_COPYRIGHT_DETAILS \"%s\"\n", &stringlit($shortdetails);
-
-sub stringlit {
- my ($lit) = @_;
- $lit =~ s!\\!\\\\!g;
- $lit =~ s!"!\\"!g;
- return $lit;
-}
-
-close $out;
-
-# Write out doc/licence.but.
+my $out = "";
+
+if ($mode eq "header") {
+ $out .= "/*\n";
+ $out .= " * licence.h - macro definitions for the PuTTY licence.\n";
+ $out .= " *\n";
+ $out .= " * Generated by @{[basename __FILE__]} from $infile.\n";
+ $out .= " * You should edit those files rather than editing this one.\n";
+ $out .= " */\n";
+ $out .= "\n";
+
+ $out .= "#define LICENCE_TEXT(parsep) \\\n";
+ for my $i (0..$#paras) {
+ my $lit = &stringlit($paras[$i]);
+ $out .= " parsep \\\n" if $i > 0;
+ $out .= " \"$lit\"";
+ $out .= " \\" if $i < $#paras;
+ $out .= "\n";
+ }
+ $out .= "\n";
-$outfile = "doc/licence.but";
-open $out, ">", $outfile or die "$outfile: open: $!\n";
-select $out;
+ $out .= sprintf "#define SHORT_COPYRIGHT_DETAILS \"%s\"\n",
+ &stringlit($shortdetails);
+} elsif ($mode eq "licencedoc") {
+ # Write out doc/licence.but.
-print "\\# Generated by @{[basename __FILE__]} from $infile.\n";
-print "\\# You should edit those files rather than editing this one.\n\n";
+ $out .= "\\# Generated by @{[basename __FILE__]} from $infile.\n";
+ $out .= "\\# You should edit those files rather than editing this one.\n\n";
-print "\\A{licence} PuTTY \\ii{Licence}\n\n";
+ $out .= "\\A{licence} PuTTY \\ii{Licence}\n\n";
-for my $i (0..$#paras) {
- my $para = &halibutescape($paras[$i]);
- if ($i == 0) {
- $para =~ s!copyright!\\i{copyright}!; # index term in paragraph 1
+ for my $i (0..$#paras) {
+ my $para = &halibutescape($paras[$i]);
+ if ($i == 0) {
+ $para =~ s!copyright!\\i{copyright}!; # index term in paragraph 1
+ }
+ $out .= "$para\n\n";
}
- print "$para\n\n";
-}
-
-close $out;
+} elsif ($mode eq "copyrightdoc") {
+ # Write out doc/copy.but, which defines a macro used in the manual
+ # preamble blurb.
-# And write out doc/copy.but, which defines a macro used in the manual
-# preamble blurb.
+ $out .= "\\# Generated by @{[basename __FILE__]} from $infile.\n";
+ $out .= "\\# You should edit those files rather than editing this one.\n\n";
-$outfile = "doc/copy.but";
-open $out, ">", $outfile or die "$outfile: open: $!\n";
-select $out;
-
-print "\\# Generated by @{[basename __FILE__]} from $infile.\n";
-print "\\# You should edit those files rather than editing this one.\n\n";
+ $out .= sprintf "\\define{shortcopyrightdetails} %s\n\n",
+ &halibutescape($shortdetails);
+}
-printf "\\define{shortcopyrightdetails} %s\n\n",
- &halibutescape($shortdetails);
+my $outfile;
+my $opened = (defined $output) ?
+ (open $outfile, ">", $output) : (open $outfile, ">-");
+$opened or die "$output: open: $!\n";
+print $outfile $out;
+close $outfile;
-close $out;
+sub stringlit {
+ my ($lit) = @_;
+ $lit =~ s!\\!\\\\!g;
+ $lit =~ s!"!\\"!g;
+ return $lit;
+}
sub halibutescape {
my ($text) = @_;
@@ -113,3 +119,16 @@ sub halibutescape {
$text =~ s!"([^"]*)"!\\q{$1}!g; # convert quoted strings to \q{}
return $text;
}
+
+sub write {
+ my ($filename, $newcontents) = @_;
+ if (open my $fh, "<", $filename) {
+ my $oldcontents = "";
+ $oldcontents .= $_ while <$fh>;
+ close $fh;
+ return if $oldcontents eq $newcontents;
+ }
+ open my $fh, ">", $filename or die "$filename: open: $!\n";
+ print $fh $newcontents;
+ close $fh;
+}
diff --git a/logging.c b/logging.c
index 31cbccfb..e065f1a4 100644
--- a/logging.c
+++ b/logging.c
@@ -81,6 +81,11 @@ void logflush(LogContext *ctx)
fflush(ctx->lgfp);
}
+LogPolicy *log_get_policy(LogContext *ctx)
+{
+ return ctx->lp;
+}
+
static void logfopen_callback(void *vctx, int mode)
{
LogContext *ctx = (LogContext *)vctx;
diff --git a/mainchan.c b/mainchan.c
deleted file mode 100644
index 8653ad02..00000000
--- a/mainchan.c
+++ /dev/null
@@ -1,538 +0,0 @@
-/*
- * SSH main session channel handling.
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#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/marshal.c b/marshal.c
deleted file mode 100644
index ff9bb851..00000000
--- a/marshal.c
+++ /dev/null
@@ -1,318 +0,0 @@
-#include <assert.h>
-#include <stddef.h>
-#include <string.h>
-
-#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/marshal.h b/marshal.h
index 108d8aeb..b9136292 100644
--- a/marshal.h
+++ b/marshal.h
@@ -4,6 +4,7 @@
#include "defs.h"
#include <stdio.h>
+#include <stdarg.h>
/*
* A sort of 'abstract base class' or 'interface' or 'trait' which is
@@ -12,6 +13,7 @@
*/
struct BinarySink {
void (*write)(BinarySink *sink, const void *data, size_t len);
+ void (*writefmtv)(BinarySink *sink, const char *fmt, va_list ap);
BinarySink *binarysink_;
};
@@ -25,6 +27,7 @@ struct BinarySink {
#define BinarySink_IMPLEMENTATION BinarySink binarysink_[1]
#define BinarySink_INIT(obj, writefn) \
((obj)->binarysink_->write = (writefn), \
+ (obj)->binarysink_->writefmtv = NULL, \
(obj)->binarysink_->binarysink_ = (obj)->binarysink_)
/*
@@ -138,6 +141,20 @@ struct BinarySink {
BinarySink_put_data(BinarySink_UPCAST(bs), val, len)
#define put_datapl(bs, pl) \
BinarySink_put_datapl(BinarySink_UPCAST(bs), pl)
+#define put_dataz(bs, val) \
+ BinarySink_put_datapl(BinarySink_UPCAST(bs), ptrlen_from_asciz(val))
+#define put_datalit(bs, val) \
+ BinarySink_put_datapl(BinarySink_UPCAST(bs), PTRLEN_LITERAL(val))
+
+/* Emit printf-formatted data, with no terminator. */
+#define put_fmt(bs, ...) \
+ BinarySink_put_fmt(BinarySink_UPCAST(bs), __VA_ARGS__)
+#define put_fmtv(bs, fmt, ap) \
+ BinarySink_put_fmtv(BinarySink_UPCAST(bs), fmt, ap)
+
+/* More complicated function implemented in write_c_string_literal.c */
+#define put_c_string_literal(bs, str) \
+ BinarySink_put_c_string_literal(BinarySink_UPCAST(bs), str)
/*
* The underlying real C functions that implement most of those
@@ -160,12 +177,14 @@ 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_stringsb(BinarySink *, strbuf *);
void BinarySink_put_asciz(BinarySink *, const char *str);
bool BinarySink_put_pstring(BinarySink *, const char *str);
void BinarySink_put_mp_ssh1(BinarySink *bs, mp_int *x);
void BinarySink_put_mp_ssh2(BinarySink *bs, mp_int *x);
+void BinarySink_put_fmt(BinarySink *, const char *fmt, ...) PRINTF_LIKE(2, 3);
+void BinarySink_put_fmtv(BinarySink *, const char *fmt, va_list ap);
+void BinarySink_put_c_string_literal(BinarySink *, ptrlen);
/* ---------------------------------------------------------------------- */
@@ -297,7 +316,7 @@ static inline void BinarySource_INIT__(BinarySource *src, ptrlen data)
#define get_err(src) (BinarySource_UPCAST(src)->err)
#define get_avail(src) (BinarySource_UPCAST(src)->len - \
- BinarySource_UPCAST(src)->pos)
+ BinarySource_UPCAST(src)->pos)
#define get_ptr(src) \
((const void *)( \
(const unsigned char *)(BinarySource_UPCAST(src)->data) + \
diff --git a/millerrabin.c b/millerrabin.c
deleted file mode 100644
index 3358bc51..00000000
--- a/millerrabin.c
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * millerrabin.c: Miller-Rabin probabilistic primality testing, as
- * declared in sshkeygen.h.
- */
-
-#include <assert.h>
-#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/minibidi.c b/minibidi.c
deleted file mode 100644
index 05d15b3d..00000000
--- a/minibidi.c
+++ /dev/null
@@ -1,2025 +0,0 @@
-/************************************************************************
- *
- * ------------
- * 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 <stdlib.h> /* 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<count && j<count) {
-
- /* find the start of the run of level=max */
- tlevel = max;
- i = j = findIndexOfRun(level, i, count, max);
- /* find the end of the run */
- while (i<count && tlevel <= level[i]) {
- i++;
- }
- for (k = i - 1; k > 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<count; i++) {
- if (tlevel == level[i]) {
- return i;
- }
- }
- return count;
-}
-
-/*
- * Returns the bidi character type of ch.
- *
- * The data table in this function is constructed from the Unicode
- * Character Database, downloadable from unicode.org at the URL
- *
- * http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
- *
- * by the following fragment of Perl:
-
-perl -ne 'split ";"; $num = hex $_[0]; $type = $_[4];' \
- -e '$fl = ($_[1] =~ /First/ ? 1 : $_[1] =~ /Last/ ? 2 : 0);' \
- -e 'if ($type eq $runtype and ($runend == $num-1 or ' \
- -e ' ($fl==2 and $pfl==1))) {$runend = $num;} else { &reset; }' \
- -e '$pfl=$fl; END { &reset }; sub reset {' \
- -e 'printf" {0x%04x, 0x%04x, %s},\n",$runstart,$runend,$runtype' \
- -e ' if defined $runstart and $runtype ne "ON";' \
- -e '$runstart=$runend=$num; $runtype=$type;}' \
- UnicodeData.txt
-
- */
-static unsigned char getType(int ch)
-{
- static const struct {
- int first, last, type;
- } lookup[] = {
- {0x0000, 0x0008, BN},
- {0x0009, 0x0009, S},
- {0x000a, 0x000a, B},
- {0x000b, 0x000b, S},
- {0x000c, 0x000c, WS},
- {0x000d, 0x000d, B},
- {0x000e, 0x001b, BN},
- {0x001c, 0x001e, B},
- {0x001f, 0x001f, S},
- {0x0020, 0x0020, WS},
- {0x0023, 0x0025, ET},
- {0x002b, 0x002b, ES},
- {0x002c, 0x002c, CS},
- {0x002d, 0x002d, ES},
- {0x002e, 0x002f, CS},
- {0x0030, 0x0039, EN},
- {0x003a, 0x003a, CS},
- {0x0041, 0x005a, L},
- {0x0061, 0x007a, L},
- {0x007f, 0x0084, BN},
- {0x0085, 0x0085, B},
- {0x0086, 0x009f, BN},
- {0x00a0, 0x00a0, CS},
- {0x00a2, 0x00a5, ET},
- {0x00aa, 0x00aa, L},
- {0x00ad, 0x00ad, BN},
- {0x00b0, 0x00b1, ET},
- {0x00b2, 0x00b3, EN},
- {0x00b5, 0x00b5, L},
- {0x00b9, 0x00b9, EN},
- {0x00ba, 0x00ba, L},
- {0x00c0, 0x00d6, L},
- {0x00d8, 0x00f6, L},
- {0x00f8, 0x0236, L},
- {0x0250, 0x02b8, L},
- {0x02bb, 0x02c1, L},
- {0x02d0, 0x02d1, L},
- {0x02e0, 0x02e4, L},
- {0x02ee, 0x02ee, L},
- {0x0300, 0x0357, NSM},
- {0x035d, 0x036f, NSM},
- {0x037a, 0x037a, L},
- {0x0386, 0x0386, L},
- {0x0388, 0x038a, L},
- {0x038c, 0x038c, L},
- {0x038e, 0x03a1, L},
- {0x03a3, 0x03ce, L},
- {0x03d0, 0x03f5, L},
- {0x03f7, 0x03fb, L},
- {0x0400, 0x0482, L},
- {0x0483, 0x0486, NSM},
- {0x0488, 0x0489, NSM},
- {0x048a, 0x04ce, L},
- {0x04d0, 0x04f5, L},
- {0x04f8, 0x04f9, L},
- {0x0500, 0x050f, L},
- {0x0531, 0x0556, L},
- {0x0559, 0x055f, L},
- {0x0561, 0x0587, L},
- {0x0589, 0x0589, L},
- {0x0591, 0x05a1, NSM},
- {0x05a3, 0x05b9, NSM},
- {0x05bb, 0x05bd, NSM},
- {0x05be, 0x05be, R},
- {0x05bf, 0x05bf, NSM},
- {0x05c0, 0x05c0, R},
- {0x05c1, 0x05c2, NSM},
- {0x05c3, 0x05c3, R},
- {0x05c4, 0x05c4, NSM},
- {0x05d0, 0x05ea, R},
- {0x05f0, 0x05f4, R},
- {0x0600, 0x0603, AL},
- {0x060c, 0x060c, CS},
- {0x060d, 0x060d, AL},
- {0x0610, 0x0615, NSM},
- {0x061b, 0x061b, AL},
- {0x061f, 0x061f, AL},
- {0x0621, 0x063a, AL},
- {0x0640, 0x064a, AL},
- {0x064b, 0x0658, NSM},
- {0x0660, 0x0669, AN},
- {0x066a, 0x066a, ET},
- {0x066b, 0x066c, AN},
- {0x066d, 0x066f, AL},
- {0x0670, 0x0670, NSM},
- {0x0671, 0x06d5, AL},
- {0x06d6, 0x06dc, NSM},
- {0x06dd, 0x06dd, AL},
- {0x06de, 0x06e4, NSM},
- {0x06e5, 0x06e6, AL},
- {0x06e7, 0x06e8, NSM},
- {0x06ea, 0x06ed, NSM},
- {0x06ee, 0x06ef, AL},
- {0x06f0, 0x06f9, EN},
- {0x06fa, 0x070d, AL},
- {0x070f, 0x070f, BN},
- {0x0710, 0x0710, AL},
- {0x0711, 0x0711, NSM},
- {0x0712, 0x072f, AL},
- {0x0730, 0x074a, NSM},
- {0x074d, 0x074f, AL},
- {0x0780, 0x07a5, AL},
- {0x07a6, 0x07b0, NSM},
- {0x07b1, 0x07b1, AL},
- {0x0901, 0x0902, NSM},
- {0x0903, 0x0939, L},
- {0x093c, 0x093c, NSM},
- {0x093d, 0x0940, L},
- {0x0941, 0x0948, NSM},
- {0x0949, 0x094c, L},
- {0x094d, 0x094d, NSM},
- {0x0950, 0x0950, L},
- {0x0951, 0x0954, NSM},
- {0x0958, 0x0961, L},
- {0x0962, 0x0963, NSM},
- {0x0964, 0x0970, L},
- {0x0981, 0x0981, NSM},
- {0x0982, 0x0983, L},
- {0x0985, 0x098c, L},
- {0x098f, 0x0990, L},
- {0x0993, 0x09a8, L},
- {0x09aa, 0x09b0, L},
- {0x09b2, 0x09b2, L},
- {0x09b6, 0x09b9, L},
- {0x09bc, 0x09bc, NSM},
- {0x09bd, 0x09c0, L},
- {0x09c1, 0x09c4, NSM},
- {0x09c7, 0x09c8, L},
- {0x09cb, 0x09cc, L},
- {0x09cd, 0x09cd, NSM},
- {0x09d7, 0x09d7, L},
- {0x09dc, 0x09dd, L},
- {0x09df, 0x09e1, L},
- {0x09e2, 0x09e3, NSM},
- {0x09e6, 0x09f1, L},
- {0x09f2, 0x09f3, ET},
- {0x09f4, 0x09fa, L},
- {0x0a01, 0x0a02, NSM},
- {0x0a03, 0x0a03, L},
- {0x0a05, 0x0a0a, L},
- {0x0a0f, 0x0a10, L},
- {0x0a13, 0x0a28, L},
- {0x0a2a, 0x0a30, L},
- {0x0a32, 0x0a33, L},
- {0x0a35, 0x0a36, L},
- {0x0a38, 0x0a39, L},
- {0x0a3c, 0x0a3c, NSM},
- {0x0a3e, 0x0a40, L},
- {0x0a41, 0x0a42, NSM},
- {0x0a47, 0x0a48, NSM},
- {0x0a4b, 0x0a4d, NSM},
- {0x0a59, 0x0a5c, L},
- {0x0a5e, 0x0a5e, L},
- {0x0a66, 0x0a6f, L},
- {0x0a70, 0x0a71, NSM},
- {0x0a72, 0x0a74, L},
- {0x0a81, 0x0a82, NSM},
- {0x0a83, 0x0a83, L},
- {0x0a85, 0x0a8d, L},
- {0x0a8f, 0x0a91, L},
- {0x0a93, 0x0aa8, L},
- {0x0aaa, 0x0ab0, L},
- {0x0ab2, 0x0ab3, L},
- {0x0ab5, 0x0ab9, L},
- {0x0abc, 0x0abc, NSM},
- {0x0abd, 0x0ac0, L},
- {0x0ac1, 0x0ac5, NSM},
- {0x0ac7, 0x0ac8, NSM},
- {0x0ac9, 0x0ac9, L},
- {0x0acb, 0x0acc, L},
- {0x0acd, 0x0acd, NSM},
- {0x0ad0, 0x0ad0, L},
- {0x0ae0, 0x0ae1, L},
- {0x0ae2, 0x0ae3, NSM},
- {0x0ae6, 0x0aef, L},
- {0x0af1, 0x0af1, ET},
- {0x0b01, 0x0b01, NSM},
- {0x0b02, 0x0b03, L},
- {0x0b05, 0x0b0c, L},
- {0x0b0f, 0x0b10, L},
- {0x0b13, 0x0b28, L},
- {0x0b2a, 0x0b30, L},
- {0x0b32, 0x0b33, L},
- {0x0b35, 0x0b39, L},
- {0x0b3c, 0x0b3c, NSM},
- {0x0b3d, 0x0b3e, L},
- {0x0b3f, 0x0b3f, NSM},
- {0x0b40, 0x0b40, L},
- {0x0b41, 0x0b43, NSM},
- {0x0b47, 0x0b48, L},
- {0x0b4b, 0x0b4c, L},
- {0x0b4d, 0x0b4d, NSM},
- {0x0b56, 0x0b56, NSM},
- {0x0b57, 0x0b57, L},
- {0x0b5c, 0x0b5d, L},
- {0x0b5f, 0x0b61, L},
- {0x0b66, 0x0b71, L},
- {0x0b82, 0x0b82, NSM},
- {0x0b83, 0x0b83, L},
- {0x0b85, 0x0b8a, L},
- {0x0b8e, 0x0b90, L},
- {0x0b92, 0x0b95, L},
- {0x0b99, 0x0b9a, L},
- {0x0b9c, 0x0b9c, L},
- {0x0b9e, 0x0b9f, L},
- {0x0ba3, 0x0ba4, L},
- {0x0ba8, 0x0baa, L},
- {0x0bae, 0x0bb5, L},
- {0x0bb7, 0x0bb9, L},
- {0x0bbe, 0x0bbf, L},
- {0x0bc0, 0x0bc0, NSM},
- {0x0bc1, 0x0bc2, L},
- {0x0bc6, 0x0bc8, L},
- {0x0bca, 0x0bcc, L},
- {0x0bcd, 0x0bcd, NSM},
- {0x0bd7, 0x0bd7, L},
- {0x0be7, 0x0bf2, L},
- {0x0bf9, 0x0bf9, ET},
- {0x0c01, 0x0c03, L},
- {0x0c05, 0x0c0c, L},
- {0x0c0e, 0x0c10, L},
- {0x0c12, 0x0c28, L},
- {0x0c2a, 0x0c33, L},
- {0x0c35, 0x0c39, L},
- {0x0c3e, 0x0c40, NSM},
- {0x0c41, 0x0c44, L},
- {0x0c46, 0x0c48, NSM},
- {0x0c4a, 0x0c4d, NSM},
- {0x0c55, 0x0c56, NSM},
- {0x0c60, 0x0c61, L},
- {0x0c66, 0x0c6f, L},
- {0x0c82, 0x0c83, L},
- {0x0c85, 0x0c8c, L},
- {0x0c8e, 0x0c90, L},
- {0x0c92, 0x0ca8, L},
- {0x0caa, 0x0cb3, L},
- {0x0cb5, 0x0cb9, L},
- {0x0cbc, 0x0cbc, NSM},
- {0x0cbd, 0x0cc4, L},
- {0x0cc6, 0x0cc8, L},
- {0x0cca, 0x0ccb, L},
- {0x0ccc, 0x0ccd, NSM},
- {0x0cd5, 0x0cd6, L},
- {0x0cde, 0x0cde, L},
- {0x0ce0, 0x0ce1, L},
- {0x0ce6, 0x0cef, L},
- {0x0d02, 0x0d03, L},
- {0x0d05, 0x0d0c, L},
- {0x0d0e, 0x0d10, L},
- {0x0d12, 0x0d28, L},
- {0x0d2a, 0x0d39, L},
- {0x0d3e, 0x0d40, L},
- {0x0d41, 0x0d43, NSM},
- {0x0d46, 0x0d48, L},
- {0x0d4a, 0x0d4c, L},
- {0x0d4d, 0x0d4d, NSM},
- {0x0d57, 0x0d57, L},
- {0x0d60, 0x0d61, L},
- {0x0d66, 0x0d6f, L},
- {0x0d82, 0x0d83, L},
- {0x0d85, 0x0d96, L},
- {0x0d9a, 0x0db1, L},
- {0x0db3, 0x0dbb, L},
- {0x0dbd, 0x0dbd, L},
- {0x0dc0, 0x0dc6, L},
- {0x0dca, 0x0dca, NSM},
- {0x0dcf, 0x0dd1, L},
- {0x0dd2, 0x0dd4, NSM},
- {0x0dd6, 0x0dd6, NSM},
- {0x0dd8, 0x0ddf, L},
- {0x0df2, 0x0df4, L},
- {0x0e01, 0x0e30, L},
- {0x0e31, 0x0e31, NSM},
- {0x0e32, 0x0e33, L},
- {0x0e34, 0x0e3a, NSM},
- {0x0e3f, 0x0e3f, ET},
- {0x0e40, 0x0e46, L},
- {0x0e47, 0x0e4e, NSM},
- {0x0e4f, 0x0e5b, L},
- {0x0e81, 0x0e82, L},
- {0x0e84, 0x0e84, L},
- {0x0e87, 0x0e88, L},
- {0x0e8a, 0x0e8a, L},
- {0x0e8d, 0x0e8d, L},
- {0x0e94, 0x0e97, L},
- {0x0e99, 0x0e9f, L},
- {0x0ea1, 0x0ea3, L},
- {0x0ea5, 0x0ea5, L},
- {0x0ea7, 0x0ea7, L},
- {0x0eaa, 0x0eab, L},
- {0x0ead, 0x0eb0, L},
- {0x0eb1, 0x0eb1, NSM},
- {0x0eb2, 0x0eb3, L},
- {0x0eb4, 0x0eb9, NSM},
- {0x0ebb, 0x0ebc, NSM},
- {0x0ebd, 0x0ebd, L},
- {0x0ec0, 0x0ec4, L},
- {0x0ec6, 0x0ec6, L},
- {0x0ec8, 0x0ecd, NSM},
- {0x0ed0, 0x0ed9, L},
- {0x0edc, 0x0edd, L},
- {0x0f00, 0x0f17, L},
- {0x0f18, 0x0f19, NSM},
- {0x0f1a, 0x0f34, L},
- {0x0f35, 0x0f35, NSM},
- {0x0f36, 0x0f36, L},
- {0x0f37, 0x0f37, NSM},
- {0x0f38, 0x0f38, L},
- {0x0f39, 0x0f39, NSM},
- {0x0f3e, 0x0f47, L},
- {0x0f49, 0x0f6a, L},
- {0x0f71, 0x0f7e, NSM},
- {0x0f7f, 0x0f7f, L},
- {0x0f80, 0x0f84, NSM},
- {0x0f85, 0x0f85, L},
- {0x0f86, 0x0f87, NSM},
- {0x0f88, 0x0f8b, L},
- {0x0f90, 0x0f97, NSM},
- {0x0f99, 0x0fbc, NSM},
- {0x0fbe, 0x0fc5, L},
- {0x0fc6, 0x0fc6, NSM},
- {0x0fc7, 0x0fcc, L},
- {0x0fcf, 0x0fcf, L},
- {0x1000, 0x1021, L},
- {0x1023, 0x1027, L},
- {0x1029, 0x102a, L},
- {0x102c, 0x102c, L},
- {0x102d, 0x1030, NSM},
- {0x1031, 0x1031, L},
- {0x1032, 0x1032, NSM},
- {0x1036, 0x1037, NSM},
- {0x1038, 0x1038, L},
- {0x1039, 0x1039, NSM},
- {0x1040, 0x1057, L},
- {0x1058, 0x1059, NSM},
- {0x10a0, 0x10c5, L},
- {0x10d0, 0x10f8, L},
- {0x10fb, 0x10fb, L},
- {0x1100, 0x1159, L},
- {0x115f, 0x11a2, L},
- {0x11a8, 0x11f9, L},
- {0x1200, 0x1206, L},
- {0x1208, 0x1246, L},
- {0x1248, 0x1248, L},
- {0x124a, 0x124d, L},
- {0x1250, 0x1256, L},
- {0x1258, 0x1258, L},
- {0x125a, 0x125d, L},
- {0x1260, 0x1286, L},
- {0x1288, 0x1288, L},
- {0x128a, 0x128d, L},
- {0x1290, 0x12ae, L},
- {0x12b0, 0x12b0, L},
- {0x12b2, 0x12b5, L},
- {0x12b8, 0x12be, L},
- {0x12c0, 0x12c0, L},
- {0x12c2, 0x12c5, L},
- {0x12c8, 0x12ce, L},
- {0x12d0, 0x12d6, L},
- {0x12d8, 0x12ee, L},
- {0x12f0, 0x130e, L},
- {0x1310, 0x1310, L},
- {0x1312, 0x1315, L},
- {0x1318, 0x131e, L},
- {0x1320, 0x1346, L},
- {0x1348, 0x135a, L},
- {0x1361, 0x137c, L},
- {0x13a0, 0x13f4, L},
- {0x1401, 0x1676, L},
- {0x1680, 0x1680, WS},
- {0x1681, 0x169a, L},
- {0x16a0, 0x16f0, L},
- {0x1700, 0x170c, L},
- {0x170e, 0x1711, L},
- {0x1712, 0x1714, NSM},
- {0x1720, 0x1731, L},
- {0x1732, 0x1734, NSM},
- {0x1735, 0x1736, L},
- {0x1740, 0x1751, L},
- {0x1752, 0x1753, NSM},
- {0x1760, 0x176c, L},
- {0x176e, 0x1770, L},
- {0x1772, 0x1773, NSM},
- {0x1780, 0x17b6, L},
- {0x17b7, 0x17bd, NSM},
- {0x17be, 0x17c5, L},
- {0x17c6, 0x17c6, NSM},
- {0x17c7, 0x17c8, L},
- {0x17c9, 0x17d3, NSM},
- {0x17d4, 0x17da, L},
- {0x17db, 0x17db, ET},
- {0x17dc, 0x17dc, L},
- {0x17dd, 0x17dd, NSM},
- {0x17e0, 0x17e9, L},
- {0x180b, 0x180d, NSM},
- {0x180e, 0x180e, WS},
- {0x1810, 0x1819, L},
- {0x1820, 0x1877, L},
- {0x1880, 0x18a8, L},
- {0x18a9, 0x18a9, NSM},
- {0x1900, 0x191c, L},
- {0x1920, 0x1922, NSM},
- {0x1923, 0x1926, L},
- {0x1927, 0x192b, NSM},
- {0x1930, 0x1931, L},
- {0x1932, 0x1932, NSM},
- {0x1933, 0x1938, L},
- {0x1939, 0x193b, NSM},
- {0x1946, 0x196d, L},
- {0x1970, 0x1974, L},
- {0x1d00, 0x1d6b, L},
- {0x1e00, 0x1e9b, L},
- {0x1ea0, 0x1ef9, L},
- {0x1f00, 0x1f15, L},
- {0x1f18, 0x1f1d, L},
- {0x1f20, 0x1f45, L},
- {0x1f48, 0x1f4d, L},
- {0x1f50, 0x1f57, L},
- {0x1f59, 0x1f59, L},
- {0x1f5b, 0x1f5b, L},
- {0x1f5d, 0x1f5d, L},
- {0x1f5f, 0x1f7d, L},
- {0x1f80, 0x1fb4, L},
- {0x1fb6, 0x1fbc, L},
- {0x1fbe, 0x1fbe, L},
- {0x1fc2, 0x1fc4, L},
- {0x1fc6, 0x1fcc, L},
- {0x1fd0, 0x1fd3, L},
- {0x1fd6, 0x1fdb, L},
- {0x1fe0, 0x1fec, L},
- {0x1ff2, 0x1ff4, L},
- {0x1ff6, 0x1ffc, L},
- {0x2000, 0x200a, WS},
- {0x200b, 0x200d, BN},
- {0x200e, 0x200e, L},
- {0x200f, 0x200f, R},
- {0x2028, 0x2028, WS},
- {0x2029, 0x2029, B},
- {0x202a, 0x202a, LRE},
- {0x202b, 0x202b, RLE},
- {0x202c, 0x202c, PDF},
- {0x202d, 0x202d, LRO},
- {0x202e, 0x202e, RLO},
- {0x202f, 0x202f, WS},
- {0x2030, 0x2034, ET},
- {0x2044, 0x2044, CS},
- {0x205f, 0x205f, WS},
- {0x2060, 0x2063, BN},
- {0x206a, 0x206f, BN},
- {0x2070, 0x2070, EN},
- {0x2071, 0x2071, L},
- {0x2074, 0x2079, EN},
- {0x207a, 0x207b, ET},
- {0x207f, 0x207f, L},
- {0x2080, 0x2089, EN},
- {0x208a, 0x208b, ET},
- {0x20a0, 0x20b1, ET},
- {0x20d0, 0x20ea, NSM},
- {0x2102, 0x2102, L},
- {0x2107, 0x2107, L},
- {0x210a, 0x2113, L},
- {0x2115, 0x2115, L},
- {0x2119, 0x211d, L},
- {0x2124, 0x2124, L},
- {0x2126, 0x2126, L},
- {0x2128, 0x2128, L},
- {0x212a, 0x212d, L},
- {0x212e, 0x212e, ET},
- {0x212f, 0x2131, L},
- {0x2133, 0x2139, L},
- {0x213d, 0x213f, L},
- {0x2145, 0x2149, L},
- {0x2160, 0x2183, L},
- {0x2212, 0x2213, ET},
- {0x2336, 0x237a, L},
- {0x2395, 0x2395, L},
- {0x2488, 0x249b, EN},
- {0x249c, 0x24e9, L},
- {0x2800, 0x28ff, L},
- {0x3000, 0x3000, WS},
- {0x3005, 0x3007, L},
- {0x3021, 0x3029, L},
- {0x302a, 0x302f, NSM},
- {0x3031, 0x3035, L},
- {0x3038, 0x303c, L},
- {0x3041, 0x3096, L},
- {0x3099, 0x309a, NSM},
- {0x309d, 0x309f, L},
- {0x30a1, 0x30fa, L},
- {0x30fc, 0x30ff, L},
- {0x3105, 0x312c, L},
- {0x3131, 0x318e, L},
- {0x3190, 0x31b7, L},
- {0x31f0, 0x321c, L},
- {0x3220, 0x3243, L},
- {0x3260, 0x327b, L},
- {0x327f, 0x32b0, L},
- {0x32c0, 0x32cb, L},
- {0x32d0, 0x32fe, L},
- {0x3300, 0x3376, L},
- {0x337b, 0x33dd, L},
- {0x33e0, 0x33fe, L},
- {0x3400, 0x4db5, L},
- {0x4e00, 0x9fa5, L},
- {0xa000, 0xa48c, L},
- {0xac00, 0xd7a3, L},
- {0xd800, 0xdff7, L},
- {0xe000, 0xfa2d, L},
- {0xfa30, 0xfa6a, L},
- {0xfb00, 0xfb06, L},
- {0xfb13, 0xfb17, L},
- {0xfb1d, 0xfb1d, R},
- {0xfb1e, 0xfb1e, NSM},
- {0xfb1f, 0xfb28, R},
- {0xfb29, 0xfb29, ET},
- {0xfb2a, 0xfb36, R},
- {0xfb38, 0xfb3c, R},
- {0xfb3e, 0xfb3e, R},
- {0xfb40, 0xfb41, R},
- {0xfb43, 0xfb44, R},
- {0xfb46, 0xfb4f, R},
- {0xfb50, 0xfbb1, AL},
- {0xfbd3, 0xfd3d, AL},
- {0xfd50, 0xfd8f, AL},
- {0xfd92, 0xfdc7, AL},
- {0xfdf0, 0xfdfc, AL},
- {0xfe00, 0xfe0f, NSM},
- {0xfe20, 0xfe23, NSM},
- {0xfe50, 0xfe50, CS},
- {0xfe52, 0xfe52, CS},
- {0xfe55, 0xfe55, CS},
- {0xfe5f, 0xfe5f, ET},
- {0xfe62, 0xfe63, ET},
- {0xfe69, 0xfe6a, ET},
- {0xfe70, 0xfe74, AL},
- {0xfe76, 0xfefc, AL},
- {0xfeff, 0xfeff, BN},
- {0xff03, 0xff05, ET},
- {0xff0b, 0xff0b, ET},
- {0xff0c, 0xff0c, CS},
- {0xff0d, 0xff0d, ET},
- {0xff0e, 0xff0e, CS},
- {0xff0f, 0xff0f, ES},
- {0xff10, 0xff19, EN},
- {0xff1a, 0xff1a, CS},
- {0xff21, 0xff3a, L},
- {0xff41, 0xff5a, L},
- {0xff66, 0xffbe, L},
- {0xffc2, 0xffc7, L},
- {0xffca, 0xffcf, L},
- {0xffd2, 0xffd7, L},
- {0xffda, 0xffdc, L},
- {0xffe0, 0xffe1, ET},
- {0xffe5, 0xffe6, ET},
- {0x10000, 0x1000b, L},
- {0x1000d, 0x10026, L},
- {0x10028, 0x1003a, L},
- {0x1003c, 0x1003d, L},
- {0x1003f, 0x1004d, L},
- {0x10050, 0x1005d, L},
- {0x10080, 0x100fa, L},
- {0x10100, 0x10100, L},
- {0x10102, 0x10102, L},
- {0x10107, 0x10133, L},
- {0x10137, 0x1013f, L},
- {0x10300, 0x1031e, L},
- {0x10320, 0x10323, L},
- {0x10330, 0x1034a, L},
- {0x10380, 0x1039d, L},
- {0x1039f, 0x1039f, L},
- {0x10400, 0x1049d, L},
- {0x104a0, 0x104a9, L},
- {0x10800, 0x10805, R},
- {0x10808, 0x10808, R},
- {0x1080a, 0x10835, R},
- {0x10837, 0x10838, R},
- {0x1083c, 0x1083c, R},
- {0x1083f, 0x1083f, R},
- {0x1d000, 0x1d0f5, L},
- {0x1d100, 0x1d126, L},
- {0x1d12a, 0x1d166, L},
- {0x1d167, 0x1d169, NSM},
- {0x1d16a, 0x1d172, L},
- {0x1d173, 0x1d17a, BN},
- {0x1d17b, 0x1d182, NSM},
- {0x1d183, 0x1d184, L},
- {0x1d185, 0x1d18b, NSM},
- {0x1d18c, 0x1d1a9, L},
- {0x1d1aa, 0x1d1ad, NSM},
- {0x1d1ae, 0x1d1dd, L},
- {0x1d400, 0x1d454, L},
- {0x1d456, 0x1d49c, L},
- {0x1d49e, 0x1d49f, L},
- {0x1d4a2, 0x1d4a2, L},
- {0x1d4a5, 0x1d4a6, L},
- {0x1d4a9, 0x1d4ac, L},
- {0x1d4ae, 0x1d4b9, L},
- {0x1d4bb, 0x1d4bb, L},
- {0x1d4bd, 0x1d4c3, L},
- {0x1d4c5, 0x1d505, L},
- {0x1d507, 0x1d50a, L},
- {0x1d50d, 0x1d514, L},
- {0x1d516, 0x1d51c, L},
- {0x1d51e, 0x1d539, L},
- {0x1d53b, 0x1d53e, L},
- {0x1d540, 0x1d544, L},
- {0x1d546, 0x1d546, L},
- {0x1d54a, 0x1d550, L},
- {0x1d552, 0x1d6a3, L},
- {0x1d6a8, 0x1d7c9, L},
- {0x1d7ce, 0x1d7ff, EN},
- {0x20000, 0x2a6d6, L},
- {0x2f800, 0x2fa1d, L},
- {0xe0001, 0xe0001, BN},
- {0xe0020, 0xe007f, BN},
- {0xe0100, 0xe01ef, NSM},
- {0xf0000, 0xffffd, L},
- {0x100000, 0x10fffd, L}
- };
-
- int i, j, k;
-
- i = -1;
- j = lenof(lookup);
-
- while (j - i > 1) {
- k = (i + j) / 2;
- if (ch < lookup[k].first)
- j = k;
- else if (ch > lookup[k].last)
- i = k;
- else
- return lookup[k].type;
- }
-
- /*
- * If we reach here, the character was not in any of the
- * intervals listed in the lookup table. This means we return
- * ON (`Other Neutrals'). This is the appropriate code for any
- * character genuinely not listed in the Unicode table, and
- * also the table above has deliberately left out any
- * characters _explicitly_ listed as ON (to save space!).
- */
- return ON;
-}
-
-/*
- * 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<<R) | (1<<AL) | (1<<RLE) | (1<<RLO);
-
- return mask & (1 << (getType(c)));
-}
-
-/*
- * The most significant 2 bits of each level are used to store
- * Override status of each character
- * This function sets the override bits of level according
- * to the value in override, and reurns the new byte.
- */
-static unsigned char setOverrideBits(
- unsigned char level, unsigned char override)
-{
- if (override == ON)
- return level;
- else if (override == R)
- return level | OISR;
- else if (override == L)
- return level | OISL;
- return level;
-}
-
-/*
- * Find the most recent run of the same value in `level', and
- * return the value _before_ it. Used to process U+202C POP
- * DIRECTIONAL FORMATTING.
- */
-static int getPreviousLevel(unsigned char *level, int from)
-{
- if (from > 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<count; i++) {
- to[i] = line[i];
- tempShape = STYPE(line[i].wc);
- switch (tempShape) {
- case SC:
- break;
-
- case SU:
- break;
-
- case SR:
- tempShape = (i+1 < count ? STYPE(line[i+1].wc) : SU);
- if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC))
- to[i].wc = SFINAL((SISOLATED(line[i].wc)));
- else
- to[i].wc = SISOLATED(line[i].wc);
- break;
-
-
- case SD:
- /* Make Ligatures */
- tempShape = (i+1 < count ? STYPE(line[i+1].wc) : SU);
- if (line[i].wc == 0x644) {
- if (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<count; i++) {
- int type = getType(line[i].wc);
- if (type == R || type == AL) {
- yes = true;
- break;
- }
- }
- if (!yes)
- return L;
-
- /* Initialize types, levels */
- types = snewn(count, unsigned char);
- levels = snewn(count, unsigned char);
-
- /* Rule (P1) NOT IMPLEMENTED
- * P1. Split the text into separate paragraphs. A paragraph separator is
- * kept with the previous paragraph. Within each paragraph, apply all the
- * other rules of this algorithm.
- */
-
- /* Rule (P2), (P3)
- * P2. In each paragraph, find the first character of type L, AL, or R.
- * P3. If a character is found in P2 and it is of type AL or R, then set
- * the paragraph embedding level to one; otherwise, set it to zero.
- */
- paragraphLevel = 0;
- for (i=0; i<count ; i++) {
- int type = getType(line[i].wc);
- if (type == R || type == AL) {
- paragraphLevel = 1;
- break;
- } else if (type == L)
- break;
- }
-
- /* Rule (X1)
- * X1. Begin by setting the current embedding level to the paragraph
- * embedding level. Set the directional override status to neutral.
- */
- currentEmbedding = paragraphLevel;
- currentOverride = ON;
-
- /* Rule (X2), (X3), (X4), (X5), (X6), (X7), (X8)
- * X2. With each RLE, compute the least greater odd embedding level.
- * X3. With each LRE, compute the least greater even embedding level.
- * X4. With each RLO, compute the least greater odd embedding level.
- * X5. With each LRO, compute the least greater even embedding level.
- * X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
- * a. Set the level of the current character to the current
- * embedding level.
- * b. Whenever the directional override status is not neutral,
- * reset the current character type to the directional
- * override status.
- * X7. With each PDF, determine the matching embedding or override code.
- * If there was a valid matching code, restore (pop) the last
- * remembered (pushed) embedding level and directional override.
- * X8. All explicit directional embeddings and overrides are completely
- * terminated at the end of each paragraph. Paragraph separators are not
- * included in the embedding. (Useless here) NOT IMPLEMENTED
- */
- bover = false;
- for (i=0; i<count; i++) {
- tempType = getType(line[i].wc);
- switch (tempType) {
- case RLE:
- currentEmbedding = levels[i] = leastGreaterOdd(currentEmbedding);
- levels[i] = setOverrideBits(levels[i], currentOverride);
- currentOverride = ON;
- break;
-
- case LRE:
- currentEmbedding = levels[i] = leastGreaterEven(currentEmbedding);
- levels[i] = setOverrideBits(levels[i], currentOverride);
- currentOverride = ON;
- break;
-
- case RLO:
- currentEmbedding = levels[i] = leastGreaterOdd(currentEmbedding);
- tempType = currentOverride = R;
- bover = true;
- break;
-
- case LRO:
- currentEmbedding = levels[i] = leastGreaterEven(currentEmbedding);
- tempType = currentOverride = L;
- bover = true;
- break;
-
- case PDF: {
- int prevlevel = getPreviousLevel(levels, i);
-
- if (prevlevel == -1) {
- currentEmbedding = paragraphLevel;
- currentOverride = ON;
- } else {
- currentOverride = currentEmbedding & OMASK;
- currentEmbedding = currentEmbedding & ~OMASK;
- }
- levels[i] = currentEmbedding;
- break;
- }
-
- /* Whitespace is treated as neutral for now */
- case WS:
- case S:
- levels[i] = currentEmbedding;
- tempType = ON;
- if (currentOverride != ON)
- tempType = currentOverride;
- break;
-
- default:
- levels[i] = currentEmbedding;
- if (currentOverride != ON)
- tempType = currentOverride;
- break;
-
- }
- types[i] = tempType;
- }
- /* this clears out all overrides, so we can use levels safely... */
- /* checks bover first */
- if (bover)
- for (i=0; i<count; i++)
- levels[i] = levels[i] & LMASK;
-
- /* Rule (X9)
- * X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes.
- * Here, they're converted to BN.
- */
- for (i=0; i<count; i++) {
- switch (types[i]) {
- case RLE:
- case LRE:
- case RLO:
- case LRO:
- case PDF:
- types[i] = BN;
- break;
- }
- }
-
- /* Rule (W1)
- * W1. Examine each non-spacing mark (NSM) in the level run, and change
- * the type of the NSM to the type of the previous character. If the NSM
- * is at the start of the level run, it will get the type of sor.
- */
- if (types[0] == NSM)
- types[0] = paragraphLevel;
-
- for (i=1; i<count; i++) {
- if (types[i] == NSM)
- types[i] = types[i-1];
- /* Is this a safe assumption?
- * I assumed the previous, IS a character.
- */
- }
-
- /* Rule (W2)
- * W2. Search backwards from each instance of a European number until the
- * first strong type (R, L, AL, or sor) is found. If an AL is found,
- * change the type of the European number to Arabic number.
- */
- for (i=0; i<count; i++) {
- if (types[i] == EN) {
- j=i;
- while (j >= 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<count; i++) {
- if (types[i] == AL)
- types[i] = R;
- }
-
- /* Rule (W4)
- * W4. A single European separator between two European numbers changes
- * to a European number. A single common separator between two numbers
- * of the same type changes to that type.
- */
- for (i=1; i<(count-1); i++) {
- if (types[i] == ES) {
- if (types[i-1] == EN && types[i+1] == EN)
- types[i] = EN;
- } else if (types[i] == CS) {
- if (types[i-1] == EN && types[i+1] == EN)
- types[i] = EN;
- else if (types[i-1] == AN && types[i+1] == AN)
- types[i] = AN;
- }
- }
-
- /* Rule (W5)
- * W5. A sequence of European terminators adjacent to European numbers
- * changes to all European numbers.
- *
- * Optimization: lots here... else ifs need rearrangement
- */
- for (i=0; i<count; i++) {
- if (types[i] == ET) {
- if (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<count; i++) {
- switch (types[i]) {
- case ES:
- case ET:
- case CS:
- types[i] = ON;
- break;
- }
- }
-
- /* Rule (W7)
- * W7. Search backwards from each instance of a European number until
- * the first strong type (R, L, or sor) is found. If an L is found,
- * then change the type of the European number to L.
- */
- for (i=0; i<count; i++) {
- if (types[i] == EN) {
- j=i;
- while (j >= 0) {
- if (types[j] == L) {
- types[i] = L;
- break;
- } else if (types[j] == R || types[j] == AL) {
- break;
- }
- j--;
- }
- }
- }
-
- /* Rule (N1)
- * N1. A sequence of neutrals takes the direction of the surrounding
- * strong text if the text on both sides has the same direction. European
- * and Arabic numbers are treated as though they were R.
- */
- if (count >= 2 && types[0] == ON) {
- if ((types[1] == R) || (types[1] == EN) || (types[1] == AN))
- types[0] = R;
- else if (types[1] == L)
- types[0] = L;
- }
- for (i=1; i<(count-1); i++) {
- if (types[i] == ON) {
- if (types[i-1] == L) {
- j=i;
- while (j<(count-1) && types[j] == ON) {
- j++;
- }
- if (types[j] == L) {
- while (i<j) {
- types[i] = L;
- i++;
- }
- }
-
- } else if ((types[i-1] == R) ||
- (types[i-1] == EN) ||
- (types[i-1] == AN)) {
- j=i;
- while (j<(count-1) && types[j] == ON) {
- j++;
- }
- if ((types[j] == R) ||
- (types[j] == EN) ||
- (types[j] == AN)) {
- while (i<j) {
- types[i] = R;
- i++;
- }
- }
- }
- }
- }
- if (count >= 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; i<count; i++) {
- if (types[i] == ON) {
- if ((levels[i] % 2) == 0)
- types[i] = L;
- else
- types[i] = R;
- }
- }
-
- /* Rule (I1)
- * I1. For all characters with an even (left-to-right) embedding
- * direction, those of type R go up one level and those of type AN or
- * EN go up two levels.
- */
- for (i=0; i<count; i++) {
- if ((levels[i] % 2) == 0) {
- if (types[i] == R)
- levels[i] += 1;
- else if (types[i] == AN || types[i] == EN)
- levels[i] += 2;
- }
- }
-
- /* Rule (I2)
- * I2. For all characters with an odd (right-to-left) embedding direction,
- * those of type L, EN or AN go up one level.
- */
- for (i=0; i<count; i++) {
- if ((levels[i] % 2) == 1) {
- if (types[i] == L || types[i] == EN || types[i] == AN)
- levels[i] += 1;
- }
- }
-
- /* Rule (L1)
- * L1. On each line, reset the embedding level of the following characters
- * to the paragraph embedding level:
- * (1)segment separators, (2)paragraph separators,
- * (3)any sequence of whitespace characters preceding
- * a segment separator or paragraph separator,
- * (4)and any sequence of white space characters
- * at the end of the line.
- * The types of characters used here are the original types, not those
- * modified by the previous phase.
- */
- j=count-1;
- while (j>0 && (getType(line[j].wc) == WS)) {
- j--;
- }
- if (j < (count-1)) {
- for (j++; j<count; j++)
- levels[j] = paragraphLevel;
- }
- for (i=0; i<count; i++) {
- tempType = getType(line[i].wc);
- if (tempType == WS) {
- j=i;
- while (j<count && (getType(line[j].wc) == WS)) {
- j++;
- }
- if (j==count || getType(line[j].wc) == B ||
- getType(line[j].wc) == S) {
- 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<count; i++)
- if ((levels[i] % 2) == 1)
- doMirror(&line[i].wc);
-
- /* Rule (L2)
- * L2. From the highest level found in the text to the lowest odd level on
- * each line, including intermediate levels not actually present in the
- * text, reverse any contiguous sequence of characters that are at that
- * level or higher
- */
- /* we flip the character string and leave the level array */
- i=0;
- tempType = levels[0];
- while (i < count) {
- if (levels[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 <stdio.h>
-#include <assert.h>
-
-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/misc.c b/misc.c
deleted file mode 100644
index 56f2ba93..00000000
--- a/misc.c
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- * 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 <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <limits.h>
-#include <ctype.h>
-#include <assert.h>
-
-#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/misc.h b/misc.h
index 7b4012b6..1b3d324a 100644
--- a/misc.h
+++ b/misc.h
@@ -1,5 +1,6 @@
/*
- * Header for misc.c.
+ * Header for miscellaneous helper functions, mostly defined in the
+ * utils subdirectory.
*/
#ifndef PUTTY_MISC_H
@@ -33,7 +34,7 @@ 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
+ * implementation struct in strbuf.c, which isn't exposed to client
* code.
*/
struct strbuf {
@@ -51,13 +52,15 @@ struct strbuf {
strbuf *strbuf_new(void);
strbuf *strbuf_new_nm(void);
+/* Helpers to allocate a strbuf containing an existing string */
+strbuf *strbuf_dup(ptrlen string);
+strbuf *strbuf_dup_nm(ptrlen string);
+
void strbuf_free(strbuf *buf);
void *strbuf_append(strbuf *buf, size_t len);
void strbuf_shrink_to(strbuf *buf, size_t new_len);
void strbuf_shrink_by(strbuf *buf, size_t amount_to_remove);
char *strbuf_to_str(strbuf *buf); /* does free buf, but you must free result */
-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);
@@ -65,13 +68,13 @@ 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). */
+ * work around the rather deficient interface of mb_to_wc. */
wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len);
wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string);
+char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len,
+ const char *defchr);
+char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string,
+ const char *defchr);
static inline int toint(unsigned u)
{
@@ -105,6 +108,20 @@ bool strendswith(const char *s, const char *t);
void base64_encode_atom(const unsigned char *data, int n, char *out);
int base64_decode_atom(const char *atom, unsigned char *out);
+void base64_decode_bs(BinarySink *bs, ptrlen data);
+void base64_decode_fp(FILE *fp, ptrlen data);
+strbuf *base64_decode_sb(ptrlen data);
+void base64_encode_bs(BinarySink *bs, ptrlen data, int cpl);
+void base64_encode_fp(FILE *fp, ptrlen data, int cpl);
+strbuf *base64_encode_sb(ptrlen data, int cpl);
+bool base64_valid(ptrlen data);
+
+void percent_encode_bs(BinarySink *bs, ptrlen data, const char *badchars);
+void percent_encode_fp(FILE *fp, ptrlen data, const char *badchars);
+strbuf *percent_encode_sb(ptrlen data, const char *badchars);
+void percent_decode_bs(BinarySink *bs, ptrlen data);
+void percent_decode_fp(FILE *fp, ptrlen data);
+strbuf *percent_decode_sb(ptrlen data);
struct bufchain_granule;
struct bufchain_tag {
@@ -123,6 +140,8 @@ ptrlen bufchain_prefix(bufchain *ch);
void bufchain_consume(bufchain *ch, size_t len);
void bufchain_fetch(bufchain *ch, void *data, size_t len);
void bufchain_fetch_consume(bufchain *ch, void *data, size_t len);
+bool bufchain_try_consume(bufchain *ch, size_t len);
+bool bufchain_try_fetch(bufchain *ch, void *data, size_t len);
bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len);
size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len);
void bufchain_set_callback_inner(
@@ -132,9 +151,9 @@ 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
+ * function. Lives here rather than in bufchain.c so that
+ * standalone programs can use the bufchain facility without this
+ * optional callback feature and not need to provide a stub of
* queue_idempotent_callback. */
bufchain_set_callback_inner(ch, ic, queue_idempotent_callback);
}
@@ -157,6 +176,21 @@ static inline ptrlen make_ptrlen(const void *ptr, size_t len)
return pl;
}
+static inline const void *ptrlen_end(ptrlen pl)
+{
+ return (const char *)pl.ptr + pl.len;
+}
+
+static inline ptrlen make_ptrlen_startend(const void *startv, const void *endv)
+{
+ const char *start = (const char *)startv, *end = (const char *)endv;
+ assert(end >= start);
+ ptrlen pl;
+ pl.ptr = start;
+ pl.len = end - start;
+ return pl;
+}
+
static inline ptrlen ptrlen_from_asciz(const char *str)
{
return make_ptrlen(str, strlen(str));
@@ -178,6 +212,8 @@ int ptrlen_strcmp(ptrlen pl1, ptrlen pl2);
bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail);
bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail);
ptrlen ptrlen_get_word(ptrlen *input, const char *separators);
+bool ptrlen_contains(ptrlen input, const char *characters);
+bool ptrlen_contains_only(ptrlen input, const char *characters);
char *mkstr(ptrlen pl);
int string_length_for_printf(size_t);
/* Derive two printf arguments from a ptrlen, suitable for "%.*s" */
@@ -196,6 +232,8 @@ int string_length_for_printf(size_t);
/* Make a ptrlen out of a constant byte array. */
#define PTRLEN_FROM_CONST_BYTES(a) make_ptrlen(a, sizeof(a))
+void wordwrap(BinarySink *bs, ptrlen input, size_t maxwid);
+
/* Wipe sensitive data out of memory that's about to be freed. Simpler
* than memset because we don't need the fill char parameter; also
* attempts (by fiddly use of volatile) to inhibit the compiler from
@@ -206,15 +244,31 @@ 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);
+ * Returns 0 for mismatch or 1 for equality (unlike memcmp), hinted at
+ * by the 'eq' in the name. */
+unsigned smemeq(const void *av, const void *bv, size_t len);
/* Encode a single UTF-8 character. Assumes that illegal characters
* (such as things in the surrogate range, or > 0x10FFFF) have already
* been removed. */
size_t encode_utf8(void *output, unsigned long ch);
+/* Encode a wide-character string into UTF-8. Tolerates surrogates if
+ * sizeof(wchar_t) == 2, assuming that in that case the wide string is
+ * encoded in UTF-16. */
+char *encode_wide_string_as_utf8(const wchar_t *wstr);
+
+/* Decode a single UTF-8 character. Returns U+FFFD for any of the
+ * illegal cases. */
+unsigned long decode_utf8(const char **utf8);
+
+/* Decode a single UTF-8 character to an output buffer of the
+ * platform's wchar_t. May write a pair of surrogates if
+ * sizeof(wchar_t) == 2, assuming that in that case the wide string is
+ * encoded in UTF-16. Otherwise, writes one character. Returns the
+ * number written. */
+size_t decode_utf8_to_wchar(const char **utf8, wchar_t *out);
+
/* Write a string out in C string-literal format. */
void write_c_string_literal(FILE *fp, ptrlen str);
@@ -374,6 +428,22 @@ static inline void PUT_16BIT_MSB_FIRST(void *vp, uint16_t value)
p[0] = (uint8_t)(value >> 8);
}
+/* For use in X11-related applications, an endianness-variable form of
+ * {GET,PUT}_16BIT which expects 'endian' to be either 'B' or 'l' */
+
+static inline uint16_t GET_16BIT_X11(char endian, const void *p)
+{
+ return endian == 'B' ? GET_16BIT_MSB_FIRST(p) : GET_16BIT_LSB_FIRST(p);
+}
+
+static inline void PUT_16BIT_X11(char endian, void *p, uint16_t value)
+{
+ if (endian == 'B')
+ PUT_16BIT_MSB_FIRST(p, value);
+ else
+ PUT_16BIT_LSB_FIRST(p, value);
+}
+
/* Replace NULL with the empty string, permitting an idiom in which we
* get a string (pointer,length) pair that might be NULL,0 and can
* then safely say things like printf("%.*s", length, NULLTOEMPTY(ptr)) */
@@ -439,4 +509,18 @@ static inline ptrlen ptrlen_from_lf(LoadedFile *lf)
* is made to handle difficult overlap cases. */
void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size);
+/* Boolean expressions used in OpenSSH certificate configuration */
+bool cert_expr_valid(const char *expression,
+ char **error_msg, ptrlen *error_loc);
+bool cert_expr_match_str(const char *expression,
+ const char *hostname, unsigned port);
+/* Build a certificate expression out of hostname wildcards. Required
+ * to handle legacy configuration from early in development, when
+ * multiple wildcards were stored separately in config, implicitly
+ * ORed together. */
+CertExprBuilder *cert_expr_builder_new(void);
+void cert_expr_builder_free(CertExprBuilder *eb);
+void cert_expr_builder_add(CertExprBuilder *eb, const char *wildcard);
+char *cert_expr_expression(CertExprBuilder *eb);
+
#endif
diff --git a/miscucs.c b/miscucs.c
deleted file mode 100644
index 7785f9b6..00000000
--- a/miscucs.c
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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/mkauto.sh b/mkauto.sh
deleted file mode 100755
index 9759438f..00000000
--- a/mkauto.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#! /bin/sh
-# This script makes the autoconf mechanism for the Unix port work.
-# It's separate from mkfiles.pl because it won't work (and isn't needed)
-# on a non-Unix system.
-
-# It's nice to be able to run this from inside the unix subdir as
-# well as from outside.
-test -f unix.h && cd ..
-
-# Run autoconf on our real configure.in.
-autoreconf -i && rm -rf autom4te.cache
diff --git a/mkfiles.pl b/mkfiles.pl
deleted file mode 100755
index b323e87f..00000000
--- a/mkfiles.pl
+++ /dev/null
@@ -1,2092 +0,0 @@
-#!/usr/bin/env perl
-#
-# Cross-platform Makefile generator.
-#
-# Reads the file `Recipe' to determine the list of generated
-# executables and their component objects. Then reads the source
-# files to compute #include dependencies. Finally, writes out the
-# various target Makefiles.
-
-# PuTTY specifics which could still do with removing:
-# - Mac makefile is not portabilised at all. Include directories
-# are hardwired, and also the libraries are fixed. This is
-# mainly because I was too scared to go anywhere near it.
-# - sbcsgen.pl is still run at startup.
-#
-# FIXME: no attempt made to handle !forceobj in the project files.
-
-use warnings;
-use FileHandle;
-use File::Basename;
-use Cwd;
-use Digest::SHA qw(sha512_hex);
-
-if ($#ARGV >= 0 and ($ARGV[0] eq "-u" or $ARGV[0] eq "-U")) {
- # Convenience for Unix users: -u means that after we finish what
- # we're doing here, we also run mkauto.sh and then 'configure' in
- # the Unix subdirectory. So it's a one-stop shop for regenerating
- # the actual end-product Unix makefile.
- #
- # Arguments supplied after -u go to configure.
- #
- # -U is identical, but runs 'configure' at the _top_ level, for
- # people who habitually do that.
- $do_unix = ($ARGV[0] eq "-U" ? 2 : 1);
- shift @ARGV;
- @confargs = @ARGV;
-}
-
-open IN, "Recipe" or do {
- # We want to deal correctly with being run from one of the
- # subdirs in the source tree. So if we can't find Recipe here,
- # try one level up.
- chdir "..";
- open IN, "Recipe" or die "unable to open Recipe file\n";
-};
-
-# HACK: One of the source files in `charset' is auto-generated by
-# sbcsgen.pl, and licence.h is likewise generated by licence.pl. We
-# need to generate those _now_, before attempting dependency analysis.
-eval 'chdir "charset"; require "./sbcsgen.pl"; chdir ".."; select STDOUT;';
-eval 'require "./licence.pl"; select STDOUT;';
-
-@srcdirs = ("./");
-
-$divert = undef; # ref to scalar in which text is currently being put
-$help = ""; # list of newline-free lines of help text
-$project_name = "project"; # this is a good enough default
-%makefiles = (); # maps makefile types to output makefile pathnames
-%makefile_extra = (); # maps makefile types to extra Makefile text
-%programs = (); # maps prog name + type letter to listref of objects/resources
-%groups = (); # maps group name to listref of objects/resources
-
-while (<IN>) {
- chomp;
- @_ = split;
-
- # If we're gathering help text, keep doing so.
- if (defined $divert) {
- if ((defined $_[0]) && $_[0] eq "!end") {
- $divert = undef;
- } else {
- ${$divert} .= "$_\n";
- }
- next;
- }
- # Skip comments and blank lines.
- next if /^\s*#/ or scalar @_ == 0;
-
- if ($_[0] eq "!begin" and $_[1] eq "help") { $divert = \$help; next; }
- if ($_[0] eq "!end") { $divert = undef; next; }
- if ($_[0] eq "!name") { $project_name = $_[1]; next; }
- if ($_[0] eq "!srcdir") { push @srcdirs, $_[1]; next; }
- if ($_[0] eq "!makefile" and &mfval($_[1])) { $makefiles{$_[1]}=$_[2]; next;}
- if ($_[0] eq "!specialobj" and &mfval($_[1])) { $specialobj{$_[1]}->{$_[2]} = 1; next;}
- if ($_[0] eq "!cflags" and &mfval($_[1])) {
- ($rest = $_) =~ s/^\s*\S+\s+\S+\s+\S+\s*//; # find rest of input line
- if ($rest eq "") {
- # Make sure this file doesn't get lumped together with any
- # other file's cflags.
- $rest = "F" . $_[2];
- } else {
- # Give this file a specific set of cflags, but permit it to
- # go together with other files using the same set.
- $rest = "C" . $rest;
- }
- $cflags{$_[1]}->{$_[2]} = $rest;
- next;
- }
- if ($_[0] eq "!forceobj") { $forceobj{$_[1]} = 1; next; }
- if ($_[0] eq "!begin") {
- if ($_[1] =~ /^>(.*)/) {
- $divert = \$auxfiles{$1};
- } elsif (&mfval($_[1])) {
- $sect = $_[2] ? $_[2] : "end";
- $divert = \($makefile_extra{$_[1]}->{$sect});
- } else {
- $dummy = '';
- $divert = \$dummy;
- }
- next;
- }
- # If we're gathering help/verbatim text, keep doing so.
- if (defined $divert) { ${$divert} .= "$_\n"; next; }
- # Ignore blank lines.
- next if scalar @_ == 0;
-
- # Now we have an ordinary line. See if it's an = line, a : line
- # or a + line.
- @objs = @_;
-
- if ($_[0] eq "+") {
- $listref = $lastlistref;
- $prog = undef;
- die "$.: unexpected + line\n" if !defined $lastlistref;
- } elsif ($#_ >= 1 && $_[1] eq "=") {
- $groups{$_[0]} = [] if !defined $groups{$_[0]};
- $listref = $groups{$_[0]};
- $prog = undef;
- shift @objs; # eat the group name
- } elsif ($#_ >= 1 && $_[1] eq ":") {
- $listref = [];
- $prog = $_[0];
- shift @objs; # eat the program name
- } else {
- die "$.: unrecognised line type\n";
- }
- shift @objs; # eat the +, the = or the :
-
- while (scalar @objs > 0) {
- $i = shift @objs;
- if ($groups{$i}) {
- foreach $j (@{$groups{$i}}) { unshift @objs, $j; }
- } elsif (($i =~ /^\[([A-Z]*)\]$/) and defined $prog) {
- $type = substr($i,1,(length $i)-2);
- die "unrecognised program type for $prog [$type]\n"
- if ! grep { $type eq $_ } qw(G C X U MX XT UT);
- } else {
- push @$listref, $i;
- }
- }
- if ($prog and $type) {
- die "multiple program entries for $prog [$type]\n"
- if defined $programs{$prog . "," . $type};
- $programs{$prog . "," . $type} = $listref;
- }
- $lastlistref = $listref;
-}
-
-close IN;
-
-foreach $aux (sort keys %auxfiles) {
- open AUX, ">$aux";
- print AUX $auxfiles{$aux};
- close AUX;
-}
-
-# Now retrieve the complete list of objects and resource files, and
-# construct dependency data for them. While we're here, expand the
-# object list for each program, and complain if its type isn't set.
-@prognames = sort keys %programs;
-%depends = ();
-@scanlist = ();
-foreach $i (@prognames) {
- ($prog, $type) = split ",", $i;
- # Strip duplicate object names.
- $prev = '';
- @list = grep { $status = ($prev ne $_); $prev=$_; $status }
- sort @{$programs{$i}};
- $programs{$i} = [@list];
- foreach $j (@list) {
- # Dependencies for "x" start with "x.c" or "x.m" (depending on
- # which one exists).
- # Dependencies for "x.res" start with "x.rc".
- # Dependencies for "x.rsrc" start with "x.r".
- # Both types of file are pushed on the list of files to scan.
- # Libraries (.lib) don't have dependencies at all.
- if ($j =~ /^(.*)\.res$/) {
- $file = "$1.rc";
- $depends{$j} = [$file];
- push @scanlist, $file;
- } elsif ($j =~ /^(.*)\.rsrc$/) {
- $file = "$1.r";
- $depends{$j} = [$file];
- push @scanlist, $file;
- } elsif ($j !~ /\./) {
- $file = "$j.c";
- $file = "$j.m" unless &findfile($file);
- $depends{$j} = [$file];
- push @scanlist, $file;
- }
- }
-}
-
-# Scan each file on @scanlist and find further inclusions.
-# Inclusions are given by lines of the form `#include "otherfile"'
-# (system headers are automatically ignored by this because they'll
-# be given in angle brackets). Files included by this method are
-# added back on to @scanlist to be scanned in turn (if not already
-# done).
-#
-# Resource scripts (.rc) can also include a file by means of:
-# - a line # ending `ICON "filename"';
-# - a line ending `RT_MANIFEST "filename"'.
-# Files included by this method are not added to @scanlist because
-# they can never include further files.
-#
-# In this pass we write out a hash %further which maps a source
-# file name into a listref containing further source file names.
-
-%further = ();
-%allsourcefiles = (); # this is wanted by some makefiles
-while (scalar @scanlist > 0) {
- $file = shift @scanlist;
- next if defined $further{$file}; # skip if we've already done it
- $further{$file} = [];
- $dirfile = &findfile($file);
- $allsourcefiles{$dirfile} = 1;
- open IN, "$dirfile" or die "unable to open source file $file\n";
- while (<IN>) {
- chomp;
- /^\s*#include\s+\"([^\"]+)\"/ and do {
- push @{$further{$file}}, $1;
- push @scanlist, $1;
- next;
- };
- /(RT_MANIFEST|ICON)\s+\"([^\"]+)\"\s*$/ and do {
- push @{$further{$file}}, $2;
- next;
- }
- }
- close IN;
-}
-
-# Now we're ready to generate the final dependencies section. For
-# each key in %depends, we must expand the dependencies list by
-# iteratively adding entries from %further.
-foreach $i (keys %depends) {
- %dep = ();
- @scanlist = @{$depends{$i}};
- foreach $i (@scanlist) { $dep{$i} = 1; }
- while (scalar @scanlist > 0) {
- $file = shift @scanlist;
- foreach $j (@{$further{$file}}) {
- if (!$dep{$j}) {
- $dep{$j} = 1;
- push @{$depends{$i}}, $j;
- push @scanlist, $j;
- }
- }
- }
-# printf "%s: %s\n", $i, join ' ',@{$depends{$i}};
-}
-
-# Validation of input.
-
-sub mfval($) {
- my ($type) = @_;
- # Returns true if the argument is a known makefile type. Otherwise,
- # prints a warning and returns false;
- if (grep { $type eq $_ }
- ("vc","vcproj","cygwin","lcc","devcppproj","gtk","unix",
- "am","osx","vstudio10","vstudio12","clangcl")) {
- return 1;
- }
- warn "$.:unknown makefile type '$type'\n";
- return 0;
-}
-
-# Utility routines while writing out the Makefiles.
-
-sub def {
- my ($x) = shift @_;
- return (defined $x) ? $x : "";
-}
-
-sub dirpfx {
- my ($path) = shift @_;
- my ($sep) = shift @_;
- my $ret = "";
- my $i;
-
- while (($i = index $path, $sep) >= 0 ||
- ($j = index $path, "/") >= 0) {
- if ($i >= 0 and ($j < 0 or $i < $j)) {
- $path = substr $path, ($i + length $sep);
- } else {
- $path = substr $path, ($j + 1);
- }
- $ret .= "..$sep";
- }
- return $ret;
-}
-
-sub findfile {
- my ($name) = @_;
- my $dir = '';
- my $i;
- my $outdir = undef;
- unless (defined $findfilecache{$name}) {
- $i = 0;
- foreach $dir (@srcdirs) {
- if (-f "$dir$name") {
- $outdir = $dir;
- $i++;
- $outdir =~ s/^\.\///;
- }
- }
- die "multiple instances of source file $name\n" if $i > 1;
- $findfilecache{$name} = (defined $outdir ? $outdir . $name : undef);
- }
- return $findfilecache{$name};
-}
-
-sub objects {
- my ($prog, $otmpl, $rtmpl, $ltmpl, $prefix, $dirsep) = @_;
- my @ret;
- my ($i, $x, $y);
- ($otmpl, $rtmpl, $ltmpl) = map { defined $_ ? $_ : "" } ($otmpl, $rtmpl, $ltmpl);
- @ret = ();
- foreach $i (@{$programs{$prog}}) {
- $x = "";
- if ($i =~ /^(.*)\.(res|rsrc)/) {
- $y = $1;
- ($x = $rtmpl) =~ s/X/$y/;
- } elsif ($i =~ /^(.*)\.lib/) {
- $y = $1;
- ($x = $ltmpl) =~ s/X/$y/;
- } elsif ($i !~ /\./) {
- ($x = $otmpl) =~ s/X/$i/;
- }
- push @ret, $x if $x ne "";
- }
- return join " ", @ret;
-}
-
-sub special {
- my ($prog, $suffix) = @_;
- my @ret;
- my ($i, $x, $y);
- ($otmpl, $rtmpl, $ltmpl) = map { defined $_ ? $_ : "" } ($otmpl, $rtmpl, $ltmpl);
- @ret = ();
- foreach $i (@{$programs{$prog}}) {
- if (substr($i, (length $i) - (length $suffix)) eq $suffix) {
- push @ret, $i;
- }
- }
- return (scalar @ret) ? (join " ", @ret) : undef;
-}
-
-sub splitline {
- my ($line, $width, $splitchar) = @_;
- my $result = "";
- my $len;
- $len = (defined $width ? $width : 76);
- $splitchar = (defined $splitchar ? $splitchar : '\\');
- while (length $line > $len) {
- $line =~ /^(.{0,$len})\s(.*)$/ or $line =~ /^(.{$len,})?\s(.*)$/;
- $result .= $1;
- $result .= " ${splitchar}\n\t\t" if $2 ne '';
- $line = $2;
- $len = 60;
- }
- return $result . $line;
-}
-
-sub deps {
- my ($otmpl, $rtmpl, $prefix, $dirsep, $mftyp, $depchar, $splitchar) = @_;
- my ($i, $x, $y);
- my @deps;
- my @ret;
- @ret = ();
- $depchar ||= ':';
- foreach $i (sort keys %depends) {
- next if $specialobj{$mftyp}->{$i};
- if ($i =~ /^(.*)\.(res|rsrc)/) {
- next if !defined $rtmpl;
- $y = $1;
- ($x = $rtmpl) =~ s/X/$y/;
- } else {
- ($x = $otmpl) =~ s/X/$i/;
- }
- @deps = @{$depends{$i}};
- @deps = map {
- $_ = &findfile($_);
- s/\//$dirsep/g;
- $_ = $prefix . $_;
- } @deps;
- push @ret, {obj => $x, obj_orig => $i, deps => [@deps]};
- }
- return @ret;
-}
-
-sub prognames {
- my ($types) = @_;
- my ($n, $prog, $type);
- my @ret;
- @ret = ();
- foreach $n (@prognames) {
- ($prog, $type) = split ",", $n;
- push @ret, $n if index(":$types:", ":$type:") >= 0;
- }
- return @ret;
-}
-
-sub progrealnames {
- my ($types) = @_;
- my ($n, $prog, $type);
- my @ret;
- @ret = ();
- foreach $n (@prognames) {
- ($prog, $type) = split ",", $n;
- push @ret, $prog if index(":$types:", ":$type:") >= 0;
- }
- return @ret;
-}
-
-sub manpages {
- my ($types,$suffix) = @_;
-
- # assume that all UNIX programs have a man page
- if($suffix eq "1" && $types =~ /:X:/) {
- return map("$_.1", &progrealnames($types));
- }
- return ();
-}
-
-$orig_dir = cwd;
-
-# Now we're ready to output the actual Makefiles.
-
-if (defined $makefiles{'clangcl'}) {
- $dirpfx = &dirpfx($makefiles{'clangcl'}, "/");
-
- ##-- Makefile for cross-compiling using clang-cl, lld-link, and
- ## MinGW's windres for resource compilation.
- #
- # This makefile allows a complete Linux-based cross-compile, but
- # using the real Visual Studio header files and libraries. In
- # order to run it, you will need:
- #
- # - clang-cl, llvm-rc and lld-link on your PATH.
- # * I built these from the up-to-date LLVM project trunk git
- # repositories, as of 2018-05-29.
- # - case-mashed copies of the Visual Studio include directories.
- # * On a real VS installation, run vcvars32.bat and look at
- # the resulting value of %INCLUDE%. Take a full copy of each
- # of those directories, and inside the copy, for each
- # include file that has an uppercase letter in its name,
- # make a lowercased symlink to it. Additionally, one of the
- # directories will contain files called driverspecs.h and
- # specstrings.h, and those will need symlinks called
- # DriverSpecs.h and SpecStrings.h.
- # * Now, on Linux, define the environment variable INCLUDE to
- # be a list, separated by *semicolons* (in the Windows
- # style), of those directories, but before all of them you
- # must also include lib/clang/5.0.0/include from the clang
- # installation area (which contains in particular a
- # clang-compatible stdarg.h overriding the Visual Studio
- # one).
- # - similarly case-mashed copies of the library directories.
- # * Again, on a real VS installation, run vcvars32 or
- # vcvarsx86_amd64 (as appropriate), look at %LIB%, make a
- # copy of each directory, and provide symlinks within that
- # directory so that all the files can be opened as
- # lowercase.
- # * Then set LIB to be a semicolon-separated list of those
- # directories (but you'll need to change which set of
- # directories depending on whether you want to do a 32-bit
- # or 64-bit build).
- # - for a 64-bit build, set 'Platform=x64' in the environment as
- # well, or else on the make command line.
- # * This is a variable understood only by this makefile - none
- # of the tools we invoke will know it - but it's consistent
- # with the way the VS scripts like vcvarsx86_amd64.bat set
- # things up, and since the environment has to change
- # _anyway_ between 32- and 64-bit builds (different set of
- # paths in $LIB) it's reasonable to have the choice of
- # compilation target driven by another environment variable
- # set in parallel with that one.
- # - for older versions of the VS libraries you may also have to
- # set EXTRA_console and/or EXTRA_windows to the name of an
- # object file manually extracted from one of those libraries.
- # * This is because old VS seems to manage its startup code by
- # having libcmt.lib contain lots of *crt0.obj objects, one
- # for each possible user entry point (main, WinMain and the
- # wide-char versions of both), of which the linker arranges
- # to include the right one by special-case code. But lld
- # only seems to mimic half of that code - it does include
- # the right crt0 object, but it doesn't also deliberately
- # _avoid_ including the _wrong_ ones, and since all those
- # objects define a common set of global symbols for other
- # parts of the library to use, lld may well select an
- # arbitrary one of them the first time it sees a reference
- # to one of those global symbols, and then later also select
- # the _right_ one for the application's entry point, causing
- # a multiple-definitions crash.
- # * So the workaround is to explicitly include the right
- # *crt0.obj file on the linker command line before lld even
- # begins searching libraries. Hence, for a console
- # application, you might extract crt0.obj from the library
- # in question and set EXTRA_console=crt0.obj, and for a GUI
- # application, do the same with wincrt0.obj. Then this
- # makefile will include the right one of those objects
- # alongside the matching /subsystem linker option.
- # - also for older versions of the VS libraries, you may also
- # have to set EXTRA_libs to include extra library files.
-
- open OUT, ">$makefiles{'clangcl'}"; select OUT;
- print
- "# Makefile for cross-compiling $project_name using clang-cl, lld-link,\n".
- "# and llvm-rc, using GNU make on Linux.\n".
- "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
- "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
- print $help;
- print
- "\n".
- "CCCMD = clang-cl\n".
- "RCCMD = llvm-rc\n".
- "ifeq (\$(Platform),x64)\n".
- "CCTARGET = x86_64-pc-windows-msvc18.0.0\n".
- "PLATFORMCFLAGS =\n".
- "else ifeq (\$(Platform),arm)\n".
- "CCTARGET = arm-pc-windows-msvc18.0.0\n".
- "PLATFORMCFLAGS = /D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE /GS-\n".
- "else ifeq (\$(Platform),arm64)\n".
- "CCTARGET = arm64-pc-windows-msvc18.0.0\n".
- "PLATFORMCFLAGS = /D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE /GS-\n".
- "else\n".
- "CCTARGET = i386-pc-windows-msvc18.0.0\n".
- "PLATFORMCFLAGS =\n".
- "endif\n".
- "CC = \$(CCCMD)\n".
- "RC = \$(RCCMD) /c 1252 \n".
- "RCPREPROC = \$(CCCMD) /P /TC\n".
- "LD = lld-link\n".
- "\n".
- "# C compilation flags\n".
- &splitline("CFLAGS = --target=\$(CCTARGET) /nologo /W3 /O1 -Wvla " .
- (join " ", map {"-I$dirpfx$_"} @srcdirs) .
- " /D_WINDOWS /D_WIN32_WINDOWS=0x500 /DWINVER=0x500 ".
- "/D_CRT_SECURE_NO_WARNINGS /D_WINSOCK_DEPRECATED_NO_WARNINGS").
- " \$(PLATFORMCFLAGS)\n".
- "LFLAGS = /incremental:no /dynamicbase /nxcompat\n".
- &splitline("RCPPFLAGS = ".(join " ", map {"-I$dirpfx$_"} @srcdirs).
- " -DWIN32 -D_WIN32 -DWINVER=0x0400")." \$(RCFL)\n".
- "\n".
- &def($makefile_extra{'clangcl'}->{'vars'}) .
- "\n".
- "\n";
- print &splitline("all:" . join "", map { " \$(BUILDDIR)$_.exe" } &progrealnames("G:C"));
- print "\n\n";
- foreach $p (&prognames("G:C")) {
- ($prog, $type) = split ",", $p;
- $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", undef);
- print &splitline("\$(BUILDDIR)$prog.exe: " . $objstr), "\n";
-
- $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", "X.lib");
- $subsys = ($type eq "G") ? "windows" : "console";
- print &splitline("\t\$(LD) \$(LFLAGS) \$(XLFLAGS) ".
- "/out:\$(BUILDDIR)$prog.exe ".
- "/lldmap:\$(BUILDDIR)$prog.map ".
- "/subsystem:$subsys\$(SUBSYSVER) ".
- "\$(EXTRA_$subsys) $objstr \$(EXTRA_libs)")."\n\n";
- }
- my $rc_pp_rules = "";
- foreach $d (&deps("\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", $dirpfx, "/", "vc")) {
- $extradeps = $forceobj{$d->{obj_orig}} ? ["*.c","*.h","*.rc"] : [];
- my $rule;
- my @deps = @{$d->{deps}};
- my @incdeps = grep { m!\.rc2?$! } @deps;
- my @rcdeps = grep { ! m!\.rc2$! } @deps;
- if ($d->{obj} =~ /\.res$/) {
- my $rc = $deps[0];
- my $rcpp = $rc;
- $rcpp =~ s!.*/!!;
- $rcpp =~ s/\.rc$/.rcpp/;
- $rcpp = "\$(BUILDDIR)" . $rcpp;
- $rule = "\$(RC) ".$rcpp." /FO ".$d->{obj};
- $rc_pp_rules .= &splitline(
- sprintf("%s: %s", $rcpp, join " ", @incdeps)) ."\n" .
- "\t\$(RCPREPROC) \$(RCPPFLAGS) /Fi\$\@ \$<\n\n";
- $rcdeps[0] = $rcpp;
- } else {
- $rule = "\$(CC) /Fo\$(BUILDDIR) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) /c \$<";
- }
- print &splitline(sprintf("%s: %s", $d->{obj},
- join " ", @$extradeps, @rcdeps)), "\n";
- print "\t" . $rule . "\n\n";
- }
- print "\n" . $rc_pp_rules;
- print &def($makefile_extra{'clangcl'}->{'end'});
- print "\nclean:\n".
- &splitline("\trm -f \$(BUILDDIR)*.obj \$(BUILDDIR)*.exe ".
- "\$(BUILDDIR)*.rcpp \$(BUILDDIR)*.res \$(BUILDDIR)*.map ".
- "\$(BUILDDIR)*.exe.manifest")."\n";
- select STDOUT; close OUT;
-}
-
-if (defined $makefiles{'cygwin'}) {
- $dirpfx = &dirpfx($makefiles{'cygwin'}, "/");
-
- ##-- MinGW/CygWin makefile (called 'cygwin' for historical reasons)
- open OUT, ">$makefiles{'cygwin'}"; select OUT;
- print
- "# Makefile for $project_name under MinGW, Cygwin, or Winelib.\n".
- "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
- "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
- # gcc command line option is -D not /D
- ($_ = $help) =~ s/([=" ])\/D/$1-D/gs;
- print $_;
- print
- "\n".
- "# You can define this path to point at your tools if you need to\n".
- "# TOOLPATH = c:\\cygwin\\bin\\ # or similar, if you're running Windows\n".
- "# TOOLPATH = /pkg/mingw32msvc/i386-mingw32msvc/bin/\n".
- "# TOOLPATH = i686-w64-mingw32-\n".
- "CC = \$(TOOLPATH)gcc\n".
- "RC = \$(TOOLPATH)windres\n".
- "# Uncomment the following two lines to compile under Winelib\n".
- "# CC = winegcc\n".
- "# RC = wrc\n".
- "# You may also need to tell windres where to find include files:\n".
- "# RCINC = --include-dir c:\\cygwin\\include\\\n".
- "\n".
- &splitline("CFLAGS = -Wall -O2 -std=gnu99 -Wvla -D_WINDOWS".
- " -DWIN32S_COMPAT -D_NO_OLDNAMES -D__USE_MINGW_ANSI_STDIO=1 " .
- (join " ", map {"-I$dirpfx$_"} @srcdirs)) .
- "\n".
- "LDFLAGS = -s\n".
- &splitline("RCFLAGS = \$(RCINC) --define WIN32=1 --define _WIN32=1 ".
- "--define WINVER=0x0400 ".(join " ", map {"-I$dirpfx$_"} @srcdirs))."\n".
- "\n".
- &def($makefile_extra{'cygwin'}->{'vars'}) .
- "\n".
- ".SUFFIXES:\n".
- "\n";
- print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
- print "\n\n";
- foreach $p (&prognames("G:C")) {
- ($prog, $type) = split ",", $p;
- $objstr = &objects($p, "X.o", "X.res.o", undef);
- print &splitline($prog . ".exe: " . $objstr), "\n";
- my $mw = $type eq "G" ? " -mwindows" : "";
- $libstr = &objects($p, undef, undef, "-lX");
- print &splitline("\t\$(CC)" . $mw . " \$(LDFLAGS) -o \$@ " .
- "-Wl,-Map,$prog.map " .
- $objstr . " $libstr", 69), "\n\n";
- }
- foreach $d (&deps("X.o", "X.res.o", $dirpfx, "/", "cygwin")) {
- if ($forceobj{$d->{obj_orig}}) {
- printf ("%s: FORCE\n", $d->{obj});
- } else {
- print &splitline(sprintf("%s: %s", $d->{obj},
- join " ", @{$d->{deps}})), "\n";
- }
- if ($d->{obj} =~ /\.res\.o$/) {
- print "\t\$(RC) \$(RCFL) \$(RCFLAGS) ".$d->{deps}->[0]." -o ".$d->{obj}."\n\n";
- } else {
- print "\t\$(CC) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) -c ".$d->{deps}->[0]."\n\n";
- }
- }
- print "\n";
- print &def($makefile_extra{'cygwin'}->{'end'});
- print "\nclean:\n".
- "\trm -f *.o *.exe *.res.o *.so *.map\n".
- "\n".
- "FORCE:\n";
- select STDOUT; close OUT;
-
-}
-
-if (defined $makefiles{'vc'}) {
- $dirpfx = &dirpfx($makefiles{'vc'}, "\\");
-
- ##-- Visual C++ makefile
- open OUT, ">$makefiles{'vc'}"; select OUT;
- print
- "# Makefile for $project_name under Visual C.\n".
- "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
- "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
- print $help;
- print
- "\n".
- "# If you rename this file to `Makefile', you should change this line,\n".
- "# so that the .rsp files still depend on the correct makefile.\n".
- "MAKEFILE = Makefile.vc\n".
- "\n".
- "# C compilation flags\n".
- "CFLAGS = /nologo /W3 /O1 " .
- (join " ", map {"-I$dirpfx$_"} @srcdirs) .
- " /D_WINDOWS /D_WIN32_WINDOWS=0x500 /DWINVER=0x500 /D_CRT_SECURE_NO_WARNINGS /D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE\n".
- "LFLAGS = /incremental:no /dynamicbase /nxcompat\n".
- "RCFLAGS = ".(join " ", map {"-I$dirpfx$_"} @srcdirs).
- " -DWIN32 -D_WIN32 -DWINVER=0x0400\n".
- "\n".
- &def($makefile_extra{'vc'}->{'vars'}) .
- "\n".
- "\n";
- print &splitline("all:" . join "", map { " \$(BUILDDIR)$_.exe" } &progrealnames("G:C"));
- print "\n\n";
- foreach $p (&prognames("G:C")) {
- ($prog, $type) = split ",", $p;
- $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", undef);
- print &splitline("\$(BUILDDIR)$prog.exe: " . $objstr), "\n";
-
- $objstr = &objects($p, "\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", "X.lib");
- $subsys = ($type eq "G") ? "windows" : "console";
- $inlinefilename = "link_$prog";
- print "\ttype <<$inlinefilename\n";
- @objlist = split " ", $objstr;
- @objlines = ("");
- foreach $i (@objlist) {
- if (length($objlines[$#objlines] . " $i") > 72) {
- push @objlines, "";
- }
- $objlines[$#objlines] .= " $i";
- }
- for ($i=0; $i<=$#objlines; $i++) {
- print "$objlines[$i]\n";
- }
- print "<<\n";
- print "\tlink \$(LFLAGS) \$(XLFLAGS) -out:\$(BUILDDIR)$prog.exe -map:\$(BUILDDIR)$prog.map -nologo -subsystem:$subsys\$(SUBSYSVER) \@$inlinefilename\n\n";
- }
- foreach $d (&deps("\$(BUILDDIR)X.obj", "\$(BUILDDIR)X.res", $dirpfx, "\\", "vc")) {
- $extradeps = $forceobj{$d->{obj_orig}} ? ["*.c","*.h","*.rc"] : [];
- print &splitline(sprintf("%s: %s", $d->{obj},
- join " ", @$extradeps, @{$d->{deps}})), "\n";
- if ($d->{obj} =~ /.res$/) {
- print "\trc /Fo@{[$d->{obj}]} \$(RCFL) -r \$(RCFLAGS) ".$d->{deps}->[0],"\n\n";
- }
- }
- print "\n";
- foreach $real_srcdir ("", @srcdirs) {
- $srcdir = $real_srcdir;
- if ($srcdir ne "") {
- $srcdir =~ s!/!\\!g;
- $srcdir = $dirpfx . $srcdir;
- $srcdir =~ s!\\\.\\!\\!;
- $srcdir = "{$srcdir}";
- }
- # The double colon at the end of the line makes this a
- # 'batch-mode inference rule', which means that nmake will
- # aggregate multiple invocations of the rule and issue just
- # one cl command with multiple source-file arguments. That
- # noticeably speeds up builds, since starting up the cl
- # process is a noticeable overhead and now has to be done far
- # fewer times.
- print "${srcdir}.c.obj::\n\tcl /Fo\$(BUILDDIR) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) /c \$<\n\n";
- }
- print &def($makefile_extra{'vc'}->{'end'});
- print "\nclean: tidy\n".
- "\t-del \$(BUILDDIR)*.exe\n\n".
- "tidy:\n".
- "\t-del \$(BUILDDIR)*.obj\n".
- "\t-del \$(BUILDDIR)*.res\n".
- "\t-del \$(BUILDDIR)*.pch\n".
- "\t-del \$(BUILDDIR)*.aps\n".
- "\t-del \$(BUILDDIR)*.ilk\n".
- "\t-del \$(BUILDDIR)*.pdb\n".
- "\t-del \$(BUILDDIR)*.rsp\n".
- "\t-del \$(BUILDDIR)*.dsp\n".
- "\t-del \$(BUILDDIR)*.dsw\n".
- "\t-del \$(BUILDDIR)*.ncb\n".
- "\t-del \$(BUILDDIR)*.opt\n".
- "\t-del \$(BUILDDIR)*.plg\n".
- "\t-del \$(BUILDDIR)*.map\n".
- "\t-del \$(BUILDDIR)*.idb\n".
- "\t-del \$(BUILDDIR)debug.log\n";
- select STDOUT; close OUT;
-}
-
-if (defined $makefiles{'vcproj'}) {
- $dirpfx = &dirpfx($makefiles{'vcproj'}, "\\");
-
- ##-- MSVC 6 Workspace and projects
- #
- # Note: All files created in this section are written in binary
- # mode, because although MSVC's command-line make can deal with
- # LF-only line endings, MSVC project files really _need_ to be
- # CRLF. Hence, in order for mkfiles.pl to generate usable project
- # files even when run from Unix, I make sure all files are binary
- # and explicitly write the CRLFs.
- #
- # Create directories if necessary
- mkdir $makefiles{'vcproj'}
- if(! -d $makefiles{'vcproj'});
- chdir $makefiles{'vcproj'};
- @deps = &deps("X.obj", "X.res", $dirpfx, "\\", "vcproj");
- %all_object_deps = map {$_->{obj} => $_->{deps}} @deps;
- # Create the project files
- # Get names of all Windows projects (GUI and console)
- my @prognames = &prognames("G:C");
- foreach $progname (@prognames) {
- create_vc_project(\%all_object_deps, $progname);
- }
- # Create the workspace file
- open OUT, ">$project_name.dsw"; binmode OUT; select OUT;
- print
- "Microsoft Developer Studio Workspace File, Format Version 6.00\r\n".
- "# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!\r\n".
- "\r\n".
- "###############################################################################\r\n".
- "\r\n";
- # List projects
- foreach $progname (@prognames) {
- ($windows_project, $type) = split ",", $progname;
- print "Project: \"$windows_project\"=\".\\$windows_project\\$windows_project.dsp\" - Package Owner=<4>\r\n";
- }
- print
- "\r\n".
- "Package=<5>\r\n".
- "{{{\r\n".
- "}}}\r\n".
- "\r\n".
- "Package=<4>\r\n".
- "{{{\r\n".
- "}}}\r\n".
- "\r\n".
- "###############################################################################\r\n".
- "\r\n".
- "Global:\r\n".
- "\r\n".
- "Package=<5>\r\n".
- "{{{\r\n".
- "}}}\r\n".
- "\r\n".
- "Package=<3>\r\n".
- "{{{\r\n".
- "}}}\r\n".
- "\r\n".
- "###############################################################################\r\n".
- "\r\n";
- select STDOUT; close OUT;
- chdir $orig_dir;
-
- sub create_vc_project {
- my ($all_object_deps, $progname) = @_;
- # Construct program's dependency info
- %seen_objects = ();
- %lib_files = ();
- %source_files = ();
- %header_files = ();
- %resource_files = ();
- @object_files = split " ", &objects($progname, "X.obj", "X.res", "X.lib");
- foreach $object_file (@object_files) {
- next if defined $seen_objects{$object_file};
- $seen_objects{$object_file} = 1;
- if($object_file =~ /\.lib$/io) {
- $lib_files{$object_file} = 1;
- next;
- }
- $object_deps = $all_object_deps{$object_file};
- foreach $object_dep (@$object_deps) {
- if($object_dep =~ /\.c$/io) {
- $source_files{$object_dep} = 1;
- next;
- }
- if($object_dep =~ /\.h$/io) {
- $header_files{$object_dep} = 1;
- next;
- }
- if($object_dep =~ /\.(rc|ico)$/io) {
- $resource_files{$object_dep} = 1;
- next;
- }
- }
- }
- $libs = join " ", sort keys %lib_files;
- @source_files = sort keys %source_files;
- @header_files = sort keys %header_files;
- @resources = sort keys %resource_files;
- ($windows_project, $type) = split ",", $progname;
- mkdir $windows_project
- if(! -d $windows_project);
- chdir $windows_project;
- $subsys = ($type eq "G") ? "windows" : "console";
- open OUT, ">$windows_project.dsp"; binmode OUT; select OUT;
- print
- "# Microsoft Developer Studio Project File - Name=\"$windows_project\" - Package Owner=<4>\r\n".
- "# Microsoft Developer Studio Generated Build File, Format Version 6.00\r\n".
- "# ** DO NOT EDIT **\r\n".
- "\r\n".
- "# TARGTYPE \"Win32 (x86) Application\" 0x0101\r\n".
- "\r\n".
- "CFG=$windows_project - Win32 Debug\r\n".
- "!MESSAGE This is not a valid makefile. To build this project using NMAKE,\r\n".
- "!MESSAGE use the Export Makefile command and run\r\n".
- "!MESSAGE \r\n".
- "!MESSAGE NMAKE /f \"$windows_project.mak\".\r\n".
- "!MESSAGE \r\n".
- "!MESSAGE You can specify a configuration when running NMAKE\r\n".
- "!MESSAGE by defining the macro CFG on the command line. For example:\r\n".
- "!MESSAGE \r\n".
- "!MESSAGE NMAKE /f \"$windows_project.mak\" CFG=\"$windows_project - Win32 Debug\"\r\n".
- "!MESSAGE \r\n".
- "!MESSAGE Possible choices for configuration are:\r\n".
- "!MESSAGE \r\n".
- "!MESSAGE \"$windows_project - Win32 Release\" (based on \"Win32 (x86) Application\")\r\n".
- "!MESSAGE \"$windows_project - Win32 Debug\" (based on \"Win32 (x86) Application\")\r\n".
- "!MESSAGE \r\n".
- "\r\n".
- "# Begin Project\r\n".
- "# PROP AllowPerConfigDependencies 0\r\n".
- "# PROP Scc_ProjName \"\"\r\n".
- "# PROP Scc_LocalPath \"\"\r\n".
- "CPP=cl.exe\r\n".
- "MTL=midl.exe\r\n".
- "RSC=rc.exe\r\n".
- "\r\n".
- "!IF \"\$(CFG)\" == \"$windows_project - Win32 Release\"\r\n".
- "\r\n".
- "# PROP BASE Use_MFC 0\r\n".
- "# PROP BASE Use_Debug_Libraries 0\r\n".
- "# PROP BASE Output_Dir \"Release\"\r\n".
- "# PROP BASE Intermediate_Dir \"Release\"\r\n".
- "# PROP BASE Target_Dir \"\"\r\n".
- "# PROP Use_MFC 0\r\n".
- "# PROP Use_Debug_Libraries 0\r\n".
- "# PROP Output_Dir \"Release\"\r\n".
- "# PROP Intermediate_Dir \"Release\"\r\n".
- "# PROP Ignore_Export_Lib 0\r\n".
- "# PROP Target_Dir \"\"\r\n".
- "# ADD BASE CPP /nologo /W3 /GX /O2 ".
- (join " ", map {"/I \"..\\..\\$dirpfx$_\""} @srcdirs) .
- " /D \"WIN32\" /D \"NDEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /c\r\n".
- "# ADD CPP /nologo /W3 /GX /O2 ".
- (join " ", map {"/I \"..\\..\\$dirpfx$_\""} @srcdirs) .
- " /D \"WIN32\" /D \"NDEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /c\r\n".
- "# ADD BASE MTL /nologo /D \"NDEBUG\" /mktyplib203 /win32\r\n".
- "# ADD MTL /nologo /D \"NDEBUG\" /mktyplib203 /win32\r\n".
- "# ADD BASE RSC /l 0x809 /d \"NDEBUG\"\r\n".
- "# ADD RSC /l 0x809 /d \"NDEBUG\"\r\n".
- "BSC32=bscmake.exe\r\n".
- "# ADD BASE BSC32 /nologo\r\n".
- "# ADD BSC32 /nologo\r\n".
- "LINK32=link.exe\r\n".
- "# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:$subsys /machine:I386\r\n".
- "# ADD LINK32 $libs /nologo /subsystem:$subsys /machine:I386\r\n".
- "# SUBTRACT LINK32 /pdb:none\r\n".
- "\r\n".
- "!ELSEIF \"\$(CFG)\" == \"$windows_project - Win32 Debug\"\r\n".
- "\r\n".
- "# PROP BASE Use_MFC 0\r\n".
- "# PROP BASE Use_Debug_Libraries 1\r\n".
- "# PROP BASE Output_Dir \"Debug\"\r\n".
- "# PROP BASE Intermediate_Dir \"Debug\"\r\n".
- "# PROP BASE Target_Dir \"\"\r\n".
- "# PROP Use_MFC 0\r\n".
- "# PROP Use_Debug_Libraries 1\r\n".
- "# PROP Output_Dir \"Debug\"\r\n".
- "# PROP Intermediate_Dir \"Debug\"\r\n".
- "# PROP Ignore_Export_Lib 0\r\n".
- "# PROP Target_Dir \"\"\r\n".
- "# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od ".
- (join " ", map {"/I \"..\\..\\$dirpfx$_\""} @srcdirs) .
- " /D \"WIN32\" /D \"_DEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /GZ /c\r\n".
- "# ADD CPP /nologo /W3 /Gm /GX /ZI /Od ".
- (join " ", map {"/I \"..\\..\\$dirpfx$_\""} @srcdirs) .
- " /D \"WIN32\" /D \"_DEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /GZ /c\r\n".
- "# ADD BASE MTL /nologo /D \"_DEBUG\" /mktyplib203 /win32\r\n".
- "# ADD MTL /nologo /D \"_DEBUG\" /mktyplib203 /win32\r\n".
- "# ADD BASE RSC /l 0x809 /d \"_DEBUG\"\r\n".
- "# ADD RSC /l 0x809 /d \"_DEBUG\"\r\n".
- "BSC32=bscmake.exe\r\n".
- "# ADD BASE BSC32 /nologo\r\n".
- "# ADD BSC32 /nologo\r\n".
- "LINK32=link.exe\r\n".
- "# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:$subsys /debug /machine:I386 /pdbtype:sept\r\n".
- "# ADD LINK32 $libs /nologo /subsystem:$subsys /debug /machine:I386 /pdbtype:sept\r\n".
- "# SUBTRACT LINK32 /pdb:none\r\n".
- "\r\n".
- "!ENDIF \r\n".
- "\r\n".
- "# Begin Target\r\n".
- "\r\n".
- "# Name \"$windows_project - Win32 Release\"\r\n".
- "# Name \"$windows_project - Win32 Debug\"\r\n".
- "# Begin Group \"Source Files\"\r\n".
- "\r\n".
- "# PROP Default_Filter \"cpp;c;cxx;rc;def;r;odl;idl;hpj;bat\"\r\n";
- foreach $source_file (@source_files) {
- print
- "# Begin Source File\r\n".
- "\r\n".
- "SOURCE=..\\..\\$source_file\r\n";
- if($source_file =~ /ssh\.c/io) {
- # Disable 'Edit and continue' as Visual Studio can't handle the macros
- print
- "\r\n".
- "!IF \"\$(CFG)\" == \"$windows_project - Win32 Release\"\r\n".
- "\r\n".
- "!ELSEIF \"\$(CFG)\" == \"$windows_project - Win32 Debug\"\r\n".
- "\r\n".
- "# ADD CPP /Zi\r\n".
- "\r\n".
- "!ENDIF \r\n".
- "\r\n";
- }
- print "# End Source File\r\n";
- }
- print
- "# End Group\r\n".
- "# Begin Group \"Header Files\"\r\n".
- "\r\n".
- "# PROP Default_Filter \"h;hpp;hxx;hm;inl\"\r\n";
- foreach $header_file (@header_files) {
- print
- "# Begin Source File\r\n".
- "\r\n".
- "SOURCE=..\\..\\$header_file\r\n".
- "# End Source File\r\n";
- }
- print
- "# End Group\r\n".
- "# Begin Group \"Resource Files\"\r\n".
- "\r\n".
- "# PROP Default_Filter \"ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe\"\r\n";
- foreach $resource_file (@resources) {
- print
- "# Begin Source File\r\n".
- "\r\n".
- "SOURCE=..\\..\\$resource_file\r\n".
- "# End Source File\r\n";
- }
- print
- "# End Group\r\n".
- "# End Target\r\n".
- "# End Project\r\n";
- select STDOUT; close OUT;
- chdir "..";
- }
-}
-
-if (defined $makefiles{'vstudio10'} || defined $makefiles{'vstudio12'}) {
-
- ##-- Visual Studio 2010+ Solution and Projects
-
- if (defined $makefiles{'vstudio10'}) {
- create_vs_solution('vstudio10', "2010", "11.00", "v100");
- }
-
- if (defined $makefiles{'vstudio12'}) {
- create_vs_solution('vstudio12', "2012", "12.00", "v110");
- }
-
- sub create_vs_solution {
- my ($makefilename, $name, $version, $toolsver) = @_;
-
- $dirpfx = &dirpfx($makefiles{$makefilename}, "\\");
-
- @deps = &deps("X.obj", "X.res", $dirpfx, "\\", $makefilename);
- %all_object_deps = map {$_->{obj} => $_->{deps}} @deps;
-
- my @prognames = &prognames("G:C");
-
- # Create the solution file.
- mkdir $makefiles{$makefilename}
- if(! -f $makefiles{$makefilename});
- chdir $makefiles{$makefilename};
-
- open OUT, ">$project_name.sln"; select OUT;
-
- print
- "Microsoft Visual Studio Solution File, Format Version $version\n" .
- "# Visual Studio $name\n";
-
- my %projguids = ();
- foreach $progname (@prognames) {
- ($windows_project, $type) = split ",", $progname;
-
- $projguids{$windows_project} = $guid =
- &invent_guid("project:$progname");
-
- print
- "Project(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"$windows_project\", \"$windows_project\\$windows_project.vcxproj\", \"{$guid}\"\n" .
- "EndProject\n";
- }
-
- print
- "Global\n" .
- " GlobalSection(SolutionConfigurationPlatforms) = preSolution\n" .
- " Debug|Win32 = Debug|Win32\n" .
- " Release|Win32 = Release|Win32\n" .
- " EndGlobalSection\n" .
- " GlobalSection(ProjectConfigurationPlatforms) = postSolution\n" ;
-
- foreach my $projguid (values %projguids) {
- print
- " {$projguid}.Debug|Win32.ActiveCfg = Debug|Win32\n" .
- " {$projguid}.Debug|Win32.Build.0 = Debug|Win32\n" .
- " {$projguid}.Release|Win32.ActiveCfg = Release|Win32\n" .
- " {$projguid}.Release|Win32.Build.0 = Release|Win32\n";
- }
-
- print
- " EndGlobalSection\n" .
- " GlobalSection(SolutionProperties) = preSolution\n" .
- " HideSolutionNode = FALSE\n" .
- " EndGlobalSection\n" .
- "EndGlobal\n";
-
- select STDOUT; close OUT;
-
- foreach $progname (@prognames) {
- ($windows_project, $type) = split ",", $progname;
- create_vs_project(\%all_object_deps, $windows_project, $type, $projguids{$windows_project}, $toolsver);
- }
-
- chdir $orig_dir;
- }
-
- sub create_vs_project {
- my ($all_object_deps, $windows_project, $type, $projguid, $toolsver) = @_;
-
- # Break down the project's dependency information into the appropriate
- # groups.
- %seen_objects = ();
- %lib_files = ();
- %source_files = ();
- %header_files = ();
- %resource_files = ();
- %icon_files = ();
-
- @object_files = split " ", &objects($progname, "X.obj", "X.res", "X.lib");
- foreach $object_file (@object_files) {
- next if defined $seen_objects{$object_file};
- $seen_objects{$object_file} = 1;
-
- if($object_file =~ /\.lib$/io) {
- $lib_files{$object_file} = 1;
- next;
- }
-
- $object_deps = $all_object_deps{$object_file};
- foreach $object_dep (@$object_deps) {
- if($object_dep eq $object_deps->[0]) {
- if($object_dep =~ /\.c$/io) {
- $source_files{$object_dep} = 1;
- } elsif($object_dep =~ /\.rc$/io) {
- $resource_files{$object_dep} = 1;
- }
- } elsif ($object_dep =~ /\.[ch]$/io) {
- $header_files{$object_dep} = 1;
- } elsif ($object_dep =~ /\.ico$/io) {
- $icon_files{$object_dep} = 1;
- }
- }
- }
-
- $libs = join ";", sort keys %lib_files;
- @source_files = sort keys %source_files;
- @header_files = sort keys %header_files;
- @resources = sort keys %resource_files;
- @icons = sort keys %icon_files;
- $subsystem = ($type eq "G") ? "Windows" : "Console";
-
- mkdir $windows_project
- if(! -d $windows_project);
- chdir $windows_project;
- open OUT, ">$windows_project.vcxproj"; select OUT;
- open FILTERS, ">$windows_project.vcxproj.filters";
-
- # The bulk of the project file is just boilerplate stuff, so we
- # can mostly just dump it out here. Note, buried in the ClCompile
- # item definition, that we use a debug information format of
- # ProgramDatabase, which disables the edit-and-continue support
- # that breaks most of the project builds.
- print
- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" .
- "<Project DefaultTargets=\"Build\" ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n" .
- " <ItemGroup Label=\"ProjectConfigurations\">\n" .
- " <ProjectConfiguration Include=\"Debug|Win32\">\n" .
- " <Configuration>Debug</Configuration>\n" .
- " <Platform>Win32</Platform>\n" .
- " </ProjectConfiguration>\n" .
- " <ProjectConfiguration Include=\"Release|Win32\">\n" .
- " <Configuration>Release</Configuration>\n" .
- " <Platform>Win32</Platform>\n" .
- " </ProjectConfiguration>\n" .
- " </ItemGroup>\n" .
- " <PropertyGroup Label=\"Globals\">\n" .
- " <SccProjectName />\n" .
- " <SccLocalPath />\n" .
- " <ProjectGuid>{$projguid}</ProjectGuid>\n" .
- " </PropertyGroup>\n" .
- " <Import Project=\"\$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n" .
- " <PropertyGroup Condition=\"'\$(Configuration)|\$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\n" .
- " <ConfigurationType>Application</ConfigurationType>\n" .
- " <UseOfMfc>false</UseOfMfc>\n" .
- " <CharacterSet>MultiByte</CharacterSet>\n" .
- " <PlatformToolset>$toolsver</PlatformToolset>\n" .
- " </PropertyGroup>\n" .
- " <PropertyGroup Condition=\"'\$(Configuration)|\$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\n" .
- " <ConfigurationType>Application</ConfigurationType>\n" .
- " <UseOfMfc>false</UseOfMfc>\n" .
- " <CharacterSet>MultiByte</CharacterSet>\n" .
- " <PlatformToolset>$toolsver</PlatformToolset>\n" .
- " </PropertyGroup>\n" .
- " <Import Project=\"\$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n" .
- " <ImportGroup Label=\"ExtensionTargets\">\n" .
- " </ImportGroup>\n" .
- " <ImportGroup Condition=\"'\$(Configuration)|\$(Platform)'=='Debug|Win32'\" Label=\"PropertySheets\">\n" .
- " <Import Project=\"\$(UserRootDir)\\Microsoft.Cpp.\$(Platform).user.props\" Condition=\"exists('\$(UserRootDir)\\Microsoft.Cpp.\$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n" .
- " </ImportGroup>\n" .
- " <ImportGroup Condition=\"'\$(Configuration)|\$(Platform)'=='Release|Win32'\" Label=\"PropertySheets\">\n" .
- " <Import Project=\"\$(UserRootDir)\\Microsoft.Cpp.\$(Platform).user.props\" Condition=\"exists('\$(UserRootDir)\\Microsoft.Cpp.\$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n" .
- " </ImportGroup>\n" .
- " <PropertyGroup Label=\"UserMacros\" />\n" .
- " <PropertyGroup Condition=\"'\$(Configuration)|\$(Platform)'=='Release|Win32'\">\n" .
- " <OutDir>.\\Release\\</OutDir>\n" .
- " <IntDir>.\\Release\\</IntDir>\n" .
- " <LinkIncremental>false</LinkIncremental>\n" .
- " </PropertyGroup>\n" .
- " <PropertyGroup Condition=\"'\$(Configuration)|\$(Platform)'=='Debug|Win32'\">\n" .
- " <OutDir>.\\Debug\\</OutDir>\n" .
- " <IntDir>.\\Debug\\</IntDir>\n" .
- " <LinkIncremental>true</LinkIncremental>\n" .
- " </PropertyGroup>\n" .
- " <ItemDefinitionGroup Condition=\"'\$(Configuration)|\$(Platform)'=='Release|Win32'\">\n" .
- " <ClCompile>\n" .
- " <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n" .
- " <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>\n" .
- " <StringPooling>true</StringPooling>\n" .
- " <FunctionLevelLinking>true</FunctionLevelLinking>\n" .
- " <Optimization>MaxSpeed</Optimization>\n" .
- " <SuppressStartupBanner>true</SuppressStartupBanner>\n" .
- " <WarningLevel>Level3</WarningLevel>\n" .
- " <AdditionalIncludeDirectories>" . (join ";", map {"..\\..\\$dirpfx$_"} @srcdirs) . ";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n" .
- " <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;POSIX;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n" .
- " <AssemblerListingLocation>.\\Release\\</AssemblerListingLocation>\n" .
- " <PrecompiledHeaderOutputFile>.\\Release\\$windows_project.pch</PrecompiledHeaderOutputFile>\n" .
- " <ObjectFileName>.\\Release\\</ObjectFileName>\n" .
- " <ProgramDataBaseFileName>.\\Release\\</ProgramDataBaseFileName>\n" .
- " </ClCompile>\n" .
- " <Midl>\n" .
- " <SuppressStartupBanner>true</SuppressStartupBanner>\n" .
- " <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n" .
- " <TypeLibraryName>.\\Release\\$windows_project.tlb</TypeLibraryName>\n" .
- " <MkTypLibCompatible>true</MkTypLibCompatible>\n" .
- " <TargetEnvironment>Win32</TargetEnvironment>\n" .
- " </Midl>\n" .
- " <ResourceCompile>\n" .
- " <Culture>0x0809</Culture>\n" .
- " <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n" .
- " </ResourceCompile>\n" .
- " <Bscmake>\n" .
- " <SuppressStartupBanner>true</SuppressStartupBanner>\n" .
- " <OutputFile>.\\Release\\$windows_project.bsc</OutputFile>\n" .
- " </Bscmake>\n" .
- " <Link>\n" .
- " <SuppressStartupBanner>true</SuppressStartupBanner>\n" .
- " <SubSystem>$subsystem</SubSystem>\n" .
- " <OutputFile>.\\Release\\$windows_project.exe</OutputFile>\n" .
- " <AdditionalDependencies>$libs;%(AdditionalDependencies)</AdditionalDependencies>\n" .
- " </Link>\n" .
- " </ItemDefinitionGroup>\n" .
- " <ItemDefinitionGroup Condition=\"'\$(Configuration)|\$(Platform)'=='Debug|Win32'\">\n" .
- " <ClCompile>\n" .
- " <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n" .
- " <InlineFunctionExpansion>Default</InlineFunctionExpansion>\n" .
- " <FunctionLevelLinking>false</FunctionLevelLinking>\n" .
- " <Optimization>Disabled</Optimization>\n" .
- " <SuppressStartupBanner>true</SuppressStartupBanner>\n" .
- " <WarningLevel>Level3</WarningLevel>\n" .
- " <MinimalRebuild>true</MinimalRebuild>\n" .
- " <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\n" .
- " <AdditionalIncludeDirectories>" . (join ";", map {"..\\..\\$dirpfx$_"} @srcdirs) . ";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n" .
- " <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;POSIX;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n" .
- " <AssemblerListingLocation>.\\Debug\\</AssemblerListingLocation>\n" .
- " <PrecompiledHeaderOutputFile>.\\Debug\\$windows_project.pch</PrecompiledHeaderOutputFile>\n" .
- " <ObjectFileName>.\\Debug\\</ObjectFileName>\n" .
- " <ProgramDataBaseFileName>.\\Debug\\</ProgramDataBaseFileName>\n" .
- " <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\n" .
- " </ClCompile>\n" .
- " <Midl>\n" .
- " <SuppressStartupBanner>true</SuppressStartupBanner>\n" .
- " <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n" .
- " <TypeLibraryName>.\\Debug\\$windows_project.tlb</TypeLibraryName>\n" .
- " <MkTypLibCompatible>true</MkTypLibCompatible>\n" .
- " <TargetEnvironment>Win32</TargetEnvironment>\n" .
- " </Midl>\n" .
- " <ResourceCompile>\n" .
- " <Culture>0x0809</Culture>\n" .
- " <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n" .
- " </ResourceCompile>\n" .
- " <Bscmake>\n" .
- " <SuppressStartupBanner>true</SuppressStartupBanner>\n" .
- " <OutputFile>.\\Debug\\$windows_project.bsc</OutputFile>\n" .
- " </Bscmake>\n" .
- " <Link>\n" .
- " <SuppressStartupBanner>true</SuppressStartupBanner>\n" .
- " <GenerateDebugInformation>true</GenerateDebugInformation>\n" .
- " <SubSystem>$subsystem</SubSystem>\n" .
- " <OutputFile>\$(TargetPath)</OutputFile>\n" .
- " <AdditionalDependencies>$libs;%(AdditionalDependencies)</AdditionalDependencies>\n" .
- " </Link>\n" .
- " </ItemDefinitionGroup>\n";
-
- # The VC++ projects don't have physical structure to them, instead
- # the files are organized by logical "filters" that are stored in
- # a separate file, so different users can organize things differently.
- # The filters file contains a copy of the ItemGroup elements from
- # the main project file that list the included items, but tack
- # on a filter name where needed.
- print FILTERS
- "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" .
- "<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n";
-
- print " <ItemGroup>\n";
- print FILTERS " <ItemGroup>\n";
- foreach $icon_file (@icons) {
- $icon_file =~ s/..\\windows\\//;
- print " <CustomBuild Include=\"..\\..\\$icon_file\" />\n";
- print FILTERS
- " <CustomBuild Include=\"..\\..\\$icon_file\">\n" .
- " <Filter>Resource Files</Filter>\n" .
- " </CustomBuild>\n";
- }
- print FILTERS " </ItemGroup>\n";
- print " </ItemGroup>\n";
-
- print " <ItemGroup>\n";
- print FILTERS " <ItemGroup>\n";
- foreach $resource_file (@resources) {
- $resource_file =~ s/..\\windows\\//;
- print
- " <ResourceCompile Include=\"..\\..\\$resource_file\">\n" .
- " <AdditionalIncludeDirectories Condition=\"'\$(Configuration)|\$(Platform)'=='Release|Win32'\">..\\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n" .
- " <AdditionalIncludeDirectories Condition=\"'\$(Configuration)|\$(Platform)'=='Debug|Win32'\">..\\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n" .
- " </ResourceCompile>\n";
- print FILTERS
- " <ResourceCompile Include=\"..\\..\\$resource_file\">\n" .
- " <Filter>Resource Files</Filter>\n" .
- " </ResourceCompile>\n";
- }
- print FILTERS " </ItemGroup>\n";
- print " </ItemGroup>\n";
-
- print " <ItemGroup>\n";
- print FILTERS " <ItemGroup>\n";
- foreach $source_file (@source_files) {
- $source_file =~ s/..\\windows\\//;
- print " <ClCompile Include=\"..\\..\\$source_file\" />\n";
- print FILTERS
- " <ClCompile Include=\"..\\..\\$source_file\">\n" .
- " <Filter>Source Files</Filter>\n" .
- " </ClCompile>";
- }
- print FILTERS " </ItemGroup>\n";
- print " </ItemGroup>\n";
-
- print " <ItemGroup>\n";
- print FILTERS " <ItemGroup>\n";
- foreach $header_file (@header_files) {
- $header_file =~ s/..\\windows\\//;
- print " <ClInclude Include=\"..\\..\\$header_file\" />\n";
- print FILTERS
- " <ClInclude Include=\"..\\..\\$header_file\">\n" .
- " <Filter>Header Files</Filter>\n" .
- " </ClInclude>";
- }
- print FILTERS " </ItemGroup>\n";
- print " </ItemGroup>\n";
-
- print
- " <Import Project=\"\$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n" .
- "</Project>";
-
- print FILTERS
- " <ItemGroup>\n" .
- " <Filter Include=\"Source Files\">\n" .
- " <UniqueIdentifier>{" . &invent_guid("sources:$windows_project") . "}</UniqueIdentifier>\n" .
- " </Filter>\n" .
- " <Filter Include=\"Header Files\">\n" .
- " <UniqueIdentifier>{" . &invent_guid("headers:$windows_project") . "}</UniqueIdentifier>\n" .
- " </Filter>\n" .
- " <Filter Include=\"Resource Files\">\n" .
- " <UniqueIdentifier>{" . &invent_guid("resources:$windows_project") . "}</UniqueIdentifier>\n" .
- " </Filter>\n" .
- " </ItemGroup>\n" .
- "</Project>";
-
- select STDOUT; close OUT; close FILTERS;
- chdir "..";
- }
-}
-
-if (defined $makefiles{'gtk'}) {
- $dirpfx = &dirpfx($makefiles{'gtk'}, "/");
-
- ##-- X/GTK/Unix makefile
- open OUT, ">$makefiles{'gtk'}"; select OUT;
- print
- "# Makefile for $project_name under X/GTK and Unix.\n".
- "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
- "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
- # gcc command line option is -D not /D
- ($_ = $help) =~ s/([=" ])\/D/$1-D/gs;
- print $_;
- print
- "\n".
- "# You can define this path to point at your tools if you need to\n".
- "# TOOLPATH = /opt/gcc/bin\n".
- "CC = \$(TOOLPATH)cc\n".
- "# If necessary set the path to krb5-config here\n".
- "KRB5CONFIG=krb5-config\n".
- "# You can manually set this to `gtk-config' or `pkg-config gtk+-1.2'\n".
- "# (depending on what works on your system) if you want to enforce\n".
- "# building with GTK 1.2, or you can set it to `pkg-config gtk+-2.0 x11'\n".
- "# if you want to enforce 2.0. The default is to try 2.0 and fall back\n".
- "# to 1.2 if it isn't found.\n".
- "GTK_CONFIG = sh -c 'pkg-config gtk+-3.0 x11 \$\$0 2>/dev/null || pkg-config gtk+-2.0 x11 \$\$0 2>/dev/null || gtk-config \$\$0'\n".
- "\n".
- "-include Makefile.local\n".
- "\n".
- "unexport CFLAGS # work around a weird issue with krb5-config\n".
- "\n".
- &splitline("CFLAGS = -O2 -Wall -std=gnu99 -Wvla -g " .
- (join " ", map {"-I$dirpfx$_"} @srcdirs) .
- " \$(shell \$(GTK_CONFIG) --cflags)").
- " -D _FILE_OFFSET_BITS=64\n".
- "XLDFLAGS = \$(LDFLAGS) \$(shell \$(GTK_CONFIG) --libs)\n".
- "ULDFLAGS = \$(LDFLAGS)\n".
- "ifeq (,\$(findstring NO_GSSAPI,\$(COMPAT)))\n".
- "ifeq (,\$(findstring STATIC_GSSAPI,\$(COMPAT)))\n".
- "XLDFLAGS+= -ldl\n".
- "ULDFLAGS+= -ldl\n".
- "else\n".
- "CFLAGS+= -DNO_LIBDL \$(shell \$(KRB5CONFIG) --cflags gssapi)\n".
- "XLDFLAGS+= \$(shell \$(KRB5CONFIG) --libs gssapi)\n".
- "ULDFLAGS+= \$(shell \$(KRB5CONFIG) --libs gssapi)\n".
- "endif\n".
- "endif\n".
- "INSTALL=install\n".
- "INSTALL_PROGRAM=\$(INSTALL)\n".
- "INSTALL_DATA=\$(INSTALL)\n".
- "prefix=/usr/local\n".
- "exec_prefix=\$(prefix)\n".
- "bindir=\$(exec_prefix)/bin\n".
- "mandir=\$(prefix)/man\n".
- "man1dir=\$(mandir)/man1\n".
- "\n".
- &def($makefile_extra{'gtk'}->{'vars'}) .
- "\n".
- ".SUFFIXES:\n".
- "\n".
- "\n";
- print &splitline("all:" . join "", map { " $_" }
- &progrealnames("X:XT:U:UT"));
- print "\n\n";
- foreach $p (&prognames("X:XT:U:UT")) {
- ($prog, $type) = split ",", $p;
- ($ldflags = $type) =~ s/T$//;
- $objstr = &objects($p, "X.o", undef, undef);
- print &splitline($prog . ": " . $objstr), "\n";
- $libstr = &objects($p, undef, undef, "-lX");
- print &splitline("\t\$(CC) -o \$@ " .
- $objstr . " \$(${ldflags}LDFLAGS) $libstr", 69), "\n\n";
- }
- foreach $d (&deps("X.o", undef, $dirpfx, "/", "gtk")) {
- if ($forceobj{$d->{obj_orig}}) {
- printf("%s: FORCE\n", $d->{obj});
- } else {
- print &splitline(sprintf("%s: %s", $d->{obj},
- join " ", @{$d->{deps}})), "\n";
- }
- print &splitline("\t\$(CC) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) -c $d->{deps}->[0]\n");
- }
- print "\n";
- print &def($makefile_extra{'gtk'}->{'end'});
- print "\nclean:\n".
- "\trm -f *.o". (join "", map { " $_" } &progrealnames("X:XT:U:UT")) . "\n";
- print "\nFORCE:\n";
- select STDOUT; close OUT;
-}
-
-if (defined $makefiles{'unix'}) {
- $dirpfx = &dirpfx($makefiles{'unix'}, "/");
-
- ##-- GTK-free pure-Unix makefile for non-GUI apps only
- open OUT, ">$makefiles{'unix'}"; select OUT;
- print
- "# Makefile for $project_name under Unix.\n".
- "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
- "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
- # gcc command line option is -D not /D
- ($_ = $help) =~ s/([=" ])\/D/$1-D/gs;
- print $_;
- print
- "\n".
- "# You can define this path to point at your tools if you need to\n".
- "# TOOLPATH = /opt/gcc/bin\n".
- "CC = \$(TOOLPATH)cc\n".
- "\n".
- "-include Makefile.local\n".
- "\n".
- "unexport CFLAGS # work around a weird issue with krb5-config\n".
- "\n".
- &splitline("CFLAGS = -O2 -Wall -std=gnu99 -Wvla -g " .
- (join " ", map {"-I$dirpfx$_"} @srcdirs)).
- " -D _FILE_OFFSET_BITS=64\n".
- "ULDFLAGS = \$(LDFLAGS)\n".
- "INSTALL=install\n".
- "INSTALL_PROGRAM=\$(INSTALL)\n".
- "INSTALL_DATA=\$(INSTALL)\n".
- "prefix=/usr/local\n".
- "exec_prefix=\$(prefix)\n".
- "bindir=\$(exec_prefix)/bin\n".
- "mandir=\$(prefix)/man\n".
- "man1dir=\$(mandir)/man1\n".
- "\n".
- &def($makefile_extra{'unix'}->{'vars'}) .
- "\n".
- ".SUFFIXES:\n".
- "\n".
- "\n";
- print &splitline("all:" . join "", map { " $_" } &progrealnames("U:UT"));
- print "\n\n";
- foreach $p (&prognames("U:UT")) {
- ($prog, $type) = split ",", $p;
- ($ldflags = $type) =~ s/T$//;
- $objstr = &objects($p, "X.o", undef, undef);
- print &splitline($prog . ": " . $objstr), "\n";
- $libstr = &objects($p, undef, undef, "-lX");
- print &splitline("\t\$(CC) -o \$@ " .
- $objstr . " \$(${ldflags}LDFLAGS) $libstr", 69), "\n\n";
- }
- foreach $d (&deps("X.o", undef, $dirpfx, "/", "unix")) {
- if ($forceobj{$d->{obj_orig}}) {
- printf("%s: FORCE\n", $d->{obj});
- } else {
- print &splitline(sprintf("%s: %s", $d->{obj},
- join " ", @{$d->{deps}})), "\n";
- }
- print &splitline("\t\$(CC) \$(COMPAT) \$(CFLAGS) \$(XFLAGS) -c $d->{deps}->[0]\n");
- }
- print "\n";
- print &def($makefile_extra{'unix'}->{'end'});
- print "\nclean:\n".
- "\trm -f *.o". (join "", map { " $_" } &progrealnames("U:UT")) . "\n";
- print "\nFORCE:\n";
- select STDOUT; close OUT;
-}
-
-if (defined $makefiles{'am'}) {
- die "Makefile.am in a subdirectory is not supported\n"
- if &dirpfx($makefiles{'am'}, "/") ne "";
-
- ##-- Unix/autoconf Makefile.am
- open OUT, ">$makefiles{'am'}"; select OUT;
- print
- "# Makefile.am for $project_name under Unix with Autoconf/Automake.\n".
- "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
- "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n\n";
-
- # 2014-02-22: as of automake-1.14 we begin to get complained at if
- # we don't use this option
- print "AUTOMAKE_OPTIONS = subdir-objects\n\n";
-
- # Complete list of source and header files. Not used by the
- # auto-generated parts of this makefile, but Recipe might like to
- # have it available as a variable so that mandatory-rebuild things
- # (version.o) can conveniently be made to depend on it.
- @sources = ("allsources", "=",
- sort grep {$_ ne "empty.h"} keys %allsourcefiles);
- print &splitline(join " ", @sources), "\n\n";
-
- @cliprogs = ("bin_PROGRAMS", "=");
- foreach $p (&prognames("U")) {
- ($prog, $type) = split ",", $p;
- push @cliprogs, $prog;
- }
- @allprogs = @cliprogs;
- foreach $p (&prognames("X")) {
- ($prog, $type) = split ",", $p;
- push @allprogs, $prog;
- }
- print "if HAVE_GTK\n";
- print &splitline(join " ", @allprogs), "\n";
- print "else\n";
- print &splitline(join " ", @cliprogs), "\n";
- print "endif\n\n";
-
- @noinstcliprogs = ("noinst_PROGRAMS", "=");
- foreach $p (&prognames("UT")) {
- ($prog, $type) = split ",", $p;
- push @noinstcliprogs, $prog;
- }
- @noinstallprogs = @noinstcliprogs;
- foreach $p (&prognames("XT")) {
- ($prog, $type) = split ",", $p;
- push @noinstallprogs, $prog;
- }
- print "if HAVE_GTK\n";
- print &splitline(join " ", @noinstallprogs), "\n";
- print "else\n";
- print &splitline(join " ", @noinstcliprogs), "\n";
- print "endif\n\n";
-
- %objtosrc = ();
- foreach $d (&deps("X", undef, "", "/", "am")) {
- $objtosrc{$d->{obj}} = $d->{deps}->[0];
- }
-
- print &splitline(join " ", "AM_CPPFLAGS", "=",
- map {"-I\$(srcdir)/$_"} @srcdirs), "\n";
-
- @amcflags = ("\$(COMPAT)", "\$(XFLAGS)", "\$(WARNINGOPTS)");
- print "if HAVE_GTK\n";
- print &splitline(join " ", "AM_CFLAGS", "=",
- "\$(GTK_CFLAGS)", @amcflags), "\n";
- print "else\n";
- print &splitline(join " ", "AM_CFLAGS", "=", @amcflags), "\n";
- print "endif\n\n";
-
- %amspeciallibs = ();
- foreach $obj (sort { $a cmp $b } keys %{$cflags{'am'}}) {
- my $flags = $cflags{'am'}->{$obj};
- $flags = "" if $flags !~ s/^C//;
- print "lib${obj}_a_SOURCES = ", $objtosrc{$obj}, "\n";
- print &splitline(join " ", "lib${obj}_a_CFLAGS", "=", @amcflags,
- $flags), "\n";
- $amspeciallibs{$obj} = "lib${obj}.a";
- }
- print &splitline(join " ", "noinst_LIBRARIES", "=",
- sort { $a cmp $b } values %amspeciallibs), "\n\n";
-
- foreach $p (&prognames("X:XT:U:UT")) {
- ($prog, $type) = split ",", $p;
- print "if HAVE_GTK\n" if $type eq "X" || $type eq "XT";
- @progsources = ("${prog}_SOURCES", "=");
- %sourcefiles = ();
- @ldadd = ();
- $objstr = &objects($p, "X", undef, undef);
- foreach $obj (split / /,$objstr) {
- if ($amspeciallibs{$obj}) {
- push @ldadd, $amspeciallibs{$obj};
- } else {
- $sourcefiles{$objtosrc{$obj}} = 1;
- }
- }
- push @progsources, sort { $a cmp $b } keys %sourcefiles;
- print &splitline(join " ", @progsources), "\n";
- if ($type eq "X" || $type eq "XT") {
- push @ldadd, "\$(GTK_LIBS)";
- }
- if (@ldadd) {
- print &splitline(join " ", "${prog}_LDADD", "=", @ldadd), "\n";
- }
- print "endif\n" if $type eq "X" || $type eq "XT";
- print "\n";
- }
- print &def($makefile_extra{'am'}->{'end'});
- select STDOUT; close OUT;
-}
-
-if (defined $makefiles{'lcc'}) {
- $dirpfx = &dirpfx($makefiles{'lcc'}, "\\");
-
- ##-- lcc makefile
- open OUT, ">$makefiles{'lcc'}"; select OUT;
- print
- "# Makefile for $project_name under lcc.\n".
- "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
- "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
- # lcc command line option is -D not /D
- ($_ = $help) =~ s/([=" ])\/D/$1-D/gs;
- print $_;
- print
- "\n".
- "# If you rename this file to `Makefile', you should change this line,\n".
- "# so that the .rsp files still depend on the correct makefile.\n".
- "MAKEFILE = Makefile.lcc\n".
- "\n".
- "# C compilation flags\n".
- "CFLAGS = -D_WINDOWS " .
- (join " ", map {"-I$dirpfx$_"} @srcdirs) .
- "\n".
- "# Resource compilation flags\n".
- "RCFLAGS = ".(join " ", map {"-I$dirpfx$_"} @srcdirs)."\n".
- "\n".
- "# Get include directory for resource compiler\n".
- "\n".
- &def($makefile_extra{'lcc'}->{'vars'}) .
- "\n";
- print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
- print "\n\n";
- foreach $p (&prognames("G:C")) {
- ($prog, $type) = split ",", $p;
- $objstr = &objects($p, "X.obj", "X.res", undef);
- print &splitline("$prog.exe: " . $objstr ), "\n";
- $subsystemtype = '';
- if ($type eq "G") { $subsystemtype = "-subsystem windows"; }
- my $libss = "shell32.lib wsock32.lib ws2_32.lib winspool.lib winmm.lib imm32.lib";
- print &splitline("\tlcclnk $subsystemtype -o $prog.exe $objstr $libss");
- print "\n\n";
- }
-
- foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\", "lcc")) {
- if ($forceobj{$d->{obj_orig}}) {
- printf("%s: FORCE\n", $d->{obj});
- } else {
- print &splitline(sprintf("%s: %s", $d->{obj},
- join " ", @{$d->{deps}})), "\n";
- }
- if ($d->{obj} =~ /\.obj$/) {
- print &splitline("\tlcc -O -p6 \$(COMPAT)".
- " \$(CFLAGS) \$(XFLAGS) ".$d->{deps}->[0],69)."\n";
- } else {
- print &splitline("\tlrc \$(RCFL) -r \$(RCFLAGS) ".
- $d->{deps}->[0],69)."\n";
- }
- }
- print "\n";
- print &def($makefile_extra{'lcc'}->{'end'});
- print "\nclean:\n".
- "\t-del *.obj\n".
- "\t-del *.exe\n".
- "\t-del *.res\n".
- "\n".
- "FORCE:\n";
-
- select STDOUT; close OUT;
-}
-
-if (defined $makefiles{'osx'}) {
- $dirpfx = &dirpfx($makefiles{'osx'}, "/");
-
- ##-- Mac OS X makefile
- open OUT, ">$makefiles{'osx'}"; select OUT;
- print
- "# Makefile for $project_name under Mac OS X.\n".
- "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
- "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
- # gcc command line option is -D not /D
- ($_ = $help) =~ s/([=" ])\/D/$1-D/gs;
- print $_;
- print
- "CC = \$(TOOLPATH)gcc\n".
- "\n".
- &splitline("CFLAGS = -O2 -Wall -std=gnu99 -Wvla -g " .
- (join " ", map {"-I$dirpfx$_"} @srcdirs))."\n".
- "MLDFLAGS = -framework Cocoa\n".
- "ULDFLAGS =\n".
- "\n" .
- &def($makefile_extra{'osx'}->{'vars'}) .
- "\n" .
- &splitline("all:" . join "", map { " $_" } &progrealnames("MX:U:UT")) .
- "\n";
- foreach $p (&prognames("MX")) {
- ($prog, $type) = split ",", $p;
- $objstr = &objects($p, "X.o", undef, undef);
- $icon = &special($p, ".icns");
- $infoplist = &special($p, "info.plist");
- print "${prog}.app:\n\tmkdir -p \$\@\n";
- print "${prog}.app/Contents: ${prog}.app\n\tmkdir -p \$\@\n";
- print "${prog}.app/Contents/MacOS: ${prog}.app/Contents\n\tmkdir -p \$\@\n";
- $targets = "${prog}.app/Contents/MacOS/$prog";
- if (defined $icon) {
- print "${prog}.app/Contents/Resources: ${prog}.app/Contents\n\tmkdir -p \$\@\n";
- print "${prog}.app/Contents/Resources/${prog}.icns: ${prog}.app/Contents/Resources $icon\n\tcp $icon \$\@\n";
- $targets .= " ${prog}.app/Contents/Resources/${prog}.icns";
- }
- if (defined $infoplist) {
- print "${prog}.app/Contents/Info.plist: ${prog}.app/Contents/Resources $infoplist\n\tcp $infoplist \$\@\n";
- $targets .= " ${prog}.app/Contents/Info.plist";
- }
- $targets .= " \$(${prog}_extra)";
- print &splitline("${prog}: $targets", 69) . "\n\n";
- print &splitline("${prog}.app/Contents/MacOS/$prog: ".
- "${prog}.app/Contents/MacOS " . $objstr), "\n";
- $libstr = &objects($p, undef, undef, "-lX");
- print &splitline("\t\$(CC) \$(MLDFLAGS) -o \$@ " .
- $objstr . " $libstr", 69), "\n\n";
- }
- foreach $p (&prognames("U:UT")) {
- ($prog, $type) = split ",", $p;
- $objstr = &objects($p, "X.o", undef, undef);
- print &splitline($prog . ": " . $objstr), "\n";
- $libstr = &objects($p, undef, undef, "-lX");
- print &splitline("\t\$(CC) \$(ULDFLAGS) -o \$@ " .
- $objstr . " $libstr", 69), "\n\n";
- }
- foreach $d (&deps("X.o", undef, $dirpfx, "/", "osx")) {
- if ($forceobj{$d->{obj_orig}}) {
- printf("%s: FORCE\n", $d->{obj});
- } else {
- print &splitline(sprintf("%s: %s", $d->{obj},
- join " ", @{$d->{deps}})), "\n";
- }
- $firstdep = $d->{deps}->[0];
- if ($firstdep =~ /\.c$/) {
- print "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS) -c \$<\n";
- } elsif ($firstdep =~ /\.m$/) {
- print "\t\$(CC) -x objective-c \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS) -c \$<\n";
- }
- }
- print "\n".&def($makefile_extra{'osx'}->{'end'});
- print "\nclean:\n".
- "\trm -f *.o *.dmg". (join "", map { " $_" } &progrealnames("U:UT")) . "\n".
- "\trm -rf *.app\n".
- "\n".
- "FORCE:\n";
- select STDOUT; close OUT;
-}
-
-if (defined $makefiles{'devcppproj'}) {
- $dirpfx = &dirpfx($makefiles{'devcppproj'}, "\\");
- $orig_dir = cwd;
-
- ##-- Dev-C++ 5 projects
- #
- # Note: All files created in this section are written in binary
- # mode to prevent any posibility of misinterpreted line endings.
- # I don't know if Dev-C++ is as touchy as MSVC with LF-only line
- # endings. But however, CRLF line endings are the common way on
- # Win32 machines where Dev-C++ is running.
- # Hence, in order for mkfiles.pl to generate CRLF project files
- # even when run from Unix, I make sure all files are binary and
- # explicitly write the CRLFs.
- #
- # Create directories if necessary
- mkdir $makefiles{'devcppproj'}
- if(! -d $makefiles{'devcppproj'});
- chdir $makefiles{'devcppproj'};
- @deps = &deps("X.obj", "X.res", $dirpfx, "\\", "devcppproj");
- %all_object_deps = map {$_->{obj} => $_->{deps}} @deps;
- # Make dir names FAT/NTFS compatible
- my @srcdirs = @srcdirs;
- for ($i=0; $i<@srcdirs; $i++) {
- $srcdirs[$i] =~ s/\//\\/g;
- $srcdirs[$i] =~ s/\\$//;
- }
- # Create the project files
- # Get names of all Windows projects (GUI and console)
- my @prognames = &prognames("G:C");
- foreach $progname (@prognames) {
- create_devcpp_project(\%all_object_deps, $progname);
- }
-
- chdir $orig_dir;
-
- sub create_devcpp_project {
- my ($all_object_deps, $progname) = @_;
- # Construct program's dependency info (Taken from 'vcproj', seems to work right here, too.)
- %seen_objects = ();
- %lib_files = ();
- %source_files = ();
- %header_files = ();
- %resource_files = ();
- @object_files = split " ", &objects($progname, "X.obj", "X.res", "X.lib");
- foreach $object_file (@object_files) {
- next if defined $seen_objects{$object_file};
- $seen_objects{$object_file} = 1;
- if($object_file =~ /\.lib$/io) {
- $lib_files{$object_file} = 1;
- next;
- }
- $object_deps = $all_object_deps{$object_file};
- foreach $object_dep (@$object_deps) {
- if($object_dep =~ /\.c$/io) {
- $source_files{$object_dep} = 1;
- next;
- }
- if($object_dep =~ /\.h$/io) {
- $header_files{$object_dep} = 1;
- next;
- }
- if($object_dep =~ /\.(rc|ico)$/io) {
- $resource_files{$object_dep} = 1;
- next;
- }
- }
- }
- $libs = join " ", sort keys %lib_files;
- @source_files = sort keys %source_files;
- @header_files = sort keys %header_files;
- @resources = sort keys %resource_files;
- ($windows_project, $type) = split ",", $progname;
- mkdir $windows_project
- if(! -d $windows_project);
- chdir $windows_project;
-
- $subsys = ($type eq "G") ? "0" : "1"; # 0 = Win32 GUI, 1 = Win32 Console
- open OUT, ">$windows_project.dev"; binmode OUT; select OUT;
- print
- "# DEV-C++ 5 Project File - $windows_project.dev\r\n".
- "# ** DO NOT EDIT **\r\n".
- "\r\n".
- # No difference between DEBUG and RELEASE here as in 'vcproj', because
- # Dev-C++ does not support multiple compilation profiles in one single project.
- # (At least I can say this for Dev-C++ 5 Beta)
- "[Project]\r\n".
- "FileName=$windows_project.dev\r\n".
- "Name=$windows_project\r\n".
- "Ver=1\r\n".
- "IsCpp=1\r\n".
- "Type=$subsys\r\n".
- # Multimon is disabled here, as Dev-C++ (Version 5 Beta) does not have multimon.h
- "Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_\@\@_\r\n".
- "CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_\@\@_\r\n".
- "Includes=" . (join ";", map {"..\\..\\$dirpfx$_"} @srcdirs) . "\r\n".
- "Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_\@\@_\r\n".
- "Libs=\r\n".
- "UnitCount=" . (@source_files + @header_files + @resources) . "\r\n".
- "Folders=\"Header Files\",\"Resource Files\",\"Source Files\"\r\n".
- "ObjFiles=\r\n".
- "PrivateResource=${windows_project}_private.rc\r\n".
- "ResourceIncludes=..\\..\\..\\WINDOWS\r\n".
- "MakeIncludes=\r\n".
- "Icon=\r\n". # It's ok to leave this blank.
- "ExeOutput=\r\n".
- "ObjectOutput=\r\n".
- "OverrideOutput=0\r\n".
- "OverrideOutputName=$windows_project.exe\r\n".
- "HostApplication=\r\n".
- "CommandLine=\r\n".
- "UseCustomMakefile=0\r\n".
- "CustomMakefile=\r\n".
- "IncludeVersionInfo=0\r\n".
- "SupportXPThemes=0\r\n".
- "CompilerSet=0\r\n".
- "CompilerSettings=0000000000000000000000\r\n".
- "\r\n";
- $unit_count = 1;
- foreach $source_file (@source_files) {
- print
- "[Unit$unit_count]\r\n".
- "FileName=..\\..\\$source_file\r\n".
- "Folder=Source Files\r\n".
- "Compile=1\r\n".
- "CompileCpp=0\r\n".
- "Link=1\r\n".
- "Priority=1000\r\n".
- "OverrideBuildCmd=0\r\n".
- "BuildCmd=\r\n".
- "\r\n";
- $unit_count++;
- }
- foreach $header_file (@header_files) {
- print
- "[Unit$unit_count]\r\n".
- "FileName=..\\..\\$header_file\r\n".
- "Folder=Header Files\r\n".
- "Compile=1\r\n".
- "CompileCpp=1\r\n". # Dev-C++ want's to compile all header files with both compilers C and C++. It does not hurt.
- "Link=1\r\n".
- "Priority=1000\r\n".
- "OverrideBuildCmd=0\r\n".
- "BuildCmd=\r\n".
- "\r\n";
- $unit_count++;
- }
- foreach $resource_file (@resources) {
- if ($resource_file =~ /.*\.(ico|cur|bmp|dlg|rc2|rct|bin|rgs|gif|jpg|jpeg|jpe)/io) { # Default filter as in 'vcproj'
- $Compile = "0"; # Don't compile images and other binary resource files
- $CompileCpp = "0";
- } else {
- $Compile = "1";
- $CompileCpp = "1"; # Dev-C++ want's to compile all .rc files with both compilers C and C++. It does not hurt.
- }
- print
- "[Unit$unit_count]\r\n".
- "FileName=..\\..\\$resource_file\r\n".
- "Folder=Resource Files\r\n".
- "Compile=$Compile\r\n".
- "CompileCpp=$CompileCpp\r\n".
- "Link=0\r\n".
- "Priority=1000\r\n".
- "OverrideBuildCmd=0\r\n".
- "BuildCmd=\r\n".
- "\r\n";
- $unit_count++;
- }
- #Note: By default, [VersionInfo] is not used.
- print
- "[VersionInfo]\r\n".
- "Major=0\r\n".
- "Minor=0\r\n".
- "Release=1\r\n".
- "Build=1\r\n".
- "LanguageID=1033\r\n".
- "CharsetID=1252\r\n".
- "CompanyName=\r\n".
- "FileVersion=0.1\r\n".
- "FileDescription=\r\n".
- "InternalName=\r\n".
- "LegalCopyright=\r\n".
- "LegalTrademarks=\r\n".
- "OriginalFilename=$windows_project.exe\r\n".
- "ProductName=$windows_project\r\n".
- "ProductVersion=0.1\r\n".
- "AutoIncBuildNr=0\r\n";
- select STDOUT; close OUT;
- chdir "..";
- }
-}
-
-# All done, so do the Unix postprocessing if asked to.
-
-if ($do_unix) {
- chdir $orig_dir;
- system "./mkauto.sh";
- die "mkfiles.pl: mkauto.sh returned $?\n" if $? > 0;
- if ($do_unix == 1) {
- chdir ($targetdir = "unix")
- or die "$targetdir: chdir: $!\n";
- }
- system "./configure", @confargs;
- die "mkfiles.pl: configure returned $?\n" if $? > 0;
-}
-
-sub invent_guid($) {
- my ($name) = @_;
-
- # Invent a GUID for use in Visual Studio project files. We need
- # a few of these for every executable file we build.
- #
- # In order to avoid having to use the non-core Perl module
- # Data::GUID, and also arrange for GUIDs to be stable, we generate
- # our GUIDs by hashing a pile of fixed (but originally randomly
- # generated) data with the filename for which we need an id.
- #
- # Hashing _just_ the filenames would clearly be cheating (it's
- # quite conceivable that someone might hash the same string for
- # another reason and so generate a colliding GUID), but hashing a
- # whole SHA-512 data block of random gibberish as well should make
- # these GUIDs pseudo-random enough to not collide with anyone
- # else's.
-
- my $randdata = pack "N*",
- 0xD4AB035F,0x76998BA0,0x2DCCB0BD,0x6D3FA320,0x53638051,0xFE312F35,
- 0xDE1CECC0,0x784DF852,0x6C9F4589,0x54B7AC23,0x14E7A1C4,0xF9BF04DF,
- 0x19C08B6D,0x3FB69EF1,0xB2DA9043,0xDB5362F3,0x25718DB6,0x733560DA,
- 0xFEF871B0,0xFECF7A0C,0x67D19C95,0xB492E911,0xF5D562A3,0xFCE1D478,
- 0x02C50434,0xF7326B7E,0x93D39872,0xCF0D0269,0x9EF24C0F,0x827689AD,
- 0x88BD20BC,0x74EA6AFE,0x29223682,0xB9AB9287,0x7EA7CE4F,0xCF81B379,
- 0x9AE4A954,0x81C7AD97,0x2FF2F031,0xC51DA3C2,0xD311CCE7,0x0A31EB8B,
- 0x1AB04242,0xAF53B714,0xFC574D40,0x8CB4ED01,0x29FEB16F,0x4904D7ED,
- 0xF5C5F5E1,0xF138A4C2,0xA9D881CE,0xCEA65187,0x4421BA97,0x0EE8428E,
- 0x9556E384,0x6D0484C9,0x561BD84B,0xD9516A40,0x6B4FD33F,0xDDFFE4C8,
- 0x3D5DF8A5,0xFE6B7D99,0x3443371B,0xF4E30A3E,0xE62B9FDA,0x6BAA75DB,
- 0x9EF3C2C7,0x6815CA42,0xE6536076,0xF851E6E2,0x39D16E69,0xBCDF3BB6,
- 0x50EFFA41,0x378CDF2A,0xB5EC0D0C,0x1E94C433,0xE818241A,0x2689EB1F,
- 0xB649CEF9,0xD7344D46,0x59C1BB13,0x27511FDF,0x7DAD1768,0xB355E29E,
- 0xDFAE550C,0x2433005B,0x09DE10B0,0xAA00BA6B,0xC144ED2D,0x8513D007,
- 0xB0315232,0x7A10DAB6,0x1D97654E,0xF048214D,0xE3059E75,0x83C225D1,
- 0xFC7AB177,0x83F2B553,0x79F7A0AF,0x1C94582C,0xF5E4AF4B,0xFB39C865,
- 0x58ABEB27,0xAAB28058,0x52C15A89,0x0EBE9741,0x343F4D26,0xF941202A,
- 0xA32FD32F,0xDCC055B8,0x64281BF3,0x468BD7BA,0x0CEE09D3,0xBB5FD2B6,
- 0xA528D412,0xA6A6967E,0xEAAF5DAE,0xDE7B2FAE,0xCA36887B,0x0DE196EB,
- 0x74B95EF0,0x9EB8B7C2,0x020BFC83,0x1445086F,0xBF4B61B2,0x89AFACEC,
- 0x80A5CD69,0xC790F744,0x435A6998,0x8DE7AC48,0x32F31BC9,0x8F760D3D,
- 0xF02A74CB,0xD7B47E20,0x9EC91035,0x70FDE74D,0x9B531362,0x9D81739A,
- 0x59ADC2EB,0x511555B5,0xCA84B8D5,0x3EC325FF,0x2E442A4C,0x82AF30D9,
- 0xBFD3EC87,0x90C59E07,0x1C6DC991,0x2D16B822,0x7EA44EB5,0x3A655A39,
- 0xAB640886,0x09311821,0x777801D9,0x489DBE61,0xA1FFEC65,0x978B49B1,
- 0x7DB700CD,0x263CF3D6,0xF977E89F,0xBA0B3D01,0x6C6CED19,0x1BE6F23A,
- 0x19E0ED98,0x8E71A499,0x70BA3271,0x3FB7EE98,0xABA46848,0x2B797959,
- 0x72C6DE59,0xE08B795C,0x02936C39,0x02185CCB,0xD6F3CE18,0xD0157A40,
- 0x833DEC3F,0x319B00C4,0x97B59513,0x900B81FD,0x9A022379,0x16E44E1A,
- 0x0C4CC540,0xCA98E7F9,0xF9431A26,0x290BCFAC,0x406B82C0,0xBC1C4585,
- 0x55C54528,0x811EBB77,0xD4EDD4F3,0xA70DC02E,0x8AD5C0D1,0x28D64EF4,
- 0xBEFF5C69,0x99852C4A,0xB4BBFF7B,0x069230AC,0xA3E141FA,0x4E99FB0E,
- 0xBC154DAA,0x323C7F15,0x86E0247E,0x2EEA3054,0xC9CA1D32,0x8964A006,
- 0xC93978AC,0xF9B2C159,0x03F2079E,0xB051D284,0x4A7EA9A9,0xF001DA1F,
- 0xD47A0DAA,0xCF7B6B73,0xF18293B2,0x84303E34,0xF8BC76C4,0xAFBEE24F,
- 0xB589CA80,0x77B5BF86,0x21B9FD5B,0x1A5071DF,0xA3863110,0x0E50CA61,
- 0x939151A5,0xD2A59021,0x83A9CDCE,0xCEC69767,0xC906BB16,0x3EE1FF4D,
- 0x1321EAE4,0x0BF940D6,0x52471E61,0x8A087056,0x66E54293,0xF84AAB9B,
- 0x08835EF1,0x8F12B77A,0xD86935A5,0x200281D7,0xCD3C37C9,0x30ABEC05,
- 0x7067E8A0,0x608C4838,0xC9F51CDE,0xA6D318DE,0x41C05B2A,0x694CCE0E,
- 0xC7842451,0xA3194393,0xFBDC2C84,0xA6D2B577,0xC91E7924,0x01EDA708,
- 0x22FBB61E,0x662F9B7B,0xDE3150C3,0x2397058C;
- my $digest = sha512_hex($name . "\0" . $randdata);
- return sprintf("%s-%s-%04x-%04x-%s",
- substr($digest,0,8),
- substr($digest,8,4),
- 0x4000 | (0xFFF & hex(substr($digest,12,4))),
- 0x8000 | (0x3FFF & hex(substr($digest,16,4))),
- substr($digest,20,12));
-}
diff --git a/mksrcarc.sh b/mksrcarc.sh
index 9b533174..4d53e80a 100644
--- a/mksrcarc.sh
+++ b/mksrcarc.sh
@@ -2,16 +2,16 @@
set -e
-perl mkfiles.pl
# These are text files.
text=`{ find . -name CVS -prune -o \
-name .cvsignore -prune -o \
-name .svn -prune -o \
+ -name .git -prune -o \
-name LATEST.VER -prune -o \
-name CHECKLST.txt -prune -o \
-name mksrcarc.sh -prune -o \
- -name '*.dsp' -prune -o \
- -name '*.dsw' -prune -o \
+ -name '*.chm' -prune -o \
+ -name '*.cur' -prune -o \
-type f -print | sed 's/^\.\///'; } | \
grep -ivE 'test/.*\.txt|MODULE|website.url' | grep -vF .ico | grep -vF .icns`
# These are files which I'm _sure_ should be treated as text, but
@@ -21,7 +21,7 @@ text=`{ find . -name CVS -prune -o \
bintext=test/*.txt
# These are actual binary files which we don't want transforming.
bin=`{ ls -1 windows/*.ico windows/website.url; \
- find . -name '*.dsp' -print -o -name '*.dsw' -print; }`
+ find . -name '*.chm' -print -o -name '*.cur' -print; }`
verbosely() {
echo "$@"
diff --git a/mkunxarc.sh b/mkunxarc.sh
index 34aee8b2..cb79c054 100755
--- a/mkunxarc.sh
+++ b/mkunxarc.sh
@@ -3,16 +3,10 @@
# Build a Unix source distribution from the PuTTY CVS area.
#
# Expects the following arguments:
-# - the version number to write into configure.ac
# - the suffix to put on the Unix source tarball
# - the options to put on the 'make' command line for the docs
-autoconfver="$1"
-arcsuffix="$2"
-docver="$3"
-
-perl mkfiles.pl
-(cd doc && make -s ${docver:+"$docver"})
+arcsuffix="$1"
relver=`cat LATEST.VER`
arcname="putty$arcsuffix"
@@ -27,12 +21,9 @@ find . -name uxarc -prune -o \
-name CVS -prune -o \
-name .cvsignore -prune -o \
-name .svn -prune -o \
- -name configure.ac -prune -o \
-name '*.zip' -prune -o \
-name '*.tar.gz' -prune -o \
-type f -exec ln -s $PWD/{} uxarc/$arcname/{} \;
-sed "s/^AC_INIT(putty,.*/AC_INIT(putty, $autoconfver)/" configure.ac > uxarc/$arcname/configure.ac
-(cd uxarc/$arcname && sh mkauto.sh) 2>errors || { cat errors >&2; exit 1; }
tar -C uxarc -chzof $arcname.tar.gz $arcname
rm -rf uxarc
diff --git a/mpint.c b/mpint.c
deleted file mode 100644
index 39f55063..00000000
--- a/mpint.c
+++ /dev/null
@@ -1,2646 +0,0 @@
-#include <assert.h>
-#include <limits.h>
-#include <stdio.h>
-
-#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, &quotient_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/mpint.h b/mpint.h
index 5611a007..4ddc0e64 100644
--- a/mpint.h
+++ b/mpint.h
@@ -43,6 +43,13 @@ void mp_free(mp_int *);
void mp_clear(mp_int *x);
/*
+ * Resize the physical size of existing mp_int, e.g. so that you have
+ * room to transform it in place to a larger value. Destroys the old
+ * mp_int in the process.
+ */
+mp_int *mp_resize(mp_int *, size_t newmaxbits);
+
+/*
* Create mp_ints from various sources: little- and big-endian binary
* data, an ordinary C unsigned integer type, a decimal or hex string
* (given either as a ptrlen or a C NUL-terminated string), and
@@ -258,6 +265,12 @@ mp_int *mp_div(mp_int *n, mp_int *d);
mp_int *mp_mod(mp_int *x, mp_int *modulus);
/*
+ * Compute the residue of x mod m, where m is a small integer. x is
+ * kept secret, but m is not.
+ */
+uint32_t mp_mod_known_integer(mp_int *x, uint32_t m);
+
+/*
* Integer nth root. mp_nthroot returns the largest integer x such
* that x^n <= y, and if 'remainder' is non-NULL then it fills it with
* the residue (y - x^n).
@@ -422,10 +435,10 @@ mp_int *mp_rshift_fixed(mp_int *x, size_t shift);
*
* 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.
+ * random_read() from sshrand.c, and the macro wrappers automate that.
*
* (This is a bit of a dodge to avoid mpint.c having a link-time
- * dependency on random.c, so that programs can link against one but
+ * dependency on sshrand.c, so that programs can link against one but
* not the other: if a client of this header uses one of these macros
* then _they_ have link-time dependencies on both modules.)
*
diff --git a/mpint_i.h b/mpint_i.h
deleted file mode 100644
index d37e75f0..00000000
--- a/mpint_i.h
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * mpint_i.h: definitions used internally by the bignum code, and
- * also a few other vaguely-bignum-like places.
- */
-
-/* ----------------------------------------------------------------------
- * The assorted conditional definitions of BignumInt and multiply
- * macros used throughout the bignum code to treat numbers as arrays
- * of the most conveniently sized word for the target machine.
- * Exported so that other code (e.g. poly1305) can use it too.
- *
- * This code must export, in whatever ifdef branch it ends up in:
- *
- * - two types: 'BignumInt' and 'BignumCarry'. BignumInt is an
- * unsigned integer type which will be used as the base word size
- * for all bignum operations. BignumCarry is an unsigned integer
- * type used to hold the carry flag taken as input and output by
- * the BignumADC macro (see below).
- *
- * - five constant macros:
- * + BIGNUM_INT_BITS, the number of bits in BignumInt,
- * + BIGNUM_INT_BYTES, the number of bytes that works out to
- * + BIGNUM_TOP_BIT, the BignumInt value consisting of only the top bit
- * + BIGNUM_INT_MASK, the BignumInt value with all bits set
- * + BIGNUM_INT_BITS_BITS, log to the base 2 of BIGNUM_INT_BITS.
- *
- * - four statement macros: BignumADC, BignumMUL, BignumMULADD,
- * BignumMULADD2. These do various kinds of multi-word arithmetic,
- * and all produce two output values.
- * * BignumADC(ret,retc,a,b,c) takes input BignumInt values a,b
- * and a BignumCarry c, and outputs a BignumInt ret = a+b+c and
- * a BignumCarry retc which is the carry off the top of that
- * addition.
- * * BignumMUL(rh,rl,a,b) returns the two halves of the
- * double-width product a*b.
- * * BignumMULADD(rh,rl,a,b,addend) returns the two halves of the
- * double-width value a*b + addend.
- * * BignumMULADD2(rh,rl,a,b,addend1,addend2) returns the two
- * halves of the double-width value a*b + addend1 + addend2.
- *
- * Every branch of the main ifdef below defines the type BignumInt and
- * the value BIGNUM_INT_BITS_BITS. The other constant macros are
- * filled in by common code further down.
- *
- * Most branches also define a macro DEFINE_BIGNUMDBLINT containing a
- * typedef statement which declares a type _twice_ the length of a
- * BignumInt. This causes the common code further down to produce a
- * default implementation of the four statement macros in terms of
- * that double-width type, and also to defined BignumCarry to be
- * BignumInt.
- *
- * However, if a particular compile target does not have a type twice
- * the length of the BignumInt you want to use but it does provide
- * some alternative means of doing add-with-carry and double-word
- * multiply, then the ifdef branch in question can just define
- * BignumCarry and the four statement macros itself, and that's fine
- * too.
- */
-
-/* You can lower the BignumInt size by defining BIGNUM_OVERRIDE on the
- * command line to be your chosen max value of BIGNUM_INT_BITS_BITS */
-#if defined BIGNUM_OVERRIDE
-#define BB_OK(b) ((b) <= BIGNUM_OVERRIDE)
-#else
-#define BB_OK(b) (1)
-#endif
-
-#if defined __SIZEOF_INT128__ && BB_OK(6)
-
- /*
- * 64-bit BignumInt using gcc/clang style 128-bit BignumDblInt.
- *
- * gcc and clang both provide a __uint128_t type on 64-bit targets
- * (and, when they do, indicate its presence by the above macro),
- * using the same 'two machine registers' kind of code generation
- * that 32-bit targets use for 64-bit ints.
- */
-
- typedef unsigned long long BignumInt;
- #define BIGNUM_INT_BITS_BITS 6
- #define DEFINE_BIGNUMDBLINT typedef __uint128_t BignumDblInt
-
-#elif defined _MSC_VER && defined _M_AMD64 && BB_OK(6)
-
- /*
- * 64-bit BignumInt, using Visual Studio x86-64 compiler intrinsics.
- *
- * 64-bit Visual Studio doesn't provide very much in the way of help
- * here: there's no int128 type, and also no inline assembler giving
- * us direct access to the x86-64 MUL or ADC instructions. However,
- * there are compiler intrinsics giving us that access, so we can
- * use those - though it turns out we have to be a little careful,
- * since they seem to generate wrong code if their pointer-typed
- * output parameters alias their inputs. Hence all the internal temp
- * variables inside the macros.
- */
-
- #include <intrin.h>
- 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/mpunsafe.c b/mpunsafe.c
deleted file mode 100644
index beec13fa..00000000
--- a/mpunsafe.c
+++ /dev/null
@@ -1,57 +0,0 @@
-#include <assert.h>
-#include <limits.h>
-#include <stdio.h>
-
-#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.)
- */
-const int deliberate_symbol_clash = 12345;
-
-static size_t mp_unsafe_words_needed(mp_int *x)
-{
- size_t words = x->nw;
- while (words > 1 && !x->w[words-1])
- words--;
- return words;
-}
-
-mp_int *mp_unsafe_shrink(mp_int *x)
-{
- x->nw = mp_unsafe_words_needed(x);
- /* This potentially leaves some allocated words between the new
- * and old values of x->nw, which won't be wiped by mp_free now
- * that x->nw doesn't mention that they exist. But we've just
- * checked they're all zero, so we don't need to wipe them now
- * either. */
- return x;
-}
-
-mp_int *mp_unsafe_copy(mp_int *x)
-{
- mp_int *copy = mp_make_sized(mp_unsafe_words_needed(x));
- mp_copy_into(copy, x);
- return copy;
-}
-
-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/mpunsafe.h b/mpunsafe.h
deleted file mode 100644
index 0b6ba3bd..00000000
--- a/mpunsafe.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * mpunsafe.h: functions that deal with mp_ints in ways that are *not*
- * expected to be constant-time. Used during key generation, in which
- * constant run time is a lost cause anyway.
- *
- * These functions are in a separate header, so that you can easily
- * check that you're not calling them in the wrong context. They're
- * also defined in a separate source file, which is only linked in to
- * the key generation tools. Furthermore, that source file also
- * defines a global symbol that intentionally conflicts with one
- * defined in the SSH client code, so that any attempt to put these
- * functions into the same binary as the live SSH client
- * implementation will cause a link-time failure. They should only be
- * linked into PuTTYgen and auxiliary test programs.
- *
- * Also, just in case those precautions aren't enough, all the unsafe
- * functions have 'unsafe' in the name.
- */
-
-#ifndef PUTTY_MPINT_UNSAFE_H
-#define PUTTY_MPINT_UNSAFE_H
-
-/*
- * The most obvious unsafe thing you want to do with an mp_int is to
- * get rid of leading zero words in its representation, so that its
- * nominal size is as close as possible to its true size, and you
- * don't waste any time processing it.
- *
- * mp_unsafe_shrink performs this operation in place, mutating the
- * size field of the mp_int it's given. It returns the same pointer it
- * was given.
- *
- * mp_unsafe_copy leaves the original mp_int alone and makes a new one
- * with the minimal size.
- */
-mp_int *mp_unsafe_shrink(mp_int *m);
-mp_int *mp_unsafe_copy(mp_int *m);
-
-/*
- * 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/network.h b/network.h
index 89419fb7..57e6662d 100644
--- a/network.h
+++ b/network.h
@@ -51,9 +51,14 @@ typedef enum PlugLogType {
PLUGLOG_PROXY_MSG,
} PlugLogType;
+typedef enum PlugCloseType {
+ PLUGCLOSE_NORMAL,
+ PLUGCLOSE_ERROR,
+ PLUGCLOSE_BROKEN_PIPE,
+ PLUGCLOSE_USER_ABORT,
+} PlugCloseType;
+
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.
@@ -67,22 +72,63 @@ struct PlugVtable {
* 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_CONNECT_SUCCESS means we have succeeded in making a
+ * connection. `addr' gives the address we connected to, if
+ * available. (But sometimes, in cases of complicated proxy
+ * setups, it might not be available, so receivers of this log
+ * event should be prepared to deal with addr==NULL.)
*
* - PLUGLOG_PROXY_MSG means that error_msg contains a line of
* logging information from whatever the connection is being
* proxied through. This will typically be a wodge of
* standard-error output from a local proxy command, so the
* receiver should probably prefix it to indicate this.
+ *
+ * Note that sometimes log messages may be sent even to Socket
+ * types that don't involve making an outgoing connection, e.g.
+ * because the same core implementation (such as Windows handle
+ * sockets) is shared between listening and connecting sockets. So
+ * all Plugs must implement this method, even if only to ignore
+ * the logged events.
*/
- void (*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);
+ void (*log)(Plug *p, PlugLogType type, SockAddr *addr, int port,
+ const char *error_msg, int error_code);
+
+ /*
+ * Notifies the Plug that the socket is closing, and something
+ * about why.
+ *
+ * - PLUGCLOSE_NORMAL means an ordinary non-error closure. In
+ * this case, error_msg should be ignored (and hopefully
+ * callers will have passed NULL).
+ *
+ * - PLUGCLOSE_ERROR indicates that an OS error occurred, and
+ * 'error_msg' contains a string describing it, for use in
+ * diagnostics. (Ownership of the string is not transferred.)
+ * This error class covers anything other than the special
+ * case below:
+ *
+ * - PLUGCLOSE_BROKEN_PIPE behaves like PLUGCLOSE_ERROR (in
+ * particular, there's still an error message provided), but
+ * distinguishes the particular error condition signalled by
+ * EPIPE / ERROR_BROKEN_PIPE, which ssh/sharing.c needs to
+ * recognise and handle specially in one situation.
+ *
+ * - PLUGCLOSE_USER_ABORT means that the close has happened as a
+ * result of some kind of deliberate user action (e.g. hitting
+ * ^C at a password prompt presented by a proxy socket setup
+ * phase). This can be used to suppress interactive error
+ * messages sent to the user (such as dialog boxes), on the
+ * grounds that the user already knows. However, 'error_msg'
+ * will still contain some appropriate text, so that
+ * non-interactive error reporting (e.g. event logs) can still
+ * record why the connection terminated.
+ */
+ void (*closing)(Plug *p, PlugCloseType type, const char *error_msg);
+
/*
+ * Provides incoming socket data to the Plug. Three cases:
+ *
* - urgent==0. `data' points to `len' bytes of perfectly
* ordinary data.
*
@@ -92,28 +138,52 @@ struct PlugVtable {
* - 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);
+ void (*receive) (Plug *p, int urgent, const char *data, size_t len);
+
/*
- * 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.
+ * 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);
+ void (*sent) (Plug *p, size_t bufsize);
+
/*
- * `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.
+ * Only called on listener-type sockets, and is passed a
+ * constructor function+context that will create a fresh Socket
+ * describing the connection. It returns nonzero if it doesn't
+ * want the connection for some reason, or 0 on success.
*/
+ int (*accepting)(Plug *p, accept_fn_t constructor, accept_ctx_t ctx);
};
-/* proxy indirection layer */
-/* NB, control of 'addr' is passed via new_connection, which takes
- * responsibility for freeing it */
+/* Proxy indirection layer.
+ *
+ * Calling new_connection transfers ownership of 'addr': the proxy
+ * layer is now responsible for freeing it, and the caller shouldn't
+ * assume it exists any more.
+ *
+ * If calling this from a backend with a Seat, you can also give it a
+ * pointer to the backend's Interactor trait. In that situation, it
+ * might replace the backend's seat with a temporary seat of its own,
+ * and give the real Seat to an Interactor somewhere in the proxy
+ * system so that it can ask for passwords (and, in the case of SSH
+ * proxying, other prompts like host key checks). If that happens,
+ * then the resulting 'temp seat' is the backend's property, and it
+ * will have to remember to free it when cleaning up, or after
+ * flushing it back into the real seat when the network connection
+ * attempt completes.
+ *
+ * You can free your TempSeat and resume using the real Seat when one
+ * of two things happens: either your Plug's closing() method is
+ * called (indicating failure to connect), or its log() method is
+ * called with PLUGLOG_CONNECT_SUCCESS. In the latter case, you'll
+ * probably want to flush the TempSeat's contents into the real Seat,
+ * of course.
+ */
Socket *new_connection(SockAddr *addr, const char *hostname,
int port, bool privport,
bool oobinline, bool nodelay, bool keepalive,
- Plug *plug, Conf *conf);
+ Plug *plug, Conf *conf, Interactor *interactor);
Socket *new_listener(const char *srcaddr, int port, Plug *plug,
bool local_host_only, Conf *conf, int addressfamily);
SockAddr *name_lookup(const char *host, int port, char **canonicalname,
@@ -125,7 +195,13 @@ SockAddr *name_lookup(const char *host, int port, char **canonicalname,
Socket *platform_new_connection(SockAddr *addr, const char *hostname,
int port, bool privport,
bool oobinline, bool nodelay, bool keepalive,
- Plug *plug, Conf *conf);
+ Plug *plug, Conf *conf, Interactor *itr);
+
+/* callback for SSH jump-host proxying */
+Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname,
+ int port, bool privport,
+ bool oobinline, bool nodelay, bool keepalive,
+ Plug *plug, Conf *conf, Interactor *itr);
/* socket functions */
@@ -171,9 +247,14 @@ static inline void sk_write_eof(Socket *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_closing(Plug *p, PlugCloseType type, const char *msg)
+{ p->vt->closing(p, type, msg); }
+static inline void plug_closing_normal(Plug *p)
+{ p->vt->closing(p, PLUGCLOSE_NORMAL, NULL); }
+static inline void plug_closing_error(Plug *p, const char *msg)
+{ p->vt->closing(p, PLUGCLOSE_ERROR, msg); }
+static inline void plug_closing_user_abort(Plug *p)
+{ p->vt->closing(p, PLUGCLOSE_USER_ABORT, "User aborted connection setup"); }
static inline void plug_receive(Plug *p, int urg, const char *data, size_t len)
{ p->vt->receive(p, urg, data, len); }
static inline void plug_sent (Plug *p, size_t bufsize)
@@ -221,7 +302,7 @@ static inline SocketPeerInfo *sk_peer_info(Socket *s)
/*
* The structure returned from sk_peer_info, and a function to free
- * one (in misc.c).
+ * one (in utils).
*/
struct SocketPeerInfo {
int addressfamily;
@@ -259,13 +340,13 @@ struct SocketPeerInfo {
void sk_free_peer_info(SocketPeerInfo *pi);
/*
- * Simple wrapper on getservbyname(), needed by ssh.c. Returns the
+ * Simple wrapper on getservbyname(), needed by portfwd.c. Returns the
* port number, in host byte order (suitable for printf and so on).
* Returns 0 on failure. Any platform not supporting getservbyname
* can just return 0 - this function is not required to handle
* numeric port specifications.
*/
-int net_service_lookup(char *service);
+int net_service_lookup(const char *service);
/*
* Look up the local hostname; return value needs freeing.
@@ -289,15 +370,25 @@ Socket *new_error_socket_consume_string(Plug *plug, char *errmsg);
*/
extern Plug *const nullplug;
+/*
+ * Some trivial no-op plug functions, also in nullplug.c; exposed here
+ * so that other Plug implementations can use them too.
+ *
+ * In particular, nullplug_log is useful to Plugs that don't need to
+ * worry about logging.
+ */
+void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr,
+ int port, const char *err_msg, int err_code);
+void nullplug_closing(Plug *plug, PlugCloseType type, const char *error_msg);
+void nullplug_receive(Plug *plug, int urgent, const char *data, size_t len);
+void nullplug_sent(Plug *plug, size_t bufsize);
+
/* ----------------------------------------------------------------------
* Functions defined outside the network code, which have to be
* declared in this header file rather than the main putty.h because
* they use types defined here.
*/
-/*
- * 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,
@@ -306,9 +397,40 @@ void backend_socket_log(Seat *seat, LogContext *logctx,
typedef struct ProxyStderrBuf {
char buf[8192];
size_t size;
+ const char *prefix; /* must be statically allocated */
} ProxyStderrBuf;
void psb_init(ProxyStderrBuf *psb);
+void psb_set_prefix(ProxyStderrBuf *psb, const char *prefix);
void log_proxy_stderr(
Plug *plug, ProxyStderrBuf *psb, const void *vdata, size_t len);
+/* ----------------------------------------------------------------------
+ * The DeferredSocketOpener trait. This is a thing that some Socket
+ * implementations may choose to own if they need to delay actually
+ * setting up the underlying connection. For example, sockets used in
+ * local-proxy handling (Unix FdSocket / Windows HandleSocket) might
+ * need to do this if they have to prompt the user interactively for
+ * parts of the command they'll run.
+ *
+ * Mostly, a DeferredSocketOpener implementation will keep to itself,
+ * arrange its own callbacks in order to do whatever setup it needs,
+ * and when it's ready, call back to its parent Socket via some
+ * implementation-specific API of its own. So the shared API here
+ * requires almost nothing: the only thing we need is a free function,
+ * so that if the owner of a Socket of this kind needs to close it
+ * before the deferred connection process is finished, the Socket can
+ * also clean up the DeferredSocketOpener dangling off it.
+ */
+
+struct DeferredSocketOpener {
+ const DeferredSocketOpenerVtable *vt;
+};
+struct DeferredSocketOpenerVtable {
+ void (*free)(DeferredSocketOpener *);
+};
+static inline void deferred_socket_opener_free(DeferredSocketOpener *dso)
+{ dso->vt->free(dso); }
+
+DeferredSocketOpener *null_deferred_socket_opener(void);
+
#endif
diff --git a/nocmdline.c b/nocmdline.c
deleted file mode 100644
index e4c6d08f..00000000
--- a/nocmdline.c
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * nocmdline.c - stubs in applications which don't do the
- * standard(ish) PuTTY tools' command-line parsing
- */
-
-#include <stdio.h>
-#include <assert.h>
-#include <stdlib.h>
-#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/nocproxy.c b/nocproxy.c
deleted file mode 100644
index f93214fa..00000000
--- a/nocproxy.c
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Routines to refuse to do cryptographic interaction with proxies
- * in PuTTY. This is a stub implementation of the same interfaces
- * provided by cproxy.c, for use in PuTTYtel.
- */
-
-#include <assert.h>
-#include <ctype.h>
-#include <string.h>
-
-#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/nogss.c b/nogss.c
deleted file mode 100644
index 844a1323..00000000
--- a/nogss.c
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * Stub definitions of the GSSAPI library list, for Unix pterm and
- * any other application that needs the symbols defined but has no
- * use for them.
- */
-
-#include "putty.h"
-
-const int ngsslibs = 0;
-const char *const gsslibnames[1] = { "dummy" };
-const struct keyvalwhere gsslibkeywords[1] = { { "dummy", 0, -1, -1 } };
diff --git a/noprint.c b/noprint.c
deleted file mode 100644
index 941da68c..00000000
--- a/noprint.c
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Stub implementation of the printing interface for PuTTY, for the
- * benefit of non-printing terminal applications.
- */
-
-#include <assert.h>
-#include <stdio.h>
-#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/noproxy.c b/noproxy.c
deleted file mode 100644
index 1d372932..00000000
--- a/noproxy.c
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * noproxy.c: an alternative to proxy.c, for use by auxiliary programs
- * that need to make network connections but don't want to include all
- * the full-on support for endless network proxies (and its
- * configuration requirements). Implements the primary APIs of
- * proxy.c, but maps them straight to the underlying network layer.
- */
-
-#include "putty.h"
-#include "network.h"
-#include "proxy.h"
-
-SockAddr *name_lookup(const char *host, int port, char **canonicalname,
- Conf *conf, int addressfamily, LogContext *logctx,
- const char *reason)
-{
- return sk_namelookup(host, canonicalname, addressfamily);
-}
-
-Socket *new_connection(SockAddr *addr, const char *hostname,
- int port, bool privport,
- bool oobinline, bool nodelay, bool keepalive,
- Plug *plug, Conf *conf)
-{
- 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/noterm.c b/noterm.c
deleted file mode 100644
index 4ca99fa2..00000000
--- a/noterm.c
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * Stubs of functions in terminal.c, for use in programs that don't
- * have a terminal.
- */
-
-#include "putty.h"
-#include "terminal.h"
-
-void term_nopaste(Terminal *term)
-{
-}
diff --git a/notiming.c b/notiming.c
deleted file mode 100644
index 3feb5cdf..00000000
--- a/notiming.c
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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/nullplug.c b/nullplug.c
deleted file mode 100644
index 953f0348..00000000
--- a/nullplug.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * nullplug.c: provide a null implementation of the Plug vtable which
- * ignores all calls. Occasionally useful in cases where we want to
- * make a network connection just to see if it works, but not do
- * anything with it afterwards except close it again.
- */
-
-#include "putty.h"
-
-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/otherbackends/CMakeLists.txt b/otherbackends/CMakeLists.txt
new file mode 100644
index 00000000..099d1253
--- /dev/null
+++ b/otherbackends/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_sources_from_current_dir(otherbackends
+ raw.c
+ rlogin.c
+ supdup.c
+ telnet.c
+ testback.c)
diff --git a/otherbackends/raw.c b/otherbackends/raw.c
new file mode 100644
index 00000000..c9a32e05
--- /dev/null
+++ b/otherbackends/raw.c
@@ -0,0 +1,377 @@
+/*
+ * "Raw" backend.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "putty.h"
+
+#define RAW_MAX_BACKLOG 4096
+
+typedef struct Raw Raw;
+struct Raw {
+ Socket *s;
+ bool closed_on_socket_error;
+ size_t bufsize;
+ Seat *seat;
+ LogContext *logctx;
+ Ldisc *ldisc;
+ bool sent_console_eof, sent_socket_eof, socket_connected;
+ char *description;
+
+ Conf *conf;
+
+ Plug plug;
+ Backend backend;
+ Interactor interactor;
+};
+
+static void raw_size(Backend *be, int width, int height);
+
+static void c_write(Raw *raw, const void *buf, size_t len)
+{
+ size_t backlog = seat_stdout(raw->seat, buf, len);
+ sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG);
+}
+
+static void raw_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
+ const char *error_msg, int error_code)
+{
+ Raw *raw = container_of(plug, Raw, plug);
+ backend_socket_log(raw->seat, raw->logctx, type, addr, port, error_msg,
+ error_code, raw->conf, raw->socket_connected);
+ if (type == PLUGLOG_CONNECT_SUCCESS) {
+ raw->socket_connected = true;
+ if (raw->ldisc)
+ ldisc_check_sendok(raw->ldisc);
+ }
+}
+
+static void raw_check_close(Raw *raw)
+{
+ /*
+ * Called after we send EOF on either the socket or the console.
+ * Its job is to wind up the session once we have sent EOF on both.
+ */
+ if (raw->sent_console_eof && raw->sent_socket_eof) {
+ if (raw->s) {
+ sk_close(raw->s);
+ raw->s = NULL;
+ seat_notify_remote_exit(raw->seat);
+ seat_notify_remote_disconnect(raw->seat);
+ }
+ }
+}
+
+static void raw_closing(Plug *plug, PlugCloseType type, const char *error_msg)
+{
+ Raw *raw = container_of(plug, Raw, plug);
+
+ if (type != PLUGCLOSE_NORMAL) {
+ /* A socket error has occurred. */
+ if (raw->s) {
+ sk_close(raw->s);
+ raw->s = NULL;
+ raw->closed_on_socket_error = true;
+ seat_notify_remote_exit(raw->seat);
+ seat_notify_remote_disconnect(raw->seat);
+ }
+ logevent(raw->logctx, error_msg);
+ if (type != PLUGCLOSE_USER_ABORT)
+ seat_connection_fatal(raw->seat, "%s", error_msg);
+ } else {
+ /* Otherwise, the remote side closed the connection normally. */
+ if (!raw->sent_console_eof && seat_eof(raw->seat)) {
+ /*
+ * The front end wants us to close the outgoing side of the
+ * connection as soon as we see EOF from the far end.
+ */
+ if (!raw->sent_socket_eof) {
+ if (raw->s)
+ sk_write_eof(raw->s);
+ raw->sent_socket_eof= true;
+ }
+ }
+ raw->sent_console_eof = true;
+ raw_check_close(raw);
+ }
+}
+
+static void raw_receive(Plug *plug, int urgent, const char *data, size_t len)
+{
+ Raw *raw = container_of(plug, Raw, plug);
+ c_write(raw, data, len);
+}
+
+static void raw_sent(Plug *plug, size_t bufsize)
+{
+ Raw *raw = container_of(plug, Raw, plug);
+ raw->bufsize = bufsize;
+ seat_sent(raw->seat, raw->bufsize);
+}
+
+static const PlugVtable Raw_plugvt = {
+ .log = raw_log,
+ .closing = raw_closing,
+ .receive = raw_receive,
+ .sent = raw_sent,
+};
+
+static char *raw_description(Interactor *itr)
+{
+ Raw *raw = container_of(itr, Raw, interactor);
+ return dupstr(raw->description);
+}
+
+static LogPolicy *raw_logpolicy(Interactor *itr)
+{
+ Raw *raw = container_of(itr, Raw, interactor);
+ return log_get_policy(raw->logctx);
+}
+
+static Seat *raw_get_seat(Interactor *itr)
+{
+ Raw *raw = container_of(itr, Raw, interactor);
+ return raw->seat;
+}
+
+static void raw_set_seat(Interactor *itr, Seat *seat)
+{
+ Raw *raw = container_of(itr, Raw, interactor);
+ raw->seat = seat;
+}
+
+static const InteractorVtable Raw_interactorvt = {
+ .description = raw_description,
+ .logpolicy = raw_logpolicy,
+ .get_seat = raw_get_seat,
+ .set_seat = raw_set_seat,
+};
+
+/*
+ * Called to set up the raw connection.
+ *
+ * Returns an error message, or NULL on success.
+ *
+ * Also places the canonical host name into `realhost'. It must be
+ * freed by the caller.
+ */
+static char *raw_init(const BackendVtable *vt, Seat *seat,
+ Backend **backend_handle, LogContext *logctx,
+ Conf *conf, const char *host, int port,
+ char **realhost, bool nodelay, bool keepalive)
+{
+ SockAddr *addr;
+ const char *err;
+ Raw *raw;
+ int addressfamily;
+ char *loghost;
+
+ raw = snew(Raw);
+ memset(raw, 0, sizeof(Raw));
+ raw->plug.vt = &Raw_plugvt;
+ raw->backend.vt = vt;
+ raw->interactor.vt = &Raw_interactorvt;
+ raw->backend.interactor = &raw->interactor;
+ raw->s = NULL;
+ raw->closed_on_socket_error = false;
+ *backend_handle = &raw->backend;
+ raw->sent_console_eof = raw->sent_socket_eof = false;
+ raw->bufsize = 0;
+ raw->socket_connected = false;
+ raw->conf = conf_copy(conf);
+ raw->description = default_description(vt, host, port);
+
+ raw->seat = seat;
+ raw->logctx = logctx;
+
+ addressfamily = conf_get_int(conf, CONF_addressfamily);
+ /*
+ * Try to find host.
+ */
+ addr = name_lookup(host, port, realhost, conf, addressfamily,
+ raw->logctx, "main connection");
+ if ((err = sk_addr_error(addr)) != NULL) {
+ sk_addr_free(addr);
+ return dupstr(err);
+ }
+
+ if (port < 0)
+ port = 23; /* default telnet port */
+
+ /*
+ * Open socket.
+ */
+ raw->s = new_connection(addr, *realhost, port, false, true, nodelay,
+ keepalive, &raw->plug, conf, &raw->interactor);
+ if ((err = sk_socket_error(raw->s)) != NULL)
+ return dupstr(err);
+
+ /* No local authentication phase in this protocol */
+ seat_set_trust_status(raw->seat, false);
+
+ loghost = conf_get_str(conf, CONF_loghost);
+ if (*loghost) {
+ char *colon;
+
+ sfree(*realhost);
+ *realhost = dupstr(loghost);
+
+ colon = host_strrchr(*realhost, ':');
+ if (colon)
+ *colon++ = '\0';
+ }
+
+ return NULL;
+}
+
+static void raw_free(Backend *be)
+{
+ Raw *raw = container_of(be, Raw, backend);
+
+ if (is_tempseat(raw->seat))
+ tempseat_free(raw->seat);
+ if (raw->s)
+ sk_close(raw->s);
+ conf_free(raw->conf);
+ sfree(raw->description);
+ sfree(raw);
+}
+
+/*
+ * Stub routine (we don't have any need to reconfigure this backend).
+ */
+static void raw_reconfig(Backend *be, Conf *conf)
+{
+}
+
+/*
+ * Called to send data down the raw connection.
+ */
+static void raw_send(Backend *be, const char *buf, size_t len)
+{
+ Raw *raw = container_of(be, Raw, backend);
+
+ if (raw->s == NULL)
+ return;
+
+ raw->bufsize = sk_write(raw->s, buf, len);
+}
+
+/*
+ * Called to query the current socket sendability status.
+ */
+static size_t raw_sendbuffer(Backend *be)
+{
+ Raw *raw = container_of(be, Raw, backend);
+ return raw->bufsize;
+}
+
+/*
+ * Called to set the size of the window
+ */
+static void raw_size(Backend *be, int width, int height)
+{
+ /* Do nothing! */
+ return;
+}
+
+/*
+ * Send raw special codes. We only handle outgoing EOF here.
+ */
+static void raw_special(Backend *be, SessionSpecialCode code, int arg)
+{
+ Raw *raw = container_of(be, Raw, backend);
+ if (code == SS_EOF && raw->s) {
+ sk_write_eof(raw->s);
+ raw->sent_socket_eof= true;
+ raw_check_close(raw);
+ }
+
+ return;
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const SessionSpecial *raw_get_specials(Backend *be)
+{
+ return NULL;
+}
+
+static bool raw_connected(Backend *be)
+{
+ Raw *raw = container_of(be, Raw, backend);
+ return raw->s != NULL;
+}
+
+static bool raw_sendok(Backend *be)
+{
+ Raw *raw = container_of(be, Raw, backend);
+ return raw->socket_connected;
+}
+
+static void raw_unthrottle(Backend *be, size_t backlog)
+{
+ Raw *raw = container_of(be, Raw, backend);
+ sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG);
+}
+
+static bool raw_ldisc(Backend *be, int option)
+{
+ if (option == LD_EDIT || option == LD_ECHO)
+ return true;
+ return false;
+}
+
+static void raw_provide_ldisc(Backend *be, Ldisc *ldisc)
+{
+ Raw *raw = container_of(be, Raw, backend);
+ raw->ldisc = ldisc;
+}
+
+static int raw_exitcode(Backend *be)
+{
+ Raw *raw = container_of(be, Raw, backend);
+ if (raw->s != NULL)
+ return -1; /* still connected */
+ else if (raw->closed_on_socket_error)
+ return INT_MAX; /* a socket error counts as an unclean exit */
+ else
+ /* Exit codes are a meaningless concept in the Raw protocol */
+ return 0;
+}
+
+/*
+ * cfg_info for Raw does nothing at all.
+ */
+static int raw_cfg_info(Backend *be)
+{
+ return 0;
+}
+
+const BackendVtable raw_backend = {
+ .init = raw_init,
+ .free = raw_free,
+ .reconfig = raw_reconfig,
+ .send = raw_send,
+ .sendbuffer = raw_sendbuffer,
+ .size = raw_size,
+ .special = raw_special,
+ .get_specials = raw_get_specials,
+ .connected = raw_connected,
+ .exitcode = raw_exitcode,
+ .sendok = raw_sendok,
+ .ldisc_option_state = raw_ldisc,
+ .provide_ldisc = raw_provide_ldisc,
+ .unthrottle = raw_unthrottle,
+ .cfg_info = raw_cfg_info,
+ .id = "raw",
+ .displayname_tc = "Raw",
+ .displayname_lc = "raw",
+ .protocol = PROT_RAW,
+ .default_port = 0,
+};
diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c
new file mode 100644
index 00000000..37087257
--- /dev/null
+++ b/otherbackends/rlogin.c
@@ -0,0 +1,496 @@
+/*
+ * Rlogin backend.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <ctype.h>
+
+#include "putty.h"
+
+#define RLOGIN_MAX_BACKLOG 4096
+
+typedef struct Rlogin Rlogin;
+struct Rlogin {
+ Socket *s;
+ bool closed_on_socket_error;
+ int bufsize;
+ bool socket_connected;
+ bool firstbyte;
+ bool cansize;
+ int term_width, term_height;
+ Seat *seat;
+ LogContext *logctx;
+ Ldisc *ldisc;
+ char *description;
+
+ Conf *conf;
+
+ /* In case we need to read a username from the terminal before starting */
+ prompts_t *prompt;
+
+ Plug plug;
+ Backend backend;
+ Interactor interactor;
+};
+
+static void rlogin_startup(Rlogin *rlogin, SeatPromptResult spr,
+ const char *ruser);
+static void rlogin_try_username_prompt(void *ctx);
+
+static void c_write(Rlogin *rlogin, const void *buf, size_t len)
+{
+ size_t backlog = seat_stdout(rlogin->seat, buf, len);
+ sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG);
+}
+
+static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
+ const char *error_msg, int error_code)
+{
+ Rlogin *rlogin = container_of(plug, Rlogin, plug);
+ backend_socket_log(rlogin->seat, rlogin->logctx, type, addr, port,
+ error_msg, error_code,
+ rlogin->conf, rlogin->socket_connected);
+ if (type == PLUGLOG_CONNECT_SUCCESS) {
+ rlogin->socket_connected = true;
+
+ char *ruser = get_remote_username(rlogin->conf);
+ if (ruser) {
+ /*
+ * If we already know the remote username, call
+ * rlogin_startup, which will send the initial protocol
+ * greeting including local username, remote username,
+ * terminal type and terminal speed.
+ */
+ /* Next terminal output will come from server */
+ seat_set_trust_status(rlogin->seat, false);
+ rlogin_startup(rlogin, SPR_OK, ruser);
+ sfree(ruser);
+ } else {
+ /*
+ * Otherwise, set up a prompts_t asking for the local
+ * username. If it completes synchronously, call
+ * rlogin_startup as above; otherwise, wait until it does.
+ */
+ rlogin->prompt = new_prompts();
+ rlogin->prompt->to_server = true;
+ rlogin->prompt->from_server = false;
+ rlogin->prompt->name = dupstr("Rlogin login name");
+ rlogin->prompt->callback = rlogin_try_username_prompt;
+ rlogin->prompt->callback_ctx = rlogin;
+ add_prompt(rlogin->prompt, dupstr("rlogin username: "), true);
+ rlogin_try_username_prompt(rlogin);
+ }
+ }
+}
+
+static void rlogin_closing(Plug *plug, PlugCloseType type,
+ const char *error_msg)
+{
+ Rlogin *rlogin = container_of(plug, Rlogin, plug);
+
+ /*
+ * We don't implement independent EOF in each direction for Telnet
+ * connections; as soon as we get word that the remote side has
+ * sent us EOF, we wind up the whole connection.
+ */
+
+ if (rlogin->s) {
+ sk_close(rlogin->s);
+ rlogin->s = NULL;
+ if (error_msg)
+ rlogin->closed_on_socket_error = true;
+ seat_notify_remote_exit(rlogin->seat);
+ seat_notify_remote_disconnect(rlogin->seat);
+ }
+ if (type != PLUGCLOSE_NORMAL) {
+ /* A socket error has occurred. */
+ logevent(rlogin->logctx, error_msg);
+ if (type != PLUGCLOSE_USER_ABORT)
+ seat_connection_fatal(rlogin->seat, "%s", error_msg);
+ }
+ /* Otherwise, the remote side closed the connection normally. */
+}
+
+static void rlogin_receive(
+ Plug *plug, int urgent, const char *data, size_t len)
+{
+ Rlogin *rlogin = container_of(plug, Rlogin, plug);
+ if (len == 0)
+ return;
+ if (urgent == 2) {
+ char c;
+
+ c = *data++;
+ len--;
+ if (c == '\x80') {
+ rlogin->cansize = true;
+ backend_size(&rlogin->backend,
+ rlogin->term_width, rlogin->term_height);
+ }
+ /*
+ * We should flush everything (aka Telnet SYNCH) if we see
+ * 0x02, and we should turn off and on _local_ flow control
+ * on 0x10 and 0x20 respectively. I'm not convinced it's
+ * worth it...
+ */
+ } else {
+ /*
+ * Main rlogin protocol. This is really simple: the first
+ * byte is expected to be NULL and is ignored, and the rest
+ * is printed.
+ */
+ if (rlogin->firstbyte) {
+ if (data[0] == '\0') {
+ data++;
+ len--;
+ }
+ rlogin->firstbyte = false;
+ }
+ if (len > 0)
+ c_write(rlogin, data, len);
+ }
+}
+
+static void rlogin_sent(Plug *plug, size_t bufsize)
+{
+ Rlogin *rlogin = container_of(plug, Rlogin, plug);
+ rlogin->bufsize = bufsize;
+ seat_sent(rlogin->seat, rlogin->bufsize);
+}
+
+static void rlogin_startup(Rlogin *rlogin, SeatPromptResult spr,
+ const char *ruser)
+{
+ char z = 0;
+ char *p;
+
+ if (spr.kind == SPRK_USER_ABORT) {
+ /* User aborted at the username prompt. */
+ sk_close(rlogin->s);
+ rlogin->s = NULL;
+ seat_notify_remote_exit(rlogin->seat);
+ } else if (spr.kind == SPRK_SW_ABORT) {
+ /* Something else went wrong at the username prompt, so we
+ * have to show some kind of error. */
+ sk_close(rlogin->s);
+ rlogin->s = NULL;
+ char *err = spr_get_error_message(spr);
+ seat_connection_fatal(rlogin->seat, "%s", err);
+ sfree(err);
+ } else {
+ sk_write(rlogin->s, &z, 1);
+ p = conf_get_str(rlogin->conf, CONF_localusername);
+ sk_write(rlogin->s, p, strlen(p));
+ sk_write(rlogin->s, &z, 1);
+ sk_write(rlogin->s, ruser, strlen(ruser));
+ sk_write(rlogin->s, &z, 1);
+ p = conf_get_str(rlogin->conf, CONF_termtype);
+ sk_write(rlogin->s, p, strlen(p));
+ sk_write(rlogin->s, "/", 1);
+ p = conf_get_str(rlogin->conf, CONF_termspeed);
+ sk_write(rlogin->s, p, strspn(p, "0123456789"));
+ rlogin->bufsize = sk_write(rlogin->s, &z, 1);
+ }
+
+ rlogin->prompt = NULL;
+ if (rlogin->ldisc)
+ ldisc_check_sendok(rlogin->ldisc);
+}
+
+static const PlugVtable Rlogin_plugvt = {
+ .log = rlogin_log,
+ .closing = rlogin_closing,
+ .receive = rlogin_receive,
+ .sent = rlogin_sent,
+};
+
+static char *rlogin_description(Interactor *itr)
+{
+ Rlogin *rlogin = container_of(itr, Rlogin, interactor);
+ return dupstr(rlogin->description);
+}
+
+static LogPolicy *rlogin_logpolicy(Interactor *itr)
+{
+ Rlogin *rlogin = container_of(itr, Rlogin, interactor);
+ return log_get_policy(rlogin->logctx);
+}
+
+static Seat *rlogin_get_seat(Interactor *itr)
+{
+ Rlogin *rlogin = container_of(itr, Rlogin, interactor);
+ return rlogin->seat;
+}
+
+static void rlogin_set_seat(Interactor *itr, Seat *seat)
+{
+ Rlogin *rlogin = container_of(itr, Rlogin, interactor);
+ rlogin->seat = seat;
+}
+
+static const InteractorVtable Rlogin_interactorvt = {
+ .description = rlogin_description,
+ .logpolicy = rlogin_logpolicy,
+ .get_seat = rlogin_get_seat,
+ .set_seat = rlogin_set_seat,
+};
+
+/*
+ * Called to set up the rlogin connection.
+ *
+ * Returns an error message, or NULL on success.
+ *
+ * Also places the canonical host name into `realhost'. It must be
+ * freed by the caller.
+ */
+static char *rlogin_init(const BackendVtable *vt, Seat *seat,
+ Backend **backend_handle, LogContext *logctx,
+ Conf *conf, const char *host, int port,
+ char **realhost, bool nodelay, bool keepalive)
+{
+ SockAddr *addr;
+ const char *err;
+ Rlogin *rlogin;
+ int addressfamily;
+ char *loghost;
+
+ rlogin = snew(Rlogin);
+ memset(rlogin, 0, sizeof(Rlogin));
+ rlogin->plug.vt = &Rlogin_plugvt;
+ rlogin->backend.vt = vt;
+ rlogin->interactor.vt = &Rlogin_interactorvt;
+ rlogin->backend.interactor = &rlogin->interactor;
+ rlogin->s = NULL;
+ rlogin->closed_on_socket_error = false;
+ rlogin->seat = seat;
+ rlogin->logctx = logctx;
+ rlogin->term_width = conf_get_int(conf, CONF_width);
+ rlogin->term_height = conf_get_int(conf, CONF_height);
+ rlogin->socket_connected = false;
+ rlogin->firstbyte = true;
+ rlogin->cansize = false;
+ rlogin->prompt = NULL;
+ rlogin->conf = conf_copy(conf);
+ rlogin->description = default_description(vt, host, port);
+ *backend_handle = &rlogin->backend;
+
+ addressfamily = conf_get_int(conf, CONF_addressfamily);
+ /*
+ * Try to find host.
+ */
+ addr = name_lookup(host, port, realhost, conf, addressfamily,
+ rlogin->logctx, "rlogin connection");
+ if ((err = sk_addr_error(addr)) != NULL) {
+ sk_addr_free(addr);
+ return dupstr(err);
+ }
+
+ if (port < 0)
+ port = 513; /* default rlogin port */
+
+ /*
+ * Open socket.
+ */
+ rlogin->s = new_connection(addr, *realhost, port, true, false,
+ nodelay, keepalive, &rlogin->plug, conf,
+ &rlogin->interactor);
+ if ((err = sk_socket_error(rlogin->s)) != NULL)
+ return dupstr(err);
+
+ loghost = conf_get_str(conf, CONF_loghost);
+ if (*loghost) {
+ char *colon;
+
+ sfree(*realhost);
+ *realhost = dupstr(loghost);
+
+ colon = host_strrchr(*realhost, ':');
+ if (colon)
+ *colon++ = '\0';
+ }
+
+ return NULL;
+}
+
+static void rlogin_free(Backend *be)
+{
+ Rlogin *rlogin = container_of(be, Rlogin, backend);
+
+ if (is_tempseat(rlogin->seat))
+ tempseat_free(rlogin->seat);
+ if (rlogin->prompt)
+ free_prompts(rlogin->prompt);
+ if (rlogin->s)
+ sk_close(rlogin->s);
+ conf_free(rlogin->conf);
+ sfree(rlogin->description);
+ sfree(rlogin);
+}
+
+/*
+ * Stub routine (we don't have any need to reconfigure this backend).
+ */
+static void rlogin_reconfig(Backend *be, Conf *conf)
+{
+}
+
+static void rlogin_try_username_prompt(void *ctx)
+{
+ Rlogin *rlogin = (Rlogin *)ctx;
+
+ SeatPromptResult spr = seat_get_userpass_input(
+ interactor_announce(&rlogin->interactor), rlogin->prompt);
+ if (spr.kind == SPRK_INCOMPLETE)
+ return;
+
+ /* Next terminal output will come from server */
+ seat_set_trust_status(rlogin->seat, false);
+
+ /* Send the rlogin setup protocol data, and then we're ready to
+ * start receiving normal input to send down the wire, which
+ * rlogin_startup will signal to rlogin_sendok by nulling out
+ * rlogin->prompt. */
+ rlogin_startup(
+ rlogin, spr, prompt_get_result_ref(rlogin->prompt->prompts[0]));
+}
+
+/*
+ * Called to send data down the rlogin connection.
+ */
+static void rlogin_send(Backend *be, const char *buf, size_t len)
+{
+ Rlogin *rlogin = container_of(be, Rlogin, backend);
+
+ if (rlogin->s == NULL)
+ return;
+
+ rlogin->bufsize = sk_write(rlogin->s, buf, len);
+}
+
+/*
+ * Called to query the current socket sendability status.
+ */
+static size_t rlogin_sendbuffer(Backend *be)
+{
+ Rlogin *rlogin = container_of(be, Rlogin, backend);
+ return rlogin->bufsize;
+}
+
+/*
+ * Called to set the size of the window
+ */
+static void rlogin_size(Backend *be, int width, int height)
+{
+ Rlogin *rlogin = container_of(be, Rlogin, backend);
+ char b[12] = { '\xFF', '\xFF', 0x73, 0x73, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ rlogin->term_width = width;
+ rlogin->term_height = height;
+
+ if (rlogin->s == NULL || !rlogin->cansize)
+ return;
+
+ b[6] = rlogin->term_width >> 8;
+ b[7] = rlogin->term_width & 0xFF;
+ b[4] = rlogin->term_height >> 8;
+ b[5] = rlogin->term_height & 0xFF;
+ rlogin->bufsize = sk_write(rlogin->s, b, 12);
+ return;
+}
+
+/*
+ * Send rlogin special codes.
+ */
+static void rlogin_special(Backend *be, SessionSpecialCode code, int arg)
+{
+ /* Do nothing! */
+ return;
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const SessionSpecial *rlogin_get_specials(Backend *be)
+{
+ return NULL;
+}
+
+static bool rlogin_connected(Backend *be)
+{
+ Rlogin *rlogin = container_of(be, Rlogin, backend);
+ return rlogin->s != NULL;
+}
+
+static bool rlogin_sendok(Backend *be)
+{
+ /*
+ * We only want to receive input data if the socket is connected
+ * and we're not still at the username prompt stage.
+ */
+ Rlogin *rlogin = container_of(be, Rlogin, backend);
+ return rlogin->socket_connected && !rlogin->prompt;
+}
+
+static void rlogin_unthrottle(Backend *be, size_t backlog)
+{
+ Rlogin *rlogin = container_of(be, Rlogin, backend);
+ sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG);
+}
+
+static bool rlogin_ldisc(Backend *be, int option)
+{
+ /* Rlogin *rlogin = container_of(be, Rlogin, backend); */
+ return false;
+}
+
+static void rlogin_provide_ldisc(Backend *be, Ldisc *ldisc)
+{
+ Rlogin *rlogin = container_of(be, Rlogin, backend);
+ rlogin->ldisc = ldisc;
+}
+
+static int rlogin_exitcode(Backend *be)
+{
+ Rlogin *rlogin = container_of(be, Rlogin, backend);
+ if (rlogin->s != NULL)
+ return -1; /* still connected */
+ else if (rlogin->closed_on_socket_error)
+ return INT_MAX; /* a socket error counts as an unclean exit */
+ else
+ /* If we ever implement RSH, we'll probably need to do this properly */
+ return 0;
+}
+
+/*
+ * cfg_info for rlogin does nothing at all.
+ */
+static int rlogin_cfg_info(Backend *be)
+{
+ return 0;
+}
+
+const BackendVtable rlogin_backend = {
+ .init = rlogin_init,
+ .free = rlogin_free,
+ .reconfig = rlogin_reconfig,
+ .send = rlogin_send,
+ .sendbuffer = rlogin_sendbuffer,
+ .size = rlogin_size,
+ .special = rlogin_special,
+ .get_specials = rlogin_get_specials,
+ .connected = rlogin_connected,
+ .exitcode = rlogin_exitcode,
+ .sendok = rlogin_sendok,
+ .ldisc_option_state = rlogin_ldisc,
+ .provide_ldisc = rlogin_provide_ldisc,
+ .unthrottle = rlogin_unthrottle,
+ .cfg_info = rlogin_cfg_info,
+ .id = "rlogin",
+ .displayname_tc = "Rlogin",
+ .displayname_lc = "Rlogin", /* proper name, so capitalise it anyway */
+ .protocol = PROT_RLOGIN,
+ .default_port = 513,
+};
diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c
new file mode 100644
index 00000000..9aaf1d4f
--- /dev/null
+++ b/otherbackends/supdup.c
@@ -0,0 +1,978 @@
+/*
+ * Supdup backend
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "putty.h"
+
+/*
+ * TTYOPT FUNCTION BITS (36-bit bitmasks)
+ */
+#define TOALT 0200000000000LL // Characters 0175 and 0176 are converted to altmode (0033) on input
+#define TOCLC 0100000000000LL // (user option bit) Convert lower-case input to upper-case
+#define TOERS 0040000000000LL // Selective erase is supported
+#define TOMVB 0010000000000LL // Backspacing is supported
+#define TOSAI 0004000000000LL // Stanford/ITS extended ASCII graphics character set is supported
+#define TOSA1 0002000000000LL // (user option bit) Characters 0001-0037 displayed using Stanford/ITS chars
+#define TOOVR 0001000000000LL // Overprinting is supported
+#define TOMVU 0000400000000LL // Moving cursor upwards is supported
+#define TOMOR 0000200000000LL // (user option bit) System should provide **MORE** processing
+#define TOROL 0000100000000LL // (user option bit) Terminal should scroll instead of wrapping
+#define TOLWR 0000020000000LL // Lowercase characters are supported
+#define TOFCI 0000010000000LL // Terminal can generate CONTROL and META characters
+#define TOLID 0000002000000LL // Line insert/delete operations supported
+#define TOCID 0000001000000LL // Character insert/delete operations supported
+#define TPCBS 0000000000040LL // Terminal is using the "intelligent terminal protocol" (must be on)
+#define TPORS 0000000000010LL // Server should process output resets
+
+// Initialization words (36-bit constants)
+#define WORDS 0777773000000 // Negative number of config words to send (6) in high 18 bits
+#define TCTYP 0000000000007 // Defines the terminal type (MUST be 7)
+#define TTYROL 0000000000001 // Scroll amount for terminal (1 line at a time)
+
+
+// %TD opcodes
+//
+#define TDMOV 0200 // Cursor positioning
+#define TDMV1 0201 // Internal cursor positioning
+#define TDEOF 0202 // Erase to end of screen
+#define TDEOL 0203 // Erase to end of line
+#define TDDLF 0204 // Clear the character the cursor is on
+#define TDCRL 0207 // Carriage return
+#define TDNOP 0210 // No-op; should be ignored.
+#define TDBS 0211 // Backspace (not in official SUPDUP spec)
+#define TDLF 0212 // Linefeed (not in official SUPDUP spec)
+#define TDCR 0213 // Carriage Return (ditto)
+#define TDORS 0214 // Output reset
+#define TDQOT 0215 // Quotes the following character
+#define TDFS 0216 // Non-destructive forward space
+#define TDMV0 0217 // General cursor positioning code
+#define TDCLR 0220 // Erase the screen, home cursor
+#define TDBEL 0221 // Generate an audio tone, bell, whatever
+#define TDILP 0223 // Insert blank lines at the cursor
+#define TDDLP 0224 // Delete lines at the cursor
+#define TDICP 0225 // Insert blanks at cursor
+#define TDDCP 0226 // Delete characters at cursor
+#define TDBOW 0227 // Display black chars on white screen
+#define TDRST 0230 // Reset %TDBOW
+
+/* Maximum number of octets following a %TD code. */
+#define TD_ARGS_MAX 4
+
+typedef struct supdup_tag Supdup;
+struct supdup_tag
+{
+ Socket *s;
+ bool socket_connected;
+ bool closed_on_socket_error;
+
+ Seat *seat;
+ LogContext *logctx;
+ Ldisc *ldisc;
+ int term_width, term_height;
+ char *description;
+
+ long long ttyopt;
+ long tcmxv;
+ long tcmxh;
+
+ bool sent_location;
+
+ Conf *conf;
+
+ int bufsize;
+
+ enum {
+ CONNECTING, // waiting for %TDNOP from server after sending connection params
+ CONNECTED // %TDNOP received, connected.
+ } state;
+
+ enum {
+ TD_TOPLEVEL,
+ TD_ARGS,
+ TD_ARGSDONE
+ } tdstate;
+
+ int td_code;
+ int td_argcount;
+ char td_args[TD_ARGS_MAX];
+ int td_argindex;
+
+ void (*print) (strbuf *outbuf, int c);
+
+ Pinger *pinger;
+
+ Plug plug;
+ Backend backend;
+ Interactor interactor;
+};
+
+#define SUPDUP_MAX_BACKLOG 4096
+
+static void c_write(Supdup *supdup, unsigned char *buf, int len)
+{
+ size_t backlog = seat_stdout(supdup->seat, buf, len);
+ sk_set_frozen(supdup->s, backlog > SUPDUP_MAX_BACKLOG);
+}
+
+static void supdup_send_location(Supdup *supdup)
+{
+ char locHeader[] = { 0300, 0302 };
+ char* locString = conf_get_str(supdup->conf, CONF_supdup_location);
+
+ sk_write(supdup->s, locHeader, sizeof(locHeader));
+ sk_write(supdup->s, locString, strlen(locString) + 1); // include NULL terminator
+}
+
+static void print_ascii(strbuf *outbuf, int c)
+{
+ /* In ASCII mode, ignore control characters. The server shouldn't
+ send them. */
+ if (c >= 040 && c < 0177)
+ put_byte (outbuf, c);
+}
+
+static void print_its(strbuf *outbuf, int c)
+{
+ /* The ITS character set is documented in RFC 734. */
+ static const char *map[] = {
+ "\xc2\xb7", "\342\206\223", "\316\261", "\316\262",
+ "\342\210\247", "\302\254", "\316\265", "\317\200",
+ "\316\273", "\xce\xb3", "\xce\xb4", "\xe2\x86\x91",
+ "\xc2\xb1", "\xe2\x8a\x95", "\342\210\236", "\342\210\202",
+ "\342\212\202", "\342\212\203", "\342\210\251", "\342\210\252",
+ "\342\210\200", "\342\210\203", "\xe2\x8a\x97", "\342\206\224",
+ "\xe2\x86\x90", "\342\206\222", "\xe2\x89\xa0", "\xe2\x97\x8a",
+ "\342\211\244", "\342\211\245", "\342\211\241", "\342\210\250",
+ " ", "!", "\"", "#", "$", "%", "&", "'",
+ "(", ")", "*", "+", ",", "-", ".", "/",
+ "0", "1", "2", "3", "4", "5", "6", "7",
+ "8", "9", ":", ";", "<", "=", ">", "?",
+ "@", "A", "B", "C", "D", "E", "F", "G",
+ "H", "I", "J", "K", "L", "M", "N", "O",
+ "P", "Q", "R", "S", "T", "U", "V", "W",
+ "X", "Y", "Z", "[", "\\", "]", "^", "_",
+ "`", "a", "b", "c", "d", "e", "f", "g",
+ "h", "i", "j", "k", "l", "m", "n", "o",
+ "p", "q", "r", "s", "t", "u", "v", "w",
+ "x", "y", "z", "{", "|", "}", "~", "\xe2\x88\xab"
+ };
+
+ put_data (outbuf, map[c], strlen(map[c]));
+}
+
+static void print_waits(strbuf *outbuf, int c)
+{
+ /* The WAITS character set used at the Stanford AI Lab is documented
+ here: https://www.saildart.org/allow/sail-charset-utf8.html */
+ static const char *map[] = {
+ "", "\342\206\223", "\316\261", "\316\262",
+ "\342\210\247", "\302\254", "\316\265", "\317\200",
+ "\316\273", "", "", "",
+ "", "", "\342\210\236", "\342\210\202",
+ "\342\212\202", "\342\212\203", "\342\210\251", "\342\210\252",
+ "\342\210\200", "\342\210\203", "\xe2\x8a\x97", "\342\206\224",
+ "_", "\342\206\222", "~", "\xe2\x89\xa0",
+ "\342\211\244", "\342\211\245", "\342\211\241", "\342\210\250",
+ " ", "!", "\"", "#", "$", "%", "&", "'",
+ "(", ")", "*", "+", ",", "-", ".", "/",
+ "0", "1", "2", "3", "4", "5", "6", "7",
+ "8", "9", ":", ";", "<", "=", ">", "?",
+ "@", "A", "B", "C", "D", "E", "F", "G",
+ "H", "I", "J", "K", "L", "M", "N", "O",
+ "P", "Q", "R", "S", "T", "U", "V", "W",
+ "X", "Y", "Z", "[", "\\", "]", "\xe2\x86\x91", "\xe2\x86\x90",
+ "`", "a", "b", "c", "d", "e", "f", "g",
+ "h", "i", "j", "k", "l", "m", "n", "o",
+ "p", "q", "r", "s", "t", "u", "v", "w",
+ "x", "y", "z", "{", "|", "\xe2\x97\x8a", "}", ""
+ };
+
+ put_data (outbuf, map[c], strlen(map[c]));
+}
+
+static void do_toplevel(Supdup *supdup, strbuf *outbuf, int c)
+{
+ // Toplevel: Waiting for a %TD code or a printable character
+ if (c >= 0200) {
+ // Handle SUPDUP %TD codes (codes greater than or equal to 200)
+ supdup->td_argindex = 0;
+ supdup->td_code = c;
+ switch (c) {
+ case TDMOV:
+ // %TD codes using 4 arguments
+ supdup->td_argcount = 4;
+ supdup->tdstate = TD_ARGS;
+ break;
+
+ case TDMV0:
+ case TDMV1:
+ // %TD codes using 2 arguments
+ supdup->td_argcount = 2;
+ supdup->tdstate = TD_ARGS;
+ break;
+
+ case TDQOT:
+ case TDILP:
+ case TDDLP:
+ case TDICP:
+ case TDDCP:
+ // %TD codes using 1 argument
+ supdup->td_argcount = 1;
+ supdup->tdstate = TD_ARGS;
+ break;
+
+ case TDEOF:
+ case TDEOL:
+ case TDDLF:
+ case TDCRL:
+ case TDNOP:
+ case TDORS:
+ case TDFS:
+ case TDCLR:
+ case TDBEL:
+ case TDBOW:
+ case TDRST:
+ case TDBS:
+ case TDCR:
+ case TDLF:
+ // %TD codes using 0 arguments
+ supdup->td_argcount = 0;
+ supdup->tdstate = TD_ARGSDONE;
+ break;
+
+ default:
+ // Unhandled, ignore
+ break;
+ }
+ } else {
+ supdup->print(outbuf, c);
+ }
+}
+
+static void do_args(Supdup *supdup, strbuf *outbuf, int c)
+{
+ // Collect up args for %TD code
+ if (supdup->td_argindex < TD_ARGS_MAX) {
+ supdup->td_args[supdup->td_argindex] = c;
+ supdup->td_argindex++;
+
+ if (supdup->td_argcount == supdup->td_argindex) {
+ // No more args, %TD code is ready to go.
+ supdup->tdstate = TD_ARGSDONE;
+ }
+ } else {
+ // Should never hit this state, if we do we will just
+ // return to TOPLEVEL.
+ supdup->tdstate = TD_TOPLEVEL;
+ }
+}
+
+static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c)
+{
+ char buf[4];
+ int x, y;
+
+ // Arguments for %TD code have been collected; dispatch based
+ // on the %TD code we're handling.
+ switch (supdup->td_code) {
+ case TDMOV:
+ /*
+ General cursor position code. Followed by four bytes;
+ the first two are the "old" vertical and horizontal
+ positions and may be ignored. The next two are the new
+ vertical and horizontal positions. The cursor should be
+ moved to this position.
+ */
+
+ // We only care about the new position.
+ put_fmt(outbuf, "\033[%d;%dH", supdup->td_args[2]+1, supdup->td_args[3]+1);
+ break;
+
+ case TDMV0:
+ case TDMV1:
+ /*
+ General cursor position code. Followed by two bytes;
+ the new vertical and horizontal positions.
+ */
+ put_fmt(outbuf, "\033[%d;%dH", supdup->td_args[0]+1, supdup->td_args[1]+1);
+ break;
+
+ case TDEOF:
+ /*
+ Erase to end of screen. This is an optional function
+ since many terminals do not support this. If the
+ terminal does not support this function, it should be
+ treated the same as %TDEOL.
+
+ %TDEOF does an erase to end of line, then erases all
+ lines lower on the screen than the cursor. The cursor
+ does not move.
+ */
+ put_fmt(outbuf, "\033[J");
+ break;
+
+ case TDEOL:
+ /*
+ Erase to end of line. This erases the character
+ position the cursor is at and all positions to the right
+ on the same line. The cursor does not move.
+ */
+ put_fmt(outbuf, "\033[K");
+ break;
+
+ case TDDLF:
+ /*
+ Clear the character position the cursor is on. The
+ cursor does not move.
+ */
+ put_fmt(outbuf, "\033[X");
+ break;
+
+ case TDCRL:
+ /*
+ If the cursor is not on the bottom line of the screen,
+ move cursor to the beginning of the next line and clear
+ that line. If the cursor is at the bottom line, scroll
+ up.
+ */
+ put_fmt(outbuf, "\015\012");
+ break;
+
+ case TDNOP:
+ /*
+ No-op; should be ignored.
+ */
+ break;
+
+ case TDORS:
+ /*
+ Output reset. This code serves as a data mark for
+ aborting output much as IAC DM does in the ordinary
+ TELNET protocol.
+ */
+ outbuf->len = 0;
+ if (!seat_get_cursor_position(supdup->seat, &x, &y))
+ x = y = 0;
+ buf[0] = 034;
+ buf[1] = 020;
+ buf[2] = y;
+ buf[3] = x;
+ sk_write(supdup->s, buf, 4);
+ break;
+
+ case TDQOT:
+ /*
+ Quotes the following character. This is used when
+ sending 8-bit codes which are not %TD codes, for
+ instance when loading programs into an intelligent
+ terminal. The following character should be passed
+ through intact to the terminal.
+ */
+
+ put_byte(outbuf, supdup->td_args[0]);
+ break;
+
+ case TDFS:
+ /*
+ Non-destructive forward space. The cursor moves right
+ one position; this code will not be sent at the end of a
+ line.
+ */
+
+ put_fmt(outbuf, "\033[C");
+ break;
+
+ case TDCLR:
+ /*
+ Erase the screen. Home the cursor to the top left hand
+ corner of the screen.
+ */
+ put_fmt(outbuf, "\033[2J\033[H");
+ break;
+
+ case TDBEL:
+ /*
+ Generate an audio tone, bell, whatever.
+ */
+
+ put_fmt(outbuf, "\007");
+ break;
+
+ case TDILP:
+ /*
+ Insert blank lines at the cursor; followed by a byte
+ containing a count of the number of blank lines to
+ insert. The cursor is unmoved. The line the cursor is
+ on and all lines below it move down; lines moved off the
+ bottom of the screen are lost.
+ */
+ put_fmt(outbuf, "\033[%dL", supdup->td_args[0]);
+ break;
+
+ case TDDLP:
+ /*
+ Delete lines at the cursor; followed by a count. The
+ cursor is unmoved. The first line deleted is the one
+ the cursor is on. Lines below those deleted move up.
+ Newly- created lines at the bottom of the screen are
+ blank.
+ */
+ put_fmt(outbuf, "\033[%dM", supdup->td_args[0]);
+ break;
+
+ case TDICP:
+ /*
+ Insert blank character positions at the cursor; followed
+ by a count. The cursor is unmoved. The character the
+ cursor is on and all characters to the right on the
+ current line move to the right; characters moved off the
+ end of the line are lost.
+ */
+ put_fmt(outbuf, "\033[%d@", supdup->td_args[0]);
+ break;
+
+ case TDDCP:
+ /*
+ Delete characters at the cursor; followed by a count.
+ The cursor is unmoved. The first character deleted is
+ the one the cursor is on. Newly-created characters at
+ the end of the line are blank.
+ */
+ put_fmt(outbuf, "\033[%dP", supdup->td_args[0]);
+ break;
+
+ case TDBOW:
+ case TDRST:
+ /*
+ Display black characters on white screen.
+ HIGHLY OPTIONAL.
+ */
+
+ // Since this is HIGHLY OPTIONAL, I'm not going
+ // to implement it yet.
+ break;
+
+ /*
+ * Non-standard (whatever "standard" means here) SUPDUP
+ * commands. These are used (at the very least) by
+ * Genera's SUPDUP implementation. Cannot find any
+ * official documentation, behavior is based on UNIX
+ * SUPDUP implementation from MIT.
+ */
+ case TDBS:
+ /*
+ * Backspace -- move cursor back one character (does not
+ * appear to wrap...)
+ */
+ put_byte(outbuf, '\010');
+ break;
+
+ case TDLF:
+ /*
+ * Linefeed -- move cursor down one line (again, no wrapping)
+ */
+ put_byte(outbuf, '\012');
+ break;
+
+ case TDCR:
+ /*
+ * Carriage return -- move cursor to start of current line.
+ */
+ put_byte(outbuf, '\015');
+ break;
+ }
+
+ // Return to top level to pick up the next %TD code or
+ // printable character.
+ supdup->tdstate = TD_TOPLEVEL;
+}
+
+static void term_out_supdup(Supdup *supdup, strbuf *outbuf, int c)
+{
+ if (supdup->tdstate == TD_TOPLEVEL) {
+ do_toplevel (supdup, outbuf, c);
+ } else if (supdup->tdstate == TD_ARGS) {
+ do_args (supdup, outbuf, c);
+ }
+
+ // If all arguments for a %TD code are ready, we will execute the code now.
+ if (supdup->tdstate == TD_ARGSDONE) {
+ do_argsdone (supdup, outbuf, c);
+ }
+}
+
+static void do_supdup_read(Supdup *supdup, const char *buf, size_t len)
+{
+ strbuf *outbuf = strbuf_new();
+
+ while (len--) {
+ int c = (unsigned char)*buf++;
+ switch (supdup->state) {
+ case CONNECTING:
+ // "Following the transmission of the terminal options by
+ // the user, the server should respond with an ASCII
+ // greeting message, terminated with a %TDNOP code..."
+ if (TDNOP == c) {
+ // Greeting done, switch to the CONNECTED state.
+ supdup->state = CONNECTED;
+ supdup->tdstate = TD_TOPLEVEL;
+ } else {
+ // Forward the greeting message (which is straight
+ // ASCII, no controls) on so it gets displayed TODO:
+ // filter out only printable chars?
+ put_byte(outbuf, c);
+ }
+ break;
+
+ case CONNECTED:
+ // "All transmissions from the server after the %TDNOP
+ // [see above] are either printing characters or virtual
+ // terminal display codes." Forward these on to the
+ // frontend which will decide what to do with them.
+ term_out_supdup(supdup, outbuf, c);
+ /*
+ * Hack to make Symbolics Genera SUPDUP happy: Wait until
+ * after we're connected (finished the initial handshake
+ * and have gotten additional data) before sending the
+ * location string. For some reason doing so earlier
+ * causes the Symbolics SUPDUP to end up in an odd state.
+ */
+ if (!supdup->sent_location) {
+ supdup_send_location(supdup);
+ supdup->sent_location = true;
+ }
+ break;
+ }
+
+ if (outbuf->len >= 4096) {
+ c_write(supdup, outbuf->u, outbuf->len);
+ outbuf->len = 0;
+ }
+ }
+
+ if (outbuf->len)
+ c_write(supdup, outbuf->u, outbuf->len);
+ strbuf_free(outbuf);
+}
+
+static void supdup_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
+ const char *error_msg, int error_code)
+{
+ Supdup *supdup = container_of(plug, Supdup, plug);
+ backend_socket_log(supdup->seat, supdup->logctx, type, addr, port,
+ error_msg, error_code,
+ supdup->conf, supdup->socket_connected);
+ if (type == PLUGLOG_CONNECT_SUCCESS) {
+ supdup->socket_connected = true;
+ if (supdup->ldisc)
+ ldisc_check_sendok(supdup->ldisc);
+ }
+}
+
+static void supdup_closing(Plug *plug, PlugCloseType type,
+ const char *error_msg)
+{
+ Supdup *supdup = container_of(plug, Supdup, plug);
+
+ /*
+ * We don't implement independent EOF in each direction for Telnet
+ * connections; as soon as we get word that the remote side has
+ * sent us EOF, we wind up the whole connection.
+ */
+
+ if (supdup->s) {
+ sk_close(supdup->s);
+ supdup->s = NULL;
+ if (error_msg)
+ supdup->closed_on_socket_error = true;
+ seat_notify_remote_exit(supdup->seat);
+ seat_notify_remote_disconnect(supdup->seat);
+ }
+ if (type != PLUGCLOSE_NORMAL) {
+ logevent(supdup->logctx, error_msg);
+ if (type != PLUGCLOSE_USER_ABORT)
+ seat_connection_fatal(supdup->seat, "%s", error_msg);
+ }
+ /* Otherwise, the remote side closed the connection normally. */
+}
+
+static void supdup_receive(Plug *plug, int urgent, const char *data, size_t len)
+{
+ Supdup *supdup = container_of(plug, Supdup, plug);
+ do_supdup_read(supdup, data, len);
+}
+
+static void supdup_sent(Plug *plug, size_t bufsize)
+{
+ Supdup *supdup = container_of(plug, Supdup, plug);
+ supdup->bufsize = bufsize;
+ seat_sent(supdup->seat, supdup->bufsize);
+}
+
+static void supdup_send_36bits(Supdup *supdup, unsigned long long thirtysix)
+{
+ //
+ // From RFC734:
+ // "Each word is sent through the 8-bit connection as six
+ // 6-bit bytes, most-significant first."
+ //
+ // Split the 36-bit word into 6 6-bit "bytes", packed into
+ // 8-bit bytes and send, most-significant byte first.
+ //
+ for (int i = 5; i >= 0; i--) {
+ char sixBits = (thirtysix >> (i * 6)) & 077;
+ sk_write(supdup->s, &sixBits, 1);
+ }
+}
+
+static void supdup_send_config(Supdup *supdup)
+{
+ supdup_send_36bits(supdup, WORDS); // negative length
+ supdup_send_36bits(supdup, TCTYP); // terminal type
+ supdup_send_36bits(supdup, supdup->ttyopt); // options
+ supdup_send_36bits(supdup, supdup->tcmxv); // height
+ supdup_send_36bits(supdup, supdup->tcmxh); // width
+ supdup_send_36bits(supdup, TTYROL); // scroll amount
+}
+
+static char *supdup_description(Interactor *itr)
+{
+ Supdup *supdup = container_of(itr, Supdup, interactor);
+ return dupstr(supdup->description);
+}
+
+static LogPolicy *supdup_logpolicy(Interactor *itr)
+{
+ Supdup *supdup = container_of(itr, Supdup, interactor);
+ return log_get_policy(supdup->logctx);
+}
+
+static Seat *supdup_get_seat(Interactor *itr)
+{
+ Supdup *supdup = container_of(itr, Supdup, interactor);
+ return supdup->seat;
+}
+
+static void supdup_set_seat(Interactor *itr, Seat *seat)
+{
+ Supdup *supdup = container_of(itr, Supdup, interactor);
+ supdup->seat = seat;
+}
+
+static const InteractorVtable Supdup_interactorvt = {
+ .description = supdup_description,
+ .logpolicy = supdup_logpolicy,
+ .get_seat = supdup_get_seat,
+ .set_seat = supdup_set_seat,
+};
+
+/*
+ * Called to set up the Supdup connection.
+ *
+ * Returns an error message, or NULL on success.
+ *
+ * Also places the canonical host name into `realhost'. It must be
+ * freed by the caller.
+ */
+static char *supdup_init(const BackendVtable *x, Seat *seat,
+ Backend **backend_handle,
+ LogContext *logctx, Conf *conf,
+ const char *host, int port, char **realhost,
+ bool nodelay, bool keepalive)
+{
+ static const PlugVtable fn_table = {
+ .log = supdup_log,
+ .closing = supdup_closing,
+ .receive = supdup_receive,
+ .sent = supdup_sent,
+ };
+ SockAddr *addr;
+ const char *err;
+ Supdup *supdup;
+ char *loghost;
+ int addressfamily;
+ const char *utf8 = "\033%G";
+
+ supdup = snew(struct supdup_tag);
+ memset(supdup, 0, sizeof(Supdup));
+ supdup->plug.vt = &fn_table;
+ supdup->backend.vt = &supdup_backend;
+ supdup->interactor.vt = &Supdup_interactorvt;
+ supdup->backend.interactor = &supdup->interactor;
+ supdup->logctx = logctx;
+ supdup->conf = conf_copy(conf);
+ supdup->s = NULL;
+ supdup->socket_connected = false;
+ supdup->closed_on_socket_error = false;
+ supdup->seat = seat;
+ supdup->term_width = conf_get_int(supdup->conf, CONF_width);
+ supdup->term_height = conf_get_int(supdup->conf, CONF_height);
+ supdup->pinger = NULL;
+ supdup->sent_location = false;
+ supdup->description = default_description(supdup->backend.vt, host, port);
+ *backend_handle = &supdup->backend;
+
+ switch (conf_get_int(supdup->conf, CONF_supdup_ascii_set)) {
+ case SUPDUP_CHARSET_ASCII:
+ supdup->print = print_ascii;
+ break;
+ case SUPDUP_CHARSET_ITS:
+ supdup->print = print_its;
+ break;
+ case SUPDUP_CHARSET_WAITS:
+ supdup->print = print_waits;
+ break;
+ }
+
+ /*
+ * Try to find host.
+ */
+ {
+ char *buf;
+ addressfamily = conf_get_int(supdup->conf, CONF_addressfamily);
+ buf = dupprintf("Looking up host \"%s\"%s", host,
+ (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
+ (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" :
+ "")));
+ logevent(supdup->logctx, buf);
+ sfree(buf);
+ }
+ addr = name_lookup(host, port, realhost, supdup->conf, addressfamily, NULL, "");
+ if ((err = sk_addr_error(addr)) != NULL) {
+ sk_addr_free(addr);
+ return dupstr(err);
+ }
+
+ if (port < 0)
+ port = 0137; /* default supdup port */
+
+ /*
+ * Open socket.
+ */
+ supdup->s = new_connection(addr, *realhost, port, false, true,
+ nodelay, keepalive, &supdup->plug, supdup->conf,
+ &supdup->interactor);
+ if ((err = sk_socket_error(supdup->s)) != NULL)
+ return dupstr(err);
+
+ supdup->pinger = pinger_new(supdup->conf, &supdup->backend);
+
+ /*
+ * We can send special commands from the start.
+ */
+ seat_update_specials_menu(supdup->seat);
+
+ /*
+ * loghost overrides realhost, if specified.
+ */
+ loghost = conf_get_str(supdup->conf, CONF_loghost);
+ if (*loghost) {
+ char *colon;
+
+ sfree(*realhost);
+ *realhost = dupstr(loghost);
+
+ colon = host_strrchr(*realhost, ':');
+ if (colon)
+ *colon++ = '\0';
+ }
+
+ /*
+ * Set up TTYOPTS based on config
+ */
+ int ascii_set = conf_get_int(supdup->conf, CONF_supdup_ascii_set);
+ int more_processing = conf_get_bool(supdup->conf, CONF_supdup_more);
+ int scrolling = conf_get_bool(supdup->conf, CONF_supdup_scroll);
+ supdup->ttyopt =
+ TOERS |
+ TOMVB |
+ (ascii_set == SUPDUP_CHARSET_ASCII ? 0 : TOSAI | TOSA1) |
+ TOMVU |
+ TOLWR |
+ TOLID |
+ TOCID |
+ TPCBS |
+ (scrolling ? TOROL : 0) |
+ (more_processing ? TOMOR : 0) |
+ TPORS;
+
+ supdup->tcmxh = supdup->term_width - 1; // -1 "..one column is used to indicate line continuation."
+ supdup->tcmxv = supdup->term_height;
+
+ /*
+ * Send our configuration words to the server
+ */
+ supdup_send_config(supdup);
+
+ /*
+ * We next expect a connection message followed by %TDNOP from the server
+ */
+ supdup->state = CONNECTING;
+ seat_set_trust_status(supdup->seat, false);
+
+ /* Make sure the terminal is in UTF-8 mode. */
+ c_write(supdup, (unsigned char *)utf8, strlen(utf8));
+
+ return NULL;
+}
+
+
+static void supdup_free(Backend *be)
+{
+ Supdup *supdup = container_of(be, Supdup, backend);
+
+ if (is_tempseat(supdup->seat))
+ tempseat_free(supdup->seat);
+ if (supdup->s)
+ sk_close(supdup->s);
+ if (supdup->pinger)
+ pinger_free(supdup->pinger);
+ conf_free(supdup->conf);
+ sfree(supdup->description);
+ sfree(supdup);
+}
+
+/*
+ * Reconfigure the Supdup backend.
+ */
+static void supdup_reconfig(Backend *be, Conf *conf)
+{
+ /* Nothing to do; SUPDUP cannot be reconfigured while running. */
+}
+
+/*
+ * Called to send data down the Supdup connection.
+ */
+static void supdup_send(Backend *be, const char *buf, size_t len)
+{
+ Supdup *supdup = container_of(be, Supdup, backend);
+ char c;
+ int i;
+
+ if (supdup->s == NULL)
+ return;
+
+ for (i = 0; i < len; i++) {
+ if (buf[i] == 034)
+ supdup->bufsize = sk_write(supdup->s, "\034\034", 2);
+ else {
+ c = buf[i] & 0177;
+ supdup->bufsize = sk_write(supdup->s, &c, 1);
+ }
+ }
+}
+
+/*
+ * Called to query the current socket sendability status.
+ */
+static size_t supdup_sendbuffer(Backend *be)
+{
+ Supdup *supdup = container_of(be, Supdup, backend);
+ return supdup->bufsize;
+}
+
+/*
+ * Called to set the size of the window from Supdup's POV.
+ */
+static void supdup_size(Backend *be, int width, int height)
+{
+ Supdup *supdup = container_of(be, Supdup, backend);
+
+ supdup->term_width = width;
+ supdup->term_height = height;
+
+ //
+ // SUPDUP does not support resizing the terminal after connection
+ // establishment.
+ //
+}
+
+/*
+ * Send Telnet special codes.
+ */
+static void supdup_special(Backend *be, SessionSpecialCode code, int arg)
+{
+}
+
+static const SessionSpecial *supdup_get_specials(Backend *be)
+{
+ return NULL;
+}
+
+static bool supdup_connected(Backend *be)
+{
+ Supdup *supdup = container_of(be, Supdup, backend);
+ return supdup->s != NULL;
+}
+
+static bool supdup_sendok(Backend *be)
+{
+ Supdup *supdup = container_of(be, Supdup, backend);
+ return supdup->socket_connected;
+}
+
+static void supdup_unthrottle(Backend *be, size_t backlog)
+{
+ Supdup *supdup = container_of(be, Supdup, backend);
+ sk_set_frozen(supdup->s, backlog > SUPDUP_MAX_BACKLOG);
+}
+
+static bool supdup_ldisc(Backend *be, int option)
+{
+ /* No support for echoing or local editing. */
+ return false;
+}
+
+static void supdup_provide_ldisc(Backend *be, Ldisc *ldisc)
+{
+ Supdup *supdup = container_of(be, Supdup, backend);
+ supdup->ldisc = ldisc;
+}
+
+static int supdup_exitcode(Backend *be)
+{
+ Supdup *supdup = container_of(be, Supdup, backend);
+ if (supdup->s != NULL)
+ return -1; /* still connected */
+ else if (supdup->closed_on_socket_error)
+ return INT_MAX; /* a socket error counts as an unclean exit */
+ else
+ /* Supdup doesn't transmit exit codes back to the client */
+ return 0;
+}
+
+/*
+ * cfg_info for Supdup does nothing at all.
+ */
+static int supdup_cfg_info(Backend *be)
+{
+ return 0;
+}
+
+const BackendVtable supdup_backend = {
+ .init = supdup_init,
+ .free = supdup_free,
+ .reconfig = supdup_reconfig,
+ .send = supdup_send,
+ .sendbuffer = supdup_sendbuffer,
+ .size = supdup_size,
+ .special = supdup_special,
+ .get_specials = supdup_get_specials,
+ .connected = supdup_connected,
+ .exitcode = supdup_exitcode,
+ .sendok = supdup_sendok,
+ .ldisc_option_state = supdup_ldisc,
+ .provide_ldisc = supdup_provide_ldisc,
+ .unthrottle = supdup_unthrottle,
+ .cfg_info = supdup_cfg_info,
+ .id = "supdup",
+ .displayname_tc = "SUPDUP",
+ .displayname_lc = "SUPDUP", /* proper name, so capitalise it anyway */
+ .protocol = PROT_SUPDUP,
+ .default_port = 0137,
+ .flags = BACKEND_RESIZE_FORBIDDEN | BACKEND_NEEDS_TERMINAL,
+};
diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c
new file mode 100644
index 00000000..23b7fc9e
--- /dev/null
+++ b/otherbackends/telnet.c
@@ -0,0 +1,1123 @@
+/*
+ * Telnet backend.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#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 "<unknown>";
+ }
+#undef telnet_str
+}
+
+struct Opt {
+ int send; /* what we initially send */
+ int nsend; /* -ve send if requested to stop it */
+ int ack, nak; /* +ve and -ve acknowledgements */
+ int option; /* the option code */
+ int index; /* index into telnet->opt_states[] */
+ enum {
+ REQUESTED, ACTIVE, INACTIVE, REALLY_INACTIVE
+ } initial_state;
+};
+
+enum {
+ OPTINDEX_NAWS,
+ OPTINDEX_TSPEED,
+ OPTINDEX_TTYPE,
+ OPTINDEX_OENV,
+ OPTINDEX_NENV,
+ OPTINDEX_ECHO,
+ OPTINDEX_WE_SGA,
+ OPTINDEX_THEY_SGA,
+ OPTINDEX_WE_BIN,
+ OPTINDEX_THEY_BIN,
+ NUM_OPTS
+};
+
+static const struct Opt o_naws =
+ { WILL, WONT, DO, DONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED };
+static const struct Opt o_tspeed =
+ { WILL, WONT, DO, DONT, TELOPT_TSPEED, OPTINDEX_TSPEED, REQUESTED };
+static const struct Opt o_ttype =
+ { WILL, WONT, DO, DONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED };
+static const struct Opt o_oenv =
+ { WILL, WONT, DO, DONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE };
+static const struct Opt o_nenv =
+ { WILL, WONT, DO, DONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED };
+static const struct Opt o_echo =
+ { DO, DONT, WILL, WONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED };
+static const struct Opt o_we_sga =
+ { WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED };
+static const struct Opt o_they_sga =
+ { DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED };
+static const struct Opt o_we_bin =
+ { WILL, WONT, DO, DONT, TELOPT_BINARY, OPTINDEX_WE_BIN, INACTIVE };
+static const struct Opt o_they_bin =
+ { DO, DONT, WILL, WONT, TELOPT_BINARY, OPTINDEX_THEY_BIN, INACTIVE };
+
+static const struct Opt *const opts[] = {
+ &o_naws, &o_tspeed, &o_ttype, &o_oenv, &o_nenv, &o_echo,
+ &o_we_sga, &o_they_sga, &o_we_bin, &o_they_bin, NULL
+};
+
+typedef struct Telnet Telnet;
+struct Telnet {
+ Socket *s;
+ bool socket_connected;
+ bool closed_on_socket_error;
+
+ Seat *seat;
+ LogContext *logctx;
+ Ldisc *ldisc;
+ int term_width, term_height;
+ char *description;
+
+ int opt_states[NUM_OPTS];
+
+ bool echoing, editing;
+ bool activated;
+ size_t bufsize;
+ bool in_synch;
+ int sb_opt;
+ strbuf *sb_buf;
+
+ enum {
+ TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT,
+ SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR
+ } state;
+
+ Conf *conf;
+
+ Pinger *pinger;
+
+ Plug plug;
+ Backend backend;
+ Interactor interactor;
+};
+
+#define TELNET_MAX_BACKLOG 4096
+
+#define SB_DELTA 1024
+
+static void c_write(Telnet *telnet, const void *buf, size_t len)
+{
+ size_t backlog = seat_stdout(telnet->seat, buf, len);
+ sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG);
+}
+
+static void log_option(Telnet *telnet, const char *sender, int cmd, int option)
+{
+ /*
+ * The strange-looking "<?""?>" below is there to avoid a
+ * trigraph - a double question mark followed by > maps to a
+ * closing brace character!
+ */
+ logeventf(telnet->logctx, "%s negotiation: %s %s", sender,
+ (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" :
+ cmd == DO ? "DO" : cmd == DONT ? "DONT" : "<?""?>"),
+ telopt(option));
+}
+
+static void send_opt(Telnet *telnet, int cmd, int option)
+{
+ unsigned char b[3];
+
+ b[0] = IAC;
+ b[1] = cmd;
+ b[2] = option;
+ telnet->bufsize = sk_write(telnet->s, b, 3);
+ log_option(telnet, "client", cmd, option);
+}
+
+static void deactivate_option(Telnet *telnet, const struct Opt *o)
+{
+ if (telnet->opt_states[o->index] == REQUESTED ||
+ telnet->opt_states[o->index] == ACTIVE)
+ send_opt(telnet, o->nsend, o->option);
+ telnet->opt_states[o->index] = REALLY_INACTIVE;
+}
+
+/*
+ * Generate side effects of enabling or disabling an option.
+ */
+static void option_side_effects(
+ Telnet *telnet, const struct Opt *o, bool enabled)
+{
+ if (o->option == TELOPT_ECHO && o->send == DO)
+ telnet->echoing = !enabled;
+ else if (o->option == TELOPT_SGA && o->send == DO)
+ telnet->editing = !enabled;
+ if (telnet->ldisc) /* cause ldisc to notice the change */
+ ldisc_echoedit_update(telnet->ldisc);
+
+ /* Ensure we get the minimum options */
+ if (!telnet->activated) {
+ if (telnet->opt_states[o_echo.index] == INACTIVE) {
+ telnet->opt_states[o_echo.index] = REQUESTED;
+ send_opt(telnet, o_echo.send, o_echo.option);
+ }
+ if (telnet->opt_states[o_we_sga.index] == INACTIVE) {
+ telnet->opt_states[o_we_sga.index] = REQUESTED;
+ send_opt(telnet, o_we_sga.send, o_we_sga.option);
+ }
+ if (telnet->opt_states[o_they_sga.index] == INACTIVE) {
+ telnet->opt_states[o_they_sga.index] = REQUESTED;
+ send_opt(telnet, o_they_sga.send, o_they_sga.option);
+ }
+ telnet->activated = true;
+ }
+}
+
+static void activate_option(Telnet *telnet, const struct Opt *o)
+{
+ if (o->send == WILL && o->option == TELOPT_NAWS)
+ backend_size(&telnet->backend,
+ telnet->term_width, telnet->term_height);
+ if (o->send == WILL &&
+ (o->option == TELOPT_NEW_ENVIRON ||
+ o->option == TELOPT_OLD_ENVIRON)) {
+ /*
+ * We may only have one kind of ENVIRON going at a time.
+ * This is a hack, but who cares.
+ */
+ deactivate_option(telnet, o->option ==
+ TELOPT_NEW_ENVIRON ? &o_oenv : &o_nenv);
+ }
+ option_side_effects(telnet, o, true);
+}
+
+static void refused_option(Telnet *telnet, const struct Opt *o)
+{
+ if (o->send == WILL && o->option == TELOPT_NEW_ENVIRON &&
+ telnet->opt_states[o_oenv.index] == INACTIVE) {
+ send_opt(telnet, WILL, TELOPT_OLD_ENVIRON);
+ telnet->opt_states[o_oenv.index] = REQUESTED;
+ }
+ option_side_effects(telnet, o, false);
+}
+
+static void proc_rec_opt(Telnet *telnet, int cmd, int option)
+{
+ const struct Opt *const *o;
+
+ log_option(telnet, "server", cmd, option);
+ for (o = opts; *o; o++) {
+ if ((*o)->option == option && (*o)->ack == cmd) {
+ switch (telnet->opt_states[(*o)->index]) {
+ case REQUESTED:
+ telnet->opt_states[(*o)->index] = ACTIVE;
+ activate_option(telnet, *o);
+ break;
+ case ACTIVE:
+ break;
+ case INACTIVE:
+ telnet->opt_states[(*o)->index] = ACTIVE;
+ send_opt(telnet, (*o)->send, option);
+ activate_option(telnet, *o);
+ break;
+ case REALLY_INACTIVE:
+ send_opt(telnet, (*o)->nsend, option);
+ break;
+ }
+ return;
+ } else if ((*o)->option == option && (*o)->nak == cmd) {
+ switch (telnet->opt_states[(*o)->index]) {
+ case REQUESTED:
+ telnet->opt_states[(*o)->index] = INACTIVE;
+ refused_option(telnet, *o);
+ break;
+ case ACTIVE:
+ telnet->opt_states[(*o)->index] = INACTIVE;
+ send_opt(telnet, (*o)->nsend, option);
+ option_side_effects(telnet, *o, false);
+ break;
+ case INACTIVE:
+ case REALLY_INACTIVE:
+ break;
+ }
+ return;
+ }
+ }
+ /*
+ * If we reach here, the option was one we weren't prepared to
+ * cope with. If the request was positive (WILL or DO), we send
+ * a negative ack to indicate refusal. If the request was
+ * negative (WONT / DONT), we must do nothing.
+ */
+ if (cmd == WILL || cmd == DO)
+ send_opt(telnet, (cmd == WILL ? DONT : WONT), option);
+}
+
+static void process_subneg(Telnet *telnet)
+{
+ unsigned char *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 subnegotiation: SB TSPEED SEND");
+ logeventf(telnet->logctx,
+ "client subnegotiation: SB TSPEED IS %s", termspeed);
+ sfree(b);
+ } else
+ logevent(telnet->logctx,
+ "server subnegotiation: SB TSPEED <something weird>");
+ 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 subnegotiation: SB TTYPE SEND");
+ logeventf(telnet->logctx,
+ "client subnegotiation: SB TTYPE IS %s", b + 4);
+ sfree(b);
+ } else
+ logevent(telnet->logctx,
+ "server subnegotiation: SB TTYPE <something weird>\r\n");
+ break;
+ case TELOPT_OLD_ENVIRON:
+ case TELOPT_NEW_ENVIRON:
+ p = telnet->sb_buf->u;
+ q = p + telnet->sb_buf->len;
+ if (p < q && *p == TELQUAL_SEND) {
+ p++;
+ logeventf(telnet->logctx, "server subnegotiation: SB %s SEND",
+ telopt(telnet->sb_opt));
+ if (telnet->sb_opt == TELOPT_OLD_ENVIRON) {
+ if (conf_get_bool(telnet->conf, CONF_rfc_environ)) {
+ value = RFC_VALUE;
+ var = RFC_VAR;
+ } else {
+ value = BSD_VALUE;
+ var = BSD_VAR;
+ }
+ /*
+ * Try to guess the sense of VAR and VALUE.
+ */
+ while (p < q) {
+ if (*p == RFC_VAR) {
+ value = RFC_VALUE;
+ var = RFC_VAR;
+ } else if (*p == BSD_VAR) {
+ value = BSD_VALUE;
+ var = BSD_VAR;
+ }
+ p++;
+ }
+ } else {
+ /*
+ * With NEW_ENVIRON, the sense of VAR and VALUE
+ * isn't in doubt.
+ */
+ value = RFC_VALUE;
+ var = RFC_VAR;
+ }
+ 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 subnegotiation: SB %s IS <nothing>",
+ telopt(telnet->sb_opt));
+ } else {
+ logeventf(telnet->logctx, "client subnegotiation: SB %s IS:",
+ telopt(telnet->sb_opt));
+ for (eval = conf_get_str_strs(telnet->conf, CONF_environmt,
+ NULL, &ekey);
+ eval != NULL;
+ eval = conf_get_str_strs(telnet->conf, CONF_environmt,
+ ekey, &ekey)) {
+ logeventf(telnet->logctx, " %s=%s", ekey, eval);
+ }
+ if (user)
+ logeventf(telnet->logctx, " USER=%s", user);
+ }
+ 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->socket_connected);
+ if (type == PLUGLOG_CONNECT_SUCCESS) {
+ telnet->socket_connected = true;
+ if (telnet->ldisc)
+ ldisc_check_sendok(telnet->ldisc);
+ }
+}
+
+static void telnet_closing(Plug *plug, PlugCloseType type,
+ const char *error_msg)
+{
+ Telnet *telnet = container_of(plug, Telnet, plug);
+
+ /*
+ * We don't implement independent EOF in each direction for Telnet
+ * connections; as soon as we get word that the remote side has
+ * sent us EOF, we wind up the whole connection.
+ */
+
+ if (telnet->s) {
+ sk_close(telnet->s);
+ telnet->s = NULL;
+ if (error_msg)
+ telnet->closed_on_socket_error = true;
+ seat_notify_remote_exit(telnet->seat);
+ seat_notify_remote_disconnect(telnet->seat);
+ }
+ if (type != PLUGCLOSE_NORMAL) {
+ logevent(telnet->logctx, error_msg);
+ if (type != PLUGCLOSE_USER_ABORT)
+ seat_connection_fatal(telnet->seat, "%s", error_msg);
+ }
+ /* Otherwise, the remote side closed the connection normally. */
+}
+
+static void telnet_receive(
+ Plug *plug, int urgent, const char *data, size_t len)
+{
+ Telnet *telnet = container_of(plug, Telnet, plug);
+ if (urgent)
+ telnet->in_synch = true;
+ do_telnet_read(telnet, data, len);
+}
+
+static void telnet_sent(Plug *plug, size_t bufsize)
+{
+ Telnet *telnet = container_of(plug, Telnet, plug);
+ telnet->bufsize = bufsize;
+ seat_sent(telnet->seat, telnet->bufsize);
+}
+
+static const PlugVtable Telnet_plugvt = {
+ .log = telnet_log,
+ .closing = telnet_closing,
+ .receive = telnet_receive,
+ .sent = telnet_sent,
+};
+
+static char *telnet_description(Interactor *itr)
+{
+ Telnet *telnet = container_of(itr, Telnet, interactor);
+ return dupstr(telnet->description);
+}
+
+static LogPolicy *telnet_logpolicy(Interactor *itr)
+{
+ Telnet *telnet = container_of(itr, Telnet, interactor);
+ return log_get_policy(telnet->logctx);
+}
+
+static Seat *telnet_get_seat(Interactor *itr)
+{
+ Telnet *telnet = container_of(itr, Telnet, interactor);
+ return telnet->seat;
+}
+
+static void telnet_set_seat(Interactor *itr, Seat *seat)
+{
+ Telnet *telnet = container_of(itr, Telnet, interactor);
+ telnet->seat = seat;
+}
+
+static const InteractorVtable Telnet_interactorvt = {
+ .description = telnet_description,
+ .logpolicy = telnet_logpolicy,
+ .get_seat = telnet_get_seat,
+ .set_seat = telnet_set_seat,
+};
+
+/*
+ * Called to set up the Telnet connection.
+ *
+ * Returns an error message, or NULL on success.
+ *
+ * Also places the canonical host name into `realhost'. It must be
+ * freed by the caller.
+ */
+static char *telnet_init(const BackendVtable *vt, Seat *seat,
+ Backend **backend_handle, LogContext *logctx,
+ Conf *conf, const char *host, int port,
+ char **realhost, bool nodelay, bool keepalive)
+{
+ SockAddr *addr;
+ const char *err;
+ Telnet *telnet;
+ char *loghost;
+ int addressfamily;
+
+ telnet = snew(Telnet);
+ memset(telnet, 0, sizeof(Telnet));
+ telnet->plug.vt = &Telnet_plugvt;
+ telnet->backend.vt = vt;
+ telnet->interactor.vt = &Telnet_interactorvt;
+ telnet->backend.interactor = &telnet->interactor;
+ telnet->conf = conf_copy(conf);
+ telnet->s = NULL;
+ telnet->socket_connected = false;
+ telnet->closed_on_socket_error = false;
+ telnet->echoing = true;
+ telnet->editing = true;
+ telnet->activated = false;
+ telnet->sb_buf = strbuf_new();
+ telnet->seat = seat;
+ telnet->logctx = logctx;
+ telnet->term_width = conf_get_int(telnet->conf, CONF_width);
+ telnet->term_height = conf_get_int(telnet->conf, CONF_height);
+ telnet->state = TOP_LEVEL;
+ telnet->ldisc = NULL;
+ telnet->pinger = NULL;
+ telnet->description = default_description(vt, host, port);
+ *backend_handle = &telnet->backend;
+
+ /*
+ * Try to find host.
+ */
+ addressfamily = conf_get_int(telnet->conf, CONF_addressfamily);
+ addr = name_lookup(host, port, realhost, telnet->conf, addressfamily,
+ telnet->logctx, "Telnet connection");
+ if ((err = sk_addr_error(addr)) != NULL) {
+ sk_addr_free(addr);
+ return dupstr(err);
+ }
+
+ if (port < 0)
+ port = 23; /* default telnet port */
+
+ /*
+ * Open socket.
+ */
+ telnet->s = new_connection(addr, *realhost, port, false, true, nodelay,
+ keepalive, &telnet->plug, telnet->conf,
+ &telnet->interactor);
+ if ((err = sk_socket_error(telnet->s)) != NULL)
+ return dupstr(err);
+
+ /* No local authentication phase in this protocol */
+ seat_set_trust_status(telnet->seat, false);
+
+ telnet->pinger = pinger_new(telnet->conf, &telnet->backend);
+
+ /*
+ * Initialise option states.
+ */
+ if (conf_get_bool(telnet->conf, CONF_passive_telnet)) {
+ const struct Opt *const *o;
+
+ for (o = opts; *o; o++)
+ telnet->opt_states[(*o)->index] = INACTIVE;
+ } else {
+ const struct Opt *const *o;
+
+ for (o = opts; *o; o++) {
+ telnet->opt_states[(*o)->index] = (*o)->initial_state;
+ if (telnet->opt_states[(*o)->index] == REQUESTED)
+ send_opt(telnet, (*o)->send, (*o)->option);
+ }
+ telnet->activated = true;
+ }
+
+ /*
+ * Set up SYNCH state.
+ */
+ telnet->in_synch = false;
+
+ /*
+ * We can send special commands from the start.
+ */
+ seat_update_specials_menu(telnet->seat);
+
+ /*
+ * loghost overrides realhost, if specified.
+ */
+ loghost = conf_get_str(telnet->conf, CONF_loghost);
+ if (*loghost) {
+ char *colon;
+
+ sfree(*realhost);
+ *realhost = dupstr(loghost);
+
+ colon = host_strrchr(*realhost, ':');
+ if (colon)
+ *colon++ = '\0';
+ }
+
+ return NULL;
+}
+
+static void telnet_free(Backend *be)
+{
+ Telnet *telnet = container_of(be, Telnet, backend);
+
+ if (is_tempseat(telnet->seat))
+ tempseat_free(telnet->seat);
+ strbuf_free(telnet->sb_buf);
+ if (telnet->s)
+ sk_close(telnet->s);
+ if (telnet->pinger)
+ pinger_free(telnet->pinger);
+ conf_free(telnet->conf);
+ sfree(telnet->description);
+ sfree(telnet);
+}
+/*
+ * Reconfigure the Telnet backend. There's no immediate action
+ * necessary, in this backend: we just save the fresh config for
+ * any subsequent negotiations.
+ */
+static void telnet_reconfig(Backend *be, Conf *conf)
+{
+ Telnet *telnet = container_of(be, Telnet, backend);
+ pinger_reconfig(telnet->pinger, telnet->conf, conf);
+ conf_free(telnet->conf);
+ telnet->conf = conf_copy(conf);
+}
+
+/*
+ * Called to send data down the Telnet connection.
+ */
+static void telnet_send(Backend *be, const char *buf, size_t len)
+{
+ Telnet *telnet = container_of(be, Telnet, backend);
+ unsigned char *p, *end;
+ static const unsigned char iac[2] = { IAC, IAC };
+ static const unsigned char cr[2] = { CR, NUL };
+#if 0
+ static const unsigned char nl[2] = { CR, LF };
+#endif
+
+ if (telnet->s == NULL)
+ return;
+
+ p = (unsigned char *)buf;
+ end = (unsigned char *)(buf + len);
+ while (p < end) {
+ unsigned char *q = p;
+
+ while (p < end && iswritable(*p))
+ p++;
+ telnet->bufsize = sk_write(telnet->s, q, p - q);
+
+ while (p < end && !iswritable(*p)) {
+ telnet->bufsize =
+ sk_write(telnet->s, *p == IAC ? iac : cr, 2);
+ p++;
+ }
+ }
+}
+
+/*
+ * Called to query the current socket sendability status.
+ */
+static size_t telnet_sendbuffer(Backend *be)
+{
+ Telnet *telnet = container_of(be, Telnet, backend);
+ return telnet->bufsize;
+}
+
+/*
+ * Called to set the size of the window from Telnet's POV.
+ */
+static void telnet_size(Backend *be, int width, int height)
+{
+ Telnet *telnet = container_of(be, Telnet, backend);
+ unsigned char b[24];
+ int n;
+
+ telnet->term_width = width;
+ telnet->term_height = height;
+
+ if (telnet->s == NULL || telnet->opt_states[o_naws.index] != ACTIVE)
+ return;
+ n = 0;
+ b[n++] = IAC;
+ b[n++] = SB;
+ b[n++] = TELOPT_NAWS;
+ b[n++] = telnet->term_width >> 8;
+ if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
+ b[n++] = telnet->term_width & 0xFF;
+ if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
+ b[n++] = telnet->term_height >> 8;
+ if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
+ b[n++] = telnet->term_height & 0xFF;
+ if (b[n-1] == IAC) b[n++] = IAC; /* duplicate any IAC byte occurs */
+ b[n++] = IAC;
+ b[n++] = SE;
+ telnet->bufsize = sk_write(telnet->s, b, n);
+ logeventf(telnet->logctx, "client subnegotiation: SB NAWS %d,%d",
+ telnet->term_width, telnet->term_height);
+}
+
+/*
+ * Send Telnet special codes.
+ */
+static void telnet_special(Backend *be, SessionSpecialCode code, int arg)
+{
+ Telnet *telnet = container_of(be, Telnet, backend);
+ unsigned char b[2];
+
+ if (telnet->s == NULL)
+ return;
+
+ b[0] = IAC;
+ switch (code) {
+ case SS_AYT:
+ b[1] = AYT;
+ telnet->bufsize = sk_write(telnet->s, b, 2);
+ break;
+ case SS_BRK:
+ b[1] = BREAK;
+ telnet->bufsize = sk_write(telnet->s, b, 2);
+ break;
+ case SS_EC:
+ b[1] = EC;
+ telnet->bufsize = sk_write(telnet->s, b, 2);
+ break;
+ case SS_EL:
+ b[1] = EL;
+ telnet->bufsize = sk_write(telnet->s, b, 2);
+ break;
+ case SS_GA:
+ b[1] = GA;
+ telnet->bufsize = sk_write(telnet->s, b, 2);
+ break;
+ case SS_NOP:
+ b[1] = NOP;
+ telnet->bufsize = sk_write(telnet->s, b, 2);
+ break;
+ case SS_ABORT:
+ b[1] = ABORT;
+ telnet->bufsize = sk_write(telnet->s, b, 2);
+ break;
+ case SS_AO:
+ b[1] = AO;
+ telnet->bufsize = sk_write(telnet->s, b, 2);
+ break;
+ case SS_IP:
+ b[1] = IP;
+ telnet->bufsize = sk_write(telnet->s, b, 2);
+ break;
+ case SS_SUSP:
+ b[1] = SUSP;
+ telnet->bufsize = sk_write(telnet->s, b, 2);
+ break;
+ case SS_EOR:
+ b[1] = EOR;
+ telnet->bufsize = sk_write(telnet->s, b, 2);
+ break;
+ case SS_EOF:
+ b[1] = xEOF;
+ telnet->bufsize = sk_write(telnet->s, b, 2);
+ break;
+ case SS_EOL:
+ /* In BINARY mode, CR-LF becomes just CR -
+ * and without the NUL suffix too. */
+ if (telnet->opt_states[o_we_bin.index] == ACTIVE)
+ telnet->bufsize = sk_write(telnet->s, "\r", 1);
+ else
+ telnet->bufsize = sk_write(telnet->s, "\r\n", 2);
+ break;
+ case SS_SYNCH:
+ b[1] = DM;
+ telnet->bufsize = sk_write(telnet->s, b, 1);
+ telnet->bufsize = sk_write_oob(telnet->s, b + 1, 1);
+ break;
+ case SS_PING:
+ if (telnet->opt_states[o_they_sga.index] == ACTIVE) {
+ b[1] = NOP;
+ telnet->bufsize = sk_write(telnet->s, b, 2);
+ }
+ break;
+ default:
+ break; /* never heard of it */
+ }
+}
+
+static const SessionSpecial *telnet_get_specials(Backend *be)
+{
+ static const SessionSpecial specials[] = {
+ {"Are You There", SS_AYT},
+ {"Break", SS_BRK},
+ {"Synch", SS_SYNCH},
+ {"Erase Character", SS_EC},
+ {"Erase Line", SS_EL},
+ {"Go Ahead", SS_GA},
+ {"No Operation", SS_NOP},
+ {NULL, SS_SEP},
+ {"Abort Process", SS_ABORT},
+ {"Abort Output", SS_AO},
+ {"Interrupt Process", SS_IP},
+ {"Suspend Process", SS_SUSP},
+ {NULL, SS_SEP},
+ {"End Of Record", SS_EOR},
+ {"End Of File", SS_EOF},
+ {NULL, SS_EXITMENU}
+ };
+ return specials;
+}
+
+static bool telnet_connected(Backend *be)
+{
+ Telnet *telnet = container_of(be, Telnet, backend);
+ return telnet->s != NULL;
+}
+
+static bool telnet_sendok(Backend *be)
+{
+ Telnet *telnet = container_of(be, Telnet, backend);
+ return telnet->socket_connected;
+}
+
+static void telnet_unthrottle(Backend *be, size_t backlog)
+{
+ Telnet *telnet = container_of(be, Telnet, backend);
+ sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG);
+}
+
+static bool telnet_ldisc(Backend *be, int option)
+{
+ Telnet *telnet = container_of(be, Telnet, backend);
+ if (option == LD_ECHO)
+ return telnet->echoing;
+ if (option == LD_EDIT)
+ return telnet->editing;
+ return false;
+}
+
+static void telnet_provide_ldisc(Backend *be, Ldisc *ldisc)
+{
+ Telnet *telnet = container_of(be, Telnet, backend);
+ telnet->ldisc = ldisc;
+}
+
+static int telnet_exitcode(Backend *be)
+{
+ Telnet *telnet = container_of(be, Telnet, backend);
+ if (telnet->s != NULL)
+ return -1; /* still connected */
+ else if (telnet->closed_on_socket_error)
+ return INT_MAX; /* a socket error counts as an unclean exit */
+ else
+ /* Telnet doesn't transmit exit codes back to the client */
+ return 0;
+}
+
+/*
+ * cfg_info for Telnet does nothing at all.
+ */
+static int telnet_cfg_info(Backend *be)
+{
+ return 0;
+}
+
+const BackendVtable telnet_backend = {
+ .init = telnet_init,
+ .free = telnet_free,
+ .reconfig = telnet_reconfig,
+ .send = telnet_send,
+ .sendbuffer = telnet_sendbuffer,
+ .size = telnet_size,
+ .special = telnet_special,
+ .get_specials = telnet_get_specials,
+ .connected = telnet_connected,
+ .exitcode = telnet_exitcode,
+ .sendok = telnet_sendok,
+ .ldisc_option_state = telnet_ldisc,
+ .provide_ldisc = telnet_provide_ldisc,
+ .unthrottle = telnet_unthrottle,
+ .cfg_info = telnet_cfg_info,
+ .id = "telnet",
+ .displayname_tc = "Telnet",
+ .displayname_lc = "Telnet", /* proper name, so capitalise it anyway */
+ .protocol = PROT_TELNET,
+ .default_port = 23,
+};
diff --git a/otherbackends/testback.c b/otherbackends/testback.c
new file mode 100644
index 00000000..f46d1d98
--- /dev/null
+++ b/otherbackends/testback.c
@@ -0,0 +1,200 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+
+static char *loop_init(const BackendVtable *, Seat *, Backend **, LogContext *,
+ Conf *, const char *, int, char **, bool, bool);
+static void loop_free(Backend *);
+static void null_reconfig(Backend *, Conf *);
+static void null_send(Backend *, const char *, size_t);
+static void loop_send(Backend *, const char *, size_t);
+static size_t null_sendbuffer(Backend *);
+static size_t loop_sendbuffer(Backend *);
+static void null_size(Backend *, int, int);
+static void null_special(Backend *, SessionSpecialCode, int);
+static const SessionSpecial *null_get_specials(Backend *);
+static bool null_connected(Backend *);
+static int null_exitcode(Backend *);
+static bool null_sendok(Backend *);
+static bool null_ldisc(Backend *, int);
+static void null_provide_ldisc(Backend *, Ldisc *);
+static void null_unthrottle(Backend *, size_t);
+static int null_cfg_info(Backend *);
+
+const BackendVtable null_backend = {
+ .init = loop_init,
+ .free = loop_free,
+ .reconfig = null_reconfig,
+ .send = null_send,
+ .sendbuffer = null_sendbuffer,
+ .size = null_size,
+ .special = null_special,
+ .get_specials = null_get_specials,
+ .connected = null_connected,
+ .exitcode = null_exitcode,
+ .sendok = null_sendok,
+ .ldisc_option_state = null_ldisc,
+ .provide_ldisc = null_provide_ldisc,
+ .unthrottle = null_unthrottle,
+ .cfg_info = null_cfg_info,
+ .id = "null",
+ .displayname_tc = "Null",
+ .displayname_lc = "null",
+ .protocol = -1,
+ .default_port = 0,
+};
+
+const BackendVtable loop_backend = {
+ .init = loop_init,
+ .free = loop_free,
+ .reconfig = null_reconfig,
+ .send = loop_send,
+ .sendbuffer = loop_sendbuffer,
+ .size = null_size,
+ .special = null_special,
+ .get_specials = null_get_specials,
+ .connected = null_connected,
+ .exitcode = null_exitcode,
+ .sendok = null_sendok,
+ .ldisc_option_state = null_ldisc,
+ .provide_ldisc = null_provide_ldisc,
+ .unthrottle = null_unthrottle,
+ .cfg_info = null_cfg_info,
+ .id = "loop",
+ .displayname_tc = "Loop",
+ .displayname_lc = "loop",
+ .protocol = -1,
+ .default_port = 0,
+};
+
+struct loop_state {
+ Seat *seat;
+ Backend backend;
+ size_t sendbuffer;
+};
+
+static char *loop_init(const BackendVtable *vt, Seat *seat,
+ Backend **backend_handle, LogContext *logctx,
+ Conf *conf, const char *host, int port,
+ char **realhost, bool nodelay, bool keepalive) {
+ struct loop_state *st = snew(struct loop_state);
+
+ /* No local authentication phase in this protocol */
+ seat_set_trust_status(seat, false);
+
+ st->seat = seat;
+ st->backend.vt = vt;
+ *backend_handle = &st->backend;
+
+ *realhost = dupstr(host);
+
+ return NULL;
+}
+
+static void loop_free(Backend *be)
+{
+ struct loop_state *st = container_of(be, struct loop_state, backend);
+
+ sfree(st);
+}
+
+static void null_reconfig(Backend *be, Conf *conf) {
+
+}
+
+static void null_send(Backend *be, const char *buf, size_t len) {
+
+}
+
+static void loop_send(Backend *be, const char *buf, size_t len) {
+ struct loop_state *st = container_of(be, struct loop_state, backend);
+
+ st->sendbuffer = seat_output(st->seat, 0, buf, len);
+}
+
+static size_t null_sendbuffer(Backend *be) {
+
+ return 0;
+}
+
+static size_t loop_sendbuffer(Backend *be) {
+ struct loop_state *st = container_of(be, struct loop_state, backend);
+
+ return st->sendbuffer;
+}
+
+static void null_size(Backend *be, int width, int height) {
+
+}
+
+static void null_special(Backend *be, SessionSpecialCode code, int arg) {
+
+}
+
+static const SessionSpecial *null_get_specials (Backend *be) {
+
+ return NULL;
+}
+
+static bool null_connected(Backend *be) {
+
+ return false;
+}
+
+static int null_exitcode(Backend *be) {
+
+ return 0;
+}
+
+static bool null_sendok(Backend *be) {
+
+ return true;
+}
+
+static void null_unthrottle(Backend *be, size_t backlog) {
+
+}
+
+static bool null_ldisc(Backend *be, int option) {
+
+ return false;
+}
+
+static void null_provide_ldisc (Backend *be, Ldisc *ldisc) {
+
+}
+
+static int null_cfg_info(Backend *be)
+{
+ return 0;
+}
diff --git a/pageant.c b/pageant.c
index 8ca9310b..455e434f 100644
--- a/pageant.c
+++ b/pageant.c
@@ -33,8 +33,10 @@ struct PageantClientDialogId {
int dummy;
};
-typedef struct PageantKeySort PageantKeySort;
-typedef struct PageantKey PageantKey;
+typedef struct PageantPrivateKeySort PageantPrivateKeySort;
+typedef struct PageantPublicKeySort PageantPublicKeySort;
+typedef struct PageantPrivateKey PageantPrivateKey;
+typedef struct PageantPublicKey PageantPublicKey;
typedef struct PageantAsyncOp PageantAsyncOp;
typedef struct PageantAsyncOpVtable PageantAsyncOpVtable;
typedef struct PageantClientRequestNode PageantClientRequestNode;
@@ -85,33 +87,106 @@ static void pageant_async_op_callback(void *vctx)
}
/*
- * Master list of all the keys we have stored, in any form at all.
+ * Master lists of all the keys we have stored, in any form at all.
+ *
+ * We store private and public keys in separate lists, because
+ * multiple public keys can share the same private key (due to one
+ * having a certificate and the other not, or having more than one
+ * different certificate). And when we decrypt or re-encrypt a private
+ * key, we don't really want to faff about doing it multiple times if
+ * there's more than one public key it goes with. If someone tries to
+ * re-encrypt a key to make their machine safer against unattended
+ * access, then it would be embarrassing to find they'd forgotten to
+ * re-encrypt the _other_ copy of it; conversely, once you've
+ * decrypted a key, it's pointless to make someone type yet another
+ * passphrase.
+ *
+ * (Causing multiple keys to become decrypted in one go isn't a
+ * security hole in its own right, because the signatures generated by
+ * certified and uncertified keys are identical. So an attacker
+ * gaining access to an agent containing one encrypted and one
+ * cleartext key with the same private half would still be *able* to
+ * generate signatures that went with the encrypted one, even if the
+ * agent refused to hand them out in response to the most obvious kind
+ * of request.)
*/
-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. */
+struct PageantPrivateKeySort {
+ /*
+ * Information used by the sorting criterion for the private key
+ * tree.
+ */
int ssh_version; /* 1 or 2; primary sort key */
- ptrlen public_blob; /* secondary sort key */
+ ptrlen base_pub; /* secondary sort key; never includes a certificate */
+};
+static int privkey_cmpfn(void *av, void *bv)
+{
+ PageantPrivateKeySort *a = (PageantPrivateKeySort *)av;
+ PageantPrivateKeySort *b = (PageantPrivateKeySort *)bv;
+
+ if (a->ssh_version != b->ssh_version)
+ return a->ssh_version < b->ssh_version ? -1 : +1;
+ else
+ return ptrlen_strcmp(a->base_pub, b->base_pub);
+}
+
+struct PageantPublicKeySort {
+ /*
+ * Information used by the sorting criterion for the public key
+ * tree. Begins with the private key sorting criterion, so that
+ * all the public keys sharing a private key appear adjacent in
+ * the tree. That's a reasonably sensible order to list them in
+ * for the user, and more importantly, it makes it easy to
+ * discover when we're deleting the last public key that goes with
+ * a particular private one, so as to delete that too. Easier than
+ * messing about with fragile reference counts.
+ */
+ PageantPrivateKeySort priv;
+ ptrlen full_pub; /* may match priv.base_pub, or may include a cert */
};
-struct PageantKey {
- PageantKeySort sort;
- strbuf *public_blob; /* the true owner of sort.public_blob */
- char *comment; /* stored separately, whether or not in rkey/skey */
+static int pubkey_cmpfn(void *av, void *bv)
+{
+ PageantPublicKeySort *a = (PageantPublicKeySort *)av;
+ PageantPublicKeySort *b = (PageantPublicKeySort *)bv;
+
+ int c = privkey_cmpfn(&a->priv, &b->priv);
+ if (c)
+ return c;
+ else
+ return ptrlen_strcmp(a->full_pub, b->full_pub);
+}
+
+struct PageantPrivateKey {
+ PageantPrivateKeySort sort;
+ strbuf *base_pub; /* the true owner of sort.base_pub */
union {
- RSAKey *rkey; /* if ssh_version == 1 */
- ssh2_userkey *skey; /* if ssh_version == 2 */
+ RSAKey *rkey; /* if sort.priv.ssh_version == 1 */
+ ssh_key *skey; /* if sort.priv.ssh_version == 2 */
};
strbuf *encrypted_key_file;
+ /* encrypted_key_comment stores the comment belonging to the
+ * encrypted key file. This is used when presenting deferred
+ * decryption prompts, because if the user had encrypted their
+ * uncert and cert keys with different passphrases, the passphrase
+ * prompt must reliably signal which file they're supposed to be
+ * entering the passphrase for. */
+ char *encrypted_key_comment;
bool decryption_prompt_active;
PageantKeyRequestNode blocked_requests;
PageantClientDialogId dlgid;
};
+static tree234 *privkeytree;
+
+struct PageantPublicKey {
+ PageantPublicKeySort sort;
+ strbuf *base_pub; /* the true owner of sort.priv.base_pub */
+ strbuf *full_pub; /* the true owner of sort.full_pub */
+ char *comment;
+};
+static tree234 *pubkeytree;
typedef struct PageantSignOp PageantSignOp;
struct PageantSignOp {
- PageantKey *pk;
+ PageantPrivateKey *priv;
strbuf *data_to_sign;
unsigned flags;
int crLine;
@@ -124,47 +199,40 @@ struct PageantSignOp {
/* Master lock that indicates whether a GUI request is currently in
* progress */
static bool gui_request_in_progress = false;
+static PageantKeyRequestNode requests_blocked_on_gui =
+ { &requests_blocked_on_gui, &requests_blocked_on_gui };
static void failure(PageantClient *pc, PageantClientRequestId *reqid,
strbuf *sb, unsigned char type, const char *fmt, ...);
-static void fail_requests_for_key(PageantKey *pk, const char *reason);
-static PageantKey *pageant_nth_key(int ssh_version, int i);
+static void fail_requests_for_key(PageantPrivateKey *priv, const char *reason);
+static PageantPublicKey *pageant_nth_pubkey(int ssh_version, int i);
-static void pk_free(PageantKey *pk)
+static void pk_priv_free(PageantPrivateKey *priv)
{
- 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 (priv->base_pub)
+ strbuf_free(priv->base_pub);
+ if (priv->sort.ssh_version == 1 && priv->rkey) {
+ freersakey(priv->rkey);
+ sfree(priv->rkey);
}
- if (pk->sort.ssh_version == 2 && pk->skey) {
- sfree(pk->skey->comment);
- ssh_key_free(pk->skey->key);
- sfree(pk->skey);
+ if (priv->sort.ssh_version == 2 && priv->skey) {
+ ssh_key_free(priv->skey);
}
- if (pk->encrypted_key_file) strbuf_free(pk->encrypted_key_file);
- fail_requests_for_key(pk, "key deleted from Pageant while signing "
+ if (priv->encrypted_key_file)
+ strbuf_free(priv->encrypted_key_file);
+ if (priv->encrypted_key_comment)
+ sfree(priv->encrypted_key_comment);
+ fail_requests_for_key(priv, "key deleted from Pageant while signing "
"request was pending");
- sfree(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);
+ sfree(priv);
}
-static inline PageantKeySort keysort(int version, ptrlen blob)
+static void pk_pub_free(PageantPublicKey *pub)
{
- PageantKeySort sort;
- sort.ssh_version = version;
- sort.public_blob = blob;
- return sort;
+ if (pub->full_pub)
+ strbuf_free(pub->full_pub);
+ sfree(pub->comment);
+ sfree(pub);
}
static strbuf *makeblob1(RSAKey *rkey)
@@ -175,126 +243,308 @@ static strbuf *makeblob1(RSAKey *rkey)
return blob;
}
-static strbuf *makeblob2(ssh2_userkey *skey)
+static strbuf *makeblob2full(ssh_key *key)
{
strbuf *blob = strbuf_new();
- ssh_key_public_blob(skey->key, BinarySink_UPCAST(blob));
+ ssh_key_public_blob(key, BinarySink_UPCAST(blob));
return blob;
}
-static PageantKey *findkey1(RSAKey *reqkey)
+static strbuf *makeblob2base(ssh_key *key)
+{
+ strbuf *blob = strbuf_new();
+ ssh_key_public_blob(ssh_key_base_key(key), BinarySink_UPCAST(blob));
+ return blob;
+}
+
+static PageantPrivateKey *pub_to_priv(PageantPublicKey *pub)
+{
+ PageantPrivateKey *priv = find234(privkeytree, &pub->sort.priv, NULL);
+ assert(priv && "Public and private trees out of sync!");
+ return priv;
+}
+
+static PageantPublicKey *findpubkey1(RSAKey *reqkey)
{
strbuf *blob = makeblob1(reqkey);
- PageantKeySort sort = keysort(1, ptrlen_from_strbuf(blob));
- PageantKey *toret = find234(keytree, &sort, NULL);
+ PageantPublicKeySort sort;
+ sort.priv.ssh_version = 1;
+ sort.priv.base_pub = ptrlen_from_strbuf(blob);
+ sort.full_pub = ptrlen_from_strbuf(blob);
+ PageantPublicKey *toret = find234(pubkeytree, &sort, NULL);
strbuf_free(blob);
return toret;
}
-static PageantKey *findkey2(ptrlen blob)
+/*
+ * Constructs the base_pub element of a PageantPublicKeySort, starting
+ * from full_pub. This may involve allocating a strbuf to store it in,
+ * which must survive until after you've finished using the resulting
+ * PageantPublicKeySort. Hence, the strbuf (if any) is returned from
+ * this function, and if it's non-NULL then the caller must eventually
+ * free it.
+ */
+static strbuf *make_base_pub_2(PageantPublicKeySort *sort)
+{
+ /* Start with the fallback option of making base_pub equal full_pub */
+ sort->priv.base_pub = sort->full_pub;
+
+ /* Now reconstruct a distinct base_pub without a cert, if possible
+ * and necessary */
+ strbuf *base_pub = NULL;
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, sort->full_pub);
+ ptrlen algname = get_string(src);
+ const ssh_keyalg *alg = find_pubkey_alg_len(algname);
+ if (alg && alg->is_certificate) {
+ ssh_key *key = ssh_key_new_pub(alg, sort->full_pub);
+ if (key) {
+ base_pub = strbuf_new();
+ ssh_key_public_blob(ssh_key_base_key(key),
+ BinarySink_UPCAST(base_pub));
+ sort->priv.base_pub = ptrlen_from_strbuf(base_pub);
+ ssh_key_free(key);
+ }
+ }
+
+ return base_pub; /* caller must free once they're done with sort */
+}
+
+static PageantPublicKey *findpubkey2(ptrlen full_pub)
{
- PageantKeySort sort = keysort(2, blob);
- return find234(keytree, &sort, NULL);
+ PageantPublicKeySort sort;
+ sort.priv.ssh_version = 2;
+ sort.full_pub = full_pub;
+ strbuf *base_pub = make_base_pub_2(&sort);
+ PageantPublicKey *toret = find234(pubkeytree, &sort, NULL);
+ if (base_pub)
+ strbuf_free(base_pub);
+ return toret;
}
-static int find_first_key_for_version(int ssh_version)
+static int find_first_pubkey_for_version(int ssh_version)
{
- PageantKeySort sort = keysort(ssh_version, PTRLEN_LITERAL(""));
+ PageantPublicKeySort sort;
+ sort.priv.ssh_version = ssh_version;
+ sort.priv.base_pub = PTRLEN_LITERAL("");
+ sort.full_pub = PTRLEN_LITERAL("");
int pos;
- if (findrelpos234(keytree, &sort, NULL, REL234_GE, &pos))
+ if (findrelpos234(pubkeytree, &sort, NULL, REL234_GE, &pos))
return pos;
- return count234(keytree);
+ return count234(pubkeytree);
}
static int count_keys(int ssh_version)
{
- return (find_first_key_for_version(ssh_version + 1) -
- find_first_key_for_version(ssh_version));
+ return (find_first_pubkey_for_version(ssh_version + 1) -
+ find_first_pubkey_for_version(ssh_version));
}
int pageant_count_ssh1_keys(void) { return count_keys(1); }
int pageant_count_ssh2_keys(void) { return count_keys(2); }
-static bool pageant_add_ssh1_key(RSAKey *rkey)
+/*
+ * Common code to add a key to the trees. We fill in as many fields
+ * here as we can share between SSH versions: the ptrlens in the
+ * sorting field, the whole of pub->sort.priv, and the linked list of
+ * blocked requests.
+ */
+static bool pageant_add_key_common(PageantPublicKey *pub,
+ PageantPrivateKey *priv)
{
- 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);
+ int ssh_version = priv->sort.ssh_version;
+
+ priv->sort.base_pub = ptrlen_from_strbuf(priv->base_pub);
+
+ pub->base_pub = strbuf_dup(priv->sort.base_pub);
+ pub->sort.priv.ssh_version = priv->sort.ssh_version;
+ pub->sort.priv.base_pub = ptrlen_from_strbuf(pub->base_pub);
+ pub->sort.full_pub = ptrlen_from_strbuf(pub->full_pub);
+ priv->blocked_requests.next = priv->blocked_requests.prev =
+ &priv->blocked_requests;
+
+ /*
+ * Try to add the private key to privkeytree, or combine new parts
+ * of it with what's already there.
+ */
+ PageantPrivateKey *priv_in_tree = add234(privkeytree, priv);
+ if (priv_in_tree == priv) {
+ /* The key wasn't in the tree at all, and we've just added it. */
+ } else {
+ /* The key was already in the tree, so we'll be freeing priv. */
+
+ if (ssh_version == 2 && priv->skey && !priv_in_tree->skey) {
+ /* The key was only stored encrypted, and now we have an
+ * unencrypted version to add to the existing record. */
+ priv_in_tree->skey = priv->skey;
+ priv->skey = NULL; /* so pk_priv_free won't free it */
+ }
+
+ if (ssh_version == 2 && priv->encrypted_key_file &&
+ !priv_in_tree->encrypted_key_file) {
+ /* Conversely, the key was only stored in clear, and now
+ * we have an encrypted version to add to it. */
+ priv_in_tree->encrypted_key_file = priv->encrypted_key_file;
+ priv->encrypted_key_file = NULL;
+ priv_in_tree->encrypted_key_comment = priv->encrypted_key_comment;
+ priv->encrypted_key_comment = NULL;
+ }
+
+ pk_priv_free(priv);
+ }
+
+ /*
+ * Try to add the public key.
+ */
+ PageantPublicKey *pub_in_tree = add234(pubkeytree, pub);
+ if (pub_in_tree == pub) {
+ /* Successfully added a new key. */
return true;
} else {
- pk_free(pk);
+ /* This public key was already there. */
+ pk_pub_free(pub);
return false;
}
}
+static bool pageant_add_ssh1_key(RSAKey *rkey)
+{
+ PageantPublicKey *pub = snew(PageantPublicKey);
+ memset(pub, 0, sizeof(PageantPublicKey));
+ PageantPrivateKey *priv = snew(PageantPrivateKey);
+ memset(priv, 0, sizeof(PageantPrivateKey));
+
+ priv->sort.ssh_version = 1;
+ priv->base_pub = makeblob1(rkey);
+ pub->full_pub = makeblob1(rkey);
+
+ if (rkey->comment)
+ pub->comment = dupstr(rkey->comment);
+
+ priv->rkey = snew(RSAKey);
+ duprsakey(priv->rkey, rkey);
+
+ return pageant_add_key_common(pub, priv);
+}
+
static bool pageant_add_ssh2_key(ssh2_userkey *skey)
{
- 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;
+ PageantPublicKey *pub = snew(PageantPublicKey);
+ memset(pub, 0, sizeof(PageantPublicKey));
+ PageantPrivateKey *priv = snew(PageantPrivateKey);
+ memset(priv, 0, sizeof(PageantPrivateKey));
- 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;
+ priv->sort.ssh_version = 2;
+ priv->base_pub = makeblob2base(skey->key);
+ pub->full_pub = makeblob2full(skey->key);
+
+ if (skey->comment)
+ pub->comment = dupstr(skey->comment);
+
+ /* Duplicate the ssh_key to go in priv */
+ {
+ strbuf *tmp = strbuf_new_nm();
+ ssh_key_openssh_blob(skey->key, BinarySink_UPCAST(tmp));
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(tmp));
+ priv->skey = ssh_key_new_priv_openssh(ssh_key_alg(skey->key), src);
+ strbuf_free(tmp);
+ }
+
+ return pageant_add_key_common(pub, priv);
+}
+
+static bool pageant_add_ssh2_key_encrypted(PageantPublicKeySort sort,
+ const char *comment, ptrlen keyfile)
+{
+ PageantPublicKey *pub = snew(PageantPublicKey);
+ memset(pub, 0, sizeof(PageantPublicKey));
+ PageantPrivateKey *priv = snew(PageantPrivateKey);
+ memset(priv, 0, sizeof(PageantPrivateKey));
+
+ assert(sort.priv.ssh_version == 2);
+ priv->sort.ssh_version = sort.priv.ssh_version;
+ priv->base_pub = strbuf_dup(sort.priv.base_pub);
+ pub->full_pub = strbuf_dup(sort.full_pub);
+
+ pub->comment = dupstr(comment);
+
+ priv->encrypted_key_file = strbuf_dup_nm(keyfile);
+ priv->encrypted_key_comment = dupstr(comment);
+
+ return pageant_add_key_common(pub, priv);
+}
+
+static void remove_pubkey_cleanup(PageantPublicKey *pub)
+{
+ /* Common function called when we've just removed a public key
+ * from pubkeytree: we must also check whether that was the last
+ * public key sharing a private half, and if so, remove the
+ * corresponding private entry too. */
+
+ PageantPublicKeySort pubsearch;
+ pubsearch.priv = pub->sort.priv;
+ pubsearch.full_pub = PTRLEN_LITERAL("");
+ PageantPublicKey *pubfound = findrel234(
+ pubkeytree, &pubsearch, NULL, REL234_GE);
+
+ if (pubfound && !privkey_cmpfn(&pub->sort.priv, &pubfound->sort.priv)) {
+ /* There's still a public key which has the same sort.priv as
+ * the one we've just removed. We're good. */
} else {
- /* The key was already in the tree in full. */
- pk_free(pk);
- return false;
+ /* We've just removed the last public key of the family, so
+ * delete the private half as well. */
+ PageantPrivateKey *priv = del234(privkeytree, &pub->sort.priv);
+ assert(priv);
+ assert(!privkey_cmpfn(&priv->sort, &pub->sort.priv));
+ pk_priv_free(priv);
}
}
+static PageantPublicKey *del_pubkey_pos(int pos)
+{
+ PageantPublicKey *deleted = delpos234(pubkeytree, pos);
+ remove_pubkey_cleanup(deleted);
+ return deleted;
+}
+
+static void del_pubkey(PageantPublicKey *to_delete)
+{
+ PageantPublicKey *deleted = del234(pubkeytree, to_delete);
+ remove_pubkey_cleanup(deleted);
+}
+
static void remove_all_keys(int ssh_version)
{
- int start = find_first_key_for_version(ssh_version);
- int end = find_first_key_for_version(ssh_version + 1);
+ int start = find_first_pubkey_for_version(ssh_version);
+ int end = find_first_pubkey_for_version(ssh_version + 1);
while (end > start) {
- PageantKey *pk = delpos234(keytree, --end);
- assert(pk->sort.ssh_version == ssh_version);
- pk_free(pk);
+ PageantPublicKey *pub = del_pubkey_pos(--end);
+ assert(pub->sort.priv.ssh_version == ssh_version);
+ pk_pub_free(pub);
}
}
static void list_keys(BinarySink *bs, int ssh_version, bool extended)
{
int i;
- PageantKey *pk;
+ PageantPublicKey *pub;
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)
+ for (i = find_first_pubkey_for_version(ssh_version);
+ NULL != (pub = index234(pubkeytree, i)); i++) {
+ if (pub->sort.priv.ssh_version != ssh_version)
break;
if (ssh_version > 1)
- put_stringpl(bs, pk->sort.public_blob);
+ put_stringpl(bs, pub->sort.full_pub);
else
- put_datapl(bs, pk->sort.public_blob); /* no header */
+ put_datapl(bs, pub->sort.full_pub); /* no header */
- put_stringpl(bs, ptrlen_from_asciz(pk->comment));
+ put_stringpl(bs, ptrlen_from_asciz(pub->comment));
if (extended) {
+ assert(ssh_version == 2); /* extended lists not supported in v1 */
+
/*
* Append to each key entry a string containing extension
* data. This string begins with a flags word, and may in
@@ -303,12 +553,14 @@ static void list_keys(BinarySink *bs, int ssh_version, bool extended)
* string, so that clients that only partially understand
* it can still find the parts they do understand.
*/
+ PageantPrivateKey *priv = pub_to_priv(pub);
+
strbuf *sb = strbuf_new();
uint32_t flags = 0;
- if (!pk->skey)
+ if (!priv->skey)
flags |= LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY;
- if (pk->encrypted_key_file)
+ if (priv->encrypted_key_file)
flags |= LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE;
put_uint32(sb, flags);
@@ -359,13 +611,24 @@ static PRINTF_LIKE(5, 6) void failure(
}
}
-static void signop_link(PageantSignOp *so)
+static void signop_link_to_key(PageantSignOp *so)
+{
+ assert(!so->pkr.prev);
+ assert(!so->pkr.next);
+
+ so->pkr.prev = so->priv->blocked_requests.prev;
+ so->pkr.next = &so->priv->blocked_requests;
+ so->pkr.prev->next = &so->pkr;
+ so->pkr.next->prev = &so->pkr;
+}
+
+static void signop_link_to_pending_gui_request(PageantSignOp *so)
{
assert(!so->pkr.prev);
assert(!so->pkr.next);
- so->pkr.prev = so->pk->blocked_requests.prev;
- so->pkr.next = &so->pk->blocked_requests;
+ so->pkr.prev = requests_blocked_on_gui.prev;
+ so->pkr.next = &requests_blocked_on_gui;
so->pkr.prev->next = &so->pkr;
so->pkr.next->prev = &so->pkr;
}
@@ -376,6 +639,7 @@ static void signop_unlink(PageantSignOp *so)
assert(so->pkr.prev);
so->pkr.next->prev = so->pkr.prev;
so->pkr.prev->next = so->pkr.next;
+ so->pkr.prev = so->pkr.next = NULL;
} else {
assert(!so->pkr.prev);
}
@@ -388,19 +652,19 @@ static void signop_free(PageantAsyncOp *pao)
sfree(so);
}
-static bool request_passphrase(PageantClient *pc, PageantKey *pk)
+static bool request_passphrase(PageantClient *pc, PageantPrivateKey *priv)
{
- if (!pk->decryption_prompt_active) {
+ if (!priv->decryption_prompt_active) {
assert(!gui_request_in_progress);
bool created_dlg = pageant_client_ask_passphrase(
- pc, &pk->dlgid, pk->comment);
+ pc, &priv->dlgid, priv->encrypted_key_comment);
if (!created_dlg)
return false;
gui_request_in_progress = true;
- pk->decryption_prompt_active = true;
+ priv->decryption_prompt_active = true;
}
return true;
@@ -413,13 +677,16 @@ static void signop_coroutine(PageantAsyncOp *pao)
crBegin(so->crLine);
- while (!so->pk->skey && gui_request_in_progress)
+ while (!so->priv->skey && gui_request_in_progress) {
+ signop_link_to_pending_gui_request(so);
crReturnV;
+ signop_unlink(so);
+ }
- if (!so->pk->skey) {
- assert(so->pk->encrypted_key_file);
+ if (!so->priv->skey) {
+ assert(so->priv->encrypted_key_file);
- if (!request_passphrase(so->pao.info->pc, so->pk)) {
+ if (!request_passphrase(so->pao.info->pc, so->priv)) {
response = strbuf_new();
failure(so->pao.info->pc, so->pao.reqid, response,
so->failure_type, "on-demand decryption could not "
@@ -427,12 +694,12 @@ static void signop_coroutine(PageantAsyncOp *pao)
goto respond;
}
- signop_link(so);
+ signop_link_to_key(so);
crReturnV;
signop_unlink(so);
}
- uint32_t supported_flags = ssh_key_alg(so->pk->skey->key)->supported_flags;
+ uint32_t supported_flags = ssh_key_supported_flags(so->priv->skey);
if (so->flags & ~supported_flags) {
/*
* We MUST reject any message containing flags we don't
@@ -445,7 +712,7 @@ static void signop_coroutine(PageantAsyncOp *pao)
goto respond;
}
- char *invalid = ssh_key_invalid(so->pk->skey->key, so->flags);
+ char *invalid = ssh_key_invalid(so->priv->skey, so->flags);
if (invalid) {
response = strbuf_new();
failure(so->pao.info->pc, so->pao.reqid, response, so->failure_type,
@@ -455,7 +722,7 @@ static void signop_coroutine(PageantAsyncOp *pao)
}
strbuf *signature = strbuf_new();
- ssh_key_sign(so->pk->skey->key, ptrlen_from_strbuf(so->data_to_sign),
+ ssh_key_sign(so->priv->skey, ptrlen_from_strbuf(so->data_to_sign),
so->flags, BinarySink_UPCAST(signature));
response = strbuf_new();
@@ -476,10 +743,10 @@ static const PageantAsyncOpVtable signop_vtable = {
.free = signop_free,
};
-static void fail_requests_for_key(PageantKey *pk, const char *reason)
+static void fail_requests_for_key(PageantPrivateKey *priv, const char *reason)
{
- while (pk->blocked_requests.next != &pk->blocked_requests) {
- PageantSignOp *so = container_of(pk->blocked_requests.next,
+ while (priv->blocked_requests.next != &priv->blocked_requests) {
+ PageantSignOp *so = container_of(priv->blocked_requests.next,
PageantSignOp, pkr);
signop_unlink(so);
strbuf *sb = strbuf_new();
@@ -492,12 +759,20 @@ static void fail_requests_for_key(PageantKey *pk, const char *reason)
}
}
-static void unblock_requests_for_key(PageantKey *pk)
+static void unblock_requests_for_key(PageantPrivateKey *priv)
{
- for (PageantKeyRequestNode *pkr = pk->blocked_requests.next;
- pkr != &pk->blocked_requests; pkr = pkr->next) {
- PageantSignOp *so = container_of(pk->blocked_requests.next,
- PageantSignOp, pkr);
+ for (PageantKeyRequestNode *pkr = priv->blocked_requests.next;
+ pkr != &priv->blocked_requests; pkr = pkr->next) {
+ PageantSignOp *so = container_of(pkr, PageantSignOp, pkr);
+ queue_toplevel_callback(pageant_async_op_callback, &so->pao);
+ }
+}
+
+static void unblock_pending_gui_requests(void)
+{
+ for (PageantKeyRequestNode *pkr = requests_blocked_on_gui.next;
+ pkr != &requests_blocked_on_gui; pkr = pkr->next) {
+ PageantSignOp *so = container_of(pkr, PageantSignOp, pkr);
queue_toplevel_callback(pageant_async_op_callback, &so->pao);
}
}
@@ -505,38 +780,33 @@ static void unblock_requests_for_key(PageantKey *pk)
void pageant_passphrase_request_success(PageantClientDialogId *dlgid,
ptrlen passphrase)
{
- PageantKey *pk = container_of(dlgid, PageantKey, dlgid);
+ PageantPrivateKey *priv = container_of(dlgid, PageantPrivateKey, dlgid);
assert(gui_request_in_progress);
gui_request_in_progress = false;
- pk->decryption_prompt_active = false;
+ priv->decryption_prompt_active = false;
- if (!pk->skey) {
+ if (!priv->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);
+ priv->encrypted_key_file));
+ strbuf *ppsb = strbuf_dup_nm(passphrase);
+ ssh2_userkey *skey = ppk_load_s(src, ppsb->s, &error);
strbuf_free(ppsb);
- if (!pk->skey) {
- fail_requests_for_key(pk, "unable to decrypt key");
+ if (!skey) {
+ fail_requests_for_key(priv, "unable to decrypt key");
return;
- } else if (pk->skey == SSH2_WRONG_PASSPHRASE) {
- pk->skey = NULL;
-
+ } else if (skey == SSH2_WRONG_PASSPHRASE) {
/*
* Find a PageantClient to use for another attempt at
* request_passphrase.
*/
- PageantKeyRequestNode *pkr = pk->blocked_requests.next;
- if (pkr == &pk->blocked_requests) {
+ PageantKeyRequestNode *pkr = priv->blocked_requests.next;
+ if (pkr == &priv->blocked_requests) {
/*
* Special case: if all the requests have gone away at
* this point, we need not bother putting up a request
@@ -545,32 +815,39 @@ void pageant_passphrase_request_success(PageantClientDialogId *dlgid,
return;
}
- PageantSignOp *so = container_of(pk->blocked_requests.next,
+ PageantSignOp *so = container_of(priv->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 "
+ priv->decryption_prompt_active = false;
+ if (!request_passphrase(so->pao.info->pc, so->priv)) {
+ fail_requests_for_key(priv, "unable to continue creating "
"passphrase prompts");
}
return;
} else {
+ priv->skey = skey->key;
+ sfree(skey->comment);
+ sfree(skey);
keylist_update();
}
}
- unblock_requests_for_key(pk);
+ unblock_requests_for_key(priv);
+
+ unblock_pending_gui_requests();
}
void pageant_passphrase_request_refused(PageantClientDialogId *dlgid)
{
- PageantKey *pk = container_of(dlgid, PageantKey, dlgid);
+ PageantPrivateKey *priv = container_of(dlgid, PageantPrivateKey, dlgid);
assert(gui_request_in_progress);
gui_request_in_progress = false;
- pk->decryption_prompt_active = false;
+ priv->decryption_prompt_active = false;
- fail_requests_for_key(pk, "user refused to supply passphrase");
+ fail_requests_for_key(priv, "user refused to supply passphrase");
+
+ unblock_pending_gui_requests();
}
typedef struct PageantImmOp PageantImmOp;
@@ -608,9 +885,11 @@ static const PageantAsyncOpVtable immop_vtable = {
.free = immop_free,
};
-static bool reencrypt_key(PageantKey *pk)
+static bool reencrypt_key(PageantPublicKey *pub)
{
- if (pk->sort.ssh_version != 2) {
+ PageantPrivateKey *priv = pub_to_priv(pub);
+
+ if (priv->sort.ssh_version != 2) {
/*
* We don't support storing SSH-1 keys in encrypted form at
* all.
@@ -618,7 +897,7 @@ static bool reencrypt_key(PageantKey *pk)
return false;
}
- if (!pk->encrypted_key_file) {
+ if (!priv->encrypted_key_file) {
/*
* We can't re-encrypt a key if it doesn't have an encrypted
* form. (We could make one up, of course - but with what
@@ -627,14 +906,12 @@ static bool reencrypt_key(PageantKey *pk)
return false;
}
- /* Only actually free pk->skey if it exists. But we return success
+ /* Only actually free priv->skey if it exists. But we return success
* regardless, so that 'please ensure this key isn't stored
* decrypted' is idempotent. */
- if (pk->skey) {
- sfree(pk->skey->comment);
- ssh_key_free(pk->skey->key);
- sfree(pk->skey);
- pk->skey = NULL;
+ if (priv->skey) {
+ ssh_key_free(priv->skey);
+ priv->skey = NULL;
}
return true;
@@ -678,9 +955,10 @@ static PageantAsyncOp *pageant_make_op(
"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);
+ PageantPublicKey *pub;
+ for (i = 0; NULL != (pub = pageant_nth_pubkey(1, i)); i++) {
+ PageantPrivateKey *priv = pub_to_priv(pub);
+ char *fingerprint = rsa_ssh1_fingerprint(priv->rkey);
pageant_client_log(pc, reqid, "returned key: %s",
fingerprint);
sfree(fingerprint);
@@ -701,12 +979,12 @@ static PageantAsyncOp *pageant_make_op(
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);
+ PageantPublicKey *pub;
+ for (i = 0; NULL != (pub = pageant_nth_pubkey(2, i)); i++) {
+ char *fingerprint = ssh2_double_fingerprint_blob(
+ pub->sort.full_pub, SSH_FPTYPE_DEFAULT);
pageant_client_log(pc, reqid, "returned key: %s %s",
- fingerprint, pk->comment);
+ fingerprint, pub->comment);
sfree(fingerprint);
}
}
@@ -719,7 +997,8 @@ static PageantAsyncOp *pageant_make_op(
* or not.
*/
RSAKey reqkey;
- PageantKey *pk;
+ PageantPublicKey *pub;
+ PageantPrivateKey *priv;
mp_int *challenge, *response;
ptrlen session_id;
unsigned response_type;
@@ -753,11 +1032,12 @@ static PageantAsyncOp *pageant_make_op(
sfree(fingerprint);
}
- if ((pk = findkey1(&reqkey)) == NULL) {
+ if ((pub = findpubkey1(&reqkey)) == NULL) {
fail("key not found");
goto challenge1_cleanup;
}
- response = rsa_ssh1_decrypt(challenge, pk->rkey);
+ priv = pub_to_priv(pub);
+ response = rsa_ssh1_decrypt(challenge, priv->rkey);
{
ssh_hash *h = ssh_hash_new(&ssh_md5);
@@ -772,7 +1052,7 @@ static PageantAsyncOp *pageant_make_op(
pageant_client_log(pc, reqid, "reply: SSH1_AGENT_RSA_RESPONSE");
- challenge1_cleanup:
+ challenge1_cleanup:
if (response)
mp_free(response);
mp_free(challenge);
@@ -785,7 +1065,7 @@ static PageantAsyncOp *pageant_make_op(
* SSH_AGENT_FAILURE, depending on whether we have that key
* or not.
*/
- PageantKey *pk;
+ PageantPublicKey *pub;
ptrlen keyblob, sigdata;
uint32_t flags;
@@ -813,12 +1093,12 @@ static PageantAsyncOp *pageant_make_op(
have_flags = true;
if (!pc->suppress_logging) {
- char *fingerprint = ssh2_fingerprint_blob(
+ char *fingerprint = ssh2_double_fingerprint_blob(
keyblob, SSH_FPTYPE_DEFAULT);
pageant_client_log(pc, reqid, "requested key: %s", fingerprint);
sfree(fingerprint);
}
- if ((pk = findkey2(keyblob)) == NULL) {
+ if ((pub = findpubkey2(keyblob)) == NULL) {
fail("key not found");
goto responded;
}
@@ -836,11 +1116,11 @@ static PageantAsyncOp *pageant_make_op(
so->pao.info = pc->info;
so->pao.cr.prev = pc->info->head.prev;
so->pao.cr.next = &pc->info->head;
+ so->pao.cr.prev->next = so->pao.cr.next->prev = &so->pao.cr;
so->pao.reqid = reqid;
- so->pk = pk;
+ so->priv = pub_to_priv(pub);
so->pkr.prev = so->pkr.next = NULL;
- so->data_to_sign = strbuf_new();
- put_datapl(so->data_to_sign, sigdata);
+ so->data_to_sign = strbuf_dup(sigdata);
so->flags = flags;
so->failure_type = failure_type;
so->crLine = 0;
@@ -885,7 +1165,7 @@ static PageantAsyncOp *pageant_make_op(
fail("key already present");
}
- add1_cleanup:
+ add1_cleanup:
if (key) {
freersakey(key);
sfree(key);
@@ -946,7 +1226,7 @@ static PageantAsyncOp *pageant_make_op(
fail("key already present");
}
- add2_cleanup:
+ add2_cleanup:
if (key) {
if (key->key)
ssh_key_free(key->key);
@@ -963,7 +1243,7 @@ static PageantAsyncOp *pageant_make_op(
* start with.
*/
RSAKey reqkey;
- PageantKey *pk;
+ PageantPublicKey *pub;
pageant_client_log(pc, reqid,
"request: SSH1_AGENTC_REMOVE_RSA_IDENTITY");
@@ -985,15 +1265,15 @@ static PageantAsyncOp *pageant_make_op(
sfree(fingerprint);
}
- pk = findkey1(&reqkey);
+ pub = findpubkey1(&reqkey);
freersakey(&reqkey);
- if (pk) {
+ if (pub) {
pageant_client_log(pc, reqid, "found with comment: %s",
- pk->rkey->comment);
+ pub->comment);
- del234(keytree, pk);
+ del_pubkey(pub);
keylist_update();
- pk_free(pk);
+ pk_pub_free(pub);
put_byte(sb, SSH_AGENT_SUCCESS);
pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS");
@@ -1008,7 +1288,7 @@ static PageantAsyncOp *pageant_make_op(
* perhaps SSH_AGENT_FAILURE if it wasn't in the list to
* start with.
*/
- PageantKey *pk;
+ PageantPublicKey *pub;
ptrlen blob;
pageant_client_log(pc, reqid, "request: SSH2_AGENTC_REMOVE_IDENTITY");
@@ -1021,23 +1301,23 @@ static PageantAsyncOp *pageant_make_op(
}
if (!pc->suppress_logging) {
- char *fingerprint = ssh2_fingerprint_blob(
+ char *fingerprint = ssh2_double_fingerprint_blob(
blob, SSH_FPTYPE_DEFAULT);
pageant_client_log(pc, reqid, "unwanted key: %s", fingerprint);
sfree(fingerprint);
}
- pk = findkey2(blob);
- if (!pk) {
+ pub = findpubkey2(blob);
+ if (!pub) {
fail("key not found");
goto responded;
}
- pageant_client_log(pc, reqid, "found with comment: %s", pk->comment);
+ pageant_client_log(pc, reqid, "found with comment: %s", pub->comment);
- del234(keytree, pk);
+ del_pubkey(pub);
keylist_update();
- pk_free(pk);
+ pk_pub_free(pub);
put_byte(sb, SSH_AGENT_SUCCESS);
pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS");
@@ -1120,22 +1400,24 @@ static PageantAsyncOp *pageant_make_op(
goto responded;
}
+ strbuf *base_pub = NULL;
+ strbuf *full_pub = NULL;
BinarySource src[1];
const char *error;
- strbuf *public_blob = strbuf_new();
+ full_pub = strbuf_new();
char *comment;
BinarySource_BARE_INIT_PL(src, keyfile);
- if (!ppk_loadpub_s(src, NULL, BinarySink_UPCAST(public_blob),
+ if (!ppk_loadpub_s(src, NULL, BinarySink_UPCAST(full_pub),
&comment, &error)) {
fail("failed to extract public key blob: %s", error);
goto add_ppk_cleanup;
}
if (!pc->suppress_logging) {
- char *fingerprint = ssh2_fingerprint_blob(
- ptrlen_from_strbuf(public_blob), SSH_FPTYPE_DEFAULT);
+ char *fingerprint = ssh2_double_fingerprint_blob(
+ ptrlen_from_strbuf(full_pub), SSH_FPTYPE_DEFAULT);
pageant_client_log(pc, reqid, "add-ppk: %s %s",
fingerprint, comment);
sfree(fingerprint);
@@ -1168,57 +1450,21 @@ static PageantAsyncOp *pageant_make_op(
goto add_ppk_cleanup;
}
- PageantKeySort sort =
- keysort(2, ptrlen_from_strbuf(public_blob));
+ PageantPublicKeySort sort;
+ sort.priv.ssh_version = 2;
+ sort.full_pub = ptrlen_from_strbuf(full_pub);
+ base_pub = make_base_pub_2(&sort);
- 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)");
- }
+ pageant_add_ssh2_key_encrypted(sort, comment, keyfile);
+ keylist_update();
+ put_byte(sb, SSH_AGENT_SUCCESS);
+ pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS");
- add_ppk_cleanup:
- if (public_blob)
- strbuf_free(public_blob);
+ add_ppk_cleanup:
+ if (full_pub)
+ strbuf_free(full_pub);
+ if (base_pub)
+ strbuf_free(base_pub);
sfree(comment);
break;
}
@@ -1239,23 +1485,23 @@ static PageantAsyncOp *pageant_make_op(
}
if (!pc->suppress_logging) {
- char *fingerprint = ssh2_fingerprint_blob(
+ char *fingerprint = ssh2_double_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) {
+ PageantPublicKey *pub = findpubkey2(blob);
+ if (!pub) {
fail("key not found");
goto responded;
}
pageant_client_log(pc, reqid,
- "found with comment: %s", pk->comment);
+ "found with comment: %s", pub->comment);
- if (!reencrypt_key(pk)) {
+ if (!reencrypt_key(pub)) {
fail("this key couldn't be re-encrypted");
goto responded;
}
@@ -1282,10 +1528,10 @@ static PageantAsyncOp *pageant_make_op(
* having made a state change.)
*/
unsigned nfailures = 0, nsuccesses = 0;
- PageantKey *pk;
+ PageantPublicKey *pub;
- for (int i = 0; (pk = index234(keytree, i)) != NULL; i++) {
- if (reencrypt_key(pk))
+ for (int i = 0; (pub = index234(pubkeytree, i)) != NULL; i++) {
+ if (reencrypt_key(pub))
nsuccesses++;
else
nfailures++;
@@ -1322,13 +1568,13 @@ static PageantAsyncOp *pageant_make_op(
"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),
+ PageantPublicKey *pub;
+ for (i = 0; NULL != (pub = pageant_nth_pubkey(2, i)); i++) {
+ char *fingerprint = ssh2_double_fingerprint_blob(
+ ptrlen_from_strbuf(pub->full_pub),
SSH_FPTYPE_DEFAULT);
pageant_client_log(pc, reqid, "returned key: %s %s",
- fingerprint, pk->comment);
+ fingerprint, pub->comment);
sfree(fingerprint);
}
}
@@ -1353,6 +1599,7 @@ static PageantAsyncOp *pageant_make_op(
io->pao.info = pc->info;
io->pao.cr.prev = pc->info->head.prev;
io->pao.cr.next = &pc->info->head;
+ io->pao.cr.prev->next = io->pao.cr.next->prev = &io->pao.cr;
io->pao.reqid = reqid;
io->response = sb;
io->crLine = 0;
@@ -1369,43 +1616,47 @@ void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid,
void pageant_init(void)
{
pageant_local = true;
- keytree = newtree234(cmpkeys);
+ pubkeytree = newtree234(pubkey_cmpfn);
+ privkeytree = newtree234(privkey_cmpfn);
}
-static PageantKey *pageant_nth_key(int ssh_version, int i)
+static PageantPublicKey *pageant_nth_pubkey(int ssh_version, int i)
{
- PageantKey *pk = index234(
- keytree, find_first_key_for_version(ssh_version) + i);
- if (pk && pk->sort.ssh_version == ssh_version)
- return pk;
+ PageantPublicKey *pub = index234(
+ pubkeytree, find_first_pubkey_for_version(ssh_version) + i);
+ if (pub && pub->sort.priv.ssh_version == ssh_version)
+ return pub;
else
return NULL;
}
bool pageant_delete_nth_ssh1_key(int i)
{
- PageantKey *pk = delpos234(keytree, find_first_key_for_version(1) + i);
- if (!pk)
+ PageantPublicKey *pub = del_pubkey_pos(
+ find_first_pubkey_for_version(1) + i);
+ if (!pub)
return false;
- pk_free(pk);
+ pk_pub_free(pub);
return true;
}
bool pageant_delete_nth_ssh2_key(int i)
{
- PageantKey *pk = delpos234(keytree, find_first_key_for_version(2) + i);
- if (!pk)
+ PageantPublicKey *pub = del_pubkey_pos(
+ find_first_pubkey_for_version(2) + i);
+ if (!pub)
return false;
- pk_free(pk);
+ pk_pub_free(pub);
return true;
}
bool pageant_reencrypt_nth_ssh2_key(int i)
{
- PageantKey *pk = index234(keytree, find_first_key_for_version(2) + i);
- if (!pk)
+ PageantPublicKey *pub = index234(
+ pubkeytree, find_first_pubkey_for_version(2) + i);
+ if (!pub)
return false;
- return reencrypt_key(pk);
+ return reencrypt_key(pub);
}
void pageant_delete_all(void)
@@ -1416,9 +1667,9 @@ void pageant_delete_all(void)
void pageant_reencrypt_all(void)
{
- PageantKey *pk;
- for (int i = 0; (pk = index234(keytree, i)) != NULL; i++)
- reencrypt_key(pk);
+ PageantPublicKey *pub;
+ for (int i = 0; (pub = index234(pubkeytree, i)) != NULL; i++)
+ reencrypt_key(pub);
}
/* ----------------------------------------------------------------------
@@ -1461,12 +1712,12 @@ struct pageant_conn_state {
Plug plug;
};
-static void pageant_conn_closing(Plug *plug, const char *error_msg,
- int error_code, bool calling_back)
+static void pageant_conn_closing(Plug *plug, PlugCloseType type,
+ const char *error_msg)
{
struct pageant_conn_state *pc = container_of(
plug, struct pageant_conn_state, plug);
- if (error_msg)
+ if (type != PLUGCLOSE_NORMAL)
pageant_listener_client_log(pc->plc, "c#%"SIZEu": error: %s",
pc->conn_index, error_msg);
else
@@ -1608,12 +1859,12 @@ struct pageant_listen_state {
Plug plug;
};
-static void pageant_listen_closing(Plug *plug, const char *error_msg,
- int error_code, bool calling_back)
+static void pageant_listen_closing(Plug *plug, PlugCloseType type,
+ const char *error_msg)
{
struct pageant_listen_state *pl = container_of(
plug, struct pageant_listen_state, plug);
- if (error_msg)
+ if (type != PLUGCLOSE_NORMAL)
pageant_listener_client_log(pl->plc, "listening socket: error: %s",
error_msg);
sk_close(pl->listensock);
@@ -1624,6 +1875,7 @@ static const PlugVtable pageant_connection_plugvt = {
.closing = pageant_conn_closing,
.receive = pageant_conn_receive,
.sent = pageant_conn_sent,
+ .log = nullplug_log,
};
static int pageant_listen_accepting(Plug *plug,
@@ -1672,6 +1924,7 @@ static int pageant_listen_accepting(Plug *plug,
static const PlugVtable pageant_listener_plugvt = {
.closing = pageant_listen_closing,
.accepting = pageant_listen_accepting,
+ .log = nullplug_log,
};
struct pageant_listen_state *pageant_listener_new(
@@ -2229,8 +2482,7 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
if (kl1) {
for (size_t i = 0; i < kl1->nkeys; i++) {
- cbkey.blob = strbuf_new();
- put_datapl(cbkey.blob, kl1->keys[i].blob);
+ cbkey.blob = strbuf_dup(kl1->keys[i].blob);
cbkey.comment = mkstr(kl1->keys[i].comment);
cbkey.ssh_version = 1;
@@ -2261,8 +2513,7 @@ int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx,
if (kl2) {
for (size_t i = 0; i < kl2->nkeys; i++) {
- cbkey.blob = strbuf_new();
- put_datapl(cbkey.blob, kl2->keys[i].blob);
+ cbkey.blob = strbuf_dup(kl2->keys[i].blob);
cbkey.comment = mkstr(kl2->keys[i].comment);
cbkey.ssh_version = 2;
diff --git a/pgssapi.c b/pgssapi.c
deleted file mode 100644
index 9d33220f..00000000
--- a/pgssapi.c
+++ /dev/null
@@ -1,105 +0,0 @@
-/* This file actually defines the GSSAPI function pointers for
- * functions we plan to import from a GSSAPI library.
- */
-#include "putty.h"
-
-#ifndef NO_GSSAPI
-
-#include "pgssapi.h"
-
-#ifndef NO_LIBDL
-
-/* Reserved static storage for GSS_oids. 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/pgssapi.h b/pgssapi.h
deleted file mode 100644
index 53d8cb61..00000000
--- a/pgssapi.h
+++ /dev/null
@@ -1,333 +0,0 @@
-#ifndef PUTTY_PGSSAPI_H
-#define PUTTY_PGSSAPI_H
-
-#include "putty.h"
-
-#ifndef NO_GSSAPI
-
-/*
- * On Unix, if we're statically linking against GSSAPI, we leave the
- * declaration of all this lot to the official header. If we're
- * dynamically linking, we declare it ourselves, because that avoids
- * us needing the official header at compile time.
- *
- * However, we still need the function pointer types, because even
- * with statically linked GSSAPI we use the ssh_gss_library wrapper.
- */
-#ifdef STATIC_GSSAPI
-#include <gssapi/gssapi.h>
-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/pockle.c b/pockle.c
deleted file mode 100644
index 60017e33..00000000
--- a/pockle.c
+++ /dev/null
@@ -1,450 +0,0 @@
-#include <assert.h>
-#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/portfwd.c b/portfwd.c
deleted file mode 100644
index f3349b80..00000000
--- a/portfwd.c
+++ /dev/null
@@ -1,1174 +0,0 @@
-/*
- * SSH port forwarding.
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#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);
-}
-
-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/pproxy.c b/pproxy.c
deleted file mode 100644
index 4b08606e..00000000
--- a/pproxy.c
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * pproxy.c: dummy implementation of platform_new_connection(), to
- * be supplanted on any platform which has its own local proxy
- * method.
- */
-
-#include "putty.h"
-#include "network.h"
-#include "proxy.h"
-
-Socket *platform_new_connection(SockAddr *addr, const char *hostname,
- int port, int privport,
- int oobinline, int nodelay, int keepalive,
- Plug *plug, Conf *conf)
-{
- return NULL;
-}
diff --git a/primecandidate.c b/primecandidate.c
deleted file mode 100644
index cf55919e..00000000
--- a/primecandidate.c
+++ /dev/null
@@ -1,445 +0,0 @@
-/*
- * primecandidate.c: implementation of the PrimeCandidateSource
- * abstraction declared in sshkeygen.h.
- */
-
-#include <assert.h>
-#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/proxy.c b/proxy.c
deleted file mode 100644
index d7069cb0..00000000
--- a/proxy.c
+++ /dev/null
@@ -1,1513 +0,0 @@
-/*
- * Network proxy abstraction in PuTTY
- *
- * A proxy layer, if necessary, wedges itself between the network
- * code and the higher level backend.
- */
-
-#include <assert.h>
-#include <ctype.h>
-#include <string.h>
-
-#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/proxy.h b/proxy.h
deleted file mode 100644
index f11e1e3d..00000000
--- a/proxy.h
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Network proxy abstraction in PuTTY
- *
- * A proxy layer, if necessary, wedges itself between the
- * network code and the higher level backend.
- *
- * Supported proxies: HTTP CONNECT, generic telnet, SOCKS 4 & 5
- */
-
-#ifndef PUTTY_PROXY_H
-#define PUTTY_PROXY_H
-
-#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/proxy/cproxy.c b/proxy/cproxy.c
new file mode 100644
index 00000000..40a2f609
--- /dev/null
+++ b/proxy/cproxy.c
@@ -0,0 +1,189 @@
+/*
+ * 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 <assert.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "putty.h"
+#include "ssh.h" /* For MD5 support */
+#include "network.h"
+#include "proxy.h"
+#include "marshal.h"
+
+const bool socks5_chap_available = true;
+const bool http_digest_available = true;
+
+strbuf *chap_response(ptrlen challenge, ptrlen password)
+{
+ strbuf *sb = strbuf_new_nm();
+ const ssh2_macalg *alg = &ssh_hmac_md5;
+ mac_simple(alg, password, challenge, strbuf_append(sb, alg->len));
+ return sb;
+}
+
+static void BinarySink_put_hex_data(BinarySink *bs, const void *vptr,
+ size_t len)
+{
+ const unsigned char *p = (const unsigned char *)vptr;
+ const char *hexdigits = "0123456789abcdef";
+ while (len-- > 0) {
+ unsigned c = *p++;
+ put_byte(bs, hexdigits[0xF & (c >> 4)]);
+ put_byte(bs, hexdigits[0xF & (c )]);
+ }
+}
+
+#define put_hex_data(bs, p, len) \
+ BinarySink_put_hex_data(BinarySink_UPCAST(bs), p, len)
+
+const char *const httphashnames[] = {
+ #define DECL_ARRAY(id, str, alg, bits, accepted) str,
+ HTTP_DIGEST_HASHES(DECL_ARRAY)
+ #undef DECL_ARRAY
+};
+
+const bool httphashaccepted[] = {
+ #define DECL_ARRAY(id, str, alg, bits, accepted) accepted,
+ HTTP_DIGEST_HASHES(DECL_ARRAY)
+ #undef DECL_ARRAY
+};
+
+static const ssh_hashalg *const httphashalgs[] = {
+ #define DECL_ARRAY(id, str, alg, bits, accepted) alg,
+ HTTP_DIGEST_HASHES(DECL_ARRAY)
+ #undef DECL_ARRAY
+};
+static const size_t httphashlengths[] = {
+ #define DECL_ARRAY(id, str, alg, bits, accepted) bits/8,
+ HTTP_DIGEST_HASHES(DECL_ARRAY)
+ #undef DECL_ARRAY
+};
+
+void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password,
+ ptrlen realm, ptrlen method, ptrlen uri, ptrlen qop,
+ ptrlen nonce, ptrlen opaque, uint32_t nonce_count,
+ HttpDigestHash hash, bool hash_username)
+{
+ unsigned char a1hash[MAX_HASH_LEN];
+ unsigned char a2hash[MAX_HASH_LEN];
+ unsigned char rsphash[MAX_HASH_LEN];
+ const ssh_hashalg *alg = httphashalgs[hash];
+ size_t hashlen = httphashlengths[hash];
+
+ unsigned char ncbuf[4];
+ PUT_32BIT_MSB_FIRST(ncbuf, nonce_count);
+
+ unsigned char client_nonce_raw[33];
+ random_read(client_nonce_raw, lenof(client_nonce_raw));
+ char client_nonce_base64[lenof(client_nonce_raw) / 3 * 4];
+ for (unsigned i = 0; i < lenof(client_nonce_raw)/3; i++)
+ base64_encode_atom(client_nonce_raw + 3*i, 3,
+ client_nonce_base64 + 4*i);
+
+ /*
+ * RFC 7616 section 3.4.2: the hash "A1" is a hash of
+ * username:realm:password (in the absence of hash names like
+ * "MD5-sess" which as far as I know don't sensibly apply to
+ * proxies and HTTP CONNECT).
+ */
+ ssh_hash *h = ssh_hash_new(alg);
+ put_datapl(h, username);
+ put_byte(h, ':');
+ put_datapl(h, realm);
+ put_byte(h, ':');
+ put_datapl(h, password);
+ ssh_hash_digest_nondestructive(h, a1hash);
+
+ /*
+ * RFC 7616 section 3.4.3: the hash "A2" is a hash of method:uri
+ * (in the absence of more interesting quality-of-protection
+ * schemes than plain "auth" - e.g. "auth-int" hashes the entire
+ * document as well - which again I don't think make sense in the
+ * context of proxies and CONNECT).
+ */
+ ssh_hash_reset(h);
+ put_datapl(h, method);
+ put_byte(h, ':');
+ put_datapl(h, uri);
+ ssh_hash_digest_nondestructive(h, a2hash);
+
+ /*
+ * RFC 7616 section 3.4.1: the overall output hash in the
+ * "response" parameter of the authorization header is a hash of
+ * A1:nonce:nonce-count:client-nonce:qop:A2, where A1 and A2 are
+ * the hashes computed above.
+ */
+ ssh_hash_reset(h);
+ put_hex_data(h, a1hash, hashlen);
+ put_byte(h, ':');
+ put_datapl(h, nonce);
+ put_byte(h, ':');
+ put_hex_data(h, ncbuf, 4);
+ put_byte(h, ':');
+ put_data(h, client_nonce_base64, lenof(client_nonce_base64));
+ put_byte(h, ':');
+ put_datapl(h, qop);
+ put_byte(h, ':');
+ put_hex_data(h, a2hash, hashlen);
+ ssh_hash_final(h, rsphash);
+
+ /*
+ * Now construct the output header (everything after the initial
+ * "Proxy-Authorization: Digest ") and write it to the provided
+ * BinarySink.
+ */
+ put_datalit(bs, "username=\"");
+ if (hash_username) {
+ /*
+ * RFC 7616 section 3.4.4: if we're hashing the username, we
+ * actually hash username:realm (like a truncated version of
+ * A1 above).
+ */
+ ssh_hash *h = ssh_hash_new(alg);
+ put_datapl(h, username);
+ put_byte(h, ':');
+ put_datapl(h, realm);
+ ssh_hash_final(h, a1hash);
+ put_hex_data(bs, a1hash, hashlen);
+ } else {
+ put_datapl(bs, username);
+ }
+ put_datalit(bs, "\", realm=\"");
+ put_datapl(bs, realm);
+ put_datalit(bs, "\", uri=\"");
+ put_datapl(bs, uri);
+ put_datalit(bs, "\", algorithm=");
+ put_dataz(bs, httphashnames[hash]);
+ put_datalit(bs, ", nonce=\"");
+ put_datapl(bs, nonce);
+ put_datalit(bs, "\", nc=");
+ put_hex_data(bs, ncbuf, 4);
+ put_datalit(bs, ", cnonce=\"");
+ put_data(bs, client_nonce_base64, lenof(client_nonce_base64));
+ put_datalit(bs, "\", qop=");
+ put_datapl(bs, qop);
+ put_datalit(bs, ", response=\"");
+ put_hex_data(bs, rsphash, hashlen);
+ put_datalit(bs, "\"");
+
+ if (opaque.ptr) {
+ put_datalit(bs, ", opaque=\"");
+ put_datapl(bs, opaque);
+ put_datalit(bs, "\"");
+ }
+
+ if (hash_username) {
+ put_datalit(bs, ", userhash=true");
+ }
+
+ smemclr(a1hash, lenof(a1hash));
+ smemclr(a2hash, lenof(a2hash));
+ smemclr(rsphash, lenof(rsphash));
+ smemclr(client_nonce_raw, lenof(client_nonce_raw));
+ smemclr(client_nonce_base64, lenof(client_nonce_base64));
+}
diff --git a/proxy/cproxy.h b/proxy/cproxy.h
new file mode 100644
index 00000000..34058dd8
--- /dev/null
+++ b/proxy/cproxy.h
@@ -0,0 +1,99 @@
+/*
+ * Header for the interaction between proxy.c and cproxy.c. Separated
+ * from proxy.h proper so that testcrypt can include it conveniently.
+ */
+
+extern const bool socks5_chap_available;
+strbuf *chap_response(ptrlen challenge, ptrlen password);
+extern const bool http_digest_available;
+
+/*
+ * List macro for the various hash functions defined for HTTP Digest.
+ *
+ * Of these, MD5 is the original one; SHA-256 is unambiguous; but
+ * SHA-512-256 seems to be controversial.
+ *
+ * RFC 7616 doesn't provide a normative reference, or any text
+ * explaining what they mean by it. They apparently expect you to
+ * already know. The problem with that is that there are two plausible
+ * things they _might_ have meant:
+ *
+ * 1. Ordinary SHA-512, truncated to 256 bits by discarding the
+ * second half of the hash output, per FIPS 180-4 section 7 (which
+ * says that in general it's OK to truncate hash functions like
+ * that if you need to). FIPS 180-4 assigns no particular specific
+ * spelling to this kind of truncated hash.
+ *
+ * 2. The same except that the initial state of the SHA-512 algorithm
+ * is reset to a different 512-bit vector to ensure that it's a
+ * distinguishable hash function in its own right, per FIPS 180-4
+ * section 6.7 (which in turn refers to section 5.3.6.2 for the
+ * actual initial values). FIPS 180-4 spells this "SHA-512/256".
+ *
+ * The text of RFC 7616 is totally silent as to which of these they
+ * meant. Their spelling is inconsistent: the protocol identifier is
+ * "SHA-512-256", but in some places in the RFC they say
+ * "SHA-512/256", matching FIPS's spelling for the hash in option 2
+ * above. On the other hand, the example authentication exchange in
+ * section 3.9.2 of the RFC contains hashes that are consistent with
+ * option 1 above (a truncation of plain SHA-512).
+ *
+ * Erratum 4897, https://www.rfc-editor.org/errata/eid4897, points out
+ * this ambiguity, and suggests correcting the example exchange to be
+ * consistent with option 2. However, as of 2021-11-27, that erratum
+ * is shown on the RFC Editor website in state "Reported", with no
+ * response (positive _or_ negative) from the RFC authors or anyone
+ * else. (And it was reported in 2016, so it's not as if they haven't
+ * had time.)
+ *
+ * So, which hash should we implement? Perhaps there's a consensus
+ * among existing implementations in the wild?
+ *
+ * I rigged up an HTTP server to present a SHA-512-256 Digest auth
+ * request, and tried various HTTP clients against it. The only HTTP
+ * client I found that accepts 'algorithm="SHA-512-256"' and sends
+ * back an auth attempt quoting the same hash is curl - and curl,
+ * bizarrely, seems to treat "SHA-512-256" as _neither_ of the above
+ * options, but as simply an alias for SHA-256!
+ *
+ * Therefore, I think the only safe answer is to refuse to support
+ * that hash at all: it's too confusing.
+ *
+ * However, I keep it in the list of hashes here, so that we can check
+ * the test case from RFC 7616, because that test case is also the
+ * only test of username hashing. So we reject it in proxy/http.c, but
+ * accept it in the internal function http_digest_response(), and
+ * treat it as option 1 (truncated SHA-512).
+ *
+ * Therefore, the parameters to each invocation of X in the following
+ * list macro are:
+ *
+ * - internal enum id for the hash
+ * - protocol identifier string
+ * - algorithm to use for computing it (as a const ssh_hashalg *)
+ * - length to truncate the output to
+ * - whether we accept it in http.c or not.
+ *
+ * Finally, the ordering of the accepted hashes is our preference
+ * order among them if the server offers a choice.
+ */
+#define HTTP_DIGEST_HASHES(X) \
+ X(HTTP_DIGEST_MD5, "MD5", &ssh_md5, 128, true) \
+ X(HTTP_DIGEST_SHA256, "SHA-256", &ssh_sha256, 256, true) \
+ X(HTTP_DIGEST_SHA512_256, "SHA-512-256", &ssh_sha512, 256, false) \
+ /* end of list */
+
+typedef enum HttpDigestHash {
+ #define DECL_ENUM(id, str, alg, bits, accepted) id,
+ HTTP_DIGEST_HASHES(DECL_ENUM)
+ #undef DECL_ENUM
+ N_HTTP_DIGEST_HASHES
+} HttpDigestHash;
+
+extern const char *const httphashnames[];
+extern const bool httphashaccepted[];
+
+void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password,
+ ptrlen realm, ptrlen method, ptrlen uri, ptrlen qop,
+ ptrlen nonce, ptrlen opaque, uint32_t nonce_count,
+ HttpDigestHash hash, bool hash_username);
diff --git a/proxy/http.c b/proxy/http.c
new file mode 100644
index 00000000..0738e37d
--- /dev/null
+++ b/proxy/http.c
@@ -0,0 +1,781 @@
+/*
+ * HTTP CONNECT proxy negotiation.
+ */
+
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+#include "sshcr.h"
+
+static bool read_line(bufchain *input, strbuf *output, bool is_header)
+{
+ char c;
+
+ while (bufchain_try_fetch(input, &c, 1)) {
+ if (is_header && output->len > 0 &&
+ output->s[output->len - 1] == '\n') {
+ /*
+ * A newline terminates the header, provided we're sure it
+ * is _not_ followed by a space or a tab.
+ */
+ if (c != ' ' && c != '\t')
+ goto done; /* we have a complete header line */
+ } else {
+ put_byte(output, c);
+ bufchain_consume(input, 1);
+
+ if (!is_header && output->len > 0 &&
+ output->s[output->len - 1] == '\n') {
+ /* If we're looking for just a line, not an HTTP
+ * header, then any newline terminates it. */
+ goto done;
+ }
+ }
+ }
+
+ return false;
+
+ done:
+ strbuf_chomp(output, '\n');
+ strbuf_chomp(output, '\r');
+ return true;
+}
+
+/* Types of HTTP authentication, in preference order. */
+typedef enum HttpAuthType {
+ AUTH_ERROR, /* if an HttpAuthDetails was never satisfactorily filled in */
+ AUTH_NONE, /* if no auth header is seen, assume no auth required */
+ AUTH_BASIC, /* username + password sent in clear (only keyless base64) */
+ AUTH_DIGEST, /* cryptographic hash, most preferred if available */
+} HttpAuthType;
+
+typedef struct HttpAuthDetails {
+ HttpAuthType auth_type;
+ bool digest_nonce_was_stale;
+ HttpDigestHash digest_hash;
+ strbuf *realm, *nonce, *opaque, *error;
+ bool got_opaque;
+ bool hash_username;
+} HttpAuthDetails;
+
+typedef struct HttpProxyNegotiator {
+ int crLine;
+ strbuf *response, *header, *token;
+ int http_status_pos;
+ size_t header_pos;
+ strbuf *username, *password;
+ int http_status;
+ bool connection_close;
+ HttpAuthDetails *next_auth;
+ bool try_auth_from_conf;
+ strbuf *uri;
+ uint32_t nonce_count;
+ prompts_t *prompts;
+ int username_prompt_index, password_prompt_index;
+ size_t content_length, chunk_length;
+ bool chunked_transfer;
+ ProxyNegotiator pn;
+} HttpProxyNegotiator;
+
+static inline HttpAuthDetails *auth_error(HttpAuthDetails *d,
+ const char *fmt, ...)
+{
+ d->auth_type = AUTH_ERROR;
+ put_fmt(d->error, "Unable to parse auth header from HTTP proxy");
+ if (fmt) {
+ va_list ap;
+ va_start(ap, fmt);
+ put_datalit(d->error, ": ");
+ put_fmtv(d->error, fmt, ap);
+ va_end(ap);
+ }
+ return d;
+}
+
+static HttpAuthDetails *http_auth_details_new(void)
+{
+ HttpAuthDetails *d = snew(HttpAuthDetails);
+ memset(d, 0, sizeof(*d));
+ d->realm = strbuf_new();
+ d->nonce = strbuf_new();
+ d->opaque = strbuf_new();
+ d->error = strbuf_new();
+ return d;
+}
+
+static void http_auth_details_free(HttpAuthDetails *d)
+{
+ strbuf_free(d->realm);
+ strbuf_free(d->nonce);
+ strbuf_free(d->opaque);
+ strbuf_free(d->error);
+ sfree(d);
+}
+
+static ProxyNegotiator *proxy_http_new(const ProxyNegotiatorVT *vt)
+{
+ HttpProxyNegotiator *s = snew(HttpProxyNegotiator);
+ memset(s, 0, sizeof(*s));
+ s->pn.vt = vt;
+ s->response = strbuf_new();
+ s->header = strbuf_new();
+ s->token = strbuf_new();
+ s->username = strbuf_new();
+ s->password = strbuf_new_nm();
+ s->uri = strbuf_new();
+ s->nonce_count = 0;
+ /*
+ * Always start with a CONNECT request containing no auth. If the
+ * proxy rejects that, it will tell us what kind of auth it would
+ * prefer.
+ */
+ s->next_auth = http_auth_details_new();
+ s->next_auth->auth_type = AUTH_NONE;
+ return &s->pn;
+}
+
+static void proxy_http_free(ProxyNegotiator *pn)
+{
+ HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn);
+ strbuf_free(s->response);
+ strbuf_free(s->header);
+ strbuf_free(s->token);
+ strbuf_free(s->username);
+ strbuf_free(s->password);
+ strbuf_free(s->uri);
+ http_auth_details_free(s->next_auth);
+ if (s->prompts)
+ free_prompts(s->prompts);
+ sfree(s);
+}
+
+#define HTTP_HEADER_LIST(X) \
+ X(HDR_CONNECTION, "Connection") \
+ X(HDR_CONTENT_LENGTH, "Content-Length") \
+ X(HDR_TRANSFER_ENCODING, "Transfer-Encoding") \
+ X(HDR_PROXY_AUTHENTICATE, "Proxy-Authenticate") \
+ X(HDR_PROXY_CONNECTION, "Proxy-Connection") \
+ /* end of list */
+
+typedef enum HttpHeader {
+ #define ENUM_DEF(id, string) id,
+ HTTP_HEADER_LIST(ENUM_DEF)
+ #undef ENUM_DEF
+ HDR_UNKNOWN
+} HttpHeader;
+
+static inline bool is_whitespace(char c)
+{
+ return (c == ' ' || c == '\t' || c == '\n');
+}
+
+static inline bool is_separator(char c)
+{
+ return (c == '(' || c == ')' || c == '<' || c == '>' || c == '@' ||
+ c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' ||
+ c == '/' || c == '[' || c == ']' || c == '?' || c == '=' ||
+ c == '{' || c == '}');
+}
+
+#define HTTP_SEPARATORS
+
+static bool get_end_of_header(HttpProxyNegotiator *s)
+{
+ size_t pos = s->header_pos;
+
+ while (pos < s->header->len && is_whitespace(s->header->s[pos]))
+ pos++;
+
+ if (pos == s->header->len) {
+ s->header_pos = pos;
+ return true;
+ }
+
+ return false;
+}
+
+static bool get_token(HttpProxyNegotiator *s)
+{
+ size_t pos = s->header_pos;
+
+ while (pos < s->header->len && is_whitespace(s->header->s[pos]))
+ pos++;
+
+ if (pos == s->header->len)
+ return false; /* end of string */
+
+ if (is_separator(s->header->s[pos]))
+ return false;
+
+ strbuf_clear(s->token);
+ while (pos < s->header->len &&
+ !is_whitespace(s->header->s[pos]) &&
+ !is_separator(s->header->s[pos]))
+ put_byte(s->token, s->header->s[pos++]);
+
+ s->header_pos = pos;
+ return true;
+}
+
+static bool get_separator(HttpProxyNegotiator *s, char sep)
+{
+ size_t pos = s->header_pos;
+
+ while (pos < s->header->len && is_whitespace(s->header->s[pos]))
+ pos++;
+
+ if (pos == s->header->len)
+ return false; /* end of string */
+
+ if (s->header->s[pos] != sep)
+ return false;
+
+ s->header_pos = ++pos;
+ return true;
+}
+
+static bool get_quoted_string(HttpProxyNegotiator *s)
+{
+ size_t pos = s->header_pos;
+
+ while (pos < s->header->len && is_whitespace(s->header->s[pos]))
+ pos++;
+
+ if (pos == s->header->len)
+ return false; /* end of string */
+
+ if (s->header->s[pos] != '"')
+ return false;
+ pos++;
+
+ strbuf_clear(s->token);
+ while (pos < s->header->len && s->header->s[pos] != '"') {
+ if (s->header->s[pos] == '\\') {
+ /* Backslash makes the next char literal, even if it's " or \ */
+ pos++;
+ if (pos == s->header->len)
+ return false; /* unexpected end of string */
+ }
+ put_byte(s->token, s->header->s[pos++]);
+ }
+
+ if (pos == s->header->len)
+ return false; /* no closing quote */
+ pos++;
+
+ s->header_pos = pos;
+ return true;
+}
+
+static HttpAuthDetails *parse_http_auth_header(HttpProxyNegotiator *s)
+{
+ HttpAuthDetails *d = http_auth_details_new();
+
+ /* Default hash for HTTP Digest is MD5, if none specified explicitly */
+ d->digest_hash = HTTP_DIGEST_MD5;
+
+ if (!get_token(s))
+ return auth_error(d, "parse error");
+
+ if (!stricmp(s->token->s, "Basic")) {
+ /* For Basic authentication, we don't need anything else. The
+ * realm string is not required for the protocol. */
+ d->auth_type = AUTH_BASIC;
+ return d;
+ }
+
+ if (!stricmp(s->token->s, "Digest")) {
+ /* Parse all the additional parts of the Digest header. */
+ if (!http_digest_available)
+ return auth_error(d, "Digest authentication not supported");
+
+ /* Parse the rest of the Digest header */
+ while (true) {
+ if (!get_token(s))
+ return auth_error(d, "parse error in Digest header");
+
+ if (!stricmp(s->token->s, "realm")) {
+ if (!get_separator(s, '=') ||
+ !get_quoted_string(s))
+ return auth_error(d, "parse error in Digest realm field");
+ put_datapl(d->realm, ptrlen_from_strbuf(s->token));
+ } else if (!stricmp(s->token->s, "nonce")) {
+ if (!get_separator(s, '=') ||
+ !get_quoted_string(s))
+ return auth_error(d, "parse error in Digest nonce field");
+ put_datapl(d->nonce, ptrlen_from_strbuf(s->token));
+ } else if (!stricmp(s->token->s, "opaque")) {
+ if (!get_separator(s, '=') ||
+ !get_quoted_string(s))
+ return auth_error(d, "parse error in Digest opaque field");
+ put_datapl(d->opaque,
+ ptrlen_from_strbuf(s->token));
+ d->got_opaque = true;
+ } else if (!stricmp(s->token->s, "stale")) {
+ if (!get_separator(s, '=') ||
+ !get_token(s))
+ return auth_error(d, "parse error in Digest stale field");
+ d->digest_nonce_was_stale = !stricmp(
+ s->token->s, "true");
+ } else if (!stricmp(s->token->s, "userhash")) {
+ if (!get_separator(s, '=') ||
+ !get_token(s))
+ return auth_error(d, "parse error in Digest userhash "
+ "field");
+ d->hash_username = !stricmp(s->token->s, "true");
+ } else if (!stricmp(s->token->s, "algorithm")) {
+ if (!get_separator(s, '=') ||
+ (!get_token(s) && !get_quoted_string(s)))
+ return auth_error(d, "parse error in Digest algorithm "
+ "field");
+ bool found = false;
+ size_t i;
+
+ for (i = 0; i < N_HTTP_DIGEST_HASHES; i++) {
+ if (!stricmp(s->token->s, httphashnames[i])) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ /* We don't even recognise the name */
+ return auth_error(d, "Digest hash algorithm '%s' not "
+ "recognised", s->token->s);
+ }
+
+ if (!httphashaccepted[i]) {
+ /* We do recognise the name but we
+ * don't like it (see comment in cproxy.h) */
+ return auth_error(d, "Digest hash algorithm '%s' not "
+ "supported", s->token->s);
+ }
+
+ d->digest_hash = i;
+ } else if (!stricmp(s->token->s, "qop")) {
+ if (!get_separator(s, '=') ||
+ !get_quoted_string(s))
+ return auth_error(d, "parse error in Digest qop field");
+ if (stricmp(s->token->s, "auth"))
+ return auth_error(d, "quality-of-protection type '%s' not "
+ "supported", s->token->s);
+ } else {
+ /* Ignore any other auth-param */
+ if (!get_separator(s, '=') ||
+ (!get_quoted_string(s) && !get_token(s)))
+ return auth_error(d, "parse error in Digest header");
+ }
+
+ if (get_end_of_header(s))
+ break;
+ if (!get_separator(s, ','))
+ return auth_error(d, "parse error in Digest header");
+ }
+ d->auth_type = AUTH_DIGEST;
+ return d;
+ }
+
+ return auth_error(d, "authentication type '%s' not supported",
+ s->token->s);
+}
+
+static void proxy_http_process_queue(ProxyNegotiator *pn)
+{
+ HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn);
+
+ crBegin(s->crLine);
+
+ /*
+ * Initialise our username and password strbufs from the Conf.
+ */
+ put_dataz(s->username, conf_get_str(pn->ps->conf, CONF_proxy_username));
+ put_dataz(s->password, conf_get_str(pn->ps->conf, CONF_proxy_password));
+ if (s->username->len || s->password->len)
+ s->try_auth_from_conf = true;
+
+ /*
+ * Set up the host:port string we're trying to connect to, also
+ * used as the URI string in HTTP Digest auth.
+ */
+ {
+ char dest[512];
+ sk_getaddr(pn->ps->remote_addr, dest, lenof(dest));
+ put_fmt(s->uri, "%s:%d", dest, pn->ps->remote_port);
+ }
+
+ while (true) {
+ /*
+ * Standard prefix for the HTTP CONNECT request.
+ */
+ put_fmt(pn->output,
+ "CONNECT %s HTTP/1.1\r\n"
+ "Host: %s\r\n", s->uri->s, s->uri->s);
+
+ /*
+ * Add an auth header, if we're planning to this time round.
+ */
+ if (s->next_auth->auth_type == AUTH_BASIC) {
+ put_datalit(pn->output, "Proxy-Authorization: Basic ");
+
+ strbuf *base64_input = strbuf_new_nm();
+ put_datapl(base64_input, ptrlen_from_strbuf(s->username));
+ put_byte(base64_input, ':');
+ put_datapl(base64_input, ptrlen_from_strbuf(s->password));
+
+ char base64_output[4];
+ for (size_t i = 0, e = base64_input->len; i < e; i += 3) {
+ base64_encode_atom(base64_input->u + i,
+ e-i > 3 ? 3 : e-i, base64_output);
+ put_data(pn->output, base64_output, 4);
+ }
+ strbuf_free(base64_input);
+ smemclr(base64_output, sizeof(base64_output));
+ put_datalit(pn->output, "\r\n");
+ } else if (s->next_auth->auth_type == AUTH_DIGEST) {
+ put_datalit(pn->output, "Proxy-Authorization: Digest ");
+
+ /* If we have a fresh nonce, reset the
+ * nonce count. Otherwise, keep incrementing it. */
+ if (!ptrlen_eq_ptrlen(ptrlen_from_strbuf(s->token),
+ ptrlen_from_strbuf(s->next_auth->nonce)))
+ s->nonce_count = 0;
+
+ http_digest_response(BinarySink_UPCAST(pn->output),
+ ptrlen_from_strbuf(s->username),
+ ptrlen_from_strbuf(s->password),
+ ptrlen_from_strbuf(s->next_auth->realm),
+ PTRLEN_LITERAL("CONNECT"),
+ ptrlen_from_strbuf(s->uri),
+ PTRLEN_LITERAL("auth"),
+ ptrlen_from_strbuf(s->next_auth->nonce),
+ (s->next_auth->got_opaque ?
+ ptrlen_from_strbuf(s->next_auth->opaque) :
+ make_ptrlen(NULL, 0)),
+ ++s->nonce_count, s->next_auth->digest_hash,
+ s->next_auth->hash_username);
+ put_datalit(pn->output, "\r\n");
+ }
+
+ /*
+ * Blank line to terminate the HTTP request.
+ */
+ put_datalit(pn->output, "\r\n");
+ crReturnV;
+
+ s->content_length = 0;
+ s->chunked_transfer = false;
+ s->connection_close = false;
+
+ /*
+ * Read and parse the HTTP status line, and check if it's a 2xx
+ * for success.
+ */
+ strbuf_clear(s->response);
+ crMaybeWaitUntilV(read_line(pn->input, s->response, false));
+ {
+ int maj_ver, min_ver, n_scanned;
+ n_scanned = sscanf(
+ s->response->s, "HTTP/%d.%d %n%d",
+ &maj_ver, &min_ver, &s->http_status_pos, &s->http_status);
+
+ if (n_scanned < 3) {
+ pn->error = dupstr("HTTP response was absent or malformed");
+ crStopV;
+ }
+
+ if (maj_ver < 1 || (maj_ver == 1 && min_ver < 1)) {
+ /* Before HTTP/1.1, connections close by default */
+ s->connection_close = true;
+ }
+ }
+
+ if (s->http_status == 407) {
+ /*
+ * If this is going to be an auth request, we expect to
+ * see at least one Proxy-Authorization header offering us
+ * auth options. Start by preloading s->next_auth with a
+ * fallback error message, which will be used if nothing
+ * better is available.
+ */
+ http_auth_details_free(s->next_auth);
+ s->next_auth = http_auth_details_new();
+ auth_error(s->next_auth, "no Proxy-Authorization header seen in "
+ "HTTP 407 Proxy Authentication Required response");
+ }
+
+ /*
+ * Read the HTTP response header section.
+ */
+ do {
+ strbuf_clear(s->header);
+ crMaybeWaitUntilV(read_line(pn->input, s->header, true));
+ s->header_pos = 0;
+
+ if (!get_token(s)) {
+ /* Possibly we ought to panic if we see an HTTP header
+ * we can't make any sense of at all? But whatever,
+ * ignore it and hope the next one makes more sense */
+ continue;
+ }
+
+ /* Parse the header name */
+ HttpHeader hdr = HDR_UNKNOWN;
+ {
+ #define CHECK_HEADER(id, string) \
+ if (!stricmp(s->token->s, string)) hdr = id;
+ HTTP_HEADER_LIST(CHECK_HEADER);
+ #undef CHECK_HEADER
+ }
+
+ if (!get_separator(s, ':'))
+ continue;
+
+ if (hdr == HDR_CONTENT_LENGTH) {
+ if (!get_token(s))
+ continue;
+ s->content_length = strtoumax(s->token->s, NULL, 10);
+ } else if (hdr == HDR_TRANSFER_ENCODING) {
+ /*
+ * The Transfer-Encoding header value should be a
+ * comma-separated list of keywords including
+ * "chunked", "deflate" and "gzip". We parse it in the
+ * most superficial way, by just looking for "chunked"
+ * and ignoring everything else.
+ *
+ * It's OK to do that because we're not actually
+ * _using_ the error document - we only have to skip
+ * over it to find the end of the HTTP response. So we
+ * don't care if it's gzipped or not.
+ */
+ while (get_token(s)) {
+ if (!stricmp(s->token->s, "chunked"))
+ s->chunked_transfer = true;
+ }
+ } else if (hdr == HDR_CONNECTION ||
+ hdr == HDR_PROXY_CONNECTION) {
+ if (!get_token(s))
+ continue;
+ if (!stricmp(s->token->s, "close"))
+ s->connection_close = true;
+ else if (!stricmp(s->token->s, "keep-alive"))
+ s->connection_close = false;
+ } else if (hdr == HDR_PROXY_AUTHENTICATE) {
+ HttpAuthDetails *auth = parse_http_auth_header(s);
+
+ /*
+ * See if we prefer this set of auth details to the
+ * previous one we had (either from a previous auth
+ * header, or the fallback when no auth header is
+ * provided at all).
+ */
+ bool change;
+
+ if (auth->auth_type != s->next_auth->auth_type) {
+ /* Use the preference order implied by the enum */
+ change = auth->auth_type > s->next_auth->auth_type;
+ } else if (auth->auth_type == AUTH_DIGEST &&
+ auth->digest_hash != s->next_auth->digest_hash) {
+ /* Choose based on the hash functions */
+ change = auth->digest_hash > s->next_auth->digest_hash;
+ } else {
+ /*
+ * If in doubt, go with the later one of the
+ * headers.
+ *
+ * The main reason for this is so that an error in
+ * interpreting an auth header will supersede the
+ * default error we preload saying 'no header
+ * found', because that would be a particularly
+ * bad error to report if there _was_ one.
+ *
+ * But we're in a tie-breaking situation by now,
+ * so there's no other reason to choose - we might
+ * as well apply the same policy everywhere else
+ * too.
+ */
+ change = true;
+ }
+
+ if (change) {
+ http_auth_details_free(s->next_auth);
+ s->next_auth = auth;
+ } else {
+ http_auth_details_free(auth);
+ }
+ }
+ } while (s->header->len > 0);
+
+ /* Read and ignore the entire response document */
+ if (!s->chunked_transfer) {
+ /* Simple approach: read exactly Content-Length bytes */
+ crMaybeWaitUntilV(bufchain_try_consume(
+ pn->input, s->content_length));
+ } else {
+ /* Chunked transfer: read a sequence of
+ * <hex length>\r\n<data>\r\n chunks, terminating in one with
+ * zero length */
+ do {
+ /*
+ * Expect a chunk length
+ */
+ s->chunk_length = 0;
+ while (true) {
+ char c;
+ crMaybeWaitUntilV(bufchain_try_fetch_consume(
+ pn->input, &c, 1));
+ if (c == '\r') {
+ continue;
+ } else if (c == '\n') {
+ break;
+ } else if ('0' <= c && c <= '9') {
+ s->chunk_length = s->chunk_length*16 + (c-'0');
+ } else if ('A' <= c && c <= 'F') {
+ s->chunk_length = s->chunk_length*16 + (c-'A'+10);
+ } else if ('a' <= c && c <= 'f') {
+ s->chunk_length = s->chunk_length*16 + (c-'a'+10);
+ } else {
+ pn->error = dupprintf(
+ "Received bad character 0x%02X in chunk length "
+ "during HTTP chunked transfer encoding",
+ (unsigned)(unsigned char)c);
+ crStopV;
+ }
+ }
+
+ /*
+ * Expect that many bytes of chunked data
+ */
+ crMaybeWaitUntilV(bufchain_try_consume(
+ pn->input, s->chunk_length));
+
+ /* Now expect \r\n */
+ {
+ char buf[2];
+ crMaybeWaitUntilV(bufchain_try_fetch_consume(
+ pn->input, buf, 2));
+ if (memcmp(buf, "\r\n", 2)) {
+ pn->error = dupprintf(
+ "Missing CRLF after chunk "
+ "during HTTP chunked transfer encoding");
+ crStopV;
+ }
+ }
+ } while (s->chunk_length);
+ }
+
+ if (200 <= s->http_status && s->http_status < 300) {
+ /* Any 2xx HTTP response means we're done */
+ goto authenticated;
+ } else if (s->http_status == 407) {
+ /* 407 is Proxy Authentication Required, which we may be
+ * able to do something about. */
+ if (s->connection_close) {
+ /* If we got 407 + connection closed, reconnect before
+ * sending our next request. */
+ pn->reconnect = true;
+ }
+
+ /* If the best we can do is report some kind of error from
+ * a Proxy-Auth header (or an error saying there wasn't
+ * one at all), and no successful parsing of an auth
+ * header superseded that, then just throw that error and
+ * die. */
+ if (s->next_auth->auth_type == AUTH_ERROR) {
+ pn->error = dupstr(s->next_auth->error->s);
+ crStopV;
+ }
+
+ /* If we have auth details from the Conf and haven't tried
+ * them yet, that's our first step. */
+ if (s->try_auth_from_conf) {
+ s->try_auth_from_conf = false;
+ continue;
+ }
+
+ /* If the server sent us stale="true" in a Digest auth
+ * header, that means we _don't_ need to request a new
+ * password yet; just try again with the existing details
+ * and the fresh nonce it sent us. */
+ if (s->next_auth->digest_nonce_was_stale)
+ continue;
+
+ /* Either we never had a password in the first place, or
+ * the one we already presented was rejected. We can only
+ * proceed from here if we have a way to ask the user
+ * questions. */
+ if (!pn->itr) {
+ pn->error = dupprintf("HTTP proxy requested authentication "
+ "which we do not have");
+ crStopV;
+ }
+
+ /*
+ * Send some prompts to the user. We'll assume the
+ * password is always required (since it's just been
+ * rejected, even if we did send one before), and we'll
+ * prompt for the username only if we don't have one from
+ * the Conf.
+ */
+ s->prompts = proxy_new_prompts(pn->ps);
+ s->prompts->to_server = true;
+ s->prompts->from_server = false;
+ s->prompts->name = dupstr("HTTP proxy authentication");
+ if (!s->username->len) {
+ s->username_prompt_index = s->prompts->n_prompts;
+ add_prompt(s->prompts, dupstr("Proxy username: "), true);
+ } else {
+ s->username_prompt_index = -1;
+ }
+
+ s->password_prompt_index = s->prompts->n_prompts;
+ add_prompt(s->prompts, dupstr("Proxy password: "), false);
+
+ while (true) {
+ SeatPromptResult spr = seat_get_userpass_input(
+ interactor_announce(pn->itr), s->prompts);
+ if (spr.kind == SPRK_OK) {
+ break;
+ } else if (spr_is_abort(spr)) {
+ proxy_spr_abort(pn, spr);
+ crStopV;
+ }
+ crReturnV;
+ }
+
+ if (s->username_prompt_index != -1) {
+ strbuf_clear(s->username);
+ put_dataz(s->username,
+ prompt_get_result_ref(
+ s->prompts->prompts[s->username_prompt_index]));
+ }
+
+ strbuf_clear(s->password);
+ put_dataz(s->password,
+ prompt_get_result_ref(
+ s->prompts->prompts[s->password_prompt_index]));
+
+ free_prompts(s->prompts);
+ s->prompts = NULL;
+ } else {
+ /* Any other HTTP response is treated as permanent failure */
+ pn->error = dupprintf("HTTP response %s",
+ s->response->s + s->http_status_pos);
+ crStopV;
+ }
+ }
+
+ authenticated:
+ /*
+ * Success! Hand over to the main connection.
+ */
+ pn->done = true;
+
+ crFinishV;
+}
+
+const struct ProxyNegotiatorVT http_proxy_negotiator_vt = {
+ .new = proxy_http_new,
+ .free = proxy_http_free,
+ .process_queue = proxy_http_process_queue,
+ .type = "HTTP",
+};
diff --git a/proxy/interactor.c b/proxy/interactor.c
new file mode 100644
index 00000000..d069d226
--- /dev/null
+++ b/proxy/interactor.c
@@ -0,0 +1,119 @@
+/*
+ * Centralised functions for the Interactor trait.
+ */
+
+#include "putty.h"
+
+Seat *interactor_borrow_seat(Interactor *itr)
+{
+ Seat *clientseat = interactor_get_seat(itr);
+ if (!clientseat)
+ return NULL;
+
+ /* If the client has already had its Seat borrowed, then look
+ * through the existing TempSeat to find the underlying one. */
+ if (is_tempseat(clientseat))
+ return tempseat_get_real(clientseat);
+
+ /* Otherwise, make a new TempSeat and give that to the client. */
+ Seat *tempseat = tempseat_new(clientseat);
+ interactor_set_seat(itr, tempseat);
+ return clientseat;
+}
+
+static Interactor *interactor_toplevel(Interactor *itr, unsigned *level_out)
+{
+ /*
+ * Find the Interactor at the top of the chain, so that all the
+ * Interactors in a stack can share that one's last-to-talk field.
+ * Also, count how far we had to go to get to it, to put in the
+ * message.
+ */
+ Interactor *itr_top = itr;
+ unsigned level = 0;
+ while (itr_top->parent) {
+ itr_top = itr_top->parent;
+ level++;
+ }
+
+ if (level_out)
+ *level_out = level;
+ return itr_top;
+}
+
+void interactor_return_seat(Interactor *itr)
+{
+ Seat *tempseat = interactor_get_seat(itr);
+ if (!is_tempseat(tempseat))
+ return; /* no-op */
+
+ /*
+ * We're about to hand this seat back to the parent Interactor to
+ * do its own thing with. It will typically expect to start in the
+ * same state as if the seat had never been borrowed, i.e. in the
+ * starting trust state.
+ *
+ * However, this may be overridden by the tempseat_flush call.
+ */
+ Seat *realseat = tempseat_get_real(tempseat);
+ seat_set_trust_status(realseat, true);
+
+ tempseat_flush(tempseat);
+ interactor_set_seat(itr, realseat);
+ tempseat_free(tempseat);
+
+ /*
+ * If we have a parent Interactor, and anyone has ever called
+ * interactor_announce, then all Interactors from now on will
+ * announce themselves even if they have nothing to say.
+ */
+ Interactor *itr_top = interactor_toplevel(itr, NULL);
+ if (itr_top->last_to_talk)
+ interactor_announce(itr);
+}
+
+InteractionReadySeat interactor_announce(Interactor *itr)
+{
+ Seat *seat = interactor_get_seat(itr);
+ assert(!is_tempseat(seat) &&
+ "Shouldn't call announce when someone else is using our seat");
+
+ InteractionReadySeat iseat;
+ iseat.seat = seat;
+
+ unsigned level;
+ Interactor *itr_top = interactor_toplevel(itr, &level);
+
+ /*
+ * Generally, we should announce ourself if the previous
+ * Interactor that said anything was not us. That includes if
+ * there was no previous Interactor to talk (i.e. if we're the
+ * first to say anything) - *except* that the primary Interactor
+ * doesn't need to announce itself, if no proxy has intervened
+ * before it.
+ */
+ bool need_announcement = (itr_top->last_to_talk != itr);
+ if (!itr->parent && !itr_top->last_to_talk)
+ need_announcement = false;
+
+ if (need_announcement) {
+ const char *prefix = "";
+ if (itr_top->last_to_talk != NULL)
+ seat_antispoof_msg(iseat, ""); /* leave a separating blank line */
+
+ char *desc = interactor_description(itr);
+ char *adjective = (level == 0 ? dupstr("primary") :
+ level == 1 ? dupstr("proxy") :
+ dupprintf("proxy^%u", level));
+ char *msg = dupprintf("%sMaking %s %s", prefix, adjective, desc);
+ sfree(adjective);
+ sfree(desc);
+
+ seat_antispoof_msg(iseat, msg);
+ sfree(msg);
+
+ itr_top->last_to_talk = itr;
+ }
+
+ return iseat;
+}
diff --git a/proxy/local.c b/proxy/local.c
new file mode 100644
index 00000000..3b2d130c
--- /dev/null
+++ b/proxy/local.c
@@ -0,0 +1,272 @@
+/*
+ * Implement LocalProxyOpener, a centralised system for setting up the
+ * command string to be run by platform-specific local-subprocess
+ * proxy types.
+ *
+ * The platform-specific local proxy code is expected to use this
+ * system by calling local_proxy_opener() from
+ * platform_new_connection(); then using the resulting
+ * DeferredSocketOpener to make a deferred version of whatever local
+ * socket type is used for talking to subcommands (Unix FdSocket,
+ * Windows HandleSocket); then passing the 'Socket *' back to us via
+ * local_proxy_opener_set_socket().
+ *
+ * The LocalProxyOpener object implemented by this code will set
+ * itself up as an Interactor if possible, so that it can prompt for
+ * the proxy username and/or password if they're referred to in the
+ * command string but not given in the config (exactly as the Telnet
+ * proxy does). Once it knows the exact command it wants to run -
+ * whether that was done immediately or after user interaction - it
+ * calls back to platform_setup_local_proxy() with the full command,
+ * which is expected to actually start the subprocess and fill in the
+ * missing details in the deferred socket, freeing the
+ * LocalProxyOpener as a side effect.
+ */
+
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+#include "sshcr.h"
+#include "proxy/proxy.h"
+
+typedef struct LocalProxyOpener {
+ int crLine;
+
+ Socket *socket;
+ char *formatted_cmd;
+ Plug *plug;
+ SockAddr *addr;
+ int port;
+ Conf *conf;
+
+ Interactor *clientitr;
+ LogPolicy *clientlp;
+ Seat *clientseat;
+ prompts_t *prompts;
+ int username_prompt_index, password_prompt_index;
+
+ Interactor interactor;
+ DeferredSocketOpener opener;
+} LocalProxyOpener;
+
+static void local_proxy_opener_free(DeferredSocketOpener *opener)
+{
+ LocalProxyOpener *lp = container_of(opener, LocalProxyOpener, opener);
+ burnstr(lp->formatted_cmd);
+ if (lp->prompts)
+ free_prompts(lp->prompts);
+ sk_addr_free(lp->addr);
+ conf_free(lp->conf);
+ sfree(lp);
+}
+
+static const DeferredSocketOpenerVtable LocalProxyOpener_openervt = {
+ .free = local_proxy_opener_free,
+};
+
+static char *local_proxy_opener_description(Interactor *itr)
+{
+ return dupstr("connection via local command");
+}
+
+static LogPolicy *local_proxy_opener_logpolicy(Interactor *itr)
+{
+ LocalProxyOpener *lp = container_of(itr, LocalProxyOpener, interactor);
+ return lp->clientlp;
+}
+
+static Seat *local_proxy_opener_get_seat(Interactor *itr)
+{
+ LocalProxyOpener *lp = container_of(itr, LocalProxyOpener, interactor);
+ return lp->clientseat;
+}
+
+static void local_proxy_opener_set_seat(Interactor *itr, Seat *seat)
+{
+ LocalProxyOpener *lp = container_of(itr, LocalProxyOpener, interactor);
+ lp->clientseat = seat;
+}
+
+static const InteractorVtable LocalProxyOpener_interactorvt = {
+ .description = local_proxy_opener_description,
+ .logpolicy = local_proxy_opener_logpolicy,
+ .get_seat = local_proxy_opener_get_seat,
+ .set_seat = local_proxy_opener_set_seat,
+};
+
+static void local_proxy_opener_cleanup_interactor(LocalProxyOpener *lp)
+{
+ if (lp->clientseat) {
+ interactor_return_seat(lp->clientitr);
+ lp->clientitr = NULL;
+ lp->clientseat = NULL;
+ }
+}
+
+static void local_proxy_opener_coroutine(void *vctx)
+{
+ LocalProxyOpener *lp = (LocalProxyOpener *)vctx;
+
+ crBegin(lp->crLine);
+
+ /*
+ * Make an initial attempt to figure out the command we want, and
+ * see if it tried to include a username or password that we don't
+ * have.
+ */
+ {
+ unsigned flags;
+ lp->formatted_cmd = format_telnet_command(
+ lp->addr, lp->port, lp->conf, &flags);
+
+ if (lp->clientseat && (flags & (TELNET_CMD_MISSING_USERNAME |
+ TELNET_CMD_MISSING_PASSWORD))) {
+ burnstr(lp->formatted_cmd);
+ lp->formatted_cmd = NULL;
+
+ /*
+ * We're missing at least one of the two parts, and we
+ * have an Interactor we can use to prompt for them, so
+ * try it.
+ */
+ lp->prompts = new_prompts();
+ lp->prompts->callback = local_proxy_opener_coroutine;
+ lp->prompts->callback_ctx = lp;
+ lp->prompts->to_server = true;
+ lp->prompts->from_server = false;
+ lp->prompts->name = dupstr("Local proxy authentication");
+ if (flags & TELNET_CMD_MISSING_USERNAME) {
+ lp->username_prompt_index = lp->prompts->n_prompts;
+ add_prompt(lp->prompts, dupstr("Proxy username: "), true);
+ } else {
+ lp->username_prompt_index = -1;
+ }
+ if (flags & TELNET_CMD_MISSING_PASSWORD) {
+ lp->password_prompt_index = lp->prompts->n_prompts;
+ add_prompt(lp->prompts, dupstr("Proxy password: "), false);
+ } else {
+ lp->password_prompt_index = -1;
+ }
+
+ while (true) {
+ SeatPromptResult spr = seat_get_userpass_input(
+ interactor_announce(&lp->interactor), lp->prompts);
+ if (spr.kind == SPRK_OK) {
+ break;
+ } else if (spr.kind == SPRK_USER_ABORT) {
+ local_proxy_opener_cleanup_interactor(lp);
+ plug_closing_user_abort(lp->plug);
+ /* That will have freed us, so we must just return
+ * without calling any crStop */
+ return;
+ } else if (spr.kind == SPRK_SW_ABORT) {
+ local_proxy_opener_cleanup_interactor(lp);
+ char *err = spr_get_error_message(spr);
+ plug_closing_error(lp->plug, err);
+ sfree(err);
+ return; /* without crStop, as above */
+ }
+ crReturnV;
+ }
+
+ if (lp->username_prompt_index != -1) {
+ conf_set_str(
+ lp->conf, CONF_proxy_username,
+ prompt_get_result_ref(
+ lp->prompts->prompts[lp->username_prompt_index]));
+ }
+
+ if (lp->password_prompt_index != -1) {
+ conf_set_str(
+ lp->conf, CONF_proxy_password,
+ prompt_get_result_ref(
+ lp->prompts->prompts[lp->password_prompt_index]));
+ }
+
+ free_prompts(lp->prompts);
+ lp->prompts = NULL;
+ }
+
+ /*
+ * Now format the command a second time, with the results of
+ * those prompts written into lp->conf.
+ */
+ lp->formatted_cmd = format_telnet_command(
+ lp->addr, lp->port, lp->conf, NULL);
+ }
+
+ /*
+ * Log the command, with some changes. Firstly, we regenerate it
+ * with the password masked; secondly, we escape control
+ * characters so that the log message is printable.
+ */
+ conf_set_str(lp->conf, CONF_proxy_password, "*password*");
+ {
+ char *censored_cmd = format_telnet_command(
+ lp->addr, lp->port, lp->conf, NULL);
+
+ strbuf *logmsg = strbuf_new();
+ put_datapl(logmsg, PTRLEN_LITERAL("Starting local proxy command: "));
+ put_c_string_literal(logmsg, ptrlen_from_asciz(censored_cmd));
+
+ plug_log(lp->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0);
+ strbuf_free(logmsg);
+ sfree(censored_cmd);
+ }
+
+ /*
+ * Now we're ready to actually do the platform-specific socket
+ * setup.
+ */
+ char *cmd = lp->formatted_cmd;
+ lp->formatted_cmd = NULL;
+
+ local_proxy_opener_cleanup_interactor(lp);
+
+ char *error_msg = platform_setup_local_proxy(lp->socket, cmd);
+ burnstr(cmd);
+
+ if (error_msg) {
+ plug_closing_error(lp->plug, error_msg);
+ sfree(error_msg);
+ } else {
+ /* If error_msg was NULL, there was no error in setup,
+ * which means that platform_setup_local_proxy will have
+ * called back to free us. So return without calling any
+ * crStop. */
+ return;
+ }
+
+ crFinishV;
+}
+
+DeferredSocketOpener *local_proxy_opener(
+ SockAddr *addr, int port, Plug *plug, Conf *conf, Interactor *itr)
+{
+ LocalProxyOpener *lp = snew(LocalProxyOpener);
+ memset(lp, 0, sizeof(*lp));
+ lp->plug = plug;
+ lp->opener.vt = &LocalProxyOpener_openervt;
+ lp->interactor.vt = &LocalProxyOpener_interactorvt;
+ lp->addr = sk_addr_dup(addr);
+ lp->port = port;
+ lp->conf = conf_copy(conf);
+
+ if (itr) {
+ lp->clientitr = itr;
+ interactor_set_child(lp->clientitr, &lp->interactor);
+ lp->clientlp = interactor_logpolicy(lp->clientitr);
+ lp->clientseat = interactor_borrow_seat(lp->clientitr);
+ }
+
+ return &lp->opener;
+}
+
+void local_proxy_opener_set_socket(DeferredSocketOpener *opener,
+ Socket *socket)
+{
+ assert(opener->vt == &LocalProxyOpener_openervt);
+ LocalProxyOpener *lp = container_of(opener, LocalProxyOpener, opener);
+ lp->socket = socket;
+ queue_toplevel_callback(local_proxy_opener_coroutine, lp);
+}
diff --git a/proxy/nocproxy.c b/proxy/nocproxy.c
new file mode 100644
index 00000000..89341489
--- /dev/null
+++ b/proxy/nocproxy.c
@@ -0,0 +1,33 @@
+/*
+ * 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 <assert.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+
+const bool socks5_chap_available = false;
+const bool http_digest_available = false;
+
+strbuf *chap_response(ptrlen challenge, ptrlen password)
+{
+ unreachable("CHAP is not built into this binary");
+}
+
+/* dummy arrays to prevent link error */
+const char *const httphashnames[] = { NULL };
+const bool httphashaccepted[] = { false };
+
+void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password,
+ ptrlen realm, ptrlen method, ptrlen uri, ptrlen qop,
+ ptrlen nonce, ptrlen opaque, uint32_t nonce_count,
+ HttpDigestHash hash, bool hash_username)
+{
+ unreachable("HTTP DIGEST is not built into this binary");
+}
diff --git a/proxy/noproxy.c b/proxy/noproxy.c
new file mode 100644
index 00000000..248688e0
--- /dev/null
+++ b/proxy/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, Interactor *itr)
+{
+ return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug);
+}
+
+Socket *new_listener(const char *srcaddr, int port, Plug *plug,
+ bool local_host_only, Conf *conf, int addressfamily)
+{
+ return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily);
+}
diff --git a/proxy/nosshproxy.c b/proxy/nosshproxy.c
new file mode 100644
index 00000000..1160b8de
--- /dev/null
+++ b/proxy/nosshproxy.c
@@ -0,0 +1,16 @@
+/*
+ * nosshproxy.c: stub implementation of sshproxy_new_connection().
+ */
+
+#include "putty.h"
+#include "network.h"
+
+const bool ssh_proxy_supported = false;
+
+Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname,
+ int port, bool privport,
+ bool oobinline, bool nodelay, bool keepalive,
+ Plug *plug, Conf *conf, Interactor *itr)
+{
+ return NULL;
+}
diff --git a/proxy/pproxy.c b/proxy/pproxy.c
new file mode 100644
index 00000000..1712ae8c
--- /dev/null
+++ b/proxy/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, Interactor *itr)
+{
+ return NULL;
+}
diff --git a/proxy/proxy.c b/proxy/proxy.c
new file mode 100644
index 00000000..bca60a35
--- /dev/null
+++ b/proxy/proxy.c
@@ -0,0 +1,656 @@
+/*
+ * Network proxy abstraction in PuTTY
+ *
+ * A proxy layer, if necessary, wedges itself between the network
+ * code and the higher level backend.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+
+#define do_proxy_dns(conf) \
+ (conf_get_int(conf, CONF_proxy_dns) == FORCE_ON || \
+ (conf_get_int(conf, CONF_proxy_dns) == AUTO && \
+ conf_get_int(conf, CONF_proxy_type) != PROXY_SOCKS4))
+
+static void proxy_negotiator_cleanup(ProxySocket *ps)
+{
+ if (ps->pn) {
+ proxy_negotiator_free(ps->pn);
+ ps->pn = NULL;
+ }
+ if (ps->clientseat) {
+ interactor_return_seat(ps->clientitr);
+ ps->clientitr = NULL;
+ ps->clientseat = NULL;
+ }
+}
+
+/*
+ * Call this when proxy negotiation is complete, so that this
+ * socket can begin working normally.
+ */
+static void proxy_activate(ProxySocket *ps)
+{
+ size_t output_before, output_after;
+
+ proxy_negotiator_cleanup(ps);
+
+ plug_log(ps->plug, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0);
+
+ /* we want to ignore new receive events until we have sent
+ * all of our buffered receive data.
+ */
+ sk_set_frozen(ps->sub_socket, true);
+
+ /* how many bytes of output have we buffered? */
+ output_before = bufchain_size(&ps->pending_oob_output_data) +
+ bufchain_size(&ps->pending_output_data);
+ /* and keep track of how many bytes do not get sent. */
+ output_after = 0;
+
+ /* send buffered OOB writes */
+ while (bufchain_size(&ps->pending_oob_output_data) > 0) {
+ ptrlen data = bufchain_prefix(&ps->pending_oob_output_data);
+ output_after += sk_write_oob(ps->sub_socket, data.ptr, data.len);
+ bufchain_consume(&ps->pending_oob_output_data, data.len);
+ }
+
+ /* send buffered normal writes */
+ while (bufchain_size(&ps->pending_output_data) > 0) {
+ ptrlen data = bufchain_prefix(&ps->pending_output_data);
+ output_after += sk_write(ps->sub_socket, data.ptr, data.len);
+ bufchain_consume(&ps->pending_output_data, data.len);
+ }
+
+ /* if we managed to send any data, let the higher levels know. */
+ if (output_after < output_before)
+ plug_sent(ps->plug, output_after);
+
+ /* if we have a pending EOF to send, send it */
+ if (ps->pending_eof) sk_write_eof(ps->sub_socket);
+
+ /* if the backend wanted the socket unfrozen, try to unfreeze.
+ * our set_frozen handler will flush buffered receive data before
+ * unfreezing the actual underlying socket.
+ */
+ if (!ps->freeze)
+ sk_set_frozen(&ps->sock, false);
+}
+
+/* basic proxy socket functions */
+
+static Plug *sk_proxy_plug (Socket *s, Plug *p)
+{
+ ProxySocket *ps = container_of(s, ProxySocket, sock);
+ Plug *ret = ps->plug;
+ if (p)
+ ps->plug = p;
+ return ret;
+}
+
+static void sk_proxy_close (Socket *s)
+{
+ ProxySocket *ps = container_of(s, ProxySocket, sock);
+
+ sk_close(ps->sub_socket);
+ sk_addr_free(ps->proxy_addr);
+ sk_addr_free(ps->remote_addr);
+ proxy_negotiator_cleanup(ps);
+ bufchain_clear(&ps->output_from_negotiator);
+ sfree(ps);
+}
+
+static size_t sk_proxy_write (Socket *s, const void *data, size_t len)
+{
+ ProxySocket *ps = container_of(s, ProxySocket, sock);
+
+ if (ps->pn) {
+ bufchain_add(&ps->pending_output_data, data, len);
+ return bufchain_size(&ps->pending_output_data);
+ }
+ return sk_write(ps->sub_socket, data, len);
+}
+
+static size_t sk_proxy_write_oob (Socket *s, const void *data, size_t len)
+{
+ ProxySocket *ps = container_of(s, ProxySocket, sock);
+
+ if (ps->pn) {
+ bufchain_clear(&ps->pending_output_data);
+ bufchain_clear(&ps->pending_oob_output_data);
+ bufchain_add(&ps->pending_oob_output_data, data, len);
+ return len;
+ }
+ return sk_write_oob(ps->sub_socket, data, len);
+}
+
+static void sk_proxy_write_eof (Socket *s)
+{
+ ProxySocket *ps = container_of(s, ProxySocket, sock);
+
+ if (ps->pn) {
+ ps->pending_eof = true;
+ return;
+ }
+ sk_write_eof(ps->sub_socket);
+}
+
+static void sk_proxy_set_frozen (Socket *s, bool is_frozen)
+{
+ ProxySocket *ps = container_of(s, ProxySocket, sock);
+
+ if (ps->pn) {
+ ps->freeze = is_frozen;
+ return;
+ }
+
+ /* handle any remaining buffered recv data first */
+ if (bufchain_size(&ps->pending_input_data) > 0) {
+ ps->freeze = is_frozen;
+
+ /* loop while we still have buffered data, and while we are
+ * unfrozen. the plug_receive call in the loop could result
+ * in a call back into this function refreezing the socket,
+ * so we have to check each time.
+ */
+ while (!ps->freeze && bufchain_size(&ps->pending_input_data) > 0) {
+ char databuf[512];
+ ptrlen data = bufchain_prefix(&ps->pending_input_data);
+ if (data.len > lenof(databuf))
+ data.len = lenof(databuf);
+ memcpy(databuf, data.ptr, data.len);
+ bufchain_consume(&ps->pending_input_data, data.len);
+ plug_receive(ps->plug, 0, databuf, data.len);
+ }
+
+ /* if we're still frozen, we'll have to wait for another
+ * call from the backend to finish unbuffering the data.
+ */
+ if (ps->freeze) return;
+ }
+
+ sk_set_frozen(ps->sub_socket, is_frozen);
+}
+
+static const char *sk_proxy_socket_error (Socket *s)
+{
+ ProxySocket *ps = container_of(s, ProxySocket, sock);
+ if (ps->error != NULL || ps->sub_socket == NULL) {
+ return ps->error;
+ }
+ return sk_socket_error(ps->sub_socket);
+}
+
+/* basic proxy plug functions */
+
+static void plug_proxy_log(Plug *plug, PlugLogType type, SockAddr *addr,
+ int port, const char *error_msg, int error_code)
+{
+ ProxySocket *ps = container_of(plug, ProxySocket, plugimpl);
+
+ plug_log(ps->plug, type, addr, port, error_msg, error_code);
+}
+
+static void plug_proxy_closing(Plug *p, PlugCloseType type,
+ const char *error_msg)
+{
+ ProxySocket *ps = container_of(p, ProxySocket, plugimpl);
+
+ proxy_negotiator_cleanup(ps);
+ plug_closing(ps->plug, type, error_msg);
+}
+
+static void proxy_negotiate(ProxySocket *ps)
+{
+ assert(ps->pn);
+ proxy_negotiator_process_queue(ps->pn);
+
+ if (ps->pn->error) {
+ char *err = dupprintf("Proxy error: %s", ps->pn->error);
+ sfree(ps->pn->error);
+ proxy_negotiator_cleanup(ps);
+ plug_closing_error(ps->plug, err);
+ sfree(err);
+ return;
+ } else if (ps->pn->aborted) {
+ proxy_negotiator_cleanup(ps);
+ plug_closing_user_abort(ps->plug);
+ return;
+ }
+
+ if (ps->pn->reconnect) {
+ sk_close(ps->sub_socket);
+ SockAddr *proxy_addr = sk_addr_dup(ps->proxy_addr);
+ ps->sub_socket = sk_new(proxy_addr, ps->proxy_port,
+ ps->proxy_privport, ps->proxy_oobinline,
+ ps->proxy_nodelay, ps->proxy_keepalive,
+ &ps->plugimpl);
+ ps->pn->reconnect = false;
+ /* If the negotiator has asked us to reconnect, they are
+ * expecting that on the next call their input queue will
+ * consist entirely of data from the _new_ connection, without
+ * any remaining data buffered from the old one. (If they'd
+ * wanted the latter, they could have read it out of the input
+ * queue before asking us to close the connection.) */
+ bufchain_clear(&ps->pending_input_data);
+ }
+
+ while (bufchain_size(&ps->output_from_negotiator)) {
+ ptrlen data = bufchain_prefix(&ps->output_from_negotiator);
+ sk_write(ps->sub_socket, data.ptr, data.len);
+ bufchain_consume(&ps->output_from_negotiator, data.len);
+ }
+ if (ps->pn->done)
+ proxy_activate(ps);
+}
+
+static void plug_proxy_receive(
+ Plug *p, int urgent, const char *data, size_t len)
+{
+ ProxySocket *ps = container_of(p, ProxySocket, plugimpl);
+
+ if (ps->pn) {
+ /* we will lose the urgentness of this data, but since most,
+ * if not all, of this data will be consumed by the negotiation
+ * process, hopefully it won't affect the protocol above us
+ */
+ bufchain_add(&ps->pending_input_data, data, len);
+ proxy_negotiate(ps);
+ } else {
+ plug_receive(ps->plug, urgent, data, len);
+ }
+}
+
+static void plug_proxy_sent (Plug *p, size_t bufsize)
+{
+ ProxySocket *ps = container_of(p, ProxySocket, plugimpl);
+
+ if (ps->pn)
+ return;
+ plug_sent(ps->plug, bufsize);
+}
+
+static int plug_proxy_accepting(Plug *p,
+ accept_fn_t constructor, accept_ctx_t ctx)
+{
+ unreachable("ProxySockets never create listening Sockets");
+}
+
+/*
+ * This function can accept a NULL pointer as `addr', in which case
+ * it will only check the host name.
+ */
+static bool proxy_for_destination(SockAddr *addr, const char *hostname,
+ int port, Conf *conf)
+{
+ int s = 0, e = 0;
+ char hostip[64];
+ int hostip_len, hostname_len;
+ const char *exclude_list;
+
+ /*
+ * Special local connections such as Unix-domain sockets
+ * unconditionally cannot be proxied, even in proxy-localhost
+ * mode. There just isn't any way to ask any known proxy type for
+ * them.
+ */
+ if (addr && sk_address_is_special_local(addr))
+ return false; /* do not proxy */
+
+ /*
+ * Check the host name and IP against the hard-coded
+ * representations of `localhost'.
+ */
+ if (!conf_get_bool(conf, CONF_even_proxy_localhost) &&
+ (sk_hostname_is_local(hostname) ||
+ (addr && sk_address_is_local(addr))))
+ return false; /* do not proxy */
+
+ /* we want a string representation of the IP address for comparisons */
+ if (addr) {
+ sk_getaddr(addr, hostip, 64);
+ hostip_len = strlen(hostip);
+ } else
+ hostip_len = 0; /* placate gcc; shouldn't be required */
+
+ hostname_len = strlen(hostname);
+
+ exclude_list = conf_get_str(conf, CONF_proxy_exclude_list);
+
+ /* now parse the exclude list, and see if either our IP
+ * or hostname matches anything in it.
+ */
+
+ while (exclude_list[s]) {
+ while (exclude_list[s] &&
+ (isspace((unsigned char)exclude_list[s]) ||
+ exclude_list[s] == ',')) s++;
+
+ if (!exclude_list[s]) break;
+
+ e = s;
+
+ while (exclude_list[e] &&
+ (isalnum((unsigned char)exclude_list[e]) ||
+ exclude_list[e] == '-' ||
+ exclude_list[e] == '.' ||
+ exclude_list[e] == '*')) e++;
+
+ if (exclude_list[s] == '*') {
+ /* wildcard at beginning of entry */
+
+ if ((addr && strnicmp(hostip + hostip_len - (e - s - 1),
+ exclude_list + s + 1, e - s - 1) == 0) ||
+ strnicmp(hostname + hostname_len - (e - s - 1),
+ exclude_list + s + 1, e - s - 1) == 0) {
+ /* IP/hostname range excluded. do not use proxy. */
+ return false;
+ }
+ } else if (exclude_list[e-1] == '*') {
+ /* wildcard at end of entry */
+
+ if ((addr && strnicmp(hostip, exclude_list + s, e - s - 1) == 0) ||
+ strnicmp(hostname, exclude_list + s, e - s - 1) == 0) {
+ /* IP/hostname range excluded. do not use proxy. */
+ return false;
+ }
+ } else {
+ /* no wildcard at either end, so let's try an absolute
+ * match (ie. a specific IP)
+ */
+
+ if (addr && strnicmp(hostip, exclude_list + s, e - s) == 0)
+ return false; /* IP/hostname excluded. do not use proxy. */
+ if (strnicmp(hostname, exclude_list + s, e - s) == 0)
+ return false; /* IP/hostname excluded. do not use proxy. */
+ }
+
+ s = e;
+
+ /* Make sure we really have reached the next comma or end-of-string */
+ while (exclude_list[s] &&
+ !isspace((unsigned char)exclude_list[s]) &&
+ exclude_list[s] != ',') s++;
+ }
+
+ /* no matches in the exclude list, so use the proxy */
+ return true;
+}
+
+static char *dns_log_msg(const char *host, int addressfamily,
+ const char *reason)
+{
+ return dupprintf("Looking up host \"%s\"%s for %s", host,
+ (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
+ addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" :
+ ""), reason);
+}
+
+SockAddr *name_lookup(const char *host, int port, char **canonicalname,
+ Conf *conf, int addressfamily, LogContext *logctx,
+ const char *reason)
+{
+ if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE &&
+ do_proxy_dns(conf) &&
+ proxy_for_destination(NULL, host, port, conf)) {
+
+ if (logctx)
+ logeventf(logctx, "Leaving host lookup to proxy of \"%s\""
+ " (for %s)", host, reason);
+
+ *canonicalname = dupstr(host);
+ return sk_nonamelookup(host);
+ } else {
+ if (logctx)
+ logevent_and_free(
+ logctx, dns_log_msg(host, addressfamily, reason));
+
+ return sk_namelookup(host, canonicalname, addressfamily);
+ }
+}
+
+static const SocketVtable ProxySocket_sockvt = {
+ .plug = sk_proxy_plug,
+ .close = sk_proxy_close,
+ .write = sk_proxy_write,
+ .write_oob = sk_proxy_write_oob,
+ .write_eof = sk_proxy_write_eof,
+ .set_frozen = sk_proxy_set_frozen,
+ .socket_error = sk_proxy_socket_error,
+ .peer_info = NULL,
+};
+
+static const PlugVtable ProxySocket_plugvt = {
+ .log = plug_proxy_log,
+ .closing = plug_proxy_closing,
+ .receive = plug_proxy_receive,
+ .sent = plug_proxy_sent,
+ .accepting = plug_proxy_accepting
+};
+
+static char *proxy_description(Interactor *itr)
+{
+ ProxySocket *ps = container_of(itr, ProxySocket, interactor);
+ assert(ps->pn);
+ return dupprintf("%s connection to %s port %d", ps->pn->vt->type,
+ conf_get_str(ps->conf, CONF_proxy_host),
+ conf_get_int(ps->conf, CONF_proxy_port));
+}
+
+static LogPolicy *proxy_logpolicy(Interactor *itr)
+{
+ ProxySocket *ps = container_of(itr, ProxySocket, interactor);
+ return ps->clientlp;
+}
+
+static Seat *proxy_get_seat(Interactor *itr)
+{
+ ProxySocket *ps = container_of(itr, ProxySocket, interactor);
+ return ps->clientseat;
+}
+
+static void proxy_set_seat(Interactor *itr, Seat *seat)
+{
+ ProxySocket *ps = container_of(itr, ProxySocket, interactor);
+ ps->clientseat = seat;
+}
+
+static const InteractorVtable ProxySocket_interactorvt = {
+ .description = proxy_description,
+ .logpolicy = proxy_logpolicy,
+ .get_seat = proxy_get_seat,
+ .set_seat = proxy_set_seat,
+};
+
+static void proxy_prompts_callback(void *ctx)
+{
+ proxy_negotiate((ProxySocket *)ctx);
+}
+
+prompts_t *proxy_new_prompts(ProxySocket *ps)
+{
+ prompts_t *prs = new_prompts();
+ prs->callback = proxy_prompts_callback;
+ prs->callback_ctx = ps;
+ return prs;
+}
+
+void proxy_spr_abort(ProxyNegotiator *pn, SeatPromptResult spr)
+{
+ if (spr.kind == SPRK_SW_ABORT) {
+ pn->error = spr_get_error_message(spr);
+ } else {
+ assert(spr.kind == SPRK_USER_ABORT);
+ pn->aborted = true;
+ }
+}
+
+Socket *new_connection(SockAddr *addr, const char *hostname,
+ int port, bool privport,
+ bool oobinline, bool nodelay, bool keepalive,
+ Plug *plug, Conf *conf, Interactor *itr)
+{
+ int type = conf_get_int(conf, CONF_proxy_type);
+
+ if (type != PROXY_NONE &&
+ proxy_for_destination(addr, hostname, port, conf))
+ {
+ ProxySocket *ps;
+ SockAddr *proxy_addr;
+ char *proxy_canonical_name;
+ Socket *sret;
+
+ if ((type == PROXY_SSH_TCPIP ||
+ type == PROXY_SSH_EXEC ||
+ type == PROXY_SSH_SUBSYSTEM) &&
+ (sret = sshproxy_new_connection(addr, hostname, port, privport,
+ oobinline, nodelay, keepalive,
+ plug, conf, itr)) != NULL)
+ return sret;
+
+ if ((sret = platform_new_connection(addr, hostname, port, privport,
+ oobinline, nodelay, keepalive,
+ plug, conf, itr)) != NULL)
+ return sret;
+
+ ps = snew(ProxySocket);
+ ps->sock.vt = &ProxySocket_sockvt;
+ ps->plugimpl.vt = &ProxySocket_plugvt;
+ ps->interactor.vt = &ProxySocket_interactorvt;
+ ps->conf = conf_copy(conf);
+ ps->plug = plug;
+ ps->remote_addr = addr; /* will need to be freed on close */
+ ps->remote_port = port;
+
+ ps->error = NULL;
+ ps->pending_eof = false;
+ ps->freeze = false;
+
+ bufchain_init(&ps->pending_input_data);
+ bufchain_init(&ps->pending_output_data);
+ bufchain_init(&ps->pending_oob_output_data);
+ bufchain_init(&ps->output_from_negotiator);
+
+ ps->sub_socket = NULL;
+
+ /*
+ * If we've been given an Interactor by the caller, set ourselves
+ * up to work with it.
+ */
+ if (itr) {
+ ps->clientitr = itr;
+ interactor_set_child(ps->clientitr, &ps->interactor);
+ ps->clientlp = interactor_logpolicy(ps->clientitr);
+ ps->clientseat = interactor_borrow_seat(ps->clientitr);
+ }
+
+ const ProxyNegotiatorVT *vt;
+ switch (type) {
+ case PROXY_HTTP:
+ vt = &http_proxy_negotiator_vt;
+ break;
+ case PROXY_SOCKS4:
+ vt = &socks4_proxy_negotiator_vt;
+ break;
+ case PROXY_SOCKS5:
+ vt = &socks5_proxy_negotiator_vt;
+ break;
+ case PROXY_TELNET:
+ vt = &telnet_proxy_negotiator_vt;
+ break;
+ default:
+ ps->error = "Proxy error: Unknown proxy method";
+ return &ps->sock;
+ }
+ ps->pn = proxy_negotiator_new(vt);
+ ps->pn->ps = ps;
+ ps->pn->done = false;
+ ps->pn->error = NULL;
+ ps->pn->aborted = false;
+ ps->pn->input = &ps->pending_input_data;
+ /* Provide an Interactor to the negotiator if and only if we
+ * are usefully able to ask interactive questions of the user */
+ ps->pn->itr = ps->clientseat ? &ps->interactor : NULL;
+ bufchain_sink_init(ps->pn->output, &ps->output_from_negotiator);
+
+ {
+ char *logmsg = dupprintf("Will use %s proxy at %s:%d to connect"
+ " to %s:%d", vt->type,
+ conf_get_str(conf, CONF_proxy_host),
+ conf_get_int(conf, CONF_proxy_port),
+ hostname, port);
+ plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
+ sfree(logmsg);
+ }
+
+ {
+ char *logmsg = dns_log_msg(conf_get_str(conf, CONF_proxy_host),
+ conf_get_int(conf, CONF_addressfamily),
+ "proxy");
+ plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
+ sfree(logmsg);
+ }
+
+ /* look-up proxy */
+ proxy_addr = sk_namelookup(conf_get_str(conf, CONF_proxy_host),
+ &proxy_canonical_name,
+ conf_get_int(conf, CONF_addressfamily));
+ if (sk_addr_error(proxy_addr) != NULL) {
+ ps->error = "Proxy error: Unable to resolve proxy host name";
+ sk_addr_free(proxy_addr);
+ return &ps->sock;
+ }
+ sfree(proxy_canonical_name);
+
+ {
+ char addrbuf[256], *logmsg;
+ sk_getaddr(proxy_addr, addrbuf, lenof(addrbuf));
+ logmsg = dupprintf("Connecting to %s proxy at %s port %d",
+ vt->type, addrbuf,
+ conf_get_int(conf, CONF_proxy_port));
+ plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
+ sfree(logmsg);
+ }
+
+ /* create the actual socket we will be using,
+ * connected to our proxy server and port.
+ */
+ ps->proxy_addr = sk_addr_dup(proxy_addr);
+ ps->proxy_port = conf_get_int(conf, CONF_proxy_port);
+ ps->proxy_privport = privport;
+ ps->proxy_oobinline = oobinline;
+ ps->proxy_nodelay = nodelay;
+ ps->proxy_keepalive = keepalive;
+ ps->sub_socket = sk_new(proxy_addr, ps->proxy_port,
+ ps->proxy_privport, ps->proxy_oobinline,
+ ps->proxy_nodelay, ps->proxy_keepalive,
+ &ps->plugimpl);
+ if (sk_socket_error(ps->sub_socket) != NULL)
+ return &ps->sock;
+
+ /* start the proxy negotiation process... */
+ sk_set_frozen(ps->sub_socket, false);
+ proxy_negotiate(ps);
+
+ return &ps->sock;
+ }
+
+ /* no proxy, so just return the direct socket */
+ return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug);
+}
+
+Socket *new_listener(const char *srcaddr, int port, Plug *plug,
+ bool local_host_only, Conf *conf, int addressfamily)
+{
+ /* TODO: SOCKS (and potentially others) support inbound
+ * TODO: connections via the proxy. support them.
+ */
+
+ return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily);
+}
diff --git a/proxy/proxy.h b/proxy/proxy.h
new file mode 100644
index 00000000..72d06d49
--- /dev/null
+++ b/proxy/proxy.h
@@ -0,0 +1,127 @@
+/*
+ * Network proxy abstraction in PuTTY
+ *
+ * A proxy layer, if necessary, wedges itself between the
+ * network code and the higher level backend.
+ *
+ * Supported proxies: HTTP CONNECT, generic telnet, SOCKS 4 & 5
+ */
+
+#ifndef PUTTY_PROXY_H
+#define PUTTY_PROXY_H
+
+typedef struct ProxySocket ProxySocket;
+typedef struct ProxyNegotiator ProxyNegotiator;
+typedef struct ProxyNegotiatorVT ProxyNegotiatorVT;
+
+struct ProxySocket {
+ const char *error;
+
+ Socket *sub_socket;
+ Plug *plug;
+ SockAddr *remote_addr;
+ int remote_port;
+
+ /* Parameters needed to make further connections to the proxy */
+ SockAddr *proxy_addr;
+ int proxy_port;
+ bool proxy_privport, proxy_oobinline, proxy_nodelay, proxy_keepalive;
+
+ bufchain pending_output_data;
+ bufchain pending_oob_output_data;
+ bufchain pending_input_data;
+ bool pending_eof;
+
+ bool freeze; /* should we freeze the underlying socket when
+ * we are done with the proxy negotiation? this
+ * simply caches the value of sk_set_frozen calls.
+ */
+
+ ProxyNegotiator *pn; /* non-NULL if still negotiating */
+ bufchain output_from_negotiator;
+
+ /* configuration, used to look up proxy settings */
+ Conf *conf;
+
+ /* for interaction with the Seat */
+ Interactor *clientitr;
+ LogPolicy *clientlp;
+ Seat *clientseat;
+
+ Socket sock;
+ Plug plugimpl;
+ Interactor interactor;
+};
+
+struct ProxyNegotiator {
+ const ProxyNegotiatorVT *vt;
+
+ /* Standard fields for any ProxyNegotiator. new() and free() don't
+ * have to set these up; that's done centrally, to save duplication. */
+ ProxySocket *ps;
+ bufchain *input;
+ bufchain_sink output[1];
+ Interactor *itr; /* NULL if we are not able to interact with the user */
+
+ /* Set to report success during proxy negotiation. */
+ bool done;
+
+ /* Set to report an error during proxy negotiation. The main
+ * ProxySocket will free it, and will then guarantee never to call
+ * process_queue again. */
+ char *error;
+
+ /* Set to report user abort during proxy negotiation. */
+ bool aborted;
+
+ /* Set to request the centralised code to make a fresh connection
+ * to the proxy server, e.g. because an HTTP proxy slammed the
+ * connection shut after sending 407 Proxy Auth Required. */
+ bool reconnect;
+};
+
+struct ProxyNegotiatorVT {
+ ProxyNegotiator *(*new)(const ProxyNegotiatorVT *);
+ void (*process_queue)(ProxyNegotiator *);
+ void (*free)(ProxyNegotiator *);
+ const char *type;
+};
+
+static inline ProxyNegotiator *proxy_negotiator_new(
+ const ProxyNegotiatorVT *vt)
+{ return vt->new(vt); }
+static inline void proxy_negotiator_process_queue(ProxyNegotiator *pn)
+{ pn->vt->process_queue(pn); }
+static inline void proxy_negotiator_free(ProxyNegotiator *pn)
+{ pn->vt->free(pn); }
+
+extern const ProxyNegotiatorVT http_proxy_negotiator_vt;
+extern const ProxyNegotiatorVT socks4_proxy_negotiator_vt;
+extern const ProxyNegotiatorVT socks5_proxy_negotiator_vt;
+extern const ProxyNegotiatorVT telnet_proxy_negotiator_vt;
+
+/*
+ * Centralised functions to allow ProxyNegotiators to get hold of a
+ * prompts_t, and to deal with SeatPromptResults coming back.
+ */
+prompts_t *proxy_new_prompts(ProxySocket *ps);
+void proxy_spr_abort(ProxyNegotiator *pn, SeatPromptResult spr);
+
+/*
+ * This may be reused by local-command proxies on individual
+ * platforms.
+ */
+#define TELNET_CMD_MISSING_USERNAME 0x0001
+#define TELNET_CMD_MISSING_PASSWORD 0x0002
+char *format_telnet_command(SockAddr *addr, int port, Conf *conf,
+ unsigned *flags_out);
+
+DeferredSocketOpener *local_proxy_opener(
+ SockAddr *addr, int port, Plug *plug, Conf *conf, Interactor *itr);
+void local_proxy_opener_set_socket(DeferredSocketOpener *opener,
+ Socket *socket);
+char *platform_setup_local_proxy(Socket *socket, const char *cmd);
+
+#include "cproxy.h"
+
+#endif
diff --git a/proxy/socks.h b/proxy/socks.h
new file mode 100644
index 00000000..3e86ae23
--- /dev/null
+++ b/proxy/socks.h
@@ -0,0 +1,72 @@
+/*
+ * Constants used in the SOCKS protocols.
+ */
+
+/* Command codes common to both versions */
+#define SOCKS_CMD_CONNECT 1
+#define SOCKS_CMD_BIND 2
+
+/* SOCKS 4 definitions */
+
+#define SOCKS4_REQUEST_VERSION 4
+#define SOCKS4_REPLY_VERSION 0
+
+#define SOCKS4_RESP_SUCCESS 90
+#define SOCKS4_RESP_FAILURE 91
+#define SOCKS4_RESP_WANT_IDENTD 92
+#define SOCKS4_RESP_IDENTD_MISMATCH 93
+
+/*
+ * Special nonsense IP address range, used as a signal to indicate
+ * that an ASCIZ hostname follows the user id field.
+ *
+ * Strictly speaking, the use of this extension indicates that we're
+ * speaking SOCKS 4A rather than vanilla SOCKS 4, although we don't
+ * bother to draw the distinction.
+ */
+#define SOCKS4A_NAME_FOLLOWS_BASE 0x00000001 /* inclusive */
+#define SOCKS4A_NAME_FOLLOWS_LIMIT 0x00000100 /* exclusive */
+
+/* SOCKS 5 definitions */
+
+#define SOCKS5_REQUEST_VERSION 5
+#define SOCKS5_REPLY_VERSION 5
+
+/* Extra command codes extending the SOCKS_CMD_* list above */
+#define SOCKS5_CMD_UDP_ASSOCIATE 3
+
+#define SOCKS5_AUTH_NONE 0
+#define SOCKS5_AUTH_GSSAPI 1
+#define SOCKS5_AUTH_PASSWORD 2
+#define SOCKS5_AUTH_CHAP 3
+#define SOCKS5_AUTH_REJECTED 0xFF /* used in reply to indicate 'no
+ * acceptable method offered' */
+
+#define SOCKS5_AUTH_PASSWORD_VERSION 1
+
+#define SOCKS5_AUTH_CHAP_VERSION 1
+
+#define SOCKS5_AUTH_CHAP_ATTR_STATUS 0x00
+#define SOCKS5_AUTH_CHAP_ATTR_INFO 0x01
+#define SOCKS5_AUTH_CHAP_ATTR_USERNAME 0x02
+#define SOCKS5_AUTH_CHAP_ATTR_CHALLENGE 0x03
+#define SOCKS5_AUTH_CHAP_ATTR_RESPONSE 0x04
+#define SOCKS5_AUTH_CHAP_ATTR_CHARSET 0x05
+#define SOCKS5_AUTH_CHAP_ATTR_IDENTIFIER 0x10
+#define SOCKS5_AUTH_CHAP_ATTR_ALGLIST 0x11
+
+#define SOCKS5_AUTH_CHAP_ALG_HMACMD5 0x85
+
+#define SOCKS5_ADDR_IPV4 1
+#define SOCKS5_ADDR_IPV6 4
+#define SOCKS5_ADDR_HOSTNAME 3
+
+#define SOCKS5_RESP_SUCCESS 0
+#define SOCKS5_RESP_FAILURE 1
+#define SOCKS5_RESP_CONNECTION_NOT_ALLOWED_BY_RULESET 2
+#define SOCKS5_RESP_NETWORK_UNREACHABLE 3
+#define SOCKS5_RESP_HOST_UNREACHABLE 4
+#define SOCKS5_RESP_CONNECTION_REFUSED 5
+#define SOCKS5_RESP_TTL_EXPIRED 6
+#define SOCKS5_RESP_COMMAND_NOT_SUPPORTED 7
+#define SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED 8
diff --git a/proxy/socks4.c b/proxy/socks4.c
new file mode 100644
index 00000000..ac85ec05
--- /dev/null
+++ b/proxy/socks4.c
@@ -0,0 +1,136 @@
+/*
+ * SOCKS 4 proxy negotiation.
+ */
+
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+#include "socks.h"
+#include "sshcr.h"
+
+typedef struct Socks4ProxyNegotiator {
+ int crLine;
+ ProxyNegotiator pn;
+} Socks4ProxyNegotiator;
+
+static ProxyNegotiator *proxy_socks4_new(const ProxyNegotiatorVT *vt)
+{
+ Socks4ProxyNegotiator *s = snew(Socks4ProxyNegotiator);
+ s->pn.vt = vt;
+ s->crLine = 0;
+ return &s->pn;
+}
+
+static void proxy_socks4_free(ProxyNegotiator *pn)
+{
+ Socks4ProxyNegotiator *s = container_of(pn, Socks4ProxyNegotiator, pn);
+ sfree(s);
+}
+
+static void proxy_socks4_process_queue(ProxyNegotiator *pn)
+{
+ Socks4ProxyNegotiator *s = container_of(pn, Socks4ProxyNegotiator, pn);
+
+ crBegin(s->crLine);
+
+ {
+ char hostname[512];
+ bool write_hostname = false;
+
+ /*
+ * SOCKS 4 request packet:
+ *
+ * byte version
+ * byte command
+ * uint16 destination port number
+ * uint32 destination IPv4 address (or something in the
+ * SOCKS4A_NAME_FOLLOWS range)
+ * asciz username
+ * asciz destination hostname (if we sent SOCKS4A_NAME_FOLLOWS_*)
+ */
+
+ put_byte(pn->output, SOCKS4_REQUEST_VERSION);
+ put_byte(pn->output, SOCKS_CMD_CONNECT);
+ put_uint16(pn->output, pn->ps->remote_port);
+
+ switch (sk_addrtype(pn->ps->remote_addr)) {
+ case ADDRTYPE_IPV4: {
+ char addr[4];
+ sk_addrcopy(pn->ps->remote_addr, addr);
+ put_data(pn->output, addr, 4);
+ break;
+ }
+ case ADDRTYPE_NAME:
+ put_uint32(pn->output, SOCKS4A_NAME_FOLLOWS_BASE);
+ sk_getaddr(pn->ps->remote_addr, hostname, lenof(hostname));
+ write_hostname = true;
+ break;
+ case ADDRTYPE_IPV6:
+ pn->error = dupstr("SOCKS version 4 does not support IPv6");
+ crStopV;
+ }
+
+ put_asciz(pn->output, conf_get_str(pn->ps->conf, CONF_proxy_username));
+
+ if (write_hostname)
+ put_asciz(pn->output, hostname);
+ }
+
+ crReturnV;
+
+ {
+ unsigned char data[8];
+ crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 8));
+
+ /*
+ * SOCKS 4 response packet:
+ *
+ * byte version
+ * byte status
+ * uint16 port number
+ * uint32 IPv4 address
+ *
+ * We don't need to worry about the port and destination address.
+ */
+
+ if (data[0] != SOCKS4_REPLY_VERSION) {
+ pn->error = dupprintf("SOCKS proxy response contained reply "
+ "version number %d (expected 0)",
+ (int)data[0]);
+ crStopV;
+ }
+
+ switch (data[1]) {
+ case SOCKS4_RESP_SUCCESS:
+ pn->done = true;
+ break;
+
+ case SOCKS4_RESP_FAILURE:
+ pn->error = dupstr("SOCKS server reported failure to connect");
+ break;
+
+ case SOCKS4_RESP_WANT_IDENTD:
+ pn->error = dupstr("SOCKS server wanted IDENTD on client");
+ break;
+
+ case SOCKS4_RESP_IDENTD_MISMATCH:
+ pn->error = dupstr("Username and IDENTD on client don't agree");
+ break;
+
+ default:
+ pn->error = dupprintf("SOCKS server sent unrecognised error "
+ "code %d", (int)data[1]);
+ break;
+ }
+ crStopV;
+ }
+
+ crFinishV;
+}
+
+const struct ProxyNegotiatorVT socks4_proxy_negotiator_vt = {
+ .new = proxy_socks4_new,
+ .free = proxy_socks4_free,
+ .process_queue = proxy_socks4_process_queue,
+ .type = "SOCKS 4",
+};
diff --git a/proxy/socks5.c b/proxy/socks5.c
new file mode 100644
index 00000000..87a0bbc8
--- /dev/null
+++ b/proxy/socks5.c
@@ -0,0 +1,498 @@
+/*
+ * SOCKS 5 proxy negotiation.
+ */
+
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+#include "socks.h"
+#include "sshcr.h"
+
+static inline const char *socks5_auth_name(unsigned char m)
+{
+ switch (m) {
+ case SOCKS5_AUTH_NONE: return "none";
+ case SOCKS5_AUTH_GSSAPI: return "GSSAPI";
+ case SOCKS5_AUTH_PASSWORD: return "password";
+ case SOCKS5_AUTH_CHAP: return "CHAP";
+ default: return "unknown";
+ }
+}
+
+static inline const char *socks5_response_text(unsigned char m)
+{
+ switch (m) {
+ case SOCKS5_RESP_SUCCESS: return "success";
+ case SOCKS5_RESP_FAILURE: return "unspecified failure";
+ case SOCKS5_RESP_CONNECTION_NOT_ALLOWED_BY_RULESET:
+ return "connection not allowed by ruleset";
+ case SOCKS5_RESP_NETWORK_UNREACHABLE: return "network unreachable";
+ case SOCKS5_RESP_HOST_UNREACHABLE: return "host unreachable";
+ case SOCKS5_RESP_CONNECTION_REFUSED: return "connection refused";
+ case SOCKS5_RESP_TTL_EXPIRED: return "TTL expired";
+ case SOCKS5_RESP_COMMAND_NOT_SUPPORTED: return "command not supported";
+ case SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED:
+ return "address type not supported";
+ default: return "unknown";
+ }
+}
+
+typedef struct Socks5ProxyNegotiator {
+ int crLine;
+ strbuf *auth_methods_offered;
+ unsigned char auth_method;
+ unsigned n_chap_attrs;
+ unsigned chap_attr, chap_attr_len;
+ unsigned char chap_buf[256];
+ strbuf *username, *password;
+ prompts_t *prompts;
+ int username_prompt_index, password_prompt_index;
+ int response_addr_length;
+ ProxyNegotiator pn;
+} Socks5ProxyNegotiator;
+
+static ProxyNegotiator *proxy_socks5_new(const ProxyNegotiatorVT *vt)
+{
+ Socks5ProxyNegotiator *s = snew(Socks5ProxyNegotiator);
+ memset(s, 0, sizeof(*s));
+ s->pn.vt = vt;
+ s->auth_methods_offered = strbuf_new();
+ s->username = strbuf_new();
+ s->password = strbuf_new_nm();
+ return &s->pn;
+}
+
+static void proxy_socks5_free(ProxyNegotiator *pn)
+{
+ Socks5ProxyNegotiator *s = container_of(pn, Socks5ProxyNegotiator, pn);
+ strbuf_free(s->auth_methods_offered);
+ strbuf_free(s->username);
+ strbuf_free(s->password);
+ if (s->prompts)
+ free_prompts(s->prompts);
+ smemclr(s, sizeof(*s));
+ sfree(s);
+}
+
+static void proxy_socks5_process_queue(ProxyNegotiator *pn)
+{
+ Socks5ProxyNegotiator *s = container_of(pn, Socks5ProxyNegotiator, pn);
+
+ crBegin(s->crLine);
+
+ /*
+ * SOCKS 5 initial client packet:
+ *
+ * byte version
+ * byte number of available auth methods
+ * byte[] that many bytes indicating auth types
+ */
+
+ put_byte(pn->output, SOCKS5_REQUEST_VERSION);
+
+ strbuf_clear(s->auth_methods_offered);
+
+ /*
+ * We have two basic kinds of authentication to offer: none at
+ * all, and password-based systems (whether the password is sent
+ * in cleartext or proved via CHAP).
+ *
+ * We always offer 'none' as an option. We offer 'password' if we
+ * either have a username and password already from the Conf, or
+ * we have a Seat available to ask for them interactively. If
+ * neither, we don't offer those options in the first place.
+ */
+ put_byte(s->auth_methods_offered, SOCKS5_AUTH_NONE);
+
+ put_dataz(s->username, conf_get_str(pn->ps->conf, CONF_proxy_username));
+ put_dataz(s->password, conf_get_str(pn->ps->conf, CONF_proxy_password));
+ if (pn->itr || (s->username->len && s->password->len)) {
+ if (socks5_chap_available)
+ put_byte(s->auth_methods_offered, SOCKS5_AUTH_CHAP);
+
+ put_byte(s->auth_methods_offered, SOCKS5_AUTH_PASSWORD);
+ }
+
+ put_byte(pn->output, s->auth_methods_offered->len);
+ put_datapl(pn->output, ptrlen_from_strbuf(s->auth_methods_offered));
+
+ crReturnV;
+
+ /*
+ * SOCKS 5 initial server packet:
+ *
+ * byte version
+ * byte selected auth method, or SOCKS5_AUTH_REJECTED
+ */
+ {
+ unsigned char data[2];
+ crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 2));
+
+ if (data[0] != SOCKS5_REPLY_VERSION) {
+ pn->error = dupprintf("SOCKS proxy returned unexpected "
+ "reply version %d (expected %d)",
+ (int)data[0], SOCKS5_REPLY_VERSION);
+ crStopV;
+ }
+
+ if (data[1] == SOCKS5_AUTH_REJECTED) {
+ pn->error = dupstr("SOCKS server rejected every authentication "
+ "method we offered");
+ crStopV;
+ }
+
+ {
+ bool found = false;
+ for (size_t i = 0; i < s->auth_methods_offered->len; i++)
+ if (s->auth_methods_offered->u[i] == data[1]) {
+ found = true;
+ break;
+ }
+
+ if (!found) {
+ pn->error = dupprintf("SOCKS server asked for auth method %d "
+ "(%s), which we did not offer",
+ (int)data[1], socks5_auth_name(data[1]));
+ crStopV;
+ }
+ }
+
+ s->auth_method = data[1];
+ }
+
+ /*
+ * The 'none' auth option requires no further negotiation. If that
+ * was the one we selected, go straight to making the connection.
+ */
+ if (s->auth_method == SOCKS5_AUTH_NONE)
+ goto authenticated;
+
+ /*
+ * Otherwise, we're going to need a username and password, so this
+ * is the moment to stop and ask for one if we don't already have
+ * them.
+ */
+ if (pn->itr && (!s->username->len || !s->password->len)) {
+ s->prompts = proxy_new_prompts(pn->ps);
+ s->prompts->to_server = true;
+ s->prompts->from_server = false;
+ s->prompts->name = dupstr("SOCKS proxy authentication");
+ if (!s->username->len) {
+ s->username_prompt_index = s->prompts->n_prompts;
+ add_prompt(s->prompts, dupstr("Proxy username: "), true);
+ } else {
+ s->username_prompt_index = -1;
+ }
+ if (!s->password->len) {
+ s->password_prompt_index = s->prompts->n_prompts;
+ add_prompt(s->prompts, dupstr("Proxy password: "), false);
+ } else {
+ s->password_prompt_index = -1;
+ }
+
+ while (true) {
+ SeatPromptResult spr = seat_get_userpass_input(
+ interactor_announce(pn->itr), s->prompts);
+ if (spr.kind == SPRK_OK) {
+ break;
+ } else if (spr_is_abort(spr)) {
+ proxy_spr_abort(pn, spr);
+ crStopV;
+ }
+ crReturnV;
+ }
+
+ if (s->username_prompt_index != -1) {
+ strbuf_clear(s->username);
+ put_dataz(s->username,
+ prompt_get_result_ref(
+ s->prompts->prompts[s->username_prompt_index]));
+ }
+
+ if (s->password_prompt_index != -1) {
+ strbuf_clear(s->password);
+ put_dataz(s->password,
+ prompt_get_result_ref(
+ s->prompts->prompts[s->password_prompt_index]));
+ }
+
+ free_prompts(s->prompts);
+ s->prompts = NULL;
+ }
+
+ /*
+ * Now process the different auth methods that will use that
+ * username and password. Note we can't do this using the natural
+ * idiom of a switch statement, because there are crReturns inside
+ * some cases.
+ */
+ if (s->auth_method == SOCKS5_AUTH_PASSWORD) {
+ /*
+ * SOCKS 5 password auth packet:
+ *
+ * byte version
+ * pstring username
+ * pstring password
+ */
+ put_byte(pn->output, SOCKS5_AUTH_PASSWORD_VERSION);
+ if (!put_pstring(pn->output, s->username->s)) {
+ pn->error = dupstr("SOCKS 5 authentication cannot support "
+ "usernames longer than 255 chars");
+ crStopV;
+ }
+ if (!put_pstring(pn->output, s->password->s)) {
+ pn->error = dupstr("SOCKS 5 authentication cannot support "
+ "passwords longer than 255 chars");
+ crStopV;
+ }
+
+ /*
+ * SOCKS 5 password reply packet:
+ *
+ * byte version
+ * byte 0 for success, >0 for failure
+ */
+ unsigned char data[2];
+ crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 2));
+
+ if (data[0] != SOCKS5_AUTH_PASSWORD_VERSION) {
+ pn->error = dupprintf(
+ "SOCKS 5 password reply had version number %d (expected "
+ "%d)", (int)data[0], SOCKS5_AUTH_PASSWORD_VERSION);
+ crStopV;
+ }
+
+ if (data[1] != 0) {
+ pn->error = dupstr("SOCKS 5 server rejected our password");
+ crStopV;
+ }
+ } else if (s->auth_method == SOCKS5_AUTH_CHAP) {
+ assert(socks5_chap_available);
+
+ /*
+ * All CHAP packets, in both directions, have the same
+ * overall format:
+ *
+ * byte version
+ * byte number of attributes
+ *
+ * and then for each attribute:
+ *
+ * byte attribute type
+ * byte length
+ * byte[] that many bytes of payload
+ *
+ * In the initial outgoing packet we send two attributes:
+ * the list of supported algorithm names, and the
+ * username.
+ *
+ * (It's possible that we ought to delay sending the
+ * username until the second packet, in case the proxy
+ * sent back an attribute indicating which character set
+ * it would like us to use.)
+ */
+ put_byte(pn->output, SOCKS5_AUTH_CHAP_VERSION);
+ put_byte(pn->output, 2); /* number of attributes */
+
+ put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_ALGLIST);
+ put_byte(pn->output, 1); /* string length */
+ put_byte(pn->output, SOCKS5_AUTH_CHAP_ALG_HMACMD5);
+
+ /* Second attribute: username */
+ {
+ put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_USERNAME);
+ if (!put_pstring(pn->output, s->username->s)) {
+ pn->error = dupstr(
+ "SOCKS 5 CHAP authentication cannot support "
+ "usernames longer than 255 chars");
+ crStopV;
+ }
+ }
+
+ while (true) {
+ /*
+ * Process a CHAP response packet, which has the same
+ * overall format as the outgoing packet shown above.
+ */
+ unsigned char data[2];
+ crMaybeWaitUntilV(bufchain_try_fetch_consume(
+ pn->input, data, 2));
+ if (data[0] != SOCKS5_AUTH_CHAP_VERSION) {
+ pn->error = dupprintf(
+ "SOCKS 5 CHAP reply had version number %d (expected "
+ "%d)", (int)data[0], SOCKS5_AUTH_CHAP_VERSION);
+ crStopV;
+ }
+
+ s->n_chap_attrs = data[1];
+ if (s->n_chap_attrs == 0) {
+ /*
+ * If we receive a CHAP packet containing no
+ * attributes, then we have nothing we didn't have
+ * before, and can't make further progress.
+ */
+ pn->error = dupprintf(
+ "SOCKS 5 CHAP reply sent no attributes");
+ crStopV;
+ }
+ while (s->n_chap_attrs-- > 0) {
+ unsigned char data[2];
+ crMaybeWaitUntilV(bufchain_try_fetch_consume(
+ pn->input, data, 2));
+ s->chap_attr = data[0];
+ s->chap_attr_len = data[1];
+ crMaybeWaitUntilV(bufchain_try_fetch_consume(
+ pn->input, s->chap_buf, s->chap_attr_len));
+
+ if (s->chap_attr == SOCKS5_AUTH_CHAP_ATTR_STATUS) {
+ if (s->chap_attr_len == 1 && s->chap_buf[0] == 0) {
+ /* Status 0 means success: we are authenticated! */
+ goto authenticated;
+ } else {
+ pn->error = dupstr(
+ "SOCKS 5 CHAP authentication failed");
+ crStopV;
+ }
+ } else if (s->chap_attr==SOCKS5_AUTH_CHAP_ATTR_CHALLENGE) {
+ /* The CHAP challenge string. Send the response */
+ strbuf *response = chap_response(
+ make_ptrlen(s->chap_buf, s->chap_attr_len),
+ ptrlen_from_strbuf(s->password));
+
+ put_byte(pn->output, SOCKS5_AUTH_CHAP_VERSION);
+ put_byte(pn->output, 1); /* number of attributes */
+ put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_RESPONSE);
+ put_byte(pn->output, response->len);
+ put_datapl(pn->output, ptrlen_from_strbuf(response));
+
+ strbuf_free(response);
+ } else {
+ /* ignore all other attributes */
+ }
+ }
+ }
+ } else {
+ unreachable("bad auth method in SOCKS 5 negotiation");
+ }
+
+ authenticated:
+
+ /*
+ * SOCKS 5 connection command:
+ *
+ * byte version
+ * byte command
+ * byte reserved (send as zero)
+ * byte address type
+ * byte[] address, with variable size (see below)
+ * uint16 port
+ */
+ put_byte(pn->output, SOCKS5_REPLY_VERSION);
+ put_byte(pn->output, SOCKS_CMD_CONNECT);
+ put_byte(pn->output, 0); /* reserved byte */
+
+ switch (sk_addrtype(pn->ps->remote_addr)) {
+ case ADDRTYPE_IPV4: {
+ /* IPv4: address is 4 raw bytes */
+ put_byte(pn->output, SOCKS5_ADDR_IPV4);
+ char buf[4];
+ sk_addrcopy(pn->ps->remote_addr, buf);
+ put_data(pn->output, buf, sizeof(buf));
+ break;
+ }
+ case ADDRTYPE_IPV6: {
+ /* IPv6: address is 16 raw bytes */
+ put_byte(pn->output, SOCKS5_ADDR_IPV6);
+ char buf[16];
+ sk_addrcopy(pn->ps->remote_addr, buf);
+ put_data(pn->output, buf, sizeof(buf));
+ break;
+ }
+ case ADDRTYPE_NAME: {
+ /* Hostname: address is a pstring (Pascal-style string,
+ * unterminated but with a one-byte prefix length) */
+ put_byte(pn->output, SOCKS5_ADDR_HOSTNAME);
+ char hostname[512];
+ sk_getaddr(pn->ps->remote_addr, hostname, lenof(hostname));
+ if (!put_pstring(pn->output, hostname)) {
+ pn->error = dupstr(
+ "SOCKS 5 cannot support host names longer than 255 chars");
+ crStopV;
+ }
+ break;
+ }
+ default:
+ unreachable("Unexpected addrtype in SOCKS 5 proxy");
+ }
+
+ put_uint16(pn->output, pn->ps->remote_port);
+ crReturnV;
+
+ /*
+ * SOCKS 5 connection response:
+ *
+ * byte version
+ * byte status
+ * byte reserved
+ * byte address type
+ * byte[] address bound to (same formats as in connection request)
+ * uint16 port
+ *
+ * We read the first four bytes and then decide what to do next.
+ */
+ {
+ unsigned char data[4];
+ crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 4));
+
+ if (data[0] != SOCKS5_REPLY_VERSION) {
+ pn->error = dupprintf("SOCKS proxy returned unexpected "
+ "reply version %d (expected %d)",
+ (int)data[0], SOCKS5_REPLY_VERSION);
+ crStopV;
+ }
+
+ if (data[1] != SOCKS5_RESP_SUCCESS) {
+ pn->error = dupprintf("SOCKS proxy failed to connect, error %d "
+ "(%s)", (int)data[1],
+ socks5_response_text(data[1]));
+ crStopV;
+ }
+
+ /*
+ * Process each address type to find out the size of the rest
+ * of the packet. Note we can't do this using the natural
+ * idiom of a switch statement, because there are crReturns
+ * inside some cases.
+ */
+ if (data[3] == SOCKS5_ADDR_IPV4) {
+ s->response_addr_length = 4;
+ } else if (data[3] == SOCKS5_ADDR_IPV6) {
+ s->response_addr_length = 16;
+ } else if (data[3] == SOCKS5_ADDR_HOSTNAME) {
+ /* Read the hostname length byte to find out how much to read */
+ unsigned char len;
+ crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, &len, 1));
+ s->response_addr_length = len;
+ break;
+ } else {
+ pn->error = dupprintf("SOCKS proxy response included unknown "
+ "address type %d", (int)data[3]);
+ crStopV;
+ }
+
+ /* Read and ignore the address and port fields */
+ crMaybeWaitUntilV(bufchain_try_consume(
+ pn->input, s->response_addr_length + 2));
+ }
+
+ /* And we're done! */
+ pn->done = true;
+ crFinishV;
+}
+
+const struct ProxyNegotiatorVT socks5_proxy_negotiator_vt = {
+ .new = proxy_socks5_new,
+ .free = proxy_socks5_free,
+ .process_queue = proxy_socks5_process_queue,
+ .type = "SOCKS 5",
+};
diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c
new file mode 100644
index 00000000..165c6c0a
--- /dev/null
+++ b/proxy/sshproxy.c
@@ -0,0 +1,746 @@
+/*
+ * sshproxy.c: implement a Socket type that talks to an entire
+ * subsidiary SSH connection (sometimes called a 'jump host').
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "network.h"
+#include "storage.h"
+#include "proxy.h"
+
+const bool ssh_proxy_supported = true;
+
+typedef struct SshProxy {
+ char *errmsg;
+ Conf *conf;
+ LogContext *logctx;
+ Backend *backend;
+ LogPolicy *clientlp;
+ Seat *clientseat;
+ Interactor *clientitr;
+
+ bool got_proxy_password, tried_proxy_password;
+ char *proxy_password;
+
+ ProxyStderrBuf psb;
+ Plug *plug;
+
+ bool frozen;
+ bufchain ssh_to_socket;
+ bool rcvd_eof_ssh_to_socket, sent_eof_ssh_to_socket;
+ bool conn_established;
+
+ SockAddr *addr;
+ int port;
+
+ /* Traits implemented: we're a Socket from the point of view of
+ * the client connection, and a Seat from the POV of the SSH
+ * backend we instantiate. */
+ Socket sock;
+ LogPolicy logpolicy;
+ Seat seat;
+} SshProxy;
+
+static Plug *sshproxy_plug(Socket *s, Plug *p)
+{
+ SshProxy *sp = container_of(s, SshProxy, sock);
+ Plug *oldplug = sp->plug;
+ if (p)
+ sp->plug = p;
+ return oldplug;
+}
+
+static void sshproxy_close(Socket *s)
+{
+ SshProxy *sp = container_of(s, SshProxy, sock);
+
+ sk_addr_free(sp->addr);
+ sfree(sp->errmsg);
+ conf_free(sp->conf);
+ if (sp->backend)
+ backend_free(sp->backend);
+ if (sp->logctx)
+ log_free(sp->logctx);
+ if (sp->proxy_password)
+ burnstr(sp->proxy_password);
+ bufchain_clear(&sp->ssh_to_socket);
+
+ delete_callbacks_for_context(sp);
+ sfree(sp);
+}
+
+static size_t sshproxy_write(Socket *s, const void *data, size_t len)
+{
+ SshProxy *sp = container_of(s, SshProxy, sock);
+ if (!sp->backend)
+ return 0;
+ backend_send(sp->backend, data, len);
+ return backend_sendbuffer(sp->backend);
+}
+
+static size_t sshproxy_write_oob(Socket *s, const void *data, size_t len)
+{
+ /*
+ * oob data is treated as inband; nasty, but nothing really
+ * better we can do
+ */
+ return sshproxy_write(s, data, len);
+}
+
+static void sshproxy_write_eof(Socket *s)
+{
+ SshProxy *sp = container_of(s, SshProxy, sock);
+ if (!sp->backend)
+ return;
+ backend_special(sp->backend, SS_EOF, 0);
+}
+
+static void try_send_ssh_to_socket(void *ctx);
+
+static void try_send_ssh_to_socket_cb(void *ctx)
+{
+ SshProxy *sp = (SshProxy *)ctx;
+ try_send_ssh_to_socket(sp);
+ if (sp->backend)
+ backend_unthrottle(sp->backend, bufchain_size(&sp->ssh_to_socket));
+}
+
+static void sshproxy_set_frozen(Socket *s, bool is_frozen)
+{
+ SshProxy *sp = container_of(s, SshProxy, sock);
+ sp->frozen = is_frozen;
+ if (!sp->frozen)
+ queue_toplevel_callback(try_send_ssh_to_socket_cb, sp);
+}
+
+static const char *sshproxy_socket_error(Socket *s)
+{
+ SshProxy *sp = container_of(s, SshProxy, sock);
+ return sp->errmsg;
+}
+
+static SocketPeerInfo *sshproxy_peer_info(Socket *s)
+{
+ return NULL;
+}
+
+static const SocketVtable SshProxy_sock_vt = {
+ .plug = sshproxy_plug,
+ .close = sshproxy_close,
+ .write = sshproxy_write,
+ .write_oob = sshproxy_write_oob,
+ .write_eof = sshproxy_write_eof,
+ .set_frozen = sshproxy_set_frozen,
+ .socket_error = sshproxy_socket_error,
+ .peer_info = sshproxy_peer_info,
+};
+
+static void sshproxy_eventlog(LogPolicy *lp, const char *event)
+{
+ SshProxy *sp = container_of(lp, SshProxy, logpolicy);
+ log_proxy_stderr(sp->plug, &sp->psb, event, strlen(event));
+ log_proxy_stderr(sp->plug, &sp->psb, "\n", 1);
+}
+
+static int sshproxy_askappend(LogPolicy *lp, Filename *filename,
+ void (*callback)(void *ctx, int result),
+ void *ctx)
+{
+ SshProxy *sp = container_of(lp, SshProxy, logpolicy);
+
+ /*
+ * If we have access to the outer LogPolicy, pass on this request
+ * to the end user.
+ */
+ if (sp->clientlp)
+ return lp_askappend(sp->clientlp, filename, callback, ctx);
+
+ /*
+ * Otherwise, fall back to the safe noninteractive assumption.
+ */
+ char *msg = dupprintf("Log file \"%s\" already exists; logging cancelled",
+ filename_to_str(filename));
+ sshproxy_eventlog(lp, msg);
+ sfree(msg);
+ return 0;
+}
+
+static void sshproxy_logging_error(LogPolicy *lp, const char *event)
+{
+ SshProxy *sp = container_of(lp, SshProxy, logpolicy);
+
+ /*
+ * If we have access to the outer LogPolicy, pass on this request
+ * to it.
+ */
+ if (sp->clientlp) {
+ lp_logging_error(sp->clientlp, event);
+ return;
+ }
+
+ /*
+ * Otherwise, the best we can do is to put it in the outer SSH
+ * connection's Event Log.
+ */
+ char *msg = dupprintf("Logging error: %s", event);
+ sshproxy_eventlog(lp, msg);
+ sfree(msg);
+}
+
+static const LogPolicyVtable SshProxy_logpolicy_vt = {
+ .eventlog = sshproxy_eventlog,
+ .askappend = sshproxy_askappend,
+ .logging_error = sshproxy_logging_error,
+ .verbose = null_lp_verbose_no,
+};
+
+/*
+ * Function called when we encounter an error during connection setup that's
+ * likely to be the cause of terminating the proxy SSH connection. Putting it
+ * in the Event Log is useful on general principles; also putting it in
+ * sp->errmsg meaks that it will be passed back through plug_closing when the
+ * proxy SSH connection actually terminates, so that the end user will see
+ * what went wrong in the proxy connection.
+ */
+static void sshproxy_error(SshProxy *sp, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ char *msg = dupvprintf(fmt, ap);
+ va_end(ap);
+
+ if (!sp->errmsg)
+ sp->errmsg = dupstr(msg);
+
+ sshproxy_eventlog(&sp->logpolicy, msg);
+ sfree(msg);
+}
+
+static void try_send_ssh_to_socket(void *ctx)
+{
+ SshProxy *sp = (SshProxy *)ctx;
+
+ if (sp->frozen)
+ return;
+
+ while (bufchain_size(&sp->ssh_to_socket)) {
+ ptrlen pl = bufchain_prefix(&sp->ssh_to_socket);
+ plug_receive(sp->plug, 0, pl.ptr, pl.len);
+ bufchain_consume(&sp->ssh_to_socket, pl.len);
+ }
+
+ if (sp->rcvd_eof_ssh_to_socket &&
+ !sp->sent_eof_ssh_to_socket) {
+ sp->sent_eof_ssh_to_socket = true;
+ plug_closing_normal(sp->plug);
+ }
+}
+
+static void sshproxy_notify_session_started(Seat *seat)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+
+ if (sp->clientseat)
+ interactor_return_seat(sp->clientitr);
+ sp->conn_established = true;
+
+ plug_log(sp->plug, PLUGLOG_CONNECT_SUCCESS, sp->addr, sp->port, NULL, 0);
+}
+
+static size_t sshproxy_output(Seat *seat, SeatOutputType type,
+ const void *data, size_t len)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+ switch (type) {
+ case SEAT_OUTPUT_STDOUT:
+ bufchain_add(&sp->ssh_to_socket, data, len);
+ try_send_ssh_to_socket(sp);
+ break;
+ case SEAT_OUTPUT_STDERR:
+ log_proxy_stderr(sp->plug, &sp->psb, data, len);
+ break;
+ }
+ return bufchain_size(&sp->ssh_to_socket);
+}
+
+static inline InteractionReadySeat wrap(Seat *seat)
+{
+ /*
+ * When we receive interaction requests from the proxy and want to
+ * pass them on to our client Seat, we have to present them to the
+ * latter in the form of an InteractionReadySeat. This forwarding
+ * scenario is the one case where we _mustn't_ get an
+ * InteractionReadySeat by calling interactor_announce(), because
+ * the point is that we're _not_ the originating Interactor, we're
+ * just forwarding the request from the real one, which has
+ * already announced itself.
+ *
+ * So, just here in the code, it really is the right thing to make
+ * an InteractionReadySeat out of a plain Seat * without an
+ * announcement.
+ */
+ InteractionReadySeat iseat;
+ iseat.seat = seat;
+ return iseat;
+}
+
+static size_t sshproxy_banner(Seat *seat, const void *data, size_t len)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+ if (sp->clientseat) {
+ /*
+ * If we have access to the outer Seat, pass the SSH login
+ * banner on to it.
+ */
+ return seat_banner(wrap(sp->clientseat), data, len);
+ } else {
+ return 0;
+ }
+}
+
+static bool sshproxy_eof(Seat *seat)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+ sp->rcvd_eof_ssh_to_socket = true;
+ try_send_ssh_to_socket(sp);
+ return false;
+}
+
+static void sshproxy_sent(Seat *seat, size_t new_bufsize)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+ plug_sent(sp->plug, new_bufsize);
+}
+
+static void sshproxy_send_close(SshProxy *sp)
+{
+ if (sp->clientseat)
+ interactor_return_seat(sp->clientitr);
+
+ if (!sp->conn_established)
+ plug_log(sp->plug, PLUGLOG_CONNECT_FAILED, sp->addr, sp->port,
+ sp->errmsg, 0);
+
+ if (sp->errmsg)
+ plug_closing_error(sp->plug, sp->errmsg);
+ else if (!sp->conn_established && backend_exitcode(sp->backend) == 0)
+ plug_closing_user_abort(sp->plug);
+ else
+ plug_closing_normal(sp->plug);
+}
+
+static void sshproxy_notify_remote_disconnect_callback(void *vctx)
+{
+ SshProxy *sp = (SshProxy *)vctx;
+
+ /* notify_remote_disconnect can be called redundantly, so first
+ * check if the backend really has become disconnected */
+ if (backend_connected(sp->backend))
+ return;
+
+ sshproxy_send_close(sp);
+}
+
+static void sshproxy_notify_remote_disconnect(Seat *seat)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+ queue_toplevel_callback(sshproxy_notify_remote_disconnect_callback, sp);
+}
+
+static SeatPromptResult sshproxy_get_userpass_input(Seat *seat, prompts_t *p)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+
+ /*
+ * If we have a stored proxy_password, use that, via logic similar
+ * to cmdline_get_passwd_input: we only try it if we're given a
+ * prompts_t containing exactly one prompt, and that prompt is set
+ * to non-echoing.
+ */
+ if (sp->got_proxy_password && !sp->tried_proxy_password &&
+ p->n_prompts == 1 && !p->prompts[0]->echo) {
+ prompt_set_result(p->prompts[0], sp->proxy_password);
+ burnstr(sp->proxy_password);
+ sp->proxy_password = NULL;
+ sp->tried_proxy_password = true;
+ return SPR_OK;
+ }
+
+ if (sp->clientseat) {
+ /*
+ * If we have access to the outer Seat, pass this prompt
+ * request on to it.
+ */
+ return seat_get_userpass_input(wrap(sp->clientseat), p);
+ }
+
+ /*
+ * Otherwise, behave as if noninteractive (like plink -batch):
+ * reject all attempts to present a prompt to the user, and log in
+ * the Event Log to say why not.
+ */
+ sshproxy_error(sp, "Unable to provide interactive authentication "
+ "requested by proxy SSH connection");
+ return SPR_SW_ABORT("Noninteractive SSH proxy cannot perform "
+ "interactive authentication");
+}
+
+static void sshproxy_connection_fatal_callback(void *vctx)
+{
+ SshProxy *sp = (SshProxy *)vctx;
+ sshproxy_send_close(sp);
+}
+
+static void sshproxy_connection_fatal(Seat *seat, const char *message)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+ if (!sp->errmsg) {
+ sp->errmsg = dupprintf(
+ "fatal error in proxy SSH connection: %s", message);
+ queue_toplevel_callback(sshproxy_connection_fatal_callback, sp);
+ }
+}
+
+static SeatPromptResult sshproxy_confirm_ssh_host_key(
+ Seat *seat, const char *host, int port, const char *keytype,
+ char *keystr, SeatDialogText *text, HelpCtx helpctx,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+
+ if (sp->clientseat) {
+ /*
+ * If we have access to the outer Seat, pass this prompt
+ * request on to it.
+ */
+ return seat_confirm_ssh_host_key(
+ wrap(sp->clientseat), host, port, keytype, keystr, text,
+ helpctx, callback, ctx);
+ }
+
+ /*
+ * Otherwise, behave as if we're in batch mode, i.e. take the safe
+ * option in the absence of interactive confirmation, i.e. abort
+ * the connection.
+ */
+ return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm host key");
+}
+
+static SeatPromptResult sshproxy_confirm_weak_crypto_primitive(
+ Seat *seat, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+
+ if (sp->clientseat) {
+ /*
+ * If we have access to the outer Seat, pass this prompt
+ * request on to it.
+ */
+ return seat_confirm_weak_crypto_primitive(
+ wrap(sp->clientseat), algtype, algname, callback, ctx);
+ }
+
+ /*
+ * Otherwise, behave as if we're in batch mode: take the safest
+ * option.
+ */
+ sshproxy_error(sp, "First %s supported by server is %s, below warning "
+ "threshold. Abandoning proxy SSH connection.",
+ algtype, algname);
+ return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm "
+ "weak crypto primitive");
+}
+
+static SeatPromptResult sshproxy_confirm_weak_cached_hostkey(
+ Seat *seat, const char *algname, const char *betteralgs,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+
+ if (sp->clientseat) {
+ /*
+ * If we have access to the outer Seat, pass this prompt
+ * request on to it.
+ */
+ return seat_confirm_weak_cached_hostkey(
+ wrap(sp->clientseat), algname, betteralgs, callback, ctx);
+ }
+
+ /*
+ * Otherwise, behave as if we're in batch mode: take the safest
+ * option.
+ */
+ sshproxy_error(sp, "First host key type stored for server is %s, below "
+ "warning threshold. Abandoning proxy SSH connection.",
+ algname);
+ return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm "
+ "weak cached host key");
+}
+
+static const SeatDialogPromptDescriptions *sshproxy_prompt_descriptions(
+ Seat *seat)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+
+ /* If we have a client seat, return their prompt descriptions, so
+ * that prompts passed on to them will make sense. */
+ if (sp->clientseat)
+ return seat_prompt_descriptions(sp->clientseat);
+
+ /* Otherwise, it doesn't matter what we return, so do the easiest thing. */
+ return nullseat_prompt_descriptions(NULL);
+}
+
+static StripCtrlChars *sshproxy_stripctrl_new(
+ Seat *seat, BinarySink *bs_out, SeatInteractionContext sic)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+ if (sp->clientseat)
+ return seat_stripctrl_new(sp->clientseat, bs_out, sic);
+ else
+ return NULL;
+}
+
+static void sshproxy_set_trust_status(Seat *seat, bool trusted)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+ if (sp->clientseat)
+ seat_set_trust_status(sp->clientseat, trusted);
+}
+
+static bool sshproxy_can_set_trust_status(Seat *seat)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+ return sp->clientseat && seat_can_set_trust_status(sp->clientseat);
+}
+
+static bool sshproxy_verbose(Seat *seat)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+ return sp->clientseat && seat_verbose(sp->clientseat);
+}
+
+static bool sshproxy_interactive(Seat *seat)
+{
+ SshProxy *sp = container_of(seat, SshProxy, seat);
+ return sp->clientseat && seat_interactive(sp->clientseat);
+}
+
+static const SeatVtable SshProxy_seat_vt = {
+ .output = sshproxy_output,
+ .eof = sshproxy_eof,
+ .sent = sshproxy_sent,
+ .banner = sshproxy_banner,
+ .get_userpass_input = sshproxy_get_userpass_input,
+ .notify_session_started = sshproxy_notify_session_started,
+ .notify_remote_exit = nullseat_notify_remote_exit,
+ .notify_remote_disconnect = sshproxy_notify_remote_disconnect,
+ .connection_fatal = sshproxy_connection_fatal,
+ .update_specials_menu = nullseat_update_specials_menu,
+ .get_ttymode = nullseat_get_ttymode,
+ .set_busy_status = nullseat_set_busy_status,
+ .confirm_ssh_host_key = sshproxy_confirm_ssh_host_key,
+ .confirm_weak_crypto_primitive = sshproxy_confirm_weak_crypto_primitive,
+ .confirm_weak_cached_hostkey = sshproxy_confirm_weak_cached_hostkey,
+ .prompt_descriptions = sshproxy_prompt_descriptions,
+ .is_utf8 = nullseat_is_never_utf8,
+ .echoedit_update = nullseat_echoedit_update,
+ .get_x_display = nullseat_get_x_display,
+ .get_windowid = nullseat_get_windowid,
+ .get_window_pixel_size = nullseat_get_window_pixel_size,
+ .stripctrl_new = sshproxy_stripctrl_new,
+ .set_trust_status = sshproxy_set_trust_status,
+ .can_set_trust_status = sshproxy_can_set_trust_status,
+ .has_mixed_input_stream = nullseat_has_mixed_input_stream_no,
+ .verbose = sshproxy_verbose,
+ .interactive = sshproxy_interactive,
+ .get_cursor_position = nullseat_get_cursor_position,
+};
+
+Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname,
+ int port, bool privport,
+ bool oobinline, bool nodelay, bool keepalive,
+ Plug *plug, Conf *clientconf,
+ Interactor *clientitr)
+{
+ SshProxy *sp = snew(SshProxy);
+ memset(sp, 0, sizeof(*sp));
+
+ sp->sock.vt = &SshProxy_sock_vt;
+ sp->logpolicy.vt = &SshProxy_logpolicy_vt;
+ sp->seat.vt = &SshProxy_seat_vt;
+ sp->plug = plug;
+ psb_init(&sp->psb);
+ bufchain_init(&sp->ssh_to_socket);
+
+ sp->addr = addr;
+ sp->port = port;
+
+ sp->conf = conf_new();
+ /* Try to treat proxy_hostname as the title of a saved session. If
+ * that fails, set up a default Conf of our own treating it as a
+ * hostname. */
+ const char *proxy_hostname = conf_get_str(clientconf, CONF_proxy_host);
+ if (do_defaults(proxy_hostname, sp->conf)) {
+ if (!conf_launchable(sp->conf)) {
+ sp->errmsg = dupprintf("saved session '%s' is not launchable",
+ proxy_hostname);
+ return &sp->sock;
+ }
+ } else {
+ do_defaults(NULL, sp->conf);
+ /* In hostname mode, we default to PROT_SSH. This is more useful than
+ * the obvious approach of defaulting to the protocol defined in
+ * Default Settings, because only SSH (ok, and bare ssh-connection)
+ * can be used for this kind of proxy. */
+ conf_set_int(sp->conf, CONF_protocol, PROT_SSH);
+ conf_set_str(sp->conf, CONF_host, proxy_hostname);
+ conf_set_int(sp->conf, CONF_port,
+ conf_get_int(clientconf, CONF_proxy_port));
+ }
+ const char *proxy_username = conf_get_str(clientconf, CONF_proxy_username);
+ if (*proxy_username)
+ conf_set_str(sp->conf, CONF_username, proxy_username);
+
+ const char *proxy_password = conf_get_str(clientconf, CONF_proxy_password);
+ if (*proxy_password) {
+ sp->proxy_password = dupstr(proxy_password);
+ sp->got_proxy_password = true;
+ }
+
+ const struct BackendVtable *backvt = backend_vt_from_proto(
+ conf_get_int(sp->conf, CONF_protocol));
+
+ /*
+ * We don't actually need an _SSH_ session specifically: it's also
+ * OK to use PROT_SSHCONN, because really, the criterion is
+ * whether setting CONF_ssh_nc_host will do anything useful. So
+ * our check is for whether the backend sets the flag promising
+ * that it does.
+ */
+ if (!backvt || !(backvt->flags & BACKEND_SUPPORTS_NC_HOST)) {
+ sp->errmsg = dupprintf("saved session '%s' is not an SSH session",
+ proxy_hostname);
+ return &sp->sock;
+ }
+
+ /*
+ * We also expect that the backend will announce a willingness to
+ * notify us that the session has started. Any backend providing
+ * NC_HOST should also provide this.
+ */
+ assert(backvt->flags & BACKEND_NOTIFIES_SESSION_START &&
+ "Backend provides NC_HOST without SESSION_START!");
+
+ /*
+ * Turn off SSH features we definitely don't want. It would be
+ * awkward and counterintuitive to have the proxy SSH connection
+ * become a connection-sharing upstream (but it's fine to have it
+ * be a downstream, if that's configured). And we don't want to
+ * open X forwardings, agent forwardings or (other) port
+ * forwardings as a side effect of this one operation.
+ */
+ conf_set_bool(sp->conf, CONF_ssh_connection_sharing_upstream, false);
+ conf_set_bool(sp->conf, CONF_x11_forward, false);
+ conf_set_bool(sp->conf, CONF_agentfwd, false);
+ for (const char *subkey;
+ (subkey = conf_get_str_nthstrkey(sp->conf, CONF_portfwd, 0)) != NULL;)
+ conf_del_str_str(sp->conf, CONF_portfwd, subkey);
+
+ /*
+ * We'll only be running one channel through this connection
+ * (since we've just turned off all the other things we might have
+ * done with it), so we can configure it as simple.
+ */
+ conf_set_bool(sp->conf, CONF_ssh_simple, true);
+
+ int proxy_type = conf_get_int(clientconf, CONF_proxy_type);
+ switch (proxy_type) {
+ case PROXY_SSH_TCPIP:
+ /*
+ * Configure the main channel of this SSH session to be a
+ * direct-tcpip connection to the destination host/port.
+ */
+ conf_set_str(sp->conf, CONF_ssh_nc_host, hostname);
+ conf_set_int(sp->conf, CONF_ssh_nc_port, port);
+ break;
+
+ case PROXY_SSH_SUBSYSTEM:
+ case PROXY_SSH_EXEC: {
+ Conf *cmd_conf = conf_copy(clientconf);
+
+ /*
+ * Unlike the Telnet and Local proxy types, we don't use the
+ * proxy username and password fields in the formatted
+ * command, because if we use them at all, it's for
+ * authenticating to the proxy SSH server.
+ */
+ conf_set_str(cmd_conf, CONF_proxy_username, "");
+ conf_set_str(cmd_conf, CONF_proxy_password, "");
+
+ char *cmd = format_telnet_command(sp->addr, sp->port, cmd_conf, NULL);
+ conf_free(cmd_conf);
+
+ conf_set_str(sp->conf, CONF_remote_cmd, cmd);
+ sfree(cmd);
+
+ conf_set_bool(sp->conf, CONF_nopty, true);
+
+ if (proxy_type == PROXY_SSH_SUBSYSTEM)
+ conf_set_bool(sp->conf, CONF_ssh_subsys, true);
+
+ break;
+ }
+
+ default:
+ unreachable("bad SSH proxy type");
+ }
+
+ /*
+ * Do the usual normalisation of things in the Conf like a "user@"
+ * prefix on the hostname field.
+ */
+ prepare_session(sp->conf);
+
+ sp->logctx = log_init(&sp->logpolicy, sp->conf);
+
+ char *error, *realhost;
+ error = backend_init(backvt, &sp->seat, &sp->backend, sp->logctx, sp->conf,
+ conf_get_str(sp->conf, CONF_host),
+ conf_get_int(sp->conf, CONF_port),
+ &realhost, nodelay,
+ conf_get_bool(sp->conf, CONF_tcp_keepalives));
+ if (error) {
+ sp->errmsg = dupprintf("unable to open SSH proxy connection: %s",
+ error);
+ return &sp->sock;
+ }
+
+ sfree(realhost);
+
+ /*
+ * If we've been given an Interactor by the caller, set ourselves
+ * up to work with it.
+ */
+ if (clientitr) {
+ sp->clientitr = clientitr;
+ interactor_set_child(sp->clientitr, sp->backend->interactor);
+
+ sp->clientlp = interactor_logpolicy(clientitr);
+
+ /*
+ * We can only borrow the client's Seat if our own backend
+ * will tell us when to give it back. (SSH-based backends
+ * _should_ do that, but we check the flag here anyway.)
+ */
+ if (backvt->flags & BACKEND_NOTIFIES_SESSION_START)
+ sp->clientseat = interactor_borrow_seat(clientitr);
+ }
+
+ return &sp->sock;
+}
diff --git a/proxy/telnet.c b/proxy/telnet.c
new file mode 100644
index 00000000..a2efb7b4
--- /dev/null
+++ b/proxy/telnet.c
@@ -0,0 +1,368 @@
+/*
+ * "Telnet" proxy negotiation.
+ *
+ * (This is for ad-hoc proxies where you connect to the proxy's
+ * telnet port and send a command such as `connect host port'. The
+ * command is configurable, since this proxy type is typically not
+ * standardised or at all well-defined.)
+ */
+
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+#include "sshcr.h"
+
+char *format_telnet_command(SockAddr *addr, int port, Conf *conf,
+ unsigned *flags_out)
+{
+ char *fmt = conf_get_str(conf, CONF_proxy_telnet_command);
+ int so = 0, eo = 0;
+ strbuf *buf = strbuf_new();
+ unsigned flags = 0;
+
+ /* we need to escape \\, \%, \r, \n, \t, \x??, \0???,
+ * %%, %host, %port, %user, and %pass
+ */
+
+ while (fmt[eo] != 0) {
+
+ /* scan forward until we hit end-of-line,
+ * or an escape character (\ or %) */
+ while (fmt[eo] != 0 && fmt[eo] != '%' && fmt[eo] != '\\')
+ eo++;
+
+ /* if we hit eol, break out of our escaping loop */
+ if (fmt[eo] == 0) break;
+
+ /* if there was any unescaped text before the escape
+ * character, send that now */
+ if (eo != so)
+ put_data(buf, fmt + so, eo - so);
+
+ so = eo++;
+
+ /* if the escape character was the last character of
+ * the line, we'll just stop and send it. */
+ if (fmt[eo] == 0) break;
+
+ if (fmt[so] == '\\') {
+
+ /* we recognize \\, \%, \r, \n, \t, \x??.
+ * anything else, we just send unescaped (including the \).
+ */
+
+ switch (fmt[eo]) {
+
+ case '\\':
+ put_byte(buf, '\\');
+ eo++;
+ break;
+
+ case '%':
+ put_byte(buf, '%');
+ eo++;
+ break;
+
+ case 'r':
+ put_byte(buf, '\r');
+ eo++;
+ break;
+
+ case 'n':
+ put_byte(buf, '\n');
+ eo++;
+ break;
+
+ case 't':
+ put_byte(buf, '\t');
+ eo++;
+ break;
+
+ case 'x':
+ case 'X': {
+ /* escaped hexadecimal value (ie. \xff) */
+ unsigned char v = 0;
+ int i = 0;
+
+ for (;;) {
+ eo++;
+ if (fmt[eo] >= '0' && fmt[eo] <= '9')
+ v += fmt[eo] - '0';
+ else if (fmt[eo] >= 'a' && fmt[eo] <= 'f')
+ v += fmt[eo] - 'a' + 10;
+ else if (fmt[eo] >= 'A' && fmt[eo] <= 'F')
+ v += fmt[eo] - 'A' + 10;
+ else {
+ /* non hex character, so we abort and just
+ * send the whole thing unescaped (including \x)
+ */
+ put_byte(buf, '\\');
+ eo = so + 1;
+ break;
+ }
+
+ /* we only extract two hex characters */
+ if (i == 1) {
+ put_byte(buf, v);
+ eo++;
+ break;
+ }
+
+ i++;
+ v <<= 4;
+ }
+ break;
+ }
+
+ default:
+ put_data(buf, fmt + so, 2);
+ eo++;
+ break;
+ }
+ } else {
+
+ /* % escape. we recognize %%, %host, %port, %user, %pass.
+ * %proxyhost, %proxyport. Anything else we just send
+ * unescaped (including the %).
+ */
+
+ if (fmt[eo] == '%') {
+ put_byte(buf, '%');
+ eo++;
+ }
+ else if (strnicmp(fmt + eo, "host", 4) == 0) {
+ char dest[512];
+ sk_getaddr(addr, dest, lenof(dest));
+ put_data(buf, dest, strlen(dest));
+ eo += 4;
+ }
+ else if (strnicmp(fmt + eo, "port", 4) == 0) {
+ put_fmt(buf, "%d", port);
+ eo += 4;
+ }
+ else if (strnicmp(fmt + eo, "user", 4) == 0) {
+ const char *username = conf_get_str(conf, CONF_proxy_username);
+ put_data(buf, username, strlen(username));
+ eo += 4;
+ if (!*username)
+ flags |= TELNET_CMD_MISSING_USERNAME;
+ }
+ else if (strnicmp(fmt + eo, "pass", 4) == 0) {
+ const char *password = conf_get_str(conf, CONF_proxy_password);
+ put_data(buf, password, strlen(password));
+ eo += 4;
+ if (!*password)
+ flags |= TELNET_CMD_MISSING_PASSWORD;
+ }
+ else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) {
+ const char *host = conf_get_str(conf, CONF_proxy_host);
+ put_data(buf, host, strlen(host));
+ eo += 9;
+ }
+ else if (strnicmp(fmt + eo, "proxyport", 9) == 0) {
+ int port = conf_get_int(conf, CONF_proxy_port);
+ put_fmt(buf, "%d", port);
+ eo += 9;
+ }
+ else {
+ /* we don't escape this, so send the % now, and
+ * don't advance eo, so that we'll consider the
+ * text immediately following the % as unescaped.
+ */
+ put_byte(buf, '%');
+ }
+ }
+
+ /* resume scanning for additional escapes after this one. */
+ so = eo;
+ }
+
+ /* if there is any unescaped text at the end of the line, send it */
+ if (eo != so) {
+ put_data(buf, fmt + so, eo - so);
+ }
+
+ if (flags_out)
+ *flags_out = flags;
+ return strbuf_to_str(buf);
+}
+
+typedef struct TelnetProxyNegotiator {
+ int crLine;
+ Conf *conf;
+ char *formatted_cmd;
+ prompts_t *prompts;
+ int username_prompt_index, password_prompt_index;
+ ProxyNegotiator pn;
+} TelnetProxyNegotiator;
+
+static ProxyNegotiator *proxy_telnet_new(const ProxyNegotiatorVT *vt)
+{
+ TelnetProxyNegotiator *s = snew(TelnetProxyNegotiator);
+ memset(s, 0, sizeof(*s));
+ s->pn.vt = vt;
+ return &s->pn;
+}
+
+static void proxy_telnet_free(ProxyNegotiator *pn)
+{
+ TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn);
+ if (s->conf)
+ conf_free(s->conf);
+ if (s->prompts)
+ free_prompts(s->prompts);
+ burnstr(s->formatted_cmd);
+ delete_callbacks_for_context(s);
+ sfree(s);
+}
+
+static void proxy_telnet_process_queue_callback(void *vctx)
+{
+ TelnetProxyNegotiator *s = (TelnetProxyNegotiator *)vctx;
+ proxy_negotiator_process_queue(&s->pn);
+}
+
+static void proxy_telnet_process_queue(ProxyNegotiator *pn)
+{
+ TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn);
+
+ crBegin(s->crLine);
+
+ s->conf = conf_copy(pn->ps->conf);
+
+ /*
+ * Make an initial attempt to figure out the command we want, and
+ * see if it tried to include a username or password that we don't
+ * have.
+ */
+ {
+ unsigned flags;
+ s->formatted_cmd = format_telnet_command(
+ pn->ps->remote_addr, pn->ps->remote_port, s->conf, &flags);
+
+ if (pn->itr && (flags & (TELNET_CMD_MISSING_USERNAME |
+ TELNET_CMD_MISSING_PASSWORD))) {
+ burnstr(s->formatted_cmd);
+ s->formatted_cmd = NULL;
+
+ /*
+ * We're missing at least one of the two parts, and we
+ * have an Interactor we can use to prompt for them, so
+ * try it.
+ */
+ s->prompts = proxy_new_prompts(pn->ps);
+ s->prompts->to_server = true;
+ s->prompts->from_server = false;
+ s->prompts->name = dupstr("Telnet proxy authentication");
+ if (flags & TELNET_CMD_MISSING_USERNAME) {
+ s->username_prompt_index = s->prompts->n_prompts;
+ add_prompt(s->prompts, dupstr("Proxy username: "), true);
+ } else {
+ s->username_prompt_index = -1;
+ }
+ if (flags & TELNET_CMD_MISSING_PASSWORD) {
+ s->password_prompt_index = s->prompts->n_prompts;
+ add_prompt(s->prompts, dupstr("Proxy password: "), false);
+ } else {
+ s->password_prompt_index = -1;
+ }
+
+ /*
+ * This prompt is presented extremely early in PuTTY's
+ * setup. (Very promptly, you might say.)
+ *
+ * In particular, we can get here through a chain of
+ * synchronous calls from backend_init, which means (in
+ * GUI PuTTY) that the terminal we'll be sending this
+ * prompt to may not have its Ldisc set up yet (due to
+ * cyclic dependencies among all the things that have to
+ * be initialised).
+ *
+ * So we'll start by having ourself called back via a
+ * toplevel callback, to make sure we don't call
+ * seat_get_userpass_input until we've returned from
+ * backend_init and the frontend has finished getting
+ * everything ready.
+ */
+ queue_toplevel_callback(proxy_telnet_process_queue_callback, s);
+ crReturnV;
+
+ while (true) {
+ SeatPromptResult spr = seat_get_userpass_input(
+ interactor_announce(pn->itr), s->prompts);
+ if (spr.kind == SPRK_OK) {
+ break;
+ } else if (spr_is_abort(spr)) {
+ proxy_spr_abort(pn, spr);
+ crStopV;
+ }
+ crReturnV;
+ }
+
+ if (s->username_prompt_index != -1) {
+ conf_set_str(
+ s->conf, CONF_proxy_username,
+ prompt_get_result_ref(
+ s->prompts->prompts[s->username_prompt_index]));
+ }
+
+ if (s->password_prompt_index != -1) {
+ conf_set_str(
+ s->conf, CONF_proxy_password,
+ prompt_get_result_ref(
+ s->prompts->prompts[s->password_prompt_index]));
+ }
+
+ free_prompts(s->prompts);
+ s->prompts = NULL;
+ }
+
+ /*
+ * Now format the command a second time, with the results of
+ * those prompts written into s->conf.
+ */
+ s->formatted_cmd = format_telnet_command(
+ pn->ps->remote_addr, pn->ps->remote_port, s->conf, NULL);
+ }
+
+ /*
+ * Log the command, with some changes. Firstly, we regenerate it
+ * with the password masked; secondly, we escape control
+ * characters so that the log message is printable.
+ */
+ conf_set_str(s->conf, CONF_proxy_password, "*password*");
+ {
+ char *censored_cmd = format_telnet_command(
+ pn->ps->remote_addr, pn->ps->remote_port, s->conf, NULL);
+
+ strbuf *logmsg = strbuf_new();
+ put_datapl(logmsg, PTRLEN_LITERAL("Sending Telnet proxy command: "));
+ put_c_string_literal(logmsg, ptrlen_from_asciz(censored_cmd));
+
+ plug_log(pn->ps->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0);
+ strbuf_free(logmsg);
+ sfree(censored_cmd);
+ }
+
+ /*
+ * Actually send the command.
+ */
+ put_dataz(pn->output, s->formatted_cmd);
+
+ /*
+ * Unconditionally report success. We don't hang around waiting
+ * for error messages from the proxy, because this proxy type is
+ * so ad-hoc that we wouldn't know how to even recognise an error
+ * message if we saw one, let alone what to do about it.
+ */
+ pn->done = true;
+
+ crFinishV;
+}
+
+const struct ProxyNegotiatorVT telnet_proxy_negotiator_vt = {
+ .new = proxy_telnet_new,
+ .free = proxy_telnet_free,
+ .process_queue = proxy_telnet_process_queue,
+ .type = "Telnet",
+};
diff --git a/pscp.c b/pscp.c
index a11d1e18..77de1cd9 100644
--- a/pscp.c
+++ b/pscp.c
@@ -1,5 +1,5 @@
/*
- * scp.c - Scp (Secure Copy) client for PuTTY.
+ * pscp.c - Scp (Secure Copy) client for PuTTY.
* Joris van Rantwijk, Simon Tatham
*
* This is mainly based on ssh-1.2.26/scp.c by Timo Rinne & Tatu Ylonen.
@@ -22,7 +22,7 @@
#include "putty.h"
#include "psftp.h"
#include "ssh.h"
-#include "sftp.h"
+#include "ssh/sftp.h"
#include "storage.h"
static bool list = false;
@@ -49,8 +49,6 @@ static void source(const char *src);
static void rsource(const char *src);
static void sink(const char *targ, const char *src);
-const char *const appname = "PSCP";
-
/*
* The maximum amount of queued data we accept before we stop and
* wait for the server to process some.
@@ -58,29 +56,37 @@ const char *const appname = "PSCP";
#define MAX_SCP_BUFSIZE 16384
void ldisc_echoedit_update(Ldisc *ldisc) { }
+void ldisc_check_sendok(Ldisc *ldisc) { }
-static size_t pscp_output(Seat *, bool is_stderr, const void *, size_t);
+static size_t pscp_output(Seat *, SeatOutputType type, const void *, size_t);
static bool pscp_eof(Seat *);
static const SeatVtable pscp_seat_vt = {
.output = pscp_output,
.eof = pscp_eof,
+ .sent = nullseat_sent,
+ .banner = nullseat_banner_to_stderr,
.get_userpass_input = filexfer_get_userpass_input,
+ .notify_session_started = nullseat_notify_session_started,
.notify_remote_exit = nullseat_notify_remote_exit,
+ .notify_remote_disconnect = nullseat_notify_remote_disconnect,
.connection_fatal = console_connection_fatal,
.update_specials_menu = nullseat_update_specials_menu,
.get_ttymode = nullseat_get_ttymode,
.set_busy_status = nullseat_set_busy_status,
- .verify_ssh_host_key = console_verify_ssh_host_key,
+ .confirm_ssh_host_key = console_confirm_ssh_host_key,
.confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive,
.confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey,
+ .prompt_descriptions = console_prompt_descriptions,
.is_utf8 = nullseat_is_never_utf8,
.echoedit_update = nullseat_echoedit_update,
.get_x_display = nullseat_get_x_display,
.get_windowid = nullseat_get_windowid,
.get_window_pixel_size = nullseat_get_window_pixel_size,
.stripctrl_new = console_stripctrl_new,
- .set_trust_status = nullseat_set_trust_status_vacuously,
+ .set_trust_status = nullseat_set_trust_status,
+ .can_set_trust_status = nullseat_can_set_trust_status_yes,
+ .has_mixed_input_stream = nullseat_has_mixed_input_stream_no,
.verbose = cmdline_seat_verbose,
.interactive = nullseat_interactive_no,
.get_cursor_position = nullseat_get_cursor_position,
@@ -141,13 +147,14 @@ static PRINTF_LIKE(2, 3) void tell_user(FILE *stream, const char *fmt, ...)
static bufchain received_data;
static BinarySink *stderr_bs;
static size_t pscp_output(
- Seat *seat, bool is_stderr, const void *data, size_t len)
+ Seat *seat, SeatOutputType type, const void *data, size_t len)
{
/*
- * stderr data is just spouted to local stderr (optionally via a
- * sanitiser) and otherwise ignored.
+ * Non-stdout data (both stderr and SSH auth banners) is just
+ * spouted to local stderr (optionally via a sanitiser) and
+ * otherwise ignored.
*/
- if (is_stderr) {
+ if (type != SEAT_OUTPUT_STDOUT) {
put_data(stderr_bs, data, len);
return 0;
}
@@ -638,8 +645,8 @@ void scp_sftp_listdir(const char *dirname)
dirh = fxp_opendir_recv(pktin, req);
if (dirh == NULL) {
- tell_user(stderr, "Unable to open %s: %s\n", dirname, fxp_error());
- errs++;
+ 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();
@@ -848,7 +855,8 @@ int scp_send_filedata(char *data, int len)
scp_sftp_fileoffset += len;
return 0;
} else {
- int bufsize = backend_send(backend, data, len);
+ backend_send(backend, data, len);
+ int bufsize = backend_sendbuffer(backend);
/*
* If the network transfer is backing up - that is, the
@@ -1806,7 +1814,7 @@ static void sink(const char *targ, const char *src)
striptarget = stripslashes(act.name, true);
if (striptarget != act.name) {
with_stripctrl(sanname, act.name) {
- with_stripctrl(santarg, act.name) {
+ with_stripctrl(santarg, striptarget) {
tell_user(stderr, "warning: remote host sent a"
" compound pathname '%s'", sanname);
tell_user(stderr, " renaming local"
@@ -2181,8 +2189,7 @@ 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] 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");
@@ -2194,7 +2201,7 @@ static void usage(void)
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(" -pwfile file login with password read from specified file\n");
printf(" -1 -2 force use of particular SSH protocol version\n");
printf(" -ssh -ssh-connection\n");
printf(" force use of particular SSH protocol variant\n");
diff --git a/psftp.c b/psftp.c
index c4b5374b..d8b5c400 100644
--- a/psftp.c
+++ b/psftp.c
@@ -12,9 +12,7 @@
#include "psftp.h"
#include "storage.h"
#include "ssh.h"
-#include "sftp.h"
-
-const char *const appname = "PSFTP";
+#include "ssh/sftp.h"
/*
* Since SFTP is a request-response oriented protocol, it requires
@@ -41,28 +39,35 @@ static bool sent_eof = false;
* Seat vtable.
*/
-static size_t psftp_output(Seat *, bool is_stderr, const void *, size_t);
+static size_t psftp_output(Seat *, SeatOutputType type, const void *, size_t);
static bool psftp_eof(Seat *);
static const SeatVtable psftp_seat_vt = {
.output = psftp_output,
.eof = psftp_eof,
+ .sent = nullseat_sent,
+ .banner = nullseat_banner_to_stderr,
.get_userpass_input = filexfer_get_userpass_input,
+ .notify_session_started = nullseat_notify_session_started,
.notify_remote_exit = nullseat_notify_remote_exit,
+ .notify_remote_disconnect = nullseat_notify_remote_disconnect,
.connection_fatal = console_connection_fatal,
.update_specials_menu = nullseat_update_specials_menu,
.get_ttymode = nullseat_get_ttymode,
.set_busy_status = nullseat_set_busy_status,
- .verify_ssh_host_key = console_verify_ssh_host_key,
+ .confirm_ssh_host_key = console_confirm_ssh_host_key,
.confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive,
.confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey,
+ .prompt_descriptions = console_prompt_descriptions,
.is_utf8 = nullseat_is_never_utf8,
.echoedit_update = nullseat_echoedit_update,
.get_x_display = nullseat_get_x_display,
.get_windowid = nullseat_get_windowid,
.get_window_pixel_size = nullseat_get_window_pixel_size,
.stripctrl_new = console_stripctrl_new,
- .set_trust_status = nullseat_set_trust_status_vacuously,
+ .set_trust_status = nullseat_set_trust_status,
+ .can_set_trust_status = nullseat_can_set_trust_status_yes,
+ .has_mixed_input_stream = nullseat_has_mixed_input_stream_no,
.verbose = cmdline_seat_verbose,
.interactive = nullseat_interactive_yes,
.get_cursor_position = nullseat_get_cursor_position,
@@ -2444,6 +2449,7 @@ int do_sftp(int mode, int modeflags, char *batchfile)
static bool verbose = false;
void ldisc_echoedit_update(Ldisc *ldisc) { }
+void ldisc_check_sendok(Ldisc *ldisc) { }
/*
* Receive a block of data from the SSH link. Block until all data
@@ -2456,13 +2462,14 @@ void ldisc_echoedit_update(Ldisc *ldisc) { }
static bufchain received_data;
static BinarySink *stderr_bs;
static size_t psftp_output(
- Seat *seat, bool is_stderr, const void *data, size_t len)
+ Seat *seat, SeatOutputType type, const void *data, size_t len)
{
/*
- * stderr data is just spouted to local stderr (optionally via a
- * sanitiser) and otherwise ignored.
+ * Non-stdout data (both stderr and SSH auth banners) is just
+ * spouted to local stderr (optionally via a sanitiser) and
+ * otherwise ignored.
*/
- if (is_stderr) {
+ if (type != SEAT_OUTPUT_STDOUT) {
put_data(stderr_bs, data, len);
return 0;
}
@@ -2529,7 +2536,7 @@ static void usage(void)
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(" -pwfile file login with password read from specified file\n");
printf(" -1 -2 force use of particular SSH protocol version\n");
printf(" -ssh -ssh-connection\n");
printf(" force use of particular SSH protocol variant\n");
@@ -2558,10 +2565,10 @@ static void usage(void)
static void version(void)
{
- char *buildinfo_text = buildinfo("\n");
- printf("psftp: %s\n%s\n", ver, buildinfo_text);
- sfree(buildinfo_text);
- exit(0);
+ char *buildinfo_text = buildinfo("\n");
+ printf("psftp: %s\n%s\n", ver, buildinfo_text);
+ sfree(buildinfo_text);
+ exit(0);
}
/*
@@ -2783,7 +2790,7 @@ const unsigned cmdline_tooltype = TOOLTYPE_FILETRANSFER;
*/
int psftp_main(int argc, char *argv[])
{
- int i, ret;
+ int i, toret;
int portnumber = 0;
char *userhost, *user;
int mode = 0;
@@ -2800,7 +2807,7 @@ int psftp_main(int argc, char *argv[])
do_defaults(NULL, conf);
for (i = 1; i < argc; i++) {
- int ret;
+ int retd;
if (argv[i][0] != '-') {
if (userhost)
usage();
@@ -2808,12 +2815,13 @@ int psftp_main(int argc, char *argv[])
userhost = dupstr(argv[i]);
continue;
}
- ret = cmdline_process_param(argv[i], i+1<argc?argv[i+1]:NULL, 1, conf);
- if (ret == -2) {
+ retd = cmdline_process_param(
+ argv[i], i+1 < argc ? argv[i+1] : NULL, 1, conf);
+ if (retd == -2) {
cmdline_error("option \"%s\" requires an argument", argv[i]);
- } else if (ret == 2) {
+ } else if (retd == 2) {
i++; /* skip next argument */
- } else if (ret == 1) {
+ } else if (retd == 1) {
/* We have our own verbosity in addition to `flags'. */
if (cmdline_verbose())
verbose = true;
@@ -2874,10 +2882,10 @@ int psftp_main(int argc, char *argv[])
* it now.
*/
if (userhost) {
- int ret;
- ret = psftp_connect(userhost, user, portnumber);
+ int retd;
+ retd = psftp_connect(userhost, user, portnumber);
sfree(userhost);
- if (ret)
+ if (retd)
return 1;
if (do_sftp_init())
return 1;
@@ -2886,7 +2894,7 @@ int psftp_main(int argc, char *argv[])
" to connect\n");
}
- ret = do_sftp(mode, modeflags, batchfile);
+ toret = do_sftp(mode, modeflags, batchfile);
if (backend && backend_connected(backend)) {
char ch;
@@ -2905,5 +2913,5 @@ int psftp_main(int argc, char *argv[])
if (psftp_logctx)
log_free(psftp_logctx);
- return ret;
+ return toret;
}
diff --git a/psftpcommon.c b/psftpcommon.c
index 21f3737e..e6c8e2d8 100644
--- a/psftpcommon.c
+++ b/psftpcommon.c
@@ -8,7 +8,7 @@
#include <string.h>
#include "putty.h"
-#include "sftp.h"
+#include "ssh/sftp.h"
#include "psftp.h"
#define MAX_NAMES_MEMORY ((size_t)8 << 20)
diff --git a/psocks.c b/psocks.c
index f29eeaa9..eb7c8f01 100644
--- a/psocks.c
+++ b/psocks.c
@@ -7,9 +7,10 @@
#include <errno.h>
#include "putty.h"
+#include "storage.h"
#include "misc.h"
#include "ssh.h"
-#include "sshchan.h"
+#include "ssh/channel.h"
#include "psocks.h"
/*
@@ -94,8 +95,7 @@ static const SshChannelVtable psocks_scvt = {
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_closing(Plug *p, PlugCloseType, const char *error_msg);
static void psocks_plug_receive(Plug *p, int urgent,
const char *data, size_t len);
static void psocks_plug_sent(Plug *p, size_t bufsize);
@@ -354,8 +354,8 @@ static void psocks_plug_log(Plug *plug, PlugLogType type, SockAddr *addr,
};
}
-static void psocks_plug_closing(Plug *plug, const char *error_msg,
- int error_code, bool calling_back)
+static void psocks_plug_closing(Plug *plug, PlugCloseType type,
+ const char *error_msg)
{
psocks_connection *conn = container_of(plug, psocks_connection, plug);
if (conn->connecting) {
@@ -523,8 +523,8 @@ void psocks_start(psocks_state *ps)
* 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)
+int check_stored_host_key(const char *hostname, int port,
+ const char *keytype, const char *key)
{
unreachable("host keys not handled in this tool");
}
diff --git a/putty.h b/putty.h
index 693a6a0e..bfb705c2 100644
--- a/putty.h
+++ b/putty.h
@@ -5,7 +5,7 @@
#include <limits.h> /* for INT_MAX */
#include "defs.h"
-#include "puttyps.h"
+#include "platform.h"
#include "network.h"
#include "misc.h"
#include "marshal.h"
@@ -21,14 +21,14 @@
* 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_MASTER_KEY_YEAR "2021"
+#define PGP_MASTER_KEY_DETAILS "RSA, 3072-bit"
+#define PGP_MASTER_KEY_FP \
+ "A872 D42F 1660 890F 0E05 223E DD43 55EA AC11 19DE"
+#define PGP_PREV_MASTER_KEY_YEAR "2018"
#define PGP_PREV_MASTER_KEY_DETAILS "RSA, 4096-bit"
#define PGP_PREV_MASTER_KEY_FP \
- "440D E3B5 B7A1 CA85 B3CC 1718 AB58 5DC6 0467 6F7C"
+ "24E1 B1C5 75EA 3C9F F752 A922 76BC 7FE4 EBFD 2D9E"
/*
* Definitions of three separate indexing schemes for colour palette
@@ -266,7 +266,6 @@ struct sesslist {
};
struct unicode_data {
- char **uni_tbl;
bool dbcs_screenfont;
int font_codepage;
int line_codepage;
@@ -326,13 +325,13 @@ typedef enum {
/*
* Send a POSIX-style signal. (Useful in SSH and also pterm.)
*
- * We use the master list in sshsignals.h to define these enum
+ * We use the master list in ssh/signal-list.h to define these enum
* values, which will come out looking like names of the form
* SS_SIGABRT, SS_SIGINT etc.
*/
#define SIGNAL_MAIN(name, text) SS_SIG ## name,
#define SIGNAL_SUB(name) SS_SIG ## name,
- #include "sshsignals.h"
+ #include "ssh/signal-list.h"
#undef SIGNAL_MAIN
#undef SIGNAL_SUB
@@ -356,7 +355,7 @@ struct SessionSpecial {
int arg;
};
-/* Needed by both sshchan.h and sshppl.h */
+/* Needed by both ssh/channel.h and ssh/ppl.h */
typedef void (*add_special_fn_t)(
void *ctx, const char *text, SessionSpecialCode code, int arg);
@@ -423,9 +422,14 @@ enum {
KEX_WARN,
KEX_DHGROUP1,
KEX_DHGROUP14,
+ KEX_DHGROUP15,
+ KEX_DHGROUP16,
+ KEX_DHGROUP17,
+ KEX_DHGROUP18,
KEX_DHGEX,
KEX_RSA,
KEX_ECDH,
+ KEX_NTRU_HYBRID,
KEX_MAX
};
@@ -453,6 +457,7 @@ enum {
CIPHER_DES,
CIPHER_ARCFOUR,
CIPHER_CHACHA20,
+ CIPHER_AESGCM,
CIPHER_MAX /* no. ciphers (inc warn) */
};
@@ -474,7 +479,9 @@ enum {
* Proxy types.
*/
PROXY_NONE, PROXY_SOCKS4, PROXY_SOCKS5,
- PROXY_HTTP, PROXY_TELNET, PROXY_CMD, PROXY_FUZZ
+ PROXY_HTTP, PROXY_TELNET, PROXY_CMD, PROXY_SSH_TCPIP,
+ PROXY_SSH_EXEC, PROXY_SSH_SUBSYSTEM,
+ PROXY_FUZZ
};
enum {
@@ -529,7 +536,14 @@ enum {
FUNKY_XTERM,
FUNKY_VT400,
FUNKY_VT100P,
- FUNKY_SCO
+ FUNKY_SCO,
+ FUNKY_XTERM_216
+};
+
+enum {
+ /* Shifted arrow key types (CONF_sharrow_type) */
+ SHARROW_APPLICATION, /* Ctrl flips between ESC O A and ESC [ A */
+ SHARROW_BITMAP /* ESC [ 1 ; n A, where n = 1 + bitmap of CAS */
};
enum {
@@ -617,9 +631,123 @@ enum {
#define BACKEND_RESIZE_FORBIDDEN 0x01 /* Backend does not allow
resizing terminal */
#define BACKEND_NEEDS_TERMINAL 0x02 /* Backend must have terminal */
+#define BACKEND_SUPPORTS_NC_HOST 0x04 /* Backend can honour
+ CONF_ssh_nc_host */
+#define BACKEND_NOTIFIES_SESSION_START 0x08 /* Backend will call
+ seat_notify_session_started */
+
+/* In (no)sshproxy.c */
+extern const bool ssh_proxy_supported;
+
+/*
+ * This structure type wraps a Seat pointer, in a way that has no
+ * purpose except to be a different type.
+ *
+ * The Seat wrapper functions that present interactive prompts all
+ * expect one of these in place of their ordinary Seat pointer. You
+ * get one by calling interactor_announce (defined below), which will
+ * print a message (if not already done) identifying the Interactor
+ * that originated the prompt.
+ *
+ * This arranges that the C type system itself will check that no call
+ * to any of those Seat methods has omitted the mandatory call to
+ * interactor_announce beforehand.
+ */
+struct InteractionReadySeat {
+ Seat *seat;
+};
+
+/*
+ * The Interactor trait is implemented by anything that is capable of
+ * presenting interactive prompts or questions to the user during
+ * network connection setup. Every Backend that ever needs to do this
+ * is an Interactor, but also, while a Backend is making its initial
+ * network connection, it may go via network proxy code which is also
+ * an Interactor and can ask questions of its own.
+ */
+struct Interactor {
+ const InteractorVtable *vt;
+
+ /* The parent Interactor that we are a proxy for, if any. */
+ Interactor *parent;
+
+ /*
+ * If we're the top-level Interactor (parent==NULL), then this
+ * field records the last Interactor that actually did anything
+ * interactive, so that we know when to announce a changeover
+ * between levels of proxying.
+ *
+ * If parent != NULL, this field is not used.
+ */
+ Interactor *last_to_talk;
+};
+
+struct InteractorVtable {
+ /*
+ * Returns a user-facing description of the nature of the network
+ * connection being made. Used in interactive proxy authentication
+ * to announce which connection attempt is now in control of the
+ * Seat.
+ *
+ * The idea is not just to be written in natural language, but to
+ * connect with the user's idea of _why_ they think some
+ * connection is being made. For example, instead of saying 'TCP
+ * connection to 123.45.67.89 port 22', you might say 'SSH
+ * connection to [logical host name for SSH host key purposes]'.
+ *
+ * The returned string must be freed by the caller.
+ */
+ char *(*description)(Interactor *itr);
+
+ /*
+ * Returns the LogPolicy associated with this Interactor. (A
+ * Backend can derive this from its logging context; a proxy
+ * Interactor inherits it from the Interactor for the parent
+ * network connection.)
+ */
+ LogPolicy *(*logpolicy)(Interactor *itr);
+
+ /*
+ * Gets and sets the Seat that this Interactor talks to. When a
+ * Seat is borrowed and replaced with a TempSeat, this will be the
+ * mechanism by which that replacement happens.
+ */
+ Seat *(*get_seat)(Interactor *itr);
+ void (*set_seat)(Interactor *itr, Seat *seat);
+};
+
+static inline char *interactor_description(Interactor *itr)
+{ return itr->vt->description(itr); }
+static inline LogPolicy *interactor_logpolicy(Interactor *itr)
+{ return itr->vt->logpolicy(itr); }
+static inline Seat *interactor_get_seat(Interactor *itr)
+{ return itr->vt->get_seat(itr); }
+static inline void interactor_set_seat(Interactor *itr, Seat *seat)
+{ itr->vt->set_seat(itr, seat); }
+
+static inline void interactor_set_child(Interactor *parent, Interactor *child)
+{ child->parent = parent; }
+Seat *interactor_borrow_seat(Interactor *itr);
+void interactor_return_seat(Interactor *itr);
+InteractionReadySeat interactor_announce(Interactor *itr);
+
+/* Interactors that are Backends will find this helper function useful
+ * in constructing their description strings */
+char *default_description(const BackendVtable *backvt,
+ const char *host, int port);
+
+/*
+ * The Backend trait is the top-level one that governs each of the
+ * user-facing main modes that PuTTY can use to talk to some
+ * destination: SSH, Telnet, serial port, pty, etc.
+ */
struct Backend {
const BackendVtable *vt;
+
+ /* Many Backends are also Interactors. If this one is, a pointer
+ * to its Interactor trait lives here. */
+ Interactor *interactor;
};
struct BackendVtable {
char *(*init) (const BackendVtable *vt, Seat *seat,
@@ -630,9 +758,8 @@ struct BackendVtable {
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 */
+ void (*send) (Backend *be, const char *buf, size_t len);
+ /* sendbuffer() returns the current amount of buffered data */
size_t (*sendbuffer) (Backend *be);
void (*size) (Backend *be, int width, int height);
void (*special) (Backend *be, SessionSpecialCode code, int arg);
@@ -641,7 +768,14 @@ struct BackendVtable {
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). */
+ * possible (passing back-pressure on to its sender).
+ *
+ * Policy rule: no backend shall return true from sendok() while
+ * its network connection attempt is still ongoing. This ensures
+ * that if making the network connection involves a proxy type
+ * which wants to interact with the user via the terminal, the
+ * proxy implementation and the backend itself won't fight over
+ * who gets the terminal input. */
bool (*sendok) (Backend *be);
bool (*ldisc_option_state) (Backend *be, int);
void (*provide_ldisc) (Backend *be, Ldisc *ldisc);
@@ -660,9 +794,10 @@ struct BackendVtable {
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;
+ * saved-session storage. 'displayname_tc' and 'displayname_lc'
+ * are human-readable names, one in title-case for config boxes,
+ * and one in lower-case for use in mid-sentence. */
+ const char *id, *displayname_tc, *displayname_lc;
int protocol;
int default_port;
@@ -681,8 +816,8 @@ 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 void backend_send(Backend *be, const char *buf, size_t len)
+{ be->vt->send(be, buf, len); }
static inline size_t backend_sendbuffer(Backend *be)
{ return be->vt->sendbuffer(be); }
static inline void backend_size(Backend *be, int width, int height)
@@ -733,6 +868,87 @@ int hwnd_parent;
#endif // PUTTYNG
/*
+ * Used by callback.c; declared up here so that prompts_t can use it
+ */
+typedef void (*toplevel_callback_fn_t)(void *ctx);
+
+/* Enum of result types in SeatPromptResult below */
+typedef enum SeatPromptResultKind {
+ /* Answer not yet available at all; either try again later or wait
+ * for a callback (depending on the request's API) */
+ SPRK_INCOMPLETE,
+
+ /* We're abandoning the connection because the user interactively
+ * told us to. (Hence, no need to present an error message
+ * telling the user we're doing that: they already know.) */
+ SPRK_USER_ABORT,
+
+ /* We're abandoning the connection for some other reason (e.g. we
+ * were unable to present the prompt at all, or a batch-mode
+ * configuration told us to give the answer no). This may
+ * ultimately have stemmed from some user configuration, but they
+ * didn't _tell us right now_ to abandon this connection, so we
+ * still need to inform them that we've done so. */
+ SPRK_SW_ABORT,
+
+ /* We're proceeding with the connection and have all requested
+ * information (if any) */
+ SPRK_OK
+} SeatPromptResultKind;
+
+/* Small struct to present the results of interactive requests from
+ * backend to Seat (see below) */
+struct SeatPromptResult {
+ SeatPromptResultKind kind;
+
+ /*
+ * In the case of SPRK_SW_ABORT, the frontend provides an error
+ * message to present to the user. But dynamically allocating it
+ * up front would mean having to make sure it got freed at any
+ * call site where one of these structs is received (and freed
+ * _once_ no matter how many times the struct is copied). So
+ * instead we provide a function that will generate the error
+ * message into a BinarySink.
+ */
+ void (*errfn)(SeatPromptResult, BinarySink *);
+
+ /*
+ * And some fields the error function can use to construct the
+ * message (holding, e.g. an OS error code).
+ */
+ const char *errdata_lit; /* statically allocated, e.g. a string literal */
+ unsigned errdata_u;
+};
+
+/* Helper function to construct the simple versions of these
+ * structures inline */
+static inline SeatPromptResult make_spr_simple(SeatPromptResultKind kind)
+{
+ SeatPromptResult spr;
+ spr.kind = kind;
+ spr.errdata_lit = NULL;
+ return spr;
+}
+
+/* Most common constructor function for SPRK_SW_ABORT errors */
+SeatPromptResult make_spr_sw_abort_static(const char *);
+
+/* Convenience macros wrapping those constructors in turn */
+#define SPR_INCOMPLETE make_spr_simple(SPRK_INCOMPLETE)
+#define SPR_USER_ABORT make_spr_simple(SPRK_USER_ABORT)
+#define SPR_SW_ABORT(lit) make_spr_sw_abort_static(lit)
+#define SPR_OK make_spr_simple(SPRK_OK)
+
+/* Query function that folds both kinds of abort together */
+static inline bool spr_is_abort(SeatPromptResult spr)
+{
+ return spr.kind == SPRK_USER_ABORT || spr.kind == SPRK_SW_ABORT;
+}
+
+/* Function to return a dynamically allocated copy of the error message */
+char *spr_get_error_message(SeatPromptResult spr);
+
+/*
* Mechanism for getting text strings such as usernames and passwords
* from the front-end.
* The fields are mostly modelled after SSH's keyboard-interactive auth.
@@ -753,7 +969,8 @@ typedef struct {
bool echo;
strbuf *result;
} prompt_t;
-typedef struct {
+typedef struct prompts_t prompts_t;
+struct prompts_t {
/*
* Indicates whether the information entered is to be used locally
* (for instance a key passphrase prompt), or is destined for the wire.
@@ -781,7 +998,25 @@ typedef struct {
prompt_t **prompts;
void *data; /* slot for housekeeping data, managed by
* seat_get_userpass_input(); initially NULL */
-} prompts_t;
+ SeatPromptResult spr; /* some implementations need to cache one of these */
+
+ /*
+ * Callback you can fill in to be notified when all the prompts'
+ * responses are available. After you receive this notification, a
+ * further call to the get_userpass_input function will return the
+ * final state of the prompts system, which is guaranteed not to
+ * be negative for 'still ongoing'.
+ */
+ toplevel_callback_fn_t callback;
+ void *callback_ctx;
+
+ /*
+ * When this prompts_t is known to an Ldisc, we might need to
+ * break the connection if things get freed in an emergency. So
+ * this is a pointer to the Ldisc's pointer to us.
+ */
+ prompts_t **ldisc_ptr_to_us;
+};
prompts_t *new_prompts(void);
void add_prompt(prompts_t *p, char *promptstr, bool echo);
void prompt_set_result(prompt_t *pr, const char *newstr);
@@ -856,6 +1091,28 @@ typedef enum SeatInteractionContext {
SIC_BANNER, SIC_KI_PROMPTS
} SeatInteractionContext;
+typedef enum SeatOutputType {
+ SEAT_OUTPUT_STDOUT, SEAT_OUTPUT_STDERR
+} SeatOutputType;
+
+typedef enum SeatDialogTextType {
+ SDT_PARA, SDT_DISPLAY, SDT_SCARY_HEADING,
+ SDT_TITLE, SDT_PROMPT, SDT_BATCH_ABORT,
+ SDT_MORE_INFO_KEY, SDT_MORE_INFO_VALUE_SHORT, SDT_MORE_INFO_VALUE_BLOB
+} SeatDialogTextType;
+struct SeatDialogTextItem {
+ SeatDialogTextType type;
+ char *text;
+};
+struct SeatDialogText {
+ size_t nitems, itemsize;
+ SeatDialogTextItem *items;
+};
+SeatDialogText *seat_dialog_text_new(void);
+void seat_dialog_text_free(SeatDialogText *sdt);
+PRINTF_LIKE(3, 4) void seat_dialog_text_append(
+ SeatDialogText *sdt, SeatDialogTextType type, const char *fmt, ...);
+
/*
* Data type 'Seat', which is an API intended to contain essentially
* everything that a back end might need to talk to its client for:
@@ -868,14 +1125,16 @@ struct Seat {
};
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.
+ * Provide output from the remote session. 'type' indicates the
+ * type of the output (stdout or stderr), which can be used to
+ * split the output into separate message channels, if the seat
+ * wants to handle them differently. But combining the channels
+ * into one is OK too; that's what terminal-window based seats do.
*
* The return value is the current size of the output backlog.
*/
- size_t (*output)(Seat *seat, bool is_stderr, const void *data, size_t len);
+ size_t (*output)(Seat *seat, SeatOutputType type,
+ const void *data, size_t len);
/*
* Called when the back end wants to indicate that EOF has arrived
@@ -886,40 +1145,49 @@ struct SeatVtable {
bool (*eof)(Seat *seat);
/*
+ * Called by the back end to notify that the output backlog has
+ * changed size. A front end in control of the event loop won't
+ * necessarily need this (they can just keep checking it via
+ * backend_sendbuffer at every opportunity), but one buried in the
+ * depths of something else (like an SSH proxy) will need to be
+ * proactively notified that the amount of buffered data has
+ * become smaller.
+ */
+ void (*sent)(Seat *seat, size_t new_sendbuffer);
+
+ /*
+ * Provide authentication-banner output from the session setup.
+ * End-user Seats can treat this as very similar to 'output', but
+ * intermediate Seats in complex proxying situations will want to
+ * implement this and 'output' differently.
+ */
+ size_t (*banner)(Seat *seat, const void *data, size_t len);
+
+ /*
* Try to get answers from a set of interactive login prompts. The
- * prompts are provided in 'p'; 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.
+ * prompts are provided in 'p'.
*
- * (FIXME: it would be nice to distinguish two classes of cancel
- * action, so the user could specify 'I want to abandon this
+ * (FIXME: it would be nice to distinguish two classes of user-
+ * abort action, so the user could specify 'I want to abandon this
* entire attempt to start a session' or the milder 'I want to
* abandon this particular form of authentication and fall back to
* a different one' - e.g. if you turn out not to be able to
* remember your private key passphrase then perhaps you'd rather
* fall back to password auth rather than aborting the whole
* session.)
+ */
+ SeatPromptResult (*get_userpass_input)(Seat *seat, prompts_t *p);
+
+ /*
+ * Notify the seat that the main session channel has been
+ * successfully set up.
*
- * (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.)
+ * This is only used as part of the SSH proxying system, so it's
+ * not necessary to implement it in all backends. A backend must
+ * call this if it advertises the BACKEND_NOTIFIES_SESSION_START
+ * flag, and otherwise, doesn't have to.
*/
- int (*get_userpass_input)(Seat *seat, prompts_t *p, bufchain *input);
+ void (*notify_session_started)(Seat *seat);
/*
* Notify the seat that the process running at the other end of
@@ -928,6 +1196,29 @@ struct SeatVtable {
void (*notify_remote_exit)(Seat *seat);
/*
+ * Notify the seat that the whole connection has finished.
+ * (Distinct from notify_remote_exit, e.g. in the case where you
+ * have port forwardings still active when the main foreground
+ * session goes away: then you'd get notify_remote_exit when the
+ * foreground session dies, but notify_remote_disconnect when the
+ * last forwarding vanishes and the network connection actually
+ * closes.)
+ *
+ * This function might be called multiple times by accident; seats
+ * should be prepared to cope.
+ *
+ * More precisely: this function notifies the seat that
+ * backend_connected() might now return false where previously it
+ * returned true. (Note the 'might': an accidental duplicate call
+ * might happen when backend_connected() was already returning
+ * false. Or even, in weird situations, when it hadn't stopped
+ * returning true yet. The point is, when you get this
+ * notification, all it's really telling you is that it's worth
+ * _checking_ backend_connected, if you weren't already.)
+ */
+ void (*notify_remote_disconnect)(Seat *seat);
+
+ /*
* Notify the seat that the connection has suffered a fatal error.
*/
void (*connection_fatal)(Seat *seat, const char *message);
@@ -967,35 +1258,47 @@ struct SeatVtable {
/*
* 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.
+ * This is called after we've already checked it by any means we
+ * can do ourselves, such as checking against host key
+ * fingerprints in the Conf or the host key cache on disk: once we
+ * call this function, we've already decided there's nothing for
+ * it but to prompt the user.
+ *
+ * 'mismatch' reports the result of checking the host key cache:
+ * it is true if the server has presented a host key different
+ * from the one we expected, and false if we had no expectation in
+ * the first place.
+ *
+ * This call may prompt the user synchronously and not return
+ * until the answer is available, or it may present the prompt and
+ * return immediately, giving the answer later via the provided
+ * callback.
*
* Return values:
*
- * - +1 means `key was OK' (either already known or the user just
- * approved it) `so continue with the connection'
+ * - +1 means `user approved the key, so continue with the
+ * connection'
*
- * - 0 means `key was not OK, abandon the connection'
+ * - 0 means `user rejected the key, abandon the connection'
*
* - -1 means `I've initiated enquiries, please wait to be called
* back via the provided function with a result that's either 0
* or +1'.
*/
- int (*verify_ssh_host_key)(
+ SeatPromptResult (*confirm_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);
+ char *keystr, SeatDialogText *text, HelpCtx helpctx,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
/*
* Check with the seat whether it's OK to use a cryptographic
* primitive from below the 'warn below this line' threshold in
* the input Conf. Return values are the same as
- * verify_ssh_host_key above.
+ * confirm_ssh_host_key above.
*/
- int (*confirm_weak_crypto_primitive)(
+ SeatPromptResult (*confirm_weak_crypto_primitive)(
Seat *seat, const char *algtype, const char *algname,
- void (*callback)(void *ctx, int result), void *ctx);
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
/*
* Variant form of confirm_weak_crypto_primitive, which prints a
@@ -1008,9 +1311,16 @@ struct SeatVtable {
* threshold is available that we don't have cached. 'betteralgs'
* lists the better algorithm(s).
*/
- int (*confirm_weak_cached_hostkey)(
+ SeatPromptResult (*confirm_weak_cached_hostkey)(
Seat *seat, const char *algname, const char *betteralgs,
- void (*callback)(void *ctx, int result), void *ctx);
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+
+ /*
+ * Some snippets of text describing the UI actions in host key
+ * prompts / dialog boxes, to be used in ssh/common.c when it
+ * assembles the full text of those prompts.
+ */
+ const SeatDialogPromptDescriptions *(*prompt_descriptions)(Seat *seat);
/*
* Indicates whether the seat is expecting to interact with the
@@ -1062,13 +1372,31 @@ struct SeatVtable {
* (and hence, can be trusted if it's asking you for secrets such
* as your passphrase); false means output is coming from the
* server.
+ */
+ void (*set_trust_status)(Seat *seat, bool trusted);
+
+ /*
+ * Query whether this Seat can do anything user-visible in
+ * response to set_trust_status.
*
* Returns true if the seat has a way to indicate this
* distinction. Returns false if not, in which case the backend
* should use a fallback defence against spoofing of PuTTY's local
* prompts by malicious servers.
*/
- bool (*set_trust_status)(Seat *seat, bool trusted);
+ bool (*can_set_trust_status)(Seat *seat);
+
+ /*
+ * Query whether this Seat's interactive prompt responses and its
+ * session input come from the same place.
+ *
+ * If false, this is used to suppress the final 'Press Return to
+ * begin session' anti-spoofing prompt in Plink. For example,
+ * Plink itself sets this flag if its standard input is redirected
+ * (and therefore not coming from the same place as the console
+ * it's sending its prompts to).
+ */
+ bool (*has_mixed_input_stream)(Seat *seat);
/*
* Ask the seat whether it would like verbose messages.
@@ -1089,34 +1417,49 @@ struct SeatVtable {
};
static inline size_t seat_output(
- Seat *seat, bool err, const void *data, size_t len)
-{ return seat->vt->output(seat, err, data, len); }
+ Seat *seat, SeatOutputType type, const void *data, size_t len)
+{ return seat->vt->output(seat, type, data, len); }
static inline bool seat_eof(Seat *seat)
{ return seat->vt->eof(seat); }
-static inline 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_sent(Seat *seat, size_t bufsize)
+{ seat->vt->sent(seat, bufsize); }
+static inline size_t seat_banner(
+ InteractionReadySeat iseat, const void *data, size_t len)
+{ return iseat.seat->vt->banner(iseat.seat, data, len); }
+static inline SeatPromptResult seat_get_userpass_input(
+ InteractionReadySeat iseat, prompts_t *p)
+{ return iseat.seat->vt->get_userpass_input(iseat.seat, p); }
+static inline void seat_notify_session_started(Seat *seat)
+{ seat->vt->notify_session_started(seat); }
static inline void seat_notify_remote_exit(Seat *seat)
{ seat->vt->notify_remote_exit(seat); }
+static inline void seat_notify_remote_disconnect(Seat *seat)
+{ seat->vt->notify_remote_disconnect(seat); }
static inline void seat_update_specials_menu(Seat *seat)
{ seat->vt->update_specials_menu(seat); }
static inline char *seat_get_ttymode(Seat *seat, const char *mode)
{ return seat->vt->get_ttymode(seat, mode); }
static inline void seat_set_busy_status(Seat *seat, BusyStatus status)
{ seat->vt->set_busy_status(seat, status); }
-static inline 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 SeatPromptResult seat_confirm_ssh_host_key(
+ InteractionReadySeat iseat, const char *h, int p, const char *ktyp,
+ char *kstr, SeatDialogText *text, HelpCtx helpctx,
+ void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
+{ return iseat.seat->vt->confirm_ssh_host_key(
+ iseat.seat, h, p, ktyp, kstr, text, helpctx, cb, ctx); }
+static inline SeatPromptResult seat_confirm_weak_crypto_primitive(
+ InteractionReadySeat iseat, const char *atyp, const char *aname,
+ void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
+{ return iseat.seat->vt->confirm_weak_crypto_primitive(
+ iseat.seat, atyp, aname, cb, ctx); }
+static inline SeatPromptResult seat_confirm_weak_cached_hostkey(
+ InteractionReadySeat iseat, const char *aname, const char *better,
+ void (*cb)(void *ctx, SeatPromptResult result), void *ctx)
+{ return iseat.seat->vt->confirm_weak_cached_hostkey(
+ iseat.seat, aname, better, cb, ctx); }
+static inline const SeatDialogPromptDescriptions *seat_prompt_descriptions(
+ Seat *seat)
+{ return seat->vt->prompt_descriptions(seat); }
static inline bool seat_is_utf8(Seat *seat)
{ return seat->vt->is_utf8(seat); }
static inline void seat_echoedit_update(Seat *seat, bool ec, bool ed)
@@ -1130,8 +1473,12 @@ static inline bool seat_get_window_pixel_size(Seat *seat, int *w, int *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 void seat_set_trust_status(Seat *seat, bool trusted)
+{ seat->vt->set_trust_status(seat, trusted); }
+static inline bool seat_can_set_trust_status(Seat *seat)
+{ return seat->vt->can_set_trust_status(seat); }
+static inline bool seat_has_mixed_input_stream(Seat *seat)
+{ return seat->vt->has_mixed_input_stream(seat); }
static inline bool seat_verbose(Seat *seat)
{ return seat->vt->verbose(seat); }
static inline bool seat_interactive(Seat *seat)
@@ -1141,18 +1488,32 @@ static inline bool seat_get_cursor_position(Seat *seat, int *x, int *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. */
+ * defined in utils. */
void seat_connection_fatal(Seat *seat, const char *fmt, ...) PRINTF_LIKE(2, 3);
/* Handy aliases for seat_output which set is_stderr to a fixed value. */
static inline size_t seat_stdout(Seat *seat, const void *data, size_t len)
-{ return seat_output(seat, false, data, len); }
+{ return seat_output(seat, SEAT_OUTPUT_STDOUT, data, len); }
static inline size_t seat_stdout_pl(Seat *seat, ptrlen data)
-{ return seat_output(seat, false, data.ptr, data.len); }
+{ return seat_output(seat, SEAT_OUTPUT_STDOUT, data.ptr, data.len); }
static inline size_t seat_stderr(Seat *seat, const void *data, size_t len)
-{ return seat_output(seat, true, data, len); }
+{ return seat_output(seat, SEAT_OUTPUT_STDERR, data, len); }
static inline size_t seat_stderr_pl(Seat *seat, ptrlen data)
-{ return seat_output(seat, true, data.ptr, data.len); }
+{ return seat_output(seat, SEAT_OUTPUT_STDERR, data.ptr, data.len); }
+
+/* Alternative API for seat_banner taking a ptrlen */
+static inline size_t seat_banner_pl(InteractionReadySeat iseat, ptrlen data)
+{ return iseat.seat->vt->banner(iseat.seat, data.ptr, data.len); }
+
+struct SeatDialogPromptDescriptions {
+ const char *hk_accept_action;
+ const char *hk_connect_once_action;
+ const char *hk_cancel_action, *hk_cancel_action_Participle;
+};
+
+/* In the utils subdir: print a message to the Seat which can't be
+ * spoofed by server-supplied auth-time output such as SSH banners */
+void seat_antispoof_msg(InteractionReadySeat iseat, const char *msg);
/*
* Stub methods for seat implementations that want to use the obvious
@@ -1162,24 +1523,30 @@ static inline size_t seat_stderr_pl(Seat *seat, ptrlen data)
* 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);
+ Seat *seat, SeatOutputType type, 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_sent(Seat *seat, size_t bufsize);
+size_t nullseat_banner(Seat *seat, const void *data, size_t len);
+size_t nullseat_banner_to_stderr(Seat *seat, const void *data, size_t len);
+SeatPromptResult nullseat_get_userpass_input(Seat *seat, prompts_t *p);
+void nullseat_notify_session_started(Seat *seat);
void nullseat_notify_remote_exit(Seat *seat);
+void nullseat_notify_remote_disconnect(Seat *seat);
void nullseat_connection_fatal(Seat *seat, const char *message);
void nullseat_update_specials_menu(Seat *seat);
char *nullseat_get_ttymode(Seat *seat, const char *mode);
void nullseat_set_busy_status(Seat *seat, BusyStatus status);
-int nullseat_verify_ssh_host_key(
+SeatPromptResult nullseat_confirm_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(
+ char *keystr, SeatDialogText *text, HelpCtx helpctx,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+SeatPromptResult nullseat_confirm_weak_crypto_primitive(
Seat *seat, const char *algtype, const char *algname,
- void (*callback)(void *ctx, int result), void *ctx);
-int nullseat_confirm_weak_cached_hostkey(
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+SeatPromptResult nullseat_confirm_weak_cached_hostkey(
Seat *seat, const char *algname, const char *betteralgs,
- void (*callback)(void *ctx, int result), void *ctx);
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat);
bool nullseat_is_never_utf8(Seat *seat);
bool nullseat_is_always_utf8(Seat *seat);
void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing);
@@ -1187,9 +1554,12 @@ 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);
+ Seat *seat, BinarySink *bs_out, SeatInteractionContext sic);
+void nullseat_set_trust_status(Seat *seat, bool trusted);
+bool nullseat_can_set_trust_status_yes(Seat *seat);
+bool nullseat_can_set_trust_status_no(Seat *seat);
+bool nullseat_has_mixed_input_stream_yes(Seat *seat);
+bool nullseat_has_mixed_input_stream_no(Seat *seat);
bool nullseat_verbose_no(Seat *seat);
bool nullseat_verbose_yes(Seat *seat);
bool nullseat_interactive_no(Seat *seat);
@@ -1198,30 +1568,58 @@ 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).
+ * support module (console.c in each platform subdirectory).
*/
void console_connection_fatal(Seat *seat, const char *message);
-int console_verify_ssh_host_key(
+SeatPromptResult console_confirm_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(
+ char *keystr, SeatDialogText *text, HelpCtx helpctx,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+SeatPromptResult console_confirm_weak_crypto_primitive(
Seat *seat, const char *algtype, const char *algname,
- void (*callback)(void *ctx, int result), void *ctx);
-int console_confirm_weak_cached_hostkey(
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+SeatPromptResult console_confirm_weak_cached_hostkey(
Seat *seat, const char *algname, const char *betteralgs,
- void (*callback)(void *ctx, int result), void *ctx);
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
StripCtrlChars *console_stripctrl_new(
- Seat *seat, BinarySink *bs_out, SeatInteractionContext sic);
-bool console_set_trust_status(Seat *seat, bool trusted);
+ Seat *seat, BinarySink *bs_out, SeatInteractionContext sic);
+void console_set_trust_status(Seat *seat, bool trusted);
+bool console_can_set_trust_status(Seat *seat);
+bool console_has_mixed_input_stream(Seat *seat);
+const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat);
/*
* Other centralised seat functions.
*/
-int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input);
+SeatPromptResult filexfer_get_userpass_input(Seat *seat, prompts_t *p);
bool cmdline_seat_verbose(Seat *seat);
+/*
+ * TempSeat: a seat implementation that can be given to a backend
+ * temporarily while network proxy setup is using the real seat.
+ * Buffers output and trust-status changes until the real seat is
+ * available again.
+ */
+
+/* Called by the proxy code to make a TempSeat. */
+Seat *tempseat_new(Seat *real);
+
+/* Query functions to tell if a Seat _is_ temporary, and if so, to
+ * return the underlying real Seat. */
+bool is_tempseat(Seat *seat);
+Seat *tempseat_get_real(Seat *seat);
+
+/* Called by interactor_return_seat once the proxy connection has
+ * finished setting up (or failed), to pass on any buffered stuff to
+ * the real seat. */
+void tempseat_flush(Seat *ts);
+
+/* Frees a TempSeat, without flushing anything it has buffered. (Call
+ * this after tempseat_flush, or alternatively, when you were going to
+ * abandon the whole connection anyway.) */
+void tempseat_free(Seat *ts);
+
typedef struct rgb {
uint8_t r, g, b;
} rgb;
@@ -1282,10 +1680,22 @@ struct TermWinVtable {
void (*refresh)(TermWin *);
+ /* request_resize asks the front end if the terminal can please be
+ * resized to (w,h) in characters. The front end MAY call
+ * term_size() in response to tell the terminal its new size
+ * (which MAY be the requested size, or some other size if the
+ * requested one can't be achieved). The front end MAY also not
+ * call term_size() at all. But the front end MUST reply to this
+ * request by calling term_resize_request_completed(), after the
+ * responding resize event has taken place (if any).
+ *
+ * The calls to term_size and term_resize_request_completed may be
+ * synchronous callbacks from within the call to request_resize(). */
void (*request_resize)(TermWin *, int w, int h);
- void (*set_title)(TermWin *, const char *title);
- void (*set_icon_title)(TermWin *, const char *icontitle);
+ void (*set_title)(TermWin *, const char *title, int codepage);
+ void (*set_icon_title)(TermWin *, const char *icontitle, int codepage);
+
/* set_minimised and set_maximised are assumed to set two
* independent settings, rather than a single three-way
* {min,normal,max} switch. The idea is that when you un-minimise
@@ -1312,6 +1722,11 @@ struct TermWinVtable {
* object, because that doesn't happen until term_init
* returns. */
void (*palette_get_overrides)(TermWin *, Terminal *);
+
+ /* Notify the front end that the terminal's buffer of unprocessed
+ * output has reduced. (Front ends will likely pass this straight
+ * on to backend_unthrottle.) */
+ void (*unthrottle)(TermWin *, size_t bufsize);
};
static inline bool win_setup_draw_ctx(TermWin *win)
@@ -1350,10 +1765,11 @@ 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_title(TermWin *win, const char *title, int codepage)
+{ win->vt->set_title(win, title, codepage); }
+static inline void win_set_icon_title(TermWin *win, const char *icontitle,
+ int codepage)
+{ win->vt->set_icon_title(win, icontitle, codepage); }
static inline void win_set_minimised(TermWin *win, bool minimised)
{ win->vt->set_minimised(win, minimised); }
static inline void win_set_maximised(TermWin *win, bool maximised)
@@ -1367,6 +1783,8 @@ static inline void win_palette_set(
{ win->vt->palette_set(win, start, ncolours, colours); }
static inline void win_palette_get_overrides(TermWin *win, Terminal *term)
{ win->vt->palette_get_overrides(win, term); }
+static inline void win_unthrottle(TermWin *win, size_t size)
+{ win->vt->unthrottle(win, size); }
/*
* Global functions not specific to a connection instance.
@@ -1417,6 +1835,8 @@ NORETURN void cleanup_exit(int);
X(BOOL, NONE, change_username) /* allow username switching in SSH-2 */ \
X(INT, INT, ssh_cipherlist) \
X(FILENAME, NONE, keyfile) \
+ X(FILENAME, NONE, detached_cert) \
+ X(STR, NONE, auth_plugin) \
/* \
* Which SSH protocol to use. \
* For historical reasons, the current legal values for CONF_sshprot \
@@ -1474,6 +1894,7 @@ NORETURN void cleanup_exit(int);
X(BOOL, NONE, bksp_is_delete) \
X(BOOL, NONE, rxvt_homeend) \
X(INT, NONE, funky_type) /* FUNKY_XTERM, FUNKY_LINUX, ... */ \
+ X(INT, NONE, sharrow_type) /* SHARROW_APPLICATION, SHARROW_BITMAP, ... */ \
X(BOOL, NONE, no_applic_c) /* totally disable app cursor keys */ \
X(BOOL, NONE, no_applic_k) /* totally disable app keypad */ \
X(BOOL, NONE, no_mouse_rep) /* totally disable mouse reporting */ \
@@ -1606,6 +2027,8 @@ NORETURN void cleanup_exit(int);
X(INT, NONE, sshbug_oldgex2) \
X(INT, NONE, sshbug_winadj) \
X(INT, NONE, sshbug_chanreq) \
+ X(INT, NONE, sshbug_dropstart) \
+ X(INT, NONE, sshbug_filter_kexinit) \
/* \
* ssh_simple means that we promise never to open any channel \
* other than the main one, which means it can safely use a very \
@@ -1686,7 +2109,7 @@ void fontspec_serialise(BinarySink *bs, FontSpec *f);
FontSpec *fontspec_deserialise(BinarySource *src);
/*
- * Exports from noise.c.
+ * Exports from each platform's noise.c.
*/
typedef enum NoiseSourceId {
NOISE_SOURCE_TIME,
@@ -1712,6 +2135,10 @@ void noise_get_heavy(void (*func) (void *, int));
void noise_get_light(void (*func) (void *, int));
void noise_regular(void);
void noise_ultralight(NoiseSourceId id, unsigned long data);
+
+/*
+ * Exports from sshrand.c.
+ */
void random_save_seed(void);
void random_destroy_seed(void);
@@ -1763,6 +2190,7 @@ FontSpec *platform_default_fontspec(const char *name);
Terminal *term_init(Conf *, struct unicode_data *, TermWin *);
void term_free(Terminal *);
void term_size(Terminal *, int, int, int);
+void term_resize_request_completed(Terminal *);
void term_paint(Terminal *, int, int, int, int, bool);
void term_scroll(Terminal *, int, int);
void term_scroll_to_selection(Terminal *, int);
@@ -1770,6 +2198,7 @@ void term_pwron(Terminal *, bool);
void term_clrsb(Terminal *);
void term_mouse(Terminal *, Mouse_Button, Mouse_Button, Mouse_Action,
int, int, bool, bool, bool);
+void term_cancel_selection_drag(Terminal *);
void term_key(Terminal *, Key_Sym, wchar_t *, size_t, unsigned int,
unsigned int);
void term_lost_clipboard_ownership(Terminal *, int clipboard);
@@ -1784,15 +2213,15 @@ 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);
+size_t term_data(Terminal *, const void *data, size_t len);
void term_provide_backend(Terminal *term, Backend *backend);
void term_provide_logctx(Terminal *term, LogContext *logctx);
void term_set_focus(Terminal *term, bool has_focus);
char *term_get_ttymode(Terminal *term, const char *mode);
-int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input);
+SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p);
void term_set_trust_status(Terminal *term, bool trusted);
void term_keyinput(Terminal *, int codepage, const void *buf, int len);
-void term_keyinputw(Terminal *, const wchar_t * widebuf, int len);
+void term_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);
@@ -1804,10 +2233,13 @@ void term_palette_override(Terminal *term, unsigned osc4_index, rgb rgb);
typedef enum SmallKeypadKey {
SKK_HOME, SKK_END, SKK_INSERT, SKK_DELETE, SKK_PGUP, SKK_PGDN,
} SmallKeypadKey;
-int format_arrow_key(char *buf, Terminal *term, int xkey, bool ctrl);
+int format_arrow_key(char *buf, Terminal *term, int xkey,
+ bool shift, bool ctrl, bool alt, bool *consumed_alt);
int format_function_key(char *buf, Terminal *term, int key_number,
- bool shift, bool ctrl);
-int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key);
+ bool shift, bool ctrl, bool alt, bool *consumed_alt);
+int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key,
+ bool shift, bool ctrl, bool alt,
+ bool *consumed_alt);
int format_numeric_keypad_key(char *buf, Terminal *term, char key,
bool shift, bool ctrl);
@@ -1870,7 +2302,7 @@ static inline void lp_logging_error(LogPolicy *lp, const char *event)
static inline bool lp_verbose(LogPolicy *lp)
{ return lp->vt->verbose(lp); }
-/* Defined in conscli.c, used in several console command-line tools */
+/* Defined in clicons.c, used in several console command-line tools */
extern LogPolicy console_cli_logpolicy[];
int console_askappend(LogPolicy *lp, Filename *filename,
@@ -1888,6 +2320,7 @@ void logfopen(LogContext *logctx);
void logfclose(LogContext *logctx);
void logtraffic(LogContext *logctx, unsigned char c, int logmode);
void logflush(LogContext *logctx);
+LogPolicy *log_get_policy(LogContext *logctx);
void logevent(LogContext *logctx, const char *event);
void logeventf(LogContext *logctx, const char *fmt, ...) PRINTF_LIKE(2, 3);
void logeventvf(LogContext *logctx, const char *fmt, va_list ap);
@@ -1937,7 +2370,7 @@ extern const struct BackendVtable rlogin_backend;
extern const struct BackendVtable telnet_backend;
/*
- * Exports from ssh.c.
+ * Exports from ssh/ssh.c.
*/
extern const struct BackendVtable ssh_backend;
extern const struct BackendVtable sshconn_backend;
@@ -1955,6 +2388,29 @@ void ldisc_configure(Ldisc *, Conf *);
void ldisc_free(Ldisc *);
void ldisc_send(Ldisc *, const void *buf, int len, bool interactive);
void ldisc_echoedit_update(Ldisc *);
+typedef struct LdiscInputToken {
+ /*
+ * Structure that encodes any single item of data that Ldisc can
+ * buffer: either a single character of raw data, or a session
+ * special.
+ */
+ bool is_special;
+ union {
+ struct {
+ /* if is_special == false */
+ char chr;
+ };
+ struct {
+ /* if is_special == true */
+ SessionSpecialCode code;
+ int arg;
+ };
+ };
+} LdiscInputToken;
+bool ldisc_has_input_buffered(Ldisc *);
+LdiscInputToken ldisc_get_input_token(Ldisc *); /* asserts there is input */
+void ldisc_enable_prompt_callback(Ldisc *, prompts_t *);
+void ldisc_check_sendok(Ldisc *);
/*
* Exports from sshrand.c.
@@ -1999,7 +2455,7 @@ void pinger_reconfig(Pinger *, Conf *oldconf, Conf *newconf);
void pinger_free(Pinger *);
/*
- * Exports from misc.c.
+ * Exports from modules in utils.
*/
#include "misc.h"
@@ -2012,19 +2468,13 @@ char const *conf_dest(Conf *conf);
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.
+ * Exports from version.c and cmake_commit.c.
*/
extern const char ver[];
extern const char commitid[];
/*
- * Exports from unicode.c.
+ * Exports from unicode.c in platform subdirs.
*/
#ifndef CP_UTF8
#define CP_UTF8 65001
@@ -2034,14 +2484,13 @@ 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);
+ char *mbstr, int mblen, const char *defchr);
wchar_t xlat_uskbd2cyrllic(int ch);
int check_compose(int first, int second);
-int decode_codepage(char *cp_name);
+int decode_codepage(const char *cp_name);
const char *cp_enumerate (int index);
const char *cp_name(int codepage);
-void get_unitab(int codepage, wchar_t * unitab, int ftype);
+void get_unitab(int codepage, wchar_t *unitab, int ftype);
/*
* Exports from wcwidth.c
@@ -2052,7 +2501,7 @@ int mk_wcwidth_cjk(unsigned int ucs);
int mk_wcswidth_cjk(const unsigned int *pwcs, size_t n);
/*
- * Exports from pageantc.c.
+ * Exports from agent-client.c in platform subdirs.
*
* agent_query returns NULL for here's-a-response, and non-NULL for
* query-in- progress. In the latter case there will be a call to
@@ -2071,8 +2520,8 @@ int mk_wcswidth_cjk(const unsigned int *pwcs, size_t n);
*
* 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.
+ * return NULL. The wrapper function agent_query_synchronous()
+ * (defined in its own module aqsync.c) makes this easier.
*/
typedef struct agent_pending_query agent_pending_query;
agent_pending_query *agent_query(
@@ -2094,7 +2543,7 @@ int wc_match(const char *wildcard, const char *target);
bool wc_unescape(char *output, const char *wildcard);
/*
- * Exports from frontend (windlg.c etc)
+ * Exports from frontend (dialog.c etc)
*/
void pgp_fingerprints(void);
/*
@@ -2104,11 +2553,11 @@ void pgp_fingerprints(void);
bool have_ssh_host_key(const char *host, int port, const char *keytype);
/*
- * Exports from console frontends (wincons.c, uxcons.c)
+ * Exports from console frontends (console.c in platform subdirs)
* that aren't equivalents to things in windlg.c et al.
*/
extern bool console_batch_mode, console_antispoof_prompt;
-int console_get_userpass_input(prompts_t *p);
+SeatPromptResult console_get_userpass_input(prompts_t *p);
bool is_interactive(void);
void console_print_error_msg(const char *prefix, const char *msg);
void console_print_error_msg_fmt_v(
@@ -2117,7 +2566,7 @@ void console_print_error_msg_fmt(const char *prefix, const char *fmt, ...)
PRINTF_LIKE(2, 3);
/*
- * Exports from printing.c.
+ * Exports from printing.c in platform subdirs.
*/
typedef struct printer_enum_tag printer_enum;
typedef struct printer_job_tag printer_job;
@@ -2138,10 +2587,15 @@ void printer_finish_job(printer_job *);
* zero out password arguments in the hope of not having them show up
* avoidably in Unix 'ps'.
*/
+struct cmdline_get_passwd_input_state { bool tried; };
+#define CMDLINE_GET_PASSWD_INPUT_STATE_INIT { .tried = false }
+extern const cmdline_get_passwd_input_state cmdline_get_passwd_input_state_new;
+
int cmdline_process_param(const char *, char *, int, Conf *);
void cmdline_run_saved(Conf *);
void cmdline_cleanup(void);
-int cmdline_get_passwd_input(prompts_t *p);
+SeatPromptResult cmdline_get_passwd_input(
+ prompts_t *p, cmdline_get_passwd_input_state *state, bool restartable);
bool cmdline_host_ok(Conf *);
bool cmdline_verbose(void);
bool cmdline_loaded_session(void);
@@ -2180,31 +2634,80 @@ 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 conf_radiobutton_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event);
#define CHECKBOX_INVERT (1<<30)
-void conf_checkbox_handler(union control *ctrl, dlgparam *dlg,
+void conf_checkbox_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event);
-void conf_editbox_handler(union control *ctrl, dlgparam *dlg,
+void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event);
-void conf_filesel_handler(union control *ctrl, dlgparam *dlg,
+void conf_filesel_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event);
-void conf_fontsel_handler(union control *ctrl, dlgparam *dlg,
+void conf_fontsel_handler(dlgcontrol *ctrl, dlgparam *dlg,
void *data, int event);
+struct conf_editbox_handler_type {
+ /* Structure passed as context2 to conf_editbox_handler */
+ enum { EDIT_STR, EDIT_INT, EDIT_FIXEDPOINT } type;
+ union {
+ /*
+ * EDIT_STR means the edit box is connected to a string
+ * field in Conf. No further parameters needed.
+ */
+
+ /*
+ * EDIT_INT means the edit box is connected to an int field in
+ * Conf, and the input string is interpreted as decimal. No
+ * further parameters needed. (But we could add one here later
+ * if for some reason we wanted int fields in hex.)
+ */
+
+ /*
+ * EDIT_FIXEDPOINT means the edit box is connected to an int
+ * field in Conf, but the input string is interpreted as
+ * _floating point_, and converted to/from the output int by
+ * means of a fixed denominator. That is,
+ *
+ * (floating value in edit box) * denominator = value in Conf
+ */
+ struct {
+ double denominator;
+ };
+ };
+};
+
+extern const struct conf_editbox_handler_type conf_editbox_str;
+extern const struct conf_editbox_handler_type conf_editbox_int;
+#define ED_STR CP(&conf_editbox_str)
+#define ED_INT CP(&conf_editbox_int)
+
void setup_config_box(struct controlbox *b, bool midsession,
int protocol, int protcfginfo);
+void setup_ca_config_box(struct controlbox *b);
+
+/* Platforms provide this to be called from config.c */
+void show_ca_config_box(dlgparam *dlg);
+extern const bool has_ca_config_box; /* false if, e.g., we're PuTTYtel */
+
+/* Visible outside config.c so that platforms can use it to recognise
+ * the proxy type control */
+void proxy_type_handler(dlgcontrol *ctrl, dlgparam *dlg,
+ void *data, int event);
+/* And then they'll set this flag in its generic.context.i */
+#define PROXY_UI_FLAG_LOCAL 1 /* has a local proxy */
+
/*
- * Exports from minibidi.c.
+ * Exports from bidi.c.
*/
#define BIDI_CHAR_INDEX_NONE ((unsigned short)-1)
typedef struct bidi_char {
unsigned int origwc, wc;
unsigned short index, nchars;
} bidi_char;
-int do_bidi(bidi_char *line, int count);
+BidiContext *bidi_new_context(void);
+void bidi_free_context(BidiContext *ctx);
+void do_bidi(BidiContext *ctx, bidi_char *line, size_t count);
int do_shape(bidi_char *line, bidi_char *to, int count);
bool is_rtl(int c);
@@ -2217,7 +2720,7 @@ enum {
X11_XDM, /* XDM-AUTHORIZATION-1 */
X11_NAUTHS
};
-extern const char *const x11_authnames[]; /* declared in x11fwd.c */
+extern const char *const x11_authnames[X11_NAUTHS];
/*
* An enum for the copy-paste UI action configuration.
@@ -2371,7 +2874,6 @@ unsigned long timing_last_clock(void);
* 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);
@@ -2398,6 +2900,16 @@ void request_callback_notifications(toplevel_callback_notify_fn_t notify,
void *ctx);
/*
+ * Facility provided by the platform to spawn a parallel subprocess
+ * and present its stdio via a Socket.
+ *
+ * 'prefix' indicates the prefix that should appear on messages passed
+ * to plug_log to provide stderr output from the process.
+ */
+Socket *platform_start_subprocess(const char *cmd, Plug *plug,
+ const char *prefix);
+
+/*
* Define no-op macros for the jump list functions, on platforms that
* don't support them. (This is a bit of a hack, and it'd be nicer to
* localise even the calls to those functions into the Windows front
diff --git a/puttymem.h b/puttymem.h
index 6513d6cf..11c1f83a 100644
--- a/puttymem.h
+++ b/puttymem.h
@@ -107,18 +107,19 @@ void *safegrowarray(void *array, size_t *size, size_t eltsize,
/*
* 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.
+ * functions when allocation fails. Usually it's provided by an
+ * implementation in utils, which ties it into an application's
+ * existing modalfatalbox() system, but standalone test applications
+ * can reimplement it some other way if they prefer.
*/
NORETURN void out_of_memory(void);
#ifdef MINEFIELD
/*
* Definitions for Minefield, PuTTY's own Windows-specific malloc
- * debugger in the style of Electric Fence. Implemented in winmisc.c,
- * and referred to by the main malloc wrappers in memory.c.
+ * debugger in the style of Electric Fence. Implemented in
+ * windows/utils/minefield.c, and referred to by the main malloc
+ * wrappers in memory.c.
*/
void *minefield_c_malloc(size_t size);
void minefield_c_free(void *p);
diff --git a/puttyps.h b/puttyps.h
deleted file mode 100644
index 27916d27..00000000
--- a/puttyps.h
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * 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/raw.c b/raw.c
deleted file mode 100644
index 0c454985..00000000
--- a/raw.c
+++ /dev/null
@@ -1,330 +0,0 @@
-/*
- * "Raw" backend.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-
-#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/release.pl b/release.pl
index c4e9822b..f9507a13 100644
--- a/release.pl
+++ b/release.pl
@@ -15,13 +15,11 @@ my $setver = 0;
my $upload = 0;
my $precheck = 0;
my $postcheck = 0;
-my $skip_ftp = 0;
GetOptions("version=s" => \$version,
"setver" => \$setver,
"upload" => \$upload,
"precheck" => \$precheck,
- "postcheck" => \$postcheck,
- "no-ftp" => \$skip_ftp)
+ "postcheck" => \$postcheck)
or &usage();
# --setver: construct a local commit which updates the version
@@ -34,10 +32,8 @@ if ($setver) {
my $builddir = tempdir(DIR => ".", CLEANUP => 1);
0 == system "git archive --format=tar HEAD | ( cd $builddir && tar xf - )"
or die;
- 0 == system "cd $builddir && ./mkfiles.pl" or die;
- 0 == system "cd $builddir && ./mkauto.sh" or die;
- 0 == system "cd $builddir && ./configure" or die;
- 0 == system "cd $builddir && make pscp plink RELEASE=${version}" or die;
+ 0 == system "cd $builddir && cmake . -DCMAKE_C_FLAGS=-DRELEASE=${version}" or die;
+ 0 == system "cd $builddir && cmake --build . -t pscp -t plink -j" or die;
our $pscp_transcript = `cd $builddir && ./pscp --help`;
$pscp_transcript =~ s/^Unidentified build/Release ${version}/m or die;
$pscp_transcript =~ s/^/\\c /mg;
@@ -78,8 +74,7 @@ if ($upload) {
or die "could not upload link maps";
for my $location (["thyestes", "www/putty/$version"],
- ["the", "www/putty/$version"],
- ["chiark", "ftp/putty-$version"]) {
+ ["the", "www/putty/$version"]) {
my ($host, $path) = @$location;
0 == system("rsync", "-av", "putty/", "$host:$path")
or die "could not upload release to $host";
@@ -111,7 +106,7 @@ if ($upload) {
}
# --precheck and --postcheck: attempt to download the release from its
-# various web and FTP locations.
+# various locations.
if ($precheck || $postcheck) {
defined $version or die "use --version";
@@ -120,7 +115,6 @@ if ($precheck || $postcheck) {
-d "putty" or die "no putty directory in cwd";
my $httpprefix = "https://the.earth.li/~sgtatham/putty/";
- my $ftpprefix = "ftp://ftp.chiark.greenend.org.uk/users/sgtatham/putty-";
# Go through all the files in build.out.
find({ wanted => sub
@@ -142,35 +136,23 @@ if ($precheck || $postcheck) {
my $http_numbered = "${httpprefix}$version/$path";
my $http_latest = "${httpprefix}latest/$path";
- my $ftp_numbered = "${ftpprefix}$version/$path";
- my $ftp_latest = "${ftpprefix}latest/$path";
- my ($http_uri, $ftp_uri);
+ my $http_uri;
if ($precheck) {
# Before the 'latest' links/redirects update,
# we just download from explicitly version-
# numbered URLs.
$http_uri = $http_numbered;
- $ftp_uri = $ftp_numbered;
}
if ($postcheck) {
# After 'latest' is updated, we're testing that
# the redirects work, so we download from the
# URLs with 'latest' in them.
$http_uri = $http_latest;
- $ftp_uri = $ftp_latest;
}
# Now test-download the files themselves.
- unless ($skip_ftp) {
- my $ftpdata = `curl -s $ftp_uri`;
- printf " got %d bytes via FTP", length $ftpdata;
- die "FTP download for $ftp_uri did not match"
- if $ftpdata ne $real_content;
- print ", ok\n";
- }
-
my $ua = LWP::UserAgent->new;
my $httpresponse = $ua->get($http_uri);
my $httpdata = $httpresponse->{_content};
diff --git a/resource.h b/resource.h
deleted file mode 100644
index e856d6b9..00000000
--- a/resource.h
+++ /dev/null
@@ -1,15 +0,0 @@
-//{{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/rlogin.c b/rlogin.c
deleted file mode 100644
index 2a3714e0..00000000
--- a/rlogin.c
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * Rlogin backend.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-#include <ctype.h>
-
-#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/scpserver.c b/scpserver.c
deleted file mode 100644
index 3c6e4559..00000000
--- a/scpserver.c
+++ /dev/null
@@ -1,1399 +0,0 @@
-/*
- * Server side of the old-school SCP protocol.
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#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/<name from the C command>.
- *
- * - "scp -t some_path_name"
- *
- * Client sends one C command, and server deposits it at
- * some_path_name itself, or in some_path_name/<name from C
- * command>, 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/sesschan.c b/sesschan.c
deleted file mode 100644
index 0cf85b3b..00000000
--- a/sesschan.c
+++ /dev/null
@@ -1,787 +0,0 @@
-/*
- * Implement the "session" channel type for the SSH server.
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-
-#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/settings.c b/settings.c
index 32a53c54..cd286eb4 100644
--- a/settings.c
+++ b/settings.c
@@ -8,8 +8,8 @@
#include "putty.h"
#include "storage.h"
#ifndef NO_GSSAPI
-#include "sshgssc.h"
-#include "sshgss.h"
+#include "ssh/gssc.h"
+#include "ssh/gss.h"
#endif
@@ -17,6 +17,7 @@
static const struct keyvalwhere ciphernames[] = {
{ "aes", CIPHER_AES, -1, -1 },
{ "chacha20", CIPHER_CHACHA20, CIPHER_AES, +1 },
+ { "aesgcm", CIPHER_AESGCM, CIPHER_CHACHA20, +1 },
{ "3des", CIPHER_3DES, -1, -1 },
{ "WARN", CIPHER_WARN, -1, -1 },
{ "des", CIPHER_DES, -1, -1 },
@@ -28,12 +29,24 @@ static const struct keyvalwhere ciphernames[] = {
* compatibility warts in load_open_settings(), and should be kept
* in sync with those. */
static const struct keyvalwhere kexnames[] = {
+ { "ntru-curve25519", KEX_NTRU_HYBRID, -1, +1 },
{ "ecdh", KEX_ECDH, -1, +1 },
/* This name is misleading: it covers both SHA-256 and SHA-1 variants */
{ "dh-gex-sha1", KEX_DHGEX, -1, -1 },
+ /* Again, this covers both SHA-256 and SHA-1, despite the name: */
{ "dh-group14-sha1", KEX_DHGROUP14, -1, -1 },
+ /* This one really is only SHA-1, though: */
{ "dh-group1-sha1", KEX_DHGROUP1, KEX_WARN, +1 },
{ "rsa", KEX_RSA, KEX_WARN, -1 },
+ /* Larger fixed DH groups: prefer the larger 15 and 16 over 14,
+ * but by default the even larger 17 and 18 go below 16.
+ * Rationale: diminishing returns of improving the DH strength are
+ * outweighed by increased CPU cost. Group 18 is painful on a slow
+ * machine. Users can override if they need to. */
+ { "dh-group15-sha512", KEX_DHGROUP15, KEX_DHGROUP14, -1 },
+ { "dh-group16-sha512", KEX_DHGROUP16, KEX_DHGROUP15, -1 },
+ { "dh-group17-sha512", KEX_DHGROUP17, KEX_DHGROUP16, +1 },
+ { "dh-group18-sha512", KEX_DHGROUP18, KEX_DHGROUP17, +1 },
{ "WARN", KEX_WARN, -1, -1 }
};
@@ -49,9 +62,9 @@ static const struct keyvalwhere hknames[] = {
/*
* 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).
+ * This is currently precisely the same as the set in
+ * ssh/ttymode-list.h, but could in principle differ if other backends
+ * started to support tty modes (e.g., the pty backend).
* The set of modes in in this array is currently significant for
* settings migration from old versions; if they change, review the
* gppmap() invocation for "TerminalModes".
@@ -624,12 +637,15 @@ void save_open_settings(settings_w *sesskey, Conf *conf)
write_setting_s(sesskey, "LogHost", conf_get_str(conf, CONF_loghost));
write_setting_b(sesskey, "SSH2DES", conf_get_bool(conf, CONF_ssh2_des_cbc));
write_setting_filename(sesskey, "PublicKeyFile", conf_get_filename(conf, CONF_keyfile));
+ write_setting_filename(sesskey, "DetachedCertificate", conf_get_filename(conf, CONF_detached_cert));
+ write_setting_s(sesskey, "AuthPlugin", conf_get_str(conf, CONF_auth_plugin));
write_setting_s(sesskey, "RemoteCommand", conf_get_str(conf, CONF_remote_cmd));
write_setting_b(sesskey, "RFCEnviron", conf_get_bool(conf, CONF_rfc_environ));
write_setting_b(sesskey, "PassiveTelnet", conf_get_bool(conf, CONF_passive_telnet));
write_setting_b(sesskey, "BackspaceIsDelete", conf_get_bool(conf, CONF_bksp_is_delete));
write_setting_b(sesskey, "RXVTHomeEnd", conf_get_bool(conf, CONF_rxvt_homeend));
write_setting_i(sesskey, "LinuxFunctionKeys", conf_get_int(conf, CONF_funky_type));
+ write_setting_i(sesskey, "ShiftedArrowKeys", conf_get_int(conf, CONF_sharrow_type));
write_setting_b(sesskey, "NoApplicationKeys", conf_get_bool(conf, CONF_no_applic_k));
write_setting_b(sesskey, "NoApplicationCursors", conf_get_bool(conf, CONF_no_applic_c));
write_setting_b(sesskey, "NoMouseReporting", conf_get_bool(conf, CONF_no_mouse_rep));
@@ -769,6 +785,8 @@ void save_open_settings(settings_w *sesskey, Conf *conf)
write_setting_i(sesskey, "BugOldGex2", 2-conf_get_int(conf, CONF_sshbug_oldgex2));
write_setting_i(sesskey, "BugWinadj", 2-conf_get_int(conf, CONF_sshbug_winadj));
write_setting_i(sesskey, "BugChanReq", 2-conf_get_int(conf, CONF_sshbug_chanreq));
+ write_setting_i(sesskey, "BugDropStart", 2-conf_get_int(conf, CONF_sshbug_dropstart));
+ write_setting_i(sesskey, "BugFilterKexinit", 2-conf_get_int(conf, CONF_sshbug_filter_kexinit));
write_setting_b(sesskey, "StampUtmp", conf_get_bool(conf, CONF_stamp_utmp));
write_setting_b(sesskey, "LoginShell", conf_get_bool(conf, CONF_login_shell));
write_setting_b(sesskey, "ScrollbarOnLeft", conf_get_bool(conf, CONF_scrollbar_on_left));
@@ -966,9 +984,9 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
* 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,"
+ *normal_default = "ecdh,dh-gex-sha1,dh-group18-sha512,dh-group17-sha512,dh-group16-sha512,dh-group15-sha512,dh-group14-sha1,rsa,"
"WARN,dh-group1-sha1",
- *bugdhgex2_default = "ecdh,dh-group14-sha1,rsa,"
+ *bugdhgex2_default = "ecdh,dh-group18-sha512,dh-group17-sha512,dh-group16-sha512,dh-group15-sha512,dh-group14-sha1,rsa,"
"WARN,dh-group1-sha1,dh-gex-sha1";
char *raw;
i = 2 - gppi_raw(sesskey, "BugDHGEx2", 0);
@@ -1039,12 +1057,16 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
#endif
gppb(sesskey, "SshNoShell", false, conf, CONF_ssh_no_shell);
gppfile(sesskey, "PublicKeyFile", conf, CONF_keyfile);
+ gppfile(sesskey, "DetachedCertificate", conf, CONF_detached_cert);
+ gpps(sesskey, "AuthPlugin", "", conf, CONF_auth_plugin);
gpps(sesskey, "RemoteCommand", "", conf, CONF_remote_cmd);
gppb(sesskey, "RFCEnviron", false, conf, CONF_rfc_environ);
gppb(sesskey, "PassiveTelnet", false, conf, CONF_passive_telnet);
gppb(sesskey, "BackspaceIsDelete", true, conf, CONF_bksp_is_delete);
gppb(sesskey, "RXVTHomeEnd", false, conf, CONF_rxvt_homeend);
gppi(sesskey, "LinuxFunctionKeys", 0, conf, CONF_funky_type);
+ gppi(sesskey, "ShiftedArrowKeys", SHARROW_APPLICATION, conf,
+ CONF_sharrow_type);
gppb(sesskey, "NoApplicationKeys", false, conf, CONF_no_applic_k);
gppb(sesskey, "NoApplicationCursors", false, conf, CONF_no_applic_c);
gppb(sesskey, "NoMouseReporting", false, conf, CONF_no_mouse_rep);
@@ -1244,6 +1266,8 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
i = gppi_raw(sesskey, "BugOldGex2", 0); conf_set_int(conf, CONF_sshbug_oldgex2, 2-i);
i = gppi_raw(sesskey, "BugWinadj", 0); conf_set_int(conf, CONF_sshbug_winadj, 2-i);
i = gppi_raw(sesskey, "BugChanReq", 0); conf_set_int(conf, CONF_sshbug_chanreq, 2-i);
+ i = gppi_raw(sesskey, "BugDropStart", 1); conf_set_int(conf, CONF_sshbug_dropstart, 2-i);
+ i = gppi_raw(sesskey, "BugFilterKexinit", 1); conf_set_int(conf, CONF_sshbug_filter_kexinit, 2-i);
conf_set_bool(conf, CONF_ssh_simple, false);
gppb(sesskey, "StampUtmp", true, conf, CONF_stamp_utmp);
gppb(sesskey, "LoginShell", true, conf, CONF_login_shell);
@@ -1302,6 +1326,8 @@ static int sessioncmp(const void *av, const void *bv)
return strcmp(a, b); /* otherwise, compare normally */
}
+bool sesslist_demo_mode = false;
+
void get_sesslist(struct sesslist *list, bool allocate)
{
int i;
@@ -1311,12 +1337,18 @@ void get_sesslist(struct sesslist *list, bool allocate)
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);
+ if (sesslist_demo_mode) {
+ put_asciz(sb, "demo-server");
+ put_asciz(sb, "demo-server-2");
+ } else {
+ if ((handle = enum_settings_start()) != NULL) {
+ while (enum_settings_next(handle, sb))
+ put_byte(sb, '\0');
+ enum_settings_finish(handle);
+ }
+ put_byte(sb, '\0');
}
- put_byte(sb, '\0');
+
list->buffer = strbuf_to_str(sb);
/*
diff --git a/sftp.c b/sftp.c
deleted file mode 100644
index a76702f8..00000000
--- a/sftp.c
+++ /dev/null
@@ -1,1205 +0,0 @@
-/*
- * sftp.c: SFTP generic client code.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <limits.h>
-
-#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/sftp.h b/sftp.h
deleted file mode 100644
index 5835c16b..00000000
--- a/sftp.h
+++ /dev/null
@@ -1,544 +0,0 @@
-/*
- * sftp.h: definitions for SFTP and the sftp.c routines.
- */
-
-#include "defs.h"
-
-#define SSH_FXP_INIT 1 /* 0x1 */
-#define SSH_FXP_VERSION 2 /* 0x2 */
-#define SSH_FXP_OPEN 3 /* 0x3 */
-#define SSH_FXP_CLOSE 4 /* 0x4 */
-#define SSH_FXP_READ 5 /* 0x5 */
-#define SSH_FXP_WRITE 6 /* 0x6 */
-#define SSH_FXP_LSTAT 7 /* 0x7 */
-#define SSH_FXP_FSTAT 8 /* 0x8 */
-#define SSH_FXP_SETSTAT 9 /* 0x9 */
-#define SSH_FXP_FSETSTAT 10 /* 0xa */
-#define SSH_FXP_OPENDIR 11 /* 0xb */
-#define SSH_FXP_READDIR 12 /* 0xc */
-#define SSH_FXP_REMOVE 13 /* 0xd */
-#define SSH_FXP_MKDIR 14 /* 0xe */
-#define SSH_FXP_RMDIR 15 /* 0xf */
-#define SSH_FXP_REALPATH 16 /* 0x10 */
-#define SSH_FXP_STAT 17 /* 0x11 */
-#define SSH_FXP_RENAME 18 /* 0x12 */
-#define SSH_FXP_STATUS 101 /* 0x65 */
-#define SSH_FXP_HANDLE 102 /* 0x66 */
-#define SSH_FXP_DATA 103 /* 0x67 */
-#define SSH_FXP_NAME 104 /* 0x68 */
-#define SSH_FXP_ATTRS 105 /* 0x69 */
-#define SSH_FXP_EXTENDED 200 /* 0xc8 */
-#define SSH_FXP_EXTENDED_REPLY 201 /* 0xc9 */
-
-#define SSH_FX_OK 0
-#define SSH_FX_EOF 1
-#define SSH_FX_NO_SUCH_FILE 2
-#define SSH_FX_PERMISSION_DENIED 3
-#define SSH_FX_FAILURE 4
-#define SSH_FX_BAD_MESSAGE 5
-#define SSH_FX_NO_CONNECTION 6
-#define SSH_FX_CONNECTION_LOST 7
-#define SSH_FX_OP_UNSUPPORTED 8
-
-#define SSH_FILEXFER_ATTR_SIZE 0x00000001
-#define SSH_FILEXFER_ATTR_UIDGID 0x00000002
-#define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004
-#define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008
-#define SSH_FILEXFER_ATTR_EXTENDED 0x80000000
-
-#define SSH_FXF_READ 0x00000001
-#define SSH_FXF_WRITE 0x00000002
-#define SSH_FXF_APPEND 0x00000004
-#define SSH_FXF_CREAT 0x00000008
-#define SSH_FXF_TRUNC 0x00000010
-#define SSH_FXF_EXCL 0x00000020
-
-#define SFTP_PROTO_VERSION 3
-
-#define PERMS_DIRECTORY 040000
-
-/*
- * External references. The sftp client module sftp.c expects to be
- * able to get at these functions.
- *
- * sftp_recvdata must never return less than len. It either blocks
- * until len is available and then returns true, or it returns false
- * for failure.
- *
- * sftp_senddata returns true on success, false on failure.
- *
- * sftp_sendbuffer returns the size of the backlog of data in the
- * transmit queue.
- */
-bool sftp_senddata(const char *data, size_t len);
-size_t sftp_sendbuffer(void);
-bool sftp_recvdata(char *data, size_t len);
-
-/*
- * Free sftp_requests
- */
-void sftp_cleanup_request(void);
-
-struct fxp_attrs {
- unsigned long flags;
- uint64_t size;
- unsigned long uid;
- unsigned long gid;
- unsigned long permissions;
- unsigned long atime;
- unsigned long mtime;
-};
-extern const struct fxp_attrs no_attrs;
-
-/*
- * Copy between the possibly-unused permissions field in an fxp_attrs
- * and a possibly-negative integer containing the same permissions.
- */
-#define PUT_PERMISSIONS(attrs, perms) \
- ((perms) >= 0 ? \
- ((attrs).flags |= SSH_FILEXFER_ATTR_PERMISSIONS, \
- (attrs).permissions = (perms)) : \
- ((attrs).flags &= ~SSH_FILEXFER_ATTR_PERMISSIONS))
-#define GET_PERMISSIONS(attrs, defaultperms) \
- ((attrs).flags & SSH_FILEXFER_ATTR_PERMISSIONS ? \
- (attrs).permissions : defaultperms)
-
-struct fxp_handle {
- char *hstring;
- int hlen;
-};
-
-struct fxp_name {
- char *filename, *longname;
- struct fxp_attrs attrs;
-};
-
-struct fxp_names {
- int nnames;
- struct fxp_name *names;
-};
-
-struct sftp_request;
-
-/*
- * Packet-manipulation functions.
- */
-
-struct sftp_packet {
- char *data;
- size_t length, maxlen, savedpos;
- int type;
- BinarySink_IMPLEMENTATION;
- BinarySource_IMPLEMENTATION;
-};
-
-/* When sending a packet, create it with sftp_pkt_init, then add
- * things to it by treating it as a BinarySink. When it's done, call
- * sftp_send_prepare, and then pkt->data and pkt->length describe its
- * wire format. */
-struct sftp_packet *sftp_pkt_init(int pkt_type);
-void sftp_send_prepare(struct sftp_packet *pkt);
-
-/* When receiving a packet, create it with sftp_recv_prepare once you
- * decode its length from the first 4 bytes of wire data. Then write
- * that many bytes into pkt->data, and call sftp_recv_finish to set up
- * the type code and BinarySource. */
-struct sftp_packet *sftp_recv_prepare(unsigned length);
-bool sftp_recv_finish(struct sftp_packet *pkt);
-
-/* Either kind of packet can be freed afterwards with sftp_pkt_free. */
-void sftp_pkt_free(struct sftp_packet *pkt);
-
-void BinarySink_put_fxp_attrs(BinarySink *bs, struct fxp_attrs attrs);
-bool BinarySource_get_fxp_attrs(BinarySource *src, struct fxp_attrs *attrs);
-#define put_fxp_attrs(bs, attrs) \
- BinarySink_put_fxp_attrs(BinarySink_UPCAST(bs), attrs)
-#define get_fxp_attrs(bs, attrs) \
- BinarySource_get_fxp_attrs(BinarySource_UPCAST(bs), attrs)
-
-/*
- * Error handling.
- */
-
-const char *fxp_error(void);
-int fxp_error_type(void);
-
-/*
- * Perform exchange of init/version packets. Return false on failure.
- */
-bool fxp_init(void);
-
-/*
- * Canonify a pathname. Concatenate the two given path elements
- * with a separating slash, unless the second is NULL.
- */
-struct sftp_request *fxp_realpath_send(const char *path);
-char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req);
-
-/*
- * Open a file. 'attrs' contains attributes to be applied to the file
- * if it's being created.
- */
-struct sftp_request *fxp_open_send(const char *path, int type,
- const struct fxp_attrs *attrs);
-struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin,
- struct sftp_request *req);
-
-/*
- * Open a directory.
- */
-struct sftp_request *fxp_opendir_send(const char *path);
-struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin,
- struct sftp_request *req);
-
-/*
- * Close a file/dir. Returns true on success, false on error.
- */
-struct sftp_request *fxp_close_send(struct fxp_handle *handle);
-bool fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req);
-
-/*
- * Make a directory.
- */
-struct sftp_request *fxp_mkdir_send(const char *path,
- const struct fxp_attrs *attrs);
-bool fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req);
-
-/*
- * Remove a directory.
- */
-struct sftp_request *fxp_rmdir_send(const char *path);
-bool fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req);
-
-/*
- * Remove a file.
- */
-struct sftp_request *fxp_remove_send(const char *fname);
-bool fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req);
-
-/*
- * Rename a file.
- */
-struct sftp_request *fxp_rename_send(const char *srcfname,
- const char *dstfname);
-bool fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req);
-
-/*
- * Return file attributes.
- */
-struct sftp_request *fxp_stat_send(const char *fname);
-bool fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req,
- struct fxp_attrs *attrs);
-struct sftp_request *fxp_fstat_send(struct fxp_handle *handle);
-bool fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req,
- struct fxp_attrs *attrs);
-
-/*
- * Set file attributes.
- */
-struct sftp_request *fxp_setstat_send(const char *fname,
- struct fxp_attrs attrs);
-bool fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *req);
-struct sftp_request *fxp_fsetstat_send(struct fxp_handle *handle,
- struct fxp_attrs attrs);
-bool fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *req);
-
-/*
- * Read from a file.
- */
-struct sftp_request *fxp_read_send(struct fxp_handle *handle,
- uint64_t offset, int len);
-int fxp_read_recv(struct sftp_packet *pktin, struct sftp_request *req,
- char *buffer, int len);
-
-/*
- * Write to a file.
- */
-struct sftp_request *fxp_write_send(struct fxp_handle *handle,
- void *buffer, uint64_t offset, int len);
-bool fxp_write_recv(struct sftp_packet *pktin, struct sftp_request *req);
-
-/*
- * Read from a directory.
- */
-struct sftp_request *fxp_readdir_send(struct fxp_handle *handle);
-struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin,
- struct sftp_request *req);
-
-/*
- * Free up an fxp_names structure.
- */
-void fxp_free_names(struct fxp_names *names);
-
-/*
- * Duplicate and free fxp_name structures.
- */
-struct fxp_name *fxp_dup_name(struct fxp_name *name);
-void fxp_free_name(struct fxp_name *name);
-
-/*
- * Store user data in an sftp_request structure.
- */
-void *fxp_get_userdata(struct sftp_request *req);
-void fxp_set_userdata(struct sftp_request *req, void *data);
-
-/*
- * These functions might well be temporary placeholders to be
- * replaced with more useful similar functions later. They form the
- * main dispatch loop for processing incoming SFTP responses.
- */
-void sftp_register(struct sftp_request *req);
-struct sftp_request *sftp_find_request(struct sftp_packet *pktin);
-struct sftp_packet *sftp_recv(void);
-
-/*
- * A wrapper to go round fxp_read_* and fxp_write_*, which manages
- * the queueing of multiple read/write requests.
- */
-
-struct fxp_xfer;
-
-struct fxp_xfer *xfer_download_init(struct fxp_handle *fh, uint64_t offset);
-void xfer_download_queue(struct fxp_xfer *xfer);
-int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin);
-bool xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len);
-
-struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64_t offset);
-bool xfer_upload_ready(struct fxp_xfer *xfer);
-void xfer_upload_data(struct fxp_xfer *xfer, char *buffer, int len);
-int xfer_upload_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin);
-
-bool xfer_done(struct fxp_xfer *xfer);
-void xfer_set_error(struct fxp_xfer *xfer);
-void xfer_cleanup(struct fxp_xfer *xfer);
-
-/*
- * Vtable for the platform-specific filesystem implementation that
- * answers requests in an SFTP server.
- */
-typedef struct SftpReplyBuilder SftpReplyBuilder;
-struct SftpServer {
- const SftpServerVtable *vt;
-};
-struct SftpServerVtable {
- SftpServer *(*new)(const SftpServerVtable *vt);
- void (*free)(SftpServer *srv);
-
- /*
- * Handle actual filesystem requests.
- *
- * Each of these functions replies by calling an 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/sign.sh b/sign.sh
index f3300322..b40c2d47 100755
--- a/sign.sh
+++ b/sign.sh
@@ -9,14 +9,14 @@
set -e
-keyname=38BA7229B7588FD1
+keyname=B43979F89F446CFD
preliminary=false
while :; do
case "$1" in
-r)
shift
- keyname=6289A25F4AE8DA82
+ keyname=E4F83EA2AA4915EC
;;
-p)
shift
diff --git a/ssh.c b/ssh.c
deleted file mode 100644
index 91e7e869..00000000
--- a/ssh.c
+++ /dev/null
@@ -1,1250 +0,0 @@
-/*
- * SSH backend.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <assert.h>
-#include <limits.h>
-#include <signal.h>
-
-#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/ssh.h b/ssh.h
index 49ab2796..5ecef0cb 100644
--- a/ssh.h
+++ b/ssh.h
@@ -299,9 +299,15 @@ struct ConnectionLayerVtable {
* subsequent channel-opens). */
void (*enable_x_fwd)(ConnectionLayer *cl);
- /* Communicate to the connection layer whether the main session
- * channel currently wants user input. */
+ /* Communicate / query whether the main session channel currently
+ * wants user input. The set function is called by mainchan; the
+ * query function is called by the top-level ssh.c. */
void (*set_wants_user_input)(ConnectionLayer *cl, bool wanted);
+ bool (*get_wants_user_input)(ConnectionLayer *cl);
+
+ /* Notify the connection layer that more data has been added to
+ * the user input queue. */
+ void (*got_user_input)(ConnectionLayer *cl);
};
struct ConnectionLayer {
@@ -371,6 +377,10 @@ static inline void ssh_enable_x_fwd(ConnectionLayer *cl)
{ cl->vt->enable_x_fwd(cl); }
static inline void ssh_set_wants_user_input(ConnectionLayer *cl, bool wanted)
{ cl->vt->set_wants_user_input(cl, wanted); }
+static inline bool ssh_get_wants_user_input(ConnectionLayer *cl)
+{ return cl->vt->get_wants_user_input(cl); }
+static inline void ssh_got_user_input(ConnectionLayer *cl)
+{ cl->vt->got_user_input(cl); }
/* Exports from portfwd.c */
PortFwdManager *portfwdmgr_new(ConnectionLayer *cl);
@@ -397,11 +407,13 @@ LogContext *ssh_get_logctx(Ssh *ssh);
void ssh_throttle_conn(Ssh *ssh, int adjust);
void ssh_got_exitcode(Ssh *ssh, int status);
void ssh_ldisc_update(Ssh *ssh);
+void ssh_check_sendok(Ssh *ssh);
void ssh_got_fallback_cmd(Ssh *ssh);
bool ssh_is_bare(Ssh *ssh);
/* Communications back to ssh.c from the BPP */
void ssh_conn_processed_data(Ssh *ssh);
+void ssh_sendbuffer_changed(Ssh *ssh);
void ssh_check_frozen(Ssh *ssh);
/* Functions to abort the connection, for various reasons. */
@@ -411,6 +423,7 @@ void ssh_proto_error(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3);
void ssh_sw_abort(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3);
void ssh_sw_abort_deferred(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3);
void ssh_user_close(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3);
+void ssh_spr_close(Ssh *ssh, SeatPromptResult spr, const char *context);
/* Bit positions in the SSH-1 cipher protocol word */
#define SSH1_CIPHER_IDEA 1
@@ -446,7 +459,7 @@ struct RSAKey {
ssh_key sshk;
};
-struct dss_key {
+struct dsa_key {
mp_int *p, *q, *g, *y, *x;
ssh_key sshk;
};
@@ -502,7 +515,7 @@ struct ec_curve {
};
const ssh_keyalg *ec_alg_by_oid(int len, const void *oid,
- const struct ec_curve **curve);
+ 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;
@@ -529,22 +542,34 @@ struct eddsa_key {
WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg);
EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg);
+typedef enum KeyComponentType {
+ KCT_TEXT, KCT_BINARY, KCT_MPINT
+} KeyComponentType;
+typedef struct key_component {
+ char *name;
+ KeyComponentType type;
+ union {
+ strbuf *str; /* used for KCT_TEXT and KCT_BINARY */
+ mp_int *mp; /* used for KCT_MPINT */
+ };
+} key_component;
typedef struct key_components {
size_t ncomponents, componentsize;
- struct {
- char *name;
- bool is_mp_int;
- union {
- char *text;
- mp_int *mp;
- };
- } *components;
+ key_component *components;
} key_components;
key_components *key_components_new(void);
void key_components_add_text(key_components *kc,
const char *name, const char *value);
+void key_components_add_text_pl(key_components *kc,
+ const char *name, ptrlen value);
+void key_components_add_binary(key_components *kc,
+ const char *name, ptrlen value);
void key_components_add_mp(key_components *kc,
const char *name, mp_int *value);
+void key_components_add_uint(key_components *kc,
+ const char *name, uintmax_t value);
+void key_components_add_copy(key_components *kc,
+ const char *name, const key_component *value);
void key_components_free(key_components *kc);
/*
@@ -572,6 +597,7 @@ bool rsa_verify(RSAKey *key);
void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key, RsaSsh1Order order);
int rsa_ssh1_public_blob_len(ptrlen data);
void rsa_ssh1_private_blob_agent(BinarySink *bs, RSAKey *key);
+void duprsakey(RSAKey *dst, const RSAKey *src);
void freersapriv(RSAKey *key);
void freersakey(RSAKey *key);
key_components *rsa_components(RSAKey *key);
@@ -603,20 +629,11 @@ 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);
+mp_int *dsa_gen_k(const char *id_string,
+ mp_int *modulus, mp_int *private_key,
+ unsigned char *digest, int digest_len);
struct ssh_cipher {
const ssh_cipheralg *vt;
@@ -634,6 +651,9 @@ struct ssh_cipheralg {
unsigned long seq);
void (*decrypt_length)(ssh_cipher *, void *blk, int len,
unsigned long seq);
+ /* For ciphers that update their state per logical message
+ * (typically, per unit independently MACed) */
+ void (*next_message)(ssh_cipher *);
const char *ssh2_id;
int blksize;
/* real_keybits is the number of bits of entropy genuinely used by
@@ -678,9 +698,13 @@ static inline void ssh_cipher_encrypt_length(
static inline void ssh_cipher_decrypt_length(
ssh_cipher *c, void *blk, int len, unsigned long seq)
{ c->vt->decrypt_length(c, blk, len, seq); }
+static inline void ssh_cipher_next_message(ssh_cipher *c)
+{ c->vt->next_message(c); }
static inline const struct ssh_cipheralg *ssh_cipher_alg(ssh_cipher *c)
{ return c->vt; }
+void nullcipher_next_message(ssh_cipher *);
+
struct ssh2_ciphers {
int nciphers;
const ssh_cipheralg *const *list;
@@ -698,6 +722,7 @@ struct ssh2_macalg {
void (*setkey)(ssh2_mac *, ptrlen key);
void (*start)(ssh2_mac *);
void (*genresult)(ssh2_mac *, unsigned char *);
+ void (*next_message)(ssh2_mac *);
const char *(*text_name)(ssh2_mac *);
const char *name, *etm_name;
int len, keylen;
@@ -717,18 +742,22 @@ static inline void ssh2_mac_start(ssh2_mac *m)
{ m->vt->start(m); }
static inline void ssh2_mac_genresult(ssh2_mac *m, unsigned char *out)
{ m->vt->genresult(m, out); }
+static inline void ssh2_mac_next_message(ssh2_mac *m)
+{ m->vt->next_message(m); }
static inline const char *ssh2_mac_text_name(ssh2_mac *m)
{ return m->vt->text_name(m); }
static inline const ssh2_macalg *ssh2_mac_alg(ssh2_mac *m)
{ return m->vt; }
-/* Centralised 'methods' for ssh2_mac, defined in sshmac.c. These run
+/* Centralised 'methods' for ssh2_mac, defined in mac.c. These run
* the MAC in a specifically SSH-2 style, i.e. taking account of a
* packet sequence number as well as the data to be authenticated. */
bool ssh2_mac_verresult(ssh2_mac *, const void *);
void ssh2_mac_generate(ssh2_mac *, void *, int, unsigned long seq);
bool ssh2_mac_verify(ssh2_mac *, const void *, int, unsigned long seq);
+void nullmac_next_message(ssh2_mac *m);
+
/* Use a MAC in its raw form, outside SSH-2 context, to MAC a given
* string with a given key in the most obvious way. */
void mac_simple(const ssh2_macalg *alg, ptrlen key, ptrlen data, void *output);
@@ -791,11 +820,20 @@ 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;
+ enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH,
+ KEXTYPE_GSS, KEXTYPE_GSS_ECDH } main_type;
const ssh_hashalg *hash;
+ union { /* publicly visible data for each type */
+ const ecdh_keyalg *ecdh_vt; /* for KEXTYPE_ECDH, KEXTYPE_GSS_ECDH */
+ };
const void *extra; /* private to the kex methods */
};
+static inline bool kex_is_gss(const struct ssh_kex *kex)
+{
+ return kex->main_type == KEXTYPE_GSS || kex->main_type == KEXTYPE_GSS_ECDH;
+}
+
struct ssh_kexes {
int nkexes;
const ssh_kex *const *list;
@@ -822,17 +860,34 @@ struct ssh_keyalg {
void (*public_blob)(ssh_key *key, BinarySink *);
void (*private_blob)(ssh_key *key, BinarySink *);
void (*openssh_blob) (ssh_key *key, BinarySink *);
+ bool (*has_private) (ssh_key *key);
char *(*cache_str) (ssh_key *key);
key_components *(*components) (ssh_key *key);
+ ssh_key *(*base_key) (ssh_key *key); /* does not confer ownership */
+ /* The following methods can be NULL if !is_certificate */
+ void (*ca_public_blob)(ssh_key *key, BinarySink *);
+ bool (*check_cert)(ssh_key *key, bool host, ptrlen principal,
+ uint64_t time, const ca_options *opts,
+ BinarySink *error);
+ void (*cert_id_string)(ssh_key *key, BinarySink *);
+ SeatDialogText *(*cert_info)(ssh_key *key);
/* 'Class methods' that don't deal with an ssh_key at all */
int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob);
+ unsigned (*supported_flags) (const ssh_keyalg *self);
+ const char *(*alternate_ssh_id) (const ssh_keyalg *self, unsigned flags);
+ char *(*alg_desc)(const ssh_keyalg *self);
+ bool (*variable_size)(const ssh_keyalg *self);
+ /* The following methods can be NULL if !is_certificate */
+ const ssh_keyalg *(*related_alg)(const ssh_keyalg *self,
+ const ssh_keyalg *base);
/* Constant data fields giving information about the key type */
const char *ssh_id; /* string identifier in the SSH protocol */
const char *cache_id; /* identifier used in PuTTY's host key cache */
const void *extra; /* private to the public key methods */
- const unsigned supported_flags; /* signature-type flags we understand */
+ bool is_certificate; /* is this a certified key type? */
+ const ssh_keyalg *base_alg; /* if so, for what underlying key alg? */
};
static inline ssh_key *ssh_key_new_pub(const ssh_keyalg *self, ptrlen pub)
@@ -858,10 +913,24 @@ static inline void ssh_key_private_blob(ssh_key *key, BinarySink *bs)
{ key->vt->private_blob(key, bs); }
static inline void ssh_key_openssh_blob(ssh_key *key, BinarySink *bs)
{ key->vt->openssh_blob(key, bs); }
+static inline bool ssh_key_has_private(ssh_key *key)
+{ return key->vt->has_private(key); }
static inline char *ssh_key_cache_str(ssh_key *key)
{ return key->vt->cache_str(key); }
static inline key_components *ssh_key_components(ssh_key *key)
{ return key->vt->components(key); }
+static inline ssh_key *ssh_key_base_key(ssh_key *key)
+{ return key->vt->base_key(key); }
+static inline void ssh_key_ca_public_blob(ssh_key *key, BinarySink *bs)
+{ key->vt->ca_public_blob(key, bs); }
+static inline void ssh_key_cert_id_string(ssh_key *key, BinarySink *bs)
+{ key->vt->cert_id_string(key, bs); }
+static inline SeatDialogText *ssh_key_cert_info(ssh_key *key)
+{ return key->vt->cert_info(key); }
+static inline bool ssh_key_check_cert(
+ ssh_key *key, bool host, ptrlen principal, uint64_t time,
+ const ca_options *opts, BinarySink *error)
+{ return key->vt->check_cert(key, host, principal, time, opts, error); }
static inline int ssh_key_public_bits(const ssh_keyalg *self, ptrlen blob)
{ return self->pubkey_bits(self, blob); }
static inline const ssh_keyalg *ssh_key_alg(ssh_key *key)
@@ -870,6 +939,73 @@ static inline const char *ssh_key_ssh_id(ssh_key *key)
{ return key->vt->ssh_id; }
static inline const char *ssh_key_cache_id(ssh_key *key)
{ return key->vt->cache_id; }
+static inline unsigned ssh_key_supported_flags(ssh_key *key)
+{ return key->vt->supported_flags(key->vt); }
+static inline unsigned ssh_keyalg_supported_flags(const ssh_keyalg *self)
+{ return self->supported_flags(self); }
+static inline const char *ssh_keyalg_alternate_ssh_id(
+ const ssh_keyalg *self, unsigned flags)
+{ return self->alternate_ssh_id(self, flags); }
+static inline char *ssh_keyalg_desc(const ssh_keyalg *self)
+{ return self->alg_desc(self); }
+static inline bool ssh_keyalg_variable_size(const ssh_keyalg *self)
+{ return self->variable_size(self); }
+static inline const ssh_keyalg *ssh_keyalg_related_alg(
+ const ssh_keyalg *self, const ssh_keyalg *base)
+{ return self->related_alg(self, base); }
+
+/* Stub functions shared between multiple key types */
+unsigned nullkey_supported_flags(const ssh_keyalg *self);
+const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags);
+ssh_key *nullkey_base_key(ssh_key *key);
+bool nullkey_variable_size_no(const ssh_keyalg *self);
+bool nullkey_variable_size_yes(const ssh_keyalg *self);
+
+/* Utility functions implemented centrally */
+ssh_key *ssh_key_clone(ssh_key *key);
+
+/*
+ * SSH2 ECDH key exchange vtable
+ */
+struct ecdh_key {
+ const ecdh_keyalg *vt;
+};
+struct ecdh_keyalg {
+ /* Unusually, the 'new' method here doesn't directly take a vt
+ * pointer, because it will also need the containing ssh_kex
+ * structure for top-level parameters, and since that contains a
+ * vt pointer anyway, we might as well _only_ pass that. */
+ ecdh_key *(*new)(const ssh_kex *kex, bool is_server);
+ void (*free)(ecdh_key *key);
+ void (*getpublic)(ecdh_key *key, BinarySink *bs);
+ bool (*getkey)(ecdh_key *key, ptrlen remoteKey, BinarySink *bs);
+ char *(*description)(const ssh_kex *kex);
+};
+static inline ecdh_key *ecdh_key_new(const ssh_kex *kex, bool is_server)
+{ return kex->ecdh_vt->new(kex, is_server); }
+static inline void ecdh_key_free(ecdh_key *key)
+{ key->vt->free(key); }
+static inline void ecdh_key_getpublic(ecdh_key *key, BinarySink *bs)
+{ key->vt->getpublic(key, bs); }
+static inline bool ecdh_key_getkey(ecdh_key *key, ptrlen remoteKey,
+ BinarySink *bs)
+{ return key->vt->getkey(key, remoteKey, bs); }
+static inline char *ecdh_keyalg_description(const ssh_kex *kex)
+{ return kex->ecdh_vt->description(kex); }
+
+/*
+ * Suffix on GSSAPI SSH protocol identifiers that indicates Kerberos 5
+ * as the mechanism.
+ *
+ * This suffix is the base64-encoded MD5 hash of the byte sequence
+ * 06 09 2A 86 48 86 F7 12 01 02 02, which in turn is the ASN.1 DER
+ * encoding of the object ID 1.2.840.113554.1.2.2 which designates
+ * Kerberos v5.
+ *
+ * (The same encoded OID, minus the two-byte DER header, is defined in
+ * ssh/pgssapi.c as GSS_MECH_KRB5.)
+ */
+#define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g=="
/*
* Enumeration of signature flags from draft-miller-ssh-agent-02
@@ -953,22 +1089,40 @@ 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_ni;
+extern const ssh_cipheralg ssh_aes256_sdctr_neon;
extern const ssh_cipheralg ssh_aes256_sdctr_sw;
+extern const ssh_cipheralg ssh_aes256_gcm;
+extern const ssh_cipheralg ssh_aes256_gcm_ni;
+extern const ssh_cipheralg ssh_aes256_gcm_neon;
+extern const ssh_cipheralg ssh_aes256_gcm_sw;
extern const ssh_cipheralg ssh_aes256_cbc;
-extern const ssh_cipheralg ssh_aes256_cbc_hw;
+extern const ssh_cipheralg ssh_aes256_cbc_ni;
+extern const ssh_cipheralg ssh_aes256_cbc_neon;
extern const ssh_cipheralg ssh_aes256_cbc_sw;
extern const ssh_cipheralg ssh_aes192_sdctr;
-extern const ssh_cipheralg ssh_aes192_sdctr_hw;
+extern const ssh_cipheralg ssh_aes192_sdctr_ni;
+extern const ssh_cipheralg ssh_aes192_sdctr_neon;
extern const ssh_cipheralg ssh_aes192_sdctr_sw;
+extern const ssh_cipheralg ssh_aes192_gcm;
+extern const ssh_cipheralg ssh_aes192_gcm_ni;
+extern const ssh_cipheralg ssh_aes192_gcm_neon;
+extern const ssh_cipheralg ssh_aes192_gcm_sw;
extern const ssh_cipheralg ssh_aes192_cbc;
-extern const ssh_cipheralg ssh_aes192_cbc_hw;
+extern const ssh_cipheralg ssh_aes192_cbc_ni;
+extern const ssh_cipheralg ssh_aes192_cbc_neon;
extern const ssh_cipheralg ssh_aes192_cbc_sw;
extern const ssh_cipheralg ssh_aes128_sdctr;
-extern const ssh_cipheralg ssh_aes128_sdctr_hw;
+extern const ssh_cipheralg ssh_aes128_sdctr_ni;
+extern const ssh_cipheralg ssh_aes128_sdctr_neon;
extern const ssh_cipheralg ssh_aes128_sdctr_sw;
+extern const ssh_cipheralg ssh_aes128_gcm;
+extern const ssh_cipheralg ssh_aes128_gcm_ni;
+extern const ssh_cipheralg ssh_aes128_gcm_neon;
+extern const ssh_cipheralg ssh_aes128_gcm_sw;
extern const ssh_cipheralg ssh_aes128_cbc;
-extern const ssh_cipheralg ssh_aes128_cbc_hw;
+extern const ssh_cipheralg ssh_aes128_cbc_ni;
+extern const ssh_cipheralg ssh_aes128_cbc_neon;
extern const ssh_cipheralg ssh_aes128_cbc_sw;
extern const ssh_cipheralg ssh_blowfish_ssh2_ctr;
extern const ssh_cipheralg ssh_blowfish_ssh2;
@@ -981,18 +1135,21 @@ extern const ssh2_ciphers ssh2_aes;
extern const ssh2_ciphers ssh2_blowfish;
extern const ssh2_ciphers ssh2_arcfour;
extern const ssh2_ciphers ssh2_ccp;
+extern const ssh2_ciphers ssh2_aesgcm;
extern const ssh_hashalg ssh_md5;
extern const ssh_hashalg ssh_sha1;
-extern const ssh_hashalg ssh_sha1_hw;
+extern const ssh_hashalg ssh_sha1_ni;
+extern const ssh_hashalg ssh_sha1_neon;
extern const ssh_hashalg ssh_sha1_sw;
extern const ssh_hashalg ssh_sha256;
-extern const ssh_hashalg ssh_sha256_hw;
+extern const ssh_hashalg ssh_sha256_ni;
+extern const ssh_hashalg ssh_sha256_neon;
extern const ssh_hashalg ssh_sha256_sw;
extern const ssh_hashalg ssh_sha384;
-extern const ssh_hashalg ssh_sha384_hw;
+extern const ssh_hashalg ssh_sha384_neon;
extern const ssh_hashalg ssh_sha384_sw;
extern const ssh_hashalg ssh_sha512;
-extern const ssh_hashalg ssh_sha512_hw;
+extern const ssh_hashalg ssh_sha512_neon;
extern const ssh_hashalg ssh_sha512_sw;
extern const ssh_hashalg ssh_sha3_224;
extern const ssh_hashalg ssh_sha3_256;
@@ -1002,8 +1159,21 @@ extern const ssh_hashalg ssh_shake256_114bytes;
extern const ssh_hashalg ssh_blake2b;
extern const ssh_kexes ssh_diffiehellman_group1;
extern const ssh_kexes ssh_diffiehellman_group14;
+extern const ssh_kexes ssh_diffiehellman_group15;
+extern const ssh_kexes ssh_diffiehellman_group16;
+extern const ssh_kexes ssh_diffiehellman_group17;
+extern const ssh_kexes ssh_diffiehellman_group18;
extern const ssh_kexes ssh_diffiehellman_gex;
+extern const ssh_kex ssh_diffiehellman_group1_sha1;
+extern const ssh_kex ssh_diffiehellman_group14_sha256;
+extern const ssh_kex ssh_diffiehellman_group14_sha1;
+extern const ssh_kex ssh_diffiehellman_group15_sha512;
+extern const ssh_kex ssh_diffiehellman_group16_sha512;
+extern const ssh_kex ssh_diffiehellman_group17_sha512;
+extern const ssh_kex ssh_diffiehellman_group18_sha512;
extern const ssh_kexes ssh_gssk5_sha1_kex;
+extern const ssh_kexes ssh_gssk5_sha2_kex;
+extern const ssh_kexes ssh_gssk5_ecdh_kex;
extern const ssh_kexes ssh_rsa_kex;
extern const ssh_kex ssh_ec_kex_curve25519;
extern const ssh_kex ssh_ec_kex_curve448;
@@ -1011,7 +1181,8 @@ 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_kexes ssh_ntru_hybrid_kex;
+extern const ssh_keyalg ssh_dsa;
extern const ssh_keyalg ssh_rsa;
extern const ssh_keyalg ssh_rsa_sha256;
extern const ssh_keyalg ssh_rsa_sha512;
@@ -1020,6 +1191,14 @@ extern const ssh_keyalg ssh_ecdsa_ed448;
extern const ssh_keyalg ssh_ecdsa_nistp256;
extern const ssh_keyalg ssh_ecdsa_nistp384;
extern const ssh_keyalg ssh_ecdsa_nistp521;
+extern const ssh_keyalg opensshcert_ssh_dsa;
+extern const ssh_keyalg opensshcert_ssh_rsa;
+extern const ssh_keyalg opensshcert_ssh_rsa_sha256;
+extern const ssh_keyalg opensshcert_ssh_rsa_sha512;
+extern const ssh_keyalg opensshcert_ssh_ecdsa_ed25519;
+extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp256;
+extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp384;
+extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp521;
extern const ssh2_macalg ssh_hmac_md5;
extern const ssh2_macalg ssh_hmac_sha1;
extern const ssh2_macalg ssh_hmac_sha1_buggy;
@@ -1027,22 +1206,31 @@ extern const ssh2_macalg ssh_hmac_sha1_96;
extern const ssh2_macalg ssh_hmac_sha1_96_buggy;
extern const ssh2_macalg ssh_hmac_sha256;
extern const ssh2_macalg ssh2_poly1305;
+extern const ssh2_macalg ssh2_aesgcm_mac;
+extern const ssh2_macalg ssh2_aesgcm_mac_sw;
+extern const ssh2_macalg ssh2_aesgcm_mac_ref_poly;
+extern const ssh2_macalg ssh2_aesgcm_mac_clmul;
+extern const ssh2_macalg ssh2_aesgcm_mac_neon;
extern const ssh_compression_alg ssh_zlib;
/* Special constructor: BLAKE2b can be instantiated with any hash
* length up to 128 bytes */
ssh_hash *blake2b_new_general(unsigned hashlen);
+/* Special test function for AES-GCM */
+void aesgcm_set_prefix_lengths(ssh2_mac *mac, size_t skip, size_t aad);
+
/*
* On some systems, you have to detect hardware crypto acceleration by
* asking the local OS API rather than OS-agnostically asking the CPU
* itself. If so, then this function should be implemented in each
* platform subdirectory.
*/
-bool platform_aes_hw_available(void);
-bool platform_sha256_hw_available(void);
-bool platform_sha1_hw_available(void);
-bool platform_sha512_hw_available(void);
+bool platform_aes_neon_available(void);
+bool platform_pmull_neon_available(void);
+bool platform_sha256_neon_available(void);
+bool platform_sha1_neon_available(void);
+bool platform_sha512_neon_available(void);
/*
* PuTTY version number formatted as an SSH version string.
@@ -1051,13 +1239,13 @@ 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
+ * that fails. This variable is the means by which pscp.c can reach
* into the SSH code and find out which one it got.
*/
extern bool ssh_fallback_cmd(Backend *backend);
/*
- * The PRNG type, defined in sshprng.c. Visible data fields are
+ * The PRNG type, defined in prng.c. Visible data fields are
* 'savesize', which suggests how many random bytes you should request
* from a particular PRNG instance to write to putty.rnd, and a
* BinarySink implementation which you can use to write seed data in
@@ -1066,7 +1254,7 @@ extern bool ssh_fallback_cmd(Backend *backend);
struct prng {
size_t savesize;
BinarySink_IMPLEMENTATION;
- /* (also there's a surrounding implementation struct in sshprng.c) */
+ /* (also there's a surrounding implementation struct in prng.c) */
};
prng *prng_new(const ssh_hashalg *hashalg);
void prng_free(prng *p);
@@ -1137,10 +1325,6 @@ struct X11FakeAuth {
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
@@ -1170,7 +1354,7 @@ 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 */
+/* X11-related helper functions in utils */
/*
* This function does the job of platform_get_x11_auth, provided
* it is told where to find a normally formatted .Xauthority file:
@@ -1189,8 +1373,13 @@ void x11_get_auth_from_authfile(struct X11Display *display,
void x11_format_auth_for_authfile(
BinarySink *bs, SockAddr *addr, int display_no,
ptrlen authproto, ptrlen authdata);
+void *x11_make_greeting(int endian, int protomajor, int protominor,
+ int auth_proto, const void *auth_data, int auth_len,
+ const char *peer_ip, int peer_port,
+ int *outlen);
int x11_identify_auth_proto(ptrlen protoname);
void *x11_dehexify(ptrlen hex, int *outlen);
+bool x11_parse_ip(const char *addr_string, unsigned long *ip);
Channel *agentf_new(SshChannel *c);
@@ -1199,7 +1388,7 @@ 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);
+mp_int *dh_create_e(dh_ctx *);
const char *dh_validate_f(dh_ctx *, mp_int *f);
mp_int *dh_find_K(dh_ctx *, mp_int *f);
@@ -1211,11 +1400,7 @@ static inline bool is_base64_char(char c)
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;
@@ -1282,6 +1467,9 @@ extern const size_t n_keyalgs;
const ssh_keyalg *find_pubkey_alg(const char *name);
const ssh_keyalg *find_pubkey_alg_len(ptrlen name);
+ptrlen pubkey_blob_to_alg_name(ptrlen blob);
+const ssh_keyalg *pubkey_blob_to_alg(ptrlen blob);
+
/* Convenient wrappers on the LoadedFile mechanism suitable for key files */
LoadedFile *lf_load_keyfile(const Filename *filename, const char **errptr);
LoadedFile *lf_load_keyfile_fp(FILE *fp, const char **errptr);
@@ -1331,12 +1519,36 @@ enum {
};
typedef enum {
+ /* Default fingerprint types strip off a certificate to show you
+ * the fingerprint of the underlying public key */
SSH_FPTYPE_MD5,
SSH_FPTYPE_SHA256,
+ /* Non-default version of each fingerprint type which is 'raw',
+ * giving you the true hash of the public key blob even if it
+ * includes a certificate */
+ SSH_FPTYPE_MD5_CERT,
+ SSH_FPTYPE_SHA256_CERT,
} FingerprintType;
+static inline bool ssh_fptype_is_cert(FingerprintType fptype)
+{
+ return fptype >= SSH_FPTYPE_MD5_CERT;
+}
+static inline FingerprintType ssh_fptype_from_cert(FingerprintType fptype)
+{
+ if (ssh_fptype_is_cert(fptype))
+ fptype -= (SSH_FPTYPE_MD5_CERT - SSH_FPTYPE_MD5);
+ return fptype;
+}
+static inline FingerprintType ssh_fptype_to_cert(FingerprintType fptype)
+{
+ if (!ssh_fptype_is_cert(fptype))
+ fptype += (SSH_FPTYPE_MD5_CERT - SSH_FPTYPE_MD5);
+ return fptype;
+}
+
+#define SSH_N_FPTYPES (SSH_FPTYPE_SHA256_CERT + 1)
#define SSH_FPTYPE_DEFAULT SSH_FPTYPE_SHA256
-#define SSH_N_FPTYPES (SSH_FPTYPE_SHA256 + 1)
FingerprintType ssh2_pick_fingerprint(char **fingerprints,
FingerprintType preferred_type);
@@ -1350,6 +1562,8 @@ void ssh2_write_pubkey(FILE *fp, const char *comment,
int keytype);
char *ssh2_fingerprint_blob(ptrlen, FingerprintType);
char *ssh2_fingerprint(ssh_key *key, FingerprintType);
+char *ssh2_double_fingerprint_blob(ptrlen, FingerprintType);
+char *ssh2_double_fingerprint(ssh_key *key, FingerprintType);
char **ssh2_all_fingerprints_for_blob(ptrlen);
char **ssh2_all_fingerprints(ssh_key *key);
void ssh2_free_all_fingerprints(char **);
@@ -1361,7 +1575,7 @@ 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 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,
@@ -1369,7 +1583,7 @@ int import_ssh1_s(BinarySource *src, int type,
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);
+ 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,
@@ -1389,8 +1603,7 @@ void aes256_decrypt_pubkey(const void *key, const void *iv,
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,
+void openssh_bcrypt(ptrlen passphrase, ptrlen salt,
int rounds, unsigned char *out, int outbytes);
/*
@@ -1603,7 +1816,7 @@ 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"
+ #include "ssh/ttymode-list.h"
#undef TTYMODE_CHAR
#undef TTYMODE_FLAG
@@ -1667,6 +1880,7 @@ void old_keyfile_warning(void);
X(BUG_CHOKES_ON_WINADJ) \
X(BUG_SENDS_LATE_REQUEST_REPLY) \
X(BUG_SSH2_OLDGEX) \
+ X(BUG_REQUIRES_FILTERED_KEXINIT) \
/* end of list */
#define TMP_DECLARE_LOG2_ENUM(thing) log2_##thing,
enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_LOG2_ENUM) };
@@ -1684,9 +1898,14 @@ unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset);
alloc_channel_id_general(tree, offsetof(type, localid)))
void add_to_commasep(strbuf *buf, const char *data);
+void add_to_commasep_pl(strbuf *buf, ptrlen data);
bool get_commasep_word(ptrlen *list, ptrlen *word);
-int verify_ssh_manual_host_key(Conf *conf, char **fingerprints, ssh_key *key);
+SeatPromptResult verify_ssh_host_key(
+ InteractionReadySeat iseat, Conf *conf, const char *host, int port,
+ ssh_key *key, const char *keytype, char *keystr, const char *keydisp,
+ char **fingerprints, int ca_count,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache;
ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void);
@@ -1698,3 +1917,35 @@ bool ssh_transient_hostkey_cache_verify(
bool ssh_transient_hostkey_cache_has(
ssh_transient_hostkey_cache *thc, const ssh_keyalg *alg);
bool ssh_transient_hostkey_cache_non_empty(ssh_transient_hostkey_cache *thc);
+
+/*
+ * Protocol definitions for authentication helper plugins
+ */
+
+#define AUTHPLUGIN_MSG_NAMES(X) \
+ X(PLUGIN_INIT, 1) \
+ X(PLUGIN_INIT_RESPONSE, 2) \
+ X(PLUGIN_PROTOCOL, 3) \
+ X(PLUGIN_PROTOCOL_ACCEPT, 4) \
+ X(PLUGIN_PROTOCOL_REJECT, 5) \
+ X(PLUGIN_AUTH_SUCCESS, 6) \
+ X(PLUGIN_AUTH_FAILURE, 7) \
+ X(PLUGIN_INIT_FAILURE, 8) \
+ X(PLUGIN_KI_SERVER_REQUEST, 20) \
+ X(PLUGIN_KI_SERVER_RESPONSE, 21) \
+ X(PLUGIN_KI_USER_REQUEST, 22) \
+ X(PLUGIN_KI_USER_RESPONSE, 23) \
+ /* end of list */
+
+#define PLUGIN_PROTOCOL_MAX_VERSION 2 /* the highest version we speak */
+
+enum {
+ #define ENUMDECL(name, value) name = value,
+ AUTHPLUGIN_MSG_NAMES(ENUMDECL)
+ #undef ENUMDECL
+
+ /* Error codes internal to this implementation, indicating failure
+ * to receive a meaningful packet at all */
+ PLUGIN_NOTYPE = 256, /* packet too short to have a type */
+ PLUGIN_EOF = 257 /* EOF from auth plugin */
+};
diff --git a/ssh/CMakeLists.txt b/ssh/CMakeLists.txt
new file mode 100644
index 00000000..d2b35311
--- /dev/null
+++ b/ssh/CMakeLists.txt
@@ -0,0 +1,52 @@
+add_library(sshcommon OBJECT
+ bpp1.c
+ bpp2.c
+ bpp-bare.c
+ ca-config.c
+ censor1.c
+ censor2.c
+ common.c
+ connection1.c
+ connection2.c
+ crc-attack-detector.c
+ gssc.c
+ login1.c
+ pgssapi.c
+ portfwd.c
+ ../sshpubk.c
+ ../sshrand.c
+ transient-hostkey-cache.c
+ transport2.c
+ verstring.c
+ x11fwd.c
+ zlib.c)
+
+add_library(sftpcommon OBJECT sftpcommon.c)
+
+add_library(sshclient STATIC
+ agentf.c
+ connection1-client.c
+ connection2-client.c
+ kex2-client.c
+ mainchan.c
+ sharing.c
+ ssh.c
+ userauth2-client.c
+ $<TARGET_OBJECTS:sshcommon>
+ $<TARGET_OBJECTS:all-backends>
+ $<TARGET_OBJECTS:logging>)
+
+add_library(sshserver STATIC
+ connection1-server.c
+ connection2-server.c
+ kex2-server.c
+ login1-server.c
+ server.c
+ sesschan.c
+ sftpserver.c
+ userauth2-server.c
+ $<TARGET_OBJECTS:sftpcommon>
+ $<TARGET_OBJECTS:sshcommon>)
+
+add_sources_from_current_dir(sftpclient sftp.c)
+target_sources(sftpclient PRIVATE $<TARGET_OBJECTS:sftpcommon>)
diff --git a/ssh/agentf.c b/ssh/agentf.c
new file mode 100644
index 00000000..6a5ecee5
--- /dev/null
+++ b/ssh/agentf.c
@@ -0,0 +1,249 @@
+/*
+ * SSH agent forwarding.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "pageant.h"
+#include "channel.h"
+
+typedef struct agentf {
+ SshChannel *c;
+ bufchain inbuffer;
+ agent_pending_query *pending;
+ bool input_wanted;
+ bool rcvd_eof;
+
+ Channel chan;
+} agentf;
+
+static void agentf_got_response(agentf *af, void *reply, int replylen)
+{
+ af->pending = NULL;
+
+ if (!reply) {
+ /* The real agent didn't send any kind of reply at all for
+ * some reason, so fake an SSH_AGENT_FAILURE. */
+ reply = "\0\0\0\1\5";
+ replylen = 5;
+ }
+
+ sshfwd_write(af->c, reply, replylen);
+}
+
+static void agentf_callback(void *vctx, void *reply, int replylen);
+
+static void agentf_try_forward(agentf *af)
+{
+ size_t datalen, length;
+ strbuf *message;
+ unsigned char msglen[4];
+ void *reply;
+ int replylen;
+
+ /*
+ * Don't try to parallelise agent requests. Wait for each one to
+ * return before attempting the next.
+ */
+ if (af->pending)
+ return;
+
+ /*
+ * If the outgoing side of the channel connection is currently
+ * throttled, don't submit any new forwarded requests to the real
+ * agent. This causes the input side of the agent forwarding not
+ * to be emptied, exerting the required back-pressure on the
+ * remote client, and encouraging it to read our responses before
+ * sending too many more requests.
+ */
+ if (!af->input_wanted)
+ return;
+
+ while (1) {
+ /*
+ * Try to extract a complete message from the input buffer.
+ */
+ datalen = bufchain_size(&af->inbuffer);
+ if (datalen < 4)
+ break; /* not even a length field available yet */
+
+ bufchain_fetch(&af->inbuffer, msglen, 4);
+ length = GET_32BIT_MSB_FIRST(msglen);
+
+ if (length > AGENT_MAX_MSGLEN-4) {
+ /*
+ * If the remote has sent a message that's just _too_
+ * long, we should reject it in advance of seeing the rest
+ * of the incoming message, and also close the connection
+ * for good measure (which avoids us having to faff about
+ * with carefully ignoring just the right number of bytes
+ * from the overlong message).
+ */
+ agentf_got_response(af, NULL, 0);
+ sshfwd_write_eof(af->c);
+ return;
+ }
+
+ if (length > datalen - 4)
+ break; /* a whole message is not yet available */
+
+ bufchain_consume(&af->inbuffer, 4);
+
+ message = strbuf_new_for_agent_query();
+ bufchain_fetch_consume(
+ &af->inbuffer, strbuf_append(message, length), length);
+ af->pending = agent_query(
+ message, &reply, &replylen, agentf_callback, af);
+ strbuf_free(message);
+
+ if (af->pending)
+ return; /* agent_query promised to reply in due course */
+
+ /*
+ * If the agent gave us an answer immediately, pass it
+ * straight on and go round this loop again.
+ */
+ agentf_got_response(af, reply, replylen);
+ sfree(reply);
+ }
+
+ /*
+ * If we get here (i.e. we left the above while loop via 'break'
+ * rather than 'return'), that means we've determined that the
+ * input buffer for the agent forwarding connection doesn't
+ * contain a complete request.
+ *
+ * So if there's potentially more data to come, we can return now,
+ * and wait for the remote client to send it. But if the remote
+ * has sent EOF, it would be a mistake to do that, because we'd be
+ * waiting a long time. So this is the moment to check for EOF,
+ * and respond appropriately.
+ */
+ if (af->rcvd_eof)
+ sshfwd_write_eof(af->c);
+}
+
+static void agentf_callback(void *vctx, void *reply, int replylen)
+{
+ agentf *af = (agentf *)vctx;
+
+ agentf_got_response(af, reply, replylen);
+ sfree(reply);
+
+ /*
+ * Now try to extract and send further messages from the channel's
+ * input-side buffer.
+ */
+ agentf_try_forward(af);
+}
+
+static void agentf_free(Channel *chan);
+static size_t agentf_send(Channel *chan, bool is_stderr, const void *, size_t);
+static void agentf_send_eof(Channel *chan);
+static char *agentf_log_close_msg(Channel *chan);
+static void agentf_set_input_wanted(Channel *chan, bool wanted);
+
+static const ChannelVtable agentf_channelvt = {
+ .free = agentf_free,
+ .open_confirmation = chan_remotely_opened_confirmation,
+ .open_failed = chan_remotely_opened_failure,
+ .send = agentf_send,
+ .send_eof = agentf_send_eof,
+ .set_input_wanted = agentf_set_input_wanted,
+ .log_close_msg = agentf_log_close_msg,
+ .want_close = chan_default_want_close,
+ .rcvd_exit_status = chan_no_exit_status,
+ .rcvd_exit_signal = chan_no_exit_signal,
+ .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
+ .run_shell = chan_no_run_shell,
+ .run_command = chan_no_run_command,
+ .run_subsystem = chan_no_run_subsystem,
+ .enable_x11_forwarding = chan_no_enable_x11_forwarding,
+ .enable_agent_forwarding = chan_no_enable_agent_forwarding,
+ .allocate_pty = chan_no_allocate_pty,
+ .set_env = chan_no_set_env,
+ .send_break = chan_no_send_break,
+ .send_signal = chan_no_send_signal,
+ .change_window_size = chan_no_change_window_size,
+ .request_response = chan_no_request_response,
+};
+
+Channel *agentf_new(SshChannel *c)
+{
+ agentf *af = snew(agentf);
+ af->c = c;
+ af->chan.vt = &agentf_channelvt;
+ af->chan.initial_fixed_window_size = 0;
+ af->rcvd_eof = false;
+ bufchain_init(&af->inbuffer);
+ af->pending = NULL;
+ af->input_wanted = true;
+ return &af->chan;
+}
+
+static void agentf_free(Channel *chan)
+{
+ assert(chan->vt == &agentf_channelvt);
+ agentf *af = container_of(chan, agentf, chan);
+
+ if (af->pending)
+ agent_cancel_query(af->pending);
+ bufchain_clear(&af->inbuffer);
+ sfree(af);
+}
+
+static size_t agentf_send(Channel *chan, bool is_stderr,
+ const void *data, size_t length)
+{
+ assert(chan->vt == &agentf_channelvt);
+ agentf *af = container_of(chan, agentf, chan);
+ bufchain_add(&af->inbuffer, data, length);
+ agentf_try_forward(af);
+
+ /*
+ * We exert back-pressure on an agent forwarding client if and
+ * only if we're waiting for the response to an asynchronous agent
+ * request. This prevents the client running out of window while
+ * receiving the _first_ message, but means that if any message
+ * takes time to process, the client will be discouraged from
+ * sending an endless stream of further ones after it.
+ */
+ return (af->pending ? bufchain_size(&af->inbuffer) : 0);
+}
+
+static void agentf_send_eof(Channel *chan)
+{
+ assert(chan->vt == &agentf_channelvt);
+ agentf *af = container_of(chan, agentf, chan);
+
+ af->rcvd_eof = true;
+
+ /* Call try_forward, which will respond to the EOF now if
+ * appropriate, or wait until the queue of outstanding requests is
+ * dealt with if not. */
+ agentf_try_forward(af);
+}
+
+static char *agentf_log_close_msg(Channel *chan)
+{
+ return dupstr("Agent-forwarding connection closed");
+}
+
+static void agentf_set_input_wanted(Channel *chan, bool wanted)
+{
+ assert(chan->vt == &agentf_channelvt);
+ agentf *af = container_of(chan, agentf, chan);
+
+ af->input_wanted = wanted;
+
+ /* Agent forwarding channels are buffer-managed by not asking the
+ * agent questions if the SSH channel isn't accepting input. So if
+ * it's started again, we should ask a question if we have one
+ * pending.. */
+ if (wanted)
+ agentf_try_forward(af);
+}
diff --git a/ssh/bpp-bare.c b/ssh/bpp-bare.c
new file mode 100644
index 00000000..f1a889aa
--- /dev/null
+++ b/ssh/bpp-bare.c
@@ -0,0 +1,206 @@
+/*
+ * Trivial binary packet protocol for the 'bare' ssh-connection
+ * protocol used in PuTTY's SSH-2 connection sharing system.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "sshcr.h"
+
+struct ssh2_bare_bpp_state {
+ int crState;
+ long packetlen, maxlen;
+ unsigned char *data;
+ unsigned long incoming_sequence, outgoing_sequence;
+ PktIn *pktin;
+
+ BinaryPacketProtocol bpp;
+};
+
+static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp);
+static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp);
+static void ssh2_bare_bpp_handle_output(BinaryPacketProtocol *bpp);
+static PktOut *ssh2_bare_bpp_new_pktout(int type);
+
+static const BinaryPacketProtocolVtable ssh2_bare_bpp_vtable = {
+ .free = ssh2_bare_bpp_free,
+ .handle_input = ssh2_bare_bpp_handle_input,
+ .handle_output = ssh2_bare_bpp_handle_output,
+ .new_pktout = ssh2_bare_bpp_new_pktout,
+ .queue_disconnect = ssh2_bpp_queue_disconnect, /* in common.c */
+
+ /* packet size limit, per protocol spec in sharing.c comment */
+ .packet_size_limit = 0x4000,
+};
+
+BinaryPacketProtocol *ssh2_bare_bpp_new(LogContext *logctx)
+{
+ struct ssh2_bare_bpp_state *s = snew(struct ssh2_bare_bpp_state);
+ memset(s, 0, sizeof(*s));
+ s->bpp.vt = &ssh2_bare_bpp_vtable;
+ s->bpp.logctx = logctx;
+ ssh_bpp_common_setup(&s->bpp);
+ return &s->bpp;
+}
+
+static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp)
+{
+ struct ssh2_bare_bpp_state *s =
+ container_of(bpp, struct ssh2_bare_bpp_state, bpp);
+ sfree(s->pktin);
+ sfree(s);
+}
+
+#define BPP_READ(ptr, len) do \
+ { \
+ bool success; \
+ crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \
+ s->bpp.in_raw, ptr, len)) || \
+ s->bpp.input_eof); \
+ if (!success) \
+ goto eof; \
+ ssh_check_frozen(s->bpp.ssh); \
+ } while (0)
+
+static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp)
+{
+ struct ssh2_bare_bpp_state *s =
+ container_of(bpp, struct ssh2_bare_bpp_state, bpp);
+
+ crBegin(s->crState);
+
+ while (1) {
+ /* Read the length field. */
+ {
+ unsigned char lenbuf[4];
+ BPP_READ(lenbuf, 4);
+ s->packetlen = toint(GET_32BIT_MSB_FIRST(lenbuf));
+ }
+
+ if (s->packetlen <= 0 || s->packetlen >= (long)OUR_V2_PACKETLIMIT) {
+ ssh_sw_abort(s->bpp.ssh, "Invalid packet length received");
+ crStopV;
+ }
+
+ /*
+ * Allocate the packet to return, now we know its length.
+ */
+ s->pktin = snew_plus(PktIn, s->packetlen);
+ s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
+ s->pktin->qnode.on_free_queue = false;
+ s->maxlen = 0;
+ s->data = snew_plus_get_aux(s->pktin);
+
+ s->pktin->sequence = s->incoming_sequence++;
+
+ /*
+ * Read the remainder of the packet.
+ */
+ BPP_READ(s->data, s->packetlen);
+
+ /*
+ * The data we just read is precisely the initial type byte
+ * followed by the packet payload.
+ */
+ s->pktin->type = s->data[0];
+ s->data++;
+ s->packetlen--;
+ BinarySource_INIT(s->pktin, s->data, s->packetlen);
+
+ if (s->pktin->type == SSH2_MSG_EXT_INFO) {
+ /*
+ * Mild layer violation: EXT_INFO is not permitted in the
+ * bare ssh-connection protocol. Faulting it here means
+ * that ssh2_common_filter_queue doesn't receive it in the
+ * first place unless it's legal to have sent it.
+ */
+ ssh_proto_error(s->bpp.ssh, "Remote side sent SSH2_MSG_EXT_INFO "
+ "in bare connection protocol");
+ return;
+ }
+
+ /*
+ * Log incoming packet, possibly omitting sensitive fields.
+ */
+ if (s->bpp.logctx) {
+ logblank_t blanks[MAX_BLANKS];
+ int nblanks = ssh2_censor_packet(
+ s->bpp.pls, s->pktin->type, false,
+ make_ptrlen(s->data, s->packetlen), blanks);
+ log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type,
+ ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
+ s->pktin->type),
+ get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks,
+ &s->pktin->sequence, 0, NULL);
+ }
+
+ if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) {
+ sfree(s->pktin);
+ s->pktin = NULL;
+ continue;
+ }
+
+ s->pktin->qnode.formal_size = get_avail(s->pktin);
+ pq_push(&s->bpp.in_pq, s->pktin);
+ s->pktin = NULL;
+ }
+
+ eof:
+ if (!s->bpp.expect_close) {
+ ssh_remote_error(s->bpp.ssh,
+ "Remote side unexpectedly closed network connection");
+ } else {
+ ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection");
+ }
+ return; /* avoid touching s now it's been freed */
+
+ crFinishV;
+}
+
+static PktOut *ssh2_bare_bpp_new_pktout(int pkt_type)
+{
+ PktOut *pkt = ssh_new_packet();
+ pkt->length = 4; /* space for packet length */
+ pkt->type = pkt_type;
+ put_byte(pkt, pkt_type);
+ return pkt;
+}
+
+static void ssh2_bare_bpp_format_packet(struct ssh2_bare_bpp_state *s,
+ PktOut *pkt)
+{
+ if (s->bpp.logctx) {
+ ptrlen pktdata = make_ptrlen(pkt->data + 5, pkt->length - 5);
+ logblank_t blanks[MAX_BLANKS];
+ int nblanks = ssh2_censor_packet(
+ s->bpp.pls, pkt->type, true, pktdata, blanks);
+ log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type,
+ ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
+ pkt->type),
+ pktdata.ptr, pktdata.len, nblanks, blanks,
+ &s->outgoing_sequence,
+ pkt->downstream_id, pkt->additional_log_text);
+ }
+
+ s->outgoing_sequence++; /* only for diagnostics, really */
+
+ PUT_32BIT_MSB_FIRST(pkt->data, pkt->length - 4);
+ bufchain_add(s->bpp.out_raw, pkt->data, pkt->length);
+}
+
+static void ssh2_bare_bpp_handle_output(BinaryPacketProtocol *bpp)
+{
+ struct ssh2_bare_bpp_state *s =
+ container_of(bpp, struct ssh2_bare_bpp_state, bpp);
+ PktOut *pkt;
+
+ while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) {
+ ssh2_bare_bpp_format_packet(s, pkt);
+ ssh_free_pktout(pkt);
+ }
+
+ ssh_sendbuffer_changed(bpp->ssh);
+}
diff --git a/sshbpp.h b/ssh/bpp.h
index 87e7d7e7..87e7d7e7 100644
--- a/sshbpp.h
+++ b/ssh/bpp.h
diff --git a/ssh/bpp1.c b/ssh/bpp1.c
new file mode 100644
index 00000000..b82932f7
--- /dev/null
+++ b/ssh/bpp1.c
@@ -0,0 +1,389 @@
+/*
+ * Binary packet protocol for SSH-1.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "sshcr.h"
+
+struct ssh1_bpp_state {
+ int crState;
+ long len, pad, biglen, length, maxlen;
+ unsigned char *data;
+ uint32_t realcrc, gotcrc;
+ int chunk;
+ PktIn *pktin;
+
+ ssh_cipher *cipher_in, *cipher_out;
+
+ struct crcda_ctx *crcda_ctx;
+ uint8_t iv[8]; /* for crcda */
+
+ bool pending_compression_request;
+ ssh_compressor *compctx;
+ ssh_decompressor *decompctx;
+
+ BinaryPacketProtocol bpp;
+};
+
+static void ssh1_bpp_free(BinaryPacketProtocol *bpp);
+static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp);
+static void ssh1_bpp_handle_output(BinaryPacketProtocol *bpp);
+static void ssh1_bpp_queue_disconnect(BinaryPacketProtocol *bpp,
+ const char *msg, int category);
+static PktOut *ssh1_bpp_new_pktout(int type);
+
+static const BinaryPacketProtocolVtable ssh1_bpp_vtable = {
+ .free = ssh1_bpp_free,
+ .handle_input = ssh1_bpp_handle_input,
+ .handle_output = ssh1_bpp_handle_output,
+ .new_pktout = ssh1_bpp_new_pktout,
+ .queue_disconnect = ssh1_bpp_queue_disconnect,
+ .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */
+};
+
+BinaryPacketProtocol *ssh1_bpp_new(LogContext *logctx)
+{
+ struct ssh1_bpp_state *s = snew(struct ssh1_bpp_state);
+ memset(s, 0, sizeof(*s));
+ s->bpp.vt = &ssh1_bpp_vtable;
+ s->bpp.logctx = logctx;
+ ssh_bpp_common_setup(&s->bpp);
+ return &s->bpp;
+}
+
+static void ssh1_bpp_free(BinaryPacketProtocol *bpp)
+{
+ struct ssh1_bpp_state *s = container_of(bpp, struct ssh1_bpp_state, bpp);
+ if (s->cipher_in)
+ ssh_cipher_free(s->cipher_in);
+ if (s->cipher_out)
+ ssh_cipher_free(s->cipher_out);
+ if (s->compctx)
+ ssh_compressor_free(s->compctx);
+ if (s->decompctx)
+ ssh_decompressor_free(s->decompctx);
+ if (s->crcda_ctx)
+ crcda_free_context(s->crcda_ctx);
+ sfree(s->pktin);
+ sfree(s);
+}
+
+void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp,
+ const ssh_cipheralg *cipher,
+ const void *session_key)
+{
+ struct ssh1_bpp_state *s;
+ assert(bpp->vt == &ssh1_bpp_vtable);
+ s = container_of(bpp, struct ssh1_bpp_state, bpp);
+
+ assert(!s->cipher_in);
+ assert(!s->cipher_out);
+
+ if (cipher) {
+ s->cipher_in = ssh_cipher_new(cipher);
+ s->cipher_out = ssh_cipher_new(cipher);
+ ssh_cipher_setkey(s->cipher_in, session_key);
+ ssh_cipher_setkey(s->cipher_out, session_key);
+
+ assert(!s->crcda_ctx);
+ s->crcda_ctx = crcda_make_context();
+
+ bpp_logevent("Initialised %s encryption", cipher->text_name);
+
+ memset(s->iv, 0, sizeof(s->iv));
+
+ assert(cipher->blksize <= sizeof(s->iv));
+ ssh_cipher_setiv(s->cipher_in, s->iv);
+ ssh_cipher_setiv(s->cipher_out, s->iv);
+ }
+}
+
+void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp)
+{
+ struct ssh1_bpp_state *s;
+ assert(bpp->vt == &ssh1_bpp_vtable);
+ s = container_of(bpp, struct ssh1_bpp_state, bpp);
+
+ assert(!s->compctx);
+ assert(!s->decompctx);
+
+ s->compctx = ssh_compressor_new(&ssh_zlib);
+ s->decompctx = ssh_decompressor_new(&ssh_zlib);
+
+ bpp_logevent("Started zlib (RFC1950) compression");
+}
+
+#define BPP_READ(ptr, len) do \
+ { \
+ bool success; \
+ crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \
+ s->bpp.in_raw, ptr, len)) || \
+ s->bpp.input_eof); \
+ if (!success) \
+ goto eof; \
+ ssh_check_frozen(s->bpp.ssh); \
+ } while (0)
+
+static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp)
+{
+ struct ssh1_bpp_state *s = container_of(bpp, struct ssh1_bpp_state, bpp);
+
+ crBegin(s->crState);
+
+ while (1) {
+ s->maxlen = 0;
+ s->length = 0;
+
+ {
+ unsigned char lenbuf[4];
+ BPP_READ(lenbuf, 4);
+ s->len = toint(GET_32BIT_MSB_FIRST(lenbuf));
+ }
+
+ if (s->len < 5 || s->len > 262144) { /* SSH1.5-mandated max size */
+ ssh_sw_abort(s->bpp.ssh,
+ "Out-of-range packet length from remote suggests"
+ " data stream corruption");
+ crStopV;
+ }
+
+ s->pad = 8 - (s->len % 8);
+ s->biglen = s->len + s->pad;
+ s->length = s->len - 5;
+
+ /*
+ * Allocate the packet to return, now we know its length.
+ */
+ s->pktin = snew_plus(PktIn, s->biglen);
+ s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
+ s->pktin->qnode.on_free_queue = false;
+ s->pktin->type = 0;
+
+ s->maxlen = s->biglen;
+ s->data = snew_plus_get_aux(s->pktin);
+
+ BPP_READ(s->data, s->biglen);
+
+ if (s->cipher_in && detect_attack(s->crcda_ctx,
+ s->data, s->biglen, s->iv)) {
+ ssh_sw_abort(s->bpp.ssh,
+ "Network attack (CRC compensation) detected!");
+ crStopV;
+ }
+ /* Save the last cipher block, to be passed to the next call
+ * to detect_attack */
+ assert(s->biglen >= 8);
+ memcpy(s->iv, s->data + s->biglen - 8, sizeof(s->iv));
+
+ if (s->cipher_in)
+ ssh_cipher_decrypt(s->cipher_in, s->data, s->biglen);
+
+ s->realcrc = crc32_ssh1(make_ptrlen(s->data, s->biglen - 4));
+ s->gotcrc = GET_32BIT_MSB_FIRST(s->data + s->biglen - 4);
+ if (s->gotcrc != s->realcrc) {
+ ssh_sw_abort(s->bpp.ssh, "Incorrect CRC received on packet");
+ crStopV;
+ }
+
+ if (s->decompctx) {
+ unsigned char *decompblk;
+ int decomplen;
+ if (!ssh_decompressor_decompress(
+ s->decompctx, s->data + s->pad, s->length + 1,
+ &decompblk, &decomplen)) {
+ ssh_sw_abort(s->bpp.ssh,
+ "Zlib decompression encountered invalid data");
+ crStopV;
+ }
+
+ if (s->maxlen < s->pad + decomplen) {
+ PktIn *old_pktin = s->pktin;
+
+ s->maxlen = s->pad + decomplen;
+ s->pktin = snew_plus(PktIn, s->maxlen);
+ *s->pktin = *old_pktin; /* structure copy */
+ s->data = snew_plus_get_aux(s->pktin);
+
+ smemclr(old_pktin, s->biglen);
+ sfree(old_pktin);
+ }
+
+ memcpy(s->data + s->pad, decompblk, decomplen);
+ sfree(decompblk);
+ s->length = decomplen - 1;
+ }
+
+ /*
+ * Now we can find the bounds of the semantic content of the
+ * packet, and the initial type byte.
+ */
+ s->data += s->pad;
+ s->pktin->type = *s->data++;
+ BinarySource_INIT(s->pktin, s->data, s->length);
+
+ if (s->bpp.logctx) {
+ logblank_t blanks[MAX_BLANKS];
+ int nblanks = ssh1_censor_packet(
+ s->bpp.pls, s->pktin->type, false,
+ make_ptrlen(s->data, s->length), blanks);
+ log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type,
+ ssh1_pkt_type(s->pktin->type),
+ get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks,
+ NULL, 0, NULL);
+ }
+
+ s->pktin->qnode.formal_size = get_avail(s->pktin);
+ pq_push(&s->bpp.in_pq, s->pktin);
+
+ {
+ int type = s->pktin->type;
+ s->pktin = NULL;
+
+ switch (type) {
+ case SSH1_SMSG_SUCCESS:
+ case SSH1_SMSG_FAILURE:
+ if (s->pending_compression_request) {
+ /*
+ * This is the response to
+ * SSH1_CMSG_REQUEST_COMPRESSION.
+ */
+ if (type == SSH1_SMSG_SUCCESS) {
+ /*
+ * If the response was positive, start
+ * compression.
+ */
+ ssh1_bpp_start_compression(&s->bpp);
+ }
+
+ /*
+ * Either way, cancel the pending flag, and
+ * schedule a run of our output side in case we
+ * had any packets queued up in the meantime.
+ */
+ s->pending_compression_request = false;
+ queue_idempotent_callback(&s->bpp.ic_out_pq);
+ }
+ break;
+ }
+ }
+ }
+
+ eof:
+ if (!s->bpp.expect_close) {
+ ssh_remote_error(s->bpp.ssh,
+ "Remote side unexpectedly closed network connection");
+ } else {
+ ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection");
+ }
+ return; /* avoid touching s now it's been freed */
+
+ crFinishV;
+}
+
+static PktOut *ssh1_bpp_new_pktout(int pkt_type)
+{
+ PktOut *pkt = ssh_new_packet();
+ pkt->length = 4 + 8; /* space for length + max padding */
+ put_byte(pkt, pkt_type);
+ pkt->prefix = pkt->length;
+ pkt->type = pkt_type;
+ pkt->downstream_id = 0;
+ pkt->additional_log_text = NULL;
+ return pkt;
+}
+
+static void ssh1_bpp_format_packet(struct ssh1_bpp_state *s, PktOut *pkt)
+{
+ int pad, biglen, pktoffs;
+ uint32_t crc;
+ int len;
+
+ if (s->bpp.logctx) {
+ ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix,
+ pkt->length - pkt->prefix);
+ logblank_t blanks[MAX_BLANKS];
+ int nblanks = ssh1_censor_packet(
+ s->bpp.pls, pkt->type, true, pktdata, blanks);
+ log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type,
+ ssh1_pkt_type(pkt->type),
+ pktdata.ptr, pktdata.len, nblanks, blanks,
+ NULL, 0, NULL);
+ }
+
+ if (s->compctx) {
+ unsigned char *compblk;
+ int complen;
+ ssh_compressor_compress(s->compctx, pkt->data + 12, pkt->length - 12,
+ &compblk, &complen, 0);
+ /* Replace the uncompressed packet data with the compressed
+ * version. */
+ pkt->length = 12;
+ put_data(pkt, compblk, complen);
+ sfree(compblk);
+ }
+
+ put_uint32(pkt, 0); /* space for CRC */
+ len = pkt->length - 4 - 8; /* len(type+data+CRC) */
+ pad = 8 - (len % 8);
+ pktoffs = 8 - pad;
+ biglen = len + pad; /* len(padding+type+data+CRC) */
+
+ random_read(pkt->data + pktoffs, 4+8 - pktoffs);
+ crc = crc32_ssh1(
+ make_ptrlen(pkt->data + pktoffs + 4, biglen - 4)); /* all ex len */
+ PUT_32BIT_MSB_FIRST(pkt->data + pktoffs + 4 + biglen - 4, crc);
+ PUT_32BIT_MSB_FIRST(pkt->data + pktoffs, len);
+
+ if (s->cipher_out)
+ ssh_cipher_encrypt(s->cipher_out, pkt->data + pktoffs + 4, biglen);
+
+ bufchain_add(s->bpp.out_raw, pkt->data + pktoffs,
+ biglen + 4); /* len(length+padding+type+data+CRC) */
+}
+
+static void ssh1_bpp_handle_output(BinaryPacketProtocol *bpp)
+{
+ struct ssh1_bpp_state *s = container_of(bpp, struct ssh1_bpp_state, bpp);
+ PktOut *pkt;
+
+ if (s->pending_compression_request) {
+ /*
+ * Don't send any output packets while we're awaiting a
+ * response to SSH1_CMSG_REQUEST_COMPRESSION, because if they
+ * cross over in transit with the responding SSH1_CMSG_SUCCESS
+ * then the other end could decode them with the wrong
+ * compression settings.
+ */
+ return;
+ }
+
+ while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) {
+ int type = pkt->type;
+ ssh1_bpp_format_packet(s, pkt);
+ ssh_free_pktout(pkt);
+
+ if (type == SSH1_CMSG_REQUEST_COMPRESSION) {
+ /*
+ * When we see the actual compression request go past, set
+ * the pending flag, and stop processing packets this
+ * time.
+ */
+ s->pending_compression_request = true;
+ break;
+ }
+ }
+
+ ssh_sendbuffer_changed(bpp->ssh);
+}
+
+static void ssh1_bpp_queue_disconnect(BinaryPacketProtocol *bpp,
+ const char *msg, int category)
+{
+ PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH1_MSG_DISCONNECT);
+ put_stringz(pkt, msg);
+ pq_push(&bpp->out_pq, pkt);
+}
diff --git a/ssh/bpp2.c b/ssh/bpp2.c
new file mode 100644
index 00000000..e019dd2e
--- /dev/null
+++ b/ssh/bpp2.c
@@ -0,0 +1,990 @@
+/*
+ * Binary packet protocol for SSH-2.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "sshcr.h"
+
+struct ssh2_bpp_direction {
+ unsigned long sequence;
+ ssh_cipher *cipher;
+ ssh2_mac *mac;
+ bool etm_mode;
+ const ssh_compression_alg *pending_compression;
+};
+
+struct ssh2_bpp_state {
+ int crState;
+ long len, pad, payload, packetlen, maclen, length, maxlen;
+ unsigned char *buf;
+ size_t bufsize;
+ unsigned char *data;
+ unsigned cipherblk;
+ PktIn *pktin;
+ struct DataTransferStats *stats;
+ bool cbc_ignore_workaround;
+
+ struct ssh2_bpp_direction in, out;
+ /* comp and decomp logically belong in the per-direction
+ * substructure, except that they have different types */
+ ssh_decompressor *in_decomp;
+ ssh_compressor *out_comp;
+
+ bool is_server;
+ bool pending_newkeys;
+ bool pending_compression, seen_userauth_success;
+ bool enforce_next_packet_is_userauth_success;
+ unsigned nnewkeys;
+ int prev_type;
+
+ BinaryPacketProtocol bpp;
+};
+
+static void ssh2_bpp_free(BinaryPacketProtocol *bpp);
+static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp);
+static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp);
+static PktOut *ssh2_bpp_new_pktout(int type);
+
+static const BinaryPacketProtocolVtable ssh2_bpp_vtable = {
+ .free = ssh2_bpp_free,
+ .handle_input = ssh2_bpp_handle_input,
+ .handle_output = ssh2_bpp_handle_output,
+ .new_pktout = ssh2_bpp_new_pktout,
+ .queue_disconnect = ssh2_bpp_queue_disconnect, /* in common.c */
+ .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */
+};
+
+BinaryPacketProtocol *ssh2_bpp_new(
+ LogContext *logctx, struct DataTransferStats *stats, bool is_server)
+{
+ struct ssh2_bpp_state *s = snew(struct ssh2_bpp_state);
+ memset(s, 0, sizeof(*s));
+ s->bpp.vt = &ssh2_bpp_vtable;
+ s->bpp.logctx = logctx;
+ s->stats = stats;
+ s->is_server = is_server;
+ ssh_bpp_common_setup(&s->bpp);
+ return &s->bpp;
+}
+
+static void ssh2_bpp_free_outgoing_crypto(struct ssh2_bpp_state *s)
+{
+ if (s->out.mac)
+ ssh2_mac_free(s->out.mac);
+ if (s->out.cipher)
+ ssh_cipher_free(s->out.cipher);
+ if (s->out_comp)
+ ssh_compressor_free(s->out_comp);
+}
+
+static void ssh2_bpp_free_incoming_crypto(struct ssh2_bpp_state *s)
+{
+ /* As above, take care to free in.mac before in.cipher */
+ if (s->in.mac)
+ ssh2_mac_free(s->in.mac);
+ if (s->in.cipher)
+ ssh_cipher_free(s->in.cipher);
+ if (s->in_decomp)
+ ssh_decompressor_free(s->in_decomp);
+}
+
+static void ssh2_bpp_free(BinaryPacketProtocol *bpp)
+{
+ struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp);
+ sfree(s->buf);
+ ssh2_bpp_free_outgoing_crypto(s);
+ ssh2_bpp_free_incoming_crypto(s);
+ sfree(s->pktin);
+ sfree(s);
+}
+
+void ssh2_bpp_new_outgoing_crypto(
+ BinaryPacketProtocol *bpp,
+ const ssh_cipheralg *cipher, const void *ckey, const void *iv,
+ const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
+ const ssh_compression_alg *compression, bool delayed_compression)
+{
+ struct ssh2_bpp_state *s;
+ assert(bpp->vt == &ssh2_bpp_vtable);
+ s = container_of(bpp, struct ssh2_bpp_state, bpp);
+
+ ssh2_bpp_free_outgoing_crypto(s);
+
+ if (cipher) {
+ s->out.cipher = ssh_cipher_new(cipher);
+ ssh_cipher_setkey(s->out.cipher, ckey);
+ ssh_cipher_setiv(s->out.cipher, iv);
+
+ s->cbc_ignore_workaround = (
+ (ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_IS_CBC) &&
+ !(s->bpp.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE));
+
+ bpp_logevent("Initialised %s outbound encryption",
+ ssh_cipher_alg(s->out.cipher)->text_name);
+ } else {
+ s->out.cipher = NULL;
+ s->cbc_ignore_workaround = false;
+ }
+ s->out.etm_mode = etm_mode;
+ if (mac) {
+ s->out.mac = ssh2_mac_new(mac, s->out.cipher);
+ /*
+ * Important that mac_setkey comes after cipher_setkey,
+ * because in the case where the MAC makes use of the cipher
+ * (e.g. AES-GCM), it will need the cipher to be keyed
+ * already.
+ */
+ ssh2_mac_setkey(s->out.mac, make_ptrlen(mac_key, mac->keylen));
+
+ bpp_logevent("Initialised %s outbound MAC algorithm%s%s",
+ ssh2_mac_text_name(s->out.mac),
+ etm_mode ? " (in ETM mode)" : "",
+ (s->out.cipher &&
+ ssh_cipher_alg(s->out.cipher)->required_mac ?
+ " (required by cipher)" : ""));
+ } else {
+ s->out.mac = NULL;
+ }
+
+ if (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);
+ /* MAC setkey has to follow cipher, just as in outgoing_crypto above */
+ ssh2_mac_setkey(s->in.mac, make_ptrlen(mac_key, mac->keylen));
+
+ bpp_logevent("Initialised %s inbound MAC algorithm%s%s",
+ ssh2_mac_text_name(s->in.mac),
+ etm_mode ? " (in ETM mode)" : "",
+ (s->in.cipher &&
+ ssh_cipher_alg(s->in.cipher)->required_mac ?
+ " (required by cipher)" : ""));
+ } else {
+ s->in.mac = NULL;
+ }
+
+ if (delayed_compression && !s->seen_userauth_success) {
+ s->in.pending_compression = compression;
+ s->in_decomp = NULL;
+
+ bpp_logevent("Will enable %s decompression after user authentication",
+ s->in.pending_compression->text_name);
+ } else {
+ s->in.pending_compression = NULL;
+
+ /* 'compression' is always non-NULL, because no compression is
+ * indicated by ssh_comp_none. But this setup call may return a
+ * null in_decomp. */
+ s->in_decomp = ssh_decompressor_new(compression);
+
+ if (s->in_decomp)
+ bpp_logevent("Initialised %s decompression",
+ ssh_decompressor_alg(s->in_decomp)->text_name);
+ }
+
+ /* Clear the pending_newkeys flag, so that handle_input below will
+ * start consuming the input data again. */
+ s->pending_newkeys = false;
+
+ /* And schedule a run of handle_input, in case there's already
+ * input data in the queue. */
+ queue_idempotent_callback(&s->bpp.ic_in_raw);
+}
+
+bool ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp)
+{
+ struct ssh2_bpp_state *s;
+ assert(bpp->vt == &ssh2_bpp_vtable);
+ s = container_of(bpp, struct ssh2_bpp_state, bpp);
+
+ return s->pending_compression;
+}
+
+static void ssh2_bpp_enable_pending_compression(struct ssh2_bpp_state *s)
+{
+ BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */
+
+ if (s->in.pending_compression) {
+ s->in_decomp = ssh_decompressor_new(s->in.pending_compression);
+ bpp_logevent("Initialised delayed %s decompression",
+ ssh_decompressor_alg(s->in_decomp)->text_name);
+ s->in.pending_compression = NULL;
+ }
+ if (s->out.pending_compression) {
+ s->out_comp = ssh_compressor_new(s->out.pending_compression);
+ bpp_logevent("Initialised delayed %s compression",
+ ssh_compressor_alg(s->out_comp)->text_name);
+ s->out.pending_compression = NULL;
+ }
+}
+
+#define BPP_READ(ptr, len) do \
+ { \
+ bool success; \
+ crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \
+ s->bpp.in_raw, ptr, len)) || \
+ s->bpp.input_eof); \
+ if (!success) \
+ goto eof; \
+ ssh_check_frozen(s->bpp.ssh); \
+ } while (0)
+
+#define userauth_range(pkttype) ((unsigned)((pkttype) - 50) < 20)
+
+static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
+{
+ struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp);
+
+ crBegin(s->crState);
+
+ while (1) {
+ s->maxlen = 0;
+ s->length = 0;
+ if (s->in.cipher)
+ s->cipherblk = ssh_cipher_alg(s->in.cipher)->blksize;
+ else
+ s->cipherblk = 8;
+ if (s->cipherblk < 8)
+ s->cipherblk = 8;
+ s->maclen = s->in.mac ? ssh2_mac_alg(s->in.mac)->len : 0;
+
+ if (s->in.cipher &&
+ (ssh_cipher_alg(s->in.cipher)->flags & SSH_CIPHER_IS_CBC) &&
+ s->in.mac && !s->in.etm_mode) {
+ /*
+ * When dealing with a CBC-mode cipher, we want to avoid the
+ * possibility of an attacker's tweaking the ciphertext stream
+ * so as to cause us to feed the same block to the block
+ * cipher more than once and thus leak information
+ * (VU#958563). The way we do this is not to take any
+ * decisions on the basis of anything we've decrypted until
+ * we've verified it with a MAC. That includes the packet
+ * length, so we just read data and check the MAC repeatedly,
+ * and when the MAC passes, see if the length we've got is
+ * plausible.
+ *
+ * This defence is unnecessary in OpenSSH ETM mode, because
+ * the whole point of ETM mode is that the attacker can't
+ * tweak the ciphertext stream at all without the MAC
+ * detecting it before we decrypt anything.
+ */
+
+ /*
+ * Make sure we have buffer space for a maximum-size packet.
+ */
+ unsigned buflimit = OUR_V2_PACKETLIMIT + s->maclen;
+ if (s->bufsize < buflimit) {
+ s->bufsize = buflimit;
+ s->buf = sresize(s->buf, s->bufsize, unsigned char);
+ }
+
+ /* Read an amount corresponding to the MAC. */
+ BPP_READ(s->buf, s->maclen);
+
+ s->packetlen = 0;
+ ssh2_mac_start(s->in.mac);
+ put_uint32(s->in.mac, s->in.sequence);
+
+ for (;;) { /* Once around this loop per cipher block. */
+ /* Read another cipher-block's worth, and tack it on to
+ * the end. */
+ BPP_READ(s->buf + (s->packetlen + s->maclen), s->cipherblk);
+ /* Decrypt one more block (a little further back in
+ * the stream). */
+ ssh_cipher_decrypt(s->in.cipher,
+ s->buf + s->packetlen, s->cipherblk);
+
+ /* Feed that block to the MAC. */
+ put_data(s->in.mac,
+ s->buf + s->packetlen, s->cipherblk);
+ s->packetlen += s->cipherblk;
+
+ /* See if that gives us a valid packet. */
+ if (ssh2_mac_verresult(s->in.mac, s->buf + s->packetlen) &&
+ ((s->len = toint(GET_32BIT_MSB_FIRST(s->buf))) ==
+ s->packetlen-4))
+ break;
+ if (s->packetlen >= (long)OUR_V2_PACKETLIMIT) {
+ ssh_sw_abort(s->bpp.ssh,
+ "No valid incoming packet found");
+ crStopV;
+ }
+ }
+ s->maxlen = s->packetlen + s->maclen;
+
+ /*
+ * Now transfer the data into an output packet.
+ */
+ s->pktin = snew_plus(PktIn, s->maxlen);
+ s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
+ s->pktin->type = 0;
+ s->pktin->qnode.on_free_queue = false;
+ s->data = snew_plus_get_aux(s->pktin);
+ memcpy(s->data, s->buf, s->maxlen);
+ } else if (s->in.mac && s->in.etm_mode) {
+ if (s->bufsize < 4) {
+ s->bufsize = 4;
+ s->buf = sresize(s->buf, s->bufsize, unsigned char);
+ }
+
+ /*
+ * OpenSSH encrypt-then-MAC mode: the packet length is
+ * unencrypted, unless the cipher supports length encryption.
+ */
+ BPP_READ(s->buf, 4);
+
+ /* Cipher supports length decryption, so do it */
+ if (s->in.cipher && (ssh_cipher_alg(s->in.cipher)->flags &
+ SSH_CIPHER_SEPARATE_LENGTH)) {
+ /* Keep the packet the same though, so the MAC passes */
+ unsigned char len[4];
+ memcpy(len, s->buf, 4);
+ ssh_cipher_decrypt_length(
+ s->in.cipher, len, 4, s->in.sequence);
+ s->len = toint(GET_32BIT_MSB_FIRST(len));
+ } else {
+ s->len = toint(GET_32BIT_MSB_FIRST(s->buf));
+ }
+
+ /*
+ * _Completely_ silly lengths should be stomped on before they
+ * do us any more damage.
+ */
+ if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT ||
+ s->len % s->cipherblk != 0) {
+ ssh_sw_abort(s->bpp.ssh,
+ "Incoming packet length field was garbled");
+ crStopV;
+ }
+
+ /*
+ * So now we can work out the total packet length.
+ */
+ s->packetlen = s->len + 4;
+
+ /*
+ * Allocate the packet to return, now we know its length.
+ */
+ s->pktin = snew_plus(PktIn, OUR_V2_PACKETLIMIT + s->maclen);
+ s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
+ s->pktin->type = 0;
+ s->pktin->qnode.on_free_queue = false;
+ s->data = snew_plus_get_aux(s->pktin);
+ memcpy(s->data, s->buf, 4);
+
+ /*
+ * Read the remainder of the packet.
+ */
+ BPP_READ(s->data + 4, s->packetlen + s->maclen - 4);
+
+ /*
+ * Check the MAC.
+ */
+ if (s->in.mac && !ssh2_mac_verify(
+ s->in.mac, s->data, s->len + 4, s->in.sequence)) {
+ ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet");
+ crStopV;
+ }
+
+ /* Decrypt everything between the length field and the MAC. */
+ if (s->in.cipher)
+ ssh_cipher_decrypt(
+ s->in.cipher, s->data + 4, s->packetlen - 4);
+ } else {
+ if (s->bufsize < s->cipherblk) {
+ s->bufsize = s->cipherblk;
+ s->buf = sresize(s->buf, s->bufsize, unsigned char);
+ }
+
+ /*
+ * Acquire and decrypt the first block of the packet. This will
+ * contain the length and padding details.
+ */
+ BPP_READ(s->buf, s->cipherblk);
+
+ if (s->in.cipher)
+ ssh_cipher_decrypt(s->in.cipher, s->buf, s->cipherblk);
+
+ /*
+ * Now get the length figure.
+ */
+ s->len = toint(GET_32BIT_MSB_FIRST(s->buf));
+
+ /*
+ * _Completely_ silly lengths should be stomped on before they
+ * do us any more damage.
+ */
+ if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT ||
+ (s->len + 4) % s->cipherblk != 0) {
+ ssh_sw_abort(s->bpp.ssh,
+ "Incoming packet was garbled on decryption");
+ crStopV;
+ }
+
+ /*
+ * So now we can work out the total packet length.
+ */
+ s->packetlen = s->len + 4;
+
+ /*
+ * Allocate the packet to return, now we know its length.
+ */
+ s->maxlen = s->packetlen + s->maclen;
+ s->pktin = snew_plus(PktIn, s->maxlen);
+ s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
+ s->pktin->type = 0;
+ s->pktin->qnode.on_free_queue = false;
+ s->data = snew_plus_get_aux(s->pktin);
+ memcpy(s->data, s->buf, s->cipherblk);
+
+ /*
+ * Read and decrypt the remainder of the packet.
+ */
+ BPP_READ(s->data + s->cipherblk,
+ s->packetlen + s->maclen - s->cipherblk);
+
+ /* Decrypt everything _except_ the MAC. */
+ if (s->in.cipher)
+ ssh_cipher_decrypt(
+ s->in.cipher,
+ s->data + s->cipherblk, s->packetlen - s->cipherblk);
+
+ /*
+ * Check the MAC.
+ */
+ if (s->in.mac && !ssh2_mac_verify(
+ s->in.mac, s->data, s->len + 4, s->in.sequence)) {
+ ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet");
+ crStopV;
+ }
+ }
+ /* Get and sanity-check the amount of random padding. */
+ s->pad = s->data[4];
+ if (s->pad < 4 || s->len - s->pad < 1) {
+ ssh_sw_abort(s->bpp.ssh,
+ "Invalid padding length on received packet");
+ crStopV;
+ }
+ /*
+ * This enables us to deduce the payload length.
+ */
+ s->payload = s->len - s->pad - 1;
+
+ s->length = s->payload + 5;
+
+ dts_consume(&s->stats->in, s->packetlen);
+
+ s->pktin->sequence = s->in.sequence++;
+ if (s->in.cipher)
+ ssh_cipher_next_message(s->in.cipher);
+ if (s->in.mac)
+ ssh2_mac_next_message(s->in.mac);
+
+ s->length = s->packetlen - s->pad;
+ assert(s->length >= 0);
+
+ /*
+ * Decompress packet payload.
+ */
+ {
+ unsigned char *newpayload;
+ int newlen;
+ if (s->in_decomp && ssh_decompressor_decompress(
+ s->in_decomp, s->data + 5, s->length - 5,
+ &newpayload, &newlen)) {
+ if (s->maxlen < newlen + 5) {
+ PktIn *old_pktin = s->pktin;
+
+ s->maxlen = newlen + 5;
+ s->pktin = snew_plus(PktIn, s->maxlen);
+ *s->pktin = *old_pktin; /* structure copy */
+ s->data = snew_plus_get_aux(s->pktin);
+
+ smemclr(old_pktin, s->packetlen + s->maclen);
+ sfree(old_pktin);
+ }
+ s->length = 5 + newlen;
+ memcpy(s->data + 5, newpayload, newlen);
+ sfree(newpayload);
+ }
+ }
+
+ /*
+ * Now we can identify the semantic content of the packet,
+ * and also the initial type byte.
+ */
+ if (s->length <= 5) { /* == 5 we hope, but robustness */
+ /*
+ * RFC 4253 doesn't explicitly say that completely empty
+ * packets with no type byte are forbidden. We handle them
+ * here by giving them a type code larger than 0xFF, which
+ * will be picked up at the next layer and trigger
+ * SSH_MSG_UNIMPLEMENTED.
+ */
+ s->pktin->type = SSH_MSG_NO_TYPE_CODE;
+ s->data += 5;
+ s->length = 0;
+ } else {
+ s->pktin->type = s->data[5];
+ s->data += 6;
+ s->length -= 6;
+ }
+ BinarySource_INIT(s->pktin, s->data, s->length);
+
+ if (s->bpp.logctx) {
+ logblank_t blanks[MAX_BLANKS];
+ int nblanks = ssh2_censor_packet(
+ s->bpp.pls, s->pktin->type, false,
+ make_ptrlen(s->data, s->length), blanks);
+ log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type,
+ ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
+ s->pktin->type),
+ s->data, s->length, nblanks, blanks,
+ &s->pktin->sequence, 0, NULL);
+ }
+
+ if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) {
+ sfree(s->pktin);
+ s->pktin = NULL;
+ continue;
+ }
+
+ s->pktin->qnode.formal_size = get_avail(s->pktin);
+ pq_push(&s->bpp.in_pq, s->pktin);
+
+ {
+ int type = s->pktin->type;
+ int prev_type = s->prev_type;
+ s->prev_type = type;
+ s->pktin = NULL;
+
+ if (s->enforce_next_packet_is_userauth_success) {
+ /* See EXT_INFO handler below */
+ if (type != SSH2_MSG_USERAUTH_SUCCESS) {
+ ssh_proto_error(s->bpp.ssh,
+ "Remote side sent SSH2_MSG_EXT_INFO "
+ "not either preceded by NEWKEYS or "
+ "followed by USERAUTH_SUCCESS");
+ return;
+ }
+ s->enforce_next_packet_is_userauth_success = false;
+ }
+
+ if (type == SSH2_MSG_NEWKEYS) {
+ if (s->nnewkeys < 2)
+ s->nnewkeys++;
+ /*
+ * Mild layer violation: in this situation we must
+ * suspend processing of the input byte stream until
+ * the transport layer has initialised the new keys by
+ * calling ssh2_bpp_new_incoming_crypto above.
+ */
+ s->pending_newkeys = true;
+ crWaitUntilV(!s->pending_newkeys);
+ continue;
+ }
+
+ if (type == SSH2_MSG_USERAUTH_SUCCESS && !s->is_server) {
+ /*
+ * Another one: if we were configured with OpenSSH's
+ * deferred compression which is triggered on receipt
+ * of USERAUTH_SUCCESS, then this is the moment to
+ * turn on compression.
+ */
+ ssh2_bpp_enable_pending_compression(s);
+
+ /*
+ * Whether or not we were doing delayed compression in
+ * _this_ set of crypto parameters, we should set a
+ * flag indicating that we're now authenticated, so
+ * that a delayed compression method enabled in any
+ * future rekey will be treated as un-delayed.
+ */
+ s->seen_userauth_success = true;
+ }
+
+ if (type == SSH2_MSG_EXT_INFO) {
+ /*
+ * And another: enforce that an incoming EXT_INFO is
+ * either the message immediately after the initial
+ * NEWKEYS, or (if we're the client) the one
+ * immediately before USERAUTH_SUCCESS.
+ */
+ if (prev_type == SSH2_MSG_NEWKEYS && s->nnewkeys == 1) {
+ /* OK - this is right after the first NEWKEYS. */
+ } else if (s->is_server) {
+ /* We're the server, so they're the client.
+ * Clients may not send EXT_INFO at _any_ other
+ * time. */
+ ssh_proto_error(s->bpp.ssh,
+ "Remote side sent SSH2_MSG_EXT_INFO "
+ "that was not immediately after the "
+ "initial NEWKEYS");
+ return;
+ } else if (s->nnewkeys > 0 && s->seen_userauth_success) {
+ /* We're the client, so they're the server. In
+ * that case they may also send EXT_INFO
+ * immediately before USERAUTH_SUCCESS. Error out
+ * immediately if this can't _possibly_ be that
+ * moment (because we haven't even seen NEWKEYS
+ * yet, or because we've already seen
+ * USERAUTH_SUCCESS). */
+ ssh_proto_error(s->bpp.ssh,
+ "Remote side sent SSH2_MSG_EXT_INFO "
+ "after USERAUTH_SUCCESS");
+ return;
+ } else {
+ /* This _could_ be OK, provided the next packet is
+ * USERAUTH_SUCCESS. Set a flag to remember to
+ * fault it if not. */
+ s->enforce_next_packet_is_userauth_success = true;
+ }
+ }
+
+ if (s->pending_compression && userauth_range(type)) {
+ /*
+ * Receiving any userauth message at all indicates
+ * that we're not about to turn on delayed compression
+ * - either because we just _have_ done, or because
+ * this message is a USERAUTH_FAILURE or some kind of
+ * intermediate 'please send more data' continuation
+ * message. Either way, we turn off the outgoing
+ * packet blockage for now, and release any queued
+ * output packets, so that we can make another attempt
+ * to authenticate. The next userauth packet we send
+ * will re-block the output direction.
+ */
+ s->pending_compression = false;
+ queue_idempotent_callback(&s->bpp.ic_out_pq);
+ }
+ }
+ }
+
+ eof:
+ /*
+ * We've seen EOF. But we might have pushed stuff on the outgoing
+ * packet queue first, and that stuff _might_ include a DISCONNECT
+ * message, in which case we'd like to use that as the diagnostic.
+ * So first wait for the queue to have been processed.
+ */
+ crMaybeWaitUntilV(!pq_peek(&s->bpp.in_pq));
+ if (!s->bpp.expect_close) {
+ ssh_remote_error(s->bpp.ssh,
+ "Remote side unexpectedly closed network connection");
+ } else {
+ ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection");
+ }
+ return; /* avoid touching s now it's been freed */
+
+ crFinishV;
+}
+
+static PktOut *ssh2_bpp_new_pktout(int pkt_type)
+{
+ PktOut *pkt = ssh_new_packet();
+ pkt->length = 5; /* space for packet length + padding length */
+ pkt->minlen = 0;
+ pkt->type = pkt_type;
+ put_byte(pkt, pkt_type);
+ pkt->prefix = pkt->length;
+ return pkt;
+}
+
+static void ssh2_bpp_format_packet_inner(struct ssh2_bpp_state *s, PktOut *pkt)
+{
+ int origlen, cipherblk, maclen, padding, unencrypted_prefix, i;
+
+ if (s->bpp.logctx) {
+ ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix,
+ pkt->length - pkt->prefix);
+ logblank_t blanks[MAX_BLANKS];
+ int nblanks = ssh2_censor_packet(
+ s->bpp.pls, pkt->type, true, pktdata, blanks);
+ log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type,
+ ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
+ pkt->type),
+ pktdata.ptr, pktdata.len, nblanks, blanks, &s->out.sequence,
+ pkt->downstream_id, pkt->additional_log_text);
+ }
+
+ cipherblk = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 8;
+ cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */
+
+ if (s->out_comp) {
+ unsigned char *newpayload;
+ int minlen, newlen;
+
+ /*
+ * Compress packet payload.
+ */
+ minlen = pkt->minlen;
+ if (minlen) {
+ /*
+ * Work out how much compressed data we need (at least) to
+ * make the overall packet length come to pkt->minlen.
+ */
+ if (s->out.mac)
+ minlen -= ssh2_mac_alg(s->out.mac)->len;
+ minlen -= 8; /* length field + min padding */
+ }
+
+ ssh_compressor_compress(s->out_comp, pkt->data + 5, pkt->length - 5,
+ &newpayload, &newlen, minlen);
+ pkt->length = 5;
+ put_data(pkt, newpayload, newlen);
+ sfree(newpayload);
+ }
+
+ /*
+ * Add padding. At least four bytes, and must also bring total
+ * length (minus MAC) up to a multiple of the block size.
+ * If pkt->forcepad is set, make sure the packet is at least that size
+ * after padding.
+ */
+ padding = 4;
+ unencrypted_prefix = (s->out.mac && s->out.etm_mode) ? 4 : 0;
+ padding +=
+ (cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk)
+ % cipherblk;
+ assert(padding <= 255);
+ maclen = s->out.mac ? ssh2_mac_alg(s->out.mac)->len : 0;
+ origlen = pkt->length;
+ for (i = 0; i < padding; i++)
+ put_byte(pkt, 0); /* make space for random padding */
+ random_read(pkt->data + origlen, padding);
+ pkt->data[4] = padding;
+ PUT_32BIT_MSB_FIRST(pkt->data, origlen + padding - 4);
+
+ /* Encrypt length if the scheme requires it */
+ if (s->out.cipher &&
+ (ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_SEPARATE_LENGTH)) {
+ ssh_cipher_encrypt_length(s->out.cipher, pkt->data, 4,
+ s->out.sequence);
+ }
+
+ put_padding(pkt, maclen, 0);
+
+ if (s->out.mac && s->out.etm_mode) {
+ /*
+ * OpenSSH-defined encrypt-then-MAC protocol.
+ */
+ if (s->out.cipher)
+ ssh_cipher_encrypt(s->out.cipher,
+ pkt->data + 4, origlen + padding - 4);
+ ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding,
+ s->out.sequence);
+ } else {
+ /*
+ * SSH-2 standard protocol.
+ */
+ if (s->out.mac)
+ ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding,
+ s->out.sequence);
+ if (s->out.cipher)
+ ssh_cipher_encrypt(s->out.cipher, pkt->data, origlen + padding);
+ }
+
+ s->out.sequence++; /* whether or not we MACed */
+ if (s->out.cipher)
+ ssh_cipher_next_message(s->out.cipher);
+ if (s->out.mac)
+ ssh2_mac_next_message(s->out.mac);
+
+ dts_consume(&s->stats->out, origlen + padding);
+}
+
+static void ssh2_bpp_format_packet(struct ssh2_bpp_state *s, PktOut *pkt)
+{
+ if (pkt->minlen > 0 && !s->out_comp) {
+ /*
+ * If we've been told to pad the packet out to a given minimum
+ * length, but we're not compressing (and hence can't get the
+ * compression to do the padding by pointlessly opening and
+ * closing zlib blocks), then our other strategy is to precede
+ * this message with an SSH_MSG_IGNORE that makes it up to the
+ * right length.
+ *
+ * A third option in principle, and the most obviously
+ * sensible, would be to set the explicit padding field in the
+ * packet to more than its minimum value. Sadly, that turns
+ * out to break some servers (our institutional memory thinks
+ * Cisco in particular) and so we abandoned that idea shortly
+ * after trying it.
+ */
+
+ /*
+ * Calculate the length we expect the real packet to have.
+ */
+ int block, length;
+ PktOut *ignore_pkt;
+
+ block = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 0;
+ if (block < 8)
+ block = 8;
+ length = pkt->length;
+ length += 4; /* minimum 4 byte padding */
+ length += block-1;
+ length -= (length % block);
+ if (s->out.mac)
+ length += ssh2_mac_alg(s->out.mac)->len;
+
+ if (length < pkt->minlen) {
+ /*
+ * We need an ignore message. Calculate its length.
+ */
+ length = pkt->minlen - length;
+
+ /*
+ * And work backwards from that to the length of the
+ * contained string.
+ */
+ if (s->out.mac)
+ length -= ssh2_mac_alg(s->out.mac)->len;
+ length -= 8; /* length field + min padding */
+ length -= 5; /* type code + string length prefix */
+
+ if (length < 0)
+ length = 0;
+
+ ignore_pkt = ssh2_bpp_new_pktout(SSH2_MSG_IGNORE);
+ put_uint32(ignore_pkt, length);
+ size_t origlen = ignore_pkt->length;
+ for (size_t i = 0; i < length; i++)
+ put_byte(ignore_pkt, 0); /* make space for random padding */
+ random_read(ignore_pkt->data + origlen, length);
+ ssh2_bpp_format_packet_inner(s, ignore_pkt);
+ bufchain_add(s->bpp.out_raw, ignore_pkt->data, ignore_pkt->length);
+ ssh_free_pktout(ignore_pkt);
+ }
+ }
+
+ ssh2_bpp_format_packet_inner(s, pkt);
+ bufchain_add(s->bpp.out_raw, pkt->data, pkt->length);
+}
+
+static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp)
+{
+ struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp);
+ PktOut *pkt;
+ int n_userauth;
+
+ /*
+ * Count the userauth packets in the queue.
+ */
+ n_userauth = 0;
+ for (pkt = pq_first(&s->bpp.out_pq); pkt != NULL;
+ pkt = pq_next(&s->bpp.out_pq, pkt))
+ if (userauth_range(pkt->type))
+ n_userauth++;
+
+ if (s->pending_compression && !n_userauth) {
+ /*
+ * We're currently blocked from sending any outgoing packets
+ * until the other end tells us whether we're going to have to
+ * enable compression or not.
+ *
+ * If our end has pushed a userauth packet on the queue, that
+ * must mean it knows that a USERAUTH_SUCCESS is not
+ * immediately forthcoming, so we unblock ourselves and send
+ * up to and including that packet. But in this if statement,
+ * there aren't any, so we're still blocked.
+ */
+ return;
+ }
+
+ if (s->cbc_ignore_workaround) {
+ /*
+ * When using a CBC-mode cipher in SSH-2, it's necessary to
+ * ensure that an attacker can't provide data to be encrypted
+ * using an IV that they know. We ensure this by inserting an
+ * SSH_MSG_IGNORE if the last cipher block of the previous
+ * packet has already been sent to the network (which we
+ * approximate conservatively by checking if it's vanished
+ * from out_raw).
+ */
+ if (bufchain_size(s->bpp.out_raw) <
+ (ssh_cipher_alg(s->out.cipher)->blksize +
+ ssh2_mac_alg(s->out.mac)->len)) {
+ /*
+ * There's less data in out_raw than the MAC size plus the
+ * cipher block size, which means at least one byte of
+ * that cipher block must already have left. Add an
+ * IGNORE.
+ */
+ pkt = ssh_bpp_new_pktout(&s->bpp, SSH2_MSG_IGNORE);
+ put_stringz(pkt, "");
+ ssh2_bpp_format_packet(s, pkt);
+ }
+ }
+
+ while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) {
+ int type = pkt->type;
+
+ if (userauth_range(type))
+ n_userauth--;
+
+ ssh2_bpp_format_packet(s, pkt);
+ ssh_free_pktout(pkt);
+
+ if (n_userauth == 0 && s->out.pending_compression && !s->is_server) {
+ /*
+ * This is the last userauth packet in the queue, so
+ * unless our side decides to send another one in future,
+ * we have to assume will potentially provoke
+ * USERAUTH_SUCCESS. Block (non-userauth) outgoing packets
+ * until we see the reply.
+ */
+ s->pending_compression = true;
+ return;
+ } else if (type == SSH2_MSG_USERAUTH_SUCCESS && s->is_server) {
+ ssh2_bpp_enable_pending_compression(s);
+ }
+ }
+
+ ssh_sendbuffer_changed(bpp->ssh);
+}
diff --git a/ssh/ca-config.c b/ssh/ca-config.c
new file mode 100644
index 00000000..8c180b36
--- /dev/null
+++ b/ssh/ca-config.c
@@ -0,0 +1,497 @@
+/*
+ * Define and handle the configuration dialog box for SSH host CAs,
+ * using the same portable dialog specification API as config.c.
+ */
+
+#include "putty.h"
+#include "dialog.h"
+#include "storage.h"
+#include "tree234.h"
+#include "ssh.h"
+
+const bool has_ca_config_box = true;
+
+#define NRSATYPES 3
+
+struct ca_state {
+ dlgcontrol *ca_name_edit;
+ dlgcontrol *ca_reclist;
+ dlgcontrol *ca_pubkey_edit;
+ dlgcontrol *ca_pubkey_info;
+ dlgcontrol *ca_validity_edit;
+ dlgcontrol *rsa_type_checkboxes[NRSATYPES];
+ char *name, *pubkey, *validity;
+ tree234 *ca_names; /* stores plain 'char *' */
+ ca_options opts;
+ strbuf *ca_pubkey_blob;
+};
+
+static int ca_name_compare(void *av, void *bv)
+{
+ return strcmp((const char *)av, (const char *)bv);
+}
+
+static inline void clear_string_tree(tree234 *t)
+{
+ char *p;
+ while ((p = delpos234(t, 0)) != NULL)
+ sfree(p);
+}
+
+static void ca_state_free(void *vctx)
+{
+ struct ca_state *st = (struct ca_state *)vctx;
+ clear_string_tree(st->ca_names);
+ freetree234(st->ca_names);
+ sfree(st->name);
+ sfree(st->validity);
+ sfree(st);
+}
+
+static void ca_refresh_name_list(struct ca_state *st)
+{
+ clear_string_tree(st->ca_names);
+
+ host_ca_enum *hce = enum_host_ca_start();
+ if (hce) {
+ strbuf *namebuf = strbuf_new();
+
+ while (strbuf_clear(namebuf), enum_host_ca_next(hce, namebuf)) {
+ char *name = dupstr(namebuf->s);
+ char *added = add234(st->ca_names, name);
+ /* Just imaginable that concurrent filesystem access might
+ * cause a repetition; avoid leaking memory if so */
+ if (added != name)
+ sfree(name);
+ }
+
+ strbuf_free(namebuf);
+ enum_host_ca_finish(hce);
+ }
+}
+
+static void set_from_hca(struct ca_state *st, host_ca *hca)
+{
+ sfree(st->name);
+ st->name = dupstr(hca->name ? hca->name : "");
+
+ sfree(st->pubkey);
+ if (hca->ca_public_key)
+ st->pubkey = strbuf_to_str(
+ base64_encode_sb(ptrlen_from_strbuf(hca->ca_public_key), 0));
+ else
+ st->pubkey = dupstr("");
+
+ st->validity = dupstr(hca->validity_expression ?
+ hca->validity_expression : "");
+
+ st->opts = hca->opts; /* structure copy */
+}
+
+static void ca_refresh_pubkey_info(struct ca_state *st, dlgparam *dp)
+{
+ char *text = NULL;
+ ssh_key *key = NULL;
+ strbuf *blob = strbuf_new();
+
+ ptrlen data = ptrlen_from_asciz(st->pubkey);
+
+ if (st->ca_pubkey_blob)
+ strbuf_free(st->ca_pubkey_blob);
+ st->ca_pubkey_blob = NULL;
+
+ if (!data.len) {
+ text = dupstr(" ");
+ goto out;
+ }
+
+ /*
+ * See if we have a plain base64-encoded public key blob.
+ */
+ if (base64_valid(data)) {
+ base64_decode_bs(BinarySink_UPCAST(blob), data);
+ } else {
+ /*
+ * Otherwise, try to decode as if it was a public key _file_.
+ */
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, data);
+ const char *error;
+ if (!ppk_loadpub_s(src, NULL, BinarySink_UPCAST(blob), NULL, &error)) {
+ text = dupprintf("Cannot decode key: %s", error);
+ goto out;
+ }
+ }
+
+ ptrlen alg_name = pubkey_blob_to_alg_name(ptrlen_from_strbuf(blob));
+ if (!alg_name.len) {
+ text = dupstr("Invalid key (no key type)");
+ goto out;
+ }
+
+ const ssh_keyalg *alg = find_pubkey_alg_len(alg_name);
+ if (!alg) {
+ text = dupprintf("Unrecognised key type '%.*s'",
+ PTRLEN_PRINTF(alg_name));
+ goto out;
+ }
+ if (alg->is_certificate) {
+ text = dupprintf("CA key may not be a certificate (type is '%.*s')",
+ PTRLEN_PRINTF(alg_name));
+ goto out;
+ }
+
+ key = ssh_key_new_pub(alg, ptrlen_from_strbuf(blob));
+ if (!key) {
+ text = dupprintf("Invalid '%.*s' key data", PTRLEN_PRINTF(alg_name));
+ goto out;
+ }
+
+ text = ssh2_fingerprint(key, SSH_FPTYPE_DEFAULT);
+ st->ca_pubkey_blob = blob;
+ blob = NULL; /* prevent free */
+
+ out:
+ dlg_text_set(st->ca_pubkey_info, dp, text);
+ if (key)
+ ssh_key_free(key);
+ sfree(text);
+ if (blob)
+ strbuf_free(blob);
+}
+
+static void ca_load_selected_record(struct ca_state *st, dlgparam *dp)
+{
+ int i = dlg_listbox_index(st->ca_reclist, dp);
+ if (i < 0) {
+ dlg_beep(dp);
+ return;
+ }
+ const char *name = index234(st->ca_names, i);
+ if (!name) { /* in case the list box and the tree got out of sync */
+ dlg_beep(dp);
+ return;
+ }
+ host_ca *hca = host_ca_load(name);
+ if (!hca) {
+ char *msg = dupprintf("Unable to load host CA record '%s'", name);
+ dlg_error_msg(dp, msg);
+ sfree(msg);
+ return;
+ }
+
+ set_from_hca(st, hca);
+ host_ca_free(hca);
+
+ dlg_refresh(st->ca_name_edit, dp);
+ dlg_refresh(st->ca_pubkey_edit, dp);
+ dlg_refresh(st->ca_validity_edit, dp);
+ for (size_t i = 0; i < NRSATYPES; i++)
+ dlg_refresh(st->rsa_type_checkboxes[i], dp);
+ ca_refresh_pubkey_info(st, dp);
+}
+
+static void ca_ok_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ if (event == EVENT_ACTION)
+ dlg_end(dp, 0);
+}
+
+static void ca_name_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ if (event == EVENT_REFRESH) {
+ dlg_editbox_set(ctrl, dp, st->name);
+ } else if (event == EVENT_VALCHANGE) {
+ sfree(st->name);
+ st->name = dlg_editbox_get(ctrl, dp);
+
+ /*
+ * Try to auto-select the typed name in the list.
+ */
+ int index;
+ if (!findrelpos234(st->ca_names, st->name, NULL, REL234_GE, &index))
+ index = count234(st->ca_names) - 1;
+ if (index >= 0)
+ dlg_listbox_select(st->ca_reclist, dp, index);
+ }
+}
+
+static void ca_reclist_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ if (event == EVENT_REFRESH) {
+ dlg_update_start(ctrl, dp);
+ dlg_listbox_clear(ctrl, dp);
+ const char *name;
+ for (int i = 0; (name = index234(st->ca_names, i)) != NULL; i++)
+ dlg_listbox_add(ctrl, dp, name);
+ dlg_update_done(ctrl, dp);
+ } else if (event == EVENT_ACTION) {
+ /* Double-clicking a session loads it */
+ ca_load_selected_record(st, dp);
+ }
+}
+
+static void ca_load_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ if (event == EVENT_ACTION) {
+ ca_load_selected_record(st, dp);
+ }
+}
+
+static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ if (event == EVENT_ACTION) {
+ if (!*st->validity) {
+ dlg_error_msg(dp, "No validity expression configured "
+ "for this key");
+ return;
+ }
+
+ char *error_msg;
+ ptrlen error_loc;
+ if (!cert_expr_valid(st->validity, &error_msg, &error_loc)) {
+ char *error_full = dupprintf("Error in expression: %s", error_msg);
+ dlg_error_msg(dp, error_full);
+ dlg_set_focus(st->ca_validity_edit, dp);
+ dlg_editbox_select_range(
+ st->ca_validity_edit, dp,
+ (const char *)error_loc.ptr - st->validity, error_loc.len);
+ sfree(error_msg);
+ sfree(error_full);
+ return;
+ }
+
+ if (!st->ca_pubkey_blob) {
+ dlg_error_msg(dp, "No valid CA public key entered");
+ return;
+ }
+
+ host_ca *hca = snew(host_ca);
+ memset(hca, 0, sizeof(*hca));
+ hca->name = dupstr(st->name);
+ hca->ca_public_key = strbuf_dup(ptrlen_from_strbuf(
+ st->ca_pubkey_blob));
+ hca->validity_expression = dupstr(st->validity);
+ hca->opts = st->opts; /* structure copy */
+
+ char *error = host_ca_save(hca);
+ host_ca_free(hca);
+
+ if (error) {
+ dlg_error_msg(dp, error);
+ sfree(error);
+ } else {
+ ca_refresh_name_list(st);
+ dlg_refresh(st->ca_reclist, dp);
+ }
+ }
+}
+
+static void ca_delete_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ if (event == EVENT_ACTION) {
+ int i = dlg_listbox_index(st->ca_reclist, dp);
+ if (i < 0) {
+ dlg_beep(dp);
+ return;
+ }
+ const char *name = index234(st->ca_names, i);
+ if (!name) { /* in case the list box and the tree got out of sync */
+ dlg_beep(dp);
+ return;
+ }
+
+ char *error = host_ca_delete(name);
+ if (error) {
+ dlg_error_msg(dp, error);
+ sfree(error);
+ } else {
+ ca_refresh_name_list(st);
+ dlg_refresh(st->ca_reclist, dp);
+ }
+ }
+}
+
+static void ca_pubkey_edit_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ if (event == EVENT_REFRESH) {
+ dlg_editbox_set(ctrl, dp, st->pubkey);
+ } else if (event == EVENT_VALCHANGE) {
+ sfree(st->pubkey);
+ st->pubkey = dlg_editbox_get(ctrl, dp);
+ ca_refresh_pubkey_info(st, dp);
+ }
+}
+
+static void ca_pubkey_file_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ if (event == EVENT_ACTION) {
+ Filename *filename = dlg_filesel_get(ctrl, dp);
+ strbuf *keyblob = strbuf_new();
+ const char *load_error;
+ bool ok = ppk_loadpub_f(filename, NULL, BinarySink_UPCAST(keyblob),
+ NULL, &load_error);
+ if (!ok) {
+ char *message = dupprintf(
+ "Unable to load public key from '%s': %s",
+ filename_to_str(filename), load_error);
+ dlg_error_msg(dp, message);
+ sfree(message);
+ } else {
+ sfree(st->pubkey);
+ st->pubkey = strbuf_to_str(
+ base64_encode_sb(ptrlen_from_strbuf(keyblob), 0));
+ dlg_refresh(st->ca_pubkey_edit, dp);
+ }
+ filename_free(filename);
+ strbuf_free(keyblob);
+ }
+}
+
+static void ca_validity_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ if (event == EVENT_REFRESH) {
+ dlg_editbox_set(ctrl, dp, st->validity);
+ } else if (event == EVENT_VALCHANGE) {
+ sfree(st->validity);
+ st->validity = dlg_editbox_get(ctrl, dp);
+ }
+}
+
+static void ca_rsa_type_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ struct ca_state *st = (struct ca_state *)ctrl->context.p;
+ size_t offset = ctrl->context2.i;
+ bool *option = (bool *)((char *)&st->opts + offset);
+
+ if (event == EVENT_REFRESH) {
+ dlg_checkbox_set(ctrl, dp, *option);
+ } else if (event == EVENT_VALCHANGE) {
+ *option = dlg_checkbox_get(ctrl, dp);
+ }
+}
+
+void setup_ca_config_box(struct controlbox *b)
+{
+ struct controlset *s;
+ dlgcontrol *c;
+
+ /* Internal state for manipulating the host CA system */
+ struct ca_state *st = (struct ca_state *)ctrl_alloc_with_free(
+ b, sizeof(struct ca_state), ca_state_free);
+ memset(st, 0, sizeof(*st));
+ st->ca_names = newtree234(ca_name_compare);
+ st->validity = dupstr("");
+ ca_refresh_name_list(st);
+
+ /* Initialise the settings to a default blank host_ca */
+ {
+ host_ca *hca = host_ca_new();
+ set_from_hca(st, hca);
+ host_ca_free(hca);
+ }
+
+ /* Action area, with the Done button in it */
+ s = ctrl_getset(b, "", "", "");
+ ctrl_columns(s, 5, 20, 20, 20, 20, 20);
+ c = ctrl_pushbutton(s, "Done", 'o', HELPCTX(ssh_kex_cert),
+ ca_ok_handler, P(st));
+ c->button.iscancel = true;
+ c->column = 4;
+
+ /* Load/save box, as similar as possible to the main saved sessions one */
+ s = ctrl_getset(b, "Main", "loadsave",
+ "Load, save or delete a host CA record");
+ ctrl_columns(s, 2, 75, 25);
+ c = ctrl_editbox(s, "Name for this CA (shown in log messages)",
+ 'n', 100, HELPCTX(ssh_kex_cert),
+ ca_name_handler, P(st), P(NULL));
+ c->column = 0;
+ st->ca_name_edit = c;
+ /* Reset columns so that the buttons are alongside the list, rather
+ * than alongside that edit box. */
+ ctrl_columns(s, 1, 100);
+ ctrl_columns(s, 2, 75, 25);
+ c = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(ssh_kex_cert),
+ ca_reclist_handler, P(st));
+ c->column = 0;
+ c->listbox.height = 6;
+ st->ca_reclist = c;
+ c = ctrl_pushbutton(s, "Load", 'l', HELPCTX(ssh_kex_cert),
+ ca_load_handler, P(st));
+ c->column = 1;
+ c = ctrl_pushbutton(s, "Save", 'v', HELPCTX(ssh_kex_cert),
+ ca_save_handler, P(st));
+ c->column = 1;
+ c = ctrl_pushbutton(s, "Delete", 'd', HELPCTX(ssh_kex_cert),
+ ca_delete_handler, P(st));
+ c->column = 1;
+
+ s = ctrl_getset(b, "Main", "pubkey", "Public key for this CA record");
+
+ ctrl_columns(s, 2, 75, 25);
+ c = ctrl_editbox(s, "Public key of certification authority", 'k', 100,
+ HELPCTX(ssh_kex_cert), ca_pubkey_edit_handler,
+ P(st), P(NULL));
+ c->column = 0;
+ st->ca_pubkey_edit = c;
+ c = ctrl_filesel(s, "Read from file", NO_SHORTCUT, NULL, false,
+ "Select public key file of certification authority",
+ HELPCTX(ssh_kex_cert), ca_pubkey_file_handler, P(st));
+ c->fileselect.just_button = true;
+ c->align_next_to = st->ca_pubkey_edit;
+ c->column = 1;
+ ctrl_columns(s, 1, 100);
+ st->ca_pubkey_info = c = ctrl_text(s, " ", HELPCTX(ssh_kex_cert));
+ c->text.wrap = false;
+
+ s = ctrl_getset(b, "Main", "options", "What this CA is trusted to do");
+
+ c = ctrl_editbox(s, "Valid hosts this key is trusted to certify", 'h', 100,
+ HELPCTX(ssh_cert_valid_expr), ca_validity_handler,
+ P(st), P(NULL));
+ st->ca_validity_edit = c;
+
+ ctrl_columns(s, 4, 44, 18, 18, 18);
+ c = ctrl_text(s, "Signature types (RSA keys only):",
+ HELPCTX(ssh_cert_rsa_hash));
+ c->column = 0;
+ dlgcontrol *sigtypelabel = c;
+ c = ctrl_checkbox(s, "SHA-1", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash),
+ ca_rsa_type_handler, P(st));
+ c->column = 1;
+ c->align_next_to = sigtypelabel;
+ c->context2 = I(offsetof(ca_options, permit_rsa_sha1));
+ st->rsa_type_checkboxes[0] = c;
+ c = ctrl_checkbox(s, "SHA-256", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash),
+ ca_rsa_type_handler, P(st));
+ c->column = 2;
+ c->align_next_to = sigtypelabel;
+ c->context2 = I(offsetof(ca_options, permit_rsa_sha256));
+ st->rsa_type_checkboxes[1] = c;
+ c = ctrl_checkbox(s, "SHA-512", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash),
+ ca_rsa_type_handler, P(st));
+ c->column = 3;
+ c->align_next_to = sigtypelabel;
+ c->context2 = I(offsetof(ca_options, permit_rsa_sha512));
+ st->rsa_type_checkboxes[2] = c;
+ ctrl_columns(s, 1, 100);
+}
diff --git a/ssh1censor.c b/ssh/censor1.c
index 8dacd3a0..8dacd3a0 100644
--- a/ssh1censor.c
+++ b/ssh/censor1.c
diff --git a/ssh2censor.c b/ssh/censor2.c
index 31ad8149..31ad8149 100644
--- a/ssh2censor.c
+++ b/ssh/censor2.c
diff --git a/ssh/channel.h b/ssh/channel.h
new file mode 100644
index 00000000..d4eb78aa
--- /dev/null
+++ b/ssh/channel.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/ssh/common.c b/ssh/common.c
new file mode 100644
index 00000000..a1b4d77d
--- /dev/null
+++ b/ssh/common.c
@@ -0,0 +1,1163 @@
+/*
+ * Supporting routines used in common by all the various components of
+ * the SSH system.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "mpint.h"
+#include "ssh.h"
+#include "storage.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "channel.h"
+
+/* ----------------------------------------------------------------------
+ * Implementation of PacketQueue.
+ */
+
+static void pq_ensure_unlinked(PacketQueueNode *node)
+{
+ if (node->on_free_queue) {
+ node->next->prev = node->prev;
+ node->prev->next = node->next;
+ } else {
+ assert(!node->next);
+ assert(!node->prev);
+ }
+}
+
+void pq_base_push(PacketQueueBase *pqb, PacketQueueNode *node)
+{
+ pq_ensure_unlinked(node);
+ node->next = &pqb->end;
+ node->prev = pqb->end.prev;
+ node->next->prev = node;
+ node->prev->next = node;
+ pqb->total_size += node->formal_size;
+
+ if (pqb->ic)
+ queue_idempotent_callback(pqb->ic);
+}
+
+void pq_base_push_front(PacketQueueBase *pqb, PacketQueueNode *node)
+{
+ pq_ensure_unlinked(node);
+ node->prev = &pqb->end;
+ node->next = pqb->end.next;
+ node->next->prev = node;
+ node->prev->next = node;
+ pqb->total_size += node->formal_size;
+
+ if (pqb->ic)
+ queue_idempotent_callback(pqb->ic);
+}
+
+static PacketQueueNode pktin_freeq_head = {
+ &pktin_freeq_head, &pktin_freeq_head, true
+};
+
+static void pktin_free_queue_callback(void *vctx)
+{
+ while (pktin_freeq_head.next != &pktin_freeq_head) {
+ PacketQueueNode *node = pktin_freeq_head.next;
+ PktIn *pktin = container_of(node, PktIn, qnode);
+ pktin_freeq_head.next = node->next;
+ sfree(pktin);
+ }
+
+ pktin_freeq_head.prev = &pktin_freeq_head;
+}
+
+static IdempotentCallback ic_pktin_free = {
+ pktin_free_queue_callback, NULL, false
+};
+
+static inline void pq_unlink_common(PacketQueueBase *pqb,
+ PacketQueueNode *node)
+{
+ node->next->prev = node->prev;
+ node->prev->next = node->next;
+
+ /* Check total_size doesn't drift out of sync downwards, by
+ * ensuring it doesn't underflow when we do this subtraction */
+ assert(pqb->total_size >= node->formal_size);
+ pqb->total_size -= node->formal_size;
+
+ /* Check total_size doesn't drift out of sync upwards, by checking
+ * that it's returned to exactly zero whenever a queue is
+ * emptied */
+ assert(pqb->end.next != &pqb->end || pqb->total_size == 0);
+}
+
+static PktIn *pq_in_after(PacketQueueBase *pqb,
+ PacketQueueNode *prev, bool pop)
+{
+ PacketQueueNode *node = prev->next;
+ if (node == &pqb->end)
+ return NULL;
+
+ if (pop) {
+ pq_unlink_common(pqb, node);
+
+ node->prev = pktin_freeq_head.prev;
+ node->next = &pktin_freeq_head;
+ node->next->prev = node;
+ node->prev->next = node;
+ node->on_free_queue = true;
+
+ queue_idempotent_callback(&ic_pktin_free);
+ }
+
+ return container_of(node, PktIn, qnode);
+}
+
+static PktOut *pq_out_after(PacketQueueBase *pqb,
+ PacketQueueNode *prev, bool pop)
+{
+ PacketQueueNode *node = prev->next;
+ if (node == &pqb->end)
+ return NULL;
+
+ if (pop) {
+ pq_unlink_common(pqb, node);
+
+ node->prev = node->next = NULL;
+ }
+
+ return container_of(node, PktOut, qnode);
+}
+
+void pq_in_init(PktInQueue *pq)
+{
+ pq->pqb.ic = NULL;
+ pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end;
+ pq->after = pq_in_after;
+ pq->pqb.total_size = 0;
+}
+
+void pq_out_init(PktOutQueue *pq)
+{
+ pq->pqb.ic = NULL;
+ pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end;
+ pq->after = pq_out_after;
+ pq->pqb.total_size = 0;
+}
+
+void pq_in_clear(PktInQueue *pq)
+{
+ PktIn *pkt;
+ pq->pqb.ic = NULL;
+ while ((pkt = pq_pop(pq)) != NULL) {
+ /* No need to actually free these packets: pq_pop on a
+ * PktInQueue will automatically move them to the free
+ * queue. */
+ }
+}
+
+void pq_out_clear(PktOutQueue *pq)
+{
+ PktOut *pkt;
+ pq->pqb.ic = NULL;
+ while ((pkt = pq_pop(pq)) != NULL)
+ ssh_free_pktout(pkt);
+}
+
+/*
+ * Concatenate the contents of the two queues q1 and q2, and leave the
+ * result in qdest. qdest must be either empty, or one of the input
+ * queues.
+ */
+void pq_base_concatenate(PacketQueueBase *qdest,
+ PacketQueueBase *q1, PacketQueueBase *q2)
+{
+ struct PacketQueueNode *head1, *tail1, *head2, *tail2;
+
+ size_t total_size = q1->total_size + q2->total_size;
+
+ /*
+ * Extract the contents from both input queues, and empty them.
+ */
+
+ head1 = (q1->end.next == &q1->end ? NULL : q1->end.next);
+ tail1 = (q1->end.prev == &q1->end ? NULL : q1->end.prev);
+ head2 = (q2->end.next == &q2->end ? NULL : q2->end.next);
+ tail2 = (q2->end.prev == &q2->end ? NULL : q2->end.prev);
+
+ q1->end.next = q1->end.prev = &q1->end;
+ q2->end.next = q2->end.prev = &q2->end;
+ q1->total_size = q2->total_size = 0;
+
+ /*
+ * Link the two lists together, handling the case where one or
+ * both is empty.
+ */
+
+ if (tail1)
+ tail1->next = head2;
+ else
+ head1 = head2;
+
+ if (head2)
+ head2->prev = tail1;
+ else
+ tail2 = tail1;
+
+ /*
+ * Check the destination queue is currently empty. (If it was one
+ * of the input queues, then it will be, because we emptied both
+ * of those just a moment ago.)
+ */
+
+ assert(qdest->end.next == &qdest->end);
+ assert(qdest->end.prev == &qdest->end);
+
+ /*
+ * If our concatenated list has anything in it, then put it in
+ * dest.
+ */
+
+ if (!head1) {
+ assert(!tail2);
+ } else {
+ assert(tail2);
+ qdest->end.next = head1;
+ qdest->end.prev = tail2;
+ head1->prev = &qdest->end;
+ tail2->next = &qdest->end;
+
+ if (qdest->ic)
+ queue_idempotent_callback(qdest->ic);
+ }
+
+ qdest->total_size = total_size;
+}
+
+/* ----------------------------------------------------------------------
+ * Low-level functions for the packet structures themselves.
+ */
+
+static void ssh_pkt_BinarySink_write(BinarySink *bs,
+ const void *data, size_t len);
+PktOut *ssh_new_packet(void)
+{
+ PktOut *pkt = snew(PktOut);
+
+ BinarySink_INIT(pkt, ssh_pkt_BinarySink_write);
+ pkt->data = NULL;
+ pkt->length = 0;
+ pkt->maxlen = 0;
+ pkt->downstream_id = 0;
+ pkt->additional_log_text = NULL;
+ pkt->qnode.next = pkt->qnode.prev = NULL;
+ pkt->qnode.on_free_queue = false;
+
+ return pkt;
+}
+
+static void ssh_pkt_adddata(PktOut *pkt, const void *data, int len)
+{
+ sgrowarrayn_nm(pkt->data, pkt->maxlen, pkt->length, len);
+ memcpy(pkt->data + pkt->length, data, len);
+ pkt->length += len;
+ pkt->qnode.formal_size = pkt->length;
+}
+
+static void ssh_pkt_BinarySink_write(BinarySink *bs,
+ const void *data, size_t len)
+{
+ PktOut *pkt = BinarySink_DOWNCAST(bs, PktOut);
+ ssh_pkt_adddata(pkt, data, len);
+}
+
+void ssh_free_pktout(PktOut *pkt)
+{
+ sfree(pkt->data);
+ sfree(pkt);
+}
+
+/* ----------------------------------------------------------------------
+ * Implement zombiechan_new() and its trivial vtable.
+ */
+
+static void zombiechan_free(Channel *chan);
+static size_t zombiechan_send(
+ Channel *chan, bool is_stderr, const void *, size_t);
+static void zombiechan_set_input_wanted(Channel *chan, bool wanted);
+static void zombiechan_do_nothing(Channel *chan);
+static void zombiechan_open_failure(Channel *chan, const char *);
+static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof);
+static char *zombiechan_log_close_msg(Channel *chan) { return NULL; }
+
+static const ChannelVtable zombiechan_channelvt = {
+ .free = zombiechan_free,
+ .open_confirmation = zombiechan_do_nothing,
+ .open_failed = zombiechan_open_failure,
+ .send = zombiechan_send,
+ .send_eof = zombiechan_do_nothing,
+ .set_input_wanted = zombiechan_set_input_wanted,
+ .log_close_msg = zombiechan_log_close_msg,
+ .want_close = zombiechan_want_close,
+ .rcvd_exit_status = chan_no_exit_status,
+ .rcvd_exit_signal = chan_no_exit_signal,
+ .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
+ .run_shell = chan_no_run_shell,
+ .run_command = chan_no_run_command,
+ .run_subsystem = chan_no_run_subsystem,
+ .enable_x11_forwarding = chan_no_enable_x11_forwarding,
+ .enable_agent_forwarding = chan_no_enable_agent_forwarding,
+ .allocate_pty = chan_no_allocate_pty,
+ .set_env = chan_no_set_env,
+ .send_break = chan_no_send_break,
+ .send_signal = chan_no_send_signal,
+ .change_window_size = chan_no_change_window_size,
+ .request_response = chan_no_request_response,
+};
+
+Channel *zombiechan_new(void)
+{
+ Channel *chan = snew(Channel);
+ chan->vt = &zombiechan_channelvt;
+ chan->initial_fixed_window_size = 0;
+ return chan;
+}
+
+static void zombiechan_free(Channel *chan)
+{
+ assert(chan->vt == &zombiechan_channelvt);
+ sfree(chan);
+}
+
+static void zombiechan_do_nothing(Channel *chan)
+{
+ assert(chan->vt == &zombiechan_channelvt);
+}
+
+static void zombiechan_open_failure(Channel *chan, const char *errtext)
+{
+ assert(chan->vt == &zombiechan_channelvt);
+}
+
+static size_t zombiechan_send(Channel *chan, bool is_stderr,
+ const void *data, size_t length)
+{
+ assert(chan->vt == &zombiechan_channelvt);
+ return 0;
+}
+
+static void zombiechan_set_input_wanted(Channel *chan, bool enable)
+{
+ assert(chan->vt == &zombiechan_channelvt);
+}
+
+static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof)
+{
+ return true;
+}
+
+/* ----------------------------------------------------------------------
+ * Common routines for handling SSH tty modes.
+ */
+
+static unsigned real_ttymode_opcode(unsigned our_opcode, int ssh_version)
+{
+ switch (our_opcode) {
+ case TTYMODE_ISPEED:
+ return ssh_version == 1 ? TTYMODE_ISPEED_SSH1 : TTYMODE_ISPEED_SSH2;
+ case TTYMODE_OSPEED:
+ return ssh_version == 1 ? TTYMODE_OSPEED_SSH1 : TTYMODE_OSPEED_SSH2;
+ default:
+ return our_opcode;
+ }
+}
+
+static unsigned our_ttymode_opcode(unsigned real_opcode, int ssh_version)
+{
+ if (ssh_version == 1) {
+ switch (real_opcode) {
+ case TTYMODE_ISPEED_SSH1:
+ return TTYMODE_ISPEED;
+ case TTYMODE_OSPEED_SSH1:
+ return TTYMODE_OSPEED;
+ default:
+ return real_opcode;
+ }
+ } else {
+ switch (real_opcode) {
+ case TTYMODE_ISPEED_SSH2:
+ return TTYMODE_ISPEED;
+ case TTYMODE_OSPEED_SSH2:
+ return TTYMODE_OSPEED;
+ default:
+ return real_opcode;
+ }
+ }
+}
+
+struct ssh_ttymodes get_ttymodes_from_conf(Seat *seat, Conf *conf)
+{
+ struct ssh_ttymodes modes;
+ size_t i;
+
+ static const struct mode_name_type {
+ const char *mode;
+ int opcode;
+ enum { TYPE_CHAR, TYPE_BOOL } type;
+ } modes_names_types[] = {
+ #define TTYMODE_CHAR(name, val, index) { #name, val, TYPE_CHAR },
+ #define TTYMODE_FLAG(name, val, field, mask) { #name, val, TYPE_BOOL },
+ #include "ttymode-list.h"
+ #undef TTYMODE_CHAR
+ #undef TTYMODE_FLAG
+ };
+
+ memset(&modes, 0, sizeof(modes));
+
+ for (i = 0; i < lenof(modes_names_types); i++) {
+ const struct mode_name_type *mode = &modes_names_types[i];
+ const char *sval = conf_get_str_str(conf, CONF_ttymodes, mode->mode);
+ char *to_free = NULL;
+
+ if (!sval)
+ sval = "N"; /* just in case */
+
+ /*
+ * sval[0] can be
+ * - 'V', indicating that an explicit value follows it;
+ * - 'A', indicating that we should pass the value through from
+ * the local environment via get_ttymode; or
+ * - 'N', indicating that we should explicitly not send this
+ * mode.
+ */
+ if (sval[0] == 'A') {
+ sval = to_free = seat_get_ttymode(seat, mode->mode);
+ } else if (sval[0] == 'V') {
+ sval++; /* skip the 'V' */
+ } else {
+ /* else 'N', or something from the future we don't understand */
+ continue;
+ }
+
+ if (sval) {
+ /*
+ * Parse the string representation of the tty mode
+ * into the integer value it will take on the wire.
+ */
+ unsigned ival = 0;
+
+ switch (mode->type) {
+ case TYPE_CHAR:
+ if (*sval) {
+ char *next = NULL;
+ /* We know ctrlparse won't write to the string, so
+ * casting away const is ugly but allowable. */
+ ival = ctrlparse((char *)sval, &next);
+ if (!next)
+ ival = sval[0];
+ } else {
+ ival = 255; /* special value meaning "don't set" */
+ }
+ break;
+ case TYPE_BOOL:
+ if (stricmp(sval, "yes") == 0 ||
+ stricmp(sval, "on") == 0 ||
+ stricmp(sval, "true") == 0 ||
+ stricmp(sval, "+") == 0)
+ ival = 1; /* true */
+ else if (stricmp(sval, "no") == 0 ||
+ stricmp(sval, "off") == 0 ||
+ stricmp(sval, "false") == 0 ||
+ stricmp(sval, "-") == 0)
+ ival = 0; /* false */
+ else
+ ival = (atoi(sval) != 0);
+ break;
+ default:
+ unreachable("Bad mode->type");
+ }
+
+ modes.have_mode[mode->opcode] = true;
+ modes.mode_val[mode->opcode] = ival;
+ }
+
+ sfree(to_free);
+ }
+
+ {
+ unsigned ospeed, ispeed;
+
+ /* Unpick the terminal-speed config string. */
+ ospeed = ispeed = 38400; /* last-resort defaults */
+ sscanf(conf_get_str(conf, CONF_termspeed), "%u,%u", &ospeed, &ispeed);
+ /* Currently we unconditionally set these */
+ modes.have_mode[TTYMODE_ISPEED] = true;
+ modes.mode_val[TTYMODE_ISPEED] = ispeed;
+ modes.have_mode[TTYMODE_OSPEED] = true;
+ modes.mode_val[TTYMODE_OSPEED] = ospeed;
+ }
+
+ return modes;
+}
+
+struct ssh_ttymodes read_ttymodes_from_packet(
+ BinarySource *bs, int ssh_version)
+{
+ struct ssh_ttymodes modes;
+ memset(&modes, 0, sizeof(modes));
+
+ while (1) {
+ unsigned real_opcode, our_opcode;
+
+ real_opcode = get_byte(bs);
+ if (real_opcode == TTYMODE_END_OF_LIST)
+ break;
+ if (real_opcode >= 160) {
+ /*
+ * RFC 4254 (and the SSH 1.5 spec): "Opcodes 160 to 255
+ * are not yet defined, and cause parsing to stop (they
+ * should only be used after any other data)."
+ *
+ * My interpretation of this is that if one of these
+ * opcodes appears, it's not a parse _error_, but it is
+ * something that we don't know how to parse even well
+ * enough to step over it to find the next opcode, so we
+ * stop parsing now and assume that the rest of the string
+ * is composed entirely of things we don't understand and
+ * (as usual for unsupported terminal modes) silently
+ * ignore.
+ */
+ return modes;
+ }
+
+ our_opcode = our_ttymode_opcode(real_opcode, ssh_version);
+ assert(our_opcode < TTYMODE_LIMIT);
+ modes.have_mode[our_opcode] = true;
+
+ if (ssh_version == 1 && real_opcode >= 1 && real_opcode <= 127)
+ modes.mode_val[our_opcode] = get_byte(bs);
+ else
+ modes.mode_val[our_opcode] = get_uint32(bs);
+ }
+
+ return modes;
+}
+
+void write_ttymodes_to_packet(BinarySink *bs, int ssh_version,
+ struct ssh_ttymodes modes)
+{
+ unsigned i;
+
+ for (i = 0; i < TTYMODE_LIMIT; i++) {
+ if (modes.have_mode[i]) {
+ unsigned val = modes.mode_val[i];
+ unsigned opcode = real_ttymode_opcode(i, ssh_version);
+
+ put_byte(bs, opcode);
+ if (ssh_version == 1 && opcode >= 1 && opcode <= 127)
+ put_byte(bs, val);
+ else
+ put_uint32(bs, val);
+ }
+ }
+
+ put_byte(bs, TTYMODE_END_OF_LIST);
+}
+
+/* ----------------------------------------------------------------------
+ * Routine for allocating a new channel ID, given a means of finding
+ * the index field in a given channel structure.
+ */
+
+unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset)
+{
+ const unsigned CHANNEL_NUMBER_OFFSET = 256;
+ search234_state ss;
+
+ /*
+ * First-fit allocation of channel numbers: we always pick the
+ * lowest unused one.
+ *
+ * Every channel before that, and no channel after it, has an ID
+ * exactly equal to its tree index plus CHANNEL_NUMBER_OFFSET. So
+ * we can use the search234 system to identify the length of that
+ * initial sequence, in a single log-time pass down the channels
+ * tree.
+ */
+ search234_start(&ss, channels);
+ while (ss.element) {
+ unsigned localid = *(unsigned *)((char *)ss.element + localid_offset);
+ if (localid == ss.index + CHANNEL_NUMBER_OFFSET)
+ search234_step(&ss, +1);
+ else
+ search234_step(&ss, -1);
+ }
+
+ /*
+ * Now ss.index gives exactly the number of channels in that
+ * initial sequence. So adding CHANNEL_NUMBER_OFFSET to it must
+ * give precisely the lowest unused channel number.
+ */
+ return ss.index + CHANNEL_NUMBER_OFFSET;
+}
+
+/* ----------------------------------------------------------------------
+ * Functions for handling the comma-separated strings used to store
+ * lists of protocol identifiers in SSH-2.
+ */
+
+void add_to_commasep_pl(strbuf *buf, ptrlen data)
+{
+ if (buf->len > 0)
+ put_byte(buf, ',');
+ put_datapl(buf, data);
+}
+
+void add_to_commasep(strbuf *buf, const char *data)
+{
+ add_to_commasep_pl(buf, ptrlen_from_asciz(data));
+}
+
+bool get_commasep_word(ptrlen *list, ptrlen *word)
+{
+ const char *comma;
+
+ /*
+ * Discard empty list elements, should there be any, because we
+ * never want to return one as if it was a real string. (This
+ * introduces a mild tolerance of badly formatted data in lists we
+ * receive, but I think that's acceptable.)
+ */
+ while (list->len > 0 && *(const char *)list->ptr == ',') {
+ list->ptr = (const char *)list->ptr + 1;
+ list->len--;
+ }
+
+ if (!list->len)
+ return false;
+
+ comma = memchr(list->ptr, ',', list->len);
+ if (!comma) {
+ *word = *list;
+ list->len = 0;
+ } else {
+ size_t wordlen = comma - (const char *)list->ptr;
+ word->ptr = list->ptr;
+ word->len = wordlen;
+ list->ptr = (const char *)list->ptr + wordlen + 1;
+ list->len -= wordlen + 1;
+ }
+ return true;
+}
+
+/* ----------------------------------------------------------------------
+ * Functions for translating SSH packet type codes into their symbolic
+ * string names.
+ */
+
+#define TRANSLATE_UNIVERSAL(y, name, value) \
+ if (type == value) return #name;
+#define TRANSLATE_KEX(y, name, value, ctx) \
+ if (type == value && pkt_kctx == ctx) return #name;
+#define TRANSLATE_AUTH(y, name, value, ctx) \
+ if (type == value && pkt_actx == ctx) return #name;
+
+const char *ssh1_pkt_type(int type)
+{
+ SSH1_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, y);
+ return "unknown";
+}
+const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type)
+{
+ SSH2_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, TRANSLATE_KEX, TRANSLATE_AUTH, y);
+ return "unknown";
+}
+
+#undef TRANSLATE_UNIVERSAL
+#undef TRANSLATE_KEX
+#undef TRANSLATE_AUTH
+
+/* ----------------------------------------------------------------------
+ * Common helper function for clients and implementations of
+ * PacketProtocolLayer.
+ */
+
+void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new)
+{
+ new->bpp = old->bpp;
+ ssh_ppl_setup_queues(new, old->in_pq, old->out_pq);
+ new->selfptr = old->selfptr;
+ new->seat = old->seat;
+ new->ssh = old->ssh;
+
+ *new->selfptr = new;
+ ssh_ppl_free(old);
+
+ /* The new layer might need to be the first one that sends a
+ * packet, so trigger a call to its main coroutine immediately. If
+ * it doesn't need to go first, the worst that will do is return
+ * straight away. */
+ queue_idempotent_callback(&new->ic_process_queue);
+}
+
+void ssh_ppl_free(PacketProtocolLayer *ppl)
+{
+ delete_callbacks_for_context(ppl);
+ ppl->vt->free(ppl);
+}
+
+static void ssh_ppl_ic_process_queue_callback(void *context)
+{
+ PacketProtocolLayer *ppl = (PacketProtocolLayer *)context;
+ ssh_ppl_process_queue(ppl);
+}
+
+void ssh_ppl_setup_queues(PacketProtocolLayer *ppl,
+ PktInQueue *inq, PktOutQueue *outq)
+{
+ ppl->in_pq = inq;
+ ppl->out_pq = outq;
+ ppl->in_pq->pqb.ic = &ppl->ic_process_queue;
+ ppl->ic_process_queue.fn = ssh_ppl_ic_process_queue_callback;
+ ppl->ic_process_queue.ctx = ppl;
+
+ /* If there's already something on the input queue, it will want
+ * handling immediately. */
+ if (pq_peek(ppl->in_pq))
+ queue_idempotent_callback(&ppl->ic_process_queue);
+}
+
+void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text)
+{
+ /* Messages sent via this function are from the SSH layer, not
+ * from the server-side process, so they always have the stderr
+ * flag set. */
+ seat_stderr_pl(ppl->seat, ptrlen_from_asciz(text));
+ sfree(text);
+}
+
+size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl)
+{
+ return ppl->out_pq->pqb.total_size;
+}
+
+static void ssh_ppl_prompts_callback(void *ctx)
+{
+ ssh_ppl_process_queue((PacketProtocolLayer *)ctx);
+}
+
+prompts_t *ssh_ppl_new_prompts(PacketProtocolLayer *ppl)
+{
+ prompts_t *p = new_prompts();
+ p->callback = ssh_ppl_prompts_callback;
+ p->callback_ctx = ppl;
+ return p;
+}
+
+/* ----------------------------------------------------------------------
+ * Common helper functions for clients and implementations of
+ * BinaryPacketProtocol.
+ */
+
+static void ssh_bpp_input_raw_data_callback(void *context)
+{
+ BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context;
+ Ssh *ssh = bpp->ssh; /* in case bpp is about to get freed */
+ ssh_bpp_handle_input(bpp);
+ /* If we've now cleared enough backlog on the input connection, we
+ * may need to unfreeze it. */
+ ssh_conn_processed_data(ssh);
+}
+
+static void ssh_bpp_output_packet_callback(void *context)
+{
+ BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context;
+ ssh_bpp_handle_output(bpp);
+}
+
+void ssh_bpp_common_setup(BinaryPacketProtocol *bpp)
+{
+ pq_in_init(&bpp->in_pq);
+ pq_out_init(&bpp->out_pq);
+ bpp->input_eof = false;
+ bpp->ic_in_raw.fn = ssh_bpp_input_raw_data_callback;
+ bpp->ic_in_raw.ctx = bpp;
+ bpp->ic_out_pq.fn = ssh_bpp_output_packet_callback;
+ bpp->ic_out_pq.ctx = bpp;
+ bpp->out_pq.pqb.ic = &bpp->ic_out_pq;
+}
+
+void ssh_bpp_free(BinaryPacketProtocol *bpp)
+{
+ delete_callbacks_for_context(bpp);
+ bpp->vt->free(bpp);
+}
+
+void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp,
+ const char *msg, int category)
+{
+ PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_DISCONNECT);
+ put_uint32(pkt, category);
+ put_stringz(pkt, msg);
+ put_stringz(pkt, "en"); /* language tag */
+ pq_push(&bpp->out_pq, pkt);
+}
+
+#define BITMAP_UNIVERSAL(y, name, value) \
+ | (value >= y && value < y+32 \
+ ? 1UL << (value >= y && value < y+32 ? (value-y) : 0) \
+ : 0)
+#define BITMAP_CONDITIONAL(y, name, value, ctx) \
+ BITMAP_UNIVERSAL(y, name, value)
+#define SSH2_BITMAP_WORD(y) \
+ (0 SSH2_MESSAGE_TYPES(BITMAP_UNIVERSAL, BITMAP_CONDITIONAL, \
+ BITMAP_CONDITIONAL, (32*y)))
+
+bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin)
+{
+ static const unsigned valid_bitmap[] = {
+ SSH2_BITMAP_WORD(0),
+ SSH2_BITMAP_WORD(1),
+ SSH2_BITMAP_WORD(2),
+ SSH2_BITMAP_WORD(3),
+ SSH2_BITMAP_WORD(4),
+ SSH2_BITMAP_WORD(5),
+ SSH2_BITMAP_WORD(6),
+ SSH2_BITMAP_WORD(7),
+ };
+
+ if (pktin->type < 0x100 &&
+ !((valid_bitmap[pktin->type >> 5] >> (pktin->type & 0x1F)) & 1)) {
+ PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_UNIMPLEMENTED);
+ put_uint32(pkt, pktin->sequence);
+ pq_push(&bpp->out_pq, pkt);
+ return true;
+ }
+
+ return false;
+}
+
+#undef BITMAP_UNIVERSAL
+#undef BITMAP_CONDITIONAL
+#undef SSH2_BITMAP_WORD
+
+/* ----------------------------------------------------------------------
+ * Centralised component of SSH host key verification.
+ *
+ * verify_ssh_host_key is called from both the SSH-1 and SSH-2
+ * transport layers, and does the initial work of checking whether the
+ * host key is already known. If so, it returns success on its own
+ * account; otherwise, it calls out to the Seat to give an interactive
+ * prompt (the nature of which varies depending on the Seat itself).
+ */
+
+SeatPromptResult verify_ssh_host_key(
+ InteractionReadySeat iseat, Conf *conf, const char *host, int port,
+ ssh_key *key, const char *keytype, char *keystr, const char *keydisp,
+ char **fingerprints, int ca_count,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{
+ /*
+ * First, check if the Conf includes a manual specification of the
+ * expected host key. If so, that completely supersedes everything
+ * else, including the normal host key cache _and_ including
+ * manual overrides: we return success or failure immediately,
+ * entirely based on whether the key matches the Conf.
+ */
+ if (conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0)) {
+ if (fingerprints) {
+ for (size_t i = 0; i < SSH_N_FPTYPES; i++) {
+ /*
+ * Each fingerprint string we've been given will have
+ * things like 'ssh-rsa 2048' at the front of it. Strip
+ * those off and narrow down to just the hash at the end
+ * of the string.
+ */
+ const char *fingerprint = fingerprints[i];
+ if (!fingerprint)
+ continue;
+ const char *p = strrchr(fingerprint, ' ');
+ fingerprint = p ? p+1 : fingerprint;
+ if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys,
+ fingerprint))
+ return SPR_OK;
+ }
+ }
+
+ if (key) {
+ /*
+ * Construct the base64-encoded public key blob and see if
+ * that's listed.
+ */
+ strbuf *binblob;
+ char *base64blob;
+ int atoms, i;
+ binblob = strbuf_new();
+ ssh_key_public_blob(key, BinarySink_UPCAST(binblob));
+ atoms = (binblob->len + 2) / 3;
+ base64blob = snewn(atoms * 4 + 1, char);
+ for (i = 0; i < atoms; i++)
+ base64_encode_atom(binblob->u + 3*i,
+ binblob->len - 3*i, base64blob + 4*i);
+ base64blob[atoms * 4] = '\0';
+ strbuf_free(binblob);
+ if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys,
+ base64blob)) {
+ sfree(base64blob);
+ return SPR_OK;
+ }
+ sfree(base64blob);
+ }
+
+ return SPR_SW_ABORT("Host key not in manually configured list");
+ }
+
+ /*
+ * Next, check the host key cache.
+ */
+ int storage_status = check_stored_host_key(host, port, keytype, keystr);
+ if (storage_status == 0) /* matching key was found in the cache */
+ return SPR_OK;
+
+ /*
+ * The key is either missing from the cache, or does not match.
+ * Either way, fall back to an interactive prompt from the Seat.
+ */
+ SeatDialogText *text = seat_dialog_text_new();
+ const SeatDialogPromptDescriptions *pds =
+ seat_prompt_descriptions(iseat.seat);
+
+ FingerprintType fptype_default =
+ ssh2_pick_default_fingerprint(fingerprints);
+
+ seat_dialog_text_append(
+ text, SDT_TITLE, "%s Security Alert", appname);
+
+ HelpCtx helpctx;
+
+ if (key && ssh_key_alg(key)->is_certificate) {
+ seat_dialog_text_append(
+ text, SDT_SCARY_HEADING, "WARNING - POTENTIAL SECURITY BREACH!");
+ seat_dialog_text_append(
+ text, SDT_PARA, "This server presented a certified host key:");
+ seat_dialog_text_append(
+ text, SDT_DISPLAY, "%s (port %d)", host, port);
+ if (ca_count) {
+ seat_dialog_text_append(
+ text, SDT_PARA, "which was signed by a different "
+ "certification authority from the %s %s is configured to "
+ "trust for this server.", ca_count > 1 ? "ones" : "one",
+ appname);
+ if (storage_status == 2) {
+ seat_dialog_text_append(
+ text, SDT_PARA, "ALSO, that key does not match the key "
+ "%s had previously cached for this server.", appname);
+ seat_dialog_text_append(
+ text, SDT_PARA, "This means that either another "
+ "certification authority is operating in this realm AND "
+ "the server administrator has changed the host key, or "
+ "you have actually connected to another computer "
+ "pretending to be the server.");
+ } else {
+ seat_dialog_text_append(
+ text, SDT_PARA, "This means that either another "
+ "certification authority is operating in this realm, or "
+ "you have actually connected to another computer "
+ "pretending to be the server.");
+ }
+ } else {
+ assert(storage_status == 2);
+ seat_dialog_text_append(
+ text, SDT_PARA, "which does not match the certified key %s "
+ "had previously cached for this server.", appname);
+ seat_dialog_text_append(
+ text, SDT_PARA, "This means that either the server "
+ "administrator has changed the host key, or you have actually "
+ "connected to another computer pretending to be the server.");
+ }
+ seat_dialog_text_append(
+ text, SDT_PARA, "The new %s key fingerprint is:", keytype);
+ seat_dialog_text_append(
+ text, SDT_DISPLAY, "%s", fingerprints[fptype_default]);
+ helpctx = HELPCTX(errors_cert_mismatch);
+ } else if (storage_status == 1) {
+ seat_dialog_text_append(
+ text, SDT_PARA, "The host key is not cached for this server:");
+ seat_dialog_text_append(
+ text, SDT_DISPLAY, "%s (port %d)", host, port);
+ seat_dialog_text_append(
+ text, SDT_PARA, "You have no guarantee that the server is the "
+ "computer you think it is.");
+ seat_dialog_text_append(
+ text, SDT_PARA, "The server's %s key fingerprint is:", keytype);
+ seat_dialog_text_append(
+ text, SDT_DISPLAY, "%s", fingerprints[fptype_default]);
+ helpctx = HELPCTX(errors_hostkey_absent);
+ } else {
+ seat_dialog_text_append(
+ text, SDT_SCARY_HEADING, "WARNING - POTENTIAL SECURITY BREACH!");
+ seat_dialog_text_append(
+ text, SDT_PARA, "The host key does not match the one %s has "
+ "cached for this server:", appname);
+ seat_dialog_text_append(
+ text, SDT_DISPLAY, "%s (port %d)", host, port);
+ seat_dialog_text_append(
+ text, SDT_PARA, "This means that either the server administrator "
+ "has changed the host key, or you have actually connected to "
+ "another computer pretending to be the server.");
+ seat_dialog_text_append(
+ text, SDT_PARA, "The new %s key fingerprint is:", keytype);
+ seat_dialog_text_append(
+ text, SDT_DISPLAY, "%s", fingerprints[fptype_default]);
+ helpctx = HELPCTX(errors_hostkey_changed);
+ }
+
+ /* The above text is printed even in batch mode. Here's where we stop if
+ * we can't present interactive prompts. */
+ seat_dialog_text_append(
+ text, SDT_BATCH_ABORT, "Connection abandoned.");
+
+ if (storage_status == 1) {
+ seat_dialog_text_append(
+ text, SDT_PARA, "If you trust this host, %s to add the key to "
+ "%s's cache and carry on connecting.",
+ pds->hk_accept_action, appname);
+ seat_dialog_text_append(
+ text, SDT_PARA, "If you want to carry on connecting just once, "
+ "without adding the key to the cache, %s.",
+ pds->hk_connect_once_action);
+ seat_dialog_text_append(
+ text, SDT_PARA, "If you do not trust this host, %s to abandon the "
+ "connection.", pds->hk_cancel_action);
+ seat_dialog_text_append(
+ text, SDT_PROMPT, "Store key in cache?");
+ } else {
+ seat_dialog_text_append(
+ text, SDT_PARA, "If you were expecting this change and trust the "
+ "new key, %s to update %s's cache and carry on connecting.",
+ pds->hk_accept_action, appname);
+ if (key && ssh_key_alg(key)->is_certificate) {
+ seat_dialog_text_append(
+ text, SDT_PARA, "(Storing this certified key in the cache "
+ "will NOT cause its certification authority to be trusted "
+ "for any other key or host.)");
+ }
+ seat_dialog_text_append(
+ text, SDT_PARA, "If you want to carry on connecting but without "
+ "updating the cache, %s.", pds->hk_connect_once_action);
+ seat_dialog_text_append(
+ text, SDT_PARA, "If you want to abandon the connection "
+ "completely, %s to cancel. %s is the ONLY guaranteed safe choice.",
+ pds->hk_cancel_action, pds->hk_cancel_action_Participle);
+ seat_dialog_text_append(
+ text, SDT_PROMPT, "Update cached key?");
+ }
+
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY,
+ "Full text of host's public key");
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_BLOB, "%s", keydisp);
+
+ if (fingerprints[SSH_FPTYPE_SHA256]) {
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY, "SHA256 fingerprint");
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s",
+ fingerprints[SSH_FPTYPE_SHA256]);
+ }
+ if (fingerprints[SSH_FPTYPE_MD5]) {
+ seat_dialog_text_append(text, SDT_MORE_INFO_KEY, "MD5 fingerprint");
+ seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s",
+ fingerprints[SSH_FPTYPE_MD5]);
+ }
+
+ SeatPromptResult toret = seat_confirm_ssh_host_key(
+ iseat, host, port, keytype, keystr, text, helpctx, callback, ctx);
+ seat_dialog_text_free(text);
+ return toret;
+}
+
+/* ----------------------------------------------------------------------
+ * Common functions shared between SSH-1 layers.
+ */
+
+bool ssh1_common_get_specials(
+ PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
+{
+ /*
+ * Don't bother offering IGNORE if we've decided the remote
+ * won't cope with it, since we wouldn't bother sending it if
+ * asked anyway.
+ */
+ if (!(ppl->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
+ add_special(ctx, "IGNORE message", SS_NOP, 0);
+ return true;
+ }
+
+ return false;
+}
+
+bool ssh1_common_filter_queue(PacketProtocolLayer *ppl)
+{
+ PktIn *pktin;
+ ptrlen msg;
+
+ while ((pktin = pq_peek(ppl->in_pq)) != NULL) {
+ switch (pktin->type) {
+ case SSH1_MSG_DISCONNECT:
+ msg = get_string(pktin);
+ ssh_remote_error(ppl->ssh,
+ "Remote side sent disconnect message:\n\"%.*s\"",
+ PTRLEN_PRINTF(msg));
+ /* don't try to pop the queue, because we've been freed! */
+ return true; /* indicate that we've been freed */
+
+ case SSH1_MSG_DEBUG:
+ msg = get_string(pktin);
+ ppl_logevent("Remote debug message: %.*s", PTRLEN_PRINTF(msg));
+ pq_pop(ppl->in_pq);
+ break;
+
+ case SSH1_MSG_IGNORE:
+ /* Do nothing, because we're ignoring it! Duhh. */
+ pq_pop(ppl->in_pq);
+ break;
+
+ default:
+ return false;
+ }
+ }
+
+ return false;
+}
+
+void ssh1_compute_session_id(
+ unsigned char *session_id, const unsigned char *cookie,
+ RSAKey *hostkey, RSAKey *servkey)
+{
+ ssh_hash *hash = ssh_hash_new(&ssh_md5);
+
+ for (size_t i = (mp_get_nbits(hostkey->modulus) + 7) / 8; i-- ;)
+ put_byte(hash, mp_get_byte(hostkey->modulus, i));
+ for (size_t i = (mp_get_nbits(servkey->modulus) + 7) / 8; i-- ;)
+ put_byte(hash, mp_get_byte(servkey->modulus, i));
+ put_data(hash, cookie, 8);
+ ssh_hash_final(hash, session_id);
+}
+
+/* ----------------------------------------------------------------------
+ * Wrapper function to handle the abort-connection modes of a
+ * SeatPromptResult without a lot of verbiage at every call site.
+ *
+ * Can become ssh_sw_abort or ssh_user_close, depending on the kind of
+ * negative SeatPromptResult.
+ */
+void ssh_spr_close(Ssh *ssh, SeatPromptResult spr, const char *context)
+{
+ if (spr.kind == SPRK_USER_ABORT) {
+ ssh_user_close(ssh, "User aborted at %s", context);
+ } else {
+ assert(spr.kind == SPRK_SW_ABORT);
+ char *err = spr_get_error_message(spr);
+ ssh_sw_abort(ssh, "%s", err);
+ sfree(err);
+ }
+}
diff --git a/ssh/connection1-client.c b/ssh/connection1-client.c
new file mode 100644
index 00000000..41bf9716
--- /dev/null
+++ b/ssh/connection1-client.c
@@ -0,0 +1,552 @@
+/*
+ * Client-specific parts of the SSH-1 connection layer.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "channel.h"
+#include "sshcr.h"
+#include "connection1.h"
+
+void ssh1_connection_direction_specific_setup(
+ struct ssh1_connection_state *s)
+{
+ if (!s->mainchan) {
+ /*
+ * Start up the main session, by telling mainchan.c to do it
+ * all just as it would in SSH-2, and translating those
+ * concepts to SSH-1's non-channel-shaped idea of the main
+ * session.
+ */
+ s->mainchan = mainchan_new(
+ &s->ppl, &s->cl, s->conf, s->term_width, s->term_height,
+ false /* is_simple */, NULL);
+ }
+}
+
+typedef void (*sf_handler_fn_t)(struct ssh1_connection_state *s,
+ bool success, void *ctx);
+
+struct outstanding_succfail {
+ sf_handler_fn_t handler;
+ void *ctx;
+ struct outstanding_succfail *next;
+
+ /*
+ * The 'trivial' flag is set if this handler is in response to a
+ * request for which the SSH-1 protocol doesn't actually specify a
+ * response packet. The client of this system (mainchan.c) will
+ * expect to get an acknowledgment regardless, so we arrange to
+ * send that ack immediately after the rest of the queue empties.
+ */
+ bool trivial;
+};
+
+static void ssh1_connection_process_trivial_succfails(void *vs);
+
+static void ssh1_queue_succfail_handler(
+ struct ssh1_connection_state *s, sf_handler_fn_t handler, void *ctx,
+ bool trivial)
+{
+ struct outstanding_succfail *osf = snew(struct outstanding_succfail);
+ osf->handler = handler;
+ osf->ctx = ctx;
+ osf->trivial = trivial;
+ osf->next = NULL;
+ if (s->succfail_tail)
+ s->succfail_tail->next = osf;
+ else
+ s->succfail_head = osf;
+ s->succfail_tail = osf;
+
+ /* In case this one was trivial and the queue was already empty,
+ * we should make sure we run the handler promptly, and the
+ * easiest way is to queue it anyway and then run a trivials pass
+ * by callback. */
+ queue_toplevel_callback(ssh1_connection_process_trivial_succfails, s);
+}
+
+static void ssh1_connection_process_succfail(
+ struct ssh1_connection_state *s, bool success)
+{
+ struct outstanding_succfail *prevhead = s->succfail_head;
+ s->succfail_head = s->succfail_head->next;
+ if (!s->succfail_head)
+ s->succfail_tail = NULL;
+ prevhead->handler(s, success, prevhead->ctx);
+ sfree(prevhead);
+}
+
+static void ssh1_connection_process_trivial_succfails(void *vs)
+{
+ struct ssh1_connection_state *s = (struct ssh1_connection_state *)vs;
+ while (s->succfail_head && s->succfail_head->trivial)
+ ssh1_connection_process_succfail(s, true);
+}
+
+bool ssh1_handle_direction_specific_packet(
+ struct ssh1_connection_state *s, PktIn *pktin)
+{
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+
+ PktOut *pktout;
+ struct ssh1_channel *c;
+ unsigned remid;
+ struct ssh_rportfwd pf, *pfp;
+ ptrlen host, data;
+ int port;
+
+ switch (pktin->type) {
+ case SSH1_SMSG_SUCCESS:
+ case SSH1_SMSG_FAILURE:
+ if (!s->succfail_head) {
+ ssh_remote_error(s->ppl.ssh,
+ "Received %s with no outstanding request",
+ ssh1_pkt_type(pktin->type));
+ return true;
+ }
+
+ ssh1_connection_process_succfail(
+ s, pktin->type == SSH1_SMSG_SUCCESS);
+ queue_toplevel_callback(
+ ssh1_connection_process_trivial_succfails, s);
+
+ return true;
+
+ case SSH1_SMSG_X11_OPEN:
+ remid = get_uint32(pktin);
+
+ /* Refuse if X11 forwarding is disabled. */
+ if (!s->X11_fwd_enabled) {
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE);
+ put_uint32(pktout, remid);
+ pq_push(s->ppl.out_pq, pktout);
+ ppl_logevent("Rejected X11 connect request");
+ } else {
+ c = snew(struct ssh1_channel);
+ c->connlayer = s;
+ ssh1_channel_init(c);
+ c->remoteid = remid;
+ c->chan = x11_new_channel(s->x11authtree, &c->sc,
+ NULL, -1, false);
+ c->remoteid = remid;
+ c->halfopen = false;
+
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
+ put_uint32(pktout, c->remoteid);
+ put_uint32(pktout, c->localid);
+ pq_push(s->ppl.out_pq, pktout);
+ ppl_logevent("Opened X11 forward channel");
+ }
+
+ return true;
+
+ case SSH1_SMSG_AGENT_OPEN:
+ remid = get_uint32(pktin);
+
+ /* Refuse if agent forwarding is disabled. */
+ if (!ssh_agent_forwarding_permitted(&s->cl)) {
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE);
+ put_uint32(pktout, remid);
+ pq_push(s->ppl.out_pq, pktout);
+ } else {
+ c = snew(struct ssh1_channel);
+ c->connlayer = s;
+ ssh1_channel_init(c);
+ c->remoteid = remid;
+ c->halfopen = false;
+
+ /*
+ * If possible, make a stream-oriented connection to the
+ * agent and set up an ordinary port-forwarding type
+ * channel over it.
+ */
+ Plug *plug;
+ Channel *ch = portfwd_raw_new(&s->cl, &plug, true);
+ Socket *skt = agent_connect(plug);
+ if (!sk_socket_error(skt)) {
+ portfwd_raw_setup(ch, skt, &c->sc);
+ c->chan = ch;
+ } else {
+ portfwd_raw_free(ch);
+
+ /*
+ * Otherwise, fall back to the old-fashioned system of
+ * parsing the forwarded data stream ourselves for
+ * message boundaries, and passing each individual
+ * message to the one-off agent_query().
+ */
+ c->chan = agentf_new(&c->sc);
+ }
+
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
+ put_uint32(pktout, c->remoteid);
+ put_uint32(pktout, c->localid);
+ pq_push(s->ppl.out_pq, pktout);
+ }
+
+ return true;
+
+ case SSH1_MSG_PORT_OPEN:
+ remid = get_uint32(pktin);
+ host = get_string(pktin);
+ port = toint(get_uint32(pktin));
+
+ pf.dhost = mkstr(host);
+ pf.dport = port;
+ pfp = find234(s->rportfwds, &pf, NULL);
+
+ if (!pfp) {
+ ppl_logevent("Rejected remote port open request for %s:%d",
+ pf.dhost, port);
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE);
+ put_uint32(pktout, remid);
+ pq_push(s->ppl.out_pq, pktout);
+ } else {
+ char *err;
+
+ c = snew(struct ssh1_channel);
+ c->connlayer = s;
+ ppl_logevent("Received remote port open request for %s:%d",
+ pf.dhost, port);
+ err = portfwdmgr_connect(
+ s->portfwdmgr, &c->chan, pf.dhost, port,
+ &c->sc, pfp->addressfamily);
+
+ if (err) {
+ ppl_logevent("Port open failed: %s", err);
+ sfree(err);
+ ssh1_channel_free(c);
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE);
+ put_uint32(pktout, remid);
+ pq_push(s->ppl.out_pq, pktout);
+ } else {
+ ssh1_channel_init(c);
+ c->remoteid = remid;
+ c->halfopen = false;
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
+ put_uint32(pktout, c->remoteid);
+ put_uint32(pktout, c->localid);
+ pq_push(s->ppl.out_pq, pktout);
+ ppl_logevent("Forwarded port opened successfully");
+ }
+ }
+
+ sfree(pf.dhost);
+
+ return true;
+
+ case SSH1_SMSG_STDOUT_DATA:
+ case SSH1_SMSG_STDERR_DATA:
+ data = get_string(pktin);
+ if (!get_err(pktin)) {
+ int bufsize = seat_output(
+ s->ppl.seat, pktin->type == SSH1_SMSG_STDERR_DATA,
+ data.ptr, data.len);
+ if (!s->stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) {
+ s->stdout_throttling = true;
+ ssh_throttle_conn(s->ppl.ssh, +1);
+ }
+ }
+
+ return true;
+
+ case SSH1_SMSG_EXIT_STATUS: {
+ int exitcode = get_uint32(pktin);
+ ppl_logevent("Server sent command exit status %d", exitcode);
+ ssh_got_exitcode(s->ppl.ssh, exitcode);
+
+ s->session_terminated = true;
+ return true;
+ }
+
+ default:
+ return false;
+ }
+}
+
+static void ssh1mainchan_succfail_wantreply(struct ssh1_connection_state *s,
+ bool success, void *ctx)
+{
+ chan_request_response(s->mainchan_chan, success);
+}
+
+static void ssh1mainchan_succfail_nowantreply(struct ssh1_connection_state *s,
+ bool success, void *ctx)
+{
+}
+
+static void ssh1mainchan_queue_response(struct ssh1_connection_state *s,
+ bool want_reply, bool trivial)
+{
+ sf_handler_fn_t handler = (want_reply ? ssh1mainchan_succfail_wantreply :
+ ssh1mainchan_succfail_nowantreply);
+ ssh1_queue_succfail_handler(s, handler, NULL, trivial);
+}
+
+static void ssh1mainchan_request_x11_forwarding(
+ SshChannel *sc, bool want_reply, const char *authproto,
+ const char *authdata, int screen_number, bool oneshot)
+{
+ struct ssh1_connection_state *s =
+ container_of(sc, struct ssh1_connection_state, mainchan_sc);
+ PktOut *pktout;
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_X11_REQUEST_FORWARDING);
+ put_stringz(pktout, authproto);
+ put_stringz(pktout, authdata);
+ if (s->local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER)
+ put_uint32(pktout, screen_number);
+ pq_push(s->ppl.out_pq, pktout);
+
+ ssh1mainchan_queue_response(s, want_reply, false);
+}
+
+static void ssh1mainchan_request_agent_forwarding(
+ SshChannel *sc, bool want_reply)
+{
+ struct ssh1_connection_state *s =
+ container_of(sc, struct ssh1_connection_state, mainchan_sc);
+ PktOut *pktout;
+
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_CMSG_AGENT_REQUEST_FORWARDING);
+ pq_push(s->ppl.out_pq, pktout);
+
+ ssh1mainchan_queue_response(s, want_reply, false);
+}
+
+static void ssh1mainchan_request_pty(
+ SshChannel *sc, bool want_reply, Conf *conf, int w, int h)
+{
+ struct ssh1_connection_state *s =
+ container_of(sc, struct ssh1_connection_state, mainchan_sc);
+ PktOut *pktout;
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_PTY);
+ put_stringz(pktout, conf_get_str(s->conf, CONF_termtype));
+ put_uint32(pktout, h);
+ put_uint32(pktout, w);
+ put_uint32(pktout, 0); /* width in pixels */
+ put_uint32(pktout, 0); /* height in pixels */
+ write_ttymodes_to_packet(
+ BinarySink_UPCAST(pktout), 1,
+ get_ttymodes_from_conf(s->ppl.seat, conf));
+ pq_push(s->ppl.out_pq, pktout);
+
+ ssh1mainchan_queue_response(s, want_reply, false);
+}
+
+static bool ssh1mainchan_send_env_var(
+ SshChannel *sc, bool want_reply, const char *var, const char *value)
+{
+ return false; /* SSH-1 doesn't support this at all */
+}
+
+static void ssh1mainchan_start_shell(SshChannel *sc, bool want_reply)
+{
+ struct ssh1_connection_state *s =
+ container_of(sc, struct ssh1_connection_state, mainchan_sc);
+ PktOut *pktout;
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_SHELL);
+ pq_push(s->ppl.out_pq, pktout);
+
+ ssh1mainchan_queue_response(s, want_reply, true);
+}
+
+static void ssh1mainchan_start_command(
+ SshChannel *sc, bool want_reply, const char *command)
+{
+ struct ssh1_connection_state *s =
+ container_of(sc, struct ssh1_connection_state, mainchan_sc);
+ PktOut *pktout;
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_CMD);
+ put_stringz(pktout, command);
+ pq_push(s->ppl.out_pq, pktout);
+
+ ssh1mainchan_queue_response(s, want_reply, true);
+}
+
+static bool ssh1mainchan_start_subsystem(
+ SshChannel *sc, bool want_reply, const char *subsystem)
+{
+ return false; /* SSH-1 doesn't support this at all */
+}
+
+static bool ssh1mainchan_send_serial_break(
+ SshChannel *sc, bool want_reply, int length)
+{
+ return false; /* SSH-1 doesn't support this at all */
+}
+
+static bool ssh1mainchan_send_signal(
+ SshChannel *sc, bool want_reply, const char *signame)
+{
+ return false; /* SSH-1 doesn't support this at all */
+}
+
+static void ssh1mainchan_send_terminal_size_change(
+ SshChannel *sc, int w, int h)
+{
+ struct ssh1_connection_state *s =
+ container_of(sc, struct ssh1_connection_state, mainchan_sc);
+ PktOut *pktout;
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_WINDOW_SIZE);
+ put_uint32(pktout, h);
+ put_uint32(pktout, w);
+ put_uint32(pktout, 0); /* width in pixels */
+ put_uint32(pktout, 0); /* height in pixels */
+ pq_push(s->ppl.out_pq, pktout);
+}
+
+static void ssh1mainchan_hint_channel_is_simple(SshChannel *sc)
+{
+}
+
+static size_t ssh1mainchan_write(
+ SshChannel *sc, bool is_stderr, const void *data, size_t len)
+{
+ struct ssh1_connection_state *s =
+ container_of(sc, struct ssh1_connection_state, mainchan_sc);
+ PktOut *pktout;
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_STDIN_DATA);
+ put_string(pktout, data, len);
+ pq_push(s->ppl.out_pq, pktout);
+
+ return 0;
+}
+
+static void ssh1mainchan_write_eof(SshChannel *sc)
+{
+ struct ssh1_connection_state *s =
+ container_of(sc, struct ssh1_connection_state, mainchan_sc);
+ PktOut *pktout;
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EOF);
+ pq_push(s->ppl.out_pq, pktout);
+}
+
+static const SshChannelVtable ssh1mainchan_vtable = {
+ .write = ssh1mainchan_write,
+ .write_eof = ssh1mainchan_write_eof,
+ .request_x11_forwarding = ssh1mainchan_request_x11_forwarding,
+ .request_agent_forwarding = ssh1mainchan_request_agent_forwarding,
+ .request_pty = ssh1mainchan_request_pty,
+ .send_env_var = ssh1mainchan_send_env_var,
+ .start_shell = ssh1mainchan_start_shell,
+ .start_command = ssh1mainchan_start_command,
+ .start_subsystem = ssh1mainchan_start_subsystem,
+ .send_serial_break = ssh1mainchan_send_serial_break,
+ .send_signal = ssh1mainchan_send_signal,
+ .send_terminal_size_change = ssh1mainchan_send_terminal_size_change,
+ .hint_channel_is_simple = ssh1mainchan_hint_channel_is_simple,
+ /* other methods are NULL */
+};
+
+static void ssh1_session_confirm_callback(void *vctx)
+{
+ struct ssh1_connection_state *s = (struct ssh1_connection_state *)vctx;
+ chan_open_confirmation(s->mainchan_chan);
+}
+
+SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan)
+{
+ struct ssh1_connection_state *s =
+ container_of(cl, struct ssh1_connection_state, cl);
+ s->mainchan_sc.vt = &ssh1mainchan_vtable;
+ s->mainchan_sc.cl = &s->cl;
+ s->mainchan_chan = chan;
+ queue_toplevel_callback(ssh1_session_confirm_callback, s);
+ return &s->mainchan_sc;
+}
+
+static void ssh1_rportfwd_response(struct ssh1_connection_state *s,
+ bool success, void *ctx)
+{
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx;
+
+ if (success) {
+ ppl_logevent("Remote port forwarding from %s enabled",
+ rpf->log_description);
+ } else {
+ ppl_logevent("Remote port forwarding from %s refused",
+ rpf->log_description);
+
+ struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf);
+ assert(realpf == rpf);
+ portfwdmgr_close(s->portfwdmgr, rpf->pfr);
+ free_rportfwd(rpf);
+ }
+}
+
+struct ssh_rportfwd *ssh1_rportfwd_alloc(
+ ConnectionLayer *cl,
+ const char *shost, int sport, const char *dhost, int dport,
+ int addressfamily, const char *log_description, PortFwdRecord *pfr,
+ ssh_sharing_connstate *share_ctx)
+{
+ struct ssh1_connection_state *s =
+ container_of(cl, struct ssh1_connection_state, cl);
+ struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd);
+
+ rpf->shost = dupstr(shost);
+ rpf->sport = sport;
+ rpf->dhost = dupstr(dhost);
+ rpf->dport = dport;
+ rpf->addressfamily = addressfamily;
+ rpf->log_description = dupstr(log_description);
+ rpf->pfr = pfr;
+
+ if (add234(s->rportfwds, rpf) != rpf) {
+ free_rportfwd(rpf);
+ return NULL;
+ }
+
+ PktOut *pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_CMSG_PORT_FORWARD_REQUEST);
+ put_uint32(pktout, rpf->sport);
+ put_stringz(pktout, rpf->dhost);
+ put_uint32(pktout, rpf->dport);
+ pq_push(s->ppl.out_pq, pktout);
+
+ ssh1_queue_succfail_handler(s, ssh1_rportfwd_response, rpf, false);
+
+ return rpf;
+}
+
+SshChannel *ssh1_serverside_x11_open(
+ ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi)
+{
+ unreachable("Should never be called in the client");
+}
+
+SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan)
+{
+ unreachable("Should never be called in the client");
+}
+
+bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s)
+{
+ seat_set_trust_status(s->ppl.seat, false);
+ if (!seat_has_mixed_input_stream(s->ppl.seat))
+ return false;
+ if (seat_can_set_trust_status(s->ppl.seat))
+ return false;
+ return true;
+}
diff --git a/ssh/connection1-server.c b/ssh/connection1-server.c
new file mode 100644
index 00000000..cc69bdb3
--- /dev/null
+++ b/ssh/connection1-server.c
@@ -0,0 +1,365 @@
+/*
+ * Server-specific parts of the SSH-1 connection layer.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "channel.h"
+#include "sshcr.h"
+#include "connection1.h"
+#include "server.h"
+
+static size_t ssh1sesschan_write(SshChannel *c, bool is_stderr,
+ const void *, size_t);
+static void ssh1sesschan_write_eof(SshChannel *c);
+static void ssh1sesschan_initiate_close(SshChannel *c, const char *err);
+static void ssh1sesschan_send_exit_status(SshChannel *c, int status);
+static void ssh1sesschan_send_exit_signal(
+ SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg);
+
+static const SshChannelVtable ssh1sesschan_vtable = {
+ .write = ssh1sesschan_write,
+ .write_eof = ssh1sesschan_write_eof,
+ .initiate_close = ssh1sesschan_initiate_close,
+ .send_exit_status = ssh1sesschan_send_exit_status,
+ .send_exit_signal = ssh1sesschan_send_exit_signal,
+ /* everything else is NULL */
+};
+
+void ssh1connection_server_configure(
+ PacketProtocolLayer *ppl, const SshServerConfig *ssc)
+{
+ struct ssh1_connection_state *s =
+ container_of(ppl, struct ssh1_connection_state, ppl);
+ s->ssc = ssc;
+}
+
+void ssh1_connection_direction_specific_setup(
+ struct ssh1_connection_state *s)
+{
+ if (!s->mainchan_chan) {
+ s->mainchan_sc.vt = &ssh1sesschan_vtable;
+ s->mainchan_sc.cl = &s->cl;
+ s->mainchan_chan = sesschan_new(
+ &s->mainchan_sc, s->ppl.logctx, NULL, s->ssc);
+ }
+}
+
+bool ssh1_handle_direction_specific_packet(
+ struct ssh1_connection_state *s, PktIn *pktin)
+{
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ PktOut *pktout;
+ struct ssh1_channel *c;
+ unsigned remid;
+ ptrlen host, cmd, data;
+ char *host_str, *err;
+ int port, listenport;
+ bool success;
+
+ switch (pktin->type) {
+ case SSH1_CMSG_EXEC_SHELL:
+ if (s->finished_setup)
+ goto unexpected_setup_packet;
+
+ ppl_logevent("Client requested a shell");
+ chan_run_shell(s->mainchan_chan);
+ s->finished_setup = true;
+ return true;
+
+ case SSH1_CMSG_EXEC_CMD:
+ if (s->finished_setup)
+ goto unexpected_setup_packet;
+
+ cmd = get_string(pktin);
+ ppl_logevent("Client sent command '%.*s'", PTRLEN_PRINTF(cmd));
+ chan_run_command(s->mainchan_chan, cmd);
+ s->finished_setup = true;
+ return true;
+
+ case SSH1_CMSG_REQUEST_COMPRESSION:
+ if (s->compressing || !s->ssc->ssh1_allow_compression) {
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_FAILURE);
+ pq_push(s->ppl.out_pq, pktout);
+ } else {
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS);
+ pq_push(s->ppl.out_pq, pktout);
+ /* Synchronous run of output formatting, to ensure that
+ * success packet is converted into wire format before we
+ * start compressing */
+ ssh_bpp_handle_output(s->ppl.bpp);
+ /* And now ensure that the _next_ packet will be the first
+ * compressed one. */
+ ssh1_bpp_start_compression(s->ppl.bpp);
+ s->compressing = true;
+ }
+
+ return true;
+
+ case SSH1_CMSG_REQUEST_PTY: {
+ if (s->finished_setup)
+ goto unexpected_setup_packet;
+
+ ptrlen termtype = get_string(pktin);
+ unsigned height = get_uint32(pktin);
+ unsigned width = get_uint32(pktin);
+ unsigned pixwidth = get_uint32(pktin);
+ unsigned pixheight = get_uint32(pktin);
+ struct ssh_ttymodes modes = read_ttymodes_from_packet(
+ BinarySource_UPCAST(pktin), 1);
+
+ if (get_err(pktin)) {
+ ppl_logevent("Unable to decode pty request packet");
+ success = false;
+ } else if (!chan_allocate_pty(
+ s->mainchan_chan, termtype, width, height,
+ pixwidth, pixheight, modes)) {
+ ppl_logevent("Unable to allocate a pty");
+ success = false;
+ } else {
+ success = true;
+ }
+
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE));
+ pq_push(s->ppl.out_pq, pktout);
+ return true;
+ }
+
+ case SSH1_CMSG_PORT_FORWARD_REQUEST:
+ if (s->finished_setup)
+ goto unexpected_setup_packet;
+
+ listenport = toint(get_uint32(pktin));
+ host = get_string(pktin);
+ port = toint(get_uint32(pktin));
+
+ ppl_logevent("Client requested port %d forward to %.*s:%d",
+ listenport, PTRLEN_PRINTF(host), port);
+
+ host_str = mkstr(host);
+ success = portfwdmgr_listen(
+ s->portfwdmgr, NULL, listenport, host_str, port, s->conf);
+ sfree(host_str);
+
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE));
+ pq_push(s->ppl.out_pq, pktout);
+ return true;
+
+ case SSH1_CMSG_X11_REQUEST_FORWARDING: {
+ if (s->finished_setup)
+ goto unexpected_setup_packet;
+
+ ptrlen authproto = get_string(pktin);
+ ptrlen authdata = get_string(pktin);
+ unsigned screen_number = 0;
+ if (s->remote_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER)
+ screen_number = get_uint32(pktin);
+
+ success = chan_enable_x11_forwarding(
+ s->mainchan_chan, false, authproto, authdata, screen_number);
+
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE));
+ pq_push(s->ppl.out_pq, pktout);
+ return true;
+ }
+
+ case SSH1_CMSG_AGENT_REQUEST_FORWARDING:
+ if (s->finished_setup)
+ goto unexpected_setup_packet;
+
+ success = chan_enable_agent_forwarding(s->mainchan_chan);
+
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE));
+ pq_push(s->ppl.out_pq, pktout);
+ return true;
+
+ case SSH1_CMSG_STDIN_DATA:
+ data = get_string(pktin);
+ chan_send(s->mainchan_chan, false, data.ptr, data.len);
+ return true;
+
+ case SSH1_CMSG_EOF:
+ chan_send_eof(s->mainchan_chan);
+ return true;
+
+ case SSH1_CMSG_WINDOW_SIZE:
+ return true;
+
+ case SSH1_MSG_PORT_OPEN:
+ remid = get_uint32(pktin);
+ host = get_string(pktin);
+ port = toint(get_uint32(pktin));
+
+ host_str = mkstr(host);
+
+ ppl_logevent("Received request to connect to port %s:%d",
+ host_str, port);
+ c = snew(struct ssh1_channel);
+ c->connlayer = s;
+ err = portfwdmgr_connect(
+ s->portfwdmgr, &c->chan, host_str, port,
+ &c->sc, ADDRTYPE_UNSPEC);
+
+ sfree(host_str);
+
+ if (err) {
+ ppl_logevent("Port open failed: %s", err);
+ sfree(err);
+ ssh1_channel_free(c);
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE);
+ put_uint32(pktout, remid);
+ pq_push(s->ppl.out_pq, pktout);
+ } else {
+ ssh1_channel_init(c);
+ c->remoteid = remid;
+ c->halfopen = false;
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION);
+ put_uint32(pktout, c->remoteid);
+ put_uint32(pktout, c->localid);
+ pq_push(s->ppl.out_pq, pktout);
+ ppl_logevent("Forwarded port opened successfully");
+ }
+
+ return true;
+
+ case SSH1_CMSG_EXIT_CONFIRMATION:
+ if (!s->sent_exit_status) {
+ ssh_proto_error(s->ppl.ssh, "Received SSH1_CMSG_EXIT_CONFIRMATION"
+ " without having sent SSH1_SMSG_EXIT_STATUS");
+ return true;
+ }
+ ppl_logevent("Client sent exit confirmation");
+ return true;
+
+ default:
+ return false;
+ }
+
+ unexpected_setup_packet:
+ ssh_proto_error(s->ppl.ssh, "Received unexpected setup packet after the "
+ "setup phase, type %d (%s)", pktin->type,
+ ssh1_pkt_type(pktin->type));
+ /* FIXME: ensure caller copes with us just having freed the whole layer */
+ return true;
+}
+
+SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan)
+{
+ unreachable("Should never be called in the server");
+}
+
+struct ssh_rportfwd *ssh1_rportfwd_alloc(
+ ConnectionLayer *cl,
+ const char *shost, int sport, const char *dhost, int dport,
+ int addressfamily, const char *log_description, PortFwdRecord *pfr,
+ ssh_sharing_connstate *share_ctx)
+{
+ unreachable("Should never be called in the server");
+}
+
+static size_t ssh1sesschan_write(SshChannel *sc, bool is_stderr,
+ const void *data, size_t len)
+{
+ struct ssh1_connection_state *s =
+ container_of(sc, struct ssh1_connection_state, mainchan_sc);
+ PktOut *pktout;
+
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp,
+ (is_stderr ? SSH1_SMSG_STDERR_DATA : SSH1_SMSG_STDOUT_DATA));
+ put_string(pktout, data, len);
+ pq_push(s->ppl.out_pq, pktout);
+
+ return 0;
+}
+
+static void ssh1sesschan_write_eof(SshChannel *sc)
+{
+ /* SSH-1 can't represent server-side EOF */
+ /* FIXME: some kind of check-termination system, whereby once this has been called _and_ we've had an exit status _and_ we've got no other channels open, we send the actual EXIT_STATUS message */
+}
+
+static void ssh1sesschan_initiate_close(SshChannel *sc, const char *err)
+{
+ /* SSH-1 relies on the client to close the connection in the end */
+}
+
+static void ssh1sesschan_send_exit_status(SshChannel *sc, int status)
+{
+ struct ssh1_connection_state *s =
+ container_of(sc, struct ssh1_connection_state, mainchan_sc);
+ PktOut *pktout;
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_EXIT_STATUS);
+ put_uint32(pktout, status);
+ pq_push(s->ppl.out_pq, pktout);
+
+ s->sent_exit_status = true;
+}
+
+static void ssh1sesschan_send_exit_signal(
+ SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg)
+{
+ /* SSH-1 has no separate representation for signals */
+ ssh1sesschan_send_exit_status(sc, 128);
+}
+
+SshChannel *ssh1_serverside_x11_open(
+ ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi)
+{
+ struct ssh1_connection_state *s =
+ container_of(cl, struct ssh1_connection_state, cl);
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ struct ssh1_channel *c = snew(struct ssh1_channel);
+ PktOut *pktout;
+
+ c->connlayer = s;
+ ssh1_channel_init(c);
+ c->halfopen = true;
+ c->chan = chan;
+
+ ppl_logevent("Forwarding X11 connection to client");
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_X11_OPEN);
+ put_uint32(pktout, c->localid);
+ pq_push(s->ppl.out_pq, pktout);
+
+ return &c->sc;
+}
+
+SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan)
+{
+ struct ssh1_connection_state *s =
+ container_of(cl, struct ssh1_connection_state, cl);
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ struct ssh1_channel *c = snew(struct ssh1_channel);
+ PktOut *pktout;
+
+ c->connlayer = s;
+ ssh1_channel_init(c);
+ c->halfopen = true;
+ c->chan = chan;
+
+ ppl_logevent("Forwarding agent connection to client");
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_AGENT_OPEN);
+ put_uint32(pktout, c->localid);
+ pq_push(s->ppl.out_pq, pktout);
+
+ return &c->sc;
+}
+
+bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s)
+{
+ return false;
+}
diff --git a/ssh/connection1.c b/ssh/connection1.c
new file mode 100644
index 00000000..0b5485f8
--- /dev/null
+++ b/ssh/connection1.c
@@ -0,0 +1,811 @@
+/*
+ * Packet protocol layer for the SSH-1 'connection protocol', i.e.
+ * everything after authentication finishes.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "channel.h"
+#include "sshcr.h"
+#include "connection1.h"
+
+static int ssh1_rportfwd_cmp(void *av, void *bv)
+{
+ struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
+ struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
+ int i;
+ if ( (i = strcmp(a->dhost, b->dhost)) != 0)
+ return i < 0 ? -1 : +1;
+ if (a->dport > b->dport)
+ return +1;
+ if (a->dport < b->dport)
+ return -1;
+ return 0;
+}
+
+static void ssh1_connection_free(PacketProtocolLayer *);
+static void ssh1_connection_process_queue(PacketProtocolLayer *);
+static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl,
+ SessionSpecialCode code, int arg);
+static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
+
+static const PacketProtocolLayerVtable ssh1_connection_vtable = {
+ .free = ssh1_connection_free,
+ .process_queue = ssh1_connection_process_queue,
+ .get_specials = ssh1_common_get_specials,
+ .special_cmd = ssh1_connection_special_cmd,
+ .reconfigure = ssh1_connection_reconfigure,
+ .queued_data_size = ssh_ppl_default_queued_data_size,
+ .name = NULL, /* no layer names in SSH-1 */
+};
+
+static void ssh1_rportfwd_remove(
+ ConnectionLayer *cl, struct ssh_rportfwd *rpf);
+static SshChannel *ssh1_lportfwd_open(
+ ConnectionLayer *cl, const char *hostname, int port,
+ const char *description, const SocketPeerInfo *pi, Channel *chan);
+static struct X11FakeAuth *ssh1_add_x11_display(
+ ConnectionLayer *cl, int authtype, struct X11Display *disp);
+static bool ssh1_agent_forwarding_permitted(ConnectionLayer *cl);
+static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height);
+static void ssh1_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize);
+static size_t ssh1_stdin_backlog(ConnectionLayer *cl);
+static void ssh1_throttle_all_channels(ConnectionLayer *cl, bool throttled);
+static bool ssh1_ldisc_option(ConnectionLayer *cl, int option);
+static void ssh1_set_ldisc_option(ConnectionLayer *cl, int option, bool value);
+static void ssh1_enable_x_fwd(ConnectionLayer *cl);
+static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted);
+static bool ssh1_get_wants_user_input(ConnectionLayer *cl);
+static void ssh1_got_user_input(ConnectionLayer *cl);
+
+static const ConnectionLayerVtable ssh1_connlayer_vtable = {
+ .rportfwd_alloc = ssh1_rportfwd_alloc,
+ .rportfwd_remove = ssh1_rportfwd_remove,
+ .lportfwd_open = ssh1_lportfwd_open,
+ .session_open = ssh1_session_open,
+ .serverside_x11_open = ssh1_serverside_x11_open,
+ .serverside_agent_open = ssh1_serverside_agent_open,
+ .add_x11_display = ssh1_add_x11_display,
+ .agent_forwarding_permitted = ssh1_agent_forwarding_permitted,
+ .terminal_size = ssh1_terminal_size,
+ .stdout_unthrottle = ssh1_stdout_unthrottle,
+ .stdin_backlog = ssh1_stdin_backlog,
+ .throttle_all_channels = ssh1_throttle_all_channels,
+ .ldisc_option = ssh1_ldisc_option,
+ .set_ldisc_option = ssh1_set_ldisc_option,
+ .enable_x_fwd = ssh1_enable_x_fwd,
+ .set_wants_user_input = ssh1_set_wants_user_input,
+ .get_wants_user_input = ssh1_get_wants_user_input,
+ .got_user_input = ssh1_got_user_input,
+ /* other methods are NULL */
+};
+
+static size_t ssh1channel_write(
+ SshChannel *c, bool is_stderr, const void *buf, size_t len);
+static void ssh1channel_write_eof(SshChannel *c);
+static void ssh1channel_initiate_close(SshChannel *c, const char *err);
+static void ssh1channel_unthrottle(SshChannel *c, size_t bufsize);
+static Conf *ssh1channel_get_conf(SshChannel *c);
+static void ssh1channel_window_override_removed(SshChannel *c) { /* ignore */ }
+
+static const SshChannelVtable ssh1channel_vtable = {
+ .write = ssh1channel_write,
+ .write_eof = ssh1channel_write_eof,
+ .initiate_close = ssh1channel_initiate_close,
+ .unthrottle = ssh1channel_unthrottle,
+ .get_conf = ssh1channel_get_conf,
+ .window_override_removed = ssh1channel_window_override_removed,
+ /* everything else is NULL */
+};
+
+static void ssh1_channel_try_eof(struct ssh1_channel *c);
+static void ssh1_channel_close_local(struct ssh1_channel *c,
+ const char *reason);
+static void ssh1_channel_destroy(struct ssh1_channel *c);
+static void ssh1_channel_check_close(struct ssh1_channel *c);
+
+static int ssh1_channelcmp(void *av, void *bv)
+{
+ const struct ssh1_channel *a = (const struct ssh1_channel *) av;
+ const struct ssh1_channel *b = (const struct ssh1_channel *) bv;
+ if (a->localid < b->localid)
+ return -1;
+ if (a->localid > b->localid)
+ return +1;
+ return 0;
+}
+
+static int ssh1_channelfind(void *av, void *bv)
+{
+ const unsigned *a = (const unsigned *) av;
+ const struct ssh1_channel *b = (const struct ssh1_channel *) bv;
+ if (*a < b->localid)
+ return -1;
+ if (*a > b->localid)
+ return +1;
+ return 0;
+}
+
+void ssh1_channel_free(struct ssh1_channel *c)
+{
+ if (c->chan)
+ chan_free(c->chan);
+ sfree(c);
+}
+
+PacketProtocolLayer *ssh1_connection_new(
+ Ssh *ssh, Conf *conf, bufchain *user_input, ConnectionLayer **cl_out)
+{
+ struct ssh1_connection_state *s = snew(struct ssh1_connection_state);
+ memset(s, 0, sizeof(*s));
+ s->ppl.vt = &ssh1_connection_vtable;
+
+ s->conf = conf_copy(conf);
+
+ s->channels = newtree234(ssh1_channelcmp);
+
+ s->x11authtree = newtree234(x11_authcmp);
+
+ s->user_input = user_input;
+
+ /* Need to get the log context for s->cl now, because we won't be
+ * helpfully notified when a copy is written into s->ppl by our
+ * owner. */
+ s->cl.vt = &ssh1_connlayer_vtable;
+ s->cl.logctx = ssh_get_logctx(ssh);
+
+ s->portfwdmgr = portfwdmgr_new(&s->cl);
+ s->rportfwds = newtree234(ssh1_rportfwd_cmp);
+
+ *cl_out = &s->cl;
+ return &s->ppl;
+}
+
+static void ssh1_connection_free(PacketProtocolLayer *ppl)
+{
+ struct ssh1_connection_state *s =
+ container_of(ppl, struct ssh1_connection_state, ppl);
+ struct X11FakeAuth *auth;
+ struct ssh1_channel *c;
+ struct ssh_rportfwd *rpf;
+
+ conf_free(s->conf);
+
+ while ((c = delpos234(s->channels, 0)) != NULL)
+ ssh1_channel_free(c);
+ freetree234(s->channels);
+ if (s->mainchan_chan)
+ chan_free(s->mainchan_chan);
+
+ if (s->x11disp)
+ x11_free_display(s->x11disp);
+ while ((auth = delpos234(s->x11authtree, 0)) != NULL)
+ x11_free_fake_auth(auth);
+ freetree234(s->x11authtree);
+
+ while ((rpf = delpos234(s->rportfwds, 0)) != NULL)
+ free_rportfwd(rpf);
+ freetree234(s->rportfwds);
+ portfwdmgr_free(s->portfwdmgr);
+
+ if (s->antispoof_prompt)
+ free_prompts(s->antispoof_prompt);
+
+ delete_callbacks_for_context(s);
+
+ sfree(s);
+}
+
+void ssh1_connection_set_protoflags(PacketProtocolLayer *ppl,
+ int local, int remote)
+{
+ assert(ppl->vt == &ssh1_connection_vtable);
+ struct ssh1_connection_state *s =
+ container_of(ppl, struct ssh1_connection_state, ppl);
+ s->local_protoflags = local;
+ s->remote_protoflags = remote;
+}
+
+static bool ssh1_connection_filter_queue(struct ssh1_connection_state *s)
+{
+ PktIn *pktin;
+ ptrlen data;
+ struct ssh1_channel *c;
+ unsigned localid;
+ bool expect_halfopen;
+
+ while (1) {
+ if (ssh1_common_filter_queue(&s->ppl))
+ return true;
+ if ((pktin = pq_peek(s->ppl.in_pq)) == NULL)
+ return false;
+
+ switch (pktin->type) {
+ case SSH1_MSG_CHANNEL_DATA:
+ case SSH1_MSG_CHANNEL_OPEN_CONFIRMATION:
+ case SSH1_MSG_CHANNEL_OPEN_FAILURE:
+ case SSH1_MSG_CHANNEL_CLOSE:
+ case SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION:
+ /*
+ * Common preliminary code for all the messages from the
+ * server that cite one of our channel ids: look up that
+ * channel id, check it exists, and if it's for a sharing
+ * downstream, pass it on.
+ */
+ localid = get_uint32(pktin);
+ c = find234(s->channels, &localid, ssh1_channelfind);
+
+ expect_halfopen = (
+ pktin->type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION ||
+ pktin->type == SSH1_MSG_CHANNEL_OPEN_FAILURE);
+
+ if (!c || c->halfopen != expect_halfopen) {
+ ssh_remote_error(
+ s->ppl.ssh, "Received %s for %s channel %u",
+ ssh1_pkt_type(pktin->type),
+ !c ? "nonexistent" : c->halfopen ? "half-open" : "open",
+ localid);
+ return true;
+ }
+
+ switch (pktin->type) {
+ case SSH1_MSG_CHANNEL_OPEN_CONFIRMATION:
+ assert(c->halfopen);
+ c->remoteid = get_uint32(pktin);
+ c->halfopen = false;
+ c->throttling_conn = false;
+
+ chan_open_confirmation(c->chan);
+
+ /*
+ * Now that the channel is fully open, it's possible
+ * in principle to immediately close it. Check whether
+ * it wants us to!
+ *
+ * This can occur if a local socket error occurred
+ * between us sending out CHANNEL_OPEN and receiving
+ * OPEN_CONFIRMATION. If that happens, all we can do
+ * is immediately initiate close proceedings now that
+ * we know the server's id to put in the close
+ * message. We'll have handled that in this code by
+ * having already turned c->chan into a zombie, so its
+ * want_close method (which ssh1_channel_check_close
+ * will consult) will already be returning true.
+ */
+ ssh1_channel_check_close(c);
+
+ if (c->pending_eof)
+ ssh1_channel_try_eof(c); /* in case we had a pending EOF */
+ break;
+
+ case SSH1_MSG_CHANNEL_OPEN_FAILURE:
+ assert(c->halfopen);
+
+ chan_open_failed(c->chan, NULL);
+ chan_free(c->chan);
+
+ del234(s->channels, c);
+ ssh1_channel_free(c);
+ break;
+
+ case SSH1_MSG_CHANNEL_DATA:
+ data = get_string(pktin);
+ if (!get_err(pktin)) {
+ int bufsize = chan_send(
+ c->chan, false, data.ptr, data.len);
+
+ if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) {
+ c->throttling_conn = true;
+ ssh_throttle_conn(s->ppl.ssh, +1);
+ }
+ }
+ break;
+
+ case SSH1_MSG_CHANNEL_CLOSE:
+ if (!(c->closes & CLOSES_RCVD_CLOSE)) {
+ c->closes |= CLOSES_RCVD_CLOSE;
+ chan_send_eof(c->chan);
+ ssh1_channel_check_close(c);
+ }
+ break;
+
+ case SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION:
+ if (!(c->closes & CLOSES_RCVD_CLOSECONF)) {
+ if (!(c->closes & CLOSES_SENT_CLOSE)) {
+ ssh_remote_error(
+ s->ppl.ssh,
+ "Received CHANNEL_CLOSE_CONFIRMATION for channel"
+ " %u for which we never sent CHANNEL_CLOSE\n",
+ c->localid);
+ return true;
+ }
+
+ c->closes |= CLOSES_RCVD_CLOSECONF;
+ ssh1_channel_check_close(c);
+ }
+ break;
+ }
+
+ pq_pop(s->ppl.in_pq);
+ break;
+
+ default:
+ if (ssh1_handle_direction_specific_packet(s, pktin)) {
+ pq_pop(s->ppl.in_pq);
+ if (ssh1_check_termination(s))
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+}
+
+static PktIn *ssh1_connection_pop(struct ssh1_connection_state *s)
+{
+ ssh1_connection_filter_queue(s);
+ return pq_pop(s->ppl.in_pq);
+}
+
+static void ssh1_connection_process_queue(PacketProtocolLayer *ppl)
+{
+ struct ssh1_connection_state *s =
+ container_of(ppl, struct ssh1_connection_state, ppl);
+ PktIn *pktin;
+
+ if (ssh1_connection_filter_queue(s)) /* no matter why we were called */
+ return;
+
+ crBegin(s->crState);
+
+ /*
+ * Signal the seat that authentication is done, so that it can
+ * deploy spoofing defences. If it doesn't have any, deploy our
+ * own fallback one.
+ *
+ * We do this here rather than at the end of userauth, because we
+ * might not have gone through userauth at all (if we're a
+ * connection-sharing downstream).
+ */
+ if (ssh1_connection_need_antispoof_prompt(s)) {
+ s->antispoof_prompt = ssh_ppl_new_prompts(&s->ppl);
+ s->antispoof_prompt->to_server = true;
+ s->antispoof_prompt->from_server = false;
+ s->antispoof_prompt->name = dupstr("Authentication successful");
+ add_prompt(
+ s->antispoof_prompt,
+ dupstr("Access granted. Press Return to begin session. "), false);
+ s->antispoof_ret = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->antispoof_prompt);
+ while (s->antispoof_ret.kind == SPRK_INCOMPLETE) {
+ crReturnV;
+ s->antispoof_ret = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->antispoof_prompt);
+ }
+ free_prompts(s->antispoof_prompt);
+ s->antispoof_prompt = NULL;
+ }
+
+ portfwdmgr_config(s->portfwdmgr, s->conf);
+ s->portfwdmgr_configured = true;
+
+ while (!s->finished_setup) {
+ ssh1_connection_direction_specific_setup(s);
+ crReturnV;
+ }
+
+ while (1) {
+
+ /*
+ * By this point, most incoming packets are already being
+ * handled by filter_queue, and we need only pay attention to
+ * the unusual ones.
+ */
+
+ if ((pktin = ssh1_connection_pop(s)) != NULL) {
+ ssh_proto_error(s->ppl.ssh, "Unexpected packet received, "
+ "type %d (%s)", pktin->type,
+ ssh1_pkt_type(pktin->type));
+ return;
+ }
+ crReturnV;
+ }
+
+ crFinishV;
+}
+
+static void ssh1_channel_check_close(struct ssh1_channel *c)
+{
+ struct ssh1_connection_state *s = c->connlayer;
+ PktOut *pktout;
+
+ if (c->halfopen) {
+ /*
+ * If we've sent out our own CHANNEL_OPEN but not yet seen
+ * either OPEN_CONFIRMATION or OPEN_FAILURE in response, then
+ * it's too early to be sending close messages of any kind.
+ */
+ return;
+ }
+
+ if ((!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes) ||
+ chan_want_close(c->chan, (c->closes & CLOSES_SENT_CLOSE),
+ (c->closes & CLOSES_RCVD_CLOSE))) &&
+ !(c->closes & CLOSES_SENT_CLOSECONF)) {
+ /*
+ * We have both sent and received CLOSE (or the channel type
+ * doesn't need us to), which means the channel is in final
+ * wind-up. Send CLOSE and/or CLOSE_CONFIRMATION, whichever we
+ * haven't sent yet.
+ */
+ if (!(c->closes & CLOSES_SENT_CLOSE)) {
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE);
+ put_uint32(pktout, c->remoteid);
+ pq_push(s->ppl.out_pq, pktout);
+ c->closes |= CLOSES_SENT_CLOSE;
+ }
+ if (c->closes & CLOSES_RCVD_CLOSE) {
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION);
+ put_uint32(pktout, c->remoteid);
+ pq_push(s->ppl.out_pq, pktout);
+ c->closes |= CLOSES_SENT_CLOSECONF;
+ }
+ }
+
+ if (!((CLOSES_SENT_CLOSECONF | CLOSES_RCVD_CLOSECONF) & ~c->closes)) {
+ /*
+ * We have both sent and received CLOSE_CONFIRMATION, which
+ * means we're completely done with the channel.
+ */
+ ssh1_channel_destroy(c);
+ }
+}
+
+static void ssh1_channel_try_eof(struct ssh1_channel *c)
+{
+ struct ssh1_connection_state *s = c->connlayer;
+ PktOut *pktout;
+ assert(c->pending_eof); /* precondition for calling us */
+ if (c->halfopen)
+ return; /* can't close: not even opened yet */
+
+ c->pending_eof = false; /* we're about to send it */
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE);
+ put_uint32(pktout, c->remoteid);
+ pq_push(s->ppl.out_pq, pktout);
+ c->closes |= CLOSES_SENT_CLOSE;
+
+ ssh1_channel_check_close(c);
+}
+
+/*
+ * Close any local socket and free any local resources associated with
+ * a channel. This converts the channel into a zombie.
+ */
+static void ssh1_channel_close_local(struct ssh1_channel *c,
+ const char *reason)
+{
+ struct ssh1_connection_state *s = c->connlayer;
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ char *msg = chan_log_close_msg(c->chan);
+
+ if (msg != NULL) {
+ ppl_logevent("%s%s%s", msg, reason ? " " : "", reason ? reason : "");
+ sfree(msg);
+ }
+
+ chan_free(c->chan);
+ c->chan = zombiechan_new();
+}
+
+static void ssh1_check_termination_callback(void *vctx)
+{
+ struct ssh1_connection_state *s = (struct ssh1_connection_state *)vctx;
+ ssh1_check_termination(s);
+}
+
+static void ssh1_channel_destroy(struct ssh1_channel *c)
+{
+ struct ssh1_connection_state *s = c->connlayer;
+
+ ssh1_channel_close_local(c, NULL);
+ del234(s->channels, c);
+ ssh1_channel_free(c);
+
+ /*
+ * If that was the last channel left open, we might need to
+ * terminate. But we'll be a bit cautious, by doing that in a
+ * toplevel callback, just in case anything on the current call
+ * stack objects to this entire PPL being freed.
+ */
+ queue_toplevel_callback(ssh1_check_termination_callback, s);
+}
+
+bool ssh1_check_termination(struct ssh1_connection_state *s)
+{
+ /*
+ * Decide whether we should terminate the SSH connection now.
+ * Called after a channel goes away, or when the main session
+ * returns SSH1_SMSG_EXIT_STATUS; we terminate when none of either
+ * is left.
+ */
+ if (s->session_terminated && count234(s->channels) == 0) {
+ PktOut *pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_CMSG_EXIT_CONFIRMATION);
+ pq_push(s->ppl.out_pq, pktout);
+
+ ssh_user_close(s->ppl.ssh, "Session finished");
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Set up most of a new ssh1_channel. Leaves chan untouched (since it
+ * will sometimes have been filled in before calling this).
+ */
+void ssh1_channel_init(struct ssh1_channel *c)
+{
+ struct ssh1_connection_state *s = c->connlayer;
+ c->closes = 0;
+ c->pending_eof = false;
+ c->throttling_conn = false;
+ c->sc.vt = &ssh1channel_vtable;
+ c->sc.cl = &s->cl;
+ c->localid = alloc_channel_id(s->channels, struct ssh1_channel);
+ add234(s->channels, c);
+}
+
+static Conf *ssh1channel_get_conf(SshChannel *sc)
+{
+ struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc);
+ struct ssh1_connection_state *s = c->connlayer;
+ return s->conf;
+}
+
+static void ssh1channel_write_eof(SshChannel *sc)
+{
+ struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc);
+
+ if (c->closes & CLOSES_SENT_CLOSE)
+ return;
+
+ c->pending_eof = true;
+ ssh1_channel_try_eof(c);
+}
+
+static void ssh1channel_initiate_close(SshChannel *sc, const char *err)
+{
+ struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc);
+ char *reason;
+
+ reason = err ? dupprintf("due to local error: %s", err) : NULL;
+ ssh1_channel_close_local(c, reason);
+ sfree(reason);
+ c->pending_eof = false; /* this will confuse a zombie channel */
+
+ ssh1_channel_check_close(c);
+}
+
+static void ssh1channel_unthrottle(SshChannel *sc, size_t bufsize)
+{
+ struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc);
+ struct ssh1_connection_state *s = c->connlayer;
+
+ if (c->throttling_conn && bufsize <= SSH1_BUFFER_LIMIT) {
+ c->throttling_conn = false;
+ ssh_throttle_conn(s->ppl.ssh, -1);
+ }
+}
+
+static size_t ssh1channel_write(
+ SshChannel *sc, bool is_stderr, const void *buf, size_t len)
+{
+ struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc);
+ struct ssh1_connection_state *s = c->connlayer;
+
+ assert(!(c->closes & CLOSES_SENT_CLOSE));
+
+ PktOut *pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_DATA);
+ put_uint32(pktout, c->remoteid);
+ put_string(pktout, buf, len);
+ pq_push(s->ppl.out_pq, pktout);
+
+ /*
+ * In SSH-1 we can return 0 here - implying that channels are
+ * never individually throttled - because the only circumstance
+ * that can cause throttling will be the whole SSH connection
+ * backing up, in which case _everything_ will be throttled as a
+ * whole.
+ */
+ return 0;
+}
+
+static struct X11FakeAuth *ssh1_add_x11_display(
+ ConnectionLayer *cl, int authtype, struct X11Display *disp)
+{
+ struct ssh1_connection_state *s =
+ container_of(cl, struct ssh1_connection_state, cl);
+ struct X11FakeAuth *auth = x11_invent_fake_auth(s->x11authtree, authtype);
+ auth->disp = disp;
+ return auth;
+}
+
+static SshChannel *ssh1_lportfwd_open(
+ ConnectionLayer *cl, const char *hostname, int port,
+ const char *description, const SocketPeerInfo *pi, Channel *chan)
+{
+ struct ssh1_connection_state *s =
+ container_of(cl, struct ssh1_connection_state, cl);
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ struct ssh1_channel *c = snew(struct ssh1_channel);
+ PktOut *pktout;
+
+ c->connlayer = s;
+ ssh1_channel_init(c);
+ c->halfopen = true;
+ c->chan = chan;
+
+ ppl_logevent("Opening connection to %s:%d for %s",
+ hostname, port, description);
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_PORT_OPEN);
+ put_uint32(pktout, c->localid);
+ put_stringz(pktout, hostname);
+ put_uint32(pktout, port);
+ /* originator string would go here, but we didn't specify
+ * SSH_PROTOFLAG_HOST_IN_FWD_OPEN */
+ pq_push(s->ppl.out_pq, pktout);
+
+ return &c->sc;
+}
+
+static void ssh1_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf)
+{
+ /*
+ * We cannot cancel listening ports on the server side in SSH-1!
+ * There's no message to support it.
+ */
+}
+
+static bool ssh1_agent_forwarding_permitted(ConnectionLayer *cl)
+{
+ struct ssh1_connection_state *s =
+ container_of(cl, struct ssh1_connection_state, cl);
+ return conf_get_bool(s->conf, CONF_agentfwd) && agent_exists();
+}
+
+static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl,
+ SessionSpecialCode code, int arg)
+{
+ struct ssh1_connection_state *s =
+ container_of(ppl, struct ssh1_connection_state, ppl);
+ PktOut *pktout;
+
+ if (code == SS_PING || code == SS_NOP) {
+ if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE);
+ put_stringz(pktout, "");
+ pq_push(s->ppl.out_pq, pktout);
+ }
+ } else if (s->mainchan) {
+ mainchan_special_cmd(s->mainchan, code, arg);
+ }
+}
+
+static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height)
+{
+ struct ssh1_connection_state *s =
+ container_of(cl, struct ssh1_connection_state, cl);
+
+ s->term_width = width;
+ s->term_height = height;
+ if (s->mainchan)
+ mainchan_terminal_size(s->mainchan, width, height);
+}
+
+static void ssh1_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize)
+{
+ struct ssh1_connection_state *s =
+ container_of(cl, struct ssh1_connection_state, cl);
+
+ if (s->stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) {
+ s->stdout_throttling = false;
+ ssh_throttle_conn(s->ppl.ssh, -1);
+ }
+}
+
+static size_t ssh1_stdin_backlog(ConnectionLayer *cl)
+{
+ return 0;
+}
+
+static void ssh1_throttle_all_channels(ConnectionLayer *cl, bool throttled)
+{
+ struct ssh1_connection_state *s =
+ container_of(cl, struct ssh1_connection_state, cl);
+ struct ssh1_channel *c;
+ int i;
+
+ for (i = 0; NULL != (c = index234(s->channels, i)); i++)
+ chan_set_input_wanted(c->chan, !throttled);
+}
+
+static bool ssh1_ldisc_option(ConnectionLayer *cl, int option)
+{
+ struct ssh1_connection_state *s =
+ container_of(cl, struct ssh1_connection_state, cl);
+
+ return s->ldisc_opts[option];
+}
+
+static void ssh1_set_ldisc_option(ConnectionLayer *cl, int option, bool value)
+{
+ struct ssh1_connection_state *s =
+ container_of(cl, struct ssh1_connection_state, cl);
+
+ s->ldisc_opts[option] = value;
+}
+
+static void ssh1_enable_x_fwd(ConnectionLayer *cl)
+{
+ struct ssh1_connection_state *s =
+ container_of(cl, struct ssh1_connection_state, cl);
+
+ s->X11_fwd_enabled = true;
+}
+
+static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted)
+{
+ struct ssh1_connection_state *s =
+ container_of(cl, struct ssh1_connection_state, cl);
+
+ s->want_user_input = wanted;
+ s->finished_setup = true;
+ if (wanted)
+ ssh_check_sendok(s->ppl.ssh);
+}
+
+static bool ssh1_get_wants_user_input(ConnectionLayer *cl)
+{
+ struct ssh1_connection_state *s =
+ container_of(cl, struct ssh1_connection_state, cl);
+
+ return s->want_user_input;
+}
+
+static void ssh1_got_user_input(ConnectionLayer *cl)
+{
+ struct ssh1_connection_state *s =
+ container_of(cl, struct ssh1_connection_state, cl);
+
+ while (s->mainchan && bufchain_size(s->user_input) > 0) {
+ /*
+ * Add user input to the main channel's buffer.
+ */
+ ptrlen data = bufchain_prefix(s->user_input);
+ if (data.len > 512)
+ data.len = 512;
+ sshfwd_write(&s->mainchan_sc, data.ptr, data.len);
+ bufchain_consume(s->user_input, data.len);
+ }
+}
+
+static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
+{
+ struct ssh1_connection_state *s =
+ container_of(ppl, struct ssh1_connection_state, ppl);
+
+ conf_free(s->conf);
+ s->conf = conf_copy(conf);
+
+ if (s->portfwdmgr_configured)
+ portfwdmgr_config(s->portfwdmgr, s->conf);
+}
diff --git a/ssh/connection1.h b/ssh/connection1.h
new file mode 100644
index 00000000..ff370131
--- /dev/null
+++ b/ssh/connection1.h
@@ -0,0 +1,124 @@
+struct ssh1_channel;
+
+struct outstanding_succfail;
+
+struct ssh1_connection_state {
+ int crState;
+
+ Conf *conf;
+ int local_protoflags, remote_protoflags;
+
+ tree234 *channels; /* indexed by local id */
+
+ /* In SSH-1, the main session doesn't take the form of a 'channel'
+ * according to the wire protocol. But we want to use the same API
+ * for it, so we define an SshChannel here - but one that uses a
+ * separate vtable from the usual one, so it doesn't map to a
+ * struct ssh1_channel as all the others do. */
+ SshChannel mainchan_sc;
+ Channel *mainchan_chan; /* the other end of mainchan_sc */
+ mainchan *mainchan; /* and its subtype */
+
+ bool got_pty;
+ bool ldisc_opts[LD_N_OPTIONS];
+ bool stdout_throttling;
+ bool want_user_input;
+ bool session_terminated;
+ int term_width, term_height, term_width_orig, term_height_orig;
+ bufchain *user_input;
+
+ bool X11_fwd_enabled;
+ struct X11Display *x11disp;
+ struct X11FakeAuth *x11auth;
+ tree234 *x11authtree;
+
+ tree234 *rportfwds;
+ PortFwdManager *portfwdmgr;
+ bool portfwdmgr_configured;
+
+ bool finished_setup;
+
+ /*
+ * These store the list of requests that we're waiting for
+ * SSH_SMSG_{SUCCESS,FAILURE} replies to. (Those messages don't
+ * come with any indication of what they're in response to, so we
+ * have to keep track of the queue ourselves.)
+ */
+ struct outstanding_succfail *succfail_head, *succfail_tail;
+
+ bool compressing; /* used in server mode only */
+ bool sent_exit_status; /* also for server mode */
+
+ prompts_t *antispoof_prompt;
+ SeatPromptResult antispoof_ret;
+
+ const SshServerConfig *ssc;
+
+ ConnectionLayer cl;
+ PacketProtocolLayer ppl;
+};
+
+struct ssh1_channel {
+ struct ssh1_connection_state *connlayer;
+
+ unsigned remoteid, localid;
+ int type;
+ /* True if we opened this channel but server hasn't confirmed. */
+ bool halfopen;
+
+ /* Bitmap of whether we've sent/received CHANNEL_CLOSE and
+ * CHANNEL_CLOSE_CONFIRMATION. */
+#define CLOSES_SENT_CLOSE 1
+#define CLOSES_SENT_CLOSECONF 2
+#define CLOSES_RCVD_CLOSE 4
+#define CLOSES_RCVD_CLOSECONF 8
+ int closes;
+
+ /*
+ * This flag indicates that an EOF is pending on the outgoing side
+ * of the channel: that is, wherever we're getting the data for
+ * this channel has sent us some data followed by EOF. We can't
+ * actually send the EOF until we've finished sending the data, so
+ * we set this flag instead to remind us to do so once our buffer
+ * is clear.
+ */
+ bool pending_eof;
+
+ /*
+ * True if this channel is causing the underlying connection to be
+ * throttled.
+ */
+ bool throttling_conn;
+
+ /*
+ * True if we currently have backed-up data on the direction of
+ * this channel pointing out of the SSH connection, and therefore
+ * would prefer the 'Channel' implementation not to read further
+ * local input if possible.
+ */
+ bool throttled_by_backlog;
+
+ Channel *chan; /* handle the client side of this channel, if not */
+ SshChannel sc; /* entry point for chan to talk back to */
+};
+
+SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan);
+void ssh1_channel_init(struct ssh1_channel *c);
+void ssh1_channel_free(struct ssh1_channel *c);
+struct ssh_rportfwd *ssh1_rportfwd_alloc(
+ ConnectionLayer *cl,
+ const char *shost, int sport, const char *dhost, int dport,
+ int addressfamily, const char *log_description, PortFwdRecord *pfr,
+ ssh_sharing_connstate *share_ctx);
+SshChannel *ssh1_serverside_x11_open(
+ ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi);
+SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan);
+
+void ssh1_connection_direction_specific_setup(
+ struct ssh1_connection_state *s);
+bool ssh1_handle_direction_specific_packet(
+ struct ssh1_connection_state *s, PktIn *pktin);
+
+bool ssh1_check_termination(struct ssh1_connection_state *s);
+
+bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s);
diff --git a/ssh/connection2-client.c b/ssh/connection2-client.c
new file mode 100644
index 00000000..f17d1e21
--- /dev/null
+++ b/ssh/connection2-client.c
@@ -0,0 +1,511 @@
+/*
+ * Client-specific parts of the SSH-2 connection layer.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "channel.h"
+#include "sshcr.h"
+#include "connection2.h"
+
+static ChanopenResult chan_open_x11(
+ struct ssh2_connection_state *s, SshChannel *sc,
+ ptrlen peeraddr, int peerport)
+{
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ char *peeraddr_str;
+ Channel *ch;
+
+ ppl_logevent("Received X11 connect request from %.*s:%d",
+ PTRLEN_PRINTF(peeraddr), peerport);
+
+ if (!s->X11_fwd_enabled && !s->connshare) {
+ CHANOPEN_RETURN_FAILURE(
+ SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
+ ("X11 forwarding is not enabled"));
+ }
+
+ peeraddr_str = peeraddr.ptr ? mkstr(peeraddr) : NULL;
+ ch = x11_new_channel(
+ s->x11authtree, sc, peeraddr_str, peerport, s->connshare != NULL);
+ sfree(peeraddr_str);
+ ppl_logevent("Opened X11 forward channel");
+ CHANOPEN_RETURN_SUCCESS(ch);
+}
+
+static ChanopenResult chan_open_forwarded_tcpip(
+ struct ssh2_connection_state *s, SshChannel *sc,
+ ptrlen fwdaddr, int fwdport, ptrlen peeraddr, int peerport)
+{
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ struct ssh_rportfwd pf, *realpf;
+ Channel *ch;
+ char *err;
+
+ ppl_logevent("Received remote port %.*s:%d open request from %.*s:%d",
+ PTRLEN_PRINTF(fwdaddr), fwdport,
+ PTRLEN_PRINTF(peeraddr), peerport);
+
+ pf.shost = mkstr(fwdaddr);
+ pf.sport = fwdport;
+ realpf = find234(s->rportfwds, &pf, NULL);
+ sfree(pf.shost);
+
+ if (realpf == NULL) {
+ CHANOPEN_RETURN_FAILURE(
+ SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
+ ("Remote port is not recognised"));
+ }
+
+ if (realpf->share_ctx) {
+ /*
+ * This port forwarding is on behalf of a connection-sharing
+ * downstream.
+ */
+ CHANOPEN_RETURN_DOWNSTREAM(realpf->share_ctx);
+ }
+
+ err = portfwdmgr_connect(
+ s->portfwdmgr, &ch, realpf->dhost, realpf->dport,
+ sc, realpf->addressfamily);
+ ppl_logevent("Attempting to forward remote port to %s:%d",
+ realpf->dhost, realpf->dport);
+ if (err != NULL) {
+ ppl_logevent("Port open failed: %s", err);
+ sfree(err);
+ CHANOPEN_RETURN_FAILURE(
+ SSH2_OPEN_CONNECT_FAILED,
+ ("Port open failed"));
+ }
+
+ ppl_logevent("Forwarded port opened successfully");
+ CHANOPEN_RETURN_SUCCESS(ch);
+}
+
+static ChanopenResult chan_open_auth_agent(
+ struct ssh2_connection_state *s, SshChannel *sc)
+{
+ if (!ssh_agent_forwarding_permitted(&s->cl)) {
+ CHANOPEN_RETURN_FAILURE(
+ SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
+ ("Agent forwarding is not enabled"));
+ }
+
+ /*
+ * If possible, make a stream-oriented connection to the agent and
+ * set up an ordinary port-forwarding type channel over it.
+ */
+ Plug *plug;
+ Channel *ch = portfwd_raw_new(&s->cl, &plug, true);
+ Socket *skt = agent_connect(plug);
+
+ if (!sk_socket_error(skt)) {
+ portfwd_raw_setup(ch, skt, sc);
+ CHANOPEN_RETURN_SUCCESS(ch);
+ } else {
+ portfwd_raw_free(ch);
+ /*
+ * Otherwise, fall back to the old-fashioned system of parsing the
+ * forwarded data stream ourselves for message boundaries, and
+ * passing each individual message to the one-off agent_query().
+ */
+ CHANOPEN_RETURN_SUCCESS(agentf_new(sc));
+ }
+}
+
+ChanopenResult ssh2_connection_parse_channel_open(
+ struct ssh2_connection_state *s, ptrlen type,
+ PktIn *pktin, SshChannel *sc)
+{
+ if (ptrlen_eq_string(type, "x11")) {
+ ptrlen peeraddr = get_string(pktin);
+ int peerport = get_uint32(pktin);
+
+ return chan_open_x11(s, sc, peeraddr, peerport);
+ } else if (ptrlen_eq_string(type, "forwarded-tcpip")) {
+ ptrlen fwdaddr = get_string(pktin);
+ int fwdport = toint(get_uint32(pktin));
+ ptrlen peeraddr = get_string(pktin);
+ int peerport = toint(get_uint32(pktin));
+
+ return chan_open_forwarded_tcpip(
+ s, sc, fwdaddr, fwdport, peeraddr, peerport);
+ } else if (ptrlen_eq_string(type, "auth-agent@openssh.com")) {
+ return chan_open_auth_agent(s, sc);
+ } else {
+ CHANOPEN_RETURN_FAILURE(
+ SSH2_OPEN_UNKNOWN_CHANNEL_TYPE,
+ ("Unsupported channel type requested"));
+ }
+}
+
+bool ssh2_connection_parse_global_request(
+ struct ssh2_connection_state *s, ptrlen type, PktIn *pktin)
+{
+ /*
+ * We don't know of any global requests that an SSH client needs
+ * to honour.
+ */
+ return false;
+}
+
+PktOut *ssh2_portfwd_chanopen(
+ struct ssh2_connection_state *s, struct ssh2_channel *c,
+ const char *hostname, int port,
+ const char *description, const SocketPeerInfo *peerinfo)
+{
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ PktOut *pktout;
+
+ /*
+ * In client mode, this function is called by portfwdmgr in
+ * response to PortListeners that were set up in
+ * portfwdmgr_config, which means that the hostname and port
+ * parameters will indicate the host we want to tell the server to
+ * connect _to_.
+ */
+
+ ppl_logevent("Opening connection to %s:%d for %s",
+ hostname, port, description);
+
+ pktout = ssh2_chanopen_init(c, "direct-tcpip");
+ {
+ char *trimmed_host = host_strduptrim(hostname);
+ put_stringz(pktout, trimmed_host);
+ sfree(trimmed_host);
+ }
+ put_uint32(pktout, port);
+
+ /*
+ * We make up values for the originator data; partly it's too much
+ * hassle to keep track, and partly I'm not convinced the server
+ * should be told details like that about my local network
+ * configuration. The "originator IP address" is syntactically a
+ * numeric IP address, and some servers (e.g., Tectia) get upset
+ * if it doesn't match this syntax.
+ */
+ put_stringz(pktout, "0.0.0.0");
+ put_uint32(pktout, 0);
+
+ return pktout;
+}
+
+static int ssh2_rportfwd_cmp(void *av, void *bv)
+{
+ struct ssh_rportfwd *a = (struct ssh_rportfwd *) av;
+ struct ssh_rportfwd *b = (struct ssh_rportfwd *) bv;
+ int i;
+ if ( (i = strcmp(a->shost, b->shost)) != 0)
+ return i < 0 ? -1 : +1;
+ if (a->sport > b->sport)
+ return +1;
+ if (a->sport < b->sport)
+ return -1;
+ return 0;
+}
+
+static void ssh2_rportfwd_globreq_response(struct ssh2_connection_state *s,
+ PktIn *pktin, void *ctx)
+{
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx;
+
+ if (pktin->type == SSH2_MSG_REQUEST_SUCCESS) {
+ ppl_logevent("Remote port forwarding from %s enabled",
+ rpf->log_description);
+ } else {
+ ppl_logevent("Remote port forwarding from %s refused",
+ rpf->log_description);
+
+ struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf);
+ assert(realpf == rpf);
+ portfwdmgr_close(s->portfwdmgr, rpf->pfr);
+ free_rportfwd(rpf);
+ }
+}
+
+struct ssh_rportfwd *ssh2_rportfwd_alloc(
+ ConnectionLayer *cl,
+ const char *shost, int sport, const char *dhost, int dport,
+ int addressfamily, const char *log_description, PortFwdRecord *pfr,
+ ssh_sharing_connstate *share_ctx)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd);
+
+ if (!s->rportfwds)
+ s->rportfwds = newtree234(ssh2_rportfwd_cmp);
+
+ rpf->shost = dupstr(shost);
+ rpf->sport = sport;
+ rpf->dhost = dupstr(dhost);
+ rpf->dport = dport;
+ rpf->addressfamily = addressfamily;
+ rpf->log_description = dupstr(log_description);
+ rpf->pfr = pfr;
+ rpf->share_ctx = share_ctx;
+
+ if (add234(s->rportfwds, rpf) != rpf) {
+ free_rportfwd(rpf);
+ return NULL;
+ }
+
+ if (!rpf->share_ctx) {
+ PktOut *pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST);
+ put_stringz(pktout, "tcpip-forward");
+ put_bool(pktout, true); /* want reply */
+ put_stringz(pktout, rpf->shost);
+ put_uint32(pktout, rpf->sport);
+ pq_push(s->ppl.out_pq, pktout);
+
+ ssh2_queue_global_request_handler(
+ s, ssh2_rportfwd_globreq_response, rpf);
+ }
+
+ return rpf;
+}
+
+void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+
+ if (rpf->share_ctx) {
+ /*
+ * We don't manufacture a cancel-tcpip-forward message for
+ * remote port forwardings being removed on behalf of a
+ * downstream; we just pass through the one the downstream
+ * sent to us.
+ */
+ } else {
+ PktOut *pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST);
+ put_stringz(pktout, "cancel-tcpip-forward");
+ put_bool(pktout, false); /* _don't_ want reply */
+ put_stringz(pktout, rpf->shost);
+ put_uint32(pktout, rpf->sport);
+ pq_push(s->ppl.out_pq, pktout);
+ }
+
+ assert(s->rportfwds);
+ struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf);
+ assert(realpf == rpf);
+ free_rportfwd(rpf);
+}
+
+SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ struct ssh2_channel *c = snew(struct ssh2_channel);
+ PktOut *pktout;
+
+ c->connlayer = s;
+ ssh2_channel_init(c);
+ c->halfopen = true;
+ c->chan = chan;
+
+ ppl_logevent("Opening main session channel");
+
+ pktout = ssh2_chanopen_init(c, "session");
+ pq_push(s->ppl.out_pq, pktout);
+
+ return &c->sc;
+}
+
+SshChannel *ssh2_serverside_x11_open(
+ ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi)
+{
+ unreachable("Should never be called in the client");
+}
+
+SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan)
+{
+ unreachable("Should never be called in the client");
+}
+
+static void ssh2_channel_response(
+ struct ssh2_channel *c, PktIn *pkt, void *ctx)
+{
+ /* If pkt==NULL (because this handler has been called in response
+ * to CHANNEL_CLOSE arriving while the request was still
+ * outstanding), we treat that the same as CHANNEL_FAILURE. */
+ chan_request_response(c->chan,
+ pkt && pkt->type == SSH2_MSG_CHANNEL_SUCCESS);
+}
+
+void ssh2channel_start_shell(SshChannel *sc, bool want_reply)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+
+ PktOut *pktout = ssh2_chanreq_init(
+ c, "shell", want_reply ? ssh2_channel_response : NULL, NULL);
+ pq_push(s->ppl.out_pq, pktout);
+}
+
+void ssh2channel_start_command(
+ SshChannel *sc, bool want_reply, const char *command)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+
+ PktOut *pktout = ssh2_chanreq_init(
+ c, "exec", want_reply ? ssh2_channel_response : NULL, NULL);
+ put_stringz(pktout, command);
+ pq_push(s->ppl.out_pq, pktout);
+}
+
+bool ssh2channel_start_subsystem(
+ SshChannel *sc, bool want_reply, const char *subsystem)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+
+ PktOut *pktout = ssh2_chanreq_init(
+ c, "subsystem", want_reply ? ssh2_channel_response : NULL, NULL);
+ put_stringz(pktout, subsystem);
+ pq_push(s->ppl.out_pq, pktout);
+
+ return true;
+}
+
+void ssh2channel_send_exit_status(SshChannel *sc, int status)
+{
+ unreachable("Should never be called in the client");
+}
+
+void ssh2channel_send_exit_signal(
+ SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg)
+{
+ unreachable("Should never be called in the client");
+}
+
+void ssh2channel_send_exit_signal_numeric(
+ SshChannel *sc, int signum, bool core_dumped, ptrlen msg)
+{
+ unreachable("Should never be called in the client");
+}
+
+void ssh2channel_request_x11_forwarding(
+ SshChannel *sc, bool want_reply, const char *authproto,
+ const char *authdata, int screen_number, bool oneshot)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+
+ PktOut *pktout = ssh2_chanreq_init(
+ c, "x11-req", want_reply ? ssh2_channel_response : NULL, NULL);
+ put_bool(pktout, oneshot);
+ put_stringz(pktout, authproto);
+ put_stringz(pktout, authdata);
+ put_uint32(pktout, screen_number);
+ pq_push(s->ppl.out_pq, pktout);
+}
+
+void ssh2channel_request_agent_forwarding(SshChannel *sc, bool want_reply)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+
+ PktOut *pktout = ssh2_chanreq_init(
+ c, "auth-agent-req@openssh.com",
+ want_reply ? ssh2_channel_response : NULL, NULL);
+ pq_push(s->ppl.out_pq, pktout);
+}
+
+void ssh2channel_request_pty(
+ SshChannel *sc, bool want_reply, Conf *conf, int w, int h)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+ strbuf *modebuf;
+
+ PktOut *pktout = ssh2_chanreq_init(
+ c, "pty-req", want_reply ? ssh2_channel_response : NULL, NULL);
+ put_stringz(pktout, conf_get_str(conf, CONF_termtype));
+ put_uint32(pktout, w);
+ put_uint32(pktout, h);
+ put_uint32(pktout, 0); /* pixel width */
+ put_uint32(pktout, 0); /* pixel height */
+ modebuf = strbuf_new();
+ write_ttymodes_to_packet(
+ BinarySink_UPCAST(modebuf), 2,
+ get_ttymodes_from_conf(s->ppl.seat, conf));
+ put_stringsb(pktout, modebuf);
+ pq_push(s->ppl.out_pq, pktout);
+}
+
+bool ssh2channel_send_env_var(
+ SshChannel *sc, bool want_reply, const char *var, const char *value)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+
+ PktOut *pktout = ssh2_chanreq_init(
+ c, "env", want_reply ? ssh2_channel_response : NULL, NULL);
+ put_stringz(pktout, var);
+ put_stringz(pktout, value);
+ pq_push(s->ppl.out_pq, pktout);
+
+ return true;
+}
+
+bool ssh2channel_send_serial_break(SshChannel *sc, bool want_reply, int length)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+
+ PktOut *pktout = ssh2_chanreq_init(
+ c, "break", want_reply ? ssh2_channel_response : NULL, NULL);
+ put_uint32(pktout, length);
+ pq_push(s->ppl.out_pq, pktout);
+
+ return true;
+}
+
+bool ssh2channel_send_signal(
+ SshChannel *sc, bool want_reply, const char *signame)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+
+ PktOut *pktout = ssh2_chanreq_init(
+ c, "signal", want_reply ? ssh2_channel_response : NULL, NULL);
+ put_stringz(pktout, signame);
+ pq_push(s->ppl.out_pq, pktout);
+
+ return true;
+}
+
+void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+
+ PktOut *pktout = ssh2_chanreq_init(c, "window-change", NULL, NULL);
+ put_uint32(pktout, w);
+ put_uint32(pktout, h);
+ put_uint32(pktout, 0); /* pixel width */
+ put_uint32(pktout, 0); /* pixel height */
+ pq_push(s->ppl.out_pq, pktout);
+}
+
+bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s)
+{
+ seat_set_trust_status(s->ppl.seat, false);
+ if (!seat_has_mixed_input_stream(s->ppl.seat))
+ return false;
+ if (seat_can_set_trust_status(s->ppl.seat))
+ return false;
+ if (ssh_is_bare(s->ppl.ssh))
+ return false;
+ return true;
+}
diff --git a/ssh/connection2-server.c b/ssh/connection2-server.c
new file mode 100644
index 00000000..c871b4b3
--- /dev/null
+++ b/ssh/connection2-server.c
@@ -0,0 +1,306 @@
+/*
+ * Server-specific parts of the SSH-2 connection layer.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "channel.h"
+#include "sshcr.h"
+#include "connection2.h"
+#include "server.h"
+
+void ssh2connection_server_configure(
+ PacketProtocolLayer *ppl, const SftpServerVtable *sftpserver_vt,
+ const SshServerConfig *ssc)
+{
+ struct ssh2_connection_state *s =
+ container_of(ppl, struct ssh2_connection_state, ppl);
+ s->sftpserver_vt = sftpserver_vt;
+ s->ssc = ssc;
+}
+
+static ChanopenResult chan_open_session(
+ struct ssh2_connection_state *s, SshChannel *sc)
+{
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+
+ ppl_logevent("Opened session channel");
+ CHANOPEN_RETURN_SUCCESS(sesschan_new(sc, s->ppl.logctx,
+ s->sftpserver_vt, s->ssc));
+}
+
+static ChanopenResult chan_open_direct_tcpip(
+ struct ssh2_connection_state *s, SshChannel *sc,
+ ptrlen dstaddr, int dstport, ptrlen peeraddr, int peerport)
+{
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ Channel *ch;
+ char *dstaddr_str, *err;
+
+ dstaddr_str = mkstr(dstaddr);
+
+ ppl_logevent("Received request to connect to port %s:%d (from %.*s:%d)",
+ dstaddr_str, dstport, PTRLEN_PRINTF(peeraddr), peerport);
+ err = portfwdmgr_connect(
+ s->portfwdmgr, &ch, dstaddr_str, dstport, sc, ADDRTYPE_UNSPEC);
+
+ sfree(dstaddr_str);
+
+ if (err != NULL) {
+ ppl_logevent("Port open failed: %s", err);
+ sfree(err);
+ CHANOPEN_RETURN_FAILURE(
+ SSH2_OPEN_CONNECT_FAILED, ("Connection failed"));
+ }
+
+ ppl_logevent("Port opened successfully");
+ CHANOPEN_RETURN_SUCCESS(ch);
+}
+
+ChanopenResult ssh2_connection_parse_channel_open(
+ struct ssh2_connection_state *s, ptrlen type,
+ PktIn *pktin, SshChannel *sc)
+{
+ if (ptrlen_eq_string(type, "session")) {
+ return chan_open_session(s, sc);
+ } else if (ptrlen_eq_string(type, "direct-tcpip")) {
+ ptrlen dstaddr = get_string(pktin);
+ int dstport = toint(get_uint32(pktin));
+ ptrlen peeraddr = get_string(pktin);
+ int peerport = toint(get_uint32(pktin));
+ return chan_open_direct_tcpip(
+ s, sc, dstaddr, dstport, peeraddr, peerport);
+ } else {
+ CHANOPEN_RETURN_FAILURE(
+ SSH2_OPEN_UNKNOWN_CHANNEL_TYPE,
+ ("Unsupported channel type requested"));
+ }
+}
+
+bool ssh2_connection_parse_global_request(
+ struct ssh2_connection_state *s, ptrlen type, PktIn *pktin)
+{
+ if (ptrlen_eq_string(type, "tcpip-forward")) {
+ char *host = mkstr(get_string(pktin));
+ unsigned port = get_uint32(pktin);
+ /* In SSH-2, the host/port we listen on are the same host/port
+ * we want reported back to us when a connection comes in,
+ * because that's what we tell the client */
+ bool toret = portfwdmgr_listen(
+ s->portfwdmgr, host, port, host, port, s->conf);
+ sfree(host);
+ return toret;
+ } else if (ptrlen_eq_string(type, "cancel-tcpip-forward")) {
+ char *host = mkstr(get_string(pktin));
+ unsigned port = get_uint32(pktin);
+ bool toret = portfwdmgr_unlisten(s->portfwdmgr, host, port);
+ sfree(host);
+ return toret;
+ } else {
+ /* Unrecognised request. */
+ return false;
+ }
+}
+
+PktOut *ssh2_portfwd_chanopen(
+ struct ssh2_connection_state *s, struct ssh2_channel *c,
+ const char *hostname, int port,
+ const char *description, const SocketPeerInfo *pi)
+{
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ PktOut *pktout;
+
+ /*
+ * In server mode, this function is called by portfwdmgr in
+ * response to PortListeners that were set up by calling
+ * portfwdmgr_listen, which means that the hostname and port
+ * parameters will identify the listening socket on which a
+ * connection just came in.
+ */
+
+ if (pi && pi->log_text)
+ ppl_logevent("Forwarding connection to listening port %s:%d from %s",
+ hostname, port, pi->log_text);
+ else
+ ppl_logevent("Forwarding connection to listening port %s:%d",
+ hostname, port);
+
+ pktout = ssh2_chanopen_init(c, "forwarded-tcpip");
+ put_stringz(pktout, hostname);
+ put_uint32(pktout, port);
+ put_stringz(pktout, (pi && pi->addr_text ? pi->addr_text : "0.0.0.0"));
+ put_uint32(pktout, (pi && pi->port >= 0 ? pi->port : 0));
+
+ return pktout;
+}
+
+struct ssh_rportfwd *ssh2_rportfwd_alloc(
+ ConnectionLayer *cl,
+ const char *shost, int sport, const char *dhost, int dport,
+ int addressfamily, const char *log_description, PortFwdRecord *pfr,
+ ssh_sharing_connstate *share_ctx)
+{
+ unreachable("Should never be called in the server");
+}
+
+void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf)
+{
+ unreachable("Should never be called in the server");
+}
+
+SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan)
+{
+ unreachable("Should never be called in the server");
+}
+
+SshChannel *ssh2_serverside_x11_open(
+ ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ struct ssh2_channel *c = snew(struct ssh2_channel);
+ PktOut *pktout;
+
+ c->connlayer = s;
+ ssh2_channel_init(c);
+ c->halfopen = true;
+ c->chan = chan;
+
+ ppl_logevent("Forwarding X11 channel to client");
+
+ pktout = ssh2_chanopen_init(c, "x11");
+ put_stringz(pktout, (pi && pi->addr_text ? pi->addr_text : "0.0.0.0"));
+ put_uint32(pktout, (pi && pi->port >= 0 ? pi->port : 0));
+ pq_push(s->ppl.out_pq, pktout);
+
+ return &c->sc;
+}
+
+SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ struct ssh2_channel *c = snew(struct ssh2_channel);
+ PktOut *pktout;
+
+ c->connlayer = s;
+ ssh2_channel_init(c);
+ c->halfopen = true;
+ c->chan = chan;
+
+ ppl_logevent("Forwarding SSH agent to client");
+
+ pktout = ssh2_chanopen_init(c, "auth-agent@openssh.com");
+ pq_push(s->ppl.out_pq, pktout);
+
+ return &c->sc;
+}
+
+void ssh2channel_start_shell(SshChannel *sc, bool want_reply)
+{
+ unreachable("Should never be called in the server");
+}
+
+void ssh2channel_start_command(
+ SshChannel *sc, bool want_reply, const char *command)
+{
+ unreachable("Should never be called in the server");
+}
+
+bool ssh2channel_start_subsystem(
+ SshChannel *sc, bool want_reply, const char *subsystem)
+{
+ unreachable("Should never be called in the server");
+}
+
+void ssh2channel_send_exit_status(SshChannel *sc, int status)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+
+ PktOut *pktout = ssh2_chanreq_init(c, "exit-status", NULL, NULL);
+ put_uint32(pktout, status);
+
+ pq_push(s->ppl.out_pq, pktout);
+}
+
+void ssh2channel_send_exit_signal(
+ SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+
+ PktOut *pktout = ssh2_chanreq_init(c, "exit-signal", NULL, NULL);
+ put_stringpl(pktout, signame);
+ put_bool(pktout, core_dumped);
+ put_stringpl(pktout, msg);
+ put_stringz(pktout, ""); /* language tag */
+
+ pq_push(s->ppl.out_pq, pktout);
+}
+
+void ssh2channel_send_exit_signal_numeric(
+ SshChannel *sc, int signum, bool core_dumped, ptrlen msg)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+
+ PktOut *pktout = ssh2_chanreq_init(c, "exit-signal", NULL, NULL);
+ put_uint32(pktout, signum);
+ put_bool(pktout, core_dumped);
+ put_stringpl(pktout, msg);
+ put_stringz(pktout, ""); /* language tag */
+
+ pq_push(s->ppl.out_pq, pktout);
+}
+
+void ssh2channel_request_x11_forwarding(
+ SshChannel *sc, bool want_reply, const char *authproto,
+ const char *authdata, int screen_number, bool oneshot)
+{
+ unreachable("Should never be called in the server");
+}
+
+void ssh2channel_request_agent_forwarding(SshChannel *sc, bool want_reply)
+{
+ unreachable("Should never be called in the server");
+}
+
+void ssh2channel_request_pty(
+ SshChannel *sc, bool want_reply, Conf *conf, int w, int h)
+{
+ unreachable("Should never be called in the server");
+}
+
+bool ssh2channel_send_env_var(
+ SshChannel *sc, bool want_reply, const char *var, const char *value)
+{
+ unreachable("Should never be called in the server");
+}
+
+bool ssh2channel_send_serial_break(SshChannel *sc, bool want_reply, int length)
+{
+ unreachable("Should never be called in the server");
+}
+
+bool ssh2channel_send_signal(
+ SshChannel *sc, bool want_reply, const char *signame)
+{
+ unreachable("Should never be called in the server");
+}
+
+void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h)
+{
+ unreachable("Should never be called in the server");
+}
+
+bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s)
+{
+ return false;
+}
diff --git a/ssh/connection2.c b/ssh/connection2.c
new file mode 100644
index 00000000..77b8d0ec
--- /dev/null
+++ b/ssh/connection2.c
@@ -0,0 +1,1746 @@
+/*
+ * Packet protocol layer for the SSH-2 connection protocol (RFC 4254).
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "channel.h"
+#include "sshcr.h"
+#include "connection2.h"
+
+static void ssh2_connection_free(PacketProtocolLayer *);
+static void ssh2_connection_process_queue(PacketProtocolLayer *);
+static bool ssh2_connection_get_specials(
+ PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx);
+static void ssh2_connection_special_cmd(PacketProtocolLayer *ppl,
+ SessionSpecialCode code, int arg);
+static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
+
+static const PacketProtocolLayerVtable ssh2_connection_vtable = {
+ .free = ssh2_connection_free,
+ .process_queue = ssh2_connection_process_queue,
+ .get_specials = ssh2_connection_get_specials,
+ .special_cmd = ssh2_connection_special_cmd,
+ .reconfigure = ssh2_connection_reconfigure,
+ .queued_data_size = ssh_ppl_default_queued_data_size,
+ .name = "ssh-connection",
+};
+
+static SshChannel *ssh2_lportfwd_open(
+ ConnectionLayer *cl, const char *hostname, int port,
+ const char *description, const SocketPeerInfo *pi, Channel *chan);
+static struct X11FakeAuth *ssh2_add_x11_display(
+ ConnectionLayer *cl, int authtype, struct X11Display *x11disp);
+static struct X11FakeAuth *ssh2_add_sharing_x11_display(
+ ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs,
+ share_channel *share_chan);
+static void ssh2_remove_sharing_x11_display(ConnectionLayer *cl,
+ struct X11FakeAuth *auth);
+static void ssh2_send_packet_from_downstream(
+ ConnectionLayer *cl, unsigned id, int type,
+ const void *pkt, int pktlen, const char *additional_log_text);
+static unsigned ssh2_alloc_sharing_channel(
+ ConnectionLayer *cl, ssh_sharing_connstate *connstate);
+static void ssh2_delete_sharing_channel(
+ ConnectionLayer *cl, unsigned localid);
+static void ssh2_sharing_queue_global_request(
+ ConnectionLayer *cl, ssh_sharing_connstate *share_ctx);
+static void ssh2_sharing_no_more_downstreams(ConnectionLayer *cl);
+static bool ssh2_agent_forwarding_permitted(ConnectionLayer *cl);
+static void ssh2_terminal_size(ConnectionLayer *cl, int width, int height);
+static void ssh2_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize);
+static size_t ssh2_stdin_backlog(ConnectionLayer *cl);
+static void ssh2_throttle_all_channels(ConnectionLayer *cl, bool throttled);
+static bool ssh2_ldisc_option(ConnectionLayer *cl, int option);
+static void ssh2_set_ldisc_option(ConnectionLayer *cl, int option, bool value);
+static void ssh2_enable_x_fwd(ConnectionLayer *cl);
+static void ssh2_set_wants_user_input(ConnectionLayer *cl, bool wanted);
+static bool ssh2_get_wants_user_input(ConnectionLayer *cl);
+static void ssh2_got_user_input(ConnectionLayer *cl);
+
+static const ConnectionLayerVtable ssh2_connlayer_vtable = {
+ .rportfwd_alloc = ssh2_rportfwd_alloc,
+ .rportfwd_remove = ssh2_rportfwd_remove,
+ .lportfwd_open = ssh2_lportfwd_open,
+ .session_open = ssh2_session_open,
+ .serverside_x11_open = ssh2_serverside_x11_open,
+ .serverside_agent_open = ssh2_serverside_agent_open,
+ .add_x11_display = ssh2_add_x11_display,
+ .add_sharing_x11_display = ssh2_add_sharing_x11_display,
+ .remove_sharing_x11_display = ssh2_remove_sharing_x11_display,
+ .send_packet_from_downstream = ssh2_send_packet_from_downstream,
+ .alloc_sharing_channel = ssh2_alloc_sharing_channel,
+ .delete_sharing_channel = ssh2_delete_sharing_channel,
+ .sharing_queue_global_request = ssh2_sharing_queue_global_request,
+ .sharing_no_more_downstreams = ssh2_sharing_no_more_downstreams,
+ .agent_forwarding_permitted = ssh2_agent_forwarding_permitted,
+ .terminal_size = ssh2_terminal_size,
+ .stdout_unthrottle = ssh2_stdout_unthrottle,
+ .stdin_backlog = ssh2_stdin_backlog,
+ .throttle_all_channels = ssh2_throttle_all_channels,
+ .ldisc_option = ssh2_ldisc_option,
+ .set_ldisc_option = ssh2_set_ldisc_option,
+ .enable_x_fwd = ssh2_enable_x_fwd,
+ .set_wants_user_input = ssh2_set_wants_user_input,
+ .get_wants_user_input = ssh2_get_wants_user_input,
+ .got_user_input = ssh2_got_user_input,
+};
+
+static char *ssh2_channel_open_failure_error_text(PktIn *pktin)
+{
+ static const char *const reasons[] = {
+ NULL,
+ "Administratively prohibited",
+ "Connect failed",
+ "Unknown channel type",
+ "Resource shortage",
+ };
+ unsigned reason_code;
+ const char *reason_code_string;
+ char reason_code_buf[256];
+ ptrlen reason;
+
+ reason_code = get_uint32(pktin);
+ if (reason_code < lenof(reasons) && reasons[reason_code]) {
+ reason_code_string = reasons[reason_code];
+ } else {
+ reason_code_string = reason_code_buf;
+ sprintf(reason_code_buf, "unknown reason code %#x", reason_code);
+ }
+
+ reason = get_string(pktin);
+
+ return dupprintf("%s [%.*s]", reason_code_string, PTRLEN_PRINTF(reason));
+}
+
+static size_t ssh2channel_write(
+ SshChannel *c, bool is_stderr, const void *buf, size_t len);
+static void ssh2channel_write_eof(SshChannel *c);
+static void ssh2channel_initiate_close(SshChannel *c, const char *err);
+static void ssh2channel_unthrottle(SshChannel *c, size_t bufsize);
+static Conf *ssh2channel_get_conf(SshChannel *c);
+static void ssh2channel_window_override_removed(SshChannel *c);
+static void ssh2channel_x11_sharing_handover(
+ SshChannel *c, ssh_sharing_connstate *share_cs, share_channel *share_chan,
+ const char *peer_addr, int peer_port, int endian,
+ int protomajor, int protominor, const void *initial_data, int initial_len);
+static void ssh2channel_hint_channel_is_simple(SshChannel *c);
+
+static const SshChannelVtable ssh2channel_vtable = {
+ .write = ssh2channel_write,
+ .write_eof = ssh2channel_write_eof,
+ .initiate_close = ssh2channel_initiate_close,
+ .unthrottle = ssh2channel_unthrottle,
+ .get_conf = ssh2channel_get_conf,
+ .window_override_removed = ssh2channel_window_override_removed,
+ .x11_sharing_handover = ssh2channel_x11_sharing_handover,
+ .send_exit_status = ssh2channel_send_exit_status,
+ .send_exit_signal = ssh2channel_send_exit_signal,
+ .send_exit_signal_numeric = ssh2channel_send_exit_signal_numeric,
+ .request_x11_forwarding = ssh2channel_request_x11_forwarding,
+ .request_agent_forwarding = ssh2channel_request_agent_forwarding,
+ .request_pty = ssh2channel_request_pty,
+ .send_env_var = ssh2channel_send_env_var,
+ .start_shell = ssh2channel_start_shell,
+ .start_command = ssh2channel_start_command,
+ .start_subsystem = ssh2channel_start_subsystem,
+ .send_serial_break = ssh2channel_send_serial_break,
+ .send_signal = ssh2channel_send_signal,
+ .send_terminal_size_change = ssh2channel_send_terminal_size_change,
+ .hint_channel_is_simple = ssh2channel_hint_channel_is_simple,
+};
+
+static void ssh2_channel_check_close(struct ssh2_channel *c);
+static void ssh2_channel_try_eof(struct ssh2_channel *c);
+static void ssh2_set_window(struct ssh2_channel *c, int newwin);
+static size_t ssh2_try_send(struct ssh2_channel *c);
+static void ssh2_try_send_and_unthrottle(struct ssh2_channel *c);
+static void ssh2_channel_check_throttle(struct ssh2_channel *c);
+static void ssh2_channel_close_local(struct ssh2_channel *c,
+ const char *reason);
+static void ssh2_channel_destroy(struct ssh2_channel *c);
+
+static void ssh2_check_termination(struct ssh2_connection_state *s);
+
+struct outstanding_global_request {
+ gr_handler_fn_t handler;
+ void *ctx;
+ struct outstanding_global_request *next;
+};
+void ssh2_queue_global_request_handler(
+ struct ssh2_connection_state *s, gr_handler_fn_t handler, void *ctx)
+{
+ struct outstanding_global_request *ogr =
+ snew(struct outstanding_global_request);
+ ogr->handler = handler;
+ ogr->ctx = ctx;
+ ogr->next = NULL;
+ if (s->globreq_tail)
+ s->globreq_tail->next = ogr;
+ else
+ s->globreq_head = ogr;
+ s->globreq_tail = ogr;
+}
+
+static int ssh2_channelcmp(void *av, void *bv)
+{
+ const struct ssh2_channel *a = (const struct ssh2_channel *) av;
+ const struct ssh2_channel *b = (const struct ssh2_channel *) bv;
+ if (a->localid < b->localid)
+ return -1;
+ if (a->localid > b->localid)
+ return +1;
+ return 0;
+}
+
+static int ssh2_channelfind(void *av, void *bv)
+{
+ const unsigned *a = (const unsigned *) av;
+ const struct ssh2_channel *b = (const struct ssh2_channel *) bv;
+ if (*a < b->localid)
+ return -1;
+ if (*a > b->localid)
+ return +1;
+ return 0;
+}
+
+/*
+ * Each channel has a queue of outstanding CHANNEL_REQUESTS and their
+ * handlers.
+ */
+struct outstanding_channel_request {
+ cr_handler_fn_t handler;
+ void *ctx;
+ struct outstanding_channel_request *next;
+};
+
+static void ssh2_channel_free(struct ssh2_channel *c)
+{
+ bufchain_clear(&c->outbuffer);
+ bufchain_clear(&c->errbuffer);
+ while (c->chanreq_head) {
+ struct outstanding_channel_request *chanreq = c->chanreq_head;
+ c->chanreq_head = c->chanreq_head->next;
+ sfree(chanreq);
+ }
+ if (c->chan) {
+ struct ssh2_connection_state *s = c->connlayer;
+ if (s->mainchan_sc == &c->sc) {
+ s->mainchan = NULL;
+ s->mainchan_sc = NULL;
+ }
+ chan_free(c->chan);
+ }
+ sfree(c);
+}
+
+PacketProtocolLayer *ssh2_connection_new(
+ Ssh *ssh, ssh_sharing_state *connshare, bool is_simple,
+ Conf *conf, const char *peer_verstring, bufchain *user_input,
+ ConnectionLayer **cl_out)
+{
+ struct ssh2_connection_state *s = snew(struct ssh2_connection_state);
+ memset(s, 0, sizeof(*s));
+ s->ppl.vt = &ssh2_connection_vtable;
+
+ s->conf = conf_copy(conf);
+
+ s->ssh_is_simple = is_simple;
+
+ /*
+ * If the ssh_no_shell option is enabled, we disable the usual
+ * termination check, so that we persist even in the absence of
+ * any at all channels (because our purpose is probably to be a
+ * background port forwarder).
+ */
+ s->persistent = conf_get_bool(s->conf, CONF_ssh_no_shell);
+
+ s->connshare = connshare;
+ s->peer_verstring = dupstr(peer_verstring);
+
+ s->channels = newtree234(ssh2_channelcmp);
+
+ s->x11authtree = newtree234(x11_authcmp);
+
+ s->user_input = user_input;
+
+ /* Need to get the log context for s->cl now, because we won't be
+ * helpfully notified when a copy is written into s->ppl by our
+ * owner. */
+ s->cl.vt = &ssh2_connlayer_vtable;
+ s->cl.logctx = ssh_get_logctx(ssh);
+
+ s->portfwdmgr = portfwdmgr_new(&s->cl);
+
+ *cl_out = &s->cl;
+ if (s->connshare)
+ ssh_connshare_provide_connlayer(s->connshare, &s->cl);
+
+ return &s->ppl;
+}
+
+static void ssh2_connection_free(PacketProtocolLayer *ppl)
+{
+ struct ssh2_connection_state *s =
+ container_of(ppl, struct ssh2_connection_state, ppl);
+ struct X11FakeAuth *auth;
+ struct ssh2_channel *c;
+ struct ssh_rportfwd *rpf;
+
+ sfree(s->peer_verstring);
+
+ conf_free(s->conf);
+
+ while ((c = delpos234(s->channels, 0)) != NULL)
+ ssh2_channel_free(c);
+ freetree234(s->channels);
+
+ while ((auth = delpos234(s->x11authtree, 0)) != NULL) {
+ if (auth->disp)
+ x11_free_display(auth->disp);
+ x11_free_fake_auth(auth);
+ }
+ freetree234(s->x11authtree);
+
+ if (s->rportfwds) {
+ while ((rpf = delpos234(s->rportfwds, 0)) != NULL)
+ free_rportfwd(rpf);
+ freetree234(s->rportfwds);
+ }
+ portfwdmgr_free(s->portfwdmgr);
+
+ if (s->antispoof_prompt)
+ free_prompts(s->antispoof_prompt);
+
+ delete_callbacks_for_context(s);
+
+ sfree(s);
+}
+
+static bool ssh2_connection_filter_queue(struct ssh2_connection_state *s)
+{
+ PktIn *pktin;
+ PktOut *pktout;
+ ptrlen type, data;
+ struct ssh2_channel *c;
+ struct outstanding_channel_request *ocr;
+ unsigned localid, remid, winsize, pktsize, ext_type;
+ bool want_reply, reply_success, expect_halfopen;
+ ChanopenResult chanopen_result;
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+
+ while (1) {
+ if (ssh2_common_filter_queue(&s->ppl))
+ return true;
+ if ((pktin = pq_peek(s->ppl.in_pq)) == NULL)
+ return false;
+
+ switch (pktin->type) {
+ case SSH2_MSG_GLOBAL_REQUEST:
+ type = get_string(pktin);
+ want_reply = get_bool(pktin);
+
+ reply_success = ssh2_connection_parse_global_request(
+ s, type, pktin);
+
+ if (want_reply) {
+ int type = (reply_success ? SSH2_MSG_REQUEST_SUCCESS :
+ SSH2_MSG_REQUEST_FAILURE);
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, type);
+ pq_push(s->ppl.out_pq, pktout);
+ }
+ pq_pop(s->ppl.in_pq);
+ break;
+
+ case SSH2_MSG_REQUEST_SUCCESS:
+ case SSH2_MSG_REQUEST_FAILURE:
+ if (!s->globreq_head) {
+ ssh_proto_error(
+ s->ppl.ssh,
+ "Received %s with no outstanding global request",
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx, s->ppl.bpp->pls->actx,
+ pktin->type));
+ return true;
+ }
+
+ s->globreq_head->handler(s, pktin, s->globreq_head->ctx);
+ {
+ struct outstanding_global_request *tmp = s->globreq_head;
+ s->globreq_head = s->globreq_head->next;
+ sfree(tmp);
+ }
+ if (!s->globreq_head)
+ s->globreq_tail = NULL;
+
+ pq_pop(s->ppl.in_pq);
+ break;
+
+ case SSH2_MSG_CHANNEL_OPEN:
+ type = get_string(pktin);
+ c = snew(struct ssh2_channel);
+ c->connlayer = s;
+ c->chan = NULL;
+
+ remid = get_uint32(pktin);
+ winsize = get_uint32(pktin);
+ pktsize = get_uint32(pktin);
+
+ chanopen_result = ssh2_connection_parse_channel_open(
+ s, type, pktin, &c->sc);
+
+ if (chanopen_result.outcome == CHANOPEN_RESULT_DOWNSTREAM) {
+ /*
+ * This channel-open request needs to go to a
+ * connection-sharing downstream, so abandon our own
+ * channel-open procedure and just pass the message on
+ * to sharing.c.
+ */
+ share_got_pkt_from_server(
+ chanopen_result.u.downstream.share_ctx, pktin->type,
+ BinarySource_UPCAST(pktin)->data,
+ BinarySource_UPCAST(pktin)->len);
+ sfree(c);
+ break;
+ }
+
+ c->remoteid = remid;
+ c->halfopen = false;
+ if (chanopen_result.outcome == CHANOPEN_RESULT_FAILURE) {
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN_FAILURE);
+ put_uint32(pktout, c->remoteid);
+ put_uint32(pktout, chanopen_result.u.failure.reason_code);
+ put_stringz(pktout, chanopen_result.u.failure.wire_message);
+ put_stringz(pktout, "en"); /* language tag */
+ pq_push(s->ppl.out_pq, pktout);
+ ppl_logevent("Rejected channel open: %s",
+ chanopen_result.u.failure.wire_message);
+ sfree(chanopen_result.u.failure.wire_message);
+ sfree(c);
+ } else {
+ c->chan = chanopen_result.u.success.channel;
+ ssh2_channel_init(c);
+ c->remwindow = winsize;
+ c->remmaxpkt = pktsize;
+ if (c->remmaxpkt > s->ppl.bpp->vt->packet_size_limit)
+ c->remmaxpkt = s->ppl.bpp->vt->packet_size_limit;
+ if (c->chan->initial_fixed_window_size) {
+ c->locwindow = c->locmaxwin = c->remlocwin =
+ c->chan->initial_fixed_window_size;
+ }
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
+ put_uint32(pktout, c->remoteid);
+ put_uint32(pktout, c->localid);
+ put_uint32(pktout, c->locwindow);
+ put_uint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */
+ pq_push(s->ppl.out_pq, pktout);
+ }
+
+ pq_pop(s->ppl.in_pq);
+ break;
+
+ case SSH2_MSG_CHANNEL_DATA:
+ case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+ case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+ case SSH2_MSG_CHANNEL_REQUEST:
+ case SSH2_MSG_CHANNEL_EOF:
+ case SSH2_MSG_CHANNEL_CLOSE:
+ case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
+ case SSH2_MSG_CHANNEL_OPEN_FAILURE:
+ case SSH2_MSG_CHANNEL_SUCCESS:
+ case SSH2_MSG_CHANNEL_FAILURE:
+ /*
+ * Common preliminary code for all the messages from the
+ * server that cite one of our channel ids: look up that
+ * channel id, check it exists, and if it's for a sharing
+ * downstream, pass it on.
+ */
+ localid = get_uint32(pktin);
+ c = find234(s->channels, &localid, ssh2_channelfind);
+
+ if (c && c->sharectx) {
+ share_got_pkt_from_server(c->sharectx, pktin->type,
+ BinarySource_UPCAST(pktin)->data,
+ BinarySource_UPCAST(pktin)->len);
+ pq_pop(s->ppl.in_pq);
+ break;
+ }
+
+ expect_halfopen = (
+ pktin->type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION ||
+ pktin->type == SSH2_MSG_CHANNEL_OPEN_FAILURE);
+
+ if (!c || c->halfopen != expect_halfopen) {
+ ssh_proto_error(s->ppl.ssh,
+ "Received %s for %s channel %u",
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type),
+ (!c ? "nonexistent" :
+ c->halfopen ? "half-open" : "open"),
+ localid);
+ return true;
+ }
+
+ switch (pktin->type) {
+ case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
+ assert(c->halfopen);
+ c->remoteid = get_uint32(pktin);
+ c->halfopen = false;
+ c->remwindow = get_uint32(pktin);
+ c->remmaxpkt = get_uint32(pktin);
+ if (c->remmaxpkt > s->ppl.bpp->vt->packet_size_limit)
+ c->remmaxpkt = s->ppl.bpp->vt->packet_size_limit;
+
+ chan_open_confirmation(c->chan);
+
+ /*
+ * Now that the channel is fully open, it's possible
+ * in principle to immediately close it. Check whether
+ * it wants us to!
+ *
+ * This can occur if a local socket error occurred
+ * between us sending out CHANNEL_OPEN and receiving
+ * OPEN_CONFIRMATION. If that happens, all we can do
+ * is immediately initiate close proceedings now that
+ * we know the server's id to put in the close
+ * message. We'll have handled that in this code by
+ * having already turned c->chan into a zombie, so its
+ * want_close method (which ssh2_channel_check_close
+ * will consult) will already be returning true.
+ */
+ ssh2_channel_check_close(c);
+
+ if (c->pending_eof)
+ ssh2_channel_try_eof(c); /* in case we had a pending EOF */
+ break;
+
+ case SSH2_MSG_CHANNEL_OPEN_FAILURE: {
+ assert(c->halfopen);
+
+ char *err = ssh2_channel_open_failure_error_text(pktin);
+ chan_open_failed(c->chan, err);
+ sfree(err);
+
+ del234(s->channels, c);
+ ssh2_channel_free(c);
+
+ break;
+ }
+
+ case SSH2_MSG_CHANNEL_DATA:
+ case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+ ext_type = (pktin->type == SSH2_MSG_CHANNEL_DATA ? 0 :
+ get_uint32(pktin));
+ data = get_string(pktin);
+ if (!get_err(pktin)) {
+ int bufsize;
+ c->locwindow -= data.len;
+ c->remlocwin -= data.len;
+ if (ext_type != 0 && ext_type != SSH2_EXTENDED_DATA_STDERR)
+ data.len = 0; /* ignore unknown extended data */
+ bufsize = chan_send(
+ c->chan, ext_type == SSH2_EXTENDED_DATA_STDERR,
+ data.ptr, data.len);
+
+ /*
+ * The channel may have turned into a connection-
+ * shared one as a result of that chan_send, e.g.
+ * if the data we just provided completed the X11
+ * auth phase and caused a callback to
+ * x11_sharing_handover. If so, do nothing
+ * further.
+ */
+ if (c->sharectx)
+ break;
+
+ /*
+ * If it looks like the remote end hit the end of
+ * its window, and we didn't want it to do that,
+ * think about using a larger window.
+ */
+ if (c->remlocwin <= 0 &&
+ c->throttle_state == UNTHROTTLED &&
+ c->locmaxwin < 0x40000000)
+ c->locmaxwin += OUR_V2_WINSIZE;
+
+ /*
+ * If we are not buffering too much data, enlarge
+ * the window again at the remote side. If we are
+ * buffering too much, we may still need to adjust
+ * the window if the server's sent excess data.
+ */
+ if (bufsize < c->locmaxwin)
+ ssh2_set_window(c, c->locmaxwin - bufsize);
+
+ /*
+ * If we're either buffering way too much data, or
+ * if we're buffering anything at all and we're in
+ * "simple" mode, throttle the whole channel.
+ */
+ if ((bufsize > c->locmaxwin ||
+ (s->ssh_is_simple && bufsize>0)) &&
+ !c->throttling_conn) {
+ c->throttling_conn = true;
+ ssh_throttle_conn(s->ppl.ssh, +1);
+ }
+ }
+ break;
+
+ case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+ if (!(c->closes & CLOSES_SENT_EOF)) {
+ c->remwindow += get_uint32(pktin);
+ ssh2_try_send_and_unthrottle(c);
+ }
+ break;
+
+ case SSH2_MSG_CHANNEL_REQUEST:
+ type = get_string(pktin);
+ want_reply = get_bool(pktin);
+
+ reply_success = false;
+
+ if (c->closes & CLOSES_SENT_CLOSE) {
+ /*
+ * We don't reply to channel requests after we've
+ * sent CHANNEL_CLOSE for the channel, because our
+ * reply might cross in the network with the other
+ * side's CHANNEL_CLOSE and arrive after they have
+ * wound the channel up completely.
+ */
+ want_reply = false;
+ }
+
+ /*
+ * Try every channel request name we recognise, no
+ * matter what the channel, and see if the Channel
+ * instance will accept it.
+ */
+ if (ptrlen_eq_string(type, "exit-status")) {
+ int exitcode = toint(get_uint32(pktin));
+ reply_success = chan_rcvd_exit_status(c->chan, exitcode);
+ } else if (ptrlen_eq_string(type, "exit-signal")) {
+ ptrlen signame;
+ int signum;
+ bool core = false;
+ ptrlen errmsg;
+ int format;
+
+ /*
+ * ICK: older versions of OpenSSH (e.g. 3.4p1)
+ * provide an `int' for the signal, despite its
+ * having been a `string' in the drafts of RFC
+ * 4254 since at least 2001. (Fixed in session.c
+ * 1.147.) Try to infer which we can safely parse
+ * it as.
+ */
+
+ size_t startpos = BinarySource_UPCAST(pktin)->pos;
+
+ for (format = 0; format < 2; format++) {
+ BinarySource_UPCAST(pktin)->pos = startpos;
+ BinarySource_UPCAST(pktin)->err = BSE_NO_ERROR;
+
+ /* placate compiler warnings about unin */
+ signame = make_ptrlen(NULL, 0);
+ signum = 0;
+
+ if (format == 0) /* standard string-based format */
+ signame = get_string(pktin);
+ else /* nonstandard integer format */
+ signum = toint(get_uint32(pktin));
+
+ core = get_bool(pktin);
+ errmsg = get_string(pktin); /* error message */
+ get_string(pktin); /* language tag */
+
+ if (!get_err(pktin) && get_avail(pktin) == 0)
+ break; /* successful parse */
+ }
+
+ switch (format) {
+ case 0:
+ reply_success = chan_rcvd_exit_signal(
+ c->chan, signame, core, errmsg);
+ break;
+ case 1:
+ reply_success = chan_rcvd_exit_signal_numeric(
+ c->chan, signum, core, errmsg);
+ break;
+ default:
+ /* Couldn't parse this message in either format */
+ reply_success = false;
+ break;
+ }
+ } else if (ptrlen_eq_string(type, "shell")) {
+ reply_success = chan_run_shell(c->chan);
+ } else if (ptrlen_eq_string(type, "exec")) {
+ ptrlen command = get_string(pktin);
+ reply_success = chan_run_command(c->chan, command);
+ } else if (ptrlen_eq_string(type, "subsystem")) {
+ ptrlen subsys = get_string(pktin);
+ reply_success = chan_run_subsystem(c->chan, subsys);
+ } else if (ptrlen_eq_string(type, "x11-req")) {
+ bool oneshot = get_bool(pktin);
+ ptrlen authproto = get_string(pktin);
+ ptrlen authdata = get_string(pktin);
+ unsigned screen_number = get_uint32(pktin);
+ reply_success = chan_enable_x11_forwarding(
+ c->chan, oneshot, authproto, authdata, screen_number);
+ } else if (ptrlen_eq_string(type,
+ "auth-agent-req@openssh.com")) {
+ reply_success = chan_enable_agent_forwarding(c->chan);
+ } else if (ptrlen_eq_string(type, "pty-req")) {
+ ptrlen termtype = get_string(pktin);
+ unsigned width = get_uint32(pktin);
+ unsigned height = get_uint32(pktin);
+ unsigned pixwidth = get_uint32(pktin);
+ unsigned pixheight = get_uint32(pktin);
+ ptrlen encoded_modes = get_string(pktin);
+ BinarySource bs_modes[1];
+ struct ssh_ttymodes modes;
+
+ BinarySource_BARE_INIT_PL(bs_modes, encoded_modes);
+ modes = read_ttymodes_from_packet(bs_modes, 2);
+ if (get_err(bs_modes) || get_avail(bs_modes) > 0) {
+ ppl_logevent("Unable to decode terminal mode string");
+ reply_success = false;
+ } else {
+ reply_success = chan_allocate_pty(
+ c->chan, termtype, width, height,
+ pixwidth, pixheight, modes);
+ }
+ } else if (ptrlen_eq_string(type, "env")) {
+ ptrlen var = get_string(pktin);
+ ptrlen value = get_string(pktin);
+
+ reply_success = chan_set_env(c->chan, var, value);
+ } else if (ptrlen_eq_string(type, "break")) {
+ unsigned length = get_uint32(pktin);
+
+ reply_success = chan_send_break(c->chan, length);
+ } else if (ptrlen_eq_string(type, "signal")) {
+ ptrlen signame = get_string(pktin);
+
+ reply_success = chan_send_signal(c->chan, signame);
+ } else if (ptrlen_eq_string(type, "window-change")) {
+ unsigned width = get_uint32(pktin);
+ unsigned height = get_uint32(pktin);
+ unsigned pixwidth = get_uint32(pktin);
+ unsigned pixheight = get_uint32(pktin);
+ reply_success = chan_change_window_size(
+ c->chan, width, height, pixwidth, pixheight);
+ }
+ if (want_reply) {
+ int type = (reply_success ? SSH2_MSG_CHANNEL_SUCCESS :
+ SSH2_MSG_CHANNEL_FAILURE);
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, type);
+ put_uint32(pktout, c->remoteid);
+ pq_push(s->ppl.out_pq, pktout);
+ }
+ break;
+
+ case SSH2_MSG_CHANNEL_SUCCESS:
+ case SSH2_MSG_CHANNEL_FAILURE:
+ ocr = c->chanreq_head;
+ if (!ocr) {
+ ssh_proto_error(
+ s->ppl.ssh,
+ "Received %s for channel %d with no outstanding "
+ "channel request",
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx, pktin->type),
+ c->localid);
+ return true;
+ }
+ ocr->handler(c, pktin, ocr->ctx);
+ c->chanreq_head = ocr->next;
+ sfree(ocr);
+ /*
+ * We may now initiate channel-closing procedures, if
+ * that CHANNEL_REQUEST was the last thing outstanding
+ * before we send CHANNEL_CLOSE.
+ */
+ ssh2_channel_check_close(c);
+ break;
+
+ case SSH2_MSG_CHANNEL_EOF:
+ if (!(c->closes & CLOSES_RCVD_EOF)) {
+ c->closes |= CLOSES_RCVD_EOF;
+ chan_send_eof(c->chan);
+ ssh2_channel_check_close(c);
+ }
+ break;
+
+ case SSH2_MSG_CHANNEL_CLOSE:
+ /*
+ * When we receive CLOSE on a channel, we assume it
+ * comes with an implied EOF if we haven't seen EOF
+ * yet.
+ */
+ if (!(c->closes & CLOSES_RCVD_EOF)) {
+ c->closes |= CLOSES_RCVD_EOF;
+ chan_send_eof(c->chan);
+ }
+
+ if (!(s->ppl.remote_bugs & BUG_SENDS_LATE_REQUEST_REPLY)) {
+ /*
+ * It also means we stop expecting to see replies
+ * to any outstanding channel requests, so clean
+ * those up too. (ssh_chanreq_init will enforce by
+ * assertion that we don't subsequently put
+ * anything back on this list.)
+ */
+ while (c->chanreq_head) {
+ struct outstanding_channel_request *ocr =
+ c->chanreq_head;
+ ocr->handler(c, NULL, ocr->ctx);
+ c->chanreq_head = ocr->next;
+ sfree(ocr);
+ }
+ }
+
+ /*
+ * And we also send an outgoing EOF, if we haven't
+ * already, on the assumption that CLOSE is a pretty
+ * forceful announcement that the remote side is doing
+ * away with the entire channel. (If it had wanted to
+ * send us EOF and continue receiving data from us, it
+ * would have just sent CHANNEL_EOF.)
+ */
+ if (!(c->closes & CLOSES_SENT_EOF)) {
+ /*
+ * Abandon any buffered data we still wanted to
+ * send to this channel. Receiving a CHANNEL_CLOSE
+ * is an indication that the server really wants
+ * to get on and _destroy_ this channel, and it
+ * isn't going to send us any further
+ * WINDOW_ADJUSTs to permit us to send pending
+ * stuff.
+ */
+ bufchain_clear(&c->outbuffer);
+ bufchain_clear(&c->errbuffer);
+
+ /*
+ * Send outgoing EOF.
+ */
+ sshfwd_write_eof(&c->sc);
+
+ /*
+ * Make sure we don't read any more from whatever
+ * our local data source is for this channel.
+ * (This will pick up on the changes made by
+ * sshfwd_write_eof.)
+ */
+ ssh2_channel_check_throttle(c);
+ }
+
+ /*
+ * Now process the actual close.
+ */
+ if (!(c->closes & CLOSES_RCVD_CLOSE)) {
+ c->closes |= CLOSES_RCVD_CLOSE;
+ ssh2_channel_check_close(c);
+ }
+
+ break;
+ }
+
+ pq_pop(s->ppl.in_pq);
+ break;
+
+ default:
+ return false;
+ }
+ }
+}
+
+static void ssh2_handle_winadj_response(struct ssh2_channel *c,
+ PktIn *pktin, void *ctx)
+{
+ unsigned *sizep = ctx;
+
+ /*
+ * Winadj responses should always be failures. However, at least
+ * one server ("boks_sshd") is known to return SUCCESS for channel
+ * requests it's never heard of, such as "winadj@putty". Raised
+ * with foxt.com as bug 090916-090424, but for the sake of a quiet
+ * life, we don't worry about what kind of response we got.
+ */
+
+ c->remlocwin += *sizep;
+ sfree(sizep);
+ /*
+ * winadj messages are only sent when the window is fully open, so
+ * if we get an ack of one, we know any pending unthrottle is
+ * complete.
+ */
+ if (c->throttle_state == UNTHROTTLING)
+ c->throttle_state = UNTHROTTLED;
+}
+
+static void ssh2_set_window(struct ssh2_channel *c, int newwin)
+{
+ struct ssh2_connection_state *s = c->connlayer;
+
+ /*
+ * Never send WINDOW_ADJUST for a channel that the remote side has
+ * already sent EOF on; there's no point, since it won't be
+ * sending any more data anyway. Ditto if _we've_ already sent
+ * CLOSE.
+ */
+ if (c->closes & (CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE))
+ return;
+
+ /*
+ * If the client-side Channel is in an initial setup phase with a
+ * fixed window size, e.g. for an X11 channel when we're still
+ * waiting to see its initial auth and may yet hand it off to a
+ * downstream, don't send any WINDOW_ADJUST either.
+ */
+ if (c->chan->initial_fixed_window_size)
+ return;
+
+ /*
+ * If the remote end has a habit of ignoring maxpkt, limit the
+ * window so that it has no choice (assuming it doesn't ignore the
+ * window as well).
+ */
+ if ((s->ppl.remote_bugs & BUG_SSH2_MAXPKT) && newwin > OUR_V2_MAXPKT)
+ newwin = OUR_V2_MAXPKT;
+
+ /*
+ * Only send a WINDOW_ADJUST if there's significantly more window
+ * available than the other end thinks there is. This saves us
+ * sending a WINDOW_ADJUST for every character in a shell session.
+ *
+ * "Significant" is arbitrarily defined as half the window size.
+ */
+ if (newwin / 2 >= c->locwindow) {
+ PktOut *pktout;
+ unsigned *up;
+
+ /*
+ * In order to keep track of how much window the client
+ * actually has available, we'd like it to acknowledge each
+ * WINDOW_ADJUST. We can't do that directly, so we accompany
+ * it with a CHANNEL_REQUEST that has to be acknowledged.
+ *
+ * This is only necessary if we're opening the window wide.
+ * If we're not, then throughput is being constrained by
+ * something other than the maximum window size anyway.
+ */
+ if (newwin == c->locmaxwin &&
+ !(s->ppl.remote_bugs & BUG_CHOKES_ON_WINADJ)) {
+ up = snew(unsigned);
+ *up = newwin - c->locwindow;
+ pktout = ssh2_chanreq_init(c, "winadj@putty.projects.tartarus.org",
+ ssh2_handle_winadj_response, up);
+ pq_push(s->ppl.out_pq, pktout);
+
+ if (c->throttle_state != UNTHROTTLED)
+ c->throttle_state = UNTHROTTLING;
+ } else {
+ /* Pretend the WINDOW_ADJUST was acked immediately. */
+ c->remlocwin = newwin;
+ c->throttle_state = THROTTLED;
+ }
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+ put_uint32(pktout, c->remoteid);
+ put_uint32(pktout, newwin - c->locwindow);
+ pq_push(s->ppl.out_pq, pktout);
+ c->locwindow = newwin;
+ }
+}
+
+static PktIn *ssh2_connection_pop(struct ssh2_connection_state *s)
+{
+ ssh2_connection_filter_queue(s);
+ return pq_pop(s->ppl.in_pq);
+}
+
+static void ssh2_connection_process_queue(PacketProtocolLayer *ppl)
+{
+ struct ssh2_connection_state *s =
+ container_of(ppl, struct ssh2_connection_state, ppl);
+ PktIn *pktin;
+
+ if (ssh2_connection_filter_queue(s)) /* no matter why we were called */
+ return;
+
+ crBegin(s->crState);
+
+ if (s->connshare)
+ share_activate(s->connshare, s->peer_verstring);
+
+ /*
+ * Signal the seat that authentication is done, so that it can
+ * deploy spoofing defences. If it doesn't have any, deploy our
+ * own fallback one.
+ *
+ * We do this here rather than at the end of userauth, because we
+ * might not have gone through userauth at all (if we're a
+ * connection-sharing downstream).
+ */
+ if (ssh2_connection_need_antispoof_prompt(s)) {
+ s->antispoof_prompt = ssh_ppl_new_prompts(&s->ppl);
+ s->antispoof_prompt->to_server = false;
+ s->antispoof_prompt->from_server = false;
+ s->antispoof_prompt->name = dupstr("Authentication successful");
+ add_prompt(
+ s->antispoof_prompt,
+ dupstr("Access granted. Press Return to begin session. "), false);
+ s->antispoof_ret = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->antispoof_prompt);
+ while (s->antispoof_ret.kind == SPRK_INCOMPLETE) {
+ crReturnV;
+ s->antispoof_ret = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->antispoof_prompt);
+ }
+ free_prompts(s->antispoof_prompt);
+ s->antispoof_prompt = NULL;
+ }
+
+ /*
+ * Enable port forwardings.
+ */
+ portfwdmgr_config(s->portfwdmgr, s->conf);
+ s->portfwdmgr_configured = true;
+
+ /*
+ * Create the main session channel, if any.
+ */
+ s->mainchan = mainchan_new(
+ &s->ppl, &s->cl, s->conf, s->term_width, s->term_height,
+ s->ssh_is_simple, &s->mainchan_sc);
+ s->started = true;
+
+ /*
+ * Transfer data!
+ */
+
+ while (1) {
+ if ((pktin = ssh2_connection_pop(s)) != NULL) {
+
+ /*
+ * _All_ the connection-layer packets we expect to
+ * receive are now handled by the dispatch table.
+ * Anything that reaches here must be bogus.
+ */
+
+ ssh_proto_error(s->ppl.ssh, "Received unexpected connection-layer "
+ "packet, type %d (%s)", pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ return;
+ }
+ crReturnV;
+ }
+
+ crFinishV;
+}
+
+static void ssh2_channel_check_close(struct ssh2_channel *c)
+{
+ struct ssh2_connection_state *s = c->connlayer;
+ PktOut *pktout;
+
+ if (c->halfopen) {
+ /*
+ * If we've sent out our own CHANNEL_OPEN but not yet seen
+ * either OPEN_CONFIRMATION or OPEN_FAILURE in response, then
+ * it's too early to be sending close messages of any kind.
+ */
+ return;
+ }
+
+ if (chan_want_close(c->chan, (c->closes & CLOSES_SENT_EOF),
+ (c->closes & CLOSES_RCVD_EOF)) &&
+ !c->chanreq_head &&
+ !(c->closes & CLOSES_SENT_CLOSE)) {
+ /*
+ * We have both sent and received EOF (or the channel is a
+ * zombie), and we have no outstanding channel requests, which
+ * means the channel is in final wind-up. But we haven't sent
+ * CLOSE, so let's do so now.
+ */
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_CLOSE);
+ put_uint32(pktout, c->remoteid);
+ pq_push(s->ppl.out_pq, pktout);
+ c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE;
+ }
+
+ if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) {
+ assert(c->chanreq_head == NULL);
+ /*
+ * We have both sent and received CLOSE, which means we're
+ * completely done with the channel.
+ */
+ ssh2_channel_destroy(c);
+ }
+}
+
+static void ssh2_channel_try_eof(struct ssh2_channel *c)
+{
+ struct ssh2_connection_state *s = c->connlayer;
+ PktOut *pktout;
+ assert(c->pending_eof); /* precondition for calling us */
+ if (c->halfopen)
+ return; /* can't close: not even opened yet */
+ if (bufchain_size(&c->outbuffer) > 0 || bufchain_size(&c->errbuffer) > 0)
+ return; /* can't send EOF: pending outgoing data */
+
+ c->pending_eof = false; /* we're about to send it */
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_EOF);
+ put_uint32(pktout, c->remoteid);
+ pq_push(s->ppl.out_pq, pktout);
+ c->closes |= CLOSES_SENT_EOF;
+ ssh2_channel_check_close(c);
+}
+
+/*
+ * Attempt to send data on an SSH-2 channel.
+ */
+static size_t ssh2_try_send(struct ssh2_channel *c)
+{
+ struct ssh2_connection_state *s = c->connlayer;
+ PktOut *pktout;
+ size_t bufsize;
+
+ if (!c->halfopen) {
+ while (c->remwindow > 0 &&
+ (bufchain_size(&c->outbuffer) > 0 ||
+ bufchain_size(&c->errbuffer) > 0)) {
+ bufchain *buf = (bufchain_size(&c->errbuffer) > 0 ?
+ &c->errbuffer : &c->outbuffer);
+
+ ptrlen data = bufchain_prefix(buf);
+ if (data.len > c->remwindow)
+ data.len = c->remwindow;
+ if (data.len > c->remmaxpkt)
+ data.len = c->remmaxpkt;
+ if (buf == &c->errbuffer) {
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_CHANNEL_EXTENDED_DATA);
+ put_uint32(pktout, c->remoteid);
+ put_uint32(pktout, SSH2_EXTENDED_DATA_STDERR);
+ } else {
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_DATA);
+ put_uint32(pktout, c->remoteid);
+ }
+ put_stringpl(pktout, data);
+ pq_push(s->ppl.out_pq, pktout);
+ bufchain_consume(buf, data.len);
+ c->remwindow -= data.len;
+ }
+ }
+
+ /*
+ * After having sent as much data as we can, return the amount
+ * still buffered.
+ */
+ bufsize = bufchain_size(&c->outbuffer) + bufchain_size(&c->errbuffer);
+
+ /*
+ * And if there's no data pending but we need to send an EOF, send
+ * it.
+ */
+ if (!bufsize && c->pending_eof)
+ ssh2_channel_try_eof(c);
+
+ ssh_sendbuffer_changed(s->ppl.ssh);
+ return bufsize;
+}
+
+static void ssh2_try_send_and_unthrottle(struct ssh2_channel *c)
+{
+ int bufsize;
+ if (c->closes & CLOSES_SENT_EOF)
+ return; /* don't send on channels we've EOFed */
+ bufsize = ssh2_try_send(c);
+ if (bufsize == 0) {
+ c->throttled_by_backlog = false;
+ ssh2_channel_check_throttle(c);
+ }
+}
+
+static void ssh2_channel_check_throttle(struct ssh2_channel *c)
+{
+ /*
+ * We don't want this channel to read further input if this
+ * particular channel has a backed-up SSH window, or if the
+ * outgoing side of the whole SSH connection is currently
+ * throttled, or if this channel already has an outgoing EOF
+ * either sent or pending.
+ */
+ chan_set_input_wanted(c->chan,
+ !c->throttled_by_backlog &&
+ !c->connlayer->all_channels_throttled &&
+ !c->pending_eof &&
+ !(c->closes & CLOSES_SENT_EOF));
+}
+
+/*
+ * Close any local socket and free any local resources associated with
+ * a channel. This converts the channel into a zombie.
+ */
+static void ssh2_channel_close_local(struct ssh2_channel *c,
+ const char *reason)
+{
+ struct ssh2_connection_state *s = c->connlayer;
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ char *msg = NULL;
+
+ if (c->sharectx)
+ return;
+
+ msg = chan_log_close_msg(c->chan);
+
+ if (msg)
+ ppl_logevent("%s%s%s", msg, reason ? " " : "", reason ? reason : "");
+
+ sfree(msg);
+
+ chan_free(c->chan);
+ c->chan = zombiechan_new();
+}
+
+static void ssh2_check_termination_callback(void *vctx)
+{
+ struct ssh2_connection_state *s = (struct ssh2_connection_state *)vctx;
+ ssh2_check_termination(s);
+}
+
+static void ssh2_channel_destroy(struct ssh2_channel *c)
+{
+ struct ssh2_connection_state *s = c->connlayer;
+
+ assert(c->chanreq_head == NULL);
+
+ ssh2_channel_close_local(c, NULL);
+ del234(s->channels, c);
+ ssh2_channel_free(c);
+
+ /*
+ * If that was the last channel left open, we might need to
+ * terminate. But we'll be a bit cautious, by doing that in a
+ * toplevel callback, just in case anything on the current call
+ * stack objects to this entire PPL being freed.
+ */
+ queue_toplevel_callback(ssh2_check_termination_callback, s);
+}
+
+static void ssh2_check_termination(struct ssh2_connection_state *s)
+{
+ /*
+ * Decide whether we should terminate the SSH connection now.
+ * Called after a channel or a downstream goes away. The general
+ * policy is that we terminate when none of either is left.
+ */
+
+ if (s->persistent)
+ return; /* persistent mode: never proactively terminate */
+
+ if (!s->started) {
+ /* At startup, we don't have any channels open because we
+ * haven't got round to opening the main one yet. In that
+ * situation, we don't want to terminate, even if a sharing
+ * connection opens and closes and causes a call to this
+ * function. */
+ return;
+ }
+
+ if (count234(s->channels) == 0 &&
+ !(s->connshare && share_ndownstreams(s->connshare) > 0)) {
+ /*
+ * We used to send SSH_MSG_DISCONNECT here, because I'd
+ * believed that _every_ conforming SSH-2 connection had to
+ * end with a disconnect being sent by at least one side;
+ * apparently I was wrong and it's perfectly OK to
+ * unceremoniously slam the connection shut when you're done,
+ * and indeed OpenSSH feels this is more polite than sending a
+ * DISCONNECT. So now we don't.
+ */
+ ssh_user_close(s->ppl.ssh, "All channels closed");
+ return;
+ }
+}
+
+/*
+ * Set up most of a new ssh2_channel. Nulls out sharectx, but leaves
+ * chan untouched (since it will sometimes have been filled in before
+ * calling this).
+ */
+void ssh2_channel_init(struct ssh2_channel *c)
+{
+ struct ssh2_connection_state *s = c->connlayer;
+ c->closes = 0;
+ c->pending_eof = false;
+ c->throttling_conn = false;
+ c->throttled_by_backlog = false;
+ c->sharectx = NULL;
+ c->locwindow = c->locmaxwin = c->remlocwin =
+ s->ssh_is_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
+ c->chanreq_head = NULL;
+ c->throttle_state = UNTHROTTLED;
+ bufchain_init(&c->outbuffer);
+ bufchain_init(&c->errbuffer);
+ c->sc.vt = &ssh2channel_vtable;
+ c->sc.cl = &s->cl;
+ c->localid = alloc_channel_id(s->channels, struct ssh2_channel);
+ add234(s->channels, c);
+}
+
+/*
+ * Construct the common parts of a CHANNEL_OPEN.
+ */
+PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type)
+{
+ struct ssh2_connection_state *s = c->connlayer;
+ PktOut *pktout;
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN);
+ put_stringz(pktout, type);
+ put_uint32(pktout, c->localid);
+ put_uint32(pktout, c->locwindow); /* our window size */
+ put_uint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */
+ return pktout;
+}
+
+/*
+ * Construct the common parts of a CHANNEL_REQUEST. If handler is not
+ * NULL then a reply will be requested and the handler will be called
+ * when it arrives. The returned packet is ready to have any
+ * request-specific data added and be sent. Note that if a handler is
+ * provided, it's essential that the request actually be sent.
+ *
+ * The handler will usually be passed the response packet in pktin. If
+ * pktin is NULL, this means that no reply will ever be forthcoming
+ * (e.g. because the entire connection is being destroyed, or because
+ * the server initiated channel closure before we saw the response)
+ * and the handler should free any storage it's holding.
+ */
+PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type,
+ cr_handler_fn_t handler, void *ctx)
+{
+ struct ssh2_connection_state *s = c->connlayer;
+ PktOut *pktout;
+
+ assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE)));
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_REQUEST);
+ put_uint32(pktout, c->remoteid);
+ put_stringz(pktout, type);
+ put_bool(pktout, handler != NULL);
+ if (handler != NULL) {
+ struct outstanding_channel_request *ocr =
+ snew(struct outstanding_channel_request);
+
+ ocr->handler = handler;
+ ocr->ctx = ctx;
+ ocr->next = NULL;
+ if (!c->chanreq_head)
+ c->chanreq_head = ocr;
+ else
+ c->chanreq_tail->next = ocr;
+ c->chanreq_tail = ocr;
+ }
+ return pktout;
+}
+
+static Conf *ssh2channel_get_conf(SshChannel *sc)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+ return s->conf;
+}
+
+static void ssh2channel_write_eof(SshChannel *sc)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+
+ if (c->closes & CLOSES_SENT_EOF)
+ return;
+
+ c->pending_eof = true;
+ ssh2_channel_try_eof(c);
+}
+
+static void ssh2channel_initiate_close(SshChannel *sc, const char *err)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ char *reason;
+
+ reason = err ? dupprintf("due to local error: %s", err) : NULL;
+ ssh2_channel_close_local(c, reason);
+ sfree(reason);
+ c->pending_eof = false; /* this will confuse a zombie channel */
+
+ ssh2_channel_check_close(c);
+}
+
+static void ssh2channel_unthrottle(SshChannel *sc, size_t bufsize)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+ size_t buflimit;
+
+ buflimit = s->ssh_is_simple ? 0 : c->locmaxwin;
+ if (bufsize < buflimit)
+ ssh2_set_window(c, buflimit - bufsize);
+
+ if (c->throttling_conn && bufsize <= buflimit) {
+ c->throttling_conn = false;
+ ssh_throttle_conn(s->ppl.ssh, -1);
+ }
+}
+
+static size_t ssh2channel_write(
+ SshChannel *sc, bool is_stderr, const void *buf, size_t len)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ assert(!(c->closes & CLOSES_SENT_EOF));
+ bufchain_add(is_stderr ? &c->errbuffer : &c->outbuffer, buf, len);
+ return ssh2_try_send(c);
+}
+
+static void ssh2channel_x11_sharing_handover(
+ SshChannel *sc, ssh_sharing_connstate *share_cs, share_channel *share_chan,
+ const char *peer_addr, int peer_port, int endian,
+ int protomajor, int protominor, const void *initial_data, int initial_len)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ /*
+ * This function is called when we've just discovered that an X
+ * forwarding channel on which we'd been handling the initial auth
+ * ourselves turns out to be destined for a connection-sharing
+ * downstream. So we turn the channel into a sharing one, meaning
+ * that we completely stop tracking windows and buffering data and
+ * just pass more or less unmodified SSH messages back and forth.
+ */
+ c->sharectx = share_cs;
+ share_setup_x11_channel(share_cs, share_chan,
+ c->localid, c->remoteid, c->remwindow,
+ c->remmaxpkt, c->locwindow,
+ peer_addr, peer_port, endian,
+ protomajor, protominor,
+ initial_data, initial_len);
+ chan_free(c->chan);
+ c->chan = NULL;
+}
+
+static void ssh2channel_window_override_removed(SshChannel *sc)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+
+ /*
+ * This function is called when a client-side Channel has just
+ * stopped requiring an initial fixed-size window.
+ */
+ assert(!c->chan->initial_fixed_window_size);
+ ssh2_set_window(c, s->ssh_is_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE);
+}
+
+static void ssh2channel_hint_channel_is_simple(SshChannel *sc)
+{
+ struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc);
+ struct ssh2_connection_state *s = c->connlayer;
+
+ PktOut *pktout = ssh2_chanreq_init(
+ c, "simple@putty.projects.tartarus.org", NULL, NULL);
+ pq_push(s->ppl.out_pq, pktout);
+}
+
+static SshChannel *ssh2_lportfwd_open(
+ ConnectionLayer *cl, const char *hostname, int port,
+ const char *description, const SocketPeerInfo *pi, Channel *chan)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ struct ssh2_channel *c = snew(struct ssh2_channel);
+ PktOut *pktout;
+
+ c->connlayer = s;
+ ssh2_channel_init(c);
+ c->halfopen = true;
+ c->chan = chan;
+
+ pktout = ssh2_portfwd_chanopen(s, c, hostname, port, description, pi);
+ pq_push(s->ppl.out_pq, pktout);
+
+ return &c->sc;
+}
+
+static void ssh2_sharing_globreq_response(
+ struct ssh2_connection_state *s, PktIn *pktin, void *ctx)
+{
+ ssh_sharing_connstate *cs = (ssh_sharing_connstate *)ctx;
+ share_got_pkt_from_server(cs, pktin->type,
+ BinarySource_UPCAST(pktin)->data,
+ BinarySource_UPCAST(pktin)->len);
+}
+
+static void ssh2_sharing_queue_global_request(
+ ConnectionLayer *cl, ssh_sharing_connstate *cs)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ ssh2_queue_global_request_handler(s, ssh2_sharing_globreq_response, cs);
+}
+
+static void ssh2_sharing_no_more_downstreams(ConnectionLayer *cl)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ queue_toplevel_callback(ssh2_check_termination_callback, s);
+}
+
+static struct X11FakeAuth *ssh2_add_x11_display(
+ ConnectionLayer *cl, int authtype, struct X11Display *disp)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ struct X11FakeAuth *auth = x11_invent_fake_auth(s->x11authtree, authtype);
+ auth->disp = disp;
+ return auth;
+}
+
+static struct X11FakeAuth *ssh2_add_sharing_x11_display(
+ ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs,
+ share_channel *share_chan)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ struct X11FakeAuth *auth;
+
+ /*
+ * Make up a new set of fake X11 auth data, and add it to the tree
+ * of currently valid ones with an indication of the sharing
+ * context that it's relevant to.
+ */
+ auth = x11_invent_fake_auth(s->x11authtree, authtype);
+ auth->share_cs = share_cs;
+ auth->share_chan = share_chan;
+
+ return auth;
+}
+
+static void ssh2_remove_sharing_x11_display(
+ ConnectionLayer *cl, struct X11FakeAuth *auth)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ del234(s->x11authtree, auth);
+ x11_free_fake_auth(auth);
+}
+
+static unsigned ssh2_alloc_sharing_channel(
+ ConnectionLayer *cl, ssh_sharing_connstate *connstate)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ struct ssh2_channel *c = snew(struct ssh2_channel);
+
+ c->connlayer = s;
+ ssh2_channel_init(c);
+ c->chan = NULL;
+ c->sharectx = connstate;
+ return c->localid;
+}
+
+static void ssh2_delete_sharing_channel(ConnectionLayer *cl, unsigned localid)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ struct ssh2_channel *c = find234(s->channels, &localid, ssh2_channelfind);
+ if (c)
+ ssh2_channel_destroy(c);
+}
+
+static void ssh2_send_packet_from_downstream(
+ ConnectionLayer *cl, unsigned id, int type,
+ const void *data, int datalen, const char *additional_log_text)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ PktOut *pkt = ssh_bpp_new_pktout(s->ppl.bpp, type);
+ pkt->downstream_id = id;
+ pkt->additional_log_text = additional_log_text;
+ put_data(pkt, data, datalen);
+ pq_push(s->ppl.out_pq, pkt);
+}
+
+static bool ssh2_agent_forwarding_permitted(ConnectionLayer *cl)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ return conf_get_bool(s->conf, CONF_agentfwd) && agent_exists();
+}
+
+static bool ssh2_connection_get_specials(
+ PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
+{
+ struct ssh2_connection_state *s =
+ container_of(ppl, struct ssh2_connection_state, ppl);
+ bool toret = false;
+
+ if (s->mainchan) {
+ mainchan_get_specials(s->mainchan, add_special, ctx);
+ toret = true;
+ }
+
+ /*
+ * Don't bother offering IGNORE if we've decided the remote
+ * won't cope with it, since we wouldn't bother sending it if
+ * asked anyway.
+ */
+ if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
+ if (toret)
+ add_special(ctx, NULL, SS_SEP, 0);
+
+ add_special(ctx, "IGNORE message", SS_NOP, 0);
+ toret = true;
+ }
+
+ return toret;
+}
+
+static void ssh2_connection_special_cmd(PacketProtocolLayer *ppl,
+ SessionSpecialCode code, int arg)
+{
+ struct ssh2_connection_state *s =
+ container_of(ppl, struct ssh2_connection_state, ppl);
+ PktOut *pktout;
+
+ if (code == SS_PING || code == SS_NOP) {
+ if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) {
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_IGNORE);
+ put_stringz(pktout, "");
+ pq_push(s->ppl.out_pq, pktout);
+ }
+ } else if (s->mainchan) {
+ mainchan_special_cmd(s->mainchan, code, arg);
+ }
+}
+
+static void ssh2_terminal_size(ConnectionLayer *cl, int width, int height)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+
+ s->term_width = width;
+ s->term_height = height;
+ if (s->mainchan)
+ mainchan_terminal_size(s->mainchan, width, height);
+}
+
+static void ssh2_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+
+ if (s->mainchan)
+ sshfwd_unthrottle(s->mainchan_sc, bufsize);
+}
+
+static size_t ssh2_stdin_backlog(ConnectionLayer *cl)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ struct ssh2_channel *c;
+
+ if (!s->mainchan)
+ return 0;
+ c = container_of(s->mainchan_sc, struct ssh2_channel, sc);
+ return s->mainchan ?
+ bufchain_size(&c->outbuffer) + bufchain_size(&c->errbuffer) : 0;
+}
+
+static void ssh2_throttle_all_channels(ConnectionLayer *cl, bool throttled)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ struct ssh2_channel *c;
+ int i;
+
+ s->all_channels_throttled = throttled;
+
+ for (i = 0; NULL != (c = index234(s->channels, i)); i++)
+ if (!c->sharectx)
+ ssh2_channel_check_throttle(c);
+}
+
+static bool ssh2_ldisc_option(ConnectionLayer *cl, int option)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+
+ return s->ldisc_opts[option];
+}
+
+static void ssh2_set_ldisc_option(ConnectionLayer *cl, int option, bool value)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+
+ s->ldisc_opts[option] = value;
+}
+
+static void ssh2_enable_x_fwd(ConnectionLayer *cl)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+
+ s->X11_fwd_enabled = true;
+}
+
+static void ssh2_set_wants_user_input(ConnectionLayer *cl, bool wanted)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+
+ s->want_user_input = wanted;
+ if (wanted)
+ ssh_check_sendok(s->ppl.ssh);
+}
+
+static bool ssh2_get_wants_user_input(ConnectionLayer *cl)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+ return s->want_user_input;
+}
+
+static void ssh2_got_user_input(ConnectionLayer *cl)
+{
+ struct ssh2_connection_state *s =
+ container_of(cl, struct ssh2_connection_state, cl);
+
+ while (s->mainchan && bufchain_size(s->user_input) > 0) {
+ /*
+ * Add user input to the main channel's buffer.
+ */
+ ptrlen data = bufchain_prefix(s->user_input);
+ sshfwd_write(s->mainchan_sc, data.ptr, data.len);
+ bufchain_consume(s->user_input, data.len);
+ }
+}
+
+static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
+{
+ struct ssh2_connection_state *s =
+ container_of(ppl, struct ssh2_connection_state, ppl);
+
+ conf_free(s->conf);
+ s->conf = conf_copy(conf);
+
+ if (s->portfwdmgr_configured)
+ portfwdmgr_config(s->portfwdmgr, s->conf);
+}
diff --git a/ssh/connection2.h b/ssh/connection2.h
new file mode 100644
index 00000000..54c3ebf9
--- /dev/null
+++ b/ssh/connection2.h
@@ -0,0 +1,236 @@
+#ifndef PUTTY_SSH2CONNECTION_H
+#define PUTTY_SSH2CONNECTION_H
+
+struct outstanding_channel_request;
+struct outstanding_global_request;
+
+struct ssh2_connection_state {
+ int crState;
+
+ ssh_sharing_state *connshare;
+ char *peer_verstring;
+
+ mainchan *mainchan;
+ SshChannel *mainchan_sc;
+ bool ldisc_opts[LD_N_OPTIONS];
+ int session_attempt, session_status;
+ int term_width, term_height;
+ bool want_user_input;
+ bufchain *user_input;
+
+ bool ssh_is_simple;
+ bool persistent;
+ bool started;
+
+ Conf *conf;
+
+ tree234 *channels; /* indexed by local id */
+ bool all_channels_throttled;
+
+ bool X11_fwd_enabled;
+ tree234 *x11authtree;
+
+ bool got_pty;
+
+ tree234 *rportfwds;
+ PortFwdManager *portfwdmgr;
+ bool portfwdmgr_configured;
+
+ prompts_t *antispoof_prompt;
+ SeatPromptResult antispoof_ret;
+
+ const SftpServerVtable *sftpserver_vt;
+ const SshServerConfig *ssc;
+
+ /*
+ * These store the list of global requests that we're waiting for
+ * replies to. (REQUEST_FAILURE doesn't come with any indication
+ * of what message caused it, so we have to keep track of the
+ * queue ourselves.)
+ */
+ struct outstanding_global_request *globreq_head, *globreq_tail;
+
+ ConnectionLayer cl;
+ PacketProtocolLayer ppl;
+};
+
+typedef void (*gr_handler_fn_t)(struct ssh2_connection_state *s,
+ PktIn *pktin, void *ctx);
+void ssh2_queue_global_request_handler(
+ struct ssh2_connection_state *s, gr_handler_fn_t handler, void *ctx);
+
+struct ssh2_channel {
+ struct ssh2_connection_state *connlayer;
+
+ unsigned remoteid, localid;
+ int type;
+ /* True if we opened this channel but server hasn't confirmed. */
+ bool halfopen;
+
+ /* Bitmap of whether we've sent/received CHANNEL_EOF and
+ * CHANNEL_CLOSE. */
+#define CLOSES_SENT_EOF 1
+#define CLOSES_SENT_CLOSE 2
+#define CLOSES_RCVD_EOF 4
+#define CLOSES_RCVD_CLOSE 8
+ int closes;
+
+ /*
+ * This flag indicates that an EOF is pending on the outgoing side
+ * of the channel: that is, wherever we're getting the data for
+ * this channel has sent us some data followed by EOF. We can't
+ * actually send the EOF until we've finished sending the data, so
+ * we set this flag instead to remind us to do so once our buffer
+ * is clear.
+ */
+ bool pending_eof;
+
+ /*
+ * True if this channel is causing the underlying connection to be
+ * throttled.
+ */
+ bool throttling_conn;
+
+ /*
+ * True if we currently have backed-up data on the direction of
+ * this channel pointing out of the SSH connection, and therefore
+ * would prefer the 'Channel' implementation not to read further
+ * local input if possible.
+ */
+ bool throttled_by_backlog;
+
+ bufchain outbuffer, errbuffer;
+ unsigned remwindow, remmaxpkt;
+ /* locwindow is signed so we can cope with excess data. */
+ int locwindow, locmaxwin;
+ /*
+ * remlocwin is the amount of local window that we think
+ * the remote end had available to it after it sent the
+ * last data packet or window adjust ack.
+ */
+ int remlocwin;
+
+ /*
+ * These store the list of channel requests that we're waiting for
+ * replies to. (CHANNEL_FAILURE doesn't come with any indication
+ * of what message caused it, so we have to keep track of the
+ * queue ourselves.)
+ */
+ struct outstanding_channel_request *chanreq_head, *chanreq_tail;
+
+ enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state;
+
+ ssh_sharing_connstate *sharectx; /* sharing context, if this is a
+ * downstream channel */
+ Channel *chan; /* handle the client side of this channel, if not */
+ SshChannel sc; /* entry point for chan to talk back to */
+};
+
+typedef void (*cr_handler_fn_t)(struct ssh2_channel *, PktIn *, void *);
+
+void ssh2_channel_init(struct ssh2_channel *c);
+PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type,
+ cr_handler_fn_t handler, void *ctx);
+
+typedef enum ChanopenOutcome {
+ CHANOPEN_RESULT_FAILURE,
+ CHANOPEN_RESULT_SUCCESS,
+ CHANOPEN_RESULT_DOWNSTREAM,
+} ChanopenOutcome;
+
+typedef struct ChanopenResult {
+ ChanopenOutcome outcome;
+ union {
+ struct {
+ char *wire_message; /* must be freed by recipient */
+ unsigned reason_code;
+ } failure;
+ struct {
+ Channel *channel;
+ } success;
+ struct {
+ ssh_sharing_connstate *share_ctx;
+ } downstream;
+ } u;
+} ChanopenResult;
+
+PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type);
+
+PktOut *ssh2_portfwd_chanopen(
+ struct ssh2_connection_state *s, struct ssh2_channel *c,
+ const char *hostname, int port,
+ const char *description, const SocketPeerInfo *peerinfo);
+
+struct ssh_rportfwd *ssh2_rportfwd_alloc(
+ ConnectionLayer *cl,
+ const char *shost, int sport, const char *dhost, int dport,
+ int addressfamily, const char *log_description, PortFwdRecord *pfr,
+ ssh_sharing_connstate *share_ctx);
+void ssh2_rportfwd_remove(
+ ConnectionLayer *cl, struct ssh_rportfwd *rpf);
+SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan);
+SshChannel *ssh2_serverside_x11_open(
+ ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi);
+SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan);
+
+void ssh2channel_send_exit_status(SshChannel *c, int status);
+void ssh2channel_send_exit_signal(
+ SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg);
+void ssh2channel_send_exit_signal_numeric(
+ SshChannel *c, int signum, bool core_dumped, ptrlen msg);
+void ssh2channel_request_x11_forwarding(
+ SshChannel *c, bool want_reply, const char *authproto,
+ const char *authdata, int screen_number, bool oneshot);
+void ssh2channel_request_agent_forwarding(SshChannel *c, bool want_reply);
+void ssh2channel_request_pty(
+ SshChannel *c, bool want_reply, Conf *conf, int w, int h);
+bool ssh2channel_send_env_var(
+ SshChannel *c, bool want_reply, const char *var, const char *value);
+void ssh2channel_start_shell(SshChannel *c, bool want_reply);
+void ssh2channel_start_command(
+ SshChannel *c, bool want_reply, const char *command);
+bool ssh2channel_start_subsystem(
+ SshChannel *c, bool want_reply, const char *subsystem);
+bool ssh2channel_send_env_var(
+ SshChannel *c, bool want_reply, const char *var, const char *value);
+bool ssh2channel_send_serial_break(
+ SshChannel *c, bool want_reply, int length);
+bool ssh2channel_send_signal(
+ SshChannel *c, bool want_reply, const char *signame);
+void ssh2channel_send_terminal_size_change(SshChannel *c, int w, int h);
+
+#define CHANOPEN_RETURN_FAILURE(code, msgparams) do \
+ { \
+ ChanopenResult toret; \
+ toret.outcome = CHANOPEN_RESULT_FAILURE; \
+ toret.u.failure.reason_code = code; \
+ toret.u.failure.wire_message = dupprintf msgparams; \
+ return toret; \
+ } while (0)
+
+#define CHANOPEN_RETURN_SUCCESS(chan) do \
+ { \
+ ChanopenResult toret; \
+ toret.outcome = CHANOPEN_RESULT_SUCCESS; \
+ toret.u.success.channel = chan; \
+ return toret; \
+ } while (0)
+
+#define CHANOPEN_RETURN_DOWNSTREAM(shctx) do \
+ { \
+ ChanopenResult toret; \
+ toret.outcome = CHANOPEN_RESULT_DOWNSTREAM; \
+ toret.u.downstream.share_ctx = shctx; \
+ return toret; \
+ } while (0)
+
+ChanopenResult ssh2_connection_parse_channel_open(
+ struct ssh2_connection_state *s, ptrlen type,
+ PktIn *pktin, SshChannel *sc);
+
+bool ssh2_connection_parse_global_request(
+ struct ssh2_connection_state *s, ptrlen type, PktIn *pktin);
+
+bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s);
+
+#endif /* PUTTY_SSH2CONNECTION_H */
diff --git a/SSHCRCDA.C b/ssh/crc-attack-detector.c
index 18044754..18044754 100644
--- a/SSHCRCDA.C
+++ b/ssh/crc-attack-detector.c
diff --git a/ssh/gss.h b/ssh/gss.h
new file mode 100644
index 00000000..c819d48b
--- /dev/null
+++ b/ssh/gss.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 {windows,unix}/gss.c or gssc.c */
+
+struct ssh_gss_library;
+
+/*
+ * Prepare a collection of GSSAPI libraries for use in a single SSH
+ * connection. Returns a structure containing a list of libraries,
+ * with their ids (see struct ssh_gss_library below) filled in so
+ * that the client can go through them in the SSH user's preferred
+ * order.
+ *
+ * Must always return non-NULL. (Even if no libraries are available,
+ * it must return an empty structure.)
+ *
+ * The free function cleans up the structure, and its associated
+ * libraries (if any).
+ */
+struct ssh_gss_liblist {
+ struct ssh_gss_library *libraries;
+ int nlibraries;
+};
+struct ssh_gss_liblist *ssh_gss_setup(Conf *conf);
+void ssh_gss_cleanup(struct ssh_gss_liblist *list);
+
+/*
+ * Fills in buf with a string describing the GSSAPI mechanism in
+ * use. buf->data is not dynamically allocated.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_indicate_mech)(struct ssh_gss_library *lib,
+ Ssh_gss_buf *buf);
+
+/*
+ * Converts a name such as a hostname into a GSSAPI internal form,
+ * which is placed in "out". The result should be freed by
+ * ssh_gss_release_name().
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_import_name)(struct ssh_gss_library *lib,
+ char *in, Ssh_gss_name *out);
+
+/*
+ * Frees the contents of an Ssh_gss_name structure filled in by
+ * ssh_gss_import_name().
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_release_name)(struct ssh_gss_library *lib,
+ Ssh_gss_name *name);
+
+/*
+ * The main GSSAPI security context setup function. The "out"
+ * parameter will need to be freed by ssh_gss_free_tok.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_init_sec_context)
+ (struct ssh_gss_library *lib,
+ Ssh_gss_ctx *ctx, Ssh_gss_name name, int delegate,
+ Ssh_gss_buf *in, Ssh_gss_buf *out, time_t *expiry,
+ unsigned long *lifetime);
+
+/*
+ * Frees the contents of an Ssh_gss_buf filled in by
+ * ssh_gss_init_sec_context(). Do not accidentally call this on
+ * something filled in by ssh_gss_get_mic() (which requires a
+ * different free function) or something filled in by any other
+ * way.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_free_tok)(struct ssh_gss_library *lib,
+ Ssh_gss_buf *);
+
+/*
+ * Acquires the credentials to perform authentication in the first
+ * place. Needs to be freed by ssh_gss_release_cred().
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_acquire_cred)(struct ssh_gss_library *lib,
+ Ssh_gss_ctx *,
+ time_t *expiry);
+
+/*
+ * Frees the contents of an Ssh_gss_ctx filled in by
+ * ssh_gss_acquire_cred().
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_release_cred)(struct ssh_gss_library *lib,
+ Ssh_gss_ctx *);
+
+/*
+ * Gets a MIC for some input data. "out" needs to be freed by
+ * ssh_gss_free_mic().
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_get_mic)(struct ssh_gss_library *lib,
+ Ssh_gss_ctx ctx, Ssh_gss_buf *in,
+ Ssh_gss_buf *out);
+
+/*
+ * Validates an input MIC for some input data.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_verify_mic)(struct ssh_gss_library *lib,
+ Ssh_gss_ctx ctx,
+ Ssh_gss_buf *in_data,
+ Ssh_gss_buf *in_mic);
+
+/*
+ * Frees the contents of an Ssh_gss_buf filled in by
+ * ssh_gss_get_mic(). Do not accidentally call this on something
+ * filled in by ssh_gss_init_sec_context() (which requires a
+ * different free function) or something filled in by any other
+ * way.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_free_mic)(struct ssh_gss_library *lib,
+ Ssh_gss_buf *);
+
+/*
+ * Return an error message after authentication failed. The
+ * message string is returned in "buf", with buf->len giving the
+ * number of characters of printable message text and buf->data
+ * containing one more character which is a trailing NUL.
+ * buf->data should be manually freed by the caller.
+ */
+typedef Ssh_gss_stat (*t_ssh_gss_display_status)(struct ssh_gss_library *lib,
+ Ssh_gss_ctx, Ssh_gss_buf *buf);
+
+struct ssh_gss_library {
+ /*
+ * Identifying number in the enumeration used by the
+ * configuration code to specify a preference order.
+ */
+ int id;
+
+ /*
+ * Filled in at initialisation time, if there's anything
+ * interesting to say about how GSSAPI was initialised (e.g.
+ * which of a number of alternative libraries was used).
+ */
+ const char *gsslogmsg;
+
+ /*
+ * Function pointers implementing the SSH wrapper layer on top
+ * of GSSAPI. (Defined in sshgssc, typically, though Windows
+ * provides an alternative layer to sit on top of the annoyingly
+ * different SSPI.)
+ */
+ t_ssh_gss_indicate_mech indicate_mech;
+ t_ssh_gss_import_name import_name;
+ t_ssh_gss_release_name release_name;
+ t_ssh_gss_init_sec_context init_sec_context;
+ t_ssh_gss_free_tok free_tok;
+ t_ssh_gss_acquire_cred acquire_cred;
+ t_ssh_gss_release_cred release_cred;
+ t_ssh_gss_get_mic get_mic;
+ t_ssh_gss_verify_mic verify_mic;
+ t_ssh_gss_free_mic free_mic;
+ t_ssh_gss_display_status display_status;
+
+ /*
+ * Additional data for the wrapper layers.
+ */
+ union {
+ struct gssapi_functions gssapi;
+ /*
+ * The SSPI wrappers don't need to store their Windows API
+ * function pointers in this structure, because there can't
+ * be more than one set of them available.
+ */
+ } u;
+
+ /*
+ * Wrapper layers will often also need to store a library handle
+ * of some sort for cleanup time.
+ */
+ void *handle;
+};
+
+/*
+ * State that has to be shared between all GSSAPI-using parts of the
+ * same SSH connection, in particular between GSS key exchange and the
+ * subsequent trivial userauth method that reuses its output.
+ */
+struct ssh_connection_shared_gss_state {
+ struct ssh_gss_liblist *libs;
+ struct ssh_gss_library *lib;
+ Ssh_gss_name srv_name;
+ Ssh_gss_ctx ctx;
+};
+
+#endif /* NO_GSSAPI */
+
+#endif /*PUTTY_SSHGSS_H*/
diff --git a/ssh/gssc.c b/ssh/gssc.c
new file mode 100644
index 00000000..d10caf8b
--- /dev/null
+++ b/ssh/gssc.c
@@ -0,0 +1,288 @@
+#include "putty.h"
+
+#include <string.h>
+#include <limits.h>
+#include "gssc.h"
+#include "misc.h"
+
+#ifndef NO_GSSAPI
+
+static Ssh_gss_stat ssh_gssapi_indicate_mech(struct ssh_gss_library *lib,
+ Ssh_gss_buf *mech)
+{
+ /* Copy constant into mech */
+ mech->length = GSS_MECH_KRB5->length;
+ mech->value = GSS_MECH_KRB5->elements;
+ return SSH_GSS_OK;
+}
+
+static Ssh_gss_stat ssh_gssapi_import_name(struct ssh_gss_library *lib,
+ char *host,
+ Ssh_gss_name *srv_name)
+{
+ struct gssapi_functions *gss = &lib->u.gssapi;
+ OM_uint32 min_stat,maj_stat;
+ gss_buffer_desc host_buf;
+ char *pStr;
+
+ pStr = dupcat("host@", host);
+
+ host_buf.value = pStr;
+ host_buf.length = strlen(pStr);
+
+ maj_stat = gss->import_name(&min_stat, &host_buf,
+ GSS_C_NT_HOSTBASED_SERVICE, srv_name);
+ /* Release buffer */
+ sfree(pStr);
+ if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
+ return SSH_GSS_FAILURE;
+}
+
+static Ssh_gss_stat ssh_gssapi_acquire_cred(struct ssh_gss_library *lib,
+ Ssh_gss_ctx *ctx,
+ time_t *expiry)
+{
+ struct gssapi_functions *gss = &lib->u.gssapi;
+ gss_OID_set_desc k5only = { 1, GSS_MECH_KRB5 };
+ gss_cred_id_t cred;
+ OM_uint32 dummy;
+ OM_uint32 time_rec;
+ gssapi_ssh_gss_ctx *gssctx = snew(gssapi_ssh_gss_ctx);
+
+ gssctx->ctx = GSS_C_NO_CONTEXT;
+ gssctx->expiry = 0;
+
+ gssctx->maj_stat =
+ gss->acquire_cred(&gssctx->min_stat, GSS_C_NO_NAME, GSS_C_INDEFINITE,
+ &k5only, GSS_C_INITIATE, &cred,
+ (gss_OID_set *)0, &time_rec);
+
+ if (gssctx->maj_stat != GSS_S_COMPLETE) {
+ sfree(gssctx);
+ return SSH_GSS_FAILURE;
+ }
+
+ /*
+ * When the credential lifetime is not yet available due to deferred
+ * processing, gss_acquire_cred should return a 0 lifetime which is
+ * distinct from GSS_C_INDEFINITE which signals a crential that never
+ * expires. However, not all implementations get this right, and with
+ * Kerberos, initiator credentials always expire at some point. So when
+ * lifetime is 0 or GSS_C_INDEFINITE we call gss_inquire_cred_by_mech() to
+ * complete deferred processing.
+ */
+ if (time_rec == GSS_C_INDEFINITE || time_rec == 0) {
+ gssctx->maj_stat =
+ gss->inquire_cred_by_mech(&gssctx->min_stat, cred,
+ (gss_OID) GSS_MECH_KRB5,
+ NULL,
+ &time_rec,
+ NULL,
+ NULL);
+ }
+ (void) gss->release_cred(&dummy, &cred);
+
+ if (gssctx->maj_stat != GSS_S_COMPLETE) {
+ sfree(gssctx);
+ return SSH_GSS_FAILURE;
+ }
+
+ if (time_rec != GSS_C_INDEFINITE)
+ gssctx->expiry = time(NULL) + time_rec;
+ else
+ gssctx->expiry = GSS_NO_EXPIRATION;
+
+ if (expiry) {
+ *expiry = gssctx->expiry;
+ }
+
+ *ctx = (Ssh_gss_ctx) gssctx;
+ return SSH_GSS_OK;
+}
+
+static Ssh_gss_stat ssh_gssapi_init_sec_context(struct ssh_gss_library *lib,
+ Ssh_gss_ctx *ctx,
+ Ssh_gss_name srv_name,
+ int to_deleg,
+ Ssh_gss_buf *recv_tok,
+ Ssh_gss_buf *send_tok,
+ time_t *expiry,
+ unsigned long *lifetime)
+{
+ struct gssapi_functions *gss = &lib->u.gssapi;
+ gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx*) *ctx;
+ OM_uint32 ret_flags;
+ OM_uint32 lifetime_rec;
+
+ if (to_deleg) to_deleg = GSS_C_DELEG_FLAG;
+ gssctx->maj_stat = gss->init_sec_context(&gssctx->min_stat,
+ GSS_C_NO_CREDENTIAL,
+ &gssctx->ctx,
+ srv_name,
+ (gss_OID) GSS_MECH_KRB5,
+ GSS_C_MUTUAL_FLAG |
+ GSS_C_INTEG_FLAG | to_deleg,
+ 0,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ recv_tok,
+ NULL, /* ignore mech type */
+ send_tok,
+ &ret_flags,
+ &lifetime_rec);
+
+ if (lifetime) {
+ if (lifetime_rec == GSS_C_INDEFINITE)
+ *lifetime = ULONG_MAX;
+ else
+ *lifetime = lifetime_rec;
+ }
+ if (expiry) {
+ if (lifetime_rec == GSS_C_INDEFINITE)
+ *expiry = GSS_NO_EXPIRATION;
+ else
+ *expiry = time(NULL) + lifetime_rec;
+ }
+
+ if (gssctx->maj_stat == GSS_S_COMPLETE) return SSH_GSS_S_COMPLETE;
+ if (gssctx->maj_stat == GSS_S_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED;
+ return SSH_GSS_FAILURE;
+}
+
+static Ssh_gss_stat ssh_gssapi_display_status(struct ssh_gss_library *lib,
+ Ssh_gss_ctx ctx,
+ Ssh_gss_buf *buf)
+{
+ struct gssapi_functions *gss = &lib->u.gssapi;
+ gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx;
+ OM_uint32 lmin,lmax;
+ OM_uint32 ccc;
+ gss_buffer_desc msg_maj=GSS_C_EMPTY_BUFFER;
+ gss_buffer_desc msg_min=GSS_C_EMPTY_BUFFER;
+
+ /* Return empty buffer in case of failure */
+ SSH_GSS_CLEAR_BUF(buf);
+
+ /* get first mesg from GSS */
+ ccc=0;
+ lmax=gss->display_status(&lmin,gssctx->maj_stat,GSS_C_GSS_CODE,(gss_OID) GSS_MECH_KRB5,&ccc,&msg_maj);
+
+ if (lmax != GSS_S_COMPLETE) return SSH_GSS_FAILURE;
+
+ /* get first mesg from Kerberos */
+ ccc=0;
+ lmax=gss->display_status(&lmin,gssctx->min_stat,GSS_C_MECH_CODE,(gss_OID) GSS_MECH_KRB5,&ccc,&msg_min);
+
+ if (lmax != GSS_S_COMPLETE) {
+ gss->release_buffer(&lmin, &msg_maj);
+ return SSH_GSS_FAILURE;
+ }
+
+ /* copy data into buffer */
+ buf->length = msg_maj.length + msg_min.length + 1;
+ buf->value = snewn(buf->length + 1, char);
+
+ /* copy mem */
+ memcpy((char *)buf->value, msg_maj.value, msg_maj.length);
+ ((char *)buf->value)[msg_maj.length] = ' ';
+ memcpy((char *)buf->value + msg_maj.length + 1, msg_min.value, msg_min.length);
+ ((char *)buf->value)[buf->length] = 0;
+ /* free mem & exit */
+ gss->release_buffer(&lmin, &msg_maj);
+ gss->release_buffer(&lmin, &msg_min);
+ return SSH_GSS_OK;
+}
+
+static Ssh_gss_stat ssh_gssapi_free_tok(struct ssh_gss_library *lib,
+ Ssh_gss_buf *send_tok)
+{
+ struct gssapi_functions *gss = &lib->u.gssapi;
+ OM_uint32 min_stat,maj_stat;
+ maj_stat = gss->release_buffer(&min_stat, send_tok);
+
+ if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
+ return SSH_GSS_FAILURE;
+}
+
+static Ssh_gss_stat ssh_gssapi_release_cred(struct ssh_gss_library *lib,
+ Ssh_gss_ctx *ctx)
+{
+ struct gssapi_functions *gss = &lib->u.gssapi;
+ gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) *ctx;
+ OM_uint32 min_stat;
+ OM_uint32 maj_stat=GSS_S_COMPLETE;
+
+ if (gssctx == NULL) return SSH_GSS_FAILURE;
+ if (gssctx->ctx != GSS_C_NO_CONTEXT)
+ maj_stat = gss->delete_sec_context(&min_stat,&gssctx->ctx,GSS_C_NO_BUFFER);
+ sfree(gssctx);
+ *ctx = NULL;
+
+ if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
+ return SSH_GSS_FAILURE;
+}
+
+
+static Ssh_gss_stat ssh_gssapi_release_name(struct ssh_gss_library *lib,
+ Ssh_gss_name *srv_name)
+{
+ struct gssapi_functions *gss = &lib->u.gssapi;
+ OM_uint32 min_stat,maj_stat;
+ maj_stat = gss->release_name(&min_stat, srv_name);
+
+ if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
+ return SSH_GSS_FAILURE;
+}
+
+static Ssh_gss_stat ssh_gssapi_get_mic(struct ssh_gss_library *lib,
+ Ssh_gss_ctx ctx, Ssh_gss_buf *buf,
+ Ssh_gss_buf *hash)
+{
+ struct gssapi_functions *gss = &lib->u.gssapi;
+ gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx;
+ if (gssctx == NULL) return SSH_GSS_FAILURE;
+ return gss->get_mic(&(gssctx->min_stat), gssctx->ctx, 0, buf, hash);
+}
+
+static Ssh_gss_stat ssh_gssapi_verify_mic(struct ssh_gss_library *lib,
+ Ssh_gss_ctx ctx, Ssh_gss_buf *buf,
+ Ssh_gss_buf *hash)
+{
+ struct gssapi_functions *gss = &lib->u.gssapi;
+ gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx *) ctx;
+ if (gssctx == NULL) return SSH_GSS_FAILURE;
+ return gss->verify_mic(&(gssctx->min_stat), gssctx->ctx, buf, hash, NULL);
+}
+
+static Ssh_gss_stat ssh_gssapi_free_mic(struct ssh_gss_library *lib,
+ Ssh_gss_buf *hash)
+{
+ /* On Unix this is the same freeing process as ssh_gssapi_free_tok. */
+ return ssh_gssapi_free_tok(lib, hash);
+}
+
+void ssh_gssapi_bind_fns(struct ssh_gss_library *lib)
+{
+ lib->indicate_mech = ssh_gssapi_indicate_mech;
+ lib->import_name = ssh_gssapi_import_name;
+ lib->release_name = ssh_gssapi_release_name;
+ lib->init_sec_context = ssh_gssapi_init_sec_context;
+ lib->free_tok = ssh_gssapi_free_tok;
+ lib->acquire_cred = ssh_gssapi_acquire_cred;
+ lib->release_cred = ssh_gssapi_release_cred;
+ lib->get_mic = ssh_gssapi_get_mic;
+ lib->verify_mic = ssh_gssapi_verify_mic;
+ lib->free_mic = ssh_gssapi_free_mic;
+ lib->display_status = ssh_gssapi_display_status;
+}
+
+#else
+
+/* Dummy function so this source file defines something if NO_GSSAPI
+ is defined. */
+
+int ssh_gssapi_init(void)
+{
+ return 0;
+}
+
+#endif
diff --git a/ssh/gssc.h b/ssh/gssc.h
new file mode 100644
index 00000000..d1d99eb1
--- /dev/null
+++ b/ssh/gssc.h
@@ -0,0 +1,24 @@
+#ifndef PUTTY_SSHGSSC_H
+#define PUTTY_SSHGSSC_H
+#include "putty.h"
+#ifndef NO_GSSAPI
+
+#include "pgssapi.h"
+#include "gss.h"
+
+typedef struct gssapi_ssh_gss_ctx {
+ OM_uint32 maj_stat;
+ OM_uint32 min_stat;
+ gss_ctx_id_t ctx;
+ time_t expiry;
+} gssapi_ssh_gss_ctx;
+
+void ssh_gssapi_bind_fns(struct ssh_gss_library *lib);
+
+#else
+
+int ssh_gssapi_init(void);
+
+#endif /*NO_GSSAPI*/
+
+#endif /*PUTTY_SSHGSSC_H*/
diff --git a/ssh/kex2-client.c b/ssh/kex2-client.c
new file mode 100644
index 00000000..26159bb5
--- /dev/null
+++ b/ssh/kex2-client.c
@@ -0,0 +1,1062 @@
+/*
+ * Client side of key exchange for the SSH-2 transport protocol (RFC 4253).
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "sshcr.h"
+#include "storage.h"
+#include "transport2.h"
+#include "mpint.h"
+
+/*
+ * Another copy of the symbol defined in mpunsafe.c. See the comment
+ * there.
+ */
+const int deliberate_symbol_clash = 12345;
+
+void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
+{
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ PktIn *pktin;
+ PktOut *pktout;
+
+ crBegin(s->crStateKex);
+
+ if (s->kex_alg->main_type == KEXTYPE_DH) {
+ /*
+ * Work out the number of bits of key we will need from the
+ * key exchange. We start with the maximum key length of
+ * either cipher...
+ */
+ {
+ int csbits, scbits;
+
+ csbits = s->out.cipher ? s->out.cipher->real_keybits : 0;
+ scbits = s->in.cipher ? s->in.cipher->real_keybits : 0;
+ s->nbits = (csbits > scbits ? csbits : scbits);
+ }
+ /* The keys only have hlen-bit entropy, since they're based on
+ * a hash. So cap the key size at hlen bits. */
+ if (s->nbits > s->kex_alg->hash->hlen * 8)
+ s->nbits = s->kex_alg->hash->hlen * 8;
+
+ /*
+ * If we're doing Diffie-Hellman group exchange, start by
+ * requesting a group.
+ */
+ if (dh_is_gex(s->kex_alg)) {
+ ppl_logevent("Doing Diffie-Hellman group exchange");
+ s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGEX;
+ /*
+ * Work out how big a DH group we will need to allow that
+ * much data.
+ */
+ s->pbits = 512 << ((s->nbits - 1) / 64);
+ if (s->pbits < DH_MIN_SIZE)
+ s->pbits = DH_MIN_SIZE;
+ if (s->pbits > DH_MAX_SIZE)
+ s->pbits = DH_MAX_SIZE;
+ if ((s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) {
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD);
+ put_uint32(pktout, s->pbits);
+ } else {
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST);
+ put_uint32(pktout, DH_MIN_SIZE);
+ put_uint32(pktout, s->pbits);
+ put_uint32(pktout, DH_MAX_SIZE);
+ }
+ pq_push(s->ppl.out_pq, pktout);
+
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+ "expecting Diffie-Hellman group, type %d (%s)",
+ pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ *aborted = true;
+ return;
+ }
+ s->p = get_mp_ssh2(pktin);
+ s->g = get_mp_ssh2(pktin);
+ if (get_err(pktin)) {
+ ssh_proto_error(s->ppl.ssh,
+ "Unable to parse Diffie-Hellman group packet");
+ *aborted = true;
+ return;
+ }
+ s->dh_ctx = dh_setup_gex(s->p, s->g);
+ s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
+ s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
+
+ ppl_logevent("Doing Diffie-Hellman key exchange using %d-bit "
+ "modulus and hash %s with a server-supplied group",
+ dh_modulus_bit_size(s->dh_ctx),
+ ssh_hash_alg(s->exhash)->text_name);
+ } else {
+ s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGROUP;
+ s->dh_ctx = dh_setup_group(s->kex_alg);
+ s->kex_init_value = SSH2_MSG_KEXDH_INIT;
+ s->kex_reply_value = SSH2_MSG_KEXDH_REPLY;
+
+ ppl_logevent("Doing Diffie-Hellman key exchange using %d-bit "
+ "modulus and hash %s with standard group \"%s\"",
+ dh_modulus_bit_size(s->dh_ctx),
+ ssh_hash_alg(s->exhash)->text_name,
+ s->kex_alg->groupname);
+ }
+
+ /*
+ * Now generate and send e for Diffie-Hellman.
+ */
+ seat_set_busy_status(s->ppl.seat, BUSY_CPU);
+ s->e = dh_create_e(s->dh_ctx);
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, s->kex_init_value);
+ put_mp_ssh2(pktout, s->e);
+ pq_push(s->ppl.out_pq, pktout);
+
+ seat_set_busy_status(s->ppl.seat, BUSY_WAITING);
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != s->kex_reply_value) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+ "expecting Diffie-Hellman reply, type %d (%s)",
+ pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ *aborted = true;
+ return;
+ }
+ seat_set_busy_status(s->ppl.seat, BUSY_CPU);
+ s->hostkeydata = get_string(pktin);
+ s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata);
+ s->f = get_mp_ssh2(pktin);
+ s->sigdata = get_string(pktin);
+ if (get_err(pktin)) {
+ ssh_proto_error(s->ppl.ssh,
+ "Unable to parse Diffie-Hellman reply packet");
+ *aborted = true;
+ return;
+ }
+
+ {
+ const char *err = dh_validate_f(s->dh_ctx, s->f);
+ if (err) {
+ ssh_proto_error(s->ppl.ssh, "Diffie-Hellman reply failed "
+ "validation: %s", err);
+ *aborted = true;
+ return;
+ }
+ }
+ mp_int *K = dh_find_K(s->dh_ctx, s->f);
+ put_mp_ssh2(s->kex_shared_secret, K);
+ mp_free(K);
+
+ /* We assume everything from now on will be quick, and it might
+ * involve user interaction. */
+ seat_set_busy_status(s->ppl.seat, BUSY_NOT);
+
+ put_stringpl(s->exhash, s->hostkeydata);
+ if (dh_is_gex(s->kex_alg)) {
+ if (!(s->ppl.remote_bugs & BUG_SSH2_OLDGEX))
+ put_uint32(s->exhash, DH_MIN_SIZE);
+ put_uint32(s->exhash, s->pbits);
+ if (!(s->ppl.remote_bugs & BUG_SSH2_OLDGEX))
+ put_uint32(s->exhash, DH_MAX_SIZE);
+ put_mp_ssh2(s->exhash, s->p);
+ put_mp_ssh2(s->exhash, s->g);
+ }
+ put_mp_ssh2(s->exhash, s->e);
+ put_mp_ssh2(s->exhash, s->f);
+
+ dh_cleanup(s->dh_ctx);
+ s->dh_ctx = NULL;
+ mp_free(s->f); s->f = NULL;
+ if (dh_is_gex(s->kex_alg)) {
+ mp_free(s->g); s->g = NULL;
+ mp_free(s->p); s->p = NULL;
+ }
+ } else if (s->kex_alg->main_type == KEXTYPE_ECDH) {
+ char *desc = ecdh_keyalg_description(s->kex_alg);
+ ppl_logevent("Doing %s, using hash %s", desc,
+ ssh_hash_alg(s->exhash)->text_name);
+ sfree(desc);
+
+ s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX;
+
+ s->ecdh_key = ecdh_key_new(s->kex_alg, false);
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_INIT);
+ {
+ strbuf *pubpoint = strbuf_new();
+ ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint));
+ put_stringsb(pktout, pubpoint);
+ }
+
+ pq_push(s->ppl.out_pq, pktout);
+
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_KEX_ECDH_REPLY) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+ "expecting ECDH reply, type %d (%s)", pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ *aborted = true;
+ return;
+ }
+
+ s->hostkeydata = get_string(pktin);
+ put_stringpl(s->exhash, s->hostkeydata);
+ s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata);
+
+ {
+ strbuf *pubpoint = strbuf_new();
+ ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint));
+ put_string(s->exhash, pubpoint->u, pubpoint->len);
+ strbuf_free(pubpoint);
+ }
+
+ {
+ ptrlen keydata = get_string(pktin);
+ put_stringpl(s->exhash, keydata);
+ bool ok = ecdh_key_getkey(s->ecdh_key, keydata,
+ BinarySink_UPCAST(s->kex_shared_secret));
+ if (!get_err(pktin) && !ok) {
+ ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve "
+ "point in ECDH reply");
+ *aborted = true;
+ return;
+ }
+ }
+
+ s->sigdata = get_string(pktin);
+ if (get_err(pktin)) {
+ ssh_proto_error(s->ppl.ssh, "Unable to parse ECDH reply packet");
+ *aborted = true;
+ return;
+ }
+
+ ecdh_key_free(s->ecdh_key);
+ s->ecdh_key = NULL;
+#ifndef NO_GSSAPI
+ } else if (kex_is_gss(s->kex_alg)) {
+ ptrlen data;
+
+ s->ppl.bpp->pls->kctx = SSH2_PKTCTX_GSSKEX;
+ s->init_token_sent = false;
+ s->complete_rcvd = false;
+ s->hkey = NULL;
+ s->keystr = NULL;
+
+ /*
+ * Work out the number of bits of key we will need from the
+ * key exchange. We start with the maximum key length of
+ * either cipher...
+ *
+ * This is rote from the KEXTYPE_DH section above.
+ */
+ {
+ int csbits, scbits;
+
+ csbits = s->out.cipher->real_keybits;
+ scbits = s->in.cipher->real_keybits;
+ s->nbits = (csbits > scbits ? csbits : scbits);
+ }
+ /* The keys only have hlen-bit entropy, since they're based on
+ * a hash. So cap the key size at hlen bits. */
+ if (s->nbits > s->kex_alg->hash->hlen * 8)
+ s->nbits = s->kex_alg->hash->hlen * 8;
+
+ assert(!s->ecdh_key);
+ assert(!s->dh_ctx);
+
+ if (s->kex_alg->main_type == KEXTYPE_GSS_ECDH) {
+ s->ecdh_key = ecdh_key_new(s->kex_alg, false);
+
+ char *desc = ecdh_keyalg_description(s->kex_alg);
+ ppl_logevent("Doing GSSAPI (with Kerberos V5) %s with hash %s",
+ desc, ssh_hash_alg(s->exhash)->text_name);
+ sfree(desc);
+ } else if (dh_is_gex(s->kex_alg)) {
+ /*
+ * Work out how big a DH group we will need to allow that
+ * much data.
+ */
+ s->pbits = 512 << ((s->nbits - 1) / 64);
+ ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman "
+ "group exchange, with minimum %d bits, and hash %s",
+ s->pbits, ssh_hash_alg(s->exhash)->text_name);
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXGSS_GROUPREQ);
+ put_uint32(pktout, s->pbits); /* min */
+ put_uint32(pktout, s->pbits); /* preferred */
+ put_uint32(pktout, s->pbits * 2); /* max */
+ pq_push(s->ppl.out_pq, pktout);
+
+ crMaybeWaitUntilV(
+ (pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_KEXGSS_GROUP) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+ "expecting Diffie-Hellman group, type %d (%s)",
+ pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ *aborted = true;
+ return;
+ }
+ s->p = get_mp_ssh2(pktin);
+ s->g = get_mp_ssh2(pktin);
+ if (get_err(pktin)) {
+ ssh_proto_error(s->ppl.ssh,
+ "Unable to parse Diffie-Hellman group packet");
+ *aborted = true;
+ return;
+ }
+ s->dh_ctx = dh_setup_gex(s->p, s->g);
+ } else {
+ s->dh_ctx = dh_setup_group(s->kex_alg);
+ ppl_logevent("Using GSSAPI (with Kerberos V5) Diffie-Hellman with"
+ " standard group \"%s\" and hash %s",
+ s->kex_alg->groupname,
+ ssh_hash_alg(s->exhash)->text_name);
+ }
+
+ /* Now generate e for Diffie-Hellman. */
+ seat_set_busy_status(s->ppl.seat, BUSY_CPU);
+ if (s->ecdh_key) {
+ s->ebuf = strbuf_new_nm();
+ ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(s->ebuf));
+ } else {
+ s->e = dh_create_e(s->dh_ctx);
+ }
+
+ if (s->shgss->lib->gsslogmsg)
+ ppl_logevent("%s", s->shgss->lib->gsslogmsg);
+
+ /* initial tokens are empty */
+ SSH_GSS_CLEAR_BUF(&s->gss_rcvtok);
+ SSH_GSS_CLEAR_BUF(&s->gss_sndtok);
+ SSH_GSS_CLEAR_BUF(&s->mic);
+ s->gss_stat = s->shgss->lib->acquire_cred(
+ s->shgss->lib, &s->shgss->ctx, &s->gss_cred_expiry);
+ if (s->gss_stat != SSH_GSS_OK) {
+ ssh_sw_abort(s->ppl.ssh,
+ "GSSAPI key exchange failed to initialise");
+ *aborted = true;
+ return;
+ }
+
+ /* now enter the loop */
+ assert(s->shgss->srv_name);
+ do {
+ /*
+ * When acquire_cred yields no useful expiration, go with the
+ * service ticket expiration.
+ */
+ s->gss_stat = s->shgss->lib->init_sec_context(
+ s->shgss->lib, &s->shgss->ctx, s->shgss->srv_name,
+ s->gss_delegate, &s->gss_rcvtok, &s->gss_sndtok,
+ (s->gss_cred_expiry == GSS_NO_EXPIRATION ?
+ &s->gss_cred_expiry : NULL), NULL);
+ SSH_GSS_CLEAR_BUF(&s->gss_rcvtok);
+
+ if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd)
+ break; /* MIC is verified after the loop */
+
+ if (s->gss_stat != SSH_GSS_S_COMPLETE &&
+ s->gss_stat != SSH_GSS_S_CONTINUE_NEEDED) {
+ if (s->shgss->lib->display_status(
+ s->shgss->lib, s->shgss->ctx,
+ &s->gss_buf) == SSH_GSS_OK) {
+ char *err = s->gss_buf.value;
+ ssh_sw_abort(s->ppl.ssh,
+ "GSSAPI key exchange failed to initialise "
+ "context: %s", err);
+ sfree(err);
+ *aborted = true;
+ return;
+ }
+ }
+ assert(s->gss_stat == SSH_GSS_S_COMPLETE ||
+ s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED);
+
+ if (!s->init_token_sent) {
+ s->init_token_sent = true;
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp,
+ SSH2_MSG_KEXGSS_INIT);
+ if (s->gss_sndtok.length == 0) {
+ ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange failed: "
+ "no initial context token");
+ *aborted = true;
+ return;
+ }
+ put_string(pktout,
+ s->gss_sndtok.value, s->gss_sndtok.length);
+ if (s->ecdh_key) {
+ put_stringpl(pktout, ptrlen_from_strbuf(s->ebuf));
+ } else {
+ put_mp_ssh2(pktout, s->e);
+ }
+ pq_push(s->ppl.out_pq, pktout);
+ s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok);
+ ppl_logevent("GSSAPI key exchange initialised");
+ } else if (s->gss_sndtok.length != 0) {
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_KEXGSS_CONTINUE);
+ put_string(pktout,
+ s->gss_sndtok.value, s->gss_sndtok.length);
+ pq_push(s->ppl.out_pq, pktout);
+ s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok);
+ }
+
+ if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd)
+ break;
+
+ wait_for_gss_token:
+ crMaybeWaitUntilV(
+ (pktin = ssh2_transport_pop(s)) != NULL);
+ switch (pktin->type) {
+ case SSH2_MSG_KEXGSS_CONTINUE:
+ data = get_string(pktin);
+ s->gss_rcvtok.value = (char *)data.ptr;
+ s->gss_rcvtok.length = data.len;
+ continue;
+ case SSH2_MSG_KEXGSS_COMPLETE:
+ s->complete_rcvd = true;
+ if (s->ecdh_key) {
+ s->fbuf = strbuf_dup_nm(get_string(pktin));
+ } else {
+ s->f = get_mp_ssh2(pktin);
+ }
+ data = get_string(pktin);
+ s->mic.value = (char *)data.ptr;
+ s->mic.length = data.len;
+ /* If there's a final token we loop to consume it */
+ if (get_bool(pktin)) {
+ data = get_string(pktin);
+ s->gss_rcvtok.value = (char *)data.ptr;
+ s->gss_rcvtok.length = data.len;
+ continue;
+ }
+ break;
+ case SSH2_MSG_KEXGSS_HOSTKEY:
+ s->hostkeydata = get_string(pktin);
+ if (s->hostkey_alg) {
+ s->hkey = ssh_key_new_pub(s->hostkey_alg,
+ s->hostkeydata);
+ put_stringpl(s->exhash, s->hostkeydata);
+ }
+ /*
+ * Can't loop as we have no token to pass to
+ * init_sec_context.
+ */
+ goto wait_for_gss_token;
+ case SSH2_MSG_KEXGSS_ERROR:
+ /*
+ * We have no use for the server's major and minor
+ * status. The minor status is really only
+ * meaningful to the server, and with luck the major
+ * status means something to us (but not really all
+ * that much). The string is more meaningful, and
+ * hopefully the server sends any error tokens, as
+ * that will produce the most useful information for
+ * us.
+ */
+ get_uint32(pktin); /* server's major status */
+ get_uint32(pktin); /* server's minor status */
+ data = get_string(pktin);
+ ppl_logevent("GSSAPI key exchange failed; "
+ "server's message: %.*s", PTRLEN_PRINTF(data));
+ /* Language tag, but we have no use for it */
+ get_string(pktin);
+ /*
+ * Wait for an error token, if there is one, or the
+ * server's disconnect. The error token, if there
+ * is one, must follow the SSH2_MSG_KEXGSS_ERROR
+ * message, per the RFC.
+ */
+ goto wait_for_gss_token;
+ default:
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet "
+ "during GSSAPI key exchange, type %d (%s)",
+ pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ *aborted = true;
+ return;
+ }
+ } while (s->gss_rcvtok.length ||
+ s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED ||
+ !s->complete_rcvd);
+
+ if (s->ecdh_key) {
+ bool ok = ecdh_key_getkey(s->ecdh_key, ptrlen_from_strbuf(s->fbuf),
+ BinarySink_UPCAST(s->kex_shared_secret));
+ if (!ok) {
+ ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve "
+ "point in GSSAPI ECDH reply");
+ *aborted = true;
+ return;
+ }
+ } else {
+ const char *err = dh_validate_f(s->dh_ctx, s->f);
+ if (err) {
+ ssh_proto_error(s->ppl.ssh, "GSSAPI reply failed "
+ "validation: %s", err);
+ *aborted = true;
+ return;
+ }
+ mp_int *K = dh_find_K(s->dh_ctx, s->f);
+ put_mp_ssh2(s->kex_shared_secret, K);
+ mp_free(K);
+ }
+
+ /* We assume everything from now on will be quick, and it might
+ * involve user interaction. */
+ seat_set_busy_status(s->ppl.seat, BUSY_NOT);
+
+ if (!s->hkey)
+ put_stringz(s->exhash, "");
+
+ if (s->ecdh_key) {
+ put_stringpl(s->exhash, ptrlen_from_strbuf(s->ebuf));
+ put_stringpl(s->exhash, ptrlen_from_strbuf(s->fbuf));
+ } else {
+ if (dh_is_gex(s->kex_alg)) {
+ /* min, preferred, max */
+ put_uint32(s->exhash, s->pbits);
+ put_uint32(s->exhash, s->pbits);
+ put_uint32(s->exhash, s->pbits * 2);
+
+ put_mp_ssh2(s->exhash, s->p);
+ put_mp_ssh2(s->exhash, s->g);
+ }
+ put_mp_ssh2(s->exhash, s->e);
+ put_mp_ssh2(s->exhash, s->f);
+ }
+
+ /*
+ * MIC verification is done below, after we compute the hash
+ * used as the MIC input.
+ */
+
+ if (s->ecdh_key) {
+ ecdh_key_free(s->ecdh_key);
+ s->ecdh_key = NULL;
+ strbuf_free(s->ebuf); s->ebuf = NULL;
+ strbuf_free(s->fbuf); s->fbuf = NULL;
+ } else {
+ dh_cleanup(s->dh_ctx);
+ s->dh_ctx = NULL;
+ mp_free(s->f); s->f = NULL;
+ if (dh_is_gex(s->kex_alg)) {
+ mp_free(s->g); s->g = NULL;
+ mp_free(s->p); s->p = NULL;
+ }
+ }
+#endif
+ } else {
+ ptrlen rsakeydata;
+
+ assert(s->kex_alg->main_type == KEXTYPE_RSA);
+ ppl_logevent("Doing RSA key exchange with hash %s",
+ ssh_hash_alg(s->exhash)->text_name);
+ s->ppl.bpp->pls->kctx = SSH2_PKTCTX_RSAKEX;
+ /*
+ * RSA key exchange. First expect a KEXRSA_PUBKEY packet
+ * from the server.
+ */
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+ "expecting RSA public key, type %d (%s)",
+ pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ *aborted = true;
+ return;
+ }
+
+ s->hostkeydata = get_string(pktin);
+ put_stringpl(s->exhash, s->hostkeydata);
+ s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata);
+
+ rsakeydata = get_string(pktin);
+
+ s->rsa_kex_key = ssh_rsakex_newkey(rsakeydata);
+ if (!s->rsa_kex_key) {
+ ssh_proto_error(s->ppl.ssh,
+ "Unable to parse RSA public key packet");
+ *aborted = true;
+ return;
+ }
+ s->rsa_kex_key_needs_freeing = true;
+
+ put_stringpl(s->exhash, rsakeydata);
+
+ /*
+ * Next, set up a shared secret K, of precisely KLEN -
+ * 2*HLEN - 49 bits, where KLEN is the bit length of the
+ * RSA key modulus and HLEN is the bit length of the hash
+ * we're using.
+ */
+ {
+ int klen = ssh_rsakex_klen(s->rsa_kex_key);
+
+ const struct ssh_rsa_kex_extra *extra =
+ (const struct ssh_rsa_kex_extra *)s->kex_alg->extra;
+ if (klen < extra->minklen) {
+ ssh_proto_error(s->ppl.ssh, "Server sent %d-bit RSA key, "
+ "less than the minimum size %d for %s "
+ "key exchange", klen, extra->minklen,
+ s->kex_alg->name);
+ *aborted = true;
+ return;
+ }
+
+ int nbits = klen - (2*s->kex_alg->hash->hlen*8 + 49);
+ assert(nbits > 0);
+
+ strbuf *buf, *outstr;
+
+ mp_int *tmp = mp_random_bits(nbits - 1);
+ mp_int *K = mp_power_2(nbits - 1);
+ mp_add_into(K, K, tmp);
+ mp_free(tmp);
+
+ /*
+ * Encode this as an mpint.
+ */
+ buf = strbuf_new_nm();
+ put_mp_ssh2(buf, K);
+
+ /*
+ * Store a copy as the output shared secret from the kex.
+ */
+ put_mp_ssh2(s->kex_shared_secret, K);
+ mp_free(K);
+
+ /*
+ * Encrypt it with the given RSA key.
+ */
+ outstr = ssh_rsakex_encrypt(s->rsa_kex_key, s->kex_alg->hash,
+ ptrlen_from_strbuf(buf));
+
+ /*
+ * And send it off in a return packet.
+ */
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_SECRET);
+ put_stringpl(pktout, ptrlen_from_strbuf(outstr));
+ pq_push(s->ppl.out_pq, pktout);
+
+ put_stringsb(s->exhash, outstr); /* frees outstr */
+
+ strbuf_free(buf);
+ }
+
+ ssh_rsakex_freekey(s->rsa_kex_key);
+ s->rsa_kex_key = NULL;
+ s->rsa_kex_key_needs_freeing = false;
+
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_KEXRSA_DONE) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+ "expecting RSA kex signature, type %d (%s)",
+ pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ *aborted = true;
+ return;
+ }
+
+ s->sigdata = get_string(pktin);
+ if (get_err(pktin)) {
+ ssh_proto_error(s->ppl.ssh, "Unable to parse RSA kex signature");
+ *aborted = true;
+ return;
+ }
+ }
+
+ ssh2transport_finalise_exhash(s);
+
+#ifndef NO_GSSAPI
+ if (kex_is_gss(s->kex_alg)) {
+ Ssh_gss_buf gss_buf;
+ SSH_GSS_CLEAR_BUF(&s->gss_buf);
+
+ gss_buf.value = s->exchange_hash;
+ gss_buf.length = s->kex_alg->hash->hlen;
+ s->gss_stat = s->shgss->lib->verify_mic(
+ s->shgss->lib, s->shgss->ctx, &gss_buf, &s->mic);
+ if (s->gss_stat != SSH_GSS_OK) {
+ if (s->shgss->lib->display_status(
+ s->shgss->lib, s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) {
+ char *err = s->gss_buf.value;
+ ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange MIC was "
+ "not valid: %s", err);
+ sfree(err);
+ } else {
+ ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange MIC was "
+ "not valid");
+ }
+ *aborted = true;
+ return;
+ }
+
+ s->gss_kex_used = true;
+
+ /*-
+ * If this the first KEX, save the GSS context for "gssapi-keyex"
+ * authentication.
+ *
+ * https://www.rfc-editor.org/rfc/rfc4462#section-4
+ *
+ * This method may be used only if the initial key exchange was
+ * performed using a GSS-API-based key exchange method defined in
+ * accordance with Section 2. The GSS-API context used with this
+ * method is always that established during an initial GSS-API-based
+ * key exchange. Any context established during key exchange for the
+ * purpose of rekeying MUST NOT be used with this method.
+ */
+ if (s->got_session_id) {
+ s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
+ }
+ ppl_logevent("GSSAPI Key Exchange complete!");
+ }
+#endif
+
+ s->dh_ctx = NULL;
+
+ /* In GSS keyex there's no hostkey signature to verify */
+ if (!kex_is_gss(s->kex_alg)) {
+ if (!s->hkey) {
+ ssh_proto_error(s->ppl.ssh, "Server's host key is invalid");
+ *aborted = true;
+ return;
+ }
+
+ if (!ssh_key_verify(
+ s->hkey, s->sigdata,
+ make_ptrlen(s->exchange_hash, s->kex_alg->hash->hlen))) {
+#ifndef FUZZING
+ ssh_proto_error(s->ppl.ssh, "Signature from server's host key "
+ "is invalid");
+ *aborted = true;
+ return;
+#endif
+ }
+ }
+
+ s->keystr = s->hkey ? ssh_key_cache_str(s->hkey) : NULL;
+#ifndef NO_GSSAPI
+ if (s->gss_kex_used) {
+ /*
+ * In a GSS-based session, check the host key (if any) against
+ * the transient host key cache.
+ */
+ if (kex_is_gss(s->kex_alg)) {
+
+ /*
+ * We've just done a GSS key exchange. If it gave us a
+ * host key, store it.
+ */
+ if (s->hkey) {
+ char *fingerprint = ssh2_double_fingerprint(
+ s->hkey, SSH_FPTYPE_DEFAULT);
+ ppl_logevent("GSS kex provided fallback host key:");
+ ppl_logevent("%s", fingerprint);
+ sfree(fingerprint);
+
+ ssh_transient_hostkey_cache_add(s->thc, s->hkey);
+ } else if (!ssh_transient_hostkey_cache_non_empty(s->thc)) {
+ /*
+ * But if it didn't, then we currently have no
+ * fallback host key to use in subsequent non-GSS
+ * rekeys. So we should immediately trigger a non-GSS
+ * rekey of our own, to set one up, before the session
+ * keys have been used for anything else.
+ *
+ * This is similar to the cross-certification done at
+ * user request in the permanent host key cache, but
+ * here we do it automatically, once, at session
+ * startup, and only add the key to the transient
+ * cache.
+ */
+ if (s->hostkey_alg) {
+ s->need_gss_transient_hostkey = true;
+ } else {
+ /*
+ * If we negotiated the "null" host key algorithm
+ * in the key exchange, that's an indication that
+ * no host key at all is available from the server
+ * (both because we listed "null" last, and
+ * because RFC 4462 section 5 says that a server
+ * MUST NOT offer "null" as a host key algorithm
+ * unless that is the only algorithm it provides
+ * at all).
+ *
+ * In that case we actually _can't_ perform a
+ * non-GSSAPI key exchange, so it's pointless to
+ * attempt one proactively. This is also likely to
+ * cause trouble later if a rekey is required at a
+ * moment whne GSS credentials are not available,
+ * but someone setting up a server in this
+ * configuration presumably accepts that as a
+ * consequence.
+ */
+ if (!s->warned_about_no_gss_transient_hostkey) {
+ ppl_logevent("No fallback host key available");
+ s->warned_about_no_gss_transient_hostkey = true;
+ }
+ }
+ }
+ } else {
+ /*
+ * We've just done a fallback key exchange, so make
+ * sure the host key it used is in the cache of keys
+ * we previously received in GSS kexes.
+ *
+ * An exception is if this was the non-GSS key exchange we
+ * triggered on purpose to populate the transient cache.
+ */
+ assert(s->hkey); /* only KEXTYPE_GSS* lets this be null */
+ char *fingerprint = ssh2_double_fingerprint(
+ s->hkey, SSH_FPTYPE_DEFAULT);
+
+ if (s->need_gss_transient_hostkey) {
+ ppl_logevent("Post-GSS rekey provided fallback host key:");
+ ppl_logevent("%s", fingerprint);
+ ssh_transient_hostkey_cache_add(s->thc, s->hkey);
+ s->need_gss_transient_hostkey = false;
+ } else if (!ssh_transient_hostkey_cache_verify(s->thc, s->hkey)) {
+ ppl_logevent("Non-GSS rekey after initial GSS kex "
+ "used host key:");
+ ppl_logevent("%s", fingerprint);
+ sfree(fingerprint);
+ ssh_sw_abort(s->ppl.ssh, "Server's host key did not match any "
+ "used in previous GSS kex");
+ *aborted = true;
+ return;
+ }
+
+ sfree(fingerprint);
+ }
+ } else
+#endif /* NO_GSSAPI */
+ if (!s->got_session_id) {
+ /*
+ * Make a note of any other host key formats that are available.
+ */
+ {
+ int i, j, nkeys = 0;
+ char *list = NULL;
+ for (i = 0; i < lenof(ssh2_hostkey_algs); i++) {
+ if (ssh2_hostkey_algs[i].alg == s->hostkey_alg)
+ continue;
+
+ for (j = 0; j < s->n_uncert_hostkeys; j++)
+ if (s->uncert_hostkeys[j] == i)
+ break;
+
+ if (j < s->n_uncert_hostkeys) {
+ char *newlist;
+ if (list)
+ newlist = dupprintf(
+ "%s/%s", list,
+ ssh2_hostkey_algs[i].alg->ssh_id);
+ else
+ newlist = dupprintf(
+ "%s", ssh2_hostkey_algs[i].alg->ssh_id);
+ sfree(list);
+ list = newlist;
+ nkeys++;
+ }
+ }
+ if (list) {
+ ppl_logevent("Server also has %s host key%s, but we "
+ "don't know %s", list,
+ nkeys > 1 ? "s" : "",
+ nkeys > 1 ? "any of them" : "it");
+ sfree(list);
+ }
+ }
+
+ ssh2_userkey uk = { .key = s->hkey, .comment = NULL };
+ char **fingerprints = ssh2_all_fingerprints(s->hkey);
+
+ FingerprintType fptype_default =
+ ssh2_pick_default_fingerprint(fingerprints);
+ ppl_logevent("Host key fingerprint is:");
+ ppl_logevent("%s", fingerprints[fptype_default]);
+
+ /*
+ * Authenticate remote host: verify host key, either by
+ * certification or by the local host key cache.
+ *
+ * (We've already checked the signature of the exchange
+ * hash.)
+ */
+ if (ssh_key_alg(s->hkey)->is_certificate) {
+ char *base_fp = ssh2_fingerprint(
+ s->hkey, ssh_fptype_to_cert(fptype_default));
+ ppl_logevent("Host key is a certificate. "
+ "Hash including certificate:");
+ ppl_logevent("%s", base_fp);
+ sfree(base_fp);
+
+ strbuf *id_string = strbuf_new();
+ StripCtrlChars *id_string_scc = stripctrl_new(
+ BinarySink_UPCAST(id_string), false, L'\0');
+ ssh_key_cert_id_string(
+ s->hkey, BinarySink_UPCAST(id_string_scc));
+ stripctrl_free(id_string_scc);
+ ppl_logevent("Certificate ID string is \"%s\"", id_string->s);
+ strbuf_free(id_string);
+
+ strbuf *ca_pub = strbuf_new();
+ ssh_key_ca_public_blob(s->hkey, BinarySink_UPCAST(ca_pub));
+ host_ca hca_search = { .ca_public_key = ca_pub };
+ host_ca *hca_found = find234(s->host_cas, &hca_search, NULL);
+
+ char *ca_fp = ssh2_fingerprint_blob(ptrlen_from_strbuf(ca_pub),
+ fptype_default);
+ ppl_logevent("Fingerprint of certification authority:");
+ ppl_logevent("%s", ca_fp);
+ sfree(ca_fp);
+
+ strbuf_free(ca_pub);
+
+ strbuf *error = strbuf_new();
+ bool cert_ok = false;
+
+ if (!hca_found) {
+ put_fmt(error, "Certification authority is not trusted");
+ } else {
+ ppl_logevent("Certification authority matches '%s'",
+ hca_found->name);
+ cert_ok = ssh_key_check_cert(
+ s->hkey,
+ true, /* host certificate */
+ ptrlen_from_asciz(s->savedhost),
+ time(NULL),
+ &hca_found->opts,
+ BinarySink_UPCAST(error));
+ }
+ if (cert_ok) {
+ strbuf_free(error);
+ ssh2_free_all_fingerprints(fingerprints);
+ ppl_logevent("Accepted certificate");
+ goto host_key_ok;
+ } else {
+ ppl_logevent("Rejected host key certificate: %s",
+ error->s);
+ strbuf_free(error);
+ /* now fall through into normal host key checking */
+ }
+ }
+
+ {
+ char *keydisp = ssh2_pubkey_openssh_str(&uk);
+
+ int ca_count = ssh_key_alg(s->hkey)->is_certificate ?
+ count234(s->host_cas) : 0;
+
+ s->spr = verify_ssh_host_key(
+ ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport,
+ s->hkey, ssh_key_cache_id(s->hkey), s->keystr, keydisp,
+ fingerprints, ca_count, ssh2_transport_dialog_callback, s);
+
+ ssh2_free_all_fingerprints(fingerprints);
+ sfree(keydisp);
+#ifdef FUZZING
+ s->spr = SPR_OK;
+#endif
+ crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+ if (spr_is_abort(s->spr)) {
+ *aborted = true;
+ ssh_spr_close(s->ppl.ssh, s->spr, "host key verification");
+ return;
+ }
+
+ if (ssh_key_alg(s->hkey)->is_certificate) {
+ /*
+ * Explain what's going on in the Event Log: if we
+ * got here by way of a certified key whose
+ * certificate we didn't like, then we should
+ * explain why we chose to continue with the
+ * connection anyway!
+ */
+ ppl_logevent("Accepting certified host key anyway based "
+ "on cache");
+ }
+ }
+
+ host_key_ok:
+
+ /*
+ * Save this host key, to check against the one presented in
+ * subsequent rekeys.
+ */
+ strbuf_clear(s->hostkeyblob);
+ ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob));
+ } else if (s->cross_certifying) {
+ assert(s->hkey);
+ assert(ssh_key_alg(s->hkey) == s->cross_certifying);
+
+ char *fingerprint = ssh2_double_fingerprint(
+ s->hkey, SSH_FPTYPE_DEFAULT);
+ ppl_logevent("Storing additional host key for this host:");
+ ppl_logevent("%s", fingerprint);
+ sfree(fingerprint);
+
+ store_host_key(s->savedhost, s->savedport,
+ ssh_key_cache_id(s->hkey), s->keystr);
+ /*
+ * Don't forget to store the new key as the one we'll be
+ * re-checking in future normal rekeys.
+ */
+ strbuf_clear(s->hostkeyblob);
+ ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob));
+ } else {
+ /*
+ * In a rekey, we never present an interactive host key
+ * verification request to the user. Instead, we simply
+ * enforce that the key we're seeing this time is identical to
+ * the one we saw before.
+ */
+ strbuf *thisblob = strbuf_new();
+ ssh_key_public_blob(s->hkey, BinarySink_UPCAST(thisblob));
+ bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(thisblob),
+ ptrlen_from_strbuf(s->hostkeyblob));
+ strbuf_free(thisblob);
+ if (!match) {
+#ifndef FUZZING
+ ssh_sw_abort(s->ppl.ssh,
+ "Host key was different in repeat key exchange");
+ *aborted = true;
+ return;
+#endif
+ }
+ }
+
+ sfree(s->keystr);
+ s->keystr = NULL;
+ if (s->hkey) {
+ ssh_key_free(s->hkey);
+ s->hkey = NULL;
+ }
+
+ crFinishV;
+}
diff --git a/ssh/kex2-server.c b/ssh/kex2-server.c
new file mode 100644
index 00000000..570d7750
--- /dev/null
+++ b/ssh/kex2-server.c
@@ -0,0 +1,337 @@
+/*
+ * Server side of key exchange for the SSH-2 transport protocol (RFC 4253).
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "sshcr.h"
+#include "server.h"
+#include "sshkeygen.h"
+#include "storage.h"
+#include "transport2.h"
+#include "mpint.h"
+
+void ssh2_transport_provide_hostkeys(PacketProtocolLayer *ppl,
+ ssh_key *const *hostkeys, int nhostkeys)
+{
+ struct ssh2_transport_state *s =
+ container_of(ppl, struct ssh2_transport_state, ppl);
+
+ s->hostkeys = hostkeys;
+ s->nhostkeys = nhostkeys;
+}
+
+static strbuf *finalise_and_sign_exhash(struct ssh2_transport_state *s)
+{
+ strbuf *sb;
+ ssh2transport_finalise_exhash(s);
+ sb = strbuf_new();
+ ssh_key_sign(
+ s->hkey, make_ptrlen(s->exchange_hash, s->kex_alg->hash->hlen),
+ s->hkflags, BinarySink_UPCAST(sb));
+ return sb;
+}
+
+void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
+{
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ PktIn *pktin;
+ PktOut *pktout;
+
+ crBegin(s->crStateKex);
+
+ {
+ int i;
+ for (i = 0; i < s->nhostkeys; i++)
+ if (ssh_key_alg(s->hostkeys[i]) == s->hostkey_alg) {
+ s->hkey = s->hostkeys[i];
+ break;
+ }
+ assert(s->hkey);
+ }
+
+ strbuf_clear(s->hostkeyblob);
+ ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob));
+ s->hostkeydata = ptrlen_from_strbuf(s->hostkeyblob);
+
+ put_stringpl(s->exhash, s->hostkeydata);
+
+ if (s->kex_alg->main_type == KEXTYPE_DH) {
+ /*
+ * If we're doing Diffie-Hellman group exchange, start by
+ * waiting for the group request.
+ */
+ if (dh_is_gex(s->kex_alg)) {
+ ppl_logevent("Doing Diffie-Hellman group exchange");
+ s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGEX;
+
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST &&
+ pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST_OLD) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+ "expecting Diffie-Hellman group exchange "
+ "request, type %d (%s)", pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ *aborted = true;
+ return;
+ }
+
+ if (pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST_OLD) {
+ s->dh_got_size_bounds = true;
+ s->dh_min_size = get_uint32(pktin);
+ s->pbits = get_uint32(pktin);
+ s->dh_max_size = get_uint32(pktin);
+ } else {
+ s->dh_got_size_bounds = false;
+ s->pbits = get_uint32(pktin);
+ }
+
+ /*
+ * This is a hopeless strategy for making a secure DH
+ * group! It's good enough for testing a client against,
+ * but not for serious use.
+ */
+ PrimeGenerationContext *pgc = primegen_new_context(
+ &primegen_probabilistic);
+ ProgressReceiver null_progress;
+ null_progress.vt = &null_progress_vt;
+ s->p = primegen_generate(pgc, pcs_new(s->pbits), &null_progress);
+ primegen_free_context(pgc);
+
+ s->g = mp_from_integer(2);
+ s->dh_ctx = dh_setup_gex(s->p, s->g);
+ s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
+ s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_GROUP);
+ put_mp_ssh2(pktout, s->p);
+ put_mp_ssh2(pktout, s->g);
+ pq_push(s->ppl.out_pq, pktout);
+ } else {
+ s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGROUP;
+ s->dh_ctx = dh_setup_group(s->kex_alg);
+ s->kex_init_value = SSH2_MSG_KEXDH_INIT;
+ s->kex_reply_value = SSH2_MSG_KEXDH_REPLY;
+ ppl_logevent("Using Diffie-Hellman with standard group \"%s\"",
+ s->kex_alg->groupname);
+ }
+
+ ppl_logevent("Doing Diffie-Hellman key exchange with hash %s",
+ ssh_hash_alg(s->exhash)->text_name);
+
+ /*
+ * Generate e for Diffie-Hellman.
+ */
+ s->e = dh_create_e(s->dh_ctx);
+
+ /*
+ * Wait to receive f.
+ */
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != s->kex_init_value) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+ "expecting Diffie-Hellman initial packet, "
+ "type %d (%s)", pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ *aborted = true;
+ return;
+ }
+ s->f = get_mp_ssh2(pktin);
+ if (get_err(pktin)) {
+ ssh_proto_error(s->ppl.ssh,
+ "Unable to parse Diffie-Hellman initial packet");
+ *aborted = true;
+ return;
+ }
+
+ {
+ const char *err = dh_validate_f(s->dh_ctx, s->f);
+ if (err) {
+ ssh_proto_error(s->ppl.ssh, "Diffie-Hellman initial packet "
+ "failed validation: %s", err);
+ *aborted = true;
+ return;
+ }
+ }
+ mp_int *K = dh_find_K(s->dh_ctx, s->f);
+ put_mp_ssh2(s->kex_shared_secret, K);
+ mp_free(K);
+
+ if (dh_is_gex(s->kex_alg)) {
+ if (s->dh_got_size_bounds)
+ put_uint32(s->exhash, s->dh_min_size);
+ put_uint32(s->exhash, s->pbits);
+ if (s->dh_got_size_bounds)
+ put_uint32(s->exhash, s->dh_max_size);
+ put_mp_ssh2(s->exhash, s->p);
+ put_mp_ssh2(s->exhash, s->g);
+ }
+ put_mp_ssh2(s->exhash, s->f);
+ put_mp_ssh2(s->exhash, s->e);
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, s->kex_reply_value);
+ put_stringpl(pktout, s->hostkeydata);
+ put_mp_ssh2(pktout, s->e);
+ put_stringsb(pktout, finalise_and_sign_exhash(s));
+ pq_push(s->ppl.out_pq, pktout);
+
+ dh_cleanup(s->dh_ctx);
+ s->dh_ctx = NULL;
+ mp_free(s->f); s->f = NULL;
+ if (dh_is_gex(s->kex_alg)) {
+ mp_free(s->g); s->g = NULL;
+ mp_free(s->p); s->p = NULL;
+ }
+ } else if (s->kex_alg->main_type == KEXTYPE_ECDH) {
+ char *desc = ecdh_keyalg_description(s->kex_alg);
+ ppl_logevent("Doing %s, using hash %s", desc,
+ ssh_hash_alg(s->exhash)->text_name);
+ sfree(desc);
+
+ s->ecdh_key = ecdh_key_new(s->kex_alg, true);
+ if (!s->ecdh_key) {
+ ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH");
+ *aborted = true;
+ return;
+ }
+
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_KEX_ECDH_INIT) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+ "expecting ECDH initial packet, type %d (%s)",
+ pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ *aborted = true;
+ return;
+ }
+
+ {
+ ptrlen keydata = get_string(pktin);
+ put_stringpl(s->exhash, keydata);
+
+ bool ok = ecdh_key_getkey(s->ecdh_key, keydata,
+ BinarySink_UPCAST(s->kex_shared_secret));
+ if (!get_err(pktin) && !ok) {
+ ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve "
+ "point in ECDH initial packet");
+ *aborted = true;
+ return;
+ }
+ }
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_REPLY);
+ put_stringpl(pktout, s->hostkeydata);
+ {
+ strbuf *pubpoint = strbuf_new();
+ ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint));
+ put_string(s->exhash, pubpoint->u, pubpoint->len);
+ put_stringsb(pktout, pubpoint);
+ }
+ put_stringsb(pktout, finalise_and_sign_exhash(s));
+ pq_push(s->ppl.out_pq, pktout);
+
+ ecdh_key_free(s->ecdh_key);
+ s->ecdh_key = NULL;
+ } else if (s->kex_alg->main_type == KEXTYPE_GSS) {
+ ssh_sw_abort(s->ppl.ssh, "GSS key exchange not supported in server");
+ } else {
+ assert(s->kex_alg->main_type == KEXTYPE_RSA);
+ ppl_logevent("Doing RSA key exchange with hash %s",
+ ssh_hash_alg(s->exhash)->text_name);
+ s->ppl.bpp->pls->kctx = SSH2_PKTCTX_RSAKEX;
+
+ const struct ssh_rsa_kex_extra *extra =
+ (const struct ssh_rsa_kex_extra *)s->kex_alg->extra;
+
+ if (s->ssc && s->ssc->rsa_kex_key) {
+ int klen = ssh_rsakex_klen(s->ssc->rsa_kex_key);
+ if (klen >= extra->minklen) {
+ ppl_logevent("Using configured %d-bit RSA key", klen);
+ s->rsa_kex_key = s->ssc->rsa_kex_key;
+ } else {
+ ppl_logevent("Configured %d-bit RSA key is too short (min %d)",
+ klen, extra->minklen);
+ }
+ }
+
+ if (!s->rsa_kex_key) {
+ ppl_logevent("Generating a %d-bit RSA key", extra->minklen);
+
+ s->rsa_kex_key = snew(RSAKey);
+
+ PrimeGenerationContext *pgc = primegen_new_context(
+ &primegen_probabilistic);
+ ProgressReceiver null_progress;
+ null_progress.vt = &null_progress_vt;
+ rsa_generate(s->rsa_kex_key, extra->minklen, false,
+ pgc, &null_progress);
+ primegen_free_context(pgc);
+
+ s->rsa_kex_key->comment = NULL;
+ s->rsa_kex_key_needs_freeing = true;
+ }
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_PUBKEY);
+ put_stringpl(pktout, s->hostkeydata);
+ {
+ strbuf *pubblob = strbuf_new();
+ ssh_key_public_blob(&s->rsa_kex_key->sshk,
+ BinarySink_UPCAST(pubblob));
+ put_string(s->exhash, pubblob->u, pubblob->len);
+ put_stringsb(pktout, pubblob);
+ }
+ pq_push(s->ppl.out_pq, pktout);
+
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_KEXRSA_SECRET) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+ "expecting RSA kex secret, type %d (%s)",
+ pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ *aborted = true;
+ return;
+ }
+
+ mp_int *K;
+ {
+ ptrlen encrypted_secret = get_string(pktin);
+ put_stringpl(s->exhash, encrypted_secret);
+ K = ssh_rsakex_decrypt(
+ s->rsa_kex_key, s->kex_alg->hash, encrypted_secret);
+ }
+
+ if (!K) {
+ ssh_proto_error(s->ppl.ssh, "Unable to decrypt RSA kex secret");
+ *aborted = true;
+ return;
+ }
+
+ put_mp_ssh2(s->kex_shared_secret, K);
+ mp_free(K);
+
+ if (s->rsa_kex_key_needs_freeing) {
+ ssh_rsakex_freekey(s->rsa_kex_key);
+ sfree(s->rsa_kex_key);
+ }
+ s->rsa_kex_key = NULL;
+ s->rsa_kex_key_needs_freeing = false;
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_DONE);
+ put_stringsb(pktout, finalise_and_sign_exhash(s));
+ pq_push(s->ppl.out_pq, pktout);
+ }
+
+ crFinishV;
+}
diff --git a/ssh/login1-server.c b/ssh/login1-server.c
new file mode 100644
index 00000000..d01c7f4c
--- /dev/null
+++ b/ssh/login1-server.c
@@ -0,0 +1,443 @@
+/*
+ * Packet protocol layer for the SSH-1 login phase, from the server side.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "mpint.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "sshcr.h"
+#include "server.h"
+#include "sshkeygen.h"
+
+struct ssh1_login_server_state {
+ int crState;
+
+ PacketProtocolLayer *successor_layer;
+
+ const SshServerConfig *ssc;
+
+ int remote_protoflags;
+ int local_protoflags;
+ unsigned long supported_ciphers_mask, supported_auths_mask;
+ unsigned cipher_type;
+
+ unsigned char cookie[8];
+ unsigned char session_key[32];
+ unsigned char session_id[16];
+ char *username_str;
+ ptrlen username;
+
+ RSAKey *servkey, *hostkey;
+ bool servkey_generated_here;
+ mp_int *sesskey;
+
+ AuthPolicy *authpolicy;
+ unsigned ap_methods, current_method;
+ unsigned char auth_rsa_expected_response[16];
+ RSAKey *authkey;
+
+ PacketProtocolLayer ppl;
+};
+
+static void ssh1_login_server_free(PacketProtocolLayer *);
+static void ssh1_login_server_process_queue(PacketProtocolLayer *);
+
+static bool ssh1_login_server_get_specials(
+ PacketProtocolLayer *ppl, add_special_fn_t add_special,
+ void *ctx) { return false; }
+static void ssh1_login_server_special_cmd(PacketProtocolLayer *ppl,
+ SessionSpecialCode code, int arg) {}
+static void ssh1_login_server_reconfigure(
+ PacketProtocolLayer *ppl, Conf *conf) {}
+
+static const PacketProtocolLayerVtable ssh1_login_server_vtable = {
+ .free = ssh1_login_server_free,
+ .process_queue = ssh1_login_server_process_queue,
+ .get_specials = ssh1_login_server_get_specials,
+ .special_cmd = ssh1_login_server_special_cmd,
+ .reconfigure = ssh1_login_server_reconfigure,
+ .queued_data_size = ssh_ppl_default_queued_data_size,
+ .name = NULL, /* no layer names in SSH-1 */
+};
+
+PacketProtocolLayer *ssh1_login_server_new(
+ PacketProtocolLayer *successor_layer, RSAKey *hostkey,
+ AuthPolicy *authpolicy, const SshServerConfig *ssc)
+{
+ struct ssh1_login_server_state *s = snew(struct ssh1_login_server_state);
+ memset(s, 0, sizeof(*s));
+ s->ppl.vt = &ssh1_login_server_vtable;
+
+ s->ssc = ssc;
+ s->hostkey = hostkey;
+ s->authpolicy = authpolicy;
+
+ s->successor_layer = successor_layer;
+ return &s->ppl;
+}
+
+static void ssh1_login_server_free(PacketProtocolLayer *ppl)
+{
+ struct ssh1_login_server_state *s =
+ container_of(ppl, struct ssh1_login_server_state, ppl);
+
+ if (s->successor_layer)
+ ssh_ppl_free(s->successor_layer);
+
+ if (s->servkey_generated_here && s->servkey) {
+ freersakey(s->servkey);
+ sfree(s->servkey);
+ }
+
+ smemclr(s->session_key, sizeof(s->session_key));
+ sfree(s->username_str);
+
+ sfree(s);
+}
+
+static bool ssh1_login_server_filter_queue(struct ssh1_login_server_state *s)
+{
+ return ssh1_common_filter_queue(&s->ppl);
+}
+
+static PktIn *ssh1_login_server_pop(struct ssh1_login_server_state *s)
+{
+ if (ssh1_login_server_filter_queue(s))
+ return NULL;
+ return pq_pop(s->ppl.in_pq);
+}
+
+static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl)
+{
+ struct ssh1_login_server_state *s =
+ container_of(ppl, struct ssh1_login_server_state, ppl);
+ PktIn *pktin;
+ PktOut *pktout;
+ int i;
+
+ /* Filter centrally handled messages off the front of the queue on
+ * every entry to this coroutine, no matter where we're resuming
+ * from, even if we're _not_ looping on pq_pop. That way we can
+ * still proactively handle those messages even if we're waiting
+ * for a user response. */
+ if (ssh1_login_server_filter_queue(s))
+ return;
+
+ crBegin(s->crState);
+
+ if (!s->servkey) {
+ int server_key_bits = s->hostkey->bytes - 256;
+ if (server_key_bits < 512)
+ server_key_bits = s->hostkey->bytes + 256;
+ s->servkey = snew(RSAKey);
+
+ PrimeGenerationContext *pgc = primegen_new_context(
+ &primegen_probabilistic);
+ ProgressReceiver null_progress;
+ null_progress.vt = &null_progress_vt;
+ rsa_generate(s->servkey, server_key_bits, false, pgc, &null_progress);
+ primegen_free_context(pgc);
+
+ s->servkey->comment = NULL;
+ s->servkey_generated_here = true;
+ }
+
+ s->local_protoflags = SSH1_PROTOFLAGS_SUPPORTED;
+ s->supported_ciphers_mask = s->ssc->ssh1_cipher_mask;
+ s->supported_auths_mask = 0;
+ s->ap_methods = auth_methods(s->authpolicy);
+ if (s->ap_methods & AUTHMETHOD_PASSWORD)
+ s->supported_auths_mask |= (1U << SSH1_AUTH_PASSWORD);
+ if (s->ap_methods & AUTHMETHOD_PUBLICKEY)
+ s->supported_auths_mask |= (1U << SSH1_AUTH_RSA);
+ if (s->ap_methods & AUTHMETHOD_TIS)
+ s->supported_auths_mask |= (1U << SSH1_AUTH_TIS);
+ if (s->ap_methods & AUTHMETHOD_CRYPTOCARD)
+ s->supported_auths_mask |= (1U << SSH1_AUTH_CCARD);
+
+ random_read(s->cookie, 8);
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_PUBLIC_KEY);
+ put_data(pktout, s->cookie, 8);
+ rsa_ssh1_public_blob(BinarySink_UPCAST(pktout),
+ s->servkey, RSA_SSH1_EXPONENT_FIRST);
+ rsa_ssh1_public_blob(BinarySink_UPCAST(pktout),
+ s->hostkey, RSA_SSH1_EXPONENT_FIRST);
+ put_uint32(pktout, s->local_protoflags);
+ put_uint32(pktout, s->supported_ciphers_mask);
+ put_uint32(pktout, s->supported_auths_mask);
+ pq_push(s->ppl.out_pq, pktout);
+
+ crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
+ if (pktin->type != SSH1_CMSG_SESSION_KEY) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet in response"
+ " to initial public key packet, type %d (%s)",
+ pktin->type, ssh1_pkt_type(pktin->type));
+ return;
+ }
+
+ {
+ ptrlen client_cookie;
+ s->cipher_type = get_byte(pktin);
+ client_cookie = get_data(pktin, 8);
+ s->sesskey = get_mp_ssh1(pktin);
+ s->remote_protoflags = get_uint32(pktin);
+
+ if (get_err(pktin)) {
+ ssh_proto_error(s->ppl.ssh, "Unable to parse session key packet");
+ return;
+ }
+ if (!ptrlen_eq_ptrlen(client_cookie, make_ptrlen(s->cookie, 8))) {
+ ssh_proto_error(s->ppl.ssh,
+ "Client sent incorrect anti-spoofing cookie");
+ return;
+ }
+ }
+ if (s->cipher_type >= 32 ||
+ !((s->supported_ciphers_mask >> s->cipher_type) & 1)) {
+ ssh_proto_error(s->ppl.ssh, "Client selected an unsupported cipher");
+ return;
+ }
+
+ {
+ RSAKey *smaller, *larger;
+ strbuf *data = strbuf_new_nm();
+
+ if (mp_get_nbits(s->hostkey->modulus) >
+ mp_get_nbits(s->servkey->modulus)) {
+ larger = s->hostkey;
+ smaller = s->servkey;
+ } else {
+ smaller = s->hostkey;
+ larger = s->servkey;
+ }
+
+ if (rsa_ssh1_decrypt_pkcs1(s->sesskey, larger, data)) {
+ mp_free(s->sesskey);
+ s->sesskey = mp_from_bytes_be(ptrlen_from_strbuf(data));
+ strbuf_clear(data);
+ if (rsa_ssh1_decrypt_pkcs1(s->sesskey, smaller, data) &&
+ data->len == sizeof(s->session_key)) {
+ memcpy(s->session_key, data->u, sizeof(s->session_key));
+ mp_free(s->sesskey);
+ s->sesskey = NULL; /* indicates success */
+ }
+ }
+
+ strbuf_free(data);
+ }
+ if (s->sesskey) {
+ ssh_proto_error(s->ppl.ssh, "Failed to decrypt session key");
+ return;
+ }
+
+ ssh1_compute_session_id(s->session_id, s->cookie, s->hostkey, s->servkey);
+
+ for (i = 0; i < 16; i++)
+ s->session_key[i] ^= s->session_id[i];
+
+ {
+ const ssh_cipheralg *cipher =
+ (s->cipher_type == SSH1_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 :
+ s->cipher_type == SSH1_CIPHER_DES ? &ssh_des : &ssh_3des_ssh1);
+ ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key);
+ }
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS);
+ pq_push(s->ppl.out_pq, pktout);
+
+ crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
+ if (pktin->type != SSH1_CMSG_USER) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet while "
+ "expecting username, type %d (%s)",
+ pktin->type, ssh1_pkt_type(pktin->type));
+ return;
+ }
+ s->username = get_string(pktin);
+ s->username.ptr = s->username_str = mkstr(s->username);
+ ppl_logevent("Received username '%.*s'", PTRLEN_PRINTF(s->username));
+
+ if (auth_none(s->authpolicy, s->username))
+ goto auth_success;
+
+ while (1) {
+ /* Signal failed authentication */
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_FAILURE);
+ pq_push(s->ppl.out_pq, pktout);
+
+ crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
+ if (pktin->type == SSH1_CMSG_AUTH_PASSWORD) {
+ s->current_method = AUTHMETHOD_PASSWORD;
+ if (!(s->ap_methods & s->current_method))
+ continue;
+
+ ptrlen password = get_string(pktin);
+
+ /* Tolerate historic traffic-analysis defence of NUL +
+ * garbage on the end of the binary password string */
+ char *nul = memchr(password.ptr, '\0', password.len);
+ if (nul)
+ password.len = (const char *)nul - (const char *)password.ptr;
+
+ if (auth_password(s->authpolicy, s->username, password, NULL))
+ goto auth_success;
+ } else if (pktin->type == SSH1_CMSG_AUTH_RSA) {
+ s->current_method = AUTHMETHOD_PUBLICKEY;
+ if (!(s->ap_methods & s->current_method))
+ continue;
+
+ {
+ mp_int *modulus = get_mp_ssh1(pktin);
+ s->authkey = auth_publickey_ssh1(
+ s->authpolicy, s->username, modulus);
+
+ if (!s->authkey &&
+ s->ssc->stunt_pretend_to_accept_any_pubkey) {
+ mp_int *zero = mp_from_integer(0);
+ mp_int *fake_challenge = mp_random_in_range(zero, modulus);
+
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_SMSG_AUTH_RSA_CHALLENGE);
+ put_mp_ssh1(pktout, fake_challenge);
+ pq_push(s->ppl.out_pq, pktout);
+
+ mp_free(zero);
+ mp_free(fake_challenge);
+ }
+
+ mp_free(modulus);
+ }
+
+ if (!s->authkey &&
+ !s->ssc->stunt_pretend_to_accept_any_pubkey)
+ continue;
+
+ if (s->authkey && s->authkey->bytes < 32) {
+ ppl_logevent("Auth key far too small");
+ continue;
+ }
+
+ if (s->authkey) {
+ unsigned char *rsabuf =
+ snewn(s->authkey->bytes, unsigned char);
+
+ random_read(rsabuf, 32);
+
+ {
+ ssh_hash *h = ssh_hash_new(&ssh_md5);
+ put_data(h, rsabuf, 32);
+ put_data(h, s->session_id, 16);
+ ssh_hash_final(h, s->auth_rsa_expected_response);
+ }
+
+ if (!rsa_ssh1_encrypt(rsabuf, 32, s->authkey)) {
+ sfree(rsabuf);
+ ppl_logevent("Failed to encrypt auth challenge");
+ continue;
+ }
+
+ mp_int *bn = mp_from_bytes_be(
+ make_ptrlen(rsabuf, s->authkey->bytes));
+ smemclr(rsabuf, s->authkey->bytes);
+ sfree(rsabuf);
+
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_SMSG_AUTH_RSA_CHALLENGE);
+ put_mp_ssh1(pktout, bn);
+ pq_push(s->ppl.out_pq, pktout);
+
+ mp_free(bn);
+ }
+
+ crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
+ if (pktin->type != SSH1_CMSG_AUTH_RSA_RESPONSE) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet in "
+ "response to RSA auth challenge, type %d (%s)",
+ pktin->type, ssh1_pkt_type(pktin->type));
+ return;
+ }
+
+ if (!s->authkey)
+ continue;
+
+ {
+ ptrlen response = get_data(pktin, 16);
+ ptrlen expected = make_ptrlen(
+ s->auth_rsa_expected_response, 16);
+ if (!ptrlen_eq_ptrlen(response, expected)) {
+ ppl_logevent("Wrong response to auth challenge");
+ continue;
+ }
+ }
+
+ goto auth_success;
+ } else if (pktin->type == SSH1_CMSG_AUTH_TIS ||
+ pktin->type == SSH1_CMSG_AUTH_CCARD) {
+ char *challenge;
+ unsigned response_type;
+ ptrlen response;
+
+ s->current_method = (pktin->type == SSH1_CMSG_AUTH_TIS ?
+ AUTHMETHOD_TIS : AUTHMETHOD_CRYPTOCARD);
+ if (!(s->ap_methods & s->current_method))
+ continue;
+
+ challenge = auth_ssh1int_challenge(
+ s->authpolicy, s->current_method, s->username);
+ if (!challenge)
+ continue;
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp,
+ (s->current_method == AUTHMETHOD_TIS ?
+ SSH1_SMSG_AUTH_TIS_CHALLENGE :
+ SSH1_SMSG_AUTH_CCARD_CHALLENGE));
+ put_stringz(pktout, challenge);
+ pq_push(s->ppl.out_pq, pktout);
+ sfree(challenge);
+
+ crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL);
+ response_type = (s->current_method == AUTHMETHOD_TIS ?
+ SSH1_CMSG_AUTH_TIS_RESPONSE :
+ SSH1_CMSG_AUTH_CCARD_RESPONSE);
+ if (pktin->type != response_type) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet in "
+ "response to %s challenge, type %d (%s)",
+ (s->current_method == AUTHMETHOD_TIS ?
+ "TIS" : "CryptoCard"),
+ pktin->type, ssh1_pkt_type(pktin->type));
+ return;
+ }
+
+ response = get_string(pktin);
+
+ if (auth_ssh1int_response(s->authpolicy, response))
+ goto auth_success;
+ }
+ }
+
+ auth_success:
+ if (!auth_successful(s->authpolicy, s->username, s->current_method)) {
+ ssh_sw_abort(s->ppl.ssh, "Multiple authentications required but SSH-1"
+ " cannot perform them");
+ return;
+ }
+
+ /* Signal successful authentication */
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS);
+ pq_push(s->ppl.out_pq, pktout);
+
+ ssh1_connection_set_protoflags(
+ s->successor_layer, s->local_protoflags, s->remote_protoflags);
+ {
+ PacketProtocolLayer *successor = s->successor_layer;
+ s->successor_layer = NULL; /* avoid freeing it ourself */
+ ssh_ppl_replace(&s->ppl, successor);
+ return; /* we've just freed s, so avoid even touching s->crState */
+ }
+
+ crFinishV;
+}
diff --git a/ssh/login1.c b/ssh/login1.c
new file mode 100644
index 00000000..52aaea0b
--- /dev/null
+++ b/ssh/login1.c
@@ -0,0 +1,1193 @@
+/*
+ * Packet protocol layer for the SSH-1 login phase (combining what
+ * SSH-2 would think of as key exchange and user authentication).
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "mpint.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "sshcr.h"
+
+typedef struct agent_key {
+ RSAKey key;
+ strbuf *comment;
+ ptrlen blob; /* only used during initial parsing of agent response */
+} agent_key;
+
+struct ssh1_login_state {
+ int crState;
+
+ PacketProtocolLayer *successor_layer;
+
+ Conf *conf;
+
+ char *savedhost;
+ int savedport;
+ bool try_agent_auth, is_trivial_auth;
+
+ int remote_protoflags;
+ int local_protoflags;
+ unsigned char session_key[32];
+ char *username;
+ agent_pending_query *auth_agent_query;
+
+ int len;
+ unsigned char *rsabuf;
+ unsigned long supported_ciphers_mask, supported_auths_mask;
+ bool tried_publickey, tried_agent;
+ bool tis_auth_refused, ccard_auth_refused;
+ unsigned char cookie[8];
+ unsigned char session_id[16];
+ int cipher_type;
+ strbuf *publickey_blob;
+ char *publickey_comment;
+ bool privatekey_available, privatekey_encrypted;
+ prompts_t *cur_prompt;
+ SeatPromptResult spr;
+ char c;
+ int pwpkt_type;
+ void *agent_response_to_free;
+ ptrlen agent_response;
+ BinarySource asrc[1]; /* response from SSH agent */
+ size_t agent_keys_len;
+ agent_key *agent_keys;
+ size_t agent_key_index, agent_key_limit;
+ bool authed;
+ RSAKey key;
+ Filename *keyfile;
+ RSAKey servkey, hostkey;
+
+ StripCtrlChars *tis_scc;
+ bool tis_scc_initialised;
+
+ PacketProtocolLayer ppl;
+};
+
+static void ssh1_login_free(PacketProtocolLayer *);
+static void ssh1_login_process_queue(PacketProtocolLayer *);
+static void ssh1_login_dialog_callback(void *, SeatPromptResult);
+static void ssh1_login_special_cmd(PacketProtocolLayer *ppl,
+ SessionSpecialCode code, int arg);
+static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
+
+static const PacketProtocolLayerVtable ssh1_login_vtable = {
+ .free = ssh1_login_free,
+ .process_queue = ssh1_login_process_queue,
+ .get_specials = ssh1_common_get_specials,
+ .special_cmd = ssh1_login_special_cmd,
+ .reconfigure = ssh1_login_reconfigure,
+ .queued_data_size = ssh_ppl_default_queued_data_size,
+ .name = NULL, /* no layer names in SSH-1 */
+};
+
+static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req);
+static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen);
+
+PacketProtocolLayer *ssh1_login_new(
+ Conf *conf, const char *host, int port,
+ PacketProtocolLayer *successor_layer)
+{
+ struct ssh1_login_state *s = snew(struct ssh1_login_state);
+ memset(s, 0, sizeof(*s));
+ s->ppl.vt = &ssh1_login_vtable;
+
+ s->conf = conf_copy(conf);
+ s->savedhost = dupstr(host);
+ s->savedport = port;
+ s->successor_layer = successor_layer;
+ s->is_trivial_auth = true;
+
+ return &s->ppl;
+}
+
+static void ssh1_login_free(PacketProtocolLayer *ppl)
+{
+ struct ssh1_login_state *s =
+ container_of(ppl, struct ssh1_login_state, ppl);
+
+ if (s->successor_layer)
+ ssh_ppl_free(s->successor_layer);
+
+ conf_free(s->conf);
+ sfree(s->savedhost);
+ sfree(s->rsabuf);
+ sfree(s->username);
+ if (s->publickey_blob)
+ strbuf_free(s->publickey_blob);
+ sfree(s->publickey_comment);
+ if (s->cur_prompt)
+ free_prompts(s->cur_prompt);
+ if (s->agent_keys) {
+ for (size_t i = 0; i < s->agent_keys_len; i++) {
+ freersakey(&s->agent_keys[i].key);
+ strbuf_free(s->agent_keys[i].comment);
+ }
+ sfree(s->agent_keys);
+ }
+ sfree(s->agent_response_to_free);
+ if (s->auth_agent_query)
+ agent_cancel_query(s->auth_agent_query);
+ sfree(s);
+}
+
+static bool ssh1_login_filter_queue(struct ssh1_login_state *s)
+{
+ return ssh1_common_filter_queue(&s->ppl);
+}
+
+static PktIn *ssh1_login_pop(struct ssh1_login_state *s)
+{
+ if (ssh1_login_filter_queue(s))
+ return NULL;
+ return pq_pop(s->ppl.in_pq);
+}
+
+static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s);
+
+static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
+{
+ struct ssh1_login_state *s =
+ container_of(ppl, struct ssh1_login_state, ppl);
+ PktIn *pktin;
+ PktOut *pkt;
+ int i;
+
+ /* Filter centrally handled messages off the front of the queue on
+ * every entry to this coroutine, no matter where we're resuming
+ * from, even if we're _not_ looping on pq_pop. That way we can
+ * still proactively handle those messages even if we're waiting
+ * for a user response. */
+ if (ssh1_login_filter_queue(s))
+ return;
+
+ crBegin(s->crState);
+
+ crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
+
+ if (pktin->type != SSH1_SMSG_PUBLIC_KEY) {
+ ssh_proto_error(s->ppl.ssh, "Public key packet not received");
+ return;
+ }
+
+ ppl_logevent("Received public keys");
+
+ {
+ ptrlen pl = get_data(pktin, 8);
+ memcpy(s->cookie, pl.ptr, pl.len);
+ }
+
+ get_rsa_ssh1_pub(pktin, &s->servkey, RSA_SSH1_EXPONENT_FIRST);
+ get_rsa_ssh1_pub(pktin, &s->hostkey, RSA_SSH1_EXPONENT_FIRST);
+
+ s->hostkey.comment = NULL; /* avoid confusing rsa_ssh1_fingerprint */
+
+ /*
+ * Log the host key fingerprint.
+ */
+ if (!get_err(pktin)) {
+ char *fingerprint = rsa_ssh1_fingerprint(&s->hostkey);
+ ppl_logevent("Host key fingerprint is:");
+ ppl_logevent(" %s", fingerprint);
+ sfree(fingerprint);
+ }
+
+ s->remote_protoflags = get_uint32(pktin);
+ s->supported_ciphers_mask = get_uint32(pktin);
+ s->supported_auths_mask = get_uint32(pktin);
+
+ if (get_err(pktin)) {
+ ssh_proto_error(s->ppl.ssh, "Bad SSH-1 public key packet");
+ return;
+ }
+
+ if ((s->ppl.remote_bugs & BUG_CHOKES_ON_RSA))
+ s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA);
+
+ s->local_protoflags =
+ s->remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED;
+ s->local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER;
+
+ ssh1_compute_session_id(s->session_id, s->cookie,
+ &s->hostkey, &s->servkey);
+
+ random_read(s->session_key, 32);
+
+ /*
+ * Verify that the `bits' and `bytes' parameters match.
+ */
+ if (s->hostkey.bits > s->hostkey.bytes * 8 ||
+ s->servkey.bits > s->servkey.bytes * 8) {
+ ssh_proto_error(s->ppl.ssh, "SSH-1 public keys were badly formatted");
+ return;
+ }
+
+ s->len = 32;
+ if (s->len < s->hostkey.bytes)
+ s->len = s->hostkey.bytes;
+ if (s->len < s->servkey.bytes)
+ s->len = s->servkey.bytes;
+
+ s->rsabuf = snewn(s->len, unsigned char);
+
+ /*
+ * Verify the host key.
+ */
+ {
+ char *keystr = rsastr_fmt(&s->hostkey);
+ char *keydisp = ssh1_pubkey_str(&s->hostkey);
+ char **fingerprints = rsa_ssh1_fake_all_fingerprints(&s->hostkey);
+
+ s->spr = verify_ssh_host_key(
+ ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport, NULL,
+ "rsa", keystr, keydisp, fingerprints, 0,
+ ssh1_login_dialog_callback, s);
+
+ ssh2_free_all_fingerprints(fingerprints);
+ sfree(keydisp);
+ sfree(keystr);
+ }
+
+#ifdef FUZZING
+ s->spr = SPR_OK;
+#endif
+ crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+
+ if (spr_is_abort(s->spr)) {
+ ssh_spr_close(s->ppl.ssh, s->spr, "host key verification");
+ return;
+ }
+
+ for (i = 0; i < 32; i++) {
+ s->rsabuf[i] = s->session_key[i];
+ if (i < 16)
+ s->rsabuf[i] ^= s->session_id[i];
+ }
+
+ {
+ RSAKey *smaller = (s->hostkey.bytes > s->servkey.bytes ?
+ &s->servkey : &s->hostkey);
+ RSAKey *larger = (s->hostkey.bytes > s->servkey.bytes ?
+ &s->hostkey : &s->servkey);
+
+ if (!rsa_ssh1_encrypt(s->rsabuf, 32, smaller) ||
+ !rsa_ssh1_encrypt(s->rsabuf, smaller->bytes, larger)) {
+ ssh_proto_error(s->ppl.ssh, "SSH-1 public key encryptions failed "
+ "due to bad formatting");
+ return;
+ }
+ }
+
+ ppl_logevent("Encrypted session key");
+
+ {
+ bool cipher_chosen = false, warn = false;
+ const char *cipher_string = NULL;
+ int i;
+ for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) {
+ int next_cipher = conf_get_int_int(
+ s->conf, CONF_ssh_cipherlist, i);
+ if (next_cipher == CIPHER_WARN) {
+ /* If/when we choose a cipher, warn about it */
+ warn = true;
+ } else if (next_cipher == CIPHER_AES) {
+ /* XXX Probably don't need to mention this. */
+ ppl_logevent("AES not supported in SSH-1, skipping");
+ } else {
+ switch (next_cipher) {
+ case CIPHER_3DES: s->cipher_type = SSH1_CIPHER_3DES;
+ cipher_string = "3DES"; break;
+ case CIPHER_BLOWFISH: s->cipher_type = SSH1_CIPHER_BLOWFISH;
+ cipher_string = "Blowfish"; break;
+ case CIPHER_DES: s->cipher_type = SSH1_CIPHER_DES;
+ cipher_string = "single-DES"; break;
+ }
+ if (s->supported_ciphers_mask & (1 << s->cipher_type))
+ cipher_chosen = true;
+ }
+ }
+ if (!cipher_chosen) {
+ if ((s->supported_ciphers_mask & (1 << SSH1_CIPHER_3DES)) == 0) {
+ ssh_proto_error(s->ppl.ssh, "Server violates SSH-1 protocol "
+ "by not supporting 3DES encryption");
+ } else {
+ /* shouldn't happen */
+ ssh_sw_abort(s->ppl.ssh, "No supported ciphers found");
+ }
+ return;
+ }
+
+ /* Warn about chosen cipher if necessary. */
+ if (warn) {
+ s->spr = seat_confirm_weak_crypto_primitive(
+ ppl_get_iseat(&s->ppl), "cipher", cipher_string,
+ ssh1_login_dialog_callback, s);
+ crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+ if (spr_is_abort(s->spr)) {
+ ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning");
+ return;
+ }
+ }
+ }
+
+ switch (s->cipher_type) {
+ case SSH1_CIPHER_3DES:
+ ppl_logevent("Using 3DES encryption");
+ break;
+ case SSH1_CIPHER_DES:
+ ppl_logevent("Using single-DES encryption");
+ break;
+ case SSH1_CIPHER_BLOWFISH:
+ ppl_logevent("Using Blowfish encryption");
+ break;
+ }
+
+ pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_SESSION_KEY);
+ put_byte(pkt, s->cipher_type);
+ put_data(pkt, s->cookie, 8);
+ put_uint16(pkt, s->len * 8);
+ put_data(pkt, s->rsabuf, s->len);
+ put_uint32(pkt, s->local_protoflags);
+ pq_push(s->ppl.out_pq, pkt);
+
+ ppl_logevent("Trying to enable encryption...");
+
+ sfree(s->rsabuf);
+ s->rsabuf = NULL;
+
+ /*
+ * Force the BPP to synchronously marshal all packets up to and
+ * including the SESSION_KEY into wire format, before we turn on
+ * crypto.
+ */
+ ssh_bpp_handle_output(s->ppl.bpp);
+
+ {
+ const ssh_cipheralg *cipher =
+ (s->cipher_type == SSH1_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 :
+ s->cipher_type == SSH1_CIPHER_DES ? &ssh_des : &ssh_3des_ssh1);
+ ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key);
+ }
+
+ freersakey(&s->servkey);
+ freersakey(&s->hostkey);
+ crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
+
+ if (pktin->type != SSH1_SMSG_SUCCESS) {
+ ssh_proto_error(s->ppl.ssh, "Encryption not successfully enabled");
+ return;
+ }
+
+ ppl_logevent("Successfully started encryption");
+
+ if ((s->username = get_remote_username(s->conf)) == NULL) {
+ s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+ s->cur_prompt->to_server = true;
+ s->cur_prompt->from_server = false;
+ s->cur_prompt->name = dupstr("SSH login name");
+ add_prompt(s->cur_prompt, dupstr("login as: "), true);
+ s->spr = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->cur_prompt);
+ while (s->spr.kind == SPRK_INCOMPLETE) {
+ crReturnV;
+ s->spr = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->cur_prompt);
+ }
+ if (spr_is_abort(s->spr)) {
+ /*
+ * Failed to get a username. Terminate.
+ */
+ ssh_spr_close(s->ppl.ssh, s->spr, "username prompt");
+ return;
+ }
+ s->username = prompt_get_result(s->cur_prompt->prompts[0]);
+ free_prompts(s->cur_prompt);
+ s->cur_prompt = NULL;
+ }
+
+ pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_USER);
+ put_stringz(pkt, s->username);
+ pq_push(s->ppl.out_pq, pkt);
+
+ ppl_logevent("Sent username \"%s\"", s->username);
+ if (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))
+ ppl_printf("Sent username \"%s\"\r\n", s->username);
+
+ crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
+
+ if (!(s->supported_auths_mask & (1 << SSH1_AUTH_RSA))) {
+ /* We must not attempt PK auth. Pretend we've already tried it. */
+ s->tried_publickey = s->tried_agent = true;
+ } else {
+ s->tried_publickey = s->tried_agent = false;
+ }
+ s->tis_auth_refused = s->ccard_auth_refused = false;
+
+ /*
+ * Load the public half of any configured keyfile for later use.
+ */
+ s->keyfile = conf_get_filename(s->conf, CONF_keyfile);
+ if (!filename_is_null(s->keyfile)) {
+ int keytype;
+ ppl_logevent("Reading key file \"%s\"", filename_to_str(s->keyfile));
+ keytype = key_type(s->keyfile);
+ if (keytype == SSH_KEYTYPE_SSH1 ||
+ keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
+ const char *error;
+ s->publickey_blob = strbuf_new();
+ if (rsa1_loadpub_f(s->keyfile,
+ BinarySink_UPCAST(s->publickey_blob),
+ &s->publickey_comment, &error)) {
+ s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1);
+ if (!s->privatekey_available)
+ ppl_logevent("Key file contains public key only");
+ s->privatekey_encrypted = rsa1_encrypted_f(s->keyfile, NULL);
+ } else {
+ ppl_logevent("Unable to load key (%s)", error);
+ ppl_printf("Unable to load key file \"%s\" (%s)\r\n",
+ filename_to_str(s->keyfile), error);
+
+ strbuf_free(s->publickey_blob);
+ s->publickey_blob = NULL;
+ }
+ } else {
+ ppl_logevent("Unable to use this key file (%s)",
+ key_type_to_str(keytype));
+ ppl_printf("Unable to use key file \"%s\" (%s)\r\n",
+ filename_to_str(s->keyfile),
+ key_type_to_str(keytype));
+ }
+ }
+
+ /* Check whether we're configured to try Pageant, and also whether
+ * it's available. */
+ s->try_agent_auth = (conf_get_bool(s->conf, CONF_tryagent) &&
+ agent_exists());
+
+ while (pktin->type == SSH1_SMSG_FAILURE) {
+ s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD;
+
+ if (s->try_agent_auth && !s->tried_agent) {
+ /*
+ * Attempt RSA authentication using Pageant.
+ */
+ s->authed = false;
+ s->tried_agent = true;
+ ppl_logevent("Pageant is running. Requesting keys.");
+
+ /* Request the keys held by the agent. */
+ {
+ strbuf *request = strbuf_new_for_agent_query();
+ put_byte(request, SSH1_AGENTC_REQUEST_RSA_IDENTITIES);
+ ssh1_login_agent_query(s, request);
+ strbuf_free(request);
+ crMaybeWaitUntilV(!s->auth_agent_query);
+ }
+ BinarySource_BARE_INIT_PL(s->asrc, s->agent_response);
+
+ get_uint32(s->asrc); /* skip length field */
+ if (get_byte(s->asrc) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
+ size_t nkeys = get_uint32(s->asrc);
+ size_t origpos = s->asrc->pos;
+
+ /*
+ * Check that the agent response is well formed.
+ */
+ for (size_t i = 0; i < nkeys; i++) {
+ get_rsa_ssh1_pub(s->asrc, NULL, RSA_SSH1_EXPONENT_FIRST);
+ get_string(s->asrc); /* comment */
+ if (get_err(s->asrc)) {
+ ppl_logevent("Pageant's response was truncated");
+ goto parsed_agent_query;
+ }
+ }
+
+ /*
+ * Copy the list of public-key blobs out of the Pageant
+ * response.
+ */
+ BinarySource_REWIND_TO(s->asrc, origpos);
+ s->agent_keys_len = nkeys;
+ s->agent_keys = snewn(s->agent_keys_len, agent_key);
+ for (size_t i = 0; i < nkeys; i++) {
+ memset(&s->agent_keys[i].key, 0,
+ sizeof(s->agent_keys[i].key));
+
+ const char *blobstart = get_ptr(s->asrc);
+ get_rsa_ssh1_pub(s->asrc, &s->agent_keys[i].key,
+ RSA_SSH1_EXPONENT_FIRST);
+ const char *blobend = get_ptr(s->asrc);
+
+ s->agent_keys[i].comment = strbuf_dup(get_string(s->asrc));
+
+ s->agent_keys[i].blob = make_ptrlen(
+ blobstart, blobend - blobstart);
+ }
+
+ ppl_logevent("Pageant has %"SIZEu" SSH-1 keys", nkeys);
+
+ if (s->publickey_blob) {
+ /*
+ * If we've been given a specific public key blob,
+ * filter the list of keys to try from the agent
+ * down to only that one, or none if it's not
+ * there.
+ */
+ ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob);
+ size_t i;
+
+ for (i = 0; i < nkeys; i++) {
+ if (ptrlen_eq_ptrlen(our_blob, s->agent_keys[i].blob))
+ break;
+ }
+
+ if (i < nkeys) {
+ ppl_logevent("Pageant key #%"SIZEu" matches "
+ "configured key file", i);
+ s->agent_key_index = i;
+ s->agent_key_limit = i+1;
+ } else {
+ ppl_logevent("Configured key file not in Pageant");
+ s->agent_key_index = 0;
+ s->agent_key_limit = 0;
+ }
+ } else {
+ /*
+ * Otherwise, try them all.
+ */
+ s->agent_key_index = 0;
+ s->agent_key_limit = nkeys;
+ }
+ } else {
+ ppl_logevent("Failed to get reply from Pageant");
+ }
+ parsed_agent_query:;
+
+ for (; s->agent_key_index < s->agent_key_limit;
+ s->agent_key_index++) {
+ ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index);
+ pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA);
+ put_mp_ssh1(pkt,
+ s->agent_keys[s->agent_key_index].key.modulus);
+ pq_push(s->ppl.out_pq, pkt);
+ crMaybeWaitUntilV((pktin = ssh1_login_pop(s))
+ != NULL);
+ if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
+ ppl_logevent("Key refused");
+ continue;
+ }
+ ppl_logevent("Received RSA challenge");
+
+ {
+ mp_int *challenge = get_mp_ssh1(pktin);
+ if (get_err(pktin)) {
+ mp_free(challenge);
+ ssh_proto_error(s->ppl.ssh, "Server's RSA challenge "
+ "was badly formatted");
+ return;
+ }
+
+ strbuf *agentreq = strbuf_new_for_agent_query();
+ put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE);
+
+ rsa_ssh1_public_blob(
+ BinarySink_UPCAST(agentreq),
+ &s->agent_keys[s->agent_key_index].key,
+ RSA_SSH1_EXPONENT_FIRST);
+
+ put_mp_ssh1(agentreq, challenge);
+ mp_free(challenge);
+
+ put_data(agentreq, s->session_id, 16);
+ put_uint32(agentreq, 1); /* response format */
+ ssh1_login_agent_query(s, agentreq);
+ strbuf_free(agentreq);
+ crMaybeWaitUntilV(!s->auth_agent_query);
+ }
+
+ {
+ const unsigned char *ret = s->agent_response.ptr;
+ if (ret) {
+ if (s->agent_response.len >= 5+16 &&
+ ret[4] == SSH1_AGENT_RSA_RESPONSE) {
+ ppl_logevent("Sending Pageant's response");
+ pkt = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE);
+ put_data(pkt, ret + 5, 16);
+ pq_push(s->ppl.out_pq, pkt);
+ s->is_trivial_auth = false;
+ crMaybeWaitUntilV(
+ (pktin = ssh1_login_pop(s))
+ != NULL);
+ if (pktin->type == SSH1_SMSG_SUCCESS) {
+ ppl_logevent("Pageant's response "
+ "accepted");
+ if (seat_verbose(s->ppl.seat)) {
+ ptrlen comment = ptrlen_from_strbuf(
+ s->agent_keys[s->agent_key_index].
+ comment);
+ ppl_printf("Authenticated using RSA "
+ "key \"%.*s\" from "
+ "agent\r\n",
+ PTRLEN_PRINTF(comment));
+ }
+ s->authed = true;
+ } else
+ ppl_logevent("Pageant's response not "
+ "accepted");
+ } else {
+ ppl_logevent("Pageant failed to answer "
+ "challenge");
+ sfree((char *)ret);
+ }
+ } else {
+ ppl_logevent("No reply received from Pageant");
+ }
+ }
+ if (s->authed)
+ break;
+ }
+ if (s->authed)
+ break;
+ }
+ if (s->publickey_blob && s->privatekey_available &&
+ !s->tried_publickey) {
+ /*
+ * Try public key authentication with the specified
+ * key file.
+ */
+ bool got_passphrase; /* need not be kept over crReturn */
+ if (seat_verbose(s->ppl.seat))
+ ppl_printf("Trying public key authentication.\r\n");
+ ppl_logevent("Trying public key \"%s\"",
+ filename_to_str(s->keyfile));
+ s->tried_publickey = true;
+ got_passphrase = false;
+ while (!got_passphrase) {
+ /*
+ * Get a passphrase, if necessary.
+ */
+ int retd;
+ char *passphrase = NULL; /* only written after crReturn */
+ const char *error;
+ if (!s->privatekey_encrypted) {
+ if (seat_verbose(s->ppl.seat))
+ ppl_printf("No passphrase required.\r\n");
+ passphrase = NULL;
+ } else {
+ s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+ s->cur_prompt->to_server = false;
+ s->cur_prompt->from_server = false;
+ s->cur_prompt->name = dupstr("SSH key passphrase");
+ add_prompt(s->cur_prompt,
+ dupprintf("Passphrase for key \"%s\": ",
+ s->publickey_comment), false);
+ s->spr = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->cur_prompt);
+ while (s->spr.kind == SPRK_INCOMPLETE) {
+ crReturnV;
+ s->spr = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->cur_prompt);
+ }
+ if (spr_is_abort(s->spr)) {
+ /* Failed to get a passphrase. Terminate. */
+ ssh_spr_close(s->ppl.ssh, s->spr, "passphrase prompt");
+ return;
+ }
+ passphrase = prompt_get_result(s->cur_prompt->prompts[0]);
+ free_prompts(s->cur_prompt);
+ s->cur_prompt = NULL;
+ }
+ /*
+ * Try decrypting key with passphrase.
+ */
+ retd = rsa1_load_f(s->keyfile, &s->key, passphrase, &error);
+ if (passphrase) {
+ smemclr(passphrase, strlen(passphrase));
+ sfree(passphrase);
+ }
+ if (retd == 1) {
+ /* Correct passphrase. */
+ got_passphrase = true;
+ } else if (retd == 0) {
+ ppl_printf("Couldn't load private key from %s (%s).\r\n",
+ filename_to_str(s->keyfile), error);
+ got_passphrase = false;
+ break; /* go and try something else */
+ } else if (retd == -1) {
+ ppl_printf("Wrong passphrase.\r\n");
+ got_passphrase = false;
+ /* and try again */
+ } else {
+ unreachable("unexpected return from rsa1_load_f()");
+ }
+ }
+
+ if (got_passphrase) {
+
+ /*
+ * Send a public key attempt.
+ */
+ pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA);
+ put_mp_ssh1(pkt, s->key.modulus);
+ pq_push(s->ppl.out_pq, pkt);
+
+ crMaybeWaitUntilV((pktin = ssh1_login_pop(s))
+ != NULL);
+ if (pktin->type == SSH1_SMSG_FAILURE) {
+ ppl_printf("Server refused our public key.\r\n");
+ continue; /* go and try something else */
+ }
+ if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
+ " in response to offer of public key, "
+ "type %d (%s)", pktin->type,
+ ssh1_pkt_type(pktin->type));
+ return;
+ }
+
+ {
+ int i;
+ unsigned char buffer[32];
+ mp_int *challenge, *response;
+
+ challenge = get_mp_ssh1(pktin);
+ if (get_err(pktin)) {
+ mp_free(challenge);
+ ssh_proto_error(s->ppl.ssh, "Server's RSA challenge "
+ "was badly formatted");
+ return;
+ }
+ response = rsa_ssh1_decrypt(challenge, &s->key);
+ freersapriv(&s->key); /* burn the evidence */
+
+ for (i = 0; i < 32; i++) {
+ buffer[i] = mp_get_byte(response, 31 - i);
+ }
+
+ {
+ ssh_hash *h = ssh_hash_new(&ssh_md5);
+ put_data(h, buffer, 32);
+ put_data(h, s->session_id, 16);
+ ssh_hash_final(h, buffer);
+ }
+
+ pkt = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE);
+ put_data(pkt, buffer, 16);
+ pq_push(s->ppl.out_pq, pkt);
+ s->is_trivial_auth = false;
+
+ mp_free(challenge);
+ mp_free(response);
+ }
+
+ crMaybeWaitUntilV((pktin = ssh1_login_pop(s))
+ != NULL);
+ if (pktin->type == SSH1_SMSG_FAILURE) {
+ if (seat_verbose(s->ppl.seat))
+ ppl_printf("Failed to authenticate with"
+ " our public key.\r\n");
+ continue; /* go and try something else */
+ } else if (pktin->type != SSH1_SMSG_SUCCESS) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
+ " in response to RSA authentication, "
+ "type %d (%s)", pktin->type,
+ ssh1_pkt_type(pktin->type));
+ return;
+ }
+
+ break; /* we're through! */
+ }
+
+ }
+
+ /*
+ * Otherwise, try various forms of password-like authentication.
+ */
+ s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+
+ if (conf_get_bool(s->conf, CONF_try_tis_auth) &&
+ (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) &&
+ !s->tis_auth_refused) {
+ ssh1_login_setup_tis_scc(s);
+ s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE;
+ ppl_logevent("Requested TIS authentication");
+ pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_TIS);
+ pq_push(s->ppl.out_pq, pkt);
+ crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
+ if (pktin->type == SSH1_SMSG_FAILURE) {
+ ppl_logevent("TIS authentication declined");
+ if (seat_interactive(s->ppl.seat))
+ ppl_printf("TIS authentication refused.\r\n");
+ s->tis_auth_refused = true;
+ continue;
+ } else if (pktin->type == SSH1_SMSG_AUTH_TIS_CHALLENGE) {
+ ptrlen challenge = get_string(pktin);
+ if (get_err(pktin)) {
+ ssh_proto_error(s->ppl.ssh, "TIS challenge packet was "
+ "badly formed");
+ return;
+ }
+ ppl_logevent("Received TIS challenge");
+ s->cur_prompt->to_server = true;
+ s->cur_prompt->from_server = true;
+ s->cur_prompt->name = dupstr("SSH TIS authentication");
+
+ strbuf *sb = strbuf_new();
+ put_datapl(sb, PTRLEN_LITERAL("\
+-- TIS authentication challenge from server: ---------------------------------\
+\r\n"));
+ if (s->tis_scc) {
+ stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb));
+ put_datapl(s->tis_scc, challenge);
+ stripctrl_retarget(s->tis_scc, NULL);
+ } else {
+ put_datapl(sb, challenge);
+ }
+ if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL))
+ put_datapl(sb, PTRLEN_LITERAL("\r\n"));
+ put_datapl(sb, PTRLEN_LITERAL("\
+-- End of TIS authentication challenge from server: --------------------------\
+\r\n"));
+
+ s->cur_prompt->instruction = strbuf_to_str(sb);
+ s->cur_prompt->instr_reqd = true;
+ add_prompt(s->cur_prompt, dupstr(
+ "TIS authentication response: "), false);
+ } else {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
+ " in response to TIS authentication, "
+ "type %d (%s)", pktin->type,
+ ssh1_pkt_type(pktin->type));
+ return;
+ }
+ } else if (conf_get_bool(s->conf, CONF_try_tis_auth) &&
+ (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) &&
+ !s->ccard_auth_refused) {
+ ssh1_login_setup_tis_scc(s);
+ s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE;
+ ppl_logevent("Requested CryptoCard authentication");
+ pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_CCARD);
+ pq_push(s->ppl.out_pq, pkt);
+ crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
+ if (pktin->type == SSH1_SMSG_FAILURE) {
+ ppl_logevent("CryptoCard authentication declined");
+ ppl_printf("CryptoCard authentication refused.\r\n");
+ s->ccard_auth_refused = true;
+ continue;
+ } else if (pktin->type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
+ ptrlen challenge = get_string(pktin);
+ if (get_err(pktin)) {
+ ssh_proto_error(s->ppl.ssh, "CryptoCard challenge packet "
+ "was badly formed");
+ return;
+ }
+ ppl_logevent("Received CryptoCard challenge");
+ s->cur_prompt->to_server = true;
+ s->cur_prompt->from_server = true;
+ s->cur_prompt->name = dupstr("SSH CryptoCard authentication");
+
+ strbuf *sb = strbuf_new();
+ put_datapl(sb, PTRLEN_LITERAL("\
+-- CryptoCard authentication challenge from server: --------------------------\
+\r\n"));
+ if (s->tis_scc) {
+ stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb));
+ put_datapl(s->tis_scc, challenge);
+ stripctrl_retarget(s->tis_scc, NULL);
+ } else {
+ put_datapl(sb, challenge);
+ }
+ if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL))
+ put_datapl(sb, PTRLEN_LITERAL("\r\n"));
+ put_datapl(sb, PTRLEN_LITERAL("\
+-- End of CryptoCard authentication challenge from server: -------------------\
+\r\n"));
+
+ s->cur_prompt->instruction = strbuf_to_str(sb);
+ s->cur_prompt->instr_reqd = true;
+ add_prompt(s->cur_prompt, dupstr(
+ "CryptoCard authentication response: "), false);
+ } else {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
+ " in response to TIS authentication, "
+ "type %d (%s)", pktin->type,
+ ssh1_pkt_type(pktin->type));
+ return;
+ }
+ }
+ if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
+ if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) {
+ ssh_sw_abort(s->ppl.ssh, "No supported authentication methods "
+ "available");
+ return;
+ }
+ s->cur_prompt->to_server = true;
+ s->cur_prompt->from_server = false;
+ s->cur_prompt->name = dupstr("SSH password");
+ add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
+ s->username, s->savedhost),
+ false);
+ }
+
+ /*
+ * Show password prompt, having first obtained it via a TIS
+ * or CryptoCard exchange if we're doing TIS or CryptoCard
+ * authentication.
+ */
+ s->spr = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->cur_prompt);
+ while (s->spr.kind == SPRK_INCOMPLETE) {
+ crReturnV;
+ s->spr = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->cur_prompt);
+ }
+ if (spr_is_abort(s->spr)) {
+ /*
+ * Failed to get a password (for example
+ * because one was supplied on the command line
+ * which has already failed to work). Terminate.
+ */
+ ssh_spr_close(s->ppl.ssh, s->spr, "password prompt");
+ return;
+ }
+
+ if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
+ /*
+ * Defence against traffic analysis: we send a
+ * whole bunch of packets containing strings of
+ * different lengths. One of these strings is the
+ * password, in a SSH1_CMSG_AUTH_PASSWORD packet.
+ * The others are all random data in
+ * SSH1_MSG_IGNORE packets. This way a passive
+ * listener can't tell which is the password, and
+ * hence can't deduce the password length.
+ *
+ * Anybody with a password length greater than 16
+ * bytes is going to have enough entropy in their
+ * password that a listener won't find it _that_
+ * much help to know how long it is. So what we'll
+ * do is:
+ *
+ * - if password length < 16, we send 15 packets
+ * containing string lengths 1 through 15
+ *
+ * - otherwise, we let N be the nearest multiple
+ * of 8 below the password length, and send 8
+ * packets containing string lengths N through
+ * N+7. This won't obscure the order of
+ * magnitude of the password length, but it will
+ * introduce a bit of extra uncertainty.
+ *
+ * A few servers can't deal with SSH1_MSG_IGNORE, at
+ * least in this context. For these servers, we need
+ * an alternative defence. We make use of the fact
+ * that the password is interpreted as a C string:
+ * so we can append a NUL, then some random data.
+ *
+ * A few servers can deal with neither SSH1_MSG_IGNORE
+ * here _nor_ a padded password string.
+ * For these servers we are left with no defences
+ * against password length sniffing.
+ */
+ if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) &&
+ !(s->ppl.remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
+ /*
+ * The server can deal with SSH1_MSG_IGNORE, so
+ * we can use the primary defence.
+ */
+ int bottom, top, pwlen, i;
+ const char *pw = prompt_get_result_ref(
+ s->cur_prompt->prompts[0]);
+
+ pwlen = strlen(pw);
+ if (pwlen < 16) {
+ bottom = 0; /* zero length passwords are OK! :-) */
+ top = 15;
+ } else {
+ bottom = pwlen & ~7;
+ top = bottom + 7;
+ }
+
+ assert(pwlen >= bottom && pwlen <= top);
+
+ for (i = bottom; i <= top; i++) {
+ if (i == pwlen) {
+ pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
+ put_stringz(pkt, pw);
+ pq_push(s->ppl.out_pq, pkt);
+ } else {
+ strbuf *random_data = strbuf_new_nm();
+ random_read(strbuf_append(random_data, i), i);
+
+ pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE);
+ put_stringsb(pkt, random_data);
+ pq_push(s->ppl.out_pq, pkt);
+ }
+ }
+ ppl_logevent("Sending password with camouflage packets");
+ }
+ else if (!(s->ppl.remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
+ /*
+ * The server can't deal with SSH1_MSG_IGNORE
+ * but can deal with padded passwords, so we
+ * can use the secondary defence.
+ */
+ strbuf *padded_pw = strbuf_new_nm();
+
+ ppl_logevent("Sending length-padded password");
+ pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
+ put_asciz(padded_pw, prompt_get_result_ref(
+ s->cur_prompt->prompts[0]));
+ size_t pad = 63 & -padded_pw->len;
+ random_read(strbuf_append(padded_pw, pad), pad);
+ put_stringsb(pkt, padded_pw);
+ pq_push(s->ppl.out_pq, pkt);
+ } else {
+ /*
+ * The server is believed unable to cope with
+ * any of our password camouflage methods.
+ */
+ ppl_logevent("Sending unpadded password");
+ pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
+ put_stringz(pkt, prompt_get_result_ref(
+ s->cur_prompt->prompts[0]));
+ pq_push(s->ppl.out_pq, pkt);
+ }
+ } else {
+ pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
+ put_stringz(pkt, prompt_get_result_ref(s->cur_prompt->prompts[0]));
+ pq_push(s->ppl.out_pq, pkt);
+ }
+ s->is_trivial_auth = false;
+ ppl_logevent("Sent password");
+ free_prompts(s->cur_prompt);
+ s->cur_prompt = NULL;
+ crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
+ if (pktin->type == SSH1_SMSG_FAILURE) {
+ if (seat_verbose(s->ppl.seat))
+ ppl_printf("Access denied\r\n");
+ ppl_logevent("Authentication refused");
+ } else if (pktin->type != SSH1_SMSG_SUCCESS) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
+ " in response to password authentication, type %d "
+ "(%s)", pktin->type, ssh1_pkt_type(pktin->type));
+ return;
+ }
+ }
+
+ if (conf_get_bool(s->conf, CONF_ssh_no_trivial_userauth) &&
+ s->is_trivial_auth) {
+ ssh_proto_error(s->ppl.ssh, "Authentication was trivial! "
+ "Abandoning session as specified in configuration.");
+ return;
+ }
+
+ ppl_logevent("Authentication successful");
+
+ if (conf_get_bool(s->conf, CONF_compression)) {
+ ppl_logevent("Requesting compression");
+ pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_COMPRESSION);
+ put_uint32(pkt, 6); /* gzip compression level */
+ pq_push(s->ppl.out_pq, pkt);
+ crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
+ if (pktin->type == SSH1_SMSG_SUCCESS) {
+ /*
+ * We don't have to actually do anything here: the SSH-1
+ * BPP will take care of automatically starting the
+ * compression, by recognising our outgoing request packet
+ * and the success response. (Horrible, but it's the
+ * easiest way to avoid race conditions if other packets
+ * cross in transit.)
+ */
+ } else if (pktin->type == SSH1_SMSG_FAILURE) {
+ ppl_logevent("Server refused to enable compression");
+ ppl_printf("Server refused to compress\r\n");
+ } else {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
+ " in response to compression request, type %d "
+ "(%s)", pktin->type, ssh1_pkt_type(pktin->type));
+ return;
+ }
+ }
+
+ ssh1_connection_set_protoflags(
+ s->successor_layer, s->local_protoflags, s->remote_protoflags);
+ {
+ PacketProtocolLayer *successor = s->successor_layer;
+ s->successor_layer = NULL; /* avoid freeing it ourself */
+ ssh_ppl_replace(&s->ppl, successor);
+ return; /* we've just freed s, so avoid even touching s->crState */
+ }
+
+ crFinishV;
+}
+
+static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s)
+{
+ if (s->tis_scc_initialised)
+ return;
+ s->tis_scc = seat_stripctrl_new(s->ppl.seat, NULL, SIC_KI_PROMPTS);
+ if (s->tis_scc)
+ stripctrl_enable_line_limiting(s->tis_scc);
+ s->tis_scc_initialised = true;
+}
+
+static void ssh1_login_dialog_callback(void *loginv, SeatPromptResult spr)
+{
+ struct ssh1_login_state *s = (struct ssh1_login_state *)loginv;
+ s->spr = spr;
+ ssh_ppl_process_queue(&s->ppl);
+}
+
+static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req)
+{
+ void *response;
+ int response_len;
+
+ sfree(s->agent_response_to_free);
+ s->agent_response_to_free = NULL;
+
+ s->auth_agent_query = agent_query(req, &response, &response_len,
+ ssh1_login_agent_callback, s);
+ if (!s->auth_agent_query)
+ ssh1_login_agent_callback(s, response, response_len);
+}
+
+static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen)
+{
+ struct ssh1_login_state *s = (struct ssh1_login_state *)loginv;
+
+ s->auth_agent_query = NULL;
+ s->agent_response_to_free = reply;
+ s->agent_response = make_ptrlen(reply, replylen);
+
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+}
+
+static void ssh1_login_special_cmd(PacketProtocolLayer *ppl,
+ SessionSpecialCode code, int arg)
+{
+ struct ssh1_login_state *s =
+ container_of(ppl, struct ssh1_login_state, ppl);
+ PktOut *pktout;
+
+ if (code == SS_PING || code == SS_NOP) {
+ if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE);
+ put_stringz(pktout, "");
+ pq_push(s->ppl.out_pq, pktout);
+ }
+ }
+}
+
+static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
+{
+ struct ssh1_login_state *s =
+ container_of(ppl, struct ssh1_login_state, ppl);
+ ssh_ppl_reconfigure(s->successor_layer, conf);
+}
diff --git a/ssh/mainchan.c b/ssh/mainchan.c
new file mode 100644
index 00000000..52492ff6
--- /dev/null
+++ b/ssh/mainchan.c
@@ -0,0 +1,539 @@
+/*
+ * SSH main session channel handling.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "ppl.h"
+#include "channel.h"
+
+static void mainchan_free(Channel *chan);
+static void mainchan_open_confirmation(Channel *chan);
+static void mainchan_open_failure(Channel *chan, const char *errtext);
+static size_t mainchan_send(
+ Channel *chan, bool is_stderr, const void *, size_t);
+static void mainchan_send_eof(Channel *chan);
+static void mainchan_set_input_wanted(Channel *chan, bool wanted);
+static char *mainchan_log_close_msg(Channel *chan);
+static bool mainchan_rcvd_exit_status(Channel *chan, int status);
+static bool mainchan_rcvd_exit_signal(
+ Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg);
+static bool mainchan_rcvd_exit_signal_numeric(
+ Channel *chan, int signum, bool core_dumped, ptrlen msg);
+static void mainchan_request_response(Channel *chan, bool success);
+
+static const ChannelVtable mainchan_channelvt = {
+ .free = mainchan_free,
+ .open_confirmation = mainchan_open_confirmation,
+ .open_failed = mainchan_open_failure,
+ .send = mainchan_send,
+ .send_eof = mainchan_send_eof,
+ .set_input_wanted = mainchan_set_input_wanted,
+ .log_close_msg = mainchan_log_close_msg,
+ .want_close = chan_default_want_close,
+ .rcvd_exit_status = mainchan_rcvd_exit_status,
+ .rcvd_exit_signal = mainchan_rcvd_exit_signal,
+ .rcvd_exit_signal_numeric = mainchan_rcvd_exit_signal_numeric,
+ .run_shell = chan_no_run_shell,
+ .run_command = chan_no_run_command,
+ .run_subsystem = chan_no_run_subsystem,
+ .enable_x11_forwarding = chan_no_enable_x11_forwarding,
+ .enable_agent_forwarding = chan_no_enable_agent_forwarding,
+ .allocate_pty = chan_no_allocate_pty,
+ .set_env = chan_no_set_env,
+ .send_break = chan_no_send_break,
+ .send_signal = chan_no_send_signal,
+ .change_window_size = chan_no_change_window_size,
+ .request_response = mainchan_request_response,
+};
+
+typedef enum MainChanType {
+ MAINCHAN_SESSION, MAINCHAN_DIRECT_TCPIP
+} MainChanType;
+
+struct mainchan {
+ SshChannel *sc;
+ Conf *conf;
+ PacketProtocolLayer *ppl;
+ ConnectionLayer *cl;
+
+ MainChanType type;
+ bool is_simple;
+
+ bool req_x11, req_agent, req_pty, req_cmd_primary, req_cmd_fallback;
+ int n_req_env, n_env_replies, n_env_fails;
+ bool eof_pending, eof_sent, got_pty, ready;
+
+ int term_width, term_height;
+
+ Channel chan;
+};
+
+mainchan *mainchan_new(
+ PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf,
+ int term_width, int term_height, bool is_simple, SshChannel **sc_out)
+{
+ mainchan *mc;
+
+ if (conf_get_bool(conf, CONF_ssh_no_shell))
+ return NULL; /* no main channel at all */
+
+ mc = snew(mainchan);
+ memset(mc, 0, sizeof(mainchan));
+ mc->ppl = ppl;
+ mc->cl = cl;
+ mc->conf = conf_copy(conf);
+ mc->term_width = term_width;
+ mc->term_height = term_height;
+ mc->is_simple = is_simple;
+
+ mc->sc = NULL;
+ mc->chan.vt = &mainchan_channelvt;
+ mc->chan.initial_fixed_window_size = 0;
+
+ if (*conf_get_str(mc->conf, CONF_ssh_nc_host)) {
+ const char *host = conf_get_str(mc->conf, CONF_ssh_nc_host);
+ int port = conf_get_int(mc->conf, CONF_ssh_nc_port);
+
+ mc->sc = ssh_lportfwd_open(cl, host, port, "main channel",
+ NULL, &mc->chan);
+ mc->type = MAINCHAN_DIRECT_TCPIP;
+ } else {
+ mc->sc = ssh_session_open(cl, &mc->chan);
+ mc->type = MAINCHAN_SESSION;
+ }
+
+ if (sc_out) *sc_out = mc->sc;
+ return mc;
+}
+
+static void mainchan_free(Channel *chan)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+ conf_free(mc->conf);
+ sfree(mc);
+}
+
+static void mainchan_try_fallback_command(mainchan *mc);
+static void mainchan_ready(mainchan *mc);
+
+static void mainchan_open_confirmation(Channel *chan)
+{
+ mainchan *mc = container_of(chan, mainchan, chan);
+ PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+ seat_update_specials_menu(mc->ppl->seat);
+ ppl_logevent("Opened main channel");
+ seat_notify_session_started(mc->ppl->seat);
+
+ if (mc->is_simple)
+ sshfwd_hint_channel_is_simple(mc->sc);
+
+ if (mc->type == MAINCHAN_SESSION) {
+ /*
+ * Send the CHANNEL_REQUESTS for the main session channel.
+ */
+ char *key, *val, *cmd;
+ struct X11Display *x11disp;
+ struct X11FakeAuth *x11auth;
+ bool retry_cmd_now = false;
+
+ if (conf_get_bool(mc->conf, CONF_x11_forward)) {
+ char *x11_setup_err;
+ if ((x11disp = x11_setup_display(
+ conf_get_str(mc->conf, CONF_x11_display),
+ mc->conf, &x11_setup_err)) == NULL) {
+ ppl_logevent("X11 forwarding not enabled: unable to"
+ " initialise X display: %s", x11_setup_err);
+ sfree(x11_setup_err);
+ } else {
+ x11auth = ssh_add_x11_display(
+ mc->cl, conf_get_int(mc->conf, CONF_x11_auth), x11disp);
+
+ sshfwd_request_x11_forwarding(
+ mc->sc, true, x11auth->protoname, x11auth->datastring,
+ x11disp->screennum, false);
+ mc->req_x11 = true;
+ }
+ }
+
+ if (ssh_agent_forwarding_permitted(mc->cl)) {
+ sshfwd_request_agent_forwarding(mc->sc, true);
+ mc->req_agent = true;
+ }
+
+ if (!conf_get_bool(mc->conf, CONF_nopty)) {
+ sshfwd_request_pty(
+ mc->sc, true, mc->conf, mc->term_width, mc->term_height);
+ mc->req_pty = true;
+ }
+
+ for (val = conf_get_str_strs(mc->conf, CONF_environmt, NULL, &key);
+ val != NULL;
+ val = conf_get_str_strs(mc->conf, CONF_environmt, key, &key)) {
+ sshfwd_send_env_var(mc->sc, true, key, val);
+ mc->n_req_env++;
+ }
+ if (mc->n_req_env)
+ ppl_logevent("Sent %d environment variables", mc->n_req_env);
+
+ cmd = conf_get_str(mc->conf, CONF_remote_cmd);
+ if (conf_get_bool(mc->conf, CONF_ssh_subsys)) {
+ retry_cmd_now = !sshfwd_start_subsystem(mc->sc, true, cmd);
+ } else if (*cmd) {
+ sshfwd_start_command(mc->sc, true, cmd);
+ } else {
+ sshfwd_start_shell(mc->sc, true);
+ }
+
+ if (retry_cmd_now)
+ mainchan_try_fallback_command(mc);
+ else
+ mc->req_cmd_primary = true;
+
+ } else {
+ ssh_set_ldisc_option(mc->cl, LD_ECHO, true);
+ ssh_set_ldisc_option(mc->cl, LD_EDIT, true);
+ mainchan_ready(mc);
+ }
+}
+
+static void mainchan_try_fallback_command(mainchan *mc)
+{
+ const char *cmd = conf_get_str(mc->conf, CONF_remote_cmd2);
+ if (conf_get_bool(mc->conf, CONF_ssh_subsys2)) {
+ sshfwd_start_subsystem(mc->sc, true, cmd);
+ } else {
+ sshfwd_start_command(mc->sc, true, cmd);
+ }
+ mc->req_cmd_fallback = true;
+}
+
+static void mainchan_request_response(Channel *chan, bool success)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+ PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+ if (mc->req_x11) {
+ mc->req_x11 = false;
+
+ if (success) {
+ ppl_logevent("X11 forwarding enabled");
+ ssh_enable_x_fwd(mc->cl);
+ } else {
+ ppl_logevent("X11 forwarding refused");
+ }
+ return;
+ }
+
+ if (mc->req_agent) {
+ mc->req_agent = false;
+
+ if (success) {
+ ppl_logevent("Agent forwarding enabled");
+ } else {
+ ppl_logevent("Agent forwarding refused");
+ }
+ return;
+ }
+
+ if (mc->req_pty) {
+ mc->req_pty = false;
+
+ if (success) {
+ ppl_logevent("Allocated pty");
+ mc->got_pty = true;
+ } else {
+ ppl_logevent("Server refused to allocate pty");
+ ppl_printf("Server refused to allocate pty\r\n");
+ ssh_set_ldisc_option(mc->cl, LD_ECHO, true);
+ ssh_set_ldisc_option(mc->cl, LD_EDIT, true);
+ }
+ return;
+ }
+
+ if (mc->n_env_replies < mc->n_req_env) {
+ int j = mc->n_env_replies++;
+ if (!success) {
+ ppl_logevent("Server refused to set environment variable %s",
+ conf_get_str_nthstrkey(mc->conf,
+ CONF_environmt, j));
+ mc->n_env_fails++;
+ }
+
+ if (mc->n_env_replies == mc->n_req_env) {
+ if (mc->n_env_fails == 0) {
+ ppl_logevent("All environment variables successfully set");
+ } else if (mc->n_env_fails == mc->n_req_env) {
+ ppl_logevent("All environment variables refused");
+ ppl_printf("Server refused to set environment "
+ "variables\r\n");
+ } else {
+ ppl_printf("Server refused to set all environment "
+ "variables\r\n");
+ }
+ }
+ return;
+ }
+
+ if (mc->req_cmd_primary) {
+ mc->req_cmd_primary = false;
+
+ if (success) {
+ ppl_logevent("Started a shell/command");
+ mainchan_ready(mc);
+ } else if (*conf_get_str(mc->conf, CONF_remote_cmd2)) {
+ ppl_logevent("Primary command failed; attempting fallback");
+ mainchan_try_fallback_command(mc);
+ } else {
+ /*
+ * If there's no remote_cmd2 configured, then we have no
+ * fallback command, so we've run out of options.
+ */
+ ssh_sw_abort_deferred(mc->ppl->ssh,
+ "Server refused to start a shell/command");
+ }
+ return;
+ }
+
+ if (mc->req_cmd_fallback) {
+ mc->req_cmd_fallback = false;
+
+ if (success) {
+ ppl_logevent("Started a shell/command");
+ ssh_got_fallback_cmd(mc->ppl->ssh);
+ mainchan_ready(mc);
+ } else {
+ ssh_sw_abort_deferred(mc->ppl->ssh,
+ "Server refused to start a shell/command");
+ }
+ return;
+ }
+}
+
+static void mainchan_ready(mainchan *mc)
+{
+ mc->ready = true;
+
+ ssh_set_wants_user_input(mc->cl, true);
+ ssh_got_user_input(mc->cl); /* in case any is already queued */
+
+ /* If an EOF arrived before we were ready, handle it now. */
+ if (mc->eof_pending) {
+ mc->eof_pending = false;
+ mainchan_special_cmd(mc, SS_EOF, 0);
+ }
+
+ ssh_ldisc_update(mc->ppl->ssh);
+ queue_idempotent_callback(&mc->ppl->ic_process_queue);
+}
+
+static void mainchan_open_failure(Channel *chan, const char *errtext)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+
+ ssh_sw_abort_deferred(mc->ppl->ssh,
+ "Server refused to open main channel: %s", errtext);
+}
+
+static size_t mainchan_send(Channel *chan, bool is_stderr,
+ const void *data, size_t length)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+ return seat_output(mc->ppl->seat, is_stderr, data, length);
+}
+
+static void mainchan_send_eof(Channel *chan)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+ PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+ if (!mc->eof_sent && (seat_eof(mc->ppl->seat) || mc->got_pty)) {
+ /*
+ * Either seat_eof told us that the front end wants us to
+ * close the outgoing side of the connection as soon as we see
+ * EOF from the far end, or else we've unilaterally decided to
+ * do that because we've allocated a remote pty and hence EOF
+ * isn't a particularly meaningful concept.
+ */
+ sshfwd_write_eof(mc->sc);
+ ppl_logevent("Sent EOF message");
+ mc->eof_sent = true;
+ ssh_set_wants_user_input(mc->cl, false); /* stop reading from stdin */
+ }
+}
+
+static void mainchan_set_input_wanted(Channel *chan, bool wanted)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+
+ /*
+ * This is the main channel of the SSH session, i.e. the one tied
+ * to the standard input (or GUI) of the primary SSH client user
+ * interface. So ssh->send_ok is how we control whether we're
+ * reading from that input.
+ */
+ ssh_set_wants_user_input(mc->cl, wanted);
+}
+
+static char *mainchan_log_close_msg(Channel *chan)
+{
+ return dupstr("Main session channel closed");
+}
+
+static bool mainchan_rcvd_exit_status(Channel *chan, int status)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+ PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+ ssh_got_exitcode(mc->ppl->ssh, status);
+ ppl_logevent("Session sent command exit status %d", status);
+ return true;
+}
+
+static void mainchan_log_exit_signal_common(
+ mainchan *mc, const char *sigdesc,
+ bool core_dumped, ptrlen msg)
+{
+ PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+ const char *core_msg = core_dumped ? " (core dumped)" : "";
+ const char *msg_pre = (msg.len ? " (" : "");
+ const char *msg_post = (msg.len ? ")" : "");
+ ppl_logevent("Session exited on %s%s%s%.*s%s",
+ sigdesc, core_msg, msg_pre, PTRLEN_PRINTF(msg), msg_post);
+}
+
+static bool mainchan_rcvd_exit_signal(
+ Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+ int exitcode;
+ char *signame_str;
+
+ /*
+ * Translate the signal description back into a locally meaningful
+ * number, or 128 if the string didn't match any we recognise.
+ */
+ exitcode = 128;
+
+ #define SIGNAL_SUB(s) \
+ if (ptrlen_eq_string(signame, #s)) \
+ exitcode = 128 + SIG ## s;
+ #define SIGNAL_MAIN(s, text) SIGNAL_SUB(s)
+ #define SIGNALS_LOCAL_ONLY
+ #include "signal-list.h"
+ #undef SIGNAL_SUB
+ #undef SIGNAL_MAIN
+ #undef SIGNALS_LOCAL_ONLY
+
+ ssh_got_exitcode(mc->ppl->ssh, exitcode);
+ if (exitcode == 128)
+ signame_str = dupprintf("unrecognised signal \"%.*s\"",
+ PTRLEN_PRINTF(signame));
+ else
+ signame_str = dupprintf("signal SIG%.*s", PTRLEN_PRINTF(signame));
+ mainchan_log_exit_signal_common(mc, signame_str, core_dumped, msg);
+ sfree(signame_str);
+ return true;
+}
+
+static bool mainchan_rcvd_exit_signal_numeric(
+ Channel *chan, int signum, bool core_dumped, ptrlen msg)
+{
+ assert(chan->vt == &mainchan_channelvt);
+ mainchan *mc = container_of(chan, mainchan, chan);
+ char *signum_str;
+
+ ssh_got_exitcode(mc->ppl->ssh, 128 + signum);
+ signum_str = dupprintf("signal %d", signum);
+ mainchan_log_exit_signal_common(mc, signum_str, core_dumped, msg);
+ sfree(signum_str);
+ return true;
+}
+
+void mainchan_get_specials(
+ mainchan *mc, add_special_fn_t add_special, void *ctx)
+{
+ /* FIXME: this _does_ depend on whether these services are supported */
+
+ add_special(ctx, "Break", SS_BRK, 0);
+
+ #define SIGNAL_MAIN(name, desc) \
+ add_special(ctx, "SIG" #name " (" desc ")", SS_SIG ## name, 0);
+ #define SIGNAL_SUB(name)
+ #include "signal-list.h"
+ #undef SIGNAL_MAIN
+ #undef SIGNAL_SUB
+
+ add_special(ctx, "More signals", SS_SUBMENU, 0);
+
+ #define SIGNAL_MAIN(name, desc)
+ #define SIGNAL_SUB(name) \
+ add_special(ctx, "SIG" #name, SS_SIG ## name, 0);
+ #include "signal-list.h"
+ #undef SIGNAL_MAIN
+ #undef SIGNAL_SUB
+
+ add_special(ctx, NULL, SS_EXITMENU, 0);
+}
+
+static const char *ssh_signal_lookup(SessionSpecialCode code)
+{
+ #define SIGNAL_SUB(name) \
+ if (code == SS_SIG ## name) return #name;
+ #define SIGNAL_MAIN(name, desc) SIGNAL_SUB(name)
+ #include "signal-list.h"
+ #undef SIGNAL_MAIN
+ #undef SIGNAL_SUB
+
+ /* If none of those clauses matched, fail lookup. */
+ return NULL;
+}
+
+void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg)
+{
+ PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+ const char *signame;
+
+ if (code == SS_EOF) {
+ if (!mc->ready) {
+ /*
+ * Buffer the EOF to send as soon as the main channel is
+ * fully set up.
+ */
+ mc->eof_pending = true;
+ } else if (!mc->eof_sent) {
+ sshfwd_write_eof(mc->sc);
+ mc->eof_sent = true;
+ }
+ } else if (code == SS_BRK) {
+ sshfwd_send_serial_break(
+ mc->sc, false, 0 /* default break length */);
+ } else if ((signame = ssh_signal_lookup(code)) != NULL) {
+ /* It's a signal. */
+ sshfwd_send_signal(mc->sc, false, signame);
+ ppl_logevent("Sent signal SIG%s", signame);
+ }
+}
+
+void mainchan_terminal_size(mainchan *mc, int width, int height)
+{
+ mc->term_width = width;
+ mc->term_height = height;
+
+ if (mc->req_pty || mc->got_pty)
+ sshfwd_send_terminal_size_change(mc->sc, width, height);
+}
diff --git a/SSHNOGSS.C b/ssh/nogss.c
index fa4ac65f..fa4ac65f 100644
--- a/SSHNOGSS.C
+++ b/ssh/nogss.c
diff --git a/noshare.c b/ssh/nosharing.c
index c45634c5..c45634c5 100644
--- a/noshare.c
+++ b/ssh/nosharing.c
diff --git a/ssh/pgssapi.c b/ssh/pgssapi.c
new file mode 100644
index 00000000..1730444d
--- /dev/null
+++ b/ssh/pgssapi.c
@@ -0,0 +1,135 @@
+/* This file actually defines the GSSAPI function pointers for
+ * functions we plan to import from a GSSAPI library.
+ */
+#include "putty.h"
+
+#ifndef NO_GSSAPI
+
+#include "pgssapi.h"
+
+#ifndef NO_LIBDL
+
+/* Reserved static storage for GSS_oids.
+ * Constants of the form GSS_C_NT_* are specified by rfc 2744.
+ * Comments are quotes from RFC 2744 itself.
+ *
+ * These may be #defined to complex expressions by the local header
+ * file, if we're including one in static-GSSAPI mode. (For example,
+ * Heimdal defines them to things like
+ * (&__gss_c_nt_user_name_oid_desc).) So we only define them if
+ * needed. */
+
+#ifndef GSS_C_NT_USER_NAME
+static gss_OID_desc oid_GSS_C_NT_USER_NAME = {
+ /* The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value */
+ 10, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01",
+ /* corresponding to an object-identifier value of
+ * {iso(1) member-body(2) United States(840) mit(113554)
+ * infosys(1) gssapi(2) generic(1) user_name(1)}. The constant
+ * GSS_C_NT_USER_NAME should be initialized to point
+ * to that gss_OID_desc. */
+};
+const_gss_OID GSS_C_NT_USER_NAME = &oid_GSS_C_NT_USER_NAME;
+#endif
+
+#ifndef GSS_C_NT_MACHINE_UID_NAME
+static gss_OID_desc oid_GSS_C_NT_MACHINE_UID_NAME = {
+ /* The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value */
+ 10, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02",
+ /* corresponding to an object-identifier value of
+ * {iso(1) member-body(2) United States(840) mit(113554)
+ * infosys(1) gssapi(2) generic(1) machine_uid_name(2)}.
+ * The constant GSS_C_NT_MACHINE_UID_NAME should be
+ * initialized to point to that gss_OID_desc. */
+};
+const_gss_OID GSS_C_NT_MACHINE_UID_NAME = &oid_GSS_C_NT_MACHINE_UID_NAME;
+#endif
+
+#ifndef GSS_C_NT_STRING_UID_NAME
+static gss_OID_desc oid_GSS_C_NT_STRING_UID_NAME = {
+ /* The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value */
+ 10, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03",
+ /* corresponding to an object-identifier value of
+ * {iso(1) member-body(2) United States(840) mit(113554)
+ * infosys(1) gssapi(2) generic(1) string_uid_name(3)}.
+ * The constant GSS_C_NT_STRING_UID_NAME should be
+ * initialized to point to that gss_OID_desc. */
+};
+const_gss_OID GSS_C_NT_STRING_UID_NAME = &oid_GSS_C_NT_STRING_UID_NAME;
+#endif
+
+#ifndef GSS_C_NT_HOSTBASED_SERVICE_X
+static gss_OID_desc oid_GSS_C_NT_HOSTBASED_SERVICE_X = {
+ /* The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value */
+ 6, "\x2b\x06\x01\x05\x06\x02",
+ /* corresponding to an object-identifier value of
+ * {iso(1) org(3) dod(6) internet(1) security(5)
+ * nametypes(6) gss-host-based-services(2))}. The constant
+ * GSS_C_NT_HOSTBASED_SERVICE_X should be initialized to point
+ * to that gss_OID_desc. This is a deprecated OID value, and
+ * implementations wishing to support hostbased-service names
+ * should instead use the GSS_C_NT_HOSTBASED_SERVICE OID,
+ * defined below, to identify such names;
+ * GSS_C_NT_HOSTBASED_SERVICE_X should be accepted a synonym
+ * for GSS_C_NT_HOSTBASED_SERVICE when presented as an input
+ * parameter, but should not be emitted by GSS-API
+ * implementations */
+};
+const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = &oid_GSS_C_NT_HOSTBASED_SERVICE_X;
+#endif
+
+#ifndef GSS_C_NT_HOSTBASED_SERVICE
+static gss_OID_desc oid_GSS_C_NT_HOSTBASED_SERVICE = {
+ /* The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value */
+ 10, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04",
+ /* corresponding to an object-identifier value of {iso(1)
+ * member-body(2) Unites States(840) mit(113554) infosys(1)
+ * gssapi(2) generic(1) service_name(4)}. The constant
+ * GSS_C_NT_HOSTBASED_SERVICE should be initialized
+ * to point to that gss_OID_desc. */
+};
+const_gss_OID GSS_C_NT_HOSTBASED_SERVICE = &oid_GSS_C_NT_HOSTBASED_SERVICE;
+#endif
+
+#ifndef GSS_C_NT_ANONYMOUS
+static gss_OID_desc oid_GSS_C_NT_ANONYMOUS = {
+ /* The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value */
+ 6, "\x2b\x06\01\x05\x06\x03",
+ /* corresponding to an object identifier value of
+ * {1(iso), 3(org), 6(dod), 1(internet), 5(security),
+ * 6(nametypes), 3(gss-anonymous-name)}. The constant
+ * and GSS_C_NT_ANONYMOUS should be initialized to point
+ * to that gss_OID_desc. */
+};
+const_gss_OID GSS_C_NT_ANONYMOUS = &oid_GSS_C_NT_ANONYMOUS;
+#endif
+
+#ifndef GSS_C_NT_EXPORT_NAME
+static gss_OID_desc oid_GSS_C_NT_EXPORT_NAME = {
+ /* The implementation must reserve static storage for a
+ * gss_OID_desc object containing the value */
+ 6, "\x2b\x06\x01\x05\x06\x04",
+ /* corresponding to an object-identifier value of
+ * {1(iso), 3(org), 6(dod), 1(internet), 5(security),
+ * 6(nametypes), 4(gss-api-exported-name)}. The constant
+ * GSS_C_NT_EXPORT_NAME should be initialized to point
+ * to that gss_OID_desc.
+ */
+};
+const_gss_OID GSS_C_NT_EXPORT_NAME = &oid_GSS_C_NT_EXPORT_NAME;
+#endif
+
+#endif /* NO_LIBDL */
+
+static gss_OID_desc gss_mech_krb5_desc =
+{ 9, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
+/* iso(1) member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) krb5(2)*/
+const gss_OID GSS_MECH_KRB5 = &gss_mech_krb5_desc;
+
+#endif /* NO_GSSAPI */
diff --git a/ssh/pgssapi.h b/ssh/pgssapi.h
new file mode 100644
index 00000000..3f21e0ff
--- /dev/null
+++ b/ssh/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 <gssapi/gssapi.h>
+typedef gss_OID const_gss_OID; /* for our prototypes below */
+#else /* STATIC_GSSAPI */
+
+/*******************************************************************************
+ * GSSAPI Definitions, taken from RFC 2744
+ ******************************************************************************/
+
+/* GSSAPI Type Definitions */
+typedef uint32_t OM_uint32;
+
+typedef struct gss_OID_desc_struct {
+ OM_uint32 length;
+ void *elements;
+} gss_OID_desc;
+typedef const gss_OID_desc *const_gss_OID;
+typedef gss_OID_desc *gss_OID;
+
+typedef struct gss_OID_set_desc_struct {
+ size_t count;
+ gss_OID elements;
+} gss_OID_set_desc;
+typedef const gss_OID_set_desc *const_gss_OID_set;
+typedef gss_OID_set_desc *gss_OID_set;
+
+typedef struct gss_buffer_desc_struct {
+ size_t length;
+ void *value;
+} gss_buffer_desc, *gss_buffer_t;
+
+typedef struct gss_channel_bindings_struct {
+ OM_uint32 initiator_addrtype;
+ gss_buffer_desc initiator_address;
+ OM_uint32 acceptor_addrtype;
+ gss_buffer_desc acceptor_address;
+ gss_buffer_desc application_data;
+} *gss_channel_bindings_t;
+
+typedef void *gss_ctx_id_t;
+typedef void *gss_name_t;
+typedef void *gss_cred_id_t;
+
+typedef OM_uint32 gss_qop_t;
+typedef int gss_cred_usage_t;
+
+/* Flag bits for context-level services. */
+
+#define GSS_C_DELEG_FLAG 1
+#define GSS_C_MUTUAL_FLAG 2
+#define GSS_C_REPLAY_FLAG 4
+#define GSS_C_SEQUENCE_FLAG 8
+#define GSS_C_CONF_FLAG 16
+#define GSS_C_INTEG_FLAG 32
+#define GSS_C_ANON_FLAG 64
+#define GSS_C_PROT_READY_FLAG 128
+#define GSS_C_TRANS_FLAG 256
+
+/* Credential usage options */
+#define GSS_C_BOTH 0
+#define GSS_C_INITIATE 1
+#define GSS_C_ACCEPT 2
+
+/*-
+ * RFC 2744 Page 86
+ * Expiration time of 2^32-1 seconds means infinite lifetime for a
+ * credential or security context
+ */
+#define GSS_C_INDEFINITE 0xfffffffful
+
+/* Status code types for gss_display_status */
+#define GSS_C_GSS_CODE 1
+#define GSS_C_MECH_CODE 2
+
+/* The constant definitions for channel-bindings address families */
+#define GSS_C_AF_UNSPEC 0
+#define GSS_C_AF_LOCAL 1
+#define GSS_C_AF_INET 2
+#define GSS_C_AF_IMPLINK 3
+#define GSS_C_AF_PUP 4
+#define GSS_C_AF_CHAOS 5
+#define GSS_C_AF_NS 6
+#define GSS_C_AF_NBS 7
+#define GSS_C_AF_ECMA 8
+#define GSS_C_AF_DATAKIT 9
+#define GSS_C_AF_CCITT 10
+#define GSS_C_AF_SNA 11
+#define GSS_C_AF_DECnet 12
+#define GSS_C_AF_DLI 13
+#define GSS_C_AF_LAT 14
+#define GSS_C_AF_HYLINK 15
+#define GSS_C_AF_APPLETALK 16
+#define GSS_C_AF_BSC 17
+#define GSS_C_AF_DSS 18
+#define GSS_C_AF_OSI 19
+#define GSS_C_AF_X25 21
+
+#define GSS_C_AF_NULLADDR 255
+
+/* Various Null values */
+#define GSS_C_NO_NAME ((gss_name_t) 0)
+#define GSS_C_NO_BUFFER ((gss_buffer_t) 0)
+#define GSS_C_NO_OID ((gss_OID) 0)
+#define GSS_C_NO_OID_SET ((gss_OID_set) 0)
+#define GSS_C_NO_CONTEXT ((gss_ctx_id_t) 0)
+#define GSS_C_NO_CREDENTIAL ((gss_cred_id_t) 0)
+#define GSS_C_NO_CHANNEL_BINDINGS ((gss_channel_bindings_t) 0)
+#define GSS_C_EMPTY_BUFFER {0, NULL}
+
+/* Major status codes */
+#define GSS_S_COMPLETE 0
+
+/* Some "helper" definitions to make the status code macros obvious. */
+#define GSS_C_CALLING_ERROR_OFFSET 24
+#define GSS_C_ROUTINE_ERROR_OFFSET 16
+
+#define GSS_C_SUPPLEMENTARY_OFFSET 0
+#define GSS_C_CALLING_ERROR_MASK 0377ul
+#define GSS_C_ROUTINE_ERROR_MASK 0377ul
+#define GSS_C_SUPPLEMENTARY_MASK 0177777ul
+
+/*
+ * The macros that test status codes for error conditions.
+ * Note that the GSS_ERROR() macro has changed slightly from
+ * the V1 GSS-API so that it now evaluates its argument
+ * only once.
+ */
+#define GSS_CALLING_ERROR(x) \
+ (x & (GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET))
+#define GSS_ROUTINE_ERROR(x) \
+ (x & (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET))
+#define GSS_SUPPLEMENTARY_INFO(x) \
+ (x & (GSS_C_SUPPLEMENTARY_MASK << GSS_C_SUPPLEMENTARY_OFFSET))
+#define GSS_ERROR(x) \
+ (x & ((GSS_C_CALLING_ERROR_MASK << GSS_C_CALLING_ERROR_OFFSET) | \
+ (GSS_C_ROUTINE_ERROR_MASK << GSS_C_ROUTINE_ERROR_OFFSET)))
+
+/* Now the actual status code definitions */
+
+/* Calling errors: */
+#define GSS_S_CALL_INACCESSIBLE_READ \
+ (1ul << GSS_C_CALLING_ERROR_OFFSET)
+#define GSS_S_CALL_INACCESSIBLE_WRITE \
+ (2ul << GSS_C_CALLING_ERROR_OFFSET)
+#define GSS_S_CALL_BAD_STRUCTURE \
+ (3ul << GSS_C_CALLING_ERROR_OFFSET)
+
+/* Routine errors: */
+#define GSS_S_BAD_MECH (1ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_NAME (2ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_NAMETYPE (3ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_BINDINGS (4ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_STATUS (5ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_SIG (6ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_MIC GSS_S_BAD_SIG
+#define GSS_S_NO_CRED (7ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_NO_CONTEXT (8ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_DEFECTIVE_TOKEN (9ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_DEFECTIVE_CREDENTIAL (10ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_CREDENTIALS_EXPIRED (11ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_CONTEXT_EXPIRED (12ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_FAILURE (13ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_BAD_QOP (14ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_UNAUTHORIZED (15ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_UNAVAILABLE (16ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_DUPLICATE_ELEMENT (17ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+#define GSS_S_NAME_NOT_MN (18ul << \
+ GSS_C_ROUTINE_ERROR_OFFSET)
+
+/* Supplementary info bits: */
+#define GSS_S_CONTINUE_NEEDED \
+ (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 0))
+#define GSS_S_DUPLICATE_TOKEN \
+ (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 1))
+#define GSS_S_OLD_TOKEN \
+ (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 2))
+#define GSS_S_UNSEQ_TOKEN \
+ (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 3))
+#define GSS_S_GAP_TOKEN \
+ (1ul << (GSS_C_SUPPLEMENTARY_OFFSET + 4))
+
+extern const_gss_OID GSS_C_NT_USER_NAME;
+extern const_gss_OID GSS_C_NT_MACHINE_UID_NAME;
+extern const_gss_OID GSS_C_NT_STRING_UID_NAME;
+extern const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X;
+extern const_gss_OID GSS_C_NT_HOSTBASED_SERVICE;
+extern const_gss_OID GSS_C_NT_ANONYMOUS;
+extern const_gss_OID GSS_C_NT_EXPORT_NAME;
+
+#endif /* STATIC_GSSAPI */
+
+extern const gss_OID GSS_MECH_KRB5;
+
+/* GSSAPI functions we use.
+ * TODO: Replace with all GSSAPI functions from RFC?
+ */
+
+/* Calling convention, just in case we need one. */
+#ifndef GSS_CC
+#define GSS_CC
+#endif /*GSS_CC*/
+
+typedef OM_uint32 (GSS_CC *t_gss_release_cred)
+ (OM_uint32 * /*minor_status*/,
+ gss_cred_id_t * /*cred_handle*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_init_sec_context)
+ (OM_uint32 * /*minor_status*/,
+ const gss_cred_id_t /*initiator_cred_handle*/,
+ gss_ctx_id_t * /*context_handle*/,
+ const gss_name_t /*target_name*/,
+ const gss_OID /*mech_type*/,
+ OM_uint32 /*req_flags*/,
+ OM_uint32 /*time_req*/,
+ const gss_channel_bindings_t /*input_chan_bindings*/,
+ const gss_buffer_t /*input_token*/,
+ gss_OID * /*actual_mech_type*/,
+ gss_buffer_t /*output_token*/,
+ OM_uint32 * /*ret_flags*/,
+ OM_uint32 * /*time_rec*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_delete_sec_context)
+ (OM_uint32 * /*minor_status*/,
+ gss_ctx_id_t * /*context_handle*/,
+ gss_buffer_t /*output_token*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_get_mic)
+ (OM_uint32 * /*minor_status*/,
+ const gss_ctx_id_t /*context_handle*/,
+ gss_qop_t /*qop_req*/,
+ const gss_buffer_t /*message_buffer*/,
+ gss_buffer_t /*msg_token*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_verify_mic)
+ (OM_uint32 * /*minor_status*/,
+ const gss_ctx_id_t /*context_handle*/,
+ const gss_buffer_t /*message_buffer*/,
+ const gss_buffer_t /*msg_token*/,
+ gss_qop_t * /*qop_state*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_display_status)
+ (OM_uint32 * /*minor_status*/,
+ OM_uint32 /*status_value*/,
+ int /*status_type*/,
+ const gss_OID /*mech_type*/,
+ OM_uint32 * /*message_context*/,
+ gss_buffer_t /*status_string*/);
+
+
+typedef OM_uint32 (GSS_CC *t_gss_import_name)
+ (OM_uint32 * /*minor_status*/,
+ const gss_buffer_t /*input_name_buffer*/,
+ const_gss_OID /*input_name_type*/,
+ gss_name_t * /*output_name*/);
+
+
+typedef OM_uint32 (GSS_CC *t_gss_release_name)
+ (OM_uint32 * /*minor_status*/,
+ gss_name_t * /*name*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_release_buffer)
+ (OM_uint32 * /*minor_status*/,
+ gss_buffer_t /*buffer*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_acquire_cred)
+ (OM_uint32 * /*minor_status*/,
+ const gss_name_t /*desired_name*/,
+ OM_uint32 /*time_req*/,
+ const gss_OID_set /*desired_mechs*/,
+ gss_cred_usage_t /*cred_usage*/,
+ gss_cred_id_t * /*output_cred_handle*/,
+ gss_OID_set * /*actual_mechs*/,
+ OM_uint32 * /*time_rec*/);
+
+typedef OM_uint32 (GSS_CC *t_gss_inquire_cred_by_mech)
+ (OM_uint32 * /*minor_status*/,
+ const gss_cred_id_t /*cred_handle*/,
+ const gss_OID /*mech_type*/,
+ gss_name_t * /*name*/,
+ OM_uint32 * /*initiator_lifetime*/,
+ OM_uint32 * /*acceptor_lifetime*/,
+ gss_cred_usage_t * /*cred_usage*/);
+
+struct gssapi_functions {
+ t_gss_delete_sec_context delete_sec_context;
+ t_gss_display_status display_status;
+ t_gss_get_mic get_mic;
+ t_gss_verify_mic verify_mic;
+ t_gss_import_name import_name;
+ t_gss_init_sec_context init_sec_context;
+ t_gss_release_buffer release_buffer;
+ t_gss_release_cred release_cred;
+ t_gss_release_name release_name;
+ t_gss_acquire_cred acquire_cred;
+ t_gss_inquire_cred_by_mech inquire_cred_by_mech;
+};
+
+#endif /* NO_GSSAPI */
+
+#endif /* PUTTY_PGSSAPI_H */
diff --git a/ssh/portfwd.c b/ssh/portfwd.c
new file mode 100644
index 00000000..b4eea3c9
--- /dev/null
+++ b/ssh/portfwd.c
@@ -0,0 +1,1179 @@
+/*
+ * SSH port forwarding.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "channel.h"
+#include "proxy/socks.h"
+
+/*
+ * Enumeration of values that live in the 'socks_state' field of
+ * struct PortForwarding.
+ */
+typedef enum {
+ SOCKS_NONE, /* direct connection (no SOCKS, or SOCKS already done) */
+ SOCKS_INITIAL, /* don't know if we're SOCKS 4 or 5 yet */
+ SOCKS_4, /* expect a SOCKS 4 (or 4A) connection message */
+ SOCKS_5_INITIAL, /* expect a SOCKS 5 preliminary message */
+ SOCKS_5_CONNECT /* expect a SOCKS 5 connection message */
+} SocksState;
+
+typedef struct PortForwarding {
+ SshChannel *c; /* channel structure held by SSH connection layer */
+ ConnectionLayer *cl; /* the connection layer itself */
+ /* Note that ssh need not be filled in if c is non-NULL */
+ Socket *s;
+ bool input_wanted;
+ bool ready;
+ SocksState socks_state;
+ /*
+ * `hostname' and `port' are the real hostname and port, once
+ * we know what we're connecting to.
+ */
+ char *hostname;
+ int port;
+ /*
+ * `socksbuf' is the buffer we use to accumulate the initial SOCKS
+ * segment of the incoming data, plus anything after that that we
+ * receive before we're ready to send data to the SSH server.
+ */
+ strbuf *socksbuf;
+ size_t socksbuf_consumed;
+
+ Plug plug;
+ Channel chan;
+} PortForwarding;
+
+struct PortListener {
+ ConnectionLayer *cl;
+ Socket *s;
+ bool is_dynamic;
+ /*
+ * `hostname' and `port' are the real hostname and port, for
+ * ordinary forwardings.
+ */
+ char *hostname;
+ int port;
+
+ Plug plug;
+};
+
+static struct PortForwarding *new_portfwd_state(void)
+{
+ struct PortForwarding *pf = snew(struct PortForwarding);
+ pf->hostname = NULL;
+ pf->socksbuf = NULL;
+ return pf;
+}
+
+static void free_portfwd_state(struct PortForwarding *pf)
+{
+ if (!pf)
+ return;
+ sfree(pf->hostname);
+ if (pf->socksbuf)
+ strbuf_free(pf->socksbuf);
+ sfree(pf);
+}
+
+static struct PortListener *new_portlistener_state(void)
+{
+ struct PortListener *pl = snew(struct PortListener);
+ pl->hostname = NULL;
+ return pl;
+}
+
+static void free_portlistener_state(struct PortListener *pl)
+{
+ if (!pl)
+ return;
+ sfree(pl->hostname);
+ sfree(pl);
+}
+
+static void pfd_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
+ const char *error_msg, int error_code)
+{
+ /* we have to dump these since we have no interface to logging.c */
+}
+
+static void pfl_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
+ const char *error_msg, int error_code)
+{
+ /* we have to dump these since we have no interface to logging.c */
+}
+
+static void pfd_close(struct PortForwarding *pf);
+
+static void pfd_closing(Plug *plug, PlugCloseType type, const char *error_msg)
+{
+ struct PortForwarding *pf =
+ container_of(plug, struct PortForwarding, plug);
+
+ if (type != PLUGCLOSE_NORMAL) {
+ /*
+ * Socket error. Slam the connection instantly shut.
+ */
+ if (pf->c) {
+ sshfwd_initiate_close(pf->c, error_msg);
+ } else {
+ /*
+ * We might not have an SSH channel, if a socket error
+ * occurred during SOCKS negotiation. If not, we must
+ * clean ourself up without sshfwd_initiate_close's call
+ * back to pfd_close.
+ */
+ pfd_close(pf);
+ }
+ } else {
+ /*
+ * Ordinary EOF received on socket. Send an EOF on the SSH
+ * channel.
+ */
+ if (pf->c)
+ sshfwd_write_eof(pf->c);
+ }
+}
+
+static void pfl_terminate(struct PortListener *pl);
+
+static void pfl_closing(Plug *plug, PlugCloseType type, const char *error_msg)
+{
+ struct PortListener *pl = (struct PortListener *) plug;
+ pfl_terminate(pl);
+}
+
+static SshChannel *wrap_lportfwd_open(
+ ConnectionLayer *cl, const char *hostname, int port,
+ Socket *s, Channel *chan)
+{
+ SocketPeerInfo *pi;
+ char *description;
+ SshChannel *toret;
+
+ pi = sk_peer_info(s);
+ if (pi && pi->log_text) {
+ description = dupprintf("forwarding from %s", pi->log_text);
+ } else {
+ description = dupstr("forwarding");
+ }
+ toret = ssh_lportfwd_open(cl, hostname, port, description, pi, chan);
+ sk_free_peer_info(pi);
+
+ sfree(description);
+ return toret;
+}
+
+static char *ipv4_to_string(unsigned ipv4)
+{
+ return dupprintf("%u.%u.%u.%u",
+ (ipv4 >> 24) & 0xFF, (ipv4 >> 16) & 0xFF,
+ (ipv4 >> 8) & 0xFF, (ipv4 ) & 0xFF);
+}
+
+static char *ipv6_to_string(ptrlen ipv6)
+{
+ const unsigned char *addr = ipv6.ptr;
+ assert(ipv6.len == 16);
+ return dupprintf("%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x",
+ (unsigned)GET_16BIT_MSB_FIRST(addr + 0),
+ (unsigned)GET_16BIT_MSB_FIRST(addr + 2),
+ (unsigned)GET_16BIT_MSB_FIRST(addr + 4),
+ (unsigned)GET_16BIT_MSB_FIRST(addr + 6),
+ (unsigned)GET_16BIT_MSB_FIRST(addr + 8),
+ (unsigned)GET_16BIT_MSB_FIRST(addr + 10),
+ (unsigned)GET_16BIT_MSB_FIRST(addr + 12),
+ (unsigned)GET_16BIT_MSB_FIRST(addr + 14));
+}
+
+static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len)
+{
+ struct PortForwarding *pf =
+ container_of(plug, struct PortForwarding, plug);
+
+ if (len == 0)
+ return;
+
+ if (pf->socks_state != SOCKS_NONE) {
+ BinarySource src[1];
+
+ /*
+ * Store all the data we've got in socksbuf.
+ */
+ put_data(pf->socksbuf, data, len);
+
+ /*
+ * Check the start of socksbuf to see if it's a valid and
+ * complete message in the SOCKS exchange.
+ */
+
+ if (pf->socks_state == SOCKS_INITIAL) {
+ /* Preliminary: check the first byte of the data (which we
+ * _must_ have by now) to find out which SOCKS major
+ * version we're speaking. */
+ switch (pf->socksbuf->u[0]) {
+ case SOCKS4_REQUEST_VERSION:
+ pf->socks_state = SOCKS_4;
+ break;
+ case SOCKS5_REQUEST_VERSION:
+ pf->socks_state = SOCKS_5_INITIAL;
+ break;
+ default:
+ pfd_close(pf); /* unrecognised version */
+ return;
+ }
+ }
+
+ BinarySource_BARE_INIT(src, pf->socksbuf->u, pf->socksbuf->len);
+ get_data(src, pf->socksbuf_consumed);
+
+ while (pf->socks_state != SOCKS_NONE) {
+ unsigned socks_version, message_type, reserved_byte;
+ unsigned reply_code, port, ipv4, method;
+ ptrlen methods;
+ const char *socks4_hostname;
+ strbuf *output;
+
+ switch (pf->socks_state) {
+ case SOCKS_INITIAL:
+ case SOCKS_NONE:
+ unreachable("These case values cannot appear");
+
+ case SOCKS_4:
+ /* SOCKS 4/4A connect message */
+ socks_version = get_byte(src);
+ message_type = get_byte(src);
+
+ if (get_err(src) == BSE_OUT_OF_DATA)
+ return;
+ if (socks_version == SOCKS4_REQUEST_VERSION &&
+ message_type == SOCKS_CMD_CONNECT) {
+ /* CONNECT message */
+ bool name_based = false;
+
+ port = get_uint16(src);
+ ipv4 = get_uint32(src);
+ if (ipv4 >= SOCKS4A_NAME_FOLLOWS_BASE &&
+ ipv4 < SOCKS4A_NAME_FOLLOWS_LIMIT) {
+ /*
+ * Addresses in this range indicate the SOCKS 4A
+ * extension to specify a hostname, which comes
+ * after the username.
+ */
+ name_based = true;
+ }
+ get_asciz(src); /* skip username */
+ socks4_hostname = name_based ? get_asciz(src) : NULL;
+
+ if (get_err(src) == BSE_OUT_OF_DATA)
+ return;
+ if (get_err(src))
+ goto socks4_reject;
+
+ pf->port = port;
+ if (name_based) {
+ pf->hostname = dupstr(socks4_hostname);
+ } else {
+ pf->hostname = ipv4_to_string(ipv4);
+ }
+
+ output = strbuf_new();
+ put_byte(output, SOCKS4_REPLY_VERSION);
+ put_byte(output, SOCKS4_RESP_SUCCESS);
+ put_uint16(output, 0); /* null port field */
+ put_uint32(output, 0); /* null address field */
+ sk_write(pf->s, output->u, output->len);
+ strbuf_free(output);
+
+ pf->socks_state = SOCKS_NONE;
+ pf->socksbuf_consumed = src->pos;
+ break;
+ }
+
+ socks4_reject:
+ output = strbuf_new();
+ put_byte(output, SOCKS4_REPLY_VERSION);
+ put_byte(output, SOCKS4_RESP_FAILURE);
+ put_uint16(output, 0); /* null port field */
+ put_uint32(output, 0); /* null address field */
+ sk_write(pf->s, output->u, output->len);
+ strbuf_free(output);
+ pfd_close(pf);
+ return;
+
+ case SOCKS_5_INITIAL:
+ /* SOCKS 5 initial method list */
+ socks_version = get_byte(src);
+ methods = get_pstring(src);
+
+ method = SOCKS5_AUTH_REJECTED;
+
+ /* Search the method list for AUTH_NONE, which is the
+ * only one this client code can speak */
+ for (size_t i = 0; i < methods.len; i++) {
+ unsigned char this_method =
+ ((const unsigned char *)methods.ptr)[i];
+ if (this_method == SOCKS5_AUTH_NONE) {
+ method = this_method;
+ break;
+ }
+ }
+
+ if (get_err(src) == BSE_OUT_OF_DATA)
+ return;
+ if (get_err(src))
+ method = SOCKS5_AUTH_REJECTED;
+
+ output = strbuf_new();
+ put_byte(output, SOCKS5_REPLY_VERSION);
+ put_byte(output, method);
+ sk_write(pf->s, output->u, output->len);
+ strbuf_free(output);
+
+ if (method == SOCKS5_AUTH_REJECTED) {
+ pfd_close(pf);
+ return;
+ }
+
+ pf->socks_state = SOCKS_5_CONNECT;
+ pf->socksbuf_consumed = src->pos;
+ break;
+
+ case SOCKS_5_CONNECT:
+ /* SOCKS 5 connect message */
+ socks_version = get_byte(src);
+ message_type = get_byte(src);
+ reserved_byte = get_byte(src);
+
+ if (socks_version == SOCKS5_REQUEST_VERSION &&
+ message_type == SOCKS_CMD_CONNECT &&
+ reserved_byte == 0) {
+
+ reply_code = SOCKS5_RESP_SUCCESS;
+
+ switch (get_byte(src)) {
+ case SOCKS5_ADDR_IPV4:
+ pf->hostname = ipv4_to_string(get_uint32(src));
+ break;
+ case SOCKS5_ADDR_IPV6:
+ pf->hostname = ipv6_to_string(get_data(src, 16));
+ break;
+ case SOCKS5_ADDR_HOSTNAME:
+ pf->hostname = mkstr(get_pstring(src));
+ break;
+ default:
+ pf->hostname = NULL;
+ reply_code = SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED;
+ break;
+ }
+
+ pf->port = get_uint16(src);
+ } else {
+ reply_code = SOCKS5_RESP_COMMAND_NOT_SUPPORTED;
+ }
+
+ if (get_err(src) == BSE_OUT_OF_DATA)
+ return;
+ if (get_err(src))
+ reply_code = SOCKS5_RESP_FAILURE;
+
+ output = strbuf_new();
+ put_byte(output, SOCKS5_REPLY_VERSION);
+ put_byte(output, reply_code);
+ put_byte(output, 0); /* reserved */
+ put_byte(output, SOCKS5_ADDR_IPV4); /* IPv4 address follows */
+ put_uint32(output, 0); /* bound IPv4 address (unused) */
+ put_uint16(output, 0); /* bound port number (unused) */
+ sk_write(pf->s, output->u, output->len);
+ strbuf_free(output);
+
+ if (reply_code != SOCKS5_RESP_SUCCESS) {
+ pfd_close(pf);
+ return;
+ }
+
+ pf->socks_state = SOCKS_NONE;
+ pf->socksbuf_consumed = src->pos;
+ break;
+ }
+ }
+
+ /*
+ * We come here when we're ready to make an actual
+ * connection.
+ */
+
+ /*
+ * Freeze the socket until the SSH server confirms the
+ * connection.
+ */
+ sk_set_frozen(pf->s, true);
+
+ pf->c = wrap_lportfwd_open(pf->cl, pf->hostname, pf->port, pf->s,
+ &pf->chan);
+ }
+ if (pf->ready)
+ sshfwd_write(pf->c, data, len);
+}
+
+static void pfd_sent(Plug *plug, size_t bufsize)
+{
+ struct PortForwarding *pf =
+ container_of(plug, struct PortForwarding, plug);
+
+ if (pf->c)
+ sshfwd_unthrottle(pf->c, bufsize);
+}
+
+static const PlugVtable PortForwarding_plugvt = {
+ .log = pfd_log,
+ .closing = pfd_closing,
+ .receive = pfd_receive,
+ .sent = pfd_sent,
+};
+
+static void pfd_chan_free(Channel *chan);
+static void pfd_open_confirmation(Channel *chan);
+static void pfd_open_failure(Channel *chan, const char *errtext);
+static size_t pfd_send(
+ Channel *chan, bool is_stderr, const void *data, size_t len);
+static void pfd_send_eof(Channel *chan);
+static void pfd_set_input_wanted(Channel *chan, bool wanted);
+static char *pfd_log_close_msg(Channel *chan);
+
+static const ChannelVtable PortForwarding_channelvt = {
+ .free = pfd_chan_free,
+ .open_confirmation = pfd_open_confirmation,
+ .open_failed = pfd_open_failure,
+ .send = pfd_send,
+ .send_eof = pfd_send_eof,
+ .set_input_wanted = pfd_set_input_wanted,
+ .log_close_msg = pfd_log_close_msg,
+ .want_close = chan_default_want_close,
+ .rcvd_exit_status = chan_no_exit_status,
+ .rcvd_exit_signal = chan_no_exit_signal,
+ .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
+ .run_shell = chan_no_run_shell,
+ .run_command = chan_no_run_command,
+ .run_subsystem = chan_no_run_subsystem,
+ .enable_x11_forwarding = chan_no_enable_x11_forwarding,
+ .enable_agent_forwarding = chan_no_enable_agent_forwarding,
+ .allocate_pty = chan_no_allocate_pty,
+ .set_env = chan_no_set_env,
+ .send_break = chan_no_send_break,
+ .send_signal = chan_no_send_signal,
+ .change_window_size = chan_no_change_window_size,
+ .request_response = chan_no_request_response,
+};
+
+Channel *portfwd_raw_new(ConnectionLayer *cl, Plug **plug, bool start_ready)
+{
+ struct PortForwarding *pf;
+
+ pf = new_portfwd_state();
+ pf->plug.vt = &PortForwarding_plugvt;
+ pf->chan.initial_fixed_window_size = 0;
+ pf->chan.vt = &PortForwarding_channelvt;
+ pf->input_wanted = true;
+
+ pf->c = NULL;
+
+ pf->cl = cl;
+ pf->input_wanted = true;
+ pf->ready = start_ready;
+
+ pf->socks_state = SOCKS_NONE;
+ pf->hostname = NULL;
+ pf->port = 0;
+
+ *plug = &pf->plug;
+ return &pf->chan;
+}
+
+void portfwd_raw_free(Channel *pfchan)
+{
+ struct PortForwarding *pf;
+ assert(pfchan->vt == &PortForwarding_channelvt);
+ pf = container_of(pfchan, struct PortForwarding, chan);
+ free_portfwd_state(pf);
+}
+
+void portfwd_raw_setup(Channel *pfchan, Socket *s, SshChannel *sc)
+{
+ struct PortForwarding *pf;
+ assert(pfchan->vt == &PortForwarding_channelvt);
+ pf = container_of(pfchan, struct PortForwarding, chan);
+
+ pf->s = s;
+ pf->c = sc;
+}
+
+/*
+ * called when someone connects to the local port
+ */
+
+static int pfl_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
+{
+ struct PortListener *pl = container_of(p, struct PortListener, plug);
+ struct PortForwarding *pf;
+ Channel *chan;
+ Plug *plug;
+ Socket *s;
+ const char *err;
+
+ chan = portfwd_raw_new(pl->cl, &plug, false);
+ s = constructor(ctx, plug);
+ if ((err = sk_socket_error(s)) != NULL) {
+ portfwd_raw_free(chan);
+ return 1;
+ }
+
+ pf = container_of(chan, struct PortForwarding, chan);
+
+ if (pl->is_dynamic) {
+ pf->s = s;
+ pf->socks_state = SOCKS_INITIAL;
+ pf->socksbuf = strbuf_new();
+ pf->socksbuf_consumed = 0;
+ pf->port = 0; /* "hostname" buffer is so far empty */
+ sk_set_frozen(s, false); /* we want to receive SOCKS _now_! */
+ } else {
+ pf->hostname = dupstr(pl->hostname);
+ pf->port = pl->port;
+ portfwd_raw_setup(
+ chan, s,
+ wrap_lportfwd_open(pl->cl, pf->hostname, pf->port, s, &pf->chan));
+ }
+
+ return 0;
+}
+
+static const PlugVtable PortListener_plugvt = {
+ .log = pfl_log,
+ .closing = pfl_closing,
+ .accepting = pfl_accepting,
+};
+
+/*
+ * Add a new port-forwarding listener from srcaddr:port -> desthost:destport.
+ *
+ * desthost == NULL indicates dynamic SOCKS port forwarding.
+ *
+ * On success, returns NULL and fills in *pl_ret. On error, returns a
+ * dynamically allocated error message string.
+ */
+static char *pfl_listen(const char *desthost, int destport,
+ const char *srcaddr, int port,
+ ConnectionLayer *cl, Conf *conf,
+ struct PortListener **pl_ret, int address_family)
+{
+ const char *err;
+ struct PortListener *pl;
+
+ /*
+ * Open socket.
+ */
+ pl = *pl_ret = new_portlistener_state();
+ pl->plug.vt = &PortListener_plugvt;
+ if (desthost) {
+ pl->hostname = dupstr(desthost);
+ pl->port = destport;
+ pl->is_dynamic = false;
+ } else
+ pl->is_dynamic = true;
+ pl->cl = cl;
+
+ pl->s = new_listener(srcaddr, port, &pl->plug,
+ !conf_get_bool(conf, CONF_lport_acceptall),
+ conf, address_family);
+ if ((err = sk_socket_error(pl->s)) != NULL) {
+ char *err_ret = dupstr(err);
+ sk_close(pl->s);
+ free_portlistener_state(pl);
+ *pl_ret = NULL;
+ return err_ret;
+ }
+
+ return NULL;
+}
+
+static char *pfd_log_close_msg(Channel *chan)
+{
+ return dupstr("Forwarded port closed");
+}
+
+static void pfd_close(struct PortForwarding *pf)
+{
+ if (!pf)
+ return;
+
+ sk_close(pf->s);
+ free_portfwd_state(pf);
+}
+
+/*
+ * Terminate a listener.
+ */
+static void pfl_terminate(struct PortListener *pl)
+{
+ if (!pl)
+ return;
+
+ sk_close(pl->s);
+ free_portlistener_state(pl);
+}
+
+static void pfd_set_input_wanted(Channel *chan, bool wanted)
+{
+ assert(chan->vt == &PortForwarding_channelvt);
+ PortForwarding *pf = container_of(chan, PortForwarding, chan);
+ pf->input_wanted = wanted;
+ sk_set_frozen(pf->s, !pf->input_wanted);
+}
+
+static void pfd_chan_free(Channel *chan)
+{
+ assert(chan->vt == &PortForwarding_channelvt);
+ PortForwarding *pf = container_of(chan, PortForwarding, chan);
+ pfd_close(pf);
+}
+
+/*
+ * Called to send data down the raw connection.
+ */
+static size_t pfd_send(
+ Channel *chan, bool is_stderr, const void *data, size_t len)
+{
+ assert(chan->vt == &PortForwarding_channelvt);
+ PortForwarding *pf = container_of(chan, PortForwarding, chan);
+ return sk_write(pf->s, data, len);
+}
+
+static void pfd_send_eof(Channel *chan)
+{
+ assert(chan->vt == &PortForwarding_channelvt);
+ PortForwarding *pf = container_of(chan, PortForwarding, chan);
+ sk_write_eof(pf->s);
+}
+
+static void pfd_open_confirmation(Channel *chan)
+{
+ assert(chan->vt == &PortForwarding_channelvt);
+ PortForwarding *pf = container_of(chan, PortForwarding, chan);
+
+ pf->ready = true;
+ sk_set_frozen(pf->s, false);
+ sk_write(pf->s, NULL, 0);
+ if (pf->socksbuf) {
+ sshfwd_write(pf->c, pf->socksbuf->u + pf->socksbuf_consumed,
+ pf->socksbuf->len - pf->socksbuf_consumed);
+ strbuf_free(pf->socksbuf);
+ pf->socksbuf = NULL;
+ }
+}
+
+static void pfd_open_failure(Channel *chan, const char *errtext)
+{
+ assert(chan->vt == &PortForwarding_channelvt);
+ PortForwarding *pf = container_of(chan, PortForwarding, chan);
+
+ logeventf(pf->cl->logctx,
+ "Forwarded connection refused by remote%s%s",
+ errtext ? ": " : "", errtext ? errtext : "");
+}
+
+/* ----------------------------------------------------------------------
+ * Code to manage the complete set of currently active port
+ * forwardings, and update it from Conf.
+ */
+
+struct PortFwdRecord {
+ enum { DESTROY, KEEP, CREATE } status;
+ int type;
+ unsigned sport, dport;
+ char *saddr, *daddr;
+ char *sserv, *dserv;
+ struct ssh_rportfwd *remote;
+ int addressfamily;
+ struct PortListener *local;
+};
+
+static int pfr_cmp(void *av, void *bv)
+{
+ PortFwdRecord *a = (PortFwdRecord *) av;
+ PortFwdRecord *b = (PortFwdRecord *) bv;
+ int i;
+ if (a->type > b->type)
+ return +1;
+ if (a->type < b->type)
+ return -1;
+ if (a->addressfamily > b->addressfamily)
+ return +1;
+ if (a->addressfamily < b->addressfamily)
+ return -1;
+ if ( (i = nullstrcmp(a->saddr, b->saddr)) != 0)
+ return i < 0 ? -1 : +1;
+ if (a->sport > b->sport)
+ return +1;
+ if (a->sport < b->sport)
+ return -1;
+ if (a->type != 'D') {
+ if ( (i = nullstrcmp(a->daddr, b->daddr)) != 0)
+ return i < 0 ? -1 : +1;
+ if (a->dport > b->dport)
+ return +1;
+ if (a->dport < b->dport)
+ return -1;
+ }
+ return 0;
+}
+
+static void pfr_free(PortFwdRecord *pfr)
+{
+ /* Dispose of any listening socket. */
+ if (pfr->local)
+ pfl_terminate(pfr->local);
+
+ sfree(pfr->saddr);
+ sfree(pfr->daddr);
+ sfree(pfr->sserv);
+ sfree(pfr->dserv);
+ sfree(pfr);
+}
+
+struct PortFwdManager {
+ ConnectionLayer *cl;
+ Conf *conf;
+ tree234 *forwardings;
+};
+
+PortFwdManager *portfwdmgr_new(ConnectionLayer *cl)
+{
+ PortFwdManager *mgr = snew(PortFwdManager);
+
+ mgr->cl = cl;
+ mgr->conf = NULL;
+ mgr->forwardings = newtree234(pfr_cmp);
+
+ return mgr;
+}
+
+void portfwdmgr_close(PortFwdManager *mgr, PortFwdRecord *pfr)
+{
+ PortFwdRecord *realpfr = del234(mgr->forwardings, pfr);
+ if (realpfr == pfr)
+ pfr_free(pfr);
+}
+
+void portfwdmgr_close_all(PortFwdManager *mgr)
+{
+ PortFwdRecord *pfr;
+
+ while ((pfr = delpos234(mgr->forwardings, 0)) != NULL)
+ pfr_free(pfr);
+}
+
+void portfwdmgr_free(PortFwdManager *mgr)
+{
+ portfwdmgr_close_all(mgr);
+ freetree234(mgr->forwardings);
+ if (mgr->conf)
+ conf_free(mgr->conf);
+ sfree(mgr);
+}
+
+void portfwdmgr_config(PortFwdManager *mgr, Conf *conf)
+{
+ PortFwdRecord *pfr;
+ int i;
+ char *key, *val;
+
+ if (mgr->conf)
+ conf_free(mgr->conf);
+ mgr->conf = conf_copy(conf);
+
+ /*
+ * Go through the existing port forwardings and tag them
+ * with status==DESTROY. Any that we want to keep will be
+ * re-enabled (status==KEEP) as we go through the
+ * configuration and find out which bits are the same as
+ * they were before.
+ */
+ for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++)
+ pfr->status = DESTROY;
+
+ for (val = conf_get_str_strs(conf, CONF_portfwd, NULL, &key);
+ val != NULL;
+ val = conf_get_str_strs(conf, CONF_portfwd, key, &key)) {
+ char *kp, *kp2, *vp, *vp2;
+ char address_family, type;
+ int sport, dport, sserv, dserv;
+ char *sports, *dports, *saddr, *host;
+
+ kp = key;
+
+ address_family = 'A';
+ type = 'L';
+ if (*kp == 'A' || *kp == '4' || *kp == '6')
+ address_family = *kp++;
+ if (*kp == 'L' || *kp == 'R')
+ type = *kp++;
+
+ if ((kp2 = host_strchr(kp, ':')) != NULL) {
+ /*
+ * There's a colon in the middle of the source port
+ * string, which means that the part before it is
+ * actually a source address.
+ */
+ char *saddr_tmp = dupprintf("%.*s", (int)(kp2 - kp), kp);
+ saddr = host_strduptrim(saddr_tmp);
+ sfree(saddr_tmp);
+ sports = kp2+1;
+ } else {
+ saddr = NULL;
+ sports = kp;
+ }
+ sport = atoi(sports);
+ sserv = 0;
+ if (sport == 0) {
+ sserv = 1;
+ sport = net_service_lookup(sports);
+ if (!sport) {
+ logeventf(mgr->cl->logctx, "Service lookup failed for source"
+ " port \"%s\"", sports);
+ }
+ }
+
+ if (type == 'L' && !strcmp(val, "D")) {
+ /* dynamic forwarding */
+ host = NULL;
+ dports = NULL;
+ dport = -1;
+ dserv = 0;
+ type = 'D';
+ } else {
+ /* ordinary forwarding */
+ vp = val;
+ vp2 = vp + host_strcspn(vp, ":");
+ host = dupprintf("%.*s", (int)(vp2 - vp), vp);
+ if (*vp2)
+ vp2++;
+ dports = vp2;
+ dport = atoi(dports);
+ dserv = 0;
+ if (dport == 0) {
+ dserv = 1;
+ dport = net_service_lookup(dports);
+ if (!dport) {
+ logeventf(mgr->cl->logctx,
+ "Service lookup failed for destination"
+ " port \"%s\"", dports);
+ }
+ }
+ }
+
+ if (sport && dport) {
+ /* Set up a description of the source port. */
+ pfr = snew(PortFwdRecord);
+ pfr->type = type;
+ pfr->saddr = saddr;
+ pfr->sserv = sserv ? dupstr(sports) : NULL;
+ pfr->sport = sport;
+ pfr->daddr = host;
+ pfr->dserv = dserv ? dupstr(dports) : NULL;
+ pfr->dport = dport;
+ pfr->local = NULL;
+ pfr->remote = NULL;
+ pfr->addressfamily = (address_family == '4' ? ADDRTYPE_IPV4 :
+ address_family == '6' ? ADDRTYPE_IPV6 :
+ ADDRTYPE_UNSPEC);
+
+ PortFwdRecord *existing = add234(mgr->forwardings, pfr);
+ if (existing != pfr) {
+ if (existing->status == DESTROY) {
+ /*
+ * We already have a port forwarding up and running
+ * with precisely these parameters. Hence, no need
+ * to do anything; simply re-tag the existing one
+ * as KEEP.
+ */
+ existing->status = KEEP;
+ }
+ /*
+ * Anything else indicates that there was a duplicate
+ * in our input, which we'll silently ignore.
+ */
+ pfr_free(pfr);
+ } else {
+ pfr->status = CREATE;
+ }
+ } else {
+ sfree(saddr);
+ sfree(host);
+ }
+ }
+
+ /*
+ * Now go through and destroy any port forwardings which were
+ * not re-enabled.
+ */
+ for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++) {
+ if (pfr->status == DESTROY) {
+ char *message;
+
+ message = dupprintf("%s port forwarding from %s%s%d",
+ pfr->type == 'L' ? "local" :
+ pfr->type == 'R' ? "remote" : "dynamic",
+ pfr->saddr ? pfr->saddr : "",
+ pfr->saddr ? ":" : "",
+ pfr->sport);
+
+ if (pfr->type != 'D') {
+ char *msg2 = dupprintf("%s to %s:%d", message,
+ pfr->daddr, pfr->dport);
+ sfree(message);
+ message = msg2;
+ }
+
+ logeventf(mgr->cl->logctx, "Cancelling %s", message);
+ sfree(message);
+
+ /* pfr->remote or pfr->local may be NULL if setting up a
+ * forwarding failed. */
+ if (pfr->remote) {
+ /*
+ * Cancel the port forwarding at the server
+ * end.
+ *
+ * Actually closing the listening port on the server
+ * side may fail - because in SSH-1 there's no message
+ * in the protocol to request it!
+ *
+ * Instead, we simply remove the record of the
+ * forwarding from our local end, so that any
+ * connections the server tries to make on it are
+ * rejected.
+ */
+ ssh_rportfwd_remove(mgr->cl, pfr->remote);
+ pfr->remote = NULL;
+ } else if (pfr->local) {
+ pfl_terminate(pfr->local);
+ pfr->local = NULL;
+ }
+
+ delpos234(mgr->forwardings, i);
+ pfr_free(pfr);
+ i--; /* so we don't skip one in the list */
+ }
+ }
+
+ /*
+ * And finally, set up any new port forwardings (status==CREATE).
+ */
+ for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++) {
+ if (pfr->status == CREATE) {
+ char *sportdesc, *dportdesc;
+ sportdesc = dupprintf("%s%s%s%s%d%s",
+ pfr->saddr ? pfr->saddr : "",
+ pfr->saddr ? ":" : "",
+ pfr->sserv ? pfr->sserv : "",
+ pfr->sserv ? "(" : "",
+ pfr->sport,
+ pfr->sserv ? ")" : "");
+ if (pfr->type == 'D') {
+ dportdesc = NULL;
+ } else {
+ dportdesc = dupprintf("%s:%s%s%d%s",
+ pfr->daddr,
+ pfr->dserv ? pfr->dserv : "",
+ pfr->dserv ? "(" : "",
+ pfr->dport,
+ pfr->dserv ? ")" : "");
+ }
+
+ if (pfr->type == 'L') {
+ char *err = pfl_listen(pfr->daddr, pfr->dport,
+ pfr->saddr, pfr->sport,
+ mgr->cl, conf, &pfr->local,
+ pfr->addressfamily);
+
+ logeventf(mgr->cl->logctx,
+ "Local %sport %s forwarding to %s%s%s",
+ pfr->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " :
+ pfr->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "",
+ sportdesc, dportdesc,
+ err ? " failed: " : "", err ? err : "");
+ if (err)
+ sfree(err);
+ } else if (pfr->type == 'D') {
+ char *err = pfl_listen(NULL, -1, pfr->saddr, pfr->sport,
+ mgr->cl, conf, &pfr->local,
+ pfr->addressfamily);
+
+ logeventf(mgr->cl->logctx,
+ "Local %sport %s SOCKS dynamic forwarding%s%s",
+ pfr->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " :
+ pfr->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "",
+ sportdesc,
+ err ? " failed: " : "", err ? err : "");
+
+ if (err)
+ sfree(err);
+ } else {
+ const char *shost;
+
+ if (pfr->saddr) {
+ shost = pfr->saddr;
+ } else if (conf_get_bool(conf, CONF_rport_acceptall)) {
+ shost = "";
+ } else {
+ shost = "localhost";
+ }
+
+ pfr->remote = ssh_rportfwd_alloc(
+ mgr->cl, shost, pfr->sport, pfr->daddr, pfr->dport,
+ pfr->addressfamily, sportdesc, pfr, NULL);
+
+ if (!pfr->remote) {
+ logeventf(mgr->cl->logctx,
+ "Duplicate remote port forwarding to %s:%d",
+ pfr->daddr, pfr->dport);
+ pfr_free(pfr);
+ } else {
+ logeventf(mgr->cl->logctx, "Requesting remote port %s"
+ " forward to %s", sportdesc, dportdesc);
+ }
+ }
+ sfree(sportdesc);
+ sfree(dportdesc);
+ }
+ }
+}
+
+bool portfwdmgr_listen(PortFwdManager *mgr, const char *host, int port,
+ const char *keyhost, int keyport, Conf *conf)
+{
+ PortFwdRecord *pfr;
+
+ pfr = snew(PortFwdRecord);
+ pfr->type = 'L';
+ pfr->saddr = host ? dupstr(host) : NULL;
+ pfr->daddr = keyhost ? dupstr(keyhost) : NULL;
+ pfr->sserv = pfr->dserv = NULL;
+ pfr->sport = port;
+ pfr->dport = keyport;
+ pfr->local = NULL;
+ pfr->remote = NULL;
+ pfr->addressfamily = ADDRTYPE_UNSPEC;
+
+ PortFwdRecord *existing = add234(mgr->forwardings, pfr);
+ if (existing != pfr) {
+ /*
+ * We had this record already. Return failure.
+ */
+ pfr_free(pfr);
+ return false;
+ }
+
+ char *err = pfl_listen(keyhost, keyport, host, port,
+ mgr->cl, conf, &pfr->local, pfr->addressfamily);
+ logeventf(mgr->cl->logctx,
+ "%s on port %s:%d to forward to client%s%s",
+ err ? "Failed to listen" : "Listening", host, port,
+ err ? ": " : "", err ? err : "");
+ if (err) {
+ sfree(err);
+ del234(mgr->forwardings, pfr);
+ pfr_free(pfr);
+ return false;
+ }
+
+ return true;
+}
+
+bool portfwdmgr_unlisten(PortFwdManager *mgr, const char *host, int port)
+{
+ PortFwdRecord pfr_key;
+
+ pfr_key.type = 'L';
+ /* Safe to cast the const away here, because it will only be used
+ * by pfr_cmp, which won't write to the string */
+ pfr_key.saddr = pfr_key.daddr = (char *)host;
+ pfr_key.sserv = pfr_key.dserv = NULL;
+ pfr_key.sport = pfr_key.dport = port;
+ pfr_key.local = NULL;
+ pfr_key.remote = NULL;
+ pfr_key.addressfamily = ADDRTYPE_UNSPEC;
+
+ PortFwdRecord *pfr = del234(mgr->forwardings, &pfr_key);
+
+ if (!pfr)
+ return false;
+
+ logeventf(mgr->cl->logctx, "Closing listening port %s:%d", host, port);
+
+ pfr_free(pfr);
+ return true;
+}
+
+/*
+ * Called when receiving a PORT OPEN from the server to make a
+ * connection to a destination host.
+ *
+ * On success, returns NULL and fills in *pf_ret. On error, returns a
+ * dynamically allocated error message string.
+ */
+char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret,
+ char *hostname, int port, SshChannel *c,
+ int addressfamily)
+{
+ SockAddr *addr;
+ const char *err;
+ char *dummy_realhost = NULL;
+ struct PortForwarding *pf;
+
+ /*
+ * Try to find host.
+ */
+ addr = name_lookup(hostname, port, &dummy_realhost, mgr->conf,
+ addressfamily, NULL, NULL);
+ if ((err = sk_addr_error(addr)) != NULL) {
+ char *err_ret = dupstr(err);
+ sk_addr_free(addr);
+ sfree(dummy_realhost);
+ return err_ret;
+ }
+
+ /*
+ * Open socket.
+ */
+ pf = new_portfwd_state();
+ *chan_ret = &pf->chan;
+ pf->plug.vt = &PortForwarding_plugvt;
+ pf->chan.initial_fixed_window_size = 0;
+ pf->chan.vt = &PortForwarding_channelvt;
+ pf->input_wanted = true;
+ pf->ready = true;
+ pf->c = c;
+ pf->cl = mgr->cl;
+ pf->socks_state = SOCKS_NONE;
+
+ pf->s = new_connection(addr, dummy_realhost, port,
+ false, true, false, false, &pf->plug, mgr->conf,
+ NULL);
+ sfree(dummy_realhost);
+ if ((err = sk_socket_error(pf->s)) != NULL) {
+ char *err_ret = dupstr(err);
+ sk_close(pf->s);
+ free_portfwd_state(pf);
+ *chan_ret = NULL;
+ return err_ret;
+ }
+
+ return NULL;
+}
diff --git a/ssh/ppl.h b/ssh/ppl.h
new file mode 100644
index 00000000..78b08efa
--- /dev/null
+++ b/ssh/ppl.h
@@ -0,0 +1,175 @@
+/*
+ * Abstraction of the various layers of SSH packet-level protocol,
+ * general enough to take in all three of the main SSH-2 layers and
+ * both of the SSH-1 phases.
+ */
+
+#ifndef PUTTY_SSHPPL_H
+#define PUTTY_SSHPPL_H
+
+typedef void (*packet_handler_fn_t)(PacketProtocolLayer *ppl, PktIn *pktin);
+typedef struct PacketProtocolLayerVtable PacketProtocolLayerVtable;
+
+struct PacketProtocolLayerVtable {
+ void (*free)(PacketProtocolLayer *);
+ void (*process_queue)(PacketProtocolLayer *ppl);
+ bool (*get_specials)(
+ PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx);
+ void (*special_cmd)(
+ PacketProtocolLayer *ppl, SessionSpecialCode code, int arg);
+ void (*reconfigure)(PacketProtocolLayer *ppl, Conf *conf);
+ size_t (*queued_data_size)(PacketProtocolLayer *ppl);
+
+ /* Protocol-level name of this layer. */
+ const char *name;
+};
+
+struct PacketProtocolLayer {
+ const struct PacketProtocolLayerVtable *vt;
+
+ /* Link to the underlying SSH BPP. */
+ BinaryPacketProtocol *bpp;
+
+ /* Queue from which the layer receives its input packets, and one
+ * to put its output packets on. */
+ PktInQueue *in_pq;
+ PktOutQueue *out_pq;
+
+ /* Idempotent callback that in_pq will be linked to, causing a
+ * call to the process_queue method. in_pq points to this, so it
+ * will be automatically triggered by pushing things on the
+ * layer's input queue, but it can also be triggered on purpose. */
+ IdempotentCallback ic_process_queue;
+
+ /* Owner's pointer to this layer. Permits a layer to unilaterally
+ * abdicate in favour of a replacement, by overwriting this
+ * pointer and then freeing itself. */
+ PacketProtocolLayer **selfptr;
+
+ /* Logging and error-reporting facilities. */
+ LogContext *logctx;
+ Seat *seat; /* for dialog boxes, session output etc */
+ Interactor *interactor; /* for ppl_get_iseat */
+ Ssh *ssh; /* for session termination + assorted connection-layer ops */
+
+ /* Known bugs in the remote implementation. */
+ unsigned remote_bugs;
+};
+
+static inline void ssh_ppl_process_queue(PacketProtocolLayer *ppl)
+{ ppl->vt->process_queue(ppl); }
+static inline bool ssh_ppl_get_specials(
+ PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
+{ return ppl->vt->get_specials(ppl, add_special, ctx); }
+static inline void ssh_ppl_special_cmd(
+ PacketProtocolLayer *ppl, SessionSpecialCode code, int arg)
+{ ppl->vt->special_cmd(ppl, code, arg); }
+static inline void ssh_ppl_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
+{ ppl->vt->reconfigure(ppl, conf); }
+static inline size_t ssh_ppl_queued_data_size(PacketProtocolLayer *ppl)
+{ return ppl->vt->queued_data_size(ppl); }
+
+static inline InteractionReadySeat ppl_get_iseat(PacketProtocolLayer *ppl)
+{ return interactor_announce(ppl->interactor); }
+
+/* ssh_ppl_free is more than just a macro wrapper on the vtable; it
+ * does centralised parts of the freeing too. */
+void ssh_ppl_free(PacketProtocolLayer *ppl);
+
+/* Helper routine to point a PPL at its input and output queues. Also
+ * sets up the IdempotentCallback on the input queue to trigger a call
+ * to process_queue whenever packets are added to it. */
+void ssh_ppl_setup_queues(PacketProtocolLayer *ppl,
+ PktInQueue *inq, PktOutQueue *outq);
+
+/* Routine a PPL can call to abdicate in favour of a replacement, by
+ * overwriting ppl->selfptr. Has the side effect of freeing 'old', so
+ * if 'old' actually called this (which is likely) then it should
+ * avoid dereferencing itself on return from this function! */
+void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new);
+
+/* Default implementation of queued_data_size, which just adds up the
+ * sizes of all the packets in pq_out. A layer can override this if it
+ * has other things to take into account as well. */
+size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl);
+
+PacketProtocolLayer *ssh1_login_new(
+ Conf *conf, const char *host, int port,
+ PacketProtocolLayer *successor_layer);
+PacketProtocolLayer *ssh1_connection_new(
+ Ssh *ssh, Conf *conf, bufchain *user_input, ConnectionLayer **cl_out);
+
+struct DataTransferStats;
+struct ssh_connection_shared_gss_state;
+PacketProtocolLayer *ssh2_transport_new(
+ Conf *conf, const char *host, int port, const char *fullhostname,
+ const char *client_greeting, const char *server_greeting,
+ struct ssh_connection_shared_gss_state *shgss,
+ struct DataTransferStats *stats, PacketProtocolLayer *higher_layer,
+ const SshServerConfig *ssc);
+PacketProtocolLayer *ssh2_userauth_new(
+ PacketProtocolLayer *successor_layer,
+ const char *hostname, int port, const char *fullhostname,
+ Filename *keyfile, Filename *detached_cert,
+ bool show_banner, bool tryagent, bool notrivialauth,
+ const char *default_username, bool change_username,
+ bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth,
+ bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss,
+ const char *auth_plugin);
+PacketProtocolLayer *ssh2_connection_new(
+ Ssh *ssh, ssh_sharing_state *connshare, bool is_simple,
+ Conf *conf, const char *peer_verstring, bufchain *user_input,
+ ConnectionLayer **cl_out);
+
+/* Can't put this in the userauth constructor without having a
+ * dependency loop at setup time (transport and userauth can't _both_
+ * be constructed second and given a pointer to the other). */
+void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth,
+ PacketProtocolLayer *transport);
+
+/* Convenience macro for protocol layers to send formatted strings to
+ * the Event Log. Assumes a function parameter called 'ppl' is in
+ * scope. */
+#define ppl_logevent(...) ( \
+ logevent_and_free((ppl)->logctx, dupprintf(__VA_ARGS__)))
+
+/* Convenience macro for protocol layers to send formatted strings to
+ * the terminal. Also expects 'ppl' to be in scope. */
+#define ppl_printf(...) \
+ ssh_ppl_user_output_string_and_free(ppl, dupprintf(__VA_ARGS__))
+void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text);
+
+/* Methods for userauth to communicate back to the transport layer */
+ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ssh2_transport_ptr);
+void ssh2_transport_notify_auth_done(PacketProtocolLayer *ssh2_transport_ptr);
+
+/* Shared method between ssh2 layers (defined in transport2.c) to
+ * handle the common packets between login and connection: DISCONNECT,
+ * DEBUG and IGNORE. Those messages are handled by the ssh2transport
+ * layer if we have one, but in bare ssh2-connection mode they have to
+ * be handled by ssh2connection. */
+bool ssh2_common_filter_queue(PacketProtocolLayer *ppl);
+
+/* Method for making a prompts_t in such a way that it will install a
+ * callback that causes this PPL's process_queue method to be called
+ * when asynchronous prompt input completes. */
+prompts_t *ssh_ppl_new_prompts(PacketProtocolLayer *ppl);
+
+/* Methods for ssh1login to pass protocol flags to ssh1connection */
+void ssh1_connection_set_protoflags(
+ PacketProtocolLayer *ppl, int local, int remote);
+
+/* Shared get_specials method between the two ssh1 layers */
+bool ssh1_common_get_specials(PacketProtocolLayer *, add_special_fn_t, void *);
+
+/* Other shared functions between ssh1 layers */
+bool ssh1_common_filter_queue(PacketProtocolLayer *ppl);
+void ssh1_compute_session_id(
+ unsigned char *session_id, const unsigned char *cookie,
+ RSAKey *hostkey, RSAKey *servkey);
+
+/* Method used by the SSH server */
+void ssh2_transport_provide_hostkeys(PacketProtocolLayer *ssh2_transport_ptr,
+ ssh_key *const *hostkeys, int nhostkeys);
+
+#endif /* PUTTY_SSHPPL_H */
diff --git a/ssh/scpserver.c b/ssh/scpserver.c
new file mode 100644
index 00000000..15633ecb
--- /dev/null
+++ b/ssh/scpserver.c
@@ -0,0 +1,1399 @@
+/*
+ * Server side of the old-school SCP protocol.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sshcr.h"
+#include "channel.h"
+#include "sftp.h"
+
+/*
+ * I think it's worth actually documenting my understanding of what
+ * this protocol _is_, since I don't know of any other documentation
+ * of it anywhere.
+ *
+ * Format of data stream
+ * ---------------------
+ *
+ * The sending side of an SCP connection - the client, if you're
+ * uploading files, or the server if you're downloading - sends a data
+ * stream consisting of a sequence of 'commands', or header records,
+ * or whatever you want to call them, interleaved with file data.
+ *
+ * Each command starts with a letter indicating what type it is, and
+ * ends with a \n.
+ *
+ * The 'C' command introduces an actual file. It is followed by an
+ * octal file-permissions mask, then a space, then a decimal file
+ * size, then a space, then the file name up to the termating newline.
+ * For example, "C0644 12345 filename.txt\n" would be a plausible C
+ * command.
+ *
+ * After the 'C' command, the sending side will transmit exactly as
+ * many bytes of file data as specified by the size field in the
+ * header line, followed by a single zero byte.
+ *
+ * The 'D' command introduces a subdirectory. Its format is identical
+ * to 'C', including the size field, but the size field is sent as
+ * zero.
+ *
+ * After the 'D' command, all subsequent C and D commands are taken to
+ * indicate files that should be placed inside that subdirectory,
+ * until a terminating 'E' command.
+ *
+ * The 'E' command indicates the end of a subdirectory. It has no
+ * arguments at all (its format is always just "E\n"). After the E
+ * command, the receiver should revert to placing further downloaded
+ * files in whatever directory it was placing them before the
+ * subdirectory opened by the just-closed D.
+ *
+ * D and E commands match like parentheses: if you send, say,
+ *
+ * C0644 123 foo.txt ( followed by data )
+ * D0755 0 subdir
+ * C0644 123 bar.txt ( followed by data )
+ * D0755 0 subsubdir
+ * C0644 123 baz.txt ( followed by data )
+ * E
+ * C0644 123 quux.txt ( followed by data )
+ * E
+ * C0644 123 wibble.txt ( followed by data )
+ *
+ * then foo.txt, subdir and wibble.txt go in the top-level destination
+ * directory; bar.txt, subsubdir and quux.txt go in 'subdir'; and
+ * baz.txt goes in 'subdir/subsubdir'.
+ *
+ * The sender terminates the data stream with EOF when it has no more
+ * files to send. I believe it is not _required_ for all D to be
+ * closed by an E before this happens - you can elide a trailing
+ * sequence of E commands without provoking an error message from the
+ * receiver.
+ *
+ * Finally, the 'T' command is sent immediately before a C or D. It is
+ * followed by four space-separated decimal integers giving an mtime
+ * and atime to be applied to the file or directory created by the
+ * following C or D command. The first two integers give the mtime,
+ * encoded as seconds and microseconds (respectively) since the Unix
+ * epoch; the next two give the atime, encoded similarly. So
+ * "T1540373455 0 1540373457 0\n" is an example of a valid T command.
+ *
+ * Acknowledgments
+ * ---------------
+ *
+ * The sending side waits for an ack from the receiving side before
+ * sending each command; before beginning to send the file data
+ * following a C command; and before sending the final EOF.
+ *
+ * (In particular, the receiving side is expected to send an initial
+ * ack before _anything_ is sent.)
+ *
+ * Normally an ack consists of a single zero byte. It's also allowable
+ * to send a byte with value 1 or 2 followed by a \n-terminated error
+ * message (where 1 means a non-fatal error and 2 means a fatal one).
+ * I have to suppose that sending an error message from client to
+ * server is of limited use, but apparently it's allowed.
+ *
+ * Initiation
+ * ----------
+ *
+ * The protocol is begun by the client sending a command string to the
+ * server via the SSH-2 "exec" request (or the analogous
+ * SSH1_CMSG_EXEC_CMD), which indicates that this is an scp session
+ * rather than any other thing; specifies the direction of transfer;
+ * says what file(s) are to be sent by the server, or where the server
+ * should put files that the client is about to send; and a couple of
+ * other options.
+ *
+ * The command string takes the following form:
+ *
+ * Start with prefix "scp ", indicating that this is an SCP command at
+ * all. Otherwise it's a request to run some completely different
+ * command in the SSH session.
+ *
+ * Next the command can contain zero or more of the following options,
+ * each followed by a space:
+ *
+ * "-v" turns on verbose server diagnostics. Of course a server is not
+ * required to actually produce any, but this is an invitation for it
+ * to send any it might have available. Diagnostics are free-form, and
+ * sent as SSH standard-error extended data, so that they are separate
+ * from the actual data stream as described above.
+ *
+ * (Servers can send standard-error output anyway if they like, and in
+ * case of an actual error, they probably will with or without -v.)
+ *
+ * "-r" indicates recursive file transfer, i.e. potentially including
+ * subdirectories. For a download, this indicates that the client is
+ * willing to receive subdirectories (a D/E command pair bracketing
+ * further files and subdirs); without it, the server should only send
+ * C commands for individual files, followed by EOF.
+ *
+ * This flag must also be specified for a recursive upload, because I
+ * believe at least one server will reject D/E pairs sent by the
+ * client if the command didn't have -r in it. (Probably a consequence
+ * of sharing code between download and upload.)
+ *
+ * "-p" means preserve file times. In a download, this requests the
+ * server to send a T command before each C or D. I don't know whether
+ * any server will insist on having seen this option from the client
+ * before accepting T commands in an upload, but it is probably
+ * sensible to send it anyway.
+ *
+ * "-d", in an upload, means that the destination pathname (see below)
+ * is expected to be a directory, and that uploaded files (and
+ * subdirs) should be put inside it. Without -d, the semantics are
+ * that _if_ the destination exists and is a directory, then files
+ * will be put in it, whereas if it is not, then just a single file
+ * (or subdir) upload is expected, which will be placed at that exact
+ * pathname.
+ *
+ * In a download, I observe that clients tend to send -d if they are
+ * requesting multiple files or a wildcard, but as far as I know,
+ * servers ignore it.
+ *
+ * After all those optional options, there is a single mandatory
+ * option indicating the direction of transfer, which is either "-f"
+ * or "-t". "-f" indicates a download; "-t" indicates an upload.
+ *
+ * After that mandatory option, there is a single space, followed by
+ * the name(s) of files to transfer.
+ *
+ * This file name field is transmitted with NO QUOTING, in spite of
+ * the fact that a server will typically interpret it as a shell
+ * command. You'd think this couldn't possibly work, in the face of
+ * almost any filename with an interesting character in it - and you'd
+ * be right. Or rather (you might argue), it works 'as designed', but
+ * it's designed in a weird way, in that it's the user's
+ * responsibility to apply quoting on the client command line to get
+ * the filename through the shell that will decode things on the
+ * server side.
+ *
+ * But one effect of this is that if you issue a download command
+ * including a wildcard, say "scp -f somedir/foo*.txt", then the shell
+ * will expand the wildcard, and actually run the server-side scp
+ * program with multiple arguments, say "somedir/foo.txt
+ * somedir/quux.txt", leading to the download sending multiple C
+ * commands. This clearly _is_ intended: it's how a command such as
+ * 'scp server:somedir/foo*.txt destdir' can work at all.
+ *
+ * (You would think, given that, that it might also be legal to send
+ * multiple space-separated filenames in order to trigger a download
+ * of exactly those files. Given how scp is invoked in practice on a
+ * typical server, this would surely actually work, but my observation
+ * is that scp clients don't in fact try this - if you run OpenSSH's
+ * scp by saying 'scp server:foo server:bar destdir' then it will make
+ * two separate connections to the server for the two files, rather
+ * than sending a single space-separated remote command. PSCP won't
+ * even do that, and will make you do it in two separate runs.)
+ *
+ * So, some examples:
+ *
+ * - "scp -f filename.txt"
+ *
+ * Server should send a single C command (plus data) for that file.
+ * Client ought to ignore the filename in the C command, in favour
+ * of saving the file under the name implied by the user's command
+ * line.
+ *
+ * - "scp -f file*.txt"
+ *
+ * Server sends zero or more C commands, then EOF. Client will have
+ * been given a target directory to put them all in, and will name
+ * each one according to the name in the C command.
+ *
+ * (You'd like the client to validate the filenames against the
+ * wildcard it sent, to ensure a malicious server didn't try to
+ * overwrite some path like ".bashrc" when you thought you were
+ * downloading only normal text files. But wildcard semantics are
+ * chosen by the server, so this is essentially hopeless to do
+ * rigorously.)
+ *
+ * - "scp -f -r somedir"
+ *
+ * Assuming somedir is actually a directory, server sends a D/E
+ * pair, in between which are the contents of the directory
+ * (perhaps including further nested D/E pairs). Client probably
+ * ignores the name field of the outermost D
+ *
+ * - "scp -f -r some*wild*card*"
+ *
+ * Server sends multiple C or D-stuff-E, one for each top-level
+ * thing matching the wildcard, whether it's a file or a directory.
+ *
+ * - "scp -t -d some_dir"
+ *
+ * Client sends stuff, and server deposits each file at
+ * some_dir/<name from the C command>.
+ *
+ * - "scp -t some_path_name"
+ *
+ * Client sends one C command, and server deposits it at
+ * some_path_name itself, or in some_path_name/<name from C
+ * command>, depending whether some_path_name was already a
+ * directory or not.
+ */
+
+/*
+ * Here's a useful debugging aid: run over a binary file containing
+ * the complete contents of the sender's data stream (e.g. extracted
+ * by contrib/logparse.pl -d), it removes the file contents, leaving
+ * only the list of commands, so you can see what the server sent.
+ *
+ * perl -pe 'read ARGV,$x,1+$1 if/^C\S+ (\d+)/'
+ */
+
+/* ----------------------------------------------------------------------
+ * Shared system for receiving replies from the SftpServer, and
+ * putting them into a set of ordinary variables rather than
+ * marshalling them into actual SFTP reply packets that we'd only have
+ * to unmarshal again.
+ */
+
+typedef struct ScpReplyReceiver ScpReplyReceiver;
+struct ScpReplyReceiver {
+ bool err;
+ unsigned code;
+ char *errmsg;
+ struct fxp_attrs attrs;
+ ptrlen name, handle, data;
+
+ SftpReplyBuilder srb;
+};
+
+static void scp_reply_ok(SftpReplyBuilder *srb)
+{
+ ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb);
+ reply->err = false;
+}
+
+static void scp_reply_error(
+ SftpReplyBuilder *srb, unsigned code, const char *msg)
+{
+ ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb);
+ reply->err = true;
+ reply->code = code;
+ sfree(reply->errmsg);
+ reply->errmsg = dupstr(msg);
+}
+
+static void scp_reply_name_count(SftpReplyBuilder *srb, unsigned count)
+{
+ ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb);
+ reply->err = false;
+}
+
+static void scp_reply_full_name(
+ SftpReplyBuilder *srb, ptrlen name,
+ ptrlen longname, struct fxp_attrs attrs)
+{
+ ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb);
+ char *p;
+ reply->err = false;
+ sfree((void *)reply->name.ptr);
+ reply->name.ptr = p = mkstr(name);
+ reply->name.len = name.len;
+ reply->attrs = attrs;
+}
+
+static void scp_reply_simple_name(SftpReplyBuilder *srb, ptrlen name)
+{
+ ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb);
+ reply->err = false;
+}
+
+static void scp_reply_handle(SftpReplyBuilder *srb, ptrlen handle)
+{
+ ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb);
+ char *p;
+ reply->err = false;
+ sfree((void *)reply->handle.ptr);
+ reply->handle.ptr = p = mkstr(handle);
+ reply->handle.len = handle.len;
+}
+
+static void scp_reply_data(SftpReplyBuilder *srb, ptrlen data)
+{
+ ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb);
+ char *p;
+ reply->err = false;
+ sfree((void *)reply->data.ptr);
+ reply->data.ptr = p = mkstr(data);
+ reply->data.len = data.len;
+}
+
+static void scp_reply_attrs(
+ SftpReplyBuilder *srb, struct fxp_attrs attrs)
+{
+ ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb);
+ reply->err = false;
+ reply->attrs = attrs;
+}
+
+static const SftpReplyBuilderVtable ScpReplyReceiver_vt = {
+ .reply_ok = scp_reply_ok,
+ .reply_error = scp_reply_error,
+ .reply_simple_name = scp_reply_simple_name,
+ .reply_name_count = scp_reply_name_count,
+ .reply_full_name = scp_reply_full_name,
+ .reply_handle = scp_reply_handle,
+ .reply_data = scp_reply_data,
+ .reply_attrs = scp_reply_attrs,
+};
+
+static void scp_reply_setup(ScpReplyReceiver *reply)
+{
+ memset(reply, 0, sizeof(*reply));
+ reply->srb.vt = &ScpReplyReceiver_vt;
+}
+
+static void scp_reply_cleanup(ScpReplyReceiver *reply)
+{
+ sfree(reply->errmsg);
+ sfree((void *)reply->name.ptr);
+ sfree((void *)reply->handle.ptr);
+ sfree((void *)reply->data.ptr);
+}
+
+/* ----------------------------------------------------------------------
+ * Source end of the SCP protocol.
+ */
+
+#define SCP_MAX_BACKLOG 65536
+
+typedef struct ScpSource ScpSource;
+typedef struct ScpSourceStackEntry ScpSourceStackEntry;
+
+struct ScpSource {
+ SftpServer *sf;
+
+ int acks;
+ bool expect_newline, eof, throttled, finished;
+
+ SshChannel *sc;
+ ScpSourceStackEntry *head;
+ bool recursive;
+ bool send_file_times;
+
+ strbuf *pending_commands[3];
+ int n_pending_commands;
+
+ uint64_t file_offset, file_size;
+
+ ScpReplyReceiver reply;
+
+ ScpServer scpserver;
+};
+
+typedef enum ScpSourceNodeType ScpSourceNodeType;
+enum ScpSourceNodeType { SCP_ROOTPATH, SCP_NAME, SCP_READDIR, SCP_READFILE };
+
+struct ScpSourceStackEntry {
+ ScpSourceStackEntry *next;
+ ScpSourceNodeType type;
+ ptrlen pathname, handle;
+ const char *wildcard;
+ struct fxp_attrs attrs;
+};
+
+static void scp_source_push(ScpSource *scp, ScpSourceNodeType type,
+ ptrlen pathname, ptrlen handle,
+ const struct fxp_attrs *attrs, const char *wc)
+{
+ size_t wc_len = wc ? strlen(wc)+1 : 0;
+ ScpSourceStackEntry *node = snew_plus(
+ ScpSourceStackEntry, pathname.len + handle.len + wc_len);
+ char *namebuf = snew_plus_get_aux(node);
+ memcpy(namebuf, pathname.ptr, pathname.len);
+ node->pathname = make_ptrlen(namebuf, pathname.len);
+ memcpy(namebuf + pathname.len, handle.ptr, handle.len);
+ node->handle = make_ptrlen(namebuf + pathname.len, handle.len);
+ if (wc) {
+ strcpy(namebuf + pathname.len + handle.len, wc);
+ node->wildcard = namebuf + pathname.len + handle.len;
+ } else {
+ node->wildcard = NULL;
+ }
+ node->attrs = attrs ? *attrs : no_attrs;
+ node->type = type;
+ node->next = scp->head;
+ scp->head = node;
+}
+
+static char *scp_source_err_base(ScpSource *scp, const char *fmt, va_list ap)
+{
+ char *msg = dupvprintf(fmt, ap);
+ sshfwd_write_ext(scp->sc, true, msg, strlen(msg));
+ sshfwd_write_ext(scp->sc, true, "\012", 1);
+ return msg;
+}
+static PRINTF_LIKE(2, 3) void scp_source_err(
+ ScpSource *scp, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ sfree(scp_source_err_base(scp, fmt, ap));
+ va_end(ap);
+}
+static PRINTF_LIKE(2, 3) void scp_source_abort(
+ ScpSource *scp, const char *fmt, ...)
+{
+ va_list ap;
+ char *msg;
+
+ va_start(ap, fmt);
+ msg = scp_source_err_base(scp, fmt, ap);
+ va_end(ap);
+
+ sshfwd_send_exit_status(scp->sc, 1);
+ sshfwd_write_eof(scp->sc);
+ sshfwd_initiate_close(scp->sc, msg);
+
+ scp->finished = true;
+}
+
+static void scp_source_push_name(
+ ScpSource *scp, ptrlen pathname, struct fxp_attrs attrs, const char *wc)
+{
+ if (!(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
+ scp_source_err(scp, "unable to read file permissions for %.*s",
+ PTRLEN_PRINTF(pathname));
+ return;
+ }
+ if (attrs.permissions & PERMS_DIRECTORY) {
+ if (!scp->recursive && !wc) {
+ scp_source_err(scp, "%.*s: is a directory",
+ PTRLEN_PRINTF(pathname));
+ return;
+ }
+ } else {
+ if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) {
+ scp_source_err(scp, "unable to read file size for %.*s",
+ PTRLEN_PRINTF(pathname));
+ return;
+ }
+ }
+
+ scp_source_push(scp, SCP_NAME, pathname, PTRLEN_LITERAL(""), &attrs, wc);
+}
+
+static void scp_source_free(ScpServer *s);
+static size_t scp_source_send(ScpServer *s, const void *data, size_t length);
+static void scp_source_eof(ScpServer *s);
+static void scp_source_throttle(ScpServer *s, bool throttled);
+
+static const ScpServerVtable ScpSource_ScpServer_vt = {
+ .free = scp_source_free,
+ .send = scp_source_send,
+ .throttle = scp_source_throttle,
+ .eof = scp_source_eof,
+};
+
+static ScpSource *scp_source_new(
+ SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen pathname)
+{
+ ScpSource *scp = snew(ScpSource);
+ memset(scp, 0, sizeof(*scp));
+
+ scp->scpserver.vt = &ScpSource_ScpServer_vt;
+ scp_reply_setup(&scp->reply);
+ scp->sc = sc;
+ scp->sf = sftpsrv_new(sftpserver_vt);
+ scp->n_pending_commands = 0;
+
+ scp_source_push(scp, SCP_ROOTPATH, pathname, PTRLEN_LITERAL(""),
+ NULL, NULL);
+
+ return scp;
+}
+
+static void scp_source_free(ScpServer *s)
+{
+ ScpSource *scp = container_of(s, ScpSource, scpserver);
+ scp_reply_cleanup(&scp->reply);
+ while (scp->n_pending_commands > 0)
+ strbuf_free(scp->pending_commands[--scp->n_pending_commands]);
+ while (scp->head) {
+ ScpSourceStackEntry *node = scp->head;
+ scp->head = node->next;
+ sfree(node);
+ }
+
+ delete_callbacks_for_context(scp);
+
+ sfree(scp);
+}
+
+static void scp_source_send_E(ScpSource *scp)
+{
+ strbuf *cmd;
+
+ assert(scp->n_pending_commands == 0);
+
+ scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new();
+ put_fmt(cmd, "E\012");
+}
+
+static void scp_source_send_CD(
+ ScpSource *scp, char cmdchar,
+ struct fxp_attrs attrs, uint64_t size, ptrlen name)
+{
+ strbuf *cmd;
+
+ assert(scp->n_pending_commands == 0);
+
+ if (scp->send_file_times && (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) {
+ scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new();
+ /* Our SFTP-based filesystem API doesn't support microsecond times */
+ put_fmt(cmd, "T%lu 0 %lu 0\012", attrs.mtime, attrs.atime);
+ }
+
+ const char *slash;
+ while ((slash = memchr(name.ptr, '/', name.len)) != NULL)
+ name = make_ptrlen(
+ slash+1, name.len - (slash+1 - (const char *)name.ptr));
+
+ scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new();
+ put_fmt(cmd, "%c%04o %"PRIu64" %.*s\012", cmdchar,
+ (unsigned)(attrs.permissions & 07777),
+ size, PTRLEN_PRINTF(name));
+
+ if (cmdchar == 'C') {
+ /* We'll also wait for an ack before sending the file data,
+ * which we record by saving a zero-length 'command' to be
+ * sent after the C. */
+ scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new();
+ }
+}
+
+static void scp_source_process_stack(ScpSource *scp);
+static void scp_source_process_stack_cb(void *vscp)
+{
+ ScpSource *scp = (ScpSource *)vscp;
+ if (scp->finished)
+ return; /* this callback is out of date */
+ scp_source_process_stack(scp);
+}
+static void scp_requeue(ScpSource *scp)
+{
+ queue_toplevel_callback(scp_source_process_stack_cb, scp);
+}
+
+static void scp_source_process_stack(ScpSource *scp)
+{
+ if (scp->throttled)
+ return;
+
+ while (scp->n_pending_commands > 0) {
+ /* Expect an ack, and consume it */
+ if (scp->eof) {
+ scp_source_abort(
+ scp, "scp: received client EOF, abandoning transfer");
+ return;
+ }
+ if (scp->acks == 0)
+ return;
+ scp->acks--;
+
+ /*
+ * Now send the actual command (unless it was the phony
+ * zero-length one that indicates our need for an ack before
+ * beginning to send file data).
+ */
+
+ if (scp->pending_commands[0]->len)
+ sshfwd_write(scp->sc, scp->pending_commands[0]->s,
+ scp->pending_commands[0]->len);
+
+ strbuf_free(scp->pending_commands[0]);
+ scp->n_pending_commands--;
+ if (scp->n_pending_commands > 0) {
+ /*
+ * We still have at least one pending command to send, so
+ * move up the queue.
+ *
+ * (We do that with a bodgy memmove, because there are at
+ * most a bounded number of commands ever pending at once,
+ * so no need to worry about quadratic time.)
+ */
+ memmove(scp->pending_commands, scp->pending_commands+1,
+ scp->n_pending_commands * sizeof(*scp->pending_commands));
+ }
+ }
+
+ /*
+ * Mostly, we start by waiting for an ack byte from the receiver.
+ */
+ if (scp->head && scp->head->type == SCP_READFILE && scp->file_offset) {
+ /*
+ * Exception: if we're already in the middle of transferring a
+ * file, we'll be called back here because the channel backlog
+ * has cleared; we don't need to wait for an ack.
+ */
+ } else if (scp->head && scp->head->type == SCP_ROOTPATH) {
+ /*
+ * Another exception: the initial action node that makes us
+ * stat the root path. We'll translate it into an SCP_NAME,
+ * and _that_ will require an ack.
+ */
+ ScpSourceStackEntry *node = scp->head;
+ scp->head = node->next;
+
+ /*
+ * Start by checking if there's a wildcard involved in the
+ * root path.
+ */
+ char *rootpath_str = mkstr(node->pathname);
+ char *rootpath_unesc = snewn(1+node->pathname.len, char);
+ ptrlen pathname;
+ const char *wildcard;
+
+ if (wc_unescape(rootpath_unesc, rootpath_str)) {
+ /*
+ * We successfully removed instances of the escape
+ * character used in our wildcard syntax, without
+ * encountering any actual wildcard chars - i.e. this is
+ * not a wildcard, just a single file. The simple case.
+ */
+ pathname = ptrlen_from_asciz(rootpath_str);
+ wildcard = NULL;
+ } else {
+ /*
+ * This is a wildcard. Separate it into a directory name
+ * (which we enforce mustn't contain wc characters, for
+ * simplicity) and a wildcard to match leaf names.
+ */
+ char *last_slash = strrchr(rootpath_str, '/');
+
+ if (last_slash) {
+ wildcard = last_slash + 1;
+ *last_slash = '\0';
+ if (!wc_unescape(rootpath_unesc, rootpath_str)) {
+ scp_source_abort(scp, "scp: wildcards in path components "
+ "before the file name not supported");
+ sfree(rootpath_str);
+ sfree(rootpath_unesc);
+ return;
+ }
+
+ pathname = ptrlen_from_asciz(rootpath_unesc);
+ } else {
+ pathname = PTRLEN_LITERAL(".");
+ wildcard = rootpath_str;
+ }
+ }
+
+ /*
+ * Now we know what directory we're scanning, and what
+ * wildcard (if any) we're using to match the filenames we get
+ * back.
+ */
+ sftpsrv_stat(scp->sf, &scp->reply.srb, pathname, true);
+ if (scp->reply.err) {
+ scp_source_abort(
+ scp, "%.*s: unable to access: %s",
+ PTRLEN_PRINTF(pathname), scp->reply.errmsg);
+ sfree(rootpath_str);
+ sfree(rootpath_unesc);
+ sfree(node);
+ return;
+ }
+
+ scp_source_push_name(scp, pathname, scp->reply.attrs, wildcard);
+
+ sfree(rootpath_str);
+ sfree(rootpath_unesc);
+ sfree(node);
+ scp_requeue(scp);
+ return;
+ } else {
+ }
+
+ if (scp->head && scp->head->type == SCP_READFILE) {
+ /*
+ * Transfer file data if our backlog hasn't filled up.
+ */
+ int backlog;
+ uint64_t limit = scp->file_size - scp->file_offset;
+ if (limit > 4096)
+ limit = 4096;
+ if (limit > 0) {
+ sftpsrv_read(scp->sf, &scp->reply.srb, scp->head->handle,
+ scp->file_offset, limit);
+ if (scp->reply.err) {
+ scp_source_abort(
+ scp, "%.*s: unable to read: %s",
+ PTRLEN_PRINTF(scp->head->pathname), scp->reply.errmsg);
+ return;
+ }
+
+ backlog = sshfwd_write(
+ scp->sc, scp->reply.data.ptr, scp->reply.data.len);
+ scp->file_offset += scp->reply.data.len;
+
+ if (backlog < SCP_MAX_BACKLOG)
+ scp_requeue(scp);
+ return;
+ }
+
+ /*
+ * If we're done, send a terminating zero byte, close our file
+ * handle, and pop the stack.
+ */
+ sshfwd_write(scp->sc, "\0", 1);
+ sftpsrv_close(scp->sf, &scp->reply.srb, scp->head->handle);
+ ScpSourceStackEntry *node = scp->head;
+ scp->head = node->next;
+ sfree(node);
+ scp_requeue(scp);
+ return;
+ }
+
+ /*
+ * If our queue is actually empty, send outgoing EOF.
+ */
+ if (!scp->head) {
+ sshfwd_send_exit_status(scp->sc, 0);
+ sshfwd_write_eof(scp->sc);
+ sshfwd_initiate_close(scp->sc, NULL);
+ scp->finished = true;
+ return;
+ }
+
+ /*
+ * Otherwise, handle a command.
+ */
+ ScpSourceStackEntry *node = scp->head;
+ scp->head = node->next;
+
+ if (node->type == SCP_READDIR) {
+ sftpsrv_readdir(scp->sf, &scp->reply.srb, node->handle, 1, true);
+ if (scp->reply.err) {
+ if (scp->reply.code != SSH_FX_EOF)
+ scp_source_err(scp, "%.*s: unable to list directory: %s",
+ PTRLEN_PRINTF(node->pathname),
+ scp->reply.errmsg);
+ sftpsrv_close(scp->sf, &scp->reply.srb, node->handle);
+
+ if (!node->wildcard) {
+ /*
+ * Send 'pop stack' or 'end of directory' command,
+ * unless this was the topmost READDIR in a
+ * wildcard-based retrieval (in which case we didn't
+ * send a D command to start, so an E now would have
+ * no stack entry to pop).
+ */
+ scp_source_send_E(scp);
+ }
+ } else if (ptrlen_eq_string(scp->reply.name, ".") ||
+ ptrlen_eq_string(scp->reply.name, "..") ||
+ (node->wildcard &&
+ !wc_match_pl(node->wildcard, scp->reply.name))) {
+ /* Skip special directory names . and .., and anything
+ * that doesn't match our wildcard (if we have one). */
+ scp->head = node; /* put back the unfinished READDIR */
+ node = NULL; /* and prevent it being freed */
+ } else {
+ 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/ssh/server.c b/ssh/server.c
new file mode 100644
index 00000000..188426b3
--- /dev/null
+++ b/ssh/server.c
@@ -0,0 +1,609 @@
+/*
+ * Top-level code for SSH server implementation.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "channel.h"
+#include "server.h"
+#ifndef NO_GSSAPI
+#include "gssc.h"
+#include "gss.h"
+#endif
+
+struct Ssh { int dummy; };
+
+typedef struct server server;
+struct server {
+ bufchain in_raw, out_raw;
+ IdempotentCallback ic_out_raw;
+ bool pending_close;
+
+ bufchain dummy_user_input; /* we never put anything on this */
+
+ PacketLogSettings pls;
+ LogContext *logctx;
+ struct DataTransferStats stats;
+
+ int remote_bugs;
+
+ Socket *socket;
+ Plug plug;
+ int conn_throttle_count;
+ bool frozen;
+
+ Conf *conf;
+ const SshServerConfig *ssc;
+ ssh_key *const *hostkeys;
+ int nhostkeys;
+ RSAKey *hostkey1;
+ AuthPolicy *authpolicy;
+ LogPolicy *logpolicy;
+ const SftpServerVtable *sftpserver_vt;
+
+ agentfwd *stunt_agentfwd;
+
+ Seat seat;
+ Ssh ssh;
+ struct ssh_version_receiver version_receiver;
+
+ BinaryPacketProtocol *bpp;
+ PacketProtocolLayer *base_layer;
+ ConnectionLayer *cl;
+
+#ifndef NO_GSSAPI
+ struct ssh_connection_shared_gss_state gss_state;
+#endif
+};
+
+static void ssh_server_free_callback(void *vsrv);
+static void server_got_ssh_version(struct ssh_version_receiver *rcv,
+ int major_version);
+static void server_connect_bpp(server *srv);
+static void server_bpp_output_raw_data_callback(void *vctx);
+
+void share_activate(ssh_sharing_state *sharestate,
+ const char *server_verstring) {}
+void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate,
+ ConnectionLayer *cl) {}
+int share_ndownstreams(ssh_sharing_state *sharestate) { return 0; }
+void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type,
+ const void *vpkt, int pktlen) {}
+void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan,
+ unsigned upstream_id, unsigned server_id,
+ unsigned server_currwin, unsigned server_maxpkt,
+ unsigned client_adjusted_window,
+ const char *peer_addr, int peer_port, int endian,
+ int protomajor, int protominor,
+ const void *initial_data, int initial_len) {}
+Channel *agentf_new(SshChannel *c) { return NULL; }
+bool agent_exists(void) { return false; }
+void ssh_got_exitcode(Ssh *ssh, int exitcode) {}
+void ssh_check_frozen(Ssh *ssh) {}
+
+mainchan *mainchan_new(
+ PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf,
+ int term_width, int term_height, bool is_simple, SshChannel **sc_out)
+{ return NULL; }
+void mainchan_get_specials(
+ mainchan *mc, add_special_fn_t add_special, void *ctx) {}
+void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg) {}
+void mainchan_terminal_size(mainchan *mc, int width, int height) {}
+
+/* Seat functions to ensure we don't get choosy about crypto - as the
+ * server, it's not up to us to give user warnings */
+static SeatPromptResult server_confirm_weak_crypto_primitive(
+ Seat *seat, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{ return SPR_OK; }
+static SeatPromptResult server_confirm_weak_cached_hostkey(
+ Seat *seat, const char *algname, const char *betteralgs,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{ return SPR_OK; }
+
+static const SeatVtable server_seat_vt = {
+ .output = nullseat_output,
+ .eof = nullseat_eof,
+ .sent = nullseat_sent,
+ .banner = nullseat_banner,
+ .get_userpass_input = nullseat_get_userpass_input,
+ .notify_session_started = nullseat_notify_session_started,
+ .notify_remote_exit = nullseat_notify_remote_exit,
+ .notify_remote_disconnect = nullseat_notify_remote_disconnect,
+ .connection_fatal = nullseat_connection_fatal,
+ .update_specials_menu = nullseat_update_specials_menu,
+ .get_ttymode = nullseat_get_ttymode,
+ .set_busy_status = nullseat_set_busy_status,
+ .confirm_ssh_host_key = nullseat_confirm_ssh_host_key,
+ .confirm_weak_crypto_primitive = server_confirm_weak_crypto_primitive,
+ .confirm_weak_cached_hostkey = server_confirm_weak_cached_hostkey,
+ .prompt_descriptions = nullseat_prompt_descriptions,
+ .is_utf8 = nullseat_is_never_utf8,
+ .echoedit_update = nullseat_echoedit_update,
+ .get_x_display = nullseat_get_x_display,
+ .get_windowid = nullseat_get_windowid,
+ .get_window_pixel_size = nullseat_get_window_pixel_size,
+ .stripctrl_new = nullseat_stripctrl_new,
+ .set_trust_status = nullseat_set_trust_status,
+ .can_set_trust_status = nullseat_can_set_trust_status_no,
+ .has_mixed_input_stream = nullseat_has_mixed_input_stream_no,
+ .verbose = nullseat_verbose_no,
+ .interactive = nullseat_interactive_no,
+ .get_cursor_position = nullseat_get_cursor_position,
+};
+
+static void server_socket_log(Plug *plug, PlugLogType type, SockAddr *addr,
+ int port, const char *error_msg, int error_code)
+{
+ /* server *srv = container_of(plug, server, plug); */
+ /* FIXME */
+}
+
+static void server_closing(Plug *plug, PlugCloseType type,
+ const char *error_msg)
+{
+ server *srv = container_of(plug, server, plug);
+ if (type != PLUGCLOSE_NORMAL) {
+ ssh_remote_error(&srv->ssh, "%s", error_msg);
+ } else if (srv->bpp) {
+ srv->bpp->input_eof = true;
+ queue_idempotent_callback(&srv->bpp->ic_in_raw);
+ }
+}
+
+static void server_receive(
+ Plug *plug, int urgent, const char *data, size_t len)
+{
+ server *srv = container_of(plug, server, plug);
+
+ /* Log raw data, if we're in that mode. */
+ if (srv->logctx)
+ log_packet(srv->logctx, PKT_INCOMING, -1, NULL, data, len,
+ 0, NULL, NULL, 0, NULL);
+
+ bufchain_add(&srv->in_raw, data, len);
+ if (!srv->frozen && srv->bpp)
+ queue_idempotent_callback(&srv->bpp->ic_in_raw);
+}
+
+static void server_sent(Plug *plug, size_t bufsize)
+{
+#ifdef FIXME
+ server *srv = container_of(plug, server, plug);
+
+ /*
+ * If the send backlog on the SSH socket itself clears, we should
+ * unthrottle the whole world if it was throttled. Also trigger an
+ * extra call to the consumer of the BPP's output, to try to send
+ * some more data off its bufchain.
+ */
+ if (bufsize < SSH_MAX_BACKLOG) {
+ srv_throttle_all(srv, 0, bufsize);
+ queue_idempotent_callback(&srv->ic_out_raw);
+ }
+#endif
+}
+
+LogContext *ssh_get_logctx(Ssh *ssh)
+{
+ server *srv = container_of(ssh, server, ssh);
+ return srv->logctx;
+}
+
+void ssh_sendbuffer_changed(Ssh *ssh)
+{
+}
+
+void ssh_throttle_conn(Ssh *ssh, int adjust)
+{
+ server *srv = container_of(ssh, server, ssh);
+ int old_count = srv->conn_throttle_count;
+ bool frozen;
+
+ srv->conn_throttle_count += adjust;
+ assert(srv->conn_throttle_count >= 0);
+
+ if (srv->conn_throttle_count && !old_count) {
+ frozen = true;
+ } else if (!srv->conn_throttle_count && old_count) {
+ frozen = false;
+ } else {
+ return; /* don't change current frozen state */
+ }
+
+ srv->frozen = frozen;
+
+ if (srv->socket) {
+ sk_set_frozen(srv->socket, frozen);
+
+ /*
+ * Now process any SSH connection data that was stashed in our
+ * queue while we were frozen.
+ */
+ queue_idempotent_callback(&srv->bpp->ic_in_raw);
+ }
+}
+
+void ssh_conn_processed_data(Ssh *ssh)
+{
+ /* FIXME: we could add the same check_frozen_state system as we
+ * have in ssh.c, but because that was originally added to work
+ * around a peculiarity of the GUI event loop, I haven't yet. */
+}
+
+Conf *make_ssh_server_conf(void)
+{
+ Conf *conf = conf_new();
+ load_open_settings(NULL, conf);
+ /* In Uppity, we support even the legacy des-cbc cipher by
+ * default, so that it will be available if the user forces it by
+ * overriding the KEXINIT strings. If the user wants it _not_
+ * supported, of course, they can override KEXINIT in the other
+ * direction. */
+ conf_set_bool(conf, CONF_ssh2_des_cbc, true);
+ return conf;
+}
+
+void ssh_check_sendok(Ssh *ssh) {}
+
+static const PlugVtable ssh_server_plugvt = {
+ .log = server_socket_log,
+ .closing = server_closing,
+ .receive = server_receive,
+ .sent = server_sent,
+};
+
+Plug *ssh_server_plug(
+ Conf *conf, const SshServerConfig *ssc,
+ ssh_key *const *hostkeys, int nhostkeys,
+ RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy,
+ const SftpServerVtable *sftpserver_vt)
+{
+ server *srv = snew(server);
+
+ memset(srv, 0, sizeof(server));
+
+ srv->plug.vt = &ssh_server_plugvt;
+ srv->conf = conf_copy(conf);
+ srv->ssc = ssc;
+ srv->logctx = log_init(logpolicy, conf);
+ conf_set_bool(srv->conf, CONF_ssh_no_shell, true);
+ srv->nhostkeys = nhostkeys;
+ srv->hostkeys = hostkeys;
+ srv->hostkey1 = hostkey1;
+ srv->authpolicy = authpolicy;
+ srv->logpolicy = logpolicy;
+ srv->sftpserver_vt = sftpserver_vt;
+
+ srv->seat.vt = &server_seat_vt;
+
+ bufchain_init(&srv->in_raw);
+ bufchain_init(&srv->out_raw);
+ bufchain_init(&srv->dummy_user_input);
+
+#ifndef NO_GSSAPI
+ /* FIXME: replace with sensible */
+ srv->gss_state.libs = snew(struct ssh_gss_liblist);
+ srv->gss_state.libs->nlibraries = 0;
+#endif
+
+ return &srv->plug;
+}
+
+void ssh_server_start(Plug *plug, Socket *socket)
+{
+ server *srv = container_of(plug, server, plug);
+ const char *our_protoversion;
+
+ if (srv->ssc->bare_connection) {
+ our_protoversion = "2.0"; /* SSH-2 only */
+ } else if (srv->hostkey1 && srv->nhostkeys) {
+ our_protoversion = "1.99"; /* offer both SSH-1 and SSH-2 */
+ } else if (srv->hostkey1) {
+ our_protoversion = "1.5"; /* SSH-1 only */
+ } else {
+ assert(srv->nhostkeys);
+ our_protoversion = "2.0"; /* SSH-2 only */
+ }
+
+ srv->socket = socket;
+
+ srv->ic_out_raw.fn = server_bpp_output_raw_data_callback;
+ srv->ic_out_raw.ctx = srv;
+ srv->version_receiver.got_ssh_version = server_got_ssh_version;
+ srv->bpp = ssh_verstring_new(
+ srv->conf, srv->logctx, srv->ssc->bare_connection,
+ our_protoversion, &srv->version_receiver,
+ true, srv->ssc->application_name);
+ server_connect_bpp(srv);
+ queue_idempotent_callback(&srv->bpp->ic_in_raw);
+}
+
+static void ssh_server_free_callback(void *vsrv)
+{
+ server *srv = (server *)vsrv;
+
+ logeventf(srv->logctx, "freeing server instance");
+
+ bufchain_clear(&srv->in_raw);
+ bufchain_clear(&srv->out_raw);
+ bufchain_clear(&srv->dummy_user_input);
+
+ if (srv->socket)
+ sk_close(srv->socket);
+
+ if (srv->stunt_agentfwd)
+ agentfwd_free(srv->stunt_agentfwd);
+
+ if (srv->base_layer)
+ ssh_ppl_free(srv->base_layer);
+ if (srv->bpp)
+ ssh_bpp_free(srv->bpp);
+
+ delete_callbacks_for_context(srv);
+
+ conf_free(srv->conf);
+ log_free(srv->logctx);
+
+#ifndef NO_GSSAPI
+ sfree(srv->gss_state.libs); /* FIXME: replace with sensible */
+#endif
+
+ LogPolicy *lp = srv->logpolicy;
+ sfree(srv);
+
+ server_instance_terminated(lp);
+}
+
+static void server_connect_bpp(server *srv)
+{
+ srv->bpp->ssh = &srv->ssh;
+ srv->bpp->in_raw = &srv->in_raw;
+ srv->bpp->out_raw = &srv->out_raw;
+ bufchain_set_callback(srv->bpp->out_raw, &srv->ic_out_raw);
+ srv->bpp->pls = &srv->pls;
+ srv->bpp->logctx = srv->logctx;
+ srv->bpp->remote_bugs = srv->remote_bugs;
+ /* Servers don't really have a notion of 'unexpected' connection
+ * closure. The client is free to close if it likes. */
+ srv->bpp->expect_close = true;
+}
+
+static void server_connect_ppl(server *srv, PacketProtocolLayer *ppl)
+{
+ ppl->bpp = srv->bpp;
+ ppl->logctx = srv->logctx;
+ ppl->ssh = &srv->ssh;
+ ppl->seat = &srv->seat;
+ ppl->interactor = NULL;
+ ppl->remote_bugs = srv->remote_bugs;
+}
+
+static void server_bpp_output_raw_data_callback(void *vctx)
+{
+ server *srv = (server *)vctx;
+
+ if (!srv->socket)
+ return;
+
+ while (bufchain_size(&srv->out_raw) > 0) {
+ size_t backlog;
+
+ ptrlen data = bufchain_prefix(&srv->out_raw);
+
+ if (srv->logctx)
+ log_packet(srv->logctx, PKT_OUTGOING, -1, NULL, data.ptr, data.len,
+ 0, NULL, NULL, 0, NULL);
+ backlog = sk_write(srv->socket, data.ptr, data.len);
+
+ bufchain_consume(&srv->out_raw, data.len);
+
+ if (backlog > SSH_MAX_BACKLOG) {
+#ifdef FIXME
+ ssh_throttle_all(ssh, 1, backlog);
+#endif
+ return;
+ }
+ }
+
+ if (srv->pending_close) {
+ sk_close(srv->socket);
+ srv->socket = NULL;
+ queue_toplevel_callback(ssh_server_free_callback, srv);
+ }
+}
+
+static void server_shutdown_internal(server *srv)
+{
+ /*
+ * We only need to free the base PPL, which will free the others
+ * (if any) transitively.
+ */
+ if (srv->base_layer) {
+ ssh_ppl_free(srv->base_layer);
+ srv->base_layer = NULL;
+ }
+
+ srv->cl = NULL;
+}
+
+static void server_initiate_connection_close(server *srv)
+{
+ /* Wind up everything above the BPP. */
+ server_shutdown_internal(srv);
+
+ /* Force any remaining queued SSH packets through the BPP, and
+ * schedule closing the network socket after they go out. */
+ ssh_bpp_handle_output(srv->bpp);
+ srv->pending_close = true;
+ queue_idempotent_callback(&srv->ic_out_raw);
+
+ /* Now we expect the other end to close the connection too in
+ * response, so arrange that we'll receive notification of that
+ * via ssh_remote_eof. */
+ srv->bpp->expect_close = true;
+}
+
+#define GET_FORMATTED_MSG(fmt) \
+ char *msg; \
+ va_list ap; \
+ va_start(ap, fmt); \
+ msg = dupvprintf(fmt, ap); \
+ va_end(ap);
+
+#define LOG_FORMATTED_MSG(logctx, fmt) do \
+ { \
+ va_list ap; \
+ va_start(ap, fmt); \
+ logeventvf(logctx, fmt, ap); \
+ va_end(ap); \
+ } while (0)
+
+void ssh_remote_error(Ssh *ssh, const char *fmt, ...)
+{
+ server *srv = container_of(ssh, server, ssh);
+ LOG_FORMATTED_MSG(srv->logctx, fmt);
+ queue_toplevel_callback(ssh_server_free_callback, srv);
+}
+
+void ssh_remote_eof(Ssh *ssh, const char *fmt, ...)
+{
+ server *srv = container_of(ssh, server, ssh);
+ LOG_FORMATTED_MSG(srv->logctx, fmt);
+ queue_toplevel_callback(ssh_server_free_callback, srv);
+}
+
+void ssh_proto_error(Ssh *ssh, const char *fmt, ...)
+{
+ server *srv = container_of(ssh, server, ssh);
+ if (srv->base_layer) {
+ GET_FORMATTED_MSG(fmt);
+ ssh_bpp_queue_disconnect(srv->bpp, msg,
+ SSH2_DISCONNECT_PROTOCOL_ERROR);
+ server_initiate_connection_close(srv);
+ logeventf(srv->logctx, "Protocol error: %s", msg);
+ sfree(msg);
+ }
+}
+
+void ssh_sw_abort(Ssh *ssh, const char *fmt, ...)
+{
+ server *srv = container_of(ssh, server, ssh);
+ LOG_FORMATTED_MSG(srv->logctx, fmt);
+ queue_toplevel_callback(ssh_server_free_callback, srv);
+}
+
+void ssh_user_close(Ssh *ssh, const char *fmt, ...)
+{
+ server *srv = container_of(ssh, server, ssh);
+ LOG_FORMATTED_MSG(srv->logctx, fmt);
+ queue_toplevel_callback(ssh_server_free_callback, srv);
+}
+
+static void server_got_ssh_version(struct ssh_version_receiver *rcv,
+ int major_version)
+{
+ server *srv = container_of(rcv, server, version_receiver);
+ BinaryPacketProtocol *old_bpp;
+ PacketProtocolLayer *connection_layer;
+
+ old_bpp = srv->bpp;
+ srv->remote_bugs = ssh_verstring_get_bugs(old_bpp);
+
+ if (srv->ssc->bare_connection) {
+ srv->bpp = ssh2_bare_bpp_new(srv->logctx);
+ server_connect_bpp(srv);
+
+ connection_layer = ssh2_connection_new(
+ &srv->ssh, NULL, false, srv->conf,
+ ssh_verstring_get_local(old_bpp), &srv->dummy_user_input,
+ &srv->cl);
+ ssh2connection_server_configure(connection_layer,
+ srv->sftpserver_vt, srv->ssc);
+ server_connect_ppl(srv, connection_layer);
+
+ srv->base_layer = connection_layer;
+ } else if (major_version == 2) {
+ PacketProtocolLayer *userauth_layer, *transport_child_layer;
+
+ srv->bpp = ssh2_bpp_new(srv->logctx, &srv->stats, true);
+ server_connect_bpp(srv);
+
+ connection_layer = ssh2_connection_new(
+ &srv->ssh, NULL, false, srv->conf,
+ ssh_verstring_get_local(old_bpp), &srv->dummy_user_input,
+ &srv->cl);
+ ssh2connection_server_configure(connection_layer,
+ srv->sftpserver_vt, srv->ssc);
+ server_connect_ppl(srv, connection_layer);
+
+ if (conf_get_bool(srv->conf, CONF_ssh_no_userauth)) {
+ userauth_layer = NULL;
+ transport_child_layer = connection_layer;
+ } else {
+ userauth_layer = ssh2_userauth_server_new(
+ connection_layer, srv->authpolicy, srv->ssc);
+ server_connect_ppl(srv, userauth_layer);
+ transport_child_layer = userauth_layer;
+ }
+
+ srv->base_layer = ssh2_transport_new(
+ srv->conf, NULL, 0, NULL,
+ ssh_verstring_get_remote(old_bpp),
+ ssh_verstring_get_local(old_bpp),
+#ifndef NO_GSSAPI
+ &srv->gss_state,
+#else
+ NULL,
+#endif
+ &srv->stats, transport_child_layer, srv->ssc);
+ ssh2_transport_provide_hostkeys(
+ srv->base_layer, srv->hostkeys, srv->nhostkeys);
+ if (userauth_layer)
+ ssh2_userauth_server_set_transport_layer(
+ userauth_layer, srv->base_layer);
+ server_connect_ppl(srv, srv->base_layer);
+
+ } else {
+ srv->bpp = ssh1_bpp_new(srv->logctx);
+ server_connect_bpp(srv);
+
+ connection_layer = ssh1_connection_new(
+ &srv->ssh, srv->conf, &srv->dummy_user_input, &srv->cl);
+ ssh1connection_server_configure(connection_layer, srv->ssc);
+ server_connect_ppl(srv, connection_layer);
+
+ srv->base_layer = ssh1_login_server_new(
+ connection_layer, srv->hostkey1, srv->authpolicy, srv->ssc);
+ server_connect_ppl(srv, srv->base_layer);
+ }
+
+ /* Connect the base layer - whichever it is - to the BPP, and set
+ * up its selfptr. */
+ srv->base_layer->selfptr = &srv->base_layer;
+ ssh_ppl_setup_queues(srv->base_layer, &srv->bpp->in_pq, &srv->bpp->out_pq);
+
+#ifdef FIXME // we probably will want one of these, in the end
+ srv->pinger = pinger_new(srv->conf, &srv->backend);
+#endif
+
+ if (srv->ssc->stunt_open_unconditional_agent_socket) {
+ char *socketname;
+ srv->stunt_agentfwd = agentfwd_new(srv->cl, &socketname);
+ if (srv->stunt_agentfwd) {
+ logeventf(srv->logctx, "opened unconditional agent socket at %s\n",
+ socketname);
+ sfree(socketname);
+ }
+ }
+
+ queue_idempotent_callback(&srv->bpp->ic_in_raw);
+ ssh_ppl_process_queue(srv->base_layer);
+
+ ssh_bpp_free(old_bpp);
+}
diff --git a/ssh/server.h b/ssh/server.h
new file mode 100644
index 00000000..c2b3647b
--- /dev/null
+++ b/ssh/server.h
@@ -0,0 +1,145 @@
+typedef struct AuthPolicy AuthPolicy;
+
+struct SshServerConfig {
+ const char *application_name;
+ const char *session_starting_dir;
+
+ RSAKey *rsa_kex_key;
+
+ /*
+ * In all of these ptrlens, setting the 'ptr' member to NULL means
+ * that we're not overriding the default configuration.
+ */
+ ptrlen banner; /* default here is 'no banner' */
+ ptrlen kex_override[NKEXLIST];
+
+ bool exit_signal_numeric; /* mimic an old server bug */
+
+ unsigned long ssh1_cipher_mask;
+ bool ssh1_allow_compression;
+ bool bare_connection;
+
+ bool stunt_pretend_to_accept_any_pubkey;
+ bool stunt_open_unconditional_agent_socket;
+ bool stunt_allow_trivial_ki_auth;
+ bool stunt_return_success_to_pubkey_offer;
+};
+
+Plug *ssh_server_plug(
+ Conf *conf, const SshServerConfig *ssc,
+ ssh_key *const *hostkeys, int nhostkeys,
+ RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy,
+ const SftpServerVtable *sftpserver_vt);
+void ssh_server_start(Plug *plug, Socket *socket);
+
+void server_instance_terminated(LogPolicy *logpolicy);
+void platform_logevent(const char *msg);
+
+#define AUTHMETHODS(X) \
+ X(NONE) \
+ X(PASSWORD) \
+ X(PUBLICKEY) \
+ X(KBDINT) \
+ X(TIS) \
+ X(CRYPTOCARD) \
+ /* end of list */
+
+#define AUTHMETHOD_BIT_INDEX(name) AUTHMETHOD_BIT_INDEX_##name,
+enum { AUTHMETHODS(AUTHMETHOD_BIT_INDEX) AUTHMETHOD_BIT_INDEX_dummy };
+#define AUTHMETHOD_BIT_VALUE(name) \
+ AUTHMETHOD_##name = 1 << AUTHMETHOD_BIT_INDEX_##name,
+enum { AUTHMETHODS(AUTHMETHOD_BIT_VALUE) AUTHMETHOD_BIT_VALUE_dummy };
+
+typedef struct AuthKbdInt AuthKbdInt;
+typedef struct AuthKbdIntPrompt AuthKbdIntPrompt;
+struct AuthKbdInt {
+ char *title, *instruction; /* both need freeing */
+ int nprompts;
+ AuthKbdIntPrompt *prompts; /* the array itself needs freeing */
+};
+struct AuthKbdIntPrompt {
+ char *prompt; /* needs freeing */
+ bool echo;
+};
+
+unsigned auth_methods(AuthPolicy *);
+bool auth_none(AuthPolicy *, ptrlen username);
+
+int auth_password(AuthPolicy *, ptrlen username, ptrlen password,
+ ptrlen *opt_new_password);
+/* auth_password returns 1 for 'accepted', 0 for 'rejected', and 2 for
+ * 'ok but now you need to change your password' */
+
+bool auth_publickey(AuthPolicy *, ptrlen username, ptrlen public_blob);
+/* auth_publickey_ssh1 must return the whole public key given the modulus,
+ * because the SSH-1 client never transmits the exponent over the wire.
+ * The key remains owned by the AuthPolicy. */
+
+AuthKbdInt *auth_kbdint_prompts(AuthPolicy *, ptrlen username);
+/* auth_kbdint_prompts returns NULL to trigger auth failure */
+int auth_kbdint_responses(AuthPolicy *, const ptrlen *responses);
+/* auth_kbdint_responses returns >0 for success, <0 for failure, and 0
+ * to indicate that we haven't decided yet and further prompts are
+ * coming */
+
+/* The very similar SSH-1 TIS and CryptoCard methods are combined into
+ * a single API for AuthPolicy, which takes a method argument */
+char *auth_ssh1int_challenge(AuthPolicy *, unsigned method, ptrlen username);
+bool auth_ssh1int_response(AuthPolicy *, ptrlen response);
+
+RSAKey *auth_publickey_ssh1(
+ AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus);
+/* auth_successful returns false if further authentication is needed */
+bool auth_successful(AuthPolicy *, ptrlen username, unsigned method);
+
+PacketProtocolLayer *ssh2_userauth_server_new(
+ PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy,
+ const SshServerConfig *ssc);
+void ssh2_userauth_server_set_transport_layer(
+ PacketProtocolLayer *userauth, PacketProtocolLayer *transport);
+
+void ssh2connection_server_configure(
+ PacketProtocolLayer *ppl, const SftpServerVtable *sftpserver_vt,
+ const SshServerConfig *ssc);
+void ssh1connection_server_configure(
+ PacketProtocolLayer *ppl, const SshServerConfig *ssc);
+
+PacketProtocolLayer *ssh1_login_server_new(
+ PacketProtocolLayer *successor_layer, RSAKey *hostkey,
+ AuthPolicy *authpolicy, const SshServerConfig *ssc);
+
+Channel *sesschan_new(SshChannel *c, LogContext *logctx,
+ const SftpServerVtable *sftpserver_vt,
+ const SshServerConfig *ssc);
+
+Backend *pty_backend_create(
+ Seat *seat, LogContext *logctx, Conf *conf, char **argv, const char *cmd,
+ struct ssh_ttymodes ttymodes, bool pipes_instead_of_pty, const char *dir,
+ const char *const *env_vars_to_unset);
+int pty_backend_exit_signum(Backend *be);
+ptrlen pty_backend_exit_signame(Backend *be, char **aux_msg);
+
+/*
+ * Establish a listening X server. Return value is the _number_ of
+ * Sockets that it established pointing at the given Plug. (0
+ * indicates complete failure.) The socket pointers themselves are
+ * written into sockets[], up to a possible total of MAX_X11_SOCKETS.
+ *
+ * The supplied Conf has necessary environment variables written into
+ * it. (And is also used to open the port listeners, though that
+ * shouldn't affect anything.)
+ */
+#define MAX_X11_SOCKETS 2
+int platform_make_x11_server(Plug *plug, const char *progname, int mindisp,
+ const char *screen_number_suffix,
+ ptrlen authproto, ptrlen authdata,
+ Socket **sockets, Conf *conf);
+
+Conf *make_ssh_server_conf(void);
+
+/* Provided by Unix front end programs to unix/sftpserver.c */
+void make_unix_sftp_filehandle_key(void *data, size_t size);
+
+typedef struct agentfwd agentfwd;
+agentfwd *agentfwd_new(ConnectionLayer *cl, char **socketname_out);
+void agentfwd_free(agentfwd *agent);
diff --git a/ssh/sesschan.c b/ssh/sesschan.c
new file mode 100644
index 00000000..9776eaf2
--- /dev/null
+++ b/ssh/sesschan.c
@@ -0,0 +1,798 @@
+/*
+ * Implement the "session" channel type for the SSH server.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "channel.h"
+#include "server.h"
+#include "sftp.h"
+
+struct agentfwd {
+ ConnectionLayer *cl;
+ Socket *socket;
+ Plug plug;
+};
+
+typedef struct sesschan {
+ SshChannel *c;
+
+ LogContext *parent_logctx, *child_logctx;
+ Conf *conf;
+ const SftpServerVtable *sftpserver_vt;
+
+ LogPolicy logpolicy;
+ Seat seat;
+
+ bool want_pty;
+ struct ssh_ttymodes ttymodes;
+ int wc, hc, wp, hp;
+ strbuf *termtype;
+
+ bool ignoring_input;
+ bool seen_eof, seen_exit;
+
+ Plug xfwd_plug;
+ int n_x11_sockets;
+ Socket *x11_sockets[MAX_X11_SOCKETS];
+
+ agentfwd *agent;
+
+ Backend *backend;
+
+ bufchain subsys_input;
+ SftpServer *sftpsrv;
+ ScpServer *scpsrv;
+ const SshServerConfig *ssc;
+
+ Channel chan;
+} sesschan;
+
+static void sesschan_free(Channel *chan);
+static size_t sesschan_send(
+ Channel *chan, bool is_stderr, const void *, size_t);
+static void sesschan_send_eof(Channel *chan);
+static char *sesschan_log_close_msg(Channel *chan);
+static bool sesschan_want_close(Channel *, bool, bool);
+static void sesschan_set_input_wanted(Channel *chan, bool wanted);
+static bool sesschan_run_shell(Channel *chan);
+static bool sesschan_run_command(Channel *chan, ptrlen command);
+static bool sesschan_run_subsystem(Channel *chan, ptrlen subsys);
+static bool sesschan_enable_x11_forwarding(
+ Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata,
+ unsigned screen_number);
+static bool sesschan_enable_agent_forwarding(Channel *chan);
+static bool sesschan_allocate_pty(
+ Channel *chan, ptrlen termtype, unsigned width, unsigned height,
+ unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes);
+static bool sesschan_set_env(Channel *chan, ptrlen var, ptrlen value);
+static bool sesschan_send_break(Channel *chan, unsigned length);
+static bool sesschan_send_signal(Channel *chan, ptrlen signame);
+static bool sesschan_change_window_size(
+ Channel *chan, unsigned width, unsigned height,
+ unsigned pixwidth, unsigned pixheight);
+
+static const ChannelVtable sesschan_channelvt = {
+ .free = sesschan_free,
+ .open_confirmation = chan_remotely_opened_confirmation,
+ .open_failed = chan_remotely_opened_failure,
+ .send = sesschan_send,
+ .send_eof = sesschan_send_eof,
+ .set_input_wanted = sesschan_set_input_wanted,
+ .log_close_msg = sesschan_log_close_msg,
+ .want_close = sesschan_want_close,
+ .rcvd_exit_status = chan_no_exit_status,
+ .rcvd_exit_signal = chan_no_exit_signal,
+ .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
+ .run_shell = sesschan_run_shell,
+ .run_command = sesschan_run_command,
+ .run_subsystem = sesschan_run_subsystem,
+ .enable_x11_forwarding = sesschan_enable_x11_forwarding,
+ .enable_agent_forwarding = sesschan_enable_agent_forwarding,
+ .allocate_pty = sesschan_allocate_pty,
+ .set_env = sesschan_set_env,
+ .send_break = sesschan_send_break,
+ .send_signal = sesschan_send_signal,
+ .change_window_size = sesschan_change_window_size,
+ .request_response = chan_no_request_response,
+};
+
+static size_t sftp_chan_send(
+ Channel *chan, bool is_stderr, const void *, size_t);
+static void sftp_chan_send_eof(Channel *chan);
+static char *sftp_log_close_msg(Channel *chan);
+
+static const ChannelVtable sftp_channelvt = {
+ .free = sesschan_free,
+ .open_confirmation = chan_remotely_opened_confirmation,
+ .open_failed = chan_remotely_opened_failure,
+ .send = sftp_chan_send,
+ .send_eof = sftp_chan_send_eof,
+ .set_input_wanted = sesschan_set_input_wanted,
+ .log_close_msg = sftp_log_close_msg,
+ .want_close = chan_default_want_close,
+ .rcvd_exit_status = chan_no_exit_status,
+ .rcvd_exit_signal = chan_no_exit_signal,
+ .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
+ .run_shell = chan_no_run_shell,
+ .run_command = chan_no_run_command,
+ .run_subsystem = chan_no_run_subsystem,
+ .enable_x11_forwarding = chan_no_enable_x11_forwarding,
+ .enable_agent_forwarding = chan_no_enable_agent_forwarding,
+ .allocate_pty = chan_no_allocate_pty,
+ .set_env = chan_no_set_env,
+ .send_break = chan_no_send_break,
+ .send_signal = chan_no_send_signal,
+ .change_window_size = chan_no_change_window_size,
+ .request_response = chan_no_request_response,
+};
+
+static size_t scp_chan_send(
+ Channel *chan, bool is_stderr, const void *, size_t);
+static void scp_chan_send_eof(Channel *chan);
+static void scp_set_input_wanted(Channel *chan, bool wanted);
+static char *scp_log_close_msg(Channel *chan);
+
+static const ChannelVtable scp_channelvt = {
+ .free = sesschan_free,
+ .open_confirmation = chan_remotely_opened_confirmation,
+ .open_failed = chan_remotely_opened_failure,
+ .send = scp_chan_send,
+ .send_eof = scp_chan_send_eof,
+ .set_input_wanted = scp_set_input_wanted,
+ .log_close_msg = scp_log_close_msg,
+ .want_close = chan_default_want_close,
+ .rcvd_exit_status = chan_no_exit_status,
+ .rcvd_exit_signal = chan_no_exit_signal,
+ .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
+ .run_shell = chan_no_run_shell,
+ .run_command = chan_no_run_command,
+ .run_subsystem = chan_no_run_subsystem,
+ .enable_x11_forwarding = chan_no_enable_x11_forwarding,
+ .enable_agent_forwarding = chan_no_enable_agent_forwarding,
+ .allocate_pty = chan_no_allocate_pty,
+ .set_env = chan_no_set_env,
+ .send_break = chan_no_send_break,
+ .send_signal = chan_no_send_signal,
+ .change_window_size = chan_no_change_window_size,
+ .request_response = chan_no_request_response,
+};
+
+static void sesschan_eventlog(LogPolicy *lp, const char *event) {}
+static void sesschan_logging_error(LogPolicy *lp, const char *event) {}
+static int sesschan_askappend(
+ LogPolicy *lp, Filename *filename,
+ void (*callback)(void *ctx, int result), void *ctx) { return 2; }
+
+static const LogPolicyVtable sesschan_logpolicy_vt = {
+ .eventlog = sesschan_eventlog,
+ .askappend = sesschan_askappend,
+ .logging_error = sesschan_logging_error,
+ .verbose = null_lp_verbose_no,
+};
+
+static size_t sesschan_seat_output(
+ Seat *, SeatOutputType type, const void *, size_t);
+static bool sesschan_seat_eof(Seat *);
+static void sesschan_notify_remote_exit(Seat *seat);
+static void sesschan_connection_fatal(Seat *seat, const char *message);
+static bool sesschan_get_window_pixel_size(Seat *seat, int *w, int *h);
+
+static const SeatVtable sesschan_seat_vt = {
+ .output = sesschan_seat_output,
+ .eof = sesschan_seat_eof,
+ .sent = nullseat_sent,
+ .banner = nullseat_banner,
+ .get_userpass_input = nullseat_get_userpass_input,
+ .notify_session_started = nullseat_notify_session_started,
+ .notify_remote_exit = sesschan_notify_remote_exit,
+ .notify_remote_disconnect = nullseat_notify_remote_disconnect,
+ .connection_fatal = sesschan_connection_fatal,
+ .update_specials_menu = nullseat_update_specials_menu,
+ .get_ttymode = nullseat_get_ttymode,
+ .set_busy_status = nullseat_set_busy_status,
+ .confirm_ssh_host_key = nullseat_confirm_ssh_host_key,
+ .confirm_weak_crypto_primitive = nullseat_confirm_weak_crypto_primitive,
+ .confirm_weak_cached_hostkey = nullseat_confirm_weak_cached_hostkey,
+ .prompt_descriptions = nullseat_prompt_descriptions,
+ .is_utf8 = nullseat_is_never_utf8,
+ .echoedit_update = nullseat_echoedit_update,
+ .get_x_display = nullseat_get_x_display,
+ .get_windowid = nullseat_get_windowid,
+ .get_window_pixel_size = sesschan_get_window_pixel_size,
+ .stripctrl_new = nullseat_stripctrl_new,
+ .set_trust_status = nullseat_set_trust_status,
+ .can_set_trust_status = nullseat_can_set_trust_status_no,
+ .has_mixed_input_stream = nullseat_has_mixed_input_stream_no,
+ .verbose = nullseat_verbose_no,
+ .interactive = nullseat_interactive_no,
+ .get_cursor_position = nullseat_get_cursor_position,
+};
+
+Channel *sesschan_new(SshChannel *c, LogContext *logctx,
+ const SftpServerVtable *sftpserver_vt,
+ const SshServerConfig *ssc)
+{
+ sesschan *sess = snew(sesschan);
+ memset(sess, 0, sizeof(sesschan));
+
+ sess->c = c;
+ sess->chan.vt = &sesschan_channelvt;
+ sess->chan.initial_fixed_window_size = 0;
+ sess->parent_logctx = logctx;
+ sess->ssc = ssc;
+
+ /* Start with a completely default Conf */
+ sess->conf = conf_new();
+ load_open_settings(NULL, sess->conf);
+
+ /* Set close-on-exit = true to suppress pty.c's "[pterm: process
+ * terminated with status x]" message */
+ conf_set_int(sess->conf, CONF_close_on_exit, FORCE_ON);
+
+ sess->seat.vt = &sesschan_seat_vt;
+ sess->logpolicy.vt = &sesschan_logpolicy_vt;
+ sess->child_logctx = log_init(&sess->logpolicy, sess->conf);
+
+ sess->sftpserver_vt = sftpserver_vt;
+
+ bufchain_init(&sess->subsys_input);
+
+ return &sess->chan;
+}
+
+static void sesschan_free(Channel *chan)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ int i;
+
+ delete_callbacks_for_context(sess);
+ conf_free(sess->conf);
+ if (sess->backend)
+ backend_free(sess->backend);
+ bufchain_clear(&sess->subsys_input);
+ if (sess->sftpsrv)
+ sftpsrv_free(sess->sftpsrv);
+ for (i = 0; i < sess->n_x11_sockets; i++)
+ sk_close(sess->x11_sockets[i]);
+ if (sess->agent)
+ agentfwd_free(sess->agent);
+
+ sfree(sess);
+}
+
+static size_t sesschan_send(Channel *chan, bool is_stderr,
+ const void *data, size_t length)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ if (!sess->backend || sess->ignoring_input)
+ return 0;
+
+ backend_send(sess->backend, data, length);
+ return backend_sendbuffer(sess->backend);
+}
+
+static void sesschan_send_eof(Channel *chan)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ if (sess->backend)
+ backend_special(sess->backend, SS_EOF, 0);
+}
+
+static char *sesschan_log_close_msg(Channel *chan)
+{
+ return dupstr("Session channel closed");
+}
+
+static void sesschan_set_input_wanted(Channel *chan, bool wanted)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ /* Request the back end to resume sending input, if it had become
+ * throttled by the channel window shortening */
+ if (wanted && sess->backend)
+ backend_unthrottle(sess->backend, 0);
+}
+
+static void sesschan_start_backend(sesschan *sess, const char *cmd)
+{
+ /*
+ * List of environment variables that we should not pass through
+ * from the login session Uppity was run in (which, it being a
+ * test server, there will usually be one of). These variables
+ * will be set as part of X or agent forwarding, and shouldn't be
+ * confusingly set in the absence of that.
+ *
+ * (DISPLAY must also be cleared, but pty.c will do that anyway
+ * when our get_x_display method returns NULL.)
+ */
+ static const char *const env_to_unset[] = {
+ "XAUTHORITY", "SSH_AUTH_SOCK", "SSH_AGENT_PID",
+ NULL /* terminator */
+ };
+
+ sess->backend = pty_backend_create(
+ &sess->seat, sess->child_logctx, sess->conf, NULL, cmd,
+ sess->ttymodes, !sess->want_pty, sess->ssc->session_starting_dir,
+ env_to_unset);
+ backend_size(sess->backend, sess->wc, sess->hc);
+}
+
+bool sesschan_run_shell(Channel *chan)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ if (sess->backend)
+ return false;
+
+ sesschan_start_backend(sess, NULL);
+ return true;
+}
+
+bool sesschan_run_command(Channel *chan, ptrlen command)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ if (sess->backend)
+ return false;
+
+ /* FIXME: make this possible to configure off */
+ if ((sess->scpsrv = scp_recognise_exec(sess->c, sess->sftpserver_vt,
+ command)) != NULL) {
+ sess->chan.vt = &scp_channelvt;
+ logevent(sess->parent_logctx, "Starting built-in SCP server");
+ return true;
+ }
+
+ char *command_str = mkstr(command);
+ sesschan_start_backend(sess, command_str);
+ sfree(command_str);
+
+ return true;
+}
+
+bool sesschan_run_subsystem(Channel *chan, ptrlen subsys)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ if (ptrlen_eq_string(subsys, "sftp") && sess->sftpserver_vt) {
+ sess->sftpsrv = sftpsrv_new(sess->sftpserver_vt);
+ sess->chan.vt = &sftp_channelvt;
+ logevent(sess->parent_logctx, "Starting built-in SFTP subsystem");
+ return true;
+ }
+
+ return false;
+}
+
+static void fwd_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
+ const char *error_msg, int error_code)
+{ /* don't expect any weirdnesses from a listening socket */ }
+static void fwd_closing(Plug *plug, PlugCloseType type, const char *error_msg)
+{ /* not here, either */ }
+
+static int xfwd_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
+{
+ sesschan *sess = container_of(p, sesschan, xfwd_plug);
+ Plug *plug;
+ Channel *chan;
+ Socket *s;
+ SocketPeerInfo *pi;
+ const char *err;
+
+ chan = portfwd_raw_new(sess->c->cl, &plug, false);
+ s = constructor(ctx, plug);
+ if ((err = sk_socket_error(s)) != NULL) {
+ portfwd_raw_free(chan);
+ return 1;
+ }
+ pi = sk_peer_info(s);
+ portfwd_raw_setup(chan, s, ssh_serverside_x11_open(sess->c->cl, chan, pi));
+ sk_free_peer_info(pi);
+
+ return 0;
+}
+
+static const PlugVtable xfwd_plugvt = {
+ .log = fwd_log,
+ .closing = fwd_closing,
+ .accepting = xfwd_accepting,
+};
+
+bool sesschan_enable_x11_forwarding(
+ Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata_hex,
+ unsigned screen_number)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ strbuf *authdata_bin;
+ size_t i;
+
+ if (oneshot)
+ return false; /* not supported */
+
+ /*
+ * Decode the authorisation data from ASCII hex into binary.
+ */
+ if (authdata_hex.len % 2)
+ return false; /* expected an even number of digits */
+ authdata_bin = strbuf_new_nm();
+ for (i = 0; i < authdata_hex.len; i += 2) {
+ const unsigned char *hex = authdata_hex.ptr;
+ char hexbuf[3];
+
+ if (!isxdigit(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;
+
+ char *screensuffix = dupprintf(".%u", screen_number);
+
+ sess->n_x11_sockets = platform_make_x11_server(
+ &sess->xfwd_plug, appname, 10, screensuffix,
+ authproto, ptrlen_from_strbuf(authdata_bin),
+ sess->x11_sockets, sess->conf);
+
+ sfree(screensuffix);
+ strbuf_free(authdata_bin);
+ return sess->n_x11_sockets != 0;
+}
+
+static int agentfwd_accepting(
+ Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
+{
+ agentfwd *agent = container_of(p, agentfwd, plug);
+ Plug *plug;
+ Channel *chan;
+ Socket *s;
+ const char *err;
+
+ chan = portfwd_raw_new(agent->cl, &plug, false);
+ s = constructor(ctx, plug);
+ if ((err = sk_socket_error(s)) != NULL) {
+ portfwd_raw_free(chan);
+ return 1;
+ }
+ portfwd_raw_setup(chan, s, ssh_serverside_agent_open(agent->cl, chan));
+
+ return 0;
+}
+
+static const PlugVtable agentfwd_plugvt = {
+ .log = fwd_log,
+ .closing = fwd_closing,
+ .accepting = agentfwd_accepting,
+};
+
+agentfwd *agentfwd_new(ConnectionLayer *cl, char **socketname_out)
+{
+ agentfwd *agent = snew(agentfwd);
+ agent->cl = cl;
+ agent->plug.vt = &agentfwd_plugvt;
+
+ char *dir_prefix = dupprintf("/tmp/%s-agentfwd", appname);
+ char *error = NULL, *socketname = NULL;
+ agent->socket = platform_make_agent_socket(
+ &agent->plug, dir_prefix, &error, &socketname);
+ sfree(dir_prefix);
+ sfree(error);
+
+ if (!agent->socket) {
+ sfree(agent);
+ sfree(socketname);
+ return NULL;
+ }
+
+ *socketname_out = socketname;
+ return agent;
+}
+
+void agentfwd_free(agentfwd *agent)
+{
+ if (agent->socket)
+ sk_close(agent->socket);
+ sfree(agent);
+}
+
+bool sesschan_enable_agent_forwarding(Channel *chan)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ char *socketname;
+
+ assert(!sess->agent);
+
+ sess->agent = agentfwd_new(sess->c->cl, &socketname);
+
+ if (!sess->agent)
+ return false;
+
+ conf_set_str_str(sess->conf, CONF_environmt, "SSH_AUTH_SOCK", socketname);
+ sfree(socketname);
+ return true;
+}
+
+bool sesschan_allocate_pty(
+ Channel *chan, ptrlen termtype, unsigned width, unsigned height,
+ unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ char *s;
+
+ if (sess->want_pty)
+ return false;
+
+ s = mkstr(termtype);
+ conf_set_str(sess->conf, CONF_termtype, s);
+ sfree(s);
+
+ sess->want_pty = true;
+ sess->ttymodes = modes;
+ sess->wc = width;
+ sess->hc = height;
+ sess->wp = pixwidth;
+ sess->hp = pixheight;
+
+ return true;
+}
+
+bool sesschan_set_env(Channel *chan, ptrlen var, ptrlen value)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ char *svar = mkstr(var), *svalue = mkstr(value);
+ conf_set_str_str(sess->conf, CONF_environmt, svar, svalue);
+ sfree(svar);
+ sfree(svalue);
+
+ return true;
+}
+
+bool sesschan_send_break(Channel *chan, unsigned length)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ if (sess->backend) {
+ /* We ignore the break length. We could pass it through as the
+ * 'arg' parameter, and have pty.c collect it and pass it on
+ * to tcsendbreak, but since tcsendbreak in turn assigns
+ * implementation-defined semantics to _its_ duration
+ * parameter, this all just sounds too difficult. */
+ backend_special(sess->backend, SS_BRK, 0);
+ return true;
+ }
+ return false;
+}
+
+bool sesschan_send_signal(Channel *chan, ptrlen signame)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ /* Start with a code that definitely isn't a signal (or indeed a
+ * special command at all), to indicate 'nothing matched'. */
+ SessionSpecialCode code = SS_EXITMENU;
+
+ #define SIGNAL_SUB(name) \
+ if (ptrlen_eq_string(signame, #name)) code = SS_SIG ## name;
+ #define SIGNAL_MAIN(name, text) SIGNAL_SUB(name)
+ #include "signal-list.h"
+ #undef SIGNAL_MAIN
+ #undef SIGNAL_SUB
+
+ if (code == SS_EXITMENU)
+ return false;
+
+ backend_special(sess->backend, code, 0);
+ return true;
+}
+
+bool sesschan_change_window_size(
+ Channel *chan, unsigned width, unsigned height,
+ unsigned pixwidth, unsigned pixheight)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ if (!sess->want_pty)
+ return false;
+
+ sess->wc = width;
+ sess->hc = height;
+ sess->wp = pixwidth;
+ sess->hp = pixheight;
+
+ if (sess->backend)
+ backend_size(sess->backend, sess->wc, sess->hc);
+
+ return true;
+}
+
+static size_t sesschan_seat_output(
+ Seat *seat, SeatOutputType type, const void *data, size_t len)
+{
+ sesschan *sess = container_of(seat, sesschan, seat);
+ return sshfwd_write_ext(sess->c, type == SEAT_OUTPUT_STDERR, data, len);
+}
+
+static void sesschan_check_close_callback(void *vctx)
+{
+ sesschan *sess = (sesschan *)vctx;
+
+ /*
+ * Once we've seen incoming EOF from the backend (aka EIO from the
+ * pty master) and also passed on the process's exit status, we
+ * should proactively initiate closure of the session channel.
+ */
+ if (sess->seen_eof && sess->seen_exit)
+ sshfwd_initiate_close(sess->c, NULL);
+}
+
+static bool sesschan_want_close(Channel *chan, bool seen_eof, bool rcvd_eof)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ /*
+ * Similarly to above, we don't want to initiate channel closure
+ * until we've sent the process's exit status, _even_ if EOF of
+ * the actual data stream has happened in both directions.
+ */
+ return (sess->seen_eof && sess->seen_exit);
+}
+
+static bool sesschan_seat_eof(Seat *seat)
+{
+ sesschan *sess = container_of(seat, sesschan, seat);
+
+ sshfwd_write_eof(sess->c);
+ sess->seen_eof = true;
+
+ queue_toplevel_callback(sesschan_check_close_callback, sess);
+ return true;
+}
+
+static void sesschan_notify_remote_exit(Seat *seat)
+{
+ sesschan *sess = container_of(seat, sesschan, seat);
+
+ if (!sess->backend)
+ return;
+
+ bool got_signal = false;
+ if (!sess->ssc->exit_signal_numeric) {
+ char *sigmsg;
+ ptrlen signame = pty_backend_exit_signame(sess->backend, &sigmsg);
+
+ if (signame.len) {
+ if (!sigmsg)
+ sigmsg = dupstr("");
+
+ sshfwd_send_exit_signal(
+ sess->c, signame, false, ptrlen_from_asciz(sigmsg));
+
+ got_signal = true;
+ }
+
+ sfree(sigmsg);
+ } else {
+ int signum = pty_backend_exit_signum(sess->backend);
+
+ if (signum >= 0) {
+ sshfwd_send_exit_signal_numeric(sess->c, signum, false,
+ PTRLEN_LITERAL(""));
+ got_signal = true;
+ }
+ }
+
+ if (!got_signal)
+ sshfwd_send_exit_status(sess->c, backend_exitcode(sess->backend));
+
+ sess->seen_exit = true;
+ queue_toplevel_callback(sesschan_check_close_callback, sess);
+}
+
+static void sesschan_connection_fatal(Seat *seat, const char *message)
+{
+ sesschan *sess = container_of(seat, sesschan, seat);
+
+ /* Closest translation I can think of */
+ sshfwd_send_exit_signal(
+ sess->c, PTRLEN_LITERAL("HUP"), false, ptrlen_from_asciz(message));
+
+ sess->ignoring_input = true;
+}
+
+static bool sesschan_get_window_pixel_size(Seat *seat, int *width, int *height)
+{
+ sesschan *sess = container_of(seat, sesschan, seat);
+
+ *width = sess->wp;
+ *height = sess->hp;
+
+ return true;
+}
+
+/* ----------------------------------------------------------------------
+ * Built-in SFTP subsystem.
+ */
+
+static size_t sftp_chan_send(Channel *chan, bool is_stderr,
+ const void *data, size_t length)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+
+ bufchain_add(&sess->subsys_input, data, length);
+
+ while (bufchain_size(&sess->subsys_input) >= 4) {
+ char lenbuf[4];
+ unsigned pktlen;
+ struct sftp_packet *pkt, *reply;
+
+ bufchain_fetch(&sess->subsys_input, lenbuf, 4);
+ pktlen = GET_32BIT_MSB_FIRST(lenbuf);
+
+ if (bufchain_size(&sess->subsys_input) - 4 < pktlen)
+ break; /* wait for more data */
+
+ bufchain_consume(&sess->subsys_input, 4);
+ pkt = sftp_recv_prepare(pktlen);
+ bufchain_fetch_consume(&sess->subsys_input, pkt->data, pktlen);
+ sftp_recv_finish(pkt);
+ reply = sftp_handle_request(sess->sftpsrv, pkt);
+ sftp_pkt_free(pkt);
+
+ sftp_send_prepare(reply);
+ sshfwd_write(sess->c, reply->data, reply->length);
+ sftp_pkt_free(reply);
+ }
+
+ return 0;
+}
+
+static void sftp_chan_send_eof(Channel *chan)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ sshfwd_write_eof(sess->c);
+}
+
+static char *sftp_log_close_msg(Channel *chan)
+{
+ return dupstr("Session channel (SFTP) closed");
+}
+
+/* ----------------------------------------------------------------------
+ * Built-in SCP subsystem.
+ */
+
+static size_t scp_chan_send(Channel *chan, bool is_stderr,
+ const void *data, size_t length)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ return scp_send(sess->scpsrv, data, length);
+}
+
+static void scp_chan_send_eof(Channel *chan)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ scp_eof(sess->scpsrv);
+}
+
+static char *scp_log_close_msg(Channel *chan)
+{
+ return dupstr("Session channel (SCP) closed");
+}
+
+static void scp_set_input_wanted(Channel *chan, bool wanted)
+{
+ sesschan *sess = container_of(chan, sesschan, chan);
+ scp_throttle(sess->scpsrv, !wanted);
+}
diff --git a/ssh/sftp.c b/ssh/sftp.c
new file mode 100644
index 00000000..1f98c5ec
--- /dev/null
+++ b/ssh/sftp.c
@@ -0,0 +1,1204 @@
+/*
+ * sftp.c: SFTP generic client code.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <limits.h>
+
+#include "misc.h"
+#include "tree234.h"
+#include "sftp.h"
+
+static const char *fxp_error_message;
+static int fxp_errtype;
+
+static void fxp_internal_error(const char *msg);
+
+/* ----------------------------------------------------------------------
+ * Client-specific parts of the send- and receive-packet system.
+ */
+
+static bool sftp_send(struct sftp_packet *pkt)
+{
+ bool ret;
+ sftp_send_prepare(pkt);
+ ret = sftp_senddata(pkt->data, pkt->length);
+ sftp_pkt_free(pkt);
+ return ret;
+}
+
+struct sftp_packet *sftp_recv(void)
+{
+ struct sftp_packet *pkt;
+ char x[4];
+
+ if (!sftp_recvdata(x, 4))
+ return NULL;
+
+ /* Impose _some_ upper bound on packet size. We never expect to
+ * receive more than 32K of data in response to an FXP_READ,
+ * because we decide how much data to ask for. FXP_READDIR and
+ * pathname-returning things like FXP_REALPATH don't have an
+ * explicit bound, so I suppose we just have to trust the server
+ * to be sensible. */
+ unsigned pktlen = GET_32BIT_MSB_FIRST(x);
+ if (pktlen > (1<<20))
+ return NULL;
+
+ pkt = sftp_recv_prepare(pktlen);
+
+ if (!sftp_recvdata(pkt->data, pkt->length)) {
+ sftp_pkt_free(pkt);
+ return NULL;
+ }
+
+ if (!sftp_recv_finish(pkt)) {
+ sftp_pkt_free(pkt);
+ return NULL;
+ }
+
+ return pkt;
+}
+
+/* ----------------------------------------------------------------------
+ * Request ID allocation and temporary dispatch routines.
+ */
+
+#define REQUEST_ID_OFFSET 256
+
+struct sftp_request {
+ unsigned id;
+ bool registered;
+ void *userdata;
+};
+
+static int sftp_reqcmp(void *av, void *bv)
+{
+ struct sftp_request *a = (struct sftp_request *)av;
+ struct sftp_request *b = (struct sftp_request *)bv;
+ if (a->id < b->id)
+ return -1;
+ if (a->id > b->id)
+ return +1;
+ return 0;
+}
+static int sftp_reqfind(void *av, void *bv)
+{
+ unsigned *a = (unsigned *) av;
+ struct sftp_request *b = (struct sftp_request *)bv;
+ if (*a < b->id)
+ return -1;
+ if (*a > b->id)
+ return +1;
+ return 0;
+}
+
+static tree234 *sftp_requests;
+
+static struct sftp_request *sftp_alloc_request(void)
+{
+ unsigned low, high, mid;
+ int tsize;
+ struct sftp_request *r;
+
+ if (sftp_requests == NULL)
+ sftp_requests = newtree234(sftp_reqcmp);
+
+ /*
+ * First-fit allocation of request IDs: always pick the lowest
+ * unused one. To do this, binary-search using the counted
+ * B-tree to find the largest ID which is in a contiguous
+ * sequence from the beginning. (Precisely everything in that
+ * sequence must have ID equal to its tree index plus
+ * REQUEST_ID_OFFSET.)
+ */
+ tsize = count234(sftp_requests);
+
+ low = -1;
+ high = tsize;
+ while (high - low > 1) {
+ mid = (high + low) / 2;
+ r = index234(sftp_requests, mid);
+ if (r->id == mid + REQUEST_ID_OFFSET)
+ low = mid; /* this one is fine */
+ else
+ high = mid; /* this one is past it */
+ }
+ /*
+ * Now low points to either -1, or the tree index of the
+ * largest ID in the initial sequence.
+ */
+ {
+ unsigned i = low + 1 + REQUEST_ID_OFFSET;
+ assert(NULL == find234(sftp_requests, &i, sftp_reqfind));
+ }
+
+ /*
+ * So the request ID we need to create is
+ * low + 1 + REQUEST_ID_OFFSET.
+ */
+ r = snew(struct sftp_request);
+ r->id = low + 1 + REQUEST_ID_OFFSET;
+ r->registered = false;
+ r->userdata = NULL;
+ add234(sftp_requests, r);
+ return r;
+}
+
+void sftp_cleanup_request(void)
+{
+ if (sftp_requests != NULL) {
+ freetree234(sftp_requests);
+ sftp_requests = NULL;
+ }
+}
+
+void sftp_register(struct sftp_request *req)
+{
+ req->registered = true;
+}
+
+struct sftp_request *sftp_find_request(struct sftp_packet *pktin)
+{
+ unsigned id;
+ struct sftp_request *req;
+
+ if (!pktin) {
+ fxp_internal_error("did not receive a valid SFTP packet\n");
+ return NULL;
+ }
+
+ id = get_uint32(pktin);
+ if (get_err(pktin)) {
+ fxp_internal_error("did not receive a valid SFTP packet\n");
+ return NULL;
+ }
+
+ req = find234(sftp_requests, &id, sftp_reqfind);
+ if (!req || !req->registered) {
+ fxp_internal_error("request ID mismatch\n");
+ return NULL;
+ }
+
+ del234(sftp_requests, req);
+
+ return req;
+}
+
+/* ----------------------------------------------------------------------
+ * SFTP primitives.
+ */
+
+/*
+ * Deal with (and free) an FXP_STATUS packet. Return 1 if
+ * SSH_FX_OK, 0 if SSH_FX_EOF, and -1 for anything else (error).
+ * Also place the status into fxp_errtype.
+ */
+static int fxp_got_status(struct sftp_packet *pktin)
+{
+ static const char *const messages[] = {
+ /* SSH_FX_OK. The only time we will display a _message_ for this
+ * is if we were expecting something other than FXP_STATUS on
+ * success, so this is actually an error message! */
+ "unexpected OK response",
+ "end of file",
+ "no such file or directory",
+ "permission denied",
+ "failure",
+ "bad message",
+ "no connection",
+ "connection lost",
+ "operation unsupported",
+ };
+
+ if (pktin->type != SSH_FXP_STATUS) {
+ fxp_error_message = "expected FXP_STATUS packet";
+ fxp_errtype = -1;
+ } else {
+ fxp_errtype = get_uint32(pktin);
+ if (get_err(pktin)) {
+ fxp_error_message = "malformed FXP_STATUS packet";
+ fxp_errtype = -1;
+ } else {
+ if (fxp_errtype < 0 || fxp_errtype >= lenof(messages))
+ fxp_error_message = "unknown error code";
+ else
+ fxp_error_message = messages[fxp_errtype];
+ }
+ }
+
+ if (fxp_errtype == SSH_FX_OK)
+ return 1;
+ else if (fxp_errtype == SSH_FX_EOF)
+ return 0;
+ else
+ return -1;
+}
+
+static void fxp_internal_error(const char *msg)
+{
+ fxp_error_message = msg;
+ fxp_errtype = -1;
+}
+
+const char *fxp_error(void)
+{
+ return fxp_error_message;
+}
+
+int fxp_error_type(void)
+{
+ return fxp_errtype;
+}
+
+/*
+ * Perform exchange of init/version packets. Return 0 on failure.
+ */
+bool fxp_init(void)
+{
+ struct sftp_packet *pktout, *pktin;
+ unsigned long remotever;
+
+ pktout = sftp_pkt_init(SSH_FXP_INIT);
+ put_uint32(pktout, SFTP_PROTO_VERSION);
+ sftp_send(pktout);
+
+ pktin = sftp_recv();
+ if (!pktin) {
+ fxp_internal_error("could not connect");
+ return false;
+ }
+ if (pktin->type != SSH_FXP_VERSION) {
+ fxp_internal_error("did not receive FXP_VERSION");
+ sftp_pkt_free(pktin);
+ return false;
+ }
+ remotever = get_uint32(pktin);
+ if (get_err(pktin)) {
+ fxp_internal_error("malformed FXP_VERSION packet");
+ sftp_pkt_free(pktin);
+ return false;
+ }
+ if (remotever > SFTP_PROTO_VERSION) {
+ fxp_internal_error("remote protocol is more advanced than we support");
+ sftp_pkt_free(pktin);
+ return false;
+ }
+ /*
+ * In principle, this packet might also contain extension-
+ * string pairs. We should work through them and look for any
+ * we recognise. In practice we don't currently do so because
+ * we know we don't recognise _any_.
+ */
+ sftp_pkt_free(pktin);
+
+ return true;
+}
+
+/*
+ * Canonify a pathname.
+ */
+struct sftp_request *fxp_realpath_send(const char *path)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_REALPATH);
+ put_uint32(pktout, req->id);
+ put_stringz(pktout, path);
+ sftp_send(pktout);
+
+ return req;
+}
+
+char *fxp_realpath_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ sfree(req);
+
+ if (pktin->type == SSH_FXP_NAME) {
+ unsigned long count;
+ char *path;
+ ptrlen name;
+
+ count = get_uint32(pktin);
+ if (get_err(pktin) || count != 1) {
+ fxp_internal_error("REALPATH did not return name count of 1\n");
+ sftp_pkt_free(pktin);
+ return NULL;
+ }
+ name = get_string(pktin);
+ if (get_err(pktin)) {
+ fxp_internal_error("REALPATH returned malformed FXP_NAME\n");
+ sftp_pkt_free(pktin);
+ return NULL;
+ }
+ path = mkstr(name);
+ sftp_pkt_free(pktin);
+ return path;
+ } else {
+ fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return NULL;
+ }
+}
+
+/*
+ * Open a file.
+ */
+struct sftp_request *fxp_open_send(const char *path, int type,
+ const struct fxp_attrs *attrs)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_OPEN);
+ put_uint32(pktout, req->id);
+ put_stringz(pktout, path);
+ put_uint32(pktout, type);
+ put_fxp_attrs(pktout, attrs ? *attrs : no_attrs);
+ sftp_send(pktout);
+
+ return req;
+}
+
+static struct fxp_handle *fxp_got_handle(struct sftp_packet *pktin)
+{
+ ptrlen id;
+ struct fxp_handle *handle;
+
+ id = get_string(pktin);
+ if (get_err(pktin)) {
+ fxp_internal_error("received malformed FXP_HANDLE");
+ sftp_pkt_free(pktin);
+ return NULL;
+ }
+ handle = snew(struct fxp_handle);
+ handle->hstring = mkstr(id);
+ handle->hlen = id.len;
+ sftp_pkt_free(pktin);
+ return handle;
+}
+
+struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin,
+ struct sftp_request *req)
+{
+ sfree(req);
+
+ if (pktin->type == SSH_FXP_HANDLE) {
+ return fxp_got_handle(pktin);
+ } else {
+ fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return NULL;
+ }
+}
+
+/*
+ * Open a directory.
+ */
+struct sftp_request *fxp_opendir_send(const char *path)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_OPENDIR);
+ put_uint32(pktout, req->id);
+ put_stringz(pktout, path);
+ sftp_send(pktout);
+
+ return req;
+}
+
+struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin,
+ struct sftp_request *req)
+{
+ sfree(req);
+ if (pktin->type == SSH_FXP_HANDLE) {
+ return fxp_got_handle(pktin);
+ } else {
+ fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return NULL;
+ }
+}
+
+/*
+ * Close a file/dir.
+ */
+struct sftp_request *fxp_close_send(struct fxp_handle *handle)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_CLOSE);
+ put_uint32(pktout, req->id);
+ put_string(pktout, handle->hstring, handle->hlen);
+ sftp_send(pktout);
+
+ sfree(handle->hstring);
+ sfree(handle);
+
+ return req;
+}
+
+bool fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ sfree(req);
+ fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return fxp_errtype == SSH_FX_OK;
+}
+
+struct sftp_request *fxp_mkdir_send(const char *path,
+ const struct fxp_attrs *attrs)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_MKDIR);
+ put_uint32(pktout, req->id);
+ put_stringz(pktout, path);
+ put_fxp_attrs(pktout, attrs ? *attrs : no_attrs);
+ sftp_send(pktout);
+
+ return req;
+}
+
+bool fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ int id;
+ sfree(req);
+ id = fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return id == 1;
+}
+
+struct sftp_request *fxp_rmdir_send(const char *path)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_RMDIR);
+ put_uint32(pktout, req->id);
+ put_stringz(pktout, path);
+ sftp_send(pktout);
+
+ return req;
+}
+
+bool fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ int id;
+ sfree(req);
+ id = fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return id == 1;
+}
+
+struct sftp_request *fxp_remove_send(const char *fname)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_REMOVE);
+ put_uint32(pktout, req->id);
+ put_stringz(pktout, fname);
+ sftp_send(pktout);
+
+ return req;
+}
+
+bool fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ int id;
+ sfree(req);
+ id = fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return id == 1;
+}
+
+struct sftp_request *fxp_rename_send(const char *srcfname,
+ const char *dstfname)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_RENAME);
+ put_uint32(pktout, req->id);
+ put_stringz(pktout, srcfname);
+ put_stringz(pktout, dstfname);
+ sftp_send(pktout);
+
+ return req;
+}
+
+bool fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ int id;
+ sfree(req);
+ id = fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return id == 1;
+}
+
+/*
+ * Retrieve the attributes of a file. We have fxp_stat which works
+ * on filenames, and fxp_fstat which works on open file handles.
+ */
+struct sftp_request *fxp_stat_send(const char *fname)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_STAT);
+ put_uint32(pktout, req->id);
+ put_stringz(pktout, fname);
+ sftp_send(pktout);
+
+ return req;
+}
+
+static bool fxp_got_attrs(struct sftp_packet *pktin, struct fxp_attrs *attrs)
+{
+ get_fxp_attrs(pktin, attrs);
+ if (get_err(pktin)) {
+ fxp_internal_error("malformed SSH_FXP_ATTRS packet");
+ sftp_pkt_free(pktin);
+ return false;
+ }
+ sftp_pkt_free(pktin);
+ return true;
+}
+
+bool fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req,
+ struct fxp_attrs *attrs)
+{
+ sfree(req);
+ if (pktin->type == SSH_FXP_ATTRS) {
+ return fxp_got_attrs(pktin, attrs);
+ } else {
+ fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return false;
+ }
+}
+
+struct sftp_request *fxp_fstat_send(struct fxp_handle *handle)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_FSTAT);
+ put_uint32(pktout, req->id);
+ put_string(pktout, handle->hstring, handle->hlen);
+ sftp_send(pktout);
+
+ return req;
+}
+
+bool fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req,
+ struct fxp_attrs *attrs)
+{
+ sfree(req);
+ if (pktin->type == SSH_FXP_ATTRS) {
+ return fxp_got_attrs(pktin, attrs);
+ } else {
+ fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return false;
+ }
+}
+
+/*
+ * Set the attributes of a file.
+ */
+struct sftp_request *fxp_setstat_send(const char *fname,
+ struct fxp_attrs attrs)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_SETSTAT);
+ put_uint32(pktout, req->id);
+ put_stringz(pktout, fname);
+ put_fxp_attrs(pktout, attrs);
+ sftp_send(pktout);
+
+ return req;
+}
+
+bool fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ int id;
+ sfree(req);
+ id = fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return id == 1;
+}
+
+struct sftp_request *fxp_fsetstat_send(struct fxp_handle *handle,
+ struct fxp_attrs attrs)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_FSETSTAT);
+ put_uint32(pktout, req->id);
+ put_string(pktout, handle->hstring, handle->hlen);
+ put_fxp_attrs(pktout, attrs);
+ sftp_send(pktout);
+
+ return req;
+}
+
+bool fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *req)
+{
+ int id;
+ sfree(req);
+ id = fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return id == 1;
+}
+
+/*
+ * Read from a file. Returns the number of bytes read, or -1 on an
+ * error, or possibly 0 if EOF. (I'm not entirely sure whether it
+ * will return 0 on EOF, or return -1 and store SSH_FX_EOF in the
+ * error indicator. It might even depend on the SFTP server.)
+ */
+struct sftp_request *fxp_read_send(struct fxp_handle *handle,
+ uint64_t offset, int len)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_READ);
+ put_uint32(pktout, req->id);
+ put_string(pktout, handle->hstring, handle->hlen);
+ put_uint64(pktout, offset);
+ put_uint32(pktout, len);
+ sftp_send(pktout);
+
+ return req;
+}
+
+int fxp_read_recv(struct sftp_packet *pktin, struct sftp_request *req,
+ char *buffer, int len)
+{
+ sfree(req);
+ if (pktin->type == SSH_FXP_DATA) {
+ ptrlen data;
+
+ data = get_string(pktin);
+ if (get_err(pktin)) {
+ fxp_internal_error("READ returned malformed SSH_FXP_DATA packet");
+ sftp_pkt_free(pktin);
+ return -1;
+ }
+
+ if (data.len > len) {
+ fxp_internal_error("READ returned more bytes than requested");
+ sftp_pkt_free(pktin);
+ return -1;
+ }
+
+ memcpy(buffer, data.ptr, data.len);
+ sftp_pkt_free(pktin);
+ return data.len;
+ } else {
+ fxp_got_status(pktin);
+ sftp_pkt_free(pktin);
+ return -1;
+ }
+}
+
+/*
+ * Read from a directory.
+ */
+struct sftp_request *fxp_readdir_send(struct fxp_handle *handle)
+{
+ struct sftp_request *req = sftp_alloc_request();
+ struct sftp_packet *pktout;
+
+ pktout = sftp_pkt_init(SSH_FXP_READDIR);
+ put_uint32(pktout, req->id);
+ put_string(pktout, handle->hstring, handle->hlen);
+ sftp_send(pktout);
+
+ return req;
+}
+
+struct fxp_names *fxp_readdir_recv(struct sftp_packet *pktin,
+ struct sftp_request *req)
+{
+ sfree(req);
+ if (pktin->type == SSH_FXP_NAME) {
+ struct fxp_names *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/ssh/sftp.h b/ssh/sftp.h
new file mode 100644
index 00000000..fb7d3267
--- /dev/null
+++ b/ssh/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 appropriate
+ * sftp_reply_foo() function on the given reply packet.
+ */
+
+ /* Should call fxp_reply_error or fxp_reply_simple_name */
+ void (*realpath)(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path);
+
+ /* Should call fxp_reply_error or fxp_reply_handle */
+ void (*open)(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path, unsigned flags, struct fxp_attrs attrs);
+
+ /* Should call fxp_reply_error or fxp_reply_handle */
+ void (*opendir)(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path);
+
+ /* Should call fxp_reply_error or fxp_reply_ok */
+ void (*close)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle);
+
+ /* Should call fxp_reply_error or fxp_reply_ok */
+ void (*mkdir)(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path, struct fxp_attrs attrs);
+
+ /* Should call fxp_reply_error or fxp_reply_ok */
+ void (*rmdir)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path);
+
+ /* Should call fxp_reply_error or fxp_reply_ok */
+ void (*remove)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path);
+
+ /* Should call fxp_reply_error or fxp_reply_ok */
+ void (*rename)(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen srcpath, ptrlen dstpath);
+
+ /* Should call fxp_reply_error or fxp_reply_attrs */
+ void (*stat)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path,
+ bool follow_symlinks);
+
+ /* Should call fxp_reply_error or fxp_reply_attrs */
+ void (*fstat)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle);
+
+ /* Should call fxp_reply_error or fxp_reply_ok */
+ void (*setstat)(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path, struct fxp_attrs attrs);
+
+ /* Should call fxp_reply_error or fxp_reply_ok */
+ void (*fsetstat)(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle, struct fxp_attrs attrs);
+
+ /* Should call fxp_reply_error or fxp_reply_data */
+ void (*read)(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle, uint64_t offset, unsigned length);
+
+ /* Should call fxp_reply_error or fxp_reply_ok */
+ void (*write)(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle, uint64_t offset, ptrlen data);
+
+ /* Should call fxp_reply_error, or fxp_reply_name_count once and
+ * then fxp_reply_full_name that many times */
+ void (*readdir)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle,
+ int max_entries, bool omit_longname);
+};
+
+static inline SftpServer *sftpsrv_new(const SftpServerVtable *vt)
+{ return vt->new(vt); }
+static inline void sftpsrv_free(SftpServer *srv)
+{ srv->vt->free(srv); }
+static inline void sftpsrv_realpath(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path)
+{ srv->vt->realpath(srv, reply, path); }
+static inline void sftpsrv_open(
+ SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path, unsigned flags, struct fxp_attrs attrs)
+{ srv->vt->open(srv, reply, path, flags, attrs); }
+static inline void sftpsrv_opendir(
+ SftpServer *srv, SftpReplyBuilder *reply, ptrlen path)
+{ srv->vt->opendir(srv, reply, path); }
+static inline void sftpsrv_close(
+ SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle)
+{ srv->vt->close(srv, reply, handle); }
+static inline void sftpsrv_mkdir(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path, struct fxp_attrs attrs)
+{ srv->vt->mkdir(srv, reply, path, attrs); }
+static inline void sftpsrv_rmdir(
+ SftpServer *srv, SftpReplyBuilder *reply, ptrlen path)
+{ srv->vt->rmdir(srv, reply, path); }
+static inline void sftpsrv_remove(
+ SftpServer *srv, SftpReplyBuilder *reply, ptrlen path)
+{ srv->vt->remove(srv, reply, path); }
+static inline void sftpsrv_rename(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen srcpath, ptrlen dstpath)
+{ srv->vt->rename(srv, reply, srcpath, dstpath); }
+static inline void sftpsrv_stat(
+ SftpServer *srv, SftpReplyBuilder *reply, ptrlen path, bool follow)
+{ srv->vt->stat(srv, reply, path, follow); }
+static inline void sftpsrv_fstat(
+ SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle)
+{ srv->vt->fstat(srv, reply, handle); }
+static inline void sftpsrv_setstat(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path, struct fxp_attrs attrs)
+{ srv->vt->setstat(srv, reply, path, attrs); }
+static inline void sftpsrv_fsetstat(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle, struct fxp_attrs attrs)
+{ srv->vt->fsetstat(srv, reply, handle, attrs); }
+static inline void sftpsrv_read(
+ SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle, uint64_t offset, unsigned length)
+{ srv->vt->read(srv, reply, handle, offset, length); }
+static inline void sftpsrv_write(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle, uint64_t offset, ptrlen data)
+{ srv->vt->write(srv, reply, handle, offset, data); }
+static inline void sftpsrv_readdir(
+ SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle,
+ int max_entries, bool omit_longname)
+{ srv->vt->readdir(srv, reply, handle, max_entries, omit_longname); }
+
+typedef struct SftpReplyBuilderVtable SftpReplyBuilderVtable;
+struct SftpReplyBuilder {
+ const SftpReplyBuilderVtable *vt;
+};
+struct SftpReplyBuilderVtable {
+ void (*reply_ok)(SftpReplyBuilder *reply);
+ void (*reply_error)(SftpReplyBuilder *reply, unsigned code,
+ const char *msg);
+ void (*reply_simple_name)(SftpReplyBuilder *reply, ptrlen name);
+ void (*reply_name_count)(SftpReplyBuilder *reply, unsigned count);
+ void (*reply_full_name)(SftpReplyBuilder *reply, ptrlen name,
+ ptrlen longname, struct fxp_attrs attrs);
+ void (*reply_handle)(SftpReplyBuilder *reply, ptrlen handle);
+ void (*reply_data)(SftpReplyBuilder *reply, ptrlen data);
+ void (*reply_attrs)(SftpReplyBuilder *reply, struct fxp_attrs attrs);
+};
+
+static inline void fxp_reply_ok(SftpReplyBuilder *reply)
+{ reply->vt->reply_ok(reply); }
+static inline void fxp_reply_error(SftpReplyBuilder *reply, unsigned code,
+ const char *msg)
+{ reply->vt->reply_error(reply, code, msg); }
+static inline void fxp_reply_simple_name(SftpReplyBuilder *reply, ptrlen name)
+{ reply->vt->reply_simple_name(reply, name); }
+static inline void fxp_reply_name_count(
+ SftpReplyBuilder *reply, unsigned count)
+{ reply->vt->reply_name_count(reply, count); }
+static inline void fxp_reply_full_name(SftpReplyBuilder *reply, ptrlen name,
+ ptrlen longname, struct fxp_attrs attrs)
+{ reply->vt->reply_full_name(reply, name, longname, attrs); }
+static inline void fxp_reply_handle(SftpReplyBuilder *reply, ptrlen handle)
+{ reply->vt->reply_handle(reply, handle); }
+static inline void fxp_reply_data(SftpReplyBuilder *reply, ptrlen data)
+{ reply->vt->reply_data(reply, data); }
+static inline void fxp_reply_attrs(
+ SftpReplyBuilder *reply, struct fxp_attrs attrs)
+{ reply->vt->reply_attrs(reply, attrs); }
+
+/*
+ * The usual implementation of an SftpReplyBuilder, containing a
+ * 'struct sftp_packet' which is assumed to be already initialised
+ * before one of the above request methods is called.
+ */
+extern const struct SftpReplyBuilderVtable DefaultSftpReplyBuilder_vt;
+typedef struct DefaultSftpReplyBuilder DefaultSftpReplyBuilder;
+struct DefaultSftpReplyBuilder {
+ SftpReplyBuilder rb;
+ struct sftp_packet *pkt;
+};
+
+/*
+ * The top-level function that handles an SFTP request, given an
+ * implementation of the above SftpServer abstraction to do the actual
+ * filesystem work. It handles all the marshalling and unmarshalling
+ * of packets, and the copying of request ids into the responses.
+ */
+struct sftp_packet *sftp_handle_request(
+ SftpServer *srv, struct sftp_packet *request);
+
+/* ----------------------------------------------------------------------
+ * Not exactly SFTP-related, but here's a system that implements an
+ * old-fashioned SCP server module, given an SftpServer vtable to use
+ * as its underlying filesystem access.
+ */
+
+typedef struct ScpServer ScpServer;
+typedef struct ScpServerVtable ScpServerVtable;
+struct ScpServer {
+ const struct ScpServerVtable *vt;
+};
+struct ScpServerVtable {
+ void (*free)(ScpServer *s);
+
+ size_t (*send)(ScpServer *s, const void *data, size_t length);
+ void (*throttle)(ScpServer *s, bool throttled);
+ void (*eof)(ScpServer *s);
+};
+
+static inline void scp_free(ScpServer *s)
+{ s->vt->free(s); }
+static inline size_t scp_send(ScpServer *s, const void *data, size_t length)
+{ return s->vt->send(s, data, length); }
+static inline void scp_throttle(ScpServer *s, bool throttled)
+{ s->vt->throttle(s, throttled); }
+static inline void scp_eof(ScpServer *s)
+{ s->vt->eof(s); }
+
+/*
+ * Create an ScpServer by calling this function, giving it the command
+ * you received from the SSH client to execute. If that command is
+ * recognised as an scp command, it will construct an ScpServer object
+ * and return it; otherwise, it will return NULL, and you should
+ * execute the command in whatever way you normally would.
+ *
+ * The ScpServer will generate output for the client by writing it to
+ * the provided SshChannel using sshfwd_write; you pass it input using
+ * the send method in its own vtable.
+ */
+ScpServer *scp_recognise_exec(
+ SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen command);
diff --git a/sftpcommon.c b/ssh/sftpcommon.c
index f2f9a3bc..f2f9a3bc 100644
--- a/sftpcommon.c
+++ b/ssh/sftpcommon.c
diff --git a/sftpserver.c b/ssh/sftpserver.c
index ba216872..ba216872 100644
--- a/sftpserver.c
+++ b/ssh/sftpserver.c
diff --git a/ssh/sharing.c b/ssh/sharing.c
new file mode 100644
index 00000000..97337229
--- /dev/null
+++ b/ssh/sharing.c
@@ -0,0 +1,2177 @@
+/*
+ * 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
+ *
+ * <protocol>-<version>-<softwareversion> [comments] \r\n
+ *
+ * The only difference is that in real SSH-2, <protocol> 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 <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <limits.h>
+#include <errno.h>
+
+#include "putty.h"
+#include "tree234.h"
+#include "ssh.h"
+#include "sshcr.h"
+
+struct ssh_sharing_state {
+ char *sockname; /* the socket name, kept for cleanup */
+ Socket *listensock; /* the master listening Socket */
+ tree234 *connections; /* holds ssh_sharing_connstates */
+ unsigned nextid; /* preferred id for next connstate */
+ ConnectionLayer *cl; /* instance of the ssh connection layer */
+ char *server_verstring; /* server version string after "SSH-" */
+
+ Plug plug;
+};
+
+struct share_globreq;
+
+struct ssh_sharing_connstate {
+ unsigned id; /* used to identify this downstream in log messages */
+
+ Socket *sock; /* the Socket for this connection */
+ struct ssh_sharing_state *parent;
+
+ int crLine; /* coroutine state for share_receive */
+
+ bool sent_verstring, got_verstring;
+ int curr_packetlen;
+
+ unsigned char recvbuf[0x4010];
+ size_t recvlen;
+
+ /*
+ * Assorted state we have to remember about this downstream, so
+ * that we can clean it up appropriately when the downstream goes
+ * away.
+ */
+
+ /* Channels which don't have a downstream id, i.e. we've passed a
+ * CHANNEL_OPEN down from the server but not had an
+ * OPEN_CONFIRMATION or OPEN_FAILURE back. If downstream goes
+ * away, we respond to all of these with OPEN_FAILURE. */
+ tree234 *halfchannels; /* stores 'struct share_halfchannel' */
+
+ /* Channels which do have a downstream id. We need to index these
+ * by both server id and upstream id, so we can find a channel
+ * when handling either an upward or a downward message referring
+ * to it. */
+ tree234 *channels_by_us; /* stores 'struct share_channel' */
+ tree234 *channels_by_server; /* stores 'struct share_channel' */
+
+ /* Another class of channel which doesn't have a downstream id.
+ * The difference between these and halfchannels is that xchannels
+ * do have an *upstream* id, because upstream has already accepted
+ * the channel request from the server. This arises in the case of
+ * X forwarding, where we have to accept the request and read the
+ * X authorisation data before we know whether the channel needs
+ * to be forwarded to a downstream. */
+ tree234 *xchannels_by_us; /* stores 'struct share_xchannel' */
+ tree234 *xchannels_by_server; /* stores 'struct share_xchannel' */
+
+ /* Remote port forwarding requests in force. */
+ tree234 *forwardings; /* stores 'struct share_forwarding' */
+
+ /* Global requests we've sent on to the server, pending replies. */
+ struct share_globreq *globreq_head, *globreq_tail;
+
+ Plug plug;
+};
+
+struct share_halfchannel {
+ unsigned server_id;
+};
+
+/* States of a share_channel. */
+enum {
+ OPEN,
+ SENT_CLOSE,
+ RCVD_CLOSE,
+ /* Downstream has sent CHANNEL_OPEN but server hasn't replied yet.
+ * If downstream goes away when a channel is in this state, we
+ * must wait for the server's response before starting to send
+ * CLOSE. Channels in this state are also not held in
+ * channels_by_server, because their server_id field is
+ * meaningless. */
+ UNACKNOWLEDGED
+};
+
+struct share_channel {
+ unsigned downstream_id, upstream_id, server_id;
+ int downstream_maxpkt;
+ int state;
+ /*
+ * Some channels (specifically, channels on which downstream has
+ * sent "x11-req") have the additional function of storing a set
+ * of downstream X authorisation data and a handle to an upstream
+ * fake set.
+ */
+ struct X11FakeAuth *x11_auth_upstream;
+ int x11_auth_proto;
+ char *x11_auth_data;
+ int x11_auth_datalen;
+ bool x11_one_shot;
+};
+
+struct share_forwarding {
+ char *host;
+ int port;
+ bool active; /* has the server sent REQUEST_SUCCESS? */
+ struct ssh_rportfwd *rpf;
+};
+
+struct share_xchannel_message {
+ struct share_xchannel_message *next;
+ int type;
+ unsigned char *data;
+ int datalen;
+};
+
+struct share_xchannel {
+ unsigned upstream_id, server_id;
+
+ /*
+ * xchannels come in two flavours: live and dead. Live ones are
+ * waiting for an OPEN_CONFIRMATION or OPEN_FAILURE from
+ * downstream; dead ones have had an OPEN_FAILURE, so they only
+ * exist as a means of letting us conveniently respond to further
+ * channel messages from the server until such time as the server
+ * sends us CHANNEL_CLOSE.
+ */
+ bool live;
+
+ /*
+ * When we receive OPEN_CONFIRMATION, we will need to send a
+ * WINDOW_ADJUST to the server to synchronise the windows. For
+ * this purpose we need to know what window we have so far offered
+ * the server. We record this as exactly the value in the
+ * OPEN_CONFIRMATION that upstream sent us, adjusted by the amount
+ * by which the two X greetings differed in length.
+ */
+ int window;
+
+ /*
+ * Linked list of SSH messages from the server relating to this
+ * channel, which we queue up until downstream sends us an
+ * OPEN_CONFIRMATION and we can belatedly send them all on.
+ */
+ struct share_xchannel_message *msghead, *msgtail;
+};
+
+enum {
+ GLOBREQ_TCPIP_FORWARD,
+ GLOBREQ_CANCEL_TCPIP_FORWARD
+};
+
+struct share_globreq {
+ struct share_globreq *next;
+ int type;
+ bool want_reply;
+ struct share_forwarding *fwd;
+};
+
+static int share_connstate_cmp(void *av, void *bv)
+{
+ const struct ssh_sharing_connstate *a =
+ (const struct ssh_sharing_connstate *)av;
+ const struct ssh_sharing_connstate *b =
+ (const struct ssh_sharing_connstate *)bv;
+
+ if (a->id < b->id)
+ return -1;
+ else if (a->id > b->id)
+ return +1;
+ else
+ return 0;
+}
+
+static unsigned share_find_unused_id
+(struct ssh_sharing_state *sharestate, unsigned first)
+{
+ int low_orig, low, mid, high, high_orig;
+ struct ssh_sharing_connstate *cs;
+ unsigned ret;
+
+ /*
+ * Find the lowest unused downstream ID greater or equal to
+ * 'first'.
+ *
+ * Begin by seeing if 'first' itself is available. If it is, we'll
+ * just return it; if it's already in the tree, we'll find the
+ * tree index where it appears and use that for the next stage.
+ */
+ {
+ struct ssh_sharing_connstate dummy;
+ dummy.id = first;
+ cs = findrelpos234(sharestate->connections, &dummy, NULL,
+ REL234_GE, &low_orig);
+ if (!cs)
+ return first;
+ }
+
+ /*
+ * Now binary-search using the counted B-tree, to find the largest
+ * ID which is in a contiguous sequence from the beginning of that
+ * range.
+ */
+ low = low_orig;
+ high = high_orig = count234(sharestate->connections);
+ while (high - low > 1) {
+ mid = (high + low) / 2;
+ cs = index234(sharestate->connections, mid);
+ if (cs->id == first + (mid - low_orig))
+ low = mid; /* this one is still in the sequence */
+ else
+ high = mid; /* this one is past the end */
+ }
+
+ /*
+ * Now low is the tree index of the largest ID in the initial
+ * sequence. So the return value is one more than low's id, and we
+ * know low's id is given by the formula in the binary search loop
+ * above.
+ *
+ * (If an SSH connection went on for _enormously_ long, we might
+ * reach a point where all ids from 'first' to UINT_MAX were in
+ * use. In that situation the formula below would wrap round by
+ * one and return zero, which is conveniently the right way to
+ * signal 'no id available' from this function.)
+ */
+ ret = first + (low - low_orig) + 1;
+ {
+ struct ssh_sharing_connstate dummy;
+ dummy.id = ret;
+ assert(NULL == find234(sharestate->connections, &dummy, NULL));
+ }
+ return ret;
+}
+
+static int share_halfchannel_cmp(void *av, void *bv)
+{
+ const struct share_halfchannel *a = (const struct share_halfchannel *)av;
+ const struct share_halfchannel *b = (const struct share_halfchannel *)bv;
+
+ if (a->server_id < b->server_id)
+ return -1;
+ else if (a->server_id > b->server_id)
+ return +1;
+ else
+ return 0;
+}
+
+static int share_channel_us_cmp(void *av, void *bv)
+{
+ const struct share_channel *a = (const struct share_channel *)av;
+ const struct share_channel *b = (const struct share_channel *)bv;
+
+ if (a->upstream_id < b->upstream_id)
+ return -1;
+ else if (a->upstream_id > b->upstream_id)
+ return +1;
+ else
+ return 0;
+}
+
+static int share_channel_server_cmp(void *av, void *bv)
+{
+ const struct share_channel *a = (const struct share_channel *)av;
+ const struct share_channel *b = (const struct share_channel *)bv;
+
+ if (a->server_id < b->server_id)
+ return -1;
+ else if (a->server_id > b->server_id)
+ return +1;
+ else
+ return 0;
+}
+
+static int share_xchannel_us_cmp(void *av, void *bv)
+{
+ const struct share_xchannel *a = (const struct share_xchannel *)av;
+ const struct share_xchannel *b = (const struct share_xchannel *)bv;
+
+ if (a->upstream_id < b->upstream_id)
+ return -1;
+ else if (a->upstream_id > b->upstream_id)
+ return +1;
+ else
+ return 0;
+}
+
+static int share_xchannel_server_cmp(void *av, void *bv)
+{
+ const struct share_xchannel *a = (const struct share_xchannel *)av;
+ const struct share_xchannel *b = (const struct share_xchannel *)bv;
+
+ if (a->server_id < b->server_id)
+ return -1;
+ else if (a->server_id > b->server_id)
+ return +1;
+ else
+ return 0;
+}
+
+static int share_forwarding_cmp(void *av, void *bv)
+{
+ const struct share_forwarding *a = (const struct share_forwarding *)av;
+ const struct share_forwarding *b = (const struct share_forwarding *)bv;
+ int i;
+
+ if ((i = strcmp(a->host, b->host)) != 0)
+ return i;
+ else if (a->port < b->port)
+ return -1;
+ else if (a->port > b->port)
+ return +1;
+ else
+ return 0;
+}
+
+static void share_xchannel_free(struct share_xchannel *xc)
+{
+ while (xc->msghead) {
+ struct share_xchannel_message *tmp = xc->msghead;
+ xc->msghead = tmp->next;
+ sfree(tmp);
+ }
+ sfree(xc);
+}
+
+static void share_connstate_free(struct ssh_sharing_connstate *cs)
+{
+ struct share_halfchannel *hc;
+ struct share_xchannel *xc;
+ struct share_channel *chan;
+ struct share_forwarding *fwd;
+
+ while ((hc = (struct share_halfchannel *)
+ delpos234(cs->halfchannels, 0)) != NULL)
+ sfree(hc);
+ freetree234(cs->halfchannels);
+
+ /* All channels live in 'channels_by_us' but only some in
+ * 'channels_by_server', so we use the former to find the list of
+ * ones to free */
+ freetree234(cs->channels_by_server);
+ while ((chan = (struct share_channel *)
+ delpos234(cs->channels_by_us, 0)) != NULL)
+ sfree(chan);
+ freetree234(cs->channels_by_us);
+
+ /* But every xchannel is in both trees, so it doesn't matter which
+ * we use to free them. */
+ while ((xc = (struct share_xchannel *)
+ delpos234(cs->xchannels_by_us, 0)) != NULL)
+ share_xchannel_free(xc);
+ freetree234(cs->xchannels_by_us);
+ freetree234(cs->xchannels_by_server);
+
+ while ((fwd = (struct share_forwarding *)
+ delpos234(cs->forwardings, 0)) != NULL)
+ sfree(fwd);
+ freetree234(cs->forwardings);
+
+ while (cs->globreq_head) {
+ struct share_globreq *globreq = cs->globreq_head;
+ cs->globreq_head = cs->globreq_head->next;
+ sfree(globreq);
+ }
+
+ if (cs->sock)
+ sk_close(cs->sock);
+
+ sfree(cs);
+}
+
+void sharestate_free(ssh_sharing_state *sharestate)
+{
+ struct ssh_sharing_connstate *cs;
+
+ platform_ssh_share_cleanup(sharestate->sockname);
+
+ while ((cs = (struct ssh_sharing_connstate *)
+ delpos234(sharestate->connections, 0)) != NULL) {
+ share_connstate_free(cs);
+ }
+ freetree234(sharestate->connections);
+ if (sharestate->listensock) {
+ sk_close(sharestate->listensock);
+ sharestate->listensock = NULL;
+ }
+ sfree(sharestate->server_verstring);
+ sfree(sharestate->sockname);
+ sfree(sharestate);
+}
+
+static struct share_halfchannel *share_add_halfchannel(
+ struct ssh_sharing_connstate *cs, unsigned server_id)
+{
+ struct share_halfchannel *hc = snew(struct share_halfchannel);
+ hc->server_id = server_id;
+ if (add234(cs->halfchannels, hc) != hc) {
+ /* Duplicate?! */
+ sfree(hc);
+ return NULL;
+ } else {
+ return hc;
+ }
+}
+
+static struct share_halfchannel *share_find_halfchannel(
+ struct ssh_sharing_connstate *cs, unsigned server_id)
+{
+ struct share_halfchannel dummyhc;
+ dummyhc.server_id = server_id;
+ return find234(cs->halfchannels, &dummyhc, NULL);
+}
+
+static void share_remove_halfchannel(struct ssh_sharing_connstate *cs,
+ struct share_halfchannel *hc)
+{
+ del234(cs->halfchannels, hc);
+ sfree(hc);
+}
+
+static struct share_channel *share_add_channel(
+ struct ssh_sharing_connstate *cs, unsigned downstream_id,
+ unsigned upstream_id, unsigned server_id, int state, int maxpkt)
+{
+ struct share_channel *chan = snew(struct share_channel);
+ chan->downstream_id = downstream_id;
+ chan->upstream_id = upstream_id;
+ chan->server_id = server_id;
+ chan->state = state;
+ chan->downstream_maxpkt = maxpkt;
+ chan->x11_auth_upstream = NULL;
+ chan->x11_auth_data = NULL;
+ chan->x11_auth_proto = -1;
+ chan->x11_auth_datalen = 0;
+ chan->x11_one_shot = false;
+ if (add234(cs->channels_by_us, chan) != chan) {
+ sfree(chan);
+ return NULL;
+ }
+ if (chan->state != UNACKNOWLEDGED) {
+ if (add234(cs->channels_by_server, chan) != chan) {
+ del234(cs->channels_by_us, chan);
+ sfree(chan);
+ return NULL;
+ }
+ }
+ return chan;
+}
+
+static void share_channel_set_server_id(struct ssh_sharing_connstate *cs,
+ struct share_channel *chan,
+ unsigned server_id, int newstate)
+{
+ chan->server_id = server_id;
+ chan->state = newstate;
+ assert(newstate != UNACKNOWLEDGED);
+ add234(cs->channels_by_server, chan);
+}
+
+static struct share_channel *share_find_channel_by_upstream(
+ struct ssh_sharing_connstate *cs, unsigned upstream_id)
+{
+ struct share_channel dummychan;
+ dummychan.upstream_id = upstream_id;
+ return find234(cs->channels_by_us, &dummychan, NULL);
+}
+
+static struct share_channel *share_find_channel_by_server(
+ struct ssh_sharing_connstate *cs, unsigned server_id)
+{
+ struct share_channel dummychan;
+ dummychan.server_id = server_id;
+ return find234(cs->channels_by_server, &dummychan, NULL);
+}
+
+static void share_remove_channel(struct ssh_sharing_connstate *cs,
+ struct share_channel *chan)
+{
+ del234(cs->channels_by_us, chan);
+ del234(cs->channels_by_server, chan);
+ if (chan->x11_auth_upstream)
+ ssh_remove_sharing_x11_display(cs->parent->cl,
+ chan->x11_auth_upstream);
+ sfree(chan->x11_auth_data);
+ sfree(chan);
+}
+
+static struct share_xchannel *share_add_xchannel(
+ struct ssh_sharing_connstate *cs, unsigned upstream_id, unsigned server_id)
+{
+ struct share_xchannel *xc = snew(struct share_xchannel);
+ xc->upstream_id = upstream_id;
+ xc->server_id = server_id;
+ xc->live = true;
+ xc->msghead = xc->msgtail = NULL;
+ if (add234(cs->xchannels_by_us, xc) != xc) {
+ sfree(xc);
+ return NULL;
+ }
+ if (add234(cs->xchannels_by_server, xc) != xc) {
+ del234(cs->xchannels_by_us, xc);
+ sfree(xc);
+ return NULL;
+ }
+ return xc;
+}
+
+static struct share_xchannel *share_find_xchannel_by_upstream(
+ struct ssh_sharing_connstate *cs, unsigned upstream_id)
+{
+ struct share_xchannel dummyxc;
+ dummyxc.upstream_id = upstream_id;
+ return find234(cs->xchannels_by_us, &dummyxc, NULL);
+}
+
+static struct share_xchannel *share_find_xchannel_by_server(
+ struct ssh_sharing_connstate *cs, unsigned server_id)
+{
+ struct share_xchannel dummyxc;
+ dummyxc.server_id = server_id;
+ return find234(cs->xchannels_by_server, &dummyxc, NULL);
+}
+
+static void share_remove_xchannel(struct ssh_sharing_connstate *cs,
+ struct share_xchannel *xc)
+{
+ del234(cs->xchannels_by_us, xc);
+ del234(cs->xchannels_by_server, xc);
+ share_xchannel_free(xc);
+}
+
+static struct share_forwarding *share_add_forwarding(
+ struct ssh_sharing_connstate *cs, const char *host, int port)
+{
+ struct share_forwarding *fwd = snew(struct share_forwarding);
+ fwd->host = dupstr(host);
+ fwd->port = port;
+ fwd->active = false;
+ if (add234(cs->forwardings, fwd) != fwd) {
+ /* Duplicate?! */
+ sfree(fwd);
+ return NULL;
+ }
+ return fwd;
+}
+
+static struct share_forwarding *share_find_forwarding(
+ struct ssh_sharing_connstate *cs, const char *host, int port)
+{
+ struct share_forwarding dummyfwd, *ret;
+ dummyfwd.host = dupstr(host);
+ dummyfwd.port = port;
+ ret = find234(cs->forwardings, &dummyfwd, NULL);
+ sfree(dummyfwd.host);
+ return ret;
+}
+
+static void share_remove_forwarding(struct ssh_sharing_connstate *cs,
+ struct share_forwarding *fwd)
+{
+ del234(cs->forwardings, fwd);
+ sfree(fwd);
+}
+
+static PRINTF_LIKE(2, 3) void log_downstream(struct ssh_sharing_connstate *cs,
+ const char *logfmt, ...)
+{
+ va_list ap;
+ char *buf;
+
+ va_start(ap, logfmt);
+ buf = dupvprintf(logfmt, ap);
+ va_end(ap);
+ logeventf(cs->parent->cl->logctx,
+ "Connection sharing downstream #%u: %s", cs->id, buf);
+ sfree(buf);
+}
+
+static PRINTF_LIKE(2, 3) void log_general(struct ssh_sharing_state *sharestate,
+ const char *logfmt, ...)
+{
+ va_list ap;
+ char *buf;
+
+ va_start(ap, logfmt);
+ buf = dupvprintf(logfmt, ap);
+ va_end(ap);
+ logeventf(sharestate->cl->logctx, "Connection sharing: %s", buf);
+ sfree(buf);
+}
+
+static void send_packet_to_downstream(struct ssh_sharing_connstate *cs,
+ int type, const void *pkt, int pktlen,
+ struct share_channel *chan)
+{
+ strbuf *packet;
+
+ if (!cs->sock) /* throw away all packets destined for a dead downstream */
+ return;
+
+ if (type == SSH2_MSG_CHANNEL_DATA) {
+ /*
+ * Special case which we take care of at a low level, so as to
+ * be sure to apply it in all cases. On rare occasions we
+ * might find that we have a channel for which the
+ * downstream's maximum packet size exceeds the max packet
+ * size we presented to the server on its behalf. (This can
+ * occur in X11 forwarding, where we have to send _our_
+ * CHANNEL_OPEN_CONFIRMATION before we discover which if any
+ * downstream the channel is destined for, so if that
+ * downstream turns out to present a smaller max packet size
+ * then we're in this situation.)
+ *
+ * If that happens, we just chop up the packet into pieces and
+ * send them as separate CHANNEL_DATA packets.
+ */
+ BinarySource src[1];
+ unsigned channel;
+ ptrlen data;
+
+ BinarySource_BARE_INIT(src, pkt, pktlen);
+ channel = get_uint32(src);
+ data = get_string(src);
+
+ do {
+ int this_len = (data.len > chan->downstream_maxpkt ?
+ chan->downstream_maxpkt : data.len);
+
+ packet = strbuf_new_nm();
+ put_uint32(packet, 0); /* placeholder for length field */
+ put_byte(packet, type);
+ put_uint32(packet, channel);
+ put_uint32(packet, this_len);
+ put_data(packet, data.ptr, this_len);
+ data.ptr = (const char *)data.ptr + this_len;
+ data.len -= this_len;
+ PUT_32BIT_MSB_FIRST(packet->s, packet->len-4);
+ sk_write(cs->sock, packet->s, packet->len);
+ strbuf_free(packet);
+ } while (data.len > 0);
+ } else {
+ /*
+ * Just do the obvious thing.
+ */
+ packet = strbuf_new_nm();
+ put_uint32(packet, 0); /* placeholder for length field */
+ put_byte(packet, type);
+ put_data(packet, pkt, pktlen);
+ PUT_32BIT_MSB_FIRST(packet->s, packet->len-4);
+ sk_write(cs->sock, packet->s, packet->len);
+ strbuf_free(packet);
+ }
+}
+
+static void share_try_cleanup(struct ssh_sharing_connstate *cs)
+{
+ int i;
+ struct share_halfchannel *hc;
+ struct share_channel *chan;
+ struct share_forwarding *fwd;
+
+ /*
+ * Any half-open channels, i.e. those for which we'd received
+ * CHANNEL_OPEN from the server but not passed back a response
+ * from downstream, should be responded to with OPEN_FAILURE.
+ */
+ while ((hc = (struct share_halfchannel *)
+ index234(cs->halfchannels, 0)) != NULL) {
+ static const char reason[] = "PuTTY downstream no longer available";
+ static const char lang[] = "en";
+ strbuf *packet;
+
+ packet = strbuf_new();
+ put_uint32(packet, hc->server_id);
+ put_uint32(packet, SSH2_OPEN_CONNECT_FAILED);
+ put_stringz(packet, reason);
+ put_stringz(packet, lang);
+ ssh_send_packet_from_downstream(
+ cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_OPEN_FAILURE,
+ packet->s, packet->len,
+ "cleanup after downstream went away");
+ strbuf_free(packet);
+
+ share_remove_halfchannel(cs, hc);
+ }
+
+ /*
+ * Any actually open channels should have a CHANNEL_CLOSE sent for
+ * them, unless we've already done so. We won't be able to
+ * actually clean them up until CHANNEL_CLOSE comes back from the
+ * server, though (unless the server happens to have sent a CLOSE
+ * already).
+ *
+ * Another annoying exception is UNACKNOWLEDGED channels, i.e.
+ * we've _sent_ a CHANNEL_OPEN to the server but not received an
+ * OPEN_CONFIRMATION or OPEN_FAILURE. We must wait for a reply
+ * before closing the channel, because until we see that reply we
+ * won't have the server's channel id to put in the close message.
+ */
+ for (i = 0; (chan = (struct share_channel *)
+ index234(cs->channels_by_us, i)) != NULL; i++) {
+ strbuf *packet;
+
+ if (chan->state != SENT_CLOSE && chan->state != UNACKNOWLEDGED) {
+ packet = strbuf_new();
+ put_uint32(packet, chan->server_id);
+ ssh_send_packet_from_downstream(
+ cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_CLOSE,
+ packet->s, packet->len,
+ "cleanup after downstream went away");
+ strbuf_free(packet);
+
+ if (chan->state != RCVD_CLOSE) {
+ chan->state = SENT_CLOSE;
+ } else {
+ /* In this case, we _can_ clear up the channel now. */
+ ssh_delete_sharing_channel(cs->parent->cl, chan->upstream_id);
+ share_remove_channel(cs, chan);
+ i--; /* don't accidentally skip one as a result */
+ }
+ }
+ }
+
+ /*
+ * Any remote port forwardings we're managing on behalf of this
+ * downstream should be cancelled. Again, we must defer those for
+ * which we haven't yet seen REQUEST_SUCCESS/FAILURE.
+ *
+ * We take a fire-and-forget approach during cleanup, not
+ * bothering to set want_reply.
+ */
+ for (i = 0; (fwd = (struct share_forwarding *)
+ index234(cs->forwardings, i)) != NULL; i++) {
+ if (fwd->active) {
+ strbuf *packet = strbuf_new();
+ put_stringz(packet, "cancel-tcpip-forward");
+ put_bool(packet, false); /* !want_reply */
+ put_stringz(packet, fwd->host);
+ put_uint32(packet, fwd->port);
+ ssh_send_packet_from_downstream(
+ cs->parent->cl, cs->id, SSH2_MSG_GLOBAL_REQUEST,
+ packet->s, packet->len,
+ "cleanup after downstream went away");
+ strbuf_free(packet);
+
+ ssh_rportfwd_remove(cs->parent->cl, fwd->rpf);
+ share_remove_forwarding(cs, fwd);
+ i--; /* don't accidentally skip one as a result */
+ }
+ }
+
+ if (count234(cs->halfchannels) == 0 &&
+ count234(cs->channels_by_us) == 0 &&
+ count234(cs->forwardings) == 0) {
+ struct ssh_sharing_state *sharestate = cs->parent;
+
+ /*
+ * Now we're _really_ done, so we can get rid of cs completely.
+ */
+ del234(sharestate->connections, cs);
+ log_downstream(cs, "disconnected");
+ share_connstate_free(cs);
+
+ /*
+ * And if this was the last downstream, notify the connection
+ * layer, because it might now be time to wind up the whole
+ * SSH connection.
+ */
+ if (count234(sharestate->connections) == 0 && sharestate->cl)
+ ssh_sharing_no_more_downstreams(sharestate->cl);
+ }
+}
+
+static void share_begin_cleanup(struct ssh_sharing_connstate *cs)
+{
+
+ sk_close(cs->sock);
+ cs->sock = NULL;
+
+ share_try_cleanup(cs);
+}
+
+static void share_disconnect(struct ssh_sharing_connstate *cs,
+ const char *message)
+{
+ strbuf *packet = strbuf_new();
+ put_uint32(packet, SSH2_DISCONNECT_PROTOCOL_ERROR);
+ put_stringz(packet, message);
+ put_stringz(packet, "en"); /* language */
+ send_packet_to_downstream(cs, SSH2_MSG_DISCONNECT,
+ packet->s, packet->len, NULL);
+ strbuf_free(packet);
+
+ share_begin_cleanup(cs);
+}
+
+static void share_closing(Plug *plug, PlugCloseType type,
+ const char *error_msg)
+{
+ struct ssh_sharing_connstate *cs = container_of(
+ plug, struct ssh_sharing_connstate, plug);
+
+ /*
+ * Most of the time, we log what went wrong when a downstream
+ * disappears with a socket error. One exception, though, is
+ * receiving EPIPE when we haven't received a protocol version
+ * string from the downstream, because that can happen as a result
+ * of plink -shareexists (opening the connection and instantly
+ * closing it again without bothering to read our version string).
+ * So that one case is not treated as a log-worthy error.
+ */
+ if (type == PLUGCLOSE_BROKEN_PIPE && !cs->got_verstring) {
+ /* do nothing */;
+ } else if (type != PLUGCLOSE_NORMAL) {
+ log_downstream(cs, "Socket error: %s", error_msg);
+ }
+ share_begin_cleanup(cs);
+}
+
+/*
+ * Append a message to the end of an xchannel's queue.
+ */
+static void share_xchannel_add_message(
+ struct share_xchannel *xc, int type, const void *data, int len)
+{
+ struct share_xchannel_message *msg;
+
+ /*
+ * Allocate the 'struct share_xchannel_message' and the actual
+ * data in one unit.
+ */
+ msg = snew_plus(struct share_xchannel_message, len);
+ msg->data = snew_plus_get_aux(msg);
+ msg->datalen = len;
+ msg->type = type;
+ memcpy(msg->data, data, len);
+
+ /*
+ * Queue it in the xchannel.
+ */
+ if (xc->msgtail)
+ xc->msgtail->next = msg;
+ else
+ xc->msghead = msg;
+ msg->next = NULL;
+ xc->msgtail = msg;
+}
+
+static void share_dead_xchannel_respond(struct ssh_sharing_connstate *cs,
+ struct share_xchannel *xc)
+{
+ /*
+ * Handle queued incoming messages from the server destined for an
+ * xchannel which is dead (i.e. downstream sent OPEN_FAILURE).
+ */
+ bool delete = false;
+ while (xc->msghead) {
+ struct share_xchannel_message *msg = xc->msghead;
+ xc->msghead = msg->next;
+
+ if (msg->type == SSH2_MSG_CHANNEL_REQUEST && msg->datalen > 4) {
+ /*
+ * A CHANNEL_REQUEST is responded to by sending
+ * CHANNEL_FAILURE, if it has want_reply set.
+ */
+ BinarySource src[1];
+ BinarySource_BARE_INIT(src, msg->data, msg->datalen);
+ get_uint32(src); /* skip channel id */
+ get_string(src); /* skip request type */
+ if (get_bool(src)) {
+ strbuf *packet = strbuf_new();
+ put_uint32(packet, xc->server_id);
+ ssh_send_packet_from_downstream(
+ cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_FAILURE,
+ packet->s, packet->len,
+ "downstream refused X channel open");
+ strbuf_free(packet);
+ }
+ } else if (msg->type == SSH2_MSG_CHANNEL_CLOSE) {
+ /*
+ * On CHANNEL_CLOSE we can discard the channel completely.
+ */
+ delete = true;
+ }
+
+ sfree(msg);
+ }
+ xc->msgtail = NULL;
+ if (delete) {
+ ssh_delete_sharing_channel(cs->parent->cl, xc->upstream_id);
+ share_remove_xchannel(cs, xc);
+ }
+}
+
+static void share_xchannel_confirmation(
+ struct ssh_sharing_connstate *cs, struct share_xchannel *xc,
+ struct share_channel *chan, unsigned downstream_window)
+{
+ strbuf *packet;
+
+ /*
+ * Send all the queued messages downstream.
+ */
+ while (xc->msghead) {
+ struct share_xchannel_message *msg = xc->msghead;
+ xc->msghead = msg->next;
+
+ if (msg->datalen >= 4)
+ PUT_32BIT_MSB_FIRST(msg->data, chan->downstream_id);
+ send_packet_to_downstream(cs, msg->type,
+ msg->data, msg->datalen, chan);
+
+ sfree(msg);
+ }
+
+ /*
+ * Send a WINDOW_ADJUST back upstream, to synchronise the window
+ * size downstream thinks it's presented with the one we've
+ * actually presented.
+ */
+ packet = strbuf_new();
+ put_uint32(packet, xc->server_id);
+ put_uint32(packet, downstream_window - xc->window);
+ ssh_send_packet_from_downstream(
+ cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_WINDOW_ADJUST,
+ packet->s, packet->len,
+ "window adjustment after downstream accepted X channel");
+ strbuf_free(packet);
+}
+
+static void share_xchannel_failure(struct ssh_sharing_connstate *cs,
+ struct share_xchannel *xc)
+{
+ /*
+ * If downstream refuses to open our X channel at all for some
+ * reason, we must respond by sending an emergency CLOSE upstream.
+ */
+ strbuf *packet = strbuf_new();
+ put_uint32(packet, xc->server_id);
+ ssh_send_packet_from_downstream(
+ cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_CLOSE,
+ packet->s, packet->len,
+ "downstream refused X channel open");
+ strbuf_free(packet);
+
+ /*
+ * Now mark the xchannel as dead, and respond to anything sent on
+ * it until we see CLOSE for it in turn.
+ */
+ xc->live = false;
+ share_dead_xchannel_respond(cs, xc);
+}
+
+void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan,
+ unsigned upstream_id, unsigned server_id,
+ unsigned server_currwin, unsigned server_maxpkt,
+ unsigned client_adjusted_window,
+ const char *peer_addr, int peer_port, int endian,
+ int protomajor, int protominor,
+ const void *initial_data, int initial_len)
+{
+ struct share_xchannel *xc;
+ void *greeting;
+ int greeting_len;
+ strbuf *packet;
+
+ /*
+ * Create an xchannel containing data we've already received from
+ * the X client, and preload it with a CHANNEL_DATA message
+ * containing our own made-up authorisation greeting and any
+ * additional data sent from the server so far.
+ */
+ xc = share_add_xchannel(cs, upstream_id, server_id);
+ greeting = x11_make_greeting(endian, protomajor, protominor,
+ chan->x11_auth_proto,
+ chan->x11_auth_data, chan->x11_auth_datalen,
+ peer_addr, peer_port, &greeting_len);
+ packet = strbuf_new_nm();
+ put_uint32(packet, 0); /* leave the channel id field unfilled - we
+ * don't know the downstream id yet */
+ put_uint32(packet, greeting_len + initial_len);
+ put_data(packet, greeting, greeting_len);
+ put_data(packet, initial_data, initial_len);
+ sfree(greeting);
+ share_xchannel_add_message(xc, SSH2_MSG_CHANNEL_DATA,
+ packet->s, packet->len);
+ strbuf_free(packet);
+
+ xc->window = client_adjusted_window + greeting_len;
+
+ /*
+ * Send on a CHANNEL_OPEN to downstream.
+ */
+ packet = strbuf_new();
+ put_stringz(packet, "x11");
+ put_uint32(packet, server_id);
+ put_uint32(packet, server_currwin);
+ put_uint32(packet, server_maxpkt);
+ put_stringz(packet, peer_addr);
+ put_uint32(packet, peer_port);
+ send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_OPEN,
+ packet->s, packet->len, NULL);
+ strbuf_free(packet);
+
+ /*
+ * If this was a once-only X forwarding, clean it up now.
+ */
+ if (chan->x11_one_shot) {
+ ssh_remove_sharing_x11_display(cs->parent->cl,
+ chan->x11_auth_upstream);
+ chan->x11_auth_upstream = NULL;
+ sfree(chan->x11_auth_data);
+ chan->x11_auth_proto = -1;
+ chan->x11_auth_datalen = 0;
+ chan->x11_one_shot = false;
+ }
+}
+
+void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type,
+ const void *vpkt, int pktlen)
+{
+ const unsigned char *pkt = (const unsigned char *)vpkt;
+ struct share_globreq *globreq;
+ size_t id_pos;
+ unsigned upstream_id, server_id;
+ struct share_channel *chan;
+ struct share_xchannel *xc;
+ BinarySource src[1];
+
+ BinarySource_BARE_INIT(src, pkt, pktlen);
+
+ switch (type) {
+ case SSH2_MSG_REQUEST_SUCCESS:
+ case SSH2_MSG_REQUEST_FAILURE:
+ globreq = cs->globreq_head;
+ assert(globreq); /* should match the queue in connection2.c */
+ if (globreq->type == GLOBREQ_TCPIP_FORWARD) {
+ if (type == SSH2_MSG_REQUEST_FAILURE) {
+ share_remove_forwarding(cs, globreq->fwd);
+ } else {
+ globreq->fwd->active = true;
+ }
+ } else if (globreq->type == GLOBREQ_CANCEL_TCPIP_FORWARD) {
+ if (type == SSH2_MSG_REQUEST_SUCCESS) {
+ share_remove_forwarding(cs, globreq->fwd);
+ }
+ }
+ if (globreq->want_reply) {
+ send_packet_to_downstream(cs, type, pkt, pktlen, NULL);
+ }
+ cs->globreq_head = globreq->next;
+ sfree(globreq);
+ if (cs->globreq_head == NULL)
+ cs->globreq_tail = NULL;
+
+ if (!cs->sock) {
+ /* Retry cleaning up this connection, in case that reply
+ * was the last thing we were waiting for. */
+ share_try_cleanup(cs);
+ }
+
+ break;
+
+ case SSH2_MSG_CHANNEL_OPEN:
+ get_string(src);
+ server_id = get_uint32(src);
+ assert(!get_err(src));
+ share_add_halfchannel(cs, server_id);
+
+ send_packet_to_downstream(cs, type, pkt, pktlen, NULL);
+ break;
+
+ case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
+ case SSH2_MSG_CHANNEL_OPEN_FAILURE:
+ case SSH2_MSG_CHANNEL_CLOSE:
+ case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+ case SSH2_MSG_CHANNEL_DATA:
+ case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+ case SSH2_MSG_CHANNEL_EOF:
+ case SSH2_MSG_CHANNEL_REQUEST:
+ case SSH2_MSG_CHANNEL_SUCCESS:
+ case SSH2_MSG_CHANNEL_FAILURE:
+ /*
+ * All these messages have the recipient channel id as the
+ * first uint32 field in the packet. Substitute the downstream
+ * channel id for our one and pass the packet downstream.
+ */
+ id_pos = src->pos;
+ upstream_id = get_uint32(src);
+ if ((chan = share_find_channel_by_upstream(cs, upstream_id)) != NULL) {
+ /*
+ * The normal case: this id refers to an open channel.
+ */
+ unsigned char *rewritten = snewn(pktlen, unsigned char);
+ memcpy(rewritten, pkt, pktlen);
+ PUT_32BIT_MSB_FIRST(rewritten + id_pos, chan->downstream_id);
+ send_packet_to_downstream(cs, type, rewritten, pktlen, chan);
+ sfree(rewritten);
+
+ /*
+ * Update the channel state, for messages that need it.
+ */
+ if (type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
+ if (chan->state == UNACKNOWLEDGED && pktlen >= 8) {
+ share_channel_set_server_id(
+ cs, chan, GET_32BIT_MSB_FIRST(pkt+4), OPEN);
+ if (!cs->sock) {
+ /* Retry cleaning up this connection, so that we
+ * can send an immediate CLOSE on this channel for
+ * which we now know the server id. */
+ share_try_cleanup(cs);
+ }
+ }
+ } else if (type == SSH2_MSG_CHANNEL_OPEN_FAILURE) {
+ ssh_delete_sharing_channel(cs->parent->cl, chan->upstream_id);
+ share_remove_channel(cs, chan);
+ } else if (type == SSH2_MSG_CHANNEL_CLOSE) {
+ if (chan->state == SENT_CLOSE) {
+ ssh_delete_sharing_channel(cs->parent->cl,
+ chan->upstream_id);
+ share_remove_channel(cs, chan);
+ if (!cs->sock) {
+ /* Retry cleaning up this connection, in case this
+ * channel closure was the last thing we were
+ * waiting for. */
+ share_try_cleanup(cs);
+ }
+ } else {
+ chan->state = RCVD_CLOSE;
+ }
+ }
+ } else if ((xc = share_find_xchannel_by_upstream(cs, upstream_id))
+ != NULL) {
+ /*
+ * The unusual case: this id refers to an xchannel. Add it
+ * to the xchannel's queue.
+ */
+ share_xchannel_add_message(xc, type, pkt, pktlen);
+
+ /* If the xchannel is dead, then also respond to it (which
+ * may involve deleting the channel). */
+ if (!xc->live)
+ share_dead_xchannel_respond(cs, xc);
+ }
+ break;
+
+ default:
+ unreachable("This packet type should never have come from "
+ "connection2.c");
+ }
+}
+
+static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs,
+ int type,
+ unsigned char *pkt, int pktlen)
+{
+ ptrlen request_name;
+ struct share_forwarding *fwd;
+ size_t id_pos;
+ unsigned maxpkt;
+ unsigned old_id, new_id, server_id;
+ struct share_globreq *globreq;
+ struct share_channel *chan;
+ struct share_halfchannel *hc;
+ struct share_xchannel *xc;
+ strbuf *packet;
+ char *err = NULL;
+ BinarySource src[1];
+ size_t wantreplypos;
+ bool orig_wantreply;
+
+ BinarySource_BARE_INIT(src, pkt, pktlen);
+
+ switch (type) {
+ case SSH2_MSG_DISCONNECT:
+ /*
+ * This message stops here: if downstream is disconnecting
+ * from us, that doesn't mean we want to disconnect from the
+ * SSH server. Close the downstream connection and start
+ * cleanup.
+ */
+ share_begin_cleanup(cs);
+ break;
+
+ case SSH2_MSG_GLOBAL_REQUEST:
+ /*
+ * The only global requests we understand are "tcpip-forward"
+ * and "cancel-tcpip-forward". Since those require us to
+ * maintain state, we must assume that other global requests
+ * will probably require that too, and so we don't forward on
+ * any request we don't understand.
+ */
+ request_name = get_string(src);
+ wantreplypos = src->pos;
+ orig_wantreply = get_bool(src);
+
+ if (ptrlen_eq_string(request_name, "tcpip-forward")) {
+ ptrlen hostpl;
+ char *host;
+ int port;
+ struct ssh_rportfwd *rpf;
+
+ /*
+ * Pick the packet apart to find the want_reply field and
+ * the host/port we're going to ask to listen on.
+ */
+ hostpl = get_string(src);
+ port = toint(get_uint32(src));
+ if (get_err(src)) {
+ err = dupprintf("Truncated GLOBAL_REQUEST packet");
+ goto confused;
+ }
+ host = mkstr(hostpl);
+
+ /*
+ * See if we can allocate space in the connection layer's
+ * tree of remote port forwardings. If we can't, it's
+ * because another client sharing this connection has
+ * already allocated the identical port forwarding, so we
+ * take it on ourselves to manufacture a failure packet
+ * and send it back to downstream.
+ */
+ rpf = ssh_rportfwd_alloc(
+ cs->parent->cl, host, port, NULL, 0, 0, NULL, NULL, cs);
+ if (!rpf) {
+ if (orig_wantreply) {
+ send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE,
+ "", 0, NULL);
+ }
+ } else {
+ /*
+ * We've managed to make space for this forwarding
+ * locally. Pass the request on to the SSH server, but
+ * set want_reply even if it wasn't originally set, so
+ * that we know whether this forwarding needs to be
+ * cleaned up if downstream goes away.
+ */
+ pkt[wantreplypos] = 1;
+ ssh_send_packet_from_downstream(
+ cs->parent->cl, cs->id, type, pkt, pktlen,
+ orig_wantreply ? NULL : "upstream added want_reply flag");
+ fwd = share_add_forwarding(cs, host, port);
+ ssh_sharing_queue_global_request(cs->parent->cl, cs);
+
+ if (fwd) {
+ globreq = snew(struct share_globreq);
+ globreq->next = NULL;
+ if (cs->globreq_tail)
+ cs->globreq_tail->next = globreq;
+ else
+ cs->globreq_head = globreq;
+ globreq->fwd = fwd;
+ globreq->want_reply = orig_wantreply;
+ globreq->type = GLOBREQ_TCPIP_FORWARD;
+
+ fwd->rpf = rpf;
+ }
+ }
+
+ sfree(host);
+ } else if (ptrlen_eq_string(request_name, "cancel-tcpip-forward")) {
+ ptrlen hostpl;
+ char *host;
+ int port;
+ struct share_forwarding *fwd;
+
+ /*
+ * Pick the packet apart to find the want_reply field and
+ * the host/port we're going to ask to listen on.
+ */
+ hostpl = get_string(src);
+ port = toint(get_uint32(src));
+ if (get_err(src)) {
+ err = dupprintf("Truncated GLOBAL_REQUEST packet");
+ goto confused;
+ }
+ host = mkstr(hostpl);
+
+ /*
+ * Look up the existing forwarding with these details.
+ */
+ fwd = share_find_forwarding(cs, host, port);
+ if (!fwd) {
+ if (orig_wantreply) {
+ send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE,
+ "", 0, NULL);
+ }
+ } else {
+ /*
+ * Tell the connection layer to stop sending us
+ * channel-opens for this forwarding.
+ */
+ ssh_rportfwd_remove(cs->parent->cl, fwd->rpf);
+
+ /*
+ * Pass the cancel request on to the SSH server, but
+ * set want_reply even if it wasn't originally set, so
+ * that _we_ know whether the forwarding has been
+ * deleted even if downstream doesn't want to know.
+ */
+ pkt[wantreplypos] = 1;
+ ssh_send_packet_from_downstream(
+ cs->parent->cl, cs->id, type, pkt, pktlen,
+ orig_wantreply ? NULL : "upstream added want_reply flag");
+ ssh_sharing_queue_global_request(cs->parent->cl, cs);
+
+ /*
+ * And queue a globreq so that when the reply comes
+ * back we know to cancel it.
+ */
+ globreq = snew(struct share_globreq);
+ globreq->next = NULL;
+ if (cs->globreq_tail)
+ cs->globreq_tail->next = globreq;
+ else
+ cs->globreq_head = globreq;
+ globreq->fwd = fwd;
+ globreq->want_reply = orig_wantreply;
+ globreq->type = GLOBREQ_CANCEL_TCPIP_FORWARD;
+ }
+
+ sfree(host);
+ } else {
+ /*
+ * Request we don't understand. Manufacture a failure
+ * message if an answer was required.
+ */
+ if (orig_wantreply)
+ send_packet_to_downstream(cs, SSH2_MSG_REQUEST_FAILURE,
+ "", 0, NULL);
+ }
+ break;
+
+ case SSH2_MSG_CHANNEL_OPEN:
+ /* Sender channel id comes after the channel type string */
+ get_string(src);
+ id_pos = src->pos;
+ old_id = get_uint32(src);
+ new_id = ssh_alloc_sharing_channel(cs->parent->cl, cs);
+ get_uint32(src); /* skip initial window size */
+ maxpkt = get_uint32(src);
+ if (get_err(src)) {
+ err = dupprintf("Truncated CHANNEL_OPEN packet");
+ goto confused;
+ }
+ share_add_channel(cs, old_id, new_id, 0, UNACKNOWLEDGED, maxpkt);
+ PUT_32BIT_MSB_FIRST(pkt + id_pos, new_id);
+ ssh_send_packet_from_downstream(cs->parent->cl, cs->id,
+ type, pkt, pktlen, NULL);
+ break;
+
+ case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
+ if (pktlen < 16) {
+ err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet");
+ goto confused;
+ }
+
+ server_id = get_uint32(src);
+ id_pos = src->pos;
+ old_id = get_uint32(src);
+ get_uint32(src); /* skip initial window size */
+ maxpkt = get_uint32(src);
+ if (get_err(src)) {
+ err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet");
+ goto confused;
+ }
+
+ /* This server id may refer to either a halfchannel or an xchannel. */
+ hc = NULL, xc = NULL; /* placate optimiser */
+ if ((hc = share_find_halfchannel(cs, server_id)) != NULL) {
+ new_id = ssh_alloc_sharing_channel(cs->parent->cl, cs);
+ } else if ((xc = share_find_xchannel_by_server(cs, server_id))
+ != NULL) {
+ new_id = xc->upstream_id;
+ } else {
+ err = dupprintf("CHANNEL_OPEN_CONFIRMATION packet cited unknown channel %u", (unsigned)server_id);
+ goto confused;
+ }
+
+ PUT_32BIT_MSB_FIRST(pkt + id_pos, new_id);
+
+ chan = share_add_channel(cs, old_id, new_id, server_id, OPEN, maxpkt);
+
+ if (hc) {
+ ssh_send_packet_from_downstream(cs->parent->cl, cs->id,
+ type, pkt, pktlen, NULL);
+ share_remove_halfchannel(cs, hc);
+ } else if (xc) {
+ unsigned downstream_window = GET_32BIT_MSB_FIRST(pkt + 8);
+ if (downstream_window < 256) {
+ err = dupprintf("Initial window size for x11 channel must be at least 256 (got %u)", downstream_window);
+ goto confused;
+ }
+ share_xchannel_confirmation(cs, xc, chan, downstream_window);
+ share_remove_xchannel(cs, xc);
+ }
+
+ break;
+
+ case SSH2_MSG_CHANNEL_OPEN_FAILURE:
+ server_id = get_uint32(src);
+ if (get_err(src)) {
+ err = dupprintf("Truncated CHANNEL_OPEN_FAILURE packet");
+ goto confused;
+ }
+
+ /* This server id may refer to either a halfchannel or an xchannel. */
+ if ((hc = share_find_halfchannel(cs, server_id)) != NULL) {
+ ssh_send_packet_from_downstream(cs->parent->cl, cs->id,
+ type, pkt, pktlen, NULL);
+ share_remove_halfchannel(cs, hc);
+ } else if ((xc = share_find_xchannel_by_server(cs, server_id))
+ != NULL) {
+ share_xchannel_failure(cs, xc);
+ } else {
+ err = dupprintf("CHANNEL_OPEN_FAILURE packet cited unknown channel %u", (unsigned)server_id);
+ goto confused;
+ }
+
+ break;
+
+ case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+ case SSH2_MSG_CHANNEL_DATA:
+ case SSH2_MSG_CHANNEL_EXTENDED_DATA:
+ case SSH2_MSG_CHANNEL_EOF:
+ case SSH2_MSG_CHANNEL_CLOSE:
+ case SSH2_MSG_CHANNEL_REQUEST:
+ case SSH2_MSG_CHANNEL_SUCCESS:
+ case SSH2_MSG_CHANNEL_FAILURE:
+ case SSH2_MSG_IGNORE:
+ case SSH2_MSG_DEBUG:
+ server_id = get_uint32(src);
+
+ if (type == SSH2_MSG_CHANNEL_REQUEST) {
+ request_name = get_string(src);
+
+ /*
+ * Agent forwarding requests from downstream are treated
+ * specially. Because OpenSSHD doesn't let us enable agent
+ * forwarding independently per session channel, and in
+ * particular because the OpenSSH-defined agent forwarding
+ * protocol does not mark agent-channel requests with the
+ * id of the session channel they originate from, the only
+ * way we can implement agent forwarding in a
+ * connection-shared PuTTY is to forward the _upstream_
+ * agent. Hence, we unilaterally deny agent forwarding
+ * requests from downstreams if we aren't prepared to
+ * forward an agent ourselves.
+ *
+ * (If we are, then we dutifully pass agent forwarding
+ * requests upstream. OpenSSHD has the curious behaviour
+ * that all but the first such request will be rejected,
+ * but all session channels opened after the first request
+ * get agent forwarding enabled whether they ask for it or
+ * not; but that's not our concern, since other SSH
+ * servers supporting the same piece of protocol might in
+ * principle at least manage to enable agent forwarding on
+ * precisely the channels that requested it, even if the
+ * subsequent CHANNEL_OPENs still can't be associated with
+ * a parent session channel.)
+ */
+ if (ptrlen_eq_string(request_name, "auth-agent-req@openssh.com") &&
+ !ssh_agent_forwarding_permitted(cs->parent->cl)) {
+
+ chan = share_find_channel_by_server(cs, server_id);
+ if (chan) {
+ packet = strbuf_new();
+ put_uint32(packet, chan->downstream_id);
+ send_packet_to_downstream(
+ cs, SSH2_MSG_CHANNEL_FAILURE,
+ packet->s, packet->len, NULL);
+ strbuf_free(packet);
+ } else {
+ char *buf = dupprintf("Agent forwarding request for "
+ "unrecognised channel %u", server_id);
+ share_disconnect(cs, buf);
+ sfree(buf);
+ return;
+ }
+ break;
+ }
+
+ /*
+ * Another thing we treat specially is X11 forwarding
+ * requests. For these, we have to make up another set of
+ * X11 auth data, and enter it into our SSH connection's
+ * list of possible X11 authorisation credentials so that
+ * when we see an X11 channel open request we can know
+ * whether it's one to handle locally or one to pass on to
+ * a downstream, and if the latter, which one.
+ */
+ if (ptrlen_eq_string(request_name, "x11-req")) {
+ bool want_reply, single_connection;
+ int screen;
+ ptrlen auth_data;
+ int auth_proto;
+
+ chan = share_find_channel_by_server(cs, server_id);
+ if (!chan) {
+ char *buf = dupprintf("X11 forwarding request for "
+ "unrecognised channel %u", server_id);
+ share_disconnect(cs, buf);
+ sfree(buf);
+ return;
+ }
+
+ /*
+ * Pick apart the whole message to find the downstream
+ * auth details.
+ */
+ want_reply = get_bool(src);
+ single_connection = get_bool(src);
+ auth_proto = x11_identify_auth_proto(get_string(src));
+ auth_data = get_string(src);
+ screen = toint(get_uint32(src));
+ if (get_err(src)) {
+ err = dupprintf("Truncated CHANNEL_REQUEST(\"x11-req\")"
+ " packet");
+ goto confused;
+ }
+
+ if (auth_proto < 0) {
+ /* Reject due to not understanding downstream's
+ * requested authorisation method. */
+ packet = strbuf_new();
+ put_uint32(packet, chan->downstream_id);
+ send_packet_to_downstream(
+ cs, SSH2_MSG_CHANNEL_FAILURE,
+ packet->s, packet->len, NULL);
+ strbuf_free(packet);
+ break;
+ }
+
+ chan->x11_auth_proto = auth_proto;
+ chan->x11_auth_data = x11_dehexify(auth_data,
+ &chan->x11_auth_datalen);
+ chan->x11_auth_upstream =
+ ssh_add_sharing_x11_display(cs->parent->cl, auth_proto,
+ cs, chan);
+ chan->x11_one_shot = single_connection;
+
+ /*
+ * Now construct a replacement X forwarding request,
+ * containing our own auth data, and send that to the
+ * server.
+ */
+ packet = strbuf_new_nm();
+ put_uint32(packet, server_id);
+ put_stringz(packet, "x11-req");
+ put_bool(packet, want_reply);
+ put_bool(packet, single_connection);
+ put_stringz(packet, chan->x11_auth_upstream->protoname);
+ put_stringz(packet, chan->x11_auth_upstream->datastring);
+ put_uint32(packet, screen);
+ ssh_send_packet_from_downstream(
+ cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_REQUEST,
+ packet->s, packet->len, NULL);
+ strbuf_free(packet);
+
+ break;
+ }
+ }
+
+ ssh_send_packet_from_downstream(cs->parent->cl, cs->id,
+ type, pkt, pktlen, NULL);
+ if (type == SSH2_MSG_CHANNEL_CLOSE && pktlen >= 4) {
+ chan = share_find_channel_by_server(cs, server_id);
+ if (chan) {
+ if (chan->state == RCVD_CLOSE) {
+ ssh_delete_sharing_channel(cs->parent->cl,
+ chan->upstream_id);
+ share_remove_channel(cs, chan);
+ } else {
+ chan->state = SENT_CLOSE;
+ }
+ }
+ }
+ break;
+
+ default:
+ err = dupprintf("Unexpected packet type %d\n", type);
+ goto confused;
+
+ /*
+ * Any other packet type is unexpected. In particular, we
+ * never pass GLOBAL_REQUESTs downstream, so we never expect
+ * to see SSH2_MSG_REQUEST_{SUCCESS,FAILURE}.
+ */
+ confused:
+ assert(err != NULL);
+ share_disconnect(cs, err);
+ sfree(err);
+ break;
+ }
+}
+
+/*
+ * An extra coroutine macro, specific to this code which is consuming
+ * 'const char *data'.
+ */
+#define crGetChar(c) do \
+ { \
+ while (len == 0) { \
+ *crLine =__LINE__; return; case __LINE__:; \
+ } \
+ len--; \
+ (c) = (unsigned char)*data++; \
+ } while (0)
+
+static void share_receive(Plug *plug, int urgent, const char *data, size_t len)
+{
+ ssh_sharing_connstate *cs = container_of(
+ plug, ssh_sharing_connstate, plug);
+ static const char expected_verstring_prefix[] =
+ "SSHCONNECTION@putty.projects.tartarus.org-2.0-";
+ unsigned char c;
+
+ crBegin(cs->crLine);
+
+ /*
+ * First read the version string from downstream.
+ */
+ cs->recvlen = 0;
+ while (1) {
+ crGetChar(c);
+ if (c == '\012')
+ break;
+ if (cs->recvlen >= sizeof(cs->recvbuf)) {
+ char *buf = dupprintf("Version string far too long\n");
+ share_disconnect(cs, buf);
+ sfree(buf);
+ 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, PlugCloseType type,
+ const char *error_msg)
+{
+ ssh_sharing_state *sharestate =
+ container_of(plug, ssh_sharing_state, plug);
+ if (type != PLUGCLOSE_NORMAL)
+ log_general(sharestate, "listening socket: %s", error_msg);
+ sk_close(sharestate->listensock);
+ sharestate->listensock = NULL;
+}
+
+static void share_send_verstring(ssh_sharing_connstate *cs)
+{
+ char *fullstring = dupcat("SSHCONNECTION@putty.projects.tartarus.org-2.0-",
+ cs->parent->server_verstring, "\015\012");
+ sk_write(cs->sock, fullstring, strlen(fullstring));
+ sfree(fullstring);
+
+ cs->sent_verstring = true;
+}
+
+int share_ndownstreams(ssh_sharing_state *sharestate)
+{
+ return count234(sharestate->connections);
+}
+
+void share_activate(ssh_sharing_state *sharestate,
+ const char *server_verstring)
+{
+ /*
+ * Indication from connection layer that we are now ready to begin
+ * serving any downstreams that have already connected to us.
+ */
+ struct ssh_sharing_connstate *cs;
+ int i;
+
+ /*
+ * Trim the server's version string down to just the software
+ * version component, removing "SSH-2.0-" or whatever at the
+ * front.
+ */
+ for (i = 0; i < 2; i++) {
+ server_verstring += strcspn(server_verstring, "-");
+ if (*server_verstring)
+ server_verstring++;
+ }
+
+ sharestate->server_verstring = dupstr(server_verstring);
+
+ for (i = 0; (cs = (struct ssh_sharing_connstate *)
+ index234(sharestate->connections, i)) != NULL; i++) {
+ assert(!cs->sent_verstring);
+ share_send_verstring(cs);
+ }
+}
+
+static const PlugVtable ssh_sharing_conn_plugvt = {
+ .closing = share_closing,
+ .receive = share_receive,
+ .sent = share_sent,
+ .log = nullplug_log,
+};
+
+static int share_listen_accepting(Plug *plug,
+ accept_fn_t constructor, accept_ctx_t ctx)
+{
+ struct ssh_sharing_state *sharestate = container_of(
+ plug, struct ssh_sharing_state, plug);
+ struct ssh_sharing_connstate *cs;
+ const char *err;
+ SocketPeerInfo *peerinfo;
+
+ /*
+ * A new downstream has connected to us.
+ */
+ cs = snew(struct ssh_sharing_connstate);
+ cs->plug.vt = &ssh_sharing_conn_plugvt;
+ cs->parent = sharestate;
+
+ if ((cs->id = share_find_unused_id(sharestate, sharestate->nextid)) == 0 &&
+ (cs->id = share_find_unused_id(sharestate, 1)) == 0) {
+ sfree(cs);
+ return 1;
+ }
+ sharestate->nextid = cs->id + 1;
+ if (sharestate->nextid == 0)
+ sharestate->nextid++; /* only happens in VERY long-running upstreams */
+
+ cs->sock = constructor(ctx, &cs->plug);
+ if ((err = sk_socket_error(cs->sock)) != NULL) {
+ sfree(cs);
+ return err != NULL;
+ }
+
+ sk_set_frozen(cs->sock, false);
+
+ add234(cs->parent->connections, cs);
+
+ cs->sent_verstring = false;
+ if (sharestate->server_verstring)
+ share_send_verstring(cs);
+
+ cs->got_verstring = false;
+ cs->recvlen = 0;
+ cs->crLine = 0;
+ cs->halfchannels = newtree234(share_halfchannel_cmp);
+ cs->channels_by_us = newtree234(share_channel_us_cmp);
+ cs->channels_by_server = newtree234(share_channel_server_cmp);
+ cs->xchannels_by_us = newtree234(share_xchannel_us_cmp);
+ cs->xchannels_by_server = newtree234(share_xchannel_server_cmp);
+ cs->forwardings = newtree234(share_forwarding_cmp);
+ cs->globreq_head = cs->globreq_tail = NULL;
+
+ peerinfo = sk_peer_info(cs->sock);
+ log_downstream(cs, "connected%s%s",
+ (peerinfo && peerinfo->log_text ? " from " : ""),
+ (peerinfo && peerinfo->log_text ? peerinfo->log_text : ""));
+ sk_free_peer_info(peerinfo);
+
+ return 0;
+}
+
+/*
+ * Decide on the string used to identify the connection point between
+ * upstream and downstream (be it a Windows named pipe or a
+ * Unix-domain socket or whatever else).
+ *
+ * I wondered about making this a SHA hash of all sorts of pieces of
+ * the PuTTY configuration - essentially everything PuTTY uses to know
+ * where and how to make a connection, including all the proxy details
+ * (or rather, all the _relevant_ ones - only including settings that
+ * other settings didn't prevent from having any effect), plus the
+ * username. However, I think it's better to keep it really simple:
+ * the connection point identifier is derived from the hostname and
+ * port used to index the host-key cache (not necessarily where we
+ * _physically_ connected to, in cases involving proxies or
+ * CONF_loghost), plus the username if one is specified.
+ *
+ * The per-platform code will quite likely hash or obfuscate this name
+ * in turn, for privacy from other users; failing that, it might
+ * transform it to avoid dangerous filename characters and so on. But
+ * that doesn't matter to us: for us, the point is that two session
+ * configurations which return the same string from this function will
+ * be treated as potentially shareable with each other.
+ */
+static char *ssh_share_sockname(const char *host, int port, Conf *conf)
+{
+ char *username = NULL;
+ char *sockname;
+
+ /* Include the username we're logging in as in the hash, unless
+ * we're using a protocol for which it's completely irrelevant. */
+ if (conf_get_int(conf, CONF_protocol) != PROT_SSHCONN)
+ username = get_remote_username(conf);
+
+ if (port == 22) {
+ if (username)
+ sockname = dupprintf("%s@%s", username, host);
+ else
+ sockname = dupprintf("%s", host);
+ } else {
+ if (username)
+ sockname = dupprintf("%s@%s:%d", username, host, port);
+ else
+ sockname = dupprintf("%s:%d", host, port);
+ }
+
+ sfree(username);
+ return sockname;
+}
+
+bool ssh_share_test_for_upstream(const char *host, int port, Conf *conf)
+{
+ char *sockname, *logtext, *ds_err, *us_err;
+ int result;
+ Socket *sock;
+
+ sockname = ssh_share_sockname(host, port, conf);
+
+ sock = NULL;
+ logtext = ds_err = us_err = NULL;
+ result = platform_ssh_share(sockname, conf, nullplug, (Plug *)NULL, &sock,
+ &logtext, &ds_err, &us_err, false, true);
+
+ sfree(logtext);
+ sfree(ds_err);
+ sfree(us_err);
+ sfree(sockname);
+
+ if (result == SHARE_NONE) {
+ assert(sock == NULL);
+ return false;
+ } else {
+ assert(result == SHARE_DOWNSTREAM);
+ sk_close(sock);
+ return true;
+ }
+}
+
+static const PlugVtable ssh_sharing_listen_plugvt = {
+ .closing = share_listen_closing,
+ .accepting = share_listen_accepting,
+ .log = nullplug_log,
+};
+
+void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate,
+ ConnectionLayer *cl)
+{
+ sharestate->cl = cl;
+}
+
+/*
+ * Init function for connection sharing. We either open a listening
+ * socket and become an upstream, or connect to an existing one and
+ * become a downstream, or do neither. We are responsible for deciding
+ * which of these to do (including checking the Conf to see if
+ * connection sharing is even enabled in the first place). If we
+ * become a downstream, we return the Socket with which we connected
+ * to the upstream; otherwise (whether or not we have established an
+ * upstream) we return NULL.
+ */
+Socket *ssh_connection_sharing_init(
+ const char *host, int port, Conf *conf, LogContext *logctx,
+ Plug *sshplug, ssh_sharing_state **state)
+{
+ int result;
+ bool can_upstream, can_downstream;
+ char *logtext, *ds_err, *us_err;
+ char *sockname;
+ Socket *sock, *toret = NULL;
+ struct ssh_sharing_state *sharestate;
+
+ if (!conf_get_bool(conf, CONF_ssh_connection_sharing))
+ return NULL; /* do not share anything */
+ can_upstream = share_can_be_upstream &&
+ conf_get_bool(conf, CONF_ssh_connection_sharing_upstream);
+ can_downstream = share_can_be_downstream &&
+ conf_get_bool(conf, CONF_ssh_connection_sharing_downstream);
+ if (!can_upstream && !can_downstream)
+ return NULL;
+
+ sockname = ssh_share_sockname(host, port, conf);
+
+ /*
+ * Create a data structure for the listening plug if we turn out
+ * to be an upstream.
+ */
+ sharestate = snew(struct ssh_sharing_state);
+ sharestate->plug.vt = &ssh_sharing_listen_plugvt;
+ sharestate->listensock = NULL;
+ sharestate->cl = NULL;
+
+ /*
+ * Now hand off to a per-platform routine that either connects to
+ * an existing upstream (using 'ssh' as the plug), establishes our
+ * own upstream (using 'sharestate' as the plug), or forks off a
+ * separate upstream and then connects to that. It will return a
+ * code telling us which kind of socket it put in 'sock'.
+ */
+ sock = NULL;
+ logtext = ds_err = us_err = NULL;
+ result = platform_ssh_share(
+ sockname, conf, sshplug, &sharestate->plug, &sock, &logtext,
+ &ds_err, &us_err, can_upstream, can_downstream);
+ switch (result) {
+ case SHARE_NONE:
+ /*
+ * We aren't sharing our connection at all (e.g. something
+ * went wrong setting the socket up). Free the upstream
+ * structure and return NULL.
+ */
+
+ if (logtext) {
+ /* For this result, if 'logtext' is not NULL then it is an
+ * error message indicating a reason why connection sharing
+ * couldn't be set up _at all_ */
+ logeventf(logctx,
+ "Could not set up connection sharing: %s", logtext);
+ } else {
+ /* Failing that, ds_err and us_err indicate why we
+ * couldn't be a downstream and an upstream respectively */
+ if (ds_err)
+ logeventf(logctx, "Could not set up connection sharing"
+ " as downstream: %s", ds_err);
+ if (us_err)
+ logeventf(logctx, "Could not set up connection sharing"
+ " as upstream: %s", us_err);
+ }
+
+ assert(sock == NULL);
+ *state = NULL;
+ sfree(sharestate);
+ sfree(sockname);
+ break;
+
+ case SHARE_DOWNSTREAM:
+ /*
+ * We are downstream, so free sharestate which it turns out we
+ * don't need after all, and return the downstream socket as a
+ * replacement for an ordinary SSH connection.
+ */
+
+ /* 'logtext' is a local endpoint address */
+ logeventf(logctx, "Using existing shared connection at %s", logtext);
+
+ *state = NULL;
+ sfree(sharestate);
+ sfree(sockname);
+ toret = sock;
+ break;
+
+ case SHARE_UPSTREAM:
+ /*
+ * We are upstream. Set up sharestate properly and pass a copy
+ * to the caller; return NULL, to tell ssh.c that it has to
+ * make an ordinary connection after all.
+ */
+
+ /* 'logtext' is a local endpoint address */
+ logeventf(logctx, "Sharing this connection at %s", logtext);
+
+ *state = sharestate;
+ sharestate->listensock = sock;
+ sharestate->connections = newtree234(share_connstate_cmp);
+ sharestate->server_verstring = NULL;
+ sharestate->sockname = sockname;
+ sharestate->nextid = 1;
+ break;
+ }
+
+ sfree(logtext);
+ sfree(ds_err);
+ sfree(us_err);
+ return toret;
+}
diff --git a/sshsignals.h b/ssh/signal-list.h
index b213c34f..b213c34f 100644
--- a/sshsignals.h
+++ b/ssh/signal-list.h
diff --git a/ssh/ssh.c b/ssh/ssh.c
new file mode 100644
index 00000000..dbd6dde6
--- /dev/null
+++ b/ssh/ssh.c
@@ -0,0 +1,1314 @@
+/*
+ * SSH backend.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <limits.h>
+#include <signal.h>
+
+#include "putty.h"
+#include "pageant.h" /* for AGENT_MAX_MSGLEN */
+#include "tree234.h"
+#include "storage.h"
+#include "marshal.h"
+#include "ssh.h"
+#include "sshcr.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "channel.h"
+#ifndef NO_GSSAPI
+#include "gssc.h"
+#include "gss.h"
+#define MIN_CTXT_LIFETIME 5 /* Avoid rekey with short lifetime (seconds) */
+#define GSS_KEX_CAPABLE (1<<0) /* Can do GSS KEX */
+#define GSS_CRED_UPDATED (1<<1) /* Cred updated since previous delegation */
+#define GSS_CTXT_EXPIRES (1<<2) /* Context expires before next timer */
+#define GSS_CTXT_MAYFAIL (1<<3) /* Context may expire during handshake */
+#endif
+
+struct Ssh {
+ Socket *s;
+ Seat *seat;
+ Conf *conf;
+
+ struct ssh_version_receiver version_receiver;
+ int remote_bugs;
+
+ Plug plug;
+ Backend backend;
+ Interactor interactor;
+
+ Ldisc *ldisc;
+ LogContext *logctx;
+
+ /* The last list returned from get_specials. */
+ SessionSpecial *specials;
+
+ bool bare_connection;
+ ssh_sharing_state *connshare;
+ bool attempting_connshare;
+
+#ifndef NO_GSSAPI
+ struct ssh_connection_shared_gss_state gss_state;
+#endif
+
+ char *savedhost;
+ int savedport;
+ char *fullhostname;
+ char *description;
+
+ bool fallback_cmd;
+ int exitcode;
+
+ int version;
+ int conn_throttle_count;
+ size_t overall_bufsize;
+ bool throttled_all;
+
+ /*
+ * logically_frozen is true if we're not currently _processing_
+ * data from the SSH socket (e.g. because a higher layer has asked
+ * us not to due to ssh_throttle_conn). socket_frozen is true if
+ * we're not even _reading_ data from the socket (i.e. it should
+ * always match the value we last passed to sk_set_frozen).
+ *
+ * The two differ in that socket_frozen can also become
+ * temporarily true because of a large backlog in the in_raw
+ * bufchain, to force no further plug_receive events until the BPP
+ * input function has had a chance to run. (Some front ends, like
+ * GTK, can persistently call the network and never get round to
+ * the toplevel callbacks.) If we've stopped reading from the
+ * socket for that reason, we absolutely _do_ want to carry on
+ * processing our input bufchain, because that's the only way
+ * it'll ever get cleared!
+ *
+ * ssh_check_frozen() resets socket_frozen, and should be called
+ * whenever either of logically_frozen and the bufchain size
+ * changes.
+ */
+ bool logically_frozen, socket_frozen;
+
+ /* in case we find these out before we have a ConnectionLayer to tell */
+ int term_width, term_height;
+
+ bufchain in_raw, out_raw, user_input;
+ bool pending_close;
+ IdempotentCallback ic_out_raw;
+
+ PacketLogSettings pls;
+ struct DataTransferStats stats;
+
+ BinaryPacketProtocol *bpp;
+
+ /*
+ * base_layer identifies the bottommost packet protocol layer, the
+ * one connected directly to the BPP's packet queues. Any
+ * operation that needs to talk to all layers (e.g. free, or
+ * get_specials) will do it by talking to this, which will
+ * recursively propagate it if necessary.
+ */
+ PacketProtocolLayer *base_layer;
+
+ /*
+ * The ConnectionLayer vtable from our connection layer.
+ */
+ ConnectionLayer *cl;
+
+ /*
+ * A dummy ConnectionLayer that can be used for logging sharing
+ * downstreams that connect before the real one is ready.
+ */
+ ConnectionLayer cl_dummy;
+
+ /*
+ * session_started is false until we initialise the main protocol
+ * layers. So it distinguishes between base_layer==NULL meaning
+ * that the SSH protocol hasn't been set up _yet_, and
+ * base_layer==NULL meaning the SSH protocol has run and finished.
+ * It's also used to mark the point where we stop counting proxy
+ * command diagnostics as pre-session-startup.
+ */
+ bool session_started;
+
+ Pinger *pinger;
+
+ char *deferred_abort_message;
+
+ bool need_random_unref;
+};
+
+
+#define ssh_logevent(params) ( \
+ logevent_and_free((ssh)->logctx, dupprintf params))
+
+static void ssh_shutdown(Ssh *ssh);
+static void ssh_throttle_all(Ssh *ssh, bool enable, size_t bufsize);
+static void ssh_bpp_output_raw_data_callback(void *vctx);
+
+LogContext *ssh_get_logctx(Ssh *ssh)
+{
+ return ssh->logctx;
+}
+
+static void ssh_connect_bpp(Ssh *ssh)
+{
+ ssh->bpp->ssh = ssh;
+ ssh->bpp->in_raw = &ssh->in_raw;
+ ssh->bpp->out_raw = &ssh->out_raw;
+ bufchain_set_callback(ssh->bpp->out_raw, &ssh->ic_out_raw);
+ ssh->bpp->pls = &ssh->pls;
+ ssh->bpp->logctx = ssh->logctx;
+ ssh->bpp->remote_bugs = ssh->remote_bugs;
+}
+
+static void ssh_connect_ppl(Ssh *ssh, PacketProtocolLayer *ppl)
+{
+ ppl->bpp = ssh->bpp;
+ ppl->seat = ssh->seat;
+ ppl->interactor = &ssh->interactor;
+ ppl->ssh = ssh;
+ ppl->logctx = ssh->logctx;
+ ppl->remote_bugs = ssh->remote_bugs;
+}
+
+static void ssh_got_ssh_version(struct ssh_version_receiver *rcv,
+ int major_version)
+{
+ Ssh *ssh = container_of(rcv, Ssh, version_receiver);
+ BinaryPacketProtocol *old_bpp;
+ PacketProtocolLayer *connection_layer;
+
+ ssh->session_started = true;
+
+ /*
+ * We don't support choosing a major protocol version dynamically,
+ * so this should always be the same value we set up in
+ * connect_to_host().
+ */
+ assert(ssh->version == major_version);
+
+ old_bpp = ssh->bpp;
+ ssh->remote_bugs = ssh_verstring_get_bugs(old_bpp);
+
+ if (!ssh->bare_connection) {
+ if (ssh->version == 2) {
+ PacketProtocolLayer *userauth_layer, *transport_child_layer;
+
+ /*
+ * We use the 'simple' variant of the SSH protocol if
+ * we're asked to, except not if we're also doing
+ * connection-sharing (either tunnelling our packets over
+ * an upstream or expecting to be tunnelled over
+ * ourselves), since then the assumption that we have only
+ * one channel to worry about is not true after all.
+ */
+ bool is_simple =
+ (conf_get_bool(ssh->conf, CONF_ssh_simple) && !ssh->connshare);
+
+ ssh->bpp = ssh2_bpp_new(ssh->logctx, &ssh->stats, false);
+ ssh_connect_bpp(ssh);
+
+#ifndef NO_GSSAPI
+ /* Load and pick the highest GSS library on the preference
+ * list. */
+ if (!ssh->gss_state.libs)
+ ssh->gss_state.libs = ssh_gss_setup(ssh->conf);
+ ssh->gss_state.lib = NULL;
+ if (ssh->gss_state.libs->nlibraries > 0) {
+ int i, j;
+ for (i = 0; i < ngsslibs; i++) {
+ int want_id = conf_get_int_int(ssh->conf,
+ CONF_ssh_gsslist, i);
+ for (j = 0; j < ssh->gss_state.libs->nlibraries; j++)
+ if (ssh->gss_state.libs->libraries[j].id == want_id) {
+ ssh->gss_state.lib =
+ &ssh->gss_state.libs->libraries[j];
+ goto got_gsslib; /* double break */
+ }
+ }
+ got_gsslib:
+ /*
+ * We always expect to have found something in
+ * the above loop: we only came here if there
+ * was at least one viable GSS library, and the
+ * preference list should always mention
+ * everything and only change the order.
+ */
+ assert(ssh->gss_state.lib);
+ }
+#endif
+
+ connection_layer = ssh2_connection_new(
+ ssh, ssh->connshare, is_simple, ssh->conf,
+ ssh_verstring_get_remote(old_bpp), &ssh->user_input, &ssh->cl);
+ ssh_connect_ppl(ssh, connection_layer);
+
+ if (conf_get_bool(ssh->conf, CONF_ssh_no_userauth)) {
+ userauth_layer = NULL;
+ transport_child_layer = connection_layer;
+ } else {
+ char *username = get_remote_username(ssh->conf);
+
+ userauth_layer = ssh2_userauth_new(
+ connection_layer, ssh->savedhost, ssh->savedport,
+ ssh->fullhostname,
+ conf_get_filename(ssh->conf, CONF_keyfile),
+ conf_get_filename(ssh->conf, CONF_detached_cert),
+ conf_get_bool(ssh->conf, CONF_ssh_show_banner),
+ conf_get_bool(ssh->conf, CONF_tryagent),
+ conf_get_bool(ssh->conf, CONF_ssh_no_trivial_userauth),
+ username,
+ conf_get_bool(ssh->conf, CONF_change_username),
+ conf_get_bool(ssh->conf, CONF_try_ki_auth),
+#ifndef NO_GSSAPI
+ conf_get_bool(ssh->conf, CONF_try_gssapi_auth),
+ conf_get_bool(ssh->conf, CONF_try_gssapi_kex),
+ conf_get_bool(ssh->conf, CONF_gssapifwd),
+ &ssh->gss_state,
+#else
+ false,
+ false,
+ false,
+ NULL,
+#endif
+ conf_get_str(ssh->conf, CONF_auth_plugin));
+ ssh_connect_ppl(ssh, userauth_layer);
+ transport_child_layer = userauth_layer;
+
+ sfree(username);
+ }
+
+ ssh->base_layer = ssh2_transport_new(
+ ssh->conf, ssh->savedhost, ssh->savedport,
+ ssh->fullhostname,
+ ssh_verstring_get_local(old_bpp),
+ ssh_verstring_get_remote(old_bpp),
+#ifndef NO_GSSAPI
+ &ssh->gss_state,
+#else
+ NULL,
+#endif
+ &ssh->stats, transport_child_layer, NULL);
+ ssh_connect_ppl(ssh, ssh->base_layer);
+
+ if (userauth_layer)
+ ssh2_userauth_set_transport_layer(userauth_layer,
+ ssh->base_layer);
+
+ } else {
+
+ ssh->bpp = ssh1_bpp_new(ssh->logctx);
+ ssh_connect_bpp(ssh);
+
+ connection_layer = ssh1_connection_new(
+ ssh, ssh->conf, &ssh->user_input, &ssh->cl);
+ ssh_connect_ppl(ssh, connection_layer);
+
+ ssh->base_layer = ssh1_login_new(
+ ssh->conf, ssh->savedhost, ssh->savedport, connection_layer);
+ ssh_connect_ppl(ssh, ssh->base_layer);
+
+ }
+
+ } else {
+ ssh->bpp = ssh2_bare_bpp_new(ssh->logctx);
+ ssh_connect_bpp(ssh);
+
+ connection_layer = ssh2_connection_new(
+ ssh, ssh->connshare, false, ssh->conf,
+ ssh_verstring_get_remote(old_bpp), &ssh->user_input, &ssh->cl);
+ ssh_connect_ppl(ssh, connection_layer);
+ ssh->base_layer = connection_layer;
+ }
+
+ /* Connect the base layer - whichever it is - to the BPP, and set
+ * up its selfptr. */
+ ssh->base_layer->selfptr = &ssh->base_layer;
+ ssh_ppl_setup_queues(ssh->base_layer, &ssh->bpp->in_pq, &ssh->bpp->out_pq);
+
+ seat_update_specials_menu(ssh->seat);
+ ssh->pinger = pinger_new(ssh->conf, &ssh->backend);
+
+ queue_idempotent_callback(&ssh->bpp->ic_in_raw);
+ ssh_ppl_process_queue(ssh->base_layer);
+
+ /* Pass in the initial terminal size, if we knew it already. */
+ ssh_terminal_size(ssh->cl, ssh->term_width, ssh->term_height);
+
+ ssh_bpp_free(old_bpp);
+}
+
+void ssh_check_frozen(Ssh *ssh)
+{
+ if (!ssh->s)
+ return;
+
+ bool prev_frozen = ssh->socket_frozen;
+ ssh->socket_frozen = (ssh->logically_frozen ||
+ bufchain_size(&ssh->in_raw) > SSH_MAX_BACKLOG);
+ sk_set_frozen(ssh->s, ssh->socket_frozen);
+ if (prev_frozen && !ssh->socket_frozen && ssh->bpp) {
+ /*
+ * If we've just unfrozen, process any SSH connection data
+ * that was stashed in our queue while we were frozen.
+ */
+ queue_idempotent_callback(&ssh->bpp->ic_in_raw);
+ }
+}
+
+void ssh_conn_processed_data(Ssh *ssh)
+{
+ ssh_check_frozen(ssh);
+}
+
+static void ssh_bpp_output_raw_data_callback(void *vctx)
+{
+ Ssh *ssh = (Ssh *)vctx;
+
+ if (!ssh->s)
+ return;
+
+ while (bufchain_size(&ssh->out_raw) > 0) {
+ size_t backlog;
+
+ ptrlen data = bufchain_prefix(&ssh->out_raw);
+
+ if (ssh->logctx)
+ log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data.ptr, data.len,
+ 0, NULL, NULL, 0, NULL);
+ backlog = sk_write(ssh->s, data.ptr, data.len);
+
+ bufchain_consume(&ssh->out_raw, data.len);
+
+ if (backlog > SSH_MAX_BACKLOG) {
+ ssh_throttle_all(ssh, true, backlog);
+ return;
+ }
+ }
+
+ ssh_check_frozen(ssh);
+
+ if (ssh->pending_close) {
+ sk_close(ssh->s);
+ ssh->s = NULL;
+ seat_notify_remote_disconnect(ssh->seat);
+ }
+}
+
+static void ssh_shutdown_internal(Ssh *ssh)
+{
+ expire_timer_context(ssh);
+
+ if (ssh->connshare) {
+ sharestate_free(ssh->connshare);
+ ssh->connshare = NULL;
+ }
+
+ if (ssh->pinger) {
+ pinger_free(ssh->pinger);
+ ssh->pinger = NULL;
+ }
+
+ /*
+ * We only need to free the base PPL, which will free the others
+ * (if any) transitively.
+ */
+ if (ssh->base_layer) {
+ ssh_ppl_free(ssh->base_layer);
+ ssh->base_layer = NULL;
+ }
+
+ ssh->cl = NULL;
+}
+
+static void ssh_shutdown(Ssh *ssh)
+{
+ ssh_shutdown_internal(ssh);
+
+ if (ssh->bpp) {
+ ssh_bpp_free(ssh->bpp);
+ ssh->bpp = NULL;
+ }
+
+ if (ssh->s) {
+ sk_close(ssh->s);
+ ssh->s = NULL;
+ seat_notify_remote_disconnect(ssh->seat);
+ }
+
+ bufchain_clear(&ssh->in_raw);
+ bufchain_clear(&ssh->out_raw);
+ bufchain_clear(&ssh->user_input);
+}
+
+static void ssh_initiate_connection_close(Ssh *ssh)
+{
+ /* Wind up everything above the BPP. */
+ ssh_shutdown_internal(ssh);
+
+ /* Force any remaining queued SSH packets through the BPP, and
+ * schedule closing the network socket after they go out. */
+ ssh_bpp_handle_output(ssh->bpp);
+ ssh->pending_close = true;
+ queue_idempotent_callback(&ssh->ic_out_raw);
+
+ /* Now we expect the other end to close the connection too in
+ * response, so arrange that we'll receive notification of that
+ * via ssh_remote_eof. */
+ ssh->bpp->expect_close = true;
+}
+
+#define GET_FORMATTED_MSG \
+ char *msg; \
+ va_list ap; \
+ va_start(ap, fmt); \
+ msg = dupvprintf(fmt, ap); \
+ va_end(ap); \
+ ((void)0) /* eat trailing semicolon */
+
+void ssh_remote_error(Ssh *ssh, const char *fmt, ...)
+{
+ if (ssh->base_layer || !ssh->session_started) {
+ GET_FORMATTED_MSG;
+
+ /* 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, PlugCloseType type, const char *error_msg)
+{
+ Ssh *ssh = container_of(plug, Ssh, plug);
+ if (type == PLUGCLOSE_USER_ABORT) {
+ ssh_user_close(ssh, "%s", error_msg);
+ } else if (type != PLUGCLOSE_NORMAL) {
+ ssh_remote_error(ssh, "%s", error_msg);
+ } else if (ssh->bpp) {
+ ssh->bpp->input_eof = true;
+ queue_idempotent_callback(&ssh->bpp->ic_in_raw);
+ }
+}
+
+static void ssh_receive(Plug *plug, int urgent, const char *data, size_t len)
+{
+ Ssh *ssh = container_of(plug, Ssh, plug);
+
+ /* Log raw data, if we're in that mode. */
+ if (ssh->logctx)
+ log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, len,
+ 0, NULL, NULL, 0, NULL);
+
+ bufchain_add(&ssh->in_raw, data, len);
+ if (!ssh->logically_frozen && ssh->bpp)
+ queue_idempotent_callback(&ssh->bpp->ic_in_raw);
+
+ ssh_check_frozen(ssh);
+}
+
+static void ssh_sent(Plug *plug, size_t bufsize)
+{
+ Ssh *ssh = container_of(plug, Ssh, plug);
+ /*
+ * If the send backlog on the SSH socket itself clears, we should
+ * unthrottle the whole world if it was throttled. Also trigger an
+ * extra call to the consumer of the BPP's output, to try to send
+ * some more data off its bufchain.
+ */
+ if (bufsize < SSH_MAX_BACKLOG) {
+ ssh_throttle_all(ssh, false, bufsize);
+ queue_idempotent_callback(&ssh->ic_out_raw);
+ ssh_sendbuffer_changed(ssh);
+ }
+}
+
+static void ssh_hostport_setup(const char *host, int port, Conf *conf,
+ char **savedhost, int *savedport,
+ char **loghost_ret)
+{
+ char *loghost = conf_get_str(conf, CONF_loghost);
+ if (loghost_ret)
+ *loghost_ret = loghost;
+
+ if (*loghost) {
+ char *tmphost;
+ char *colon;
+
+ tmphost = dupstr(loghost);
+ *savedport = 22; /* default ssh port */
+
+ /*
+ * A colon suffix on the hostname string also lets us affect
+ * savedport. (Unless there are multiple colons, in which case
+ * we assume this is an unbracketed IPv6 literal.)
+ */
+ colon = host_strrchr(tmphost, ':');
+ if (colon && colon == host_strchr(tmphost, ':')) {
+ *colon++ = '\0';
+ if (*colon)
+ *savedport = atoi(colon);
+ }
+
+ *savedhost = host_strduptrim(tmphost);
+ sfree(tmphost);
+ } else {
+ *savedhost = host_strduptrim(host);
+ if (port < 0)
+ port = 22; /* default ssh port */
+ *savedport = port;
+ }
+}
+
+static bool ssh_test_for_upstream(const char *host, int port, Conf *conf)
+{
+ char *savedhost;
+ int savedport;
+ bool ret;
+
+ random_ref(); /* platform may need this to determine share socket name */
+ ssh_hostport_setup(host, port, conf, &savedhost, &savedport, NULL);
+ ret = ssh_share_test_for_upstream(savedhost, savedport, conf);
+ sfree(savedhost);
+ random_unref();
+
+ return ret;
+}
+
+static char *ssh_close_warn_text(Backend *be)
+{
+ Ssh *ssh = container_of(be, Ssh, backend);
+ if (!ssh->connshare)
+ return NULL;
+ int ndowns = share_ndownstreams(ssh->connshare);
+ if (ndowns == 0)
+ return NULL;
+ char *msg = dupprintf("This will also close %d downstream connection%s.",
+ ndowns, ndowns==1 ? "" : "s");
+ return msg;
+}
+
+static const PlugVtable Ssh_plugvt = {
+ .log = ssh_socket_log,
+ .closing = ssh_closing,
+ .receive = ssh_receive,
+ .sent = ssh_sent,
+};
+
+static char *ssh_description(Interactor *itr)
+{
+ Ssh *ssh = container_of(itr, Ssh, interactor);
+ return dupstr(ssh->description);
+}
+
+static LogPolicy *ssh_logpolicy(Interactor *itr)
+{
+ Ssh *ssh = container_of(itr, Ssh, interactor);
+ return log_get_policy(ssh->logctx);
+}
+
+static Seat *ssh_get_seat(Interactor *itr)
+{
+ Ssh *ssh = container_of(itr, Ssh, interactor);
+ return ssh->seat;
+}
+
+static void ssh_set_seat(Interactor *itr, Seat *seat)
+{
+ Ssh *ssh = container_of(itr, Ssh, interactor);
+ ssh->seat = seat;
+}
+
+static const InteractorVtable Ssh_interactorvt = {
+ .description = ssh_description,
+ .logpolicy = ssh_logpolicy,
+ .get_seat = ssh_get_seat,
+ .set_seat = ssh_set_seat,
+};
+
+/*
+ * Connect to specified host and port.
+ * Returns an error message, or NULL on success.
+ * Also places the canonical host name into `realhost'. It must be
+ * freed by the caller.
+ */
+static char *connect_to_host(
+ Ssh *ssh, const char *host, int port, char *loghost, char **realhost,
+ bool nodelay, bool keepalive)
+{
+ SockAddr *addr;
+ const char *err;
+ int addressfamily, sshprot;
+
+ ssh->plug.vt = &Ssh_plugvt;
+
+ /*
+ * Try connection-sharing, in case that means we don't open a
+ * socket after all. ssh_connection_sharing_init will connect to a
+ * previously established upstream if it can, and failing that,
+ * establish a listening socket for _us_ to be the upstream. In
+ * the latter case it will return NULL just as if it had done
+ * nothing, because here we only need to care if we're a
+ * downstream and need to do our connection setup differently.
+ */
+ ssh->connshare = NULL;
+ ssh->attempting_connshare = true; /* affects socket logging behaviour */
+ ssh->s = ssh_connection_sharing_init(
+ ssh->savedhost, ssh->savedport, ssh->conf, ssh->logctx,
+ &ssh->plug, &ssh->connshare);
+ if (ssh->connshare)
+ ssh_connshare_provide_connlayer(ssh->connshare, &ssh->cl_dummy);
+ ssh->attempting_connshare = false;
+ if (ssh->s != NULL) {
+ /*
+ * We are a downstream.
+ */
+ ssh->bare_connection = true;
+ ssh->fullhostname = NULL;
+ *realhost = dupstr(host); /* best we can do */
+
+ if (seat_verbose(ssh->seat) || seat_interactive(ssh->seat)) {
+ /* In an interactive session, or in verbose mode, announce
+ * in the console window that we're a sharing downstream,
+ * to avoid confusing users as to why this session doesn't
+ * behave in quite the usual way. */
+ const char *msg =
+ "Reusing a shared connection to this server.\r\n";
+ seat_stderr_pl(ssh->seat, ptrlen_from_asciz(msg));
+ }
+ } else {
+ /*
+ * We're not a downstream, so open a normal socket.
+ */
+
+ /*
+ * Try to find host.
+ */
+ addressfamily = conf_get_int(ssh->conf, CONF_addressfamily);
+ addr = name_lookup(host, port, realhost, ssh->conf, addressfamily,
+ ssh->logctx, "SSH connection");
+ if ((err = sk_addr_error(addr)) != NULL) {
+ sk_addr_free(addr);
+ return dupstr(err);
+ }
+ ssh->fullhostname = dupstr(*realhost); /* save in case of GSSAPI */
+
+ ssh->s = new_connection(addr, *realhost, port,
+ false, true, nodelay, keepalive,
+ &ssh->plug, ssh->conf, &ssh->interactor);
+ if ((err = sk_socket_error(ssh->s)) != NULL) {
+ ssh->s = NULL;
+ seat_notify_remote_exit(ssh->seat);
+ seat_notify_remote_disconnect(ssh->seat);
+ return dupstr(err);
+ }
+ }
+
+ /*
+ * The SSH version number is always fixed (since we no longer support
+ * fallback between versions), so set it now.
+ */
+ sshprot = conf_get_int(ssh->conf, CONF_sshprot);
+ assert(sshprot == 0 || sshprot == 3);
+ if (sshprot == 0)
+ /* SSH-1 only */
+ ssh->version = 1;
+ if (sshprot == 3 || ssh->bare_connection) {
+ /* SSH-2 only */
+ ssh->version = 2;
+ }
+
+ /*
+ * Set up the initial BPP that will do the version string
+ * exchange, and get it started so that it can send the outgoing
+ * version string early if it wants to.
+ */
+ ssh->version_receiver.got_ssh_version = ssh_got_ssh_version;
+ ssh->bpp = ssh_verstring_new(
+ ssh->conf, ssh->logctx, ssh->bare_connection,
+ ssh->version == 1 ? "1.5" : "2.0", &ssh->version_receiver,
+ false, "PuTTY");
+ ssh_connect_bpp(ssh);
+ queue_idempotent_callback(&ssh->bpp->ic_in_raw);
+
+ /*
+ * loghost, if configured, overrides realhost.
+ */
+ if (*loghost) {
+ sfree(*realhost);
+ *realhost = dupstr(loghost);
+ }
+
+ return NULL;
+}
+
+/*
+ * Throttle or unthrottle the SSH connection.
+ */
+void ssh_throttle_conn(Ssh *ssh, int adjust)
+{
+ int old_count = ssh->conn_throttle_count;
+ bool frozen;
+
+ ssh->conn_throttle_count += adjust;
+ assert(ssh->conn_throttle_count >= 0);
+
+ if (ssh->conn_throttle_count && !old_count) {
+ frozen = true;
+ } else if (!ssh->conn_throttle_count && old_count) {
+ frozen = false;
+ } else {
+ return; /* don't change current frozen state */
+ }
+
+ ssh->logically_frozen = frozen;
+ ssh_check_frozen(ssh);
+}
+
+/*
+ * Throttle or unthrottle _all_ local data streams (for when sends
+ * on the SSH connection itself back up).
+ */
+static void ssh_throttle_all(Ssh *ssh, bool enable, size_t bufsize)
+{
+ if (enable == ssh->throttled_all)
+ return;
+ ssh->throttled_all = enable;
+ ssh->overall_bufsize = bufsize;
+
+ ssh_throttle_all_channels(ssh->cl, enable);
+}
+
+static void ssh_cache_conf_values(Ssh *ssh)
+{
+ ssh->pls.omit_passwords = conf_get_bool(ssh->conf, CONF_logomitpass);
+ ssh->pls.omit_data = conf_get_bool(ssh->conf, CONF_logomitdata);
+}
+
+bool ssh_is_bare(Ssh *ssh)
+{
+ return ssh->backend.vt->protocol == PROT_SSHCONN;
+}
+
+/* Dummy connlayer must provide ssh_sharing_no_more_downstreams,
+ * because it might be called early due to plink -shareexists */
+static void dummy_sharing_no_more_downstreams(ConnectionLayer *cl) {}
+static const ConnectionLayerVtable dummy_connlayer_vtable = {
+ .sharing_no_more_downstreams = dummy_sharing_no_more_downstreams,
+};
+
+/*
+ * Called to set up the connection.
+ *
+ * Returns an error message, or NULL on success.
+ */
+static char *ssh_init(const BackendVtable *vt, Seat *seat,
+ Backend **backend_handle, LogContext *logctx,
+ Conf *conf, const char *host, int port,
+ char **realhost, bool nodelay, bool keepalive)
+{
+ Ssh *ssh;
+
+ ssh = snew(Ssh);
+ memset(ssh, 0, sizeof(Ssh));
+
+ ssh->conf = conf_copy(conf);
+ ssh_cache_conf_values(ssh);
+ ssh->exitcode = -1;
+ ssh->pls.kctx = SSH2_PKTCTX_NOKEX;
+ ssh->pls.actx = SSH2_PKTCTX_NOAUTH;
+ bufchain_init(&ssh->in_raw);
+ bufchain_init(&ssh->out_raw);
+ bufchain_init(&ssh->user_input);
+ ssh->ic_out_raw.fn = ssh_bpp_output_raw_data_callback;
+ ssh->ic_out_raw.ctx = ssh;
+
+ ssh->term_width = conf_get_int(ssh->conf, CONF_width);
+ ssh->term_height = conf_get_int(ssh->conf, CONF_height);
+
+ ssh->backend.vt = vt;
+ ssh->interactor.vt = &Ssh_interactorvt;
+ ssh->backend.interactor = &ssh->interactor;
+ *backend_handle = &ssh->backend;
+
+ ssh->bare_connection = (vt->protocol == PROT_SSHCONN);
+
+ ssh->seat = seat;
+ ssh->cl_dummy.vt = &dummy_connlayer_vtable;
+ ssh->cl_dummy.logctx = ssh->logctx = logctx;
+
+ char *loghost;
+
+ ssh_hostport_setup(host, port, ssh->conf,
+ &ssh->savedhost, &ssh->savedport, &loghost);
+ ssh->description = default_description(vt, ssh->savedhost, ssh->savedport);
+
+ random_ref(); /* do this now - may be needed by sharing setup code */
+ ssh->need_random_unref = true;
+
+ char *conn_err = connect_to_host(
+ ssh, host, port, loghost, realhost, nodelay, keepalive);
+ if (conn_err) {
+ /* Call random_unref now instead of waiting until the caller
+ * frees this useless Ssh object, in case the caller is
+ * impatient and just exits without bothering, in which case
+ * the random seed won't be re-saved. */
+ ssh->need_random_unref = false;
+ random_unref();
+ return conn_err;
+ }
+
+ return NULL;
+}
+
+static void ssh_free(Backend *be)
+{
+ Ssh *ssh = container_of(be, Ssh, backend);
+ bool need_random_unref;
+
+ ssh_shutdown(ssh);
+
+ if (is_tempseat(ssh->seat))
+ tempseat_free(ssh->seat);
+
+ conf_free(ssh->conf);
+ if (ssh->connshare)
+ sharestate_free(ssh->connshare);
+ sfree(ssh->savedhost);
+ sfree(ssh->fullhostname);
+ sfree(ssh->specials);
+
+#ifndef NO_GSSAPI
+ if (ssh->gss_state.srv_name)
+ ssh->gss_state.lib->release_name(
+ ssh->gss_state.lib, &ssh->gss_state.srv_name);
+ if (ssh->gss_state.ctx != NULL)
+ ssh->gss_state.lib->release_cred(
+ ssh->gss_state.lib, &ssh->gss_state.ctx);
+ if (ssh->gss_state.libs)
+ ssh_gss_cleanup(ssh->gss_state.libs);
+#endif
+
+ sfree(ssh->deferred_abort_message);
+ sfree(ssh->description);
+
+ delete_callbacks_for_context(ssh); /* likely to catch ic_out_raw */
+
+ need_random_unref = ssh->need_random_unref;
+ sfree(ssh);
+
+ if (need_random_unref)
+ random_unref();
+}
+
+/*
+ * Reconfigure the SSH backend.
+ */
+static void ssh_reconfig(Backend *be, Conf *conf)
+{
+ Ssh *ssh = container_of(be, Ssh, backend);
+
+ if (ssh->pinger)
+ pinger_reconfig(ssh->pinger, ssh->conf, conf);
+
+ ssh_ppl_reconfigure(ssh->base_layer, conf);
+
+ conf_free(ssh->conf);
+ ssh->conf = conf_copy(conf);
+ ssh_cache_conf_values(ssh);
+}
+
+/*
+ * Called to send data down the SSH connection.
+ */
+static void ssh_send(Backend *be, const char *buf, size_t len)
+{
+ Ssh *ssh = container_of(be, Ssh, backend);
+
+ if (ssh == NULL || ssh->s == NULL)
+ return;
+
+ bufchain_add(&ssh->user_input, buf, len);
+ if (ssh->cl)
+ ssh_got_user_input(ssh->cl);
+}
+
+/*
+ * Called to query the current amount of buffered stdin data.
+ */
+static size_t ssh_sendbuffer(Backend *be)
+{
+ Ssh *ssh = container_of(be, Ssh, backend);
+ size_t backlog;
+
+ if (!ssh || !ssh->s || !ssh->cl)
+ return 0;
+
+ backlog = ssh_stdin_backlog(ssh->cl);
+
+ if (ssh->base_layer)
+ backlog += ssh_ppl_queued_data_size(ssh->base_layer);
+
+ /*
+ * If the SSH socket itself has backed up, add the total backup
+ * size on that to any individual buffer on the stdin channel.
+ */
+ if (ssh->throttled_all)
+ backlog += ssh->overall_bufsize;
+
+ return backlog;
+}
+
+void ssh_sendbuffer_changed(Ssh *ssh)
+{
+ seat_sent(ssh->seat, ssh_sendbuffer(&ssh->backend));
+}
+
+/*
+ * Called to set the size of the window from SSH's POV.
+ */
+static void ssh_size(Backend *be, int width, int height)
+{
+ Ssh *ssh = container_of(be, Ssh, backend);
+
+ ssh->term_width = width;
+ ssh->term_height = height;
+ if (ssh->cl)
+ ssh_terminal_size(ssh->cl, ssh->term_width, ssh->term_height);
+}
+
+struct ssh_add_special_ctx {
+ SessionSpecial *specials;
+ size_t nspecials, specials_size;
+};
+
+static void ssh_add_special(void *vctx, const char *text,
+ SessionSpecialCode code, int arg)
+{
+ struct ssh_add_special_ctx *ctx = (struct ssh_add_special_ctx *)vctx;
+ SessionSpecial *spec;
+
+ sgrowarray(ctx->specials, ctx->specials_size, ctx->nspecials);
+ spec = &ctx->specials[ctx->nspecials++];
+ spec->name = text;
+ spec->code = code;
+ spec->arg = arg;
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const SessionSpecial *ssh_get_specials(Backend *be)
+{
+ Ssh *ssh = container_of(be, Ssh, backend);
+
+ /*
+ * Ask all our active protocol layers what specials they've got,
+ * and amalgamate the list into one combined one.
+ */
+
+ struct ssh_add_special_ctx ctx[1];
+
+ ctx->specials = NULL;
+ ctx->nspecials = ctx->specials_size = 0;
+
+ if (ssh->base_layer)
+ ssh_ppl_get_specials(ssh->base_layer, ssh_add_special, ctx);
+
+ if (ctx->specials) {
+ /* If the list is non-empty, terminate it with a SS_EXITMENU. */
+ ssh_add_special(ctx, NULL, SS_EXITMENU, 0);
+ }
+
+ sfree(ssh->specials);
+ ssh->specials = ctx->specials;
+ return ssh->specials;
+}
+
+/*
+ * Send special codes.
+ */
+static void ssh_special(Backend *be, SessionSpecialCode code, int arg)
+{
+ Ssh *ssh = container_of(be, Ssh, backend);
+
+ if (ssh->base_layer)
+ ssh_ppl_special_cmd(ssh->base_layer, code, arg);
+}
+
+/*
+ * This is called when the seat's output channel manages to clear some
+ * backlog.
+ */
+static void ssh_unthrottle(Backend *be, size_t bufsize)
+{
+ Ssh *ssh = container_of(be, Ssh, backend);
+
+ if (ssh->cl)
+ ssh_stdout_unthrottle(ssh->cl, bufsize);
+}
+
+static bool ssh_connected(Backend *be)
+{
+ Ssh *ssh = container_of(be, Ssh, backend);
+ return ssh->s != NULL;
+}
+
+static bool ssh_sendok(Backend *be)
+{
+ Ssh *ssh = container_of(be, Ssh, backend);
+ return ssh->cl && ssh_get_wants_user_input(ssh->cl);
+}
+
+void ssh_check_sendok(Ssh *ssh)
+{
+ /* Called when the connection layer might have caused ssh_sendok
+ * to start returning true */
+ if (ssh->ldisc)
+ ldisc_check_sendok(ssh->ldisc);
+}
+
+void ssh_ldisc_update(Ssh *ssh)
+{
+ /* Called when the connection layer wants to propagate an update
+ * to the line discipline options */
+ if (ssh->ldisc)
+ ldisc_echoedit_update(ssh->ldisc);
+}
+
+static bool ssh_ldisc(Backend *be, int option)
+{
+ Ssh *ssh = container_of(be, Ssh, backend);
+ return ssh->cl ? ssh_ldisc_option(ssh->cl, option) : false;
+}
+
+static void ssh_provide_ldisc(Backend *be, Ldisc *ldisc)
+{
+ Ssh *ssh = container_of(be, Ssh, backend);
+ ssh->ldisc = ldisc;
+}
+
+void ssh_got_exitcode(Ssh *ssh, int exitcode)
+{
+ ssh->exitcode = exitcode;
+}
+
+static int ssh_return_exitcode(Backend *be)
+{
+ Ssh *ssh = container_of(be, Ssh, backend);
+ if (ssh->s && (!ssh->session_started || ssh->base_layer))
+ return -1;
+ else
+ return (ssh->exitcode >= 0 ? ssh->exitcode : INT_MAX);
+}
+
+/*
+ * cfg_info for SSH is the protocol running in this session.
+ * (1 or 2 for the full SSH-1 or SSH-2 protocol; -1 for the bare
+ * SSH-2 connection protocol, i.e. a downstream; 0 for not-decided-yet.)
+ */
+static int ssh_cfg_info(Backend *be)
+{
+ Ssh *ssh = container_of(be, Ssh, backend);
+ if (ssh->version == 0)
+ return 0; /* don't know yet */
+ else if (ssh->bare_connection)
+ return -1;
+ else
+ return ssh->version;
+}
+
+/*
+ * Gross hack: pscp will try to start SFTP but fall back to scp1 if
+ * that fails. This variable is the means by which pscp.c can reach
+ * into the SSH code and find out which one it got.
+ */
+extern bool ssh_fallback_cmd(Backend *be)
+{
+ Ssh *ssh = container_of(be, Ssh, backend);
+ return ssh->fallback_cmd;
+}
+
+void ssh_got_fallback_cmd(Ssh *ssh)
+{
+ ssh->fallback_cmd = true;
+}
+
+const BackendVtable ssh_backend = {
+ .init = ssh_init,
+ .free = ssh_free,
+ .reconfig = ssh_reconfig,
+ .send = ssh_send,
+ .sendbuffer = ssh_sendbuffer,
+ .size = ssh_size,
+ .special = ssh_special,
+ .get_specials = ssh_get_specials,
+ .connected = ssh_connected,
+ .exitcode = ssh_return_exitcode,
+ .sendok = ssh_sendok,
+ .ldisc_option_state = ssh_ldisc,
+ .provide_ldisc = ssh_provide_ldisc,
+ .unthrottle = ssh_unthrottle,
+ .cfg_info = ssh_cfg_info,
+ .test_for_upstream = ssh_test_for_upstream,
+ .close_warn_text = ssh_close_warn_text,
+ .id = "ssh",
+ .displayname_tc = "SSH",
+ .displayname_lc = "SSH", /* proper name, so capitalise it anyway */
+ .protocol = PROT_SSH,
+ .flags = BACKEND_SUPPORTS_NC_HOST | BACKEND_NOTIFIES_SESSION_START,
+ .default_port = 22,
+};
+
+const BackendVtable sshconn_backend = {
+ .init = ssh_init,
+ .free = ssh_free,
+ .reconfig = ssh_reconfig,
+ .send = ssh_send,
+ .sendbuffer = ssh_sendbuffer,
+ .size = ssh_size,
+ .special = ssh_special,
+ .get_specials = ssh_get_specials,
+ .connected = ssh_connected,
+ .exitcode = ssh_return_exitcode,
+ .sendok = ssh_sendok,
+ .ldisc_option_state = ssh_ldisc,
+ .provide_ldisc = ssh_provide_ldisc,
+ .unthrottle = ssh_unthrottle,
+ .cfg_info = ssh_cfg_info,
+ .test_for_upstream = ssh_test_for_upstream,
+ .close_warn_text = ssh_close_warn_text,
+ .id = "ssh-connection",
+ .displayname_tc = "Bare ssh-connection",
+ .displayname_lc = "bare ssh-connection",
+ .protocol = PROT_SSHCONN,
+ .flags = BACKEND_SUPPORTS_NC_HOST | BACKEND_NOTIFIES_SESSION_START,
+};
diff --git a/ssh2transhk.c b/ssh/transient-hostkey-cache.c
index 2e77fdf9..2e77fdf9 100644
--- a/ssh2transhk.c
+++ b/ssh/transient-hostkey-cache.c
diff --git a/ssh/transport2.c b/ssh/transport2.c
new file mode 100644
index 00000000..ce6318aa
--- /dev/null
+++ b/ssh/transport2.c
@@ -0,0 +1,2407 @@
+/*
+ * Packet protocol layer for the SSH-2 transport protocol (RFC 4253).
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "sshcr.h"
+#include "server.h"
+#include "storage.h"
+#include "transport2.h"
+#include "mpint.h"
+
+const struct ssh_signkey_with_user_pref_id ssh2_hostkey_algs[] = {
+ #define ARRAYENT_HOSTKEY_ALGORITHM(type, alg) { &alg, type },
+ HOSTKEY_ALGORITHMS(ARRAYENT_HOSTKEY_ALGORITHM)
+};
+
+const static ssh2_macalg *const macs[] = {
+ &ssh_hmac_sha256, &ssh_hmac_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 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,
+ .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 SeatPromptResult 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);
+static int ca_blob_compare(void *av, void *bv);
+
+PacketProtocolLayer *ssh2_transport_new(
+ Conf *conf, const char *host, int port, const char *fullhostname,
+ const char *client_greeting, const char *server_greeting,
+ struct ssh_connection_shared_gss_state *shgss,
+ struct DataTransferStats *stats, PacketProtocolLayer *higher_layer,
+ const SshServerConfig *ssc)
+{
+ struct ssh2_transport_state *s = snew(struct ssh2_transport_state);
+ memset(s, 0, sizeof(*s));
+ s->ppl.vt = &ssh2_transport_vtable;
+
+ s->conf = conf_copy(conf);
+ s->savedhost = dupstr(host);
+ s->savedport = port;
+ s->fullhostname = dupstr(fullhostname);
+ s->shgss = shgss;
+ s->client_greeting = dupstr(client_greeting);
+ s->server_greeting = dupstr(server_greeting);
+ s->stats = stats;
+ s->hostkeyblob = strbuf_new();
+ s->host_cas = newtree234(ca_blob_compare);
+
+ pq_in_init(&s->pq_in_higher);
+ pq_out_init(&s->pq_out_higher);
+ s->pq_out_higher.pqb.ic = &s->ic_pq_out_higher;
+ s->ic_pq_out_higher.fn = ssh2_transport_higher_layer_packet_callback;
+ s->ic_pq_out_higher.ctx = &s->ppl;
+
+ s->higher_layer = higher_layer;
+ s->higher_layer->selfptr = &s->higher_layer;
+ ssh_ppl_setup_queues(s->higher_layer, &s->pq_in_higher, &s->pq_out_higher);
+
+#ifndef NO_GSSAPI
+ s->gss_cred_expiry = GSS_NO_EXPIRATION;
+ s->shgss->srv_name = GSS_C_NO_NAME;
+ s->shgss->ctx = NULL;
+#endif
+ s->thc = ssh_transient_hostkey_cache_new();
+ s->gss_kex_used = false;
+
+ s->outgoing_kexinit = strbuf_new();
+ s->incoming_kexinit = strbuf_new();
+ if (ssc) {
+ s->ssc = ssc;
+ s->client_kexinit = s->incoming_kexinit;
+ s->server_kexinit = s->outgoing_kexinit;
+ s->cstrans = &s->in;
+ s->sctrans = &s->out;
+ s->out.mkkey_adjust = 1;
+ } else {
+ s->client_kexinit = s->outgoing_kexinit;
+ s->server_kexinit = s->incoming_kexinit;
+ s->cstrans = &s->out;
+ s->sctrans = &s->in;
+ s->in.mkkey_adjust = 1;
+ }
+
+ s->weak_algorithms_consented_to = newtree234(weak_algorithm_compare);
+
+ ssh2_transport_set_max_data_size(s);
+
+ return &s->ppl;
+}
+
+static void ssh2_transport_free(PacketProtocolLayer *ppl)
+{
+ struct ssh2_transport_state *s =
+ container_of(ppl, struct ssh2_transport_state, ppl);
+
+ /*
+ * As our last act before being freed, move any outgoing packets
+ * off our higher layer's output queue on to our own output queue.
+ * We might be being freed while the SSH connection is still alive
+ * (because we're initiating shutdown from our end), in which case
+ * we don't want those last few packets to get lost.
+ *
+ * (If our owner were to have already destroyed our output pq
+ * before wanting to free us, then it would have to reset our
+ * publicly visible out_pq field to NULL to inhibit this attempt.
+ * But that's not how I expect the shutdown sequence to go in
+ * practice.)
+ */
+ if (s->ppl.out_pq)
+ pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher);
+
+ conf_free(s->conf);
+
+ ssh_ppl_free(s->higher_layer);
+
+ pq_in_clear(&s->pq_in_higher);
+ pq_out_clear(&s->pq_out_higher);
+
+ sfree(s->savedhost);
+ sfree(s->fullhostname);
+ sfree(s->client_greeting);
+ sfree(s->server_greeting);
+ sfree(s->keystr);
+ strbuf_free(s->hostkeyblob);
+ {
+ host_ca *hca;
+ while ( (hca = delpos234(s->host_cas, 0)) )
+ host_ca_free(hca);
+ freetree234(s->host_cas);
+ }
+ if (s->hkey && !s->hostkeys) {
+ ssh_key_free(s->hkey);
+ s->hkey = NULL;
+ }
+ for (size_t i = 0; i < NKEXLIST; i++)
+ sfree(s->kexlists[i].algs);
+ if (s->f) mp_free(s->f);
+ if (s->p) mp_free(s->p);
+ if (s->g) mp_free(s->g);
+ if (s->ebuf) strbuf_free(s->ebuf);
+ if (s->fbuf) strbuf_free(s->fbuf);
+ if (s->kex_shared_secret) strbuf_free(s->kex_shared_secret);
+ if (s->dh_ctx)
+ dh_cleanup(s->dh_ctx);
+ if (s->rsa_kex_key_needs_freeing) {
+ ssh_rsakex_freekey(s->rsa_kex_key);
+ sfree(s->rsa_kex_key);
+ }
+ if (s->ecdh_key)
+ ecdh_key_free(s->ecdh_key);
+ if (s->exhash)
+ ssh_hash_free(s->exhash);
+ strbuf_free(s->outgoing_kexinit);
+ strbuf_free(s->incoming_kexinit);
+ ssh_transient_hostkey_cache_free(s->thc);
+
+ freetree234(s->weak_algorithms_consented_to);
+
+ expire_timer_context(s);
+ sfree(s);
+}
+
+/*
+ * SSH-2 key derivation (RFC 4253 section 7.2).
+ */
+static void ssh2_mkkey(
+ struct ssh2_transport_state *s, strbuf *out,
+ strbuf *kex_shared_secret, unsigned char *H, char chr, int keylen)
+{
+ int hlen = s->kex_alg->hash->hlen;
+ int keylen_padded;
+ unsigned char *key;
+ ssh_hash *h;
+
+ if (keylen == 0)
+ return;
+
+ /*
+ * Round the requested amount of key material up to a multiple of
+ * the length of the hash we're using to make it. This makes life
+ * simpler because then we can just write each hash output block
+ * straight into the output buffer without fiddling about
+ * truncating the last one. Since it's going into a strbuf, and
+ * strbufs are always smemclr()ed on free, there's no need to
+ * worry about leaving extra potentially-sensitive data in memory
+ * that the caller didn't ask for.
+ */
+ keylen_padded = ((keylen + hlen - 1) / hlen) * hlen;
+
+ strbuf_clear(out);
+ key = strbuf_append(out, keylen_padded);
+
+ /* First hlen bytes. */
+ h = ssh_hash_new(s->kex_alg->hash);
+ if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY))
+ put_datapl(h, ptrlen_from_strbuf(kex_shared_secret));
+ put_data(h, H, hlen);
+ put_byte(h, chr);
+ put_data(h, s->session_id, s->session_id_len);
+ ssh_hash_digest(h, key);
+
+ /* Subsequent blocks of hlen bytes. */
+ if (keylen_padded > hlen) {
+ int offset;
+
+ ssh_hash_reset(h);
+ if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY))
+ put_datapl(h, ptrlen_from_strbuf(kex_shared_secret));
+ put_data(h, H, hlen);
+
+ for (offset = hlen; offset < keylen_padded; offset += hlen) {
+ put_data(h, key + offset - hlen, hlen);
+ ssh_hash_digest_nondestructive(h, key + offset);
+ }
+
+ }
+
+ ssh_hash_free(h);
+}
+
+/*
+ * Find a slot in a KEXINIT algorithm list to use for a new algorithm.
+ * If the algorithm is already in the list, return a pointer to its
+ * entry, otherwise return an entry from the end of the list.
+ *
+ * 'name' is expected to be a ptrlen which it's safe to keep a copy
+ * of.
+ */
+static struct kexinit_algorithm *ssh2_kexinit_addalg_pl(
+ struct kexinit_algorithm_list *list, ptrlen name)
+{
+ for (size_t i = 0; i < list->nalgs; i++)
+ if (ptrlen_eq_ptrlen(list->algs[i].name, name))
+ return &list->algs[i];
+
+ sgrowarray(list->algs, list->algsize, list->nalgs);
+ struct kexinit_algorithm *entry = &list->algs[list->nalgs++];
+ entry->name = name;
+ return entry;
+}
+
+static struct kexinit_algorithm *ssh2_kexinit_addalg(
+ struct kexinit_algorithm_list *list, const char *name)
+{
+ return ssh2_kexinit_addalg_pl(list, ptrlen_from_asciz(name));
+}
+
+bool ssh2_common_filter_queue(PacketProtocolLayer *ppl)
+{
+ static const char *const ssh2_disconnect_reasons[] = {
+ NULL,
+ "host not allowed to connect",
+ "protocol error",
+ "key exchange failed",
+ "host authentication failed",
+ "MAC error",
+ "compression error",
+ "service not available",
+ "protocol version not supported",
+ "host key not verifiable",
+ "connection lost",
+ "by application",
+ "too many connections",
+ "auth cancelled by user",
+ "no more auth methods available",
+ "illegal user name",
+ };
+
+ PktIn *pktin;
+ ptrlen msg;
+ int reason;
+
+ while ((pktin = pq_peek(ppl->in_pq)) != NULL) {
+ switch (pktin->type) {
+ case SSH2_MSG_DISCONNECT:
+ reason = get_uint32(pktin);
+ msg = get_string(pktin);
+
+ ssh_remote_error(
+ ppl->ssh, "Remote side sent disconnect message\n"
+ "type %d (%s):\n\"%.*s\"", reason,
+ ((reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
+ ssh2_disconnect_reasons[reason] : "unknown"),
+ PTRLEN_PRINTF(msg));
+ /* don't try to pop the queue, because we've been freed! */
+ return true; /* indicate that we've been freed */
+
+ case SSH2_MSG_DEBUG:
+ /* XXX maybe we should actually take notice of the return value */
+ get_bool(pktin);
+ msg = get_string(pktin);
+ ppl_logevent("Remote debug message: %.*s", PTRLEN_PRINTF(msg));
+ pq_pop(ppl->in_pq);
+ break;
+
+ case SSH2_MSG_IGNORE:
+ /* Do nothing, because we're ignoring it! Duhh. */
+ pq_pop(ppl->in_pq);
+ break;
+
+ case SSH2_MSG_EXT_INFO: {
+ /*
+ * The BPP enforces that these turn up only at legal
+ * points in the protocol. In particular, it will not pass
+ * an EXT_INFO on to us if it arrives before encryption is
+ * enabled (which is when a MITM could inject one
+ * maliciously).
+ *
+ * However, one of the criteria for legality is that a
+ * server is permitted to send this message immediately
+ * _before_ USERAUTH_SUCCESS. So we may receive this
+ * message not yet knowing whether it's legal to have sent
+ * it - we won't know until the BPP processes the next
+ * packet.
+ *
+ * But that should be OK, because firstly, an
+ * out-of-sequence EXT_INFO that's still within the
+ * encrypted session is only a _protocol_ violation, not
+ * an attack; secondly, any data we set in response to
+ * such an illegal EXT_INFO won't have a chance to affect
+ * the session before the BPP aborts it anyway.
+ */
+ uint32_t nexts = get_uint32(pktin);
+ for (uint32_t i = 0; i < nexts && !get_err(pktin); i++) {
+ ptrlen extname = get_string(pktin);
+ ptrlen extvalue = get_string(pktin);
+ if (ptrlen_eq_string(extname, "server-sig-algs")) {
+ /*
+ * Server has sent a list of signature algorithms
+ * it will potentially accept for user
+ * authentication keys. Check in particular
+ * whether the RFC 8332 improved versions of
+ * ssh-rsa are in the list, and set flags in the
+ * BPP if so.
+ *
+ * TODO: another thing we _could_ do here is to
+ * record a full list of the algorithm identifiers
+ * we've seen, whether we understand them
+ * ourselves or not. Then we could use that as a
+ * pre-filter during userauth, to skip keys in the
+ * SSH agent if we already know the server can't
+ * possibly accept them. (Even if the key
+ * algorithm is one that the agent and the server
+ * both understand but we do not.)
+ */
+ ptrlen algname;
+ while (get_commasep_word(&extvalue, &algname)) {
+ if (ptrlen_eq_string(algname, "rsa-sha2-256"))
+ ppl->bpp->ext_info_rsa_sha256_ok = true;
+ if (ptrlen_eq_string(algname, "rsa-sha2-512"))
+ ppl->bpp->ext_info_rsa_sha512_ok = true;
+ }
+ }
+ }
+ pq_pop(ppl->in_pq);
+ break;
+ }
+
+ default:
+ return false;
+ }
+ }
+
+ return false;
+}
+
+static bool ssh2_transport_filter_queue(struct ssh2_transport_state *s)
+{
+ PktIn *pktin;
+
+ while (1) {
+ if (ssh2_common_filter_queue(&s->ppl))
+ return true;
+ if ((pktin = pq_peek(s->ppl.in_pq)) == NULL)
+ return false;
+
+ /* Pass on packets to the next layer if they're outside
+ * the range reserved for the transport protocol. */
+ if (pktin->type >= 50) {
+ /* ... except that we shouldn't tolerate higher-layer
+ * packets coming from the server before we've seen
+ * the first NEWKEYS. */
+ if (!s->higher_layer_ok) {
+ ssh_proto_error(s->ppl.ssh, "Received premature higher-"
+ "layer packet, type %d (%s)", pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ return true;
+ }
+
+ pq_pop(s->ppl.in_pq);
+ pq_push(&s->pq_in_higher, pktin);
+ } else {
+ /* Anything else is a transport-layer packet that the main
+ * process_queue coroutine should handle. */
+ return false;
+ }
+ }
+}
+
+PktIn *ssh2_transport_pop(struct ssh2_transport_state *s)
+{
+ if (ssh2_transport_filter_queue(s))
+ return NULL; /* we've been freed */
+ return pq_pop(s->ppl.in_pq);
+}
+
+static void ssh2_write_kexinit_lists(
+ BinarySink *pktout,
+ struct kexinit_algorithm_list kexlists[NKEXLIST],
+ Conf *conf, const SshServerConfig *ssc, int remote_bugs,
+ const char *hk_host, int hk_port, const ssh_keyalg *hk_prev,
+ ssh_transient_hostkey_cache *thc, tree234 *host_cas,
+ ssh_key *const *our_hostkeys, int our_nhostkeys,
+ bool first_time, bool can_gssapi_keyex, bool transient_hostkey_mode)
+{
+ int i, j, k;
+ bool warn;
+
+ int n_preferred_kex;
+ const ssh_kexes *preferred_kex[KEX_MAX + 3]; /* +3 for GSSAPI */
+ int n_preferred_hk;
+ int preferred_hk[HK_MAX];
+ int n_preferred_ciphers;
+ const ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
+ const ssh_compression_alg *preferred_comp;
+ const ssh2_macalg *const *maclist;
+ int nmacs;
+
+ struct kexinit_algorithm *alg;
+
+ /*
+ * Set up the preferred key exchange. (NULL => warn below here)
+ */
+ n_preferred_kex = 0;
+ if (can_gssapi_keyex) {
+ preferred_kex[n_preferred_kex++] = &ssh_gssk5_ecdh_kex;
+ preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha2_kex;
+ preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha1_kex;
+ }
+ for (i = 0; i < KEX_MAX; i++) {
+ switch (conf_get_int_int(conf, CONF_ssh_kexlist, i)) {
+ case KEX_DHGEX:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_diffiehellman_gex;
+ break;
+ case KEX_DHGROUP18:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_diffiehellman_group18;
+ break;
+ case KEX_DHGROUP17:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_diffiehellman_group17;
+ break;
+ case KEX_DHGROUP16:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_diffiehellman_group16;
+ break;
+ case KEX_DHGROUP15:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_diffiehellman_group15;
+ break;
+ case KEX_DHGROUP14:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_diffiehellman_group14;
+ break;
+ case KEX_DHGROUP1:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_diffiehellman_group1;
+ break;
+ case KEX_RSA:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_rsa_kex;
+ break;
+ case KEX_ECDH:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_ecdh_kex;
+ break;
+ case KEX_NTRU_HYBRID:
+ preferred_kex[n_preferred_kex++] =
+ &ssh_ntru_hybrid_kex;
+ break;
+ case KEX_WARN:
+ /* Flag for later. Don't bother if it's the last in
+ * the list. */
+ if (i < KEX_MAX - 1) {
+ preferred_kex[n_preferred_kex++] = NULL;
+ }
+ break;
+ }
+ }
+
+ /*
+ * Set up the preferred host key types. These are just the ids
+ * in the enum in putty.h, so 'warn below here' is indicated
+ * by HK_WARN.
+ */
+ n_preferred_hk = 0;
+ for (i = 0; i < HK_MAX; i++) {
+ int id = conf_get_int_int(conf, CONF_ssh_hklist, i);
+ /* As above, don't bother with HK_WARN if it's last in the
+ * list */
+ if (id != HK_WARN || i < HK_MAX - 1)
+ preferred_hk[n_preferred_hk++] = id;
+ }
+
+ /*
+ * Set up the preferred ciphers. (NULL => warn below here)
+ */
+ n_preferred_ciphers = 0;
+ for (i = 0; i < CIPHER_MAX; i++) {
+ switch (conf_get_int_int(conf, CONF_ssh_cipherlist, i)) {
+ case CIPHER_BLOWFISH:
+ preferred_ciphers[n_preferred_ciphers++] = &ssh2_blowfish;
+ break;
+ case CIPHER_DES:
+ if (conf_get_bool(conf, CONF_ssh2_des_cbc))
+ preferred_ciphers[n_preferred_ciphers++] = &ssh2_des;
+ break;
+ case CIPHER_3DES:
+ preferred_ciphers[n_preferred_ciphers++] = &ssh2_3des;
+ break;
+ case CIPHER_AES:
+ preferred_ciphers[n_preferred_ciphers++] = &ssh2_aes;
+ break;
+ case CIPHER_ARCFOUR:
+ preferred_ciphers[n_preferred_ciphers++] = &ssh2_arcfour;
+ break;
+ case CIPHER_CHACHA20:
+ preferred_ciphers[n_preferred_ciphers++] = &ssh2_ccp;
+ break;
+ case CIPHER_AESGCM:
+ preferred_ciphers[n_preferred_ciphers++] = &ssh2_aesgcm;
+ break;
+ case CIPHER_WARN:
+ /* Flag for later. Don't bother if it's the last in
+ * the list. */
+ if (i < CIPHER_MAX - 1) {
+ preferred_ciphers[n_preferred_ciphers++] = NULL;
+ }
+ break;
+ }
+ }
+
+ /*
+ * Set up preferred compression.
+ */
+ if (conf_get_bool(conf, CONF_compression))
+ preferred_comp = &ssh_zlib;
+ else
+ preferred_comp = &ssh_comp_none;
+
+ for (i = 0; i < NKEXLIST; i++)
+ kexlists[i].nalgs = 0;
+ /* List key exchange algorithms. */
+ warn = false;
+ for (i = 0; i < n_preferred_kex; i++) {
+ const ssh_kexes *k = preferred_kex[i];
+ if (!k) warn = true;
+ else for (j = 0; j < k->nkexes; j++) {
+ alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_KEX],
+ k->list[j]->name);
+ alg->u.kex.kex = k->list[j];
+ alg->u.kex.warn = warn;
+ }
+ }
+ /* List server host key algorithms. */
+ if (our_hostkeys) {
+ /*
+ * In server mode, we just list the algorithms that match the
+ * host keys we actually have.
+ */
+ for (i = 0; i < our_nhostkeys; i++) {
+ const ssh_keyalg *keyalg = ssh_key_alg(our_hostkeys[i]);
+
+ alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY],
+ keyalg->ssh_id);
+ alg->u.hk.hostkey = keyalg;
+ alg->u.hk.hkflags = 0;
+ alg->u.hk.warn = false;
+
+ uint32_t supported_flags = ssh_keyalg_supported_flags(keyalg);
+ static const uint32_t try_flags[] = {
+ SSH_AGENT_RSA_SHA2_256,
+ SSH_AGENT_RSA_SHA2_512,
+ };
+ for (size_t i = 0; i < lenof(try_flags); i++) {
+ if (try_flags[i] & ~supported_flags)
+ continue; /* these flags not supported */
+
+ alg = ssh2_kexinit_addalg(
+ &kexlists[KEXLIST_HOSTKEY],
+ ssh_keyalg_alternate_ssh_id(keyalg, try_flags[i]));
+
+ alg->u.hk.hostkey = keyalg;
+ alg->u.hk.hkflags = try_flags[i];
+ alg->u.hk.warn = false;
+ }
+ }
+ } else if (first_time) {
+ /*
+ * In the first key exchange, we list all the algorithms we're
+ * prepared to cope with, but (if configured to) we prefer
+ * those algorithms for which we have a host key for this
+ * host.
+ *
+ * If the host key algorithm is below the warning
+ * threshold, we warn even if we did already have a key
+ * for it, on the basis that if the user has just
+ * reconfigured that host key type to be warned about,
+ * they surely _do_ want to be alerted that a server
+ * they're actually connecting to is using it.
+ */
+
+ bool accept_certs = false;
+ {
+ host_ca_enum *handle = enum_host_ca_start();
+ if (handle) {
+ strbuf *name = strbuf_new();
+ while (strbuf_clear(name), enum_host_ca_next(handle, name)) {
+ host_ca *hca = host_ca_load(name->s);
+ if (!hca)
+ continue;
+
+ if (hca->ca_public_key &&
+ cert_expr_match_str(hca->validity_expression,
+ hk_host, hk_port)) {
+ accept_certs = true;
+ add234(host_cas, hca);
+ } else {
+ host_ca_free(hca);
+ }
+ }
+ enum_host_ca_finish(handle);
+ strbuf_free(name);
+ }
+ }
+
+ if (accept_certs) {
+ /* Add all the certificate algorithms first, in preference order */
+ warn = false;
+ for (i = 0; i < n_preferred_hk; i++) {
+ if (preferred_hk[i] == HK_WARN)
+ warn = true;
+ for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
+ const struct ssh_signkey_with_user_pref_id *a =
+ &ssh2_hostkey_algs[j];
+ if (!a->alg->is_certificate)
+ continue;
+ if (a->id != preferred_hk[i])
+ continue;
+ alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY],
+ a->alg->ssh_id);
+ alg->u.hk.hostkey = a->alg;
+ alg->u.hk.warn = warn;
+ }
+ }
+ }
+
+ /* Next, add algorithms we already know a key for (unless
+ * configured not to do that) */
+ warn = false;
+ for (i = 0; i < n_preferred_hk; i++) {
+ if (preferred_hk[i] == HK_WARN)
+ warn = true;
+ for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
+ const struct ssh_signkey_with_user_pref_id *a =
+ &ssh2_hostkey_algs[j];
+ if (a->alg->is_certificate && accept_certs)
+ continue; /* already added this one */
+ if (a->id != preferred_hk[i])
+ continue;
+ if (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) &&
+ have_ssh_host_key(hk_host, hk_port,
+ a->alg->cache_id)) {
+ alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY],
+ a->alg->ssh_id);
+ alg->u.hk.hostkey = a->alg;
+ alg->u.hk.warn = warn;
+ }
+ }
+ }
+
+ /* And finally, everything else */
+ warn = false;
+ for (i = 0; i < n_preferred_hk; i++) {
+ if (preferred_hk[i] == HK_WARN)
+ warn = true;
+ for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
+ const struct ssh_signkey_with_user_pref_id *a =
+ &ssh2_hostkey_algs[j];
+ if (a->alg->is_certificate)
+ continue;
+ if (a->id != preferred_hk[i])
+ continue;
+ alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY],
+ a->alg->ssh_id);
+ alg->u.hk.hostkey = a->alg;
+ alg->u.hk.warn = warn;
+ }
+ }
+#ifndef NO_GSSAPI
+ } else if (transient_hostkey_mode) {
+ /*
+ * If we've previously done a GSSAPI KEX, then we list
+ * precisely the algorithms for which a previous GSS key
+ * exchange has delivered us a host key, because we expect
+ * one of exactly those keys to be used in any subsequent
+ * non-GSS-based rekey.
+ *
+ * An exception is if this is the key exchange we
+ * triggered for the purposes of populating that cache -
+ * in which case the cache will currently be empty, which
+ * isn't helpful!
+ */
+ warn = false;
+ for (i = 0; i < n_preferred_hk; i++) {
+ if (preferred_hk[i] == HK_WARN)
+ warn = true;
+ for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
+ if (ssh2_hostkey_algs[j].id != preferred_hk[i])
+ continue;
+ if (ssh_transient_hostkey_cache_has(
+ thc, ssh2_hostkey_algs[j].alg)) {
+ alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY],
+ ssh2_hostkey_algs[j].alg->ssh_id);
+ alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg;
+ alg->u.hk.warn = warn;
+ }
+ }
+ }
+#endif
+ } else {
+ /*
+ * In subsequent key exchanges, we list only the host key
+ * algorithm that was selected in the first key exchange,
+ * so that we keep getting the same host key and hence
+ * don't have to interrupt the user's session to ask for
+ * reverification.
+ */
+ assert(hk_prev);
+ alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], hk_prev->ssh_id);
+ alg->u.hk.hostkey = hk_prev;
+ alg->u.hk.warn = false;
+ }
+ if (can_gssapi_keyex) {
+ alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], "null");
+ alg->u.hk.hostkey = NULL;
+ }
+ /* List encryption algorithms (client->server then server->client). */
+ for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) {
+ warn = false;
+#ifdef FUZZING
+ alg = ssh2_kexinit_addalg(&kexlists[K], "none");
+ alg->u.cipher.cipher = NULL;
+ alg->u.cipher.warn = warn;
+#endif /* FUZZING */
+ for (i = 0; i < n_preferred_ciphers; i++) {
+ const ssh2_ciphers *c = preferred_ciphers[i];
+ if (!c) warn = true;
+ else for (j = 0; j < c->nciphers; j++) {
+ alg = ssh2_kexinit_addalg(&kexlists[k],
+ c->list[j]->ssh2_id);
+ alg->u.cipher.cipher = c->list[j];
+ alg->u.cipher.warn = warn;
+ }
+ }
+ }
+
+ /*
+ * Be prepared to work around the buggy MAC problem.
+ */
+ if (remote_bugs & BUG_SSH2_HMAC) {
+ maclist = buggymacs;
+ nmacs = lenof(buggymacs);
+ } else {
+ maclist = macs;
+ nmacs = lenof(macs);
+ }
+
+ /* List MAC algorithms (client->server then server->client). */
+ for (j = KEXLIST_CSMAC; j <= KEXLIST_SCMAC; j++) {
+#ifdef FUZZING
+ alg = ssh2_kexinit_addalg(kexlists[j], "none");
+ alg->u.mac.mac = NULL;
+ alg->u.mac.etm = false;
+#endif /* FUZZING */
+ for (i = 0; i < nmacs; i++) {
+ alg = ssh2_kexinit_addalg(&kexlists[j], maclist[i]->name);
+ alg->u.mac.mac = maclist[i];
+ alg->u.mac.etm = false;
+ }
+ for (i = 0; i < nmacs; i++) {
+ /* For each MAC, there may also be an ETM version,
+ * which we list second. */
+ if (maclist[i]->etm_name) {
+ alg = ssh2_kexinit_addalg(&kexlists[j], maclist[i]->etm_name);
+ alg->u.mac.mac = maclist[i];
+ alg->u.mac.etm = true;
+ }
+ }
+ }
+
+ /* List client->server compression algorithms,
+ * then server->client compression algorithms. (We use the
+ * same set twice.) */
+ for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) {
+ assert(lenof(compressions) > 1);
+ /* Prefer non-delayed versions */
+ alg = ssh2_kexinit_addalg(&kexlists[j], preferred_comp->name);
+ alg->u.comp.comp = preferred_comp;
+ alg->u.comp.delayed = false;
+ if (preferred_comp->delayed_name) {
+ alg = ssh2_kexinit_addalg(&kexlists[j],
+ preferred_comp->delayed_name);
+ alg->u.comp.comp = preferred_comp;
+ alg->u.comp.delayed = true;
+ }
+ for (i = 0; i < lenof(compressions); i++) {
+ const ssh_compression_alg *c = compressions[i];
+ alg = ssh2_kexinit_addalg(&kexlists[j], c->name);
+ alg->u.comp.comp = c;
+ alg->u.comp.delayed = false;
+ if (c->delayed_name) {
+ alg = ssh2_kexinit_addalg(&kexlists[j], c->delayed_name);
+ alg->u.comp.comp = c;
+ alg->u.comp.delayed = true;
+ }
+ }
+ }
+
+ /*
+ * Finally, format the lists into text and write them into the
+ * outgoing KEXINIT packet.
+ */
+ for (i = 0; i < NKEXLIST; i++) {
+ strbuf *list = strbuf_new();
+ if (ssc && ssc->kex_override[i].ptr) {
+ put_datapl(list, ssc->kex_override[i]);
+ } else {
+ for (j = 0; j < kexlists[i].nalgs; j++)
+ add_to_commasep_pl(list, kexlists[i].algs[j].name);
+ }
+ if (i == KEXLIST_KEX && first_time) {
+ if (our_hostkeys) /* we're the server */
+ add_to_commasep(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, "");
+}
+
+struct server_hostkeys {
+ int *indices;
+ size_t n, size;
+};
+
+static bool ssh2_scan_kexinits(
+ ptrlen client_kexinit, ptrlen server_kexinit,
+ struct kexinit_algorithm_list kexlists[NKEXLIST],
+ const ssh_kex **kex_alg, const ssh_keyalg **hostkey_alg,
+ transport_direction *cs, transport_direction *sc,
+ bool *warn_kex, bool *warn_hk, bool *warn_cscipher, bool *warn_sccipher,
+ Ssh *ssh, bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet,
+ struct server_hostkeys *server_hostkeys, 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 < kexlists[i].nalgs; j++) {
+ if (ptrlen_eq_ptrlen(found, kexlists[i].algs[j].name)) {
+ selected[i] = &kexlists[i].algs[j];
+ break;
+ }
+ }
+ if (!selected[i]) {
+ /*
+ * In the client, this should never happen! But in the
+ * server, where we allow manual override on the command
+ * line of the exact KEXINIT strings, it can happen
+ * because the command line contained a typo. So we
+ * produce a reasonably useful message instead of an
+ * assertion failure.
+ */
+ 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.
+ */
+ ptrlen list = slists[KEXLIST_HOSTKEY];
+ for (ptrlen word; get_commasep_word(&list, &word) ;) {
+ for (i = 0; i < lenof(ssh2_hostkey_algs); i++)
+ if (ptrlen_eq_string(word, ssh2_hostkey_algs[i].alg->ssh_id)) {
+ sgrowarray(server_hostkeys->indices, server_hostkeys->size,
+ server_hostkeys->n);
+ server_hostkeys->indices[server_hostkeys->n++] = i;
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+static inline bool delay_outgoing_kexinit(struct ssh2_transport_state *s)
+{
+ if (!(s->ppl.remote_bugs & BUG_REQUIRES_FILTERED_KEXINIT))
+ return false; /* bug flag not enabled => no need to delay */
+ if (s->incoming_kexinit->len)
+ return false; /* already got a remote KEXINIT we can filter against */
+ return true;
+}
+
+static void filter_outgoing_kexinit(struct ssh2_transport_state *s)
+{
+ strbuf *pktout = strbuf_new();
+ BinarySource osrc[1], isrc[1];
+ BinarySource_BARE_INIT(
+ osrc, s->outgoing_kexinit->u, s->outgoing_kexinit->len);
+ BinarySource_BARE_INIT(
+ isrc, s->incoming_kexinit->u, s->incoming_kexinit->len);
+
+ /* Skip the packet type bytes from both packets */
+ get_byte(osrc);
+ get_byte(isrc);
+
+ /* Copy our cookie into the real output packet; skip their cookie */
+ put_datapl(pktout, get_data(osrc, 16));
+ get_data(isrc, 16);
+
+ /*
+ * Now we expect NKEXLIST+2 name-lists. We write into the outgoing
+ * packet a subset of our intended outgoing one, containing only
+ * names mentioned in the incoming out.
+ *
+ * NKEXLIST+2 because for this purpose we treat the 'languages'
+ * lists the same as the rest. In the rest of this code base we
+ * ignore those.
+ */
+ strbuf *out = strbuf_new();
+ for (size_t i = 0; i < NKEXLIST+2; i++) {
+ strbuf_clear(out);
+ ptrlen olist = get_string(osrc), ilist = get_string(isrc);
+ for (ptrlen oword; get_commasep_word(&olist, &oword) ;) {
+ ptrlen ilist_copy = ilist;
+ bool add = false;
+ for (ptrlen iword; get_commasep_word(&ilist_copy, &iword) ;) {
+ if (ptrlen_eq_ptrlen(oword, iword)) {
+ /* Found this word in the incoming list. */
+ add = true;
+ break;
+ }
+ }
+
+ if (i == KEXLIST_KEX && ptrlen_eq_string(oword, "ext-info-c")) {
+ /* Special case: this will _never_ match anything from the
+ * server, and we need it to enable SHA-2 based RSA.
+ *
+ * If this ever turns out to confuse any server all by
+ * itself then I suppose we'll need an even more
+ * draconian bug flag to exclude that too. (Obv, such
+ * a server wouldn't be able to speak SHA-2 RSA
+ * anyway.) */
+ add = true;
+ }
+
+ if (add)
+ add_to_commasep_pl(out, oword);
+ }
+ put_stringpl(pktout, ptrlen_from_strbuf(out));
+ }
+ strbuf_free(out);
+
+ /*
+ * Finally, copy the remaining parts of our intended KEXINIT.
+ */
+ put_bool(pktout, get_bool(osrc)); /* first-kex-packet-follows */
+ put_uint32(pktout, get_uint32(osrc)); /* reserved word */
+
+ /*
+ * Dump this data into s->outgoing_kexinit in place of what we had
+ * there before. We need to remember the KEXINIT we _really_ sent,
+ * not the one we'd have liked to send, since the host key
+ * signature will be validated against the former.
+ */
+ strbuf_shrink_to(s->outgoing_kexinit, 1); /* keep the type byte */
+ put_datapl(s->outgoing_kexinit, ptrlen_from_strbuf(pktout));
+ strbuf_free(pktout);
+}
+
+void ssh2transport_finalise_exhash(struct ssh2_transport_state *s)
+{
+ put_datapl(s->exhash, ptrlen_from_strbuf(s->kex_shared_secret));
+ assert(ssh_hash_alg(s->exhash)->hlen <= sizeof(s->exchange_hash));
+ ssh_hash_final(s->exhash, s->exchange_hash);
+ s->exhash = NULL;
+
+#if 0
+ debug("Exchange hash is:\n");
+ dmemdump(s->exchange_hash, s->kex_alg->hash->hlen);
+#endif
+}
+
+static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
+{
+ struct ssh2_transport_state *s =
+ container_of(ppl, struct ssh2_transport_state, ppl);
+ PktIn *pktin;
+ PktOut *pktout;
+
+ /* Filter centrally handled messages off the front of the queue on
+ * every entry to this coroutine, no matter where we're resuming
+ * from, even if we're _not_ looping on pq_pop. That way we can
+ * still proactively handle those messages even if we're waiting
+ * for a user response. */
+ if (ssh2_transport_filter_queue(s))
+ return; /* we've been freed */
+
+ crBegin(s->crState);
+
+ s->in.cipher = s->out.cipher = NULL;
+ s->in.mac = s->out.mac = NULL;
+ s->in.comp = s->out.comp = NULL;
+
+ s->got_session_id = false;
+ s->need_gss_transient_hostkey = false;
+ s->warned_about_no_gss_transient_hostkey = false;
+
+ begin_key_exchange:
+
+#ifndef NO_GSSAPI
+ if (s->need_gss_transient_hostkey) {
+ /*
+ * This flag indicates a special case in which we must not do
+ * GSS key exchange even if we could. (See comments below,
+ * where the flag was set on the previous key exchange.)
+ */
+ s->can_gssapi_keyex = false;
+ } else if (conf_get_bool(s->conf, CONF_try_gssapi_kex)) {
+ /*
+ * We always check if we have GSS creds before we come up with
+ * the kex algorithm list, otherwise future rekeys will fail
+ * when creds expire. To make this so, this code section must
+ * follow the begin_key_exchange label above, otherwise this
+ * section would execute just once per-connection.
+ *
+ * Update GSS state unless the reason we're here is that a
+ * timer just checked the GSS state and decided that we should
+ * rekey to update delegated credentials. In that case, the
+ * state is "fresh".
+ */
+ if (s->rekey_class != RK_GSS_UPDATE)
+ ssh2_transport_gss_update(s, true);
+
+ /* Do GSSAPI KEX when capable */
+ s->can_gssapi_keyex = s->gss_status & GSS_KEX_CAPABLE;
+
+ /*
+ * But not when failure is likely. [ GSS implementations may
+ * attempt (and fail) to use a ticket that is almost expired
+ * when retrieved from the ccache that actually expires by the
+ * time the server receives it. ]
+ *
+ * Note: The first time always try KEXGSS if we can, failures
+ * will be very rare, and disabling the initial GSS KEX is
+ * worse. Some day GSS libraries will ignore cached tickets
+ * whose lifetime is critically short, and will instead use
+ * fresh ones.
+ */
+ if (!s->got_session_id && (s->gss_status & GSS_CTXT_MAYFAIL) != 0)
+ s->can_gssapi_keyex = false;
+ s->gss_delegate = conf_get_bool(s->conf, CONF_gssapifwd);
+ } else {
+ s->can_gssapi_keyex = false;
+ }
+#endif
+
+ s->ppl.bpp->pls->kctx = SSH2_PKTCTX_NOKEX;
+
+ /*
+ * Construct our KEXINIT packet, in a strbuf so we can refer to it
+ * later.
+ */
+ strbuf_clear(s->outgoing_kexinit);
+ put_byte(s->outgoing_kexinit, SSH2_MSG_KEXINIT);
+ random_read(strbuf_append(s->outgoing_kexinit, 16), 16);
+ ssh2_write_kexinit_lists(
+ BinarySink_UPCAST(s->outgoing_kexinit), s->kexlists,
+ s->conf, s->ssc, s->ppl.remote_bugs,
+ s->savedhost, s->savedport, s->hostkey_alg, s->thc, s->host_cas,
+ s->hostkeys, s->nhostkeys,
+ !s->got_session_id, s->can_gssapi_keyex,
+ s->gss_kex_used && !s->need_gss_transient_hostkey);
+ /* First KEX packet does _not_ follow, because we're not that brave. */
+ put_bool(s->outgoing_kexinit, false);
+ put_uint32(s->outgoing_kexinit, 0); /* reserved */
+
+ /*
+ * Send our KEXINIT, most of the time.
+ *
+ * An exception: in BUG_REQUIRES_FILTERED_KEXINIT mode, we have to
+ * have seen at least one KEXINIT from the server first, so that
+ * we can filter our own KEXINIT down to contain only algorithms
+ * the server mentioned.
+ *
+ * But we only need to do this on the _first_ key exchange, when
+ * we've never seen a KEXINIT from the server before. In rekeys,
+ * we still have the server's previous KEXINIT lying around, so we
+ * can filter based on that.
+ *
+ * (And a good thing too, since the way you _initiate_ a rekey is
+ * by sending your KEXINIT, so we'd have no way to prod the server
+ * into sending its first!)
+ */
+ s->kexinit_delayed = delay_outgoing_kexinit(s);
+ if (!s->kexinit_delayed) {
+ if (s->ppl.remote_bugs & BUG_REQUIRES_FILTERED_KEXINIT) {
+ /* Filter based on the KEXINIT from the previous exchange */
+ filter_outgoing_kexinit(s);
+ }
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT);
+ put_data(pktout, s->outgoing_kexinit->u + 1,
+ s->outgoing_kexinit->len - 1); /* omit type byte */
+ pq_push(s->ppl.out_pq, pktout);
+ }
+
+ /*
+ * Flag that KEX is in progress.
+ */
+ s->kex_in_progress = true;
+
+ /*
+ * Wait for the other side's KEXINIT, and save it.
+ */
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_KEXINIT) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+ "expecting KEXINIT, type %d (%s)", pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx, pktin->type));
+ return;
+ }
+ strbuf_clear(s->incoming_kexinit);
+ put_byte(s->incoming_kexinit, SSH2_MSG_KEXINIT);
+ put_data(s->incoming_kexinit, get_ptr(pktin), get_avail(pktin));
+
+ /*
+ * If we've delayed sending our KEXINIT so as to filter it down to
+ * only things the server won't choke on, send ours now.
+ */
+ if (s->kexinit_delayed) {
+ filter_outgoing_kexinit(s);
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT);
+ put_data(pktout, s->outgoing_kexinit->u + 1,
+ s->outgoing_kexinit->len - 1); /* omit type byte */
+ pq_push(s->ppl.out_pq, pktout);
+ }
+
+ /*
+ * Work through the two KEXINIT packets in parallel to find the
+ * selected algorithm identifiers.
+ */
+ {
+ struct server_hostkeys hks = { NULL, 0, 0 };
+
+ 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, &hks,
+ &s->hkflags, &s->can_send_ext_info)) {
+ sfree(hks.indices);
+ 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 (int i = 0; i < hks.n; i++) {
+ int j = hks.indices[i];
+ if (ssh2_hostkey_algs[j].alg != s->hostkey_alg &&
+ ssh2_hostkey_algs[j].alg->cache_id &&
+ !have_ssh_host_key(s->savedhost, s->savedport,
+ ssh2_hostkey_algs[j].alg->cache_id)) {
+ s->uncert_hostkeys[s->n_uncert_hostkeys++] = j;
+ }
+ }
+
+ sfree(hks.indices);
+ }
+
+ if (s->warn_kex) {
+ s->spr = ssh2_transport_confirm_weak_crypto_primitive(
+ s, "key-exchange algorithm", s->kex_alg->name, s->kex_alg);
+ crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+ if (spr_is_abort(s->spr)) {
+ ssh_spr_close(s->ppl.ssh, s->spr, "kex warning");
+ return;
+ }
+ }
+
+ if (s->warn_hk) {
+ int j, k;
+ 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->spr = seat_confirm_weak_cached_hostkey(
+ ppl_get_iseat(&s->ppl), s->hostkey_alg->ssh_id, betteralgs,
+ ssh2_transport_dialog_callback, s);
+ sfree(betteralgs);
+ } else {
+ /* If none exist, use the more general 'weak crypto'
+ * warning prompt */
+ s->spr = ssh2_transport_confirm_weak_crypto_primitive(
+ s, "host key type", s->hostkey_alg->ssh_id,
+ s->hostkey_alg);
+ }
+ crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+ if (spr_is_abort(s->spr)) {
+ ssh_spr_close(s->ppl.ssh, s->spr, "host key warning");
+ return;
+ }
+ }
+
+ if (s->warn_cscipher) {
+ s->spr = ssh2_transport_confirm_weak_crypto_primitive(
+ s, "client-to-server cipher", s->out.cipher->ssh2_id,
+ s->out.cipher);
+ crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+ if (spr_is_abort(s->spr)) {
+ ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning");
+ return;
+ }
+ }
+
+ if (s->warn_sccipher) {
+ s->spr = ssh2_transport_confirm_weak_crypto_primitive(
+ s, "server-to-client cipher", s->in.cipher->ssh2_id,
+ s->in.cipher);
+ crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+ if (spr_is_abort(s->spr)) {
+ ssh_spr_close(s->ppl.ssh, s->spr, "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);
+ if (s->kex_shared_secret)
+ strbuf_free(s->kex_shared_secret);
+ s->kex_shared_secret = strbuf_new_nm();
+ put_stringz(s->exhash, s->client_greeting);
+ put_stringz(s->exhash, s->server_greeting);
+ put_string(s->exhash, s->client_kexinit->u, s->client_kexinit->len);
+ put_string(s->exhash, s->server_kexinit->u, s->server_kexinit->len);
+ s->crStateKex = 0;
+ while (1) {
+ bool aborted = false;
+ ssh2kex_coroutine(s, &aborted);
+ if (aborted)
+ return; /* disaster: our entire state has been freed */
+ if (!s->crStateKex)
+ break; /* kex phase has terminated normally */
+ crReturnV;
+ }
+
+ /*
+ * The exchange hash from the very first key exchange is also
+ * the session id, used in session key construction and
+ * authentication.
+ */
+ if (!s->got_session_id) {
+ assert(sizeof(s->exchange_hash) <= sizeof(s->session_id));
+ memcpy(s->session_id, s->exchange_hash, sizeof(s->exchange_hash));
+ s->session_id_len = s->kex_alg->hash->hlen;
+ assert(s->session_id_len <= sizeof(s->session_id));
+ s->got_session_id = true;
+ }
+
+ /*
+ * Send SSH2_MSG_NEWKEYS.
+ */
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_NEWKEYS);
+ pq_push(s->ppl.out_pq, pktout);
+ /* Start counting down the outgoing-data limit for these cipher keys. */
+ dts_reset(&s->stats->out, s->max_data_size);
+
+ /*
+ * Force the BPP to synchronously marshal all packets up to and
+ * including that NEWKEYS into wire format, before we switch over
+ * to new crypto.
+ */
+ ssh_bpp_handle_output(s->ppl.bpp);
+
+ /*
+ * We've sent outgoing NEWKEYS, so create and initialise outgoing
+ * session keys.
+ */
+ {
+ strbuf *cipher_key = strbuf_new_nm();
+ strbuf *cipher_iv = strbuf_new_nm();
+ strbuf *mac_key = strbuf_new_nm();
+
+ if (s->out.cipher) {
+ ssh2_mkkey(s, cipher_iv, s->kex_shared_secret, s->exchange_hash,
+ 'A' + s->out.mkkey_adjust, s->out.cipher->blksize);
+ ssh2_mkkey(s, cipher_key, s->kex_shared_secret, s->exchange_hash,
+ 'C' + s->out.mkkey_adjust,
+ s->out.cipher->padded_keybytes);
+ }
+ if (s->out.mac) {
+ ssh2_mkkey(s, mac_key, s->kex_shared_secret, s->exchange_hash,
+ 'E' + s->out.mkkey_adjust, s->out.mac->keylen);
+ }
+
+ ssh2_bpp_new_outgoing_crypto(
+ s->ppl.bpp,
+ s->out.cipher, cipher_key->u, cipher_iv->u,
+ s->out.mac, s->out.etm_mode, mac_key->u,
+ s->out.comp, s->out.comp_delayed);
+
+ strbuf_free(cipher_key);
+ strbuf_free(cipher_iv);
+ strbuf_free(mac_key);
+ }
+
+ /*
+ * If that was our first key exchange, this is the moment to send
+ * our EXT_INFO, if we're sending one.
+ */
+ if (!s->post_newkeys_ext_info) {
+ s->post_newkeys_ext_info = true; /* never do this again */
+ if (s->can_send_ext_info) {
+ strbuf *extinfo = strbuf_new();
+ uint32_t n_exts = 0;
+
+ if (s->ssc) {
+ /* Server->client EXT_INFO lists our supported user
+ * key algorithms. */
+ n_exts++;
+ put_stringz(extinfo, "server-sig-algs");
+ strbuf *list = strbuf_new();
+ for (size_t i = 0; i < n_keyalgs; i++)
+ add_to_commasep(list, all_keyalgs[i]->ssh_id);
+ put_stringsb(extinfo, list);
+ } else {
+ /* Client->server EXT_INFO is currently not sent, but here's
+ * where we should put things in it if we ever want to. */
+ }
+
+ /* Only send EXT_INFO if it's non-empty */
+ if (n_exts) {
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_EXT_INFO);
+ put_uint32(pktout, n_exts);
+ put_datapl(pktout, ptrlen_from_strbuf(extinfo));
+ pq_push(s->ppl.out_pq, pktout);
+ }
+
+ strbuf_free(extinfo);
+ }
+ }
+
+ /*
+ * Now our end of the key exchange is complete, we can send all
+ * our queued higher-layer packets. Transfer the whole of the next
+ * layer's outgoing queue on to our own.
+ */
+ pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher);
+ ssh_sendbuffer_changed(s->ppl.ssh);
+
+ /*
+ * Expect SSH2_MSG_NEWKEYS from server.
+ */
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_NEWKEYS) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+ "expecting SSH_MSG_NEWKEYS, type %d (%s)",
+ pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ return;
+ }
+ /* Start counting down the incoming-data limit for these cipher keys. */
+ dts_reset(&s->stats->in, s->max_data_size);
+
+ /*
+ * We've seen incoming NEWKEYS, so create and initialise
+ * incoming session keys.
+ */
+ {
+ strbuf *cipher_key = strbuf_new_nm();
+ strbuf *cipher_iv = strbuf_new_nm();
+ strbuf *mac_key = strbuf_new_nm();
+
+ if (s->in.cipher) {
+ ssh2_mkkey(s, cipher_iv, s->kex_shared_secret, s->exchange_hash,
+ 'A' + s->in.mkkey_adjust, s->in.cipher->blksize);
+ ssh2_mkkey(s, cipher_key, s->kex_shared_secret, s->exchange_hash,
+ 'C' + s->in.mkkey_adjust,
+ s->in.cipher->padded_keybytes);
+ }
+ if (s->in.mac) {
+ ssh2_mkkey(s, mac_key, s->kex_shared_secret, s->exchange_hash,
+ 'E' + s->in.mkkey_adjust, s->in.mac->keylen);
+ }
+
+ ssh2_bpp_new_incoming_crypto(
+ s->ppl.bpp,
+ s->in.cipher, cipher_key->u, cipher_iv->u,
+ s->in.mac, s->in.etm_mode, mac_key->u,
+ s->in.comp, s->in.comp_delayed);
+
+ strbuf_free(cipher_key);
+ strbuf_free(cipher_iv);
+ strbuf_free(mac_key);
+ }
+
+ /*
+ * Free shared secret.
+ */
+ strbuf_free(s->kex_shared_secret);
+ s->kex_shared_secret = NULL;
+
+ /*
+ * Update the specials menu to list the remaining uncertified host
+ * keys.
+ */
+ seat_update_specials_menu(s->ppl.seat);
+
+ /*
+ * Key exchange is over. Loop straight back round if we have a
+ * deferred rekey reason.
+ */
+ if (s->deferred_rekey_reason) {
+ ppl_logevent("%s", s->deferred_rekey_reason);
+ pktin = NULL;
+ s->deferred_rekey_reason = NULL;
+ goto begin_key_exchange;
+ }
+
+ /*
+ * Otherwise, schedule a timer for our next rekey.
+ */
+ s->kex_in_progress = false;
+ s->last_rekey = GETTICKCOUNT();
+ (void) ssh2_transport_timer_update(s, 0);
+
+ /*
+ * Now we're encrypting. Get the next-layer protocol started if it
+ * hasn't already, and then sit here waiting for reasons to go
+ * back to the start and do a repeat key exchange. One of those
+ * reasons is that we receive KEXINIT from the other end; the
+ * other is if we find rekey_reason is non-NULL, i.e. we've
+ * decided to initiate a rekey ourselves for some reason.
+ */
+ if (!s->higher_layer_ok) {
+ if (!s->hostkeys) {
+ /* We're the client, so send SERVICE_REQUEST. */
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_REQUEST);
+ put_stringz(pktout, s->higher_layer->vt->name);
+ pq_push(s->ppl.out_pq, pktout);
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_SERVICE_ACCEPT) {
+ ssh_sw_abort(s->ppl.ssh, "Server refused request to start "
+ "'%s' protocol", s->higher_layer->vt->name);
+ return;
+ }
+ } else {
+ ptrlen service_name;
+
+ /* We're the server, so expect SERVICE_REQUEST. */
+ crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_SERVICE_REQUEST) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
+ "expecting SERVICE_REQUEST, type %d (%s)",
+ pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ return;
+ }
+ service_name = get_string(pktin);
+ if (!ptrlen_eq_string(service_name, s->higher_layer->vt->name)) {
+ ssh_proto_error(s->ppl.ssh, "Client requested service "
+ "'%.*s' when we only support '%s'",
+ PTRLEN_PRINTF(service_name),
+ s->higher_layer->vt->name);
+ return;
+ }
+
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_ACCEPT);
+ put_stringz(pktout, s->higher_layer->vt->name);
+ pq_push(s->ppl.out_pq, pktout);
+ }
+
+ s->higher_layer_ok = true;
+ queue_idempotent_callback(&s->higher_layer->ic_process_queue);
+ }
+
+ s->rekey_class = RK_NONE;
+ do {
+ crReturnV;
+
+ /* Pass through outgoing packets from the higher layer. */
+ pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher);
+ ssh_sendbuffer_changed(s->ppl.ssh);
+
+ /* Wait for either a KEXINIT, or something setting
+ * s->rekey_class. This call to ssh2_transport_pop also has
+ * the side effect of transferring incoming packets _to_ the
+ * higher layer (via filter_queue). */
+ if ((pktin = ssh2_transport_pop(s)) != NULL) {
+ if (pktin->type != SSH2_MSG_KEXINIT) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected transport-"
+ "layer packet outside a key exchange, "
+ "type %d (%s)", pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ return;
+ }
+ pq_push_front(s->ppl.in_pq, pktin);
+ ppl_logevent("Remote side initiated key re-exchange");
+ s->rekey_class = RK_SERVER;
+ }
+
+ if (s->rekey_class == RK_POST_USERAUTH) {
+ /*
+ * userauth has seen a USERAUTH_SUCCESS. This may be the
+ * moment to do an immediate rekey with different
+ * parameters. But it may not; so here we turn that rekey
+ * class into either RK_NONE or RK_NORMAL.
+ *
+ * Currently the only reason for this is if we've done a
+ * GSS key exchange and don't have anything in our
+ * transient hostkey cache, in which case we should make
+ * an attempt to populate the cache now.
+ */
+ if (s->need_gss_transient_hostkey) {
+ s->rekey_reason = "populating transient host key cache";
+ s->rekey_class = RK_NORMAL;
+ } else {
+ /* No need to rekey at this time. */
+ s->rekey_class = RK_NONE;
+ }
+ }
+
+ if (!s->rekey_class) {
+ /* If we don't yet have any other reason to rekey, check
+ * if we've hit our data limit in either direction. */
+ if (s->stats->in.expired) {
+ s->rekey_reason = "too much data received";
+ s->rekey_class = RK_NORMAL;
+ } else if (s->stats->out.expired) {
+ s->rekey_reason = "too much data sent";
+ s->rekey_class = RK_NORMAL;
+ }
+ }
+
+ if (s->rekey_class != RK_NONE && s->rekey_class != RK_SERVER) {
+ /*
+ * Special case: if the server bug is set that doesn't
+ * allow rekeying, we give a different log message and
+ * continue waiting. (If such a server _initiates_ a
+ * rekey, we process it anyway!)
+ */
+ if ((s->ppl.remote_bugs & BUG_SSH2_REKEY)) {
+ ppl_logevent("Remote bug prevents key re-exchange (%s)",
+ s->rekey_reason);
+ /* Reset the counters, so that at least this message doesn't
+ * hit the event log _too_ often. */
+ dts_reset(&s->stats->in, s->max_data_size);
+ dts_reset(&s->stats->out, s->max_data_size);
+ (void) ssh2_transport_timer_update(s, 0);
+ s->rekey_class = RK_NONE;
+ } else {
+ ppl_logevent("Initiating key re-exchange (%s)",
+ s->rekey_reason);
+ }
+ }
+ } while (s->rekey_class == RK_NONE);
+
+ /* Once we exit the above loop, we really are rekeying. */
+ goto begin_key_exchange;
+
+ crFinishV;
+}
+
+static void ssh2_transport_higher_layer_packet_callback(void *context)
+{
+ PacketProtocolLayer *ppl = (PacketProtocolLayer *)context;
+ ssh_ppl_process_queue(ppl);
+}
+
+static void ssh2_transport_timer(void *ctx, unsigned long now)
+{
+ struct ssh2_transport_state *s = (struct ssh2_transport_state *)ctx;
+ unsigned long mins;
+ unsigned long ticks;
+
+ if (s->kex_in_progress || now != s->next_rekey)
+ return;
+
+ mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60);
+ if (mins == 0)
+ return;
+
+ /* Rekey if enough time has elapsed */
+ ticks = mins * 60 * TICKSPERSEC;
+ if (now - s->last_rekey > ticks - 30*TICKSPERSEC) {
+ s->rekey_reason = "timeout";
+ s->rekey_class = RK_NORMAL;
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+ return;
+ }
+
+#ifndef NO_GSSAPI
+ /*
+ * Rekey now if we have a new cred or context expires this cycle,
+ * but not if this is unsafe.
+ */
+ if (conf_get_int(s->conf, CONF_gssapirekey)) {
+ ssh2_transport_gss_update(s, false);
+ if ((s->gss_status & GSS_KEX_CAPABLE) != 0 &&
+ (s->gss_status & GSS_CTXT_MAYFAIL) == 0 &&
+ (s->gss_status & (GSS_CRED_UPDATED|GSS_CTXT_EXPIRES)) != 0) {
+ s->rekey_reason = "GSS credentials updated";
+ s->rekey_class = RK_GSS_UPDATE;
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+ return;
+ }
+ }
+#endif
+
+ /* Try again later. */
+ (void) ssh2_transport_timer_update(s, 0);
+}
+
+/*
+ * The rekey_time is zero except when re-configuring.
+ *
+ * We either schedule the next timer and return false, or return true
+ * to run the callback now, which will call us again to re-schedule on
+ * completion.
+ */
+static bool ssh2_transport_timer_update(struct ssh2_transport_state *s,
+ unsigned long rekey_time)
+{
+ unsigned long mins;
+ unsigned long ticks;
+
+ mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60);
+ ticks = mins * 60 * TICKSPERSEC;
+
+ /* Handle change from previous setting */
+ if (rekey_time != 0 && rekey_time != mins) {
+ unsigned long next;
+ unsigned long now = GETTICKCOUNT();
+
+ mins = rekey_time;
+ ticks = mins * 60 * TICKSPERSEC;
+ next = s->last_rekey + ticks;
+
+ /* If overdue, caller will rekey synchronously now */
+ if (now - s->last_rekey > ticks)
+ return true;
+ ticks = next - now;
+ }
+
+#ifndef NO_GSSAPI
+ if (s->gss_kex_used) {
+ /*
+ * If we've used GSSAPI key exchange, then we should
+ * periodically check whether we need to do another one to
+ * pass new credentials to the server.
+ */
+ unsigned long gssmins;
+
+ /* Check cascade conditions more frequently if configured */
+ gssmins = sanitise_rekey_time(
+ conf_get_int(s->conf, CONF_gssapirekey), GSS_DEF_REKEY_MINS);
+ if (gssmins > 0) {
+ if (gssmins < mins)
+ ticks = (mins = gssmins) * 60 * TICKSPERSEC;
+
+ if ((s->gss_status & GSS_KEX_CAPABLE) != 0) {
+ /*
+ * Run next timer even sooner if it would otherwise be
+ * too close to the context expiration time
+ */
+ if ((s->gss_status & GSS_CTXT_EXPIRES) == 0 &&
+ s->gss_ctxt_lifetime - mins * 60 < 2 * MIN_CTXT_LIFETIME)
+ ticks -= 2 * MIN_CTXT_LIFETIME * TICKSPERSEC;
+ }
+ }
+ }
+#endif
+
+ /* Schedule the next timer */
+ s->next_rekey = schedule_timer(ticks, ssh2_transport_timer, s);
+ return false;
+}
+
+void ssh2_transport_dialog_callback(void *vctx, SeatPromptResult spr)
+{
+ struct ssh2_transport_state *s = (struct ssh2_transport_state *)vctx;
+ s->spr = spr;
+ ssh_ppl_process_queue(&s->ppl);
+}
+
+#ifndef NO_GSSAPI
+/*
+ * This is called at the beginning of each SSH rekey to determine
+ * whether we are GSS capable, and if we did GSS key exchange, and are
+ * delegating credentials, it is also called periodically to determine
+ * whether we should rekey in order to delegate (more) fresh
+ * credentials. This is called "credential cascading".
+ *
+ * On Windows, with SSPI, we may not get the credential expiration, as
+ * Windows automatically renews from cached passwords, so the
+ * credential effectively never expires. Since we still want to
+ * cascade when the local TGT is updated, we use the expiration of a
+ * newly obtained context as a proxy for the expiration of the TGT.
+ */
+static void ssh2_transport_gss_update(struct ssh2_transport_state *s,
+ bool definitely_rekeying)
+{
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+ int gss_stat;
+ time_t gss_cred_expiry;
+ unsigned long mins;
+ Ssh_gss_buf gss_sndtok;
+ Ssh_gss_buf gss_rcvtok;
+ Ssh_gss_ctx gss_ctx;
+
+ s->gss_status = 0;
+
+ /*
+ * Nothing to do if no GSSAPI libraries are configured or GSSAPI
+ * auth is not enabled.
+ */
+ if (s->shgss->libs->nlibraries == 0)
+ return;
+ if (!conf_get_bool(s->conf, CONF_try_gssapi_auth) &&
+ !conf_get_bool(s->conf, CONF_try_gssapi_kex))
+ return;
+
+ /* Import server name and cache it */
+ if (s->shgss->srv_name == GSS_C_NO_NAME) {
+ gss_stat = s->shgss->lib->import_name(
+ s->shgss->lib, s->fullhostname, &s->shgss->srv_name);
+ if (gss_stat != SSH_GSS_OK) {
+ if (gss_stat == SSH_GSS_BAD_HOST_NAME)
+ ppl_logevent("GSSAPI import name failed - Bad service name;"
+ " won't use GSS key exchange");
+ else
+ ppl_logevent("GSSAPI import name failed;"
+ " won't use GSS key exchange");
+ return;
+ }
+ }
+
+ /*
+ * Do we (still) have credentials? Capture the credential
+ * expiration when available
+ */
+ gss_stat = s->shgss->lib->acquire_cred(
+ s->shgss->lib, &gss_ctx, &gss_cred_expiry);
+ if (gss_stat != SSH_GSS_OK)
+ return;
+
+ SSH_GSS_CLEAR_BUF(&gss_sndtok);
+ SSH_GSS_CLEAR_BUF(&gss_rcvtok);
+
+ /*
+ * When acquire_cred yields no useful expiration, get a proxy for
+ * the cred expiration from the context expiration.
+ */
+ gss_stat = s->shgss->lib->init_sec_context(
+ s->shgss->lib, &gss_ctx, s->shgss->srv_name,
+ 0 /* don't delegate */, &gss_rcvtok, &gss_sndtok,
+ (gss_cred_expiry == GSS_NO_EXPIRATION ? &gss_cred_expiry : NULL),
+ &s->gss_ctxt_lifetime);
+
+ /* This context was for testing only. */
+ if (gss_ctx)
+ s->shgss->lib->release_cred(s->shgss->lib, &gss_ctx);
+
+ if (gss_stat != SSH_GSS_OK &&
+ gss_stat != SSH_GSS_S_CONTINUE_NEEDED) {
+ /*
+ * No point in verbosely interrupting the user to tell them we
+ * couldn't get GSS credentials, if this was only a check
+ * between key exchanges to see if fresh ones were available.
+ * When we do do a rekey, this message (if displayed) will
+ * appear among the standard rekey blurb, but when we're not,
+ * it shouldn't pop up all the time regardless.
+ */
+ if (definitely_rekeying)
+ ppl_logevent("No GSSAPI security context available");
+
+ return;
+ }
+
+ if (gss_sndtok.length)
+ s->shgss->lib->free_tok(s->shgss->lib, &gss_sndtok);
+
+ s->gss_status |= GSS_KEX_CAPABLE;
+
+ /*
+ * When rekeying to cascade, avoid doing this too close to the
+ * context expiration time, since the key exchange might fail.
+ */
+ if (s->gss_ctxt_lifetime < MIN_CTXT_LIFETIME)
+ s->gss_status |= GSS_CTXT_MAYFAIL;
+
+ /*
+ * If we're not delegating credentials, rekeying is not used to
+ * refresh them. We must avoid setting GSS_CRED_UPDATED or
+ * GSS_CTXT_EXPIRES when credential delegation is disabled.
+ */
+ if (!conf_get_bool(s->conf, CONF_gssapifwd))
+ return;
+
+ if (s->gss_cred_expiry != GSS_NO_EXPIRATION &&
+ difftime(gss_cred_expiry, s->gss_cred_expiry) > 0)
+ s->gss_status |= GSS_CRED_UPDATED;
+
+ mins = sanitise_rekey_time(
+ conf_get_int(s->conf, CONF_gssapirekey), GSS_DEF_REKEY_MINS);
+ if (mins > 0 && s->gss_ctxt_lifetime <= mins * 60)
+ s->gss_status |= GSS_CTXT_EXPIRES;
+}
+#endif /* NO_GSSAPI */
+
+ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ppl)
+{
+ struct ssh2_transport_state *s;
+
+ assert(ppl->vt == &ssh2_transport_vtable);
+ s = container_of(ppl, struct ssh2_transport_state, ppl);
+
+ assert(s->got_session_id);
+ return make_ptrlen(s->session_id, s->session_id_len);
+}
+
+void ssh2_transport_notify_auth_done(PacketProtocolLayer *ppl)
+{
+ struct ssh2_transport_state *s;
+
+ assert(ppl->vt == &ssh2_transport_vtable);
+ s = container_of(ppl, struct ssh2_transport_state, ppl);
+
+ s->rekey_reason = NULL; /* will be filled in later */
+ s->rekey_class = RK_POST_USERAUTH;
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+}
+
+static bool ssh2_transport_get_specials(
+ PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
+{
+ struct ssh2_transport_state *s =
+ container_of(ppl, struct ssh2_transport_state, ppl);
+ bool need_separator = false;
+ bool toret = false;
+
+ if (ssh_ppl_get_specials(s->higher_layer, add_special, ctx)) {
+ need_separator = true;
+ toret = true;
+ }
+
+ /*
+ * Don't bother offering rekey-based specials if we've decided the
+ * remote won't cope with it, since we wouldn't bother sending it
+ * if asked anyway.
+ */
+ if (!(s->ppl.remote_bugs & BUG_SSH2_REKEY)) {
+ if (need_separator) {
+ add_special(ctx, NULL, SS_SEP, 0);
+ need_separator = false;
+ }
+
+ add_special(ctx, "Repeat key exchange", SS_REKEY, 0);
+ toret = true;
+
+ if (s->n_uncert_hostkeys) {
+ int i;
+
+ add_special(ctx, NULL, SS_SEP, 0);
+ add_special(ctx, "Cache new host key type", SS_SUBMENU, 0);
+ for (i = 0; i < s->n_uncert_hostkeys; i++) {
+ const ssh_keyalg *alg =
+ ssh2_hostkey_algs[s->uncert_hostkeys[i]].alg;
+
+ add_special(ctx, alg->ssh_id, SS_XCERT, s->uncert_hostkeys[i]);
+ }
+ add_special(ctx, NULL, SS_EXITMENU, 0);
+ }
+ }
+
+ return toret;
+}
+
+static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl,
+ SessionSpecialCode code, int arg)
+{
+ struct ssh2_transport_state *s =
+ container_of(ppl, struct ssh2_transport_state, ppl);
+
+ if (code == SS_REKEY) {
+ if (!s->kex_in_progress) {
+ s->rekey_reason = "at user request";
+ s->rekey_class = RK_NORMAL;
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+ }
+ } else if (code == SS_XCERT) {
+ if (!s->kex_in_progress) {
+ s->cross_certifying = s->hostkey_alg = ssh2_hostkey_algs[arg].alg;
+ s->rekey_reason = "cross-certifying new host key";
+ s->rekey_class = RK_NORMAL;
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+ }
+ } else {
+ /* Send everything else to the next layer up. This includes
+ * SS_PING/SS_NOP, which we _could_ handle here - but it's
+ * better to put them in the connection layer, so they'll
+ * still work in bare connection mode. */
+ ssh_ppl_special_cmd(s->higher_layer, code, arg);
+ }
+}
+
+/* Safely convert rekey_time to unsigned long minutes */
+static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def)
+{
+ if (rekey_time < 0 || rekey_time > MAX_TICK_MINS)
+ rekey_time = def;
+ return (unsigned long)rekey_time;
+}
+
+static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s)
+{
+ s->max_data_size = parse_blocksize(
+ conf_get_str(s->conf, CONF_ssh_rekey_data));
+}
+
+static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
+{
+ struct ssh2_transport_state *s;
+ const char *rekey_reason = NULL;
+ bool rekey_mandatory = false;
+ unsigned long old_max_data_size, rekey_time;
+ int i;
+
+ assert(ppl->vt == &ssh2_transport_vtable);
+ s = container_of(ppl, struct ssh2_transport_state, ppl);
+
+ rekey_time = sanitise_rekey_time(
+ conf_get_int(conf, CONF_ssh_rekey_time), 60);
+ if (ssh2_transport_timer_update(s, rekey_time))
+ rekey_reason = "timeout shortened";
+
+ old_max_data_size = s->max_data_size;
+ ssh2_transport_set_max_data_size(s);
+ if (old_max_data_size != s->max_data_size &&
+ s->max_data_size != 0) {
+ if (s->max_data_size < old_max_data_size) {
+ unsigned long diff = old_max_data_size - s->max_data_size;
+
+ dts_consume(&s->stats->out, diff);
+ dts_consume(&s->stats->in, diff);
+ if (s->stats->out.expired || s->stats->in.expired)
+ rekey_reason = "data limit lowered";
+ } else {
+ unsigned long diff = s->max_data_size - old_max_data_size;
+ if (s->stats->out.running)
+ s->stats->out.remaining += diff;
+ if (s->stats->in.running)
+ s->stats->in.remaining += diff;
+ }
+ }
+
+ if (conf_get_bool(s->conf, CONF_compression) !=
+ conf_get_bool(conf, CONF_compression)) {
+ rekey_reason = "compression setting changed";
+ rekey_mandatory = true;
+ }
+
+ for (i = 0; i < CIPHER_MAX; i++)
+ if (conf_get_int_int(s->conf, CONF_ssh_cipherlist, i) !=
+ conf_get_int_int(conf, CONF_ssh_cipherlist, i)) {
+ rekey_reason = "cipher settings changed";
+ rekey_mandatory = true;
+ }
+ if (conf_get_bool(s->conf, CONF_ssh2_des_cbc) !=
+ conf_get_bool(conf, CONF_ssh2_des_cbc)) {
+ rekey_reason = "cipher settings changed";
+ rekey_mandatory = true;
+ }
+
+ conf_free(s->conf);
+ s->conf = conf_copy(conf);
+
+ if (rekey_reason) {
+ if (!s->kex_in_progress && !ssh2_bpp_rekey_inadvisable(s->ppl.bpp)) {
+ s->rekey_reason = rekey_reason;
+ s->rekey_class = RK_NORMAL;
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+ } else if (rekey_mandatory) {
+ s->deferred_rekey_reason = rekey_reason;
+ }
+ }
+
+ /* Also pass the configuration along to our higher layer */
+ ssh_ppl_reconfigure(s->higher_layer, conf);
+}
+
+static int weak_algorithm_compare(void *av, void *bv)
+{
+ uintptr_t a = (uintptr_t)av, b = (uintptr_t)bv;
+ return a < b ? -1 : a > b ? +1 : 0;
+}
+
+static int ca_blob_compare(void *av, void *bv)
+{
+ host_ca *a = (host_ca *)av, *b = (host_ca *)bv;
+ strbuf *apk = a->ca_public_key, *bpk = b->ca_public_key;
+ /* Ordering by public key is arbitrary here, so do whatever is easiest */
+ if (apk->len < bpk->len)
+ return -1;
+ if (apk->len > bpk->len)
+ return +1;
+ return memcmp(apk->u, bpk->u, apk->len);
+}
+
+/*
+ * Wrapper on 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 SeatPromptResult 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 SPR_OK;
+ add234(s->weak_algorithms_consented_to, (void *)alg);
+
+ return seat_confirm_weak_crypto_primitive(
+ ppl_get_iseat(&s->ppl), 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/ssh/transport2.h b/ssh/transport2.h
new file mode 100644
index 00000000..204573fb
--- /dev/null
+++ b/ssh/transport2.h
@@ -0,0 +1,261 @@
+/*
+ * Header connecting the pieces of the SSH-2 transport layer.
+ */
+
+#ifndef PUTTY_SSH2TRANSPORT_H
+#define PUTTY_SSH2TRANSPORT_H
+
+#ifndef NO_GSSAPI
+#include "gssc.h"
+#include "gss.h"
+#define MIN_CTXT_LIFETIME 5 /* Avoid rekey with short lifetime (seconds) */
+#define GSS_KEX_CAPABLE (1<<0) /* Can do GSS KEX */
+#define GSS_CRED_UPDATED (1<<1) /* Cred updated since previous delegation */
+#define GSS_CTXT_EXPIRES (1<<2) /* Context expires before next timer */
+#define GSS_CTXT_MAYFAIL (1<<3) /* Context may expire during handshake */
+#endif
+
+#define DH_MIN_SIZE 1024
+#define DH_MAX_SIZE 8192
+
+struct kexinit_algorithm {
+ ptrlen name;
+ union {
+ struct {
+ const ssh_kex *kex;
+ bool warn;
+ } kex;
+ struct {
+ const ssh_keyalg *hostkey;
+ unsigned hkflags;
+ bool warn;
+ } hk;
+ struct {
+ const ssh_cipheralg *cipher;
+ bool warn;
+ } cipher;
+ struct {
+ const ssh2_macalg *mac;
+ bool etm;
+ } mac;
+ struct {
+ const ssh_compression_alg *comp;
+ bool delayed;
+ } comp;
+ } u;
+};
+struct kexinit_algorithm_list {
+ struct kexinit_algorithm *algs;
+ size_t nalgs, algsize;
+};
+
+#define HOSTKEY_ALGORITHMS(X) \
+ X(HK_ED25519, ssh_ecdsa_ed25519) \
+ X(HK_ED448, ssh_ecdsa_ed448) \
+ X(HK_ECDSA, ssh_ecdsa_nistp256) \
+ X(HK_ECDSA, ssh_ecdsa_nistp384) \
+ X(HK_ECDSA, ssh_ecdsa_nistp521) \
+ X(HK_DSA, ssh_dsa) \
+ X(HK_RSA, ssh_rsa_sha512) \
+ X(HK_RSA, ssh_rsa_sha256) \
+ X(HK_RSA, ssh_rsa) \
+ X(HK_ED25519, opensshcert_ssh_ecdsa_ed25519) \
+ /* OpenSSH defines no certified version of Ed448 */ \
+ X(HK_ECDSA, opensshcert_ssh_ecdsa_nistp256) \
+ X(HK_ECDSA, opensshcert_ssh_ecdsa_nistp384) \
+ X(HK_ECDSA, opensshcert_ssh_ecdsa_nistp521) \
+ X(HK_DSA, opensshcert_ssh_dsa) \
+ X(HK_RSA, opensshcert_ssh_rsa_sha512) \
+ X(HK_RSA, opensshcert_ssh_rsa_sha256) \
+ X(HK_RSA, opensshcert_ssh_rsa) \
+ /* end of list */
+#define COUNT_HOSTKEY_ALGORITHM(type, alg) +1
+#define N_HOSTKEY_ALGORITHMS (0 HOSTKEY_ALGORITHMS(COUNT_HOSTKEY_ALGORITHM))
+
+struct ssh_signkey_with_user_pref_id {
+ const ssh_keyalg *alg;
+ int id;
+};
+extern const struct ssh_signkey_with_user_pref_id
+ ssh2_hostkey_algs[N_HOSTKEY_ALGORITHMS];
+
+/*
+ * Enumeration of high-level classes of reason why we might need to do
+ * a repeat key exchange. A full detailed reason in human-readable
+ * string form for the Event Log is also provided, but this enum type
+ * is used to discriminate between classes of reason that the code
+ * needs to treat differently.
+ *
+ * RK_NONE == 0 is the value indicating that no rekey is currently
+ * needed at all. RK_INITIAL indicates that we haven't even done the
+ * _first_ key exchange yet. RK_SERVER indicates that we're rekeying
+ * because the server asked for it, not because we decided it
+ * ourselves. RK_NORMAL is the usual case. RK_GSS_UPDATE indicates
+ * that we're rekeying because we've just got new GSSAPI credentials
+ * (hence there's no point in doing a preliminary check for new GSS
+ * creds, because we already know the answer); RK_POST_USERAUTH
+ * indicates that _if_ we're going to need a post-userauth immediate
+ * rekey for any reason, this is the moment to do it.
+ *
+ * So RK_POST_USERAUTH only tells the transport layer to _consider_
+ * rekeying, not to definitely do it. Also, that one enum value is
+ * special in that the user-readable reason text is passed in to the
+ * transport layer as NULL, whereas fills in the reason text after it
+ * decides whether it needs a rekey at all. In the other cases,
+ * rekey_reason is passed in to the at the same time as rekey_class.
+ */
+typedef enum RekeyClass {
+ RK_NONE = 0,
+ RK_INITIAL,
+ RK_SERVER,
+ RK_NORMAL,
+ RK_POST_USERAUTH,
+ RK_GSS_UPDATE
+} RekeyClass;
+
+typedef struct transport_direction {
+ const ssh_cipheralg *cipher;
+ const ssh2_macalg *mac;
+ bool etm_mode;
+ const ssh_compression_alg *comp;
+ bool comp_delayed;
+ int mkkey_adjust;
+} transport_direction;
+
+struct ssh2_transport_state {
+ int crState, crStateKex;
+
+ PacketProtocolLayer *higher_layer;
+ PktInQueue pq_in_higher;
+ PktOutQueue pq_out_higher;
+ IdempotentCallback ic_pq_out_higher;
+
+ Conf *conf;
+ char *savedhost;
+ int savedport;
+ const char *rekey_reason;
+ enum RekeyClass rekey_class;
+
+ unsigned long max_data_size;
+
+ const ssh_kex *kex_alg;
+ const ssh_keyalg *hostkey_alg;
+ unsigned char session_id[MAX_HASH_LEN];
+ int session_id_len;
+ int dh_min_size, dh_max_size;
+ bool dh_got_size_bounds;
+ dh_ctx *dh_ctx;
+ ssh_hash *exhash;
+
+ struct DataTransferStats *stats;
+
+ const SshServerConfig *ssc;
+
+ char *client_greeting, *server_greeting;
+
+ bool kex_in_progress, kexinit_delayed;
+ unsigned long next_rekey, last_rekey;
+ const char *deferred_rekey_reason;
+ bool higher_layer_ok;
+
+ /*
+ * Fully qualified host name, which we need if doing GSSAPI.
+ */
+ char *fullhostname;
+
+ /* shgss is outside the ifdef on purpose to keep APIs simple. If
+ * NO_GSSAPI is not defined, then it's just an opaque structure
+ * tag and the pointer will be NULL. */
+ struct ssh_connection_shared_gss_state *shgss;
+#ifndef NO_GSSAPI
+ int gss_status;
+ time_t gss_cred_expiry; /* Re-delegate if newer */
+ unsigned long gss_ctxt_lifetime; /* Re-delegate when short */
+#endif
+ ssh_transient_hostkey_cache *thc;
+
+ bool gss_kex_used;
+
+ tree234 *host_cas;
+
+ int nbits, pbits;
+ bool warn_kex, warn_hk, warn_cscipher, warn_sccipher;
+ mp_int *p, *g, *e, *f;
+ strbuf *ebuf, *fbuf;
+ strbuf *kex_shared_secret;
+ strbuf *outgoing_kexinit, *incoming_kexinit;
+ strbuf *client_kexinit, *server_kexinit; /* aliases to the above */
+ int kex_init_value, kex_reply_value;
+ transport_direction in, out, *cstrans, *sctrans;
+ ptrlen hostkeydata, sigdata;
+ strbuf *hostkeyblob; /* used in server to construct host key to
+ * send to client; in client to check in rekeys */
+ char *keystr;
+ ssh_key *hkey; /* actual host key */
+ unsigned hkflags; /* signing flags, used in server */
+ RSAKey *rsa_kex_key; /* for RSA kex */
+ bool rsa_kex_key_needs_freeing;
+ ecdh_key *ecdh_key; /* for ECDH kex */
+ unsigned char exchange_hash[MAX_HASH_LEN];
+ bool can_gssapi_keyex;
+ bool need_gss_transient_hostkey;
+ bool warned_about_no_gss_transient_hostkey;
+ bool got_session_id;
+ bool can_send_ext_info, post_newkeys_ext_info;
+ SeatPromptResult spr;
+ bool guessok;
+ bool ignorepkt;
+ struct kexinit_algorithm_list kexlists[NKEXLIST];
+#ifndef NO_GSSAPI
+ Ssh_gss_buf gss_buf;
+ Ssh_gss_buf gss_rcvtok, gss_sndtok;
+ Ssh_gss_stat gss_stat;
+ Ssh_gss_buf mic;
+ bool init_token_sent;
+ bool complete_rcvd;
+ bool gss_delegate;
+#endif
+
+ /* List of crypto primitives below the warning threshold that the
+ * user has already clicked OK to, so that we don't keep asking
+ * about them again during rekeys. This directly stores pointers
+ * to the algorithm vtables, compared by pointer value (which is
+ * not a determinism hazard, because we're only using it as a
+ * set). */
+ tree234 *weak_algorithms_consented_to;
+
+ /*
+ * List of host key algorithms for which we _don't_ have a stored
+ * host key. These are indices into the main hostkey_algs[] array
+ */
+ int uncert_hostkeys[N_HOSTKEY_ALGORITHMS];
+ int n_uncert_hostkeys;
+
+ /*
+ * Indicate that the current rekey is intended to finish with a
+ * newly cross-certified host key. To double-check that we
+ * certified the right one, we set this to point to the host key
+ * algorithm we expect it to be.
+ */
+ const ssh_keyalg *cross_certifying;
+
+ ssh_key *const *hostkeys;
+ int nhostkeys;
+
+ PacketProtocolLayer ppl;
+};
+
+/* Helpers shared between transport and kex */
+PktIn *ssh2_transport_pop(struct ssh2_transport_state *s);
+void ssh2_transport_dialog_callback(void *, SeatPromptResult);
+
+/* Provided by transport for use in kex */
+void ssh2transport_finalise_exhash(struct ssh2_transport_state *s);
+
+/* Provided by kex for use in transport. Must set the 'aborted' flag
+ * if it throws a connection-terminating error, so that the caller
+ * won't have to check that by looking inside its state parameter
+ * which might already have been freed. */
+void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted);
+
+#endif /* PUTTY_SSH2TRANSPORT_H */
diff --git a/sshttymodes.h b/ssh/ttymode-list.h
index 8fffe1c5..8fffe1c5 100644
--- a/sshttymodes.h
+++ b/ssh/ttymode-list.h
diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c
new file mode 100644
index 00000000..b7e36371
--- /dev/null
+++ b/ssh/userauth2-client.c
@@ -0,0 +1,2540 @@
+/*
+ * Packet protocol layer for the client side of the SSH-2 userauth
+ * protocol (RFC 4252).
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "sshcr.h"
+
+#ifndef NO_GSSAPI
+#include "gssc.h"
+#include "gss.h"
+#endif
+
+#define BANNER_LIMIT 131072
+
+typedef struct agent_key {
+ strbuf *blob, *comment;
+ ptrlen algorithm;
+} agent_key;
+
+struct ssh2_userauth_state {
+ int crState;
+
+ PacketProtocolLayer *transport_layer, *successor_layer;
+ Filename *keyfile, *detached_cert_file;
+ bool show_banner, tryagent, notrivialauth, change_username;
+ char *hostname, *fullhostname;
+ int port;
+ char *default_username;
+ bool try_ki_auth, try_gssapi_auth, try_gssapi_kex_auth, gssapi_fwd;
+
+ ptrlen session_id;
+ enum {
+ AUTH_TYPE_NONE,
+ AUTH_TYPE_PUBLICKEY,
+ AUTH_TYPE_PUBLICKEY_OFFER_LOUD,
+ AUTH_TYPE_PUBLICKEY_OFFER_QUIET,
+ AUTH_TYPE_PASSWORD,
+ AUTH_TYPE_GSSAPI, /* always QUIET */
+ AUTH_TYPE_KEYBOARD_INTERACTIVE,
+ AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET
+ } type;
+ bool need_pw, can_pubkey, can_passwd, can_keyb_inter;
+ SeatPromptResult spr;
+ bool tried_pubkey_config, done_agent;
+ struct ssh_connection_shared_gss_state *shgss;
+#ifndef NO_GSSAPI
+ bool can_gssapi;
+ bool can_gssapi_keyex_auth;
+ bool tried_gssapi;
+ bool tried_gssapi_keyex_auth;
+ time_t gss_cred_expiry;
+ Ssh_gss_buf gss_buf;
+ Ssh_gss_buf gss_rcvtok, gss_sndtok;
+ Ssh_gss_stat gss_stat;
+#endif
+ bool suppress_wait_for_response_packet;
+ strbuf *last_methods_string;
+ bool kbd_inter_refused;
+ prompts_t *cur_prompt;
+ uint32_t num_prompts;
+ const char *username;
+ char *locally_allocated_username;
+ char *password;
+ bool got_username;
+ strbuf *publickey_blob, *detached_cert_blob, *cert_pubkey_diagnosed;
+ bool privatekey_available, privatekey_encrypted;
+ char *publickey_algorithm;
+ char *publickey_comment;
+ void *agent_response_to_free;
+ ptrlen agent_response;
+ BinarySource asrc[1]; /* for reading SSH agent response */
+ size_t agent_keys_len;
+ agent_key *agent_keys;
+ size_t agent_key_index, agent_key_limit;
+ ptrlen agent_keyalg;
+ unsigned signflags;
+ int len;
+ PktOut *pktout;
+ bool is_trivial_auth;
+
+ agent_pending_query *auth_agent_query;
+ bufchain banner;
+ bufchain_sink banner_bs;
+ StripCtrlChars *banner_scc;
+ bool banner_scc_initialised;
+
+ char *authplugin_cmd;
+ Socket *authplugin;
+ uint32_t authplugin_version;
+ Plug authplugin_plug;
+ bufchain authplugin_bc;
+ strbuf *authplugin_incoming_msg;
+ size_t authplugin_backlog;
+ bool authplugin_eof;
+ bool authplugin_ki_active;
+
+ StripCtrlChars *ki_scc;
+ bool ki_scc_initialised;
+ bool ki_printed_header;
+
+ PacketProtocolLayer ppl;
+};
+
+static void ssh2_userauth_free(PacketProtocolLayer *);
+static void ssh2_userauth_process_queue(PacketProtocolLayer *);
+static bool ssh2_userauth_get_specials(
+ PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx);
+static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl,
+ SessionSpecialCode code, int arg);
+static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
+
+static void ssh2_userauth_agent_query(struct ssh2_userauth_state *, strbuf *);
+static void ssh2_userauth_agent_callback(void *, void *, int);
+static void ssh2_userauth_add_sigblob(
+ struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob);
+static void ssh2_userauth_add_alg_and_publickey(
+ struct ssh2_userauth_state *s, PktOut *pkt, ptrlen alg, ptrlen pkblob);
+static void ssh2_userauth_add_session_id(
+ struct ssh2_userauth_state *s, strbuf *sigdata);
+#ifndef NO_GSSAPI
+static PktOut *ssh2_userauth_gss_packet(
+ struct ssh2_userauth_state *s, const char *authtype);
+#endif
+static bool ssh2_userauth_ki_setup_prompts(
+ struct ssh2_userauth_state *s, BinarySource *src, bool plugin);
+static bool ssh2_userauth_ki_run_prompts(struct ssh2_userauth_state *s);
+static void ssh2_userauth_ki_write_responses(
+ struct ssh2_userauth_state *s, BinarySink *bs);
+
+static const PacketProtocolLayerVtable ssh2_userauth_vtable = {
+ .free = ssh2_userauth_free,
+ .process_queue = ssh2_userauth_process_queue,
+ .get_specials = ssh2_userauth_get_specials,
+ .special_cmd = ssh2_userauth_special_cmd,
+ .reconfigure = ssh2_userauth_reconfigure,
+ .queued_data_size = ssh_ppl_default_queued_data_size,
+ .name = "ssh-userauth",
+};
+
+PacketProtocolLayer *ssh2_userauth_new(
+ PacketProtocolLayer *successor_layer,
+ const char *hostname, int port, const char *fullhostname,
+ Filename *keyfile, Filename *detached_cert_file,
+ bool show_banner, bool tryagent, bool notrivialauth,
+ const char *default_username, bool change_username,
+ bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth,
+ bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss,
+ const char *authplugin_cmd)
+{
+ struct ssh2_userauth_state *s = snew(struct ssh2_userauth_state);
+ memset(s, 0, sizeof(*s));
+ s->ppl.vt = &ssh2_userauth_vtable;
+
+ s->successor_layer = successor_layer;
+ s->hostname = dupstr(hostname);
+ s->port = port;
+ s->fullhostname = dupstr(fullhostname);
+ s->keyfile = filename_copy(keyfile);
+ s->detached_cert_file = filename_copy(detached_cert_file);
+ s->show_banner = show_banner;
+ s->tryagent = tryagent;
+ s->notrivialauth = notrivialauth;
+ s->default_username = dupstr(default_username);
+ s->change_username = change_username;
+ s->try_ki_auth = try_ki_auth;
+ s->try_gssapi_auth = try_gssapi_auth;
+ s->try_gssapi_kex_auth = try_gssapi_kex_auth;
+ s->gssapi_fwd = gssapi_fwd;
+ s->shgss = shgss;
+ s->last_methods_string = strbuf_new();
+ s->is_trivial_auth = true;
+ bufchain_init(&s->banner);
+ bufchain_sink_init(&s->banner_bs, &s->banner);
+ s->authplugin_cmd = dupstr(authplugin_cmd);
+ bufchain_init(&s->authplugin_bc);
+
+ return &s->ppl;
+}
+
+void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth,
+ PacketProtocolLayer *transport)
+{
+ struct ssh2_userauth_state *s =
+ container_of(userauth, struct ssh2_userauth_state, ppl);
+ s->transport_layer = transport;
+}
+
+static void ssh2_userauth_free(PacketProtocolLayer *ppl)
+{
+ struct ssh2_userauth_state *s =
+ container_of(ppl, struct ssh2_userauth_state, ppl);
+ bufchain_clear(&s->banner);
+
+ if (s->successor_layer)
+ ssh_ppl_free(s->successor_layer);
+
+ if (s->agent_keys) {
+ for (size_t i = 0; i < s->agent_keys_len; i++) {
+ strbuf_free(s->agent_keys[i].blob);
+ strbuf_free(s->agent_keys[i].comment);
+ }
+ sfree(s->agent_keys);
+ }
+ sfree(s->agent_response_to_free);
+ if (s->auth_agent_query)
+ agent_cancel_query(s->auth_agent_query);
+ filename_free(s->keyfile);
+ filename_free(s->detached_cert_file);
+ sfree(s->default_username);
+ sfree(s->locally_allocated_username);
+ sfree(s->hostname);
+ sfree(s->fullhostname);
+ if (s->cur_prompt)
+ free_prompts(s->cur_prompt);
+ sfree(s->publickey_comment);
+ sfree(s->publickey_algorithm);
+ if (s->publickey_blob)
+ strbuf_free(s->publickey_blob);
+ if (s->detached_cert_blob)
+ strbuf_free(s->detached_cert_blob);
+ if (s->cert_pubkey_diagnosed)
+ strbuf_free(s->cert_pubkey_diagnosed);
+ strbuf_free(s->last_methods_string);
+ if (s->banner_scc)
+ stripctrl_free(s->banner_scc);
+ if (s->ki_scc)
+ stripctrl_free(s->ki_scc);
+ sfree(s->authplugin_cmd);
+ if (s->authplugin)
+ sk_close(s->authplugin);
+ bufchain_clear(&s->authplugin_bc);
+ if (s->authplugin_incoming_msg)
+ strbuf_free(s->authplugin_incoming_msg);
+ sfree(s);
+}
+
+static void ssh2_userauth_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 bool ssh2_userauth_signflags(struct ssh2_userauth_state *s,
+ unsigned *signflags, const char **algname)
+{
+ *signflags = 0; /* default */
+
+ const ssh_keyalg *alg = find_pubkey_alg(*algname);
+ if (!alg)
+ return false; /* we don't know how to upgrade this */
+
+ unsigned supported_flags = ssh_keyalg_supported_flags(alg);
+
+ if (s->ppl.bpp->ext_info_rsa_sha512_ok &&
+ (supported_flags & SSH_AGENT_RSA_SHA2_512)) {
+ *signflags = SSH_AGENT_RSA_SHA2_512;
+ } else if (s->ppl.bpp->ext_info_rsa_sha256_ok &&
+ (supported_flags & SSH_AGENT_RSA_SHA2_256)) {
+ *signflags = SSH_AGENT_RSA_SHA2_256;
+ } else {
+ return false;
+ }
+
+ *algname = ssh_keyalg_alternate_ssh_id(alg, *signflags);
+ return true;
+}
+
+static void authplugin_plug_log(Plug *plug, PlugLogType type, SockAddr *addr,
+ int port, const char *err_msg, int err_code)
+{
+ struct ssh2_userauth_state *s = container_of(
+ plug, struct ssh2_userauth_state, authplugin_plug);
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+
+ if (type == PLUGLOG_PROXY_MSG)
+ ppl_logevent("%s", err_msg);
+}
+
+static void authplugin_plug_closing(
+ Plug *plug, PlugCloseType type, const char *error_msg)
+{
+ struct ssh2_userauth_state *s = container_of(
+ plug, struct ssh2_userauth_state, authplugin_plug);
+ s->authplugin_eof = true;
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+}
+
+static void authplugin_plug_receive(
+ Plug *plug, int urgent, const char *data, size_t len)
+{
+ struct ssh2_userauth_state *s = container_of(
+ plug, struct ssh2_userauth_state, authplugin_plug);
+ bufchain_add(&s->authplugin_bc, data, len);
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+}
+
+static void authplugin_plug_sent(Plug *plug, size_t bufsize)
+{
+ struct ssh2_userauth_state *s = container_of(
+ plug, struct ssh2_userauth_state, authplugin_plug);
+ s->authplugin_backlog = bufsize;
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+}
+
+static const PlugVtable authplugin_plugvt = {
+ .log = authplugin_plug_log,
+ .closing = authplugin_plug_closing,
+ .receive = authplugin_plug_receive,
+ .sent = authplugin_plug_sent,
+};
+
+static strbuf *authplugin_newmsg(uint8_t type)
+{
+ strbuf *amsg = strbuf_new_nm();
+ put_uint32(amsg, 0); /* fill in later */
+ put_byte(amsg, type);
+ return amsg;
+}
+
+static void authplugin_send_free(struct ssh2_userauth_state *s, strbuf *amsg)
+{
+ PUT_32BIT_MSB_FIRST(amsg->u, amsg->len - 4);
+ assert(s->authplugin);
+ s->authplugin_backlog = sk_write(s->authplugin, amsg->u, amsg->len);
+ strbuf_free(amsg);
+}
+
+static bool authplugin_expect_msg(struct ssh2_userauth_state *s,
+ unsigned *type, BinarySource *src)
+{
+ if (s->authplugin_eof) {
+ *type = PLUGIN_EOF;
+ return true;
+ }
+ uint8_t len[4];
+ if (!bufchain_try_fetch(&s->authplugin_bc, len, 4))
+ return false;
+ size_t size = GET_32BIT_MSB_FIRST(len);
+ if (bufchain_size(&s->authplugin_bc) - 4 < size)
+ return false;
+ if (s->authplugin_incoming_msg) {
+ strbuf_clear(s->authplugin_incoming_msg);
+ } else {
+ s->authplugin_incoming_msg = strbuf_new_nm();
+ }
+ bufchain_consume(&s->authplugin_bc, 4); /* eat length field */
+ bufchain_fetch_consume(
+ &s->authplugin_bc, strbuf_append(s->authplugin_incoming_msg, size),
+ size);
+ BinarySource_BARE_INIT_PL(
+ src, ptrlen_from_strbuf(s->authplugin_incoming_msg));
+ *type = get_byte(src);
+ if (get_err(src))
+ *type = PLUGIN_NOTYPE;
+ return true;
+}
+
+static void authplugin_bad_packet(struct ssh2_userauth_state *s,
+ unsigned type, const char *fmt, ...)
+{
+ strbuf *msg = strbuf_new();
+ switch (type) {
+ case PLUGIN_EOF:
+ put_dataz(msg, "Unexpected end of file from auth helper plugin");
+ break;
+ case PLUGIN_NOTYPE:
+ put_dataz(msg, "Received malformed packet from auth helper plugin "
+ "(too short to have a type code)");
+ break;
+ default:
+ put_fmt(msg, "Received unknown message type %u "
+ "from auth helper plugin", type);
+ break;
+
+ #define CASEDECL(name, value) \
+ case name: \
+ put_fmt(msg, "Received unexpected %s message from auth helper " \
+ "plugin", #name); \
+ break;
+ AUTHPLUGIN_MSG_NAMES(CASEDECL);
+ #undef CASEDECL
+ }
+ if (fmt) {
+ put_dataz(msg, " (");
+ va_list ap;
+ va_start(ap, fmt);
+ put_fmt(msg, fmt, ap);
+ va_end(ap);
+ put_dataz(msg, ")");
+ }
+ ssh_sw_abort(s->ppl.ssh, "%s", msg->s);
+ strbuf_free(msg);
+}
+
+static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
+{
+ struct ssh2_userauth_state *s =
+ container_of(ppl, struct ssh2_userauth_state, ppl);
+ PktIn *pktin;
+
+ ssh2_userauth_filter_queue(s); /* no matter why we were called */
+
+ crBegin(s->crState);
+
+#ifndef NO_GSSAPI
+ s->tried_gssapi = false;
+ s->tried_gssapi_keyex_auth = false;
+#endif
+
+ /*
+ * Misc one-time setup for authentication.
+ */
+ s->publickey_blob = NULL;
+ s->session_id = ssh2_transport_get_session_id(s->transport_layer);
+
+ /*
+ * Load the public half of any configured public key file for
+ * later use.
+ */
+ if (!filename_is_null(s->keyfile)) {
+ int keytype;
+ ppl_logevent("Reading key file \"%s\"",
+ filename_to_str(s->keyfile));
+ keytype = key_type(s->keyfile);
+ if (keytype == SSH_KEYTYPE_SSH2 ||
+ keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
+ keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
+ const char *error;
+ s->publickey_blob = strbuf_new();
+ if (ppk_loadpub_f(s->keyfile, &s->publickey_algorithm,
+ BinarySink_UPCAST(s->publickey_blob),
+ &s->publickey_comment, &error)) {
+ s->privatekey_available = (keytype == SSH_KEYTYPE_SSH2);
+ if (!s->privatekey_available)
+ ppl_logevent("Key file contains public key only");
+ s->privatekey_encrypted = ppk_encrypted_f(s->keyfile, NULL);
+ } else {
+ ppl_logevent("Unable to load key (%s)", error);
+ ppl_printf("Unable to load key file \"%s\" (%s)\r\n",
+ filename_to_str(s->keyfile), error);
+ strbuf_free(s->publickey_blob);
+ s->publickey_blob = NULL;
+ }
+ } else {
+ ppl_logevent("Unable to use this key file (%s)",
+ key_type_to_str(keytype));
+ ppl_printf("Unable to use key file \"%s\" (%s)\r\n",
+ filename_to_str(s->keyfile),
+ key_type_to_str(keytype));
+ s->publickey_blob = NULL;
+ }
+ }
+
+ /*
+ * If the user provided a detached certificate file, load that.
+ */
+ if (!filename_is_null(s->detached_cert_file)) {
+ char *cert_error = NULL;
+ strbuf *cert_blob = strbuf_new();
+ char *algname = NULL;
+ char *comment = NULL;
+
+ ppl_logevent("Reading certificate file \"%s\"",
+ filename_to_str(s->detached_cert_file));
+ int keytype = key_type(s->detached_cert_file);
+ if (!(keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
+ keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)) {
+ cert_error = dupstr(key_type_to_str(keytype));
+ goto cert_load_done;
+ }
+
+ const char *error;
+ bool success = ppk_loadpub_f(
+ s->detached_cert_file, &algname,
+ BinarySink_UPCAST(cert_blob), &comment, &error);
+
+ if (!success) {
+ cert_error = dupstr(error);
+ goto cert_load_done;
+ }
+
+ const ssh_keyalg *certalg = find_pubkey_alg(algname);
+ if (!certalg) {
+ cert_error = dupprintf(
+ "unrecognised certificate type '%s'", algname);
+ goto cert_load_done;
+ }
+
+ if (!certalg->is_certificate) {
+ cert_error = dupprintf(
+ "key type '%s' is not a certificate", certalg->ssh_id);
+ goto cert_load_done;
+ }
+
+ /* OK, store the certificate blob to substitute for the
+ * public blob in all publickey auth packets. */
+ if (s->detached_cert_blob)
+ strbuf_free(s->detached_cert_blob);
+ s->detached_cert_blob = cert_blob;
+ cert_blob = NULL; /* prevent free */
+
+ cert_load_done:
+ if (cert_error) {
+ ppl_logevent("Unable to use this certificate file (%s)",
+ cert_error);
+ ppl_printf(
+ "Unable to use certificate file \"%s\" (%s)\r\n",
+ filename_to_str(s->detached_cert_file), cert_error);
+ sfree(cert_error);
+ }
+
+ if (cert_blob)
+ strbuf_free(cert_blob);
+ sfree(algname);
+ sfree(comment);
+ }
+
+ /*
+ * Find out about any keys Pageant has (but if there's a public
+ * key configured, filter out all others).
+ */
+ if (s->tryagent && agent_exists()) {
+ ppl_logevent("Pageant is running. Requesting keys.");
+
+ /* Request the keys held by the agent. */
+ {
+ strbuf *request = strbuf_new_for_agent_query();
+ put_byte(request, SSH2_AGENTC_REQUEST_IDENTITIES);
+ ssh2_userauth_agent_query(s, request);
+ strbuf_free(request);
+ crWaitUntilV(!s->auth_agent_query);
+ }
+ BinarySource_BARE_INIT_PL(s->asrc, s->agent_response);
+
+ get_uint32(s->asrc); /* skip length field */
+ if (get_byte(s->asrc) == SSH2_AGENT_IDENTITIES_ANSWER) {
+ size_t nkeys = get_uint32(s->asrc);
+ size_t origpos = s->asrc->pos;
+
+ /*
+ * Check that the agent response is well formed.
+ */
+ for (size_t i = 0; i < nkeys; i++) {
+ get_string(s->asrc); /* blob */
+ get_string(s->asrc); /* comment */
+ if (get_err(s->asrc)) {
+ ppl_logevent("Pageant's response was truncated");
+ goto done_agent_query;
+ }
+ }
+
+ /*
+ * Copy the list of public-key blobs out of the Pageant
+ * response.
+ */
+ BinarySource_REWIND_TO(s->asrc, origpos);
+ s->agent_keys_len = nkeys;
+ s->agent_keys = snewn(s->agent_keys_len, agent_key);
+ for (size_t i = 0; i < nkeys; i++) {
+ s->agent_keys[i].blob = strbuf_dup(get_string(s->asrc));
+ s->agent_keys[i].comment = strbuf_dup(get_string(s->asrc));
+
+ /* Also, extract the algorithm string from the start
+ * of the public-key blob. */
+ s->agent_keys[i].algorithm = pubkey_blob_to_alg_name(
+ ptrlen_from_strbuf(s->agent_keys[i].blob));
+ }
+
+ ppl_logevent("Pageant has %"SIZEu" SSH-2 keys", nkeys);
+
+ if (s->publickey_blob) {
+ /*
+ * If we've been given a specific public key blob,
+ * filter the list of keys to try from the agent down
+ * to only that one, or none if it's not there.
+ */
+ ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob);
+ size_t i;
+
+ for (i = 0; i < nkeys; i++) {
+ if (ptrlen_eq_ptrlen(our_blob, ptrlen_from_strbuf(
+ s->agent_keys[i].blob)))
+ break;
+ }
+
+ if (i < nkeys) {
+ ppl_logevent("Pageant key #%"SIZEu" matches "
+ "configured key file", i);
+ s->agent_key_index = i;
+ s->agent_key_limit = i+1;
+ } else {
+ ppl_logevent("Configured key file not in Pageant");
+ s->agent_key_index = 0;
+ s->agent_key_limit = 0;
+ }
+ } else {
+ /*
+ * Otherwise, try them all.
+ */
+ s->agent_key_index = 0;
+ s->agent_key_limit = nkeys;
+ }
+ } else {
+ ppl_logevent("Failed to get reply from Pageant");
+ }
+ done_agent_query:;
+ }
+
+ s->got_username = false;
+
+ if (*s->authplugin_cmd) {
+ s->authplugin_plug.vt = &authplugin_plugvt;
+ s->authplugin = platform_start_subprocess(
+ s->authplugin_cmd, &s->authplugin_plug, "plugin");
+ ppl_logevent("Started authentication plugin: %s", s->authplugin_cmd);
+ }
+
+ if (s->authplugin) {
+ strbuf *amsg = authplugin_newmsg(PLUGIN_INIT);
+ put_uint32(amsg, PLUGIN_PROTOCOL_MAX_VERSION);
+ put_stringz(amsg, s->hostname);
+ put_uint32(amsg, s->port);
+ put_stringz(amsg, s->username ? s->username : "");
+ authplugin_send_free(s, amsg);
+
+ BinarySource src[1];
+ unsigned type;
+ crMaybeWaitUntilV(authplugin_expect_msg(s, &type, src));
+ switch (type) {
+ case PLUGIN_INIT_RESPONSE: {
+ s->authplugin_version = get_uint32(src);
+ ptrlen username = get_string(src);
+ if (get_err(src)) {
+ ssh_sw_abort(s->ppl.ssh, "Received malformed "
+ "PLUGIN_INIT_RESPONSE from auth helper plugin");
+ return;
+ }
+ if (s->authplugin_version > PLUGIN_PROTOCOL_MAX_VERSION) {
+ ssh_sw_abort(s->ppl.ssh, "Auth helper plugin announced "
+ "unsupported version number %"PRIu32,
+ s->authplugin_version);
+ return;
+ }
+ if (username.len) {
+ sfree(s->default_username);
+ s->default_username = mkstr(username);
+ ppl_logevent("Authentication plugin set username '%s'",
+ s->default_username);
+ }
+ break;
+ }
+ case PLUGIN_INIT_FAILURE: {
+ ptrlen message = get_string(src);
+ if (get_err(src)) {
+ ssh_sw_abort(s->ppl.ssh, "Received malformed "
+ "PLUGIN_INIT_FAILURE from auth helper plugin");
+ return;
+ }
+ /* This is a controlled error, so we need not completely
+ * abandon the connection. Instead, inform the user, and
+ * proceed as if the plugin was not present */
+ ppl_printf("Authentication plugin failed to initialise:\r\n");
+ seat_set_trust_status(s->ppl.seat, false);
+ ppl_printf("%.*s\r\n", PTRLEN_PRINTF(message));
+ seat_set_trust_status(s->ppl.seat, true);
+ sk_close(s->authplugin);
+ s->authplugin = NULL;
+ break;
+ }
+ default:
+ authplugin_bad_packet(s, type, "expected PLUGIN_INIT_RESPONSE or "
+ "PLUGIN_INIT_FAILURE");
+ return;
+ }
+ }
+
+ /*
+ * We repeat this whole loop, including the username prompt,
+ * until we manage a successful authentication. If the user
+ * types the wrong _password_, they can be sent back to the
+ * beginning to try another username, if this is configured on.
+ * (If they specify a username in the config, they are never
+ * asked, even if they do give a wrong password.)
+ *
+ * I think this best serves the needs of
+ *
+ * - the people who have no configuration, no keys, and just
+ * want to try repeated (username,password) pairs until they
+ * type both correctly
+ *
+ * - people who have keys and configuration but occasionally
+ * need to fall back to passwords
+ *
+ * - people with a key held in Pageant, who might not have
+ * logged in to a particular machine before; so they want to
+ * type a username, and then _either_ their key will be
+ * accepted, _or_ they will type a password. If they mistype
+ * the username they will want to be able to get back and
+ * retype it!
+ */
+ while (1) {
+ /*
+ * Get a username.
+ */
+ if (s->got_username && !s->change_username) {
+ /*
+ * We got a username last time round this loop, and
+ * with change_username turned off we don't try to get
+ * it again.
+ */
+ } else if ((s->username = s->default_username) == NULL) {
+ s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+ s->cur_prompt->to_server = true;
+ s->cur_prompt->from_server = false;
+ s->cur_prompt->name = dupstr("SSH login name");
+ add_prompt(s->cur_prompt, dupstr("login as: "), true);
+ s->spr = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->cur_prompt);
+ while (s->spr.kind == SPRK_INCOMPLETE) {
+ crReturnV;
+ s->spr = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->cur_prompt);
+ }
+ if (spr_is_abort(s->spr)) {
+ /*
+ * seat_get_userpass_input() failed to get a username.
+ * Terminate.
+ */
+ free_prompts(s->cur_prompt);
+ s->cur_prompt = NULL;
+ ssh_spr_close(s->ppl.ssh, s->spr, "username prompt");
+ return;
+ }
+ sfree(s->locally_allocated_username); /* for change_username */
+ s->username = s->locally_allocated_username =
+ prompt_get_result(s->cur_prompt->prompts[0]);
+ free_prompts(s->cur_prompt);
+ s->cur_prompt = NULL;
+ } else {
+ if (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))
+ ppl_printf("Using username \"%s\".\r\n", s->username);
+ }
+ s->got_username = true;
+
+ /*
+ * Send an authentication request using method "none": (a)
+ * just in case it succeeds, and (b) so that we know what
+ * authentication methods we can usefully try next.
+ */
+ s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH;
+
+ s->pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+ put_stringz(s->pktout, s->username);
+ put_stringz(s->pktout, s->successor_layer->vt->name);
+ put_stringz(s->pktout, "none"); /* method */
+ pq_push(s->ppl.out_pq, s->pktout);
+ s->type = AUTH_TYPE_NONE;
+
+ s->tried_pubkey_config = false;
+ s->kbd_inter_refused = false;
+ s->done_agent = false;
+
+ while (1) {
+ /*
+ * Wait for the result of the last authentication request,
+ * unless the request terminated for some reason on our
+ * own side.
+ */
+ if (s->suppress_wait_for_response_packet) {
+ pktin = NULL;
+ s->suppress_wait_for_response_packet = false;
+ } else {
+ crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+ }
+
+ /*
+ * Now is a convenient point to spew any banner material
+ * that we've accumulated. (This should ensure that when
+ * we exit the auth loop, we haven't any left to deal
+ * with.)
+ *
+ * Don't show the banner if we're operating in non-verbose
+ * non-interactive mode. (It's probably a script, which
+ * means nobody will read the banner _anyway_, and
+ * moreover the printing of the banner will screw up
+ * processing on the output of (say) plink.)
+ *
+ * The banner data has been sanitised already by this
+ * point, but we still need to precede and follow it with
+ * anti-spoofing header lines.
+ */
+ if (bufchain_size(&s->banner) &&
+ (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))) {
+ if (s->banner_scc) {
+ seat_antispoof_msg(
+ ppl_get_iseat(&s->ppl),
+ "Pre-authentication banner message from server:");
+ seat_set_trust_status(s->ppl.seat, false);
+ }
+
+ bool mid_line = false;
+ while (bufchain_size(&s->banner) > 0) {
+ ptrlen data = bufchain_prefix(&s->banner);
+ seat_banner_pl(ppl_get_iseat(&s->ppl), data);
+ mid_line =
+ (((const char *)data.ptr)[data.len-1] != '\n');
+ bufchain_consume(&s->banner, data.len);
+ }
+ bufchain_clear(&s->banner);
+
+ if (mid_line)
+ seat_banner_pl(ppl_get_iseat(&s->ppl),
+ PTRLEN_LITERAL("\r\n"));
+
+ if (s->banner_scc) {
+ seat_set_trust_status(s->ppl.seat, true);
+ seat_antispoof_msg(ppl_get_iseat(&s->ppl),
+ "End of banner message from server");
+ }
+ }
+
+ if (pktin && pktin->type == SSH2_MSG_USERAUTH_SUCCESS) {
+ ppl_logevent("Access granted");
+ goto userauth_success;
+ }
+
+ if (pktin && pktin->type != SSH2_MSG_USERAUTH_FAILURE &&
+ s->type != AUTH_TYPE_GSSAPI) {
+ ssh_proto_error(s->ppl.ssh, "Received unexpected packet "
+ "in response to authentication request, "
+ "type %d (%s)", pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ return;
+ }
+
+ /*
+ * OK, we're now sitting on a USERAUTH_FAILURE message, so
+ * we can look at the string in it and know what we can
+ * helpfully try next.
+ */
+ if (pktin && pktin->type == SSH2_MSG_USERAUTH_FAILURE) {
+ ptrlen methods = get_string(pktin);
+ bool partial_success = get_bool(pktin);
+
+ if (!partial_success) {
+ /*
+ * We have received an unequivocal Access
+ * Denied. This can translate to a variety of
+ * messages, or no message at all.
+ *
+ * For forms of authentication which are attempted
+ * implicitly, by which I mean without printing
+ * anything in the window indicating that we're
+ * trying them, we should never print 'Access
+ * denied'.
+ *
+ * If we do print a message saying that we're
+ * attempting some kind of authentication, it's OK
+ * to print a followup message saying it failed -
+ * but the message may sometimes be more specific
+ * than simply 'Access denied'.
+ *
+ * Additionally, if we'd just tried password
+ * authentication, we should break out of this
+ * whole loop so as to go back to the username
+ * prompt (iff we're configured to allow
+ * username change attempts).
+ */
+ if (s->type == AUTH_TYPE_NONE) {
+ /* do nothing */
+ } else if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD ||
+ s->type == AUTH_TYPE_PUBLICKEY_OFFER_QUIET) {
+ if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD)
+ ppl_printf("Server refused our key\r\n");
+ ppl_logevent("Server refused our key");
+ } else if (s->type == AUTH_TYPE_PUBLICKEY) {
+ /* This _shouldn't_ happen except by a
+ * protocol bug causing client and server to
+ * disagree on what is a correct signature. */
+ ppl_printf("Server refused public-key signature"
+ " despite accepting key!\r\n");
+ ppl_logevent("Server refused public-key signature"
+ " despite accepting key!");
+ } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) {
+ /* quiet, so no ppl_printf */
+ ppl_logevent("Server refused keyboard-interactive "
+ "authentication");
+ } else if (s->type==AUTH_TYPE_GSSAPI) {
+ /* always quiet, so no ppl_printf */
+ /* also, the code down in the GSSAPI block has
+ * already logged this in the Event Log */
+ } else if (s->type == AUTH_TYPE_KEYBOARD_INTERACTIVE) {
+ ppl_logevent("Keyboard-interactive authentication "
+ "failed");
+ ppl_printf("Access denied\r\n");
+ } else {
+ assert(s->type == AUTH_TYPE_PASSWORD);
+ ppl_logevent("Password authentication failed");
+ ppl_printf("Access denied\r\n");
+
+ if (s->change_username) {
+ /* XXX perhaps we should allow
+ * keyboard-interactive to do this too? */
+ goto try_new_username;
+ }
+ }
+ } else {
+ ppl_printf("Further authentication required\r\n");
+ ppl_logevent("Further authentication required");
+ }
+
+ /*
+ * Save the methods string for use in error messages.
+ */
+ strbuf_clear(s->last_methods_string);
+ put_datapl(s->last_methods_string, methods);
+
+ /*
+ * Scan it for method identifiers we know about.
+ */
+ bool srv_pubkey = false, srv_passwd = false;
+ bool srv_keyb_inter = false;
+#ifndef NO_GSSAPI
+ bool srv_gssapi = false, srv_gssapi_keyex_auth = false;
+#endif
+
+ for (ptrlen method; get_commasep_word(&methods, &method) ;) {
+ if (ptrlen_eq_string(method, "publickey"))
+ srv_pubkey = true;
+ else if (ptrlen_eq_string(method, "password"))
+ srv_passwd = true;
+ else if (ptrlen_eq_string(method, "keyboard-interactive"))
+ srv_keyb_inter = true;
+#ifndef NO_GSSAPI
+ else if (ptrlen_eq_string(method, "gssapi-with-mic"))
+ srv_gssapi = true;
+ else if (ptrlen_eq_string(method, "gssapi-keyex"))
+ srv_gssapi_keyex_auth = true;
+#endif
+ }
+
+ /*
+ * And combine those flags with our own configuration
+ * and context to set the main can_foo variables.
+ */
+ s->can_pubkey = srv_pubkey;
+ s->can_passwd = srv_passwd;
+ s->can_keyb_inter = s->try_ki_auth && srv_keyb_inter;
+#ifndef NO_GSSAPI
+ s->can_gssapi = s->try_gssapi_auth && srv_gssapi &&
+ s->shgss->libs->nlibraries > 0;
+ s->can_gssapi_keyex_auth = s->try_gssapi_kex_auth &&
+ srv_gssapi_keyex_auth &&
+ s->shgss->libs->nlibraries > 0 && s->shgss->ctx;
+#endif
+ }
+
+ s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH;
+
+#ifndef NO_GSSAPI
+ if (s->can_gssapi_keyex_auth && !s->tried_gssapi_keyex_auth) {
+
+ /* gssapi-keyex authentication */
+
+ s->type = AUTH_TYPE_GSSAPI;
+ s->tried_gssapi_keyex_auth = true;
+ s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI;
+
+ if (s->shgss->lib->gsslogmsg)
+ ppl_logevent("%s", s->shgss->lib->gsslogmsg);
+
+ ppl_logevent("Trying gssapi-keyex...");
+ s->pktout = ssh2_userauth_gss_packet(s, "gssapi-keyex");
+ pq_push(s->ppl.out_pq, s->pktout);
+ s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
+ s->shgss->ctx = NULL;
+
+ continue;
+ } else
+#endif /* NO_GSSAPI */
+
+ if (s->can_pubkey && !s->done_agent &&
+ s->agent_key_index < s->agent_key_limit) {
+
+ /*
+ * Attempt public-key authentication using a key from Pageant.
+ */
+ s->agent_keyalg = s->agent_keys[s->agent_key_index].algorithm;
+ char *alg_tmp = mkstr(s->agent_keyalg);
+ const char *newalg = alg_tmp;
+ if (ssh2_userauth_signflags(s, &s->signflags, &newalg))
+ s->agent_keyalg = ptrlen_from_asciz(newalg);
+ sfree(alg_tmp);
+
+ s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY;
+
+ ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index);
+
+ /* See if server will accept it */
+ s->pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+ put_stringz(s->pktout, s->username);
+ put_stringz(s->pktout, s->successor_layer->vt->name);
+ put_stringz(s->pktout, "publickey");
+ /* method */
+ put_bool(s->pktout, false); /* no signature included */
+ ssh2_userauth_add_alg_and_publickey(
+ s, s->pktout, s->agent_keyalg, ptrlen_from_strbuf(
+ s->agent_keys[s->agent_key_index].blob));
+ pq_push(s->ppl.out_pq, s->pktout);
+ s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET;
+
+ crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
+
+ /* Offer of key refused, presumably via
+ * USERAUTH_FAILURE. Requeue for the next iteration. */
+ pq_push_front(s->ppl.in_pq, pktin);
+
+ } else {
+ strbuf *agentreq, *sigdata;
+ ptrlen comment = ptrlen_from_strbuf(
+ s->agent_keys[s->agent_key_index].comment);
+
+ if (seat_verbose(s->ppl.seat))
+ ppl_printf("Authenticating with public key "
+ "\"%.*s\" from agent\r\n",
+ PTRLEN_PRINTF(comment));
+
+ /*
+ * Server is willing to accept the key.
+ * Construct a SIGN_REQUEST.
+ */
+ s->pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+ put_stringz(s->pktout, s->username);
+ put_stringz(s->pktout, s->successor_layer->vt->name);
+ put_stringz(s->pktout, "publickey");
+ /* method */
+ put_bool(s->pktout, true); /* signature included */
+ ssh2_userauth_add_alg_and_publickey(
+ s, s->pktout, s->agent_keyalg, ptrlen_from_strbuf(
+ s->agent_keys[s->agent_key_index].blob));
+
+ /* Ask agent for signature. */
+ agentreq = strbuf_new_for_agent_query();
+ put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST);
+ put_stringpl(agentreq, ptrlen_from_strbuf(
+ s->agent_keys[s->agent_key_index].blob));
+ /* Now the data to be signed... */
+ sigdata = strbuf_new();
+ ssh2_userauth_add_session_id(s, sigdata);
+ put_data(sigdata, s->pktout->data + 5,
+ s->pktout->length - 5);
+ put_stringsb(agentreq, sigdata);
+ /* And finally the flags word. */
+ put_uint32(agentreq, s->signflags);
+ ssh2_userauth_agent_query(s, agentreq);
+ strbuf_free(agentreq);
+ crWaitUntilV(!s->auth_agent_query);
+
+ if (s->agent_response.ptr) {
+ ptrlen sigblob;
+ BinarySource src[1];
+ BinarySource_BARE_INIT(src, s->agent_response.ptr,
+ s->agent_response.len);
+ get_uint32(src); /* skip length field */
+ if (get_byte(src) == SSH2_AGENT_SIGN_RESPONSE &&
+ (sigblob = get_string(src), !get_err(src))) {
+ ppl_logevent("Sending Pageant's response");
+ ssh2_userauth_add_sigblob(
+ s, s->pktout,
+ ptrlen_from_strbuf(
+ s->agent_keys[s->agent_key_index].blob),
+ sigblob);
+ pq_push(s->ppl.out_pq, s->pktout);
+ s->type = AUTH_TYPE_PUBLICKEY;
+ s->is_trivial_auth = false;
+ } else {
+ ppl_logevent("Pageant refused signing request");
+ ppl_printf("Pageant failed to "
+ "provide a signature\r\n");
+ s->suppress_wait_for_response_packet = true;
+ ssh_free_pktout(s->pktout);
+ }
+ } else {
+ ppl_logevent("Pageant failed to respond to "
+ "signing request");
+ ppl_printf("Pageant failed to "
+ "respond to signing request\r\n");
+ s->suppress_wait_for_response_packet = true;
+ ssh_free_pktout(s->pktout);
+ }
+ }
+
+ /* Do we have any keys left to try? */
+ if (++s->agent_key_index >= s->agent_key_limit)
+ s->done_agent = true;
+
+ } else if (s->can_pubkey && s->publickey_blob &&
+ s->privatekey_available && !s->tried_pubkey_config) {
+
+ ssh2_userkey *key; /* not live over crReturn */
+ char *passphrase; /* not live over crReturn */
+
+ s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY;
+
+ s->tried_pubkey_config = true;
+
+ /*
+ * Try the public key supplied in the configuration.
+ *
+ * First, try to upgrade its algorithm.
+ */
+ const char *newalg = s->publickey_algorithm;
+ if (ssh2_userauth_signflags(s, &s->signflags, &newalg)) {
+ sfree(s->publickey_algorithm);
+ s->publickey_algorithm = dupstr(newalg);
+ }
+
+ /*
+ * Offer the public blob to see if the server is willing to
+ * accept it.
+ */
+ s->pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+ put_stringz(s->pktout, s->username);
+ put_stringz(s->pktout, s->successor_layer->vt->name);
+ put_stringz(s->pktout, "publickey"); /* method */
+ put_bool(s->pktout, false);
+ /* no signature included */
+ ssh2_userauth_add_alg_and_publickey(
+ s, s->pktout, ptrlen_from_asciz(s->publickey_algorithm),
+ ptrlen_from_strbuf(s->publickey_blob));
+ pq_push(s->ppl.out_pq, s->pktout);
+ ppl_logevent("Offered public key");
+
+ crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) {
+ /* Key refused. Give up. */
+ pq_push_front(s->ppl.in_pq, pktin);
+ s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD;
+ continue; /* process this new message */
+ }
+ ppl_logevent("Offer of public key accepted");
+
+ /*
+ * Actually attempt a serious authentication using
+ * the key.
+ */
+ if (seat_verbose(s->ppl.seat))
+ ppl_printf("Authenticating with public key \"%s\"\r\n",
+ s->publickey_comment);
+
+ key = NULL;
+ while (!key) {
+ const char *error; /* not live over crReturn */
+ if (s->privatekey_encrypted) {
+ /*
+ * Get a passphrase from the user.
+ */
+ s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+ s->cur_prompt->to_server = false;
+ s->cur_prompt->from_server = false;
+ s->cur_prompt->name = dupstr("SSH key passphrase");
+ add_prompt(s->cur_prompt,
+ dupprintf("Passphrase for key \"%s\": ",
+ s->publickey_comment),
+ false);
+ s->spr = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->cur_prompt);
+ while (s->spr.kind == SPRK_INCOMPLETE) {
+ crReturnV;
+ s->spr = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->cur_prompt);
+ }
+ if (spr_is_abort(s->spr)) {
+ /* Failed to get a passphrase. Terminate. */
+ free_prompts(s->cur_prompt);
+ s->cur_prompt = NULL;
+ ssh_bpp_queue_disconnect(
+ s->ppl.bpp, "Unable to authenticate",
+ SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
+ ssh_spr_close(s->ppl.ssh, s->spr,
+ "passphrase prompt");
+ return;
+ }
+ passphrase =
+ prompt_get_result(s->cur_prompt->prompts[0]);
+ free_prompts(s->cur_prompt);
+ s->cur_prompt = NULL;
+ } else {
+ passphrase = NULL; /* no passphrase needed */
+ }
+
+ /*
+ * Try decrypting the key.
+ */
+ key = ppk_load_f(s->keyfile, passphrase, &error);
+ if (passphrase) {
+ /* burn the evidence */
+ smemclr(passphrase, strlen(passphrase));
+ sfree(passphrase);
+ }
+ if (key == SSH2_WRONG_PASSPHRASE || key == NULL) {
+ if (passphrase &&
+ (key == SSH2_WRONG_PASSPHRASE)) {
+ ppl_printf("Wrong passphrase\r\n");
+ key = NULL;
+ /* and loop again */
+ } else {
+ ppl_printf("Unable to load private key (%s)\r\n",
+ error);
+ key = NULL;
+ s->suppress_wait_for_response_packet = true;
+ break; /* try something else */
+ }
+ } else {
+ /* FIXME: if we ever support variable signature
+ * flags, this is somewhere they'll need to be
+ * put */
+ char *invalid = ssh_key_invalid(key->key, 0);
+ if (invalid) {
+ ppl_printf("Cannot use this private key (%s)\r\n",
+ invalid);
+ ssh_key_free(key->key);
+ sfree(key->comment);
+ sfree(key);
+ sfree(invalid);
+ key = NULL;
+ s->suppress_wait_for_response_packet = true;
+ break; /* try something else */
+ }
+ }
+ }
+
+ if (key) {
+ strbuf *pkblob, *sigdata, *sigblob;
+
+ /*
+ * We have loaded the private key and the server
+ * has announced that it's willing to accept it.
+ * Hallelujah. Generate a signature and send it.
+ */
+ s->pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+ put_stringz(s->pktout, s->username);
+ put_stringz(s->pktout, s->successor_layer->vt->name);
+ put_stringz(s->pktout, "publickey"); /* method */
+ put_bool(s->pktout, true); /* signature follows */
+ pkblob = strbuf_new();
+ ssh_key_public_blob(key->key, BinarySink_UPCAST(pkblob));
+ ssh2_userauth_add_alg_and_publickey(
+ s, s->pktout,
+ ptrlen_from_asciz(s->publickey_algorithm),
+ ptrlen_from_strbuf(pkblob));
+
+ /*
+ * The data to be signed is:
+ *
+ * string session-id
+ *
+ * followed by everything so far placed in the
+ * outgoing packet.
+ */
+ sigdata = strbuf_new();
+ ssh2_userauth_add_session_id(s, sigdata);
+ put_data(sigdata, s->pktout->data + 5,
+ s->pktout->length - 5);
+ sigblob = strbuf_new();
+ ssh_key_sign(key->key, ptrlen_from_strbuf(sigdata),
+ s->signflags, BinarySink_UPCAST(sigblob));
+ strbuf_free(sigdata);
+ ssh2_userauth_add_sigblob(
+ s, s->pktout, ptrlen_from_strbuf(pkblob),
+ ptrlen_from_strbuf(sigblob));
+ strbuf_free(pkblob);
+ strbuf_free(sigblob);
+
+ pq_push(s->ppl.out_pq, s->pktout);
+ ppl_logevent("Sent public key signature");
+ s->type = AUTH_TYPE_PUBLICKEY;
+ ssh_key_free(key->key);
+ sfree(key->comment);
+ sfree(key);
+ s->is_trivial_auth = false;
+ }
+
+#ifndef NO_GSSAPI
+ } else if (s->can_gssapi && !s->tried_gssapi) {
+
+ /* gssapi-with-mic authentication */
+
+ ptrlen data;
+
+ s->type = AUTH_TYPE_GSSAPI;
+ s->tried_gssapi = true;
+ s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI;
+
+ if (s->shgss->lib->gsslogmsg)
+ ppl_logevent("%s", s->shgss->lib->gsslogmsg);
+
+ /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */
+ ppl_logevent("Trying gssapi-with-mic...");
+ s->pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+ put_stringz(s->pktout, s->username);
+ put_stringz(s->pktout, s->successor_layer->vt->name);
+ put_stringz(s->pktout, "gssapi-with-mic");
+ ppl_logevent("Attempting GSSAPI authentication");
+
+ /* add mechanism info */
+ s->shgss->lib->indicate_mech(s->shgss->lib, &s->gss_buf);
+
+ /* number of GSSAPI mechanisms */
+ put_uint32(s->pktout, 1);
+
+ /* length of OID + 2 */
+ put_uint32(s->pktout, s->gss_buf.length + 2);
+ put_byte(s->pktout, SSH2_GSS_OIDTYPE);
+
+ /* length of OID */
+ put_byte(s->pktout, s->gss_buf.length);
+
+ put_data(s->pktout, s->gss_buf.value, s->gss_buf.length);
+ pq_push(s->ppl.out_pq, s->pktout);
+ crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) {
+ ppl_logevent("GSSAPI authentication request refused");
+ pq_push_front(s->ppl.in_pq, pktin);
+ continue;
+ }
+
+ /* check returned packet ... */
+
+ data = get_string(pktin);
+ s->gss_rcvtok.value = (char *)data.ptr;
+ s->gss_rcvtok.length = data.len;
+ if (s->gss_rcvtok.length != s->gss_buf.length + 2 ||
+ ((char *)s->gss_rcvtok.value)[0] != SSH2_GSS_OIDTYPE ||
+ ((char *)s->gss_rcvtok.value)[1] != s->gss_buf.length ||
+ memcmp((char *)s->gss_rcvtok.value + 2,
+ s->gss_buf.value,s->gss_buf.length) ) {
+ ppl_logevent("GSSAPI authentication - wrong response "
+ "from server");
+ continue;
+ }
+
+ /* Import server name if not cached from KEX */
+ if (s->shgss->srv_name == GSS_C_NO_NAME) {
+ s->gss_stat = s->shgss->lib->import_name(
+ s->shgss->lib, s->fullhostname, &s->shgss->srv_name);
+ if (s->gss_stat != SSH_GSS_OK) {
+ if (s->gss_stat == SSH_GSS_BAD_HOST_NAME)
+ ppl_logevent("GSSAPI import name failed -"
+ " Bad service name");
+ else
+ ppl_logevent("GSSAPI import name failed");
+ continue;
+ }
+ }
+
+ /* Allocate our gss_ctx */
+ s->gss_stat = s->shgss->lib->acquire_cred(
+ s->shgss->lib, &s->shgss->ctx, NULL);
+ if (s->gss_stat != SSH_GSS_OK) {
+ ppl_logevent("GSSAPI authentication failed to get "
+ "credentials");
+ /* The failure was on our side, so the server
+ * won't be sending a response packet indicating
+ * failure. Avoid waiting for it next time round
+ * the loop. */
+ s->suppress_wait_for_response_packet = true;
+ continue;
+ }
+
+ /* initial tokens are empty */
+ SSH_GSS_CLEAR_BUF(&s->gss_rcvtok);
+ SSH_GSS_CLEAR_BUF(&s->gss_sndtok);
+
+ /* now enter the loop */
+ do {
+ /*
+ * When acquire_cred yields no useful expiration, go with
+ * the service ticket expiration.
+ */
+ s->gss_stat = s->shgss->lib->init_sec_context(
+ s->shgss->lib,
+ &s->shgss->ctx,
+ s->shgss->srv_name,
+ s->gssapi_fwd,
+ &s->gss_rcvtok,
+ &s->gss_sndtok,
+ NULL,
+ NULL);
+
+ if (s->gss_stat!=SSH_GSS_S_COMPLETE &&
+ s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) {
+ ppl_logevent("GSSAPI authentication initialisation "
+ "failed");
+
+ if (s->shgss->lib->display_status(
+ s->shgss->lib, s->shgss->ctx, &s->gss_buf)
+ == SSH_GSS_OK) {
+ ppl_logevent("%s", (char *)s->gss_buf.value);
+ sfree(s->gss_buf.value);
+ }
+
+ pq_push_front(s->ppl.in_pq, pktin);
+ break;
+ }
+ ppl_logevent("GSSAPI authentication initialised");
+
+ /*
+ * Client and server now exchange tokens until GSSAPI
+ * no longer says CONTINUE_NEEDED
+ */
+ if (s->gss_sndtok.length != 0) {
+ s->is_trivial_auth = false;
+ s->pktout =
+ ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_TOKEN);
+ put_string(s->pktout,
+ s->gss_sndtok.value, s->gss_sndtok.length);
+ pq_push(s->ppl.out_pq, s->pktout);
+ s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok);
+ }
+
+ if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) {
+ crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+
+ if (pktin->type == SSH2_MSG_USERAUTH_GSSAPI_ERRTOK) {
+ /*
+ * Per RFC 4462 section 3.9, this packet
+ * type MUST immediately precede an
+ * ordinary USERAUTH_FAILURE.
+ *
+ * We currently don't know how to do
+ * anything with the GSSAPI error token
+ * contained in this packet, so we ignore
+ * it and just wait for the following
+ * FAILURE.
+ */
+ crMaybeWaitUntilV(
+ (pktin = ssh2_userauth_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_USERAUTH_FAILURE) {
+ ssh_proto_error(
+ s->ppl.ssh, "Received unexpected packet "
+ "after SSH_MSG_USERAUTH_GSSAPI_ERRTOK "
+ "(expected SSH_MSG_USERAUTH_FAILURE): "
+ "type %d (%s)", pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx,
+ pktin->type));
+ return;
+ }
+ }
+
+ if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) {
+ ppl_logevent("GSSAPI authentication failed");
+ s->gss_stat = SSH_GSS_FAILURE;
+ pq_push_front(s->ppl.in_pq, pktin);
+ break;
+ } else if (pktin->type !=
+ SSH2_MSG_USERAUTH_GSSAPI_TOKEN) {
+ ppl_logevent("GSSAPI authentication -"
+ " bad server response");
+ s->gss_stat = SSH_GSS_FAILURE;
+ break;
+ }
+ data = get_string(pktin);
+ s->gss_rcvtok.value = (char *)data.ptr;
+ s->gss_rcvtok.length = data.len;
+ }
+ } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED);
+
+ if (s->gss_stat != SSH_GSS_OK) {
+ s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
+ continue;
+ }
+ ppl_logevent("GSSAPI authentication loop finished OK");
+
+ /* Now send the MIC */
+
+ s->pktout = ssh2_userauth_gss_packet(s, "gssapi-with-mic");
+ pq_push(s->ppl.out_pq, s->pktout);
+
+ s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
+ continue;
+#endif
+ } else if (s->can_keyb_inter && !s->kbd_inter_refused) {
+
+ /*
+ * Keyboard-interactive authentication.
+ */
+
+ s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+
+ s->ppl.bpp->pls->actx = SSH2_PKTCTX_KBDINTER;
+
+ s->pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+ put_stringz(s->pktout, s->username);
+ put_stringz(s->pktout, s->successor_layer->vt->name);
+ put_stringz(s->pktout, "keyboard-interactive");
+ /* method */
+ put_stringz(s->pktout, ""); /* lang */
+ put_stringz(s->pktout, ""); /* submethods */
+ pq_push(s->ppl.out_pq, s->pktout);
+
+ ppl_logevent("Attempting keyboard-interactive authentication");
+
+ if (s->authplugin) {
+ strbuf *amsg = authplugin_newmsg(PLUGIN_PROTOCOL);
+ put_stringz(amsg, "keyboard-interactive");
+ authplugin_send_free(s, amsg);
+
+ BinarySource src[1];
+ unsigned type;
+ crMaybeWaitUntilV(authplugin_expect_msg(s, &type, src));
+ switch (type) {
+ case PLUGIN_PROTOCOL_REJECT: {
+ ptrlen message = PTRLEN_LITERAL("");
+ if (s->authplugin_version >= 2) {
+ /* draft protocol didn't include a message here */
+ message = get_string(src);
+ }
+ if (get_err(src)) {
+ ssh_sw_abort(s->ppl.ssh, "Received malformed "
+ "PLUGIN_PROTOCOL_REJECT from auth "
+ "helper plugin");
+ return;
+ }
+ if (message.len) {
+ /* If the plugin sent a message about
+ * _why_ it didn't want to do k-i, pass
+ * that message on to the user. (It might
+ * say, for example, what went wrong when
+ * it tried to open its config file.) */
+ ppl_printf("Authentication plugin failed to set "
+ "up keyboard-interactive "
+ "authentication:\r\n");
+ seat_set_trust_status(s->ppl.seat, false);
+ ppl_printf("%.*s\r\n", PTRLEN_PRINTF(message));
+ seat_set_trust_status(s->ppl.seat, true);
+ ppl_logevent("Authentication plugin declined to "
+ "help with keyboard-interactive: "
+ "%.*s", PTRLEN_PRINTF(message));
+ } else {
+ ppl_logevent("Authentication plugin declined to "
+ "help with keyboard-interactive");
+ }
+ s->authplugin_ki_active = false;
+ break;
+ }
+ case PLUGIN_PROTOCOL_ACCEPT:
+ s->authplugin_ki_active = true;
+ ppl_logevent("Authentication plugin agreed to help "
+ "with keyboard-interactive");
+ break;
+ default:
+ authplugin_bad_packet(
+ s, type, "expected PLUGIN_PROTOCOL_ACCEPT or "
+ "PLUGIN_PROTOCOL_REJECT");
+ return;
+ }
+ } else {
+ s->authplugin_ki_active = false;
+ }
+
+ if (!s->ki_scc_initialised) {
+ s->ki_scc = seat_stripctrl_new(
+ s->ppl.seat, NULL, SIC_KI_PROMPTS);
+ if (s->ki_scc)
+ stripctrl_enable_line_limiting(s->ki_scc);
+ s->ki_scc_initialised = true;
+ }
+
+ crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
+ /* Server is not willing to do keyboard-interactive
+ * at all (or, bizarrely but legally, accepts the
+ * user without actually issuing any prompts).
+ * Give up on it entirely. */
+ pq_push_front(s->ppl.in_pq, pktin);
+ s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET;
+ s->kbd_inter_refused = true; /* don't try it again */
+ continue;
+ }
+
+ s->ki_printed_header = false;
+
+ /*
+ * Loop while we still have prompts to send to the user.
+ */
+ if (!s->authplugin_ki_active) {
+ /*
+ * The simple case: INFO_REQUESTs are passed on to
+ * the user, and responses are sent straight back
+ * to the SSH server.
+ */
+ while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
+ if (!ssh2_userauth_ki_setup_prompts(
+ s, BinarySource_UPCAST(pktin), false))
+ return;
+ crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s));
+
+ if (spr_is_abort(s->spr)) {
+ /*
+ * Failed to get responses. Terminate.
+ */
+ free_prompts(s->cur_prompt);
+ s->cur_prompt = NULL;
+ ssh_bpp_queue_disconnect(
+ s->ppl.bpp, "Unable to authenticate",
+ SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
+ ssh_spr_close(s->ppl.ssh, s->spr, "keyboard-"
+ "interactive authentication prompt");
+ return;
+ }
+
+ /*
+ * Send the response(s) to the server.
+ */
+ s->pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE);
+ ssh2_userauth_ki_write_responses(
+ s, BinarySink_UPCAST(s->pktout));
+ s->pktout->minlen = 256;
+ pq_push(s->ppl.out_pq, s->pktout);
+
+ /*
+ * Get the next packet in case it's another
+ * INFO_REQUEST.
+ */
+ crMaybeWaitUntilV(
+ (pktin = ssh2_userauth_pop(s)) != NULL);
+ }
+ } else {
+ /*
+ * The case where a plugin is involved:
+ * INFO_REQUEST from the server is sent to the
+ * plugin, which sends responses that we hand back
+ * to the server. But in the meantime, the plugin
+ * might send USER_REQUEST for us to pass to the
+ * user, and then we send responses to that.
+ */
+ while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
+ strbuf *amsg = authplugin_newmsg(
+ PLUGIN_KI_SERVER_REQUEST);
+ put_datapl(amsg, get_data(pktin, get_avail(pktin)));
+ authplugin_send_free(s, amsg);
+
+ BinarySource src[1];
+ unsigned type;
+ while (true) {
+ crMaybeWaitUntilV(authplugin_expect_msg(
+ s, &type, src));
+ if (type != PLUGIN_KI_USER_REQUEST)
+ break;
+
+ if (!ssh2_userauth_ki_setup_prompts(s, src, true))
+ return;
+ crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s));
+
+ if (spr_is_abort(s->spr)) {
+ /*
+ * Failed to get responses. Terminate.
+ */
+ free_prompts(s->cur_prompt);
+ s->cur_prompt = NULL;
+ ssh_bpp_queue_disconnect(
+ s->ppl.bpp, "Unable to authenticate",
+ SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
+ ssh_spr_close(
+ s->ppl.ssh, s->spr, "keyboard-"
+ "interactive authentication prompt");
+ return;
+ }
+
+ /*
+ * Send the responses on to the plugin.
+ */
+ strbuf *amsg = authplugin_newmsg(
+ PLUGIN_KI_USER_RESPONSE);
+ ssh2_userauth_ki_write_responses(
+ s, BinarySink_UPCAST(amsg));
+ authplugin_send_free(s, amsg);
+ }
+
+ if (type != PLUGIN_KI_SERVER_RESPONSE) {
+ authplugin_bad_packet(
+ s, type, "expected PLUGIN_KI_SERVER_RESPONSE "
+ "or PLUGIN_PROTOCOL_USER_REQUEST");
+ return;
+ }
+
+ s->pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE);
+ put_datapl(s->pktout, get_data(src, get_avail(src)));
+ s->pktout->minlen = 256;
+ pq_push(s->ppl.out_pq, s->pktout);
+
+ /*
+ * Get the next packet in case it's another
+ * INFO_REQUEST.
+ */
+ crMaybeWaitUntilV(
+ (pktin = ssh2_userauth_pop(s)) != NULL);
+ }
+ }
+
+ /*
+ * Print our trailer line, if we printed a header.
+ */
+ if (s->ki_printed_header) {
+ seat_set_trust_status(s->ppl.seat, true);
+ seat_antispoof_msg(
+ ppl_get_iseat(&s->ppl),
+ (s->authplugin_ki_active ?
+ "End of keyboard-interactive prompts from plugin" :
+ "End of keyboard-interactive prompts from server"));
+ }
+
+ /*
+ * We should have SUCCESS or FAILURE now.
+ */
+ pq_push_front(s->ppl.in_pq, pktin);
+
+ if (s->authplugin_ki_active) {
+ /*
+ * As our last communication with the plugin, tell
+ * it whether the k-i authentication succeeded.
+ */
+ int plugin_msg = -1;
+ if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) {
+ plugin_msg = PLUGIN_AUTH_SUCCESS;
+ } else if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) {
+ /*
+ * Peek in the failure packet to see if it's a
+ * partial success.
+ */
+ BinarySource src[1];
+ BinarySource_BARE_INIT(
+ src, get_ptr(pktin), get_avail(pktin));
+ get_string(pktin); /* skip methods */
+ bool partial_success = get_bool(pktin);
+ if (!get_err(src)) {
+ plugin_msg = partial_success ?
+ PLUGIN_AUTH_SUCCESS : PLUGIN_AUTH_FAILURE;
+ }
+ }
+
+ if (plugin_msg >= 0) {
+ strbuf *amsg = authplugin_newmsg(plugin_msg);
+ authplugin_send_free(s, amsg);
+
+ /* Wait until we've actually sent it, in case
+ * we close the connection to the plugin
+ * before that outgoing message has left our
+ * own buffers */
+ crMaybeWaitUntilV(s->authplugin_backlog == 0);
+ }
+ }
+ } else if (s->can_passwd) {
+ s->is_trivial_auth = false;
+ /*
+ * Plain old password authentication.
+ */
+ bool changereq_first_time; /* not live over crReturn */
+
+ s->ppl.bpp->pls->actx = SSH2_PKTCTX_PASSWORD;
+
+ s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+ s->cur_prompt->to_server = true;
+ s->cur_prompt->from_server = false;
+ s->cur_prompt->name = dupstr("SSH password");
+ add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
+ s->username, s->hostname),
+ false);
+
+ s->spr = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->cur_prompt);
+ while (s->spr.kind == SPRK_INCOMPLETE) {
+ crReturnV;
+ s->spr = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->cur_prompt);
+ }
+ if (spr_is_abort(s->spr)) {
+ /*
+ * Failed to get responses. Terminate.
+ */
+ free_prompts(s->cur_prompt);
+ s->cur_prompt = NULL;
+ ssh_bpp_queue_disconnect(
+ s->ppl.bpp, "Unable to authenticate",
+ SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
+ ssh_spr_close(s->ppl.ssh, s->spr, "password prompt");
+ return;
+ }
+ /*
+ * Squirrel away the password. (We may need it later if
+ * asked to change it.)
+ */
+ s->password = prompt_get_result(s->cur_prompt->prompts[0]);
+ free_prompts(s->cur_prompt);
+ s->cur_prompt = NULL;
+
+ /*
+ * Send the password packet.
+ *
+ * We pad out the password packet to 256 bytes to make
+ * it harder for an attacker to find the length of the
+ * user's password.
+ *
+ * Anyone using a password longer than 256 bytes
+ * probably doesn't have much to worry about from
+ * people who find out how long their password is!
+ */
+ s->pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+ put_stringz(s->pktout, s->username);
+ put_stringz(s->pktout, s->successor_layer->vt->name);
+ put_stringz(s->pktout, "password");
+ put_bool(s->pktout, false);
+ put_stringz(s->pktout, s->password);
+ s->pktout->minlen = 256;
+ pq_push(s->ppl.out_pq, s->pktout);
+ ppl_logevent("Sent password");
+ s->type = AUTH_TYPE_PASSWORD;
+
+ /*
+ * Wait for next packet, in case it's a password change
+ * request.
+ */
+ crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+ changereq_first_time = true;
+
+ while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) {
+
+ /*
+ * We're being asked for a new password
+ * (perhaps not for the first time).
+ * Loop until the server accepts it.
+ */
+
+ bool got_new = false; /* not live over crReturn */
+ ptrlen prompt; /* not live over crReturn */
+
+ {
+ const char *msg;
+ if (changereq_first_time)
+ msg = "Server requested password change";
+ else
+ msg = "Server rejected new password";
+ ppl_logevent("%s", msg);
+ ppl_printf("%s\r\n", msg);
+ }
+
+ prompt = get_string(pktin);
+
+ s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+ s->cur_prompt->to_server = true;
+ s->cur_prompt->from_server = false;
+ s->cur_prompt->name = dupstr("New SSH password");
+ s->cur_prompt->instruction = mkstr(prompt);
+ s->cur_prompt->instr_reqd = true;
+ /*
+ * There's no explicit requirement in the protocol
+ * for the "old" passwords in the original and
+ * password-change messages to be the same, and
+ * apparently some Cisco kit supports password change
+ * by the user entering a blank password originally
+ * and the real password subsequently, so,
+ * reluctantly, we prompt for the old password again.
+ *
+ * (On the other hand, some servers don't even bother
+ * to check this field.)
+ */
+ add_prompt(s->cur_prompt,
+ dupstr("Current password (blank for previously entered password): "),
+ false);
+ add_prompt(s->cur_prompt, dupstr("Enter new password: "),
+ false);
+ add_prompt(s->cur_prompt, dupstr("Confirm new password: "),
+ false);
+
+ /*
+ * Loop until the user manages to enter the same
+ * password twice.
+ */
+ while (!got_new) {
+ s->spr = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->cur_prompt);
+ while (s->spr.kind == SPRK_INCOMPLETE) {
+ crReturnV;
+ s->spr = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->cur_prompt);
+ }
+ if (spr_is_abort(s->spr)) {
+ /*
+ * Failed to get responses. Terminate.
+ */
+ /* burn the evidence */
+ free_prompts(s->cur_prompt);
+ s->cur_prompt = NULL;
+ smemclr(s->password, strlen(s->password));
+ sfree(s->password);
+ ssh_bpp_queue_disconnect(
+ s->ppl.bpp, "Unable to authenticate",
+ SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
+ ssh_spr_close(s->ppl.ssh, s->spr,
+ "password-change prompt");
+ return;
+ }
+
+ /*
+ * If the user specified a new original password
+ * (IYSWIM), overwrite any previously specified
+ * one.
+ * (A side effect is that the user doesn't have to
+ * re-enter it if they louse up the new password.)
+ */
+ if (s->cur_prompt->prompts[0]->result->s[0]) {
+ smemclr(s->password, strlen(s->password));
+ /* burn the evidence */
+ sfree(s->password);
+ s->password = prompt_get_result(
+ s->cur_prompt->prompts[0]);
+ }
+
+ /*
+ * Check the two new passwords match.
+ */
+ got_new = !strcmp(
+ prompt_get_result_ref(s->cur_prompt->prompts[1]),
+ prompt_get_result_ref(s->cur_prompt->prompts[2]));
+ if (!got_new)
+ /* They don't. Silly user. */
+ ppl_printf("Passwords do not match\r\n");
+
+ }
+
+ /*
+ * Send the new password (along with the old one).
+ * (see above for padding rationale)
+ */
+ s->pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+ put_stringz(s->pktout, s->username);
+ put_stringz(s->pktout, s->successor_layer->vt->name);
+ put_stringz(s->pktout, "password");
+ put_bool(s->pktout, true);
+ put_stringz(s->pktout, s->password);
+ put_stringz(s->pktout, prompt_get_result_ref(
+ s->cur_prompt->prompts[1]));
+ free_prompts(s->cur_prompt);
+ s->cur_prompt = NULL;
+ s->pktout->minlen = 256;
+ pq_push(s->ppl.out_pq, s->pktout);
+ ppl_logevent("Sent new password");
+
+ /*
+ * Now see what the server has to say about it.
+ * (If it's CHANGEREQ again, it's not happy with the
+ * new password.)
+ */
+ crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
+ changereq_first_time = false;
+
+ }
+
+ /*
+ * We need to reexamine the current pktin at the top
+ * of the loop. Either:
+ * - we weren't asked to change password at all, in
+ * which case it's a SUCCESS or FAILURE with the
+ * usual meaning
+ * - we sent a new password, and the server was
+ * either OK with it (SUCCESS or FAILURE w/partial
+ * success) or unhappy with the _old_ password
+ * (FAILURE w/o partial success)
+ * In any of these cases, we go back to the top of
+ * the loop and start again.
+ */
+ pq_push_front(s->ppl.in_pq, pktin);
+
+ /*
+ * We don't need the old password any more, in any
+ * case. Burn the evidence.
+ */
+ smemclr(s->password, strlen(s->password));
+ sfree(s->password);
+
+ } else {
+ ssh_bpp_queue_disconnect(
+ s->ppl.bpp,
+ "No supported authentication methods available",
+ SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE);
+ ssh_sw_abort(s->ppl.ssh, "No supported authentication methods "
+ "available (server sent: %s)",
+ s->last_methods_string->s);
+ return;
+ }
+
+ }
+ try_new_username:;
+ }
+
+ userauth_success:
+ if (s->notrivialauth && s->is_trivial_auth) {
+ ssh_proto_error(s->ppl.ssh, "Authentication was trivial! "
+ "Abandoning session as specified in configuration.");
+ return;
+ }
+
+ /*
+ * We've just received USERAUTH_SUCCESS, and we haven't sent
+ * any packets since. Signal the transport layer to consider
+ * doing an immediate rekey, if it has any reason to want to.
+ */
+ ssh2_transport_notify_auth_done(s->transport_layer);
+
+ /*
+ * Finally, hand over to our successor layer, and return
+ * immediately without reaching the crFinishV: ssh_ppl_replace
+ * will have freed us, so crFinishV's zeroing-out of crState would
+ * be a use-after-free bug.
+ */
+ {
+ PacketProtocolLayer *successor = s->successor_layer;
+ s->successor_layer = NULL; /* avoid freeing it ourself */
+ ssh_ppl_replace(&s->ppl, successor);
+ return; /* we've just freed s, so avoid even touching s->crState */
+ }
+
+ crFinishV;
+}
+
+static bool ssh2_userauth_ki_setup_prompts(
+ struct ssh2_userauth_state *s, BinarySource *src, bool plugin)
+{
+ ptrlen name, inst;
+ strbuf *sb;
+
+ /*
+ * We've got a fresh USERAUTH_INFO_REQUEST. Get the preamble and
+ * start building a prompt.
+ */
+ name = get_string(src);
+ inst = get_string(src);
+ get_string(src); /* skip language tag */
+ s->cur_prompt = ssh_ppl_new_prompts(&s->ppl);
+ s->cur_prompt->to_server = true;
+ s->cur_prompt->from_server = true;
+
+ /*
+ * Get any prompt(s) from the packet.
+ */
+ s->num_prompts = get_uint32(src);
+ for (uint32_t i = 0; i < s->num_prompts; i++) {
+ s->is_trivial_auth = false;
+ ptrlen prompt = get_string(src);
+ bool echo = get_bool(src);
+
+ if (get_err(src)) {
+ ssh_proto_error(s->ppl.ssh, "%s sent truncated %s packet",
+ plugin ? "Plugin" : "Server",
+ plugin ? "PLUGIN_KI_USER_REQUEST" :
+ "SSH_MSG_USERAUTH_INFO_REQUEST");
+ return false;
+ }
+
+ sb = strbuf_new();
+ if (!prompt.len) {
+ put_fmt(sb, "<%s failed to send prompt>: ",
+ plugin ? "plugin" : "server");
+ } else if (s->ki_scc) {
+ stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb));
+ put_datapl(s->ki_scc, prompt);
+ stripctrl_retarget(s->ki_scc, NULL);
+ } else {
+ put_datapl(sb, prompt);
+ }
+ add_prompt(s->cur_prompt, strbuf_to_str(sb), echo);
+ }
+
+ /*
+ * Make the header strings. This includes the 'name' (optional
+ * dialog-box title) and 'instruction' from the server.
+ *
+ * First, display our disambiguating header line if this is the
+ * first time round the loop - _unless_ the server has sent a
+ * completely empty k-i packet with no prompts _or_ text, which
+ * apparently some do. In that situation there's no need to alert
+ * the user that the following text is server- supplied, because,
+ * well, _what_ text?
+ *
+ * We also only do this if we got a stripctrl, because if we
+ * didn't, that suggests this is all being done via dialog boxes
+ * anyway.
+ */
+ if (!s->ki_printed_header && s->ki_scc &&
+ (s->num_prompts || name.len || inst.len)) {
+ seat_antispoof_msg(
+ ppl_get_iseat(&s->ppl),
+ (plugin ?
+ "Keyboard-interactive authentication prompts from plugin:" :
+ "Keyboard-interactive authentication prompts from server:"));
+ s->ki_printed_header = true;
+ seat_set_trust_status(s->ppl.seat, false);
+ }
+
+ sb = strbuf_new();
+ if (name.len) {
+ if (s->ki_scc) {
+ stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb));
+ put_datapl(s->ki_scc, name);
+ stripctrl_retarget(s->ki_scc, NULL);
+ } else {
+ put_datapl(sb, name);
+ }
+ s->cur_prompt->name_reqd = true;
+ } else {
+ if (plugin)
+ put_datapl(sb, PTRLEN_LITERAL(
+ "Communication with authentication plugin"));
+ else
+ put_datapl(sb, PTRLEN_LITERAL("SSH server authentication"));
+ s->cur_prompt->name_reqd = false;
+ }
+ s->cur_prompt->name = strbuf_to_str(sb);
+
+ sb = strbuf_new();
+ if (inst.len) {
+ if (s->ki_scc) {
+ stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb));
+ put_datapl(s->ki_scc, inst);
+ stripctrl_retarget(s->ki_scc, NULL);
+ } else {
+ put_datapl(sb, inst);
+ }
+ s->cur_prompt->instr_reqd = true;
+ } else {
+ s->cur_prompt->instr_reqd = false;
+ }
+ if (sb->len)
+ s->cur_prompt->instruction = strbuf_to_str(sb);
+ else
+ strbuf_free(sb);
+
+ return true;
+}
+
+static bool ssh2_userauth_ki_run_prompts(struct ssh2_userauth_state *s)
+{
+ s->spr = seat_get_userpass_input(
+ ppl_get_iseat(&s->ppl), s->cur_prompt);
+ return s->spr.kind != SPRK_INCOMPLETE;
+}
+
+static void ssh2_userauth_ki_write_responses(
+ struct ssh2_userauth_state *s, BinarySink *bs)
+{
+ put_uint32(bs, s->num_prompts);
+ for (uint32_t i = 0; i < s->num_prompts; i++)
+ put_stringz(bs, prompt_get_result_ref(s->cur_prompt->prompts[i]));
+
+ /*
+ * Free the prompts structure from this iteration. If there's
+ * another, a new one will be allocated when we return to the top
+ * of this while loop.
+ */
+ free_prompts(s->cur_prompt);
+ s->cur_prompt = NULL;
+}
+
+static void ssh2_userauth_add_session_id(
+ struct ssh2_userauth_state *s, strbuf *sigdata)
+{
+ if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) {
+ put_datapl(sigdata, s->session_id);
+ } else {
+ put_stringpl(sigdata, s->session_id);
+ }
+}
+
+static void ssh2_userauth_agent_query(
+ struct ssh2_userauth_state *s, strbuf *req)
+{
+ void *response;
+ int response_len;
+
+ sfree(s->agent_response_to_free);
+ s->agent_response_to_free = NULL;
+
+ s->auth_agent_query = agent_query(req, &response, &response_len,
+ ssh2_userauth_agent_callback, s);
+ if (!s->auth_agent_query)
+ ssh2_userauth_agent_callback(s, response, response_len);
+}
+
+static void ssh2_userauth_agent_callback(void *uav, void *reply, int replylen)
+{
+ struct ssh2_userauth_state *s = (struct ssh2_userauth_state *)uav;
+
+ s->auth_agent_query = NULL;
+ s->agent_response_to_free = reply;
+ s->agent_response = make_ptrlen(reply, replylen);
+
+ queue_idempotent_callback(&s->ppl.ic_process_queue);
+}
+
+/*
+ * Helper function to add the algorithm and public key strings to a
+ * "publickey" auth packet. Deals with overriding both strings if the
+ * user has provided a detached certificate which matches the public
+ * key in question.
+ */
+static void ssh2_userauth_add_alg_and_publickey(
+ struct ssh2_userauth_state *s, PktOut *pkt, ptrlen alg, ptrlen pkblob)
+{
+ PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
+
+ if (s->detached_cert_blob) {
+ ptrlen detached_cert_pl = ptrlen_from_strbuf(s->detached_cert_blob);
+ strbuf *certbase = NULL, *pkbase = NULL;
+ bool done = false;
+ const ssh_keyalg *pkalg = find_pubkey_alg_len(alg);
+ ssh_key *certkey = NULL, *pk = NULL;
+ strbuf *fail_reason = strbuf_new();
+ bool verbose = true;
+
+ /*
+ * Whether or not we send the certificate, we're likely to
+ * generate a log message about it. But we don't want to log
+ * once for the offer and once for the real auth attempt, so
+ * we de-duplicate by remembering the last public key this
+ * function saw. */
+ if (!s->cert_pubkey_diagnosed)
+ s->cert_pubkey_diagnosed = strbuf_new();
+ if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(s->cert_pubkey_diagnosed),
+ pkblob)) {
+ verbose = false;
+ } else {
+ /* Log this time, but arrange that we don't mention it next time */
+ strbuf_clear(s->cert_pubkey_diagnosed);
+ put_datapl(s->cert_pubkey_diagnosed, pkblob);
+ }
+
+ /*
+ * Check that the public key we're replacing is compatible
+ * with the certificate, in that they should have the same
+ * base public key.
+ */
+
+ const ssh_keyalg *certalg = pubkey_blob_to_alg(detached_cert_pl);
+ assert(certalg); /* we checked this before setting s->detached_blob */
+ assert(certalg->is_certificate); /* and this too */
+
+ certkey = ssh_key_new_pub(certalg, detached_cert_pl);
+ if (!certkey) {
+ put_fmt(fail_reason, "certificate key file is invalid");
+ goto no_match;
+ }
+
+ certbase = strbuf_new();
+ ssh_key_public_blob(ssh_key_base_key(certkey),
+ BinarySink_UPCAST(certbase));
+ if (ptrlen_eq_ptrlen(pkblob, ptrlen_from_strbuf(certbase)))
+ goto match; /* yes, a match! */
+
+ /*
+ * If we reach here, the certificate's base key was not
+ * identical to the key we're given. But it might still be
+ * identical to the _base_ key of the key we're given, if we
+ * were using a differently certified version of the same key.
+ * In that situation, the detached cert should still override.
+ */
+ if (!pkalg) {
+ put_fmt(fail_reason, "unable to identify algorithm of base key");
+ goto no_match;
+ }
+
+ pk = ssh_key_new_pub(pkalg, pkblob);
+ if (!pk) {
+ put_fmt(fail_reason, "base public key is invalid");
+ goto no_match;
+ }
+
+ pkbase = strbuf_new();
+ ssh_key_public_blob(ssh_key_base_key(pk), BinarySink_UPCAST(pkbase));
+ if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(pkbase),
+ ptrlen_from_strbuf(certbase)))
+ goto match; /* yes, a match on 2nd attempt! */
+
+ /* Give up; we've tried to match these keys up and failed. */
+ put_fmt(fail_reason, "base public key does not match certificate");
+ goto no_match;
+
+ match:
+ /*
+ * The two keys match, so insert the detached certificate into
+ * the output packet in place of the public key we were given.
+ *
+ * However, we need to be a bit careful with the algorithm
+ * name: we might need to upgrade it to one that matches the
+ * original algorithm name. (If we were asked to add an
+ * ssh-rsa key but were given algorithm name "rsa-sha2-512",
+ * then instead of the certificate's algorithm name
+ * ssh-rsa-cert-v01@... we need to write the corresponding
+ * SHA-512 name rsa-sha2-512-cert-v01@... .)
+ */
+ if (verbose) {
+ ppl_logevent("Sending public key with certificate from \"%s\"",
+ filename_to_str(s->detached_cert_file));
+ }
+ put_stringz(pkt, ssh_keyalg_related_alg(certalg, pkalg)->ssh_id);
+ put_stringpl(pkt, ptrlen_from_strbuf(s->detached_cert_blob));
+ done = true;
+ goto out;
+
+ no_match:
+ /* Log that we didn't send the certificate, if this public key
+ * isn't the same one as last call to this function. (Need to
+ * avoid verbosely logging once for the offer and once for the
+ * real auth attempt.) */
+ if (verbose) {
+ ppl_logevent("Not substituting certificate \"%s\" for public "
+ "key: %s", filename_to_str(s->detached_cert_file),
+ fail_reason->s);
+ if (s->publickey_blob) {
+ /* If the user provided a specific key file to use (i.e.
+ * this wasn't just a key we picked opportunistically out
+ * of an agent), then they probably _care_ that we didn't
+ * send the certificate, so we should make a loud error
+ * message about it as well as just commenting in the
+ * Event Log. */
+ ppl_printf("Unable to use certificate \"%s\" with public "
+ "key \"%s\": %s\r\n",
+ filename_to_str(s->detached_cert_file),
+ filename_to_str(s->keyfile),
+ fail_reason->s);
+ }
+ }
+
+ out:
+ /* Whether we did that or not, free our stuff. */
+ if (certbase)
+ strbuf_free(certbase);
+ if (pkbase)
+ strbuf_free(pkbase);
+ if (certkey)
+ ssh_key_free(certkey);
+ if (pk)
+ ssh_key_free(pk);
+ strbuf_free(fail_reason);
+
+ /* And if we did, don't fall through to the alternative below */
+ if (done)
+ return;
+ }
+
+ /* In all other cases, just put in what we were given. */
+ put_stringpl(pkt, alg);
+ put_stringpl(pkt, pkblob);
+}
+
+/*
+ * Helper function to add an SSH-2 signature blob to a packet. Expects
+ * to be shown the public key blob as well as the signature blob.
+ * Normally just appends the sig blob unmodified as a string, except
+ * that it optionally breaks it open and fiddle with it to work around
+ * BUG_SSH2_RSA_PADDING.
+ */
+static void ssh2_userauth_add_sigblob(
+ struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob)
+{
+ BinarySource pk[1], sig[1];
+ BinarySource_BARE_INIT_PL(pk, pkblob);
+ BinarySource_BARE_INIT_PL(sig, sigblob);
+
+ /* dmemdump(pkblob, pkblob_len); */
+ /* dmemdump(sigblob, sigblob_len); */
+
+ /*
+ * See if this is in fact an ssh-rsa signature and a buggy
+ * server; otherwise we can just do this the easy way.
+ */
+ if ((s->ppl.remote_bugs & BUG_SSH2_RSA_PADDING) &&
+ ptrlen_eq_string(get_string(pk), "ssh-rsa") &&
+ ptrlen_eq_string(get_string(sig), "ssh-rsa")) {
+ ptrlen mod_mp, sig_mp;
+ size_t sig_prefix_len;
+
+ /*
+ * Find the modulus and signature integers.
+ */
+ get_string(pk); /* skip over exponent */
+ mod_mp = get_string(pk); /* remember modulus */
+ sig_prefix_len = sig->pos;
+ sig_mp = get_string(sig);
+ if (get_err(pk) || get_err(sig))
+ goto give_up;
+
+ /*
+ * Find the byte length of the modulus, not counting leading
+ * zeroes.
+ */
+ while (mod_mp.len > 0 && *(const char *)mod_mp.ptr == 0) {
+ mod_mp.len--;
+ mod_mp.ptr = (const char *)mod_mp.ptr + 1;
+ }
+
+ /* debug("modulus length is %d\n", len); */
+ /* debug("signature length is %d\n", siglen); */
+
+ if (mod_mp.len > sig_mp.len) {
+ strbuf *substr = strbuf_new();
+ put_data(substr, sigblob.ptr, sig_prefix_len);
+ put_uint32(substr, mod_mp.len);
+ put_padding(substr, mod_mp.len - sig_mp.len, 0);
+ put_datapl(substr, sig_mp);
+ put_stringsb(pkt, substr);
+ return;
+ }
+
+ /* Otherwise fall through and do it the easy way. We also come
+ * here as a fallback if we discover above that the key blob
+ * is misformatted in some way. */
+ give_up:;
+ }
+
+ put_stringpl(pkt, sigblob);
+}
+
+#ifndef NO_GSSAPI
+static PktOut *ssh2_userauth_gss_packet(
+ struct ssh2_userauth_state *s, const char *authtype)
+{
+ strbuf *sb;
+ PktOut *p;
+ Ssh_gss_buf buf;
+ Ssh_gss_buf mic;
+
+ /*
+ * The mic is computed over the session id + intended
+ * USERAUTH_REQUEST packet.
+ */
+ sb = strbuf_new();
+ put_stringpl(sb, s->session_id);
+ put_byte(sb, SSH2_MSG_USERAUTH_REQUEST);
+ put_stringz(sb, s->username);
+ put_stringz(sb, s->successor_layer->vt->name);
+ put_stringz(sb, authtype);
+
+ /* Compute the mic */
+ buf.value = sb->s;
+ buf.length = sb->len;
+ s->shgss->lib->get_mic(s->shgss->lib, s->shgss->ctx, &buf, &mic);
+ strbuf_free(sb);
+
+ /* Now we can build the real packet */
+ if (strcmp(authtype, "gssapi-with-mic") == 0) {
+ p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_MIC);
+ } else {
+ p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
+ put_stringz(p, s->username);
+ put_stringz(p, s->successor_layer->vt->name);
+ put_stringz(p, authtype);
+ }
+ put_string(p, mic.value, mic.length);
+
+ return p;
+}
+#endif
+
+static bool ssh2_userauth_get_specials(
+ PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx)
+{
+ /* No specials provided by this layer. */
+ return false;
+}
+
+static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl,
+ SessionSpecialCode code, int arg)
+{
+ /* No specials provided by this layer. */
+}
+
+static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
+{
+ struct ssh2_userauth_state *s =
+ container_of(ppl, struct ssh2_userauth_state, ppl);
+ ssh_ppl_reconfigure(s->successor_layer, conf);
+}
diff --git a/ssh/userauth2-server.c b/ssh/userauth2-server.c
new file mode 100644
index 00000000..bfe258ce
--- /dev/null
+++ b/ssh/userauth2-server.c
@@ -0,0 +1,379 @@
+/*
+ * Packet protocol layer for the server side of the SSH-2 userauth
+ * protocol (RFC 4252).
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "ppl.h"
+#include "sshcr.h"
+#include "server.h"
+
+#ifndef NO_GSSAPI
+#include "gssc.h"
+#include "gss.h"
+#endif
+
+struct ssh2_userauth_server_state {
+ int crState;
+
+ PacketProtocolLayer *transport_layer, *successor_layer;
+ ptrlen session_id;
+
+ AuthPolicy *authpolicy;
+ const SshServerConfig *ssc;
+
+ ptrlen username, service, method;
+ unsigned methods, this_method;
+ bool partial_success;
+
+ AuthKbdInt *aki;
+
+ PacketProtocolLayer ppl;
+};
+
+static void ssh2_userauth_server_free(PacketProtocolLayer *);
+static void ssh2_userauth_server_process_queue(PacketProtocolLayer *);
+
+static const PacketProtocolLayerVtable ssh2_userauth_server_vtable = {
+ .free = ssh2_userauth_server_free,
+ .process_queue = ssh2_userauth_server_process_queue,
+ .queued_data_size = ssh_ppl_default_queued_data_size,
+ .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) ||
+ s->ssc->stunt_return_success_to_pubkey_offer;
+ algorithm = get_string(pktin);
+ blob = get_string(pktin);
+
+ key_really_ok = auth_publickey(s->authpolicy, s->username, blob);
+ send_pk_ok = key_really_ok ||
+ s->ssc->stunt_pretend_to_accept_any_pubkey;
+
+ if (!has_signature) {
+ if (!send_pk_ok)
+ goto failure;
+
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_USERAUTH_PK_OK);
+ put_stringpl(pktout, algorithm);
+ put_stringpl(pktout, blob);
+ pq_push(s->ppl.out_pq, pktout);
+ continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */
+ }
+
+ if (!key_really_ok)
+ goto failure;
+
+ keyalg = find_pubkey_alg_len(algorithm);
+ if (!keyalg)
+ goto failure;
+ key = ssh_key_new_pub(keyalg, blob);
+ if (!key)
+ goto failure;
+
+ sigdata = strbuf_new();
+ ssh2_userauth_server_add_session_id(s, sigdata);
+ put_byte(sigdata, SSH2_MSG_USERAUTH_REQUEST);
+ put_stringpl(sigdata, s->username);
+ put_stringpl(sigdata, s->service);
+ put_stringpl(sigdata, s->method);
+ put_bool(sigdata, has_signature);
+ put_stringpl(sigdata, algorithm);
+ put_stringpl(sigdata, blob);
+
+ signature = get_string(pktin);
+ success = ssh_key_verify(key, signature,
+ ptrlen_from_strbuf(sigdata)) ||
+ s->ssc->stunt_return_success_to_pubkey_offer;
+ ssh_key_free(key);
+ strbuf_free(sigdata);
+
+ if (!success)
+ goto failure;
+ } else if (ptrlen_eq_string(s->method, "keyboard-interactive")) {
+ int i, ok;
+ unsigned n;
+
+ s->this_method = AUTHMETHOD_KBDINT;
+ if (!(s->methods & s->this_method))
+ goto failure;
+
+ do {
+ s->aki = auth_kbdint_prompts(s->authpolicy, s->username);
+ if (!s->aki)
+ goto failure;
+
+ pktout = ssh_bpp_new_pktout(
+ s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_REQUEST);
+ put_stringz(pktout, s->aki->title);
+ put_stringz(pktout, s->aki->instruction);
+ put_stringz(pktout, ""); /* language tag */
+ put_uint32(pktout, s->aki->nprompts);
+ for (i = 0; i < s->aki->nprompts; i++) {
+ put_stringz(pktout, s->aki->prompts[i].prompt);
+ put_bool(pktout, s->aki->prompts[i].echo);
+ }
+ pq_push(s->ppl.out_pq, pktout);
+
+ crMaybeWaitUntilV(
+ (pktin = ssh2_userauth_server_pop(s)) != NULL);
+ if (pktin->type != SSH2_MSG_USERAUTH_INFO_RESPONSE) {
+ ssh_proto_error(
+ s->ppl.ssh, "Received unexpected packet when "
+ "expecting USERAUTH_INFO_RESPONSE, type %d (%s)",
+ pktin->type,
+ ssh2_pkt_type(s->ppl.bpp->pls->kctx,
+ s->ppl.bpp->pls->actx, pktin->type));
+ return;
+ }
+
+ n = get_uint32(pktin);
+ if (n != s->aki->nprompts) {
+ ssh_proto_error(
+ s->ppl.ssh, "Received %u keyboard-interactive "
+ "responses after sending %u prompts",
+ n, s->aki->nprompts);
+ return;
+ }
+
+ {
+ ptrlen *responses = snewn(s->aki->nprompts, ptrlen);
+ for (i = 0; i < s->aki->nprompts; i++)
+ responses[i] = get_string(pktin);
+ ok = auth_kbdint_responses(s->authpolicy, responses);
+ sfree(responses);
+ }
+
+ free_auth_kbdint(s->aki);
+ s->aki = NULL;
+ } while (ok == 0);
+
+ if (ok <= 0)
+ goto failure;
+ } else {
+ goto failure;
+ }
+
+ /*
+ * If we get here, we've successfully completed this
+ * authentication step.
+ */
+ if (auth_successful(s->authpolicy, s->username, s->this_method)) {
+ /*
+ * ... and it was the last one, so we're completely done.
+ */
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_SUCCESS);
+ pq_push(s->ppl.out_pq, pktout);
+ break;
+ } else {
+ /*
+ * ... but another is required, so fall through to
+ * generation of USERAUTH_FAILURE, having first refreshed
+ * the bit mask of available methods.
+ */
+ s->methods = auth_methods(s->authpolicy);
+ }
+ s->partial_success = true;
+
+ failure:
+ pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE);
+ {
+ strbuf *list = strbuf_new();
+ if (s->methods & AUTHMETHOD_NONE)
+ add_to_commasep(list, "none");
+ if (s->methods & AUTHMETHOD_PASSWORD)
+ add_to_commasep(list, "password");
+ if (s->methods & AUTHMETHOD_PUBLICKEY)
+ add_to_commasep(list, "publickey");
+ if (s->methods & AUTHMETHOD_KBDINT)
+ add_to_commasep(list, "keyboard-interactive");
+ put_stringsb(pktout, list);
+ }
+ put_bool(pktout, s->partial_success);
+ pq_push(s->ppl.out_pq, pktout);
+ }
+
+ /*
+ * Finally, hand over to our successor layer, and return
+ * immediately without reaching the crFinishV: ssh_ppl_replace
+ * will have freed us, so crFinishV's zeroing-out of crState would
+ * be a use-after-free bug.
+ */
+ {
+ PacketProtocolLayer *successor = s->successor_layer;
+ s->successor_layer = NULL; /* avoid freeing it ourself */
+ ssh_ppl_replace(&s->ppl, successor);
+ return; /* we've just freed s, so avoid even touching s->crState */
+ }
+
+ crFinishV;
+}
diff --git a/ssh/verstring.c b/ssh/verstring.c
new file mode 100644
index 00000000..aa4c2c20
--- /dev/null
+++ b/ssh/verstring.c
@@ -0,0 +1,642 @@
+/*
+ * Code to handle the initial SSH version string exchange.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "bpp.h"
+#include "sshcr.h"
+
+#define PREFIX_MAXLEN 64
+
+struct ssh_verstring_state {
+ int crState;
+
+ Conf *conf;
+ ptrlen prefix_wanted;
+ char *our_protoversion;
+ struct ssh_version_receiver *receiver;
+
+ bool send_early;
+
+ bool found_prefix;
+ int major_protoversion;
+ int remote_bugs;
+ char prefix[PREFIX_MAXLEN];
+ char *impl_name;
+ strbuf *vstring;
+ char *protoversion;
+ const char *softwareversion;
+
+ char *our_vstring;
+ int i;
+
+ BinaryPacketProtocol bpp;
+};
+
+static void ssh_verstring_free(BinaryPacketProtocol *bpp);
+static void ssh_verstring_handle_input(BinaryPacketProtocol *bpp);
+static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp);
+static PktOut *ssh_verstring_new_pktout(int type);
+static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp,
+ const char *msg, int category);
+
+static const BinaryPacketProtocolVtable ssh_verstring_vtable = {
+ .free = ssh_verstring_free,
+ .handle_input = ssh_verstring_handle_input,
+ .handle_output = ssh_verstring_handle_output,
+ .new_pktout = ssh_verstring_new_pktout,
+ .queue_disconnect = ssh_verstring_queue_disconnect,
+ .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */
+};
+
+static void ssh_detect_bugs(struct ssh_verstring_state *s);
+static bool ssh_version_includes_v1(const char *ver);
+static bool ssh_version_includes_v2(const char *ver);
+
+BinaryPacketProtocol *ssh_verstring_new(
+ Conf *conf, LogContext *logctx, bool bare_connection_mode,
+ const char *protoversion, struct ssh_version_receiver *rcv,
+ bool server_mode, const char *impl_name)
+{
+ struct ssh_verstring_state *s = snew(struct ssh_verstring_state);
+
+ memset(s, 0, sizeof(struct ssh_verstring_state));
+
+ if (!bare_connection_mode) {
+ s->prefix_wanted = PTRLEN_LITERAL("SSH-");
+ } else {
+ /*
+ * Ordinary SSH begins with the banner "SSH-x.y-...". Here,
+ * we're going to be speaking just the ssh-connection
+ * subprotocol, extracted and given a trivial binary packet
+ * protocol, so we need a new banner.
+ *
+ * The new banner is like the ordinary SSH banner, but
+ * replaces the prefix 'SSH-' at the start with a new name. In
+ * proper SSH style (though of course this part of the proper
+ * SSH protocol _isn't_ subject to this kind of
+ * DNS-domain-based extension), we define the new name in our
+ * extension space.
+ */
+ s->prefix_wanted = PTRLEN_LITERAL(
+ "SSHCONNECTION@putty.projects.tartarus.org-");
+ }
+ assert(s->prefix_wanted.len <= PREFIX_MAXLEN);
+
+ s->conf = conf_copy(conf);
+ s->bpp.logctx = logctx;
+ s->our_protoversion = dupstr(protoversion);
+ s->receiver = rcv;
+ s->impl_name = dupstr(impl_name);
+ s->vstring = strbuf_new();
+
+ /*
+ * We send our version string early if we can. But if it includes
+ * SSH-1, we can't, because we have to take the other end into
+ * account too (see below).
+ *
+ * In server mode, we do send early.
+ */
+ s->send_early = server_mode || !ssh_version_includes_v1(protoversion);
+
+ /*
+ * Override: we don't send our version string early if the server
+ * has a bug that will make it discard it. See for example
+ * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991958
+ */
+ if (conf_get_int(s->conf, CONF_sshbug_dropstart) == FORCE_ON)
+ s->send_early = false;
+
+ s->bpp.vt = &ssh_verstring_vtable;
+ ssh_bpp_common_setup(&s->bpp);
+ return &s->bpp;
+}
+
+void ssh_verstring_free(BinaryPacketProtocol *bpp)
+{
+ struct ssh_verstring_state *s =
+ container_of(bpp, struct ssh_verstring_state, bpp);
+ conf_free(s->conf);
+ sfree(s->impl_name);
+ strbuf_free(s->vstring);
+ sfree(s->protoversion);
+ sfree(s->our_vstring);
+ sfree(s->our_protoversion);
+ sfree(s);
+}
+
+static int ssh_versioncmp(const char *a, const char *b)
+{
+ char *ae, *be;
+ unsigned long av, bv;
+
+ av = strtoul(a, &ae, 10);
+ bv = strtoul(b, &be, 10);
+ if (av != bv)
+ return (av < bv ? -1 : +1);
+ if (*ae == '.')
+ ae++;
+ if (*be == '.')
+ be++;
+ av = strtoul(ae, &ae, 10);
+ bv = strtoul(be, &be, 10);
+ if (av != bv)
+ return (av < bv ? -1 : +1);
+ return 0;
+}
+
+static bool ssh_version_includes_v1(const char *ver)
+{
+ return ssh_versioncmp(ver, "2.0") < 0;
+}
+
+static bool ssh_version_includes_v2(const char *ver)
+{
+ return ssh_versioncmp(ver, "1.99") >= 0;
+}
+
+static void ssh_verstring_send(struct ssh_verstring_state *s)
+{
+ BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */
+ char *p;
+ int sv_pos;
+
+ /*
+ * Construct our outgoing version string.
+ */
+ s->our_vstring = dupprintf(
+ "%.*s%s-%s%s",
+ (int)s->prefix_wanted.len, (const char *)s->prefix_wanted.ptr,
+ s->our_protoversion, s->impl_name, sshver);
+ sv_pos = s->prefix_wanted.len + strlen(s->our_protoversion) + 1;
+
+ /* Convert minus signs and spaces in the software version string
+ * into underscores. */
+ for (p = s->our_vstring + sv_pos; *p; p++) {
+ if (*p == '-' || *p == ' ')
+ *p = '_';
+ }
+
+#ifdef FUZZING
+ /*
+ * Replace the first character of the string with an "I" if we're
+ * compiling this code for fuzzing - i.e. the protocol prefix
+ * becomes "ISH-" instead of "SSH-".
+ *
+ * This is irrelevant to any real client software (the only thing
+ * reading the output of PuTTY built for fuzzing is the fuzzer,
+ * which can adapt to whatever it sees anyway). But it's a safety
+ * precaution making it difficult to accidentally run such a
+ * version of PuTTY (which would be hugely insecure) against a
+ * live peer implementation.
+ *
+ * (So the replacement prefix "ISH" notionally stands for
+ * 'Insecure Shell', of course.)
+ */
+ s->our_vstring[0] = 'I';
+#endif
+
+ /*
+ * Now send that version string, plus trailing \r\n or just \n
+ * (the latter in SSH-1 mode).
+ */
+ bufchain_add(s->bpp.out_raw, s->our_vstring, strlen(s->our_vstring));
+ if (ssh_version_includes_v2(s->our_protoversion))
+ bufchain_add(s->bpp.out_raw, "\015", 1);
+ bufchain_add(s->bpp.out_raw, "\012", 1);
+
+ bpp_logevent("We claim version: %s", s->our_vstring);
+}
+
+#define BPP_WAITFOR(minlen) do \
+ { \
+ bool success; \
+ crMaybeWaitUntilV( \
+ (success = (bufchain_size(s->bpp.in_raw) >= (minlen))) || \
+ s->bpp.input_eof); \
+ if (!success) \
+ goto eof; \
+ } while (0)
+
+void ssh_verstring_handle_input(BinaryPacketProtocol *bpp)
+{
+ struct ssh_verstring_state *s =
+ container_of(bpp, struct ssh_verstring_state, bpp);
+
+ crBegin(s->crState);
+
+ /*
+ * If we're sending our version string up front before seeing the
+ * other side's, then do it now.
+ */
+ if (s->send_early)
+ ssh_verstring_send(s);
+
+ /*
+ * Search for a line beginning with the protocol name prefix in
+ * the input.
+ */
+ s->i = 0;
+ while (1) {
+ /*
+ * Every time round this loop, we're at the start of a new
+ * line, so look for the prefix.
+ */
+ BPP_WAITFOR(s->prefix_wanted.len);
+ bufchain_fetch(s->bpp.in_raw, s->prefix, s->prefix_wanted.len);
+ if (!memcmp(s->prefix, s->prefix_wanted.ptr, s->prefix_wanted.len)) {
+ bufchain_consume(s->bpp.in_raw, s->prefix_wanted.len);
+ ssh_check_frozen(s->bpp.ssh);
+ break;
+ }
+
+ /*
+ * If we didn't find it, consume data until we see a newline.
+ */
+ while (1) {
+ ptrlen data;
+ char *nl;
+
+ /* Wait to receive at least 1 byte, but then consume more
+ * than that if it's there. */
+ BPP_WAITFOR(1);
+ data = bufchain_prefix(s->bpp.in_raw);
+ if ((nl = memchr(data.ptr, '\012', data.len)) != NULL) {
+ bufchain_consume(s->bpp.in_raw, nl - (char *)data.ptr + 1);
+ ssh_check_frozen(s->bpp.ssh);
+ break;
+ } else {
+ bufchain_consume(s->bpp.in_raw, data.len);
+ ssh_check_frozen(s->bpp.ssh);
+ }
+ }
+ }
+
+ s->found_prefix = true;
+
+ /*
+ * Copy the greeting line so far into vstring.
+ */
+ put_data(s->vstring, s->prefix_wanted.ptr, s->prefix_wanted.len);
+
+ /*
+ * Now read the rest of the greeting line.
+ */
+ s->i = 0;
+ do {
+ ptrlen data;
+ char *nl;
+
+ BPP_WAITFOR(1);
+ data = bufchain_prefix(s->bpp.in_raw);
+ if ((nl = memchr(data.ptr, '\012', data.len)) != NULL) {
+ data.len = nl - (char *)data.ptr + 1;
+ }
+
+ put_datapl(s->vstring, data);
+ bufchain_consume(s->bpp.in_raw, data.len);
+ ssh_check_frozen(s->bpp.ssh);
+
+ } while (s->vstring->s[s->vstring->len-1] != '\012');
+
+ /*
+ * Trim \r and \n from the version string, and replace them with
+ * a NUL terminator.
+ */
+ while (s->vstring->len > 0 &&
+ (s->vstring->s[s->vstring->len-1] == '\015' ||
+ s->vstring->s[s->vstring->len-1] == '\012'))
+ strbuf_shrink_by(s->vstring, 1);
+
+ bpp_logevent("Remote version: %s", s->vstring->s);
+
+ /*
+ * Pick out the protocol version and software version. The former
+ * goes in a separately allocated string, so that s->vstring
+ * remains intact for later use in key exchange; the latter is the
+ * tail of s->vstring, so it doesn't need to be allocated.
+ */
+ {
+ const char *pv_start = s->vstring->s + s->prefix_wanted.len;
+ int pv_len = strcspn(pv_start, "-");
+ s->protoversion = dupprintf("%.*s", pv_len, pv_start);
+ s->softwareversion = pv_start + pv_len;
+ if (*s->softwareversion) {
+ assert(*s->softwareversion == '-');
+ s->softwareversion++;
+ }
+ }
+
+ ssh_detect_bugs(s);
+
+ /*
+ * Figure out what actual SSH protocol version we're speaking.
+ */
+ if (ssh_version_includes_v2(s->our_protoversion) &&
+ ssh_version_includes_v2(s->protoversion)) {
+ /*
+ * We're doing SSH-2.
+ */
+ s->major_protoversion = 2;
+ } else if (ssh_version_includes_v1(s->our_protoversion) &&
+ ssh_version_includes_v1(s->protoversion)) {
+ /*
+ * We're doing SSH-1.
+ */
+ s->major_protoversion = 1;
+
+ /*
+ * There are multiple minor versions of SSH-1, and the
+ * protocol does not specify that the minimum of client
+ * and server versions is used. So we must adjust our
+ * outgoing protocol version to be no higher than that of
+ * the other side.
+ */
+ if (!s->send_early &&
+ ssh_versioncmp(s->our_protoversion, s->protoversion) > 0) {
+ sfree(s->our_protoversion);
+ s->our_protoversion = dupstr(s->protoversion);
+ }
+ } else {
+ /*
+ * Unable to agree on a major protocol version at all.
+ */
+ if (!ssh_version_includes_v2(s->our_protoversion)) {
+ ssh_sw_abort(s->bpp.ssh,
+ "SSH protocol version 1 required by our "
+ "configuration but not provided by remote");
+ } else {
+ ssh_sw_abort(s->bpp.ssh,
+ "SSH protocol version 2 required by our "
+ "configuration but remote only provides "
+ "(old, insecure) SSH-1");
+ }
+ crStopV;
+ }
+
+ bpp_logevent("Using SSH protocol version %d", s->major_protoversion);
+
+ if (!s->send_early) {
+ /*
+ * If we didn't send our version string early, construct and
+ * send it now, because now we know what it is.
+ */
+ ssh_verstring_send(s);
+ }
+
+ /*
+ * And we're done. Notify our receiver that we now know our
+ * protocol version. This will cause it to disconnect us from the
+ * input stream and ultimately free us, because our job is now
+ * done.
+ */
+ s->receiver->got_ssh_version(s->receiver, s->major_protoversion);
+ return;
+
+ eof:
+ ssh_remote_error(s->bpp.ssh,
+ "Remote side unexpectedly closed network connection");
+ return; /* avoid touching s now it's been freed */
+
+ crFinishV;
+}
+
+static PktOut *ssh_verstring_new_pktout(int type)
+{
+ unreachable("Should never try to send packets during SSH version "
+ "string exchange");
+}
+
+static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp)
+{
+ if (pq_peek(&bpp->out_pq)) {
+ unreachable("Should never try to send packets during SSH version "
+ "string exchange");
+ }
+}
+
+/*
+ * Examine the remote side's version string, and compare it against a
+ * list of known buggy implementations.
+ */
+static void ssh_detect_bugs(struct ssh_verstring_state *s)
+{
+ BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */
+ const char *imp = s->softwareversion;
+
+ s->remote_bugs = 0;
+
+ /*
+ * General notes on server version strings:
+ * - Not all servers reporting "Cisco-1.25" have all the bugs listed
+ * here -- in particular, we've heard of one that's perfectly happy
+ * with SSH1_MSG_IGNOREs -- but this string never seems to change,
+ * so we can't distinguish them.
+ */
+ if (conf_get_int(s->conf, CONF_sshbug_ignore1) == FORCE_ON ||
+ (conf_get_int(s->conf, CONF_sshbug_ignore1) == AUTO &&
+ (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") ||
+ !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") ||
+ !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25") ||
+ !strcmp(imp, "OSU_1.4alpha3") || !strcmp(imp, "OSU_1.5alpha4")))) {
+ /*
+ * These versions don't support SSH1_MSG_IGNORE, so we have
+ * to use a different defence against password length
+ * sniffing.
+ */
+ s->remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE;
+ bpp_logevent("We believe remote version has SSH-1 ignore bug");
+ }
+
+ if (conf_get_int(s->conf, CONF_sshbug_plainpw1) == FORCE_ON ||
+ (conf_get_int(s->conf, CONF_sshbug_plainpw1) == AUTO &&
+ (!strcmp(imp, "Cisco-1.25") || !strcmp(imp, "OSU_1.4alpha3")))) {
+ /*
+ * These versions need a plain password sent; they can't
+ * handle having a null and a random length of data after
+ * the password.
+ */
+ s->remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD;
+ bpp_logevent("We believe remote version needs a "
+ "plain SSH-1 password");
+ }
+
+ if (conf_get_int(s->conf, CONF_sshbug_rsa1) == FORCE_ON ||
+ (conf_get_int(s->conf, CONF_sshbug_rsa1) == AUTO &&
+ (!strcmp(imp, "Cisco-1.25")))) {
+ /*
+ * These versions apparently have no clue whatever about
+ * RSA authentication and will panic and die if they see
+ * an AUTH_RSA message.
+ */
+ s->remote_bugs |= BUG_CHOKES_ON_RSA;
+ bpp_logevent("We believe remote version can't handle SSH-1 "
+ "RSA authentication");
+ }
+
+ if (conf_get_int(s->conf, CONF_sshbug_hmac2) == FORCE_ON ||
+ (conf_get_int(s->conf, CONF_sshbug_hmac2) == AUTO &&
+ !wc_match("* VShell", imp) &&
+ (wc_match("2.1.0*", imp) || wc_match("2.0.*", imp) ||
+ wc_match("2.2.0*", imp) || wc_match("2.3.0*", imp) ||
+ wc_match("2.1 *", imp)))) {
+ /*
+ * These versions have the HMAC bug.
+ */
+ s->remote_bugs |= BUG_SSH2_HMAC;
+ bpp_logevent("We believe remote version has SSH-2 HMAC bug");
+ }
+
+ if (conf_get_int(s->conf, CONF_sshbug_derivekey2) == FORCE_ON ||
+ (conf_get_int(s->conf, CONF_sshbug_derivekey2) == AUTO &&
+ !wc_match("* VShell", imp) &&
+ (wc_match("2.0.0*", imp) || wc_match("2.0.10*", imp) ))) {
+ /*
+ * These versions have the key-derivation bug (failing to
+ * include the literal shared secret in the hashes that
+ * generate the keys).
+ */
+ s->remote_bugs |= BUG_SSH2_DERIVEKEY;
+ bpp_logevent("We believe remote version has SSH-2 "
+ "key-derivation bug");
+ }
+
+ if (conf_get_int(s->conf, CONF_sshbug_rsapad2) == FORCE_ON ||
+ (conf_get_int(s->conf, CONF_sshbug_rsapad2) == AUTO &&
+ (wc_match("OpenSSH_2.[5-9]*", imp) ||
+ wc_match("OpenSSH_3.[0-2]*", imp) ||
+ wc_match("mod_sftp/0.[0-8]*", imp) ||
+ wc_match("mod_sftp/0.9.[0-8]", imp)))) {
+ /*
+ * These versions have the SSH-2 RSA padding bug.
+ */
+ s->remote_bugs |= BUG_SSH2_RSA_PADDING;
+ bpp_logevent("We believe remote version has SSH-2 RSA padding bug");
+ }
+
+ if (conf_get_int(s->conf, CONF_sshbug_pksessid2) == FORCE_ON ||
+ (conf_get_int(s->conf, CONF_sshbug_pksessid2) == AUTO &&
+ wc_match("OpenSSH_2.[0-2]*", imp))) {
+ /*
+ * These versions have the SSH-2 session-ID bug in
+ * public-key authentication.
+ */
+ s->remote_bugs |= BUG_SSH2_PK_SESSIONID;
+ bpp_logevent("We believe remote version has SSH-2 "
+ "public-key-session-ID bug");
+ }
+
+ if (conf_get_int(s->conf, CONF_sshbug_rekey2) == FORCE_ON ||
+ (conf_get_int(s->conf, CONF_sshbug_rekey2) == AUTO &&
+ (wc_match("DigiSSH_2.0", imp) ||
+ wc_match("OpenSSH_2.[0-4]*", imp) ||
+ wc_match("OpenSSH_2.5.[0-3]*", imp) ||
+ wc_match("Sun_SSH_1.0", imp) ||
+ wc_match("Sun_SSH_1.0.1", imp) ||
+ /* All versions <= 1.2.6 (they changed their format in 1.2.7) */
+ wc_match("WeOnlyDo-*", imp)))) {
+ /*
+ * These versions have the SSH-2 rekey bug.
+ */
+ s->remote_bugs |= BUG_SSH2_REKEY;
+ bpp_logevent("We believe remote version has SSH-2 rekey bug");
+ }
+
+ if (conf_get_int(s->conf, CONF_sshbug_maxpkt2) == FORCE_ON ||
+ (conf_get_int(s->conf, CONF_sshbug_maxpkt2) == AUTO &&
+ (wc_match("1.36_sshlib GlobalSCAPE", imp) ||
+ wc_match("1.36 sshlib: GlobalScape", imp)))) {
+ /*
+ * This version ignores our makpkt and needs to be throttled.
+ */
+ s->remote_bugs |= BUG_SSH2_MAXPKT;
+ bpp_logevent("We believe remote version ignores SSH-2 "
+ "maximum packet size");
+ }
+
+ if (conf_get_int(s->conf, CONF_sshbug_ignore2) == FORCE_ON) {
+ /*
+ * Servers that don't support SSH2_MSG_IGNORE. Currently,
+ * none detected automatically.
+ */
+ s->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE;
+ bpp_logevent("We believe remote version has SSH-2 ignore bug");
+ }
+
+ if (conf_get_int(s->conf, CONF_sshbug_oldgex2) == FORCE_ON ||
+ (conf_get_int(s->conf, CONF_sshbug_oldgex2) == AUTO &&
+ (wc_match("OpenSSH_2.[235]*", imp)))) {
+ /*
+ * These versions only support the original (pre-RFC4419)
+ * SSH-2 GEX request, and disconnect with a protocol error if
+ * we use the newer version.
+ */
+ s->remote_bugs |= BUG_SSH2_OLDGEX;
+ bpp_logevent("We believe remote version has outdated SSH-2 GEX");
+ }
+
+ if (conf_get_int(s->conf, CONF_sshbug_winadj) == FORCE_ON) {
+ /*
+ * Servers that don't support our winadj request for one
+ * reason or another. Currently, none detected automatically.
+ */
+ s->remote_bugs |= BUG_CHOKES_ON_WINADJ;
+ bpp_logevent("We believe remote version has winadj bug");
+ }
+
+ if (conf_get_int(s->conf, CONF_sshbug_chanreq) == FORCE_ON ||
+ (conf_get_int(s->conf, CONF_sshbug_chanreq) == AUTO &&
+ (wc_match("OpenSSH_[2-5].*", imp) ||
+ wc_match("OpenSSH_6.[0-6]*", imp) ||
+ wc_match("dropbear_0.[2-4][0-9]*", imp) ||
+ wc_match("dropbear_0.5[01]*", imp)))) {
+ /*
+ * These versions have the SSH-2 channel request bug.
+ * OpenSSH 6.7 and above do not:
+ * https://bugzilla.mindrot.org/show_bug.cgi?id=1818
+ * dropbear_0.52 and above do not:
+ * https://secure.ucc.asn.au/hg/dropbear/rev/cd02449b709c
+ */
+ s->remote_bugs |= BUG_SENDS_LATE_REQUEST_REPLY;
+ bpp_logevent("We believe remote version has SSH-2 "
+ "channel request bug");
+ }
+
+ if (conf_get_int(s->conf, CONF_sshbug_filter_kexinit) == FORCE_ON) {
+ s->remote_bugs |= BUG_REQUIRES_FILTERED_KEXINIT;
+ bpp_logevent("We believe remote version requires us to "
+ "filter our KEXINIT");
+ }
+}
+
+const char *ssh_verstring_get_remote(BinaryPacketProtocol *bpp)
+{
+ struct ssh_verstring_state *s =
+ container_of(bpp, struct ssh_verstring_state, bpp);
+ return s->vstring->s;
+}
+
+const char *ssh_verstring_get_local(BinaryPacketProtocol *bpp)
+{
+ struct ssh_verstring_state *s =
+ container_of(bpp, struct ssh_verstring_state, bpp);
+ return s->our_vstring;
+}
+
+int ssh_verstring_get_bugs(BinaryPacketProtocol *bpp)
+{
+ struct ssh_verstring_state *s =
+ container_of(bpp, struct ssh_verstring_state, bpp);
+ return s->remote_bugs;
+}
+
+static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp,
+ const char *msg, int category)
+{
+ /* No way to send disconnect messages at this stage of the protocol! */
+}
diff --git a/ssh/x11fwd.c b/ssh/x11fwd.c
new file mode 100644
index 00000000..c5698f9b
--- /dev/null
+++ b/ssh/x11fwd.c
@@ -0,0 +1,638 @@
+/*
+ * Platform-independent bits of X11 forwarding.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <time.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "channel.h"
+#include "tree234.h"
+
+struct XDMSeen {
+ unsigned int time;
+ unsigned char clientid[6];
+};
+
+typedef struct X11Connection {
+ unsigned char firstpkt[12]; /* first X data packet */
+ tree234 *authtree;
+ struct X11Display *disp;
+ char *auth_protocol;
+ unsigned char *auth_data;
+ int data_read, auth_plen, auth_psize, auth_dlen, auth_dsize;
+ bool verified;
+ bool input_wanted;
+ bool no_data_sent_to_x_client;
+ char *peer_addr;
+ int peer_port;
+ SshChannel *c; /* channel structure held by SSH backend */
+ Socket *s;
+
+ Plug plug;
+ Channel chan;
+} X11Connection;
+
+static int xdmseen_cmp(void *a, void *b)
+{
+ struct XDMSeen *sa = a, *sb = b;
+ return sa->time > sb->time ? 1 :
+ sa->time < sb->time ? -1 :
+ memcmp(sa->clientid, sb->clientid, sizeof(sa->clientid));
+}
+
+struct X11FakeAuth *x11_invent_fake_auth(tree234 *authtree, int authtype)
+{
+ struct X11FakeAuth *auth = snew(struct X11FakeAuth);
+ int i;
+
+ /*
+ * This function has the job of inventing a set of X11 fake auth
+ * data, and adding it to 'authtree'. We must preserve the
+ * property that for any given actual authorisation attempt, _at
+ * most one_ thing in the tree can possibly match it.
+ *
+ * For MIT-MAGIC-COOKIE-1, that's not too difficult: the match
+ * criterion is simply that the entire cookie is correct, so we
+ * just have to make sure we don't make up two cookies the same.
+ * (Vanishingly unlikely, but we check anyway to be sure, and go
+ * round again inventing a new cookie if add234 tells us the one
+ * we thought of is already in use.)
+ *
+ * For XDM-AUTHORIZATION-1, it's a little more fiddly. The setup
+ * with XA1 is that half the cookie is used as a DES key with
+ * which to CBC-encrypt an assortment of stuff. Happily, the stuff
+ * encrypted _begins_ with the other half of the cookie, and the
+ * IV is always zero, which means that any valid XA1 authorisation
+ * attempt for a given cookie must begin with the same cipher
+ * block, consisting of the DES ECB encryption of the first half
+ * of the cookie using the second half as a key. So we compute
+ * that cipher block here and now, and use it as the sorting key
+ * for distinguishing XA1 entries in the tree.
+ */
+
+ if (authtype == X11_MIT) {
+ auth->proto = X11_MIT;
+
+ /* MIT-MAGIC-COOKIE-1. Cookie size is 128 bits (16 bytes). */
+ auth->datalen = 16;
+ auth->data = snewn(auth->datalen, unsigned char);
+ auth->xa1_firstblock = NULL;
+
+ while (1) {
+ random_read(auth->data, auth->datalen);
+ if (add234(authtree, auth) == auth)
+ break;
+ }
+
+ auth->xdmseen = NULL;
+ } else {
+ assert(authtype == X11_XDM);
+ auth->proto = X11_XDM;
+
+ /* XDM-AUTHORIZATION-1. Cookie size is 16 bytes; byte 8 is zero. */
+ auth->datalen = 16;
+ auth->data = snewn(auth->datalen, unsigned char);
+ auth->xa1_firstblock = snewn(8, unsigned char);
+ memset(auth->xa1_firstblock, 0, 8);
+
+ while (1) {
+ random_read(auth->data, 15);
+ auth->data[15] = auth->data[8];
+ auth->data[8] = 0;
+
+ memcpy(auth->xa1_firstblock, auth->data, 8);
+ des_encrypt_xdmauth(auth->data + 9, auth->xa1_firstblock, 8);
+ if (add234(authtree, auth) == auth)
+ break;
+ }
+
+ auth->xdmseen = newtree234(xdmseen_cmp);
+ }
+ auth->protoname = dupstr(x11_authnames[auth->proto]);
+ auth->datastring = snewn(auth->datalen * 2 + 1, char);
+ for (i = 0; i < auth->datalen; i++)
+ sprintf(auth->datastring + i*2, "%02x",
+ auth->data[i]);
+
+ auth->disp = NULL;
+ auth->share_cs = NULL;
+ auth->share_chan = NULL;
+
+ return auth;
+}
+
+void x11_free_fake_auth(struct X11FakeAuth *auth)
+{
+ if (auth->data)
+ smemclr(auth->data, auth->datalen);
+ sfree(auth->data);
+ sfree(auth->protoname);
+ sfree(auth->datastring);
+ sfree(auth->xa1_firstblock);
+ if (auth->xdmseen != NULL) {
+ struct XDMSeen *seen;
+ while ((seen = delpos234(auth->xdmseen, 0)) != NULL)
+ sfree(seen);
+ freetree234(auth->xdmseen);
+ }
+ sfree(auth);
+}
+
+int x11_authcmp(void *av, void *bv)
+{
+ struct X11FakeAuth *a = (struct X11FakeAuth *)av;
+ struct X11FakeAuth *b = (struct X11FakeAuth *)bv;
+
+ if (a->proto < b->proto)
+ return -1;
+ else if (a->proto > b->proto)
+ return +1;
+
+ if (a->proto == X11_MIT) {
+ if (a->datalen < b->datalen)
+ return -1;
+ else if (a->datalen > b->datalen)
+ return +1;
+
+ return memcmp(a->data, b->data, a->datalen);
+ } else {
+ assert(a->proto == X11_XDM);
+
+ return memcmp(a->xa1_firstblock, b->xa1_firstblock, 8);
+ }
+}
+
+#define XDM_MAXSKEW 20*60 /* 20 minute clock skew should be OK */
+
+static 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;
+}
+
+static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port,
+ const char *error_msg, int error_code)
+{
+ /* We have no interface to the logging module here, so we drop these. */
+}
+
+static void x11_send_init_error(struct X11Connection *conn,
+ const char *err_message);
+
+static void x11_closing(Plug *plug, PlugCloseType type, const char *error_msg)
+{
+ struct X11Connection *xconn = container_of(
+ plug, struct X11Connection, plug);
+
+ if (type != PLUGCLOSE_NORMAL) {
+ /*
+ * Socket error. If we're still at the connection setup stage,
+ * construct an X11 error packet passing on the problem.
+ */
+ if (xconn->no_data_sent_to_x_client) {
+ char *err_message = dupprintf("unable to connect to forwarded "
+ "X server: %s", error_msg);
+ x11_send_init_error(xconn, err_message);
+ sfree(err_message);
+ }
+
+ /*
+ * Whether we did that or not, now we slam the connection
+ * shut.
+ */
+ sshfwd_initiate_close(xconn->c, error_msg);
+ } else {
+ /*
+ * Ordinary EOF received on socket. Send an EOF on the SSH
+ * channel.
+ */
+ if (xconn->c)
+ sshfwd_write_eof(xconn->c);
+ }
+}
+
+static void x11_receive(Plug *plug, int urgent, const char *data, size_t len)
+{
+ struct X11Connection *xconn = container_of(
+ plug, struct X11Connection, plug);
+
+ xconn->no_data_sent_to_x_client = false;
+ sshfwd_write(xconn->c, data, len);
+}
+
+static void x11_sent(Plug *plug, size_t bufsize)
+{
+ struct X11Connection *xconn = container_of(
+ plug, struct X11Connection, plug);
+
+ sshfwd_unthrottle(xconn->c, bufsize);
+}
+
+static const PlugVtable X11Connection_plugvt = {
+ .log = x11_log,
+ .closing = x11_closing,
+ .receive = x11_receive,
+ .sent = x11_sent,
+};
+
+static void x11_chan_free(Channel *chan);
+static size_t x11_send(
+ Channel *chan, bool is_stderr, const void *vdata, size_t len);
+static void x11_send_eof(Channel *chan);
+static void x11_set_input_wanted(Channel *chan, bool wanted);
+static char *x11_log_close_msg(Channel *chan);
+
+static const ChannelVtable X11Connection_channelvt = {
+ .free = x11_chan_free,
+ .open_confirmation = chan_remotely_opened_confirmation,
+ .open_failed = chan_remotely_opened_failure,
+ .send = x11_send,
+ .send_eof = x11_send_eof,
+ .set_input_wanted = x11_set_input_wanted,
+ .log_close_msg = x11_log_close_msg,
+ .want_close = chan_default_want_close,
+ .rcvd_exit_status = chan_no_exit_status,
+ .rcvd_exit_signal = chan_no_exit_signal,
+ .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric,
+ .run_shell = chan_no_run_shell,
+ .run_command = chan_no_run_command,
+ .run_subsystem = chan_no_run_subsystem,
+ .enable_x11_forwarding = chan_no_enable_x11_forwarding,
+ .enable_agent_forwarding = chan_no_enable_agent_forwarding,
+ .allocate_pty = chan_no_allocate_pty,
+ .set_env = chan_no_set_env,
+ .send_break = chan_no_send_break,
+ .send_signal = chan_no_send_signal,
+ .change_window_size = chan_no_change_window_size,
+ .request_response = chan_no_request_response,
+};
+
+/*
+ * Called to set up the X11Connection structure, though this does not
+ * yet connect to an actual server.
+ */
+Channel *x11_new_channel(tree234 *authtree, SshChannel *c,
+ const char *peeraddr, int peerport,
+ bool connection_sharing_possible)
+{
+ struct X11Connection *xconn;
+
+ /*
+ * Open socket.
+ */
+ xconn = snew(struct X11Connection);
+ xconn->plug.vt = &X11Connection_plugvt;
+ xconn->chan.vt = &X11Connection_channelvt;
+ xconn->chan.initial_fixed_window_size =
+ (connection_sharing_possible ? 128 : 0);
+ xconn->auth_protocol = NULL;
+ xconn->authtree = authtree;
+ xconn->verified = false;
+ xconn->data_read = 0;
+ xconn->input_wanted = true;
+ xconn->no_data_sent_to_x_client = true;
+ xconn->c = c;
+
+ /*
+ * We don't actually open a local socket to the X server just yet,
+ * because we don't know which one it is. Instead, we'll wait
+ * until we see the incoming authentication data, which may tell
+ * us what display to connect to, or whether we have to divert
+ * this X forwarding channel to a connection-sharing downstream
+ * rather than handling it ourself.
+ */
+ xconn->disp = NULL;
+ xconn->s = NULL;
+
+ /*
+ * Stash the peer address we were given in its original text form.
+ */
+ xconn->peer_addr = peeraddr ? dupstr(peeraddr) : NULL;
+ xconn->peer_port = peerport;
+
+ return &xconn->chan;
+}
+
+static void x11_chan_free(Channel *chan)
+{
+ assert(chan->vt == &X11Connection_channelvt);
+ X11Connection *xconn = container_of(chan, X11Connection, chan);
+
+ if (xconn->auth_protocol) {
+ sfree(xconn->auth_protocol);
+ sfree(xconn->auth_data);
+ }
+
+ if (xconn->s)
+ sk_close(xconn->s);
+
+ sfree(xconn->peer_addr);
+ sfree(xconn);
+}
+
+static void x11_set_input_wanted(Channel *chan, bool wanted)
+{
+ assert(chan->vt == &X11Connection_channelvt);
+ X11Connection *xconn = container_of(chan, X11Connection, chan);
+
+ xconn->input_wanted = wanted;
+ if (xconn->s)
+ sk_set_frozen(xconn->s, !xconn->input_wanted);
+}
+
+static void x11_send_init_error(struct X11Connection *xconn,
+ const char *err_message)
+{
+ char *full_message;
+ int msglen, msgsize;
+ unsigned char *reply;
+
+ full_message = dupprintf("%s X11 proxy: %s\n", appname, err_message);
+
+ msglen = strlen(full_message);
+ reply = snewn(8 + msglen+1 + 4, unsigned char); /* include zero */
+ msgsize = (msglen + 3) & ~3;
+ reply[0] = 0; /* failure */
+ reply[1] = msglen; /* length of reason string */
+ memcpy(reply + 2, xconn->firstpkt + 2, 4); /* major/minor proto vsn */
+ PUT_16BIT_X11(xconn->firstpkt[0], reply + 6, msgsize >> 2);/* data len */
+ memset(reply + 8, 0, msgsize);
+ memcpy(reply + 8, full_message, msglen);
+ sshfwd_write(xconn->c, reply, 8 + msgsize);
+ sshfwd_write_eof(xconn->c);
+ xconn->no_data_sent_to_x_client = false;
+ sfree(reply);
+ sfree(full_message);
+}
+
+/*
+ * Called to send data down the raw connection.
+ */
+static size_t x11_send(
+ Channel *chan, bool is_stderr, const void *vdata, size_t len)
+{
+ assert(chan->vt == &X11Connection_channelvt);
+ X11Connection *xconn = container_of(chan, X11Connection, chan);
+ const char *data = (const char *)vdata;
+
+ /*
+ * Read the first packet.
+ */
+ while (len > 0 && xconn->data_read < 12)
+ xconn->firstpkt[xconn->data_read++] = (unsigned char) (len--, *data++);
+ if (xconn->data_read < 12)
+ return 0;
+
+ /*
+ * If we have not allocated the auth_protocol and auth_data
+ * strings, do so now.
+ */
+ if (!xconn->auth_protocol) {
+ char endian = xconn->firstpkt[0];
+ xconn->auth_plen = GET_16BIT_X11(endian, xconn->firstpkt + 6);
+ xconn->auth_dlen = GET_16BIT_X11(endian, xconn->firstpkt + 8);
+ xconn->auth_psize = (xconn->auth_plen + 3) & ~3;
+ xconn->auth_dsize = (xconn->auth_dlen + 3) & ~3;
+ /* Leave room for a terminating zero, to make our lives easier. */
+ xconn->auth_protocol = snewn(xconn->auth_psize + 1, char);
+ xconn->auth_data = snewn(xconn->auth_dsize, unsigned char);
+ }
+
+ /*
+ * Read the auth_protocol and auth_data strings.
+ */
+ while (len > 0 &&
+ xconn->data_read < 12 + xconn->auth_psize)
+ xconn->auth_protocol[xconn->data_read++ - 12] = (len--, *data++);
+ while (len > 0 &&
+ xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize)
+ xconn->auth_data[xconn->data_read++ - 12 -
+ xconn->auth_psize] = (unsigned char) (len--, *data++);
+ if (xconn->data_read < 12 + xconn->auth_psize + xconn->auth_dsize)
+ return 0;
+
+ /*
+ * If we haven't verified the authorisation, do so now.
+ */
+ if (!xconn->verified) {
+ const char *err;
+ 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), NULL);
+ if ((err = sk_socket_error(xconn->s)) != NULL) {
+ char *err_message = dupprintf("unable to connect to"
+ " forwarded X server: %s", err);
+ x11_send_init_error(xconn, err_message);
+ sfree(err_message);
+ return 0;
+ }
+
+ /*
+ * Write a new connection header containing our replacement
+ * auth data.
+ */
+ socketdatalen = 0; /* placate compiler warning */
+ socketdata = sk_getxdmdata(xconn->s, &socketdatalen);
+ if (socketdata && socketdatalen==6) {
+ sprintf(new_peer_addr, "%d.%d.%d.%d", socketdata[0],
+ socketdata[1], socketdata[2], socketdata[3]);
+ new_peer_port = GET_16BIT_MSB_FIRST(socketdata + 4);
+ } else {
+ strcpy(new_peer_addr, "0.0.0.0");
+ new_peer_port = 0;
+ }
+
+ greeting = x11_make_greeting(xconn->firstpkt[0],
+ protomajor, protominor,
+ xconn->disp->localauthproto,
+ xconn->disp->localauthdata,
+ xconn->disp->localauthdatalen,
+ new_peer_addr, new_peer_port,
+ &greeting_len);
+
+ sk_write(xconn->s, greeting, greeting_len);
+
+ smemclr(greeting, greeting_len);
+ sfree(greeting);
+
+ /*
+ * Now we're done.
+ */
+ xconn->verified = true;
+ }
+
+ /*
+ * After initialisation, just copy data simply.
+ */
+
+ return sk_write(xconn->s, data, len);
+}
+
+static void x11_send_eof(Channel *chan)
+{
+ assert(chan->vt == &X11Connection_channelvt);
+ X11Connection *xconn = container_of(chan, X11Connection, chan);
+
+ if (xconn->s) {
+ sk_write_eof(xconn->s);
+ } else {
+ /*
+ * If EOF is received from the X client before we've got to
+ * the point of actually connecting to an X server, then we
+ * should send an EOF back to the client so that the
+ * forwarded channel will be terminated.
+ */
+ if (xconn->c)
+ sshfwd_write_eof(xconn->c);
+ }
+}
+
+static char *x11_log_close_msg(Channel *chan)
+{
+ return dupstr("Forwarded X11 connection terminated");
+}
diff --git a/ssh/zlib.c b/ssh/zlib.c
new file mode 100644
index 00000000..0841faa3
--- /dev/null
+++ b/ssh/zlib.c
@@ -0,0 +1,1252 @@
+/*
+ * 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 <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "defs.h"
+#include "ssh.h"
+
+/* ----------------------------------------------------------------------
+ * Basic LZ77 code. This bit is designed modularly, so it could be
+ * ripped out and used in a different LZ77 compressor. Go to it,
+ * and good luck :-)
+ */
+
+struct LZ77InternalContext;
+struct LZ77Context {
+ struct LZ77InternalContext *ictx;
+ void *userdata;
+ void (*literal) (struct LZ77Context *ctx, unsigned char c);
+ void (*match) (struct LZ77Context *ctx, int distance, int len);
+};
+
+/*
+ * Initialise the private fields of an LZ77Context. It's up to the
+ * user to initialise the public fields.
+ */
+static int lz77_init(struct LZ77Context *ctx);
+
+/*
+ * Supply data to be compressed. Will update the private fields of
+ * the LZ77Context, and will call literal() and match() to output.
+ * If `compress' is false, it will never emit a match, but will
+ * instead call literal() for everything.
+ */
+static void lz77_compress(struct LZ77Context *ctx,
+ const unsigned char *data, int len);
+
+/*
+ * Modifiable parameters.
+ */
+#define WINSIZE 32768 /* window size. Must be power of 2! */
+#define HASHMAX 2039 /* one more than max hash value */
+#define MAXMATCH 32 /* how many matches we track */
+#define HASHCHARS 3 /* how many chars make a hash */
+
+/*
+ * This compressor takes a less slapdash approach than the
+ * gzip/zlib one. Rather than allowing our hash chains to fall into
+ * disuse near the far end, we keep them doubly linked so we can
+ * _find_ the far end, and then every time we add a new byte to the
+ * window (thus rolling round by one and removing the previous
+ * byte), we can carefully remove the hash chain entry.
+ */
+
+#define INVALID -1 /* invalid hash _and_ invalid offset */
+struct WindowEntry {
+ short next, prev; /* array indices within the window */
+ short hashval;
+};
+
+struct HashEntry {
+ short first; /* window index of first in chain */
+};
+
+struct Match {
+ int distance, len;
+};
+
+struct LZ77InternalContext {
+ struct WindowEntry win[WINSIZE];
+ unsigned char data[WINSIZE];
+ int winpos;
+ struct HashEntry hashtab[HASHMAX];
+ unsigned char pending[HASHCHARS];
+ int npending;
+};
+
+static int lz77_hash(const unsigned char *data)
+{
+ return (257 * data[0] + 263 * data[1] + 269 * data[2]) % HASHMAX;
+}
+
+static int lz77_init(struct LZ77Context *ctx)
+{
+ struct LZ77InternalContext *st;
+ int i;
+
+ st = snew(struct LZ77InternalContext);
+ if (!st)
+ return 0;
+
+ ctx->ictx = st;
+
+ for (i = 0; i < WINSIZE; i++)
+ st->win[i].next = st->win[i].prev = st->win[i].hashval = INVALID;
+ for (i = 0; i < HASHMAX; i++)
+ st->hashtab[i].first = INVALID;
+ st->winpos = 0;
+
+ st->npending = 0;
+
+ return 1;
+}
+
+static void lz77_advance(struct LZ77InternalContext *st,
+ unsigned char c, int hash)
+{
+ int off;
+
+ /*
+ * Remove the hash entry at winpos from the tail of its chain,
+ * or empty the chain if it's the only thing on the chain.
+ */
+ if (st->win[st->winpos].prev != INVALID) {
+ st->win[st->win[st->winpos].prev].next = INVALID;
+ } else if (st->win[st->winpos].hashval != INVALID) {
+ st->hashtab[st->win[st->winpos].hashval].first = INVALID;
+ }
+
+ /*
+ * Create a new entry at winpos and add it to the head of its
+ * hash chain.
+ */
+ st->win[st->winpos].hashval = hash;
+ st->win[st->winpos].prev = INVALID;
+ off = st->win[st->winpos].next = st->hashtab[hash].first;
+ st->hashtab[hash].first = st->winpos;
+ if (off != INVALID)
+ st->win[off].prev = st->winpos;
+ st->data[st->winpos] = c;
+
+ /*
+ * Advance the window pointer.
+ */
+ st->winpos = (st->winpos + 1) & (WINSIZE - 1);
+}
+
+#define CHARAT(k) ( (k)<0 ? st->data[(st->winpos+k)&(WINSIZE-1)] : data[k] )
+
+static void lz77_compress(struct LZ77Context *ctx,
+ const unsigned char *data, int len)
+{
+ struct LZ77InternalContext *st = ctx->ictx;
+ int i, distance, off, nmatch, matchlen, advance;
+ struct Match defermatch, matches[MAXMATCH];
+ int deferchr;
+
+ assert(st->npending <= HASHCHARS);
+
+ /*
+ * Add any pending characters from last time to the window. (We
+ * might not be able to.)
+ *
+ * This leaves st->pending empty in the usual case (when len >=
+ * HASHCHARS); otherwise it leaves st->pending empty enough that
+ * adding all the remaining 'len' characters will not push it past
+ * HASHCHARS in size.
+ */
+ for (i = 0; i < st->npending; i++) {
+ unsigned char foo[HASHCHARS];
+ int j;
+ if (len + st->npending - i < HASHCHARS) {
+ /* Update the pending array. */
+ for (j = i; j < st->npending; j++)
+ st->pending[j - i] = st->pending[j];
+ break;
+ }
+ for (j = 0; j < HASHCHARS; j++)
+ foo[j] = (i + j < st->npending ? st->pending[i + j] :
+ data[i + j - st->npending]);
+ lz77_advance(st, foo[0], lz77_hash(foo));
+ }
+ st->npending -= i;
+
+ defermatch.distance = 0; /* appease compiler */
+ defermatch.len = 0;
+ deferchr = '\0';
+ while (len > 0) {
+
+ if (len >= HASHCHARS) {
+ /*
+ * Hash the next few characters.
+ */
+ int hash = lz77_hash(data);
+
+ /*
+ * Look the hash up in the corresponding hash chain and see
+ * what we can find.
+ */
+ nmatch = 0;
+ for (off = st->hashtab[hash].first;
+ off != INVALID; off = st->win[off].next) {
+ /* distance = 1 if off == st->winpos-1 */
+ /* distance = WINSIZE if off == st->winpos */
+ distance =
+ WINSIZE - (off + WINSIZE - st->winpos) % WINSIZE;
+ for (i = 0; i < HASHCHARS; i++)
+ if (CHARAT(i) != CHARAT(i - distance))
+ break;
+ if (i == HASHCHARS) {
+ matches[nmatch].distance = distance;
+ matches[nmatch].len = 3;
+ if (++nmatch >= MAXMATCH)
+ break;
+ }
+ }
+ } else {
+ nmatch = 0;
+ }
+
+ if (nmatch > 0) {
+ /*
+ * We've now filled up matches[] with nmatch potential
+ * matches. Follow them down to find the longest. (We
+ * assume here that it's always worth favouring a
+ * longer match over a shorter one.)
+ */
+ matchlen = HASHCHARS;
+ while (matchlen < len) {
+ int j;
+ for (i = j = 0; i < nmatch; i++) {
+ if (CHARAT(matchlen) ==
+ CHARAT(matchlen - matches[i].distance)) {
+ matches[j++] = matches[i];
+ }
+ }
+ if (j == 0)
+ break;
+ matchlen++;
+ nmatch = j;
+ }
+
+ /*
+ * We've now got all the longest matches. We favour the
+ * shorter distances, which means we go with matches[0].
+ * So see if we want to defer it or throw it away.
+ */
+ matches[0].len = matchlen;
+ if (defermatch.len > 0) {
+ if (matches[0].len > defermatch.len + 1) {
+ /* We have a better match. Emit the deferred char,
+ * and defer this match. */
+ ctx->literal(ctx, (unsigned char) deferchr);
+ defermatch = matches[0];
+ deferchr = data[0];
+ advance = 1;
+ } else {
+ /* We don't have a better match. Do the deferred one. */
+ ctx->match(ctx, defermatch.distance, defermatch.len);
+ advance = defermatch.len - 1;
+ defermatch.len = 0;
+ }
+ } else {
+ /* There was no deferred match. Defer this one. */
+ defermatch = matches[0];
+ deferchr = data[0];
+ advance = 1;
+ }
+ } else {
+ /*
+ * We found no matches. Emit the deferred match, if
+ * any; otherwise emit a literal.
+ */
+ if (defermatch.len > 0) {
+ ctx->match(ctx, defermatch.distance, defermatch.len);
+ advance = defermatch.len - 1;
+ defermatch.len = 0;
+ } else {
+ ctx->literal(ctx, data[0]);
+ advance = 1;
+ }
+ }
+
+ /*
+ * Now advance the position by `advance' characters,
+ * keeping the window and hash chains consistent.
+ */
+ while (advance > 0) {
+ if (len >= HASHCHARS) {
+ lz77_advance(st, *data, lz77_hash(data));
+ } else {
+ assert(st->npending < HASHCHARS);
+ st->pending[st->npending++] = *data;
+ }
+ data++;
+ len--;
+ advance--;
+ }
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * Zlib compression. We always use the static Huffman tree option.
+ * Mostly this is because it's hard to scan a block in advance to
+ * work out better trees; dynamic trees are great when you're
+ * compressing a large file under no significant time constraint,
+ * but when you're compressing little bits in real time, things get
+ * hairier.
+ *
+ * I suppose it's possible that I could compute Huffman trees based
+ * on the frequencies in the _previous_ block, as a sort of
+ * heuristic, but I'm not confident that the gain would balance out
+ * having to transmit the trees.
+ */
+
+struct Outbuf {
+ strbuf *outbuf;
+ unsigned long outbits;
+ int noutbits;
+ bool firstblock;
+};
+
+static void outbits(struct Outbuf *out, unsigned long bits, int nbits)
+{
+ assert(out->noutbits + nbits <= 32);
+ out->outbits |= bits << out->noutbits;
+ out->noutbits += nbits;
+ while (out->noutbits >= 8) {
+ put_byte(out->outbuf, out->outbits & 0xFF);
+ out->outbits >>= 8;
+ out->noutbits -= 8;
+ }
+}
+
+static const unsigned char mirrorbytes[256] = {
+ 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
+ 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+ 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+ 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+ 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
+ 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+ 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
+ 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+ 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+ 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+ 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
+ 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+ 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
+ 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+ 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+ 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+ 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
+ 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+ 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
+ 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+ 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+ 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+ 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
+ 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+ 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
+ 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+ 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+ 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+ 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
+ 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+ 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
+ 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
+};
+
+typedef struct {
+ short code, extrabits;
+ int min, max;
+} coderecord;
+
+static const coderecord lencodes[] = {
+ {257, 0, 3, 3},
+ {258, 0, 4, 4},
+ {259, 0, 5, 5},
+ {260, 0, 6, 6},
+ {261, 0, 7, 7},
+ {262, 0, 8, 8},
+ {263, 0, 9, 9},
+ {264, 0, 10, 10},
+ {265, 1, 11, 12},
+ {266, 1, 13, 14},
+ {267, 1, 15, 16},
+ {268, 1, 17, 18},
+ {269, 2, 19, 22},
+ {270, 2, 23, 26},
+ {271, 2, 27, 30},
+ {272, 2, 31, 34},
+ {273, 3, 35, 42},
+ {274, 3, 43, 50},
+ {275, 3, 51, 58},
+ {276, 3, 59, 66},
+ {277, 4, 67, 82},
+ {278, 4, 83, 98},
+ {279, 4, 99, 114},
+ {280, 4, 115, 130},
+ {281, 5, 131, 162},
+ {282, 5, 163, 194},
+ {283, 5, 195, 226},
+ {284, 5, 227, 257},
+ {285, 0, 258, 258},
+};
+
+static const coderecord distcodes[] = {
+ {0, 0, 1, 1},
+ {1, 0, 2, 2},
+ {2, 0, 3, 3},
+ {3, 0, 4, 4},
+ {4, 1, 5, 6},
+ {5, 1, 7, 8},
+ {6, 2, 9, 12},
+ {7, 2, 13, 16},
+ {8, 3, 17, 24},
+ {9, 3, 25, 32},
+ {10, 4, 33, 48},
+ {11, 4, 49, 64},
+ {12, 5, 65, 96},
+ {13, 5, 97, 128},
+ {14, 6, 129, 192},
+ {15, 6, 193, 256},
+ {16, 7, 257, 384},
+ {17, 7, 385, 512},
+ {18, 8, 513, 768},
+ {19, 8, 769, 1024},
+ {20, 9, 1025, 1536},
+ {21, 9, 1537, 2048},
+ {22, 10, 2049, 3072},
+ {23, 10, 3073, 4096},
+ {24, 11, 4097, 6144},
+ {25, 11, 6145, 8192},
+ {26, 12, 8193, 12288},
+ {27, 12, 12289, 16384},
+ {28, 13, 16385, 24576},
+ {29, 13, 24577, 32768},
+};
+
+static void zlib_literal(struct LZ77Context *ectx, unsigned char c)
+{
+ struct Outbuf *out = (struct Outbuf *) ectx->userdata;
+
+ if (c <= 143) {
+ /* 0 through 143 are 8 bits long starting at 00110000. */
+ outbits(out, mirrorbytes[0x30 + c], 8);
+ } else {
+ /* 144 through 255 are 9 bits long starting at 110010000. */
+ outbits(out, 1 + 2 * mirrorbytes[0x90 - 144 + c], 9);
+ }
+}
+
+static void zlib_match(struct LZ77Context *ectx, int distance, int len)
+{
+ const coderecord *d, *l;
+ int i, j, k;
+ struct Outbuf *out = (struct Outbuf *) ectx->userdata;
+
+ while (len > 0) {
+ int thislen;
+
+ /*
+ * We can transmit matches of lengths 3 through 258
+ * inclusive. So if len exceeds 258, we must transmit in
+ * several steps, with 258 or less in each step.
+ *
+ * Specifically: if len >= 261, we can transmit 258 and be
+ * sure of having at least 3 left for the next step. And if
+ * len <= 258, we can just transmit len. But if len == 259
+ * or 260, we must transmit len-3.
+ */
+ thislen = (len > 260 ? 258 : len <= 258 ? len : len - 3);
+ len -= thislen;
+
+ /*
+ * Binary-search to find which length code we're
+ * transmitting.
+ */
+ i = -1;
+ j = lenof(lencodes);
+ while (1) {
+ assert(j - i >= 2);
+ k = (j + i) / 2;
+ if (thislen < lencodes[k].min)
+ j = k;
+ else if (thislen > lencodes[k].max)
+ i = k;
+ else {
+ l = &lencodes[k];
+ break; /* found it! */
+ }
+ }
+
+ /*
+ * Transmit the length code. 256-279 are seven bits
+ * starting at 0000000; 280-287 are eight bits starting at
+ * 11000000.
+ */
+ if (l->code <= 279) {
+ outbits(out, mirrorbytes[(l->code - 256) * 2], 7);
+ } else {
+ outbits(out, mirrorbytes[0xc0 - 280 + l->code], 8);
+ }
+
+ /*
+ * Transmit the extra bits.
+ */
+ if (l->extrabits)
+ outbits(out, thislen - l->min, l->extrabits);
+
+ /*
+ * Binary-search to find which distance code we're
+ * transmitting.
+ */
+ i = -1;
+ j = lenof(distcodes);
+ while (1) {
+ assert(j - i >= 2);
+ k = (j + i) / 2;
+ if (distance < distcodes[k].min)
+ j = k;
+ else if (distance > distcodes[k].max)
+ i = k;
+ else {
+ d = &distcodes[k];
+ break; /* found it! */
+ }
+ }
+
+ /*
+ * Transmit the distance code. Five bits starting at 00000.
+ */
+ outbits(out, mirrorbytes[d->code * 8], 5);
+
+ /*
+ * Transmit the extra bits.
+ */
+ if (d->extrabits)
+ outbits(out, distance - d->min, d->extrabits);
+ }
+}
+
+struct ssh_zlib_compressor {
+ struct LZ77Context ectx;
+ ssh_compressor sc;
+};
+
+static ssh_compressor *zlib_compress_init(void)
+{
+ struct Outbuf *out;
+ struct ssh_zlib_compressor *comp = snew(struct ssh_zlib_compressor);
+
+ lz77_init(&comp->ectx);
+ comp->sc.vt = &ssh_zlib;
+ comp->ectx.literal = zlib_literal;
+ comp->ectx.match = zlib_match;
+
+ out = snew(struct Outbuf);
+ out->outbuf = NULL;
+ out->outbits = out->noutbits = 0;
+ out->firstblock = true;
+ comp->ectx.userdata = out;
+
+ return &comp->sc;
+}
+
+static void zlib_compress_cleanup(ssh_compressor *sc)
+{
+ struct ssh_zlib_compressor *comp =
+ container_of(sc, struct ssh_zlib_compressor, sc);
+ struct Outbuf *out = (struct Outbuf *)comp->ectx.userdata;
+ if (out->outbuf)
+ strbuf_free(out->outbuf);
+ sfree(out);
+ sfree(comp->ectx.ictx);
+ sfree(comp);
+}
+
+static void zlib_compress_block(
+ ssh_compressor *sc, const unsigned char *block, int len,
+ unsigned char **outblock, int *outlen, int minlen)
+{
+ struct ssh_zlib_compressor *comp =
+ container_of(sc, struct ssh_zlib_compressor, sc);
+ struct Outbuf *out = (struct Outbuf *) comp->ectx.userdata;
+ bool in_block;
+
+ assert(!out->outbuf);
+ out->outbuf = strbuf_new_nm();
+
+ /*
+ * If this is the first block, output the Zlib (RFC1950) header
+ * bytes 78 9C. (Deflate compression, 32K window size, default
+ * algorithm.)
+ */
+ if (out->firstblock) {
+ outbits(out, 0x9C78, 16);
+ out->firstblock = false;
+
+ in_block = false;
+ } else
+ in_block = true;
+
+ if (!in_block) {
+ /*
+ * Start a Deflate (RFC1951) fixed-trees block. We
+ * transmit a zero bit (BFINAL=0), followed by a zero
+ * bit and a one bit (BTYPE=01). Of course these are in
+ * the wrong order (01 0).
+ */
+ outbits(out, 2, 3);
+ }
+
+ /*
+ * Do the compression.
+ */
+ lz77_compress(&comp->ectx, block, len);
+
+ /*
+ * End the block (by transmitting code 256, which is
+ * 0000000 in fixed-tree mode), and transmit some empty
+ * blocks to ensure we have emitted the byte containing the
+ * last piece of genuine data. There are three ways we can
+ * do this:
+ *
+ * - Minimal flush. Output end-of-block and then open a
+ * new static block. This takes 9 bits, which is
+ * guaranteed to flush out the last genuine code in the
+ * closed block; but allegedly zlib can't handle it.
+ *
+ * - Zlib partial flush. Output EOB, open and close an
+ * empty static block, and _then_ open the new block.
+ * This is the best zlib can handle.
+ *
+ * - Zlib sync flush. Output EOB, then an empty
+ * _uncompressed_ block (000, then sync to byte
+ * boundary, then send bytes 00 00 FF FF). Then open the
+ * new block.
+ *
+ * For the moment, we will use Zlib partial flush.
+ */
+ outbits(out, 0, 7); /* close block */
+ outbits(out, 2, 3 + 7); /* empty static block */
+ outbits(out, 2, 3); /* open new block */
+
+ /*
+ * If we've been asked to pad out the compressed data until it's
+ * at least a given length, do so by emitting further empty static
+ * blocks.
+ */
+ while (out->outbuf->len < minlen) {
+ outbits(out, 0, 7); /* close block */
+ outbits(out, 2, 3); /* open new static block */
+ }
+
+ *outlen = out->outbuf->len;
+ *outblock = (unsigned char *)strbuf_to_str(out->outbuf);
+ out->outbuf = NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Zlib decompression. Of course, even though our compressor always
+ * uses static trees, our _decompressor_ has to be capable of
+ * handling dynamic trees if it sees them.
+ */
+
+/*
+ * The way we work the Huffman decode is to have a table lookup on
+ * the first N bits of the input stream (in the order they arrive,
+ * of course, i.e. the first bit of the Huffman code is in bit 0).
+ * Each table entry lists the number of bits to consume, plus
+ * either an output code or a pointer to a secondary table.
+ */
+struct zlib_table;
+struct zlib_tableentry;
+
+struct zlib_tableentry {
+ unsigned char nbits;
+ short code;
+ struct zlib_table *nexttable;
+};
+
+struct zlib_table {
+ int mask; /* mask applied to input bit stream */
+ struct zlib_tableentry *table;
+};
+
+#define MAXCODELEN 16
+#define MAXSYMS 288
+
+/*
+ * Build a single-level decode table for elements
+ * [minlength,maxlength) of the provided code/length tables, and
+ * recurse to build subtables.
+ */
+static struct zlib_table *zlib_mkonetab(int *codes, unsigned char *lengths,
+ int nsyms,
+ int pfx, int pfxbits, int bits)
+{
+ struct zlib_table *tab = snew(struct zlib_table);
+ int pfxmask = (1 << pfxbits) - 1;
+ int nbits, i, j, code;
+
+ tab->table = snewn((size_t)1 << bits, struct zlib_tableentry);
+ tab->mask = (1 << bits) - 1;
+
+ for (code = 0; code <= tab->mask; code++) {
+ tab->table[code].code = -1;
+ tab->table[code].nbits = 0;
+ tab->table[code].nexttable = NULL;
+ }
+
+ for (i = 0; i < nsyms; i++) {
+ if (lengths[i] <= pfxbits || (codes[i] & pfxmask) != pfx)
+ continue;
+ code = (codes[i] >> pfxbits) & tab->mask;
+ for (j = code; j <= tab->mask; j += 1 << (lengths[i] - pfxbits)) {
+ tab->table[j].code = i;
+ nbits = lengths[i] - pfxbits;
+ if (tab->table[j].nbits < nbits)
+ tab->table[j].nbits = nbits;
+ }
+ }
+ for (code = 0; code <= tab->mask; code++) {
+ if (tab->table[code].nbits <= bits)
+ continue;
+ /* Generate a subtable. */
+ tab->table[code].code = -1;
+ nbits = tab->table[code].nbits - bits;
+ if (nbits > 7)
+ nbits = 7;
+ tab->table[code].nbits = bits;
+ tab->table[code].nexttable = zlib_mkonetab(codes, lengths, nsyms,
+ pfx | (code << pfxbits),
+ pfxbits + bits, nbits);
+ }
+
+ return tab;
+}
+
+/*
+ * Build a decode table, given a set of Huffman tree lengths.
+ */
+static struct zlib_table *zlib_mktable(unsigned char *lengths,
+ int nlengths)
+{
+ int count[MAXCODELEN], startcode[MAXCODELEN], codes[MAXSYMS];
+ int code, maxlen;
+ int i, j;
+
+ /* Count the codes of each length. */
+ maxlen = 0;
+ for (i = 1; i < MAXCODELEN; i++)
+ count[i] = 0;
+ for (i = 0; i < nlengths; i++) {
+ count[lengths[i]]++;
+ if (maxlen < lengths[i])
+ maxlen = lengths[i];
+ }
+ /* Determine the starting code for each length block. */
+ code = 0;
+ for (i = 1; i < MAXCODELEN; i++) {
+ startcode[i] = code;
+ code += count[i];
+ code <<= 1;
+ }
+ /* Determine the code for each symbol. Mirrored, of course. */
+ for (i = 0; i < nlengths; i++) {
+ code = startcode[lengths[i]]++;
+ codes[i] = 0;
+ for (j = 0; j < lengths[i]; j++) {
+ codes[i] = (codes[i] << 1) | (code & 1);
+ code >>= 1;
+ }
+ }
+
+ /*
+ * Now we have the complete list of Huffman codes. Build a
+ * table.
+ */
+ return zlib_mkonetab(codes, lengths, nlengths, 0, 0,
+ maxlen < 9 ? maxlen : 9);
+}
+
+static int zlib_freetable(struct zlib_table **ztab)
+{
+ struct zlib_table *tab;
+ int code;
+
+ if (ztab == NULL)
+ return -1;
+
+ if (*ztab == NULL)
+ return 0;
+
+ tab = *ztab;
+
+ for (code = 0; code <= tab->mask; code++)
+ if (tab->table[code].nexttable != NULL)
+ zlib_freetable(&tab->table[code].nexttable);
+
+ sfree(tab->table);
+ tab->table = NULL;
+
+ sfree(tab);
+ *ztab = NULL;
+
+ return (0);
+}
+
+struct zlib_decompress_ctx {
+ struct zlib_table *staticlentable, *staticdisttable;
+ struct zlib_table *currlentable, *currdisttable, *lenlentable;
+ enum {
+ START, OUTSIDEBLK,
+ TREES_HDR, TREES_LENLEN, TREES_LEN, TREES_LENREP,
+ INBLK, GOTLENSYM, GOTLEN, GOTDISTSYM,
+ UNCOMP_LEN, UNCOMP_NLEN, UNCOMP_DATA
+ } state;
+ int sym, hlit, hdist, hclen, lenptr, lenextrabits, lenaddon, len,
+ lenrep;
+ int uncomplen;
+ unsigned char lenlen[19];
+
+ /*
+ * Array that accumulates the code lengths sent in the header of a
+ * dynamic-Huffman-tree block.
+ *
+ * There are 286 actual symbols in the literal/length alphabet
+ * (256 literals plus 20 length categories), and 30 symbols in the
+ * distance alphabet. However, the block header transmits the
+ * number of code lengths for the former alphabet as a 5-bit value
+ * HLIT to be added to 257, and the latter as a 5-bit value HDIST
+ * to be added to 1. This means that the number of _code lengths_
+ * can go as high as 288 for the symbol alphabet and 32 for the
+ * distance alphabet - each of those values being 2 more than the
+ * maximum number of actual symbols.
+ *
+ * It's tempting to rule that sending out-of-range HLIT or HDIST
+ * is therefore just illegal, and to fault it when we initially
+ * receive that header. But instead I've chosen to permit the
+ * Huffman-code definition to include code length entries for
+ * those unused symbols; if a header of that form is transmitted,
+ * then the effect will be that in the main body of the block,
+ * some bit sequence(s) will generate an illegal symbol number,
+ * and _that_ will be faulted as a decoding error.
+ *
+ * Rationale: this can already happen! The standard Huffman code
+ * used in a _static_ block for the literal/length alphabet is
+ * defined in such a way that it includes codes for symbols 287
+ * and 288, which are then never actually sent in the body of the
+ * block. And I think that if the standard static tree definition
+ * is willing to include Huffman codes that don't correspond to a
+ * symbol, then it's an excessive restriction on dynamic tables
+ * not to permit them to do the same. In particular, it would be
+ * strange for a dynamic block not to be able to exactly mimic
+ * either or both of the Huffman codes used by a static block for
+ * the corresponding alphabet.
+ *
+ * So we place no constraint on HLIT or HDIST during code
+ * construction, and we make this array large enough to include
+ * the maximum number of code lengths that can possibly arise as a
+ * result. It's only trying to _use_ the junk Huffman codes after
+ * table construction is completed that will provoke a decode
+ * error.
+ */
+ unsigned char lengths[288 + 32];
+
+ unsigned long bits;
+ int nbits;
+ unsigned char window[WINSIZE];
+ int winpos;
+ strbuf *outblk;
+
+ ssh_decompressor dc;
+};
+
+static ssh_decompressor *zlib_decompress_init(void)
+{
+ struct zlib_decompress_ctx *dctx = snew(struct zlib_decompress_ctx);
+ unsigned char lengths[288];
+
+ memset(lengths, 8, 144);
+ memset(lengths + 144, 9, 256 - 144);
+ memset(lengths + 256, 7, 280 - 256);
+ memset(lengths + 280, 8, 288 - 280);
+ dctx->staticlentable = zlib_mktable(lengths, 288);
+ memset(lengths, 5, 32);
+ dctx->staticdisttable = zlib_mktable(lengths, 32);
+ dctx->state = START; /* even before header */
+ dctx->currlentable = dctx->currdisttable = dctx->lenlentable = NULL;
+ dctx->bits = 0;
+ dctx->nbits = 0;
+ dctx->winpos = 0;
+ dctx->outblk = NULL;
+
+ dctx->dc.vt = &ssh_zlib;
+ return &dctx->dc;
+}
+
+static void zlib_decompress_cleanup(ssh_decompressor *dc)
+{
+ struct zlib_decompress_ctx *dctx =
+ container_of(dc, struct zlib_decompress_ctx, dc);
+
+ if (dctx->currlentable && dctx->currlentable != dctx->staticlentable)
+ zlib_freetable(&dctx->currlentable);
+ if (dctx->currdisttable && dctx->currdisttable != dctx->staticdisttable)
+ zlib_freetable(&dctx->currdisttable);
+ if (dctx->lenlentable)
+ zlib_freetable(&dctx->lenlentable);
+ zlib_freetable(&dctx->staticlentable);
+ zlib_freetable(&dctx->staticdisttable);
+ if (dctx->outblk)
+ strbuf_free(dctx->outblk);
+ sfree(dctx);
+}
+
+static int zlib_huflookup(unsigned long *bitsp, int *nbitsp,
+ struct zlib_table *tab)
+{
+ unsigned long bits = *bitsp;
+ int nbits = *nbitsp;
+ while (1) {
+ struct zlib_tableentry *ent;
+ ent = &tab->table[bits & tab->mask];
+ if (ent->nbits > nbits)
+ return -1; /* not enough data */
+ bits >>= ent->nbits;
+ nbits -= ent->nbits;
+ if (ent->code == -1)
+ tab = ent->nexttable;
+ else {
+ *bitsp = bits;
+ *nbitsp = nbits;
+ return ent->code;
+ }
+
+ if (!tab) {
+ /*
+ * There was a missing entry in the table, presumably
+ * due to an invalid Huffman table description, and the
+ * subsequent data has attempted to use the missing
+ * entry. Return a decoding failure.
+ */
+ return -2;
+ }
+ }
+}
+
+static void zlib_emit_char(struct zlib_decompress_ctx *dctx, int c)
+{
+ dctx->window[dctx->winpos] = c;
+ dctx->winpos = (dctx->winpos + 1) & (WINSIZE - 1);
+ put_byte(dctx->outblk, c);
+}
+
+#define EATBITS(n) ( dctx->nbits -= (n), dctx->bits >>= (n) )
+
+static bool zlib_decompress_block(
+ ssh_decompressor *dc, const unsigned char *block, int len,
+ unsigned char **outblock, int *outlen)
+{
+ struct zlib_decompress_ctx *dctx =
+ container_of(dc, struct zlib_decompress_ctx, dc);
+ const coderecord *rec;
+ int code, blktype, rep, dist, nlen, header;
+ static const unsigned char lenlenmap[] = {
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
+ };
+
+ assert(!dctx->outblk);
+ dctx->outblk = strbuf_new_nm();
+
+ while (len > 0 || dctx->nbits > 0) {
+ while (dctx->nbits < 24 && len > 0) {
+ dctx->bits |= (*block++) << dctx->nbits;
+ dctx->nbits += 8;
+ len--;
+ }
+ switch (dctx->state) {
+ case START:
+ /* Expect 16-bit zlib header. */
+ if (dctx->nbits < 16)
+ goto finished; /* done all we can */
+
+ /*
+ * The header is stored as a big-endian 16-bit integer,
+ * in contrast to the general little-endian policy in
+ * the rest of the format :-(
+ */
+ header = (((dctx->bits & 0xFF00) >> 8) |
+ ((dctx->bits & 0x00FF) << 8));
+ EATBITS(16);
+
+ /*
+ * Check the header:
+ *
+ * - bits 8-11 should be 1000 (Deflate/RFC1951)
+ * - bits 12-15 should be at most 0111 (window size)
+ * - bit 5 should be zero (no dictionary present)
+ * - we don't care about bits 6-7 (compression rate)
+ * - bits 0-4 should be set up to make the whole thing
+ * a multiple of 31 (checksum).
+ */
+ if ((header & 0x0F00) != 0x0800 ||
+ (header & 0xF000) > 0x7000 ||
+ (header & 0x0020) != 0x0000 ||
+ (header % 31) != 0)
+ goto decode_error;
+
+ dctx->state = OUTSIDEBLK;
+ break;
+ case OUTSIDEBLK:
+ /* Expect 3-bit block header. */
+ if (dctx->nbits < 3)
+ goto finished; /* done all we can */
+ EATBITS(1);
+ blktype = dctx->bits & 3;
+ EATBITS(2);
+ if (blktype == 0) {
+ int to_eat = dctx->nbits & 7;
+ dctx->state = UNCOMP_LEN;
+ EATBITS(to_eat); /* align to byte boundary */
+ } else if (blktype == 1) {
+ dctx->currlentable = dctx->staticlentable;
+ dctx->currdisttable = dctx->staticdisttable;
+ dctx->state = INBLK;
+ } else if (blktype == 2) {
+ dctx->state = TREES_HDR;
+ }
+ break;
+ case TREES_HDR:
+ /*
+ * Dynamic block header. Five bits of HLIT, five of
+ * HDIST, four of HCLEN.
+ */
+ if (dctx->nbits < 5 + 5 + 4)
+ goto finished; /* done all we can */
+ dctx->hlit = 257 + (dctx->bits & 31);
+ EATBITS(5);
+ dctx->hdist = 1 + (dctx->bits & 31);
+ EATBITS(5);
+ dctx->hclen = 4 + (dctx->bits & 15);
+ EATBITS(4);
+ dctx->lenptr = 0;
+ dctx->state = TREES_LENLEN;
+ memset(dctx->lenlen, 0, sizeof(dctx->lenlen));
+ break;
+ case TREES_LENLEN:
+ if (dctx->nbits < 3)
+ goto finished;
+ while (dctx->lenptr < dctx->hclen && dctx->nbits >= 3) {
+ dctx->lenlen[lenlenmap[dctx->lenptr++]] =
+ (unsigned char) (dctx->bits & 7);
+ EATBITS(3);
+ }
+ if (dctx->lenptr == dctx->hclen) {
+ dctx->lenlentable = zlib_mktable(dctx->lenlen, 19);
+ dctx->state = TREES_LEN;
+ dctx->lenptr = 0;
+ }
+ break;
+ case TREES_LEN:
+ if (dctx->lenptr >= dctx->hlit + dctx->hdist) {
+ dctx->currlentable = zlib_mktable(dctx->lengths, dctx->hlit);
+ dctx->currdisttable = zlib_mktable(dctx->lengths + dctx->hlit,
+ dctx->hdist);
+ zlib_freetable(&dctx->lenlentable);
+ dctx->lenlentable = NULL;
+ dctx->state = INBLK;
+ break;
+ }
+ code =
+ zlib_huflookup(&dctx->bits, &dctx->nbits, dctx->lenlentable);
+ if (code == -1)
+ goto finished;
+ if (code == -2)
+ goto decode_error;
+ if (code < 16)
+ dctx->lengths[dctx->lenptr++] = code;
+ else {
+ dctx->lenextrabits = (code == 16 ? 2 : code == 17 ? 3 : 7);
+ dctx->lenaddon = (code == 18 ? 11 : 3);
+ dctx->lenrep = (code == 16 && dctx->lenptr > 0 ?
+ dctx->lengths[dctx->lenptr - 1] : 0);
+ dctx->state = TREES_LENREP;
+ }
+ break;
+ case TREES_LENREP:
+ if (dctx->nbits < dctx->lenextrabits)
+ goto finished;
+ rep =
+ dctx->lenaddon +
+ (dctx->bits & ((1 << dctx->lenextrabits) - 1));
+ EATBITS(dctx->lenextrabits);
+ while (rep > 0 && dctx->lenptr < dctx->hlit + dctx->hdist) {
+ dctx->lengths[dctx->lenptr] = dctx->lenrep;
+ dctx->lenptr++;
+ rep--;
+ }
+ dctx->state = TREES_LEN;
+ break;
+ case INBLK:
+ code =
+ zlib_huflookup(&dctx->bits, &dctx->nbits, dctx->currlentable);
+ if (code == -1)
+ goto finished;
+ if (code == -2)
+ goto decode_error;
+ if (code < 256)
+ zlib_emit_char(dctx, code);
+ else if (code == 256) {
+ dctx->state = OUTSIDEBLK;
+ if (dctx->currlentable != dctx->staticlentable) {
+ zlib_freetable(&dctx->currlentable);
+ dctx->currlentable = NULL;
+ }
+ if (dctx->currdisttable != dctx->staticdisttable) {
+ zlib_freetable(&dctx->currdisttable);
+ dctx->currdisttable = NULL;
+ }
+ } else if (code < 286) {
+ dctx->state = GOTLENSYM;
+ dctx->sym = code;
+ } else {
+ /* literal/length symbols 286 and 287 are invalid */
+ goto decode_error;
+ }
+ break;
+ case GOTLENSYM:
+ rec = &lencodes[dctx->sym - 257];
+ if (dctx->nbits < rec->extrabits)
+ goto finished;
+ dctx->len =
+ rec->min + (dctx->bits & ((1 << rec->extrabits) - 1));
+ EATBITS(rec->extrabits);
+ dctx->state = GOTLEN;
+ break;
+ case GOTLEN:
+ code =
+ zlib_huflookup(&dctx->bits, &dctx->nbits,
+ dctx->currdisttable);
+ if (code == -1)
+ goto finished;
+ if (code == -2)
+ goto decode_error;
+ if (code >= 30) /* dist symbols 30 and 31 are invalid */
+ goto decode_error;
+ dctx->state = GOTDISTSYM;
+ dctx->sym = code;
+ break;
+ case GOTDISTSYM:
+ rec = &distcodes[dctx->sym];
+ if (dctx->nbits < rec->extrabits)
+ goto finished;
+ dist = rec->min + (dctx->bits & ((1 << rec->extrabits) - 1));
+ EATBITS(rec->extrabits);
+ dctx->state = INBLK;
+ while (dctx->len--)
+ zlib_emit_char(dctx, dctx->window[(dctx->winpos - dist) &
+ (WINSIZE - 1)]);
+ break;
+ case UNCOMP_LEN:
+ /*
+ * Uncompressed block. We expect to see a 16-bit LEN.
+ */
+ if (dctx->nbits < 16)
+ goto finished;
+ dctx->uncomplen = dctx->bits & 0xFFFF;
+ EATBITS(16);
+ dctx->state = UNCOMP_NLEN;
+ break;
+ case UNCOMP_NLEN:
+ /*
+ * Uncompressed block. We expect to see a 16-bit NLEN,
+ * which should be the one's complement of the previous
+ * LEN.
+ */
+ if (dctx->nbits < 16)
+ goto finished;
+ nlen = dctx->bits & 0xFFFF;
+ EATBITS(16);
+ if (dctx->uncomplen != (nlen ^ 0xFFFF))
+ goto decode_error;
+ if (dctx->uncomplen == 0)
+ dctx->state = OUTSIDEBLK; /* block is empty */
+ else
+ dctx->state = UNCOMP_DATA;
+ break;
+ case UNCOMP_DATA:
+ if (dctx->nbits < 8)
+ goto finished;
+ zlib_emit_char(dctx, dctx->bits & 0xFF);
+ EATBITS(8);
+ if (--dctx->uncomplen == 0)
+ dctx->state = OUTSIDEBLK; /* end of uncompressed block */
+ break;
+ }
+ }
+
+ finished:
+ *outlen = dctx->outblk->len;
+ *outblock = (unsigned char *)strbuf_to_str(dctx->outblk);
+ dctx->outblk = NULL;
+ return true;
+
+ decode_error:
+ *outblock = NULL;
+ *outlen = 0;
+ return false;
+}
+
+const ssh_compression_alg ssh_zlib = {
+ .name = "zlib",
+ .delayed_name = "zlib@openssh.com", /* delayed version */
+ .compress_new = zlib_compress_init,
+ .compress_free = zlib_compress_cleanup,
+ .compress = zlib_compress_block,
+ .decompress_new = zlib_decompress_init,
+ .decompress_free = zlib_decompress_cleanup,
+ .decompress = zlib_decompress_block,
+ .text_name = "zlib (RFC1950)",
+};
diff --git a/ssh1bpp.c b/ssh1bpp.c
deleted file mode 100644
index 46eccbeb..00000000
--- a/ssh1bpp.c
+++ /dev/null
@@ -1,387 +0,0 @@
-/*
- * Binary packet protocol for SSH-1.
- */
-
-#include <assert.h>
-
-#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/ssh1connection-client.c b/ssh1connection-client.c
deleted file mode 100644
index cf7dd04e..00000000
--- a/ssh1connection-client.c
+++ /dev/null
@@ -1,547 +0,0 @@
-/*
- * Client-specific parts of the SSH-1 connection layer.
- */
-
-#include <assert.h>
-
-#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/ssh1connection-server.c b/ssh1connection-server.c
deleted file mode 100644
index 4d55abe5..00000000
--- a/ssh1connection-server.c
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * Server-specific parts of the SSH-1 connection layer.
- */
-
-#include <assert.h>
-
-#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/ssh1connection.c b/ssh1connection.c
deleted file mode 100644
index d805dd29..00000000
--- a/ssh1connection.c
+++ /dev/null
@@ -1,815 +0,0 @@
-/*
- * Packet protocol layer for the SSH-1 'connection protocol', i.e.
- * everything after authentication finishes.
- */
-
-#include <assert.h>
-
-#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);
- 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/ssh1connection.h b/ssh1connection.h
deleted file mode 100644
index 44370787..00000000
--- a/ssh1connection.h
+++ /dev/null
@@ -1,123 +0,0 @@
-struct ssh1_channel;
-
-struct outstanding_succfail;
-
-struct ssh1_connection_state {
- int crState;
-
- Conf *conf;
- int local_protoflags, remote_protoflags;
-
- tree234 *channels; /* indexed by local id */
-
- /* In SSH-1, the main session doesn't take the form of a 'channel'
- * according to the wire protocol. But we want to use the same API
- * for it, so we define an SshChannel here - but one that uses a
- * separate vtable from the usual one, so it doesn't map to a
- * struct ssh1_channel as all the others do. */
- SshChannel mainchan_sc;
- Channel *mainchan_chan; /* the other end of mainchan_sc */
- mainchan *mainchan; /* and its subtype */
-
- bool got_pty;
- bool ldisc_opts[LD_N_OPTIONS];
- bool stdout_throttling;
- bool want_user_input;
- bool session_terminated;
- int term_width, term_height, term_width_orig, term_height_orig;
-
- 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/ssh1login-server.c b/ssh1login-server.c
deleted file mode 100644
index c9c10eef..00000000
--- a/ssh1login-server.c
+++ /dev/null
@@ -1,447 +0,0 @@
-/*
- * Packet protocol layer for the SSH-1 login phase, from the server side.
- */
-
-#include <assert.h>
-
-#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/ssh1login.c b/ssh1login.c
deleted file mode 100644
index 519838d7..00000000
--- a/ssh1login.c
+++ /dev/null
@@ -1,1254 +0,0 @@
-/*
- * Packet protocol layer for the SSH-1 login phase (combining what
- * SSH-2 would think of as key exchange and user authentication).
- */
-
-#include <assert.h>
-
-#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]);
- 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/ssh2bpp-bare.c b/ssh2bpp-bare.c
deleted file mode 100644
index 90f196e8..00000000
--- a/ssh2bpp-bare.c
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Trivial binary packet protocol for the 'bare' ssh-connection
- * protocol used in PuTTY's SSH-2 connection sharing system.
- */
-
-#include <assert.h>
-
-#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/ssh2bpp.c b/ssh2bpp.c
deleted file mode 100644
index 09b23e5a..00000000
--- a/ssh2bpp.c
+++ /dev/null
@@ -1,982 +0,0 @@
-/*
- * Binary packet protocol for SSH-2.
- */
-
-#include <assert.h>
-
-#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/ssh2connection-client.c b/ssh2connection-client.c
deleted file mode 100644
index 0b13efe6..00000000
--- a/ssh2connection-client.c
+++ /dev/null
@@ -1,505 +0,0 @@
-/*
- * Client-specific parts of the SSH-2 connection layer.
- */
-
-#include <assert.h>
-
-#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/ssh2connection-server.c b/ssh2connection-server.c
deleted file mode 100644
index 1467db11..00000000
--- a/ssh2connection-server.c
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Server-specific parts of the SSH-2 connection layer.
- */
-
-#include <assert.h>
-
-#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/ssh2connection.c b/ssh2connection.c
deleted file mode 100644
index ecb421ea..00000000
--- a/ssh2connection.c
+++ /dev/null
@@ -1,1745 +0,0 @@
-/*
- * Packet protocol layer for the SSH-2 connection protocol (RFC 4254).
- */
-
-#include <assert.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "sshbpp.h"
-#include "sshppl.h"
-#include "sshchan.h"
-#include "sshcr.h"
-#include "ssh2connection.h"
-
-static void ssh2_connection_free(PacketProtocolLayer *);
-static void ssh2_connection_process_queue(PacketProtocolLayer *);
-static bool ssh2_connection_get_specials(
- PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx);
-static void ssh2_connection_special_cmd(PacketProtocolLayer *ppl,
- SessionSpecialCode code, int arg);
-static 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");
- 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);
- 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/ssh2connection.h b/ssh2connection.h
deleted file mode 100644
index d3bb240a..00000000
--- a/ssh2connection.h
+++ /dev/null
@@ -1,235 +0,0 @@
-#ifndef PUTTY_SSH2CONNECTION_H
-#define PUTTY_SSH2CONNECTION_H
-
-struct outstanding_channel_request;
-struct outstanding_global_request;
-
-struct ssh2_connection_state {
- int crState;
-
- ssh_sharing_state *connshare;
- char *peer_verstring;
-
- mainchan *mainchan;
- SshChannel *mainchan_sc;
- bool ldisc_opts[LD_N_OPTIONS];
- int session_attempt, session_status;
- int term_width, term_height;
- bool want_user_input;
-
- 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/ssh2kex-client.c b/ssh2kex-client.c
deleted file mode 100644
index 1dd960c1..00000000
--- a/ssh2kex-client.c
+++ /dev/null
@@ -1,930 +0,0 @@
-/*
- * Client side of key exchange for the SSH-2 transport protocol (RFC 4253).
- */
-
-#include <assert.h>
-
-#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.
- */
-const int deliberate_symbol_clash = 12345;
-
-void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
-{
- PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
- PktIn *pktin;
- PktOut *pktout;
-
- crBegin(s->crStateKex);
-
- if (s->kex_alg->main_type == KEXTYPE_DH) {
- /*
- * Work out the number of bits of key we will need from the
- * key exchange. We start with the maximum key length of
- * either cipher...
- */
- {
- int csbits, scbits;
-
- csbits = s->out.cipher ? s->out.cipher->real_keybits : 0;
- scbits = s->in.cipher ? s->in.cipher->real_keybits : 0;
- s->nbits = (csbits > scbits ? csbits : scbits);
- }
- /* The keys only have hlen-bit entropy, since they're based on
- * a hash. So cap the key size at hlen bits. */
- if (s->nbits > s->kex_alg->hash->hlen * 8)
- s->nbits = s->kex_alg->hash->hlen * 8;
-
- /*
- * If we're doing Diffie-Hellman group exchange, start by
- * requesting a group.
- */
- if (dh_is_gex(s->kex_alg)) {
- ppl_logevent("Doing Diffie-Hellman group exchange");
- s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGEX;
- /*
- * Work out how big a DH group we will need to allow that
- * much data.
- */
- s->pbits = 512 << ((s->nbits - 1) / 64);
- if (s->pbits < DH_MIN_SIZE)
- s->pbits = DH_MIN_SIZE;
- if (s->pbits > DH_MAX_SIZE)
- s->pbits = DH_MAX_SIZE;
- if ((s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) {
- pktout = ssh_bpp_new_pktout(
- s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD);
- put_uint32(pktout, s->pbits);
- } else {
- pktout = ssh_bpp_new_pktout(
- s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST);
- put_uint32(pktout, DH_MIN_SIZE);
- put_uint32(pktout, s->pbits);
- put_uint32(pktout, DH_MAX_SIZE);
- }
- pq_push(s->ppl.out_pq, pktout);
-
- crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL);
- if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) {
- ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
- "expecting Diffie-Hellman group, type %d (%s)",
- pktin->type,
- ssh2_pkt_type(s->ppl.bpp->pls->kctx,
- s->ppl.bpp->pls->actx,
- pktin->type));
- *aborted = true;
- return;
- }
- s->p = get_mp_ssh2(pktin);
- s->g = get_mp_ssh2(pktin);
- if (get_err(pktin)) {
- ssh_proto_error(s->ppl.ssh,
- "Unable to parse Diffie-Hellman group packet");
- *aborted = true;
- return;
- }
- s->dh_ctx = dh_setup_gex(s->p, s->g);
- s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
- s->kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
-
- ppl_logevent("Doing Diffie-Hellman key exchange using %d-bit "
- "modulus and hash %s with a server-supplied group",
- dh_modulus_bit_size(s->dh_ctx),
- ssh_hash_alg(s->exhash)->text_name);
- } else {
- s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGROUP;
- s->dh_ctx = dh_setup_group(s->kex_alg);
- s->kex_init_value = SSH2_MSG_KEXDH_INIT;
- s->kex_reply_value = SSH2_MSG_KEXDH_REPLY;
-
- ppl_logevent("Doing Diffie-Hellman key exchange using %d-bit "
- "modulus and hash %s with standard group \"%s\"",
- dh_modulus_bit_size(s->dh_ctx),
- ssh_hash_alg(s->exhash)->text_name,
- s->kex_alg->groupname);
- }
-
- /*
- * Now generate and send e for Diffie-Hellman.
- */
- seat_set_busy_status(s->ppl.seat, BUSY_CPU);
- s->e = dh_create_e(s->dh_ctx, 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/ssh2kex-server.c b/ssh2kex-server.c
deleted file mode 100644
index 1fbb588b..00000000
--- a/ssh2kex-server.c
+++ /dev/null
@@ -1,330 +0,0 @@
-/*
- * Server side of key exchange for the SSH-2 transport protocol (RFC 4253).
- */
-
-#include <assert.h>
-
-#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/ssh2transport.c b/ssh2transport.c
deleted file mode 100644
index 4e1b443d..00000000
--- a/ssh2transport.c
+++ /dev/null
@@ -1,2181 +0,0 @@
-/*
- * Packet protocol layer for the SSH-2 transport protocol (RFC 4253).
- */
-
-#include <assert.h>
-
-#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/ssh2transport.h b/ssh2transport.h
deleted file mode 100644
index 349c06f0..00000000
--- a/ssh2transport.h
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * 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/ssh2userauth-server.c b/ssh2userauth-server.c
deleted file mode 100644
index cacddee6..00000000
--- a/ssh2userauth-server.c
+++ /dev/null
@@ -1,377 +0,0 @@
-/*
- * Packet protocol layer for the server side of the SSH-2 userauth
- * protocol (RFC 4252).
- */
-
-#include <assert.h>
-
-#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/ssh2userauth.c b/ssh2userauth.c
deleted file mode 100644
index 451d5abe..00000000
--- a/ssh2userauth.c
+++ /dev/null
@@ -1,1973 +0,0 @@
-/*
- * Packet protocol layer for the client side of the SSH-2 userauth
- * protocol (RFC 4252).
- */
-
-#include <assert.h>
-
-#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;
-
-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;
-
- 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]);
- 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");
- } else {
- assert(s->type == AUTH_TYPE_PASSWORD);
- ppl_logevent("Password authentication failed");
- ppl_printf("Access denied\r\n");
-
- if (s->change_username) {
- /* XXX perhaps we should allow
- * keyboard-interactive to do this too? */
- goto try_new_username;
- }
- }
- } else {
- ppl_printf("Further authentication required\r\n");
- ppl_logevent("Further authentication required");
- }
-
- /*
- * Save the methods string for use in error messages.
- */
- strbuf_clear(s->last_methods_string);
- put_datapl(s->last_methods_string, methods);
-
- /*
- * Scan it for method identifiers we know about.
- */
- bool srv_pubkey = false, srv_passwd = false;
- bool srv_keyb_inter = false;
-#ifndef NO_GSSAPI
- bool srv_gssapi = false, srv_gssapi_keyex_auth = false;
-#endif
-
- for (ptrlen method; get_commasep_word(&methods, &method) ;) {
- if (ptrlen_eq_string(method, "publickey"))
- srv_pubkey = true;
- else if (ptrlen_eq_string(method, "password"))
- srv_passwd = true;
- else if (ptrlen_eq_string(method, "keyboard-interactive"))
- srv_keyb_inter = true;
-#ifndef NO_GSSAPI
- else if (ptrlen_eq_string(method, "gssapi-with-mic"))
- srv_gssapi = true;
- else if (ptrlen_eq_string(method, "gssapi-keyex"))
- srv_gssapi_keyex_auth = true;
-#endif
- }
-
- /*
- * And combine those flags with our own configuration
- * and context to set the main can_foo variables.
- */
- s->can_pubkey = srv_pubkey;
- s->can_passwd = srv_passwd;
- s->can_keyb_inter = s->try_ki_auth && srv_keyb_inter;
-#ifndef NO_GSSAPI
- s->can_gssapi = s->try_gssapi_auth && srv_gssapi &&
- s->shgss->libs->nlibraries > 0;
- s->can_gssapi_keyex_auth = s->try_gssapi_kex_auth &&
- srv_gssapi_keyex_auth &&
- s->shgss->libs->nlibraries > 0 && s->shgss->ctx;
-#endif
- }
-
- s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH;
-
-#ifndef NO_GSSAPI
- if (s->can_gssapi_keyex_auth && !s->tried_gssapi_keyex_auth) {
-
- /* gssapi-keyex authentication */
-
- s->type = AUTH_TYPE_GSSAPI;
- s->tried_gssapi_keyex_auth = true;
- s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI;
-
- if (s->shgss->lib->gsslogmsg)
- ppl_logevent("%s", s->shgss->lib->gsslogmsg);
-
- ppl_logevent("Trying gssapi-keyex...");
- s->pktout = ssh2_userauth_gss_packet(s, "gssapi-keyex");
- pq_push(s->ppl.out_pq, s->pktout);
- s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx);
- s->shgss->ctx = NULL;
-
- continue;
- } else
-#endif /* NO_GSSAPI */
-
- if (s->can_pubkey && !s->done_agent &&
- s->agent_key_index < s->agent_key_limit) {
-
- /*
- * Attempt public-key authentication using a key from Pageant.
- */
- s->agent_keyalg = s->agent_keys[s->agent_key_index].algorithm;
- 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");
-
- /*
- * 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);
- 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 */
- smemclr(passphrase, strlen(passphrase));
- sfree(passphrase);
- }
- if (key == SSH2_WRONG_PASSPHRASE || key == NULL) {
- if (passphrase &&
- (key == SSH2_WRONG_PASSPHRASE)) {
- ppl_printf("Wrong passphrase\r\n");
- key = NULL;
- /* and loop again */
- } else {
- ppl_printf("Unable to load private key (%s)\r\n",
- error);
- key = NULL;
- s->suppress_wait_for_response_packet = true;
- break; /* try something else */
- }
- } else {
- /* FIXME: if we ever support variable signature
- * flags, this is somewhere they'll need to be
- * put */
- char *invalid = ssh_key_invalid(key->key, 0);
- if (invalid) {
- ppl_printf("Cannot use this private key (%s)\r\n",
- invalid);
- ssh_key_free(key->key);
- sfree(key->comment);
- sfree(key);
- sfree(invalid);
- key = NULL;
- s->suppress_wait_for_response_packet = true;
- break; /* try something else */
- }
- }
- }
-
- if (key) {
- strbuf *pkblob, *sigdata, *sigblob;
-
- /*
- * We have loaded the private key and the server
- * has announced that it's willing to accept it.
- * Hallelujah. Generate a signature and send it.
- */
- s->pktout = ssh_bpp_new_pktout(
- s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST);
- put_stringz(s->pktout, s->username);
- put_stringz(s->pktout, s->successor_layer->vt->name);
- put_stringz(s->pktout, "publickey"); /* method */
- put_bool(s->pktout, true); /* signature follows */
- 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(
- "<server failed to send prompt>: "));
- } 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).
- */
- 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.
- */
- 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++) {
- 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);
- 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);
- put_stringz(s->pktout, s->password);
- s->pktout->minlen = 256;
- pq_push(s->ppl.out_pq, s->pktout);
- ppl_logevent("Sent password");
- s->type = AUTH_TYPE_PASSWORD;
-
- /*
- * Wait for next packet, in case it's a password change
- * request.
- */
- crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
- changereq_first_time = true;
-
- while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) {
-
- /*
- * We're being asked for a new password
- * (perhaps not for the first time).
- * Loop until the server accepts it.
- */
-
- bool got_new = false; /* not live over crReturn */
- ptrlen prompt; /* not live over crReturn */
-
- {
- const char *msg;
- if (changereq_first_time)
- msg = "Server requested password change";
- else
- msg = "Server rejected new password";
- ppl_logevent("%s", msg);
- ppl_printf("%s\r\n", msg);
- }
-
- prompt = get_string(pktin);
-
- s->cur_prompt = 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:
- 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/sshaes.c b/sshaes.c
deleted file mode 100644
index 6671879e..00000000
--- a/sshaes.c
+++ /dev/null
@@ -1,1913 +0,0 @@
-/*
- * sshaes.c - implementation of AES
- */
-
-#include <assert.h>
-#include <stdlib.h>
-
-#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(<wmmintrin.h>) && \
- (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(<arm_neon.h>) && \
- (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 <arm64_neon.h> in place
- * of the standard <arm_neon.h> */
-# 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]/<X^8+X^4+X^3+X+1> be the finite field
- * used in AES, and let R = F[Y]/<Y^4+1> 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 <wmmintrin.h>
-#include <smmintrin.h>
-
-#if defined(__clang__) || defined(__GNUC__)
-#include <cpuid.h>
-#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 <arm64_neon.h>
-#else
-#include <arm_neon.h>
-#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/ssharcf.c b/ssharcf.c
deleted file mode 100644
index 53821473..00000000
--- a/ssharcf.c
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Arcfour (RC4) implementation for PuTTY.
- *
- * Coded from Schneier.
- */
-
-#include <assert.h>
-#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/sshargon2.c b/sshargon2.c
deleted file mode 100644
index 25385d7e..00000000
--- a/sshargon2.c
+++ /dev/null
@@ -1,565 +0,0 @@
-/*
- * Implementation of the Argon2 password hash function.
- *
- * My sources for the algorithm description and test vectors (the latter in
- * test/cryptsuite.py) were the reference implementation on Github, and also
- * the Internet-Draft description:
- *
- * https://github.com/P-H-C/phc-winner-argon2
- * https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-argon2-13
- */
-
-#include <assert.h>
-
-#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/sshauxcrypt.c b/sshauxcrypt.c
deleted file mode 100644
index 7f64b27a..00000000
--- a/sshauxcrypt.c
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * 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/sshbcrypt.c b/sshbcrypt.c
deleted file mode 100644
index 7cdd44ed..00000000
--- a/sshbcrypt.c
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * 'bcrypt' password hash function, for PuTTY's import/export of
- * OpenSSH encrypted private key files.
- *
- * This is not really the same as the original bcrypt; OpenSSH has
- * modified it in various ways, and of course we have to do the same.
- */
-
-#include <stddef.h>
-#include <string.h>
-#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/sshblowf.c b/sshblowf.c
deleted file mode 100644
index c74f06c0..00000000
--- a/sshblowf.c
+++ /dev/null
@@ -1,699 +0,0 @@
-/*
- * Blowfish implementation for PuTTY.
- *
- * Coded from scratch from the algorithm description.
- */
-
-#include <assert.h>
-#include <stdio.h>
-#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/sshblowf.h b/sshblowf.h
deleted file mode 100644
index a9efe3da..00000000
--- a/sshblowf.h
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * 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/sshccp.c b/sshccp.c
deleted file mode 100644
index dd25b997..00000000
--- a/sshccp.c
+++ /dev/null
@@ -1,1062 +0,0 @@
-/*
- * ChaCha20-Poly1305 Implementation for SSH-2
- *
- * Protocol spec:
- * http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?rev=1.2&content-type=text/x-cvsweb-markup
- *
- * ChaCha20 spec:
- * http://cr.yp.to/chacha/chacha-20080128.pdf
- *
- * Salsa20 spec:
- * http://cr.yp.to/snuffle/spec.pdf
- *
- * Poly1305-AES spec:
- * http://cr.yp.to/mac/poly1305-20050329.pdf
- *
- * The nonce for the Poly1305 is the second part of the key output
- * from the first round of ChaCha20. This removes the AES requirement.
- * This is undocumented!
- *
- * This has an intricate link between the cipher and the MAC. The
- * keying of both is done in by the cipher and setting of the IV is
- * done by the MAC. One cannot operate without the other. The
- * configuration of the ssh_cipheralg structure ensures that the MAC is
- * set (and others ignored) if this cipher is chosen.
- *
- * This cipher also encrypts the length using a different
- * instantiation of the cipher using a different key and IV made from
- * the sequence number which is passed in addition when calling
- * encrypt/decrypt on it.
- */
-
-#include "ssh.h"
-#include "mpint_i.h"
-
-#ifndef INLINE
-#define INLINE
-#endif
-
-/* ChaCha20 implementation, only supporting 256-bit keys */
-
-/* State for each ChaCha20 instance */
-struct chacha20 {
- /* Current context, usually with the count incremented
- * 0-3 are the static constant
- * 4-11 are the key
- * 12-13 are the counter
- * 14-15 are the IV */
- uint32_t state[16];
- /* The output of the state above ready to xor */
- unsigned char current[64];
- /* The index of the above currently used to allow a true streaming cipher */
- int currentIndex;
-};
-
-static INLINE void chacha20_round(struct chacha20 *ctx)
-{
- int i;
- uint32_t copy[16];
-
- /* Take a copy */
- memcpy(copy, ctx->state, sizeof(copy));
-
- /* A circular rotation for a 32bit number */
-#define rotl(x, shift) x = ((x << shift) | (x >> (32 - shift)))
-
- /* What to do for each quarter round operation */
-#define qrop(a, b, c, d) \
- copy[a] += copy[b]; \
- copy[c] ^= copy[a]; \
- rotl(copy[c], d)
-
- /* A quarter round */
-#define quarter(a, b, c, d) \
- qrop(a, b, d, 16); \
- qrop(c, d, b, 12); \
- qrop(a, b, d, 8); \
- qrop(c, d, b, 7)
-
- /* Do 20 rounds, in pairs because every other is different */
- for (i = 0; i < 20; i += 2) {
- /* A round */
- quarter(0, 4, 8, 12);
- quarter(1, 5, 9, 13);
- quarter(2, 6, 10, 14);
- quarter(3, 7, 11, 15);
- /* Another slightly different round */
- quarter(0, 5, 10, 15);
- quarter(1, 6, 11, 12);
- quarter(2, 7, 8, 13);
- quarter(3, 4, 9, 14);
- }
-
- /* Dump the macros, don't need them littering */
-#undef rotl
-#undef qrop
-#undef quarter
-
- /* Add the initial state */
- for (i = 0; i < 16; ++i) {
- copy[i] += ctx->state[i];
- }
-
- /* Update the content of the xor buffer */
- for (i = 0; i < 16; ++i) {
- ctx->current[i * 4 + 0] = copy[i] >> 0;
- ctx->current[i * 4 + 1] = copy[i] >> 8;
- ctx->current[i * 4 + 2] = copy[i] >> 16;
- ctx->current[i * 4 + 3] = copy[i] >> 24;
- }
- /* State full, reset pointer to beginning */
- ctx->currentIndex = 0;
- smemclr(copy, sizeof(copy));
-
- /* Increment round counter */
- ++ctx->state[12];
- /* Check for overflow, not done in one line so the 32 bits are chopped by the type */
- if (!(uint32_t)(ctx->state[12])) {
- ++ctx->state[13];
- }
-}
-
-/* Initialise context with 256bit key */
-static void chacha20_key(struct chacha20 *ctx, const unsigned char *key)
-{
- static const char constant[16] = "expand 32-byte k";
-
- /* Add the fixed string to the start of the state */
- ctx->state[0] = GET_32BIT_LSB_FIRST(constant + 0);
- ctx->state[1] = GET_32BIT_LSB_FIRST(constant + 4);
- ctx->state[2] = GET_32BIT_LSB_FIRST(constant + 8);
- ctx->state[3] = GET_32BIT_LSB_FIRST(constant + 12);
-
- /* Add the key */
- ctx->state[4] = GET_32BIT_LSB_FIRST(key + 0);
- ctx->state[5] = GET_32BIT_LSB_FIRST(key + 4);
- ctx->state[6] = GET_32BIT_LSB_FIRST(key + 8);
- ctx->state[7] = GET_32BIT_LSB_FIRST(key + 12);
- ctx->state[8] = GET_32BIT_LSB_FIRST(key + 16);
- ctx->state[9] = GET_32BIT_LSB_FIRST(key + 20);
- ctx->state[10] = GET_32BIT_LSB_FIRST(key + 24);
- ctx->state[11] = GET_32BIT_LSB_FIRST(key + 28);
-
- /* New key, dump context */
- ctx->currentIndex = 64;
-}
-
-static void chacha20_iv(struct chacha20 *ctx, const unsigned char *iv)
-{
- ctx->state[12] = 0;
- ctx->state[13] = 0;
- ctx->state[14] = GET_32BIT_MSB_FIRST(iv);
- ctx->state[15] = GET_32BIT_MSB_FIRST(iv + 4);
-
- /* New IV, dump context */
- ctx->currentIndex = 64;
-}
-
-static void chacha20_encrypt(struct chacha20 *ctx, unsigned char *blk, int len)
-{
- while (len) {
- /* If we don't have any state left, then cycle to the next */
- if (ctx->currentIndex >= 64) {
- chacha20_round(ctx);
- }
-
- /* Do the xor while there's some state left and some plaintext left */
- while (ctx->currentIndex < 64 && len) {
- *blk++ ^= ctx->current[ctx->currentIndex++];
- --len;
- }
- }
-}
-
-/* Decrypt is encrypt... It's xor against a PRNG... */
-static INLINE void chacha20_decrypt(struct chacha20 *ctx,
- unsigned char *blk, int len)
-{
- chacha20_encrypt(ctx, blk, len);
-}
-
-/* Poly1305 implementation (no AES, nonce is not encrypted) */
-
-#define NWORDS ((130 + BIGNUM_INT_BITS-1) / BIGNUM_INT_BITS)
-typedef struct bigval {
- BignumInt w[NWORDS];
-} bigval;
-
-static void bigval_clear(bigval *r)
-{
- int i;
- for (i = 0; i < NWORDS; i++)
- r->w[i] = 0;
-}
-
-static void bigval_import_le(bigval *r, const void *vdata, int len)
-{
- const unsigned char *data = (const unsigned char *)vdata;
- int i;
- bigval_clear(r);
- for (i = 0; i < len; i++)
- r->w[i / BIGNUM_INT_BYTES] |=
- (BignumInt)data[i] << (8 * (i % BIGNUM_INT_BYTES));
-}
-
-static void bigval_export_le(const bigval *r, void *vdata, int len)
-{
- unsigned char *data = (unsigned char *)vdata;
- int i;
- for (i = 0; i < len; i++)
- data[i] = r->w[i / BIGNUM_INT_BYTES] >> (8 * (i % BIGNUM_INT_BYTES));
-}
-
-/*
- * Core functions to do arithmetic mod p = 2^130-5. The whole
- * collection of these, up to and including the surrounding #if, are
- * generated automatically for various sizes of BignumInt by
- * contrib/make1305.py.
- */
-
-#if BIGNUM_INT_BITS == 16
-
-static void bigval_add(bigval *r, const bigval *a, const bigval *b)
-{
- BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
- BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26;
- BignumCarry carry;
-
- v0 = a->w[0];
- v1 = a->w[1];
- v2 = a->w[2];
- v3 = a->w[3];
- v4 = a->w[4];
- v5 = a->w[5];
- v6 = a->w[6];
- v7 = a->w[7];
- v8 = a->w[8];
- v9 = b->w[0];
- v10 = b->w[1];
- v11 = b->w[2];
- v12 = b->w[3];
- v13 = b->w[4];
- v14 = b->w[5];
- v15 = b->w[6];
- v16 = b->w[7];
- v17 = b->w[8];
- BignumADC(v18, carry, v0, v9, 0);
- BignumADC(v19, carry, v1, v10, carry);
- BignumADC(v20, carry, v2, v11, carry);
- BignumADC(v21, carry, v3, v12, carry);
- BignumADC(v22, carry, v4, v13, carry);
- BignumADC(v23, carry, v5, v14, carry);
- BignumADC(v24, carry, v6, v15, carry);
- BignumADC(v25, carry, v7, v16, carry);
- v26 = v8 + v17 + carry;
- r->w[0] = v18;
- r->w[1] = v19;
- r->w[2] = v20;
- r->w[3] = v21;
- r->w[4] = v22;
- r->w[5] = v23;
- r->w[6] = v24;
- r->w[7] = v25;
- r->w[8] = v26;
-}
-
-static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b)
-{
- BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
- BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27;
- BignumInt v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40;
- BignumInt v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53;
- BignumInt v54, v55, v56, v57, v58, v59, v60, v61, v62, v63, v64, v65, v66;
- BignumInt v67, v68, v69, v70, v71, v72, v73, v74, v75, v76, v77, v78, v79;
- BignumInt v80, v81, v82, v83, v84, v85, v86, v87, v88, v89, v90, v91, v92;
- BignumInt v93, v94, v95, v96, v97, v98, v99, v100, v101, v102, v103, v104;
- BignumInt v105, v106, v107, v108, v109, v110, v111, v112, v113, v114;
- BignumInt v115, v116, v117, v118, v119, v120, v121, v122, v123, v124;
- BignumInt v125, v126, v127, v128, v129, v130, v131, v132, v133, v134;
- BignumInt v135, v136, v137, v138, v139, v140, v141, v142, v143, v144;
- BignumInt v145, v146, v147, v148, v149, v150, v151, v152, v153, v154;
- BignumInt v155, v156, v157, v158, v159, v160, v161, v162, v163, v164;
- BignumInt v165, v166, v167, v168, v169, v170, v171, v172, v173, v174;
- BignumInt v175, v176, v177, v178, v180, v181, v182, v183, v184, v185;
- BignumInt v186, v187, v188, v189, v190, v191, v192, v193, v194, v195;
- BignumInt v196, v197, v198, v199, v200, v201, v202, v203, v204, v205;
- BignumInt v206, v207, v208, v210, v212, v213, v214, v215, v216, v217;
- BignumInt v218, v219, v220, v221, v222, v223, v224, v225, v226, v227;
- BignumInt v228, v229;
- BignumCarry carry;
-
- v0 = a->w[0];
- v1 = a->w[1];
- v2 = a->w[2];
- v3 = a->w[3];
- v4 = a->w[4];
- v5 = a->w[5];
- v6 = a->w[6];
- v7 = a->w[7];
- v8 = a->w[8];
- v9 = b->w[0];
- v10 = b->w[1];
- v11 = b->w[2];
- v12 = b->w[3];
- v13 = b->w[4];
- v14 = b->w[5];
- v15 = b->w[6];
- v16 = b->w[7];
- v17 = b->w[8];
- BignumMUL(v19, v18, v0, v9);
- BignumMULADD(v21, v20, v0, v10, v19);
- BignumMULADD(v23, v22, v0, v11, v21);
- BignumMULADD(v25, v24, v0, v12, v23);
- BignumMULADD(v27, v26, v0, v13, v25);
- BignumMULADD(v29, v28, v0, v14, v27);
- BignumMULADD(v31, v30, v0, v15, v29);
- BignumMULADD(v33, v32, v0, v16, v31);
- BignumMULADD(v35, v34, v0, v17, v33);
- BignumMULADD(v37, v36, v1, v9, v20);
- BignumMULADD2(v39, v38, v1, v10, v22, v37);
- BignumMULADD2(v41, v40, v1, v11, v24, v39);
- BignumMULADD2(v43, v42, v1, v12, v26, v41);
- BignumMULADD2(v45, v44, v1, v13, v28, v43);
- BignumMULADD2(v47, v46, v1, v14, v30, v45);
- BignumMULADD2(v49, v48, v1, v15, v32, v47);
- BignumMULADD2(v51, v50, v1, v16, v34, v49);
- BignumMULADD2(v53, v52, v1, v17, v35, v51);
- BignumMULADD(v55, v54, v2, v9, v38);
- BignumMULADD2(v57, v56, v2, v10, v40, v55);
- BignumMULADD2(v59, v58, v2, v11, v42, v57);
- BignumMULADD2(v61, v60, v2, v12, v44, v59);
- BignumMULADD2(v63, v62, v2, v13, v46, v61);
- BignumMULADD2(v65, v64, v2, v14, v48, v63);
- BignumMULADD2(v67, v66, v2, v15, v50, v65);
- BignumMULADD2(v69, v68, v2, v16, v52, v67);
- BignumMULADD2(v71, v70, v2, v17, v53, v69);
- BignumMULADD(v73, v72, v3, v9, v56);
- BignumMULADD2(v75, v74, v3, v10, v58, v73);
- BignumMULADD2(v77, v76, v3, v11, v60, v75);
- BignumMULADD2(v79, v78, v3, v12, v62, v77);
- BignumMULADD2(v81, v80, v3, v13, v64, v79);
- BignumMULADD2(v83, v82, v3, v14, v66, v81);
- BignumMULADD2(v85, v84, v3, v15, v68, v83);
- BignumMULADD2(v87, v86, v3, v16, v70, v85);
- BignumMULADD2(v89, v88, v3, v17, v71, v87);
- BignumMULADD(v91, v90, v4, v9, v74);
- BignumMULADD2(v93, v92, v4, v10, v76, v91);
- BignumMULADD2(v95, v94, v4, v11, v78, v93);
- BignumMULADD2(v97, v96, v4, v12, v80, v95);
- BignumMULADD2(v99, v98, v4, v13, v82, v97);
- BignumMULADD2(v101, v100, v4, v14, v84, v99);
- BignumMULADD2(v103, v102, v4, v15, v86, v101);
- BignumMULADD2(v105, v104, v4, v16, v88, v103);
- BignumMULADD2(v107, v106, v4, v17, v89, v105);
- BignumMULADD(v109, v108, v5, v9, v92);
- BignumMULADD2(v111, v110, v5, v10, v94, v109);
- BignumMULADD2(v113, v112, v5, v11, v96, v111);
- BignumMULADD2(v115, v114, v5, v12, v98, v113);
- BignumMULADD2(v117, v116, v5, v13, v100, v115);
- BignumMULADD2(v119, v118, v5, v14, v102, v117);
- BignumMULADD2(v121, v120, v5, v15, v104, v119);
- BignumMULADD2(v123, v122, v5, v16, v106, v121);
- BignumMULADD2(v125, v124, v5, v17, v107, v123);
- BignumMULADD(v127, v126, v6, v9, v110);
- BignumMULADD2(v129, v128, v6, v10, v112, v127);
- BignumMULADD2(v131, v130, v6, v11, v114, v129);
- BignumMULADD2(v133, v132, v6, v12, v116, v131);
- BignumMULADD2(v135, v134, v6, v13, v118, v133);
- BignumMULADD2(v137, v136, v6, v14, v120, v135);
- BignumMULADD2(v139, v138, v6, v15, v122, v137);
- BignumMULADD2(v141, v140, v6, v16, v124, v139);
- BignumMULADD2(v143, v142, v6, v17, v125, v141);
- BignumMULADD(v145, v144, v7, v9, v128);
- BignumMULADD2(v147, v146, v7, v10, v130, v145);
- BignumMULADD2(v149, v148, v7, v11, v132, v147);
- BignumMULADD2(v151, v150, v7, v12, v134, v149);
- BignumMULADD2(v153, v152, v7, v13, v136, v151);
- BignumMULADD2(v155, v154, v7, v14, v138, v153);
- BignumMULADD2(v157, v156, v7, v15, v140, v155);
- BignumMULADD2(v159, v158, v7, v16, v142, v157);
- BignumMULADD2(v161, v160, v7, v17, v143, v159);
- BignumMULADD(v163, v162, v8, v9, v146);
- BignumMULADD2(v165, v164, v8, v10, v148, v163);
- BignumMULADD2(v167, v166, v8, v11, v150, v165);
- BignumMULADD2(v169, v168, v8, v12, v152, v167);
- BignumMULADD2(v171, v170, v8, v13, v154, v169);
- BignumMULADD2(v173, v172, v8, v14, v156, v171);
- BignumMULADD2(v175, v174, v8, v15, v158, v173);
- BignumMULADD2(v177, v176, v8, v16, v160, v175);
- v178 = v8 * v17 + v161 + v177;
- v180 = (v162) & ((((BignumInt)1) << 2)-1);
- v181 = ((v162) >> 2) | ((v164) << 14);
- v182 = ((v164) >> 2) | ((v166) << 14);
- v183 = ((v166) >> 2) | ((v168) << 14);
- v184 = ((v168) >> 2) | ((v170) << 14);
- v185 = ((v170) >> 2) | ((v172) << 14);
- v186 = ((v172) >> 2) | ((v174) << 14);
- v187 = ((v174) >> 2) | ((v176) << 14);
- v188 = ((v176) >> 2) | ((v178) << 14);
- v189 = (v178) >> 2;
- v190 = (v189) & ((((BignumInt)1) << 2)-1);
- v191 = (v178) >> 4;
- BignumMUL(v193, v192, 5, v181);
- BignumMULADD(v195, v194, 5, v182, v193);
- BignumMULADD(v197, v196, 5, v183, v195);
- BignumMULADD(v199, v198, 5, v184, v197);
- BignumMULADD(v201, v200, 5, v185, v199);
- BignumMULADD(v203, v202, 5, v186, v201);
- BignumMULADD(v205, v204, 5, v187, v203);
- BignumMULADD(v207, v206, 5, v188, v205);
- v208 = 5 * v190 + v207;
- v210 = 25 * v191;
- BignumADC(v212, carry, v18, v192, 0);
- BignumADC(v213, carry, v36, v194, carry);
- BignumADC(v214, carry, v54, v196, carry);
- BignumADC(v215, carry, v72, v198, carry);
- BignumADC(v216, carry, v90, v200, carry);
- BignumADC(v217, carry, v108, v202, carry);
- BignumADC(v218, carry, v126, v204, carry);
- BignumADC(v219, carry, v144, v206, carry);
- v220 = v180 + v208 + carry;
- BignumADC(v221, carry, v212, v210, 0);
- BignumADC(v222, carry, v213, 0, carry);
- BignumADC(v223, carry, v214, 0, carry);
- BignumADC(v224, carry, v215, 0, carry);
- BignumADC(v225, carry, v216, 0, carry);
- BignumADC(v226, carry, v217, 0, carry);
- BignumADC(v227, carry, v218, 0, carry);
- BignumADC(v228, carry, v219, 0, carry);
- v229 = v220 + 0 + carry;
- r->w[0] = v221;
- r->w[1] = v222;
- r->w[2] = v223;
- r->w[3] = v224;
- r->w[4] = v225;
- r->w[5] = v226;
- r->w[6] = v227;
- r->w[7] = v228;
- r->w[8] = v229;
-}
-
-static void bigval_final_reduce(bigval *n)
-{
- BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v13, v14, v15;
- BignumInt v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28;
- BignumInt v29, v30, v31, v32, v34, v35, v36, v37, v38, v39, v40, v41, v42;
- BignumInt v43;
- BignumCarry carry;
-
- v0 = n->w[0];
- v1 = n->w[1];
- v2 = n->w[2];
- v3 = n->w[3];
- v4 = n->w[4];
- v5 = n->w[5];
- v6 = n->w[6];
- v7 = n->w[7];
- v8 = n->w[8];
- v9 = (v8) >> 2;
- v10 = (v8) & ((((BignumInt)1) << 2)-1);
- v11 = 5 * v9;
- BignumADC(v13, carry, v0, v11, 0);
- BignumADC(v14, carry, v1, 0, carry);
- BignumADC(v15, carry, v2, 0, carry);
- BignumADC(v16, carry, v3, 0, carry);
- BignumADC(v17, carry, v4, 0, carry);
- BignumADC(v18, carry, v5, 0, carry);
- BignumADC(v19, carry, v6, 0, carry);
- BignumADC(v20, carry, v7, 0, carry);
- v21 = v10 + 0 + carry;
- BignumADC(v22, carry, v13, 5, 0);
- (void)v22;
- BignumADC(v23, carry, v14, 0, carry);
- (void)v23;
- BignumADC(v24, carry, v15, 0, carry);
- (void)v24;
- BignumADC(v25, carry, v16, 0, carry);
- (void)v25;
- BignumADC(v26, carry, v17, 0, carry);
- (void)v26;
- BignumADC(v27, carry, v18, 0, carry);
- (void)v27;
- BignumADC(v28, carry, v19, 0, carry);
- (void)v28;
- BignumADC(v29, carry, v20, 0, carry);
- (void)v29;
- v30 = v21 + 0 + carry;
- v31 = (v30) >> 2;
- v32 = 5 * v31;
- BignumADC(v34, carry, v13, v32, 0);
- BignumADC(v35, carry, v14, 0, carry);
- BignumADC(v36, carry, v15, 0, carry);
- BignumADC(v37, carry, v16, 0, carry);
- BignumADC(v38, carry, v17, 0, carry);
- BignumADC(v39, carry, v18, 0, carry);
- BignumADC(v40, carry, v19, 0, carry);
- BignumADC(v41, carry, v20, 0, carry);
- v42 = v21 + 0 + carry;
- v43 = (v42) & ((((BignumInt)1) << 2)-1);
- n->w[0] = v34;
- n->w[1] = v35;
- n->w[2] = v36;
- n->w[3] = v37;
- n->w[4] = v38;
- n->w[5] = v39;
- n->w[6] = v40;
- n->w[7] = v41;
- n->w[8] = v43;
-}
-
-#elif BIGNUM_INT_BITS == 32
-
-static void bigval_add(bigval *r, const bigval *a, const bigval *b)
-{
- BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
- BignumCarry carry;
-
- v0 = a->w[0];
- v1 = a->w[1];
- v2 = a->w[2];
- v3 = a->w[3];
- v4 = a->w[4];
- v5 = b->w[0];
- v6 = b->w[1];
- v7 = b->w[2];
- v8 = b->w[3];
- v9 = b->w[4];
- BignumADC(v10, carry, v0, v5, 0);
- BignumADC(v11, carry, v1, v6, carry);
- BignumADC(v12, carry, v2, v7, carry);
- BignumADC(v13, carry, v3, v8, carry);
- v14 = v4 + v9 + carry;
- r->w[0] = v10;
- r->w[1] = v11;
- r->w[2] = v12;
- r->w[3] = v13;
- r->w[4] = v14;
-}
-
-static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b)
-{
- BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
- BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27;
- BignumInt v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40;
- BignumInt v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53;
- BignumInt v54, v55, v56, v57, v58, v60, v61, v62, v63, v64, v65, v66, v67;
- BignumInt v68, v69, v70, v71, v72, v73, v74, v75, v76, v78, v80, v81, v82;
- BignumInt v83, v84, v85, v86, v87, v88, v89;
- BignumCarry carry;
-
- v0 = a->w[0];
- v1 = a->w[1];
- v2 = a->w[2];
- v3 = a->w[3];
- v4 = a->w[4];
- v5 = b->w[0];
- v6 = b->w[1];
- v7 = b->w[2];
- v8 = b->w[3];
- v9 = b->w[4];
- BignumMUL(v11, v10, v0, v5);
- BignumMULADD(v13, v12, v0, v6, v11);
- BignumMULADD(v15, v14, v0, v7, v13);
- BignumMULADD(v17, v16, v0, v8, v15);
- BignumMULADD(v19, v18, v0, v9, v17);
- BignumMULADD(v21, v20, v1, v5, v12);
- BignumMULADD2(v23, v22, v1, v6, v14, v21);
- BignumMULADD2(v25, v24, v1, v7, v16, v23);
- BignumMULADD2(v27, v26, v1, v8, v18, v25);
- BignumMULADD2(v29, v28, v1, v9, v19, v27);
- BignumMULADD(v31, v30, v2, v5, v22);
- BignumMULADD2(v33, v32, v2, v6, v24, v31);
- BignumMULADD2(v35, v34, v2, v7, v26, v33);
- BignumMULADD2(v37, v36, v2, v8, v28, v35);
- BignumMULADD2(v39, v38, v2, v9, v29, v37);
- BignumMULADD(v41, v40, v3, v5, v32);
- BignumMULADD2(v43, v42, v3, v6, v34, v41);
- BignumMULADD2(v45, v44, v3, v7, v36, v43);
- BignumMULADD2(v47, v46, v3, v8, v38, v45);
- BignumMULADD2(v49, v48, v3, v9, v39, v47);
- BignumMULADD(v51, v50, v4, v5, v42);
- BignumMULADD2(v53, v52, v4, v6, v44, v51);
- BignumMULADD2(v55, v54, v4, v7, v46, v53);
- BignumMULADD2(v57, v56, v4, v8, v48, v55);
- v58 = v4 * v9 + v49 + v57;
- v60 = (v50) & ((((BignumInt)1) << 2)-1);
- v61 = ((v50) >> 2) | ((v52) << 30);
- v62 = ((v52) >> 2) | ((v54) << 30);
- v63 = ((v54) >> 2) | ((v56) << 30);
- v64 = ((v56) >> 2) | ((v58) << 30);
- v65 = (v58) >> 2;
- v66 = (v65) & ((((BignumInt)1) << 2)-1);
- v67 = (v58) >> 4;
- BignumMUL(v69, v68, 5, v61);
- BignumMULADD(v71, v70, 5, v62, v69);
- BignumMULADD(v73, v72, 5, v63, v71);
- BignumMULADD(v75, v74, 5, v64, v73);
- v76 = 5 * v66 + v75;
- v78 = 25 * v67;
- BignumADC(v80, carry, v10, v68, 0);
- BignumADC(v81, carry, v20, v70, carry);
- BignumADC(v82, carry, v30, v72, carry);
- BignumADC(v83, carry, v40, v74, carry);
- v84 = v60 + v76 + carry;
- BignumADC(v85, carry, v80, v78, 0);
- BignumADC(v86, carry, v81, 0, carry);
- BignumADC(v87, carry, v82, 0, carry);
- BignumADC(v88, carry, v83, 0, carry);
- v89 = v84 + 0 + carry;
- r->w[0] = v85;
- r->w[1] = v86;
- r->w[2] = v87;
- r->w[3] = v88;
- r->w[4] = v89;
-}
-
-static void bigval_final_reduce(bigval *n)
-{
- BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v9, v10, v11, v12, v13, v14;
- BignumInt v15, v16, v17, v18, v19, v20, v22, v23, v24, v25, v26, v27;
- BignumCarry carry;
-
- v0 = n->w[0];
- v1 = n->w[1];
- v2 = n->w[2];
- v3 = n->w[3];
- v4 = n->w[4];
- v5 = (v4) >> 2;
- v6 = (v4) & ((((BignumInt)1) << 2)-1);
- v7 = 5 * v5;
- BignumADC(v9, carry, v0, v7, 0);
- BignumADC(v10, carry, v1, 0, carry);
- BignumADC(v11, carry, v2, 0, carry);
- BignumADC(v12, carry, v3, 0, carry);
- v13 = v6 + 0 + carry;
- BignumADC(v14, carry, v9, 5, 0);
- (void)v14;
- BignumADC(v15, carry, v10, 0, carry);
- (void)v15;
- BignumADC(v16, carry, v11, 0, carry);
- (void)v16;
- BignumADC(v17, carry, v12, 0, carry);
- (void)v17;
- v18 = v13 + 0 + carry;
- v19 = (v18) >> 2;
- v20 = 5 * v19;
- BignumADC(v22, carry, v9, v20, 0);
- BignumADC(v23, carry, v10, 0, carry);
- BignumADC(v24, carry, v11, 0, carry);
- BignumADC(v25, carry, v12, 0, carry);
- v26 = v13 + 0 + carry;
- v27 = (v26) & ((((BignumInt)1) << 2)-1);
- n->w[0] = v22;
- n->w[1] = v23;
- n->w[2] = v24;
- n->w[3] = v25;
- n->w[4] = v27;
-}
-
-#elif BIGNUM_INT_BITS == 64
-
-static void bigval_add(bigval *r, const bigval *a, const bigval *b)
-{
- BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8;
- BignumCarry carry;
-
- v0 = a->w[0];
- v1 = a->w[1];
- v2 = a->w[2];
- v3 = b->w[0];
- v4 = b->w[1];
- v5 = b->w[2];
- BignumADC(v6, carry, v0, v3, 0);
- BignumADC(v7, carry, v1, v4, carry);
- v8 = v2 + v5 + carry;
- r->w[0] = v6;
- r->w[1] = v7;
- r->w[2] = v8;
-}
-
-static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b)
-{
- BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14;
- BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v24, v25, v26, v27, v28;
- BignumInt v29, v30, v31, v32, v33, v34, v36, v38, v39, v40, v41, v42, v43;
- BignumCarry carry;
-
- v0 = a->w[0];
- v1 = a->w[1];
- v2 = a->w[2];
- v3 = b->w[0];
- v4 = b->w[1];
- v5 = b->w[2];
- BignumMUL(v7, v6, v0, v3);
- BignumMULADD(v9, v8, v0, v4, v7);
- BignumMULADD(v11, v10, v0, v5, v9);
- BignumMULADD(v13, v12, v1, v3, v8);
- BignumMULADD2(v15, v14, v1, v4, v10, v13);
- BignumMULADD2(v17, v16, v1, v5, v11, v15);
- BignumMULADD(v19, v18, v2, v3, v14);
- BignumMULADD2(v21, v20, v2, v4, v16, v19);
- v22 = v2 * v5 + v17 + v21;
- v24 = (v18) & ((((BignumInt)1) << 2)-1);
- v25 = ((v18) >> 2) | ((v20) << 62);
- v26 = ((v20) >> 2) | ((v22) << 62);
- v27 = (v22) >> 2;
- v28 = (v27) & ((((BignumInt)1) << 2)-1);
- v29 = (v22) >> 4;
- BignumMUL(v31, v30, 5, v25);
- BignumMULADD(v33, v32, 5, v26, v31);
- v34 = 5 * v28 + v33;
- v36 = 25 * v29;
- BignumADC(v38, carry, v6, v30, 0);
- BignumADC(v39, carry, v12, v32, carry);
- v40 = v24 + v34 + carry;
- BignumADC(v41, carry, v38, v36, 0);
- BignumADC(v42, carry, v39, 0, carry);
- v43 = v40 + 0 + carry;
- r->w[0] = v41;
- r->w[1] = v42;
- r->w[2] = v43;
-}
-
-static void bigval_final_reduce(bigval *n)
-{
- BignumInt v0, v1, v2, v3, v4, v5, v7, v8, v9, v10, v11, v12, v13, v14;
- BignumInt v16, v17, v18, v19;
- BignumCarry carry;
-
- v0 = n->w[0];
- v1 = n->w[1];
- v2 = n->w[2];
- v3 = (v2) >> 2;
- v4 = (v2) & ((((BignumInt)1) << 2)-1);
- v5 = 5 * v3;
- BignumADC(v7, carry, v0, v5, 0);
- BignumADC(v8, carry, v1, 0, carry);
- v9 = v4 + 0 + carry;
- BignumADC(v10, carry, v7, 5, 0);
- (void)v10;
- BignumADC(v11, carry, v8, 0, carry);
- (void)v11;
- v12 = v9 + 0 + carry;
- v13 = (v12) >> 2;
- v14 = 5 * v13;
- BignumADC(v16, carry, v7, v14, 0);
- BignumADC(v17, carry, v8, 0, carry);
- v18 = v9 + 0 + carry;
- v19 = (v18) & ((((BignumInt)1) << 2)-1);
- n->w[0] = v16;
- n->w[1] = v17;
- n->w[2] = v19;
-}
-
-#else
-#error Add another bit count to contrib/make1305.py and rerun it
-#endif
-
-struct poly1305 {
- unsigned char nonce[16];
- bigval r;
- bigval h;
-
- /* Buffer in case we get less that a multiple of 16 bytes */
- unsigned char buffer[16];
- int bufferIndex;
-};
-
-static void poly1305_init(struct poly1305 *ctx)
-{
- memset(ctx->nonce, 0, 16);
- ctx->bufferIndex = 0;
- bigval_clear(&ctx->h);
-}
-
-static void poly1305_key(struct poly1305 *ctx, ptrlen key)
-{
- assert(key.len == 32); /* Takes a 256 bit key */
-
- unsigned char key_copy[16];
- memcpy(key_copy, key.ptr, 16);
-
- /* Key the MAC itself
- * bytes 4, 8, 12 and 16 are required to have their top four bits clear */
- key_copy[3] &= 0x0f;
- key_copy[7] &= 0x0f;
- key_copy[11] &= 0x0f;
- key_copy[15] &= 0x0f;
- /* bytes 5, 9 and 13 are required to have their bottom two bits clear */
- key_copy[4] &= 0xfc;
- key_copy[8] &= 0xfc;
- key_copy[12] &= 0xfc;
- bigval_import_le(&ctx->r, key_copy, 16);
- smemclr(key_copy, sizeof(key_copy));
-
- /* Use second 128 bits as the nonce */
- memcpy(ctx->nonce, (const char *)key.ptr + 16, 16);
-}
-
-/* Feed up to 16 bytes (should only be less for the last chunk) */
-static void poly1305_feed_chunk(struct poly1305 *ctx,
- const unsigned char *chunk, int len)
-{
- bigval c;
- bigval_import_le(&c, chunk, len);
- c.w[len / BIGNUM_INT_BYTES] |=
- (BignumInt)1 << (8 * (len % BIGNUM_INT_BYTES));
- bigval_add(&c, &c, &ctx->h);
- bigval_mul_mod_p(&ctx->h, &c, &ctx->r);
-}
-
-static void poly1305_feed(struct poly1305 *ctx,
- const unsigned char *buf, int len)
-{
- /* Check for stuff left in the buffer from last time */
- if (ctx->bufferIndex) {
- /* Try to fill up to 16 */
- while (ctx->bufferIndex < 16 && len) {
- ctx->buffer[ctx->bufferIndex++] = *buf++;
- --len;
- }
- if (ctx->bufferIndex == 16) {
- poly1305_feed_chunk(ctx, ctx->buffer, 16);
- ctx->bufferIndex = 0;
- }
- }
-
- /* Process 16 byte whole chunks */
- while (len >= 16) {
- poly1305_feed_chunk(ctx, buf, 16);
- len -= 16;
- buf += 16;
- }
-
- /* Cache stuff that's left over */
- if (len) {
- memcpy(ctx->buffer, buf, len);
- ctx->bufferIndex = len;
- }
-}
-
-/* Finalise and populate buffer with 16 byte with MAC */
-static void poly1305_finalise(struct poly1305 *ctx, unsigned char *mac)
-{
- bigval tmp;
-
- if (ctx->bufferIndex) {
- poly1305_feed_chunk(ctx, ctx->buffer, ctx->bufferIndex);
- }
-
- bigval_import_le(&tmp, ctx->nonce, 16);
- bigval_final_reduce(&ctx->h);
- bigval_add(&tmp, &tmp, &ctx->h);
- bigval_export_le(&tmp, mac, 16);
-}
-
-/* SSH-2 wrapper */
-
-struct ccp_context {
- struct chacha20 a_cipher; /* Used for length */
- struct chacha20 b_cipher; /* Used for content */
-
- /* Cache of the first 4 bytes because they are the sequence number */
- /* Kept in 8 bytes with the top as zero to allow easy passing to setiv */
- int mac_initialised; /* Where we have got to in filling mac_iv */
- unsigned char mac_iv[8];
-
- struct poly1305 mac;
-
- BinarySink_IMPLEMENTATION;
- ssh_cipher ciph;
- ssh2_mac mac_if;
-};
-
-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/sshchan.h b/sshchan.h
deleted file mode 100644
index 14dfccb5..00000000
--- a/sshchan.h
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Abstraction of the various ways to handle the local end of an SSH
- * connection-layer channel.
- */
-
-#ifndef PUTTY_SSHCHAN_H
-#define PUTTY_SSHCHAN_H
-
-typedef struct ChannelVtable ChannelVtable;
-
-struct ChannelVtable {
- void (*free)(Channel *);
-
- /* Called for channel types that were created at the same time as
- * we sent an outgoing CHANNEL_OPEN, when the confirmation comes
- * back from the server indicating that the channel has been
- * opened, or the failure message indicating that it hasn't,
- * respectively. In the latter case, this must _not_ free the
- * Channel structure - the client will call the free method
- * separately. But it might do logging or other local cleanup. */
- void (*open_confirmation)(Channel *);
- void (*open_failed)(Channel *, const char *error_text);
-
- size_t (*send)(Channel *, bool is_stderr, const void *buf, size_t len);
- void (*send_eof)(Channel *);
- void (*set_input_wanted)(Channel *, bool wanted);
-
- char *(*log_close_msg)(Channel *);
-
- bool (*want_close)(Channel *, bool sent_local_eof, bool rcvd_remote_eof);
-
- /* A method for every channel request we know of. All of these
- * return true for success or false for failure. */
- bool (*rcvd_exit_status)(Channel *, int status);
- bool (*rcvd_exit_signal)(
- Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg);
- bool (*rcvd_exit_signal_numeric)(
- Channel *chan, int signum, bool core_dumped, ptrlen msg);
- bool (*run_shell)(Channel *chan);
- bool (*run_command)(Channel *chan, ptrlen command);
- bool (*run_subsystem)(Channel *chan, ptrlen subsys);
- bool (*enable_x11_forwarding)(
- Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata,
- unsigned screen_number);
- bool (*enable_agent_forwarding)(Channel *chan);
- bool (*allocate_pty)(
- Channel *chan, ptrlen termtype, unsigned width, unsigned height,
- unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes);
- bool (*set_env)(Channel *chan, ptrlen var, ptrlen value);
- bool (*send_break)(Channel *chan, unsigned length);
- bool (*send_signal)(Channel *chan, ptrlen signame);
- bool (*change_window_size)(
- Channel *chan, unsigned width, unsigned height,
- unsigned pixwidth, unsigned pixheight);
-
- /* A method for signalling success/failure responses to channel
- * requests initiated from the SshChannel vtable with want_reply
- * true. */
- void (*request_response)(Channel *, bool success);
-};
-
-struct Channel {
- const struct ChannelVtable *vt;
- unsigned initial_fixed_window_size;
-};
-
-static inline void chan_free(Channel *ch)
-{ ch->vt->free(ch); }
-static inline void chan_open_confirmation(Channel *ch)
-{ ch->vt->open_confirmation(ch); }
-static inline void chan_open_failed(Channel *ch, const char *err)
-{ ch->vt->open_failed(ch, err); }
-static inline size_t chan_send(
- Channel *ch, bool err, const void *buf, size_t len)
-{ return ch->vt->send(ch, err, buf, len); }
-static inline void chan_send_eof(Channel *ch)
-{ ch->vt->send_eof(ch); }
-static inline void chan_set_input_wanted(Channel *ch, bool wanted)
-{ ch->vt->set_input_wanted(ch, wanted); }
-static inline char *chan_log_close_msg(Channel *ch)
-{ return ch->vt->log_close_msg(ch); }
-static inline bool chan_want_close(Channel *ch, bool leof, bool reof)
-{ return ch->vt->want_close(ch, leof, reof); }
-static inline bool chan_rcvd_exit_status(Channel *ch, int status)
-{ return ch->vt->rcvd_exit_status(ch, status); }
-static inline bool chan_rcvd_exit_signal(
- Channel *ch, ptrlen sig, bool core, ptrlen msg)
-{ return ch->vt->rcvd_exit_signal(ch, sig, core, msg); }
-static inline bool chan_rcvd_exit_signal_numeric(
- Channel *ch, int sig, bool core, ptrlen msg)
-{ return ch->vt->rcvd_exit_signal_numeric(ch, sig, core, msg); }
-static inline bool chan_run_shell(Channel *ch)
-{ return ch->vt->run_shell(ch); }
-static inline bool chan_run_command(Channel *ch, ptrlen cmd)
-{ return ch->vt->run_command(ch, cmd); }
-static inline bool chan_run_subsystem(Channel *ch, ptrlen subsys)
-{ return ch->vt->run_subsystem(ch, subsys); }
-static inline bool chan_enable_x11_forwarding(
- Channel *ch, bool oneshot, ptrlen ap, ptrlen ad, unsigned scr)
-{ return ch->vt->enable_x11_forwarding(ch, oneshot, ap, ad, scr); }
-static inline bool chan_enable_agent_forwarding(Channel *ch)
-{ return ch->vt->enable_agent_forwarding(ch); }
-static inline bool chan_allocate_pty(
- Channel *ch, ptrlen termtype, unsigned w, unsigned h,
- unsigned pw, unsigned ph, struct ssh_ttymodes modes)
-{ return ch->vt->allocate_pty(ch, termtype, w, h, pw, ph, modes); }
-static inline bool chan_set_env(Channel *ch, ptrlen var, ptrlen value)
-{ return ch->vt->set_env(ch, var, value); }
-static inline bool chan_send_break(Channel *ch, unsigned length)
-{ return ch->vt->send_break(ch, length); }
-static inline bool chan_send_signal(Channel *ch, ptrlen signame)
-{ return ch->vt->send_signal(ch, signame); }
-static inline bool chan_change_window_size(
- Channel *ch, unsigned w, unsigned h, unsigned pw, unsigned ph)
-{ return ch->vt->change_window_size(ch, w, h, pw, ph); }
-static inline void chan_request_response(Channel *ch, bool success)
-{ ch->vt->request_response(ch, success); }
-
-/*
- * Reusable methods you can put in vtables to give default handling of
- * some of those functions.
- */
-
-/* open_confirmation / open_failed for any channel it doesn't apply to */
-void chan_remotely_opened_confirmation(Channel *chan);
-void chan_remotely_opened_failure(Channel *chan, const char *errtext);
-
-/* want_close for any channel that wants the default behaviour of not
- * closing until both directions have had an EOF */
-bool chan_default_want_close(Channel *, bool, bool);
-
-/* default implementations that refuse all the channel requests */
-bool chan_no_exit_status(Channel *, int);
-bool chan_no_exit_signal(Channel *, ptrlen, bool, ptrlen);
-bool chan_no_exit_signal_numeric(Channel *, int, bool, ptrlen);
-bool chan_no_run_shell(Channel *chan);
-bool chan_no_run_command(Channel *chan, ptrlen command);
-bool chan_no_run_subsystem(Channel *chan, ptrlen subsys);
-bool chan_no_enable_x11_forwarding(
- Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata,
- unsigned screen_number);
-bool chan_no_enable_agent_forwarding(Channel *chan);
-bool chan_no_allocate_pty(
- Channel *chan, ptrlen termtype, unsigned width, unsigned height,
- unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes);
-bool chan_no_set_env(Channel *chan, ptrlen var, ptrlen value);
-bool chan_no_send_break(Channel *chan, unsigned length);
-bool chan_no_send_signal(Channel *chan, ptrlen signame);
-bool chan_no_change_window_size(
- Channel *chan, unsigned width, unsigned height,
- unsigned pixwidth, unsigned pixheight);
-
-/* default implementation that never expects to receive a response */
-void chan_no_request_response(Channel *, bool);
-
-/*
- * Constructor for a trivial do-nothing implementation of
- * ChannelVtable. Used for 'zombie' channels, i.e. channels whose
- * proper local source of data has been shut down or otherwise stopped
- * existing, but the SSH side is still there and needs some kind of a
- * Channel implementation to talk to. In particular, the want_close
- * method for this channel always returns 'yes, please close this
- * channel asap', regardless of whether local and/or remote EOF have
- * been sent - indeed, even if _neither_ has.
- */
-Channel *zombiechan_new(void);
-
-/* ----------------------------------------------------------------------
- * This structure is owned by an SSH connection layer, and identifies
- * the connection layer's end of the channel, for the Channel
- * implementation to talk back to.
- */
-
-typedef struct SshChannelVtable SshChannelVtable;
-
-struct SshChannelVtable {
- size_t (*write)(SshChannel *c, bool is_stderr, const void *, size_t);
- void (*write_eof)(SshChannel *c);
- void (*initiate_close)(SshChannel *c, const char *err);
- void (*unthrottle)(SshChannel *c, size_t bufsize);
- Conf *(*get_conf)(SshChannel *c);
- void (*window_override_removed)(SshChannel *c);
- void (*x11_sharing_handover)(SshChannel *c,
- ssh_sharing_connstate *share_cs,
- share_channel *share_chan,
- const char *peer_addr, int peer_port,
- int endian, int protomajor, int protominor,
- const void *initial_data, int initial_len);
-
- /*
- * All the outgoing channel requests we support. Each one has a
- * want_reply flag, which will cause a callback to
- * chan_request_response when the result is available.
- *
- * The ones that return 'bool' use it to indicate that the SSH
- * protocol in use doesn't support this request at all.
- *
- * (It's also intentional that not all of them have a want_reply
- * flag: the ones that don't are because SSH-1 has no method for
- * signalling success or failure of that request, or because we
- * wouldn't do anything usefully different with the reply in any
- * case.)
- */
- void (*send_exit_status)(SshChannel *c, int status);
- void (*send_exit_signal)(
- SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg);
- void (*send_exit_signal_numeric)(
- SshChannel *c, int signum, bool core_dumped, ptrlen msg);
- void (*request_x11_forwarding)(
- SshChannel *c, bool want_reply, const char *authproto,
- const char *authdata, int screen_number, bool oneshot);
- void (*request_agent_forwarding)(
- SshChannel *c, bool want_reply);
- void (*request_pty)(
- SshChannel *c, bool want_reply, Conf *conf, int w, int h);
- bool (*send_env_var)(
- SshChannel *c, bool want_reply, const char *var, const char *value);
- void (*start_shell)(
- SshChannel *c, bool want_reply);
- void (*start_command)(
- SshChannel *c, bool want_reply, const char *command);
- bool (*start_subsystem)(
- SshChannel *c, bool want_reply, const char *subsystem);
- bool (*send_serial_break)(
- SshChannel *c, bool want_reply, int length); /* length=0 for default */
- bool (*send_signal)(
- SshChannel *c, bool want_reply, const char *signame);
- void (*send_terminal_size_change)(
- SshChannel *c, int w, int h);
- void (*hint_channel_is_simple)(SshChannel *c);
-};
-
-struct SshChannel {
- const struct SshChannelVtable *vt;
- ConnectionLayer *cl;
-};
-
-static inline size_t sshfwd_write_ext(
- SshChannel *c, bool is_stderr, const void *data, size_t len)
-{ return c->vt->write(c, is_stderr, data, len); }
-static inline size_t sshfwd_write(SshChannel *c, const void *data, size_t len)
-{ return sshfwd_write_ext(c, false, data, len); }
-static inline void sshfwd_write_eof(SshChannel *c)
-{ c->vt->write_eof(c); }
-static inline void sshfwd_initiate_close(SshChannel *c, const char *err)
-{ c->vt->initiate_close(c, err); }
-static inline void sshfwd_unthrottle(SshChannel *c, size_t bufsize)
-{ c->vt->unthrottle(c, bufsize); }
-static inline Conf *sshfwd_get_conf(SshChannel *c)
-{ return c->vt->get_conf(c); }
-static inline void sshfwd_window_override_removed(SshChannel *c)
-{ c->vt->window_override_removed(c); }
-static inline void sshfwd_x11_sharing_handover(
- SshChannel *c, ssh_sharing_connstate *cs, share_channel *sch,
- const char *addr, int port, int endian, int maj, int min,
- const void *idata, int ilen)
-{ c->vt->x11_sharing_handover(c, cs, sch, addr, port, endian,
- maj, min, idata, ilen); }
-static inline void sshfwd_send_exit_status(SshChannel *c, int status)
-{ c->vt->send_exit_status(c, status); }
-static inline void sshfwd_send_exit_signal(
- SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg)
-{ c->vt->send_exit_signal(c, signame, core_dumped, msg); }
-static inline void sshfwd_send_exit_signal_numeric(
- SshChannel *c, int signum, bool core_dumped, ptrlen msg)
-{ c->vt->send_exit_signal_numeric(c, signum, core_dumped, msg); }
-static inline void sshfwd_request_x11_forwarding(
- SshChannel *c, bool want_reply, const char *proto,
- const char *data, int scr, bool once)
-{ c->vt->request_x11_forwarding(c, want_reply, proto, data, scr, once); }
-static inline void sshfwd_request_agent_forwarding(
- SshChannel *c, bool want_reply)
-{ c->vt->request_agent_forwarding(c, want_reply); }
-static inline void sshfwd_request_pty(
- SshChannel *c, bool want_reply, Conf *conf, int w, int h)
-{ c->vt->request_pty(c, want_reply, conf, w, h); }
-static inline bool sshfwd_send_env_var(
- SshChannel *c, bool want_reply, const char *var, const char *value)
-{ return c->vt->send_env_var(c, want_reply, var, value); }
-static inline void sshfwd_start_shell(
- SshChannel *c, bool want_reply)
-{ c->vt->start_shell(c, want_reply); }
-static inline void sshfwd_start_command(
- SshChannel *c, bool want_reply, const char *command)
-{ c->vt->start_command(c, want_reply, command); }
-static inline bool sshfwd_start_subsystem(
- SshChannel *c, bool want_reply, const char *subsystem)
-{ return c->vt->start_subsystem(c, want_reply, subsystem); }
-static inline bool sshfwd_send_serial_break(
- SshChannel *c, bool want_reply, int length)
-{ return c->vt->send_serial_break(c, want_reply, length); }
-static inline bool sshfwd_send_signal(
- SshChannel *c, bool want_reply, const char *signame)
-{ return c->vt->send_signal(c, want_reply, signame); }
-static inline void sshfwd_send_terminal_size_change(
- SshChannel *c, int w, int h)
-{ c->vt->send_terminal_size_change(c, w, h); }
-static inline void sshfwd_hint_channel_is_simple(SshChannel *c)
-{ c->vt->hint_channel_is_simple(c); }
-
-/* ----------------------------------------------------------------------
- * The 'main' or primary channel of the SSH connection is special,
- * because it's the one that's connected directly to parts of the
- * frontend such as the terminal and the specials menu. So it exposes
- * a richer API.
- */
-
-mainchan *mainchan_new(
- PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf,
- int term_width, int term_height, bool is_simple, SshChannel **sc_out);
-void mainchan_get_specials(
- mainchan *mc, add_special_fn_t add_special, void *ctx);
-void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg);
-void mainchan_terminal_size(mainchan *mc, int width, int height);
-
-#endif /* PUTTY_SSHCHAN_H */
diff --git a/sshcommon.c b/sshcommon.c
deleted file mode 100644
index 5485e33a..00000000
--- a/sshcommon.c
+++ /dev/null
@@ -1,946 +0,0 @@
-/*
- * Supporting routines used in common by all the various components of
- * the SSH system.
- */
-
-#include <assert.h>
-#include <stdlib.h>
-
-#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/sshcr.h b/sshcr.h
index e87ce864..12bfea6c 100644
--- a/sshcr.h
+++ b/sshcr.h
@@ -18,7 +18,6 @@
* 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
diff --git a/sshcrc.c b/sshcrc.c
deleted file mode 100644
index b1dc8333..00000000
--- a/sshcrc.c
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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 <stdint.h>
-#include <stdlib.h>
-
-#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/sshcrcda.c b/sshcrcda.c
deleted file mode 100644
index 18044754..00000000
--- a/sshcrcda.c
+++ /dev/null
@@ -1,171 +0,0 @@
-/* $OpenBSD: deattack.c,v 1.14 2001/06/23 15:12:18 itojun Exp $ */
-
-/*
- * Cryptographic attack detector for ssh - source code
- *
- * Copyright (c) 1998 CORE SDI S.A., Buenos Aires, Argentina.
- *
- * All rights reserved. Redistribution and use in source and binary
- * forms, with or without modification, are permitted provided that
- * this copyright notice is retained.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES ARE DISCLAIMED. IN NO EVENT SHALL CORE SDI S.A. BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR
- * CONSEQUENTIAL DAMAGES RESULTING FROM THE USE OR MISUSE OF THIS
- * SOFTWARE.
- *
- * Ariel Futoransky <futo@core-sdi.com>
- * <http://www.core-sdi.com>
- *
- * Modified for use in PuTTY by Simon Tatham
- */
-
-#include <assert.h>
-#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/sshdes.c b/sshdes.c
deleted file mode 100644
index d2ba9825..00000000
--- a/sshdes.c
+++ /dev/null
@@ -1,1048 +0,0 @@
-/*
- * 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 <stdio.h>
-
-#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/sshdh.c b/sshdh.c
deleted file mode 100644
index b3756120..00000000
--- a/sshdh.c
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Diffie-Hellman implementation for PuTTY.
- */
-
-#include <assert.h>
-
-#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/sshdss.c b/sshdss.c
deleted file mode 100644
index 3e0c7618..00000000
--- a/sshdss.c
+++ /dev/null
@@ -1,503 +0,0 @@
-/*
- * Digital Signature Standard implementation for PuTTY.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#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/sshdssg.c b/sshdssg.c
deleted file mode 100644
index 3b527256..00000000
--- a/sshdssg.c
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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/sshecc.c b/sshecc.c
deleted file mode 100644
index ba580dbf..00000000
--- a/sshecc.c
+++ /dev/null
@@ -1,1706 +0,0 @@
-/*
- * 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 <stdlib.h>
-#include <assert.h>
-
-#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/sshgss.h b/sshgss.h
deleted file mode 100644
index c640636d..00000000
--- a/sshgss.h
+++ /dev/null
@@ -1,217 +0,0 @@
-#ifndef PUTTY_SSHGSS_H
-#define PUTTY_SSHGSS_H
-#include "putty.h"
-#include "pgssapi.h"
-
-#ifndef NO_GSSAPI
-
-#define SSH2_GSS_OIDTYPE 0x06
-typedef void *Ssh_gss_ctx;
-
-typedef enum Ssh_gss_stat {
- SSH_GSS_OK = 0,
- SSH_GSS_S_CONTINUE_NEEDED,
- SSH_GSS_NO_MEM,
- SSH_GSS_BAD_HOST_NAME,
- SSH_GSS_BAD_MIC,
- SSH_GSS_NO_CREDS,
- SSH_GSS_FAILURE
-} Ssh_gss_stat;
-
-#define SSH_GSS_S_COMPLETE SSH_GSS_OK
-
-#define SSH_GSS_CLEAR_BUF(buf) do { \
- (*buf).length = 0; \
- (*buf).value = NULL; \
-} while (0)
-
-typedef gss_buffer_desc Ssh_gss_buf;
-typedef gss_name_t Ssh_gss_name;
-
-#define GSS_NO_EXPIRATION ((time_t)-1)
-
-#define GSS_DEF_REKEY_MINS 2 /* Default minutes between GSS cache checks */
-
-/* Functions, provided by either 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/sshgssc.c b/sshgssc.c
deleted file mode 100644
index d9f62c39..00000000
--- a/sshgssc.c
+++ /dev/null
@@ -1,288 +0,0 @@
-#include "putty.h"
-
-#include <string.h>
-#include <limits.h>
-#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/sshgssc.h b/sshgssc.h
deleted file mode 100644
index 07fac009..00000000
--- a/sshgssc.h
+++ /dev/null
@@ -1,24 +0,0 @@
-#ifndef PUTTY_SSHGSSC_H
-#define PUTTY_SSHGSSC_H
-#include "putty.h"
-#ifndef NO_GSSAPI
-
-#include "pgssapi.h"
-#include "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/sshhmac.c b/sshhmac.c
deleted file mode 100644
index 8f18eb28..00000000
--- a/sshhmac.c
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Implementation of HMAC (RFC 2104) for PuTTY, in a general form that
- * can wrap any underlying hash function.
- */
-
-#include "ssh.h"
-
-struct hmac {
- const ssh_hashalg *hashalg;
- ssh_hash *h_outer, *h_inner, *h_live;
- uint8_t *digest;
- strbuf *text_name;
- ssh2_mac mac;
-};
-
-struct hmac_extra {
- const ssh_hashalg *hashalg_base;
- const char *suffix, *annotation;
-};
-
-static ssh2_mac *hmac_new(const ssh2_macalg *alg, ssh_cipher *cipher)
-{
- struct hmac *ctx = snew(struct hmac);
- const struct hmac_extra *extra = (const struct hmac_extra *)alg->extra;
-
- ctx->h_outer = ssh_hash_new(extra->hashalg_base);
- /* In case that hashalg was a selector vtable, we'll now switch to
- * using whatever real one it selected, for all future purposes. */
- ctx->hashalg = ssh_hash_alg(ctx->h_outer);
- ctx->h_inner = ssh_hash_new(ctx->hashalg);
- ctx->h_live = ssh_hash_new(ctx->hashalg);
-
- /*
- * HMAC is not well defined as a wrapper on an absolutely general
- * hash function; it expects that the function it's wrapping will
- * consume data in fixed-size blocks, and it's partially defined
- * in terms of that block size. So we insist that the hash we're
- * given must have defined a meaningful block size.
- */
- assert(ctx->hashalg->blocklen);
-
- ctx->digest = snewn(ctx->hashalg->hlen, uint8_t);
-
- ctx->text_name = strbuf_new();
- 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/sshkeygen.h b/sshkeygen.h
index 971a3633..60b2e836 100644
--- a/sshkeygen.h
+++ b/sshkeygen.h
@@ -94,6 +94,14 @@ typedef struct MillerRabin MillerRabin;
MillerRabin *miller_rabin_new(mp_int *p);
void miller_rabin_free(MillerRabin *mr);
+/* Perform a single Miller-Rabin test, using a specified witness value.
+ * Used in the test suite. */
+struct mr_result {
+ unsigned passed;
+ unsigned potential_primitive_root;
+};
+struct mr_result miller_rabin_test(MillerRabin *mr, mp_int *w);
+
/* Perform a single Miller-Rabin test, using a random witness value. */
bool miller_rabin_test_random(MillerRabin *mr);
@@ -286,7 +294,7 @@ extern const PrimeGenerationPolicy primegen_provable_maurer_complex;
int rsa_generate(RSAKey *key, int bits, bool strong,
PrimeGenerationContext *pgc, ProgressReceiver *prog);
-int dsa_generate(struct dss_key *key, int bits, PrimeGenerationContext *pgc,
+int dsa_generate(struct dsa_key *key, int bits, PrimeGenerationContext *pgc,
ProgressReceiver *prog);
int ecdsa_generate(struct ecdsa_key *key, int bits);
int eddsa_generate(struct eddsa_key *key, int bits);
diff --git a/sshmd5.c b/sshmd5.c
deleted file mode 100644
index 9155c99e..00000000
--- a/sshmd5.c
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * MD5 implementation for PuTTY. Written directly from the spec by
- * Simon Tatham.
- */
-
-#include <assert.h>
-#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(&copy->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/sshnogss.c b/sshnogss.c
deleted file mode 100644
index fa4ac65f..00000000
--- a/sshnogss.c
+++ /dev/null
@@ -1,19 +0,0 @@
-#include "putty.h"
-#ifndef NO_GSSAPI
-
-/* For platforms not supporting GSSAPI */
-
-struct ssh_gss_liblist *ssh_gss_setup(Conf *conf)
-{
- struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist *);
- list->libraries = NULL;
- list->nlibraries = 0;
- return list;
-}
-
-void ssh_gss_cleanup(struct ssh_gss_liblist *list)
-{
- sfree(list);
-}
-
-#endif /* NO_GSSAPI */
diff --git a/sshppl.h b/sshppl.h
deleted file mode 100644
index 2c1dbf42..00000000
--- a/sshppl.h
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Abstraction of the various layers of SSH packet-level protocol,
- * general enough to take in all three of the main SSH-2 layers and
- * both of the SSH-1 phases.
- */
-
-#ifndef PUTTY_SSHPPL_H
-#define PUTTY_SSHPPL_H
-
-typedef void (*packet_handler_fn_t)(PacketProtocolLayer *ppl, PktIn *pktin);
-typedef struct PacketProtocolLayerVtable PacketProtocolLayerVtable;
-
-struct PacketProtocolLayerVtable {
- void (*free)(PacketProtocolLayer *);
- void (*process_queue)(PacketProtocolLayer *ppl);
- bool (*get_specials)(
- PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx);
- void (*special_cmd)(
- PacketProtocolLayer *ppl, SessionSpecialCode code, int arg);
- 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/sshprime.c b/sshprime.c
deleted file mode 100644
index d9bdebba..00000000
--- a/sshprime.c
+++ /dev/null
@@ -1,762 +0,0 @@
-/*
- * Prime generation.
- */
-
-#include <assert.h>
-#include <math.h>
-
-#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/sshprng.c b/sshprng.c
deleted file mode 100644
index 58df9945..00000000
--- a/sshprng.c
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * 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/sshpubk.c b/sshpubk.c
index 3fad8870..0648b4c4 100644
--- a/sshpubk.c
+++ b/sshpubk.c
@@ -199,8 +199,7 @@ static int rsa1_load_s_internal(BinarySource *src, RSAKey *key, bool pub_only,
if (enclen & 7)
goto end;
- buf = strbuf_new_nm();
- put_datapl(buf, get_data(src, enclen));
+ buf = strbuf_dup_nm(get_data(src, enclen));
unsigned char keybuf[16];
hash_simple(&ssh_md5, ptrlen_from_asciz(passphrase), keybuf);
@@ -563,12 +562,20 @@ const ssh_keyalg *const all_keyalgs[] = {
&ssh_rsa,
&ssh_rsa_sha256,
&ssh_rsa_sha512,
- &ssh_dss,
+ &ssh_dsa,
&ssh_ecdsa_nistp256,
&ssh_ecdsa_nistp384,
&ssh_ecdsa_nistp521,
&ssh_ecdsa_ed25519,
&ssh_ecdsa_ed448,
+ &opensshcert_ssh_dsa,
+ &opensshcert_ssh_rsa,
+ &opensshcert_ssh_rsa_sha256,
+ &opensshcert_ssh_rsa_sha512,
+ &opensshcert_ssh_ecdsa_ed25519,
+ &opensshcert_ssh_ecdsa_nistp256,
+ &opensshcert_ssh_ecdsa_nistp384,
+ &opensshcert_ssh_ecdsa_nistp521,
};
const size_t n_keyalgs = lenof(all_keyalgs);
@@ -586,6 +593,18 @@ const ssh_keyalg *find_pubkey_alg(const char *name)
return find_pubkey_alg_len(ptrlen_from_asciz(name));
}
+ptrlen pubkey_blob_to_alg_name(ptrlen blob)
+{
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, blob);
+ return get_string(src);
+}
+
+const ssh_keyalg *pubkey_blob_to_alg(ptrlen blob)
+{
+ return find_pubkey_alg_len(pubkey_blob_to_alg_name(blob));
+}
+
struct ppk_cipher {
const char *name;
size_t blocklen, keylen, ivlen;
@@ -1226,7 +1245,7 @@ bool ppk_loadpub_s(BinarySource *src, char **algorithm, BinarySink *bs,
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";
+ error = "not a public key or a PuTTY SSH-2 private key";
goto error;
}
@@ -1238,7 +1257,7 @@ bool ppk_loadpub_s(BinarySource *src, char **algorithm, BinarySink *bs,
if (0 == strncmp(header, "PuTTY-User-Key-File-", 20))
error = "PuTTY key format too new";
else
- error = "not a PuTTY SSH-2 private key";
+ error = "not a public key or a PuTTY SSH-2 private key";
goto error;
}
error = "file format error";
@@ -1380,37 +1399,6 @@ int base64_lines(int datalen)
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,
@@ -1550,33 +1538,33 @@ strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase,
}
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);
+ put_fmt(out, "PuTTY-User-Key-File-%u: %s\n",
+ params.fmt_version, ssh_key_ssh_id(key->key));
+ put_fmt(out, "Encryption: %s\n", cipherstr);
+ put_fmt(out, "Comment: %s\n", key->comment);
+ put_fmt(out, "Public-Lines: %d\n", base64_lines(pub_blob->len));
+ base64_encode_bs(BinarySink_UPCAST(out), ptrlen_from_strbuf(pub_blob), 64);
if (params.fmt_version == 3 && ciphertype->keylen != 0) {
- 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);
+ put_fmt(out, "Key-Derivation: %s\n",
+ params.argon2_flavour == Argon2d ? "Argon2d" :
+ params.argon2_flavour == Argon2i ? "Argon2i" : "Argon2id");
+ put_fmt(out, "Argon2-Memory: %"PRIu32"\n", params.argon2_mem);
assert(!params.argon2_passes_auto);
- 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: ");
+ put_fmt(out, "Argon2-Passes: %"PRIu32"\n", params.argon2_passes);
+ put_fmt(out, "Argon2-Parallelism: %"PRIu32"\n",
+ params.argon2_parallelism);
+ put_fmt(out, "Argon2-Salt: ");
for (size_t i = 0; i < passphrase_salt->len; i++)
- strbuf_catf(out, "%02x", passphrase_salt->u[i]);
- strbuf_catf(out, "\n");
+ put_fmt(out, "%02x", passphrase_salt->u[i]);
+ put_fmt(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: ");
+ put_fmt(out, "Private-Lines: %d\n", base64_lines(priv_encrypted_len));
+ base64_encode_bs(BinarySink_UPCAST(out),
+ make_ptrlen(priv_blob_encrypted, priv_encrypted_len), 64);
+ put_fmt(out, "Private-MAC: ");
for (i = 0; i < macalg->len; i++)
- strbuf_catf(out, "%02x", priv_mac[i]);
- strbuf_catf(out, "\n");
+ put_fmt(out, "%02x", priv_mac[i]);
+ put_fmt(out, "\n");
strbuf_free(cipher_mac_keys_blob);
strbuf_free(passphrase_salt);
@@ -1740,7 +1728,7 @@ static void ssh2_fingerprint_blob_md5(ptrlen blob, strbuf *sb)
hash_simple(&ssh_md5, blob, digest);
for (unsigned i = 0; i < 16; i++)
- strbuf_catf(sb, "%02x%s", digest[i], i==15 ? "" : ":");
+ put_fmt(sb, "%02x%s", digest[i], i==15 ? "" : ":");
}
static void ssh2_fingerprint_blob_sha256(ptrlen blob, strbuf *sb)
@@ -1764,6 +1752,7 @@ static void ssh2_fingerprint_blob_sha256(ptrlen blob, strbuf *sb)
char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype)
{
strbuf *sb = strbuf_new();
+ strbuf *tmp = NULL;
/*
* Identify the key algorithm, if possible.
@@ -1778,24 +1767,63 @@ char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype)
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);
+ put_fmt(sb, "%.*s %d ", PTRLEN_PRINTF(algname), bits);
+
+ if (!ssh_fptype_is_cert(fptype) && alg->is_certificate) {
+ ssh_key *key = ssh_key_new_pub(alg, blob);
+ if (key) {
+ tmp = strbuf_new();
+ ssh_key_public_blob(ssh_key_base_key(key),
+ BinarySink_UPCAST(tmp));
+ blob = ptrlen_from_strbuf(tmp);
+ ssh_key_free(key);
+ }
+ }
} else {
- strbuf_catf(sb, "%.*s ", PTRLEN_PRINTF(algname));
+ put_fmt(sb, "%.*s ", PTRLEN_PRINTF(algname));
}
}
- switch (fptype) {
+ switch (ssh_fptype_from_cert(fptype)) {
case SSH_FPTYPE_MD5:
ssh2_fingerprint_blob_md5(blob, sb);
break;
case SSH_FPTYPE_SHA256:
ssh2_fingerprint_blob_sha256(blob, sb);
break;
+ default:
+ unreachable("ssh_fptype_from_cert ruled out the other values");
}
+ if (tmp)
+ strbuf_free(tmp);
+
return strbuf_to_str(sb);
}
+char *ssh2_double_fingerprint_blob(ptrlen blob, FingerprintType fptype)
+{
+ if (ssh_fptype_is_cert(fptype))
+ fptype = ssh_fptype_from_cert(fptype);
+
+ char *fp = ssh2_fingerprint_blob(blob, fptype);
+ char *p = strrchr(fp, ' ');
+ char *hash = p ? p + 1 : fp;
+
+ char *fpc = ssh2_fingerprint_blob(blob, ssh_fptype_to_cert(fptype));
+ char *pc = strrchr(fpc, ' ');
+ char *hashc = pc ? pc + 1 : fpc;
+
+ if (strcmp(hash, hashc)) {
+ char *tmp = dupprintf("%s (with certificate: %s)", fp, hashc);
+ sfree(fp);
+ fp = tmp;
+ }
+
+ sfree(fpc);
+ return fp;
+}
+
char **ssh2_all_fingerprints_for_blob(ptrlen blob)
{
char **fps = snewn(SSH_N_FPTYPES, char *);
@@ -1813,6 +1841,15 @@ char *ssh2_fingerprint(ssh_key *data, FingerprintType fptype)
return ret;
}
+char *ssh2_double_fingerprint(ssh_key *data, FingerprintType fptype)
+{
+ strbuf *blob = strbuf_new();
+ ssh_key_public_blob(data, BinarySink_UPCAST(blob));
+ char *ret = ssh2_double_fingerprint_blob(ptrlen_from_strbuf(blob), fptype);
+ strbuf_free(blob);
+ return ret;
+}
+
char **ssh2_all_fingerprints(ssh_key *data)
{
strbuf *blob = strbuf_new();
@@ -1869,7 +1906,7 @@ static int key_type_s_internal(BinarySource *src)
if (find_pubkey_alg_len(get_nonchars(src, " \n")) > 0 &&
get_chars(src, " ").len == 1 &&
get_chars(src, "0123456789ABCDEFGHIJKLMNOPQRSTUV"
- "WXYZabcdefghijklmnopqrstuvwxyz+/=").len > 0 &&
+ "WXYZabcdefghijklmnopqrstuvwxyz+/=").len > 0 &&
get_nonchars(src, " \n").len == 0)
return SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH;
@@ -1936,47 +1973,3 @@ const char *key_type_to_str(int type)
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/sshrsa.c b/sshrsa.c
deleted file mode 100644
index e3dcbdda..00000000
--- a/sshrsa.c
+++ /dev/null
@@ -1,1109 +0,0 @@
-/*
- * RSA implementation for PuTTY.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#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/sshrsag.c b/sshrsag.c
deleted file mode 100644
index b9676e7a..00000000
--- a/sshrsag.c
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * RSA key generation.
- */
-
-#include <assert.h>
-
-#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<b, optionally with minimum separation */
- if (b_min < a + min_separation)
- b_min = a + min_separation;
- /* And cap at the upper limit */
- if (b_min > 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/sshserver.c b/sshserver.c
deleted file mode 100644
index cc1c880d..00000000
--- a/sshserver.c
+++ /dev/null
@@ -1,591 +0,0 @@
-/*
- * Top-level code for SSH server implementation.
- */
-
-#include <assert.h>
-#include <stddef.h>
-
-#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/sshserver.h b/sshserver.h
deleted file mode 100644
index 5cc393df..00000000
--- a/sshserver.h
+++ /dev/null
@@ -1,143 +0,0 @@
-typedef struct AuthPolicy AuthPolicy;
-
-struct SshServerConfig {
- const char *application_name;
- const char *session_starting_dir;
-
- RSAKey *rsa_kex_key;
-
- /*
- * In all of these ptrlens, setting the 'ptr' member to NULL means
- * that we're not overriding the default configuration.
- */
- ptrlen banner; /* default here is 'no banner' */
- ptrlen kex_override[NKEXLIST];
-
- bool exit_signal_numeric; /* mimic an old server bug */
-
- unsigned long ssh1_cipher_mask;
- bool ssh1_allow_compression;
- bool bare_connection;
-
- bool stunt_pretend_to_accept_any_pubkey;
- bool stunt_open_unconditional_agent_socket;
-};
-
-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/sshsh256.c b/sshsh256.c
deleted file mode 100644
index db1f96bd..00000000
--- a/sshsh256.c
+++ /dev/null
@@ -1,940 +0,0 @@
-/*
- * SHA-256 algorithm as described at
- *
- * http://csrc.nist.gov/cryptval/shs.html
- */
-
-#include "ssh.h"
-#include <assert.h>
-
-/*
- * 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(<wmmintrin.h>) && \
- (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(<arm_neon.h>) && \
- (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(&copy->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 <wmmintrin.h>
-#include <smmintrin.h>
-#include <immintrin.h>
-#if defined(__clang__) || defined(__GNUC__)
-#include <shaintrin.h>
-#endif
-
-#if defined(__clang__) || defined(__GNUC__)
-#include <cpuid.h>
-#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(&copy->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 <arm64_neon.h>
-#else
-#include <arm_neon.h>
-#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(&copy->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/sshsh512.c b/sshsh512.c
deleted file mode 100644
index cba7f38d..00000000
--- a/sshsh512.c
+++ /dev/null
@@ -1,836 +0,0 @@
-/*
- * SHA-512 algorithm as described at
- *
- * http://csrc.nist.gov/cryptval/shs.html
- *
- * Modifications made for SHA-384 also
- */
-
-#include <assert.h>
-#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(<arm_neon.h>) && \
- (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(&copy->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 <arm64_neon.h>
-#else
-#include <arm_neon.h>
-#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 <arm_neon.h>.
- * 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(&copy->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/sshsha.c b/sshsha.c
deleted file mode 100644
index a5e79e6a..00000000
--- a/sshsha.c
+++ /dev/null
@@ -1,934 +0,0 @@
-/*
- * SHA-1 algorithm as described at
- *
- * http://csrc.nist.gov/cryptval/shs.html
- */
-
-#include "ssh.h"
-#include <assert.h>
-
-/*
- * 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(<wmmintrin.h>) && \
- (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(<arm_neon.h>) && \
- (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(&copy->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 <wmmintrin.h>
-#include <smmintrin.h>
-#include <immintrin.h>
-#if defined(__clang__) || defined(__GNUC__)
-#include <shaintrin.h>
-#endif
-
-#if defined(__clang__) || defined(__GNUC__)
-#include <cpuid.h>
-#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(&copy->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 <arm64_neon.h>
-#else
-#include <arm_neon.h>
-#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(&copy->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/sshshare.c b/sshshare.c
deleted file mode 100644
index b5da8657..00000000
--- a/sshshare.c
+++ /dev/null
@@ -1,2180 +0,0 @@
-/*
- * Support for SSH connection sharing, i.e. permitting one PuTTY to
- * open its own channels over the SSH session being run by another.
- */
-
-/*
- * Discussion and technical documentation
- * ======================================
- *
- * The basic strategy for PuTTY's implementation of SSH connection
- * sharing is to have a single 'upstream' PuTTY process, which manages
- * the real SSH connection and all the cryptography, and then zero or
- * more 'downstream' PuTTYs, which never talk to the real host but
- * only talk to the upstream through local IPC (Unix-domain sockets or
- * Windows named pipes).
- *
- * The downstreams communicate with the upstream using a protocol
- * derived from SSH itself, which I'll document in detail below. In
- * brief, though: the downstream->upstream protocol uses a trivial
- * binary packet protocol (just length/type/data) to encapsulate
- * unencrypted SSH messages, and downstreams talk to the upstream more
- * or less as if it was an SSH server itself. (So downstreams can
- * themselves open multiple SSH channels, for example, by sending
- * multiple SSH2_MSG_CHANNEL_OPENs; they can send CHANNEL_REQUESTs of
- * their choice within each channel, and they handle their own
- * WINDOW_ADJUST messages.)
- *
- * The upstream would ideally handle these downstreams by just putting
- * their messages into the queue for proper SSH-2 encapsulation and
- * encryption and sending them straight on to the server. However,
- * that's not quite feasible as written, because client-side channel
- * IDs could easily conflict (between multiple downstreams, or between
- * a downstream and the upstream). To protect against that, the
- * upstream rewrites the client-side channel IDs in messages it passes
- * on to the server, so that it's performing what you might describe
- * as 'channel-number NAT'. Then the upstream remembers which of its
- * own channel IDs are channels it's managing itself, and which are
- * placeholders associated with a particular downstream, so that when
- * replies come in from the server they can be sent on to the relevant
- * downstream (after un-NATting the channel number, of course).
- *
- * Global requests from downstreams are only accepted if the upstream
- * knows what to do about them; currently the only such requests are
- * the ones having to do with remote-to-local port forwarding (in
- * which, again, the upstream remembers that some of the forwardings
- * it's asked the server to set up were on behalf of particular
- * downstreams, and sends the incoming CHANNEL_OPENs to those
- * downstreams when connections come in).
- *
- * Other fiddly pieces of this mechanism are X forwarding and
- * (OpenSSH-style) agent forwarding. Both of these have a fundamental
- * problem arising from the protocol design: that the CHANNEL_OPEN
- * from the server introducing a forwarded connection does not carry
- * any indication of which session channel gave rise to it; so if
- * session channels from multiple downstreams enable those forwarding
- * methods, it's hard for the upstream to know which downstream to
- * send the resulting connections back to.
- *
- * For X forwarding, we can work around this in a really painful way
- * by using the fake X11 authorisation data sent to the server as part
- * of the forwarding setup: upstream ensures that every X forwarding
- * request carries distinguishable fake auth data, and then when X
- * connections come in it waits to see the auth data in the X11 setup
- * message before it decides which downstream to pass the connection
- * on to.
- *
- * For agent forwarding, that workaround is unavailable. As a result,
- * this system (and, as far as I can think of, any other system too)
- * has the fundamental constraint that it can only forward one SSH
- * agent - it can't forward two agents to different session channels.
- * So downstreams can request agent forwarding if they like, but if
- * they do, they'll get whatever SSH agent is known to the upstream
- * (if any) forwarded to their sessions.
- *
- * Downstream-to-upstream protocol
- * -------------------------------
- *
- * Here I document in detail the protocol spoken between PuTTY
- * downstreams and upstreams over local IPC. The IPC mechanism can
- * vary between host platforms, but the protocol is the same.
- *
- * The protocol commences with a version exchange which is exactly
- * like the SSH-2 one, in that each side sends a single line of text
- * of the form
- *
- * <protocol>-<version>-<softwareversion> [comments] \r\n
- *
- * The only difference is that in real SSH-2, <protocol> 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 <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <limits.h>
-#include <errno.h>
-
-#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/sshutils.c b/sshutils.c
deleted file mode 100644
index 1ee3342b..00000000
--- a/sshutils.c
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Supporting routines used in common by all the various components of
- * the SSH system.
- */
-
-#include <assert.h>
-#include <stdlib.h>
-
-#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/sshverstring.c b/sshverstring.c
deleted file mode 100644
index 8951e4cb..00000000
--- a/sshverstring.c
+++ /dev/null
@@ -1,628 +0,0 @@
-/*
- * Code to handle the initial SSH version string exchange.
- */
-
-#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
-
-#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.
- */
- s->our_vstring = dupprintf(
- "%.*s%s-%s%s",
- (int)s->prefix_wanted.len, (const char *)s->prefix_wanted.ptr,
- s->our_protoversion, s->impl_name, sshver);
- sv_pos = s->prefix_wanted.len + strlen(s->our_protoversion) + 1;
-
- /* Convert minus signs and spaces in the software version string
- * into underscores. */
- for (p = s->our_vstring + sv_pos; *p; p++) {
- if (*p == '-' || *p == ' ')
- *p = '_';
- }
-
-#ifdef FUZZING
- /*
- * Replace the first character of the string with an "I" if we're
- * compiling this code for fuzzing - i.e. the protocol prefix
- * becomes "ISH-" instead of "SSH-".
- *
- * This is irrelevant to any real client software (the only thing
- * reading the output of PuTTY built for fuzzing is the fuzzer,
- * which can adapt to whatever it sees anyway). But it's a safety
- * precaution making it difficult to accidentally run such a
- * version of PuTTY (which would be hugely insecure) against a
- * live peer implementation.
- *
- * (So the replacement prefix "ISH" notionally stands for
- * 'Insecure Shell', of course.)
- */
- s->our_vstring[0] = 'I';
-#endif
-
- /*
- * Now send that version string, plus trailing \r\n or just \n
- * (the latter in SSH-1 mode).
- */
- bufchain_add(s->bpp.out_raw, s->our_vstring, strlen(s->our_vstring));
- if (ssh_version_includes_v2(s->our_protoversion))
- bufchain_add(s->bpp.out_raw, "\015", 1);
- bufchain_add(s->bpp.out_raw, "\012", 1);
-
- bpp_logevent("We claim version: %s", s->our_vstring);
-}
-
-#define BPP_WAITFOR(minlen) do \
- { \
- bool success; \
- crMaybeWaitUntilV( \
- (success = (bufchain_size(s->bpp.in_raw) >= (minlen))) || \
- s->bpp.input_eof); \
- if (!success) \
- goto eof; \
- } while (0)
-
-void ssh_verstring_handle_input(BinaryPacketProtocol *bpp)
-{
- struct ssh_verstring_state *s =
- container_of(bpp, struct ssh_verstring_state, bpp);
-
- crBegin(s->crState);
-
- /*
- * If we're sending our version string up front before seeing the
- * other side's, then do it now.
- */
- if (s->send_early)
- ssh_verstring_send(s);
-
- /*
- * Search for a line beginning with the protocol name prefix in
- * the input.
- */
- s->i = 0;
- while (1) {
- /*
- * Every time round this loop, we're at the start of a new
- * line, so look for the prefix.
- */
- BPP_WAITFOR(s->prefix_wanted.len);
- bufchain_fetch(s->bpp.in_raw, s->prefix, s->prefix_wanted.len);
- if (!memcmp(s->prefix, s->prefix_wanted.ptr, s->prefix_wanted.len)) {
- bufchain_consume(s->bpp.in_raw, s->prefix_wanted.len);
- ssh_check_frozen(s->bpp.ssh);
- break;
- }
-
- /*
- * If we didn't find it, consume data until we see a newline.
- */
- while (1) {
- ptrlen data;
- char *nl;
-
- /* Wait to receive at least 1 byte, but then consume more
- * than that if it's there. */
- BPP_WAITFOR(1);
- data = bufchain_prefix(s->bpp.in_raw);
- if ((nl = memchr(data.ptr, '\012', data.len)) != NULL) {
- bufchain_consume(s->bpp.in_raw, nl - (char *)data.ptr + 1);
- ssh_check_frozen(s->bpp.ssh);
- break;
- } else {
- bufchain_consume(s->bpp.in_raw, data.len);
- ssh_check_frozen(s->bpp.ssh);
- }
- }
- }
-
- s->found_prefix = true;
-
- /*
- * Copy the greeting line so far into vstring.
- */
- put_data(s->vstring, s->prefix_wanted.ptr, s->prefix_wanted.len);
-
- /*
- * Now read the rest of the greeting line.
- */
- s->i = 0;
- do {
- ptrlen data;
- char *nl;
-
- BPP_WAITFOR(1);
- data = bufchain_prefix(s->bpp.in_raw);
- if ((nl = memchr(data.ptr, '\012', data.len)) != NULL) {
- data.len = nl - (char *)data.ptr + 1;
- }
-
- put_datapl(s->vstring, data);
- bufchain_consume(s->bpp.in_raw, data.len);
- ssh_check_frozen(s->bpp.ssh);
-
- } while (s->vstring->s[s->vstring->len-1] != '\012');
-
- /*
- * Trim \r and \n from the version string, and replace them with
- * a NUL terminator.
- */
- while (s->vstring->len > 0 &&
- (s->vstring->s[s->vstring->len-1] == '\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/sshzlib.c b/sshzlib.c
deleted file mode 100644
index 9ad04ed2..00000000
--- a/sshzlib.c
+++ /dev/null
@@ -1,1253 +0,0 @@
-/*
- * Zlib (RFC1950 / RFC1951) compression for PuTTY.
- *
- * There will no doubt be criticism of my decision to reimplement
- * Zlib compression from scratch instead of using the existing zlib
- * code. People will cry `reinventing the wheel'; they'll claim
- * that the `fundamental basis of OSS' is code reuse; they'll want
- * to see a really good reason for me having chosen not to use the
- * existing code.
- *
- * Well, here are my reasons. Firstly, I don't want to link the
- * whole of zlib into the PuTTY binary; PuTTY is justifiably proud
- * of its small size and I think zlib contains a lot of unnecessary
- * baggage for the kind of compression that SSH requires.
- *
- * Secondly, I also don't like the alternative of using zlib.dll.
- * Another thing PuTTY is justifiably proud of is its ease of
- * installation, and the last thing I want to do is to start
- * mandating DLLs. Not only that, but there are two _kinds_ of
- * zlib.dll kicking around, one with C calling conventions on the
- * exported functions and another with WINAPI conventions, and
- * there would be a significant danger of getting the wrong one.
- *
- * Thirdly, there seems to be a difference of opinion on the IETF
- * secsh mailing list about the correct way to round off a
- * compressed packet and start the next. In particular, there's
- * some talk of switching to a mechanism zlib isn't currently
- * capable of supporting (see below for an explanation). Given that
- * sort of uncertainty, I thought it might be better to have code
- * that will support even the zlib-incompatible worst case.
- *
- * Fourthly, it's a _second implementation_. Second implementations
- * are fundamentally a Good Thing in standardisation efforts. The
- * difference of opinion mentioned above has arisen _precisely_
- * because there has been only one zlib implementation and
- * everybody has used it. I don't intend that this should happen
- * again.
- */
-
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#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/storage.h b/storage.h
index 6464b69d..e9138f40 100644
--- a/storage.h
+++ b/storage.h
@@ -6,6 +6,8 @@
#ifndef PUTTY_STORAGE_H
#define PUTTY_STORAGE_H
+#include "defs.h"
+
/* ----------------------------------------------------------------------
* Functions to save and restore PuTTY sessions. Note that this is
* only the low-level code to do the reading and writing. The
@@ -81,8 +83,8 @@ void enum_settings_finish(settings_e *handle);
* 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);
+int check_stored_host_key(const char *hostname, int port,
+ const char *keytype, const char *key);
/*
* Write a host key into the database, overwriting any previous
@@ -92,6 +94,31 @@ void store_host_key(const char *hostname, int port,
const char *keytype, const char *key);
/* ----------------------------------------------------------------------
+ * Functions to access PuTTY's configuration for trusted host
+ * certification authorities. This must be stored separately from the
+ * saved-session data, because the whole point is to avoid having to
+ * configure CAs separately per session.
+ */
+
+struct host_ca {
+ char *name;
+ strbuf *ca_public_key;
+ char *validity_expression;
+ ca_options opts;
+};
+
+host_ca_enum *enum_host_ca_start(void);
+bool enum_host_ca_next(host_ca_enum *handle, strbuf *out);
+void enum_host_ca_finish(host_ca_enum *handle);
+
+host_ca *host_ca_load(const char *name);
+char *host_ca_save(host_ca *); /* NULL on success, or dynamic error msg */
+char *host_ca_delete(const char *name); /* likewise */
+
+host_ca *host_ca_new(void); /* initialises to default settings */
+void host_ca_free(host_ca *);
+
+/* ----------------------------------------------------------------------
* Functions to access PuTTY's random number seed file.
*/
diff --git a/stripctrl.c b/stripctrl.c
deleted file mode 100644
index 58289b10..00000000
--- a/stripctrl.c
+++ /dev/null
@@ -1,476 +0,0 @@
-/*
- * stripctrl.c: a facility for stripping control characters out of a
- * data stream (defined as any multibyte character in the system
- * locale which is neither printable nor \n), using the standard C
- * library multibyte character facilities.
- */
-
-#include <assert.h>
-#include <locale.h>
-#include <string.h>
-#include <wchar.h>
-#include <wctype.h>
-
-#include "putty.h"
-#include "terminal.h"
-#include "misc.h"
-#include "marshal.h"
-
-#define SCC_BUFSIZE 64
-#define LINE_LIMIT 77
-
-typedef struct StripCtrlCharsImpl StripCtrlCharsImpl;
-struct StripCtrlCharsImpl {
- mbstate_t mbs_in, mbs_out;
-
- bool permit_cr;
- wchar_t substitution;
-
- char buf[SCC_BUFSIZE];
- size_t buflen;
-
- Terminal *term;
- bool last_term_utf;
- struct term_utf8_decode utf8;
- unsigned long (*translate)(Terminal *, term_utf8_decode *, unsigned char);
-
- bool line_limit;
- bool line_start;
- size_t line_chars_remaining;
-
- BinarySink *bs_out;
-
- StripCtrlChars public;
-};
-
-static void stripctrl_locale_BinarySink_write(
- BinarySink *bs, const void *vp, size_t len);
-static void stripctrl_term_BinarySink_write(
- BinarySink *bs, const void *vp, size_t len);
-
-static StripCtrlCharsImpl *stripctrl_new_common(
- BinarySink *bs_out, bool permit_cr, wchar_t substitution)
-{
- StripCtrlCharsImpl *scc = snew(StripCtrlCharsImpl);
- memset(scc, 0, sizeof(StripCtrlCharsImpl)); /* zeroes mbstates */
- scc->bs_out = bs_out;
- scc->permit_cr = permit_cr;
- scc->substitution = substitution;
- return scc;
-}
-
-StripCtrlChars *stripctrl_new(
- BinarySink *bs_out, bool permit_cr, wchar_t substitution)
-{
- StripCtrlCharsImpl *scc = stripctrl_new_common(
- bs_out, permit_cr, substitution);
- BinarySink_INIT(&scc->public, stripctrl_locale_BinarySink_write);
- return &scc->public;
-}
-
-StripCtrlChars *stripctrl_new_term_fn(
- BinarySink *bs_out, bool permit_cr, wchar_t substitution,
- Terminal *term, unsigned long (*translate)(
- Terminal *, term_utf8_decode *, unsigned char))
-{
- StripCtrlCharsImpl *scc = stripctrl_new_common(
- bs_out, permit_cr, substitution);
- scc->term = term;
- scc->translate = translate;
- BinarySink_INIT(&scc->public, stripctrl_term_BinarySink_write);
- return &scc->public;
-}
-
-void stripctrl_retarget(StripCtrlChars *sccpub, BinarySink *new_bs_out)
-{
- StripCtrlCharsImpl *scc =
- container_of(sccpub, StripCtrlCharsImpl, public);
- scc->bs_out = new_bs_out;
- stripctrl_reset(sccpub);
-}
-
-void stripctrl_reset(StripCtrlChars *sccpub)
-{
- StripCtrlCharsImpl *scc =
- container_of(sccpub, StripCtrlCharsImpl, public);
-
- /*
- * Clear all the fields that might have been in the middle of a
- * multibyte character or non-default shift state, so that we can
- * start converting a fresh piece of data to send to a channel
- * that hasn't seen the previous output.
- */
- memset(&scc->utf8, 0, sizeof(scc->utf8));
- memset(&scc->mbs_in, 0, sizeof(scc->mbs_in));
- memset(&scc->mbs_out, 0, sizeof(scc->mbs_out));
-
- /*
- * Also, reset the line-limiting system to its starting state.
- */
- scc->line_start = true;
-}
-
-void stripctrl_free(StripCtrlChars *sccpub)
-{
- StripCtrlCharsImpl *scc =
- container_of(sccpub, StripCtrlCharsImpl, public);
- smemclr(scc, sizeof(StripCtrlCharsImpl));
- sfree(scc);
-}
-
-void stripctrl_enable_line_limiting(StripCtrlChars *sccpub)
-{
- StripCtrlCharsImpl *scc =
- container_of(sccpub, StripCtrlCharsImpl, public);
- scc->line_limit = true;
- scc->line_start = true;
-}
-
-static inline bool stripctrl_ctrlchar_ok(StripCtrlCharsImpl *scc, wchar_t wc)
-{
- return wc == L'\n' || (wc == L'\r' && scc->permit_cr);
-}
-
-static inline void stripctrl_check_line_limit(
- StripCtrlCharsImpl *scc, wchar_t wc, size_t width)
-{
- if (!scc->line_limit)
- return; /* nothing to do */
-
- if (scc->line_start) {
- put_datapl(scc->bs_out, PTRLEN_LITERAL("| "));
- scc->line_start = false;
- scc->line_chars_remaining = LINE_LIMIT;
- }
-
- if (wc == '\n') {
- scc->line_start = true;
- return;
- }
-
- if (scc->line_chars_remaining < width) {
- put_datapl(scc->bs_out, PTRLEN_LITERAL("\r\n> "));
- scc->line_chars_remaining = LINE_LIMIT;
- }
-
- assert(width <= scc->line_chars_remaining);
- scc->line_chars_remaining -= width;
-}
-
-static inline void stripctrl_locale_put_wc(StripCtrlCharsImpl *scc, wchar_t wc)
-{
- int width = mk_wcwidth(wc);
- if ((iswprint(wc) && width >= 0) || stripctrl_ctrlchar_ok(scc, wc)) {
- /* Printable character, or one we're going to let through anyway. */
- if (width < 0)
- width = 0; /* sanitise for stripctrl_check_line_limit */
- } else if (scc->substitution) {
- wc = scc->substitution;
- width = mk_wcwidth(wc);
- assert(width >= 0);
- } else {
- /* No defined substitution, so don't write any output wchar_t. */
- return;
- }
-
- stripctrl_check_line_limit(scc, wc, width);
-
- char outbuf[MB_LEN_MAX];
- size_t produced = wcrtomb(outbuf, wc, &scc->mbs_out);
- if (produced > 0)
- put_data(scc->bs_out, outbuf, produced);
-}
-
-static inline void stripctrl_term_put_wc(
- StripCtrlCharsImpl *scc, unsigned long wc)
-{
- ptrlen prefix = PTRLEN_LITERAL("");
- int width = term_char_width(scc->term, wc);
-
- if (!(wc & ~0x9F) || width < 0) {
- /* This is something the terminal interprets as a control
- * character. */
- if (!stripctrl_ctrlchar_ok(scc, wc)) {
- if (!scc->substitution) {
- return;
- } else {
- wc = scc->substitution;
- width = term_char_width(scc->term, wc);
- assert(width >= 0);
- }
- } else {
- if (width < 0)
- width = 0; /* sanitise for stripctrl_check_line_limit */
- }
-
- if (wc == '\012') {
- /* Precede \n with \r, because our terminal will not
- * generally be in the ONLCR mode where it assumes that
- * internally, and any \r on input has been stripped
- * out. */
- prefix = PTRLEN_LITERAL("\r");
- }
- }
-
- stripctrl_check_line_limit(scc, wc, width);
-
- if (prefix.len)
- put_datapl(scc->bs_out, prefix);
-
- 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/stubs/CMakeLists.txt b/stubs/CMakeLists.txt
new file mode 100644
index 00000000..dc02aca3
--- /dev/null
+++ b/stubs/CMakeLists.txt
@@ -0,0 +1,31 @@
+# This subdirectory is generally full of 'stubs' in the sense of
+# functions and types that don't do anything interesting, and are
+# substituted in some contexts for ones that do.
+#
+# Some of the files here, with names beginning 'no-', are substituted
+# at link time, conditional on the application. For example, a program
+# that doesn't use the timing subsystem but still includes a module
+# that makes a passing reference to it (say, in a context that never
+# turns out to be called) can link against no-timing.c in place of the
+# real timing.c.
+#
+# Other files, with names beginning 'null-', provide non-functional
+# implementations of a particular internal API, or a selection of
+# non-functional methods for that API that real implementations can
+# selectively use. Those are linked in to a program _alongside_ real
+# implementations of the same API.
+#
+# So the cmake setup for this directory puts all the 'null-' files
+# into the utils library (at the end of the link, where they'll be
+# available everywhere), but doesn't mention the 'no-' files, because
+# those will be selected manually by add_executable() commands
+# elsewhere.
+
+add_sources_from_current_dir(utils
+ null-lp.c
+ null-cipher.c
+ null-key.c
+ null-mac.c
+ null-opener.c
+ null-plug.c
+ null-seat.c)
diff --git a/stubs/no-ca-config.c b/stubs/no-ca-config.c
new file mode 100644
index 00000000..573f770f
--- /dev/null
+++ b/stubs/no-ca-config.c
@@ -0,0 +1,14 @@
+/*
+ * Stub version of setup_ca_config_box, for tools that don't have SSH
+ * code linked in.
+ */
+
+#include "putty.h"
+#include "dialog.h"
+
+const bool has_ca_config_box = false;
+
+void setup_ca_config_box(struct controlbox *b)
+{
+ unreachable("should never call setup_ca_config_box in this application");
+}
diff --git a/stubs/no-cmdline.c b/stubs/no-cmdline.c
new file mode 100644
index 00000000..2476354e
--- /dev/null
+++ b/stubs/no-cmdline.c
@@ -0,0 +1,22 @@
+/*
+ * no-cmdline.c - stubs in applications which don't do the
+ * standard(ish) PuTTY tools' command-line parsing
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include "putty.h"
+
+/*
+ * Stub version of the function in cmdline.c which provides the
+ * password to SSH authentication by remembering it having been passed
+ * as a command-line option. If we're not doing normal command-line
+ * handling, then there is no such option, so that function always
+ * returns failure.
+ */
+SeatPromptResult cmdline_get_passwd_input(
+ prompts_t *p, cmdline_get_passwd_input_state *state, bool restartable)
+{
+ return SPR_INCOMPLETE;
+}
diff --git a/NOGSS.C b/stubs/no-gss.c
index 844a1323..844a1323 100644
--- a/NOGSS.C
+++ b/stubs/no-gss.c
diff --git a/NOPRINT.C b/stubs/no-print.c
index 941da68c..941da68c 100644
--- a/NOPRINT.C
+++ b/stubs/no-print.c
diff --git a/norand.c b/stubs/no-rand.c
index 2ad9f661..2ad9f661 100644
--- a/norand.c
+++ b/stubs/no-rand.c
diff --git a/stubs/no-term.c b/stubs/no-term.c
new file mode 100644
index 00000000..c2e534b2
--- /dev/null
+++ b/stubs/no-term.c
@@ -0,0 +1,16 @@
+/*
+ * Stubs of functions in terminal.c, for use in programs that don't
+ * have a terminal.
+ */
+
+#include "putty.h"
+#include "terminal.h"
+
+void term_nopaste(Terminal *term)
+{
+}
+
+SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p)
+{
+ return SPR_SW_ABORT("No terminal to send interactive prompts to");
+}
diff --git a/stubs/no-timing.c b/stubs/no-timing.c
new file mode 100644
index 00000000..d1a0ef9f
--- /dev/null
+++ b/stubs/no-timing.c
@@ -0,0 +1,21 @@
+/*
+ * no-timing.c: stub version of timing API.
+ *
+ * Used in any tool which needs a subsystem linked against the
+ * timing API but doesn't want to actually provide timing. For
+ * example, key generation tools need the random number generator,
+ * but they don't want the hassle of calling noise_regular() at
+ * regular intervals - and they don't _need_ it either, since they
+ * have their own rigorous and different means of noise collection.
+ */
+
+#include "putty.h"
+
+unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx)
+{
+ return 0;
+}
+
+void expire_timer_context(void *ctx)
+{
+}
diff --git a/stubs/null-cipher.c b/stubs/null-cipher.c
new file mode 100644
index 00000000..e11c7bbc
--- /dev/null
+++ b/stubs/null-cipher.c
@@ -0,0 +1,11 @@
+/*
+ * Implementation of shared trivial routines that ssh_cipher
+ * implementations might use.
+ */
+
+#include "ssh.h"
+
+void nullcipher_next_message(ssh_cipher *cipher)
+{
+ /* Most ciphers don't do anything at all with this */
+}
diff --git a/stubs/null-key.c b/stubs/null-key.c
new file mode 100644
index 00000000..dae5c1bb
--- /dev/null
+++ b/stubs/null-key.c
@@ -0,0 +1,22 @@
+#include "misc.h"
+#include "ssh.h"
+
+unsigned nullkey_supported_flags(const ssh_keyalg *self)
+{
+ return 0;
+}
+
+const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags)
+{
+ /* There are no alternate ids */
+ return self->ssh_id;
+}
+
+ssh_key *nullkey_base_key(ssh_key *key)
+{
+ /* When a key is not certified, it is its own base */
+ return key;
+}
+
+bool nullkey_variable_size_no(const ssh_keyalg *self) { return false; }
+bool nullkey_variable_size_yes(const ssh_keyalg *self) { return true; }
diff --git a/stubs/null-lp.c b/stubs/null-lp.c
new file mode 100644
index 00000000..193c3392
--- /dev/null
+++ b/stubs/null-lp.c
@@ -0,0 +1,8 @@
+/*
+ * Stub methods usable by LogPolicy implementations.
+ */
+
+#include "putty.h"
+
+bool null_lp_verbose_no(LogPolicy *lp) { return false; }
+bool null_lp_verbose_yes(LogPolicy *lp) { return true; }
diff --git a/stubs/null-mac.c b/stubs/null-mac.c
new file mode 100644
index 00000000..4d836704
--- /dev/null
+++ b/stubs/null-mac.c
@@ -0,0 +1,11 @@
+/*
+ * Implementation of shared trivial routines that ssh2_mac
+ * implementations might use.
+ */
+
+#include "ssh.h"
+
+void nullmac_next_message(ssh2_mac *m)
+{
+ /* Most MACs don't do anything at all with this */
+}
diff --git a/stubs/null-opener.c b/stubs/null-opener.c
new file mode 100644
index 00000000..6fdb7c28
--- /dev/null
+++ b/stubs/null-opener.c
@@ -0,0 +1,20 @@
+/*
+ * Null implementation of DeferredSocketOpener. Doesn't even bother to
+ * allocate and free itself: there's just one static implementation
+ * which we hand out to any caller.
+ */
+
+#include "putty.h"
+
+static void null_opener_free(DeferredSocketOpener *opener) {}
+
+static const DeferredSocketOpenerVtable NullOpener_vt = {
+ .free = null_opener_free,
+};
+
+static DeferredSocketOpener null_opener = { .vt = &NullOpener_vt };
+
+DeferredSocketOpener *null_deferred_socket_opener(void)
+{
+ return &null_opener;
+}
diff --git a/stubs/null-plug.c b/stubs/null-plug.c
new file mode 100644
index 00000000..d583d156
--- /dev/null
+++ b/stubs/null-plug.c
@@ -0,0 +1,40 @@
+/*
+ * nullplug.c: provide a null implementation of the Plug vtable which
+ * ignores all calls. Occasionally useful in cases where we want to
+ * make a network connection just to see if it works, but not do
+ * anything with it afterwards except close it again.
+ */
+
+#include "putty.h"
+
+void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr,
+ int port, const char *err_msg, int err_code)
+{
+}
+
+void nullplug_closing(Plug *plug, PlugCloseType type, const char *error_msg)
+{
+}
+
+void nullplug_receive(Plug *plug, int urgent, const char *data, size_t len)
+{
+}
+
+void nullplug_sent(Plug *plug, size_t bufsize)
+{
+}
+
+static const PlugVtable nullplug_plugvt = {
+ .log = nullplug_log,
+ .closing = nullplug_closing,
+ .receive = nullplug_receive,
+ .sent = nullplug_sent,
+};
+
+static Plug nullplug_plug = { &nullplug_plugvt };
+
+/*
+ * There's a singleton instance of nullplug, because it's not
+ * interesting enough to worry about making more than one of them.
+ */
+Plug *const nullplug = &nullplug_plug;
diff --git a/stubs/null-seat.c b/stubs/null-seat.c
new file mode 100644
index 00000000..37cb0f4c
--- /dev/null
+++ b/stubs/null-seat.c
@@ -0,0 +1,65 @@
+/*
+ * Stub methods usable by Seat implementations.
+ */
+
+#include "putty.h"
+
+size_t nullseat_output(
+ Seat *seat, SeatOutputType type, const void *data, size_t len) {return 0;}
+bool nullseat_eof(Seat *seat) { return true; }
+void nullseat_sent(Seat *seat, size_t bufsize) {}
+size_t nullseat_banner(Seat *seat, const void *data, size_t len) {return 0;}
+size_t nullseat_banner_to_stderr(Seat *seat, const void *data, size_t len)
+{ return seat_output(seat, SEAT_OUTPUT_STDERR, data, len); }
+SeatPromptResult nullseat_get_userpass_input(Seat *seat, prompts_t *p)
+{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); }
+void nullseat_notify_session_started(Seat *seat) {}
+void nullseat_notify_remote_exit(Seat *seat) {}
+void nullseat_notify_remote_disconnect(Seat *seat) {}
+void nullseat_connection_fatal(Seat *seat, const char *message) {}
+void nullseat_update_specials_menu(Seat *seat) {}
+char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; }
+void nullseat_set_busy_status(Seat *seat, BusyStatus status) {}
+SeatPromptResult nullseat_confirm_ssh_host_key(
+ Seat *seat, const char *host, int port, const char *keytype,
+ char *keystr, SeatDialogText *text, HelpCtx helpctx,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); }
+SeatPromptResult nullseat_confirm_weak_crypto_primitive(
+ Seat *seat, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); }
+SeatPromptResult nullseat_confirm_weak_cached_hostkey(
+ Seat *seat, const char *algname, const char *betteralgs,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); }
+bool nullseat_is_never_utf8(Seat *seat) { return false; }
+bool nullseat_is_always_utf8(Seat *seat) { return true; }
+void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing) {}
+const char *nullseat_get_x_display(Seat *seat) { return NULL; }
+bool nullseat_get_windowid(Seat *seat, long *id_out) { return false; }
+bool nullseat_get_window_pixel_size(
+ Seat *seat, int *width, int *height) { return false; }
+StripCtrlChars *nullseat_stripctrl_new(
+ Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) {return NULL;}
+void nullseat_set_trust_status(Seat *seat, bool trusted) {}
+bool nullseat_can_set_trust_status_yes(Seat *seat) { return true; }
+bool nullseat_can_set_trust_status_no(Seat *seat) { return false; }
+bool nullseat_has_mixed_input_stream_yes(Seat *seat) { return true; }
+bool nullseat_has_mixed_input_stream_no(Seat *seat) { return false; }
+bool nullseat_verbose_no(Seat *seat) { return false; }
+bool nullseat_verbose_yes(Seat *seat) { return true; }
+bool nullseat_interactive_no(Seat *seat) { return false; }
+bool nullseat_interactive_yes(Seat *seat) { return true; }
+bool nullseat_get_cursor_position(Seat *seat, int *x, int *y) { return false; }
+
+const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat)
+{
+ static const SeatDialogPromptDescriptions descs = {
+ .hk_accept_action = "",
+ .hk_connect_once_action = "",
+ .hk_cancel_action = "",
+ .hk_cancel_action_Participle = "",
+ };
+ return &descs;
+}
diff --git a/supdup.c b/supdup.c
deleted file mode 100644
index f210ebe3..00000000
--- a/supdup.c
+++ /dev/null
@@ -1,923 +0,0 @@
-/*
-* Supdup backend
-*/
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-
-#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/telnet.c b/telnet.c
deleted file mode 100644
index 3a60e646..00000000
--- a/telnet.c
+++ /dev/null
@@ -1,1070 +0,0 @@
-/*
- * Telnet backend.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-
-#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 "<unknown>";
- }
-#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 <something weird>");
- 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 <something weird>\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 <nothing>",
- 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/terminal.c b/terminal.c
deleted file mode 100644
index f8f9417c..00000000
--- a/terminal.c
+++ /dev/null
@@ -1,7656 +0,0 @@
-/*
- * Terminal emulator.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <limits.h>
-#include <wchar.h>
-
-#include <time.h>
-#include <assert.h>
-#include "putty.h"
-#include "terminal.h"
-
-#define VT52_PLUS
-
-#define CL_ANSIMIN 0x0001 /* Codes in all ANSI like terminals. */
-#define CL_VT100 0x0002 /* VT100 */
-#define CL_VT100AVO 0x0004 /* VT100 +AVO; 132x24 (not 132x14) & attrs */
-#define CL_VT102 0x0008 /* VT102 */
-#define CL_VT220 0x0010 /* VT220 */
-#define CL_VT320 0x0020 /* VT320 */
-#define CL_VT420 0x0040 /* VT420 */
-#define CL_VT510 0x0080 /* VT510, NB VT510 includes ANSI */
-#define CL_VT340TEXT 0x0100 /* VT340 extensions that appear in the VT420 */
-#define CL_SCOANSI 0x1000 /* SCOANSI not in ANSIMIN. */
-#define CL_ANSI 0x2000 /* ANSI ECMA-48 not in the VT100..VT420 */
-#define CL_OTHER 0x4000 /* Others, Xterm, linux, putty, dunno, etc */
-
-#define TM_VT100 (CL_ANSIMIN|CL_VT100)
-#define TM_VT100AVO (TM_VT100|CL_VT100AVO)
-#define TM_VT102 (TM_VT100AVO|CL_VT102)
-#define TM_VT220 (TM_VT102|CL_VT220)
-#define TM_VTXXX (TM_VT220|CL_VT340TEXT|CL_VT510|CL_VT420|CL_VT320)
-#define TM_SCOANSI (CL_ANSIMIN|CL_SCOANSI)
-
-#define TM_PUTTY (0xFFFF)
-
-#define UPDATE_DELAY ((TICKSPERSEC+49)/50)/* ticks to defer window update */
-#define TBLINK_DELAY ((TICKSPERSEC*9+19)/20)/* ticks between text blinks*/
-#define CBLINK_DELAY (CURSORBLINK) /* ticks between cursor blinks */
-#define VBELL_DELAY (VBELL_TIMEOUT) /* visual bell timeout in ticks */
-
-#define compatibility(x) \
- if ( ((CL_##x)&term->compatibility_level) == 0 ) { \
- term->termstate=TOPLEVEL; \
- break; \
- }
-#define compatibility2(x,y) \
- if ( ((CL_##x|CL_##y)&term->compatibility_level) == 0 ) { \
- term->termstate=TOPLEVEL; \
- break; \
- }
-
-#define has_compat(x) ( ((CL_##x)&term->compatibility_level) != 0 )
-
-static const char *const EMPTY_WINDOW_TITLE = "";
-
-static const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 };
-
-#define sel_nl_sz (sizeof(sel_nl)/sizeof(wchar_t))
-static const wchar_t sel_nl[] = SEL_NL;
-
-/*
- * 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--) {
- assert(n < ldata->cols);
- readliteral(bs, ldata->chars + n, ldata, &state);
- n++;
- }
- }
- }
-
- assert(n == ldata->cols);
-}
-static void readliteral_chr(BinarySource *bs, termchar *c, termline *ldata,
- unsigned long *state)
-{
- int byte;
-
- /*
- * 00000000-0000007F: 0xxxxxxx
- * 00000080-00003FFF: 10xxxxxx xxxxxxxx
- * 00004000-001FFFFF: 110xxxxx xxxxxxxx xxxxxxxx
- * 00200000-0FFFFFFF: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx
- * 10000000-FFFFFFFF: 11110ZZZ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
- */
-
- byte = get_byte(bs);
- if (byte < 0x80) {
- c->chr = byte | *state;
- } else if (byte < 0xC0) {
- c->chr = (byte &~ 0xC0) << 8;
- c->chr |= get_byte(bs);
- } else if (byte < 0xE0) {
- c->chr = (byte &~ 0xE0) << 16;
- c->chr |= get_uint16(bs);
- } else if (byte < 0xF0) {
- c->chr = (byte &~ 0xF0) << 24;
- c->chr |= get_byte(bs) << 16;
- c->chr |= get_uint16(bs);
- } else {
- assert(byte == 0xF0);
- c->chr = get_uint32(bs);
- }
- *state = c->chr & ~0xFF;
-}
-static void readliteral_attr(BinarySource *bs, termchar *c, termline *ldata,
- unsigned long *state)
-{
- unsigned val, attr, colourbits;
-
- val = get_uint16(bs);
-
- if (val >= 0x8000) {
- val &= ~0x8000;
- val <<= 16;
- val |= get_uint16(bs);
- }
-
- colourbits = (val >> (32-9)) & 0xFF;
- attr = (val & ((1<<(32-9))-1));
-
- attr = (((attr >> (ATTR_FGSHIFT + 4)) << (ATTR_FGSHIFT + 8)) |
- (attr & ((1 << (ATTR_FGSHIFT + 4))-1)));
- attr = (((attr >> (ATTR_BGSHIFT + 4)) << (ATTR_BGSHIFT + 8)) |
- (attr & ((1 << (ATTR_BGSHIFT + 4))-1)));
-
- attr |= (colourbits >> 4) << (ATTR_BGSHIFT + 4);
- attr |= (colourbits & 0xF) << (ATTR_FGSHIFT + 4);
-
- c->attr = attr;
-}
-static void readliteral_truecolour(
- BinarySource *bs, termchar *c, termline *ldata, unsigned long *state)
-{
- int flags = get_byte(bs);
-
- if (flags & 1) {
- c->truecolour.fg.enabled = true;
- c->truecolour.fg.r = get_byte(bs);
- c->truecolour.fg.g = get_byte(bs);
- c->truecolour.fg.b = get_byte(bs);
- } else {
- c->truecolour.fg = optionalrgb_none;
- }
-
- if (flags & 2) {
- c->truecolour.bg.enabled = true;
- c->truecolour.bg.r = get_byte(bs);
- c->truecolour.bg.g = get_byte(bs);
- c->truecolour.bg.b = get_byte(bs);
- } else {
- c->truecolour.bg = optionalrgb_none;
- }
-}
-static void readliteral_cc(BinarySource *bs, termchar *c, termline *ldata,
- unsigned long *state)
-{
- termchar n;
- unsigned long zstate;
- int x = c - ldata->chars;
-
- c->cc_next = 0;
-
- while (1) {
- zstate = 0;
- readliteral_chr(bs, &n, ldata, &zstate);
- if (!n.chr)
- break;
- add_cc(ldata, x, n.chr);
- }
-}
-
-static termline *decompressline(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 <putty@projects.tartarus.org> "
- "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;
-
- assert(!screen);
-
- if (term->erase_to_scrollback &&
- term->alt_which && term->alt_screen) {
- altlines = term->alt_sblines;
- }
- if (y < -altlines) {
- whichtree = term->scrollback;
- treeindex = y + altlines + count234(term->scrollback);
- } else {
- whichtree = term->alt_screen;
- treeindex = y + term->alt_sblines;
- /* treeindex = y + count234(term->alt_screen); */
- }
- }
- if (whichtree == term->scrollback) {
- 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);
- term->xterm_mouse = 0;
- term->xterm_extended_mouse = false;
- term->urxvt_extended_mouse = false;
- 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);
- term->no_remote_charset = conf_get_bool(term->conf, CONF_no_remote_charset);
- term->no_remote_resize = conf_get_bool(term->conf, CONF_no_remote_resize);
- term->no_remote_wintitle = conf_get_bool(term->conf, CONF_no_remote_wintitle);
- term->no_remote_clearscroll = conf_get_bool(term->conf, CONF_no_remote_clearscroll);
- term->rawcnp = conf_get_bool(term->conf, CONF_rawcnp);
- term->utf8linedraw = conf_get_bool(term->conf, CONF_utf8linedraw);
- term->rect_select = conf_get_bool(term->conf, CONF_rect_select);
- term->remote_qtitle_action = conf_get_int(term->conf, CONF_remote_qtitle_action);
- term->rxvt_homeend = conf_get_bool(term->conf, CONF_rxvt_homeend);
- term->scroll_on_disp = conf_get_bool(term->conf, CONF_scroll_on_disp);
- term->scroll_on_key = conf_get_bool(term->conf, CONF_scroll_on_key);
- term->xterm_mouse_forbidden = conf_get_bool(term->conf, CONF_no_mouse_rep);
- term->xterm_256_colour = conf_get_bool(term->conf, CONF_xterm_256_colour);
- term->true_colour = conf_get_bool(term->conf, CONF_true_colour);
-
- /*
- * Parse the control-character escapes in the configured
- * answerback string.
- */
- {
- char *answerback = conf_get_str(term->conf, CONF_answerback);
- 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;
- }
- if (!conf_get_str(term->conf, CONF_printer)) {
- term_print_finish(term);
- }
- if (palette_changed)
- term_notify_palette_changed(term);
- term_schedule_tblink(term);
- term_schedule_cblink(term);
- term_copy_stuff_from_conf(term);
- term_update_raw_mouse_mode(term);
-}
-
-/*
- * Clear the scrollback.
- */
-void term_clrsb(Terminal *term)
-{
- unsigned char *line;
- int i;
-
- /*
- * Scroll forward to the current screen, if we were back in the
- * scrollback somewhere until now.
- */
- term->disptop = 0;
-
- /*
- * Clear the actual scrollback.
- */
- while ((line = delpos234(term->scrollback, 0)) != NULL) {
- sfree(line); /* this is compressed data, not a termline */
- }
-
- /*
- * When clearing the scrollback, we also truncate any termlines on
- * the current screen which have remembered data from a previous
- * larger window size. Rationale: clearing the scrollback is
- * sometimes done to protect privacy, so the user intention is
- * specifically that we should not retain evidence of what
- * previously happened in the terminal, and that ought to include
- * evidence to the right as well as evidence above.
- */
- for (i = 0; i < term->rows; i++)
- check_line_size(term, scrlineptr(i));
-
- /*
- * That operation has invalidated the selection, if it overlapped
- * the scrollback at all.
- */
- if (term->selstate != NO_SELECTION && term->selstart.y < 0)
- deselect(term);
-
- /*
- * There are now no lines of real scrollback which can be pulled
- * back into the screen by a resize, and no lines of the alternate
- * screen which should be displayed as if part of the scrollback.
- */
- term->tempsblines = 0;
- term->alt_sblines = 0;
-
- /*
- * The scrollbar will need updating to reflect the new state of
- * the world.
- */
- term->win_scrollbar_update_pending = true;
- term_schedule_update(term);
-}
-
-const optionalrgb optionalrgb_none = {0, 0, 0, 0};
-
-void term_setup_window_titles(Terminal *term, const char *title_hostname)
-{
- const char *conf_title = conf_get_str(term->conf, CONF_wintitle);
- sfree(term->window_title);
- sfree(term->icon_title);
- if (*conf_title) {
- term->window_title = dupstr(conf_title);
- term->icon_title = dupstr(conf_title);
- } else {
- if (title_hostname && *title_hostname)
- term->window_title = dupcat(title_hostname, " - ", appname);
- else
- term->window_title = dupstr(appname);
- term->icon_title = dupstr(term->window_title);
- }
- term->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);
- 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;
-
- 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;
-
- /* 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;
-
- 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;
- 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);
- 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 {
- 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);
- 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;
- 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;
- case 1047: /* alternate screen */
- compatibility(OTHER);
- deselect(term);
- swap_screen(term, term->no_alt_screen ? 0 : state, true, true);
- if (term->scroll_on_disp)
- term->disptop = 0;
- break;
- case 1048: /* save/restore cursor */
- if (!term->no_alt_screen)
- save_cursor(term, state);
- if (!state) seen_disp_event(term);
- break;
- case 1049: /* cursor & alternate screen */
- if (state && !term->no_alt_screen)
- save_cursor(term, state);
- if (!state) seen_disp_event(term);
- compatibility(OTHER);
- deselect(term);
- swap_screen(term, term->no_alt_screen ? 0 : state, true, false);
- if (!state && !term->no_alt_screen)
- save_cursor(term, state);
- if (term->scroll_on_disp)
- term->disptop = 0;
- break;
- case 2004: /* xterm bracketed paste */
- term->bracketed_paste = state ? true : false;
- break;
- }
- } else if (query == 0) {
- switch (mode) {
- case 4: /* IRM: set insert mode */
- compatibility(VT102);
- term->insert = state;
- break;
- case 12: /* SRM: set echo mode */
- term->srm_echo = !state;
- break;
- case 20: /* LNM: Return sends ... */
- term->cr_lf_return = state;
- break;
- case 34: /* WYULCURM: Make cursor BIG */
- compatibility2(OTHER, VT220);
- term->big_cursor = !state;
- }
- }
-}
-
-/*
- * Process an OSC sequence: set window title or icon name.
- */
-static void do_osc(Terminal *term)
-{
- if (term->osc_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;
- }
- }
-}
-
-/*
- * ANSI printing routines.
- */
-static void term_print_setup(Terminal *term, char *printer)
-{
- bufchain_clear(&term->printer_buf);
- term->print_job = printer_start_job(printer);
-}
-static void term_print_flush(Terminal *term)
-{
- size_t size;
- while ((size = bufchain_size(&term->printer_buf)) > 5) {
- ptrlen data = bufchain_prefix(&term->printer_buf);
- if (data.len > size-5)
- data.len = size-5;
- printer_job_data(term->print_job, data.ptr, data.len);
- bufchain_consume(&term->printer_buf, data.len);
- }
-}
-static void term_print_finish(Terminal *term)
-{
- size_t size;
- char c;
-
- if (!term->printing && !term->only_printing)
- return; /* we need do nothing */
-
- term_print_flush(term);
- while ((size = bufchain_size(&term->printer_buf)) > 0) {
- ptrlen data = bufchain_prefix(&term->printer_buf);
- c = *(char *)data.ptr;
- if (c == '\033' || c == '\233') {
- bufchain_consume(&term->printer_buf, size);
- break;
- } else {
- printer_job_data(term->print_job, &c, 1);
- bufchain_consume(&term->printer_buf, 1);
- }
- }
- printer_finish_job(term->print_job);
- term->print_job = NULL;
- term->printing = term->only_printing = false;
-}
-
-static void term_display_graphic_char(Terminal *term, unsigned long c)
-{
- termline *cline = scrlineptr(term->curs.y);
- int width = 0;
- if (DIRECT_CHAR(c))
- width = 1;
- if (!width)
- width = term_char_width(term, c);
-
- if (term->wrapnext && term->wrap && width > 0) {
- cline->lattr |= LATTR_WRAPPED;
- if (term->curs.y == term->marg_b)
- scroll(term, term->marg_t, term->marg_b, 1, true);
- else if (term->curs.y < term->rows - 1)
- term->curs.y++;
- term->curs.x = 0;
- term->wrapnext = false;
- cline = scrlineptr(term->curs.y);
- }
- if (term->insert && width > 0)
- insch(term, width);
- if (term->selstate != NO_SELECTION) {
- pos cursplus = term->curs;
- incpos(cursplus);
- check_selection(term, term->curs, cursplus);
- }
- if (((c & CSET_MASK) == CSET_ASCII ||
- (c & CSET_MASK) == 0) && term->logctx)
- logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII);
-
- check_trust_status(term, cline);
-
- int linecols = term->cols;
- if (cline->trusted)
- linecols -= TRUST_SIGIL_WIDTH;
-
- /*
- * 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 {
- return c | CSET_ASCII;
- }
- } else if ((c & 0xe0) == 0xc0) {
- utf8->size = utf8->state = 1;
- utf8->chr = (c & 0x1f);
- } else if ((c & 0xf0) == 0xe0) {
- utf8->size = utf8->state = 2;
- utf8->chr = (c & 0x0f);
- } else if ((c & 0xf8) == 0xf0) {
- utf8->size = utf8->state = 3;
- utf8->chr = (c & 0x07);
- } else if ((c & 0xfc) == 0xf8) {
- utf8->size = utf8->state = 4;
- utf8->chr = (c & 0x03);
- } else if ((c & 0xfe) == 0xfc) {
- utf8->size = utf8->state = 5;
- utf8->chr = (c & 0x01);
- } else {
- return UCSINVALID;
- }
- return UCSINCOMPLETE;
- case 1:
- case 2:
- case 3:
- case 4:
- case 5:
- if ((c & 0xC0) != 0x80) {
- utf8->state = 0;
- return UCSTRUNCATED; /* caller will then give us the
- * same byte again */
- }
- utf8->chr = (utf8->chr << 6) | (c & 0x3f);
- if (--utf8->state)
- return UCSINCOMPLETE;
-
- unsigned long t = utf8->chr;
-
- /* Is somebody trying to be evil! */
- if (t < 0x80 ||
- (t < 0x800 && utf8->size >= 2) ||
- (t < 0x10000 && utf8->size >= 3) ||
- (t < 0x200000 && utf8->size >= 4) ||
- (t < 0x4000000 && utf8->size >= 5))
- return UCSINVALID;
-
- /* Unicode line separator and paragraph separator are CR-LF */
- if (t == 0x2028 || t == 0x2029)
- return 0x85;
-
- /* High controls are probably a Baaad idea too. */
- if (t < 0xA0)
- return 0xFFFD;
-
- /* The UTF-16 surrogates are not nice either. */
- /* The standard give the option of decoding these:
- * I don't want to! */
- if (t >= 0xD800 && t < 0xE000)
- return UCSINVALID;
-
- /* ISO 10646 characters now limited to UTF-16 range. */
- if (t > 0x10FFFF)
- return UCSINVALID;
-
- /* This is currently a TagPhobic application.. */
- if (t >= 0xE0000 && t <= 0xE007F)
- return UCSINCOMPLETE;
-
- /* U+FEFF is best seen as a null. */
- if (t == 0xFEFF)
- return UCSINCOMPLETE;
- /* But U+FFFE is an error. */
- if (t == 0xFFFE || t == 0xFFFF)
- return UCSINVALID;
-
- return t;
- }
- } else if (term->sco_acs &&
- (c!='\033' && c!='\012' && c!='\015' && c!='\b')) {
- /* Are we in the nasty ACS mode? Note: no sco in utf mode. */
- if (term->sco_acs == 2)
- c |= 0x80;
-
- return c | CSET_SCOACS;
- } else {
- switch (term->cset_attr[term->cset]) {
- /*
- * Linedraw characters are different from 'ESC ( B'
- * only for a small range. For ones outside that
- * range, make sure we use the same font as well as
- * the same encoding.
- */
- case CSET_LINEDRW:
- if (term->ucsdata->unitab_ctrl[c] != 0xFF)
- return term->ucsdata->unitab_ctrl[c];
- else
- return c | CSET_LINEDRW;
- break;
-
- case CSET_GBCHR:
- /* If UK-ASCII, make the '#' a LineDraw Pound */
- if (c == '#')
- return '}' | CSET_LINEDRW;
- /* fall through */
-
- case CSET_ASCII:
- if (term->ucsdata->unitab_ctrl[c] != 0xFF)
- return term->ucsdata->unitab_ctrl[c];
- else
- return c | CSET_ASCII;
- break;
- case CSET_SCOACS:
- if (c >= ' ')
- return c | CSET_SCOACS;
- break;
- }
- }
- return c;
-}
-
-/*
- * Remove everything currently in `inbuf' and stick it up on the
- * in-memory display. There's a big state machine in here to
- * process escape sequences...
- */
-static void term_out(Terminal *term)
-{
- 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);
- 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;
- case ']': /* OSC: xterm escape sequences */
- /* 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;
- } else if (c < '@') {
- if (term->esc_query)
- term->esc_query = -1;
- else if (c == '?')
- term->esc_query = 1;
- else
- term->esc_query = c;
- term->termstate = SEEN_CSI;
- } else
-#define CLAMP(arg, lim) ((arg) = ((arg) > (lim)) ? (lim) : (arg))
- switch (ANSI(c, term->esc_query)) {
- case 'A': /* CUU: move up N lines */
- CLAMP(term->esc_args[0], term->rows);
- move(term, term->curs.x,
- term->curs.y - def(term->esc_args[0], 1), 1);
- seen_disp_event(term);
- break;
- case 'e': /* VPR: move down N lines */
- compatibility(ANSI);
- /* FALLTHROUGH */
- case 'B': /* CUD: Cursor down */
- CLAMP(term->esc_args[0], term->rows);
- move(term, term->curs.x,
- term->curs.y + def(term->esc_args[0], 1), 1);
- seen_disp_event(term);
- break;
- case 'b': /* REP: repeat previous grap */
- CLAMP(term->esc_args[0], term->rows * term->cols);
- if (term->last_graphic_char) {
- unsigned i;
- for (i = 0; i < term->esc_args[0]; i++)
- term_display_graphic_char(
- term, term->last_graphic_char);
- }
- break;
- case ANSI('c', '>'): /* DA: report xterm version */
- compatibility(OTHER);
- /* this reports xterm version 136 so that VIM can
- use the drag messages from the mouse reporting */
- if (term->ldisc)
- ldisc_send(term->ldisc, "\033[>0;136;0c", 11,
- false);
- break;
- case 'a': /* HPR: move right N cols */
- compatibility(ANSI);
- /* FALLTHROUGH */
- case 'C': /* CUF: Cursor right */
- CLAMP(term->esc_args[0], term->cols);
- move(term, term->curs.x + def(term->esc_args[0], 1),
- term->curs.y, 1);
- seen_disp_event(term);
- break;
- case 'D': /* CUB: move left N cols */
- CLAMP(term->esc_args[0], term->cols);
- move(term, term->curs.x - def(term->esc_args[0], 1),
- term->curs.y, 1);
- seen_disp_event(term);
- break;
- case 'E': /* CNL: move down N lines and CR */
- compatibility(ANSI);
- CLAMP(term->esc_args[0], term->rows);
- move(term, 0,
- term->curs.y + def(term->esc_args[0], 1), 1);
- seen_disp_event(term);
- break;
- case 'F': /* CPL: move up N lines and CR */
- compatibility(ANSI);
- CLAMP(term->esc_args[0], term->rows);
- move(term, 0,
- term->curs.y - def(term->esc_args[0], 1), 1);
- seen_disp_event(term);
- break;
- case 'G': /* CHA */
- case '`': /* HPA: set horizontal posn */
- compatibility(ANSI);
- CLAMP(term->esc_args[0], term->cols);
- move(term, def(term->esc_args[0], 1) - 1,
- term->curs.y, 0);
- seen_disp_event(term);
- break;
- case 'd': /* VPA: set vertical posn */
- compatibility(ANSI);
- CLAMP(term->esc_args[0], term->rows);
- move(term, term->curs.x,
- ((term->dec_om ? term->marg_t : 0) +
- def(term->esc_args[0], 1) - 1),
- (term->dec_om ? 2 : 0));
- seen_disp_event(term);
- break;
- case 'H': /* CUP */
- case 'f': /* HVP: set horz and vert posns at once */
- if (term->esc_nargs < 2)
- term->esc_args[1] = ARG_DEFAULT;
- CLAMP(term->esc_args[0], term->rows);
- CLAMP(term->esc_args[1], term->cols);
- move(term, def(term->esc_args[1], 1) - 1,
- ((term->dec_om ? term->marg_t : 0) +
- def(term->esc_args[0], 1) - 1),
- (term->dec_om ? 2 : 0));
- seen_disp_event(term);
- break;
- case 'J': { /* ED: erase screen or parts of it */
- unsigned int i = def(term->esc_args[0], 0);
- if (i == 3) {
- /* Erase Saved Lines (xterm)
- * This follows Thomas Dickey's xterm. */
- if (!term->no_remote_clearscroll)
- term_clrsb(term);
- } else {
- i++;
- if (i > 3)
- i = 0;
- erase_lots(term, false, !!(i & 2), !!(i & 1));
- }
- if (term->scroll_on_disp)
- term->disptop = 0;
- seen_disp_event(term);
- break;
- }
- case 'K': { /* EL: erase line or parts of it */
- unsigned int i = def(term->esc_args[0], 0) + 1;
- if (i > 3)
- i = 0;
- erase_lots(term, true, !!(i & 2), !!(i & 1));
- seen_disp_event(term);
- break;
- }
- case 'L': /* IL: insert lines */
- compatibility(VT102);
- CLAMP(term->esc_args[0], term->rows);
- if (term->curs.y <= term->marg_b)
- scroll(term, term->curs.y, term->marg_b,
- -def(term->esc_args[0], 1), false);
- seen_disp_event(term);
- break;
- case 'M': /* DL: delete lines */
- compatibility(VT102);
- CLAMP(term->esc_args[0], term->rows);
- if (term->curs.y <= term->marg_b)
- scroll(term, term->curs.y, term->marg_b,
- def(term->esc_args[0], 1),
- true);
- seen_disp_event(term);
- break;
- case '@': /* ICH: insert chars */
- /* XXX VTTEST says this is vt220, vt510 manual says vt102 */
- compatibility(VT102);
- CLAMP(term->esc_args[0], term->cols);
- insch(term, def(term->esc_args[0], 1));
- seen_disp_event(term);
- break;
- case 'P': /* DCH: delete chars */
- compatibility(VT102);
- CLAMP(term->esc_args[0], term->cols);
- insch(term, -def(term->esc_args[0], 1));
- seen_disp_event(term);
- break;
- case 'c': /* DA: terminal type query */
- compatibility(VT100);
- /* This is the response for a VT102 */
- if (term->ldisc)
- ldisc_send(term->ldisc, term->id_string,
- strlen(term->id_string), false);
- break;
- case 'n': /* DSR: cursor position query */
- if (term->ldisc) {
- if (term->esc_args[0] == 6) {
- char buf[32];
- sprintf(buf, "\033[%d;%dR", term->curs.y + 1,
- term->curs.x + 1);
- ldisc_send(term->ldisc, buf, strlen(buf),
- false);
- } else if (term->esc_args[0] == 5) {
- ldisc_send(term->ldisc, "\033[0n", 4, false);
- }
- }
- break;
- case 'h': /* SM: toggle modes to high */
- case ANSI_QUE('h'):
- compatibility(VT100);
- for (int i = 0; i < term->esc_nargs; i++)
- toggle_mode(term, term->esc_args[i],
- term->esc_query, true);
- break;
- case 'i': /* MC: Media copy */
- case ANSI_QUE('i'): {
- compatibility(VT100);
- char *printer;
- if (term->esc_nargs != 1) break;
- if (term->esc_args[0] == 5 &&
- (printer = conf_get_str(term->conf,
- CONF_printer))[0]) {
- term->printing = true;
- term->only_printing = !term->esc_query;
- term->print_state = 0;
- term_print_setup(term, printer);
- } else if (term->esc_args[0] == 4 &&
- term->printing) {
- term_print_finish(term);
- }
- break;
- }
- case 'l': /* RM: toggle modes to low */
- case ANSI_QUE('l'):
- compatibility(VT100);
- for (int i = 0; i < term->esc_nargs; i++)
- toggle_mode(term, term->esc_args[i],
- term->esc_query, false);
- break;
- case 'g': /* TBC: clear tabs */
- compatibility(VT100);
- if (term->esc_nargs == 1) {
- if (term->esc_args[0] == 0) {
- term->tabs[term->curs.x] = false;
- } else if (term->esc_args[0] == 3) {
- int i;
- for (i = 0; i < term->cols; i++)
- term->tabs[i] = false;
- }
- }
- break;
- case 'r': /* DECSTBM: set scroll margins */
- compatibility(VT100);
- if (term->esc_nargs <= 2) {
- int top, bot;
- CLAMP(term->esc_args[0], term->rows);
- CLAMP(term->esc_args[1], term->rows);
- top = def(term->esc_args[0], 1) - 1;
- bot = (term->esc_nargs <= 1
- || term->esc_args[1] == 0 ?
- term->rows :
- def(term->esc_args[1], term->rows)) - 1;
- if (bot >= term->rows)
- bot = term->rows - 1;
- /* VTTEST Bug 9 - if region is less than 2 lines
- * don't change region.
- */
- if (bot - top > 0) {
- term->marg_t = top;
- term->marg_b = bot;
- term->curs.x = 0;
- /*
- * I used to think the cursor should be
- * placed at the top of the newly marginned
- * area. Apparently not: VMS TPU falls over
- * if so.
- *
- * Well actually it should for
- * Origin mode - RDB
- */
- term->curs.y = (term->dec_om ?
- term->marg_t : 0);
- seen_disp_event(term);
- }
- }
- break;
- case 'm': /* SGR: set graphics rendition */
- /*
- * A VT100 without the AVO only had one
- * attribute, either underline or reverse
- * video depending on the cursor type, this
- * was selected by CSI 7m.
- *
- * case 2:
- * This is sometimes DIM, eg on the GIGI and
- * Linux
- * case 8:
- * This is sometimes INVIS various ANSI.
- * case 21:
- * This like 22 disables BOLD, DIM and INVIS
- *
- * The ANSI colours appear on any terminal
- * that has colour (obviously) but the
- * interaction between sgr0 and the colours
- * varies but is usually related to the
- * background colour erase item. The
- * interaction between colour attributes and
- * the mono ones is also very implementation
- * dependent.
- *
- * The 39 and 49 attributes are likely to be
- * unimplemented.
- */
- for (int i = 0; i < term->esc_nargs; i++)
- switch (def(term->esc_args[i], 0)) {
- case 0: /* restore defaults */
- term->curr_attr = term->default_attr;
- term->curr_truecolour =
- term->basic_erase_char.truecolour;
- break;
- case 1: /* enable bold */
- compatibility(VT100AVO);
- term->curr_attr |= ATTR_BOLD;
- break;
- case 2: /* enable dim */
- compatibility(OTHER);
- term->curr_attr |= ATTR_DIM;
- break;
- case 21: /* (enable double underline) */
- compatibility(OTHER);
- case 4: /* enable underline */
- compatibility(VT100AVO);
- term->curr_attr |= ATTR_UNDER;
- break;
- case 5: /* enable blink */
- compatibility(VT100AVO);
- term->curr_attr |= ATTR_BLINK;
- break;
- case 6: /* SCO light bkgrd */
- compatibility(SCOANSI);
- term->blink_is_real = false;
- term->curr_attr |= ATTR_BLINK;
- term_schedule_tblink(term);
- break;
- case 7: /* enable reverse video */
- term->curr_attr |= ATTR_REVERSE;
- break;
- case 9: /* enable strikethrough */
- term->curr_attr |= ATTR_STRIKE;
- break;
- case 10: /* SCO acs off */
- compatibility(SCOANSI);
- if (term->no_remote_charset) break;
- term->sco_acs = 0; break;
- case 11: /* SCO acs on */
- compatibility(SCOANSI);
- if (term->no_remote_charset) break;
- term->sco_acs = 1; break;
- case 12: /* SCO acs on, |0x80 */
- compatibility(SCOANSI);
- if (term->no_remote_charset) break;
- term->sco_acs = 2; break;
- case 22: /* disable bold and dim */
- compatibility2(OTHER, VT220);
- term->curr_attr &= ~(ATTR_BOLD | ATTR_DIM);
- break;
- case 24: /* disable underline */
- compatibility2(OTHER, VT220);
- term->curr_attr &= ~ATTR_UNDER;
- break;
- case 25: /* disable blink */
- compatibility2(OTHER, VT220);
- term->curr_attr &= ~ATTR_BLINK;
- break;
- case 27: /* disable reverse video */
- compatibility2(OTHER, VT220);
- term->curr_attr &= ~ATTR_REVERSE;
- break;
- case 29: /* disable strikethrough */
- term->curr_attr &= ~ATTR_STRIKE;
- break;
- case 30:
- case 31:
- case 32:
- case 33:
- case 34:
- case 35:
- case 36:
- case 37:
- /* foreground */
- term->curr_truecolour.fg.enabled = false;
- term->curr_attr &= ~ATTR_FGMASK;
- term->curr_attr |=
- (term->esc_args[i] - 30)<<ATTR_FGSHIFT;
- break;
- case 90:
- case 91:
- case 92:
- case 93:
- case 94:
- case 95:
- case 96:
- case 97:
- /* aixterm-style bright foreground */
- term->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)<<ATTR_BGSHIFT;
- break;
- case 100:
- case 101:
- case 102:
- case 103:
- case 104:
- case 105:
- case 106:
- case 107:
- /* aixterm-style bright background */
- term->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;
- }
- }
- 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; it<term->cols ; 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; it<nbc; it++) {
- int ipos = term->wcTo[it].index;
- for (int j = 0; j < term->wcTo[it].nchars; j++) {
- if (ipos != BIDI_CHAR_INDEX_NONE) {
- term->ltemp[opos] = ldata->chars[ipos];
- if (term->ltemp[opos].cc_next)
- term->ltemp[opos].cc_next -= opos - ipos;
-
- if (j > 0)
- term->ltemp[opos].chr = UCSWIDE;
- else if (term->wcTo[it].origwc != term->wcTo[it].wc)
- term->ltemp[opos].chr = term->wcTo[it].wc;
- } else {
- term->ltemp[opos] = term->basic_erase_char;
- term->ltemp[opos].chr =
- j > 0 ? UCSWIDE : term->wcTo[it].origwc;
- }
- opos++;
- }
- }
- assert(opos == term->cols);
- term_bidi_cache_store(term, scr_y, ldata->chars,
- term->ltemp, term->wcTo,
- term->cols, ldata->size, ldata->trusted);
-
- lchars = term->ltemp;
- } else {
- lchars = term->post_bidi_cache[scr_y].chars;
- }
- } else {
- lchars = NULL;
- }
-
- return lchars;
-}
-
-static void do_paint_draw(Terminal *term, termline *ldata, int x, int y,
- wchar_t *ch, int ccount,
- unsigned long attr, truecolour tc)
-{
- if (ch[0] == TRUST_SIGIL_CHAR) {
- assert(ldata->trusted);
- assert(ccount == 1);
- assert(attr & ATTR_WIDE);
- wchar_t tch[2];
- tch[0] = tch[1] = L' ';
- win_draw_text(term->win, x, y, tch, 2, term->basic_erase_char.attr,
- ldata->lattr, term->basic_erase_char.truecolour);
- win_draw_trust_sigil(term->win, x, y);
- } else {
- win_draw_text(term->win, x, y, ch, ccount, attr, ldata->lattr, tc);
- if (attr & (TATTR_ACTCURS | TATTR_PASCURS))
- win_draw_cursor(term->win, x, y, ch, ccount,
- attr, ldata->lattr, tc);
- }
-}
-
-/*
- * Given a context, update the window.
- */
-static void do_paint(Terminal *term)
-{
- int i, j, our_curs_y, our_curs_x;
- int rv, cursor;
- pos scrpos;
- wchar_t *ch;
- size_t chlen;
- termchar *newline;
-
- chlen = 1024;
- ch = snewn(chlen, wchar_t);
-
- newline = snewn(term->cols, termchar);
-
- rv = (!term->rvideo ^ !term->in_vbell ? ATTR_REVERSE : 0);
-
- /* Depends on:
- * screen array, disptop, scrtop,
- * selection, rv,
- * blinkpc, blink_is_real, tblinker,
- * curs.y, curs.x, cblinker, blink_cur, cursor_on, has_focus, wrapnext
- */
-
- /* Has the cursor position or type changed ? */
- if (term->cursor_on) {
- if (term->has_focus) {
- if (term->cblinker || !term->blink_cur)
- cursor = TATTR_ACTCURS;
- else
- cursor = 0;
- } else
- cursor = TATTR_PASCURS;
- if (term->wrapnext)
- cursor |= TATTR_RIGHTCURS;
- } else
- cursor = 0;
- our_curs_y = term->curs.y - term->disptop;
- {
- /*
- * Adjust the cursor position:
- * - for bidi
- * - in the case where it's resting on the right-hand half
- * of a CJK wide character. xterm's behaviour here,
- * which seems adequate to me, is to display the cursor
- * covering the _whole_ character, exactly as if it were
- * one space to the left.
- */
- termline *ldata = lineptr(term->curs.y);
- termchar *lchars;
-
- our_curs_x = term->curs.x;
-
- if ( (lchars = term_bidi_line(term, ldata, our_curs_y)) != NULL) {
- our_curs_x = term->post_bidi_cache[our_curs_y].forward[our_curs_x];
- } else
- lchars = ldata->chars;
-
- if (our_curs_x > 0 &&
- lchars[our_curs_x].chr == UCSWIDE)
- our_curs_x--;
-
- unlineptr(ldata);
- }
-
- /*
- * If the cursor is not where it was last time we painted, and
- * its previous position is visible on screen, invalidate its
- * previous position.
- */
- if (term->dispcursy >= 0 &&
- (term->curstype != cursor ||
- term->dispcursy != our_curs_y ||
- term->dispcursx != our_curs_x)) {
- termchar *dispcurs = term->disptext[term->dispcursy]->chars +
- term->dispcursx;
-
- if (term->dispcursx > 0 && dispcurs->chr == UCSWIDE)
- dispcurs[-1].attr |= ATTR_INVALID;
- if (term->dispcursx < term->cols-1 && dispcurs[1].chr == UCSWIDE)
- dispcurs[1].attr |= ATTR_INVALID;
- dispcurs->attr |= ATTR_INVALID;
-
- term->curstype = 0;
- }
- term->dispcursx = term->dispcursy = -1;
-
- /* The normal screen data */
- for (i = 0; i < term->rows; i++) {
- termline *ldata;
- termchar *lchars;
- bool dirty_line, dirty_run, selected;
- unsigned long attr = 0, cset = 0;
- int start = 0;
- int ccount = 0;
- bool last_run_dirty = false;
- int laststart;
- bool dirtyrect;
- int *backward;
- truecolour tc;
-
- scrpos.y = i + term->disptop;
- ldata = lineptr(scrpos.y);
-
- /* Do Arabic shaping and bidi. */
- lchars = term_bidi_line(term, ldata, i);
- if (lchars) {
- backward = term->post_bidi_cache[i].backward;
- } else {
- lchars = ldata->chars;
- backward = NULL;
- }
-
- /*
- * First loop: work along the line deciding what we want
- * each character cell to look like.
- */
- for (j = 0; j < term->cols; j++) {
- unsigned long tattr, tchar;
- termchar *d = lchars + j;
- scrpos.x = backward ? backward[j] : j;
-
- tchar = d->chr;
- tattr = d->attr;
-
- if (!term->ansi_colour)
- tattr = (tattr & ~(ATTR_FGMASK | ATTR_BGMASK)) |
- ATTR_DEFFG | ATTR_DEFBG;
-
- if (!term->xterm_256_colour) {
- int colour;
- colour = (tattr & ATTR_FGMASK) >> ATTR_FGSHIFT;
- if (colour >= 16 && colour < 256)
- tattr = (tattr &~ ATTR_FGMASK) | ATTR_DEFFG;
- colour = (tattr & ATTR_BGMASK) >> ATTR_BGSHIFT;
- if (colour >= 16 && colour < 256)
- tattr = (tattr &~ ATTR_BGMASK) | ATTR_DEFBG;
- }
-
- if (term->true_colour) {
- tc = d->truecolour;
- } else {
- tc.fg = tc.bg = optionalrgb_none;
- }
-
- switch (tchar & CSET_MASK) {
- case CSET_ASCII:
- tchar = term->ucsdata->unitab_line[tchar & 0xFF];
- break;
- case CSET_LINEDRW:
- tchar = term->ucsdata->unitab_xterm[tchar & 0xFF];
- break;
- case CSET_SCOACS:
- tchar = term->ucsdata->unitab_scoacs[tchar&0xFF];
- break;
- }
- if (j < term->cols-1 && d[1].chr == UCSWIDE)
- tattr |= ATTR_WIDE;
-
- /* Video reversing things */
- if (term->selstate == DRAGGING || term->selstate == SELECTED) {
- if (term->seltype == LEXICOGRAPHIC)
- selected = (posle(term->selstart, scrpos) &&
- poslt(scrpos, term->selend));
- else
- selected = (posPle(term->selstart, scrpos) &&
- posPle_left(scrpos, term->selend));
- } else
- selected = false;
- tattr = (tattr ^ rv
- ^ (selected ? ATTR_REVERSE : 0));
-
- /* 'Real' blinking ? */
- if (term->blink_is_real && (tattr & ATTR_BLINK)) {
- if (term->has_focus && term->tblinker) {
- tchar = term->ucsdata->unitab_line[(unsigned char)' '];
- }
- tattr &= ~ATTR_BLINK;
- }
-
- /*
- * Check the font we'll _probably_ be using to see if
- * the character is wide when we don't want it to be.
- */
- if (tchar != term->disptext[i]->chars[j].chr ||
- tattr != (term->disptext[i]->chars[j].attr &~
- (ATTR_NARROW | DATTR_MASK))) {
- if ((tattr & ATTR_WIDE) == 0 &&
- win_char_width(term->win, tchar) == 2)
- tattr |= ATTR_NARROW;
- } else if (term->disptext[i]->chars[j].attr & ATTR_NARROW)
- tattr |= ATTR_NARROW;
-
- if (i == our_curs_y && j == our_curs_x) {
- tattr |= cursor;
- term->curstype = cursor;
- term->dispcursx = j;
- term->dispcursy = i;
- }
-
- /* FULL-TERMCHAR */
- newline[j].attr = tattr;
- newline[j].chr = tchar;
- newline[j].truecolour = tc;
- /* Combining characters are still read from lchars */
- newline[j].cc_next = 0;
- }
-
- /*
- * Now loop over the line again, noting where things have
- * changed.
- *
- * During this loop, we keep track of where we last saw
- * DATTR_STARTRUN. Any mismatch automatically invalidates
- * _all_ of the containing run that was last printed: that
- * is, any rectangle that was drawn in one go in the
- * previous update should be either left completely alone
- * or overwritten in its entirety. This, along with the
- * expectation that front ends clip all text runs to their
- * bounding rectangle, should solve any possible problems
- * with fonts that overflow their character cells.
- */
- laststart = 0;
- dirtyrect = false;
- for (j = 0; j < term->cols; j++) {
- if (term->disptext[i]->chars[j].attr & DATTR_STARTRUN) {
- laststart = j;
- dirtyrect = false;
- }
-
- if (term->disptext[i]->chars[j].chr != newline[j].chr ||
- (term->disptext[i]->chars[j].attr &~ DATTR_MASK)
- != newline[j].attr) {
- int k;
-
- if (!dirtyrect) {
- for (k = laststart; k < j; k++)
- term->disptext[i]->chars[k].attr |= ATTR_INVALID;
-
- dirtyrect = true;
- }
- }
-
- if (dirtyrect)
- term->disptext[i]->chars[j].attr |= ATTR_INVALID;
- }
-
- /*
- * Finally, loop once more and actually do the drawing.
- */
- dirty_run = dirty_line = (ldata->lattr !=
- term->disptext[i]->lattr);
- term->disptext[i]->lattr = ldata->lattr;
-
- tc = term->erase_char.truecolour;
- for (j = 0; j < term->cols; j++) {
- unsigned long tattr, tchar;
- bool break_run, do_copy;
- termchar *d = lchars + j;
-
- tattr = newline[j].attr;
- tchar = newline[j].chr;
-
- if ((term->disptext[i]->chars[j].attr ^ tattr) & ATTR_WIDE)
- dirty_line = true;
-
- break_run = ((tattr ^ attr) & term->attr_mask) != 0;
-
- if (!truecolour_equal(newline[j].truecolour, tc))
- break_run = true;
-
-#ifdef USES_VTLINE_HACK
- /* Special hack for VT100 Linedraw glyphs */
- if ((tchar >= 0x23BA && tchar <= 0x23BD) ||
- (j > 0 && (newline[j-1].chr >= 0x23BA &&
- newline[j-1].chr <= 0x23BD)))
- break_run = true;
-#endif
-
- /*
- * Separate out sequences of characters that have the
- * same CSET, if that CSET is a magic one.
- */
- if (CSET_OF(tchar) != cset)
- break_run = true;
-
- /*
- * Break on both sides of any combined-character cell.
- */
- if (d->cc_next != 0 ||
- (j > 0 && d[-1].cc_next != 0))
- break_run = true;
-
- /*
- * Break on both sides of a trust sigil.
- */
- if (d->chr == TRUST_SIGIL_CHAR ||
- (j >= 2 && d[-1].chr == UCSWIDE &&
- d[-2].chr == TRUST_SIGIL_CHAR))
- break_run = true;
-
- if (!term->ucsdata->dbcs_screenfont && !dirty_line) {
- if (term->disptext[i]->chars[j].chr == tchar &&
- (term->disptext[i]->chars[j].attr &~ DATTR_MASK) == tattr)
- break_run = true;
- else if (!dirty_run && ccount == 1)
- break_run = true;
- }
-
- if (break_run) {
- if ((dirty_run || last_run_dirty) && ccount > 0)
- do_paint_draw(term, ldata, start, i, ch, ccount, attr, tc);
- start = j;
- ccount = 0;
- attr = tattr;
- tc = newline[j].truecolour;
- cset = CSET_OF(tchar);
- if (term->ucsdata->dbcs_screenfont)
- last_run_dirty = dirty_run;
- dirty_run = dirty_line;
- }
-
- do_copy = false;
- if (!termchars_equal_override(&term->disptext[i]->chars[j],
- d, tchar, tattr)) {
- do_copy = true;
- dirty_run = true;
- }
-
- sgrowarrayn(ch, chlen, ccount, 2);
-
-#ifdef PLATFORM_IS_UTF16
- if (tchar > 0x10000 && tchar < 0x110000) {
- ch[ccount++] = (wchar_t) HIGH_SURROGATE_OF(tchar);
- ch[ccount++] = (wchar_t) LOW_SURROGATE_OF(tchar);
- } else
-#endif /* PLATFORM_IS_UTF16 */
- ch[ccount++] = (wchar_t) tchar;
-
- if (d->cc_next) {
- termchar *dd = d;
-
- while (dd->cc_next) {
- unsigned long schar;
-
- dd += dd->cc_next;
-
- schar = dd->chr;
- switch (schar & CSET_MASK) {
- case CSET_ASCII:
- schar = term->ucsdata->unitab_line[schar & 0xFF];
- break;
- case CSET_LINEDRW:
- schar = term->ucsdata->unitab_xterm[schar & 0xFF];
- break;
- case CSET_SCOACS:
- schar = term->ucsdata->unitab_scoacs[schar&0xFF];
- break;
- }
-
- sgrowarrayn(ch, chlen, ccount, 2);
-
-#ifdef PLATFORM_IS_UTF16
- if (schar > 0x10000 && schar < 0x110000) {
- ch[ccount++] = (wchar_t) HIGH_SURROGATE_OF(schar);
- ch[ccount++] = (wchar_t) LOW_SURROGATE_OF(schar);
- } else
-#endif /* PLATFORM_IS_UTF16 */
- ch[ccount++] = (wchar_t) schar;
- }
-
- attr |= TATTR_COMBINING;
- }
-
- if (do_copy) {
- copy_termchar(term->disptext[i], j, d);
- term->disptext[i]->chars[j].chr = tchar;
- term->disptext[i]->chars[j].attr = tattr;
- term->disptext[i]->chars[j].truecolour = tc;
- if (start == j)
- term->disptext[i]->chars[j].attr |= DATTR_STARTRUN;
- }
-
- /* If it's a wide char step along to the next one. */
- if (tattr & ATTR_WIDE) {
- if (++j < term->cols) {
- d++;
- /*
- * By construction above, the cursor should not
- * be on the right-hand half of this character.
- * Ever.
- */
- assert(!(i == our_curs_y && j == our_curs_x));
- if (!termchars_equal(&term->disptext[i]->chars[j], d))
- dirty_run = true;
- copy_termchar(term->disptext[i], j, d);
- }
- }
- }
- if (dirty_run && ccount > 0)
- do_paint_draw(term, ldata, start, i, ch, ccount, attr, tc);
-
- unlineptr(ldata);
- }
-
- sfree(newline);
- sfree(ch);
-}
-
-/*
- * Invalidate the whole screen so it will be repainted in full.
- */
-void term_invalidate(Terminal *term)
-{
- int i, j;
-
- for (i = 0; i < term->rows; i++)
- for (j = 0; j < term->cols; j++)
- term->disptext[i]->chars[j].attr |= ATTR_INVALID;
-
- term_schedule_update(term);
-}
-
-/*
- * Paint the window in response to a WM_PAINT message.
- */
-void term_paint(Terminal *term,
- int left, int top, int right, int bottom, bool immediately)
-{
- int i, j;
- if (left < 0) left = 0;
- if (top < 0) top = 0;
- if (right >= term->cols) right = term->cols-1;
- if (bottom >= term->rows) bottom = term->rows-1;
-
- for (i = top; i <= bottom && i < term->rows; i++) {
- if ((term->disptext[i]->lattr & LATTR_MODE) == LATTR_NORM)
- for (j = left; j <= right && j < term->cols; j++)
- term->disptext[i]->chars[j].attr |= ATTR_INVALID;
- else
- for (j = left / 2; j <= right / 2 + 1 && j < term->cols; j++)
- term->disptext[i]->chars[j].attr |= ATTR_INVALID;
- }
-
- if (immediately) {
- do_paint(term);
- } else {
- term_schedule_update(term);
- }
-}
-
-/*
- * Attempt to scroll the scrollback. The second parameter gives the
- * position we want to scroll to; the first is +1 to denote that
- * this position is relative to the beginning of the scrollback, -1
- * to denote it is relative to the end, and 0 to denote that it is
- * relative to the current position.
- */
-void term_scroll(Terminal *term, int rel, int where)
-{
- int sbtop = -sblines(term);
-
- term->disptop = (rel < 0 ? 0 : rel > 0 ? sbtop : term->disptop) + where;
- if (term->disptop < sbtop)
- term->disptop = sbtop;
- if (term->disptop > 0)
- term->disptop = 0;
- term->win_scrollbar_update_pending = true;
- term_schedule_update(term);
-}
-
-/*
- * Scroll the scrollback to centre it on the beginning or end of the
- * current selection, if any.
- */
-void term_scroll_to_selection(Terminal *term, int which_end)
-{
- pos target;
- int y;
- int sbtop = -sblines(term);
-
- if (term->selstate != SELECTED)
- return;
- if (which_end)
- target = term->selend;
- else
- target = term->selstart;
-
- y = target.y - term->rows/2;
- if (y < sbtop)
- y = sbtop;
- else if (y > 0)
- y = 0;
- term_scroll(term, -1, y);
-}
-
-/*
- * Helper routine for clipme(): growing buffer.
- */
-typedef struct {
- size_t bufsize; /* amount of allocated space in textbuf/attrbuf */
- size_t bufpos; /* amount of actual data */
- wchar_t *textbuf; /* buffer for copied text */
- wchar_t *textptr; /* = textbuf + bufpos (current insertion point) */
- int *attrbuf; /* buffer for copied attributes */
- int *attrptr; /* = attrbuf + bufpos */
- truecolour *tcbuf; /* buffer for copied colours */
- truecolour *tcptr; /* = tcbuf + bufpos */
-} clip_workbuf;
-
-static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr, truecolour tc)
-{
- if (b->bufpos >= b->bufsize) {
- sgrowarray(b->textbuf, b->bufsize, b->bufpos);
- b->textptr = b->textbuf + b->bufpos;
- b->attrbuf = sresize(b->attrbuf, b->bufsize, int);
- b->attrptr = b->attrbuf + b->bufpos;
- b->tcbuf = sresize(b->tcbuf, b->bufsize, truecolour);
- b->tcptr = b->tcbuf + b->bufpos;
- }
- *b->textptr++ = chr;
- *b->attrptr++ = attr;
- *b->tcptr++ = tc;
- b->bufpos++;
-}
-
-static void clipme(Terminal *term, pos top, pos bottom, bool rect, bool desel,
- const int *clipboards, int n_clipboards)
-{
- clip_workbuf buf;
- int old_top_x;
- int attr;
- truecolour tc;
-
- buf.bufsize = 5120;
- buf.bufpos = 0;
- buf.textptr = buf.textbuf = snewn(buf.bufsize, wchar_t);
- buf.attrptr = buf.attrbuf = snewn(buf.bufsize, int);
- buf.tcptr = buf.tcbuf = snewn(buf.bufsize, truecolour);
-
- old_top_x = top.x; /* needed for rect==1 */
-
- while (poslt(top, bottom)) {
- bool nl = false;
- termline *ldata = lineptr(top.y);
- pos nlpos;
-
- /*
- * nlpos will point at the maximum position on this line we
- * should copy up to. So we start it at the end of the
- * line...
- */
- nlpos.y = top.y;
- nlpos.x = term->cols;
-
- /*
- * ... move it backwards if there's unused space at the end
- * of the line (and also set `nl' if this is the case,
- * because in normal selection mode this means we need a
- * newline at the end)...
- */
- if (!(ldata->lattr & LATTR_WRAPPED)) {
- while (nlpos.x &&
- IS_SPACE_CHR(ldata->chars[nlpos.x - 1].chr) &&
- !ldata->chars[nlpos.x - 1].cc_next &&
- poslt(top, nlpos))
- decpos(nlpos);
- if (poslt(nlpos, bottom))
- nl = true;
- } else {
- if (ldata->trusted) {
- /* A wrapped line with a trust sigil on it terminates
- * a few characters earlier. */
- nlpos.x = (nlpos.x < TRUST_SIGIL_WIDTH ? 0 :
- nlpos.x - TRUST_SIGIL_WIDTH);
- }
- if (ldata->lattr & LATTR_WRAPPED2) {
- /* Ignore the last char on the line in a WRAPPED2 line. */
- decpos(nlpos);
- }
- }
-
- /*
- * ... and then clip it to the terminal x coordinate if
- * we're doing rectangular selection. (In this case we
- * still did the above, so that copying e.g. the right-hand
- * column from a table doesn't fill with spaces on the
- * right.)
- */
- if (rect) {
- if (nlpos.x > bottom.x)
- nlpos.x = bottom.x;
- nl = (top.y < bottom.y);
- }
-
- while (poslt(top, bottom) && poslt(top, nlpos)) {
-#if 0
- char cbuf[16], *p;
- sprintf(cbuf, "<U+%04x>", (ldata[top.x] & 0xFFFF));
-#else
- wchar_t cbuf[16], *p;
- int c;
- int x = top.x;
-
- if (ldata->chars[x].chr == UCSWIDE) {
- top.x++;
- continue;
- }
-
- while (1) {
- int uc = ldata->chars[x].chr;
- attr = ldata->chars[x].attr;
- tc = ldata->chars[x].truecolour;
-
- switch (uc & CSET_MASK) {
- case CSET_LINEDRW:
- if (!term->rawcnp) {
- uc = term->ucsdata->unitab_xterm[uc & 0xFF];
- break;
- }
- case CSET_ASCII:
- uc = term->ucsdata->unitab_line[uc & 0xFF];
- break;
- case CSET_SCOACS:
- uc = term->ucsdata->unitab_scoacs[uc&0xFF];
- break;
- }
- switch (uc & CSET_MASK) {
- case CSET_ACP:
- uc = term->ucsdata->unitab_font[uc & 0xFF];
- break;
- case CSET_OEMCP:
- uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
- break;
- }
-
- c = (uc & ~CSET_MASK);
-#ifdef PLATFORM_IS_UTF16
- if (uc > 0x10000 && uc < 0x110000) {
- cbuf[0] = 0xD800 | ((uc - 0x10000) >> 10);
- cbuf[1] = 0xDC00 | ((uc - 0x10000) & 0x3FF);
- cbuf[2] = 0;
- } else
-#endif
- {
- cbuf[0] = uc;
- cbuf[1] = 0;
- }
-
- if (DIRECT_FONT(uc)) {
- if (c >= ' ' && c != 0x7F) {
- char buf[4];
- WCHAR wbuf[4];
- int rv;
- if (is_dbcs_leadbyte(term->ucsdata->font_codepage, (BYTE) c)) {
- buf[0] = c;
- buf[1] = (char) (0xFF & ldata->chars[top.x + 1].chr);
- rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 2, wbuf, 4);
- top.x++;
- } else {
- buf[0] = c;
- rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 1, wbuf, 4);
- }
-
- if (rv > 0) {
- memcpy(cbuf, wbuf, rv * sizeof(wchar_t));
- cbuf[rv] = 0;
- }
- }
- }
-#endif
-
- for (p = cbuf; *p; p++)
- clip_addchar(&buf, *p, attr, tc);
-
- if (ldata->chars[x].cc_next)
- x += ldata->chars[x].cc_next;
- else
- break;
- }
- top.x++;
- }
- if (nl) {
- int i;
- for (i = 0; i < sel_nl_sz; i++)
- clip_addchar(&buf, sel_nl[i], 0, term->basic_erase_char.truecolour);
- }
- top.y++;
- top.x = rect ? old_top_x : 0;
-
- unlineptr(ldata);
- }
-#if SELECTION_NUL_TERMINATED
- clip_addchar(&buf, 0, 0, term->basic_erase_char.truecolour);
-#endif
- /* Finally, transfer all that to the clipboard(s). */
- {
- int i;
- bool clip_local = false;
- for (i = 0; i < n_clipboards; i++) {
- if (clipboards[i] == CLIP_LOCAL) {
- clip_local = true;
- } else if (clipboards[i] != CLIP_NULL) {
- win_clip_write(
- term->win, clipboards[i], buf.textbuf, buf.attrbuf,
- buf.tcbuf, buf.bufpos, desel);
- }
- }
- if (clip_local) {
- sfree(term->last_selected_text);
- sfree(term->last_selected_attr);
- sfree(term->last_selected_tc);
- term->last_selected_text = buf.textbuf;
- term->last_selected_attr = buf.attrbuf;
- term->last_selected_tc = buf.tcbuf;
- term->last_selected_len = buf.bufpos;
- } else {
- sfree(buf.textbuf);
- sfree(buf.attrbuf);
- sfree(buf.tcbuf);
- }
- }
-}
-
-void term_copyall(Terminal *term, const int *clipboards, int n_clipboards)
-{
- pos top;
- pos bottom;
- tree234 *screen = term->screen;
- top.y = -sblines(term);
- top.x = 0;
- bottom.y = find_last_nonempty_line(term, screen);
- bottom.x = term->cols;
- clipme(term, top, bottom, false, true, clipboards, n_clipboards);
-}
-
-static void paste_from_clip_local(void *vterm)
-{
- Terminal *term = (Terminal *)vterm;
- term_do_paste(term, term->last_selected_text, term->last_selected_len);
-}
-
-void term_request_copy(Terminal *term, const int *clipboards, int n_clipboards)
-{
- int i;
- for (i = 0; i < n_clipboards; i++) {
- assert(clipboards[i] != CLIP_LOCAL);
- if (clipboards[i] != CLIP_NULL) {
- win_clip_write(term->win, clipboards[i],
- term->last_selected_text, term->last_selected_attr,
- term->last_selected_tc, term->last_selected_len,
- false);
- }
- }
-}
-
-void term_request_paste(Terminal *term, int clipboard)
-{
- switch (clipboard) {
- case CLIP_NULL:
- /* Do nothing: CLIP_NULL never has data in it. */
- break;
- case CLIP_LOCAL:
- queue_toplevel_callback(paste_from_clip_local, term);
- break;
- default:
- win_clip_request_paste(term->win, clipboard);
- break;
- }
-}
-
-/*
- * The wordness array is mainly for deciding the disposition of the
- * US-ASCII characters.
- */
-static int wordtype(Terminal *term, int uc)
-{
- struct ucsword {
- int start, end, ctype;
- };
- static const struct ucsword ucs_words[] = {
- {
- 128, 160, 0}, {
- 161, 191, 1}, {
- 215, 215, 1}, {
- 247, 247, 1}, {
- 0x037e, 0x037e, 1}, /* Greek question mark */
- {
- 0x0387, 0x0387, 1}, /* Greek ano teleia */
- {
- 0x055a, 0x055f, 1}, /* Armenian punctuation */
- {
- 0x0589, 0x0589, 1}, /* Armenian full stop */
- {
- 0x0700, 0x070d, 1}, /* Syriac punctuation */
- {
- 0x104a, 0x104f, 1}, /* Myanmar punctuation */
- {
- 0x10fb, 0x10fb, 1}, /* Georgian punctuation */
- {
- 0x1361, 0x1368, 1}, /* Ethiopic punctuation */
- {
- 0x166d, 0x166e, 1}, /* Canadian Syl. punctuation */
- {
- 0x17d4, 0x17dc, 1}, /* Khmer punctuation */
- {
- 0x1800, 0x180a, 1}, /* Mongolian punctuation */
- {
- 0x2000, 0x200a, 0}, /* Various spaces */
- {
- 0x2070, 0x207f, 2}, /* superscript */
- {
- 0x2080, 0x208f, 2}, /* subscript */
- {
- 0x200b, 0x27ff, 1}, /* punctuation and symbols */
- {
- 0x3000, 0x3000, 0}, /* ideographic space */
- {
- 0x3001, 0x3020, 1}, /* ideographic punctuation */
- {
- 0x303f, 0x309f, 3}, /* Hiragana */
- {
- 0x30a0, 0x30ff, 3}, /* Katakana */
- {
- 0x3300, 0x9fff, 3}, /* CJK Ideographs */
- {
- 0xac00, 0xd7a3, 3}, /* Hangul Syllables */
- {
- 0xf900, 0xfaff, 3}, /* CJK Ideographs */
- {
- 0xfe30, 0xfe6b, 1}, /* punctuation forms */
- {
- 0xff00, 0xff0f, 1}, /* half/fullwidth ASCII */
- {
- 0xff1a, 0xff20, 1}, /* half/fullwidth ASCII */
- {
- 0xff3b, 0xff40, 1}, /* half/fullwidth ASCII */
- {
- 0xff5b, 0xff64, 1}, /* half/fullwidth ASCII */
- {
- 0xfff0, 0xffff, 0}, /* half/fullwidth ASCII */
- {
- 0, 0, 0}
- };
- const struct ucsword *wptr;
-
- switch (uc & CSET_MASK) {
- case CSET_LINEDRW:
- uc = term->ucsdata->unitab_xterm[uc & 0xFF];
- break;
- case CSET_ASCII:
- uc = term->ucsdata->unitab_line[uc & 0xFF];
- break;
- case CSET_SCOACS:
- uc = term->ucsdata->unitab_scoacs[uc&0xFF];
- break;
- }
- switch (uc & CSET_MASK) {
- case CSET_ACP:
- uc = term->ucsdata->unitab_font[uc & 0xFF];
- break;
- case CSET_OEMCP:
- uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
- break;
- }
-
- /* For DBCS fonts I can't do anything useful. Even this will sometimes
- * fail as there's such a thing as a double width space. :-(
- */
- if (term->ucsdata->dbcs_screenfont &&
- term->ucsdata->font_codepage == term->ucsdata->line_codepage)
- return (uc != ' ');
-
- if (uc < 0x80)
- return term->wordness[uc];
-
- for (wptr = ucs_words; wptr->start; wptr++) {
- if (uc >= wptr->start && uc <= wptr->end)
- return wptr->ctype;
- }
-
- return 2;
-}
-
-static int line_cols(Terminal *term, termline *ldata)
-{
- int cols = term->cols;
- if (ldata->trusted) {
- cols -= TRUST_SIGIL_WIDTH;
- }
- if (ldata->lattr & LATTR_WRAPPED2)
- cols--;
- if (cols < 0)
- cols = 0;
- return cols;
-}
-
-/*
- * Spread the selection outwards according to the selection mode.
- */
-static pos sel_spread_half(Terminal *term, pos p, int dir)
-{
- termline *ldata;
- short wvalue;
- int topy = -sblines(term);
-
- ldata = lineptr(p.y);
-
- switch (term->selmode) {
- case SM_CHAR:
- /*
- * In this mode, every character is a separate unit, except
- * for runs of spaces at the end of a non-wrapping line.
- */
- if (!(ldata->lattr & LATTR_WRAPPED)) {
- termchar *q = ldata->chars + line_cols(term, ldata);
- while (q > ldata->chars &&
- IS_SPACE_CHR(q[-1].chr) && !q[-1].cc_next)
- q--;
- if (q == ldata->chars + term->cols)
- q--;
- if (p.x >= q - ldata->chars)
- p.x = (dir == -1 ? q - ldata->chars : term->cols - 1);
- }
- break;
- case SM_WORD:
- /*
- * In this mode, the units are maximal runs of characters
- * whose `wordness' has the same value.
- */
- wvalue = wordtype(term, UCSGET(ldata->chars, p.x));
- if (dir == +1) {
- while (1) {
- int maxcols = line_cols(term, ldata);
- if (p.x < maxcols-1) {
- if (wordtype(term, UCSGET(ldata->chars, p.x+1)) == wvalue)
- p.x++;
- else
- break;
- } else {
- if (p.y+1 < term->rows &&
- (ldata->lattr & LATTR_WRAPPED)) {
- termline *ldata2;
- ldata2 = lineptr(p.y+1);
- if (wordtype(term, UCSGET(ldata2->chars, 0))
- == wvalue) {
- p.x = 0;
- p.y++;
- unlineptr(ldata);
- ldata = ldata2;
- } else {
- unlineptr(ldata2);
- break;
- }
- } else
- break;
- }
- }
- } else {
- while (1) {
- if (p.x > 0) {
- if (wordtype(term, UCSGET(ldata->chars, p.x-1)) == wvalue)
- p.x--;
- else
- break;
- } else {
- termline *ldata2;
- int maxcols;
- if (p.y <= topy)
- break;
- ldata2 = lineptr(p.y-1);
- maxcols = line_cols(term, ldata2);
- if (ldata2->lattr & LATTR_WRAPPED) {
- if (wordtype(term, UCSGET(ldata2->chars, maxcols-1))
- == wvalue) {
- p.x = maxcols-1;
- p.y--;
- unlineptr(ldata);
- ldata = ldata2;
- } else {
- unlineptr(ldata2);
- break;
- }
- } else
- break;
- }
- }
- }
- break;
- case SM_LINE:
- /*
- * In this mode, every line is a unit.
- */
- p.x = (dir == -1 ? 0 : term->cols - 1);
- break;
- }
-
- unlineptr(ldata);
- return p;
-}
-
-static void sel_spread(Terminal *term)
-{
- if (term->seltype == LEXICOGRAPHIC) {
- term->selstart = sel_spread_half(term, term->selstart, -1);
- decpos(term->selend);
- term->selend = sel_spread_half(term, term->selend, +1);
- incpos(term->selend);
- }
-}
-
-static void term_paste_callback(void *vterm)
-{
- Terminal *term = (Terminal *)vterm;
-
- if (term->paste_len == 0)
- return;
-
- while (term->paste_pos < term->paste_len) {
- int n = 0;
- while (n + term->paste_pos < term->paste_len) {
- if (term->paste_buffer[term->paste_pos + n++] == '\015')
- break;
- }
- if (term->ldisc) {
- strbuf *buf = term_input_data_from_unicode(
- term, term->paste_buffer + term->paste_pos, n);
- term_keyinput_internal(term, buf->s, buf->len, false);
- strbuf_free(buf);
- }
- term->paste_pos += n;
-
- if (term->paste_pos < term->paste_len) {
- queue_toplevel_callback(term_paste_callback, term);
- return;
- }
- }
- term_bracketed_paste_stop(term);
- sfree(term->paste_buffer);
- term->paste_buffer = NULL;
- term->paste_len = 0;
-}
-
-/*
- * Specialist string compare function. Returns true if the buffer of
- * alen wide characters starting at a has as a prefix the buffer of
- * blen characters starting at b.
- */
-static bool wstartswith(const wchar_t *a, size_t alen,
- const wchar_t *b, size_t blen)
-{
- return alen >= blen && !wcsncmp(a, b, blen);
-}
-
-void term_do_paste(Terminal *term, const wchar_t *data, int len)
-{
- const wchar_t *p;
- bool paste_controls = conf_get_bool(term->conf, CONF_paste_controls);
-
- /*
- * Pasting data into the terminal counts as a keyboard event (for
- * purposes of the 'Reset scrollback on keypress' config option),
- * unless the paste is zero-length.
- */
- if (len == 0)
- return;
- term_seen_key_event(term);
-
- if (term->paste_buffer)
- sfree(term->paste_buffer);
- term->paste_pos = term->paste_len = 0;
- term->paste_buffer = snewn(len + 12, wchar_t);
-
- if (term->bracketed_paste)
- term_bracketed_paste_start(term);
-
- p = data;
- while (p < data + len) {
- wchar_t wc = *p++;
-
- if (wc == sel_nl[0] &&
- wstartswith(p-1, data+len-(p-1), sel_nl, sel_nl_sz)) {
- /*
- * This is the (platform-dependent) sequence that the host
- * OS uses to represent newlines in clipboard data.
- * Normalise it to a press of CR.
- */
- p += sel_nl_sz - 1;
- wc = '\015';
- }
-
- if ((wc & ~(wint_t)0x9F) == 0) {
- /*
- * This is a control code, either in the range 0x00-0x1F
- * or 0x80-0x9F. We reject all of these in pastecontrols
- * mode, except for a small set of permitted ones.
- */
- if (!paste_controls) {
- /* In line with xterm 292, accepted control chars are:
- * CR, LF, tab, backspace. (And DEL, i.e. 0x7F, but
- * that's permitted by virtue of not matching the bit
- * mask that got us into this if statement, so we
- * don't have to permit it here. */
- static const unsigned mask =
- (1<<13) | (1<<10) | (1<<9) | (1<<8);
-
- if (wc > 15 || !((mask >> wc) & 1))
- continue;
- }
-
- if (wc == '\033' && term->bracketed_paste &&
- wstartswith(p-1, data+len-(p-1), L"\033[201~", 6)) {
- /*
- * Also, in bracketed-paste mode, reject the ESC
- * character that begins the end-of-paste sequence.
- */
- continue;
- }
- }
-
- term->paste_buffer[term->paste_len++] = wc;
- }
-
- /* Assume a small paste will be OK in one go. */
- if (term->paste_len < 256) {
- if (term->ldisc) {
- strbuf *buf = term_input_data_from_unicode(
- term, term->paste_buffer, term->paste_len);
- term_keyinput_internal(term, buf->s, buf->len, false);
- strbuf_free(buf);
- }
- if (term->paste_buffer)
- sfree(term->paste_buffer);
- term_bracketed_paste_stop(term);
- term->paste_buffer = NULL;
- term->paste_pos = term->paste_len = 0;
- }
-
- queue_toplevel_callback(term_paste_callback, term);
-}
-
-void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked,
- Mouse_Action a, int x, int y, bool shift, bool ctrl, bool alt)
-{
- pos selpoint;
- termline *ldata;
- bool raw_mouse = (term->xterm_mouse &&
- !term->no_mouse_rep &&
- !(term->mouse_override && shift));
- int default_seltype;
-
- if (y < 0) {
- y = 0;
- if (a == MA_DRAG && !raw_mouse)
- term_scroll(term, 0, -1);
- }
- if (y >= term->rows) {
- y = term->rows - 1;
- if (a == MA_DRAG && !raw_mouse)
- term_scroll(term, 0, +1);
- }
- if (x < 0) {
- if (y > 0 && !raw_mouse && term->seltype != RECTANGULAR) {
- /*
- * When we're using the mouse for normal raster-based
- * selection, dragging off the left edge of a terminal row
- * is treated the same as the right-hand end of the
- * previous row, in that it's considered to identify a
- * point _before_ the first character on row y.
- *
- * But if the mouse action is going to be used for
- * anything else - rectangular selection, or xterm mouse
- * tracking - then we disable this special treatment.
- */
- x = term->cols - 1;
- y--;
- } else
- x = 0;
- }
- if (x >= term->cols)
- x = term->cols - 1;
-
- selpoint.y = y + term->disptop;
- ldata = lineptr(selpoint.y);
-
- if ((ldata->lattr & LATTR_MODE) != LATTR_NORM)
- x /= 2;
-
- /*
- * Transform x through the bidi algorithm to find the _logical_
- * click point from the physical one.
- */
- if (term_bidi_line(term, ldata, y) != NULL) {
- x = term->post_bidi_cache[y].backward[x];
- }
-
- selpoint.x = x;
- unlineptr(ldata);
-
- /*
- * If we're in the middle of a selection operation, we ignore raw
- * mouse mode until it's done (we must have been not in raw mouse
- * mode when it started).
- * This makes use of Shift for selection reliable, and avoids the
- * host seeing mouse releases for which they never saw corresponding
- * presses.
- */
- if (raw_mouse &&
- (term->selstate != ABOUT_TO) && (term->selstate != DRAGGING)) {
- int encstate = 0, r, c;
- bool wheel;
- char abuf[32];
- int len = 0;
-
- if (term->ldisc) {
-
- switch (braw) {
- case MBT_LEFT:
- encstate = 0x00; /* left button down */
- wheel = false;
- break;
- case MBT_MIDDLE:
- encstate = 0x01;
- wheel = false;
- break;
- case MBT_RIGHT:
- encstate = 0x02;
- wheel = false;
- break;
- case MBT_WHEEL_UP:
- encstate = 0x40;
- wheel = true;
- break;
- case MBT_WHEEL_DOWN:
- encstate = 0x41;
- wheel = true;
- break;
- default:
- return;
- }
- if (wheel) {
- /* For mouse wheel buttons, we only ever expect to see
- * MA_CLICK actions, and we don't try to keep track of
- * the buttons being 'pressed' (since without matching
- * click/release pairs that's pointless). */
- if (a != MA_CLICK)
- return;
- } else switch (a) {
- case MA_DRAG:
- if (term->xterm_mouse == 1)
- return;
- encstate += 0x20;
- break;
- case MA_RELEASE:
- /* If multiple extensions are enabled, the xterm 1006 is used, so it's okay to check for only that */
- if (!term->xterm_extended_mouse)
- encstate = 0x03;
- term->mouse_is_down = 0;
- break;
- case MA_CLICK:
- if (term->mouse_is_down == braw)
- return;
- term->mouse_is_down = braw;
- break;
- default:
- return;
- }
- if (shift)
- encstate += 0x04;
- if (ctrl)
- encstate += 0x10;
- r = y + 1;
- c = x + 1;
-
- /* Check the extensions in decreasing order of preference. Encoding the release event above assumes that 1006 comes first. */
- if (term->xterm_extended_mouse) {
- len = sprintf(abuf, "\033[<%d;%d;%d%c", encstate, c, r, a == MA_RELEASE ? 'm' : 'M');
- } else if (term->urxvt_extended_mouse) {
- len = sprintf(abuf, "\033[%d;%d;%dM", encstate + 32, c, r);
- } else if (c <= 223 && r <= 223) {
- len = sprintf(abuf, "\033[M%c%c%c", encstate + 32, c + 32, r + 32);
- }
- if (len > 0)
- ldisc_send(term->ldisc, abuf, len, false);
- }
- return;
- }
-
- /*
- * Set the selection type (rectangular or normal) at the start
- * of a selection attempt, from the state of Alt.
- */
- if (!alt ^ !term->rect_select)
- default_seltype = RECTANGULAR;
- else
- default_seltype = LEXICOGRAPHIC;
-
- if (term->selstate == NO_SELECTION) {
- term->seltype = default_seltype;
- }
-
- if (bcooked == MBT_SELECT && a == MA_CLICK) {
- deselect(term);
- term->selstate = ABOUT_TO;
- term->seltype = default_seltype;
- term->selanchor = selpoint;
- term->selmode = SM_CHAR;
- } else if (bcooked == MBT_SELECT && (a == MA_2CLK || a == MA_3CLK)) {
- deselect(term);
- term->selmode = (a == MA_2CLK ? SM_WORD : SM_LINE);
- term->selstate = DRAGGING;
- term->selstart = term->selanchor = selpoint;
- term->selend = term->selstart;
- incpos(term->selend);
- sel_spread(term);
- } else if ((bcooked == MBT_SELECT && a == MA_DRAG) ||
- (bcooked == MBT_EXTEND && a != MA_RELEASE)) {
- if (a == MA_DRAG &&
- (term->selstate == NO_SELECTION || term->selstate == SELECTED)) {
- /*
- * This can happen if a front end has passed us a MA_DRAG
- * without a prior MA_CLICK. OS X GTK does so, for
- * example, if the initial button press was eaten by the
- * WM when it activated the window in the first place. The
- * nicest thing to do in this situation is to ignore
- * further drags, and wait for the user to click in the
- * window again properly if they want to select.
- */
- return;
- }
- if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint))
- return;
- if (bcooked == MBT_EXTEND && a != MA_DRAG &&
- term->selstate == SELECTED) {
- if (term->seltype == LEXICOGRAPHIC) {
- /*
- * For normal selection, we extend by moving
- * whichever end of the current selection is closer
- * to the mouse.
- */
- if (posdiff(selpoint, term->selstart) <
- posdiff(term->selend, term->selstart) / 2) {
- term->selanchor = term->selend;
- decpos(term->selanchor);
- } else {
- term->selanchor = term->selstart;
- }
- } else {
- /*
- * For rectangular selection, we have a choice of
- * _four_ places to put selanchor and selpoint: the
- * four corners of the selection.
- */
- if (2*selpoint.x < term->selstart.x + term->selend.x)
- term->selanchor.x = term->selend.x-1;
- else
- term->selanchor.x = term->selstart.x;
-
- if (2*selpoint.y < term->selstart.y + term->selend.y)
- term->selanchor.y = term->selend.y;
- else
- term->selanchor.y = term->selstart.y;
- }
- term->selstate = DRAGGING;
- }
- if (term->selstate != ABOUT_TO && term->selstate != DRAGGING)
- term->selanchor = selpoint;
- term->selstate = DRAGGING;
- if (term->seltype == LEXICOGRAPHIC) {
- /*
- * For normal selection, we set (selstart,selend) to
- * (selpoint,selanchor) in some order.
- */
- if (poslt(selpoint, term->selanchor)) {
- term->selstart = selpoint;
- term->selend = term->selanchor;
- incpos(term->selend);
- } else {
- term->selstart = term->selanchor;
- term->selend = selpoint;
- incpos(term->selend);
- }
- } else {
- /*
- * For rectangular selection, we may need to
- * interchange x and y coordinates (if the user has
- * dragged in the -x and +y directions, or vice versa).
- */
- term->selstart.x = min(term->selanchor.x, selpoint.x);
- term->selend.x = 1+max(term->selanchor.x, selpoint.x);
- term->selstart.y = min(term->selanchor.y, selpoint.y);
- term->selend.y = max(term->selanchor.y, selpoint.y);
- }
- sel_spread(term);
- } else if ((bcooked == MBT_SELECT || bcooked == MBT_EXTEND) &&
- a == MA_RELEASE) {
- if (term->selstate == DRAGGING) {
- /*
- * We've completed a selection. We now transfer the
- * data to the clipboard.
- */
- clipme(term, term->selstart, term->selend,
- (term->seltype == RECTANGULAR), false,
- term->mouse_select_clipboards,
- term->n_mouse_select_clipboards);
- term->selstate = SELECTED;
- } else
- term->selstate = NO_SELECTION;
- } else if (bcooked == MBT_PASTE
- && (a == MA_CLICK
-#if MULTICLICK_ONLY_EVENT
- || a == MA_2CLK || a == MA_3CLK
-#endif
- )) {
- term_request_paste(term, term->mouse_paste_clipboard);
- }
-
- /*
- * Since terminal output is suppressed during drag-selects, we
- * should make sure to write any pending output if one has just
- * finished.
- */
- if (term->selstate != DRAGGING)
- term_out(term);
- term_schedule_update(term);
-}
-
-int format_arrow_key(char *buf, Terminal *term, int xkey, bool ctrl)
-{
- 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
- /* 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);
- }
-
- return p - buf;
-}
-
-int format_function_key(char *buf, Terminal *term, int key_number,
- bool shift, bool ctrl)
-{
- char *p = buf;
-
- static const int key_number_to_tilde_code[] = {
- -1, /* no such key as F0 */
- 11, 12, 13, 14, 15, /*gap*/ 17, 18, 19, 20, 21, /*gap*/
- 23, 24, 25, 26, /*gap*/ 28, 29, /*gap*/ 31, 32, 33, 34,
- };
-
- assert(key_number > 0);
- assert(key_number < lenof(key_number_to_tilde_code));
-
- int index = (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;
-}
-
-int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key)
-{
- char *p = buf;
-
- int code;
- switch (key) {
- case SKK_HOME: code = 1; break;
- case SKK_INSERT: code = 2; break;
- case SKK_DELETE: code = 3; break;
- case SKK_END: code = 4; break;
- case SKK_PGUP: code = 5; break;
- case SKK_PGDN: code = 6; break;
- default: unreachable("bad small keypad key enum value");
- }
-
- /* Reorder edit keys to physical order */
- if (term->funky_type == FUNKY_VT400 && code <= 6)
- code = "\0\2\1\4\5\3\6"[code];
-
- if (term->vt52_mode && code > 0 && code <= 6) {
- p += sprintf(p, "\x1B%c", " HLMEIG"[code]);
- } else if (term->funky_type == FUNKY_SCO) {
- static const char codes[] = "HL.FIG";
- if (code == 3) {
- *p++ = '\x7F';
- } else {
- p += sprintf(p, "\x1B[%c", codes[code-1]);
- }
- } else if ((code == 1 || code == 4) && term->rxvt_homeend) {
- p += sprintf(p, code == 1 ? "\x1B[H" : "\x1BOw");
- } else {
- 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)
-{
- bufchain_add(&term->inbuf, data, len);
- term_added_data(term);
-
- /*
- * 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)
-{
- term->has_focus = has_focus;
- term_schedule_cblink(term);
-}
-
-/*
- * Provide "auto" settings for remote tty modes, suitable for an
- * application with a terminal window.
- */
-char *term_get_ttymode(Terminal *term, const char *mode)
-{
- const char *val = NULL;
- if (strcmp(mode, "ERASE") == 0) {
- val = term->bksp_is_delete ? "^?" : "^H";
- } else if (strcmp(mode, "IUTF8") == 0) {
- val = (term->ucsdata->line_codepage == CP_UTF8) ? "yes" : "no";
- }
- /* FIXME: perhaps we should set ONLCR based on lfhascr as well? */
- /* FIXME: or ECHO and friends based on local echo state? */
- return dupstr(val);
-}
-
-struct term_userpass_state {
- 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/terminal.h b/terminal.h
deleted file mode 100644
index c463f7a8..00000000
--- a/terminal.h
+++ /dev/null
@@ -1,529 +0,0 @@
-/*
- * 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;
- 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)
-
-#define OSC_STR_MAX 2048
- int osc_strlen;
- char osc_string[OSC_STR_MAX + 1];
- bool osc_w;
-
- char id_string[1024];
-
- unsigned char *tabs;
-
- enum {
- TOPLEVEL,
- SEEN_ESC,
- SEEN_CSI,
- SEEN_OSC,
- SEEN_OSC_W,
-
- DO_CTRLS,
-
- SEEN_OSC_P,
- OSC_STRING, OSC_MAYBE_ST,
- VT52_ESC,
- VT52_Y1,
- VT52_Y2,
- VT52_FG,
- VT52_BG
- } termstate;
-
- enum {
- NO_SELECTION, ABOUT_TO, DRAGGING, SELECTED
- } selstate;
- enum {
- LEXICOGRAPHIC, RECTANGULAR
- } seltype;
- enum {
- SM_CHAR, SM_WORD, SM_LINE
- } selmode;
- pos selstart, selend, selanchor;
-
- short wordness[256];
-
- /* Mask of attributes to pay attention to when painting. */
- int attr_mask;
-
- wchar_t *paste_buffer;
- int paste_len, paste_pos;
-
- Backend *backend;
-
- Ldisc *ldisc;
-
- TermWin *win;
-
- LogContext *logctx;
-
- struct unicode_data *ucsdata;
-
- unsigned long last_graphic_char;
-
- /*
- * We maintain a full copy of a Conf here, not merely a pointer
- * to it. That way, when we're passed a new one for
- * reconfiguration, we can check the differences and adjust the
- * _current_ setting of (e.g.) auto wrap mode rather than only
- * the default.
- */
- Conf *conf;
-
- /*
- * GUI implementations of seat_output call term_out, but it can
- * also be called from the ldisc if the ldisc is called _within_
- * term_out. So we have to guard against re-entrancy - if
- * seat_output is called recursively like this, it will simply add
- * data to the end of the buffer term_out is in the process of
- * working through.
- */
- bool in_term_out;
-
- /*
- * We don't permit window updates too close together, to avoid CPU
- * churn pointlessly redrawing the window faster than the user can
- * read. So after an update, we set window_update_cooldown = true
- * and schedule a timer to reset it to false. In between those
- * times, window updates are not performed, and instead we set
- * window_update_pending = true, which will remind us to perform
- * the deferred redraw when the cooldown period ends and
- * window_update_cooldown is reset to false.
- */
- bool window_update_pending, window_update_cooldown;
- long window_update_cooldown_end;
-
- /*
- * Track pending blinks and tblinks.
- */
- bool tblink_pending, cblink_pending;
- long next_tblink, next_cblink;
-
- /*
- * These are buffers used by the bidi and Arabic shaping code.
- */
- termchar *ltemp;
- int ltemp_size;
- bidi_char *wcFrom, *wcTo;
- int wcFromTo_size;
- struct bidi_cache_entry *pre_bidi_cache, *post_bidi_cache;
- size_t bidi_cache_size;
-
- /*
- * Current trust state, used to annotate every line of the
- * terminal that a graphic character is output to.
- */
- bool trusted;
-
- /*
- * We copy a bunch of stuff out of the Conf structure into local
- * fields in the Terminal structure, to avoid the repeated
- * tree234 lookups which would be involved in fetching them from
- * the former every time.
- */
- bool ansi_colour;
- 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;
- bool no_remote_charset;
- bool no_remote_resize;
- bool no_remote_wintitle;
- bool no_remote_clearscroll;
- bool rawcnp;
- bool utf8linedraw;
- bool rect_select;
- int remote_qtitle_action;
- bool rxvt_homeend;
- bool scroll_on_disp;
- bool scroll_on_key;
- bool xterm_256_colour;
- bool true_colour;
-
- wchar_t *last_selected_text;
- int *last_selected_attr;
- truecolour *last_selected_tc;
- size_t last_selected_len;
- int mouse_select_clipboards[N_CLIPBOARDS];
- int n_mouse_select_clipboards;
- int mouse_paste_clipboard;
-
- char *window_title, *icon_title;
- 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;
-};
-
-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/terminal/bidi.c b/terminal/bidi.c
new file mode 100644
index 00000000..c17671b6
--- /dev/null
+++ b/terminal/bidi.c
@@ -0,0 +1,3609 @@
+/*
+ * Implementation of the Unicode bidirectional and Arabic shaping
+ * algorithms for PuTTY.
+ *
+ * Original version written and kindly contributed to this code base
+ * by Ahmad Khalifa of Arabeyes. The bidi part was almost completely
+ * rewritten in 2021 by Simon Tatham to bring it up to date, but the
+ * shaping part is still the one by the original authors.
+ *
+ * Implementation notes:
+ *
+ * Algorithm version
+ * -----------------
+ *
+ * This algorithm is up to date with Unicode Standard Annex #9
+ * revision 44:
+ *
+ * https://www.unicode.org/reports/tr9/tr9-44.html
+ *
+ * and passes the full conformance test suite in Unicode 14.0.0.
+ *
+ * Paragraph and line handling
+ * ---------------------------
+ *
+ * The full Unicode bidi algorithm expects to receive text containing
+ * multiple paragraphs, together with a decision about how those
+ * paragraphs are broken up into lines. It calculates embedding levels
+ * a whole paragraph at a time without considering the line breaks,
+ * but then the final reordering of the text for display is done to
+ * each _line_ independently based on the levels computed for the text
+ * in that line.
+ *
+ * This algorithm omits all of that, because it's intended for use as
+ * a display-time transformation of a text terminal, which doesn't
+ * preserve enough semantic information to decide what's a paragraph
+ * break and what is not. So a piece of input text provided to this
+ * algorithm is always expected to consist of exactly one paragraph
+ * *and* exactly one line.
+ *
+ * Embeddings, overrides and isolates
+ * ----------------------------------
+ *
+ * This implementation has full support for all the Unicode special
+ * control characters that modify bidi behaviour, such as
+ *
+ * U+202A LEFT-TO-RIGHT EMBEDDING
+ * U+202B RIGHT-TO-LEFT EMBEDDING
+ * U+202D LEFT-TO-RIGHT OVERRIDE
+ * U+202E RIGHT-TO-LEFT OVERRIDE
+ * U+202C POP DIRECTIONAL FORMATTING
+ * U+2068 FIRST STRONG ISOLATE
+ * U+2066 LEFT-TO-RIGHT ISOLATE
+ * U+2067 RIGHT-TO-LEFT ISOLATE
+ * U+2069 POP DIRECTIONAL ISOLATE
+ *
+ * However, at present, the terminal emulator that is a client of this
+ * code has no way to pass those in (because they're dropped during
+ * escape sequence processing and don't get stored in the terminal
+ * state). Nonetheless, the code is all here, so if the terminal
+ * emulator becomes able to record those characters at some later
+ * point, we'll be all set to take account of them during bidi.
+ *
+ * But the _main_ purpose of supporting the full bidi algorithm is
+ * simply that that's the easiest way to be sure it's correct, because
+ * if you support the whole thing, you can run the full conformance
+ * test suite. (And I don't 100% believe that restricting to the
+ * subset of _tests_ valid with a reduced character set will test the
+ * full set of _functionality_ relevant to the reduced set.)
+ *
+ * Retained formatting characters
+ * ------------------------------
+ *
+ * The standard bidi algorithm, in step X9, deletes assorted
+ * formatting characters from the text: all the embedding and override
+ * section initiator characters, the Pop Directional Formatting
+ * character that closes one of those sections again, and any
+ * character labelled as Boundary Neutral. So the characters it
+ * returns are not a _full_ reordering of the input; some input
+ * characters vanish completely.
+ *
+ * This would be fine, if it were not for the fact that - as far as I
+ * can see - _exactly one_ Unicode code point in the discarded
+ * category has a wcwidth() of more than 0, namely U+00AD SOFT HYPHEN
+ * which is a printing character for terminal purposes but has a bidi
+ * class of BN.
+ *
+ * Therefore, we must implement a modified version of the algorithm,
+ * as described in section 5.2 of TR9, which retains those formatting
+ * characters so that a client can find out where they ended up in the
+ * reordering.
+ *
+ * Section 5.2 describes a set of modifications to the algorithm that
+ * are _intended_ to achieve this without changing the rest of the
+ * behaviour: that is, if you take the output of the modified
+ * algorithm and delete all the characters that the standard algorithm
+ * would have removed, you should end up with the remaining characters
+ * in the same order that the standard algorithm would have delivered.
+ * However, section 5.2 admits the possibility of error, and says "in
+ * case of any deviation the explicit algorithm is the normative
+ * statement for conformance". And indeed, in one or two places I
+ * found I had to make my own tweaks to the section 5.2 description in
+ * order to get the whole test suite to pass, because I think the 5.2
+ * modifications if taken literally don't quite achieve that. My
+ * justification is that sentence of 5.2: in case of doubt, the right
+ * thing is to make the code behave the same as the official
+ * algorithm.
+ *
+ * It's possible that there might still be some undiscovered
+ * discrepancies between the behaviour of the standard and modified
+ * algorithms. So, just in case, I've kept in this code the ability to
+ * implement the _standard_ algorithm too! If you compile with
+ * -DREMOVE_FORMATTING_CHARS, this code should go back to implementing
+ * the literal UAX#9 bidi algorithm - so you can run your suspect
+ * input through both versions, making it much easier to figure out
+ * why they differ, and in which of the many stages of the algorithm
+ * the difference was introduced.
+ *
+ * However, beware that when compiling in this mode, the do_bidi
+ * interface to the terminal will stop working, and just abort() when
+ * called! The only useful thing you can do with this mode is to run
+ * the companion program bidi_test.c.
+ */
+
+#include <stdlib.h> /* definition of wchar_t */
+
+#include "putty.h"
+#include "misc.h"
+#include "bidi.h"
+
+typedef struct {
+ char type;
+ wchar_t form_b;
+} shape_node;
+
+/* Kept near the actual table, for verification. */
+#define SHAPE_FIRST 0x621
+#define SHAPE_LAST (SHAPE_FIRST + lenof(shapetypes) - 1)
+
+static const shape_node shapetypes[] = {
+ /* index, Typ, Iso, Ligature Index*/
+ /* 621 */ {SU, 0xFE80},
+ /* 622 */ {SR, 0xFE81},
+ /* 623 */ {SR, 0xFE83},
+ /* 624 */ {SR, 0xFE85},
+ /* 625 */ {SR, 0xFE87},
+ /* 626 */ {SD, 0xFE89},
+ /* 627 */ {SR, 0xFE8D},
+ /* 628 */ {SD, 0xFE8F},
+ /* 629 */ {SR, 0xFE93},
+ /* 62A */ {SD, 0xFE95},
+ /* 62B */ {SD, 0xFE99},
+ /* 62C */ {SD, 0xFE9D},
+ /* 62D */ {SD, 0xFEA1},
+ /* 62E */ {SD, 0xFEA5},
+ /* 62F */ {SR, 0xFEA9},
+ /* 630 */ {SR, 0xFEAB},
+ /* 631 */ {SR, 0xFEAD},
+ /* 632 */ {SR, 0xFEAF},
+ /* 633 */ {SD, 0xFEB1},
+ /* 634 */ {SD, 0xFEB5},
+ /* 635 */ {SD, 0xFEB9},
+ /* 636 */ {SD, 0xFEBD},
+ /* 637 */ {SD, 0xFEC1},
+ /* 638 */ {SD, 0xFEC5},
+ /* 639 */ {SD, 0xFEC9},
+ /* 63A */ {SD, 0xFECD},
+ /* 63B */ {SU, 0x0},
+ /* 63C */ {SU, 0x0},
+ /* 63D */ {SU, 0x0},
+ /* 63E */ {SU, 0x0},
+ /* 63F */ {SU, 0x0},
+ /* 640 */ {SC, 0x0},
+ /* 641 */ {SD, 0xFED1},
+ /* 642 */ {SD, 0xFED5},
+ /* 643 */ {SD, 0xFED9},
+ /* 644 */ {SD, 0xFEDD},
+ /* 645 */ {SD, 0xFEE1},
+ /* 646 */ {SD, 0xFEE5},
+ /* 647 */ {SD, 0xFEE9},
+ /* 648 */ {SR, 0xFEED},
+ /* 649 */ {SR, 0xFEEF}, /* SD */
+ /* 64A */ {SD, 0xFEF1},
+ /* 64B */ {SU, 0x0},
+ /* 64C */ {SU, 0x0},
+ /* 64D */ {SU, 0x0},
+ /* 64E */ {SU, 0x0},
+ /* 64F */ {SU, 0x0},
+ /* 650 */ {SU, 0x0},
+ /* 651 */ {SU, 0x0},
+ /* 652 */ {SU, 0x0},
+ /* 653 */ {SU, 0x0},
+ /* 654 */ {SU, 0x0},
+ /* 655 */ {SU, 0x0},
+ /* 656 */ {SU, 0x0},
+ /* 657 */ {SU, 0x0},
+ /* 658 */ {SU, 0x0},
+ /* 659 */ {SU, 0x0},
+ /* 65A */ {SU, 0x0},
+ /* 65B */ {SU, 0x0},
+ /* 65C */ {SU, 0x0},
+ /* 65D */ {SU, 0x0},
+ /* 65E */ {SU, 0x0},
+ /* 65F */ {SU, 0x0},
+ /* 660 */ {SU, 0x0},
+ /* 661 */ {SU, 0x0},
+ /* 662 */ {SU, 0x0},
+ /* 663 */ {SU, 0x0},
+ /* 664 */ {SU, 0x0},
+ /* 665 */ {SU, 0x0},
+ /* 666 */ {SU, 0x0},
+ /* 667 */ {SU, 0x0},
+ /* 668 */ {SU, 0x0},
+ /* 669 */ {SU, 0x0},
+ /* 66A */ {SU, 0x0},
+ /* 66B */ {SU, 0x0},
+ /* 66C */ {SU, 0x0},
+ /* 66D */ {SU, 0x0},
+ /* 66E */ {SU, 0x0},
+ /* 66F */ {SU, 0x0},
+ /* 670 */ {SU, 0x0},
+ /* 671 */ {SR, 0xFB50},
+ /* 672 */ {SU, 0x0},
+ /* 673 */ {SU, 0x0},
+ /* 674 */ {SU, 0x0},
+ /* 675 */ {SU, 0x0},
+ /* 676 */ {SU, 0x0},
+ /* 677 */ {SU, 0x0},
+ /* 678 */ {SU, 0x0},
+ /* 679 */ {SD, 0xFB66},
+ /* 67A */ {SD, 0xFB5E},
+ /* 67B */ {SD, 0xFB52},
+ /* 67C */ {SU, 0x0},
+ /* 67D */ {SU, 0x0},
+ /* 67E */ {SD, 0xFB56},
+ /* 67F */ {SD, 0xFB62},
+ /* 680 */ {SD, 0xFB5A},
+ /* 681 */ {SU, 0x0},
+ /* 682 */ {SU, 0x0},
+ /* 683 */ {SD, 0xFB76},
+ /* 684 */ {SD, 0xFB72},
+ /* 685 */ {SU, 0x0},
+ /* 686 */ {SD, 0xFB7A},
+ /* 687 */ {SD, 0xFB7E},
+ /* 688 */ {SR, 0xFB88},
+ /* 689 */ {SU, 0x0},
+ /* 68A */ {SU, 0x0},
+ /* 68B */ {SU, 0x0},
+ /* 68C */ {SR, 0xFB84},
+ /* 68D */ {SR, 0xFB82},
+ /* 68E */ {SR, 0xFB86},
+ /* 68F */ {SU, 0x0},
+ /* 690 */ {SU, 0x0},
+ /* 691 */ {SR, 0xFB8C},
+ /* 692 */ {SU, 0x0},
+ /* 693 */ {SU, 0x0},
+ /* 694 */ {SU, 0x0},
+ /* 695 */ {SU, 0x0},
+ /* 696 */ {SU, 0x0},
+ /* 697 */ {SU, 0x0},
+ /* 698 */ {SR, 0xFB8A},
+ /* 699 */ {SU, 0x0},
+ /* 69A */ {SU, 0x0},
+ /* 69B */ {SU, 0x0},
+ /* 69C */ {SU, 0x0},
+ /* 69D */ {SU, 0x0},
+ /* 69E */ {SU, 0x0},
+ /* 69F */ {SU, 0x0},
+ /* 6A0 */ {SU, 0x0},
+ /* 6A1 */ {SU, 0x0},
+ /* 6A2 */ {SU, 0x0},
+ /* 6A3 */ {SU, 0x0},
+ /* 6A4 */ {SD, 0xFB6A},
+ /* 6A5 */ {SU, 0x0},
+ /* 6A6 */ {SD, 0xFB6E},
+ /* 6A7 */ {SU, 0x0},
+ /* 6A8 */ {SU, 0x0},
+ /* 6A9 */ {SD, 0xFB8E},
+ /* 6AA */ {SU, 0x0},
+ /* 6AB */ {SU, 0x0},
+ /* 6AC */ {SU, 0x0},
+ /* 6AD */ {SD, 0xFBD3},
+ /* 6AE */ {SU, 0x0},
+ /* 6AF */ {SD, 0xFB92},
+ /* 6B0 */ {SU, 0x0},
+ /* 6B1 */ {SD, 0xFB9A},
+ /* 6B2 */ {SU, 0x0},
+ /* 6B3 */ {SD, 0xFB96},
+ /* 6B4 */ {SU, 0x0},
+ /* 6B5 */ {SU, 0x0},
+ /* 6B6 */ {SU, 0x0},
+ /* 6B7 */ {SU, 0x0},
+ /* 6B8 */ {SU, 0x0},
+ /* 6B9 */ {SU, 0x0},
+ /* 6BA */ {SR, 0xFB9E},
+ /* 6BB */ {SD, 0xFBA0},
+ /* 6BC */ {SU, 0x0},
+ /* 6BD */ {SU, 0x0},
+ /* 6BE */ {SD, 0xFBAA},
+ /* 6BF */ {SU, 0x0},
+ /* 6C0 */ {SR, 0xFBA4},
+ /* 6C1 */ {SD, 0xFBA6},
+ /* 6C2 */ {SU, 0x0},
+ /* 6C3 */ {SU, 0x0},
+ /* 6C4 */ {SU, 0x0},
+ /* 6C5 */ {SR, 0xFBE0},
+ /* 6C6 */ {SR, 0xFBD9},
+ /* 6C7 */ {SR, 0xFBD7},
+ /* 6C8 */ {SR, 0xFBDB},
+ /* 6C9 */ {SR, 0xFBE2},
+ /* 6CA */ {SU, 0x0},
+ /* 6CB */ {SR, 0xFBDE},
+ /* 6CC */ {SD, 0xFBFC},
+ /* 6CD */ {SU, 0x0},
+ /* 6CE */ {SU, 0x0},
+ /* 6CF */ {SU, 0x0},
+ /* 6D0 */ {SU, 0x0},
+ /* 6D1 */ {SU, 0x0},
+ /* 6D2 */ {SR, 0xFBAE},
+};
+
+/*
+ * Returns the bidi character type of ch.
+ *
+ * The data table in this function is constructed from the Unicode
+ * Character Database version 14.0.0, downloadable from unicode.org at
+ * the URL
+ *
+ * https://www.unicode.org/Public/14.0.0/ucd/
+ *
+ * by the following fragment of Perl:
+
+perl -ne '@_=split ";"; $num = hex $_[0]; $type = $_[4];' \
+ -e '$fl = ($_[1] =~ /First/ ? 1 : $_[1] =~ /Last/ ? 2 : 0);' \
+ -e 'if ($type eq $runtype and ($runend == $num-1 or ' \
+ -e ' ($fl==2 and $pfl==1))) {$runend = $num;} else { &reset; }' \
+ -e '$pfl=$fl; END { &reset }; sub reset {' \
+ -e 'printf" {0x%04x, 0x%04x, %s},\n",$runstart,$runend,$runtype' \
+ -e ' if defined $runstart and $runtype ne "ON";' \
+ -e '$runstart=$runend=$num; $runtype=$type;}' \
+ UnicodeData.txt
+
+ */
+unsigned char bidi_getType(int ch)
+{
+ static const struct {
+ int first, last, type;
+ } lookup[] = {
+ {0x0000, 0x0008, BN},
+ {0x0009, 0x0009, S},
+ {0x000a, 0x000a, B},
+ {0x000b, 0x000b, S},
+ {0x000c, 0x000c, WS},
+ {0x000d, 0x000d, B},
+ {0x000e, 0x001b, BN},
+ {0x001c, 0x001e, B},
+ {0x001f, 0x001f, S},
+ {0x0020, 0x0020, WS},
+ {0x0023, 0x0025, ET},
+ {0x002b, 0x002b, ES},
+ {0x002c, 0x002c, CS},
+ {0x002d, 0x002d, ES},
+ {0x002e, 0x002f, CS},
+ {0x0030, 0x0039, EN},
+ {0x003a, 0x003a, CS},
+ {0x0041, 0x005a, L},
+ {0x0061, 0x007a, L},
+ {0x007f, 0x0084, BN},
+ {0x0085, 0x0085, B},
+ {0x0086, 0x009f, BN},
+ {0x00a0, 0x00a0, CS},
+ {0x00a2, 0x00a5, ET},
+ {0x00aa, 0x00aa, L},
+ {0x00ad, 0x00ad, BN},
+ {0x00b0, 0x00b1, ET},
+ {0x00b2, 0x00b3, EN},
+ {0x00b5, 0x00b5, L},
+ {0x00b9, 0x00b9, EN},
+ {0x00ba, 0x00ba, L},
+ {0x00c0, 0x00d6, L},
+ {0x00d8, 0x00f6, L},
+ {0x00f8, 0x02b8, L},
+ {0x02bb, 0x02c1, L},
+ {0x02d0, 0x02d1, L},
+ {0x02e0, 0x02e4, L},
+ {0x02ee, 0x02ee, L},
+ {0x0300, 0x036f, NSM},
+ {0x0370, 0x0373, L},
+ {0x0376, 0x0377, L},
+ {0x037a, 0x037d, L},
+ {0x037f, 0x037f, L},
+ {0x0386, 0x0386, L},
+ {0x0388, 0x038a, L},
+ {0x038c, 0x038c, L},
+ {0x038e, 0x03a1, L},
+ {0x03a3, 0x03f5, L},
+ {0x03f7, 0x0482, L},
+ {0x0483, 0x0489, NSM},
+ {0x048a, 0x052f, L},
+ {0x0531, 0x0556, L},
+ {0x0559, 0x0589, L},
+ {0x058f, 0x058f, ET},
+ {0x0591, 0x05bd, NSM},
+ {0x05be, 0x05be, R},
+ {0x05bf, 0x05bf, NSM},
+ {0x05c0, 0x05c0, R},
+ {0x05c1, 0x05c2, NSM},
+ {0x05c3, 0x05c3, R},
+ {0x05c4, 0x05c5, NSM},
+ {0x05c6, 0x05c6, R},
+ {0x05c7, 0x05c7, NSM},
+ {0x05d0, 0x05ea, R},
+ {0x05ef, 0x05f4, R},
+ {0x0600, 0x0605, AN},
+ {0x0608, 0x0608, AL},
+ {0x0609, 0x060a, ET},
+ {0x060b, 0x060b, AL},
+ {0x060c, 0x060c, CS},
+ {0x060d, 0x060d, AL},
+ {0x0610, 0x061a, NSM},
+ {0x061b, 0x064a, AL},
+ {0x064b, 0x065f, NSM},
+ {0x0660, 0x0669, AN},
+ {0x066a, 0x066a, ET},
+ {0x066b, 0x066c, AN},
+ {0x066d, 0x066f, AL},
+ {0x0670, 0x0670, NSM},
+ {0x0671, 0x06d5, AL},
+ {0x06d6, 0x06dc, NSM},
+ {0x06dd, 0x06dd, AN},
+ {0x06df, 0x06e4, NSM},
+ {0x06e5, 0x06e6, AL},
+ {0x06e7, 0x06e8, NSM},
+ {0x06ea, 0x06ed, NSM},
+ {0x06ee, 0x06ef, AL},
+ {0x06f0, 0x06f9, EN},
+ {0x06fa, 0x070d, AL},
+ {0x070f, 0x0710, AL},
+ {0x0711, 0x0711, NSM},
+ {0x0712, 0x072f, AL},
+ {0x0730, 0x074a, NSM},
+ {0x074d, 0x07a5, AL},
+ {0x07a6, 0x07b0, NSM},
+ {0x07b1, 0x07b1, AL},
+ {0x07c0, 0x07ea, R},
+ {0x07eb, 0x07f3, NSM},
+ {0x07f4, 0x07f5, R},
+ {0x07fa, 0x07fa, R},
+ {0x07fd, 0x07fd, NSM},
+ {0x07fe, 0x0815, R},
+ {0x0816, 0x0819, NSM},
+ {0x081a, 0x081a, R},
+ {0x081b, 0x0823, NSM},
+ {0x0824, 0x0824, R},
+ {0x0825, 0x0827, NSM},
+ {0x0828, 0x0828, R},
+ {0x0829, 0x082d, NSM},
+ {0x0830, 0x083e, R},
+ {0x0840, 0x0858, R},
+ {0x0859, 0x085b, NSM},
+ {0x085e, 0x085e, R},
+ {0x0860, 0x086a, AL},
+ {0x0870, 0x088e, AL},
+ {0x0890, 0x0891, AN},
+ {0x0898, 0x089f, NSM},
+ {0x08a0, 0x08c9, AL},
+ {0x08ca, 0x08e1, NSM},
+ {0x08e2, 0x08e2, AN},
+ {0x08e3, 0x0902, NSM},
+ {0x0903, 0x0939, L},
+ {0x093a, 0x093a, NSM},
+ {0x093b, 0x093b, L},
+ {0x093c, 0x093c, NSM},
+ {0x093d, 0x0940, L},
+ {0x0941, 0x0948, NSM},
+ {0x0949, 0x094c, L},
+ {0x094d, 0x094d, NSM},
+ {0x094e, 0x0950, L},
+ {0x0951, 0x0957, NSM},
+ {0x0958, 0x0961, L},
+ {0x0962, 0x0963, NSM},
+ {0x0964, 0x0980, L},
+ {0x0981, 0x0981, NSM},
+ {0x0982, 0x0983, L},
+ {0x0985, 0x098c, L},
+ {0x098f, 0x0990, L},
+ {0x0993, 0x09a8, L},
+ {0x09aa, 0x09b0, L},
+ {0x09b2, 0x09b2, L},
+ {0x09b6, 0x09b9, L},
+ {0x09bc, 0x09bc, NSM},
+ {0x09bd, 0x09c0, L},
+ {0x09c1, 0x09c4, NSM},
+ {0x09c7, 0x09c8, L},
+ {0x09cb, 0x09cc, L},
+ {0x09cd, 0x09cd, NSM},
+ {0x09ce, 0x09ce, L},
+ {0x09d7, 0x09d7, L},
+ {0x09dc, 0x09dd, L},
+ {0x09df, 0x09e1, L},
+ {0x09e2, 0x09e3, NSM},
+ {0x09e6, 0x09f1, L},
+ {0x09f2, 0x09f3, ET},
+ {0x09f4, 0x09fa, L},
+ {0x09fb, 0x09fb, ET},
+ {0x09fc, 0x09fd, L},
+ {0x09fe, 0x09fe, NSM},
+ {0x0a01, 0x0a02, NSM},
+ {0x0a03, 0x0a03, L},
+ {0x0a05, 0x0a0a, L},
+ {0x0a0f, 0x0a10, L},
+ {0x0a13, 0x0a28, L},
+ {0x0a2a, 0x0a30, L},
+ {0x0a32, 0x0a33, L},
+ {0x0a35, 0x0a36, L},
+ {0x0a38, 0x0a39, L},
+ {0x0a3c, 0x0a3c, NSM},
+ {0x0a3e, 0x0a40, L},
+ {0x0a41, 0x0a42, NSM},
+ {0x0a47, 0x0a48, NSM},
+ {0x0a4b, 0x0a4d, NSM},
+ {0x0a51, 0x0a51, NSM},
+ {0x0a59, 0x0a5c, L},
+ {0x0a5e, 0x0a5e, L},
+ {0x0a66, 0x0a6f, L},
+ {0x0a70, 0x0a71, NSM},
+ {0x0a72, 0x0a74, L},
+ {0x0a75, 0x0a75, NSM},
+ {0x0a76, 0x0a76, L},
+ {0x0a81, 0x0a82, NSM},
+ {0x0a83, 0x0a83, L},
+ {0x0a85, 0x0a8d, L},
+ {0x0a8f, 0x0a91, L},
+ {0x0a93, 0x0aa8, L},
+ {0x0aaa, 0x0ab0, L},
+ {0x0ab2, 0x0ab3, L},
+ {0x0ab5, 0x0ab9, L},
+ {0x0abc, 0x0abc, NSM},
+ {0x0abd, 0x0ac0, L},
+ {0x0ac1, 0x0ac5, NSM},
+ {0x0ac7, 0x0ac8, NSM},
+ {0x0ac9, 0x0ac9, L},
+ {0x0acb, 0x0acc, L},
+ {0x0acd, 0x0acd, NSM},
+ {0x0ad0, 0x0ad0, L},
+ {0x0ae0, 0x0ae1, L},
+ {0x0ae2, 0x0ae3, NSM},
+ {0x0ae6, 0x0af0, L},
+ {0x0af1, 0x0af1, ET},
+ {0x0af9, 0x0af9, L},
+ {0x0afa, 0x0aff, NSM},
+ {0x0b01, 0x0b01, NSM},
+ {0x0b02, 0x0b03, L},
+ {0x0b05, 0x0b0c, L},
+ {0x0b0f, 0x0b10, L},
+ {0x0b13, 0x0b28, L},
+ {0x0b2a, 0x0b30, L},
+ {0x0b32, 0x0b33, L},
+ {0x0b35, 0x0b39, L},
+ {0x0b3c, 0x0b3c, NSM},
+ {0x0b3d, 0x0b3e, L},
+ {0x0b3f, 0x0b3f, NSM},
+ {0x0b40, 0x0b40, L},
+ {0x0b41, 0x0b44, NSM},
+ {0x0b47, 0x0b48, L},
+ {0x0b4b, 0x0b4c, L},
+ {0x0b4d, 0x0b4d, NSM},
+ {0x0b55, 0x0b56, NSM},
+ {0x0b57, 0x0b57, L},
+ {0x0b5c, 0x0b5d, L},
+ {0x0b5f, 0x0b61, L},
+ {0x0b62, 0x0b63, NSM},
+ {0x0b66, 0x0b77, L},
+ {0x0b82, 0x0b82, NSM},
+ {0x0b83, 0x0b83, L},
+ {0x0b85, 0x0b8a, L},
+ {0x0b8e, 0x0b90, L},
+ {0x0b92, 0x0b95, L},
+ {0x0b99, 0x0b9a, L},
+ {0x0b9c, 0x0b9c, L},
+ {0x0b9e, 0x0b9f, L},
+ {0x0ba3, 0x0ba4, L},
+ {0x0ba8, 0x0baa, L},
+ {0x0bae, 0x0bb9, L},
+ {0x0bbe, 0x0bbf, L},
+ {0x0bc0, 0x0bc0, NSM},
+ {0x0bc1, 0x0bc2, L},
+ {0x0bc6, 0x0bc8, L},
+ {0x0bca, 0x0bcc, L},
+ {0x0bcd, 0x0bcd, NSM},
+ {0x0bd0, 0x0bd0, L},
+ {0x0bd7, 0x0bd7, L},
+ {0x0be6, 0x0bf2, L},
+ {0x0bf9, 0x0bf9, ET},
+ {0x0c00, 0x0c00, NSM},
+ {0x0c01, 0x0c03, L},
+ {0x0c04, 0x0c04, NSM},
+ {0x0c05, 0x0c0c, L},
+ {0x0c0e, 0x0c10, L},
+ {0x0c12, 0x0c28, L},
+ {0x0c2a, 0x0c39, L},
+ {0x0c3c, 0x0c3c, NSM},
+ {0x0c3d, 0x0c3d, L},
+ {0x0c3e, 0x0c40, NSM},
+ {0x0c41, 0x0c44, L},
+ {0x0c46, 0x0c48, NSM},
+ {0x0c4a, 0x0c4d, NSM},
+ {0x0c55, 0x0c56, NSM},
+ {0x0c58, 0x0c5a, L},
+ {0x0c5d, 0x0c5d, L},
+ {0x0c60, 0x0c61, L},
+ {0x0c62, 0x0c63, NSM},
+ {0x0c66, 0x0c6f, L},
+ {0x0c77, 0x0c77, L},
+ {0x0c7f, 0x0c80, L},
+ {0x0c81, 0x0c81, NSM},
+ {0x0c82, 0x0c8c, L},
+ {0x0c8e, 0x0c90, L},
+ {0x0c92, 0x0ca8, L},
+ {0x0caa, 0x0cb3, L},
+ {0x0cb5, 0x0cb9, L},
+ {0x0cbc, 0x0cbc, NSM},
+ {0x0cbd, 0x0cc4, L},
+ {0x0cc6, 0x0cc8, L},
+ {0x0cca, 0x0ccb, L},
+ {0x0ccc, 0x0ccd, NSM},
+ {0x0cd5, 0x0cd6, L},
+ {0x0cdd, 0x0cde, L},
+ {0x0ce0, 0x0ce1, L},
+ {0x0ce2, 0x0ce3, NSM},
+ {0x0ce6, 0x0cef, L},
+ {0x0cf1, 0x0cf2, L},
+ {0x0d00, 0x0d01, NSM},
+ {0x0d02, 0x0d0c, L},
+ {0x0d0e, 0x0d10, L},
+ {0x0d12, 0x0d3a, L},
+ {0x0d3b, 0x0d3c, NSM},
+ {0x0d3d, 0x0d40, L},
+ {0x0d41, 0x0d44, NSM},
+ {0x0d46, 0x0d48, L},
+ {0x0d4a, 0x0d4c, L},
+ {0x0d4d, 0x0d4d, NSM},
+ {0x0d4e, 0x0d4f, L},
+ {0x0d54, 0x0d61, L},
+ {0x0d62, 0x0d63, NSM},
+ {0x0d66, 0x0d7f, L},
+ {0x0d81, 0x0d81, NSM},
+ {0x0d82, 0x0d83, L},
+ {0x0d85, 0x0d96, L},
+ {0x0d9a, 0x0db1, L},
+ {0x0db3, 0x0dbb, L},
+ {0x0dbd, 0x0dbd, L},
+ {0x0dc0, 0x0dc6, L},
+ {0x0dca, 0x0dca, NSM},
+ {0x0dcf, 0x0dd1, L},
+ {0x0dd2, 0x0dd4, NSM},
+ {0x0dd6, 0x0dd6, NSM},
+ {0x0dd8, 0x0ddf, L},
+ {0x0de6, 0x0def, L},
+ {0x0df2, 0x0df4, L},
+ {0x0e01, 0x0e30, L},
+ {0x0e31, 0x0e31, NSM},
+ {0x0e32, 0x0e33, L},
+ {0x0e34, 0x0e3a, NSM},
+ {0x0e3f, 0x0e3f, ET},
+ {0x0e40, 0x0e46, L},
+ {0x0e47, 0x0e4e, NSM},
+ {0x0e4f, 0x0e5b, L},
+ {0x0e81, 0x0e82, L},
+ {0x0e84, 0x0e84, L},
+ {0x0e86, 0x0e8a, L},
+ {0x0e8c, 0x0ea3, L},
+ {0x0ea5, 0x0ea5, L},
+ {0x0ea7, 0x0eb0, L},
+ {0x0eb1, 0x0eb1, NSM},
+ {0x0eb2, 0x0eb3, L},
+ {0x0eb4, 0x0ebc, NSM},
+ {0x0ebd, 0x0ebd, L},
+ {0x0ec0, 0x0ec4, L},
+ {0x0ec6, 0x0ec6, L},
+ {0x0ec8, 0x0ecd, NSM},
+ {0x0ed0, 0x0ed9, L},
+ {0x0edc, 0x0edf, L},
+ {0x0f00, 0x0f17, L},
+ {0x0f18, 0x0f19, NSM},
+ {0x0f1a, 0x0f34, L},
+ {0x0f35, 0x0f35, NSM},
+ {0x0f36, 0x0f36, L},
+ {0x0f37, 0x0f37, NSM},
+ {0x0f38, 0x0f38, L},
+ {0x0f39, 0x0f39, NSM},
+ {0x0f3e, 0x0f47, L},
+ {0x0f49, 0x0f6c, L},
+ {0x0f71, 0x0f7e, NSM},
+ {0x0f7f, 0x0f7f, L},
+ {0x0f80, 0x0f84, NSM},
+ {0x0f85, 0x0f85, L},
+ {0x0f86, 0x0f87, NSM},
+ {0x0f88, 0x0f8c, L},
+ {0x0f8d, 0x0f97, NSM},
+ {0x0f99, 0x0fbc, NSM},
+ {0x0fbe, 0x0fc5, L},
+ {0x0fc6, 0x0fc6, NSM},
+ {0x0fc7, 0x0fcc, L},
+ {0x0fce, 0x0fda, L},
+ {0x1000, 0x102c, L},
+ {0x102d, 0x1030, NSM},
+ {0x1031, 0x1031, L},
+ {0x1032, 0x1037, NSM},
+ {0x1038, 0x1038, L},
+ {0x1039, 0x103a, NSM},
+ {0x103b, 0x103c, L},
+ {0x103d, 0x103e, NSM},
+ {0x103f, 0x1057, L},
+ {0x1058, 0x1059, NSM},
+ {0x105a, 0x105d, L},
+ {0x105e, 0x1060, NSM},
+ {0x1061, 0x1070, L},
+ {0x1071, 0x1074, NSM},
+ {0x1075, 0x1081, L},
+ {0x1082, 0x1082, NSM},
+ {0x1083, 0x1084, L},
+ {0x1085, 0x1086, NSM},
+ {0x1087, 0x108c, L},
+ {0x108d, 0x108d, NSM},
+ {0x108e, 0x109c, L},
+ {0x109d, 0x109d, NSM},
+ {0x109e, 0x10c5, L},
+ {0x10c7, 0x10c7, L},
+ {0x10cd, 0x10cd, L},
+ {0x10d0, 0x1248, L},
+ {0x124a, 0x124d, L},
+ {0x1250, 0x1256, L},
+ {0x1258, 0x1258, L},
+ {0x125a, 0x125d, L},
+ {0x1260, 0x1288, L},
+ {0x128a, 0x128d, L},
+ {0x1290, 0x12b0, L},
+ {0x12b2, 0x12b5, L},
+ {0x12b8, 0x12be, L},
+ {0x12c0, 0x12c0, L},
+ {0x12c2, 0x12c5, L},
+ {0x12c8, 0x12d6, L},
+ {0x12d8, 0x1310, L},
+ {0x1312, 0x1315, L},
+ {0x1318, 0x135a, L},
+ {0x135d, 0x135f, NSM},
+ {0x1360, 0x137c, L},
+ {0x1380, 0x138f, L},
+ {0x13a0, 0x13f5, L},
+ {0x13f8, 0x13fd, L},
+ {0x1401, 0x167f, L},
+ {0x1680, 0x1680, WS},
+ {0x1681, 0x169a, L},
+ {0x16a0, 0x16f8, L},
+ {0x1700, 0x1711, L},
+ {0x1712, 0x1714, NSM},
+ {0x1715, 0x1715, L},
+ {0x171f, 0x1731, L},
+ {0x1732, 0x1733, NSM},
+ {0x1734, 0x1736, L},
+ {0x1740, 0x1751, L},
+ {0x1752, 0x1753, NSM},
+ {0x1760, 0x176c, L},
+ {0x176e, 0x1770, L},
+ {0x1772, 0x1773, NSM},
+ {0x1780, 0x17b3, L},
+ {0x17b4, 0x17b5, NSM},
+ {0x17b6, 0x17b6, L},
+ {0x17b7, 0x17bd, NSM},
+ {0x17be, 0x17c5, L},
+ {0x17c6, 0x17c6, NSM},
+ {0x17c7, 0x17c8, L},
+ {0x17c9, 0x17d3, NSM},
+ {0x17d4, 0x17da, L},
+ {0x17db, 0x17db, ET},
+ {0x17dc, 0x17dc, L},
+ {0x17dd, 0x17dd, NSM},
+ {0x17e0, 0x17e9, L},
+ {0x180b, 0x180d, NSM},
+ {0x180e, 0x180e, BN},
+ {0x180f, 0x180f, NSM},
+ {0x1810, 0x1819, L},
+ {0x1820, 0x1878, L},
+ {0x1880, 0x1884, L},
+ {0x1885, 0x1886, NSM},
+ {0x1887, 0x18a8, L},
+ {0x18a9, 0x18a9, NSM},
+ {0x18aa, 0x18aa, L},
+ {0x18b0, 0x18f5, L},
+ {0x1900, 0x191e, L},
+ {0x1920, 0x1922, NSM},
+ {0x1923, 0x1926, L},
+ {0x1927, 0x1928, NSM},
+ {0x1929, 0x192b, L},
+ {0x1930, 0x1931, L},
+ {0x1932, 0x1932, NSM},
+ {0x1933, 0x1938, L},
+ {0x1939, 0x193b, NSM},
+ {0x1946, 0x196d, L},
+ {0x1970, 0x1974, L},
+ {0x1980, 0x19ab, L},
+ {0x19b0, 0x19c9, L},
+ {0x19d0, 0x19da, L},
+ {0x1a00, 0x1a16, L},
+ {0x1a17, 0x1a18, NSM},
+ {0x1a19, 0x1a1a, L},
+ {0x1a1b, 0x1a1b, NSM},
+ {0x1a1e, 0x1a55, L},
+ {0x1a56, 0x1a56, NSM},
+ {0x1a57, 0x1a57, L},
+ {0x1a58, 0x1a5e, NSM},
+ {0x1a60, 0x1a60, NSM},
+ {0x1a61, 0x1a61, L},
+ {0x1a62, 0x1a62, NSM},
+ {0x1a63, 0x1a64, L},
+ {0x1a65, 0x1a6c, NSM},
+ {0x1a6d, 0x1a72, L},
+ {0x1a73, 0x1a7c, NSM},
+ {0x1a7f, 0x1a7f, NSM},
+ {0x1a80, 0x1a89, L},
+ {0x1a90, 0x1a99, L},
+ {0x1aa0, 0x1aad, L},
+ {0x1ab0, 0x1ace, NSM},
+ {0x1b00, 0x1b03, NSM},
+ {0x1b04, 0x1b33, L},
+ {0x1b34, 0x1b34, NSM},
+ {0x1b35, 0x1b35, L},
+ {0x1b36, 0x1b3a, NSM},
+ {0x1b3b, 0x1b3b, L},
+ {0x1b3c, 0x1b3c, NSM},
+ {0x1b3d, 0x1b41, L},
+ {0x1b42, 0x1b42, NSM},
+ {0x1b43, 0x1b4c, L},
+ {0x1b50, 0x1b6a, L},
+ {0x1b6b, 0x1b73, NSM},
+ {0x1b74, 0x1b7e, L},
+ {0x1b80, 0x1b81, NSM},
+ {0x1b82, 0x1ba1, L},
+ {0x1ba2, 0x1ba5, NSM},
+ {0x1ba6, 0x1ba7, L},
+ {0x1ba8, 0x1ba9, NSM},
+ {0x1baa, 0x1baa, L},
+ {0x1bab, 0x1bad, NSM},
+ {0x1bae, 0x1be5, L},
+ {0x1be6, 0x1be6, NSM},
+ {0x1be7, 0x1be7, L},
+ {0x1be8, 0x1be9, NSM},
+ {0x1bea, 0x1bec, L},
+ {0x1bed, 0x1bed, NSM},
+ {0x1bee, 0x1bee, L},
+ {0x1bef, 0x1bf1, NSM},
+ {0x1bf2, 0x1bf3, L},
+ {0x1bfc, 0x1c2b, L},
+ {0x1c2c, 0x1c33, NSM},
+ {0x1c34, 0x1c35, L},
+ {0x1c36, 0x1c37, NSM},
+ {0x1c3b, 0x1c49, L},
+ {0x1c4d, 0x1c88, L},
+ {0x1c90, 0x1cba, L},
+ {0x1cbd, 0x1cc7, L},
+ {0x1cd0, 0x1cd2, NSM},
+ {0x1cd3, 0x1cd3, L},
+ {0x1cd4, 0x1ce0, NSM},
+ {0x1ce1, 0x1ce1, L},
+ {0x1ce2, 0x1ce8, NSM},
+ {0x1ce9, 0x1cec, L},
+ {0x1ced, 0x1ced, NSM},
+ {0x1cee, 0x1cf3, L},
+ {0x1cf4, 0x1cf4, NSM},
+ {0x1cf5, 0x1cf7, L},
+ {0x1cf8, 0x1cf9, NSM},
+ {0x1cfa, 0x1cfa, L},
+ {0x1d00, 0x1dbf, L},
+ {0x1dc0, 0x1dff, NSM},
+ {0x1e00, 0x1f15, L},
+ {0x1f18, 0x1f1d, L},
+ {0x1f20, 0x1f45, L},
+ {0x1f48, 0x1f4d, L},
+ {0x1f50, 0x1f57, L},
+ {0x1f59, 0x1f59, L},
+ {0x1f5b, 0x1f5b, L},
+ {0x1f5d, 0x1f5d, L},
+ {0x1f5f, 0x1f7d, L},
+ {0x1f80, 0x1fb4, L},
+ {0x1fb6, 0x1fbc, L},
+ {0x1fbe, 0x1fbe, L},
+ {0x1fc2, 0x1fc4, L},
+ {0x1fc6, 0x1fcc, L},
+ {0x1fd0, 0x1fd3, L},
+ {0x1fd6, 0x1fdb, L},
+ {0x1fe0, 0x1fec, L},
+ {0x1ff2, 0x1ff4, L},
+ {0x1ff6, 0x1ffc, L},
+ {0x2000, 0x200a, WS},
+ {0x200b, 0x200d, BN},
+ {0x200e, 0x200e, L},
+ {0x200f, 0x200f, R},
+ {0x2028, 0x2028, WS},
+ {0x2029, 0x2029, B},
+ {0x202a, 0x202a, LRE},
+ {0x202b, 0x202b, RLE},
+ {0x202c, 0x202c, PDF},
+ {0x202d, 0x202d, LRO},
+ {0x202e, 0x202e, RLO},
+ {0x202f, 0x202f, CS},
+ {0x2030, 0x2034, ET},
+ {0x2044, 0x2044, CS},
+ {0x205f, 0x205f, WS},
+ {0x2060, 0x2064, BN},
+ {0x2066, 0x2066, LRI},
+ {0x2067, 0x2067, RLI},
+ {0x2068, 0x2068, FSI},
+ {0x2069, 0x2069, PDI},
+ {0x206a, 0x206f, BN},
+ {0x2070, 0x2070, EN},
+ {0x2071, 0x2071, L},
+ {0x2074, 0x2079, EN},
+ {0x207a, 0x207b, ES},
+ {0x207f, 0x207f, L},
+ {0x2080, 0x2089, EN},
+ {0x208a, 0x208b, ES},
+ {0x2090, 0x209c, L},
+ {0x20a0, 0x20c0, ET},
+ {0x20d0, 0x20f0, NSM},
+ {0x2102, 0x2102, L},
+ {0x2107, 0x2107, L},
+ {0x210a, 0x2113, L},
+ {0x2115, 0x2115, L},
+ {0x2119, 0x211d, L},
+ {0x2124, 0x2124, L},
+ {0x2126, 0x2126, L},
+ {0x2128, 0x2128, L},
+ {0x212a, 0x212d, L},
+ {0x212e, 0x212e, ET},
+ {0x212f, 0x2139, L},
+ {0x213c, 0x213f, L},
+ {0x2145, 0x2149, L},
+ {0x214e, 0x214f, L},
+ {0x2160, 0x2188, L},
+ {0x2212, 0x2212, ES},
+ {0x2213, 0x2213, ET},
+ {0x2336, 0x237a, L},
+ {0x2395, 0x2395, L},
+ {0x2488, 0x249b, EN},
+ {0x249c, 0x24e9, L},
+ {0x26ac, 0x26ac, L},
+ {0x2800, 0x28ff, L},
+ {0x2c00, 0x2ce4, L},
+ {0x2ceb, 0x2cee, L},
+ {0x2cef, 0x2cf1, NSM},
+ {0x2cf2, 0x2cf3, L},
+ {0x2d00, 0x2d25, L},
+ {0x2d27, 0x2d27, L},
+ {0x2d2d, 0x2d2d, L},
+ {0x2d30, 0x2d67, L},
+ {0x2d6f, 0x2d70, L},
+ {0x2d7f, 0x2d7f, NSM},
+ {0x2d80, 0x2d96, L},
+ {0x2da0, 0x2da6, L},
+ {0x2da8, 0x2dae, L},
+ {0x2db0, 0x2db6, L},
+ {0x2db8, 0x2dbe, L},
+ {0x2dc0, 0x2dc6, L},
+ {0x2dc8, 0x2dce, L},
+ {0x2dd0, 0x2dd6, L},
+ {0x2dd8, 0x2dde, L},
+ {0x2de0, 0x2dff, NSM},
+ {0x3000, 0x3000, WS},
+ {0x3005, 0x3007, L},
+ {0x3021, 0x3029, L},
+ {0x302a, 0x302d, NSM},
+ {0x302e, 0x302f, L},
+ {0x3031, 0x3035, L},
+ {0x3038, 0x303c, L},
+ {0x3041, 0x3096, L},
+ {0x3099, 0x309a, NSM},
+ {0x309d, 0x309f, L},
+ {0x30a1, 0x30fa, L},
+ {0x30fc, 0x30ff, L},
+ {0x3105, 0x312f, L},
+ {0x3131, 0x318e, L},
+ {0x3190, 0x31bf, L},
+ {0x31f0, 0x321c, L},
+ {0x3220, 0x324f, L},
+ {0x3260, 0x327b, L},
+ {0x327f, 0x32b0, L},
+ {0x32c0, 0x32cb, L},
+ {0x32d0, 0x3376, L},
+ {0x337b, 0x33dd, L},
+ {0x33e0, 0x33fe, L},
+ {0x3400, 0x4dbf, L},
+ {0x4e00, 0xa48c, L},
+ {0xa4d0, 0xa60c, L},
+ {0xa610, 0xa62b, L},
+ {0xa640, 0xa66e, L},
+ {0xa66f, 0xa672, NSM},
+ {0xa674, 0xa67d, NSM},
+ {0xa680, 0xa69d, L},
+ {0xa69e, 0xa69f, NSM},
+ {0xa6a0, 0xa6ef, L},
+ {0xa6f0, 0xa6f1, NSM},
+ {0xa6f2, 0xa6f7, L},
+ {0xa722, 0xa787, L},
+ {0xa789, 0xa7ca, L},
+ {0xa7d0, 0xa7d1, L},
+ {0xa7d3, 0xa7d3, L},
+ {0xa7d5, 0xa7d9, L},
+ {0xa7f2, 0xa801, L},
+ {0xa802, 0xa802, NSM},
+ {0xa803, 0xa805, L},
+ {0xa806, 0xa806, NSM},
+ {0xa807, 0xa80a, L},
+ {0xa80b, 0xa80b, NSM},
+ {0xa80c, 0xa824, L},
+ {0xa825, 0xa826, NSM},
+ {0xa827, 0xa827, L},
+ {0xa82c, 0xa82c, NSM},
+ {0xa830, 0xa837, L},
+ {0xa838, 0xa839, ET},
+ {0xa840, 0xa873, L},
+ {0xa880, 0xa8c3, L},
+ {0xa8c4, 0xa8c5, NSM},
+ {0xa8ce, 0xa8d9, L},
+ {0xa8e0, 0xa8f1, NSM},
+ {0xa8f2, 0xa8fe, L},
+ {0xa8ff, 0xa8ff, NSM},
+ {0xa900, 0xa925, L},
+ {0xa926, 0xa92d, NSM},
+ {0xa92e, 0xa946, L},
+ {0xa947, 0xa951, NSM},
+ {0xa952, 0xa953, L},
+ {0xa95f, 0xa97c, L},
+ {0xa980, 0xa982, NSM},
+ {0xa983, 0xa9b2, L},
+ {0xa9b3, 0xa9b3, NSM},
+ {0xa9b4, 0xa9b5, L},
+ {0xa9b6, 0xa9b9, NSM},
+ {0xa9ba, 0xa9bb, L},
+ {0xa9bc, 0xa9bd, NSM},
+ {0xa9be, 0xa9cd, L},
+ {0xa9cf, 0xa9d9, L},
+ {0xa9de, 0xa9e4, L},
+ {0xa9e5, 0xa9e5, NSM},
+ {0xa9e6, 0xa9fe, L},
+ {0xaa00, 0xaa28, L},
+ {0xaa29, 0xaa2e, NSM},
+ {0xaa2f, 0xaa30, L},
+ {0xaa31, 0xaa32, NSM},
+ {0xaa33, 0xaa34, L},
+ {0xaa35, 0xaa36, NSM},
+ {0xaa40, 0xaa42, L},
+ {0xaa43, 0xaa43, NSM},
+ {0xaa44, 0xaa4b, L},
+ {0xaa4c, 0xaa4c, NSM},
+ {0xaa4d, 0xaa4d, L},
+ {0xaa50, 0xaa59, L},
+ {0xaa5c, 0xaa7b, L},
+ {0xaa7c, 0xaa7c, NSM},
+ {0xaa7d, 0xaaaf, L},
+ {0xaab0, 0xaab0, NSM},
+ {0xaab1, 0xaab1, L},
+ {0xaab2, 0xaab4, NSM},
+ {0xaab5, 0xaab6, L},
+ {0xaab7, 0xaab8, NSM},
+ {0xaab9, 0xaabd, L},
+ {0xaabe, 0xaabf, NSM},
+ {0xaac0, 0xaac0, L},
+ {0xaac1, 0xaac1, NSM},
+ {0xaac2, 0xaac2, L},
+ {0xaadb, 0xaaeb, L},
+ {0xaaec, 0xaaed, NSM},
+ {0xaaee, 0xaaf5, L},
+ {0xaaf6, 0xaaf6, NSM},
+ {0xab01, 0xab06, L},
+ {0xab09, 0xab0e, L},
+ {0xab11, 0xab16, L},
+ {0xab20, 0xab26, L},
+ {0xab28, 0xab2e, L},
+ {0xab30, 0xab69, L},
+ {0xab70, 0xabe4, L},
+ {0xabe5, 0xabe5, NSM},
+ {0xabe6, 0xabe7, L},
+ {0xabe8, 0xabe8, NSM},
+ {0xabe9, 0xabec, L},
+ {0xabed, 0xabed, NSM},
+ {0xabf0, 0xabf9, L},
+ {0xac00, 0xd7a3, L},
+ {0xd7b0, 0xd7c6, L},
+ {0xd7cb, 0xd7fb, L},
+ {0xd800, 0xfa6d, L},
+ {0xfa70, 0xfad9, L},
+ {0xfb00, 0xfb06, L},
+ {0xfb13, 0xfb17, L},
+ {0xfb1d, 0xfb1d, R},
+ {0xfb1e, 0xfb1e, NSM},
+ {0xfb1f, 0xfb28, R},
+ {0xfb29, 0xfb29, ES},
+ {0xfb2a, 0xfb36, R},
+ {0xfb38, 0xfb3c, R},
+ {0xfb3e, 0xfb3e, R},
+ {0xfb40, 0xfb41, R},
+ {0xfb43, 0xfb44, R},
+ {0xfb46, 0xfb4f, R},
+ {0xfb50, 0xfbc2, AL},
+ {0xfbd3, 0xfd3d, AL},
+ {0xfd50, 0xfd8f, AL},
+ {0xfd92, 0xfdc7, AL},
+ {0xfdf0, 0xfdfc, AL},
+ {0xfe00, 0xfe0f, NSM},
+ {0xfe20, 0xfe2f, NSM},
+ {0xfe50, 0xfe50, CS},
+ {0xfe52, 0xfe52, CS},
+ {0xfe55, 0xfe55, CS},
+ {0xfe5f, 0xfe5f, ET},
+ {0xfe62, 0xfe63, ES},
+ {0xfe69, 0xfe6a, ET},
+ {0xfe70, 0xfe74, AL},
+ {0xfe76, 0xfefc, AL},
+ {0xfeff, 0xfeff, BN},
+ {0xff03, 0xff05, ET},
+ {0xff0b, 0xff0b, ES},
+ {0xff0c, 0xff0c, CS},
+ {0xff0d, 0xff0d, ES},
+ {0xff0e, 0xff0f, CS},
+ {0xff10, 0xff19, EN},
+ {0xff1a, 0xff1a, CS},
+ {0xff21, 0xff3a, L},
+ {0xff41, 0xff5a, L},
+ {0xff66, 0xffbe, L},
+ {0xffc2, 0xffc7, L},
+ {0xffca, 0xffcf, L},
+ {0xffd2, 0xffd7, L},
+ {0xffda, 0xffdc, L},
+ {0xffe0, 0xffe1, ET},
+ {0xffe5, 0xffe6, ET},
+ {0x10000, 0x1000b, L},
+ {0x1000d, 0x10026, L},
+ {0x10028, 0x1003a, L},
+ {0x1003c, 0x1003d, L},
+ {0x1003f, 0x1004d, L},
+ {0x10050, 0x1005d, L},
+ {0x10080, 0x100fa, L},
+ {0x10100, 0x10100, L},
+ {0x10102, 0x10102, L},
+ {0x10107, 0x10133, L},
+ {0x10137, 0x1013f, L},
+ {0x1018d, 0x1018e, L},
+ {0x101d0, 0x101fc, L},
+ {0x101fd, 0x101fd, NSM},
+ {0x10280, 0x1029c, L},
+ {0x102a0, 0x102d0, L},
+ {0x102e0, 0x102e0, NSM},
+ {0x102e1, 0x102fb, EN},
+ {0x10300, 0x10323, L},
+ {0x1032d, 0x1034a, L},
+ {0x10350, 0x10375, L},
+ {0x10376, 0x1037a, NSM},
+ {0x10380, 0x1039d, L},
+ {0x1039f, 0x103c3, L},
+ {0x103c8, 0x103d5, L},
+ {0x10400, 0x1049d, L},
+ {0x104a0, 0x104a9, L},
+ {0x104b0, 0x104d3, L},
+ {0x104d8, 0x104fb, L},
+ {0x10500, 0x10527, L},
+ {0x10530, 0x10563, L},
+ {0x1056f, 0x1057a, L},
+ {0x1057c, 0x1058a, L},
+ {0x1058c, 0x10592, L},
+ {0x10594, 0x10595, L},
+ {0x10597, 0x105a1, L},
+ {0x105a3, 0x105b1, L},
+ {0x105b3, 0x105b9, L},
+ {0x105bb, 0x105bc, L},
+ {0x10600, 0x10736, L},
+ {0x10740, 0x10755, L},
+ {0x10760, 0x10767, L},
+ {0x10780, 0x10785, L},
+ {0x10787, 0x107b0, L},
+ {0x107b2, 0x107ba, L},
+ {0x10800, 0x10805, R},
+ {0x10808, 0x10808, R},
+ {0x1080a, 0x10835, R},
+ {0x10837, 0x10838, R},
+ {0x1083c, 0x1083c, R},
+ {0x1083f, 0x10855, R},
+ {0x10857, 0x1089e, R},
+ {0x108a7, 0x108af, R},
+ {0x108e0, 0x108f2, R},
+ {0x108f4, 0x108f5, R},
+ {0x108fb, 0x1091b, R},
+ {0x10920, 0x10939, R},
+ {0x1093f, 0x1093f, R},
+ {0x10980, 0x109b7, R},
+ {0x109bc, 0x109cf, R},
+ {0x109d2, 0x10a00, R},
+ {0x10a01, 0x10a03, NSM},
+ {0x10a05, 0x10a06, NSM},
+ {0x10a0c, 0x10a0f, NSM},
+ {0x10a10, 0x10a13, R},
+ {0x10a15, 0x10a17, R},
+ {0x10a19, 0x10a35, R},
+ {0x10a38, 0x10a3a, NSM},
+ {0x10a3f, 0x10a3f, NSM},
+ {0x10a40, 0x10a48, R},
+ {0x10a50, 0x10a58, R},
+ {0x10a60, 0x10a9f, R},
+ {0x10ac0, 0x10ae4, R},
+ {0x10ae5, 0x10ae6, NSM},
+ {0x10aeb, 0x10af6, R},
+ {0x10b00, 0x10b35, R},
+ {0x10b40, 0x10b55, R},
+ {0x10b58, 0x10b72, R},
+ {0x10b78, 0x10b91, R},
+ {0x10b99, 0x10b9c, R},
+ {0x10ba9, 0x10baf, R},
+ {0x10c00, 0x10c48, R},
+ {0x10c80, 0x10cb2, R},
+ {0x10cc0, 0x10cf2, R},
+ {0x10cfa, 0x10cff, R},
+ {0x10d00, 0x10d23, AL},
+ {0x10d24, 0x10d27, NSM},
+ {0x10d30, 0x10d39, AN},
+ {0x10e60, 0x10e7e, AN},
+ {0x10e80, 0x10ea9, R},
+ {0x10eab, 0x10eac, NSM},
+ {0x10ead, 0x10ead, R},
+ {0x10eb0, 0x10eb1, R},
+ {0x10f00, 0x10f27, R},
+ {0x10f30, 0x10f45, AL},
+ {0x10f46, 0x10f50, NSM},
+ {0x10f51, 0x10f59, AL},
+ {0x10f70, 0x10f81, R},
+ {0x10f82, 0x10f85, NSM},
+ {0x10f86, 0x10f89, R},
+ {0x10fb0, 0x10fcb, R},
+ {0x10fe0, 0x10ff6, R},
+ {0x11000, 0x11000, L},
+ {0x11001, 0x11001, NSM},
+ {0x11002, 0x11037, L},
+ {0x11038, 0x11046, NSM},
+ {0x11047, 0x1104d, L},
+ {0x11066, 0x1106f, L},
+ {0x11070, 0x11070, NSM},
+ {0x11071, 0x11072, L},
+ {0x11073, 0x11074, NSM},
+ {0x11075, 0x11075, L},
+ {0x1107f, 0x11081, NSM},
+ {0x11082, 0x110b2, L},
+ {0x110b3, 0x110b6, NSM},
+ {0x110b7, 0x110b8, L},
+ {0x110b9, 0x110ba, NSM},
+ {0x110bb, 0x110c1, L},
+ {0x110c2, 0x110c2, NSM},
+ {0x110cd, 0x110cd, L},
+ {0x110d0, 0x110e8, L},
+ {0x110f0, 0x110f9, L},
+ {0x11100, 0x11102, NSM},
+ {0x11103, 0x11126, L},
+ {0x11127, 0x1112b, NSM},
+ {0x1112c, 0x1112c, L},
+ {0x1112d, 0x11134, NSM},
+ {0x11136, 0x11147, L},
+ {0x11150, 0x11172, L},
+ {0x11173, 0x11173, NSM},
+ {0x11174, 0x11176, L},
+ {0x11180, 0x11181, NSM},
+ {0x11182, 0x111b5, L},
+ {0x111b6, 0x111be, NSM},
+ {0x111bf, 0x111c8, L},
+ {0x111c9, 0x111cc, NSM},
+ {0x111cd, 0x111ce, L},
+ {0x111cf, 0x111cf, NSM},
+ {0x111d0, 0x111df, L},
+ {0x111e1, 0x111f4, L},
+ {0x11200, 0x11211, L},
+ {0x11213, 0x1122e, L},
+ {0x1122f, 0x11231, NSM},
+ {0x11232, 0x11233, L},
+ {0x11234, 0x11234, NSM},
+ {0x11235, 0x11235, L},
+ {0x11236, 0x11237, NSM},
+ {0x11238, 0x1123d, L},
+ {0x1123e, 0x1123e, NSM},
+ {0x11280, 0x11286, L},
+ {0x11288, 0x11288, L},
+ {0x1128a, 0x1128d, L},
+ {0x1128f, 0x1129d, L},
+ {0x1129f, 0x112a9, L},
+ {0x112b0, 0x112de, L},
+ {0x112df, 0x112df, NSM},
+ {0x112e0, 0x112e2, L},
+ {0x112e3, 0x112ea, NSM},
+ {0x112f0, 0x112f9, L},
+ {0x11300, 0x11301, NSM},
+ {0x11302, 0x11303, L},
+ {0x11305, 0x1130c, L},
+ {0x1130f, 0x11310, L},
+ {0x11313, 0x11328, L},
+ {0x1132a, 0x11330, L},
+ {0x11332, 0x11333, L},
+ {0x11335, 0x11339, L},
+ {0x1133b, 0x1133c, NSM},
+ {0x1133d, 0x1133f, L},
+ {0x11340, 0x11340, NSM},
+ {0x11341, 0x11344, L},
+ {0x11347, 0x11348, L},
+ {0x1134b, 0x1134d, L},
+ {0x11350, 0x11350, L},
+ {0x11357, 0x11357, L},
+ {0x1135d, 0x11363, L},
+ {0x11366, 0x1136c, NSM},
+ {0x11370, 0x11374, NSM},
+ {0x11400, 0x11437, L},
+ {0x11438, 0x1143f, NSM},
+ {0x11440, 0x11441, L},
+ {0x11442, 0x11444, NSM},
+ {0x11445, 0x11445, L},
+ {0x11446, 0x11446, NSM},
+ {0x11447, 0x1145b, L},
+ {0x1145d, 0x1145d, L},
+ {0x1145e, 0x1145e, NSM},
+ {0x1145f, 0x11461, L},
+ {0x11480, 0x114b2, L},
+ {0x114b3, 0x114b8, NSM},
+ {0x114b9, 0x114b9, L},
+ {0x114ba, 0x114ba, NSM},
+ {0x114bb, 0x114be, L},
+ {0x114bf, 0x114c0, NSM},
+ {0x114c1, 0x114c1, L},
+ {0x114c2, 0x114c3, NSM},
+ {0x114c4, 0x114c7, L},
+ {0x114d0, 0x114d9, L},
+ {0x11580, 0x115b1, L},
+ {0x115b2, 0x115b5, NSM},
+ {0x115b8, 0x115bb, L},
+ {0x115bc, 0x115bd, NSM},
+ {0x115be, 0x115be, L},
+ {0x115bf, 0x115c0, NSM},
+ {0x115c1, 0x115db, L},
+ {0x115dc, 0x115dd, NSM},
+ {0x11600, 0x11632, L},
+ {0x11633, 0x1163a, NSM},
+ {0x1163b, 0x1163c, L},
+ {0x1163d, 0x1163d, NSM},
+ {0x1163e, 0x1163e, L},
+ {0x1163f, 0x11640, NSM},
+ {0x11641, 0x11644, L},
+ {0x11650, 0x11659, L},
+ {0x11680, 0x116aa, L},
+ {0x116ab, 0x116ab, NSM},
+ {0x116ac, 0x116ac, L},
+ {0x116ad, 0x116ad, NSM},
+ {0x116ae, 0x116af, L},
+ {0x116b0, 0x116b5, NSM},
+ {0x116b6, 0x116b6, L},
+ {0x116b7, 0x116b7, NSM},
+ {0x116b8, 0x116b9, L},
+ {0x116c0, 0x116c9, L},
+ {0x11700, 0x1171a, L},
+ {0x1171d, 0x1171f, NSM},
+ {0x11720, 0x11721, L},
+ {0x11722, 0x11725, NSM},
+ {0x11726, 0x11726, L},
+ {0x11727, 0x1172b, NSM},
+ {0x11730, 0x11746, L},
+ {0x11800, 0x1182e, L},
+ {0x1182f, 0x11837, NSM},
+ {0x11838, 0x11838, L},
+ {0x11839, 0x1183a, NSM},
+ {0x1183b, 0x1183b, L},
+ {0x118a0, 0x118f2, L},
+ {0x118ff, 0x11906, L},
+ {0x11909, 0x11909, L},
+ {0x1190c, 0x11913, L},
+ {0x11915, 0x11916, L},
+ {0x11918, 0x11935, L},
+ {0x11937, 0x11938, L},
+ {0x1193b, 0x1193c, NSM},
+ {0x1193d, 0x1193d, L},
+ {0x1193e, 0x1193e, NSM},
+ {0x1193f, 0x11942, L},
+ {0x11943, 0x11943, NSM},
+ {0x11944, 0x11946, L},
+ {0x11950, 0x11959, L},
+ {0x119a0, 0x119a7, L},
+ {0x119aa, 0x119d3, L},
+ {0x119d4, 0x119d7, NSM},
+ {0x119da, 0x119db, NSM},
+ {0x119dc, 0x119df, L},
+ {0x119e0, 0x119e0, NSM},
+ {0x119e1, 0x119e4, L},
+ {0x11a00, 0x11a00, L},
+ {0x11a01, 0x11a06, NSM},
+ {0x11a07, 0x11a08, L},
+ {0x11a09, 0x11a0a, NSM},
+ {0x11a0b, 0x11a32, L},
+ {0x11a33, 0x11a38, NSM},
+ {0x11a39, 0x11a3a, L},
+ {0x11a3b, 0x11a3e, NSM},
+ {0x11a3f, 0x11a46, L},
+ {0x11a47, 0x11a47, NSM},
+ {0x11a50, 0x11a50, L},
+ {0x11a51, 0x11a56, NSM},
+ {0x11a57, 0x11a58, L},
+ {0x11a59, 0x11a5b, NSM},
+ {0x11a5c, 0x11a89, L},
+ {0x11a8a, 0x11a96, NSM},
+ {0x11a97, 0x11a97, L},
+ {0x11a98, 0x11a99, NSM},
+ {0x11a9a, 0x11aa2, L},
+ {0x11ab0, 0x11af8, L},
+ {0x11c00, 0x11c08, L},
+ {0x11c0a, 0x11c2f, L},
+ {0x11c30, 0x11c36, NSM},
+ {0x11c38, 0x11c3d, NSM},
+ {0x11c3e, 0x11c45, L},
+ {0x11c50, 0x11c6c, L},
+ {0x11c70, 0x11c8f, L},
+ {0x11c92, 0x11ca7, NSM},
+ {0x11ca9, 0x11ca9, L},
+ {0x11caa, 0x11cb0, NSM},
+ {0x11cb1, 0x11cb1, L},
+ {0x11cb2, 0x11cb3, NSM},
+ {0x11cb4, 0x11cb4, L},
+ {0x11cb5, 0x11cb6, NSM},
+ {0x11d00, 0x11d06, L},
+ {0x11d08, 0x11d09, L},
+ {0x11d0b, 0x11d30, L},
+ {0x11d31, 0x11d36, NSM},
+ {0x11d3a, 0x11d3a, NSM},
+ {0x11d3c, 0x11d3d, NSM},
+ {0x11d3f, 0x11d45, NSM},
+ {0x11d46, 0x11d46, L},
+ {0x11d47, 0x11d47, NSM},
+ {0x11d50, 0x11d59, L},
+ {0x11d60, 0x11d65, L},
+ {0x11d67, 0x11d68, L},
+ {0x11d6a, 0x11d8e, L},
+ {0x11d90, 0x11d91, NSM},
+ {0x11d93, 0x11d94, L},
+ {0x11d95, 0x11d95, NSM},
+ {0x11d96, 0x11d96, L},
+ {0x11d97, 0x11d97, NSM},
+ {0x11d98, 0x11d98, L},
+ {0x11da0, 0x11da9, L},
+ {0x11ee0, 0x11ef2, L},
+ {0x11ef3, 0x11ef4, NSM},
+ {0x11ef5, 0x11ef8, L},
+ {0x11fb0, 0x11fb0, L},
+ {0x11fc0, 0x11fd4, L},
+ {0x11fdd, 0x11fe0, ET},
+ {0x11fff, 0x12399, L},
+ {0x12400, 0x1246e, L},
+ {0x12470, 0x12474, L},
+ {0x12480, 0x12543, L},
+ {0x12f90, 0x12ff2, L},
+ {0x13000, 0x1342e, L},
+ {0x13430, 0x13438, L},
+ {0x14400, 0x14646, L},
+ {0x16800, 0x16a38, L},
+ {0x16a40, 0x16a5e, L},
+ {0x16a60, 0x16a69, L},
+ {0x16a6e, 0x16abe, L},
+ {0x16ac0, 0x16ac9, L},
+ {0x16ad0, 0x16aed, L},
+ {0x16af0, 0x16af4, NSM},
+ {0x16af5, 0x16af5, L},
+ {0x16b00, 0x16b2f, L},
+ {0x16b30, 0x16b36, NSM},
+ {0x16b37, 0x16b45, L},
+ {0x16b50, 0x16b59, L},
+ {0x16b5b, 0x16b61, L},
+ {0x16b63, 0x16b77, L},
+ {0x16b7d, 0x16b8f, L},
+ {0x16e40, 0x16e9a, L},
+ {0x16f00, 0x16f4a, L},
+ {0x16f4f, 0x16f4f, NSM},
+ {0x16f50, 0x16f87, L},
+ {0x16f8f, 0x16f92, NSM},
+ {0x16f93, 0x16f9f, L},
+ {0x16fe0, 0x16fe1, L},
+ {0x16fe3, 0x16fe3, L},
+ {0x16fe4, 0x16fe4, NSM},
+ {0x16ff0, 0x16ff1, L},
+ {0x17000, 0x187f7, L},
+ {0x18800, 0x18cd5, L},
+ {0x18d00, 0x18d08, L},
+ {0x1aff0, 0x1aff3, L},
+ {0x1aff5, 0x1affb, L},
+ {0x1affd, 0x1affe, L},
+ {0x1b000, 0x1b122, L},
+ {0x1b150, 0x1b152, L},
+ {0x1b164, 0x1b167, L},
+ {0x1b170, 0x1b2fb, L},
+ {0x1bc00, 0x1bc6a, L},
+ {0x1bc70, 0x1bc7c, L},
+ {0x1bc80, 0x1bc88, L},
+ {0x1bc90, 0x1bc99, L},
+ {0x1bc9c, 0x1bc9c, L},
+ {0x1bc9d, 0x1bc9e, NSM},
+ {0x1bc9f, 0x1bc9f, L},
+ {0x1bca0, 0x1bca3, BN},
+ {0x1cf00, 0x1cf2d, NSM},
+ {0x1cf30, 0x1cf46, NSM},
+ {0x1cf50, 0x1cfc3, L},
+ {0x1d000, 0x1d0f5, L},
+ {0x1d100, 0x1d126, L},
+ {0x1d129, 0x1d166, L},
+ {0x1d167, 0x1d169, NSM},
+ {0x1d16a, 0x1d172, L},
+ {0x1d173, 0x1d17a, BN},
+ {0x1d17b, 0x1d182, NSM},
+ {0x1d183, 0x1d184, L},
+ {0x1d185, 0x1d18b, NSM},
+ {0x1d18c, 0x1d1a9, L},
+ {0x1d1aa, 0x1d1ad, NSM},
+ {0x1d1ae, 0x1d1e8, L},
+ {0x1d242, 0x1d244, NSM},
+ {0x1d2e0, 0x1d2f3, L},
+ {0x1d360, 0x1d378, L},
+ {0x1d400, 0x1d454, L},
+ {0x1d456, 0x1d49c, L},
+ {0x1d49e, 0x1d49f, L},
+ {0x1d4a2, 0x1d4a2, L},
+ {0x1d4a5, 0x1d4a6, L},
+ {0x1d4a9, 0x1d4ac, L},
+ {0x1d4ae, 0x1d4b9, L},
+ {0x1d4bb, 0x1d4bb, L},
+ {0x1d4bd, 0x1d4c3, L},
+ {0x1d4c5, 0x1d505, L},
+ {0x1d507, 0x1d50a, L},
+ {0x1d50d, 0x1d514, L},
+ {0x1d516, 0x1d51c, L},
+ {0x1d51e, 0x1d539, L},
+ {0x1d53b, 0x1d53e, L},
+ {0x1d540, 0x1d544, L},
+ {0x1d546, 0x1d546, L},
+ {0x1d54a, 0x1d550, L},
+ {0x1d552, 0x1d6a5, L},
+ {0x1d6a8, 0x1d6da, L},
+ {0x1d6dc, 0x1d714, L},
+ {0x1d716, 0x1d74e, L},
+ {0x1d750, 0x1d788, L},
+ {0x1d78a, 0x1d7c2, L},
+ {0x1d7c4, 0x1d7cb, L},
+ {0x1d7ce, 0x1d7ff, EN},
+ {0x1d800, 0x1d9ff, L},
+ {0x1da00, 0x1da36, NSM},
+ {0x1da37, 0x1da3a, L},
+ {0x1da3b, 0x1da6c, NSM},
+ {0x1da6d, 0x1da74, L},
+ {0x1da75, 0x1da75, NSM},
+ {0x1da76, 0x1da83, L},
+ {0x1da84, 0x1da84, NSM},
+ {0x1da85, 0x1da8b, L},
+ {0x1da9b, 0x1da9f, NSM},
+ {0x1daa1, 0x1daaf, NSM},
+ {0x1df00, 0x1df1e, L},
+ {0x1e000, 0x1e006, NSM},
+ {0x1e008, 0x1e018, NSM},
+ {0x1e01b, 0x1e021, NSM},
+ {0x1e023, 0x1e024, NSM},
+ {0x1e026, 0x1e02a, NSM},
+ {0x1e100, 0x1e12c, L},
+ {0x1e130, 0x1e136, NSM},
+ {0x1e137, 0x1e13d, L},
+ {0x1e140, 0x1e149, L},
+ {0x1e14e, 0x1e14f, L},
+ {0x1e290, 0x1e2ad, L},
+ {0x1e2ae, 0x1e2ae, NSM},
+ {0x1e2c0, 0x1e2eb, L},
+ {0x1e2ec, 0x1e2ef, NSM},
+ {0x1e2f0, 0x1e2f9, L},
+ {0x1e2ff, 0x1e2ff, ET},
+ {0x1e7e0, 0x1e7e6, L},
+ {0x1e7e8, 0x1e7eb, L},
+ {0x1e7ed, 0x1e7ee, L},
+ {0x1e7f0, 0x1e7fe, L},
+ {0x1e800, 0x1e8c4, R},
+ {0x1e8c7, 0x1e8cf, R},
+ {0x1e8d0, 0x1e8d6, NSM},
+ {0x1e900, 0x1e943, R},
+ {0x1e944, 0x1e94a, NSM},
+ {0x1e94b, 0x1e94b, R},
+ {0x1e950, 0x1e959, R},
+ {0x1e95e, 0x1e95f, R},
+ {0x1ec71, 0x1ecb4, AL},
+ {0x1ed01, 0x1ed3d, AL},
+ {0x1ee00, 0x1ee03, AL},
+ {0x1ee05, 0x1ee1f, AL},
+ {0x1ee21, 0x1ee22, AL},
+ {0x1ee24, 0x1ee24, AL},
+ {0x1ee27, 0x1ee27, AL},
+ {0x1ee29, 0x1ee32, AL},
+ {0x1ee34, 0x1ee37, AL},
+ {0x1ee39, 0x1ee39, AL},
+ {0x1ee3b, 0x1ee3b, AL},
+ {0x1ee42, 0x1ee42, AL},
+ {0x1ee47, 0x1ee47, AL},
+ {0x1ee49, 0x1ee49, AL},
+ {0x1ee4b, 0x1ee4b, AL},
+ {0x1ee4d, 0x1ee4f, AL},
+ {0x1ee51, 0x1ee52, AL},
+ {0x1ee54, 0x1ee54, AL},
+ {0x1ee57, 0x1ee57, AL},
+ {0x1ee59, 0x1ee59, AL},
+ {0x1ee5b, 0x1ee5b, AL},
+ {0x1ee5d, 0x1ee5d, AL},
+ {0x1ee5f, 0x1ee5f, AL},
+ {0x1ee61, 0x1ee62, AL},
+ {0x1ee64, 0x1ee64, AL},
+ {0x1ee67, 0x1ee6a, AL},
+ {0x1ee6c, 0x1ee72, AL},
+ {0x1ee74, 0x1ee77, AL},
+ {0x1ee79, 0x1ee7c, AL},
+ {0x1ee7e, 0x1ee7e, AL},
+ {0x1ee80, 0x1ee89, AL},
+ {0x1ee8b, 0x1ee9b, AL},
+ {0x1eea1, 0x1eea3, AL},
+ {0x1eea5, 0x1eea9, AL},
+ {0x1eeab, 0x1eebb, AL},
+ {0x1f100, 0x1f10a, EN},
+ {0x1f110, 0x1f12e, L},
+ {0x1f130, 0x1f169, L},
+ {0x1f170, 0x1f1ac, L},
+ {0x1f1e6, 0x1f202, L},
+ {0x1f210, 0x1f23b, L},
+ {0x1f240, 0x1f248, L},
+ {0x1f250, 0x1f251, L},
+ {0x1fbf0, 0x1fbf9, EN},
+ {0x20000, 0x2a6df, L},
+ {0x2a700, 0x2b738, L},
+ {0x2b740, 0x2b81d, L},
+ {0x2b820, 0x2cea1, L},
+ {0x2ceb0, 0x2ebe0, L},
+ {0x2f800, 0x2fa1d, L},
+ {0x30000, 0x3134a, L},
+ {0xe0001, 0xe0001, BN},
+ {0xe0020, 0xe007f, BN},
+ {0xe0100, 0xe01ef, NSM},
+ {0xf0000, 0xffffd, L},
+ {0x100000, 0x10fffd, L},
+ };
+
+ int i, j, k;
+
+ i = -1;
+ j = lenof(lookup);
+
+ while (j - i > 1) {
+ k = (i + j) / 2;
+ if (ch < lookup[k].first)
+ j = k;
+ else if (ch > lookup[k].last)
+ i = k;
+ else
+ return lookup[k].type;
+ }
+
+ /*
+ * If we reach here, the character was not in any of the
+ * intervals listed in the lookup table. This means we return
+ * ON (`Other Neutrals'). This is the appropriate code for any
+ * character genuinely not listed in the Unicode table, and
+ * also the table above has deliberately left out any
+ * characters _explicitly_ listed as ON (to save space!).
+ */
+ return ON;
+}
+
+/*
+ * Return the mirrored version of a glyph.
+
+ * The data table in this function is constructed from the Unicode
+ * Character Database version 14.0.0, downloadable from unicode.org at
+ * the URL
+ *
+ * https://www.unicode.org/Public/14.0.0/ucd/
+ *
+ * by the following fragment of Perl:
+
+perl -e '
+ while (<<>>) {
+ chomp; s{\s}{}g; s{#.*$}{}; next unless /./;
+ @_ = split /;/, $_;
+ $src = hex $_[0]; $dst = hex $_[1];
+ $m{$src}=$dst; $m{$dst}=$src;
+ }
+ for $src (sort {$a <=> $b} keys %m) {
+ printf " {0x%04x, 0x%04x},\n", $src, $m{$src};
+ }
+' BidiMirroring.txt
+
+ *
+ * FIXME: there are also glyphs which the text rendering engine is
+ * supposed to display left-right reflected, since no mirrored glyph
+ * exists in Unicode itself to indicate the reflected form. Those are
+ * listed in comments in BidiMirroring.txt. Many of them are
+ * mathematical, e.g. the square root sign, or set difference
+ * operator, or integral sign. No API currently exists here to
+ * communicate the need for that reflected display back to the client.
+ */
+static unsigned mirror_glyph(unsigned int ch)
+{
+ static const struct {
+ unsigned src, dst;
+ } mirror_pairs[] = {
+ {0x0028, 0x0029},
+ {0x0029, 0x0028},
+ {0x003c, 0x003e},
+ {0x003e, 0x003c},
+ {0x005b, 0x005d},
+ {0x005d, 0x005b},
+ {0x007b, 0x007d},
+ {0x007d, 0x007b},
+ {0x00ab, 0x00bb},
+ {0x00bb, 0x00ab},
+ {0x0f3a, 0x0f3b},
+ {0x0f3b, 0x0f3a},
+ {0x0f3c, 0x0f3d},
+ {0x0f3d, 0x0f3c},
+ {0x169b, 0x169c},
+ {0x169c, 0x169b},
+ {0x2039, 0x203a},
+ {0x203a, 0x2039},
+ {0x2045, 0x2046},
+ {0x2046, 0x2045},
+ {0x207d, 0x207e},
+ {0x207e, 0x207d},
+ {0x208d, 0x208e},
+ {0x208e, 0x208d},
+ {0x2208, 0x220b},
+ {0x2209, 0x220c},
+ {0x220a, 0x220d},
+ {0x220b, 0x2208},
+ {0x220c, 0x2209},
+ {0x220d, 0x220a},
+ {0x2215, 0x29f5},
+ {0x221f, 0x2bfe},
+ {0x2220, 0x29a3},
+ {0x2221, 0x299b},
+ {0x2222, 0x29a0},
+ {0x2224, 0x2aee},
+ {0x223c, 0x223d},
+ {0x223d, 0x223c},
+ {0x2243, 0x22cd},
+ {0x2245, 0x224c},
+ {0x224c, 0x2245},
+ {0x2252, 0x2253},
+ {0x2253, 0x2252},
+ {0x2254, 0x2255},
+ {0x2255, 0x2254},
+ {0x2264, 0x2265},
+ {0x2265, 0x2264},
+ {0x2266, 0x2267},
+ {0x2267, 0x2266},
+ {0x2268, 0x2269},
+ {0x2269, 0x2268},
+ {0x226a, 0x226b},
+ {0x226b, 0x226a},
+ {0x226e, 0x226f},
+ {0x226f, 0x226e},
+ {0x2270, 0x2271},
+ {0x2271, 0x2270},
+ {0x2272, 0x2273},
+ {0x2273, 0x2272},
+ {0x2274, 0x2275},
+ {0x2275, 0x2274},
+ {0x2276, 0x2277},
+ {0x2277, 0x2276},
+ {0x2278, 0x2279},
+ {0x2279, 0x2278},
+ {0x227a, 0x227b},
+ {0x227b, 0x227a},
+ {0x227c, 0x227d},
+ {0x227d, 0x227c},
+ {0x227e, 0x227f},
+ {0x227f, 0x227e},
+ {0x2280, 0x2281},
+ {0x2281, 0x2280},
+ {0x2282, 0x2283},
+ {0x2283, 0x2282},
+ {0x2284, 0x2285},
+ {0x2285, 0x2284},
+ {0x2286, 0x2287},
+ {0x2287, 0x2286},
+ {0x2288, 0x2289},
+ {0x2289, 0x2288},
+ {0x228a, 0x228b},
+ {0x228b, 0x228a},
+ {0x228f, 0x2290},
+ {0x2290, 0x228f},
+ {0x2291, 0x2292},
+ {0x2292, 0x2291},
+ {0x2298, 0x29b8},
+ {0x22a2, 0x22a3},
+ {0x22a3, 0x22a2},
+ {0x22a6, 0x2ade},
+ {0x22a8, 0x2ae4},
+ {0x22a9, 0x2ae3},
+ {0x22ab, 0x2ae5},
+ {0x22b0, 0x22b1},
+ {0x22b1, 0x22b0},
+ {0x22b2, 0x22b3},
+ {0x22b3, 0x22b2},
+ {0x22b4, 0x22b5},
+ {0x22b5, 0x22b4},
+ {0x22b6, 0x22b7},
+ {0x22b7, 0x22b6},
+ {0x22b8, 0x27dc},
+ {0x22c9, 0x22ca},
+ {0x22ca, 0x22c9},
+ {0x22cb, 0x22cc},
+ {0x22cc, 0x22cb},
+ {0x22cd, 0x2243},
+ {0x22d0, 0x22d1},
+ {0x22d1, 0x22d0},
+ {0x22d6, 0x22d7},
+ {0x22d7, 0x22d6},
+ {0x22d8, 0x22d9},
+ {0x22d9, 0x22d8},
+ {0x22da, 0x22db},
+ {0x22db, 0x22da},
+ {0x22dc, 0x22dd},
+ {0x22dd, 0x22dc},
+ {0x22de, 0x22df},
+ {0x22df, 0x22de},
+ {0x22e0, 0x22e1},
+ {0x22e1, 0x22e0},
+ {0x22e2, 0x22e3},
+ {0x22e3, 0x22e2},
+ {0x22e4, 0x22e5},
+ {0x22e5, 0x22e4},
+ {0x22e6, 0x22e7},
+ {0x22e7, 0x22e6},
+ {0x22e8, 0x22e9},
+ {0x22e9, 0x22e8},
+ {0x22ea, 0x22eb},
+ {0x22eb, 0x22ea},
+ {0x22ec, 0x22ed},
+ {0x22ed, 0x22ec},
+ {0x22f0, 0x22f1},
+ {0x22f1, 0x22f0},
+ {0x22f2, 0x22fa},
+ {0x22f3, 0x22fb},
+ {0x22f4, 0x22fc},
+ {0x22f6, 0x22fd},
+ {0x22f7, 0x22fe},
+ {0x22fa, 0x22f2},
+ {0x22fb, 0x22f3},
+ {0x22fc, 0x22f4},
+ {0x22fd, 0x22f6},
+ {0x22fe, 0x22f7},
+ {0x2308, 0x2309},
+ {0x2309, 0x2308},
+ {0x230a, 0x230b},
+ {0x230b, 0x230a},
+ {0x2329, 0x232a},
+ {0x232a, 0x2329},
+ {0x2768, 0x2769},
+ {0x2769, 0x2768},
+ {0x276a, 0x276b},
+ {0x276b, 0x276a},
+ {0x276c, 0x276d},
+ {0x276d, 0x276c},
+ {0x276e, 0x276f},
+ {0x276f, 0x276e},
+ {0x2770, 0x2771},
+ {0x2771, 0x2770},
+ {0x2772, 0x2773},
+ {0x2773, 0x2772},
+ {0x2774, 0x2775},
+ {0x2775, 0x2774},
+ {0x27c3, 0x27c4},
+ {0x27c4, 0x27c3},
+ {0x27c5, 0x27c6},
+ {0x27c6, 0x27c5},
+ {0x27c8, 0x27c9},
+ {0x27c9, 0x27c8},
+ {0x27cb, 0x27cd},
+ {0x27cd, 0x27cb},
+ {0x27d5, 0x27d6},
+ {0x27d6, 0x27d5},
+ {0x27dc, 0x22b8},
+ {0x27dd, 0x27de},
+ {0x27de, 0x27dd},
+ {0x27e2, 0x27e3},
+ {0x27e3, 0x27e2},
+ {0x27e4, 0x27e5},
+ {0x27e5, 0x27e4},
+ {0x27e6, 0x27e7},
+ {0x27e7, 0x27e6},
+ {0x27e8, 0x27e9},
+ {0x27e9, 0x27e8},
+ {0x27ea, 0x27eb},
+ {0x27eb, 0x27ea},
+ {0x27ec, 0x27ed},
+ {0x27ed, 0x27ec},
+ {0x27ee, 0x27ef},
+ {0x27ef, 0x27ee},
+ {0x2983, 0x2984},
+ {0x2984, 0x2983},
+ {0x2985, 0x2986},
+ {0x2986, 0x2985},
+ {0x2987, 0x2988},
+ {0x2988, 0x2987},
+ {0x2989, 0x298a},
+ {0x298a, 0x2989},
+ {0x298b, 0x298c},
+ {0x298c, 0x298b},
+ {0x298d, 0x2990},
+ {0x298e, 0x298f},
+ {0x298f, 0x298e},
+ {0x2990, 0x298d},
+ {0x2991, 0x2992},
+ {0x2992, 0x2991},
+ {0x2993, 0x2994},
+ {0x2994, 0x2993},
+ {0x2995, 0x2996},
+ {0x2996, 0x2995},
+ {0x2997, 0x2998},
+ {0x2998, 0x2997},
+ {0x299b, 0x2221},
+ {0x29a0, 0x2222},
+ {0x29a3, 0x2220},
+ {0x29a4, 0x29a5},
+ {0x29a5, 0x29a4},
+ {0x29a8, 0x29a9},
+ {0x29a9, 0x29a8},
+ {0x29aa, 0x29ab},
+ {0x29ab, 0x29aa},
+ {0x29ac, 0x29ad},
+ {0x29ad, 0x29ac},
+ {0x29ae, 0x29af},
+ {0x29af, 0x29ae},
+ {0x29b8, 0x2298},
+ {0x29c0, 0x29c1},
+ {0x29c1, 0x29c0},
+ {0x29c4, 0x29c5},
+ {0x29c5, 0x29c4},
+ {0x29cf, 0x29d0},
+ {0x29d0, 0x29cf},
+ {0x29d1, 0x29d2},
+ {0x29d2, 0x29d1},
+ {0x29d4, 0x29d5},
+ {0x29d5, 0x29d4},
+ {0x29d8, 0x29d9},
+ {0x29d9, 0x29d8},
+ {0x29da, 0x29db},
+ {0x29db, 0x29da},
+ {0x29e8, 0x29e9},
+ {0x29e9, 0x29e8},
+ {0x29f5, 0x2215},
+ {0x29f8, 0x29f9},
+ {0x29f9, 0x29f8},
+ {0x29fc, 0x29fd},
+ {0x29fd, 0x29fc},
+ {0x2a2b, 0x2a2c},
+ {0x2a2c, 0x2a2b},
+ {0x2a2d, 0x2a2e},
+ {0x2a2e, 0x2a2d},
+ {0x2a34, 0x2a35},
+ {0x2a35, 0x2a34},
+ {0x2a3c, 0x2a3d},
+ {0x2a3d, 0x2a3c},
+ {0x2a64, 0x2a65},
+ {0x2a65, 0x2a64},
+ {0x2a79, 0x2a7a},
+ {0x2a7a, 0x2a79},
+ {0x2a7b, 0x2a7c},
+ {0x2a7c, 0x2a7b},
+ {0x2a7d, 0x2a7e},
+ {0x2a7e, 0x2a7d},
+ {0x2a7f, 0x2a80},
+ {0x2a80, 0x2a7f},
+ {0x2a81, 0x2a82},
+ {0x2a82, 0x2a81},
+ {0x2a83, 0x2a84},
+ {0x2a84, 0x2a83},
+ {0x2a85, 0x2a86},
+ {0x2a86, 0x2a85},
+ {0x2a87, 0x2a88},
+ {0x2a88, 0x2a87},
+ {0x2a89, 0x2a8a},
+ {0x2a8a, 0x2a89},
+ {0x2a8b, 0x2a8c},
+ {0x2a8c, 0x2a8b},
+ {0x2a8d, 0x2a8e},
+ {0x2a8e, 0x2a8d},
+ {0x2a8f, 0x2a90},
+ {0x2a90, 0x2a8f},
+ {0x2a91, 0x2a92},
+ {0x2a92, 0x2a91},
+ {0x2a93, 0x2a94},
+ {0x2a94, 0x2a93},
+ {0x2a95, 0x2a96},
+ {0x2a96, 0x2a95},
+ {0x2a97, 0x2a98},
+ {0x2a98, 0x2a97},
+ {0x2a99, 0x2a9a},
+ {0x2a9a, 0x2a99},
+ {0x2a9b, 0x2a9c},
+ {0x2a9c, 0x2a9b},
+ {0x2a9d, 0x2a9e},
+ {0x2a9e, 0x2a9d},
+ {0x2a9f, 0x2aa0},
+ {0x2aa0, 0x2a9f},
+ {0x2aa1, 0x2aa2},
+ {0x2aa2, 0x2aa1},
+ {0x2aa6, 0x2aa7},
+ {0x2aa7, 0x2aa6},
+ {0x2aa8, 0x2aa9},
+ {0x2aa9, 0x2aa8},
+ {0x2aaa, 0x2aab},
+ {0x2aab, 0x2aaa},
+ {0x2aac, 0x2aad},
+ {0x2aad, 0x2aac},
+ {0x2aaf, 0x2ab0},
+ {0x2ab0, 0x2aaf},
+ {0x2ab1, 0x2ab2},
+ {0x2ab2, 0x2ab1},
+ {0x2ab3, 0x2ab4},
+ {0x2ab4, 0x2ab3},
+ {0x2ab5, 0x2ab6},
+ {0x2ab6, 0x2ab5},
+ {0x2ab7, 0x2ab8},
+ {0x2ab8, 0x2ab7},
+ {0x2ab9, 0x2aba},
+ {0x2aba, 0x2ab9},
+ {0x2abb, 0x2abc},
+ {0x2abc, 0x2abb},
+ {0x2abd, 0x2abe},
+ {0x2abe, 0x2abd},
+ {0x2abf, 0x2ac0},
+ {0x2ac0, 0x2abf},
+ {0x2ac1, 0x2ac2},
+ {0x2ac2, 0x2ac1},
+ {0x2ac3, 0x2ac4},
+ {0x2ac4, 0x2ac3},
+ {0x2ac5, 0x2ac6},
+ {0x2ac6, 0x2ac5},
+ {0x2ac7, 0x2ac8},
+ {0x2ac8, 0x2ac7},
+ {0x2ac9, 0x2aca},
+ {0x2aca, 0x2ac9},
+ {0x2acb, 0x2acc},
+ {0x2acc, 0x2acb},
+ {0x2acd, 0x2ace},
+ {0x2ace, 0x2acd},
+ {0x2acf, 0x2ad0},
+ {0x2ad0, 0x2acf},
+ {0x2ad1, 0x2ad2},
+ {0x2ad2, 0x2ad1},
+ {0x2ad3, 0x2ad4},
+ {0x2ad4, 0x2ad3},
+ {0x2ad5, 0x2ad6},
+ {0x2ad6, 0x2ad5},
+ {0x2ade, 0x22a6},
+ {0x2ae3, 0x22a9},
+ {0x2ae4, 0x22a8},
+ {0x2ae5, 0x22ab},
+ {0x2aec, 0x2aed},
+ {0x2aed, 0x2aec},
+ {0x2aee, 0x2224},
+ {0x2af7, 0x2af8},
+ {0x2af8, 0x2af7},
+ {0x2af9, 0x2afa},
+ {0x2afa, 0x2af9},
+ {0x2bfe, 0x221f},
+ {0x2e02, 0x2e03},
+ {0x2e03, 0x2e02},
+ {0x2e04, 0x2e05},
+ {0x2e05, 0x2e04},
+ {0x2e09, 0x2e0a},
+ {0x2e0a, 0x2e09},
+ {0x2e0c, 0x2e0d},
+ {0x2e0d, 0x2e0c},
+ {0x2e1c, 0x2e1d},
+ {0x2e1d, 0x2e1c},
+ {0x2e20, 0x2e21},
+ {0x2e21, 0x2e20},
+ {0x2e22, 0x2e23},
+ {0x2e23, 0x2e22},
+ {0x2e24, 0x2e25},
+ {0x2e25, 0x2e24},
+ {0x2e26, 0x2e27},
+ {0x2e27, 0x2e26},
+ {0x2e28, 0x2e29},
+ {0x2e29, 0x2e28},
+ {0x2e55, 0x2e56},
+ {0x2e56, 0x2e55},
+ {0x2e57, 0x2e58},
+ {0x2e58, 0x2e57},
+ {0x2e59, 0x2e5a},
+ {0x2e5a, 0x2e59},
+ {0x2e5b, 0x2e5c},
+ {0x2e5c, 0x2e5b},
+ {0x3008, 0x3009},
+ {0x3009, 0x3008},
+ {0x300a, 0x300b},
+ {0x300b, 0x300a},
+ {0x300c, 0x300d},
+ {0x300d, 0x300c},
+ {0x300e, 0x300f},
+ {0x300f, 0x300e},
+ {0x3010, 0x3011},
+ {0x3011, 0x3010},
+ {0x3014, 0x3015},
+ {0x3015, 0x3014},
+ {0x3016, 0x3017},
+ {0x3017, 0x3016},
+ {0x3018, 0x3019},
+ {0x3019, 0x3018},
+ {0x301a, 0x301b},
+ {0x301b, 0x301a},
+ {0xfe59, 0xfe5a},
+ {0xfe5a, 0xfe59},
+ {0xfe5b, 0xfe5c},
+ {0xfe5c, 0xfe5b},
+ {0xfe5d, 0xfe5e},
+ {0xfe5e, 0xfe5d},
+ {0xfe64, 0xfe65},
+ {0xfe65, 0xfe64},
+ {0xff08, 0xff09},
+ {0xff09, 0xff08},
+ {0xff1c, 0xff1e},
+ {0xff1e, 0xff1c},
+ {0xff3b, 0xff3d},
+ {0xff3d, 0xff3b},
+ {0xff5b, 0xff5d},
+ {0xff5d, 0xff5b},
+ {0xff5f, 0xff60},
+ {0xff60, 0xff5f},
+ {0xff62, 0xff63},
+ {0xff63, 0xff62},
+ };
+
+ int i, j, k;
+
+ i = -1;
+ j = lenof(mirror_pairs);
+
+ while (j - i > 1) {
+ k = (i + j) / 2;
+ if (ch < mirror_pairs[k].src)
+ j = k;
+ else if (ch > mirror_pairs[k].src)
+ i = k;
+ else
+ return mirror_pairs[k].dst;
+ }
+
+ return ch;
+}
+
+/*
+ * Identify the bracket characters treated specially by bidi rule
+ * BD19, and return their paired character(s).
+ *
+ * The data table in this function is constructed from the Unicode
+ * Character Database version 14.0.0, downloadable from unicode.org at
+ * the URL
+ *
+ * https://www.unicode.org/Public/14.0.0/ucd/
+ *
+ * by the following fragment of Perl:
+
+perl -e '
+ open BIDIBRACKETS, "<", $ARGV[0] or die;
+ while (<BIDIBRACKETS>) {
+ chomp; s{\s}{}g; s{#.*$}{}; next unless /./;
+ @_ = split /;/, $_;
+ $src = hex $_[0]; $dst = hex $_[1]; $kind = $_[2];
+ $m{$src}=[$kind, $dst];
+ }
+ open UNICODEDATA, "<", $ARGV[1] or die;
+ while (<UNICODEDATA>) {
+ chomp; @_ = split /;/, $_;
+ $src = hex $_[0]; next unless defined $m{$src};
+ if ($_[5] =~ /^[0-9a-f]+$/i) {
+ $equiv = hex $_[5];
+ $e{$src} = $equiv;
+ $e{$equiv} = $src;
+ }
+ }
+ for $src (sort {$a <=> $b} keys %m) {
+ ($kind, $dst) = @{$m{$src}};
+ $equiv = 0 + $e{$dst};
+ printf " {0x%04x, {0x%04x, 0x%04x, %s}},\n", $src, $dst, $equiv,
+ $kind eq "c" ? "BT_CLOSE" : "BT_OPEN";
+ }
+' BidiBrackets.txt UnicodeData.txt
+
+ */
+typedef enum { BT_NONE, BT_OPEN, BT_CLOSE } BracketType;
+typedef struct BracketTypeData {
+ unsigned partner, equiv_partner;
+ BracketType type;
+} BracketTypeData;
+static BracketTypeData bracket_type(unsigned int ch)
+{
+ static const struct {
+ unsigned src;
+ BracketTypeData payload;
+ } bracket_pairs[] = {
+ {0x0028, {0x0029, 0x0000, BT_OPEN}},
+ {0x0029, {0x0028, 0x0000, BT_CLOSE}},
+ {0x005b, {0x005d, 0x0000, BT_OPEN}},
+ {0x005d, {0x005b, 0x0000, BT_CLOSE}},
+ {0x007b, {0x007d, 0x0000, BT_OPEN}},
+ {0x007d, {0x007b, 0x0000, BT_CLOSE}},
+ {0x0f3a, {0x0f3b, 0x0000, BT_OPEN}},
+ {0x0f3b, {0x0f3a, 0x0000, BT_CLOSE}},
+ {0x0f3c, {0x0f3d, 0x0000, BT_OPEN}},
+ {0x0f3d, {0x0f3c, 0x0000, BT_CLOSE}},
+ {0x169b, {0x169c, 0x0000, BT_OPEN}},
+ {0x169c, {0x169b, 0x0000, BT_CLOSE}},
+ {0x2045, {0x2046, 0x0000, BT_OPEN}},
+ {0x2046, {0x2045, 0x0000, BT_CLOSE}},
+ {0x207d, {0x207e, 0x0000, BT_OPEN}},
+ {0x207e, {0x207d, 0x0000, BT_CLOSE}},
+ {0x208d, {0x208e, 0x0000, BT_OPEN}},
+ {0x208e, {0x208d, 0x0000, BT_CLOSE}},
+ {0x2308, {0x2309, 0x0000, BT_OPEN}},
+ {0x2309, {0x2308, 0x0000, BT_CLOSE}},
+ {0x230a, {0x230b, 0x0000, BT_OPEN}},
+ {0x230b, {0x230a, 0x0000, BT_CLOSE}},
+ {0x2329, {0x232a, 0x3009, BT_OPEN}},
+ {0x232a, {0x2329, 0x3008, BT_CLOSE}},
+ {0x2768, {0x2769, 0x0000, BT_OPEN}},
+ {0x2769, {0x2768, 0x0000, BT_CLOSE}},
+ {0x276a, {0x276b, 0x0000, BT_OPEN}},
+ {0x276b, {0x276a, 0x0000, BT_CLOSE}},
+ {0x276c, {0x276d, 0x0000, BT_OPEN}},
+ {0x276d, {0x276c, 0x0000, BT_CLOSE}},
+ {0x276e, {0x276f, 0x0000, BT_OPEN}},
+ {0x276f, {0x276e, 0x0000, BT_CLOSE}},
+ {0x2770, {0x2771, 0x0000, BT_OPEN}},
+ {0x2771, {0x2770, 0x0000, BT_CLOSE}},
+ {0x2772, {0x2773, 0x0000, BT_OPEN}},
+ {0x2773, {0x2772, 0x0000, BT_CLOSE}},
+ {0x2774, {0x2775, 0x0000, BT_OPEN}},
+ {0x2775, {0x2774, 0x0000, BT_CLOSE}},
+ {0x27c5, {0x27c6, 0x0000, BT_OPEN}},
+ {0x27c6, {0x27c5, 0x0000, BT_CLOSE}},
+ {0x27e6, {0x27e7, 0x0000, BT_OPEN}},
+ {0x27e7, {0x27e6, 0x0000, BT_CLOSE}},
+ {0x27e8, {0x27e9, 0x0000, BT_OPEN}},
+ {0x27e9, {0x27e8, 0x0000, BT_CLOSE}},
+ {0x27ea, {0x27eb, 0x0000, BT_OPEN}},
+ {0x27eb, {0x27ea, 0x0000, BT_CLOSE}},
+ {0x27ec, {0x27ed, 0x0000, BT_OPEN}},
+ {0x27ed, {0x27ec, 0x0000, BT_CLOSE}},
+ {0x27ee, {0x27ef, 0x0000, BT_OPEN}},
+ {0x27ef, {0x27ee, 0x0000, BT_CLOSE}},
+ {0x2983, {0x2984, 0x0000, BT_OPEN}},
+ {0x2984, {0x2983, 0x0000, BT_CLOSE}},
+ {0x2985, {0x2986, 0x0000, BT_OPEN}},
+ {0x2986, {0x2985, 0x0000, BT_CLOSE}},
+ {0x2987, {0x2988, 0x0000, BT_OPEN}},
+ {0x2988, {0x2987, 0x0000, BT_CLOSE}},
+ {0x2989, {0x298a, 0x0000, BT_OPEN}},
+ {0x298a, {0x2989, 0x0000, BT_CLOSE}},
+ {0x298b, {0x298c, 0x0000, BT_OPEN}},
+ {0x298c, {0x298b, 0x0000, BT_CLOSE}},
+ {0x298d, {0x2990, 0x0000, BT_OPEN}},
+ {0x298e, {0x298f, 0x0000, BT_CLOSE}},
+ {0x298f, {0x298e, 0x0000, BT_OPEN}},
+ {0x2990, {0x298d, 0x0000, BT_CLOSE}},
+ {0x2991, {0x2992, 0x0000, BT_OPEN}},
+ {0x2992, {0x2991, 0x0000, BT_CLOSE}},
+ {0x2993, {0x2994, 0x0000, BT_OPEN}},
+ {0x2994, {0x2993, 0x0000, BT_CLOSE}},
+ {0x2995, {0x2996, 0x0000, BT_OPEN}},
+ {0x2996, {0x2995, 0x0000, BT_CLOSE}},
+ {0x2997, {0x2998, 0x0000, BT_OPEN}},
+ {0x2998, {0x2997, 0x0000, BT_CLOSE}},
+ {0x29d8, {0x29d9, 0x0000, BT_OPEN}},
+ {0x29d9, {0x29d8, 0x0000, BT_CLOSE}},
+ {0x29da, {0x29db, 0x0000, BT_OPEN}},
+ {0x29db, {0x29da, 0x0000, BT_CLOSE}},
+ {0x29fc, {0x29fd, 0x0000, BT_OPEN}},
+ {0x29fd, {0x29fc, 0x0000, BT_CLOSE}},
+ {0x2e22, {0x2e23, 0x0000, BT_OPEN}},
+ {0x2e23, {0x2e22, 0x0000, BT_CLOSE}},
+ {0x2e24, {0x2e25, 0x0000, BT_OPEN}},
+ {0x2e25, {0x2e24, 0x0000, BT_CLOSE}},
+ {0x2e26, {0x2e27, 0x0000, BT_OPEN}},
+ {0x2e27, {0x2e26, 0x0000, BT_CLOSE}},
+ {0x2e28, {0x2e29, 0x0000, BT_OPEN}},
+ {0x2e29, {0x2e28, 0x0000, BT_CLOSE}},
+ {0x2e55, {0x2e56, 0x0000, BT_OPEN}},
+ {0x2e56, {0x2e55, 0x0000, BT_CLOSE}},
+ {0x2e57, {0x2e58, 0x0000, BT_OPEN}},
+ {0x2e58, {0x2e57, 0x0000, BT_CLOSE}},
+ {0x2e59, {0x2e5a, 0x0000, BT_OPEN}},
+ {0x2e5a, {0x2e59, 0x0000, BT_CLOSE}},
+ {0x2e5b, {0x2e5c, 0x0000, BT_OPEN}},
+ {0x2e5c, {0x2e5b, 0x0000, BT_CLOSE}},
+ {0x3008, {0x3009, 0x232a, BT_OPEN}},
+ {0x3009, {0x3008, 0x2329, BT_CLOSE}},
+ {0x300a, {0x300b, 0x0000, BT_OPEN}},
+ {0x300b, {0x300a, 0x0000, BT_CLOSE}},
+ {0x300c, {0x300d, 0x0000, BT_OPEN}},
+ {0x300d, {0x300c, 0x0000, BT_CLOSE}},
+ {0x300e, {0x300f, 0x0000, BT_OPEN}},
+ {0x300f, {0x300e, 0x0000, BT_CLOSE}},
+ {0x3010, {0x3011, 0x0000, BT_OPEN}},
+ {0x3011, {0x3010, 0x0000, BT_CLOSE}},
+ {0x3014, {0x3015, 0x0000, BT_OPEN}},
+ {0x3015, {0x3014, 0x0000, BT_CLOSE}},
+ {0x3016, {0x3017, 0x0000, BT_OPEN}},
+ {0x3017, {0x3016, 0x0000, BT_CLOSE}},
+ {0x3018, {0x3019, 0x0000, BT_OPEN}},
+ {0x3019, {0x3018, 0x0000, BT_CLOSE}},
+ {0x301a, {0x301b, 0x0000, BT_OPEN}},
+ {0x301b, {0x301a, 0x0000, BT_CLOSE}},
+ {0xfe59, {0xfe5a, 0x0000, BT_OPEN}},
+ {0xfe5a, {0xfe59, 0x0000, BT_CLOSE}},
+ {0xfe5b, {0xfe5c, 0x0000, BT_OPEN}},
+ {0xfe5c, {0xfe5b, 0x0000, BT_CLOSE}},
+ {0xfe5d, {0xfe5e, 0x0000, BT_OPEN}},
+ {0xfe5e, {0xfe5d, 0x0000, BT_CLOSE}},
+ {0xff08, {0xff09, 0x0000, BT_OPEN}},
+ {0xff09, {0xff08, 0x0000, BT_CLOSE}},
+ {0xff3b, {0xff3d, 0x0000, BT_OPEN}},
+ {0xff3d, {0xff3b, 0x0000, BT_CLOSE}},
+ {0xff5b, {0xff5d, 0x0000, BT_OPEN}},
+ {0xff5d, {0xff5b, 0x0000, BT_CLOSE}},
+ {0xff5f, {0xff60, 0x0000, BT_OPEN}},
+ {0xff60, {0xff5f, 0x0000, BT_CLOSE}},
+ {0xff62, {0xff63, 0x0000, BT_OPEN}},
+ {0xff63, {0xff62, 0x0000, BT_CLOSE}},
+ };
+
+ int i, j, k;
+
+ i = -1;
+ j = lenof(bracket_pairs);
+
+ while (j - i > 1) {
+ k = (i + j) / 2;
+ if (ch < bracket_pairs[k].src) {
+ j = k;
+ } else if (ch > bracket_pairs[k].src) {
+ i = k;
+ } else {
+ return bracket_pairs[k].payload;
+ }
+ }
+
+ static const BracketTypeData null = { 0, 0, BT_NONE };
+ return null;
+}
+
+/*
+ * Function exported to front ends to allow them to identify
+ * bidi-active characters (in case, for example, the platform's
+ * text display function can't conveniently be prevented from doing
+ * its own bidi and so special treatment is required for characters
+ * that would cause the bidi algorithm to activate).
+ *
+ * This function is passed a single Unicode code point, and returns
+ * nonzero if the presence of this code point can possibly cause
+ * the bidi algorithm to do any reordering. Thus, any string
+ * composed entirely of characters for which is_rtl() returns zero
+ * should be safe to pass to a bidi-active platform display
+ * function without fear.
+ *
+ * (is_rtl() must therefore also return true for any character
+ * which would be affected by Arabic shaping, but this isn't
+ * important because all such characters are right-to-left so it
+ * would have flagged them anyway.)
+ */
+bool is_rtl(int c)
+{
+ return typeIsBidiActive(bidi_getType(c));
+}
+
+/* The Main shaping function, and the only one to be used
+ * by the outside world.
+ *
+ * line: buffer to apply shaping to. this must be passed by doBidi() first
+ * to: output buffer for the shaped data
+ * count: number of characters in line
+ */
+int do_shape(bidi_char *line, bidi_char *to, int count)
+{
+ int i, tempShape;
+ bool ligFlag = false;
+
+ for (i=0; i<count; i++) {
+ to[i] = line[i];
+ tempShape = STYPE(line[i].wc);
+ switch (tempShape) {
+ case SC:
+ break;
+
+ case SU:
+ break;
+
+ case SR:
+ tempShape = (i+1 < count ? STYPE(line[i+1].wc) : SU);
+ if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC))
+ to[i].wc = SFINAL((SISOLATED(line[i].wc)));
+ else
+ to[i].wc = SISOLATED(line[i].wc);
+ break;
+
+
+ case SD:
+ /* Make Ligatures */
+ tempShape = (i+1 < count ? STYPE(line[i+1].wc) : SU);
+ if (line[i].wc == 0x644) {
+ if (i > 0) switch (line[i-1].wc) {
+ case 0x622:
+ ligFlag = true;
+ if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC))
+ to[i].wc = 0xFEF6;
+ else
+ to[i].wc = 0xFEF5;
+ break;
+ case 0x623:
+ ligFlag = true;
+ if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC))
+ to[i].wc = 0xFEF8;
+ else
+ to[i].wc = 0xFEF7;
+ break;
+ case 0x625:
+ ligFlag = true;
+ if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC))
+ to[i].wc = 0xFEFA;
+ else
+ to[i].wc = 0xFEF9;
+ break;
+ case 0x627:
+ ligFlag = true;
+ if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC))
+ to[i].wc = 0xFEFC;
+ else
+ to[i].wc = 0xFEFB;
+ break;
+ }
+ if (ligFlag) {
+ to[i-1].wc = 0x20;
+ ligFlag = false;
+ break;
+ }
+ }
+
+ if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) {
+ tempShape = (i > 0 ? STYPE(line[i-1].wc) : SU);
+ if ((tempShape == SR) || (tempShape == SD) || (tempShape == SC))
+ to[i].wc = SMEDIAL((SISOLATED(line[i].wc)));
+ else
+ to[i].wc = SFINAL((SISOLATED(line[i].wc)));
+ break;
+ }
+
+ tempShape = (i > 0 ? STYPE(line[i-1].wc) : SU);
+ if ((tempShape == SR) || (tempShape == SD) || (tempShape == SC))
+ to[i].wc = SINITIAL((SISOLATED(line[i].wc)));
+ else
+ to[i].wc = SISOLATED(line[i].wc);
+ break;
+
+
+ }
+ }
+ return 1;
+}
+
+typedef enum { DO_NEUTRAL, DO_LTR, DO_RTL } DirectionalOverride;
+
+typedef struct DSStackEntry {
+ /*
+ * An entry in the directional status stack (rule section X).
+ */
+ unsigned char level;
+ bool isolate;
+ DirectionalOverride override;
+} DSStackEntry;
+
+typedef struct BracketStackEntry {
+ /*
+ * An entry in the bracket-pair-tracking stack (rule BD16).
+ */
+ unsigned ch;
+ size_t c;
+} BracketStackEntry;
+
+typedef struct IsolatingRunSequence {
+ size_t start, end;
+ BidiType sos, eos, embeddingDirection;
+} IsolatingRunSequence;
+
+#define MAX_DEPTH 125 /* specified in the standard */
+
+struct BidiContext {
+ /*
+ * Storage space preserved between runs, all allocated to the same
+ * length (internal_array_sizes).
+ */
+ size_t internal_array_sizes;
+ BidiType *types, *origTypes;
+ unsigned char *levels;
+ size_t *irsindices, *bracketpos;
+ bool *irsdone;
+
+ /*
+ * Separately allocated with its own size field
+ */
+ IsolatingRunSequence *irslist;
+ size_t irslistsize;
+
+ /*
+ * Rewritten to point to the input to the currently active run of
+ * the bidi algorithm
+ */
+ bidi_char *text;
+ size_t textlen;
+
+ /*
+ * State within a run of the algorithm
+ */
+ BidiType paragraphOverride;
+ DSStackEntry dsstack[MAX_DEPTH + 2];
+ size_t ds_sp;
+ size_t overflowIsolateCount, overflowEmbeddingCount, validIsolateCount;
+ unsigned char paragraphLevel;
+ size_t *irs;
+ size_t irslen;
+ BidiType sos, eos, embeddingDirection;
+ BracketStackEntry bstack[63]; /* constant size specified in rule BD16 */
+};
+
+BidiContext *bidi_new_context(void)
+{
+ BidiContext *ctx = snew(BidiContext);
+ memset(ctx, 0, sizeof(BidiContext));
+ return ctx;
+}
+
+void bidi_free_context(BidiContext *ctx)
+{
+ sfree(ctx->types);
+ sfree(ctx->origTypes);
+ sfree(ctx->levels);
+ sfree(ctx->irsindices);
+ sfree(ctx->irsdone);
+ sfree(ctx->bracketpos);
+ sfree(ctx->irslist);
+ sfree(ctx);
+}
+
+static void ensure_arrays(BidiContext *ctx, size_t textlen)
+{
+ if (textlen <= ctx->internal_array_sizes)
+ return;
+ ctx->internal_array_sizes = textlen;
+ ctx->types = sresize(ctx->types, ctx->internal_array_sizes, BidiType);
+ ctx->origTypes = sresize(ctx->origTypes, ctx->internal_array_sizes,
+ BidiType);
+ ctx->levels = sresize(ctx->levels, ctx->internal_array_sizes,
+ unsigned char);
+ ctx->irsindices = sresize(ctx->irsindices, ctx->internal_array_sizes,
+ size_t);
+ ctx->irsdone = sresize(ctx->irsdone, ctx->internal_array_sizes, bool);
+ ctx->bracketpos = sresize(ctx->bracketpos, ctx->internal_array_sizes,
+ size_t);
+}
+
+static void setup_types(BidiContext *ctx)
+{
+ for (size_t i = 0; i < ctx->textlen; i++)
+ ctx->types[i] = ctx->origTypes[i] = bidi_getType(ctx->text[i].wc);
+}
+
+static bool text_needs_bidi(BidiContext *ctx)
+{
+ /*
+ * Initial optimisation: check for any bidi-active character at
+ * all in an input line. If there aren't any, we can skip the
+ * whole algorithm.
+ *
+ * Also include the paragraph override in this check!
+ */
+ for (size_t i = 0; i < ctx->textlen; i++)
+ if (typeIsBidiActive(ctx->types[i]))
+ return true;
+ return typeIsBidiActive(ctx->paragraphOverride);
+}
+
+static size_t find_matching_pdi(const BidiType *types, size_t i, size_t size)
+{
+ /* Assuming that types[i] is an isolate initiator, find its
+ * matching PDI by rule BD9. */
+ unsigned counter = 1;
+ i++;
+ for (; i < size; i++) {
+ BidiType t = types[i];
+ if (typeIsIsolateInitiator(t)) {
+ counter++;
+ } else if (t == PDI) {
+ counter--;
+ if (counter == 0)
+ return i;
+ }
+ }
+
+ /* If no PDI was found, return the length of the array. */
+ return size;
+}
+
+static unsigned char rule_p2_p3(const BidiType *types, size_t size)
+{
+ /*
+ * Rule P2. Find the first strong type (L, R or AL), ignoring
+ * anything inside an isolated segment.
+ *
+ * Rule P3. If that type is R or AL, choose a paragraph embeddding
+ * level of 1, otherwise 0.
+ */
+ for (size_t i = 0; i < size; i++) {
+ BidiType t = types[i];
+ if (typeIsIsolateInitiator(t))
+ i = find_matching_pdi(types, i, size);
+ else if (typeIsStrong(t))
+ return (t == L ? 0 : 1);
+ }
+
+ return 0; /* default if no strong type found */
+}
+
+static void set_paragraph_level(BidiContext *ctx)
+{
+ if (ctx->paragraphOverride == L)
+ ctx->paragraphLevel = 0;
+ else if (ctx->paragraphOverride == R)
+ ctx->paragraphLevel = 1;
+ else
+ ctx->paragraphLevel = rule_p2_p3(ctx->types, ctx->textlen);
+}
+
+static inline unsigned char nextOddLevel(unsigned char x) { return (x+1)|1; }
+static inline unsigned char nextEvenLevel(unsigned char x) { return (x|1)+1; }
+
+static inline void push(BidiContext *ctx, unsigned char level,
+ DirectionalOverride override, bool isolate)
+{
+ ctx->ds_sp++;
+ assert(ctx->ds_sp < lenof(ctx->dsstack));
+ ctx->dsstack[ctx->ds_sp].level = level;
+ ctx->dsstack[ctx->ds_sp].override = override;
+ ctx->dsstack[ctx->ds_sp].isolate = isolate;
+}
+
+static inline void pop(BidiContext *ctx)
+{
+ assert(ctx->ds_sp > 0);
+ ctx->ds_sp--;
+}
+
+static void process_explicit_embeddings(BidiContext *ctx)
+{
+ /*
+ * Rule X1 initialisation.
+ */
+ ctx->ds_sp = (size_t)-1;
+ push(ctx, ctx->paragraphLevel, DO_NEUTRAL, false);
+ ctx->overflowIsolateCount = 0;
+ ctx->overflowEmbeddingCount = 0;
+ ctx->validIsolateCount = 0;
+
+ #define stk (&ctx->dsstack[ctx->ds_sp])
+
+ for (size_t i = 0; i < ctx->textlen; i++) {
+ BidiType t = ctx->types[i];
+ switch (t) {
+ case RLE: case LRE: case RLO: case LRO: {
+ /* Rules X2-X5 */
+ unsigned char newLevel;
+ DirectionalOverride override;
+
+#ifndef REMOVE_FORMATTING_CHARS
+ ctx->levels[i] = stk->level;
+#endif
+
+ switch (t) {
+ case RLE: /* rule X2 */
+ newLevel = nextOddLevel(stk->level);
+ override = DO_NEUTRAL;
+ break;
+ case LRE: /* rule X3 */
+ newLevel = nextEvenLevel(stk->level);
+ override = DO_NEUTRAL;
+ break;
+ case RLO: /* rule X4 */
+ newLevel = nextOddLevel(stk->level);
+ override = DO_RTL;
+ break;
+ case LRO: /* rule X5 */
+ newLevel = nextEvenLevel(stk->level);
+ override = DO_LTR;
+ break;
+ default:
+ unreachable("how did this get past the outer switch?");
+ }
+
+ if (newLevel <= MAX_DEPTH &&
+ ctx->overflowIsolateCount == 0 &&
+ ctx->overflowEmbeddingCount == 0) {
+ /* Embedding code is valid. Push a stack entry. */
+ push(ctx, newLevel, override, false);
+ } else {
+ /* Embedding code is an overflow one. */
+ if (ctx->overflowIsolateCount == 0)
+ ctx->overflowEmbeddingCount++;
+ }
+ break;
+ }
+
+ case RLI: case LRI: case FSI: {
+ /* Rules X5a, X5b, X5c */
+
+ if (t == FSI) {
+ /* Rule X5c: decide whether this should be treated
+ * like RLI or LRI */
+ size_t pdi = find_matching_pdi(ctx->types, i, ctx->textlen);
+ unsigned char level = rule_p2_p3(ctx->types + (i + 1),
+ pdi - (i + 1));
+ t = (level == 1 ? RLI : LRI);
+ }
+
+ ctx->levels[i] = stk->level;
+ if (stk->override != DO_NEUTRAL)
+ ctx->types[i] = (stk->override == DO_LTR ? L :
+ stk->override == DO_RTL ? R : t);
+
+ unsigned char newLevel = (t == RLI ? nextOddLevel(stk->level) :
+ nextEvenLevel(stk->level));
+
+ if (newLevel <= MAX_DEPTH &&
+ ctx->overflowIsolateCount == 0 &&
+ ctx->overflowEmbeddingCount == 0) {
+ /* Isolate code is valid. Push a stack entry. */
+ push(ctx, newLevel, DO_NEUTRAL, true);
+ ctx->validIsolateCount++;
+ } else {
+ /* Isolate code is an overflow one. */
+ ctx->overflowIsolateCount++;
+ }
+ break;
+ }
+
+ case PDI: {
+ /* Rule X6a */
+ if (ctx->overflowIsolateCount > 0) {
+ ctx->overflowIsolateCount--;
+ } else if (ctx->validIsolateCount == 0) {
+ /* Do nothing: spurious isolate-pop */
+ } else {
+ /* Valid isolate-pop. We expect that the stack must
+ * therefore contain at least one isolate==true entry,
+ * so pop everything up to and including it. */
+ ctx->overflowEmbeddingCount = 0;
+ while (!stk->isolate)
+ pop(ctx);
+ pop(ctx);
+ ctx->validIsolateCount--;
+ }
+ ctx->levels[i] = stk->level;
+ if (stk->override != DO_NEUTRAL)
+ ctx->types[i] = (stk->override == DO_LTR ? L : R);
+ break;
+ }
+
+ case PDF: {
+ /* Rule X7 */
+ if (ctx->overflowIsolateCount > 0) {
+ /* Do nothing if we've overflowed on isolates */
+ } else if (ctx->overflowEmbeddingCount > 0) {
+ ctx->overflowEmbeddingCount--;
+ } else if (ctx->ds_sp > 0 && !stk->isolate) {
+ pop(ctx);
+ } else {
+ /* Do nothing: spurious embedding-pop */
+ }
+
+#ifndef REMOVE_FORMATTING_CHARS
+ ctx->levels[i] = stk->level;
+#endif
+ break;
+ }
+
+ case B: {
+ /* Rule X8: if an explicit paragraph separator appears in
+ * this text at all then it does not participate in any of
+ * the above, and just gets assigned the paragraph level.
+ *
+ * PS, it had better be right at the end of the text,
+ * because we have not implemented rule P1 in this code. */
+ assert(i == ctx->textlen - 1);
+ ctx->levels[i] = ctx->paragraphLevel;
+ break;
+ }
+
+ case BN: {
+ /*
+ * The section 5.2 adjustment to rule X6 says that we
+ * apply it to BN just like any other class. But I think
+ * this can't possibly give the same results as the
+ * unmodified algorithm.
+ *
+ * Proof: adding RLO BN or LRO BN at the end of a
+ * paragraph should not change the output of the standard
+ * algorithm, because the override doesn't affect the BN
+ * in rule X6, and then rule X9 removes both. But with the
+ * modified rule X6, the BN is changed into R or L, and
+ * then rule X9 doesn't remove it, and then you've added a
+ * strong type that will set eos for the level run just
+ * before the override. And whatever the standard
+ * algorithm set eos to, _one_ of these override sequences
+ * will disagree with it.
+ *
+ * So I think we just set the BN's level, and don't change
+ * its type.
+ */
+ ctx->levels[i] = stk->level;
+ break;
+ }
+
+ default: {
+ /* Rule X6. */
+ ctx->levels[i] = stk->level;
+ if (stk->override != DO_NEUTRAL)
+ ctx->types[i] = (stk->override == DO_LTR ? L : R);
+ break;
+ }
+ }
+ }
+
+ #undef stk
+}
+
+static void remove_embedding_characters(BidiContext *ctx)
+{
+#ifndef REMOVE_FORMATTING_CHARS
+ /*
+ * Rule X9, as modified by section 5.2: turn embedding (but not
+ * isolate) characters into BN.
+ */
+ for (size_t i = 0; i < ctx->textlen; i++) {
+ BidiType t = ctx->types[i];
+ if (typeIsRemovedDuringProcessing(t)) {
+ ctx->types[i] = BN;
+
+ /*
+ * My own adjustment to the section 5.2 mods: a sequence
+ * of contiguous BN generated by this setup should never
+ * be at different levels from each other.
+ *
+ * An example where this goes wrong is if you open two
+ * LREs in sequence, then close them again:
+ *
+ * ... LRE LRE PDF PDF ...
+ *
+ * The initial level assignment gives level 0 to the outer
+ * LRE/PDF pair, and level 2 to the inner one. The
+ * standard algorithm would remove all four, so this
+ * doesn't matter, and you end up with no break in the
+ * surrounding level run. But if you just rewrite the
+ * types of all those characters to BN and leave the
+ * levels in that state, then the modified algorithm will
+ * leave the middle two BN at level 2, dividing what
+ * should have been a long level run at level 0 into two
+ * separate ones.
+ */
+ if (i > 0 && ctx->types[i-1] == BN)
+ ctx->levels[i] = ctx->levels[i-1];
+ }
+ }
+#else
+ /*
+ * Rule X9, original version: completely remove embedding
+ * start/end characters and also boundary neutrals.
+ */
+ size_t outpos = 0;
+ for (size_t i = 0; i < ctx->textlen; i++) {
+ BidiType t = ctx->types[i];
+ if (!typeIsRemovedDuringProcessing(t)) {
+ ctx->text[outpos] = ctx->text[i];
+ ctx->levels[outpos] = ctx->levels[i];
+ ctx->types[outpos] = ctx->types[i];
+ ctx->origTypes[outpos] = ctx->origTypes[i];
+ outpos++;
+ }
+ }
+ ctx->textlen = outpos;
+#endif
+}
+
+typedef void (*irs_fn_t)(BidiContext *ctx);
+
+static void find_isolating_run_sequences(BidiContext *ctx, irs_fn_t process)
+{
+ /*
+ * Rule X10 / BD13. Now that we've assigned an embedding level to
+ * each character in the text, we have to divide the text into
+ * subsequences on which to do the next stage of processing.
+ *
+ * In earlier issues of the bidi algorithm, these subsequences
+ * were contiguous in the original text, and each one was a 'level
+ * run': a maximal contiguous subsequence of characters all at the
+ * same embedding level.
+ *
+ * But now we have isolates, and the point of an (isolate
+ * initiator ... PDI) sequence is that the whole sequence should
+ * be treated like a single BN for the purposes of formatting
+ * everything outside it. As a result, we now have to recombine
+ * our level runs into longer sequences, on the principle that if
+ * a level run ends with an isolate initiator, then we bring it
+ * together with whatever later level run starts with the matching
+ * PDI.
+ *
+ * These subsequences are no longer contiguous (the whole point is
+ * that between the isolate initiator and the PDI is some other
+ * text that we've skipped over). They're called 'isolating run
+ * sequences'.
+ */
+
+ memset(ctx->irsdone, 0, ctx->textlen);
+ size_t i = 0;
+ size_t n_irs = 0;
+ size_t indexpos = 0;
+ while (i < ctx->textlen) {
+ if (ctx->irsdone[i]) {
+ i++;
+ continue;
+ }
+
+ /*
+ * Found a character not already processed. Start a new
+ * sequence here.
+ */
+ sgrowarray(ctx->irslist, ctx->irslistsize, n_irs);
+ IsolatingRunSequence *irs = &ctx->irslist[n_irs++];
+ irs->start = indexpos;
+ size_t j = i;
+ size_t irslevel = ctx->levels[i];
+ while (j < ctx->textlen) {
+ /*
+ * We expect that all level runs in this sequence will be
+ * at the same level as each other, by construction of how
+ * we set up the levels from the isolates in the first
+ * place.
+ */
+ assert(ctx->levels[j] == irslevel);
+
+ do {
+ ctx->irsdone[j] = true;
+ ctx->irsindices[indexpos++] = j++;
+ } while (j < ctx->textlen && ctx->levels[j] == irslevel);
+ if (!typeIsIsolateInitiator(ctx->types[j-1]))
+ break; /* this IRS is ended */
+ j = find_matching_pdi(ctx->types, j-1, ctx->textlen);
+ }
+ irs->end = indexpos;
+
+ /*
+ * Determine the start-of-sequence and end-of-sequence types
+ * for this sequence.
+ *
+ * These depend on the embedding levels of surrounding text.
+ * But processing each run can change those levels. That's why
+ * we have to use a two-pass strategy here, first identifying
+ * all the isolating run sequences using the input level data,
+ * and not processing any of them until we know where they all
+ * are.
+ */
+ size_t p;
+ unsigned char level_inside, level_outside, level_max;
+
+ p = i;
+ level_inside = ctx->levels[p];
+ level_outside = ctx->paragraphLevel;
+ while (p > 0) {
+ p--;
+ if (ctx->types[p] != BN) {
+ level_outside = ctx->levels[p];
+ break;
+ }
+ }
+ level_max = max(level_inside, level_outside);
+ irs->sos = (level_max % 2 ? R : L);
+
+ p = ctx->irsindices[irs->end - 1];
+ level_inside = ctx->levels[p];
+ level_outside = ctx->paragraphLevel;
+ if (typeIsIsolateInitiator(ctx->types[p])) {
+ /* Special case: if an isolating run sequence ends in an
+ * unmatched isolate initiator, then level_outside is
+ * taken to be the paragraph embedding level and the
+ * loop below is skipped. */
+ } else {
+ while (p+1 < ctx->textlen) {
+ p++;
+ if (ctx->types[p] != BN) {
+ level_outside = ctx->levels[p];
+ break;
+ }
+ }
+ }
+ level_max = max(level_inside, level_outside);
+ irs->eos = (level_max % 2 ? R : L);
+
+ irs->embeddingDirection = (irslevel % 2 ? R : L);
+
+ /*
+ * Now we've listed in ctx->irsindices[] the index of every
+ * character that's part of this isolating run sequence, and
+ * recorded an entry in irslist containing the interval of
+ * indices relevant to this IRS, plus its assorted metadata.
+ * We've also marked those locations in the input text as done
+ * in ctx->irsdone, so that we'll skip over them when the
+ * outer iteration reaches them later.
+ */
+ }
+
+ for (size_t k = 0; k < n_irs; k++) {
+ IsolatingRunSequence *irs = &ctx->irslist[k];
+ ctx->irs = ctx->irsindices + irs->start;
+ ctx->irslen = irs->end - irs->start;
+ ctx->sos = irs->sos;
+ ctx->eos = irs->eos;
+ ctx->embeddingDirection = irs->embeddingDirection;
+ process(ctx);
+ }
+
+ /* Reset irslen to 0 when we've finished. This means any other
+ * functions that absentmindedly try to use irslen at all will end
+ * up doing nothing at all, which should be easier to detect and
+ * debug than if they run on subtly the wrong subset of the
+ * text. */
+ ctx->irslen = 0;
+}
+
+static void remove_nsm(BidiContext *ctx)
+{
+ /* Rule W1: NSM gains the type of the previous character, or sos
+ * at the start of the run, with the exception that isolation
+ * boundaries turn into ON. */
+ BidiType prevType = ctx->sos;
+ for (size_t c = 0; c < ctx->irslen; c++) {
+ size_t i = ctx->irs[c];
+ BidiType t = ctx->types[i];
+ if (t == NSM) {
+ ctx->types[i] = prevType;
+ } else if (typeIsIsolateInitiatorOrPDI(t)) {
+ prevType = ON;
+#ifndef REMOVE_FORMATTING_CHARS
+ } else if (t == BN) {
+ /* section 5.2 adjustment: these don't affect prevType */
+#endif
+ } else {
+ prevType = t;
+ }
+ }
+}
+
+static void change_en_to_an(BidiContext *ctx)
+{
+ /* Rule W2: EN becomes AN if the previous strong type is AL. (The
+ * spec says that the 'previous strong type' is counted as sos at
+ * the start of the run, although it hardly matters, since sos
+ * can't be AL.) */
+ BidiType prevStrongType = ctx->sos;
+ for (size_t c = 0; c < ctx->irslen; c++) {
+ size_t i = ctx->irs[c];
+ BidiType t = ctx->types[i];
+ if (t == EN && prevStrongType == AL) {
+ ctx->types[i] = AN;
+ } else if (typeIsStrong(t)) {
+ prevStrongType = t;
+ }
+ }
+}
+
+static void change_al_to_r(BidiContext *ctx)
+{
+ /* Rule W3: AL becomes R unconditionally. (The only difference
+ * between the two types was their effect on nearby numbers, which
+ * was dealt with in rule W2, so now we're done with the
+ * distinction.) */
+ for (size_t c = 0; c < ctx->irslen; c++) {
+ size_t i = ctx->irs[c];
+ if (ctx->types[i] == AL)
+ ctx->types[i] = R;
+ }
+}
+
+static void eliminate_separators_between_numbers(BidiContext *ctx)
+{
+ /* Rule W4: a single numeric separator between two numbers of the
+ * same type compatible with that separator takes the type of the
+ * number. ES is a separator type compatible only with EN; CS is a
+ * separator type compatible with either EN or AN.
+ *
+ * Section 5.2 adjustment: intervening BNs do not break this, so
+ * instead of simply looking at types[irs[c-1]] and types[irs[c+1]],
+ * we must track the last three indices we saw that were not BN. */
+ size_t i1 = 0, i2 = 0;
+ BidiType t0 = ON, t1 = ON, t2 = ON;
+ for (size_t c = 0; c < ctx->irslen; c++) {
+ size_t i = ctx->irs[c];
+ BidiType t = ctx->types[i];
+
+#ifndef REMOVE_FORMATTING_CHARS
+ if (t == BN)
+ continue;
+#endif
+
+ i1 = i2; i2 = i;
+ t0 = t1; t1 = t2; t2 = t;
+ if (t0 == t2 && ((t1 == ES && t0 == EN) ||
+ (t1 == CS && (t0 == EN || t0 == AN)))) {
+ ctx->types[i1] = t0;
+ }
+ }
+}
+
+static void eliminate_et_next_to_en(BidiContext *ctx)
+{
+ /* Rule W5: a sequence of ET adjacent to an EN take the type EN.
+ * This is easiest to implement with one loop in each direction.
+ *
+ * Section 5.2 adjustment: include BN with ET. (We don't need to
+ * #ifdef that out, because in the standard algorithm, we won't
+ * have any BN left in any case.) */
+
+ bool modifying = false;
+
+ for (size_t c = 0; c < ctx->irslen; c++) {
+ size_t i = ctx->irs[c];
+ BidiType t = ctx->types[i];
+ if (t == EN) {
+ modifying = true;
+ } else if (modifying && typeIsETOrBN(t)) {
+ ctx->types[i] = EN;
+ } else {
+ modifying = false;
+ }
+ }
+
+ for (size_t c = ctx->irslen; c-- > 0 ;) {
+ size_t i = ctx->irs[c];
+ BidiType t = ctx->types[i];
+ if (t == EN) {
+ modifying = true;
+ } else if (modifying && typeIsETOrBN(t)) {
+ ctx->types[i] = EN;
+ } else {
+ modifying = false;
+ }
+ }
+}
+
+static void eliminate_separators_and_terminators(BidiContext *ctx)
+{
+ /* Rule W6: all separators and terminators change to ON.
+ *
+ * (The spec is not quite clear on which bidi types are included
+ * in this; one assumes ES, ET and CS, but what about S? I _think_
+ * the answer is that this is a rule in the W section, so it's
+ * implicitly supposed to only apply to types designated as weakly
+ * directional, so not S.) */
+
+#ifndef REMOVE_FORMATTING_CHARS
+ /*
+ * Section 5.2 adjustment: this also applies to any BN adjacent on
+ * either side to one of these types, which is easiest to
+ * implement with a separate double-loop converting those to an
+ * arbitrary one of the affected types, say CS.
+ *
+ * This double loop can be completely skipped in the standard
+ * algorithm.
+ */
+ bool modifying = false;
+
+ for (size_t c = 0; c < ctx->irslen; c++) {
+ size_t i = ctx->irs[c];
+ BidiType t = ctx->types[i];
+ if (typeIsWeakSeparatorOrTerminator(t)) {
+ modifying = true;
+ } else if (modifying && t == BN) {
+ ctx->types[i] = CS;
+ } else {
+ modifying = false;
+ }
+ }
+
+ for (size_t c = ctx->irslen; c-- > 0 ;) {
+ size_t i = ctx->irs[c];
+ BidiType t = ctx->types[i];
+ if (typeIsWeakSeparatorOrTerminator(t)) {
+ modifying = true;
+ } else if (modifying && t == BN) {
+ ctx->types[i] = CS;
+ } else {
+ modifying = false;
+ }
+ }
+#endif
+
+ /* Now the main part of rule W6 */
+ for (size_t c = 0; c < ctx->irslen; c++) {
+ size_t i = ctx->irs[c];
+ BidiType t = ctx->types[i];
+ if (typeIsWeakSeparatorOrTerminator(t))
+ ctx->types[i] = ON;
+ }
+}
+
+static void change_en_to_l(BidiContext *ctx)
+{
+ /* Rule W7: EN becomes L if the previous strong type (or sos) is L. */
+ BidiType prevStrongType = ctx->sos;
+ for (size_t c = 0; c < ctx->irslen; c++) {
+ size_t i = ctx->irs[c];
+ BidiType t = ctx->types[i];
+ if (t == EN && prevStrongType == L) {
+ ctx->types[i] = L;
+ } else if (typeIsStrong(t)) {
+ prevStrongType = t;
+ }
+ }
+}
+
+typedef void (*bracket_pair_fn)(BidiContext *ctx, size_t copen, size_t cclose);
+
+static void find_bracket_pairs(BidiContext *ctx, bracket_pair_fn process)
+{
+ const size_t NO_BRACKET = ~(size_t)0;
+
+ /*
+ * Rule BD16.
+ */
+ size_t sp = 0;
+ for (size_t c = 0; c < ctx->irslen; c++)
+ ctx->bracketpos[c] = NO_BRACKET;
+
+ for (size_t c = 0; c < ctx->irslen; c++) {
+ size_t i = ctx->irs[c];
+ unsigned wc = ctx->text[i].wc;
+ BracketTypeData bt = bracket_type(wc);
+ if (bt.type == BT_OPEN) {
+ if (sp >= lenof(ctx->bstack)) {
+ /*
+ * Stack overflow. The spec says we simply give up at
+ * this point.
+ */
+ goto found_all_pairs;
+ }
+
+ ctx->bstack[sp].ch = wc;
+ ctx->bstack[sp].c = c;
+ sp++;
+ } else if (bt.type == BT_CLOSE) {
+ size_t new_sp = sp;
+
+ /*
+ * Search up the stack for an entry containing a matching
+ * open bracket. If we find it, pop that entry and
+ * everything deeper, and record a matching pair. If we
+ * reach the bottom of the stack without finding anything,
+ * leave sp where it started.
+ */
+ while (new_sp-- > 0) {
+ if (ctx->bstack[new_sp].ch == bt.partner ||
+ ctx->bstack[new_sp].ch == bt.equiv_partner) {
+ /* Found a stack element matching this one */
+ size_t cstart = ctx->bstack[new_sp].c;
+ ctx->bracketpos[cstart] = c;
+ sp = new_sp;
+ break;
+ }
+ }
+ }
+ }
+
+ found_all_pairs:
+ for (size_t c = 0; c < ctx->irslen; c++) {
+ if (ctx->bracketpos[c] != NO_BRACKET) {
+ process(ctx, c, ctx->bracketpos[c]);
+ }
+ }
+}
+
+static BidiType get_bracket_type(BidiContext *ctx, size_t copen, size_t cclose)
+{
+ /*
+ * Rule N0: a pair of matched brackets containing at least one
+ * strong type takes on the current embedding direction, unless
+ * all of these are true at once:
+ *
+ * (a) there are no strong types inside the brackets matching the
+ * current embedding direction
+ * (b) there _is_ at least one strong type inside the brackets
+ * that is _opposite_ to the current embedding direction
+ * (c) the strong type preceding the open bracket is also
+ * opposite to the current embedding direction
+ *
+ * in which case they take on the opposite direction.
+ *
+ * For these purposes, number types (EN and AN) count as R.
+ */
+
+ bool foundOppositeTypeInside = false;
+ for (size_t c = copen + 1; c < cclose; c++) {
+ size_t i = ctx->irs[c];
+ BidiType t = ctx->types[i];
+ if (typeIsStrongOrNumber(t)) {
+ t = t == L ? L : R; /* numbers count as R */
+ if (t == ctx->embeddingDirection) {
+ /* Found something inside the brackets matching the
+ * current level, so (a) is violated. */
+ return ctx->embeddingDirection;
+ } else {
+ foundOppositeTypeInside = true;
+ }
+ }
+ }
+
+ if (!foundOppositeTypeInside) {
+ /* No strong types at all inside the brackets, so return ON to
+ * indicate that we're not messing with their type at all. */
+ return ON;
+ }
+
+ /* There was an opposite strong type in the brackets. Look
+ * backwards to the preceding strong type, and go with that,
+ * whichever it is. */
+ for (size_t c = copen; c-- > 0 ;) {
+ size_t i = ctx->irs[c];
+ BidiType t = ctx->types[i];
+ if (typeIsStrongOrNumber(t)) {
+ t = t == L ? L : R; /* numbers count as R */
+ return t;
+ }
+ }
+
+ /* Fallback: if the preceding strong type was not found, go with
+ * sos. */
+ return ctx->sos;
+}
+
+static void reset_bracket_type(BidiContext *ctx, size_t c, BidiType t)
+{
+ /* Final bullet point of rule N0: when we change the type of a
+ * bracket, the same change applies to any contiguous sequence of
+ * characters after it whose _original_ bidi type was NSM. */
+ do {
+ ctx->types[ctx->irs[c++]] = t;
+
+#ifndef REMOVE_FORMATTING_CHARS
+ while (c < ctx->irslen && ctx->origTypes[ctx->irs[c]] == BN) {
+ /* Section 5.2 adjustment: skip past BN in the process. */
+ c++;
+ }
+#endif
+ } while (c < ctx->irslen && ctx->origTypes[ctx->irs[c]] == NSM);
+}
+
+static void resolve_brackets(BidiContext *ctx, size_t copen, size_t cclose)
+{
+ if (typeIsNeutral(ctx->types[ctx->irs[copen]]) &&
+ typeIsNeutral(ctx->types[ctx->irs[cclose]])) {
+ BidiType t = get_bracket_type(ctx, copen, cclose);
+ if (t != ON) {
+ reset_bracket_type(ctx, copen, t);
+ reset_bracket_type(ctx, cclose, t);
+ }
+ }
+}
+
+static void remove_ni(BidiContext *ctx)
+{
+ /*
+ * Rules N1 and N2 together: neutral or isolate characters take
+ * the direction of the surrounding strong text if the nearest
+ * strong characters on each side match, and otherwise, they take
+ * the embedding direction.
+ */
+ const size_t NO_INDEX = ~(size_t)0;
+ BidiType prevStrongType = ctx->sos;
+ size_t c_ni_start = NO_INDEX;
+ for (size_t c = 0; c <= ctx->irslen; c++) {
+ BidiType t;
+
+ if (c < ctx->irslen) {
+ size_t i = ctx->irs[c];
+ t = ctx->types[i];
+ } else {
+ /* One extra loop iteration, using eos to resolve the
+ * final sequence of NI if any */
+ t = ctx->eos;
+ }
+
+ if (typeIsStrongOrNumber(t)) {
+ t = t == L ? L : R; /* numbers count as R */
+ if (c_ni_start != NO_INDEX) {
+ /* There are some NI we have to fix up */
+ BidiType ni_type = (t == prevStrongType ? t :
+ ctx->embeddingDirection);
+ for (size_t c2 = c_ni_start; c2 < c; c2++) {
+ size_t i2 = ctx->irs[c2];
+ BidiType t2 = ctx->types[i2];
+ if (typeIsNeutralOrIsolate(t2))
+ ctx->types[i2] = ni_type;
+ }
+ }
+ prevStrongType = t;
+ c_ni_start = NO_INDEX;
+ } else if (typeIsNeutralOrIsolate(t) && c_ni_start == NO_INDEX) {
+ c_ni_start = c;
+ }
+ }
+}
+
+static void resolve_implicit_levels(BidiContext *ctx)
+{
+ /* Rules I1 and I2 */
+ for (size_t c = 0; c < ctx->irslen; c++) {
+ size_t i = ctx->irs[c];
+ unsigned char level = ctx->levels[i];
+ BidiType t = ctx->types[i];
+ if (level % 2 == 0) {
+ /* Rule I1 */
+ if (t == R)
+ ctx->levels[i] += 1;
+ else if (t == AN || t == EN)
+ ctx->levels[i] += 2;
+ } else {
+ /* Rule I2 */
+ if (t == L || t == AN || t == EN)
+ ctx->levels[i] += 1;
+ }
+ }
+}
+
+static void process_isolating_run_sequence(BidiContext *ctx)
+{
+ /* Section W: resolve weak types */
+ remove_nsm(ctx);
+ change_en_to_an(ctx);
+ change_al_to_r(ctx);
+ eliminate_separators_between_numbers(ctx);
+ eliminate_et_next_to_en(ctx);
+ eliminate_separators_and_terminators(ctx);
+ change_en_to_l(ctx);
+
+ /* Section N: resolve neutral types (and isolates) */
+ find_bracket_pairs(ctx, resolve_brackets);
+ remove_ni(ctx);
+
+ /* Section I: resolve implicit levels */
+ resolve_implicit_levels(ctx);
+}
+
+static void reset_whitespace_and_separators(BidiContext *ctx)
+{
+ /*
+ * Rule L1: segment and paragraph separators, plus whitespace
+ * preceding them, all reset to the paragraph embedding level.
+ * This also applies to whitespace at the very end.
+ *
+ * This is done using the original types, not the versions that
+ * the rest of this algorithm has been merrily mutating.
+ */
+ bool modifying = true;
+ for (size_t i = ctx->textlen; i-- > 0 ;) {
+ BidiType t = ctx->origTypes[i];
+ if (typeIsSegmentOrParaSeparator(t)) {
+ ctx->levels[i] = ctx->paragraphLevel;
+ modifying = true;
+ } else if (modifying) {
+ if (typeIsWhitespaceOrIsolate(t)) {
+ ctx->levels[i] = ctx->paragraphLevel;
+ } else if (!typeIsRemovedDuringProcessing(t)) {
+ modifying = false;
+ }
+ }
+ }
+
+#ifndef REMOVE_FORMATTING_CHARS
+ /*
+ * Section 5.2 adjustment: types removed by rule X9 take the level
+ * of the character to their left.
+ */
+ for (size_t i = 0; i < ctx->textlen; i++) {
+ BidiType t = ctx->origTypes[i];
+ if (typeIsRemovedDuringProcessing(t)) {
+ /* Section 5.2 adjustment */
+ ctx->levels[i] = (i > 0 ? ctx->levels[i-1] : ctx->paragraphLevel);
+ }
+ }
+#endif /* ! REMOVE_FORMATTING_CHARS */
+}
+
+static void reverse(BidiContext *ctx, size_t start, size_t end)
+{
+ for (size_t i = start, j = end; i < j; i++, j--) {
+ bidi_char tmp = ctx->text[i];
+ ctx->text[i] = ctx->text[j];
+ ctx->text[j] = tmp;
+ }
+}
+
+static void mirror_glyphs(BidiContext *ctx)
+{
+ /*
+ * Rule L3: any character with a mirror-image pair at an odd
+ * embedding level is replaced by its mirror image.
+ *
+ * This is specified in the standard as happening _after_ rule L2
+ * (the actual reordering of the text). But it's much easier to
+ * implement it before, while our levels[] array still matches up
+ * to the text order.
+ */
+ for (size_t i = 0; i < ctx->textlen; i++) {
+ if (ctx->levels[i] % 2)
+ ctx->text[i].wc = mirror_glyph(ctx->text[i].wc);
+ }
+}
+
+static void reverse_sequences(BidiContext *ctx)
+{
+ /*
+ * Rule L2: every maximal contiguous sequence of characters at a
+ * given level or higher is reversed.
+ */
+ unsigned level = 0;
+ for (size_t i = 0; i < ctx->textlen; i++)
+ level = max(level, ctx->levels[i]);
+
+ for (; level >= 1; level--) {
+ for (size_t i = 0; i < ctx->textlen; i++) {
+ if (ctx->levels[i] >= level) {
+ size_t start = i;
+ while (i+1 < ctx->textlen && ctx->levels[i+1] >= level)
+ i++;
+ reverse(ctx, start, i);
+ }
+ }
+ }
+}
+
+/*
+ * The Main Bidi Function. The two wrappers below it present different
+ * external APIs for different purposes, but everything comes through
+ * here.
+ *
+ * text: a buffer of size textlen containing text to apply the
+ * Bidirectional algorithm to.
+ */
+static void do_bidi_new(BidiContext *ctx, bidi_char *text, size_t textlen)
+{
+ ensure_arrays(ctx, textlen);
+ ctx->text = text;
+ ctx->textlen = textlen;
+ setup_types(ctx);
+
+ /* Quick initial test: see if we need to bother with any work at all */
+ if (!text_needs_bidi(ctx))
+ return;
+
+ set_paragraph_level(ctx);
+ process_explicit_embeddings(ctx);
+ remove_embedding_characters(ctx);
+ find_isolating_run_sequences(ctx, process_isolating_run_sequence);
+
+ /* If this implementation distinguished paragraphs from lines,
+ * then this would be the point where we repeat the remainder of
+ * the algorithm once for each line in the paragraph. */
+
+ reset_whitespace_and_separators(ctx);
+ mirror_glyphs(ctx);
+ reverse_sequences(ctx);
+}
+
+size_t do_bidi_test(BidiContext *ctx, bidi_char *text, size_t textlen,
+ int override)
+{
+ ctx->paragraphOverride = (override > 0 ? L : override < 0 ? R : ON);
+ do_bidi_new(ctx, text, textlen);
+ return ctx->textlen;
+}
+
+void do_bidi(BidiContext *ctx, bidi_char *text, size_t textlen)
+{
+#ifdef REMOVE_FORMATTING_CHARACTERS
+ abort(); /* can't use the standard algorithm in a live terminal */
+#else
+ ctx->paragraphOverride = ON;
+ do_bidi_new(ctx, text, textlen);
+#endif
+}
diff --git a/terminal/bidi.h b/terminal/bidi.h
new file mode 100644
index 00000000..90d68e5b
--- /dev/null
+++ b/terminal/bidi.h
@@ -0,0 +1,147 @@
+/*
+ * Header file shared between bidi.c and its tests. Not used by
+ * anything outside the bidi subsystem.
+ */
+
+#ifndef PUTTY_BIDI_H
+#define PUTTY_BIDI_H
+
+#define LMASK 0x3F /* Embedding Level mask */
+#define OMASK 0xC0 /* Override mask */
+#define OISL 0x80 /* Override is L */
+#define OISR 0x40 /* Override is R */
+
+/* Shaping Helpers */
+#define STYPE(xh) ((((xh) >= SHAPE_FIRST) && ((xh) <= SHAPE_LAST)) ? \
+shapetypes[(xh)-SHAPE_FIRST].type : SU) /*))*/
+#define SISOLATED(xh) (shapetypes[(xh)-SHAPE_FIRST].form_b)
+#define SFINAL(xh) ((xh)+1)
+#define SINITIAL(xh) ((xh)+2)
+#define SMEDIAL(ch) ((ch)+3)
+
+#define leastGreaterOdd(x) ( ((x)+1) | 1 )
+#define leastGreaterEven(x) ( ((x)+2) &~ 1 )
+
+/* Function declarations used outside bidi.c */
+unsigned char bidi_getType(int ch);
+
+/* Bidi character types */
+#define BIDI_CHAR_TYPE_LIST(X) \
+ X(L) \
+ X(LRE) \
+ X(LRO) \
+ X(LRI) \
+ X(R) \
+ X(AL) \
+ X(RLE) \
+ X(RLO) \
+ X(RLI) \
+ X(PDF) \
+ X(PDI) \
+ X(FSI) \
+ X(EN) \
+ X(ES) \
+ X(ET) \
+ X(AN) \
+ X(CS) \
+ X(NSM) \
+ X(BN) \
+ X(B) \
+ X(S) \
+ X(WS) \
+ X(ON) \
+ /* end of list */
+
+/* Shaping Types */
+#define SHAPING_CHAR_TYPE_LIST(X) \
+ X(SL) /* Left-Joining, doesn't exist in U+0600 - U+06FF */ \
+ X(SR) /* Right-Joining, ie has Isolated, Final */ \
+ X(SD) /* Dual-Joining, ie has Isolated, Final, Initial, Medial */ \
+ X(SU) /* Non-Joining */ \
+ X(SC) /* Join-Causing, like U+0640 (TATWEEL) */ \
+ /* end of list */
+
+#define ENUM_DECL(name) name,
+typedef enum { BIDI_CHAR_TYPE_LIST(ENUM_DECL) N_BIDI_TYPES } BidiType;
+typedef enum { SHAPING_CHAR_TYPE_LIST(ENUM_DECL) N_SHAPING_TYPES } ShapingType;
+#undef ENUM_DECL
+
+static inline bool typeIsStrong(BidiType t)
+{
+ return ((1<<L) | (1<<R) | (1<<AL)) & (1 << t);
+}
+static inline bool typeIsWeak(BidiType t)
+{
+ return ((1<<EN) | (1<<ES) | (1<<ET) | (1<<AN) |
+ (1<<CS) | (1<<NSM) | (1<<BN)) & (1 << t);
+}
+static inline bool typeIsNeutral(BidiType t)
+{
+ return ((1<<B) | (1<<S) | (1<<WS) | (1<<ON)) & (1 << t);
+}
+static inline bool typeIsBidiActive(BidiType t)
+{
+ return ((1<<R) | (1<<AL) | (1<<AN) | (1<<RLE) | (1<<LRE) | (1<<RLO) |
+ (1<<LRO) | (1<<PDF) | (1<<RLI)) & (1 << t);
+}
+static inline bool typeIsIsolateInitiator(BidiType t)
+{
+ return ((1<<LRI) | (1<<RLI) | (1<<FSI)) & (1 << t);
+}
+static inline bool typeIsIsolateInitiatorOrPDI(BidiType t)
+{
+ return ((1<<LRI) | (1<<RLI) | (1<<FSI) | (1<<PDI)) & (1 << t);
+}
+static inline bool typeIsEmbeddingInitiator(BidiType t)
+{
+ return ((1<<LRE) | (1<<RLE) | (1<<LRO) | (1<<RLO)) & (1 << t);
+}
+static inline bool typeIsEmbeddingInitiatorOrPDF(BidiType t)
+{
+ return ((1<<LRE) | (1<<RLE) | (1<<LRO) | (1<<RLO) | (1<<PDF)) & (1 << t);
+}
+static inline bool typeIsWeakSeparatorOrTerminator(BidiType t)
+{
+ return ((1<<ES) | (1<<ET) | (1<<CS)) & (1 << t);
+}
+static inline bool typeIsNeutralOrIsolate(BidiType t)
+{
+ return ((1<<S) | (1<<WS) | (1<<ON) | (1<<FSI) | (1<<LRI) | (1<<RLI) |
+ (1<<PDI)) & (1 << t);
+}
+static inline bool typeIsSegmentOrParaSeparator(BidiType t)
+{
+ return ((1<<S) | (1<<B)) & (1 << t);
+}
+static inline bool typeIsWhitespaceOrIsolate(BidiType t)
+{
+ return ((1<<WS) | (1<<FSI) | (1<<LRI) | (1<<RLI) | (1<<PDI)) & (1 << t);
+}
+static inline bool typeIsRemovedDuringProcessing(BidiType t)
+{
+ return ((1<<RLE) | (1<<LRE) | (1<<RLO) | (1<<LRO) | (1<<PDF) |
+ (1<<BN)) & (1 << t);
+}
+static inline bool typeIsStrongOrNumber(BidiType t)
+{
+ return ((1<<L) | (1<<R) | (1<<AL) | (1<<EN) | (1<<AN)) & (1 << t);
+}
+static inline bool typeIsETOrBN(BidiType t)
+{
+ return ((1<<ET) | (1<<BN)) & (1 << t);
+}
+
+/*
+ * More featureful interface to the bidi code, for use in bidi_test.c.
+ * It returns a potentially different value of textlen (in case we're
+ * compiling in REMOVE_FORMATTING_CHARACTERS mode), and also permits
+ * you to pass in an override to the paragraph direction (because many
+ * of the UCD conformance tests use one).
+ *
+ * 'override' is 0 for no override, +1 for left-to-right, -1 for
+ * right-to-left.
+ */
+size_t do_bidi_test(BidiContext *ctx, bidi_char *text, size_t textlen,
+ int override);
+
+#endif /* PUTTY_BIDI_H */
diff --git a/terminal/bidi_gettype.c b/terminal/bidi_gettype.c
new file mode 100644
index 00000000..f3f5338e
--- /dev/null
+++ b/terminal/bidi_gettype.c
@@ -0,0 +1,33 @@
+/*
+ * Standalone test program that exposes the minibidi getType function.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#include "putty.h"
+#include "misc.h"
+#include "bidi.h"
+
+void out_of_memory(void)
+{
+ fprintf(stderr, "out of memory!\n");
+ exit(2);
+}
+
+#define TYPETONAME(X) #X,
+static const char *const typenames[] = { BIDI_CHAR_TYPE_LIST(TYPETONAME) };
+#undef TYPETONAME
+
+int main(int argc, char **argv)
+{
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ unsigned long chr = strtoul(argv[i], NULL, 0);
+ int type = bidi_getType(chr);
+ printf("U+%04x: %s\n", (unsigned)chr, typenames[type]);
+ }
+
+ return 0;
+}
diff --git a/terminal/bidi_test.c b/terminal/bidi_test.c
new file mode 100644
index 00000000..1acd1d68
--- /dev/null
+++ b/terminal/bidi_test.c
@@ -0,0 +1,372 @@
+/*
+ * Test program that reads the Unicode bidi algorithm test case lists
+ * that form part of the Unicode Character Database:
+ *
+ * https://www.unicode.org/Public/UCD/latest/ucd/BidiTest.txt
+ * https://www.unicode.org/Public/UCD/latest/ucd/BidiCharacterTest.txt
+ */
+
+#include <ctype.h>
+
+#include "putty.h"
+#include "misc.h"
+#include "bidi.h"
+
+static int pass = 0, fail = 0;
+
+static BidiContext *ctx;
+
+static const char *extract_word(char **ptr)
+{
+ char *p = *ptr;
+ while (*p && isspace((unsigned char)*p)) p++;
+
+ char *start = p;
+ while (*p && !isspace((unsigned char)*p)) p++;
+
+ if (*p) {
+ *p++ = '\0';
+ while (*p && isspace((unsigned char)*p)) p++;
+ }
+
+ *ptr = p;
+ return start;
+}
+
+#define TYPETONAME(X) #X,
+static const char *const typenames[] = { BIDI_CHAR_TYPE_LIST(TYPETONAME) };
+#undef TYPETONAME
+
+static void run_test(const char *filename, unsigned lineno,
+ bidi_char *bcs, size_t bcs_len,
+ const unsigned *order, size_t order_len,
+ int override)
+{
+ size_t bcs_orig_len = bcs_len;
+ bidi_char *bcs_orig = snewn(bcs_orig_len, bidi_char);
+ if (bcs_orig_len)
+ memcpy(bcs_orig, bcs, bcs_orig_len * sizeof(bidi_char));
+
+ bcs_len = do_bidi_test(ctx, bcs, bcs_len, override);
+
+ /*
+ * TR9 revision 44 rule X9 says we remove explicit embedding
+ * controls and BN characters. So the test cases don't list them
+ * in the expected outputs. Do the same to our own output - unless
+ * we're testing the standard version of the algorithm, in which
+ * case, we expect the output to be exactly as the test cases say.
+ */
+ unsigned *our_order = snewn(bcs_len, unsigned);
+ size_t our_order_len = 0;
+ for (size_t i = 0; i < bcs_len; i++) {
+ BidiType t = bidi_getType(bcs[i].wc);
+#ifndef REMOVE_FORMATTING_CHARS
+ if (typeIsRemovedDuringProcessing(t))
+ continue;
+#endif
+ our_order[our_order_len++] = bcs[i].index;
+ }
+
+ bool ok = false;
+ if (our_order_len == order_len) {
+ ok = true;
+ for (size_t i = 0; i < our_order_len; i++)
+ if (our_order[i] != order[i])
+ ok = false;
+ }
+ if (ok) {
+ pass++;
+ } else {
+ fail++;
+ printf("%s:%u: failed order\n", filename, lineno);
+ printf(" input chars:");
+ for (size_t i = 0; i < bcs_orig_len; i++)
+ printf(" %04x", bcs_orig[i].wc);
+ printf("\n");
+ printf(" classes: ");
+ for (size_t i = 0; i < bcs_orig_len; i++)
+ printf(" %-4s", typenames[bidi_getType(bcs_orig[i].wc)]);
+ printf("\n");
+ printf(" para level = %s\n",
+ override > 0 ? "LTR" : override < 0 ? "RTL" : "auto");
+ printf(" expected:");
+ for (size_t i = 0; i < order_len; i++)
+ printf(" %u", order[i]);
+ printf("\n");
+ printf(" got: ");
+ for (size_t i = 0; i < our_order_len; i++)
+ printf(" %u", our_order[i]);
+ printf("\n");
+ }
+
+ /* Put the original data back so we can re-test with another override */
+ memcpy(bcs, bcs_orig, bcs_orig_len * sizeof(bidi_char));
+
+ sfree(bcs_orig);
+ sfree(our_order);
+}
+
+static void class_test(const char *filename, FILE *fp)
+{
+ unsigned lineno = 0;
+ size_t bcs_size = 0, bcs_len = 0;
+ bidi_char *bcs = NULL;
+ size_t order_size = 0, order_len = 0;
+ unsigned *order = NULL;
+
+ /* Preliminary: find a representative character of every bidi
+ * type. Prefer positive-width ones if available. */
+ unsigned representatives[N_BIDI_TYPES];
+ for (size_t i = 0; i < N_BIDI_TYPES; i++)
+ representatives[i] = 0;
+ for (unsigned uc = 1; uc < 0x110000; uc++) {
+ unsigned type = bidi_getType(uc);
+ if (!representatives[type] ||
+ (mk_wcwidth(representatives[type]) <= 0 && mk_wcwidth(uc) > 0))
+ representatives[type] = uc;
+ }
+
+ while (true) {
+ lineno++;
+ char *line = chomp(fgetline(fp));
+ if (!line)
+ break;
+
+ /* Skip blank lines and comments */
+ if (!line[0] || line[0] == '#') {
+ sfree(line);
+ continue;
+ }
+
+ /* Parse @Reorder lines, which tell us the expected output
+ * order for all following test cases (until superseded) */
+ if (strstartswith(line, "@Reorder:")) {
+ char *p = line;
+ extract_word(&p); /* eat the "@Reorder:" header itself */
+ order_len = 0;
+ while (1) {
+ const char *word = extract_word(&p);
+ if (!*word)
+ break;
+ sgrowarray(order, order_size, order_len);
+ order[order_len++] = strtoul(word, NULL, 0);
+ }
+
+ sfree(line);
+ continue;
+ }
+
+ /* Skip @Levels lines, which we don't (yet?) do anything with */
+ if (strstartswith(line, "@Levels:")) {
+ sfree(line);
+ continue;
+ }
+
+ /* Everything remaining should be an actual test */
+ char *semicolon = strchr(line, ';');
+ if (!semicolon) {
+ printf("%s:%u: bad test line': no bitmap\n", filename, lineno);
+ sfree(line);
+ continue;
+ }
+ *semicolon++ = '\0';
+ unsigned bitmask = strtoul(semicolon, NULL, 0);
+ char *p = line;
+ bcs_len = 0;
+ bool test_ok = true;
+ while (1) {
+ const char *word = extract_word(&p);
+ if (!*word)
+ break;
+ unsigned type;
+ for (type = 0; type < N_BIDI_TYPES; type++)
+ if (!strcmp(word, typenames[type]))
+ break;
+ if (type == N_BIDI_TYPES) {
+ printf("%s:%u: bad test line: bad bidi type '%s'\n",
+ filename, lineno, word);
+ test_ok = false;
+ break;
+ }
+ sgrowarray(bcs, bcs_size, bcs_len);
+ bcs[bcs_len].wc = representatives[type];
+ bcs[bcs_len].origwc = bcs[bcs_len].wc;
+ bcs[bcs_len].index = bcs_len;
+ bcs[bcs_len].nchars = 1;
+ bcs_len++;
+ }
+
+ if (!test_ok) {
+ sfree(line);
+ continue;
+ }
+
+ if (bitmask & 1)
+ run_test(filename, lineno, bcs, bcs_len, order, order_len, 0);
+ if (bitmask & 2)
+ run_test(filename, lineno, bcs, bcs_len, order, order_len, +1);
+ if (bitmask & 4)
+ run_test(filename, lineno, bcs, bcs_len, order, order_len, -1);
+
+ sfree(line);
+ }
+
+ sfree(bcs);
+ sfree(order);
+}
+
+static void char_test(const char *filename, FILE *fp)
+{
+ unsigned lineno = 0;
+ size_t bcs_size = 0, bcs_len = 0;
+ bidi_char *bcs = NULL;
+ size_t order_size = 0, order_len = 0;
+ unsigned *order = NULL;
+
+ while (true) {
+ lineno++;
+ char *line = chomp(fgetline(fp));
+ if (!line)
+ break;
+
+ /* Skip blank lines and comments */
+ if (!line[0] || line[0] == '#') {
+ sfree(line);
+ continue;
+ }
+
+ /* Break each test line up into its main fields */
+ ptrlen input_pl, para_dir_pl, order_pl;
+ {
+ ptrlen pl = ptrlen_from_asciz(line);
+ input_pl = ptrlen_get_word(&pl, ";");
+ para_dir_pl = ptrlen_get_word(&pl, ";");
+ ptrlen_get_word(&pl, ";"); /* paragraph level, which we ignore */
+ ptrlen_get_word(&pl, ";"); /* embedding levels, which we ignore */
+ order_pl = ptrlen_get_word(&pl, ";");
+ }
+
+ int override;
+ {
+ char *para_dir_str = mkstr(para_dir_pl);
+ unsigned para_dir = strtoul(para_dir_str, NULL, 0);
+ sfree(para_dir_str);
+
+ override = (para_dir == 0 ? +1 : para_dir == 1 ? -1 : 0);
+ }
+
+ /* Break up the input into Unicode characters */
+ bcs_len = 0;
+ {
+ ptrlen pl = input_pl;
+ while (pl.len) {
+ ptrlen chr = ptrlen_get_word(&pl, " ");
+ char *chrstr = mkstr(chr);
+ sgrowarray(bcs, bcs_size, bcs_len);
+ bcs[bcs_len].wc = strtoul(chrstr, NULL, 16);
+ bcs[bcs_len].origwc = bcs[bcs_len].wc;
+ bcs[bcs_len].index = bcs_len;
+ bcs[bcs_len].nchars = 1;
+ bcs_len++;
+ sfree(chrstr);
+ }
+ }
+
+ /* Ditto the expected output order */
+ order_len = 0;
+ {
+ ptrlen pl = order_pl;
+ while (pl.len) {
+ ptrlen chr = ptrlen_get_word(&pl, " ");
+ char *chrstr = mkstr(chr);
+ sgrowarray(order, order_size, order_len);
+ order[order_len++] = strtoul(chrstr, NULL, 0);
+ sfree(chrstr);
+ }
+ }
+
+ run_test(filename, lineno, bcs, bcs_len, order, order_len, override);
+ sfree(line);
+ }
+
+ sfree(bcs);
+ sfree(order);
+}
+
+void out_of_memory(void)
+{
+ fprintf(stderr, "out of memory!\n");
+ exit(2);
+}
+
+static void usage(FILE *fp)
+{
+ fprintf(fp, "\
+usage: bidi_test ( ( --class | --char ) infile... )...\n\
+e.g.: bidi_test --class BidiTest.txt --char BidiCharacterTest.txt\n\
+also: --help display this text\n\
+");
+}
+
+int main(int argc, char **argv)
+{
+ void (*testfn)(const char *, FILE *) = NULL;
+ bool doing_opts = true;
+ const char *filename = NULL;
+ bool done_something = false;
+
+ ctx = bidi_new_context();
+
+ while (--argc > 0) {
+ const char *arg = *++argv;
+ if (doing_opts && arg[0] == '-' && arg[1]) {
+ if (!strcmp(arg, "--")) {
+ doing_opts = false;
+ } else if (!strcmp(arg, "--class")) {
+ testfn = class_test;
+ } else if (!strcmp(arg, "--char")) {
+ testfn = char_test;
+ } else if (!strcmp(arg, "--help")) {
+ usage(stdout);
+ return 0;
+ } else {
+ fprintf(stderr, "unrecognised option '%s'\n", arg);
+ return 1;
+ }
+ } else {
+ const char *filename = arg;
+
+ if (!testfn) {
+ fprintf(stderr, "no mode argument provided before filename "
+ "'%s'\n", filename);
+ return 1;
+ }
+
+ if (!strcmp(filename, "-")) {
+ testfn("<standard input>", stdin);
+ } else {
+ FILE *fp = fopen(filename, "r");
+ if (!fp) {
+ fprintf(stderr, "unable to open '%s'\n", filename);
+ return 1;
+ }
+ testfn(filename, fp);
+ fclose(fp);
+ }
+ done_something = true;
+ }
+ }
+
+ if (!done_something) {
+ usage(stderr);
+ return 1;
+ }
+
+ if (!filename)
+ filename = "-";
+
+ printf("pass %d fail %d total %d\n", pass, fail, pass + fail);
+
+ bidi_free_context(ctx);
+ return fail != 0;
+}
diff --git a/terminal/terminal.c b/terminal/terminal.c
new file mode 100644
index 00000000..37fa3513
--- /dev/null
+++ b/terminal/terminal.c
@@ -0,0 +1,7910 @@
+/*
+ * Terminal emulator.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <limits.h>
+#include <wchar.h>
+
+#include <time.h>
+#include <assert.h>
+#include "putty.h"
+#include "terminal.h"
+
+#define VT52_PLUS
+
+#define CL_ANSIMIN 0x0001 /* Codes in all ANSI like terminals. */
+#define CL_VT100 0x0002 /* VT100 */
+#define CL_VT100AVO 0x0004 /* VT100 +AVO; 132x24 (not 132x14) & attrs */
+#define CL_VT102 0x0008 /* VT102 */
+#define CL_VT220 0x0010 /* VT220 */
+#define CL_VT320 0x0020 /* VT320 */
+#define CL_VT420 0x0040 /* VT420 */
+#define CL_VT510 0x0080 /* VT510, NB VT510 includes ANSI */
+#define CL_VT340TEXT 0x0100 /* VT340 extensions that appear in the VT420 */
+#define CL_SCOANSI 0x1000 /* SCOANSI not in ANSIMIN. */
+#define CL_ANSI 0x2000 /* ANSI ECMA-48 not in the VT100..VT420 */
+#define CL_OTHER 0x4000 /* Others, Xterm, linux, putty, dunno, etc */
+
+#define TM_VT100 (CL_ANSIMIN|CL_VT100)
+#define TM_VT100AVO (TM_VT100|CL_VT100AVO)
+#define TM_VT102 (TM_VT100AVO|CL_VT102)
+#define TM_VT220 (TM_VT102|CL_VT220)
+#define TM_VTXXX (TM_VT220|CL_VT340TEXT|CL_VT510|CL_VT420|CL_VT320)
+#define TM_SCOANSI (CL_ANSIMIN|CL_SCOANSI)
+
+#define TM_PUTTY (0xFFFF)
+
+#define UPDATE_DELAY ((TICKSPERSEC+49)/50)/* ticks to defer window update */
+#define TBLINK_DELAY ((TICKSPERSEC*9+19)/20)/* ticks between text blinks*/
+#define CBLINK_DELAY (CURSORBLINK) /* ticks between cursor blinks */
+#define VBELL_DELAY (VBELL_TIMEOUT) /* visual bell timeout in ticks */
+
+#define compatibility(x) \
+ if ( ((CL_##x)&term->compatibility_level) == 0 ) { \
+ term->termstate=TOPLEVEL; \
+ break; \
+ }
+#define compatibility2(x,y) \
+ if ( ((CL_##x|CL_##y)&term->compatibility_level) == 0 ) { \
+ term->termstate=TOPLEVEL; \
+ break; \
+ }
+
+#define has_compat(x) ( ((CL_##x)&term->compatibility_level) != 0 )
+
+static const char *const EMPTY_WINDOW_TITLE = "";
+
+static const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 };
+
+#define sel_nl_sz (sizeof(sel_nl)/sizeof(wchar_t))
+static const wchar_t sel_nl[] = SEL_NL;
+
+/*
+ * 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, bool);
+static void term_update_raw_mouse_mode(Terminal *term);
+static void term_out_cb(void *);
+
+static termline *newtermline(Terminal *term, int cols, bool bce)
+{
+ termline *line;
+ int j;
+
+ line = snew(termline);
+ line->chars = snewn(cols, termchar);
+ for (j = 0; j < cols; j++)
+ line->chars[j] = (bce ? term->erase_char : term->basic_erase_char);
+ line->cols = line->size = cols;
+ line->lattr = LATTR_NORM;
+ line->trusted = false;
+ line->temporary = false;
+ line->cc_free = 0;
+
+ return line;
+}
+
+static void freetermline(termline *line)
+{
+ if (line) {
+ sfree(line->chars);
+ sfree(line);
+ }
+}
+
+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--) {
+ assert(n < ldata->cols);
+ readliteral(bs, ldata->chars + n, ldata, &state);
+ n++;
+ }
+ }
+ }
+
+ assert(n == ldata->cols);
+}
+static void readliteral_chr(BinarySource *bs, termchar *c, termline *ldata,
+ unsigned long *state)
+{
+ int byte;
+
+ /*
+ * 00000000-0000007F: 0xxxxxxx
+ * 00000080-00003FFF: 10xxxxxx xxxxxxxx
+ * 00004000-001FFFFF: 110xxxxx xxxxxxxx xxxxxxxx
+ * 00200000-0FFFFFFF: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx
+ * 10000000-FFFFFFFF: 11110ZZZ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
+ */
+
+ byte = get_byte(bs);
+ if (byte < 0x80) {
+ c->chr = byte | *state;
+ } else if (byte < 0xC0) {
+ c->chr = (byte &~ 0xC0) << 8;
+ c->chr |= get_byte(bs);
+ } else if (byte < 0xE0) {
+ c->chr = (byte &~ 0xE0) << 16;
+ c->chr |= get_uint16(bs);
+ } else if (byte < 0xF0) {
+ c->chr = (byte &~ 0xF0) << 24;
+ c->chr |= get_byte(bs) << 16;
+ c->chr |= get_uint16(bs);
+ } else {
+ assert(byte == 0xF0);
+ c->chr = get_uint32(bs);
+ }
+ *state = c->chr & ~0xFF;
+}
+static void readliteral_attr(BinarySource *bs, termchar *c, termline *ldata,
+ unsigned long *state)
+{
+ unsigned val, attr, colourbits;
+
+ val = get_uint16(bs);
+
+ if (val >= 0x8000) {
+ val &= ~0x8000;
+ val <<= 16;
+ val |= get_uint16(bs);
+ }
+
+ colourbits = (val >> (32-9)) & 0xFF;
+ attr = (val & ((1<<(32-9))-1));
+
+ attr = (((attr >> (ATTR_FGSHIFT + 4)) << (ATTR_FGSHIFT + 8)) |
+ (attr & ((1 << (ATTR_FGSHIFT + 4))-1)));
+ attr = (((attr >> (ATTR_BGSHIFT + 4)) << (ATTR_BGSHIFT + 8)) |
+ (attr & ((1 << (ATTR_BGSHIFT + 4))-1)));
+
+ attr |= (colourbits >> 4) << (ATTR_BGSHIFT + 4);
+ attr |= (colourbits & 0xF) << (ATTR_FGSHIFT + 4);
+
+ c->attr = attr;
+}
+static void readliteral_truecolour(
+ BinarySource *bs, termchar *c, termline *ldata, unsigned long *state)
+{
+ int flags = get_byte(bs);
+
+ if (flags & 1) {
+ c->truecolour.fg.enabled = true;
+ c->truecolour.fg.r = get_byte(bs);
+ c->truecolour.fg.g = get_byte(bs);
+ c->truecolour.fg.b = get_byte(bs);
+ } else {
+ c->truecolour.fg = optionalrgb_none;
+ }
+
+ if (flags & 2) {
+ c->truecolour.bg.enabled = true;
+ c->truecolour.bg.r = get_byte(bs);
+ c->truecolour.bg.g = get_byte(bs);
+ c->truecolour.bg.b = get_byte(bs);
+ } else {
+ c->truecolour.bg = optionalrgb_none;
+ }
+}
+static void readliteral_cc(BinarySource *bs, termchar *c, termline *ldata,
+ unsigned long *state)
+{
+ termchar n;
+ unsigned long zstate;
+ int x = c - ldata->chars;
+
+ c->cc_next = 0;
+
+ while (1) {
+ zstate = 0;
+ readliteral_chr(bs, &n, ldata, &zstate);
+ if (!n.chr)
+ break;
+ add_cc(ldata, x, n.chr);
+ }
+}
+
+static termline *decompressline(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 <putty@projects.tartarus.org> "
+ "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;
+
+ assert(!screen);
+
+ if (term->erase_to_scrollback &&
+ term->alt_which && term->alt_screen) {
+ altlines = term->alt_sblines;
+ }
+ if (y < -altlines) {
+ whichtree = term->scrollback;
+ treeindex = y + altlines + count234(term->scrollback);
+ } else {
+ whichtree = term->alt_screen;
+ treeindex = y + term->alt_sblines;
+ /* treeindex = y + count234(term->alt_screen); */
+ }
+ }
+ if (whichtree == term->scrollback) {
+ 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);
+ term->xterm_mouse = 0;
+ term->xterm_extended_mouse = false;
+ term->urxvt_extended_mouse = false;
+ win_set_raw_mouse_mode(term->win, false);
+ term->win_pointer_shape_pending = true;
+ term->win_pointer_shape_raw = false;
+ term->bracketed_paste = false;
+ term->srm_echo = false;
+ {
+ int i;
+ for (i = 0; i < 256; i++)
+ term->wordness[i] = conf_get_int_int(term->conf, CONF_wordness, i);
+ }
+ if (term->screen) {
+ swap_screen(term, 1, false, false);
+ erase_lots(term, false, true, true);
+ swap_screen(term, 0, false, false);
+ if (clear)
+ erase_lots(term, false, true, true);
+ term->curs.y = find_last_nonempty_line(term, term->screen) + 1;
+ if (term->curs.y == term->rows) {
+ term->curs.y--;
+ scroll(term, 0, term->rows - 1, 1, true);
+ }
+ } else {
+ term->curs.y = 0;
+ }
+ term->curs.x = 0;
+ term_schedule_tblink(term);
+ term_schedule_cblink(term);
+ term_schedule_update(term);
+}
+
+/*
+ * Force a screen update.
+ */
+void term_update(Terminal *term)
+{
+ term->window_update_pending = false;
+
+ if (term->win_move_pending) {
+ win_move(term->win, term->win_move_pending_x,
+ term->win_move_pending_y);
+ term->win_move_pending = false;
+ }
+ if (term->win_resize_pending == WIN_RESIZE_NEED_SEND) {
+ term->win_resize_pending = WIN_RESIZE_AWAIT_REPLY;
+ win_request_resize(term->win, term->win_resize_pending_w,
+ term->win_resize_pending_h);
+ }
+ if (term->win_zorder_pending) {
+ win_set_zorder(term->win, term->win_zorder_top);
+ term->win_zorder_pending = false;
+ }
+ if (term->win_minimise_pending) {
+ win_set_minimised(term->win, term->win_minimise_enable);
+ term->win_minimise_pending = false;
+ }
+ if (term->win_maximise_pending) {
+ win_set_maximised(term->win, term->win_maximise_enable);
+ term->win_maximise_pending = false;
+ }
+ if (term->win_title_pending) {
+ win_set_title(term->win, term->window_title,
+ term->wintitle_codepage);
+ term->win_title_pending = false;
+ }
+ if (term->win_icon_title_pending) {
+ win_set_icon_title(term->win, term->icon_title,
+ term->icontitle_codepage);
+ term->win_icon_title_pending = false;
+ }
+ if (term->win_pointer_shape_pending) {
+ win_set_raw_mouse_mode_pointer(term->win, term->win_pointer_shape_raw);
+ term->win_pointer_shape_pending = false;
+ }
+ if (term->win_refresh_pending) {
+ win_refresh(term->win);
+ term->win_refresh_pending = false;
+ }
+ if (term->win_palette_pending) {
+ unsigned start = term->win_palette_pending_min;
+ unsigned ncolours = term->win_palette_pending_limit - start;
+ win_palette_set(term->win, start, ncolours, term->palette + start);
+ term->win_palette_pending = false;
+ }
+
+ if (win_setup_draw_ctx(term->win)) {
+ 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.
+ */
+static void term_copy_stuff_from_conf(Terminal *term)
+{
+ term->ansi_colour = conf_get_bool(term->conf, CONF_ansi_colour);
+ term->no_arabicshaping = conf_get_bool(term->conf, CONF_no_arabicshaping);
+ term->beep = conf_get_int(term->conf, CONF_beep);
+ term->bellovl = conf_get_bool(term->conf, CONF_bellovl);
+ term->bellovl_n = conf_get_int(term->conf, CONF_bellovl_n);
+ term->bellovl_s = conf_get_int(term->conf, CONF_bellovl_s);
+ term->bellovl_t = conf_get_int(term->conf, CONF_bellovl_t);
+ term->no_bidi = conf_get_bool(term->conf, CONF_no_bidi);
+ term->bksp_is_delete = conf_get_bool(term->conf, CONF_bksp_is_delete);
+ term->blink_cur = conf_get_bool(term->conf, CONF_blink_cur);
+ term->blinktext = conf_get_bool(term->conf, CONF_blinktext);
+ term->cjk_ambig_wide = conf_get_bool(term->conf, CONF_cjk_ambig_wide);
+ term->conf_height = conf_get_int(term->conf, CONF_height);
+ term->conf_width = conf_get_int(term->conf, CONF_width);
+ term->crhaslf = conf_get_bool(term->conf, CONF_crhaslf);
+ term->erase_to_scrollback = conf_get_bool(term->conf, CONF_erase_to_scrollback);
+ term->funky_type = conf_get_int(term->conf, CONF_funky_type);
+ term->sharrow_type = conf_get_int(term->conf, CONF_sharrow_type);
+ term->lfhascr = conf_get_bool(term->conf, CONF_lfhascr);
+ term->logflush = conf_get_bool(term->conf, CONF_logflush);
+ term->logtype = conf_get_int(term->conf, CONF_logtype);
+ term->mouse_override = conf_get_bool(term->conf, CONF_mouse_override);
+ term->nethack_keypad = conf_get_bool(term->conf, CONF_nethack_keypad);
+ term->no_alt_screen = conf_get_bool(term->conf, CONF_no_alt_screen);
+ term->no_applic_c = conf_get_bool(term->conf, CONF_no_applic_c);
+ term->no_applic_k = conf_get_bool(term->conf, CONF_no_applic_k);
+ term->no_dbackspace = conf_get_bool(term->conf, CONF_no_dbackspace);
+ term->no_mouse_rep = conf_get_bool(term->conf, CONF_no_mouse_rep);
+ term->no_remote_charset = conf_get_bool(term->conf, CONF_no_remote_charset);
+ term->no_remote_resize = conf_get_bool(term->conf, CONF_no_remote_resize);
+ term->no_remote_wintitle = conf_get_bool(term->conf, CONF_no_remote_wintitle);
+ term->no_remote_clearscroll = conf_get_bool(term->conf, CONF_no_remote_clearscroll);
+ term->rawcnp = conf_get_bool(term->conf, CONF_rawcnp);
+ term->utf8linedraw = conf_get_bool(term->conf, CONF_utf8linedraw);
+ term->rect_select = conf_get_bool(term->conf, CONF_rect_select);
+ term->remote_qtitle_action = conf_get_int(term->conf, CONF_remote_qtitle_action);
+ term->rxvt_homeend = conf_get_bool(term->conf, CONF_rxvt_homeend);
+ term->scroll_on_disp = conf_get_bool(term->conf, CONF_scroll_on_disp);
+ term->scroll_on_key = conf_get_bool(term->conf, CONF_scroll_on_key);
+ term->xterm_mouse_forbidden = conf_get_bool(term->conf, CONF_no_mouse_rep);
+ term->xterm_256_colour = conf_get_bool(term->conf, CONF_xterm_256_colour);
+ term->true_colour = conf_get_bool(term->conf, CONF_true_colour);
+
+ /*
+ * Parse the control-character escapes in the configured
+ * answerback string.
+ */
+ {
+ char *answerback = conf_get_str(term->conf, CONF_answerback);
+ 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->wintitle_codepage = DEFAULT_CODEPAGE;
+ term->win_title_pending = true;
+ term_schedule_update(term);
+ }
+ }
+
+ /*
+ * Just setting conf is sufficient to cause colour setting changes
+ * to appear on the next ESC]R palette reset. But we should also
+ * check whether any colour settings have been changed, so that
+ * they can be updated immediately if they haven't been overridden
+ * by some escape sequence.
+ */
+ {
+ int i, j;
+ for (i = 0; i < CONF_NCOLOURS; i++) {
+ for (j = 0; j < 3; j++)
+ if (conf_get_int_int(term->conf, CONF_colours, i*3+j) !=
+ conf_get_int_int(conf, CONF_colours, i*3+j))
+ break;
+ if (j < 3) {
+ /* Actually enacting the change has to be deferred
+ * until the new conf is installed. */
+ palette_changed = true;
+ break;
+ }
+ }
+ }
+
+ conf_free(term->conf);
+ term->conf = conf_copy(conf);
+
+ if (reset_wrap)
+ term->alt_wrap = term->wrap = conf_get_bool(term->conf, CONF_wrap_mode);
+ if (reset_decom)
+ term->alt_om = term->dec_om = conf_get_bool(term->conf, CONF_dec_om);
+ if (reset_bce) {
+ term->use_bce = conf_get_bool(term->conf, CONF_bce);
+ set_erase_char(term);
+ }
+ if (reset_tblink) {
+ term->blink_is_real = conf_get_bool(term->conf, CONF_blinktext);
+ }
+ if (reset_charclass)
+ for (i = 0; i < 256; i++)
+ term->wordness[i] = conf_get_int_int(term->conf, CONF_wordness, i);
+
+ if (conf_get_bool(term->conf, CONF_no_alt_screen))
+ swap_screen(term, 0, false, false);
+ if (conf_get_bool(term->conf, CONF_no_remote_charset)) {
+ term->cset_attr[0] = term->cset_attr[1] = CSET_ASCII;
+ term->sco_acs = term->alt_sco_acs = 0;
+ term->utf = false;
+ }
+ if (!conf_get_str(term->conf, CONF_printer)) {
+ term_print_finish(term);
+ }
+ if (palette_changed)
+ term_notify_palette_changed(term);
+ term_schedule_tblink(term);
+ term_schedule_cblink(term);
+ term_copy_stuff_from_conf(term);
+ term_update_raw_mouse_mode(term);
+}
+
+/*
+ * Clear the scrollback.
+ */
+void term_clrsb(Terminal *term)
+{
+ unsigned char *line;
+ int i;
+
+ /*
+ * Scroll forward to the current screen, if we were back in the
+ * scrollback somewhere until now.
+ */
+ term->disptop = 0;
+
+ /*
+ * Clear the actual scrollback.
+ */
+ while ((line = delpos234(term->scrollback, 0)) != NULL) {
+ sfree(line); /* this is compressed data, not a termline */
+ }
+
+ /*
+ * When clearing the scrollback, we also truncate any termlines on
+ * the current screen which have remembered data from a previous
+ * larger window size. Rationale: clearing the scrollback is
+ * sometimes done to protect privacy, so the user intention is
+ * specifically that we should not retain evidence of what
+ * previously happened in the terminal, and that ought to include
+ * evidence to the right as well as evidence above.
+ */
+ for (i = 0; i < term->rows; i++)
+ check_line_size(term, scrlineptr(i));
+
+ /*
+ * That operation has invalidated the selection, if it overlapped
+ * the scrollback at all.
+ */
+ if (term->selstate != NO_SELECTION && term->selstart.y < 0)
+ deselect(term);
+
+ /*
+ * There are now no lines of real scrollback which can be pulled
+ * back into the screen by a resize, and no lines of the alternate
+ * screen which should be displayed as if part of the scrollback.
+ */
+ term->tempsblines = 0;
+ term->alt_sblines = 0;
+
+ /*
+ * The scrollbar will need updating to reflect the new state of
+ * the world.
+ */
+ term->win_scrollbar_update_pending = true;
+ term_schedule_update(term);
+}
+
+const optionalrgb optionalrgb_none = {0, 0, 0, 0};
+
+void term_setup_window_titles(Terminal *term, const char *title_hostname)
+{
+ const char *conf_title = conf_get_str(term->conf, CONF_wintitle);
+ sfree(term->window_title);
+ sfree(term->icon_title);
+ if (*conf_title) {
+ term->window_title = dupstr(conf_title);
+ term->icon_title = dupstr(conf_title);
+ } else {
+ if (title_hostname && *title_hostname)
+ term->window_title = dupcat(title_hostname, " - ", appname);
+ else
+ term->window_title = dupstr(appname);
+ term->icon_title = dupstr(term->window_title);
+ }
+ term->wintitle_codepage = term->icontitle_codepage = DEFAULT_CODEPAGE;
+ term->win_title_pending = true;
+ term->win_icon_title_pending = true;
+}
+
+static void palette_rebuild(Terminal *term)
+{
+ unsigned min_changed = OSC4_NCOLOURS, max_changed = 0;
+
+ if (term->win_palette_pending) {
+ /* Possibly extend existing range. */
+ min_changed = term->win_palette_pending_min;
+ max_changed = term->win_palette_pending_limit - 1;
+ } else {
+ /* Start with empty range. */
+ min_changed = OSC4_NCOLOURS;
+ max_changed = 0;
+ }
+
+ for (unsigned i = 0; i < OSC4_NCOLOURS; i++) {
+ rgb new_value;
+ bool found = false;
+
+ for (unsigned j = lenof(term->subpalettes); j-- > 0 ;) {
+ if (term->subpalettes[j].present[i]) {
+ new_value = term->subpalettes[j].values[i];
+ found = true;
+ break;
+ }
+ }
+
+ assert(found); /* we expect SUBPAL_CONF to always be set */
+
+ if (new_value.r != term->palette[i].r ||
+ new_value.g != term->palette[i].g ||
+ new_value.b != term->palette[i].b) {
+ term->palette[i] = new_value;
+ if (min_changed > i)
+ min_changed = i;
+ if (max_changed < i)
+ max_changed = i;
+ }
+ }
+
+ if (min_changed <= max_changed) {
+ /*
+ * At least one colour changed (or we had an update scheduled
+ * already). Schedule a redraw event to pass the result back
+ * to the TermWin. This also requires invalidating the rest
+ * of the window, because usually all the text will need
+ * redrawing in the new colours.
+ * (If there was an update pending and this palette rebuild
+ * didn't actually change anything, we'll harmlessly reinforce
+ * the existing update request.)
+ */
+ term->win_palette_pending = true;
+ term->win_palette_pending_min = min_changed;
+ term->win_palette_pending_limit = max_changed + 1;
+ term_invalidate(term);
+ }
+}
+
+/*
+ * Rebuild the palette from configuration and platform colours.
+ * If 'keep_overrides' set, any escape-sequence-specified overrides will
+ * remain in place.
+ */
+static void palette_reset(Terminal *term, bool keep_overrides)
+{
+ for (unsigned i = 0; i < OSC4_NCOLOURS; i++)
+ term->subpalettes[SUBPAL_CONF].present[i] = true;
+
+ /*
+ * Copy all the palette information out of the Conf.
+ */
+ for (unsigned i = 0; i < CONF_NCOLOURS; i++) {
+ rgb *col = &term->subpalettes[SUBPAL_CONF].values[
+ colour_indices_conf_to_osc4[i]];
+ col->r = conf_get_int_int(term->conf, CONF_colours, i*3+0);
+ col->g = conf_get_int_int(term->conf, CONF_colours, i*3+1);
+ col->b = conf_get_int_int(term->conf, CONF_colours, i*3+2);
+ }
+
+ /*
+ * Directly invent the rest of the xterm-256 colours.
+ */
+ for (unsigned i = 0; i < 216; i++) {
+ rgb *col = &term->subpalettes[SUBPAL_CONF].values[i + 16];
+ int r = i / 36, g = (i / 6) % 6, b = i % 6;
+ col->r = r ? r * 40 + 55 : 0;
+ col->g = g ? g * 40 + 55 : 0;
+ col->b = b ? b * 40 + 55 : 0;
+ }
+ for (unsigned i = 0; i < 24; i++) {
+ rgb *col = &term->subpalettes[SUBPAL_CONF].values[i + 232];
+ int shade = i * 10 + 8;
+ col->r = col->g = col->b = shade;
+ }
+
+ /*
+ * Re-fetch any OS-local overrides.
+ */
+ for (unsigned i = 0; i < OSC4_NCOLOURS; i++)
+ term->subpalettes[SUBPAL_PLATFORM].present[i] = false;
+ win_palette_get_overrides(term->win, term);
+
+ if (!keep_overrides) {
+ /*
+ * Get rid of all escape-sequence configuration.
+ */
+ for (unsigned i = 0; i < OSC4_NCOLOURS; i++)
+ term->subpalettes[SUBPAL_SESSION].present[i] = false;
+ }
+
+ /*
+ * Rebuild the composite palette.
+ */
+ palette_rebuild(term);
+}
+
+void term_palette_override(Terminal *term, unsigned osc4_index, rgb rgb)
+{
+ /*
+ * We never expect to be called except as re-entry from our own
+ * call to win_palette_get_overrides above, so we need not mess
+ * about calling palette_rebuild.
+ */
+ term->subpalettes[SUBPAL_PLATFORM].present[osc4_index] = true;
+ term->subpalettes[SUBPAL_PLATFORM].values[osc4_index] = rgb;
+}
+
+/*
+ * Initialise the terminal.
+ */
+Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, TermWin *win)
+{
+ Terminal *term;
+
+ /*
+ * Allocate a new Terminal structure and initialise the fields
+ * that need it.
+ */
+ term = snew(Terminal);
+ 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;
+
+ 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;
+
+ /* 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;
+
+ term->bracketed_paste_active = false;
+
+ term->window_title = dupstr("");
+ term->icon_title = dupstr("");
+ term->wintitle_codepage = term->icontitle_codepage = DEFAULT_CODEPAGE;
+ 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 = WIN_RESIZE_NO;
+ 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;
+
+ term->bidi_ctx = bidi_new_context();
+
+ 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);
+
+ bidi_free_context(term->bidi_ctx);
+
+ 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);
+}
+
+void term_resize_request_completed(Terminal *term)
+{
+ assert(term->win_resize_pending == WIN_RESIZE_AWAIT_REPLY);
+ term->win_resize_pending = WIN_RESIZE_NO;
+ queue_toplevel_callback(term_out_cb, term);
+}
+
+/*
+ * Hand a backend to the terminal, so it can be notified of resizes.
+ */
+void term_provide_backend(Terminal *term, Backend *backend)
+{
+ term->backend = backend;
+ if (term->backend && term->cols > 0 && term->rows > 0)
+ backend_size(term->backend, term->cols, term->rows);
+}
+
+/* Find the bottom line on the screen that has any content.
+ * If only the top line has content, returns 0.
+ * If no lines have content, return -1.
+ */
+static int find_last_nonempty_line(Terminal *term, tree234 *screen)
+{
+ int i;
+ for (i = count234(screen) - 1; i >= 0; i--) {
+ termline *line = index234(screen, i);
+ int j;
+ for (j = 0; j < line->cols; j++)
+ if (!termchars_equal(&line->chars[j], &term->erase_char))
+ break;
+ if (j != line->cols) break;
+ }
+ return i;
+}
+
+/*
+ * Swap screens. If `reset' is true and we have been asked to
+ * switch to the alternate screen, we must bring most of its
+ * configuration from the main screen and erase the contents of the
+ * alternate screen completely. (This is even true if we're already
+ * on it! Blame xterm.)
+ */
+static void swap_screen(Terminal *term, int which,
+ bool reset, bool keep_cur_pos)
+{
+ int t;
+ bool bt;
+ pos tp;
+ truecolour ttc;
+ tree234 *ttr;
+
+ if (!which)
+ reset = false; /* do no weird resetting if which==0 */
+
+ if (which != term->alt_which) {
+ if (term->erase_to_scrollback && term->alt_screen &&
+ term->alt_which && term->disptop < 0) {
+ /*
+ * We're swapping away from the alternate screen, so some
+ * lines are about to vanish from the virtual scrollback.
+ * Adjust disptop by that much, so that (if we're not
+ * resetting the scrollback anyway on a display event) the
+ * current scroll position still ends up pointing at the
+ * same text.
+ */
+ term->disptop += term->alt_sblines;
+ if (term->disptop > 0)
+ term->disptop = 0;
+ }
+
+ term->alt_which = which;
+
+ ttr = term->alt_screen;
+ term->alt_screen = term->screen;
+ term->screen = ttr;
+ term->alt_sblines = (
+ term->alt_screen ?
+ find_last_nonempty_line(term, term->alt_screen) + 1 : 0);
+ t = term->curs.x;
+ if (!reset && !keep_cur_pos)
+ term->curs.x = term->alt_x;
+ term->alt_x = t;
+ t = term->curs.y;
+ if (!reset && !keep_cur_pos)
+ term->curs.y = term->alt_y;
+ term->alt_y = t;
+ t = term->marg_t;
+ if (!reset) term->marg_t = term->alt_t;
+ term->alt_t = t;
+ t = term->marg_b;
+ if (!reset) term->marg_b = term->alt_b;
+ term->alt_b = t;
+ bt = term->dec_om;
+ if (!reset) term->dec_om = term->alt_om;
+ term->alt_om = bt;
+ bt = term->wrap;
+ if (!reset) term->wrap = term->alt_wrap;
+ term->alt_wrap = bt;
+ bt = term->wrapnext;
+ if (!reset) term->wrapnext = term->alt_wnext;
+ term->alt_wnext = bt;
+ bt = term->insert;
+ if (!reset) term->insert = term->alt_ins;
+ term->alt_ins = bt;
+ t = term->cset;
+ if (!reset) term->cset = term->alt_cset;
+ term->alt_cset = t;
+ bt = term->utf;
+ if (!reset) term->utf = term->alt_utf;
+ term->alt_utf = bt;
+ t = term->sco_acs;
+ if (!reset) term->sco_acs = term->alt_sco_acs;
+ term->alt_sco_acs = t;
+
+ tp = term->savecurs;
+ if (!reset)
+ term->savecurs = term->alt_savecurs;
+ term->alt_savecurs = tp;
+ t = term->save_cset;
+ if (!reset)
+ term->save_cset = term->alt_save_cset;
+ term->alt_save_cset = t;
+ t = term->save_csattr;
+ if (!reset)
+ term->save_csattr = term->alt_save_csattr;
+ term->alt_save_csattr = t;
+ t = term->save_attr;
+ if (!reset)
+ term->save_attr = term->alt_save_attr;
+ term->alt_save_attr = t;
+ ttc = term->save_truecolour;
+ if (!reset)
+ term->save_truecolour = term->alt_save_truecolour;
+ term->alt_save_truecolour = ttc;
+ bt = term->save_utf;
+ if (!reset)
+ term->save_utf = term->alt_save_utf;
+ term->alt_save_utf = bt;
+ bt = term->save_wnext;
+ if (!reset)
+ term->save_wnext = term->alt_save_wnext;
+ term->alt_save_wnext = bt;
+ t = term->save_sco_acs;
+ if (!reset)
+ term->save_sco_acs = term->alt_save_sco_acs;
+ term->alt_save_sco_acs = t;
+
+ if (term->erase_to_scrollback && term->alt_screen &&
+ term->alt_which && term->disptop < 0) {
+ /*
+ * Inverse of the adjustment at the top of this function.
+ * This time, we're swapping _to_ the alternate screen, so
+ * some lines are about to _appear_ in the virtual
+ * scrollback, and we adjust disptop in the other
+ * direction.
+ *
+ * Both these adjustments depend on the value stored in
+ * term->alt_sblines while the alt screen is selected,
+ * which is why we had to do one _before_ switching away
+ * from it and the other _after_ switching to it.
+ */
+ term->disptop -= term->alt_sblines;
+ int limit = -sblines(term);
+ if (term->disptop < limit)
+ term->disptop = limit;
+ }
+ }
+
+ if (reset && term->screen) {
+ /*
+ * Yes, this _is_ supposed to honour background-colour-erase.
+ */
+ erase_lots(term, false, true, true);
+ }
+}
+
+/*
+ * 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);
+ clear_line(term, line);
+ line->trusted = false;
+ addpos234(term->screen, line, botline);
+
+ /*
+ * If the selection endpoints move into the scrollback,
+ * we keep them moving until they hit the top. However,
+ * of course, if the line _hasn't_ moved into the
+ * scrollback then we don't do this, and cut them off
+ * at the top of the scroll region.
+ *
+ * This applies to selstart and selend (for an existing
+ * selection), and also selanchor (for one being
+ * selected as we speak).
+ */
+ seltop = sb ? -term->savelines : topline;
+
+ if (term->selstate != NO_SELECTION) {
+ if (term->selstart.y >= seltop &&
+ term->selstart.y <= botline) {
+ term->selstart.y--;
+ if (term->selstart.y < seltop) {
+ term->selstart.y = seltop;
+ term->selstart.x = 0;
+ }
+ }
+ if (term->selend.y >= seltop && term->selend.y <= botline) {
+ term->selend.y--;
+ if (term->selend.y < seltop) {
+ term->selend.y = seltop;
+ term->selend.x = 0;
+ }
+ }
+ if (term->selanchor.y >= seltop &&
+ term->selanchor.y <= botline) {
+ term->selanchor.y--;
+ if (term->selanchor.y < seltop) {
+ term->selanchor.y = seltop;
+ term->selanchor.x = 0;
+ }
+ }
+ }
+ }
+ }
+}
+
+/*
+ * 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 {
+ 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);
+ while (n--)
+ copy_termchar(ldata, term->curs.x + n, &term->erase_char);
+ }
+}
+
+static void term_update_raw_mouse_mode(Terminal *term)
+{
+ bool want_raw = (term->xterm_mouse != 0 && !term->xterm_mouse_forbidden);
+ win_set_raw_mouse_mode(term->win, want_raw);
+ term->win_pointer_shape_pending = true;
+ term->win_pointer_shape_raw = want_raw;
+ term_schedule_update(term);
+}
+
+static void term_request_resize(Terminal *term, int cols, int rows)
+{
+ if (term->cols == cols && term->rows == rows)
+ return; /* don't need to do anything */
+
+ term->win_resize_pending = WIN_RESIZE_NEED_SEND;
+ term->win_resize_pending_w = cols;
+ term->win_resize_pending_h = rows;
+ term_schedule_update(term);
+}
+
+/*
+ * Toggle terminal mode `mode' to state `state'. (`query' indicates
+ * whether the mode is a DEC private one or a normal one.)
+ */
+static void toggle_mode(Terminal *term, int mode, int query, bool state)
+{
+ if (query == 1) {
+ switch (mode) {
+ case 1: /* DECCKM: application cursor keys */
+ term->app_cursor_keys = state;
+ break;
+ case 2: /* DECANM: VT52 mode */
+ term->vt52_mode = !state;
+ if (term->vt52_mode) {
+ term->blink_is_real = false;
+ term->vt52_bold = false;
+ } else {
+ term->blink_is_real = term->blinktext;
+ }
+ term_schedule_tblink(term);
+ break;
+ case 3: /* DECCOLM: 80/132 columns */
+ deselect(term);
+ if (!term->no_remote_resize)
+ term_request_resize(term, state ? 132 : 80, term->rows);
+ term->reset_132 = state;
+ term->alt_t = term->marg_t = 0;
+ term->alt_b = term->marg_b = term->rows - 1;
+ move(term, 0, 0, 0);
+ erase_lots(term, false, true, true);
+ break;
+ case 5: /* DECSCNM: reverse video */
+ /*
+ * Toggle reverse video. If we receive an OFF within the
+ * visual bell timeout period after an ON, we trigger an
+ * effective visual bell, so that ESC[?5hESC[?5l will
+ * always be an actually _visible_ visual bell.
+ */
+ if (term->rvideo && !state) {
+ /* This is an OFF, so set up a vbell */
+ term_schedule_vbell(term, true, term->rvbell_startpoint);
+ } else if (!term->rvideo && state) {
+ /* This is an ON, so we notice the time and save it. */
+ term->rvbell_startpoint = GETTICKCOUNT();
+ }
+ term->rvideo = state;
+ seen_disp_event(term);
+ break;
+ case 6: /* DECOM: DEC origin mode */
+ term->dec_om = state;
+ break;
+ case 7: /* DECAWM: auto wrap */
+ term->wrap = state;
+ break;
+ case 8: /* DECARM: auto key repeat */
+ term->repeat_off = !state;
+ break;
+ case 25: /* DECTCEM: enable/disable cursor */
+ compatibility2(OTHER, VT220);
+ term->cursor_on = state;
+ seen_disp_event(term);
+ break;
+ case 47: /* alternate screen */
+ compatibility(OTHER);
+ deselect(term);
+ swap_screen(term, term->no_alt_screen ? 0 : state, false, false);
+ if (term->scroll_on_disp)
+ term->disptop = 0;
+ break;
+ case 1000: /* xterm mouse 1 (normal) */
+ term->xterm_mouse = state ? 1 : 0;
+ term_update_raw_mouse_mode(term);
+ break;
+ case 1002: /* xterm mouse 2 (inc. button drags) */
+ term->xterm_mouse = state ? 2 : 0;
+ term_update_raw_mouse_mode(term);
+ break;
+ case 1006: /* xterm extended mouse */
+ term->xterm_extended_mouse = state;
+ break;
+ case 1015: /* urxvt extended mouse */
+ term->urxvt_extended_mouse = state;
+ break;
+ case 1047: /* alternate screen */
+ compatibility(OTHER);
+ deselect(term);
+ swap_screen(term, term->no_alt_screen ? 0 : state, true, true);
+ if (term->scroll_on_disp)
+ term->disptop = 0;
+ break;
+ case 1048: /* save/restore cursor */
+ if (!term->no_alt_screen)
+ save_cursor(term, state);
+ if (!state) seen_disp_event(term);
+ break;
+ case 1049: /* cursor & alternate screen */
+ if (state && !term->no_alt_screen)
+ save_cursor(term, state);
+ if (!state) seen_disp_event(term);
+ compatibility(OTHER);
+ deselect(term);
+ swap_screen(term, term->no_alt_screen ? 0 : state, true, false);
+ if (!state && !term->no_alt_screen)
+ save_cursor(term, state);
+ if (term->scroll_on_disp)
+ term->disptop = 0;
+ break;
+ case 2004: /* xterm bracketed paste */
+ term->bracketed_paste = state ? true : false;
+ break;
+ }
+ } else if (query == 0) {
+ switch (mode) {
+ case 4: /* IRM: set insert mode */
+ compatibility(VT102);
+ term->insert = state;
+ break;
+ case 12: /* SRM: set echo mode */
+ term->srm_echo = !state;
+ break;
+ case 20: /* LNM: Return sends ... */
+ term->cr_lf_return = state;
+ break;
+ case 34: /* WYULCURM: Make cursor BIG */
+ compatibility2(OTHER, VT220);
+ term->big_cursor = !state;
+ }
+ }
+}
+
+/*
+ * Process an OSC sequence: set window title or icon name.
+ */
+static void do_osc(Terminal *term)
+{
+ if (term->osc_w) {
+ while (term->osc_strlen--)
+ term->wordness[(unsigned char)term->osc_string[term->osc_strlen]] =
+ term->esc_args[0];
+ } else {
+ term->osc_string[term->osc_strlen] = '\0';
+ switch (term->esc_args[0]) {
+ case 0:
+ case 1:
+ if (!term->no_remote_wintitle) {
+ sfree(term->icon_title);
+ term->icon_title = dupstr(term->osc_string);
+ term->icontitle_codepage = term->ucsdata->line_codepage;
+ term->win_icon_title_pending = true;
+ term_schedule_update(term);
+ }
+ if (term->esc_args[0] == 1)
+ break;
+ /* fall through: parameter 0 means set both */
+ case 2:
+ case 21:
+ if (!term->no_remote_wintitle) {
+ sfree(term->window_title);
+ term->window_title = dupstr(term->osc_string);
+ term->wintitle_codepage = term->ucsdata->line_codepage;
+ term->win_title_pending = true;
+ term_schedule_update(term);
+ }
+ break;
+ case 4:
+ if (term->ldisc && !strcmp(term->osc_string, "?")) {
+ unsigned index = term->esc_args[1];
+ if (index < OSC4_NCOLOURS) {
+ rgb colour = term->palette[index];
+ char *reply_buf = dupprintf(
+ "\033]4;%u;rgb:%04x/%04x/%04x\007", index,
+ (unsigned)colour.r * 0x0101,
+ (unsigned)colour.g * 0x0101,
+ (unsigned)colour.b * 0x0101);
+ ldisc_send(term->ldisc, reply_buf, strlen(reply_buf),
+ false);
+ sfree(reply_buf);
+ }
+ }
+ break;
+ }
+ }
+}
+
+/*
+ * ANSI printing routines.
+ */
+static void term_print_setup(Terminal *term, char *printer)
+{
+ bufchain_clear(&term->printer_buf);
+ term->print_job = printer_start_job(printer);
+}
+static void term_print_flush(Terminal *term)
+{
+ size_t size;
+ while ((size = bufchain_size(&term->printer_buf)) > 5) {
+ ptrlen data = bufchain_prefix(&term->printer_buf);
+ if (data.len > size-5)
+ data.len = size-5;
+ printer_job_data(term->print_job, data.ptr, data.len);
+ bufchain_consume(&term->printer_buf, data.len);
+ }
+}
+static void term_print_finish(Terminal *term)
+{
+ size_t size;
+ char c;
+
+ if (!term->printing && !term->only_printing)
+ return; /* we need do nothing */
+
+ term_print_flush(term);
+ while ((size = bufchain_size(&term->printer_buf)) > 0) {
+ ptrlen data = bufchain_prefix(&term->printer_buf);
+ c = *(char *)data.ptr;
+ if (c == '\033' || c == '\233') {
+ bufchain_consume(&term->printer_buf, size);
+ break;
+ } else {
+ printer_job_data(term->print_job, &c, 1);
+ bufchain_consume(&term->printer_buf, 1);
+ }
+ }
+ printer_finish_job(term->print_job);
+ term->print_job = NULL;
+ term->printing = term->only_printing = false;
+}
+
+static void term_display_graphic_char(Terminal *term, unsigned long c)
+{
+ termline *cline = scrlineptr(term->curs.y);
+ int width = 0;
+ if (DIRECT_CHAR(c))
+ width = 1;
+ if (!width)
+ width = term_char_width(term, c);
+
+ if (term->wrapnext && term->wrap && width > 0) {
+ cline->lattr |= LATTR_WRAPPED;
+ if (term->curs.y == term->marg_b)
+ scroll(term, term->marg_t, term->marg_b, 1, true);
+ else if (term->curs.y < term->rows - 1)
+ term->curs.y++;
+ term->curs.x = 0;
+ term->wrapnext = false;
+ cline = scrlineptr(term->curs.y);
+ }
+ if (term->insert && width > 0)
+ insch(term, width);
+ if (term->selstate != NO_SELECTION) {
+ pos cursplus = term->curs;
+ incpos(cursplus);
+ check_selection(term, term->curs, cursplus);
+ }
+ if (((c & CSET_MASK) == CSET_ASCII ||
+ (c & CSET_MASK) == 0) && term->logctx)
+ logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII);
+
+ check_trust_status(term, cline);
+
+ int linecols = term->cols;
+ if (cline->trusted)
+ linecols -= TRUST_SIGIL_WIDTH;
+
+ /*
+ * 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);
+ strbuf_shrink_to(buf, rv < 0 ? 0 : rv);
+ }
+
+ return buf;
+}
+
+static strbuf *term_input_data_from_charset(
+ Terminal *term, int codepage, const char *str, int len)
+{
+ strbuf *buf;
+
+ if (codepage < 0) {
+ buf = strbuf_new();
+ put_data(buf, str, len);
+ } else {
+ int widesize = len * 2; /* allow for UTF-16 surrogates */
+ wchar_t *widebuf = snewn(widesize, wchar_t);
+ int widelen = mb_to_wc(codepage, 0, str, len, widebuf, widesize);
+ buf = term_input_data_from_unicode(term, widebuf, widelen);
+ sfree(widebuf);
+ }
+
+ return buf;
+}
+
+static inline void term_bracketed_paste_start(Terminal *term)
+{
+ ptrlen seq = PTRLEN_LITERAL("\033[200~");
+ if (term->ldisc)
+ ldisc_send(term->ldisc, seq.ptr, seq.len, false);
+ term->bracketed_paste_active = true;
+}
+
+static inline void term_bracketed_paste_stop(Terminal *term)
+{
+ if (!term->bracketed_paste_active)
+ return;
+
+ ptrlen seq = PTRLEN_LITERAL("\033[201~");
+ if (term->ldisc)
+ ldisc_send(term->ldisc, seq.ptr, seq.len, false);
+ term->bracketed_paste_active = false;
+}
+
+static inline void term_keyinput_internal(
+ Terminal *term, const void *buf, int len, bool interactive)
+{
+ if (term->srm_echo) {
+ /*
+ * Implement the terminal-level local echo behaviour that
+ * ECMA-48 specifies when terminal mode 12 is configured off
+ * (ESC[12l). In this mode, data input to the terminal via the
+ * keyboard is also added to the output buffer. But this
+ * doesn't apply to escape sequences generated as session
+ * input _within_ the terminal, e.g. in response to terminal
+ * query sequences, or the bracketing sequences of bracketed
+ * paste mode. Those will be sent directly via
+ * ldisc_send(term->ldisc, ...) and won't go through this
+ * function.
+ */
+
+ /* Mimic the special case of negative length in ldisc_send */
+ int true_len = len >= 0 ? len : strlen(buf);
+
+ bufchain_add(&term->inbuf, buf, true_len);
+ term_added_data(term, false);
+ }
+ if (interactive)
+ term_bracketed_paste_stop(term);
+ if (term->ldisc)
+ ldisc_send(term->ldisc, buf, len, interactive);
+ term_seen_key_event(term);
+}
+
+unsigned long term_translate(
+ Terminal *term, struct term_utf8_decode *utf8, unsigned char c)
+{
+ if (in_utf(term)) {
+ switch (utf8->state) {
+ case 0:
+ if (c < 0x80) {
+ /* UTF-8 must be stateless so we ignore iso2022. */
+ if (term->ucsdata->unitab_ctrl[c] != 0xFF) {
+ return term->ucsdata->unitab_ctrl[c];
+ } else if ((term->utf8linedraw) &&
+ (term->cset_attr[term->cset] == CSET_LINEDRW)) {
+ /* Linedraw characters are explicitly enabled */
+ return c | CSET_LINEDRW;
+ } else {
+ return c | CSET_ASCII;
+ }
+ } else if ((c & 0xe0) == 0xc0) {
+ utf8->size = utf8->state = 1;
+ utf8->chr = (c & 0x1f);
+ } else if ((c & 0xf0) == 0xe0) {
+ utf8->size = utf8->state = 2;
+ utf8->chr = (c & 0x0f);
+ } else if ((c & 0xf8) == 0xf0) {
+ utf8->size = utf8->state = 3;
+ utf8->chr = (c & 0x07);
+ } else if ((c & 0xfc) == 0xf8) {
+ utf8->size = utf8->state = 4;
+ utf8->chr = (c & 0x03);
+ } else if ((c & 0xfe) == 0xfc) {
+ utf8->size = utf8->state = 5;
+ utf8->chr = (c & 0x01);
+ } else {
+ return UCSINVALID;
+ }
+ return UCSINCOMPLETE;
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ if ((c & 0xC0) != 0x80) {
+ utf8->state = 0;
+ return UCSTRUNCATED; /* caller will then give us the
+ * same byte again */
+ }
+ utf8->chr = (utf8->chr << 6) | (c & 0x3f);
+ if (--utf8->state)
+ return UCSINCOMPLETE;
+
+ unsigned long t = utf8->chr;
+
+ /* Is somebody trying to be evil! */
+ if (t < 0x80 ||
+ (t < 0x800 && utf8->size >= 2) ||
+ (t < 0x10000 && utf8->size >= 3) ||
+ (t < 0x200000 && utf8->size >= 4) ||
+ (t < 0x4000000 && utf8->size >= 5))
+ return UCSINVALID;
+
+ /* Unicode line separator and paragraph separator are CR-LF */
+ if (t == 0x2028 || t == 0x2029)
+ return 0x85;
+
+ /* High controls are probably a Baaad idea too. */
+ if (t < 0xA0)
+ return 0xFFFD;
+
+ /* The UTF-16 surrogates are not nice either. */
+ /* The standard give the option of decoding these:
+ * I don't want to! */
+ if (t >= 0xD800 && t < 0xE000)
+ return UCSINVALID;
+
+ /* ISO 10646 characters now limited to UTF-16 range. */
+ if (t > 0x10FFFF)
+ return UCSINVALID;
+
+ /* This is currently a TagPhobic application.. */
+ if (t >= 0xE0000 && t <= 0xE007F)
+ return UCSINCOMPLETE;
+
+ /* U+FEFF is best seen as a null. */
+ if (t == 0xFEFF)
+ return UCSINCOMPLETE;
+ /* But U+FFFE is an error. */
+ if (t == 0xFFFE || t == 0xFFFF)
+ return UCSINVALID;
+
+ return t;
+ }
+ } else if (term->sco_acs &&
+ (c!='\033' && c!='\012' && c!='\015' && c!='\b')) {
+ /* Are we in the nasty ACS mode? Note: no sco in utf mode. */
+ if (term->sco_acs == 2)
+ c |= 0x80;
+
+ return c | CSET_SCOACS;
+ } else {
+ switch (term->cset_attr[term->cset]) {
+ /*
+ * Linedraw characters are different from 'ESC ( B'
+ * only for a small range. For ones outside that
+ * range, make sure we use the same font as well as
+ * the same encoding.
+ */
+ case CSET_LINEDRW:
+ if (term->ucsdata->unitab_ctrl[c] != 0xFF)
+ return term->ucsdata->unitab_ctrl[c];
+ else
+ return c | CSET_LINEDRW;
+ break;
+
+ case CSET_GBCHR:
+ /* If UK-ASCII, make the '#' a LineDraw Pound */
+ if (c == '#')
+ return '}' | CSET_LINEDRW;
+ /* fall through */
+
+ case CSET_ASCII:
+ if (term->ucsdata->unitab_ctrl[c] != 0xFF)
+ return term->ucsdata->unitab_ctrl[c];
+ else
+ return c | CSET_ASCII;
+ break;
+ case CSET_SCOACS:
+ if (c >= ' ')
+ return c | CSET_SCOACS;
+ break;
+ }
+ }
+ return c;
+}
+
+/*
+ * Remove everything currently in `inbuf' and stick it up on the
+ * in-memory display. There's a big state machine in here to
+ * process escape sequences...
+ */
+static void term_out(Terminal *term, bool called_from_term_data)
+{
+ unsigned long c;
+ int unget;
+ const unsigned char *chars;
+ size_t nchars_got = 0, nchars_used = 0;
+
+ /*
+ * During drag-selects, we do not process terminal input, because
+ * the user will want the screen to hold still to be selected.
+ */
+ if (term->selstate == DRAGGING)
+ return;
+
+ unget = -1;
+
+ chars = NULL; /* placate compiler warnings */
+ while (nchars_got < nchars_used ||
+ unget != -1 ||
+ bufchain_size(&term->inbuf) > 0) {
+ if (unget != -1) {
+ /*
+ * Handle a character we left in 'unget' the last time
+ * round this loop. This happens if a UTF-8 sequence is
+ * aborted early, by containing fewer continuation bytes
+ * than its introducer expected: the non-continuation byte
+ * that interrupted the sequence must now be processed
+ * as a fresh piece of input in its own right.
+ */
+ c = unget;
+ unget = -1;
+ } else {
+ /*
+ * If we're waiting for a terminal resize triggered by an
+ * escape sequence, we defer processing the terminal
+ * output until we receive acknowledgment from the front
+ * end that the resize has happened, so that further
+ * output will be processed in the context of the new
+ * size.
+ *
+ * This test goes inside the main while-loop, so that we
+ * exit early if we encounter a resize escape sequence
+ * part way through term->inbuf.
+ *
+ * It's also in the branch of this if statement that
+ * doesn't deal with a character left in 'unget' by the
+ * previous loop iteration, because if we break out of
+ * this loop with an ungot character still pending, we'll
+ * lose it. (And in any case, if the previous thing that
+ * happened was a truncated UTF-8 sequence, then it won't
+ * have scheduled a pending resize.)
+ */
+ if (term->win_resize_pending != WIN_RESIZE_NO)
+ break;
+
+ if (nchars_got == nchars_used) {
+ /* Delete the previous chunk from the bufchain */
+ bufchain_consume(&term->inbuf, nchars_used);
+ nchars_used = 0;
+
+ if (bufchain_size(&term->inbuf) == 0)
+ break; /* no more data */
+
+ ptrlen data = bufchain_prefix(&term->inbuf);
+ chars = data.ptr;
+ nchars_got = data.len;
+ assert(chars != NULL);
+ assert(nchars_used < nchars_got);
+ }
+ c = chars[nchars_used++];
+
+ /*
+ * Optionally log the session traffic to a file. Useful for
+ * debugging and possibly also useful for actual logging.
+ */
+ if (term->logtype == LGTYP_DEBUG && term->logctx)
+ logtraffic(term->logctx, (unsigned char) c, LGTYP_DEBUG);
+ }
+
+ /* Note only VT220+ are 8-bit VT102 is seven bit, it shouldn't even
+ * be able to display 8-bit characters, but I'll let that go 'cause
+ * of i18n.
+ */
+
+ /*
+ * If we're printing, add the character to the printer
+ * buffer.
+ */
+ if (term->printing) {
+ bufchain_add(&term->printer_buf, &c, 1);
+
+ /*
+ * If we're in print-only mode, we use a much simpler
+ * state machine designed only to recognise the ESC[4i
+ * termination sequence.
+ */
+ if (term->only_printing) {
+ if (c == '\033')
+ term->print_state = 1;
+ else if (c == (unsigned char)'\233')
+ term->print_state = 2;
+ else if (c == '[' && term->print_state == 1)
+ term->print_state = 2;
+ else if (c == '4' && term->print_state == 2)
+ term->print_state = 3;
+ else if (c == 'i' && term->print_state == 3)
+ term->print_state = 4;
+ else
+ term->print_state = 0;
+ if (term->print_state == 4) {
+ term_print_finish(term);
+ }
+ continue;
+ }
+ }
+
+ /* Do character-set translation. */
+ if (term->termstate == TOPLEVEL) {
+ unsigned long t = term_translate(term, &term->utf8, c);
+ switch (t) {
+ case UCSINCOMPLETE:
+ continue; /* didn't complete a multibyte char */
+ case UCSTRUNCATED:
+ unget = c;
+ /* fall through */
+ case UCSINVALID:
+ c = UCSERR;
+ break;
+ default:
+ c = t;
+ break;
+ }
+ }
+
+ /*
+ * How about C1 controls?
+ * Explicitly ignore SCI (0x9a), which we don't translate to DECID.
+ */
+ if ((c & -32) == 0x80 && term->termstate < DO_CTRLS &&
+ !term->vt52_mode && has_compat(VT220)) {
+ if (c == 0x9a)
+ c = 0;
+ else {
+ term->termstate = SEEN_ESC;
+ term->esc_query = 0;
+ c = '@' + (c & 0x1F);
+ }
+ }
+
+ /* Or the GL control. */
+ if (c == '\177' && term->termstate < DO_CTRLS && has_compat(OTHER)) {
+ if (term->curs.x && !term->wrapnext)
+ term->curs.x--;
+ term->wrapnext = false;
+ /* destructive backspace might be disabled */
+ if (!term->no_dbackspace) {
+ check_boundary(term, term->curs.x, term->curs.y);
+ check_boundary(term, term->curs.x+1, term->curs.y);
+ copy_termchar(scrlineptr(term->curs.y),
+ term->curs.x, &term->erase_char);
+ }
+ } 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 */
+ if (term->termstate == SEEN_OSC ||
+ term->termstate == SEEN_OSC_W) {
+ /*
+ * In an OSC context, BEL is one of the ways to terminate
+ * the whole sequence. We process it as such even if we
+ * haven't got into the final OSC_STRING state yet, so that
+ * OSC sequences without a string will be handled cleanly.
+ */
+ do_osc(term);
+ term->termstate = TOPLEVEL;
+ break;
+ }
+
+ struct beeptime *newbeep;
+ unsigned long ticks;
+
+ ticks = GETTICKCOUNT();
+
+ if (!term->beep_overloaded) {
+ newbeep = snew(struct beeptime);
+ newbeep->ticks = ticks;
+ newbeep->next = NULL;
+ if (!term->beephead)
+ term->beephead = newbeep;
+ else
+ term->beeptail->next = newbeep;
+ term->beeptail = newbeep;
+ term->nbeeps++;
+ }
+
+ /*
+ * Throw out any beeps that happened more than
+ * t seconds ago.
+ */
+ while (term->beephead &&
+ term->beephead->ticks < ticks - term->bellovl_t) {
+ struct beeptime *tmp = term->beephead;
+ term->beephead = tmp->next;
+ sfree(tmp);
+ if (!term->beephead)
+ term->beeptail = NULL;
+ term->nbeeps--;
+ }
+
+ if (term->bellovl && term->beep_overloaded &&
+ ticks - term->lastbeep >= (unsigned)term->bellovl_s) {
+ /*
+ * If we're currently overloaded and the
+ * last beep was more than s seconds ago,
+ * leave overload mode.
+ */
+ term->beep_overloaded = false;
+ } else if (term->bellovl && !term->beep_overloaded &&
+ term->nbeeps >= term->bellovl_n) {
+ /*
+ * Now, if we have n or more beeps
+ * remaining in the queue, go into overload
+ * mode.
+ */
+ term->beep_overloaded = true;
+ }
+ term->lastbeep = ticks;
+
+ /*
+ * Perform an actual beep if we're not overloaded.
+ */
+ if (!term->bellovl || !term->beep_overloaded) {
+ win_bell(term->win, term->beep);
+
+ if (term->beep == BELL_VISUAL) {
+ term_schedule_vbell(term, false, 0);
+ }
+ }
+ seen_disp_event(term);
+ break;
+ }
+ case '\b': /* BS: Back space */
+ if (term->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 if (term->termstate == SEEN_OSC ||
+ term->termstate == SEEN_OSC_W) {
+ /* Be prepared to terminate an OSC early */
+ term->termstate = OSC_MAYBE_ST;
+ } else {
+ compatibility(ANSIMIN);
+ term->termstate = SEEN_ESC;
+ term->esc_query = 0;
+ }
+ break;
+ case '\015': /* CR: Carriage return */
+ term->curs.x = 0;
+ term->wrapnext = false;
+ seen_disp_event(term);
+
+ if (term->crhaslf) {
+ if (term->curs.y == term->marg_b)
+ scroll(term, term->marg_t, term->marg_b, 1, true);
+ else if (term->curs.y < term->rows - 1)
+ term->curs.y++;
+ }
+ if (term->logctx)
+ logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII);
+ break;
+ case '\014': /* FF: Form feed */
+ if (has_compat(SCOANSI)) {
+ move(term, 0, 0, 0);
+ erase_lots(term, false, false, true);
+ if (term->scroll_on_disp)
+ term->disptop = 0;
+ term->wrapnext = false;
+ seen_disp_event(term);
+ break;
+ }
+ case '\013': /* VT: Line tabulation */
+ compatibility(VT100);
+ case '\012': /* LF: Line feed */
+ if (term->curs.y == term->marg_b)
+ scroll(term, term->marg_t, term->marg_b, 1, true);
+ else if (term->curs.y < term->rows - 1)
+ term->curs.y++;
+ if (term->lfhascr)
+ term->curs.x = 0;
+ term->wrapnext = false;
+ seen_disp_event(term);
+ if (term->logctx)
+ logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII);
+ break;
+ case '\t': { /* HT: Character tabulation */
+ pos old_curs = term->curs;
+ termline *ldata = scrlineptr(term->curs.y);
+
+ do {
+ term->curs.x++;
+ } while (term->curs.x < term->cols - 1 &&
+ !term->tabs[term->curs.x]);
+
+ if ((ldata->lattr & LATTR_MODE) != LATTR_NORM) {
+ if (term->curs.x >= term->cols / 2)
+ term->curs.x = term->cols / 2 - 1;
+ } else {
+ if (term->curs.x >= term->cols)
+ term->curs.x = term->cols - 1;
+ }
+
+ check_selection(term, old_curs, term->curs);
+ seen_disp_event(term);
+ break;
+ }
+ }
+ } else
+ switch (term->termstate) {
+ case TOPLEVEL:
+ /* Only graphic characters get this far;
+ * ctrls are stripped above */
+ term_display_graphic_char(term, c);
+ term->last_graphic_char = c;
+ break;
+
+ case OSC_MAYBE_ST:
+ /*
+ * This state is virtually identical to SEEN_ESC, with the
+ * exception that we have an OSC sequence in the pipeline,
+ * and _if_ we see a backslash, we process it.
+ */
+ if (c == '\\') {
+ do_osc(term);
+ term->termstate = TOPLEVEL;
+ break;
+ }
+ /* else fall through */
+ case SEEN_ESC:
+ if (c >= ' ' && c <= '/') {
+ if (term->esc_query)
+ term->esc_query = -1;
+ else
+ term->esc_query = c;
+ break;
+ }
+ term->termstate = TOPLEVEL;
+ switch (ANSI(c, term->esc_query)) {
+ case '[': /* enter CSI mode */
+ term->termstate = SEEN_CSI;
+ term->esc_nargs = 1;
+ term->esc_args[0] = ARG_DEFAULT;
+ term->esc_query = 0;
+ break;
+ case ']': /* OSC: xterm escape sequences */
+ /* Compatibility is nasty here, xterm, linux, decterm yuk! */
+ compatibility(OTHER);
+ term->termstate = SEEN_OSC;
+ term->osc_strlen = 0;
+ term->esc_args[0] = 0;
+ term->esc_nargs = 1;
+ break;
+ case '7': /* DECSC: save cursor */
+ compatibility(VT100);
+ save_cursor(term, true);
+ break;
+ case '8': /* DECRC: restore cursor */
+ compatibility(VT100);
+ save_cursor(term, false);
+ 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_request_resize(term, 80, term->rows);
+ term->reset_132 = false;
+ }
+ if (term->scroll_on_disp)
+ term->disptop = 0;
+ seen_disp_event(term);
+ break;
+ case 'H': /* HTS: set a tab */
+ compatibility(VT100);
+ term->tabs[term->curs.x] = true;
+ break;
+
+ case ANSI('8', '#'): { /* DECALN: fills screen with Es :-) */
+ compatibility(VT100);
+ termline *ldata;
+ int i, j;
+ pos scrtop, scrbot;
+
+ for (i = 0; i < term->rows; i++) {
+ ldata = scrlineptr(i);
+ check_line_size(term, ldata);
+ for (j = 0; j < term->cols; j++) {
+ copy_termchar(ldata, j,
+ &term->basic_erase_char);
+ ldata->chars[j].chr = 'E';
+ }
+ ldata->lattr = LATTR_NORM;
+ }
+ if (term->scroll_on_disp)
+ term->disptop = 0;
+ seen_disp_event(term);
+ scrtop.x = scrtop.y = 0;
+ scrbot.x = 0;
+ scrbot.y = term->rows;
+ check_selection(term, scrtop, scrbot);
+ break;
+ }
+
+ case ANSI('3', '#'):
+ case ANSI('4', '#'):
+ case ANSI('5', '#'):
+ case ANSI('6', '#'): {
+ compatibility(VT100);
+ int nlattr;
+ termline *ldata;
+
+ switch (ANSI(c, term->esc_query)) {
+ case ANSI('3', '#'): /* DECDHL: 2*height, top */
+ nlattr = LATTR_TOP;
+ break;
+ case ANSI('4', '#'): /* DECDHL: 2*height, bottom */
+ nlattr = LATTR_BOT;
+ break;
+ case ANSI('5', '#'): /* DECSWL: normal */
+ nlattr = LATTR_NORM;
+ break;
+ default: /* case ANSI('6', '#'): DECDWL: 2*width */
+ nlattr = LATTR_WIDE;
+ break;
+ }
+ ldata = scrlineptr(term->curs.y);
+ check_line_size(term, ldata);
+ check_trust_status(term, ldata);
+ ldata->lattr = nlattr;
+ break;
+ }
+ /* GZD4: G0 designate 94-set */
+ case ANSI('A', '('):
+ compatibility(VT100);
+ if (!term->no_remote_charset)
+ term->cset_attr[0] = CSET_GBCHR;
+ break;
+ case ANSI('B', '('):
+ compatibility(VT100);
+ if (!term->no_remote_charset)
+ term->cset_attr[0] = CSET_ASCII;
+ break;
+ case ANSI('0', '('):
+ compatibility(VT100);
+ if (!term->no_remote_charset)
+ term->cset_attr[0] = CSET_LINEDRW;
+ break;
+ case ANSI('U', '('):
+ compatibility(OTHER);
+ if (!term->no_remote_charset)
+ term->cset_attr[0] = CSET_SCOACS;
+ break;
+ /* G1D4: G1-designate 94-set */
+ case ANSI('A', ')'):
+ compatibility(VT100);
+ if (!term->no_remote_charset)
+ term->cset_attr[1] = CSET_GBCHR;
+ break;
+ case ANSI('B', ')'):
+ compatibility(VT100);
+ if (!term->no_remote_charset)
+ term->cset_attr[1] = CSET_ASCII;
+ break;
+ case ANSI('0', ')'):
+ compatibility(VT100);
+ if (!term->no_remote_charset)
+ term->cset_attr[1] = CSET_LINEDRW;
+ break;
+ case ANSI('U', ')'):
+ compatibility(OTHER);
+ if (!term->no_remote_charset)
+ term->cset_attr[1] = CSET_SCOACS;
+ break;
+ /* DOCS: Designate other coding system */
+ case ANSI('8', '%'): /* Old Linux code */
+ case ANSI('G', '%'):
+ compatibility(OTHER);
+ if (!term->no_remote_charset)
+ term->utf = true;
+ break;
+ case ANSI('@', '%'):
+ compatibility(OTHER);
+ if (!term->no_remote_charset)
+ term->utf = false;
+ break;
+ }
+ break;
+ case SEEN_CSI:
+ term->termstate = TOPLEVEL; /* default */
+ if (isdigit(c)) {
+ if (term->esc_nargs <= ARGS_MAX) {
+ if (term->esc_args[term->esc_nargs - 1] == ARG_DEFAULT)
+ term->esc_args[term->esc_nargs - 1] = 0;
+ if (term->esc_args[term->esc_nargs - 1] <=
+ UINT_MAX / 10 &&
+ term->esc_args[term->esc_nargs - 1] * 10 <=
+ UINT_MAX - c - '0')
+ term->esc_args[term->esc_nargs - 1] =
+ 10 * term->esc_args[term->esc_nargs - 1] +
+ c - '0';
+ else
+ term->esc_args[term->esc_nargs - 1] = UINT_MAX;
+ }
+ term->termstate = SEEN_CSI;
+ } else if (c == ';') {
+ if (term->esc_nargs < ARGS_MAX)
+ term->esc_args[term->esc_nargs++] = ARG_DEFAULT;
+ term->termstate = SEEN_CSI;
+ } else if (c < '@') {
+ if (term->esc_query)
+ term->esc_query = -1;
+ else if (c == '?')
+ term->esc_query = 1;
+ else
+ term->esc_query = c;
+ term->termstate = SEEN_CSI;
+ } else
+#define CLAMP(arg, lim) ((arg) = ((arg) > (lim)) ? (lim) : (arg))
+ switch (ANSI(c, term->esc_query)) {
+ case 'A': /* CUU: move up N lines */
+ CLAMP(term->esc_args[0], term->rows);
+ move(term, term->curs.x,
+ term->curs.y - def(term->esc_args[0], 1), 1);
+ seen_disp_event(term);
+ break;
+ case 'e': /* VPR: move down N lines */
+ compatibility(ANSI);
+ /* FALLTHROUGH */
+ case 'B': /* CUD: Cursor down */
+ CLAMP(term->esc_args[0], term->rows);
+ move(term, term->curs.x,
+ term->curs.y + def(term->esc_args[0], 1), 1);
+ seen_disp_event(term);
+ break;
+ case 'b': /* REP: repeat previous grap */
+ CLAMP(term->esc_args[0], term->rows * term->cols);
+ if (term->last_graphic_char) {
+ unsigned i;
+ for (i = 0; i < term->esc_args[0]; i++)
+ term_display_graphic_char(
+ term, term->last_graphic_char);
+ }
+ break;
+ case ANSI('c', '>'): /* DA: report xterm version */
+ compatibility(OTHER);
+ /* this reports xterm version 136 so that VIM can
+ use the drag messages from the mouse reporting */
+ if (term->ldisc)
+ ldisc_send(term->ldisc, "\033[>0;136;0c", 11,
+ false);
+ break;
+ case 'a': /* HPR: move right N cols */
+ compatibility(ANSI);
+ /* FALLTHROUGH */
+ case 'C': /* CUF: Cursor right */
+ CLAMP(term->esc_args[0], term->cols);
+ move(term, term->curs.x + def(term->esc_args[0], 1),
+ term->curs.y, 1);
+ seen_disp_event(term);
+ break;
+ case 'D': /* CUB: move left N cols */
+ CLAMP(term->esc_args[0], term->cols);
+ move(term, term->curs.x - def(term->esc_args[0], 1),
+ term->curs.y, 1);
+ seen_disp_event(term);
+ break;
+ case 'E': /* CNL: move down N lines and CR */
+ compatibility(ANSI);
+ CLAMP(term->esc_args[0], term->rows);
+ move(term, 0,
+ term->curs.y + def(term->esc_args[0], 1), 1);
+ seen_disp_event(term);
+ break;
+ case 'F': /* CPL: move up N lines and CR */
+ compatibility(ANSI);
+ CLAMP(term->esc_args[0], term->rows);
+ move(term, 0,
+ term->curs.y - def(term->esc_args[0], 1), 1);
+ seen_disp_event(term);
+ break;
+ case 'G': /* CHA */
+ case '`': /* HPA: set horizontal posn */
+ compatibility(ANSI);
+ CLAMP(term->esc_args[0], term->cols);
+ move(term, def(term->esc_args[0], 1) - 1,
+ term->curs.y, 0);
+ seen_disp_event(term);
+ break;
+ case 'd': /* VPA: set vertical posn */
+ compatibility(ANSI);
+ CLAMP(term->esc_args[0], term->rows);
+ move(term, term->curs.x,
+ ((term->dec_om ? term->marg_t : 0) +
+ def(term->esc_args[0], 1) - 1),
+ (term->dec_om ? 2 : 0));
+ seen_disp_event(term);
+ break;
+ case 'H': /* CUP */
+ case 'f': /* HVP: set horz and vert posns at once */
+ if (term->esc_nargs < 2)
+ term->esc_args[1] = ARG_DEFAULT;
+ CLAMP(term->esc_args[0], term->rows);
+ CLAMP(term->esc_args[1], term->cols);
+ move(term, def(term->esc_args[1], 1) - 1,
+ ((term->dec_om ? term->marg_t : 0) +
+ def(term->esc_args[0], 1) - 1),
+ (term->dec_om ? 2 : 0));
+ seen_disp_event(term);
+ break;
+ case 'J': { /* ED: erase screen or parts of it */
+ unsigned int i = def(term->esc_args[0], 0);
+ if (i == 3) {
+ /* Erase Saved Lines (xterm)
+ * This follows Thomas Dickey's xterm. */
+ if (!term->no_remote_clearscroll)
+ term_clrsb(term);
+ } else {
+ i++;
+ if (i > 3)
+ i = 0;
+ erase_lots(term, false, !!(i & 2), !!(i & 1));
+ }
+ if (term->scroll_on_disp)
+ term->disptop = 0;
+ seen_disp_event(term);
+ break;
+ }
+ case 'K': { /* EL: erase line or parts of it */
+ unsigned int i = def(term->esc_args[0], 0) + 1;
+ if (i > 3)
+ i = 0;
+ erase_lots(term, true, !!(i & 2), !!(i & 1));
+ seen_disp_event(term);
+ break;
+ }
+ case 'L': /* IL: insert lines */
+ compatibility(VT102);
+ CLAMP(term->esc_args[0], term->rows);
+ if (term->curs.y <= term->marg_b)
+ scroll(term, term->curs.y, term->marg_b,
+ -def(term->esc_args[0], 1), false);
+ seen_disp_event(term);
+ break;
+ case 'M': /* DL: delete lines */
+ compatibility(VT102);
+ CLAMP(term->esc_args[0], term->rows);
+ if (term->curs.y <= term->marg_b)
+ scroll(term, term->curs.y, term->marg_b,
+ def(term->esc_args[0], 1),
+ true);
+ seen_disp_event(term);
+ break;
+ case '@': /* ICH: insert chars */
+ /* XXX VTTEST says this is vt220, vt510 manual says vt102 */
+ compatibility(VT102);
+ CLAMP(term->esc_args[0], term->cols);
+ insch(term, def(term->esc_args[0], 1));
+ seen_disp_event(term);
+ break;
+ case 'P': /* DCH: delete chars */
+ compatibility(VT102);
+ CLAMP(term->esc_args[0], term->cols);
+ insch(term, -def(term->esc_args[0], 1));
+ seen_disp_event(term);
+ break;
+ case 'c': /* DA: terminal type query */
+ compatibility(VT100);
+ /* This is the response for a VT102 */
+ if (term->ldisc)
+ ldisc_send(term->ldisc, term->id_string,
+ strlen(term->id_string), false);
+ break;
+ case 'n': /* DSR: cursor position query */
+ if (term->ldisc) {
+ if (term->esc_args[0] == 6) {
+ char buf[32];
+ sprintf(buf, "\033[%d;%dR", term->curs.y + 1,
+ term->curs.x + 1);
+ ldisc_send(term->ldisc, buf, strlen(buf),
+ false);
+ } else if (term->esc_args[0] == 5) {
+ ldisc_send(term->ldisc, "\033[0n", 4, false);
+ }
+ }
+ break;
+ case 'h': /* SM: toggle modes to high */
+ case ANSI_QUE('h'):
+ compatibility(VT100);
+ for (int i = 0; i < term->esc_nargs; i++)
+ toggle_mode(term, term->esc_args[i],
+ term->esc_query, true);
+ break;
+ case 'i': /* MC: Media copy */
+ case ANSI_QUE('i'): {
+ compatibility(VT100);
+ char *printer;
+ if (term->esc_nargs != 1) break;
+ if (term->esc_args[0] == 5 &&
+ (printer = conf_get_str(term->conf,
+ CONF_printer))[0]) {
+ term->printing = true;
+ term->only_printing = !term->esc_query;
+ term->print_state = 0;
+ term_print_setup(term, printer);
+ } else if (term->esc_args[0] == 4 &&
+ term->printing) {
+ term_print_finish(term);
+ }
+ break;
+ }
+ case 'l': /* RM: toggle modes to low */
+ case ANSI_QUE('l'):
+ compatibility(VT100);
+ for (int i = 0; i < term->esc_nargs; i++)
+ toggle_mode(term, term->esc_args[i],
+ term->esc_query, false);
+ break;
+ case 'g': /* TBC: clear tabs */
+ compatibility(VT100);
+ if (term->esc_nargs == 1) {
+ if (term->esc_args[0] == 0) {
+ term->tabs[term->curs.x] = false;
+ } else if (term->esc_args[0] == 3) {
+ int i;
+ for (i = 0; i < term->cols; i++)
+ term->tabs[i] = false;
+ }
+ }
+ break;
+ case 'r': /* DECSTBM: set scroll margins */
+ compatibility(VT100);
+ if (term->esc_nargs <= 2) {
+ int top, bot;
+ CLAMP(term->esc_args[0], term->rows);
+ CLAMP(term->esc_args[1], term->rows);
+ top = def(term->esc_args[0], 1) - 1;
+ bot = (term->esc_nargs <= 1
+ || term->esc_args[1] == 0 ?
+ term->rows :
+ def(term->esc_args[1], term->rows)) - 1;
+ if (bot >= term->rows)
+ bot = term->rows - 1;
+ /* VTTEST Bug 9 - if region is less than 2 lines
+ * don't change region.
+ */
+ if (bot - top > 0) {
+ term->marg_t = top;
+ term->marg_b = bot;
+ term->curs.x = 0;
+ /*
+ * I used to think the cursor should be
+ * placed at the top of the newly marginned
+ * area. Apparently not: VMS TPU falls over
+ * if so.
+ *
+ * Well actually it should for
+ * Origin mode - RDB
+ */
+ term->curs.y = (term->dec_om ?
+ term->marg_t : 0);
+ seen_disp_event(term);
+ }
+ }
+ break;
+ case 'm': /* SGR: set graphics rendition */
+ /*
+ * A VT100 without the AVO only had one
+ * attribute, either underline or reverse
+ * video depending on the cursor type, this
+ * was selected by CSI 7m.
+ *
+ * case 2:
+ * This is sometimes DIM, eg on the GIGI and
+ * Linux
+ * case 8:
+ * This is sometimes INVIS various ANSI.
+ * case 21:
+ * This like 22 disables BOLD, DIM and INVIS
+ *
+ * The ANSI colours appear on any terminal
+ * that has colour (obviously) but the
+ * interaction between sgr0 and the colours
+ * varies but is usually related to the
+ * background colour erase item. The
+ * interaction between colour attributes and
+ * the mono ones is also very implementation
+ * dependent.
+ *
+ * The 39 and 49 attributes are likely to be
+ * unimplemented.
+ */
+ for (int i = 0; i < term->esc_nargs; i++)
+ switch (def(term->esc_args[i], 0)) {
+ case 0: /* restore defaults */
+ term->curr_attr = term->default_attr;
+ term->curr_truecolour =
+ term->basic_erase_char.truecolour;
+ break;
+ case 1: /* enable bold */
+ compatibility(VT100AVO);
+ term->curr_attr |= ATTR_BOLD;
+ break;
+ case 2: /* enable dim */
+ compatibility(OTHER);
+ term->curr_attr |= ATTR_DIM;
+ break;
+ case 21: /* (enable double underline) */
+ compatibility(OTHER);
+ case 4: /* enable underline */
+ compatibility(VT100AVO);
+ term->curr_attr |= ATTR_UNDER;
+ break;
+ case 5: /* enable blink */
+ compatibility(VT100AVO);
+ term->curr_attr |= ATTR_BLINK;
+ break;
+ case 6: /* SCO light bkgrd */
+ compatibility(SCOANSI);
+ term->blink_is_real = false;
+ term->curr_attr |= ATTR_BLINK;
+ term_schedule_tblink(term);
+ break;
+ case 7: /* enable reverse video */
+ term->curr_attr |= ATTR_REVERSE;
+ break;
+ case 9: /* enable strikethrough */
+ term->curr_attr |= ATTR_STRIKE;
+ break;
+ case 10: /* SCO acs off */
+ compatibility(SCOANSI);
+ if (term->no_remote_charset) break;
+ term->sco_acs = 0; break;
+ case 11: /* SCO acs on */
+ compatibility(SCOANSI);
+ if (term->no_remote_charset) break;
+ term->sco_acs = 1; break;
+ case 12: /* SCO acs on, |0x80 */
+ compatibility(SCOANSI);
+ if (term->no_remote_charset) break;
+ term->sco_acs = 2; break;
+ case 22: /* disable bold and dim */
+ compatibility2(OTHER, VT220);
+ term->curr_attr &= ~(ATTR_BOLD | ATTR_DIM);
+ break;
+ case 24: /* disable underline */
+ compatibility2(OTHER, VT220);
+ term->curr_attr &= ~ATTR_UNDER;
+ break;
+ case 25: /* disable blink */
+ compatibility2(OTHER, VT220);
+ term->curr_attr &= ~ATTR_BLINK;
+ break;
+ case 27: /* disable reverse video */
+ compatibility2(OTHER, VT220);
+ term->curr_attr &= ~ATTR_REVERSE;
+ break;
+ case 29: /* disable strikethrough */
+ term->curr_attr &= ~ATTR_STRIKE;
+ break;
+ case 30:
+ case 31:
+ case 32:
+ case 33:
+ case 34:
+ case 35:
+ case 36:
+ case 37:
+ /* foreground */
+ term->curr_truecolour.fg.enabled = false;
+ term->curr_attr &= ~ATTR_FGMASK;
+ term->curr_attr |=
+ (term->esc_args[i] - 30)<<ATTR_FGSHIFT;
+ break;
+ case 90:
+ case 91:
+ case 92:
+ case 93:
+ case 94:
+ case 95:
+ case 96:
+ case 97:
+ /* aixterm-style bright foreground */
+ term->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)<<ATTR_BGSHIFT;
+ break;
+ case 100:
+ case 101:
+ case 102:
+ case 103:
+ case 104:
+ case 105:
+ case 106:
+ case 107:
+ /* aixterm-style bright background */
+ term->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_request_resize(term, term->cols, 24);
+ deselect(term);
+ } else if (term->esc_nargs >= 1 &&
+ term->esc_args[0] >= 1 &&
+ term->esc_args[0] < 24) {
+ compatibility(OTHER);
+
+ switch (term->esc_args[0]) {
+ int len;
+ char buf[80];
+ const char *p;
+ case 1:
+ term->win_minimise_pending = true;
+ term->win_minimise_enable = false;
+ term_schedule_update(term);
+ break;
+ case 2:
+ term->win_minimise_pending = true;
+ term->win_minimise_enable = true;
+ term_schedule_update(term);
+ break;
+ case 3:
+ if (term->esc_nargs >= 3) {
+ if (!term->no_remote_resize) {
+ term->win_move_pending = true;
+ term->win_move_pending_x =
+ def(term->esc_args[1], 0);
+ term->win_move_pending_y =
+ def(term->esc_args[2], 0);
+ term_schedule_update(term);
+ }
+ }
+ break;
+ case 4:
+ /* We should resize the window to a given
+ * size in pixels here, but currently our
+ * resizing code isn't healthy enough to
+ * manage it. */
+ break;
+ case 5:
+ /* move to top */
+ term->win_zorder_pending = true;
+ term->win_zorder_top = true;
+ term_schedule_update(term);
+ break;
+ case 6:
+ /* move to bottom */
+ term->win_zorder_pending = true;
+ term->win_zorder_top = false;
+ term_schedule_update(term);
+ break;
+ case 7:
+ term->win_refresh_pending = true;
+ term_schedule_update(term);
+ break;
+ case 8:
+ if (term->esc_nargs >= 3 &&
+ !term->no_remote_resize) {
+ term_request_resize(
+ term,
+ def(term->esc_args[2],
+ term->conf_width),
+ def(term->esc_args[1],
+ term->conf_height));
+ }
+ break;
+ case 9:
+ if (term->esc_nargs >= 2) {
+ term->win_maximise_pending = true;
+ term->win_maximise_enable =
+ term->esc_args[1];
+ term_schedule_update(term);
+ }
+ break;
+ case 11:
+ if (term->ldisc)
+ ldisc_send(term->ldisc, term->minimised ?
+ "\033[2t" : "\033[1t", 4,
+ false);
+ break;
+ case 13:
+ if (term->ldisc) {
+ len = sprintf(buf, "\033[3;%u;%ut",
+ term->winpos_x,
+ term->winpos_y);
+ ldisc_send(term->ldisc, buf, len, false);
+ }
+ break;
+ case 14:
+ if (term->ldisc) {
+ len = sprintf(buf, "\033[4;%u;%ut",
+ term->winpixsize_y,
+ term->winpixsize_x);
+ ldisc_send(term->ldisc, buf, len, false);
+ }
+ break;
+ case 18:
+ if (term->ldisc) {
+ len = sprintf(buf, "\033[8;%d;%dt",
+ term->rows, term->cols);
+ ldisc_send(term->ldisc, buf, len, false);
+ }
+ break;
+ case 19:
+ /*
+ * Hmmm. Strictly speaking we
+ * should return `the size of the
+ * screen in characters', but
+ * that's not easy: (a) window
+ * furniture being what it is it's
+ * hard to compute, and (b) in
+ * resize-font mode maximising the
+ * window wouldn't change the
+ * number of characters. *shrug*. I
+ * think we'll ignore it for the
+ * moment and see if anyone
+ * complains, and then ask them
+ * what they would like it to do.
+ */
+ break;
+ case 20:
+ if (term->ldisc &&
+ term->remote_qtitle_action != TITLE_NONE) {
+ if(term->remote_qtitle_action == TITLE_REAL)
+ p = term->icon_title;
+ else
+ p = EMPTY_WINDOW_TITLE;
+ len = strlen(p);
+ ldisc_send(term->ldisc, "\033]L", 3,
+ false);
+ ldisc_send(term->ldisc, p, len, false);
+ ldisc_send(term->ldisc, "\033\\", 2,
+ false);
+ }
+ break;
+ case 21:
+ if (term->ldisc &&
+ term->remote_qtitle_action != TITLE_NONE) {
+ if(term->remote_qtitle_action == TITLE_REAL)
+ p = term->window_title;
+ else
+ p = EMPTY_WINDOW_TITLE;
+ len = strlen(p);
+ ldisc_send(term->ldisc, "\033]l", 3,
+ false);
+ ldisc_send(term->ldisc, p, len, false);
+ ldisc_send(term->ldisc, "\033\\", 2,
+ false);
+ }
+ break;
+ }
+ }
+ break;
+ case 'S': /* SU: Scroll up */
+ CLAMP(term->esc_args[0], term->rows);
+ compatibility(SCOANSI);
+ scroll(term, term->marg_t, term->marg_b,
+ def(term->esc_args[0], 1), true);
+ term->wrapnext = false;
+ 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_request_resize(
+ term,
+ term->cols,
+ def(term->esc_args[0], term->conf_height));
+ deselect(term);
+ }
+ break;
+ case ANSI('|', '$'): /* DECSCPP */
+ /*
+ * Set number of columns per page
+ * Docs imply range is only 80 or 132, but
+ * I'll allow any.
+ */
+ compatibility(VT340TEXT);
+ if (term->esc_nargs <= 1) {
+ if (!term->no_remote_resize)
+ term_request_resize(
+ term,
+ def(term->esc_args[0], term->conf_width),
+ term->rows);
+ deselect(term);
+ }
+ break;
+ case 'X': { /* ECH: write N spaces w/o moving cursor */
+ /* XXX VTTEST says this is vt220, vt510 manual
+ * says vt100 */
+ compatibility(ANSIMIN);
+ CLAMP(term->esc_args[0], term->cols);
+ int n = def(term->esc_args[0], 1);
+ pos cursplus;
+ int p = term->curs.x;
+ termline *cline = scrlineptr(term->curs.y);
+
+ check_trust_status(term, cline);
+ if (n > term->cols - term->curs.x)
+ n = term->cols - term->curs.x;
+ cursplus = term->curs;
+ cursplus.x += n;
+ check_boundary(term, term->curs.x, term->curs.y);
+ check_boundary(term, term->curs.x+n, term->curs.y);
+ check_selection(term, term->curs, cursplus);
+ while (n--)
+ copy_termchar(cline, p++,
+ &term->erase_char);
+ seen_disp_event(term);
+ break;
+ }
+ case 'x': /* DECREQTPARM: report terminal characteristics */
+ compatibility(VT100);
+ if (term->ldisc) {
+ char buf[32];
+ int i = def(term->esc_args[0], 0);
+ if (i == 0 || i == 1) {
+ strcpy(buf, "\033[2;1;1;112;112;1;0x");
+ buf[2] += i;
+ ldisc_send(term->ldisc, buf, 20, false);
+ }
+ }
+ break;
+ case 'Z': { /* CBT */
+ compatibility(OTHER);
+ CLAMP(term->esc_args[0], term->cols);
+ int i = def(term->esc_args[0], 1);
+ pos old_curs = term->curs;
+
+ for(;i>0 && term->curs.x>0; i--) {
+ do {
+ term->curs.x--;
+ } while (term->curs.x >0 &&
+ !term->tabs[term->curs.x]);
+ }
+ check_selection(term, old_curs, term->curs);
+ break;
+ }
+ case ANSI('c', '='): /* Hide or Show Cursor */
+ compatibility(SCOANSI);
+ switch(term->esc_args[0]) {
+ case 0: /* hide cursor */
+ term->cursor_on = false;
+ break;
+ case 1: /* restore cursor */
+ term->big_cursor = false;
+ term->cursor_on = true;
+ break;
+ case 2: /* block cursor */
+ term->big_cursor = true;
+ term->cursor_on = true;
+ break;
+ }
+ break;
+ case ANSI('C', '='):
+ /*
+ * set cursor start on scanline esc_args[0] and
+ * end on scanline esc_args[1].If you set
+ * the bottom scan line to a value less than
+ * the top scan line, the cursor will disappear.
+ */
+ compatibility(SCOANSI);
+ if (term->esc_nargs >= 2) {
+ if (term->esc_args[0] > term->esc_args[1])
+ term->cursor_on = false;
+ else
+ term->cursor_on = true;
+ }
+ break;
+ case ANSI('D', '='):
+ compatibility(SCOANSI);
+ term->blink_is_real = false;
+ term_schedule_tblink(term);
+ if (term->esc_args[0]>=1)
+ term->curr_attr |= ATTR_BLINK;
+ else
+ term->curr_attr &= ~ATTR_BLINK;
+ break;
+ case ANSI('E', '='):
+ compatibility(SCOANSI);
+ term->blink_is_real = (term->esc_args[0] >= 1);
+ term_schedule_tblink(term);
+ break;
+ case ANSI('F', '='): /* set normal foreground */
+ compatibility(SCOANSI);
+ if (term->esc_args[0] < 16) {
+ long colour =
+ (sco2ansicolour[term->esc_args[0] & 0x7] |
+ (term->esc_args[0] & 0x8)) <<
+ ATTR_FGSHIFT;
+ term->curr_attr &= ~ATTR_FGMASK;
+ term->curr_attr |= colour;
+ term->curr_truecolour.fg = optionalrgb_none;
+ term->default_attr &= ~ATTR_FGMASK;
+ term->default_attr |= colour;
+ set_erase_char(term);
+ }
+ break;
+ case ANSI('G', '='): /* set normal background */
+ compatibility(SCOANSI);
+ if (term->esc_args[0] < 16) {
+ long colour =
+ (sco2ansicolour[term->esc_args[0] & 0x7] |
+ (term->esc_args[0] & 0x8)) <<
+ ATTR_BGSHIFT;
+ term->curr_attr &= ~ATTR_BGMASK;
+ term->curr_attr |= colour;
+ term->curr_truecolour.bg = optionalrgb_none;
+ term->default_attr &= ~ATTR_BGMASK;
+ term->default_attr |= colour;
+ set_erase_char(term);
+ }
+ break;
+ case ANSI('L', '='):
+ compatibility(SCOANSI);
+ term->use_bce = (term->esc_args[0] <= 0);
+ set_erase_char(term);
+ break;
+ case ANSI('p', '"'): /* DECSCL: set compat level */
+ /*
+ * Allow the host to make this emulator a
+ * 'perfect' VT102. This first appeared in
+ * the VT220, but we do need to get back to
+ * PuTTY mode so I won't check it.
+ *
+ * The arg in 40..42,50 are a PuTTY extension.
+ * The 2nd arg, 8bit vs 7bit is not checked.
+ *
+ * Setting VT102 mode should also change
+ * the Fkeys to generate PF* codes as a
+ * real VT102 has no Fkeys. The VT220 does
+ * this, F11..F13 become ESC,BS,LF other
+ * Fkeys send nothing.
+ *
+ * Note ESC c will NOT change this!
+ */
+
+ switch (term->esc_args[0]) {
+ case 61:
+ term->compatibility_level &= ~TM_VTXXX;
+ term->compatibility_level |= TM_VT102;
+ break;
+ case 62:
+ term->compatibility_level &= ~TM_VTXXX;
+ term->compatibility_level |= TM_VT220;
+ break;
+
+ default:
+ if (term->esc_args[0] > 60 &&
+ term->esc_args[0] < 70)
+ term->compatibility_level |= TM_VTXXX;
+ break;
+
+ case 40:
+ term->compatibility_level &= TM_VTXXX;
+ break;
+ case 41:
+ term->compatibility_level = TM_PUTTY;
+ break;
+ case 42:
+ term->compatibility_level = TM_SCOANSI;
+ break;
+
+ case ARG_DEFAULT:
+ term->compatibility_level = TM_PUTTY;
+ break;
+ case 50:
+ break;
+ }
+
+ /* Change the response to CSI c */
+ if (term->esc_args[0] == 50) {
+ int i;
+ char lbuf[64];
+ strcpy(term->id_string, "\033[?");
+ for (i = 1; i < term->esc_nargs; i++) {
+ if (i != 1)
+ strcat(term->id_string, ";");
+ sprintf(lbuf, "%u", term->esc_args[i]);
+ strcat(term->id_string, lbuf);
+ }
+ strcat(term->id_string, "c");
+ }
+#if 0
+ /* Is this a good idea ?
+ * Well we should do a soft reset at this point ...
+ */
+ if (!has_compat(VT420) && has_compat(VT100)) {
+ if (!term->no_remote_resize)
+ term_request_resize(term,
+ term->reset_132 ? 132 : 80,
+ 24);
+ }
+#endif
+ break;
+ }
+ break;
+ case SEEN_OSC:
+ term->osc_w = false;
+ switch (c) {
+ case 'P': /* Linux palette sequence */
+ term->termstate = SEEN_OSC_P;
+ term->osc_strlen = 0;
+ break;
+ case 'R': /* Linux palette reset */
+ palette_reset(term, false);
+ term_invalidate(term);
+ term->termstate = TOPLEVEL;
+ break;
+ case 'W': /* word-set */
+ term->termstate = SEEN_OSC_W;
+ term->osc_w = true;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (term->esc_args[term->esc_nargs-1] <= UINT_MAX / 10 &&
+ term->esc_args[term->esc_nargs-1] * 10 <= UINT_MAX - c - '0')
+ term->esc_args[term->esc_nargs-1] =
+ 10 * term->esc_args[term->esc_nargs-1] + c - '0';
+ else
+ term->esc_args[term->esc_nargs-1] = UINT_MAX;
+ break;
+ case 0x9C:
+ /* Terminate even though we aren't in OSC_STRING yet */
+ do_osc(term);
+ term->termstate = TOPLEVEL;
+ break;
+ case 0xC2:
+ if (in_utf(term)) {
+ /* Or be prepared for the UTF-8 version of that */
+ term->termstate = OSC_MAYBE_ST_UTF8;
+ }
+ break;
+ default:
+ /*
+ * _Most_ other characters here terminate the
+ * immediate parsing of the OSC sequence and go
+ * into OSC_STRING state, but we deal with a
+ * couple of exceptions first.
+ */
+ if (c == 'L' && term->esc_args[0] == 2) {
+ /*
+ * Grotty hack to support xterm and DECterm title
+ * sequences concurrently.
+ */
+ term->esc_args[0] = 1;
+ } else if (c == ';' && term->esc_nargs == 1 &&
+ term->esc_args[0] == 4) {
+ /*
+ * xterm's OSC 4 sequence to query the current
+ * RGB value of a colour takes a second
+ * numeric argument which is easiest to parse
+ * using the existing system rather than in
+ * do_osc.
+ */
+ term->esc_args[term->esc_nargs++] = 0;
+ } else {
+ term->termstate = OSC_STRING;
+ term->osc_strlen = 0;
+ }
+ }
+ break;
+ case OSC_STRING:
+ /*
+ * OSC sequences can be terminated or aborted in
+ * various ways.
+ *
+ * The official way to terminate an OSC, per written
+ * standards, is the String Terminator, SC. That can
+ * appear in a 7-bit two-character form ESC \, or as
+ * an 8-bit C1 control 0x9C.
+ *
+ * We only accept 0x9C in circumstances where it
+ * doesn't interfere with our main character set
+ * processing: so in ISO 8859-1, for example, the byte
+ * 0x9C is interpreted as ST, but in CP437 it's
+ * interpreted as an ordinary printing character (as
+ * it happens, the pound sign), because you might
+ * perfectly well want to put it in the window title
+ * like any other printing character.
+ *
+ * In particular, in UTF-8 mode, 0x9C is a perfectly
+ * valid continuation byte for an ordinary printing
+ * character, so we don't accept the C1 control form
+ * of ST unless it appears as a full UTF-8 character
+ * in its own right, i.e. bytes 0xC2 0x9C.
+ *
+ * BEL is also treated as a clean termination of OSC,
+ * which I believe was a behaviour introduced by
+ * xterm.
+ *
+ * To prevent run-on storage of OSC data forever if
+ * emission of a control sequence is interrupted, we
+ * also treat various control characters as illegal,
+ * so that they abort the OSC without processing it
+ * and return to TOPLEVEL state. These are CR, LF, and
+ * any ESC that is *not* followed by \.
+ */
+
+ if (c == '\012' || c == '\015') {
+ /* CR or LF aborts */
+ term->termstate = TOPLEVEL;
+ break;
+ }
+
+ if (c == '\033') {
+ /* ESC goes into a state where we wait to see if
+ * the next character is \ */
+ term->termstate = OSC_MAYBE_ST;
+ break;
+ }
+
+ if (c == '\007' || (c == 0x9C && !in_utf(term) &&
+ term->ucsdata->unitab_ctrl[c] != 0xFF)) {
+ /* BEL, or the C1 ST appearing as a one-byte
+ * encoding, cleanly terminates the OSC right here */
+ do_osc(term);
+ term->termstate = TOPLEVEL;
+ break;
+ }
+
+ if (c == 0xC2 && in_utf(term)) {
+ /* 0xC2 is the UTF-8 character that might
+ * introduce the encoding of C1 ST */
+ term->termstate = OSC_MAYBE_ST_UTF8;
+ break;
+ }
+
+ /* Anything else gets added to the string */
+ if (term->osc_strlen < OSC_STR_MAX)
+ term->osc_string[term->osc_strlen++] = (char)c;
+ break;
+ case OSC_MAYBE_ST_UTF8:
+ /* In UTF-8 mode, we've seen C2, so are we now seeing
+ * 9C? */
+ if (c == 0x9C) {
+ /* Yes, so cleanly terminate the OSC */
+ do_osc(term);
+ term->termstate = TOPLEVEL;
+ break;
+ }
+ /* No, so append the pending C2 byte to the OSC string
+ * followed by the current character, and go back to
+ * OSC string accumulation */
+ if (term->osc_strlen < OSC_STR_MAX)
+ term->osc_string[term->osc_strlen++] = 0xC2;
+ if (term->osc_strlen < OSC_STR_MAX)
+ term->osc_string[term->osc_strlen++] = (char)c;
+ term->termstate = OSC_STRING;
+ break;
+ case SEEN_OSC_P: {
+ int max = (term->osc_strlen == 0 ? 21 : 15);
+ int val;
+ if ((int)c >= '0' && (int)c <= '9')
+ val = c - '0';
+ else if ((int)c >= 'A' && (int)c <= 'A' + max - 10)
+ val = c - 'A' + 10;
+ else if ((int)c >= 'a' && (int)c <= 'a' + max - 10)
+ val = c - 'a' + 10;
+ else {
+ term->termstate = TOPLEVEL;
+ break;
+ }
+ term->osc_string[term->osc_strlen++] = val;
+ if (term->osc_strlen >= 7) {
+ unsigned oscp_index = term->osc_string[0];
+ assert(oscp_index < OSCP_NCOLOURS);
+ unsigned osc4_index =
+ colour_indices_oscp_to_osc4[oscp_index];
+
+ rgb *value = &term->subpalettes[SUBPAL_SESSION].values[
+ osc4_index];
+ value->r = term->osc_string[1] * 16 + term->osc_string[2];
+ value->g = term->osc_string[3] * 16 + term->osc_string[4];
+ value->b = term->osc_string[5] * 16 + term->osc_string[6];
+ term->subpalettes[SUBPAL_SESSION].present[
+ osc4_index] = true;
+
+ palette_rebuild(term);
+
+ term->termstate = TOPLEVEL;
+ }
+ break;
+ }
+ case SEEN_OSC_W:
+ switch (c) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (term->esc_args[0] <= UINT_MAX / 10 &&
+ term->esc_args[0] * 10 <= UINT_MAX - c - '0')
+ term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+ else
+ term->esc_args[0] = UINT_MAX;
+ break;
+ case 0x9C:
+ /* Terminate even though we aren't in OSC_STRING yet */
+ do_osc(term);
+ term->termstate = TOPLEVEL;
+ break;
+ case 0xC2:
+ if (in_utf(term)) {
+ /* Or be prepared for the UTF-8 version of that */
+ term->termstate = OSC_MAYBE_ST_UTF8;
+ }
+ break;
+ default:
+ term->termstate = OSC_STRING;
+ term->osc_strlen = 0;
+ }
+ break;
+ case VT52_ESC:
+ term->termstate = TOPLEVEL;
+ 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);
+ }
+ }
+
+ bufchain_consume(&term->inbuf, nchars_used);
+
+ if (!called_from_term_data)
+ win_unthrottle(term->win, bufchain_size(&term->inbuf));
+
+ term_print_flush(term);
+ if (term->logflush && term->logctx)
+ logflush(term->logctx);
+}
+
+/* Wrapper on term_out with the right prototype to be a toplevel callback */
+void term_out_cb(void *ctx)
+{
+ term_out((Terminal *)ctx, false);
+}
+
+/*
+ * Small subroutine to parse three consecutive escape-sequence
+ * arguments representing a true-colour RGB triple into an
+ * optionalrgb.
+ */
+static void parse_optionalrgb(optionalrgb *out, unsigned *values)
+{
+ out->enabled = true;
+ out->r = values[0] < 256 ? values[0] : 0;
+ out->g = values[1] < 256 ? values[1] : 0;
+ out->b = values[2] < 256 ? values[2] : 0;
+}
+
+/*
+ * To prevent having to run the reasonably tricky bidi algorithm
+ * too many times, we maintain a cache of the last lineful of data
+ * fed to the algorithm on each line of the display.
+ */
+static bool term_bidi_cache_hit(Terminal *term, int line,
+ termchar *lbefore, int width, bool trusted)
+{
+ int i;
+
+ if (!term->pre_bidi_cache)
+ return false; /* cache doesn't even exist yet! */
+
+ if (line >= term->bidi_cache_size)
+ return false; /* cache doesn't have this many lines */
+
+ if (!term->pre_bidi_cache[line].chars)
+ return false; /* cache doesn't contain _this_ line */
+
+ if (term->pre_bidi_cache[line].width != width)
+ return false; /* line is wrong width */
+
+ if (term->pre_bidi_cache[line].trusted != trusted)
+ return false; /* line has wrong trust state */
+
+ for (i = 0; i < width; i++)
+ if (!termchars_equal(term->pre_bidi_cache[line].chars+i, lbefore+i))
+ return false; /* line doesn't match cache */
+
+ return true; /* it didn't match. */
+}
+
+static void term_bidi_cache_store(Terminal *term, int line, termchar *lbefore,
+ termchar *lafter, bidi_char *wcTo,
+ int width, int size, bool trusted)
+{
+ size_t i, j;
+
+ if (!term->pre_bidi_cache || term->bidi_cache_size <= line) {
+ j = term->bidi_cache_size;
+ sgrowarray(term->pre_bidi_cache, term->bidi_cache_size, line);
+ term->post_bidi_cache = sresize(term->post_bidi_cache,
+ term->bidi_cache_size,
+ struct bidi_cache_entry);
+ while (j < term->bidi_cache_size) {
+ term->pre_bidi_cache[j].chars =
+ term->post_bidi_cache[j].chars = NULL;
+ term->pre_bidi_cache[j].width =
+ term->post_bidi_cache[j].width = -1;
+ term->pre_bidi_cache[j].trusted = false;
+ term->post_bidi_cache[j].trusted = false;
+ term->pre_bidi_cache[j].forward =
+ term->post_bidi_cache[j].forward = NULL;
+ term->pre_bidi_cache[j].backward =
+ term->post_bidi_cache[j].backward = NULL;
+ j++;
+ }
+ }
+
+ sfree(term->pre_bidi_cache[line].chars);
+ sfree(term->post_bidi_cache[line].chars);
+ sfree(term->post_bidi_cache[line].forward);
+ sfree(term->post_bidi_cache[line].backward);
+
+ term->pre_bidi_cache[line].width = width;
+ term->pre_bidi_cache[line].trusted = trusted;
+ term->pre_bidi_cache[line].chars = snewn(size, termchar);
+ term->post_bidi_cache[line].width = width;
+ term->post_bidi_cache[line].trusted = trusted;
+ term->post_bidi_cache[line].chars = snewn(size, termchar);
+ term->post_bidi_cache[line].forward = snewn(width, int);
+ term->post_bidi_cache[line].backward = snewn(width, int);
+
+ memcpy(term->pre_bidi_cache[line].chars, lbefore, size * TSIZE);
+ memcpy(term->post_bidi_cache[line].chars, lafter, size * TSIZE);
+ memset(term->post_bidi_cache[line].forward, 0, width * sizeof(int));
+ memset(term->post_bidi_cache[line].backward, 0, width * sizeof(int));
+
+ for (i = j = 0; j < width; j += wcTo[i].nchars, i++) {
+ int p = wcTo[i].index;
+
+ if (p != BIDI_CHAR_INDEX_NONE) {
+ assert(0 <= p && p < width);
+
+ for (int x = 0; x < wcTo[i].nchars; x++) {
+ term->post_bidi_cache[line].backward[j+x] = p+x;
+ term->post_bidi_cache[line].forward[p+x] = j+x;
+ }
+ }
+ }
+}
+
+/*
+ * Prepare the bidi information for a screen line. Returns the
+ * transformed list of termchars, or NULL if no transformation at
+ * all took place (because bidi is disabled). If return was
+ * non-NULL, auxiliary information such as the forward and reverse
+ * mappings of permutation position are available in
+ * term->post_bidi_cache[scr_y].*.
+ */
+static termchar *term_bidi_line(Terminal *term, struct termline *ldata,
+ int scr_y)
+{
+ termchar *lchars;
+ int it;
+
+ /* Do Arabic shaping and bidi. */
+ if (!term->no_bidi || !term->no_arabicshaping ||
+ (ldata->trusted && term->cols > TRUST_SIGIL_WIDTH)) {
+
+ if (!term_bidi_cache_hit(term, scr_y, ldata->chars, term->cols,
+ ldata->trusted)) {
+
+ if (term->wcFromTo_size < term->cols) {
+ term->wcFromTo_size = term->cols;
+ term->wcFrom = sresize(term->wcFrom, term->wcFromTo_size,
+ bidi_char);
+ term->wcTo = sresize(term->wcTo, term->wcFromTo_size,
+ bidi_char);
+ }
+
+ for(it=0; it<term->cols ; it++)
+ {
+ unsigned long uc = (ldata->chars[it].chr);
+
+ switch (uc & CSET_MASK) {
+ case CSET_LINEDRW:
+ if (!term->rawcnp) {
+ uc = term->ucsdata->unitab_xterm[uc & 0xFF];
+ break;
+ }
+ case CSET_ASCII:
+ uc = term->ucsdata->unitab_line[uc & 0xFF];
+ break;
+ case CSET_SCOACS:
+ uc = term->ucsdata->unitab_scoacs[uc&0xFF];
+ break;
+ }
+ switch (uc & CSET_MASK) {
+ case CSET_ACP:
+ uc = term->ucsdata->unitab_font[uc & 0xFF];
+ break;
+ case CSET_OEMCP:
+ uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
+ break;
+ }
+
+ term->wcFrom[it].origwc = term->wcFrom[it].wc =
+ (unsigned int)uc;
+ term->wcFrom[it].index = it;
+ term->wcFrom[it].nchars = 1;
+ }
+
+ if (ldata->trusted && term->cols > TRUST_SIGIL_WIDTH) {
+ memmove(
+ term->wcFrom + TRUST_SIGIL_WIDTH, term->wcFrom,
+ (term->cols - TRUST_SIGIL_WIDTH) * sizeof(*term->wcFrom));
+ for (it = 0; it < TRUST_SIGIL_WIDTH; it++) {
+ term->wcFrom[it].origwc = term->wcFrom[it].wc =
+ (it == 0 ? TRUST_SIGIL_CHAR :
+ it == 1 ? UCSWIDE : ' ');
+ term->wcFrom[it].index = BIDI_CHAR_INDEX_NONE;
+ term->wcFrom[it].nchars = 1;
+ }
+ }
+
+ int nbc = 0;
+ for (it = 0; it < term->cols; it++) {
+ term->wcFrom[nbc] = term->wcFrom[it];
+ if (it+1 < term->cols && term->wcFrom[it+1].wc == UCSWIDE) {
+ term->wcFrom[nbc].nchars++;
+ it++;
+ }
+ nbc++;
+ }
+
+ if(!term->no_bidi)
+ do_bidi(term->bidi_ctx, term->wcFrom, nbc);
+
+ if(!term->no_arabicshaping) {
+ do_shape(term->wcFrom, term->wcTo, nbc);
+ } else {
+ /* If we're not calling do_shape, we must copy the
+ * data into wcTo anyway, unchanged */
+ memcpy(term->wcTo, term->wcFrom, nbc * sizeof(*term->wcTo));
+ }
+
+ if (term->ltemp_size < ldata->size) {
+ term->ltemp_size = ldata->size;
+ term->ltemp = sresize(term->ltemp, term->ltemp_size,
+ termchar);
+ }
+
+ memcpy(term->ltemp, ldata->chars, ldata->size * TSIZE);
+
+ int opos = 0;
+ for (it=0; it<nbc; it++) {
+ int ipos = term->wcTo[it].index;
+ for (int j = 0; j < term->wcTo[it].nchars; j++) {
+ if (ipos != BIDI_CHAR_INDEX_NONE) {
+ term->ltemp[opos] = ldata->chars[ipos];
+ if (term->ltemp[opos].cc_next)
+ term->ltemp[opos].cc_next -= opos - ipos;
+
+ if (j > 0)
+ term->ltemp[opos].chr = UCSWIDE;
+ else if (term->wcTo[it].origwc != term->wcTo[it].wc)
+ term->ltemp[opos].chr = term->wcTo[it].wc;
+ } else {
+ term->ltemp[opos] = term->basic_erase_char;
+ term->ltemp[opos].chr =
+ j > 0 ? UCSWIDE : term->wcTo[it].origwc;
+ }
+ opos++;
+ }
+ }
+ assert(opos == term->cols);
+ term_bidi_cache_store(term, scr_y, ldata->chars,
+ term->ltemp, term->wcTo,
+ term->cols, ldata->size, ldata->trusted);
+
+ lchars = term->ltemp;
+ } else {
+ lchars = term->post_bidi_cache[scr_y].chars;
+ }
+ } else {
+ lchars = NULL;
+ }
+
+ return lchars;
+}
+
+static void do_paint_draw(Terminal *term, termline *ldata, int x, int y,
+ wchar_t *ch, int ccount,
+ unsigned long attr, truecolour tc)
+{
+ if (ch[0] == TRUST_SIGIL_CHAR) {
+ assert(ldata->trusted);
+ assert(ccount == 1);
+ assert(attr & ATTR_WIDE);
+ wchar_t tch[2];
+ tch[0] = tch[1] = L' ';
+ win_draw_text(term->win, x, y, tch, 2, term->basic_erase_char.attr,
+ ldata->lattr, term->basic_erase_char.truecolour);
+ win_draw_trust_sigil(term->win, x, y);
+ } else {
+ win_draw_text(term->win, x, y, ch, ccount, attr, ldata->lattr, tc);
+ if (attr & (TATTR_ACTCURS | TATTR_PASCURS))
+ win_draw_cursor(term->win, x, y, ch, ccount,
+ attr, ldata->lattr, tc);
+ }
+}
+
+/*
+ * Given a context, update the window.
+ */
+static void do_paint(Terminal *term)
+{
+ int i, j, our_curs_y, our_curs_x;
+ int rv, cursor;
+ pos scrpos;
+ wchar_t *ch;
+ size_t chlen;
+ termchar *newline;
+
+ chlen = 1024;
+ ch = snewn(chlen, wchar_t);
+
+ newline = snewn(term->cols, termchar);
+
+ rv = (!term->rvideo ^ !term->in_vbell ? ATTR_REVERSE : 0);
+
+ /* Depends on:
+ * screen array, disptop, scrtop,
+ * selection, rv,
+ * blinkpc, blink_is_real, tblinker,
+ * curs.y, curs.x, cblinker, blink_cur, cursor_on, has_focus, wrapnext
+ */
+
+ /* Has the cursor position or type changed ? */
+ if (term->cursor_on) {
+ if (term->has_focus) {
+ if (term->cblinker || !term->blink_cur)
+ cursor = TATTR_ACTCURS;
+ else
+ cursor = 0;
+ } else
+ cursor = TATTR_PASCURS;
+ if (term->wrapnext)
+ cursor |= TATTR_RIGHTCURS;
+ } else
+ cursor = 0;
+ our_curs_y = term->curs.y - term->disptop;
+ {
+ /*
+ * Adjust the cursor position:
+ * - for bidi
+ * - in the case where it's resting on the right-hand half
+ * of a CJK wide character. xterm's behaviour here,
+ * which seems adequate to me, is to display the cursor
+ * covering the _whole_ character, exactly as if it were
+ * one space to the left.
+ */
+ termline *ldata = lineptr(term->curs.y);
+ termchar *lchars;
+
+ our_curs_x = term->curs.x;
+
+ if ( (lchars = term_bidi_line(term, ldata, our_curs_y)) != NULL) {
+ our_curs_x = term->post_bidi_cache[our_curs_y].forward[our_curs_x];
+ } else
+ lchars = ldata->chars;
+
+ if (our_curs_x > 0 &&
+ lchars[our_curs_x].chr == UCSWIDE)
+ our_curs_x--;
+
+ unlineptr(ldata);
+ }
+
+ /*
+ * If the cursor is not where it was last time we painted, and
+ * its previous position is visible on screen, invalidate its
+ * previous position.
+ */
+ if (term->dispcursy >= 0 &&
+ (term->curstype != cursor ||
+ term->dispcursy != our_curs_y ||
+ term->dispcursx != our_curs_x)) {
+ termchar *dispcurs = term->disptext[term->dispcursy]->chars +
+ term->dispcursx;
+
+ if (term->dispcursx > 0 && dispcurs->chr == UCSWIDE)
+ dispcurs[-1].attr |= ATTR_INVALID;
+ if (term->dispcursx < term->cols-1 && dispcurs[1].chr == UCSWIDE)
+ dispcurs[1].attr |= ATTR_INVALID;
+ dispcurs->attr |= ATTR_INVALID;
+
+ term->curstype = 0;
+ }
+ term->dispcursx = term->dispcursy = -1;
+
+ /* The normal screen data */
+ for (i = 0; i < term->rows; i++) {
+ termline *ldata;
+ termchar *lchars;
+ bool dirty_line, dirty_run, selected;
+ unsigned long attr = 0, cset = 0;
+ int start = 0;
+ int ccount = 0;
+ bool last_run_dirty = false;
+ int laststart;
+ bool dirtyrect;
+ int *backward;
+ truecolour tc;
+
+ scrpos.y = i + term->disptop;
+ ldata = lineptr(scrpos.y);
+
+ /* Do Arabic shaping and bidi. */
+ lchars = term_bidi_line(term, ldata, i);
+ if (lchars) {
+ backward = term->post_bidi_cache[i].backward;
+ } else {
+ lchars = ldata->chars;
+ backward = NULL;
+ }
+
+ /*
+ * First loop: work along the line deciding what we want
+ * each character cell to look like.
+ */
+ for (j = 0; j < term->cols; j++) {
+ unsigned long tattr, tchar;
+ termchar *d = lchars + j;
+ scrpos.x = backward ? backward[j] : j;
+
+ tchar = d->chr;
+ tattr = d->attr;
+
+ if (!term->ansi_colour)
+ tattr = (tattr & ~(ATTR_FGMASK | ATTR_BGMASK)) |
+ ATTR_DEFFG | ATTR_DEFBG;
+
+ if (!term->xterm_256_colour) {
+ int colour;
+ colour = (tattr & ATTR_FGMASK) >> ATTR_FGSHIFT;
+ if (colour >= 16 && colour < 256)
+ tattr = (tattr &~ ATTR_FGMASK) | ATTR_DEFFG;
+ colour = (tattr & ATTR_BGMASK) >> ATTR_BGSHIFT;
+ if (colour >= 16 && colour < 256)
+ tattr = (tattr &~ ATTR_BGMASK) | ATTR_DEFBG;
+ }
+
+ if (term->true_colour) {
+ tc = d->truecolour;
+ } else {
+ tc.fg = tc.bg = optionalrgb_none;
+ }
+
+ switch (tchar & CSET_MASK) {
+ case CSET_ASCII:
+ tchar = term->ucsdata->unitab_line[tchar & 0xFF];
+ break;
+ case CSET_LINEDRW:
+ tchar = term->ucsdata->unitab_xterm[tchar & 0xFF];
+ break;
+ case CSET_SCOACS:
+ tchar = term->ucsdata->unitab_scoacs[tchar&0xFF];
+ break;
+ }
+ if (j < term->cols-1 && d[1].chr == UCSWIDE)
+ tattr |= ATTR_WIDE;
+
+ /* Video reversing things */
+ if (term->selstate == DRAGGING || term->selstate == SELECTED) {
+ if (term->seltype == LEXICOGRAPHIC)
+ selected = (posle(term->selstart, scrpos) &&
+ poslt(scrpos, term->selend));
+ else
+ selected = (posPle(term->selstart, scrpos) &&
+ posPle_left(scrpos, term->selend));
+ } else
+ selected = false;
+ tattr = (tattr ^ rv
+ ^ (selected ? ATTR_REVERSE : 0));
+
+ /* 'Real' blinking ? */
+ if (term->blink_is_real && (tattr & ATTR_BLINK)) {
+ if (term->has_focus && term->tblinker) {
+ tchar = term->ucsdata->unitab_line[(unsigned char)' '];
+ }
+ tattr &= ~ATTR_BLINK;
+ }
+
+ /*
+ * Check the font we'll _probably_ be using to see if
+ * the character is wide when we don't want it to be.
+ */
+ if (tchar != term->disptext[i]->chars[j].chr ||
+ tattr != (term->disptext[i]->chars[j].attr &~
+ (ATTR_NARROW | DATTR_MASK))) {
+ if ((tattr & ATTR_WIDE) == 0 &&
+ win_char_width(term->win, tchar) == 2)
+ tattr |= ATTR_NARROW;
+ } else if (term->disptext[i]->chars[j].attr & ATTR_NARROW)
+ tattr |= ATTR_NARROW;
+
+ if (i == our_curs_y && j == our_curs_x) {
+ tattr |= cursor;
+ term->curstype = cursor;
+ term->dispcursx = j;
+ term->dispcursy = i;
+ }
+
+ /* FULL-TERMCHAR */
+ newline[j].attr = tattr;
+ newline[j].chr = tchar;
+ newline[j].truecolour = tc;
+ /* Combining characters are still read from lchars */
+ newline[j].cc_next = 0;
+ }
+
+ /*
+ * Now loop over the line again, noting where things have
+ * changed.
+ *
+ * During this loop, we keep track of where we last saw
+ * DATTR_STARTRUN. Any mismatch automatically invalidates
+ * _all_ of the containing run that was last printed: that
+ * is, any rectangle that was drawn in one go in the
+ * previous update should be either left completely alone
+ * or overwritten in its entirety. This, along with the
+ * expectation that front ends clip all text runs to their
+ * bounding rectangle, should solve any possible problems
+ * with fonts that overflow their character cells.
+ */
+ laststart = 0;
+ dirtyrect = false;
+ for (j = 0; j < term->cols; j++) {
+ if (term->disptext[i]->chars[j].attr & DATTR_STARTRUN) {
+ laststart = j;
+ dirtyrect = false;
+ }
+
+ if (term->disptext[i]->chars[j].chr != newline[j].chr ||
+ (term->disptext[i]->chars[j].attr &~ DATTR_MASK)
+ != newline[j].attr) {
+ int k;
+
+ if (!dirtyrect) {
+ for (k = laststart; k < j; k++)
+ term->disptext[i]->chars[k].attr |= ATTR_INVALID;
+
+ dirtyrect = true;
+ }
+ }
+
+ if (dirtyrect)
+ term->disptext[i]->chars[j].attr |= ATTR_INVALID;
+ }
+
+ /*
+ * Finally, loop once more and actually do the drawing.
+ */
+ dirty_run = dirty_line = (ldata->lattr !=
+ term->disptext[i]->lattr);
+ term->disptext[i]->lattr = ldata->lattr;
+
+ tc = term->erase_char.truecolour;
+ for (j = 0; j < term->cols; j++) {
+ unsigned long tattr, tchar;
+ bool break_run, do_copy;
+ termchar *d = lchars + j;
+
+ tattr = newline[j].attr;
+ tchar = newline[j].chr;
+
+ if ((term->disptext[i]->chars[j].attr ^ tattr) & ATTR_WIDE)
+ dirty_line = true;
+
+ break_run = ((tattr ^ attr) & term->attr_mask) != 0;
+
+ if (!truecolour_equal(newline[j].truecolour, tc))
+ break_run = true;
+
+#ifdef USES_VTLINE_HACK
+ /* Special hack for VT100 Linedraw glyphs */
+ if ((tchar >= 0x23BA && tchar <= 0x23BD) ||
+ (j > 0 && (newline[j-1].chr >= 0x23BA &&
+ newline[j-1].chr <= 0x23BD)))
+ break_run = true;
+#endif
+
+ /*
+ * Separate out sequences of characters that have the
+ * same CSET, if that CSET is a magic one.
+ */
+ if (CSET_OF(tchar) != cset)
+ break_run = true;
+
+ /*
+ * Break on both sides of any combined-character cell.
+ */
+ if (d->cc_next != 0 ||
+ (j > 0 && d[-1].cc_next != 0))
+ break_run = true;
+
+ /*
+ * Break on both sides of a trust sigil.
+ */
+ if (d->chr == TRUST_SIGIL_CHAR ||
+ (j >= 2 && d[-1].chr == UCSWIDE &&
+ d[-2].chr == TRUST_SIGIL_CHAR))
+ break_run = true;
+
+ if (!term->ucsdata->dbcs_screenfont && !dirty_line) {
+ if (term->disptext[i]->chars[j].chr == tchar &&
+ (term->disptext[i]->chars[j].attr &~ DATTR_MASK)==tattr &&
+ truecolour_equal(
+ term->disptext[i]->chars[j].truecolour, tc))
+ break_run = true;
+ else if (!dirty_run && ccount == 1)
+ break_run = true;
+ }
+
+ if (break_run) {
+ if ((dirty_run || last_run_dirty) && ccount > 0)
+ do_paint_draw(term, ldata, start, i, ch, ccount, attr, tc);
+ start = j;
+ ccount = 0;
+ attr = tattr;
+ tc = newline[j].truecolour;
+ cset = CSET_OF(tchar);
+ if (term->ucsdata->dbcs_screenfont)
+ last_run_dirty = dirty_run;
+ dirty_run = dirty_line;
+ }
+
+ do_copy = false;
+ if (!termchars_equal_override(&term->disptext[i]->chars[j],
+ d, tchar, tattr)) {
+ do_copy = true;
+ dirty_run = true;
+ }
+
+ sgrowarrayn(ch, chlen, ccount, 2);
+
+#ifdef PLATFORM_IS_UTF16
+ if (tchar > 0x10000 && tchar < 0x110000) {
+ ch[ccount++] = (wchar_t) HIGH_SURROGATE_OF(tchar);
+ ch[ccount++] = (wchar_t) LOW_SURROGATE_OF(tchar);
+ } else
+#endif /* PLATFORM_IS_UTF16 */
+ ch[ccount++] = (wchar_t) tchar;
+
+ if (d->cc_next) {
+ termchar *dd = d;
+
+ while (dd->cc_next) {
+ unsigned long schar;
+
+ dd += dd->cc_next;
+
+ schar = dd->chr;
+ switch (schar & CSET_MASK) {
+ case CSET_ASCII:
+ schar = term->ucsdata->unitab_line[schar & 0xFF];
+ break;
+ case CSET_LINEDRW:
+ schar = term->ucsdata->unitab_xterm[schar & 0xFF];
+ break;
+ case CSET_SCOACS:
+ schar = term->ucsdata->unitab_scoacs[schar&0xFF];
+ break;
+ }
+
+ sgrowarrayn(ch, chlen, ccount, 2);
+
+#ifdef PLATFORM_IS_UTF16
+ if (schar > 0x10000 && schar < 0x110000) {
+ ch[ccount++] = (wchar_t) HIGH_SURROGATE_OF(schar);
+ ch[ccount++] = (wchar_t) LOW_SURROGATE_OF(schar);
+ } else
+#endif /* PLATFORM_IS_UTF16 */
+ ch[ccount++] = (wchar_t) schar;
+ }
+
+ attr |= TATTR_COMBINING;
+ }
+
+ if (do_copy) {
+ copy_termchar(term->disptext[i], j, d);
+ term->disptext[i]->chars[j].chr = tchar;
+ term->disptext[i]->chars[j].attr = tattr;
+ term->disptext[i]->chars[j].truecolour = tc;
+ if (start == j)
+ term->disptext[i]->chars[j].attr |= DATTR_STARTRUN;
+ }
+
+ /* If it's a wide char step along to the next one. */
+ if (tattr & ATTR_WIDE) {
+ if (++j < term->cols) {
+ d++;
+ /*
+ * By construction above, the cursor should not
+ * be on the right-hand half of this character.
+ * Ever.
+ */
+ assert(!(i == our_curs_y && j == our_curs_x));
+ if (!termchars_equal(&term->disptext[i]->chars[j], d))
+ dirty_run = true;
+ copy_termchar(term->disptext[i], j, d);
+ }
+ }
+ }
+ if (dirty_run && ccount > 0)
+ do_paint_draw(term, ldata, start, i, ch, ccount, attr, tc);
+
+ unlineptr(ldata);
+ }
+
+ sfree(newline);
+ sfree(ch);
+}
+
+/*
+ * Invalidate the whole screen so it will be repainted in full.
+ */
+void term_invalidate(Terminal *term)
+{
+ int i, j;
+
+ for (i = 0; i < term->rows; i++)
+ for (j = 0; j < term->cols; j++)
+ term->disptext[i]->chars[j].attr |= ATTR_INVALID;
+
+ term_schedule_update(term);
+}
+
+/*
+ * Paint the window in response to a WM_PAINT message.
+ */
+void term_paint(Terminal *term,
+ int left, int top, int right, int bottom, bool immediately)
+{
+ int i, j;
+ if (left < 0) left = 0;
+ if (top < 0) top = 0;
+ if (right >= term->cols) right = term->cols-1;
+ if (bottom >= term->rows) bottom = term->rows-1;
+
+ for (i = top; i <= bottom && i < term->rows; i++) {
+ if ((term->disptext[i]->lattr & LATTR_MODE) == LATTR_NORM)
+ for (j = left; j <= right && j < term->cols; j++)
+ term->disptext[i]->chars[j].attr |= ATTR_INVALID;
+ else
+ for (j = left / 2; j <= right / 2 + 1 && j < term->cols; j++)
+ term->disptext[i]->chars[j].attr |= ATTR_INVALID;
+ }
+
+ if (immediately) {
+ do_paint(term);
+ } else {
+ term_schedule_update(term);
+ }
+}
+
+/*
+ * Attempt to scroll the scrollback. The second parameter gives the
+ * position we want to scroll to; the first is +1 to denote that
+ * this position is relative to the beginning of the scrollback, -1
+ * to denote it is relative to the end, and 0 to denote that it is
+ * relative to the current position.
+ */
+void term_scroll(Terminal *term, int rel, int where)
+{
+ int sbtop = -sblines(term);
+
+ term->disptop = (rel < 0 ? 0 : rel > 0 ? sbtop : term->disptop) + where;
+ if (term->disptop < sbtop)
+ term->disptop = sbtop;
+ if (term->disptop > 0)
+ term->disptop = 0;
+ term->win_scrollbar_update_pending = true;
+ term_schedule_update(term);
+}
+
+/*
+ * Scroll the scrollback to centre it on the beginning or end of the
+ * current selection, if any.
+ */
+void term_scroll_to_selection(Terminal *term, int which_end)
+{
+ pos target;
+ int y;
+ int sbtop = -sblines(term);
+
+ if (term->selstate != SELECTED)
+ return;
+ if (which_end)
+ target = term->selend;
+ else
+ target = term->selstart;
+
+ y = target.y - term->rows/2;
+ if (y < sbtop)
+ y = sbtop;
+ else if (y > 0)
+ y = 0;
+ term_scroll(term, -1, y);
+}
+
+/*
+ * Helper routine for clipme(): growing buffer.
+ */
+typedef struct {
+ size_t bufsize; /* amount of allocated space in textbuf/attrbuf */
+ size_t bufpos; /* amount of actual data */
+ wchar_t *textbuf; /* buffer for copied text */
+ wchar_t *textptr; /* = textbuf + bufpos (current insertion point) */
+ int *attrbuf; /* buffer for copied attributes */
+ int *attrptr; /* = attrbuf + bufpos */
+ truecolour *tcbuf; /* buffer for copied colours */
+ truecolour *tcptr; /* = tcbuf + bufpos */
+} clip_workbuf;
+
+static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr, truecolour tc)
+{
+ if (b->bufpos >= b->bufsize) {
+ sgrowarray(b->textbuf, b->bufsize, b->bufpos);
+ b->textptr = b->textbuf + b->bufpos;
+ b->attrbuf = sresize(b->attrbuf, b->bufsize, int);
+ b->attrptr = b->attrbuf + b->bufpos;
+ b->tcbuf = sresize(b->tcbuf, b->bufsize, truecolour);
+ b->tcptr = b->tcbuf + b->bufpos;
+ }
+ *b->textptr++ = chr;
+ *b->attrptr++ = attr;
+ *b->tcptr++ = tc;
+ b->bufpos++;
+}
+
+static void clipme(Terminal *term, pos top, pos bottom, bool rect, bool desel,
+ const int *clipboards, int n_clipboards)
+{
+ clip_workbuf buf;
+ int old_top_x;
+ int attr;
+ truecolour tc;
+
+ buf.bufsize = 5120;
+ buf.bufpos = 0;
+ buf.textptr = buf.textbuf = snewn(buf.bufsize, wchar_t);
+ buf.attrptr = buf.attrbuf = snewn(buf.bufsize, int);
+ buf.tcptr = buf.tcbuf = snewn(buf.bufsize, truecolour);
+
+ old_top_x = top.x; /* needed for rect==1 */
+
+ while (poslt(top, bottom)) {
+ bool nl = false;
+ termline *ldata = lineptr(top.y);
+ pos nlpos;
+
+ /*
+ * nlpos will point at the maximum position on this line we
+ * should copy up to. So we start it at the end of the
+ * line...
+ */
+ nlpos.y = top.y;
+ nlpos.x = term->cols;
+
+ /*
+ * ... move it backwards if there's unused space at the end
+ * of the line (and also set `nl' if this is the case,
+ * because in normal selection mode this means we need a
+ * newline at the end)...
+ */
+ if (!(ldata->lattr & LATTR_WRAPPED)) {
+ while (nlpos.x &&
+ IS_SPACE_CHR(ldata->chars[nlpos.x - 1].chr) &&
+ !ldata->chars[nlpos.x - 1].cc_next &&
+ poslt(top, nlpos))
+ decpos(nlpos);
+ if (poslt(nlpos, bottom))
+ nl = true;
+ } else {
+ if (ldata->trusted) {
+ /* A wrapped line with a trust sigil on it terminates
+ * a few characters earlier. */
+ nlpos.x = (nlpos.x < TRUST_SIGIL_WIDTH ? 0 :
+ nlpos.x - TRUST_SIGIL_WIDTH);
+ }
+ if (ldata->lattr & LATTR_WRAPPED2) {
+ /* Ignore the last char on the line in a WRAPPED2 line. */
+ decpos(nlpos);
+ }
+ }
+
+ /*
+ * ... and then clip it to the terminal x coordinate if
+ * we're doing rectangular selection. (In this case we
+ * still did the above, so that copying e.g. the right-hand
+ * column from a table doesn't fill with spaces on the
+ * right.)
+ */
+ if (rect) {
+ if (nlpos.x > bottom.x)
+ nlpos.x = bottom.x;
+ nl = (top.y < bottom.y);
+ }
+
+ while (poslt(top, bottom) && poslt(top, nlpos)) {
+#if 0
+ char cbuf[16], *p;
+ sprintf(cbuf, "<U+%04x>", (ldata[top.x] & 0xFFFF));
+#else
+ wchar_t cbuf[16], *p;
+ int c;
+ int x = top.x;
+
+ if (ldata->chars[x].chr == UCSWIDE) {
+ top.x++;
+ continue;
+ }
+
+ while (1) {
+ int uc = ldata->chars[x].chr;
+ attr = ldata->chars[x].attr;
+ tc = ldata->chars[x].truecolour;
+
+ switch (uc & CSET_MASK) {
+ case CSET_LINEDRW:
+ if (!term->rawcnp) {
+ uc = term->ucsdata->unitab_xterm[uc & 0xFF];
+ break;
+ }
+ case CSET_ASCII:
+ uc = term->ucsdata->unitab_line[uc & 0xFF];
+ break;
+ case CSET_SCOACS:
+ uc = term->ucsdata->unitab_scoacs[uc&0xFF];
+ break;
+ }
+ switch (uc & CSET_MASK) {
+ case CSET_ACP:
+ uc = term->ucsdata->unitab_font[uc & 0xFF];
+ break;
+ case CSET_OEMCP:
+ uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
+ break;
+ }
+
+ c = (uc & ~CSET_MASK);
+#ifdef PLATFORM_IS_UTF16
+ if (uc > 0x10000 && uc < 0x110000) {
+ cbuf[0] = 0xD800 | ((uc - 0x10000) >> 10);
+ cbuf[1] = 0xDC00 | ((uc - 0x10000) & 0x3FF);
+ cbuf[2] = 0;
+ } else
+#endif
+ {
+ cbuf[0] = uc;
+ cbuf[1] = 0;
+ }
+
+ if (DIRECT_FONT(uc)) {
+ if (c >= ' ' && c != 0x7F) {
+ char buf[4];
+ WCHAR wbuf[4];
+ int rv;
+ if (is_dbcs_leadbyte(term->ucsdata->font_codepage, (BYTE) c)) {
+ buf[0] = c;
+ buf[1] = (char) (0xFF & ldata->chars[top.x + 1].chr);
+ rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 2, wbuf, 4);
+ top.x++;
+ } else {
+ buf[0] = c;
+ rv = mb_to_wc(term->ucsdata->font_codepage, 0, buf, 1, wbuf, 4);
+ }
+
+ if (rv > 0) {
+ memcpy(cbuf, wbuf, rv * sizeof(wchar_t));
+ cbuf[rv] = 0;
+ }
+ }
+ }
+#endif
+
+ for (p = cbuf; *p; p++)
+ clip_addchar(&buf, *p, attr, tc);
+
+ if (ldata->chars[x].cc_next)
+ x += ldata->chars[x].cc_next;
+ else
+ break;
+ }
+ top.x++;
+ }
+ if (nl) {
+ int i;
+ for (i = 0; i < sel_nl_sz; i++)
+ clip_addchar(&buf, sel_nl[i], 0, term->basic_erase_char.truecolour);
+ }
+ top.y++;
+ top.x = rect ? old_top_x : 0;
+
+ unlineptr(ldata);
+ }
+#if SELECTION_NUL_TERMINATED
+ clip_addchar(&buf, 0, 0, term->basic_erase_char.truecolour);
+#endif
+ /* Finally, transfer all that to the clipboard(s). */
+ {
+ int i;
+ bool clip_local = false;
+ for (i = 0; i < n_clipboards; i++) {
+ if (clipboards[i] == CLIP_LOCAL) {
+ clip_local = true;
+ } else if (clipboards[i] != CLIP_NULL) {
+ win_clip_write(
+ term->win, clipboards[i], buf.textbuf, buf.attrbuf,
+ buf.tcbuf, buf.bufpos, desel);
+ }
+ }
+ if (clip_local) {
+ sfree(term->last_selected_text);
+ sfree(term->last_selected_attr);
+ sfree(term->last_selected_tc);
+ term->last_selected_text = buf.textbuf;
+ term->last_selected_attr = buf.attrbuf;
+ term->last_selected_tc = buf.tcbuf;
+ term->last_selected_len = buf.bufpos;
+ } else {
+ sfree(buf.textbuf);
+ sfree(buf.attrbuf);
+ sfree(buf.tcbuf);
+ }
+ }
+}
+
+void term_copyall(Terminal *term, const int *clipboards, int n_clipboards)
+{
+ pos top;
+ pos bottom;
+ tree234 *screen = term->screen;
+ top.y = -sblines(term);
+ top.x = 0;
+ bottom.y = find_last_nonempty_line(term, screen);
+ bottom.x = term->cols;
+ clipme(term, top, bottom, false, true, clipboards, n_clipboards);
+}
+
+static void paste_from_clip_local(void *vterm)
+{
+ Terminal *term = (Terminal *)vterm;
+ term_do_paste(term, term->last_selected_text, term->last_selected_len);
+}
+
+void term_request_copy(Terminal *term, const int *clipboards, int n_clipboards)
+{
+ int i;
+ for (i = 0; i < n_clipboards; i++) {
+ assert(clipboards[i] != CLIP_LOCAL);
+ if (clipboards[i] != CLIP_NULL) {
+ win_clip_write(term->win, clipboards[i],
+ term->last_selected_text, term->last_selected_attr,
+ term->last_selected_tc, term->last_selected_len,
+ false);
+ }
+ }
+}
+
+void term_request_paste(Terminal *term, int clipboard)
+{
+ switch (clipboard) {
+ case CLIP_NULL:
+ /* Do nothing: CLIP_NULL never has data in it. */
+ break;
+ case CLIP_LOCAL:
+ queue_toplevel_callback(paste_from_clip_local, term);
+ break;
+ default:
+ win_clip_request_paste(term->win, clipboard);
+ break;
+ }
+}
+
+/*
+ * The wordness array is mainly for deciding the disposition of the
+ * US-ASCII characters.
+ */
+static int wordtype(Terminal *term, int uc)
+{
+ struct ucsword {
+ int start, end, ctype;
+ };
+ static const struct ucsword ucs_words[] = {
+ {
+ 128, 160, 0}, {
+ 161, 191, 1}, {
+ 215, 215, 1}, {
+ 247, 247, 1}, {
+ 0x037e, 0x037e, 1}, /* Greek question mark */
+ {
+ 0x0387, 0x0387, 1}, /* Greek ano teleia */
+ {
+ 0x055a, 0x055f, 1}, /* Armenian punctuation */
+ {
+ 0x0589, 0x0589, 1}, /* Armenian full stop */
+ {
+ 0x0700, 0x070d, 1}, /* Syriac punctuation */
+ {
+ 0x104a, 0x104f, 1}, /* Myanmar punctuation */
+ {
+ 0x10fb, 0x10fb, 1}, /* Georgian punctuation */
+ {
+ 0x1361, 0x1368, 1}, /* Ethiopic punctuation */
+ {
+ 0x166d, 0x166e, 1}, /* Canadian Syl. punctuation */
+ {
+ 0x17d4, 0x17dc, 1}, /* Khmer punctuation */
+ {
+ 0x1800, 0x180a, 1}, /* Mongolian punctuation */
+ {
+ 0x2000, 0x200a, 0}, /* Various spaces */
+ {
+ 0x2070, 0x207f, 2}, /* superscript */
+ {
+ 0x2080, 0x208f, 2}, /* subscript */
+ {
+ 0x200b, 0x27ff, 1}, /* punctuation and symbols */
+ {
+ 0x3000, 0x3000, 0}, /* ideographic space */
+ {
+ 0x3001, 0x3020, 1}, /* ideographic punctuation */
+ {
+ 0x303f, 0x309f, 3}, /* Hiragana */
+ {
+ 0x30a0, 0x30ff, 3}, /* Katakana */
+ {
+ 0x3300, 0x9fff, 3}, /* CJK Ideographs */
+ {
+ 0xac00, 0xd7a3, 3}, /* Hangul Syllables */
+ {
+ 0xf900, 0xfaff, 3}, /* CJK Ideographs */
+ {
+ 0xfe30, 0xfe6b, 1}, /* punctuation forms */
+ {
+ 0xff00, 0xff0f, 1}, /* half/fullwidth ASCII */
+ {
+ 0xff1a, 0xff20, 1}, /* half/fullwidth ASCII */
+ {
+ 0xff3b, 0xff40, 1}, /* half/fullwidth ASCII */
+ {
+ 0xff5b, 0xff64, 1}, /* half/fullwidth ASCII */
+ {
+ 0xfff0, 0xffff, 0}, /* half/fullwidth ASCII */
+ {
+ 0, 0, 0}
+ };
+ const struct ucsword *wptr;
+
+ switch (uc & CSET_MASK) {
+ case CSET_LINEDRW:
+ uc = term->ucsdata->unitab_xterm[uc & 0xFF];
+ break;
+ case CSET_ASCII:
+ uc = term->ucsdata->unitab_line[uc & 0xFF];
+ break;
+ case CSET_SCOACS:
+ uc = term->ucsdata->unitab_scoacs[uc&0xFF];
+ break;
+ }
+ switch (uc & CSET_MASK) {
+ case CSET_ACP:
+ uc = term->ucsdata->unitab_font[uc & 0xFF];
+ break;
+ case CSET_OEMCP:
+ uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
+ break;
+ }
+
+ /* For DBCS fonts I can't do anything useful. Even this will sometimes
+ * fail as there's such a thing as a double width space. :-(
+ */
+ if (term->ucsdata->dbcs_screenfont &&
+ term->ucsdata->font_codepage == term->ucsdata->line_codepage)
+ return (uc != ' ');
+
+ if (uc < 0x80)
+ return term->wordness[uc];
+
+ for (wptr = ucs_words; wptr->start; wptr++) {
+ if (uc >= wptr->start && uc <= wptr->end)
+ return wptr->ctype;
+ }
+
+ return 2;
+}
+
+static int line_cols(Terminal *term, termline *ldata)
+{
+ int cols = term->cols;
+ if (ldata->trusted) {
+ cols -= TRUST_SIGIL_WIDTH;
+ }
+ if (ldata->lattr & LATTR_WRAPPED2)
+ cols--;
+ if (cols < 0)
+ cols = 0;
+ return cols;
+}
+
+/*
+ * Spread the selection outwards according to the selection mode.
+ */
+static pos sel_spread_half(Terminal *term, pos p, int dir)
+{
+ termline *ldata;
+ short wvalue;
+ int topy = -sblines(term);
+
+ ldata = lineptr(p.y);
+
+ switch (term->selmode) {
+ case SM_CHAR:
+ /*
+ * In this mode, every character is a separate unit, except
+ * for runs of spaces at the end of a non-wrapping line.
+ */
+ if (!(ldata->lattr & LATTR_WRAPPED)) {
+ termchar *q = ldata->chars + line_cols(term, ldata);
+ while (q > ldata->chars &&
+ IS_SPACE_CHR(q[-1].chr) && !q[-1].cc_next)
+ q--;
+ if (q == ldata->chars + term->cols)
+ q--;
+ if (p.x >= q - ldata->chars)
+ p.x = (dir == -1 ? q - ldata->chars : term->cols - 1);
+ }
+ break;
+ case SM_WORD:
+ /*
+ * In this mode, the units are maximal runs of characters
+ * whose `wordness' has the same value.
+ */
+ wvalue = wordtype(term, UCSGET(ldata->chars, p.x));
+ if (dir == +1) {
+ while (1) {
+ int maxcols = line_cols(term, ldata);
+ if (p.x < maxcols-1) {
+ if (wordtype(term, UCSGET(ldata->chars, p.x+1)) == wvalue)
+ p.x++;
+ else
+ break;
+ } else {
+ if (p.y+1 < term->rows &&
+ (ldata->lattr & LATTR_WRAPPED)) {
+ termline *ldata2;
+ ldata2 = lineptr(p.y+1);
+ if (wordtype(term, UCSGET(ldata2->chars, 0))
+ == wvalue) {
+ p.x = 0;
+ p.y++;
+ unlineptr(ldata);
+ ldata = ldata2;
+ } else {
+ unlineptr(ldata2);
+ break;
+ }
+ } else
+ break;
+ }
+ }
+ } else {
+ while (1) {
+ if (p.x > 0) {
+ if (wordtype(term, UCSGET(ldata->chars, p.x-1)) == wvalue)
+ p.x--;
+ else
+ break;
+ } else {
+ termline *ldata2;
+ int maxcols;
+ if (p.y <= topy)
+ break;
+ ldata2 = lineptr(p.y-1);
+ maxcols = line_cols(term, ldata2);
+ if (ldata2->lattr & LATTR_WRAPPED) {
+ if (wordtype(term, UCSGET(ldata2->chars, maxcols-1))
+ == wvalue) {
+ p.x = maxcols-1;
+ p.y--;
+ unlineptr(ldata);
+ ldata = ldata2;
+ } else {
+ unlineptr(ldata2);
+ break;
+ }
+ } else
+ break;
+ }
+ }
+ }
+ break;
+ case SM_LINE:
+ /*
+ * In this mode, every line is a unit.
+ */
+ p.x = (dir == -1 ? 0 : term->cols - 1);
+ break;
+ }
+
+ unlineptr(ldata);
+ return p;
+}
+
+static void sel_spread(Terminal *term)
+{
+ if (term->seltype == LEXICOGRAPHIC) {
+ term->selstart = sel_spread_half(term, term->selstart, -1);
+ decpos(term->selend);
+ term->selend = sel_spread_half(term, term->selend, +1);
+ incpos(term->selend);
+ }
+}
+
+static void term_paste_callback(void *vterm)
+{
+ Terminal *term = (Terminal *)vterm;
+
+ if (term->paste_len == 0)
+ return;
+
+ while (term->paste_pos < term->paste_len) {
+ int n = 0;
+ while (n + term->paste_pos < term->paste_len) {
+ if (term->paste_buffer[term->paste_pos + n++] == '\015')
+ break;
+ }
+ if (term->ldisc) {
+ strbuf *buf = term_input_data_from_unicode(
+ term, term->paste_buffer + term->paste_pos, n);
+ term_keyinput_internal(term, buf->s, buf->len, false);
+ strbuf_free(buf);
+ }
+ term->paste_pos += n;
+
+ if (term->paste_pos < term->paste_len) {
+ queue_toplevel_callback(term_paste_callback, term);
+ return;
+ }
+ }
+ term_bracketed_paste_stop(term);
+ sfree(term->paste_buffer);
+ term->paste_buffer = NULL;
+ term->paste_len = 0;
+}
+
+/*
+ * Specialist string compare function. Returns true if the buffer of
+ * alen wide characters starting at a has as a prefix the buffer of
+ * blen characters starting at b.
+ */
+static bool wstartswith(const wchar_t *a, size_t alen,
+ const wchar_t *b, size_t blen)
+{
+ return alen >= blen && !wcsncmp(a, b, blen);
+}
+
+void term_do_paste(Terminal *term, const wchar_t *data, int len)
+{
+ const wchar_t *p;
+ bool paste_controls = conf_get_bool(term->conf, CONF_paste_controls);
+
+ /*
+ * Pasting data into the terminal counts as a keyboard event (for
+ * purposes of the 'Reset scrollback on keypress' config option),
+ * unless the paste is zero-length.
+ */
+ if (len == 0)
+ return;
+ term_seen_key_event(term);
+
+ if (term->paste_buffer)
+ sfree(term->paste_buffer);
+ term->paste_pos = term->paste_len = 0;
+ term->paste_buffer = snewn(len + 12, wchar_t);
+
+ if (term->bracketed_paste)
+ term_bracketed_paste_start(term);
+
+ p = data;
+ while (p < data + len) {
+ wchar_t wc = *p++;
+
+ if (wc == sel_nl[0] &&
+ wstartswith(p-1, data+len-(p-1), sel_nl, sel_nl_sz)) {
+ /*
+ * This is the (platform-dependent) sequence that the host
+ * OS uses to represent newlines in clipboard data.
+ * Normalise it to a press of CR.
+ */
+ p += sel_nl_sz - 1;
+ wc = '\015';
+ }
+
+ if ((wc & ~(wint_t)0x9F) == 0) {
+ /*
+ * This is a control code, either in the range 0x00-0x1F
+ * or 0x80-0x9F. We reject all of these in pastecontrols
+ * mode, except for a small set of permitted ones.
+ */
+ if (!paste_controls) {
+ /* In line with xterm 292, accepted control chars are:
+ * CR, LF, tab, backspace. (And DEL, i.e. 0x7F, but
+ * that's permitted by virtue of not matching the bit
+ * mask that got us into this if statement, so we
+ * don't have to permit it here. */
+ static const unsigned mask =
+ (1<<13) | (1<<10) | (1<<9) | (1<<8);
+
+ if (wc > 15 || !((mask >> wc) & 1))
+ continue;
+ }
+
+ if (wc == '\033' && term->bracketed_paste &&
+ wstartswith(p-1, data+len-(p-1), L"\033[201~", 6)) {
+ /*
+ * Also, in bracketed-paste mode, reject the ESC
+ * character that begins the end-of-paste sequence.
+ */
+ continue;
+ }
+ }
+
+ term->paste_buffer[term->paste_len++] = wc;
+ }
+
+ /* Assume a small paste will be OK in one go. */
+ if (term->paste_len < 256) {
+ if (term->ldisc) {
+ strbuf *buf = term_input_data_from_unicode(
+ term, term->paste_buffer, term->paste_len);
+ term_keyinput_internal(term, buf->s, buf->len, false);
+ strbuf_free(buf);
+ }
+ if (term->paste_buffer)
+ sfree(term->paste_buffer);
+ term_bracketed_paste_stop(term);
+ term->paste_buffer = NULL;
+ term->paste_pos = term->paste_len = 0;
+ }
+
+ queue_toplevel_callback(term_paste_callback, term);
+}
+
+void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked,
+ Mouse_Action a, int x, int y, bool shift, bool ctrl, bool alt)
+{
+ pos selpoint;
+ termline *ldata;
+ bool raw_mouse = (term->xterm_mouse &&
+ !term->no_mouse_rep &&
+ !(term->mouse_override && shift));
+ int default_seltype;
+
+ if (y < 0) {
+ y = 0;
+ if (a == MA_DRAG && !raw_mouse)
+ term_scroll(term, 0, -1);
+ }
+ if (y >= term->rows) {
+ y = term->rows - 1;
+ if (a == MA_DRAG && !raw_mouse)
+ term_scroll(term, 0, +1);
+ }
+ if (x < 0) {
+ if (y > 0 && !raw_mouse && term->seltype != RECTANGULAR) {
+ /*
+ * When we're using the mouse for normal raster-based
+ * selection, dragging off the left edge of a terminal row
+ * is treated the same as the right-hand end of the
+ * previous row, in that it's considered to identify a
+ * point _before_ the first character on row y.
+ *
+ * But if the mouse action is going to be used for
+ * anything else - rectangular selection, or xterm mouse
+ * tracking - then we disable this special treatment.
+ */
+ x = term->cols - 1;
+ y--;
+ } else
+ x = 0;
+ }
+ if (x >= term->cols)
+ x = term->cols - 1;
+
+ selpoint.y = y + term->disptop;
+ ldata = lineptr(selpoint.y);
+
+ if ((ldata->lattr & LATTR_MODE) != LATTR_NORM)
+ x /= 2;
+
+ /*
+ * Transform x through the bidi algorithm to find the _logical_
+ * click point from the physical one.
+ */
+ if (term_bidi_line(term, ldata, y) != NULL) {
+ x = term->post_bidi_cache[y].backward[x];
+ }
+
+ selpoint.x = x;
+ unlineptr(ldata);
+
+ /*
+ * If we're in the middle of a selection operation, we ignore raw
+ * mouse mode until it's done (we must have been not in raw mouse
+ * mode when it started).
+ * This makes use of Shift for selection reliable, and avoids the
+ * host seeing mouse releases for which they never saw corresponding
+ * presses.
+ */
+ if (raw_mouse &&
+ (term->selstate != ABOUT_TO) && (term->selstate != DRAGGING)) {
+ int encstate = 0, r, c;
+ bool wheel;
+ char abuf[32];
+ int len = 0;
+
+ if (term->ldisc) {
+
+ switch (braw) {
+ case MBT_LEFT:
+ encstate = 0x00; /* left button down */
+ wheel = false;
+ break;
+ case MBT_MIDDLE:
+ encstate = 0x01;
+ wheel = false;
+ break;
+ case MBT_RIGHT:
+ encstate = 0x02;
+ wheel = false;
+ break;
+ case MBT_WHEEL_UP:
+ encstate = 0x40;
+ wheel = true;
+ break;
+ case MBT_WHEEL_DOWN:
+ encstate = 0x41;
+ wheel = true;
+ break;
+ default:
+ return;
+ }
+ if (wheel) {
+ /* For mouse wheel buttons, we only ever expect to see
+ * MA_CLICK actions, and we don't try to keep track of
+ * the buttons being 'pressed' (since without matching
+ * click/release pairs that's pointless). */
+ if (a != MA_CLICK)
+ return;
+ } else switch (a) {
+ case MA_DRAG:
+ if (term->xterm_mouse == 1)
+ return;
+ encstate += 0x20;
+ break;
+ case MA_RELEASE:
+ /* If multiple extensions are enabled, the xterm 1006 is used, so it's okay to check for only that */
+ if (!term->xterm_extended_mouse)
+ encstate = 0x03;
+ term->mouse_is_down = 0;
+ break;
+ case MA_CLICK:
+ if (term->mouse_is_down == braw)
+ return;
+ term->mouse_is_down = braw;
+ break;
+ default:
+ return;
+ }
+ if (shift)
+ encstate += 0x04;
+ if (ctrl)
+ encstate += 0x10;
+ r = y + 1;
+ c = x + 1;
+
+ /* Check the extensions in decreasing order of preference. Encoding the release event above assumes that 1006 comes first. */
+ if (term->xterm_extended_mouse) {
+ len = sprintf(abuf, "\033[<%d;%d;%d%c", encstate, c, r, a == MA_RELEASE ? 'm' : 'M');
+ } else if (term->urxvt_extended_mouse) {
+ len = sprintf(abuf, "\033[%d;%d;%dM", encstate + 32, c, r);
+ } else if (c <= 223 && r <= 223) {
+ len = sprintf(abuf, "\033[M%c%c%c", encstate + 32, c + 32, r + 32);
+ }
+ if (len > 0)
+ ldisc_send(term->ldisc, abuf, len, false);
+ }
+ return;
+ }
+
+ /*
+ * Set the selection type (rectangular or normal) at the start
+ * of a selection attempt, from the state of Alt.
+ */
+ if (!alt ^ !term->rect_select)
+ default_seltype = RECTANGULAR;
+ else
+ default_seltype = LEXICOGRAPHIC;
+
+ if (term->selstate == NO_SELECTION) {
+ term->seltype = default_seltype;
+ }
+
+ if (bcooked == MBT_SELECT && a == MA_CLICK) {
+ deselect(term);
+ term->selstate = ABOUT_TO;
+ term->seltype = default_seltype;
+ term->selanchor = selpoint;
+ term->selmode = SM_CHAR;
+ } else if (bcooked == MBT_SELECT && (a == MA_2CLK || a == MA_3CLK)) {
+ deselect(term);
+ term->selmode = (a == MA_2CLK ? SM_WORD : SM_LINE);
+ term->selstate = DRAGGING;
+ term->selstart = term->selanchor = selpoint;
+ term->selend = term->selstart;
+ incpos(term->selend);
+ sel_spread(term);
+ } else if ((bcooked == MBT_SELECT && a == MA_DRAG) ||
+ (bcooked == MBT_EXTEND && a != MA_RELEASE)) {
+ if (a == MA_DRAG &&
+ (term->selstate == NO_SELECTION || term->selstate == SELECTED)) {
+ /*
+ * This can happen if a front end has passed us a MA_DRAG
+ * without a prior MA_CLICK. OS X GTK does so, for
+ * example, if the initial button press was eaten by the
+ * WM when it activated the window in the first place. The
+ * nicest thing to do in this situation is to ignore
+ * further drags, and wait for the user to click in the
+ * window again properly if they want to select.
+ */
+ return;
+ }
+ if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint))
+ return;
+ if (bcooked == MBT_EXTEND && a != MA_DRAG &&
+ term->selstate == SELECTED) {
+ if (term->seltype == LEXICOGRAPHIC) {
+ /*
+ * For normal selection, we extend by moving
+ * whichever end of the current selection is closer
+ * to the mouse.
+ */
+ if (posdiff(selpoint, term->selstart) <
+ posdiff(term->selend, term->selstart) / 2) {
+ term->selanchor = term->selend;
+ decpos(term->selanchor);
+ } else {
+ term->selanchor = term->selstart;
+ }
+ } else {
+ /*
+ * For rectangular selection, we have a choice of
+ * _four_ places to put selanchor and selpoint: the
+ * four corners of the selection.
+ */
+ if (2*selpoint.x < term->selstart.x + term->selend.x)
+ term->selanchor.x = term->selend.x-1;
+ else
+ term->selanchor.x = term->selstart.x;
+
+ if (2*selpoint.y < term->selstart.y + term->selend.y)
+ term->selanchor.y = term->selend.y;
+ else
+ term->selanchor.y = term->selstart.y;
+ }
+ term->selstate = DRAGGING;
+ }
+ if (term->selstate != ABOUT_TO && term->selstate != DRAGGING)
+ term->selanchor = selpoint;
+ term->selstate = DRAGGING;
+ if (term->seltype == LEXICOGRAPHIC) {
+ /*
+ * For normal selection, we set (selstart,selend) to
+ * (selpoint,selanchor) in some order.
+ */
+ if (poslt(selpoint, term->selanchor)) {
+ term->selstart = selpoint;
+ term->selend = term->selanchor;
+ incpos(term->selend);
+ } else {
+ term->selstart = term->selanchor;
+ term->selend = selpoint;
+ incpos(term->selend);
+ }
+ } else {
+ /*
+ * For rectangular selection, we may need to
+ * interchange x and y coordinates (if the user has
+ * dragged in the -x and +y directions, or vice versa).
+ */
+ term->selstart.x = min(term->selanchor.x, selpoint.x);
+ term->selend.x = 1+max(term->selanchor.x, selpoint.x);
+ term->selstart.y = min(term->selanchor.y, selpoint.y);
+ term->selend.y = max(term->selanchor.y, selpoint.y);
+ }
+ sel_spread(term);
+ } else if ((bcooked == MBT_SELECT || bcooked == MBT_EXTEND) &&
+ a == MA_RELEASE) {
+ if (term->selstate == DRAGGING) {
+ /*
+ * We've completed a selection. We now transfer the
+ * data to the clipboard.
+ */
+ clipme(term, term->selstart, term->selend,
+ (term->seltype == RECTANGULAR), false,
+ term->mouse_select_clipboards,
+ term->n_mouse_select_clipboards);
+ term->selstate = SELECTED;
+ } else
+ term->selstate = NO_SELECTION;
+ } else if (bcooked == MBT_PASTE
+ && (a == MA_CLICK
+#if MULTICLICK_ONLY_EVENT
+ || a == MA_2CLK || a == MA_3CLK
+#endif
+ )) {
+ term_request_paste(term, term->mouse_paste_clipboard);
+ }
+
+ /*
+ * Since terminal output is suppressed during drag-selects, we
+ * should make sure to write any pending output if one has just
+ * finished.
+ */
+ term_out(term, false);
+ term_schedule_update(term);
+}
+
+void term_cancel_selection_drag(Terminal *term)
+{
+ /*
+ * In unusual circumstances, a mouse drag might be interrupted by
+ * something that steals the rest of the mouse gesture. An example
+ * is the GTK popup menu appearing. In that situation, we'll never
+ * receive the MA_RELEASE that finishes the DRAGGING state, which
+ * means terminal output could be suppressed indefinitely. Call
+ * this function from the front end in such situations to restore
+ * sensibleness.
+ */
+ if (term->selstate == DRAGGING)
+ term->selstate = NO_SELECTION;
+ term_out(term, false);
+ term_schedule_update(term);
+}
+
+static int shift_bitmap(bool shift, bool ctrl, bool alt, bool *consumed_alt)
+{
+ int bitmap = (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0);
+ if (bitmap)
+ bitmap++;
+ if (alt && consumed_alt)
+ *consumed_alt = true;
+ return bitmap;
+}
+
+int format_arrow_key(char *buf, Terminal *term, int xkey,
+ bool shift, bool ctrl, bool alt, bool *consumed_alt)
+{
+ char *p = buf;
+
+ if (term->vt52_mode)
+ p += sprintf(p, "\x1B%c", xkey);
+ else {
+ bool app_flg = (term->app_cursor_keys && !term->no_applic_c);
+#if 0
+ /*
+ * RDB: VT100 & VT102 manuals both state the app cursor
+ * keys only work if the app keypad is on.
+ *
+ * SGT: That may well be true, but xterm disagrees and so
+ * does at least one application, so I've #if'ed this out
+ * and the behaviour is back to PuTTY's original: app
+ * cursor and app keypad are independently switchable
+ * modes. If anyone complains about _this_ I'll have to
+ * put in a configurable option.
+ */
+ if (!term->app_keypad_keys)
+ app_flg = 0;
+#endif
+
+ int bitmap = 0;
+
+ /* Adjustment based on Shift, Ctrl and/or Alt */
+ switch (term->sharrow_type) {
+ case SHARROW_APPLICATION:
+ if (ctrl)
+ app_flg = !app_flg;
+ break;
+ case SHARROW_BITMAP:
+ bitmap = shift_bitmap(shift, ctrl, alt, consumed_alt);
+ break;
+ }
+
+ if (app_flg)
+ p += sprintf(p, "\x1BO%c", xkey);
+ else if (bitmap)
+ p += sprintf(p, "\x1B[1;%d%c", bitmap, xkey);
+ else
+ p += sprintf(p, "\x1B[%c", xkey);
+ }
+
+ return p - buf;
+}
+
+int format_function_key(char *buf, Terminal *term, int key_number,
+ bool shift, bool ctrl, bool alt, bool *consumed_alt)
+{
+ char *p = buf;
+
+ static const int key_number_to_tilde_code[] = {
+ -1, /* no such key as F0 */
+ 11, 12, 13, 14, 15, /*gap*/ 17, 18, 19, 20, 21, /*gap*/
+ 23, 24, 25, 26, /*gap*/ 28, 29, /*gap*/ 31, 32, 33, 34,
+ };
+
+ assert(key_number > 0);
+ assert(key_number < lenof(key_number_to_tilde_code));
+
+ int index = key_number;
+ if (term->funky_type != FUNKY_XTERM_216 && term->funky_type != FUNKY_SCO) {
+ if (shift && index <= 10) {
+ shift = false;
+ index += 10;
+ }
+ }
+
+ int code = key_number_to_tilde_code[index];
+
+ if (term->funky_type == FUNKY_SCO) {
+ /* SCO function keys */
+ static const char sco_codes[] =
+ "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
+ index = (key_number >= 1 && key_number <= 12) ? key_number - 1 : 0;
+ if (shift) index += 12;
+ if (ctrl) index += 24;
+ p += sprintf(p, "\x1B[%c", sco_codes[index]);
+ } else if ((term->vt52_mode || term->funky_type == FUNKY_VT100P) &&
+ code >= 11 && code <= 24) {
+ int offt = 0;
+ if (code > 15)
+ offt++;
+ if (code > 21)
+ offt++;
+ if (term->vt52_mode)
+ p += sprintf(p, "\x1B%c", code + 'P' - 11 - offt);
+ else
+ p += sprintf(p, "\x1BO%c", code + 'P' - 11 - offt);
+ } else if (term->funky_type == FUNKY_LINUX && code >= 11 && code <= 15) {
+ p += sprintf(p, "\x1B[[%c", code + 'A' - 11);
+ } else if ((term->funky_type == FUNKY_XTERM ||
+ term->funky_type == FUNKY_XTERM_216) &&
+ code >= 11 && code <= 14) {
+ if (term->vt52_mode)
+ p += sprintf(p, "\x1B%c", code + 'P' - 11);
+ else {
+ int bitmap = 0;
+ if (term->funky_type == FUNKY_XTERM_216)
+ bitmap = shift_bitmap(shift, ctrl, alt, consumed_alt);
+ if (bitmap)
+ p += sprintf(p, "\x1B[1;%d%c", bitmap, code + 'P' - 11);
+ else
+ p += sprintf(p, "\x1BO%c", code + 'P' - 11);
+ }
+ } else {
+ int bitmap = 0;
+ if (term->funky_type == FUNKY_XTERM_216)
+ bitmap = shift_bitmap(shift, ctrl, alt, consumed_alt);
+ if (bitmap)
+ p += sprintf(p, "\x1B[%d;%d~", code, bitmap);
+ else
+ p += sprintf(p, "\x1B[%d~", code);
+ }
+
+ return p - buf;
+}
+
+int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key,
+ bool shift, bool ctrl, bool alt,
+ bool *consumed_alt)
+{
+ char *p = buf;
+
+ int code;
+ switch (key) {
+ case SKK_HOME: code = 1; break;
+ case SKK_INSERT: code = 2; break;
+ case SKK_DELETE: code = 3; break;
+ case SKK_END: code = 4; break;
+ case SKK_PGUP: code = 5; break;
+ case SKK_PGDN: code = 6; break;
+ default: unreachable("bad small keypad key enum value");
+ }
+
+ /* Reorder edit keys to physical order */
+ if (term->funky_type == FUNKY_VT400 && code <= 6)
+ code = "\0\2\1\4\5\3\6"[code];
+
+ if (term->vt52_mode && code > 0 && code <= 6) {
+ p += sprintf(p, "\x1B%c", " HLMEIG"[code]);
+ } else if (term->funky_type == FUNKY_SCO) {
+ static const char codes[] = "HL.FIG";
+ if (code == 3) {
+ *p++ = '\x7F';
+ } else {
+ p += sprintf(p, "\x1B[%c", codes[code-1]);
+ }
+ } else if ((code == 1 || code == 4) && term->rxvt_homeend) {
+ p += sprintf(p, code == 1 ? "\x1B[H" : "\x1BOw");
+ } else {
+ if (term->vt52_mode) {
+ p += sprintf(p, "\x1B[%d~", code);
+ } else {
+ int bitmap = 0;
+ if (term->funky_type == FUNKY_XTERM_216)
+ bitmap = shift_bitmap(shift, ctrl, alt, consumed_alt);
+ if (bitmap)
+ p += sprintf(p, "\x1B[%d;%d~", code, bitmap);
+ else
+ p += sprintf(p, "\x1B[%d~", code);
+ }
+ }
+
+ return p - buf;
+}
+
+int format_numeric_keypad_key(char *buf, Terminal *term, char key,
+ bool shift, bool ctrl)
+{
+ char *p = buf;
+ bool app_keypad = (term->app_keypad_keys && !term->no_applic_k);
+
+ if (term->nethack_keypad && (key >= '1' && key <= '9')) {
+ static const char nh_base[] = "bjnh.lyku";
+ char c = nh_base[key - '1'];
+ if (ctrl && c != '.')
+ c &= 0x1F;
+ else if (shift && c != '.')
+ c += 'A'-'a';
+ *p++ = c;
+ } else {
+ int xkey = 0;
+
+ if (term->funky_type == FUNKY_VT400 ||
+ (term->funky_type <= FUNKY_LINUX && app_keypad)) {
+ switch (key) {
+ case 'G': xkey = 'P'; break;
+ case '/': xkey = 'Q'; break;
+ case '*': xkey = 'R'; break;
+ case '-': xkey = 'S'; break;
+ }
+ }
+
+ if (app_keypad) {
+ switch (key) {
+ case '0': xkey = 'p'; break;
+ case '1': xkey = 'q'; break;
+ case '2': xkey = 'r'; break;
+ case '3': xkey = 's'; break;
+ case '4': xkey = 't'; break;
+ case '5': xkey = 'u'; break;
+ case '6': xkey = 'v'; break;
+ case '7': xkey = 'w'; break;
+ case '8': xkey = 'x'; break;
+ case '9': xkey = 'y'; break;
+ case '.': xkey = 'n'; break;
+ case '\r': xkey = 'M'; break;
+
+ case '+':
+ /*
+ * Keypad + is tricky. It covers a space that would
+ * be taken up on the VT100 by _two_ keys; so we
+ * let Shift select between the two. Worse still,
+ * in xterm function key mode we change which two...
+ */
+ if (term->funky_type == FUNKY_XTERM)
+ xkey = shift ? 'l' : 'k';
+ else
+ xkey = shift ? 'm' : 'l';
+ break;
+
+ case '/':
+ if (term->funky_type == FUNKY_XTERM)
+ xkey = 'o';
+ break;
+ case '*':
+ if (term->funky_type == FUNKY_XTERM)
+ xkey = 'j';
+ break;
+ case '-':
+ if (term->funky_type == FUNKY_XTERM)
+ xkey = 'm';
+ break;
+ }
+ }
+
+ if (xkey) {
+ if (term->vt52_mode) {
+ if (xkey >= 'P' && xkey <= 'S')
+ p += sprintf(p, "\x1B%c", xkey);
+ else
+ p += sprintf(p, "\x1B?%c", xkey);
+ } else
+ p += sprintf(p, "\x1BO%c", xkey);
+ }
+ }
+
+ return p - buf;
+}
+
+void term_keyinputw(Terminal *term, const wchar_t *widebuf, int len)
+{
+ strbuf *buf = term_input_data_from_unicode(term, widebuf, len);
+ if (buf->len)
+ term_keyinput_internal(term, buf->s, buf->len, true);
+ strbuf_free(buf);
+}
+
+void term_keyinput(Terminal *term, int codepage, const void *str, int len)
+{
+ if (codepage < 0 || codepage == term->ucsdata->line_codepage) {
+ /*
+ * This text needs no translation, either because it's already
+ * in the right character set, or because we got the special
+ * codepage value -1 from our caller which means 'this data
+ * should be charset-agnostic, just send it raw' (for really
+ * simple things like control characters).
+ */
+ term_keyinput_internal(term, str, len, true);
+ } else {
+ strbuf *buf = term_input_data_from_charset(term, codepage, str, len);
+ if (buf->len)
+ term_keyinput_internal(term, buf->s, buf->len, true);
+ strbuf_free(buf);
+ }
+}
+
+void term_nopaste(Terminal *term)
+{
+ if (term->paste_len == 0)
+ return;
+ sfree(term->paste_buffer);
+ term_bracketed_paste_stop(term);
+ term->paste_buffer = NULL;
+ term->paste_len = 0;
+}
+
+static void deselect(Terminal *term)
+{
+ term->selstate = NO_SELECTION;
+ term->selstart.x = term->selstart.y = term->selend.x = term->selend.y = 0;
+}
+
+void term_lost_clipboard_ownership(Terminal *term, int clipboard)
+{
+ if (!(term->n_mouse_select_clipboards > 1 &&
+ clipboard == term->mouse_select_clipboards[1]))
+ return;
+
+ deselect(term);
+ term_update(term);
+
+ /*
+ * Since terminal output is suppressed during drag-selects, we
+ * should make sure to write any pending output if one has just
+ * finished.
+ */
+ term_out(term, false);
+}
+
+static void term_added_data(Terminal *term, bool called_from_term_data)
+{
+ if (!term->in_term_out) {
+ term->in_term_out = true;
+ term_reset_cblink(term);
+ term_out(term, called_from_term_data);
+ term->in_term_out = false;
+ }
+}
+
+size_t term_data(Terminal *term, const void *data, size_t len)
+{
+ bufchain_add(&term->inbuf, data, len);
+ term_added_data(term, true);
+ return bufchain_size(&term->inbuf);
+}
+
+void term_provide_logctx(Terminal *term, LogContext *logctx)
+{
+ term->logctx = logctx;
+}
+
+void term_set_focus(Terminal *term, bool has_focus)
+{
+ term->has_focus = has_focus;
+ term_schedule_cblink(term);
+}
+
+/*
+ * Provide "auto" settings for remote tty modes, suitable for an
+ * application with a terminal window.
+ */
+char *term_get_ttymode(Terminal *term, const char *mode)
+{
+ const char *val = NULL;
+ if (strcmp(mode, "ERASE") == 0) {
+ val = term->bksp_is_delete ? "^?" : "^H";
+ } else if (strcmp(mode, "IUTF8") == 0) {
+ val = (term->ucsdata->line_codepage == CP_UTF8) ? "yes" : "no";
+ }
+ /* FIXME: perhaps we should set ONLCR based on lfhascr as well? */
+ /* FIXME: or ECHO and friends based on local echo state? */
+ return dupstr(val);
+}
+
+struct term_userpass_state {
+ 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, data.ptr, data.len);
+}
+
+/*
+ * Signal that a prompts_t is done. This involves sending a
+ * notification to the caller, and also turning off our own callback
+ * that listens for more data arriving in the ldisc's input queue.
+ */
+static inline SeatPromptResult signal_prompts_t(Terminal *term, prompts_t *p,
+ SeatPromptResult spr)
+{
+ assert(p->callback && "Asynchronous userpass input requires a callback");
+ queue_toplevel_callback(p->callback, p->callback_ctx);
+ if (term->ldisc)
+ ldisc_enable_prompt_callback(term->ldisc, NULL);
+ p->spr = spr;
+ return spr;
+}
+
+/*
+ * Process some terminal data in the course of username/password
+ * input.
+ */
+SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p)
+{
+ if (!term->ldisc) {
+ /* Can't handle interactive prompts without an ldisc */
+ return signal_prompts_t(term, p, SPR_SW_ABORT(
+ "Terminal not prepared for interactive prompts"));
+ }
+
+ if (p->spr.kind != SPRK_INCOMPLETE) {
+ /* We've already finished these prompts, so return the same
+ * result again */
+ return p->spr;
+ }
+
+ struct term_userpass_state *s = (struct term_userpass_state *)p->data;
+
+ if (!s) {
+ /*
+ * First call. Set some stuff up.
+ */
+ p->data = s = snew(struct term_userpass_state);
+ p->spr = SPR_INCOMPLETE;
+ 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 (!ldisc_has_input_buffered(term->ldisc))
+ break;
+
+ /* FIXME: should we be using local-line-editing code instead? */
+ while (!finished_prompt && ldisc_has_input_buffered(term->ldisc)) {
+ LdiscInputToken tok = ldisc_get_input_token(term->ldisc);
+
+ char c;
+ if (tok.is_special) {
+ switch (tok.code) {
+ case SS_EOL: c = 13; break;
+ case SS_EC: c = 8; break;
+ case SS_IP: c = 3; break;
+ case SS_EOF: c = 3; break;
+ default: continue;
+ }
+ } else {
+ c = tok.chr;
+ }
+
+ 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 signal_prompts_t(term, p, SPR_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) {
+ ldisc_enable_prompt_callback(term->ldisc, p);
+ return SPR_INCOMPLETE;
+ } else {
+ sfree(s);
+ p->data = NULL;
+ return signal_prompts_t(term, p, SPR_OK);
+ }
+}
+
+void term_notify_minimised(Terminal *term, bool minimised)
+{
+ term->minimised = minimised;
+}
+
+void term_notify_palette_changed(Terminal *term)
+{
+ palette_reset(term, true);
+}
+
+void term_notify_window_pos(Terminal *term, int x, int y)
+{
+ term->winpos_x = x;
+ term->winpos_y = y;
+}
+
+void term_notify_window_size_pixels(Terminal *term, int x, int y)
+{
+ term->winpixsize_x = x;
+ term->winpixsize_y = y;
+}
diff --git a/terminal/terminal.h b/terminal/terminal.h
new file mode 100644
index 00000000..3f918b22
--- /dev/null
+++ b/terminal/terminal.h
@@ -0,0 +1,563 @@
+/*
+ * 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;
+ 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)
+
+#define OSC_STR_MAX 2048
+ int osc_strlen;
+ char osc_string[OSC_STR_MAX + 1];
+ bool osc_w;
+
+ char id_string[1024];
+
+ unsigned char *tabs;
+
+ enum {
+ TOPLEVEL,
+ SEEN_ESC,
+ SEEN_CSI,
+ SEEN_OSC,
+ SEEN_OSC_W,
+
+ DO_CTRLS,
+
+ SEEN_OSC_P,
+ OSC_STRING, OSC_MAYBE_ST, OSC_MAYBE_ST_UTF8,
+ VT52_ESC,
+ VT52_Y1,
+ VT52_Y2,
+ VT52_FG,
+ VT52_BG
+ } termstate;
+
+ enum {
+ NO_SELECTION, ABOUT_TO, DRAGGING, SELECTED
+ } selstate;
+ enum {
+ LEXICOGRAPHIC, RECTANGULAR
+ } seltype;
+ enum {
+ SM_CHAR, SM_WORD, SM_LINE
+ } selmode;
+ pos selstart, selend, selanchor;
+
+ short wordness[256];
+
+ /* Mask of attributes to pay attention to when painting. */
+ int attr_mask;
+
+ wchar_t *paste_buffer;
+ int paste_len, paste_pos;
+
+ Backend *backend;
+
+ Ldisc *ldisc;
+
+ TermWin *win;
+
+ LogContext *logctx;
+
+ struct unicode_data *ucsdata;
+
+ unsigned long last_graphic_char;
+
+ /*
+ * We maintain a full copy of a Conf here, not merely a pointer
+ * to it. That way, when we're passed a new one for
+ * reconfiguration, we can check the differences and adjust the
+ * _current_ setting of (e.g.) auto wrap mode rather than only
+ * the default.
+ */
+ Conf *conf;
+
+ /*
+ * GUI implementations of seat_output call term_out, but it can
+ * also be called from the ldisc if the ldisc is called _within_
+ * term_out. So we have to guard against re-entrancy - if
+ * seat_output is called recursively like this, it will simply add
+ * data to the end of the buffer term_out is in the process of
+ * working through.
+ */
+ bool in_term_out;
+
+ /*
+ * We don't permit window updates too close together, to avoid CPU
+ * churn pointlessly redrawing the window faster than the user can
+ * read. So after an update, we set window_update_cooldown = true
+ * and schedule a timer to reset it to false. In between those
+ * times, window updates are not performed, and instead we set
+ * window_update_pending = true, which will remind us to perform
+ * the deferred redraw when the cooldown period ends and
+ * window_update_cooldown is reset to false.
+ */
+ bool window_update_pending, window_update_cooldown;
+ long window_update_cooldown_end;
+
+ /*
+ * Track pending blinks and tblinks.
+ */
+ bool tblink_pending, cblink_pending;
+ long next_tblink, next_cblink;
+
+ /*
+ * These are buffers used by the bidi and Arabic shaping code.
+ */
+ termchar *ltemp;
+ int ltemp_size;
+ bidi_char *wcFrom, *wcTo;
+ int wcFromTo_size;
+ struct bidi_cache_entry *pre_bidi_cache, *post_bidi_cache;
+ size_t bidi_cache_size;
+
+ /*
+ * Current trust state, used to annotate every line of the
+ * terminal that a graphic character is output to.
+ */
+ bool trusted;
+
+ /*
+ * We copy a bunch of stuff out of the Conf structure into local
+ * fields in the Terminal structure, to avoid the repeated
+ * tree234 lookups which would be involved in fetching them from
+ * the former every time.
+ */
+ bool ansi_colour;
+ 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, sharrow_type;
+ bool lfhascr;
+ bool logflush;
+ int logtype;
+ bool mouse_override;
+ bool nethack_keypad;
+ bool no_alt_screen;
+ bool no_applic_c;
+ bool no_applic_k;
+ bool no_dbackspace;
+ bool no_mouse_rep;
+ bool no_remote_charset;
+ bool no_remote_resize;
+ bool no_remote_wintitle;
+ bool no_remote_clearscroll;
+ bool rawcnp;
+ bool utf8linedraw;
+ bool rect_select;
+ int remote_qtitle_action;
+ bool rxvt_homeend;
+ bool scroll_on_disp;
+ bool scroll_on_key;
+ bool xterm_256_colour;
+ bool true_colour;
+
+ wchar_t *last_selected_text;
+ int *last_selected_attr;
+ truecolour *last_selected_tc;
+ size_t last_selected_len;
+ int mouse_select_clipboards[N_CLIPBOARDS];
+ int n_mouse_select_clipboards;
+ int mouse_paste_clipboard;
+
+ char *window_title, *icon_title;
+ int wintitle_codepage, icontitle_codepage;
+ bool minimised;
+
+ BidiContext *bidi_ctx;
+
+ /* Multi-layered colour palette. The colours from Conf (plus the
+ * default xterm-256 ones that don't have Conf ids at all) have
+ * lowest priority, followed by platform overrides if any,
+ * followed by escape-sequence overrides during the session. */
+ struct term_subpalette {
+ rgb values[OSC4_NCOLOURS];
+ bool present[OSC4_NCOLOURS];
+ } subpalettes[3];
+#define SUBPAL_CONF 0
+#define SUBPAL_PLATFORM 1
+#define SUBPAL_SESSION 2
+
+ /* The composite palette that we make out of the above */
+ rgb palette[OSC4_NCOLOURS];
+
+ unsigned winpos_x, winpos_y, winpixsize_x, winpixsize_y;
+
+ /*
+ * Assorted 'pending' flags for ancillary window changes performed
+ * in term_update. Generally, to trigger one of these operations,
+ * you set the pending flag and/or the parameters here, then call
+ * term_schedule_update.
+ */
+ bool win_move_pending;
+ int win_move_pending_x, win_move_pending_y;
+ bool win_zorder_pending;
+ bool win_zorder_top;
+ bool win_minimise_pending;
+ bool win_minimise_enable;
+ bool win_maximise_pending;
+ bool win_maximise_enable;
+ bool win_title_pending, win_icon_title_pending;
+ bool win_pointer_shape_pending;
+ bool win_pointer_shape_raw;
+ bool win_refresh_pending;
+ bool win_scrollbar_update_pending;
+ bool win_palette_pending;
+ unsigned win_palette_pending_min, win_palette_pending_limit;
+
+ /*
+ * Unlike the rest of the above 'pending' flags, the one for
+ * window resizing has to be more complicated, because it's very
+ * likely that a server sending a window-resize escape sequence is
+ * going to follow it up immediately with further terminal output
+ * that draws a full-screen application expecting the terminal to
+ * be the new size.
+ *
+ * So, once we've requested a window resize from the TermWin, we
+ * have to stop processing terminal data until we get back the
+ * notification that our window really has changed size (or until
+ * we find out that it's not going to).
+ *
+ * Hence, window resizes go through a small state machine with two
+ * different kinds of 'pending'. NEED_SEND is the state where
+ * we've received an escape sequence asking for a new size but not
+ * yet sent it to the TermWin via win_request_resize; AWAIT_REPLY
+ * is the state where we've sent it to the TermWin and are
+ * expecting a call back to term_size().
+ *
+ * So _both_ of those 'pending' states inhibit terminal output
+ * processing.
+ *
+ * (Hence, once we're in either state, we should never handle
+ * another resize sequence, so the only possible path through this
+ * state machine is to get all the way back to the ground state
+ * before doing anything else interesting.)
+ */
+ enum {
+ WIN_RESIZE_NO, WIN_RESIZE_NEED_SEND, WIN_RESIZE_AWAIT_REPLY
+ } win_resize_pending;
+ int win_resize_pending_w, win_resize_pending_h;
+};
+
+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/test/agentmulti.py b/test/agentmulti.py
new file mode 100755
index 00000000..019bf2b6
--- /dev/null
+++ b/test/agentmulti.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import random
+import socket
+import sys
+
+from ssh import *
+
+def make_connections(n):
+ connections = []
+
+ for _ in range(n):
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(os.environ["SSH_AUTH_SOCK"])
+ connections.append(s)
+
+ return connections
+
+def use_connection(s, idstring):
+ print("Trying {}...".format(idstring), end="")
+ sys.stdout.flush()
+
+ s.send(ssh_string(ssh_byte(SSH2_AGENTC_EXTENSION) + ssh_string(
+ b"nonexistent-agent-extension@putty.projects.tartarus.org")))
+ length = ssh_decode_uint32(s.recv(4))
+ assert length < AGENT_MAX_MSGLEN
+ msg = s.recv(length)
+ msgtype = msg[0]
+ msgstring = (
+ "SSH_AGENT_EXTENSION_FAILURE" if msgtype == SSH_AGENT_EXTENSION_FAILURE
+ else "SSH_AGENT_FAILURE" if msgtype == SSH_AGENT_FAILURE
+ else "type {:d}".format(msgtype))
+ print("got", msgstring, "with {:d}-byte payload".format(len(msg)-1))
+
+def randomly_use_connections(connections, iterations):
+ for _ in range(iterations):
+ index = random.randrange(0, len(connections))
+ s = connections[index]
+ use_connection(connections[index], "#{:d}".format(index))
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Test handling of multiple agent connections.')
+ parser.add_argument("--nsockets", type=int, default=128,
+ help="Number of simultaneous connections to make.")
+ parser.add_argument("--ntries", type=int, default=1024,
+ help="Number of messages to send in total.")
+ args = parser.parse_args()
+
+ connections = make_connections(args.nsockets)
+ randomly_use_connections(connections, args.ntries)
+
+if __name__ == '__main__':
+ main()
diff --git a/test/ca.py b/test/ca.py
new file mode 100755
index 00000000..ebd80599
--- /dev/null
+++ b/test/ca.py
@@ -0,0 +1,198 @@
+#!/usr/bin/env python3
+#
+# Implementation of OpenSSH certificate creation. Used in
+# cryptsuite.py to construct certificates for test purposes.
+#
+# Can also be run standalone to function as an actual CA, though I
+# don't currently know of any reason you'd want to use it in place of
+# ssh-keygen. In that mode, it depends on having an SSH agent
+# available to do the signing.
+
+import argparse
+import base64
+import enum
+import hashlib
+import io
+import os
+
+import ssh
+
+class Container:
+ pass
+
+class CertType(enum.Enum):
+ user = 1
+ host = 2
+
+def maybe_encode(s):
+ if isinstance(s, bytes):
+ return s
+ return s.encode('UTF-8')
+
+def make_signature_preimage(
+ key_to_certify, ca_key, certtype, keyid, serial, principals,
+ valid_after=0, valid_before=0xFFFFFFFFFFFFFFFF,
+ critical_options={}, extensions={},
+ reserved=b'', nonce=None):
+
+ alg, pubkeydata = ssh.ssh_decode_string(key_to_certify, True)
+
+ if nonce is None:
+ nonce = os.urandom(32)
+
+ buf = io.BytesIO()
+ buf.write(ssh.ssh_string(alg + b"-cert-v01@openssh.com"))
+ buf.write(ssh.ssh_string(nonce))
+ buf.write(pubkeydata)
+ buf.write(ssh.ssh_uint64(serial))
+ buf.write(ssh.ssh_uint32(certtype.value if isinstance(certtype, CertType)
+ else certtype))
+ buf.write(ssh.ssh_string(maybe_encode(keyid)))
+ buf.write(ssh.ssh_string(b''.join(
+ ssh.ssh_string(maybe_encode(principal))
+ for principal in principals)))
+ buf.write(ssh.ssh_uint64(valid_after))
+ buf.write(ssh.ssh_uint64(valid_before))
+ buf.write(ssh.ssh_string(b''.join(
+ ssh.ssh_string(opt) + ssh.ssh_string(val)
+ for opt, val in sorted([(maybe_encode(opt), maybe_encode(val))
+ for opt, val in critical_options.items()]))))
+ buf.write(ssh.ssh_string(b''.join(
+ ssh.ssh_string(opt) + ssh.ssh_string(val)
+ for opt, val in sorted([(maybe_encode(opt), maybe_encode(val))
+ for opt, val in extensions.items()]))))
+ buf.write(ssh.ssh_string(reserved))
+ # The CA key here can be a raw 'bytes', or an ssh_key object
+ # exposed via testcrypt
+ if type(ca_key) != bytes:
+ ca_key = ca_key.public_blob()
+ buf.write(ssh.ssh_string(ca_key))
+
+ return buf.getvalue()
+
+def make_full_cert(preimage, signature):
+ return preimage + ssh.ssh_string(signature)
+
+def sign_cert_via_testcrypt(preimage, ca_key, signflags=None):
+ # Expects ca_key to be a testcrypt ssh_key object
+ signature = ca_key.sign(preimage, 0 if signflags is None else signflags)
+ return make_full_cert(preimage, signature)
+
+def sign_cert_via_agent(preimage, ca_key, signflags=None):
+ # Expects ca_key to be a binary public key blob, and for a
+ # currently running SSH agent to contain the corresponding private
+ # key.
+ import agenttest
+ sign_request = (ssh.ssh_byte(ssh.SSH2_AGENTC_SIGN_REQUEST) +
+ ssh.ssh_string(ca_key) + ssh.ssh_string(preimage))
+ if signflags is not None:
+ sign_request += ssh.ssh_uint32(signflags)
+ sign_response = agenttest.agent_query(sign_request)
+ msgtype, sign_response = ssh.ssh_decode_byte(sign_response, True)
+ if msgtype == ssh.SSH2_AGENT_SIGN_RESPONSE:
+ signature, sign_response = ssh.ssh_decode_string(sign_response, True)
+ return make_full_cert(preimage, signature)
+ elif msgtype == ssh.SSH2_AGENT_FAILURE:
+ raise IOError("Agent refused to return a signature")
+ else:
+ raise IOError("Agent returned unexpecteed message type {:d}"
+ .format(msgtype))
+
+def read_pubkey_file(fh):
+ b64buf = io.StringIO()
+ comment = None
+
+ lines = (line.rstrip("\r\n") for line in iter(fh.readline, ""))
+ line = next(lines)
+
+ if line == "---- BEGIN SSH2 PUBLIC KEY ----":
+ # RFC 4716 public key. Read headers like Comment:
+ line = next(lines)
+ while ":" in line:
+ key, val = line.split(":", 1)
+ if key == "Comment":
+ comment = val.strip("\r\n")
+ line = next(lines)
+ # Now expect lines of base64 data.
+ while line != "---- END SSH2 PUBLIC KEY ----":
+ b64buf.write(line)
+ line = next(lines)
+
+ else:
+ # OpenSSH public key. Expect the b64buf blob to be the second word.
+ fields = line.split(" ", 2)
+ b64buf.write(fields[1])
+ if len(fields) > 1:
+ comment = fields[2]
+
+ return base64.b64decode(b64buf.getvalue()), comment
+
+def write_pubkey_file(fh, key, comment=None):
+ alg = ssh.ssh_decode_string(key)
+ fh.write(alg.decode('ASCII'))
+ fh.write(" " + base64.b64encode(key).decode('ASCII'))
+ if comment is not None:
+ fh.write(" " + comment)
+ fh.write("\n")
+
+def default_signflags(key):
+ alg = ssh.ssh_decode_string(key)
+ if alg == b'ssh-rsa':
+ return 4 # RSA-SHA-512
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Create and sign OpenSSH certificates.')
+ parser.add_argument("key_to_certify", help="Public key to be certified.")
+ parser.add_argument("--ca-key", required=True,
+ help="Public key of the CA. Must be present in a "
+ "currently accessible SSH agent.")
+ parser.add_argument("-o", "--output", required=True,
+ help="File to write output OpenSSH key to.")
+ parser.add_argument("--type", required=True, choices={'user', 'host'},
+ help="Type of certificate to make.")
+ parser.add_argument("--principal", "--user", "--host",
+ required=True, action="append",
+ help="User names or host names to authorise.")
+ parser.add_argument("--key-id", "--keyid", required=True,
+ help="Human-readable key ID string for log files.")
+ parser.add_argument("--serial", type=int, required=True,
+ help="Serial number to write into certificate.")
+ parser.add_argument("--signflags", type=int, help="Signature flags "
+ "(e.g. 2 = RSA-SHA-256, 4 = RSA-SHA-512).")
+ args = parser.parse_args()
+
+ with open(args.key_to_certify) as fh:
+ key_to_certify, comment = read_pubkey_file(fh)
+ with open(args.ca_key) as fh:
+ ca_key, _ = read_pubkey_file(fh)
+
+ extensions = {
+ 'permit-X11-forwarding': '',
+ 'permit-agent-forwarding': '',
+ 'permit-port-forwarding': '',
+ 'permit-pty': '',
+ 'permit-user-rc': '',
+ }
+
+ # FIXME: for a full-featured command-line CA we'd need to add
+ # command-line options for crit opts, extensions and validity
+ # period
+ preimage = make_signature_preimage(
+ key_to_certify = key_to_certify,
+ ca_key = ca_key,
+ certtype = getattr(CertType, args.type),
+ keyid = args.key_id,
+ serial = args.serial,
+ principals = args.principal,
+ extensions = extensions)
+
+ signflags = (args.signflags if args.signflags is not None
+ else default_signflags(ca_key))
+ cert = sign_cert_via_agent(preimage, ca_key, signflags)
+
+ with open(args.output, "w") as fh:
+ write_pubkey_file(fh, cert, comment)
+
+if __name__ == '__main__':
+ main()
diff --git a/test/cryptsuite.py b/test/cryptsuite.py
index 757de673..69b492e8 100644
--- a/test/cryptsuite.py
+++ b/test/cryptsuite.py
@@ -8,7 +8,7 @@ import functools
import contextlib
import hashlib
import binascii
-import base64
+from base64 import b64decode as b64
import json
try:
from math import gcd
@@ -18,14 +18,10 @@ except ImportError:
from eccref import *
from testcrypt import *
from ssh import *
+from ca import CertType, make_signature_preimage, sign_cert_via_testcrypt
assert sys.version_info[:2] >= (3,0), "This is Python 3 code"
-try:
- base64decode = base64.decodebytes
-except AttributeError:
- base64decode = base64.decodestring
-
def unhex(s):
return binascii.unhexlify(s.replace(" ", "").replace("\n", ""))
@@ -141,6 +137,19 @@ def mac_str(alg, key, message, cipher=None):
def lcm(a, b):
return a * b // gcd(a, b)
+def get_implementations(alg):
+ return get_implementations_commasep(alg).decode("ASCII").split(",")
+
+def get_aes_impls():
+ return [impl.rsplit("_", 1)[-1]
+ for impl in get_implementations("aes128_cbc")
+ if impl.startswith("aes128_cbc_")]
+
+def get_aesgcm_impls():
+ return [impl.split("_", 1)[1]
+ for impl in get_implementations("aesgcm")
+ if impl.startswith("aesgcm_")]
+
class MyTestBase(unittest.TestCase):
"Intermediate class that adds useful helper methods."
def assertEqualBin(self, x, y):
@@ -1071,6 +1080,49 @@ class keygen(MyTestBase):
add(po, p6, [p3,p4], 2)
add(po, p, [p2,p5,p6], 2)
+ # Combined certificate for the moduli and generator orders of
+ # the three NIST curves, generated by contrib/proveprime.py
+ # (with some cosmetic tidying)
+ p256 = 2**256 - 2**224 + 2**192 + 2**96 - 1
+ p384 = 2**384 - 2**128 - 2**96 + 2**32 - 1
+ p521 = 2**521 - 1
+ order256 = p256 - 0x4319055358e8617b0c46353d039cdaae
+ order384 = p384 - 0x389cb27e0bc8d21fa7e5f24cb74f58851313e696333ad68c
+ t = 0x5ae79787c40d069948033feb708f65a2fc44a36477663b851449048e16ec79bf6
+ order521 = p521 - t
+ p0 = order384 // 12895580879789762060783039592702
+ p1 = 1059392654943455286185473617842338478315215895509773412096307
+ p2 = 55942463741690639
+ p3 = 37344768852931
+ p4 = order521 // 1898873518475180724503002533770555108536
+ p5 = p4 // 994165722
+ p6 = 144471089338257942164514676806340723
+ p7 = p384 // 2054993070433694
+ p8 = 1357291859799823621
+ po = pockle_new()
+ add_small(po, 2, 3, 5, 11, 17, 19, 31, 41, 53, 67, 71, 109, 131, 149,
+ 157, 257, 521, 641, 1613, 2731, 3407, 6317, 8191, 8389,
+ 14461, 17449, 38189, 38557, 42641, 51481, 61681, 65537,
+ 133279, 248431, 312289, 409891, 490463, 858001, 6700417,
+ 187019741)
+ add(po, p3, [149, 11, 5, 3, 2], 3)
+ add(po, p2, [p3], 2)
+ add(po, p8, [6317, 67, 2, 2], 2)
+ add(po, p6, [133279, 14461, 109, 3], 7)
+ add(po, p1, [p2, 248431], 2)
+ add(po, order256, [187019741, 38189, 17449, 3407, 131, 71, 2, 2, 2, 2],
+ 7)
+ add(po, p256, [6700417, 490463, 65537, 641, 257, 17, 5, 5, 3, 2], 6)
+ add(po, p0, [p1], 2)
+ add(po, p7, [p8, 312289, 38557, 8389, 11, 2], 3)
+ add(po, p5, [p6, 19], 2)
+ add(po, order384, [p0], 2)
+ add(po, p384, [p7], 2)
+ add(po, p4, [p5], 2)
+ add(po, order521, [p4], 2)
+ add(po, p521, [858001, 409891, 61681, 51481, 42641, 8191, 2731, 1613,
+ 521, 157, 131, 53, 41, 31, 17, 11, 5, 5, 3, 2], 3)
+
def testPockleNegative(self):
def add_small(po, p):
self.assertEqual(pockle_add_small_prime(po, p), 'POCKLE_OK')
@@ -1137,6 +1189,193 @@ class keygen(MyTestBase):
self.assertEqual(pockle_add_prime(po, 1, [2], 1),
'POCKLE_PRIME_SMALLER_THAN_2')
+ def testMillerRabin(self):
+ # A prime congruent to 3 mod 4, so M-R can only do one
+ # iteration: either a^{(p-1)/2} == +1, or -1. Either counts as
+ # a pass; the latter also means the number is potentially a
+ # primitive root.
+ n = 0xe76e6aaa42b5d7423aa4da5613eb21c3
+ mr = miller_rabin_new(n)
+ self.assertEqual(miller_rabin_test(mr, 2), "passed+ppr")
+ self.assertEqual(miller_rabin_test(mr, 4), "passed")
+
+ # The 'potential primitive root' test only means that M-R
+ # didn't _rule out_ the number being a primitive root, by
+ # finding that any of the powers _it tested_ less than n-1
+ # came out to be 1. In this case, 2 really is a primitive
+ # root, but since 13 | n-1, the 13th powers mod n form a
+ # multiplicative subgroup. So 2^13 is not a primitive root,
+ # and yet, M-R can't tell the difference, because it only
+ # tried the exponent (n-1)/2, not the actual counterexample
+ # (n-1)/13.
+ self.assertEqual(miller_rabin_test(mr, 2**13), "passed+ppr")
+
+ # A prime congruent to 1 mod a reasonably large power of 2, so
+ # M-R has lots of scope to have different things happen. 3 is
+ # a primitive root, so we expect that 3, 3^2, 3^4, ..., 3^256
+ # should all pass for different reasons, with only the first
+ # of them returning passed+ppr.
+ n = 0xb1b65ebe489ff0ab4597bb67c3d22d01
+ mr = miller_rabin_new(n)
+ w = 3
+ self.assertEqual(miller_rabin_test(mr, w), "passed+ppr")
+ for i in range(1, 10):
+ w = w * w % n
+ self.assertEqual(miller_rabin_test(mr, w), "passed")
+
+ # A prime with an _absurdly_ large power-of-2 factor in its
+ # multiplicative group.
+ n = 0x600000000000000000000000000000000000000000000001
+ mr = miller_rabin_new(n)
+ w = 10
+ self.assertEqual(miller_rabin_test(mr, w), "passed+ppr")
+ for i in range(1, 200):
+ w = w * w % n
+ self.assertEqual(miller_rabin_test(mr, w), "passed")
+
+ # A blatantly composite number. But we still expect to see a
+ # pass if we give the witness 1 (which will give a maximal
+ # trailing string of 1s), or -1 (which will give -1 when
+ # raised to the maximal odd factor of n-1, or indeed any other
+ # odd power).
+ n = 0x1010101010101010101010101010101
+ mr = miller_rabin_new(n)
+ self.assertEqual(miller_rabin_test(mr, 1), "passed")
+ self.assertEqual(miller_rabin_test(mr, n-1), "passed")
+ self.assertEqual(miller_rabin_test(mr, 2), "failed")
+
+ # A Carmichael number, as a proper test that M-R detects
+ # things the Fermat test would not.
+ #
+ # (Its prime factorisation is 26823115100268314289505807 *
+ # 53646230200536628579011613 * 80469345300804942868517419,
+ # which is enough to re-check its Carmichaelness.)
+ n = 0xffffffffffffffffcf8032f3e044b4a8b1b1bf0b526538eae953d90f44d65511
+ mr = miller_rabin_new(n)
+ self.assertEqual(miller_rabin_test(mr, 16), "passed")
+ assert(pow(2, n-1, n) == 1) # Fermat test would pass, but ...
+ self.assertEqual(miller_rabin_test(mr, 2), "failed") # ... this fails
+
+ # A white-box test for the side-channel-safe M-R
+ # implementation, which has to check a^e against +-1 for every
+ # exponent e of the form floor((n-1) / power of 2), so as to
+ # avoid giving away exactly how many of the trailing values of
+ # that sequence are significant to the test.
+ #
+ # When the power of 2 is large enough that the division was
+ # not exact, the results of these comparisons are _not_
+ # significant to the test, and we're required to ignore them!
+ #
+ # This pair of values has the property that none of the values
+ # legitimately computed by M-R is either +1 _or_ -1, but if
+ # you shift n-1 right by one too many bits (losing the lowest
+ # set bit of 0x6d00 to get 0x36), then _that_ power of the
+ # witness integer is -1. This should not cause a spurious pass.
+ n = 0x6d01
+ mr = miller_rabin_new(n)
+ self.assertEqual(miller_rabin_test(mr, 0x251), "failed")
+
+class ntru(MyTestBase):
+ def testMultiply(self):
+ self.assertEqual(
+ ntru_ring_multiply([1,1,1,1,1,1], [1,1,1,1,1,1], 11, 59),
+ [1,2,3,4,5,6,5,4,3,2,1])
+ self.assertEqual(ntru_ring_multiply(
+ [1,0,1,2,0,0,1,2,0,1,2], [2,0,0,1,0,1,2,2,2,0,2], 11, 3),
+ [1,0,0,0,0,0,0,0,0,0,0])
+
+ def testInvert(self):
+ # Over GF(3), x^11-x-1 factorises as
+ # (x^3+x^2+2) * (x^8+2*x^7+x^6+2*x^4+2*x^3+x^2+x+1)
+ # so we expect that 2,0,1,1 has no inverse, being one of those factors.
+ self.assertEqual(ntru_ring_invert([0], 11, 3), None)
+ self.assertEqual(ntru_ring_invert([1], 11, 3),
+ [1,0,0,0,0,0,0,0,0,0,0])
+ self.assertEqual(ntru_ring_invert([2,0,1,1], 11, 3), None)
+ self.assertEqual(ntru_ring_invert([1,0,1,2,0,0,1,2,0,1,2], 11, 3),
+ [2,0,0,1,0,1,2,2,2,0,2])
+
+ self.assertEqual(ntru_ring_invert([1,0,1,2,0,0,1,2,0,1,2], 11, 59),
+ [1,26,10,1,38,48,34,37,53,3,53])
+
+ def testMod3Round3(self):
+ # Try a prime congruent to 1 mod 3
+ self.assertEqual(ntru_mod3([4,5,6,0,1,2,3], 7, 7),
+ [0,1,-1,0,1,-1,0])
+ self.assertEqual(ntru_round3([4,5,6,0,1,2,3], 7, 7),
+ [-3,-3,0,0,0,3,3])
+
+ # And one congruent to 2 mod 3
+ self.assertEqual(ntru_mod3([6,7,8,9,10,0,1,2,3,4,5], 11, 11),
+ [1,-1,0,1,-1,0,1,-1,0,1,-1])
+ self.assertEqual(ntru_round3([6,7,8,9,10,0,1,2,3,4,5], 11, 11),
+ [-6,-3,-3,-3,0,0,0,3,3,3,6])
+
+ def testBiasScale(self):
+ self.assertEqual(ntru_bias([0,1,2,3,4,5,6,7,8,9,10], 4, 11, 11),
+ [4,5,6,7,8,9,10,0,1,2,3])
+ self.assertEqual(ntru_scale([0,1,2,3,4,5,6,7,8,9,10], 4, 11, 11),
+ [0,4,8,1,5,9,2,6,10,3,7])
+
+ def testEncode(self):
+ # Test a small case. Worked through in detail:
+ #
+ # Pass 1:
+ # Input list is (89:123, 90:234, 344:345, 432:456, 222:567)
+ # (89:123, 90:234) -> (89+123*90 : 123*234) = (11159:28782)
+ # Emit low byte of 11159 = 0x97, and get (43:113)
+ # (344:345, 432:456) -> (344+345*432 : 345*456) = (149384:157320)
+ # Emit low byte of 149384 = 0x88, and get (583:615)
+ # Odd pair (222:567) is copied to end of new list
+ # Final list is (43:113, 583:615, 222:567)
+ # Pass 2:
+ # Input list is (43:113, 583:615, 222:567)
+ # (43:113, 583:615) -> (43+113*583, 113*615) = (65922:69495)
+ # Emit low byte of 65922 = 0x82, and get (257:272)
+ # Odd pair (222:567) is copied to end of new list
+ # Final list is (257:272, 222:567)
+ # Pass 3:
+ # Input list is (257:272, 222:567)
+ # (257:272, 222:567) -> (257+272*222, 272*567) = (60641:154224)
+ # Emit low byte of 60641 = 0xe1, and get (236:603)
+ # Final list is (236:603)
+ # Cleanup:
+ # Emit low byte of 236 = 0xec, and get (0:3)
+ # Emit low byte of 0 = 0x00, and get (0:1)
+
+ ms = [123,234,345,456,567]
+ rs = [89,90,344,432,222]
+ encoding = unhex('978882e1ec00')
+ sched = ntru_encode_schedule(ms)
+ self.assertEqual(sched.encode(rs), encoding)
+ self.assertEqual(sched.decode(encoding), rs)
+
+ # Encode schedules for sntrup761 public keys and ciphertexts
+ pubsched = ntru_encode_schedule([4591]*761)
+ self.assertEqual(pubsched.length(), 1158)
+ ciphersched = ntru_encode_schedule([1531]*761)
+ self.assertEqual(ciphersched.length(), 1007)
+
+ # Test round-trip encoding using those schedules
+ testlist = list(range(761))
+ pubtext = pubsched.encode(testlist)
+ self.assertEqual(pubsched.decode(pubtext), testlist)
+ ciphertext = ciphersched.encode(testlist)
+ self.assertEqual(ciphersched.decode(ciphertext), testlist)
+
+ def testCore(self):
+ # My own set of NTRU Prime parameters, satisfying all the
+ # requirements and tiny enough for convenient testing
+ p, q, w = 11, 59, 3
+
+ with random_prng('ntru keygen seed'):
+ keypair = ntru_keygen(p, q, w)
+ plaintext = ntru_gen_short(p, w)
+
+ ciphertext = ntru_encrypt(plaintext, ntru_pubkey(keypair), p, q)
+ recovered = ntru_decrypt(ciphertext, keypair)
+ self.assertEqual(plaintext, recovered)
+
class crypt(MyTestBase):
def testSSH1Fingerprint(self):
# Example key and reference fingerprint value generated by
@@ -1148,9 +1387,9 @@ class crypt(MyTestBase):
def testSSH2Fingerprints(self):
# A sensible key blob that we can make sense of.
- sensible_blob = base64.decodebytes(
- b'AAAAC3NzaC1lZDI1NTE5AAAAICWiV0VAD4lQ7taUN7vZ5Rkc'
- b'SLJBW5ubn6ZINwCOzpn3')
+ sensible_blob = b64(
+ 'AAAAC3NzaC1lZDI1NTE5AAAAICWiV0VAD4lQ7taUN7vZ5Rkc'
+ 'SLJBW5ubn6ZINwCOzpn3')
self.assertEqual(ssh2_fingerprint_blob(sensible_blob, "sha256"),
b'ssh-ed25519 255 SHA256:'
b'E4VmaHW0sUF7SUgSEOmMJ8WBtt0e/j3zbsKvyqfFnu4')
@@ -1176,14 +1415,43 @@ class crypt(MyTestBase):
self.assertEqual(ssh2_fingerprint_blob(very_silly_blob, "md5"),
b'ac:bd:18:db:4c:c2:f8:5c:ed:ef:65:4f:cc:c4:a4:d8')
+ # A certified key.
+ cert_blob = b64(
+ 'AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIJ4Ds9YwRHxs'
+ 'xdtUitRbZGz0MgKGZSBVrTHI1AbvetofAAAAIMt0/CMBL+64GQ/r/JyGxo6oHs86'
+ 'i9bOHhMJYbDbxEJfAAAAAAAAAG8AAAABAAAAAmlkAAAADAAAAAh1c2VybmFtZQAA'
+ 'AAAAAAPoAAAAAAAAB9AAAAAAAAAAAAAAAAAAAAE+AAAAIHNzaC1lZDI1NTE5LWNl'
+ 'cnQtdjAxQG9wZW5zc2guY29tAAAAICl5MiUNt8hoAAHT0v00JYOkWe2UW31+Qq5Q'
+ 'HYKWGyVjAAAAIMUJEFAmSV/qtoxSmVOHUgTMKYjqkDy8fTfsfCKV+sN7AAAAAAAA'
+ 'AG8AAAABAAAAAmlkAAAAEgAAAA5kb2Vzbid0IG1hdHRlcgAAAAAAAAPoAAAAAAAA'
+ 'B9AAAAAAAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIMUJEFAmSV/qtoxS'
+ 'mVOHUgTMKYjqkDy8fTfsfCKV+sN7AAAAUwAAAAtzc2gtZWQyNTUxOQAAAEAXbRz3'
+ 'lBmoU4FVge29jn04MfubF6U0CoPG1nbeZSgDN2iz7qtZ75XIk5O/Z/W9nA8jwsiz'
+ 'iSEMItjvR7HEN8MIAAAAUwAAAAtzc2gtZWQyNTUxOQAAAECszhkY8bUbSCjmHEMP'
+ 'LjcOX6OaeBzPIYYYXJzpLn+m+CIwDXRIxyvON5/d/TomgAFNJutfOEsqIzy5OAvl'
+ 'p5IO')
+ self.assertEqual(ssh2_fingerprint_blob(cert_blob, "sha256"),
+ b'ssh-ed25519-cert-v01@openssh.com 255 '
+ b'SHA256:42JaqhHUNa5CoKxGWqtKXF0Awz7b0aPrtgBZ9VLLHfY')
+ self.assertEqual(ssh2_fingerprint_blob(cert_blob, "md5"),
+ b'ssh-ed25519-cert-v01@openssh.com 255 '
+ b'8e:40:00:e0:1f:4a:9c:b3:c8:e9:05:59:04:03:44:b3')
+ self.assertEqual(ssh2_fingerprint_blob(cert_blob, "sha256-cert"),
+ b'ssh-ed25519-cert-v01@openssh.com 255 '
+ b'SHA256:W/+SDEg7S+/dAn4SrodJ2c8bYvt13XXA7YYlQ6E8R5U')
+ self.assertEqual(ssh2_fingerprint_blob(cert_blob, "md5-cert"),
+ b'ssh-ed25519-cert-v01@openssh.com 255 '
+ b'03:cf:aa:8e:aa:c3:a0:97:bb:2e:7e:57:9d:08:b5:be')
+
+
def testAES(self):
# My own test cases, generated by a mostly independent
# reference implementation of AES in Python. ('Mostly'
# independent in that it was written by me.)
- def vector(cipher, key, iv, plaintext, ciphertext):
- for suffix in "hw", "sw":
- c = ssh_cipher_new("{}_{}".format(cipher, suffix))
+ def vector(cipherbase, key, iv, plaintext, ciphertext):
+ for cipher in get_implementations(cipherbase):
+ c = ssh_cipher_new(cipher)
if c is None: return # skip test if HW AES not available
ssh_cipher_setkey(c, key)
ssh_cipher_setiv(c, iv)
@@ -1302,7 +1570,7 @@ class crypt(MyTestBase):
# We also test this at all three AES key lengths, in case the
# core cipher routines are written separately for each one.
- for suffix in "hw", "sw":
+ for suffix in get_aes_impls():
for keylen in [128, 192, 256]:
hexTestValues = ["00000000", "00000001", "ffffffff"]
for ivHexBytes in itertools.product(*([hexTestValues] * 4)):
@@ -1325,7 +1593,7 @@ class crypt(MyTestBase):
for keylen in [128, 192, 256]:
decryptions = []
- for suffix in "hw", "sw":
+ for suffix in get_aes_impls():
c = ssh_cipher_new("aes{:d}_cbc_{}".format(keylen, suffix))
if c is None: continue
ssh_cipher_setkey(c, test_key[:keylen//8])
@@ -1493,23 +1761,11 @@ class crypt(MyTestBase):
("3des_ssh1", 24, 8, False, unhex('d5f1cc25b8fbc62de63590b9b92344adf6dd72753273ff0fb32d4dbc6af858529129f34242f3d557eed3a5c84204eb4f868474294964cf70df5d8f45dfccfc45')),
("des_cbc", 8, 8, True, unhex('051524e77fb40e109d9fffeceacf0f28c940e2f8415ddccc117020bdd2612af5036490b12085d0e46129919b8e499f51cb82a4b341d7a1a1ea3e65201ef248f6')),
("aes256_ctr", 32, 16, False, unhex('b87b35e819f60f0f398a37b05d7bcf0b04ad4ebe570bd08e8bfa8606bafb0db2cfcd82baf2ccceae5de1a3c1ae08a8b8fdd884fdc5092031ea8ce53333e62976')),
- ("aes256_ctr_hw", 32, 16, False, unhex('b87b35e819f60f0f398a37b05d7bcf0b04ad4ebe570bd08e8bfa8606bafb0db2cfcd82baf2ccceae5de1a3c1ae08a8b8fdd884fdc5092031ea8ce53333e62976')),
- ("aes256_ctr_sw", 32, 16, False, unhex('b87b35e819f60f0f398a37b05d7bcf0b04ad4ebe570bd08e8bfa8606bafb0db2cfcd82baf2ccceae5de1a3c1ae08a8b8fdd884fdc5092031ea8ce53333e62976')),
("aes256_cbc", 32, 16, True, unhex('381cbb2fbcc48118d0094540242bd990dd6af5b9a9890edd013d5cad2d904f34b9261c623a452f32ea60e5402919a77165df12862742f1059f8c4a862f0827c5')),
- ("aes256_cbc_hw", 32, 16, True, unhex('381cbb2fbcc48118d0094540242bd990dd6af5b9a9890edd013d5cad2d904f34b9261c623a452f32ea60e5402919a77165df12862742f1059f8c4a862f0827c5')),
- ("aes256_cbc_sw", 32, 16, True, unhex('381cbb2fbcc48118d0094540242bd990dd6af5b9a9890edd013d5cad2d904f34b9261c623a452f32ea60e5402919a77165df12862742f1059f8c4a862f0827c5')),
("aes192_ctr", 24, 16, False, unhex('06bcfa7ccf075d723e12b724695a571a0fad67c56287ea609c410ac12749c51bb96e27fa7e1c7ea3b14792bbbb8856efb0617ebec24a8e4a87340d820cf347b8')),
- ("aes192_ctr_hw", 24, 16, False, unhex('06bcfa7ccf075d723e12b724695a571a0fad67c56287ea609c410ac12749c51bb96e27fa7e1c7ea3b14792bbbb8856efb0617ebec24a8e4a87340d820cf347b8')),
- ("aes192_ctr_sw", 24, 16, False, unhex('06bcfa7ccf075d723e12b724695a571a0fad67c56287ea609c410ac12749c51bb96e27fa7e1c7ea3b14792bbbb8856efb0617ebec24a8e4a87340d820cf347b8')),
("aes192_cbc", 24, 16, True, unhex('ac97f8698170f9c05341214bd7624d5d2efef8311596163dc597d9fe6c868971bd7557389974612cbf49ea4e7cc6cc302d4cc90519478dd88a4f09b530c141f3')),
- ("aes192_cbc_hw", 24, 16, True, unhex('ac97f8698170f9c05341214bd7624d5d2efef8311596163dc597d9fe6c868971bd7557389974612cbf49ea4e7cc6cc302d4cc90519478dd88a4f09b530c141f3')),
- ("aes192_cbc_sw", 24, 16, True, unhex('ac97f8698170f9c05341214bd7624d5d2efef8311596163dc597d9fe6c868971bd7557389974612cbf49ea4e7cc6cc302d4cc90519478dd88a4f09b530c141f3')),
("aes128_ctr", 16, 16, False, unhex('0ad4ddfd2360ec59d77dcb9a981f92109437c68c5e7f02f92017d9f424f89ab7850473ac0e19274125e740f252c84ad1f6ad138b6020a03bdaba2f3a7378ce1e')),
- ("aes128_ctr_hw", 16, 16, False, unhex('0ad4ddfd2360ec59d77dcb9a981f92109437c68c5e7f02f92017d9f424f89ab7850473ac0e19274125e740f252c84ad1f6ad138b6020a03bdaba2f3a7378ce1e')),
- ("aes128_ctr_sw", 16, 16, False, unhex('0ad4ddfd2360ec59d77dcb9a981f92109437c68c5e7f02f92017d9f424f89ab7850473ac0e19274125e740f252c84ad1f6ad138b6020a03bdaba2f3a7378ce1e')),
("aes128_cbc", 16, 16, True, unhex('36de36917fb7955a711c8b0bf149b29120a77524f393ae3490f4ce5b1d5ca2a0d7064ce3c38e267807438d12c0e40cd0d84134647f9f4a5b11804a0cc5070e62')),
- ("aes128_cbc_hw", 16, 16, True, unhex('36de36917fb7955a711c8b0bf149b29120a77524f393ae3490f4ce5b1d5ca2a0d7064ce3c38e267807438d12c0e40cd0d84134647f9f4a5b11804a0cc5070e62')),
- ("aes128_cbc_sw", 16, 16, True, unhex('36de36917fb7955a711c8b0bf149b29120a77524f393ae3490f4ce5b1d5ca2a0d7064ce3c38e267807438d12c0e40cd0d84134647f9f4a5b11804a0cc5070e62')),
("blowfish_ctr", 32, 8, False, unhex('079daf0f859363ccf72e975764d709232ec48adc74f88ccd1f342683f0bfa89ca0e8dbfccc8d4d99005d6b61e9cc4e6eaa2fd2a8163271b94bf08ef212129f01')),
("blowfish_ssh2", 16, 8, True, unhex('e986b7b01f17dfe80ee34cac81fa029b771ec0f859ae21ae3ec3df1674bc4ceb54a184c6c56c17dd2863c3e9c068e76fd9aef5673465995f0d648b0bb848017f')),
("blowfish_ssh1", 32, 8, True, unhex('d44092a9035d895acf564ba0365d19570fbb4f125d5a4fd2a1812ee6c8a1911a51bb181fbf7d1a261253cab71ee19346eb477b3e7ecf1d95dd941e635c1a4fbf')),
@@ -1517,36 +1773,97 @@ class crypt(MyTestBase):
("arcfour128", 16, None, False, unhex('fd4af54c5642cb29629e50a15d22e4944e21ffba77d0543b27590eafffe3886686d1aefae0484afc9e67edc0e67eb176bbb5340af1919ea39adfe866d066dd05')),
]
- for alg, keylen, ivlen, simple_cbc, c in ciphers:
- cipher = ssh_cipher_new(alg)
- if cipher is None:
- continue # hardware-accelerated cipher not available
-
- ssh_cipher_setkey(cipher, k[:keylen])
- if ivlen is not None:
- ssh_cipher_setiv(cipher, iv[:ivlen])
- self.assertEqualBin(ssh_cipher_encrypt(cipher, p), c)
-
- ssh_cipher_setkey(cipher, k[:keylen])
- if ivlen is not None:
- ssh_cipher_setiv(cipher, iv[:ivlen])
- self.assertEqualBin(ssh_cipher_decrypt(cipher, c), p)
-
- if simple_cbc:
- # CBC ciphers (other than the three-layered CBC used
- # by SSH-1 3DES) have more specific semantics for
- # their IV than 'some kind of starting state for the
- # cipher mode': the IV is specifically supposed to
- # represent the previous block of ciphertext. So we
- # can check that, by supplying the IV _as_ a
- # ciphertext block via a call to decrypt(), and seeing
- # if that causes our test ciphertext to decrypt the
- # same way as when we provided the same IV via
- # setiv().
+ for algbase, keylen, ivlen, simple_cbc, c in ciphers:
+ for alg in get_implementations(algbase):
+ cipher = ssh_cipher_new(alg)
+ if cipher is None:
+ continue # hardware-accelerated cipher not available
+
ssh_cipher_setkey(cipher, k[:keylen])
- ssh_cipher_decrypt(cipher, iv[:ivlen])
+ if ivlen is not None:
+ ssh_cipher_setiv(cipher, iv[:ivlen])
+ self.assertEqualBin(ssh_cipher_encrypt(cipher, p), c)
+
+ ssh_cipher_setkey(cipher, k[:keylen])
+ if ivlen is not None:
+ ssh_cipher_setiv(cipher, iv[:ivlen])
self.assertEqualBin(ssh_cipher_decrypt(cipher, c), p)
+ if simple_cbc:
+ # CBC ciphers (other than the three-layered CBC used
+ # by SSH-1 3DES) have more specific semantics for
+ # their IV than 'some kind of starting state for the
+ # cipher mode': the IV is specifically supposed to
+ # represent the previous block of ciphertext. So we
+ # can check that, by supplying the IV _as_ a
+ # ciphertext block via a call to decrypt(), and seeing
+ # if that causes our test ciphertext to decrypt the
+ # same way as when we provided the same IV via
+ # setiv().
+ ssh_cipher_setkey(cipher, k[:keylen])
+ ssh_cipher_decrypt(cipher, iv[:ivlen])
+ self.assertEqualBin(ssh_cipher_decrypt(cipher, c), p)
+
+ def testChaCha20Poly1305(self):
+ # A test case of this cipher taken from a real connection to
+ # OpenSSH.
+ key = unhex('49e67c5ae596ea7f230e266538d0e373'
+ '177cc8fe08ff7b642c22d736ca975655'
+ 'c3fb639010fd297ca03c36b20a182ef4'
+ '0e1272f0c54251c175546ee00b150805')
+ len_p = unhex('00000128')
+ len_c = unhex('3ff3677b')
+ msg_p = unhex('0807000000020000000f736572766572'
+ '2d7369672d616c6773000000db737368'
+ '2d656432353531392c736b2d7373682d'
+ '65643235353139406f70656e7373682e'
+ '636f6d2c7373682d7273612c7273612d'
+ '736861322d3235362c7273612d736861'
+ '322d3531322c7373682d6473732c6563'
+ '6473612d736861322d6e697374703235'
+ '362c65636473612d736861322d6e6973'
+ '74703338342c65636473612d73686132'
+ '2d6e697374703532312c736b2d656364'
+ '73612d736861322d6e69737470323536'
+ '406f70656e7373682e636f6d2c776562'
+ '617574686e2d736b2d65636473612d73'
+ '6861322d6e69737470323536406f7065'
+ '6e7373682e636f6d0000001f7075626c'
+ '69636b65792d686f7374626f756e6440'
+ '6f70656e7373682e636f6d0000000130'
+ 'c34aaefcafae6fc2')
+ msg_c = unhex('bf587eabf385b1281fa9c755d8515dfd'
+ 'c40cb5e993b346e722dce48b1741b4e5'
+ 'ce9ae075f6df0a1d2f72f94f73570125'
+ '7011630bbb0c7febd767184c0d5aa810'
+ '47cbce82972129a234b8ac5fc5f2b5be'
+ '9264baca6d13ff3c9813a61e1f23468f'
+ '31964b60fc3f0888a227f02c737b2d27'
+ 'b7ae3cd60ede17533863a5bb6bb2d60a'
+ 'c998ccd27e8ba56259f676ed04749fad'
+ '4114678fb871add3a40625110637947c'
+ 'e91459811622fd3d1fa7eb7efad4b1e8'
+ '97f3e860473935d3d8df0679a8b0df85'
+ 'aa4124f2d9ac7207abd10719f465c9ed'
+ '859d2b03bde55315b9024f660ba8d63a'
+ '64e0beb81e532201df830a52cf221484'
+ '18d0c4c7da242346161d7320ac534cb5'
+ 'c6b6fec905ee5f424becb9f97c3afbc5'
+ '5ef4ba369e61bce847158f0dc5bd7227'
+ '3b8693642db36f87')
+ mac = unhex('09757178642dfc9f2c38ac5999e0fcfd')
+ seqno = 3
+ c = ssh_cipher_new('chacha20_poly1305')
+ m = ssh2_mac_new('poly1305', c)
+ c.setkey(key)
+ self.assertEqualBin(c.encrypt_length(len_p, seqno), len_c)
+ self.assertEqualBin(c.encrypt(msg_p), msg_c)
+ m.start()
+ m.update(ssh_uint32(seqno) + len_c + msg_c)
+ self.assertEqualBin(m.genresult(), mac)
+ self.assertEqualBin(c.decrypt_length(len_c, seqno), len_p)
+ self.assertEqualBin(c.decrypt(msg_c), msg_p)
+
def testRSAKex(self):
# Round-trip test of the RSA key exchange functions, plus a
# hardcoded plain/ciphertext pair to guard against the
@@ -1646,13 +1963,13 @@ class crypt(MyTestBase):
]
with random_prng("doesn't matter"):
- ecdh25519 = ssh_ecdhkex_newkey('curve25519')
- ecdh448 = ssh_ecdhkex_newkey('curve448')
+ ecdh25519 = ecdh_key_new('curve25519', False)
+ ecdh448 = ecdh_key_new('curve448', False)
for pub in bad_keys_25519:
- key = ssh_ecdhkex_getkey(ecdh25519, unhex(pub))
+ key = ecdh_key_getkey(ecdh25519, unhex(pub))
self.assertEqual(key, None)
for pub in bad_keys_448:
- key = ssh_ecdhkex_getkey(ecdh448, unhex(pub))
+ key = ecdh_key_getkey(ecdh448, unhex(pub))
self.assertEqual(key, None)
def testPRNG(self):
@@ -1980,6 +2297,20 @@ culpa qui officia deserunt mollit anim id est laborum.
"aeae2a21201eef5e347de22c922192e8f46274b0c9d33e965155a91e7686"
"9d530e"))
+ def testOpenSSHBcrypt(self):
+ # Test case created by making an OpenSSH private key file
+ # using their own ssh-keygen, then decrypting it successfully
+ # using PuTTYgen and printing the inputs and outputs to
+ # openssh_bcrypt in the process. So this output key is known
+ # to agree with OpenSSH's own answer.
+
+ self.assertEqualBin(
+ openssh_bcrypt('test passphrase',
+ unhex('d0c3b40ace4afeaf8c0f81202ae36718'),
+ 16, 48),
+ unhex('d78ba86e7273de0e007ab0ba256646823d5c902bc44293ae'
+ '78547e9a7f629be928cc78ff78a75a4feb7aa6f125079c7d'))
+
def testRSAVerify(self):
def blobs(n, e, d, p, q, iqmp):
pubblob = ssh_string(b"ssh-rsa") + ssh2_mpint(e) + ssh2_mpint(n)
@@ -2059,8 +2390,8 @@ culpa qui officia deserunt mollit anim id est laborum.
for alg, pubb64, privb64, bits, cachestr, siglist in test_keys:
# Decode the blobs in the above test data.
- pubblob = base64decode(pubb64.encode('ASCII'))
- privblob = base64decode(privb64.encode('ASCII'))
+ pubblob = b64(pubb64)
+ privblob = b64(privb64)
# Check the method that examines a public blob directly
# and returns an integer showing the key size.
@@ -2094,7 +2425,7 @@ culpa qui officia deserunt mollit anim id est laborum.
# value.
for flags, sigb64 in siglist:
# Decode the signature blob from the test data.
- sigblob = base64decode(sigb64.encode('ASCII'))
+ sigblob = b64(sigb64)
# Sign our test message, and check it produces exactly
# the expected signature blob.
@@ -2182,7 +2513,7 @@ Private-MAC: 6f5e588e475e55434106ec2c3569695b03f423228b44993a9e97d52ffe7be5a8
(True, algorithm, public_blob, comment, None))
self.assertEqual(ppk_loadpub_s("not a key file"),
(False, None, b'', None,
- b'not a PuTTY SSH-2 private key'))
+ b'not a public key or a PuTTY SSH-2 private key'))
k1, c, e = ppk_load_s(input_clear_key, None)
self.assertEqual((c, e), (comment, None))
@@ -2244,7 +2575,7 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b
(True, algorithm, public_blob, comment, None))
self.assertEqual(ppk_loadpub_s("not a key file"),
(False, None, b'', None,
- b'not a PuTTY SSH-2 private key'))
+ b'not a public key or a PuTTY SSH-2 private key'))
k1, c, e = ppk_load_s(v2_clear_key, None)
self.assertEqual((c, e), (comment, None))
@@ -2321,10 +2652,474 @@ Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b
self.assertEqual(rsa1_save_sb(k2, comment, pp),
input_encrypted_key)
+ def testOpenSSHCert(self):
+ def per_base_keytype_tests(alg, run_validation_tests=False,
+ run_ca_rsa_tests=False, ca_signflags=None):
+ cert_pub = sign_cert_via_testcrypt(
+ make_signature_preimage(
+ key_to_certify = base_key.public_blob(),
+ ca_key = ca_key,
+ certtype = CertType.user,
+ keyid = b'id',
+ serial = 111,
+ principals = [b'username'],
+ valid_after = 1000,
+ valid_before = 2000), ca_key, signflags=ca_signflags)
+
+ certified_key = ssh_key_new_priv(alg + '-cert', cert_pub,
+ base_key.private_blob())
+
+ # Check the simple certificate methods
+ self.assertEqual(certified_key.cert_id_string(), b'id')
+ self.assertEqual(certified_key.ca_public_blob(),
+ ca_key.public_blob())
+ recovered_base_key = certified_key.base_key()
+ self.assertEqual(recovered_base_key.public_blob(),
+ base_key.public_blob())
+ self.assertEqual(recovered_base_key.private_blob(),
+ base_key.private_blob())
+
+ # Check that an ordinary key also supports base_key()
+ redundant_base_key = base_key.base_key()
+ self.assertEqual(redundant_base_key.public_blob(),
+ base_key.public_blob())
+ self.assertEqual(redundant_base_key.private_blob(),
+ base_key.private_blob())
+
+ # Test signing and verifying using the certified key type
+ test_string = b'hello, world'
+ base_sig = base_key.sign(test_string, 0)
+ certified_sig = certified_key.sign(test_string, 0)
+ self.assertEqual(base_sig, certified_sig)
+ self.assertEqual(certified_key.verify(base_sig, test_string), True)
+
+ # Check a successful certificate verification
+ result, err = certified_key.check_cert(
+ False, b'username', 1000, '')
+ self.assertEqual(result, True)
+
+ # If the key type is RSA, check that the validator rejects
+ # wrong kinds of CA signature
+ if run_ca_rsa_tests:
+ forbid_all = ",".join(["permit_rsa_sha1=false",
+ "permit_rsa_sha256=false,"
+ "permit_rsa_sha512=false"])
+ result, err = certified_key.check_cert(
+ False, b'username', 1000, forbid_all)
+ self.assertEqual(result, False)
+
+ algname = ("rsa-sha2-512" if ca_signflags == 4 else
+ "rsa-sha2-256" if ca_signflags == 2 else
+ "ssh-rsa")
+ self.assertEqual(err, (
+ "Certificate signature uses '{}' signature type "
+ "(forbidden by user configuration)".format(algname)
+ .encode("ASCII")))
+
+ permitflag = ("permit_rsa_sha512" if ca_signflags == 4 else
+ "permit_rsa_sha256" if ca_signflags == 2 else
+ "permit_rsa_sha1")
+ result, err = certified_key.check_cert(
+ False, b'username', 1000, "{},{}=true".format(
+ forbid_all, permitflag))
+ self.assertEqual(result, True)
+
+ # That's the end of the tests we need to repeat for all
+ # the key types. Now we move on to detailed tests of the
+ # validation, which are independent of key type, so we
+ # only need to test this part once.
+ if not run_validation_tests:
+ return
+
+ # Check cert verification at the other end of the valid
+ # time range
+ result, err = certified_key.check_cert(
+ False, b'username', 1999, '')
+ self.assertEqual(result, True)
+
+ # Oops, wrong certificate type
+ result, err = certified_key.check_cert(
+ True, b'username', 1000, '')
+ self.assertEqual(result, False)
+ self.assertEqual(err, b'Certificate type is user; expected host')
+
+ # Oops, wrong username
+ result, err = certified_key.check_cert(
+ False, b'someoneelse', 1000, '')
+ self.assertEqual(result, False)
+ self.assertEqual(err, b'Certificate\'s username list ["username"] '
+ b'does not contain expected username "someoneelse"')
+
+ # Oops, time is wrong. (But we can't check the full error
+ # message including the translated start/end times, because
+ # those vary with LC_TIME.)
+ result, err = certified_key.check_cert(
+ False, b'someoneelse', 999, '')
+ self.assertEqual(result, False)
+ self.assertEqual(err[:30], b'Certificate is not valid until')
+ result, err = certified_key.check_cert(
+ False, b'someoneelse', 2000, '')
+ self.assertEqual(result, False)
+ self.assertEqual(err[:22], b'Certificate expired at')
+
+ # Modify the certificate so that the signature doesn't validate
+ username_position = cert_pub.index(b'username')
+ bytelist = list(cert_pub)
+ bytelist[username_position] ^= 1
+ miscertified_key = ssh_key_new_priv(alg + '-cert', bytes(bytelist),
+ base_key.private_blob())
+ result, err = miscertified_key.check_cert(
+ False, b'username', 1000, '')
+ self.assertEqual(result, False)
+ self.assertEqual(err, b"Certificate's signature is invalid")
+
+ # Make a certificate containing a critical option, to test we
+ # reject it
+ cert_pub = sign_cert_via_testcrypt(
+ make_signature_preimage(
+ key_to_certify = base_key.public_blob(),
+ ca_key = ca_key,
+ certtype = CertType.user,
+ keyid = b'id',
+ serial = 112,
+ principals = [b'username'],
+ critical_options = {b'unknown-option': b'yikes!'}), ca_key)
+ certified_key = ssh_key_new_priv(alg + '-cert', cert_pub,
+ base_key.private_blob())
+ result, err = certified_key.check_cert(
+ False, b'username', 1000, '')
+ self.assertEqual(result, False)
+ self.assertEqual(err, b'Certificate specifies an unsupported '
+ b'critical option "unknown-option"')
+
+ # Make a certificate containing a non-critical extension, to
+ # test we _accept_ it
+ cert_pub = sign_cert_via_testcrypt(
+ make_signature_preimage(
+ key_to_certify = base_key.public_blob(),
+ ca_key = ca_key,
+ certtype = CertType.user,
+ keyid = b'id',
+ serial = 113,
+ principals = [b'username'],
+ extensions = {b'unknown-ext': b'whatever, dude'}), ca_key)
+ certified_key = ssh_key_new_priv(alg + '-cert', cert_pub,
+ base_key.private_blob())
+ result, err = certified_key.check_cert(
+ False, b'username', 1000, '')
+ self.assertEqual(result, True)
+
+ # Make a certificate on the CA key, and re-sign the main
+ # key using that, to ensure that two-level certs are rejected
+ ca_self_certificate = sign_cert_via_testcrypt(
+ make_signature_preimage(
+ key_to_certify = ca_key.public_blob(),
+ ca_key = ca_key,
+ certtype = CertType.user,
+ keyid = b'id',
+ serial = 111,
+ principals = [b"doesn't matter"],
+ valid_after = 1000,
+ valid_before = 2000), ca_key, signflags=ca_signflags)
+ import base64
+ self_signed_ca_key = ssh_key_new_pub(
+ alg + '-cert', ca_self_certificate)
+ cert_pub = sign_cert_via_testcrypt(
+ make_signature_preimage(
+ key_to_certify = base_key.public_blob(),
+ ca_key = self_signed_ca_key,
+ certtype = CertType.user,
+ keyid = b'id',
+ serial = 111,
+ principals = [b'username'],
+ valid_after = 1000,
+ valid_before = 2000), ca_key, signflags=ca_signflags)
+ certified_key = ssh_key_new_priv(alg + '-cert', cert_pub,
+ base_key.private_blob())
+ result, err = certified_key.check_cert(
+ False, b'username', 1500, '')
+ self.assertEqual(result, False)
+ self.assertEqual(
+ err, b'Certificate is signed with a certified key '
+ b'(forbidden by OpenSSH certificate specification)')
+
+ # Now try a host certificate. We don't need to do _all_ the
+ # checks over again, but at least make sure that setting
+ # CertType.host leads to the certificate validating with
+ # host=True and not with host=False.
+ #
+ # Also, in this test, give two hostnames.
+ cert_pub = sign_cert_via_testcrypt(
+ make_signature_preimage(
+ key_to_certify = base_key.public_blob(),
+ ca_key = ca_key,
+ certtype = CertType.host,
+ keyid = b'id',
+ serial = 114,
+ principals = [b'hostname.example.com',
+ b'hostname2.example.com'],
+ valid_after = 1000,
+ valid_before = 2000), ca_key)
+
+ certified_key = ssh_key_new_priv(alg + '-cert', cert_pub,
+ base_key.private_blob())
+
+ # Check certificate type
+ result, err = certified_key.check_cert(
+ True, b'hostname.example.com', 1000, '')
+ self.assertEqual(result, True)
+ result, err = certified_key.check_cert(
+ False, b'hostname.example.com', 1000, '')
+ self.assertEqual(result, False)
+ self.assertEqual(err, b'Certificate type is host; expected user')
+
+ # Check the second hostname and an unknown one
+ result, err = certified_key.check_cert(
+ True, b'hostname2.example.com', 1000, '')
+ self.assertEqual(result, True)
+ result, err = certified_key.check_cert(
+ True, b'hostname3.example.com', 1000, '')
+ self.assertEqual(result, False)
+ self.assertEqual(err, b'Certificate\'s hostname list ['
+ b'"hostname.example.com", "hostname2.example.com"] '
+ b'does not contain expected hostname '
+ b'"hostname3.example.com"')
+
+ # And just for luck, try a totally unknown certificate type,
+ # making sure that it's rejected in both modes and gives the
+ # right error message
+ cert_pub = sign_cert_via_testcrypt(
+ make_signature_preimage(
+ key_to_certify = base_key.public_blob(),
+ ca_key = ca_key,
+ certtype = 12345,
+ keyid = b'id',
+ serial = 114,
+ principals = [b'username', b'hostname.example.com'],
+ valid_after = 1000,
+ valid_before = 2000), ca_key)
+ certified_key = ssh_key_new_priv(alg + '-cert', cert_pub,
+ base_key.private_blob())
+ result, err = certified_key.check_cert(
+ False, b'username', 1000, '')
+ self.assertEqual(result, False)
+ self.assertEqual(err, b'Certificate type is unknown value 12345; '
+ b'expected user')
+ result, err = certified_key.check_cert(
+ True, b'hostname.example.com', 1000, '')
+ self.assertEqual(result, False)
+ self.assertEqual(err, b'Certificate type is unknown value 12345; '
+ b'expected host')
+
+ ca_key = ssh_key_new_priv('ed25519', b64('AAAAC3NzaC1lZDI1NTE5AAAAIMUJEFAmSV/qtoxSmVOHUgTMKYjqkDy8fTfsfCKV+sN7'), b64('AAAAIK4STyaf63xHidqhvUop9/OKiYqSh/YEWLCp1lL5Vs4u'))
+
+ base_key = ssh_key_new_priv('ed25519', b64('AAAAC3NzaC1lZDI1NTE5AAAAIMt0/CMBL+64GQ/r/JyGxo6oHs86i9bOHhMJYbDbxEJf'), b64('AAAAIB38jy02ZWYb4EXrJG9RIljEhqidrG5DdhZvMvoeOTZs'))
+ per_base_keytype_tests('ed25519', run_validation_tests=True)
+
+ base_key = ssh_key_new_priv('p256', b64('AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGc8VXplXScdWckJgAw6Hag5PP7g0JEVdLY5lP2ujvVxU5GwwquYLbX3yyj1zY5h2n9GoXrnRxzR5+5g8wsNjTA='), b64('AAAAICVRicPD5MyOHfKdnC/8IP84t+nQ4bqmMUyX7NHyCKjS'))
+ per_base_keytype_tests('p256')
+
+ base_key = ssh_key_new_priv('p384', b64('AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBLITujAbKwHDEzVDFqWtA+CleAhN/Y+53mHbEoTpU0aof9L+2lHeUshXdxHDLxY69wO5+WfqWJCwSY58PuXIZzIisQkvIKq6LhpzK6C5JpWJ8Kbv7su+qZPf5sYoxx0xZg=='), b64('AAAAMHyQTQYcIA/bR4ZvWS86ohb5Lu0MhzjD8bUb3q8jnROOe3BrE9I8oJcx+l1lddPouA=='))
+ per_base_keytype_tests('p384')
+
+ base_key = ssh_key_new_priv('p521', b64('AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADButwMRGdLkFhWcSDsLhRhgyrLQq1/A0M8x4GgEmesh4iydo4tGKZR14GhHvx150IWTE1Tre4wyH+1FsTfAlpUBgBDQjsZE0D3u3SLp4qjjhzyrJGhEUDd9J6lsr6JrXbTefz5+LkM9m5l86y9PoAgT+F25OiTYlfvR5qx/pzIPoCnpA=='), b64('AAAAQgFV8xBXC7XZNxdW1oWg6yCZjys2AX4beZVehE9A2R/4m11dHnfqoE1FzbRxj9xqwKvHZRhMOJ//DYuhtcG6+6yHsA=='))
+ per_base_keytype_tests('p521')
+
+ base_key = ssh_key_new_priv('dsa', b64('AAAAB3NzaC1kc3MAAABCAXgDrF9Fw/Ty+QcoljAGjGL/Ph5+NBQqUYADm4wxF+aazjQXLuZ0VW9OdYBisgDZlYDj/w7y9NxCBgax2BSkhDNxAAAAFQC/YwnFzcom6cRRHPXtOUDLi2I29QAAAEIAqGOUYpfFPwzhgAmYXwWKdK8ouSUplNE29FOpv6NYjyf7k+tLSWF3b8oZdtw6XP8lr4vcKXC9Ik0YpKYKM7iKfb8AAABCAUDCcojlDLQmLHg8HhFCtT/CpayNh4OfmSrP8XOwJnFD/eBaSGuPB5EvGd+m6gr+Pc0RSAlWP1aIzUbYkQ33Yk58'), b64('AAAAFQChVuOTNrCwLSJygxlRQhDwHozwSg=='))
+ per_base_keytype_tests('dsa')
+
+ base_key = ssh_key_new_priv('rsa', b64('AAAAB3NzaC1yc2EAAAADAQABAAAAgQDXLnqGPQLL9byoHFQWPiF5Uzcd0KedMRRJmuwyCAWprlh8EN43mL2F7q27Uv54m/ztqW4DsVtiCN6cDYvB9QPNYFR5npwsEAJ06Ro4s9ZpFsZVOvitqeoYIs+jkS8vq5V8X4hwLlJ8vXYPD6rHJhOz6HFpImHmVu40Mu5lq+MCQQ=='), b64('AAAAgH5dBwrJzVilKHK4oBCnz9SFr7pMjAHdjoJi/g2rdFfe0IubBEQ16CY8sb1t0Y5WXEPc2YRFpNp/RurxcX8nOWFPzgNJXEtkKpKO9Juqu5hL4xcf8QKC2aJFk3EXrn/M6dXEdjqN4UhsT6iFTsHKU4b8T6VTtgKzwkOdic/YotaBAAAAQQD6liDTlzTKzLhbypI6l+y2BGA3Kkzz71Y2o7XH/6bZ6HJOFgHuJeL3eNQptzd8Q+ctfvR0fa2PItYydDOlVUeZAAAAQQDb1IsO1/fkflDZhPQT2XOxtrjgQhotKjr6CSmJtDNmo1mOCN+mOgxtDfJ0PNEEM1P9CO2Ia3njtkxt4Ep2EpjpAAAAQQClRxLEHsRK9nMPZ4HW45iyw5dHhYar9pYUql2VnixWQxrHy13ZIaWxi6xwWjuPglrdBgEQfYwH9KGmlFmZXT/Z'))
+ per_base_keytype_tests('rsa')
+
+ # Now switch to an RSA certifying key, and test different RSA
+ # signature subtypes being used to sign the certificate
+ ca_key = ssh_key_new_priv('rsa', b64('AAAAB3NzaC1yc2EAAAADAQABAAAAgQCKHiavhtnAZQLUPtYlzlQmVTHSKq2ChCKZP0cLNtN2YSS0/f4D1hi8W04Qh/JuSXZAdUThTAVjxDmxpiOMNwa/2WDXMuqip47dzZSQxtSdvTfeL9TVC/M1NaOzy8bqFx6pzi37zPATETT4PP1Zt/Pd23ZJYhwjxSyTlqj7529v0w=='), b64('AAAAgCwTZyEIlaCyG28EBm7WI0CAW3/IIsrNxATHjrJjcqQKaB5iF5e90PL66DSaTaEoTFZRlgOXsPiffBHXBO0P+lTyZ2jlq2J2zgeofRH3Yong4BT4xDtqBKtxixgC1MAHmrOnRXjAcDUiLxIGgU0YKSv0uAlgARsUwDsk0GEvK+jBAAAAQQDMi7liRBQ4/Z6a4wDL/rVnIJ9x+2h2UPK9J8U7f97x/THIBtfkbf9O7nDP6onValuSr86tMR24DJZsEXaGPwjDAAAAQQCs3J3D3jNVwwk16oySRSjA5x3tKCEITYMluyXX06cvFew8ldgRCYl1sh8RYAfbBKXhnJD77qIxtVNaF1yl/guxAAAAQFTRdKRUF2wLu/K/Rr34trwKrV6aW0GWyHlLuWvF7FUB85aDmtqYI2BSk92mVCKHBNw2T3cJMabN9JOznjtADiM='))
+ per_base_keytype_tests('rsa', run_ca_rsa_tests=True)
+ per_base_keytype_tests('rsa', run_ca_rsa_tests=True, ca_signflags=2)
+ per_base_keytype_tests('rsa', run_ca_rsa_tests=True, ca_signflags=4)
+
+ def testAESGCMBlockBoundaries(self):
+ # For standard AES-GCM test vectors, see the separate tests in
+ # standard_test_vectors.testAESGCM. This function will test
+ # the local interface, including the skip length and the
+ # machinery for incremental MAC update.
+
+ def aesgcm(key, iv, aes_impl, gcm_impl):
+ c = ssh_cipher_new('aes{:d}_gcm_{}'.format(8*len(key), aes_impl))
+ m = ssh2_mac_new('aesgcm_{}'.format(gcm_impl), c)
+ if m is None: return # skip test if HW GCM not available
+ c.setkey(key)
+ c.setiv(iv + b'\0'*4)
+ m.setkey(b'')
+ return c, m
+
+ def test_one(aes_impl, gcm_impl):
+ # An actual test from a session with OpenSSH, which
+ # demonstrates that the implementation in practice matches up
+ # to what the test vectors say. This is its SSH2_MSG_EXT_INFO
+ # packet.
+ key = unhex('dbf98b2f56c83fb2f9476aa876511225')
+ iv = unhex('9af15ecccf2bacaaa9625a6a')
+ plain = unhex('1007000000020000000f736572766572'
+ '2d7369672d616c6773000000db737368'
+ '2d656432353531392c736b2d7373682d'
+ '65643235353139406f70656e7373682e'
+ '636f6d2c7373682d7273612c7273612d'
+ '736861322d3235362c7273612d736861'
+ '322d3531322c7373682d6473732c6563'
+ '6473612d736861322d6e697374703235'
+ '362c65636473612d736861322d6e6973'
+ '74703338342c65636473612d73686132'
+ '2d6e697374703532312c736b2d656364'
+ '73612d736861322d6e69737470323536'
+ '406f70656e7373682e636f6d2c776562'
+ '617574686e2d736b2d65636473612d73'
+ '6861322d6e69737470323536406f7065'
+ '6e7373682e636f6d0000001f7075626c'
+ '69636b65792d686f7374626f756e6440'
+ '6f70656e7373682e636f6d0000000130'
+ '5935130804ad4b19ed2789210290c438')
+ aad = unhex('00000130')
+ cipher = unhex('c4b88f35c1ef8aa6225033c3f185d648'
+ '3c485d84930d5846f7851daacbff49d5'
+ '8cf72169fca7ab3c170376df65dd69de'
+ 'c40a94c6b8e3da6d61161ab19be27466'
+ '02e0dfa3330faae291ef4173a20e87a4'
+ 'd40728c645baa72916c1958531ef7b54'
+ '27228513e53005e6d17b9bb384b8d8c1'
+ '92b8a10b731459eed5a0fb120c283412'
+ 'e34445981df1257f1c35a06196731fed'
+ '1b3115f419e754de0b634bf68768cb02'
+ '29e70bb2259cedb5101ff6a4ac19aaad'
+ '46f1c30697361b45d6c152c3069cee6b'
+ 'd46e9785d65ea6bf7fca41f0ac3c8e93'
+ 'ce940b0059c39d51e49c17f60d48d633'
+ '5bae4402faab61d8d65221b24b400e65'
+ '89f941ff48310231a42641851ea00832'
+ '2c2d188f4cc6a4ec6002161c407d0a92'
+ 'f1697bb319fbec1ca63fa8e7ac171c85'
+ '5b60142bfcf4e5b0a9ada3451799866e')
+
+ c, m = aesgcm(key, iv, aes_impl, gcm_impl)
+ len_dec = c.decrypt_length(aad, 123)
+ self.assertEqual(len_dec, aad) # length not actually encrypted
+ m.start()
+ # We expect 4 bytes skipped (the sequence number that
+ # ChaCha20-Poly1305 wants at the start of its MAC), and 4
+ # bytes AAD. These were initialised by the call to
+ # encrypt_length.
+ m.update(b'fake' + aad + cipher)
+ self.assertEqualBin(m.genresult(),
+ unhex('4a5a6d57d54888b4e58c57a96e00b73a'))
+ self.assertEqualBin(c.decrypt(cipher), plain)
+
+ c, m = aesgcm(key, iv, aes_impl, gcm_impl)
+ len_enc = c.encrypt_length(aad, 123)
+ self.assertEqual(len_enc, aad) # length not actually encrypted
+ self.assertEqualBin(c.encrypt(plain), cipher)
+
+ # Test incremental update.
+ def testIncremental(skiplen, aad, plain):
+ key, iv = b'SomeRandomKeyVal', b'SomeRandomIV'
+ mac_input = b'x' * skiplen + aad + plain
+
+ c, m = aesgcm(key, iv, aes_impl, gcm_impl)
+ aesgcm_set_prefix_lengths(m, skiplen, len(aad))
+
+ m.start()
+ m.update(mac_input)
+ reference_mac = m.genresult()
+
+ # Break the input just once, at each possible byte
+ # position.
+ for i in range(1, len(mac_input)):
+ c.setiv(iv + b'\0'*4)
+ m.setkey(b'')
+ aesgcm_set_prefix_lengths(m, skiplen, len(aad))
+ m.start()
+ m.update(mac_input[:i])
+ m.update(mac_input[i:])
+ self.assertEqualBin(m.genresult(), reference_mac)
+
+ # Feed the entire input in a byte at a time.
+ c.setiv(iv + b'\0'*4)
+ m.setkey(b'')
+ aesgcm_set_prefix_lengths(m, skiplen, len(aad))
+ m.start()
+ for i in range(len(mac_input)):
+ m.update(mac_input[i:i+1])
+ self.assertEqualBin(m.genresult(), reference_mac)
+
+ # Incremental test with more than a full block of each thing
+ testIncremental(23, b'abcdefghijklmnopqrst',
+ b'Lorem ipsum dolor sit amet')
+
+ # Incremental test with exactly a full block of each thing
+ testIncremental(16, b'abcdefghijklmnop',
+ b'Lorem ipsum dolo')
+
+ # Incremental test with less than a full block of each thing
+ testIncremental(7, b'abcdefghij',
+ b'Lorem ipsum')
+
+ for aes_impl in get_aes_impls():
+ for gcm_impl in get_aesgcm_impls():
+ with self.subTest(aes_impl=aes_impl, gcm_impl=gcm_impl):
+ test_one(aes_impl, gcm_impl)
+
+ def testAESGCMIV(self):
+ key = b'SomeRandomKeyVal'
+
+ def test(gcm, cbc, iv_fixed, iv_msg):
+ gcm.setiv(ssh_uint32(iv_fixed) + ssh_uint64(iv_msg) + b'fake')
+
+ cbc.setiv(b'\0' * 16)
+ preimage = cbc.decrypt(gcm.encrypt(b'\0' * 16))
+ self.assertEqualBin(preimage, ssh_uint32(iv_fixed) +
+ ssh_uint64(iv_msg) + ssh_uint32(1))
+ cbc.setiv(b'\0' * 16)
+ preimage = cbc.decrypt(gcm.encrypt(b'\0' * 16))
+ self.assertEqualBin(preimage, ssh_uint32(iv_fixed) +
+ ssh_uint64(iv_msg) + ssh_uint32(2))
+
+ gcm.next_message()
+ iv_msg = (iv_msg + 1) & ((1<<64)-1)
+
+ cbc.setiv(b'\0' * 16)
+ preimage = cbc.decrypt(gcm.encrypt(b'\0' * 16))
+ self.assertEqualBin(preimage, ssh_uint32(iv_fixed) +
+ ssh_uint64(iv_msg) + ssh_uint32(1))
+ cbc.setiv(b'\0' * 16)
+ preimage = cbc.decrypt(gcm.encrypt(b'\0' * 16))
+ self.assertEqualBin(preimage, ssh_uint32(iv_fixed) +
+ ssh_uint64(iv_msg) + ssh_uint32(2))
+
+
+ for impl in get_aes_impls():
+ with self.subTest(aes_impl=impl):
+ gcm = ssh_cipher_new('aes{:d}_gcm_{}'.format(8*len(key), impl))
+ gcm.setkey(key)
+
+ cbc = ssh_cipher_new('aes{:d}_cbc_{}'.format(8*len(key), impl))
+ cbc.setkey(key)
+
+ # A simple test to ensure the low word gets
+ # incremented and that the whole IV looks basically
+ # the way we expect it to
+ test(gcm, cbc, 0x27182818, 0x3141592653589793)
+
+ # Test that carries are propagated into the high word
+ test(gcm, cbc, 0x27182818, 0x00000000FFFFFFFF)
+
+ # Test that carries _aren't_ propagated out of the
+ # high word of the message counter into the fixed word
+ # at the top
+ test(gcm, cbc, 0x27182818, 0xFFFFFFFFFFFFFFFF)
+
class standard_test_vectors(MyTestBase):
def testAES(self):
def vector(cipher, key, plaintext, ciphertext):
- for suffix in "hw", "sw":
+ for suffix in get_aes_impls():
c = ssh_cipher_new("{}_{}".format(cipher, suffix))
if c is None: return # skip test if HW AES not available
ssh_cipher_setkey(c, key)
@@ -2540,7 +3335,7 @@ class standard_test_vectors(MyTestBase):
unhex('56be34521d144c88dbb8c733f0e8b3f6'))
def testSHA1(self):
- for hashname in ['sha1_sw', 'sha1_hw']:
+ for hashname in get_implementations("sha1"):
if ssh_hash_new(hashname) is None:
continue # skip testing of unavailable HW implementation
@@ -2577,7 +3372,7 @@ class standard_test_vectors(MyTestBase):
"cb0082c8f197d260991ba6a460e76e202bad27b3"))
def testSHA256(self):
- for hashname in ['sha256_sw', 'sha256_hw']:
+ for hashname in get_implementations("sha256"):
if ssh_hash_new(hashname) is None:
continue # skip testing of unavailable HW implementation
@@ -2621,36 +3416,36 @@ class standard_test_vectors(MyTestBase):
"8ad3361763f7e9b2d95f4f0da6e1ccbc"))
def testSHA384(self):
- for hashname in ['sha384_sw', 'sha384_hw']:
+ for hashname in get_implementations("sha384"):
if ssh_hash_new(hashname) is None:
continue # skip testing of unavailable HW implementation
# Test cases from RFC 6234 section 8.5, omitting the ones
# whose input is not a multiple of 8 bits
- self.assertEqualBin(hash_str('sha384', "abc"), unhex(
+ self.assertEqualBin(hash_str(hashname, "abc"), unhex(
'cb00753f45a35e8bb5a03d699ac65007272c32ab0eded163'
'1a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7'))
- self.assertEqualBin(hash_str('sha384',
+ self.assertEqualBin(hash_str(hashname,
"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn"
"hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"),
unhex('09330c33f71147e83d192fc782cd1b4753111b173b3b05d2'
'2fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039'))
- self.assertEqualBin(hash_str_iter('sha384',
+ self.assertEqualBin(hash_str_iter(hashname,
("a" * 1000 for _ in range(1000))), unhex(
'9d0e1809716474cb086e834e310a4a1ced149e9c00f24852'
'7972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985'))
- self.assertEqualBin(hash_str('sha384',
+ self.assertEqualBin(hash_str(hashname,
"01234567012345670123456701234567" * 20), unhex(
'2fc64a4f500ddb6828f6a3430b8dd72a368eb7f3a8322a70'
'bc84275b9c0b3ab00d27a5cc3c2d224aa6b61a0d79fb4596'))
- self.assertEqualBin(hash_str('sha384', b"\xB9"), unhex(
+ self.assertEqualBin(hash_str(hashname, b"\xB9"), unhex(
'bc8089a19007c0b14195f4ecc74094fec64f01f90929282c'
'2fb392881578208ad466828b1c6c283d2722cf0ad1ab6938'))
- self.assertEqualBin(hash_str('sha384',
+ self.assertEqualBin(hash_str(hashname,
unhex("a41c497779c0375ff10a7f4e08591739")), unhex(
'c9a68443a005812256b8ec76b00516f0dbb74fab26d66591'
'3f194b6ffb0e91ea9967566b58109cbc675cc208e4c823f7'))
- self.assertEqualBin(hash_str('sha384', unhex(
+ self.assertEqualBin(hash_str(hashname, unhex(
"399669e28f6b9c6dbcbb6912ec10ffcf74790349b7dc8fbe4a8e7b3b5621"
"db0f3e7dc87f823264bbe40d1811c9ea2061e1c84ad10a23fac1727e7202"
"fc3f5042e6bf58cba8a2746e1f64f9b9ea352c711507053cf4e5339d5286"
@@ -2663,42 +3458,42 @@ class standard_test_vectors(MyTestBase):
'38e42b5c4de660f5de8fb2a5b2fbd2a3cbffd20cff1288c0'))
def testSHA512(self):
- for hashname in ['sha512_sw', 'sha512_hw']:
+ for hashname in get_implementations("sha512"):
if ssh_hash_new(hashname) is None:
continue # skip testing of unavailable HW implementation
# Test cases from RFC 6234 section 8.5, omitting the ones
# whose input is not a multiple of 8 bits
- self.assertEqualBin(hash_str('sha512', "abc"), unhex(
+ self.assertEqualBin(hash_str(hashname, "abc"), unhex(
'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55'
'd39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94f'
'a54ca49f'))
- self.assertEqualBin(hash_str('sha512',
+ self.assertEqualBin(hash_str(hashname,
"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn"
"hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"),
unhex('8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299'
'aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26'
'545e96e55b874be909'))
- self.assertEqualBin(hash_str_iter('sha512',
+ self.assertEqualBin(hash_str_iter(hashname,
("a" * 1000 for _ in range(1000))), unhex(
'e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa9'
'73ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217'
'ad8cc09b'))
- self.assertEqualBin(hash_str('sha512',
+ self.assertEqualBin(hash_str(hashname,
"01234567012345670123456701234567" * 20), unhex(
'89d05ba632c699c31231ded4ffc127d5a894dad412c0e024db872d1abd2b'
'a8141a0f85072a9be1e2aa04cf33c765cb510813a39cd5a84c4acaa64d3f'
'3fb7bae9'))
- self.assertEqualBin(hash_str('sha512', b"\xD0"), unhex(
+ self.assertEqualBin(hash_str(hashname, b"\xD0"), unhex(
'9992202938e882e73e20f6b69e68a0a7149090423d93c81bab3f21678d4a'
'ceeee50e4e8cafada4c85a54ea8306826c4ad6e74cece9631bfa8a549b4a'
'b3fbba15'))
- self.assertEqualBin(hash_str('sha512',
+ self.assertEqualBin(hash_str(hashname,
unhex("8d4e3c0e3889191491816e9d98bff0a0")), unhex(
'cb0b67a4b8712cd73c9aabc0b199e9269b20844afb75acbdd1c153c98289'
'24c3ddedaafe669c5fdd0bc66f630f6773988213eb1b16f517ad0de4b2f0'
'c95c90f8'))
- self.assertEqualBin(hash_str('sha512', unhex(
+ self.assertEqualBin(hash_str(hashname, unhex(
"a55f20c411aad132807a502d65824e31a2305432aa3d06d3e282a8d84e0d"
"e1de6974bf495469fc7f338f8054d58c26c49360c3e87af56523acf6d89d"
"03e56ff2f868002bc3e431edc44df2f0223d4bb3b243586e1a7d92493669"
@@ -2967,9 +3762,9 @@ class standard_test_vectors(MyTestBase):
for method, priv, pub, expected in rfc7748s5_2:
with queued_specific_random_data(unhex(priv)):
- ecdh = ssh_ecdhkex_newkey(method)
- key = ssh_ecdhkex_getkey(ecdh, unhex(pub))
- self.assertEqual(int(key), expected)
+ ecdh = ecdh_key_new(method, False)
+ key = ecdh_key_getkey(ecdh, unhex(pub))
+ self.assertEqual(key, ssh2_mpint(expected))
# Bidirectional tests, consisting of the input random number
# strings for both parties, and the expected public values and
@@ -2991,15 +3786,15 @@ class standard_test_vectors(MyTestBase):
for method, apriv, apub, bpriv, bpub, expected in rfc7748s6:
with queued_specific_random_data(unhex(apriv)):
- alice = ssh_ecdhkex_newkey(method)
+ alice = ecdh_key_new(method, False)
with queued_specific_random_data(unhex(bpriv)):
- bob = ssh_ecdhkex_newkey(method)
- self.assertEqualBin(ssh_ecdhkex_getpublic(alice), unhex(apub))
- self.assertEqualBin(ssh_ecdhkex_getpublic(bob), unhex(bpub))
- akey = ssh_ecdhkex_getkey(alice, unhex(bpub))
- bkey = ssh_ecdhkex_getkey(bob, unhex(apub))
- self.assertEqual(int(akey), expected)
- self.assertEqual(int(bkey), expected)
+ bob = ecdh_key_new(method, False)
+ self.assertEqualBin(ecdh_key_getpublic(alice), unhex(apub))
+ self.assertEqualBin(ecdh_key_getpublic(bob), unhex(bpub))
+ akey = ecdh_key_getkey(alice, unhex(bpub))
+ bkey = ecdh_key_getkey(bob, unhex(apub))
+ self.assertEqual(akey, ssh2_mpint(expected))
+ self.assertEqual(bkey, ssh2_mpint(expected))
def testCRC32(self):
self.assertEqual(crc32_rfc1662("123456789"), 0xCBF43926)
@@ -3045,6 +3840,247 @@ class standard_test_vectors(MyTestBase):
self.assertEqual(crc32_rfc1662(vec[:-4]), expected)
self.assertEqual(crc32_rfc1662(vec), 0x2144DF1C)
+ def testHttpDigest(self):
+ # RFC 7616 section 3.9.1
+ params = ["Mufasa", "Circle of Life", "http-auth@example.org",
+ "GET", "/dir/index.html", "auth",
+ "7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v",
+ "FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS", 1,
+ "MD5", False]
+ cnonce = b64('f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ')
+ with queued_specific_random_data(cnonce):
+ self.assertEqual(http_digest_response(*params),
+ b'username="Mufasa", '
+ b'realm="http-auth@example.org", '
+ b'uri="/dir/index.html", '
+ b'algorithm=MD5, '
+ b'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", '
+ b'nc=00000001, '
+ b'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ", '
+ b'qop=auth, '
+ b'response="8ca523f5e9506fed4657c9700eebdbec", '
+ b'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"')
+
+ # And again with all the same details except the hash
+ params[9] = "SHA-256"
+ with queued_specific_random_data(cnonce):
+ self.assertEqual(http_digest_response(*params),
+ b'username="Mufasa", '
+ b'realm="http-auth@example.org", '
+ b'uri="/dir/index.html", '
+ b'algorithm=SHA-256, '
+ b'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", '
+ b'nc=00000001, '
+ b'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ", '
+ b'qop=auth, '
+ b'response="753927fa0e85d155564e2e272a28d1802ca10daf4496794697cf8db5856cb6c1", '
+ b'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"')
+
+ # RFC 7616 section 3.9.2, using SHA-512-256 (demonstrating
+ # that they think it's just a 256-bit truncation of SHA-512,
+ # and not the version defined in FIPS 180-4 which also uses
+ # a different initial hash state), and username hashing.
+ #
+ # We don't actually support SHA-512-256 in the top-level proxy
+ # client code (see the comment in proxy/cproxy.h). However,
+ # this internal http_digest_response function still provides
+ # it, simply so that we can run this test case from the RFC,
+ # because it's the only provided test case for username
+ # hashing, and this confirms that we've got the preimage right
+ # for the username hash.
+ params = ["J\u00E4s\u00F8n Doe".encode("UTF-8"),
+ "Secret, or not?", "api@example.org",
+ "GET", "/doe.json", "auth",
+ "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK",
+ "HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS", 1,
+ "SHA-512-256", True]
+ cnonce = b64('NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v')
+ with queued_specific_random_data(cnonce):
+ self.assertEqual(http_digest_response(*params),
+ b'username="488869477bf257147b804c45308cd62ac4e25eb717b12b298c79e62dcea254ec", '
+ b'realm="api@example.org", '
+ b'uri="/doe.json", '
+ b'algorithm=SHA-512-256, '
+ b'nonce="5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", '
+ b'nc=00000001, '
+ b'cnonce="NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v", '
+ b'qop=auth, '
+ b'response="ae66e67d6b427bd3f120414a82e4acff38e8ecd9101d6c861229025f607a79dd", '
+ b'opaque="HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS", '
+ b'userhash=true')
+
+ def testAESGCM(self):
+ def test(key, iv, plaintext, aad, ciphertext, mac):
+ c = ssh_cipher_new('aes{:d}_gcm'.format(8*len(key)))
+ m = ssh2_mac_new('aesgcm_{}'.format(impl), c)
+ if m is None: return # skip test if HW GCM not available
+ c.setkey(key)
+ c.setiv(iv + b'\0'*4)
+ m.setkey(b'')
+ aesgcm_set_prefix_lengths(m, 0, len(aad))
+
+ # Some test cases have plaintext/ciphertext that is not a
+ # multiple of the cipher block size. Our MAC
+ # implementation supports this, but the cipher
+ # implementation expects block-granular input.
+ padlen = 15 & -len(plaintext)
+ ciphertext_got = c.encrypt(plaintext + b'0' * padlen)[
+ :len(plaintext)]
+
+ m.start()
+ m.update(aad + ciphertext)
+ mac_got = m.genresult()
+
+ self.assertEqualBin(ciphertext_got, ciphertext)
+ self.assertEqualBin(mac_got, mac)
+
+ c.setiv(iv + b'\0'*4)
+
+ for impl in get_aesgcm_impls():
+ # 'The Galois/Counter Mode of Operation', McGrew and
+ # Viega, Appendix B. All the tests except the ones whose
+ # IV is the wrong length, because handling that requires
+ # an extra evaluation of the polynomial hash, which is
+ # never used in an SSH context, so I didn't implement it
+ # just for the sake of test vectors.
+
+ # Test Case 1
+ test(unhex('00000000000000000000000000000000'),
+ unhex('000000000000000000000000'),
+ unhex(''), unhex(''), unhex(''),
+ unhex('58e2fccefa7e3061367f1d57a4e7455a'))
+
+ # Test Case 2
+ test(unhex('00000000000000000000000000000000'),
+ unhex('000000000000000000000000'),
+ unhex('00000000000000000000000000000000'),
+ unhex(''),
+ unhex('0388dace60b6a392f328c2b971b2fe78'),
+ unhex('ab6e47d42cec13bdf53a67b21257bddf'))
+
+ # Test Case 3
+ test(unhex('feffe9928665731c6d6a8f9467308308'),
+ unhex('cafebabefacedbaddecaf888'),
+ unhex('d9313225f88406e5a55909c5aff5269a'
+ '86a7a9531534f7da2e4c303d8a318a72'
+ '1c3c0c95956809532fcf0e2449a6b525'
+ 'b16aedf5aa0de657ba637b391aafd255'),
+ unhex(''),
+ unhex('42831ec2217774244b7221b784d0d49c'
+ 'e3aa212f2c02a4e035c17e2329aca12e'
+ '21d514b25466931c7d8f6a5aac84aa05'
+ '1ba30b396a0aac973d58e091473f5985'),
+ unhex('4d5c2af327cd64a62cf35abd2ba6fab4'))
+
+ # Test Case 4
+ test(unhex('feffe9928665731c6d6a8f9467308308'),
+ unhex('cafebabefacedbaddecaf888'),
+ unhex('d9313225f88406e5a55909c5aff5269a'
+ '86a7a9531534f7da2e4c303d8a318a72'
+ '1c3c0c95956809532fcf0e2449a6b525'
+ 'b16aedf5aa0de657ba637b39'),
+ unhex('feedfacedeadbeeffeedfacedeadbeef'
+ 'abaddad2'),
+ unhex('42831ec2217774244b7221b784d0d49c'
+ 'e3aa212f2c02a4e035c17e2329aca12e'
+ '21d514b25466931c7d8f6a5aac84aa05'
+ '1ba30b396a0aac973d58e091'),
+ unhex('5bc94fbc3221a5db94fae95ae7121a47'))
+
+ # Test Case 7
+ test(unhex('00000000000000000000000000000000'
+ '0000000000000000'),
+ unhex('000000000000000000000000'),
+ unhex(''), unhex(''), unhex(''),
+ unhex('cd33b28ac773f74ba00ed1f312572435'))
+
+ # Test Case 8
+ test(unhex('00000000000000000000000000000000'
+ '0000000000000000'),
+ unhex('000000000000000000000000'),
+ unhex('00000000000000000000000000000000'),
+ unhex(''),
+ unhex('98e7247c07f0fe411c267e4384b0f600'),
+ unhex('2ff58d80033927ab8ef4d4587514f0fb'))
+
+ # Test Case 9
+ test(unhex('feffe9928665731c6d6a8f9467308308'
+ 'feffe9928665731c'),
+ unhex('cafebabefacedbaddecaf888'),
+ unhex('d9313225f88406e5a55909c5aff5269a'
+ '86a7a9531534f7da2e4c303d8a318a72'
+ '1c3c0c95956809532fcf0e2449a6b525'
+ 'b16aedf5aa0de657ba637b391aafd255'),
+ unhex(''),
+ unhex('3980ca0b3c00e841eb06fac4872a2757'
+ '859e1ceaa6efd984628593b40ca1e19c'
+ '7d773d00c144c525ac619d18c84a3f47'
+ '18e2448b2fe324d9ccda2710acade256'),
+ unhex('9924a7c8587336bfb118024db8674a14'))
+
+ # Test Case 10
+ test(unhex('feffe9928665731c6d6a8f9467308308'
+ 'feffe9928665731c'),
+ unhex('cafebabefacedbaddecaf888'),
+ unhex('d9313225f88406e5a55909c5aff5269a'
+ '86a7a9531534f7da2e4c303d8a318a72'
+ '1c3c0c95956809532fcf0e2449a6b525'
+ 'b16aedf5aa0de657ba637b39'),
+ unhex('feedfacedeadbeeffeedfacedeadbeef'
+ 'abaddad2'),
+ unhex('3980ca0b3c00e841eb06fac4872a2757'
+ '859e1ceaa6efd984628593b40ca1e19c'
+ '7d773d00c144c525ac619d18c84a3f47'
+ '18e2448b2fe324d9ccda2710'),
+ unhex('2519498e80f1478f37ba55bd6d27618c'))
+
+ # Test Case 13
+ test(unhex('00000000000000000000000000000000'
+ '00000000000000000000000000000000'),
+ unhex('000000000000000000000000'),
+ unhex(''), unhex(''), unhex(''),
+ unhex('530f8afbc74536b9a963b4f1c4cb738b'))
+
+ # Test Case 14
+ test(unhex('00000000000000000000000000000000'
+ '00000000000000000000000000000000'),
+ unhex('000000000000000000000000'),
+ unhex('00000000000000000000000000000000'),
+ unhex(''),
+ unhex('cea7403d4d606b6e074ec5d3baf39d18'),
+ unhex('d0d1c8a799996bf0265b98b5d48ab919'))
+
+ # Test Case 15
+ test(unhex('feffe9928665731c6d6a8f9467308308'
+ 'feffe9928665731c6d6a8f9467308308'),
+ unhex('cafebabefacedbaddecaf888'),
+ unhex('d9313225f88406e5a55909c5aff5269a'
+ '86a7a9531534f7da2e4c303d8a318a72'
+ '1c3c0c95956809532fcf0e2449a6b525'
+ 'b16aedf5aa0de657ba637b391aafd255'),
+ unhex(''),
+ unhex('522dc1f099567d07f47f37a32a84427d'
+ '643a8cdcbfe5c0c97598a2bd2555d1aa'
+ '8cb08e48590dbb3da7b08b1056828838'
+ 'c5f61e6393ba7a0abcc9f662898015ad'),
+ unhex('b094dac5d93471bdec1a502270e3cc6c'))
+
+ # Test Case 16
+ test(unhex('feffe9928665731c6d6a8f9467308308'
+ 'feffe9928665731c6d6a8f9467308308'),
+ unhex('cafebabefacedbaddecaf888'),
+ unhex('d9313225f88406e5a55909c5aff5269a'
+ '86a7a9531534f7da2e4c303d8a318a72'
+ '1c3c0c95956809532fcf0e2449a6b525'
+ 'b16aedf5aa0de657ba637b39'),
+ unhex('feedfacedeadbeeffeedfacedeadbeef'
+ 'abaddad2'),
+ unhex('522dc1f099567d07f47f37a32a84427d'
+ '643a8cdcbfe5c0c97598a2bd2555d1aa'
+ '8cb08e48590dbb3da7b08b1056828838'
+ 'c5f61e6393ba7a0abcc9f662'),
+ unhex('76fc6ece0f4e1768cddf8853bb2d551b'))
+
if __name__ == "__main__":
# Run the tests, suppressing automatic sys.exit and collecting the
# unittest.TestProgram instance returned by unittest.main instead.
diff --git a/test/fuzzterm.c b/test/fuzzterm.c
new file mode 100644
index 00000000..0d4597b1
--- /dev/null
+++ b/test/fuzzterm.c
@@ -0,0 +1,218 @@
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "putty.h"
+#include "dialog.h"
+#include "terminal.h"
+
+static const TermWinVtable fuzz_termwin_vt;
+
+int main(int argc, char **argv)
+{
+ char blk[512];
+ size_t len;
+ Terminal *term;
+ Conf *conf;
+ struct unicode_data ucsdata;
+ TermWin termwin;
+
+ termwin.vt = &fuzz_termwin_vt;
+
+ conf = conf_new();
+ do_defaults(NULL, conf);
+ init_ucs(&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, blk, len);
+ }
+ term_update(term);
+ return 0;
+}
+
+/* functions required by terminal.c */
+static bool fuzz_setup_draw_ctx(TermWin *tw) { return true; }
+static void fuzz_draw_text(
+ TermWin *tw, int x, int y, wchar_t *text, int len,
+ unsigned long attr, int lattr, truecolour tc)
+{
+ int i;
+
+ printf("TEXT[attr=%08lx,lattr=%02x]@(%d,%d):", attr, lattr, x, y);
+ for (i = 0; i < len; i++) {
+ printf(" %x", (unsigned)text[i]);
+ }
+ printf("\n");
+}
+static void fuzz_draw_cursor(
+ TermWin *tw, int x, int y, wchar_t *text, int len,
+ unsigned long attr, int lattr, truecolour tc)
+{
+ int i;
+
+ printf("CURS[attr=%08lx,lattr=%02x]@(%d,%d):", attr, lattr, x, y);
+ for (i = 0; i < len; i++) {
+ printf(" %x", (unsigned)text[i]);
+ }
+ printf("\n");
+}
+static void fuzz_draw_trust_sigil(TermWin *tw, int x, int y)
+{
+ printf("TRUST@(%d,%d)\n", x, y);
+}
+static int fuzz_char_width(TermWin *tw, int uc) { return 1; }
+static void fuzz_free_draw_ctx(TermWin *tw) {}
+static void fuzz_set_cursor_pos(TermWin *tw, int x, int y) {}
+static void fuzz_set_raw_mouse_mode(TermWin *tw, bool enable) {}
+static void fuzz_set_scrollbar(TermWin *tw, int total, int start, int page) {}
+static void fuzz_bell(TermWin *tw, int mode) {}
+static void fuzz_clip_write(
+ TermWin *tw, int clipboard, wchar_t *text, int *attrs,
+ truecolour *colours, int len, bool must_deselect) {}
+static void fuzz_clip_request_paste(TermWin *tw, int clipboard) {}
+static void fuzz_refresh(TermWin *tw) {}
+static void fuzz_request_resize(TermWin *tw, int w, int h) {}
+static void fuzz_set_title(TermWin *tw, const char *title, int codepage) {}
+static void fuzz_set_icon_title(TermWin *tw, const char *icontitle, int cp) {}
+static void fuzz_set_minimised(TermWin *tw, bool minimised) {}
+static void fuzz_set_maximised(TermWin *tw, bool maximised) {}
+static void fuzz_move(TermWin *tw, int x, int y) {}
+static void fuzz_set_zorder(TermWin *tw, bool top) {}
+static void fuzz_palette_set(TermWin *tw, unsigned start, unsigned ncolours,
+ const rgb *colours) {}
+static void fuzz_palette_get_overrides(TermWin *tw, Terminal *term) {}
+static void fuzz_unthrottle(TermWin *tw, size_t size) {}
+
+static const TermWinVtable fuzz_termwin_vt = {
+ .setup_draw_ctx = fuzz_setup_draw_ctx,
+ .draw_text = fuzz_draw_text,
+ .draw_cursor = fuzz_draw_cursor,
+ .draw_trust_sigil = fuzz_draw_trust_sigil,
+ .char_width = fuzz_char_width,
+ .free_draw_ctx = fuzz_free_draw_ctx,
+ .set_cursor_pos = fuzz_set_cursor_pos,
+ .set_raw_mouse_mode = fuzz_set_raw_mouse_mode,
+ .set_scrollbar = fuzz_set_scrollbar,
+ .bell = fuzz_bell,
+ .clip_write = fuzz_clip_write,
+ .clip_request_paste = fuzz_clip_request_paste,
+ .refresh = fuzz_refresh,
+ .request_resize = fuzz_request_resize,
+ .set_title = fuzz_set_title,
+ .set_icon_title = fuzz_set_icon_title,
+ .set_minimised = fuzz_set_minimised,
+ .set_maximised = fuzz_set_maximised,
+ .move = fuzz_move,
+ .set_zorder = fuzz_set_zorder,
+ .palette_set = fuzz_palette_set,
+ .palette_get_overrides = fuzz_palette_get_overrides,
+ .unthrottle = fuzz_unthrottle,
+};
+
+void ldisc_send(Ldisc *ldisc, const void *buf, int len, bool interactive) {}
+void ldisc_echoedit_update(Ldisc *ldisc) {}
+bool ldisc_has_input_buffered(Ldisc *ldisc) { return false; }
+LdiscInputToken ldisc_get_input_token(Ldisc *ldisc)
+{ unreachable("This fake ldisc never has any buffered input"); }
+void ldisc_enable_prompt_callback(Ldisc *ldisc, prompts_t *p)
+{ unreachable("This fake ldisc should never be used for user/pass prompts"); }
+void modalfatalbox(const char *fmt, ...) { exit(0); }
+void nonfatal(const char *fmt, ...) { }
+
+/* needed by timing.c */
+void timer_change_notify(unsigned long next) { }
+
+/* needed by config.c */
+
+void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int whichbutton) { }
+int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp) { return 0; }
+void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked) { }
+bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp) { return false; }
+void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { }
+char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp)
+{ return dupstr("moo"); }
+void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp) { }
+void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index) { }
+void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text) { }
+void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp,
+ char const *text, int id) { }
+int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index)
+{ return 0; }
+int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp) { return -1; }
+bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index)
+{ return false; }
+void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index) { }
+void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { }
+void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn) { }
+Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) { return NULL; }
+void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fn) { }
+FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp) { return NULL; }
+void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp) { }
+void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp) { }
+void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp) { }
+void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text) { }
+dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp)
+{ return NULL; }
+void dlg_beep(dlgparam *dp) { }
+void dlg_error_msg(dlgparam *dp, const char *msg) { }
+void dlg_end(dlgparam *dp, int value) { }
+void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp,
+ int r, int g, int b) { }
+bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp,
+ int *r, int *g, int *b) { return false; }
+void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp) { }
+bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp) { return false; }
+
+const int ngsslibs = 0;
+const char *const gsslibnames[0] = { };
+const struct keyvalwhere gsslibkeywords[0] = { };
+
+/*
+ * Default settings that are specific to Unix plink.
+ */
+char *platform_default_s(const char *name)
+{
+ if (!strcmp(name, "TermType"))
+ return dupstr(getenv("TERM"));
+ if (!strcmp(name, "SerialLine"))
+ return dupstr("/dev/ttyS0");
+ return NULL;
+}
+
+bool platform_default_b(const char *name, bool def)
+{
+ return def;
+}
+
+int platform_default_i(const char *name, int def)
+{
+ return def;
+}
+
+FontSpec *platform_default_fontspec(const char *name)
+{
+ return fontspec_new("");
+}
+
+Filename *platform_default_filename(const char *name)
+{
+ if (!strcmp(name, "LogFileName"))
+ return filename_from_str("putty.log");
+ else
+ return filename_from_str("");
+}
+
+char *x_get_default(const char *key)
+{
+ return NULL; /* this is a stub */
+}
diff --git a/test/list-accel.py b/test/list-accel.py
new file mode 100755
index 00000000..ac92d376
--- /dev/null
+++ b/test/list-accel.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+
+# Simple client of the testcrypt system that reports the available
+# variants of each of the crypto primitives that have hardware-
+# accelerated implementations.
+#
+# It will report the set of primitives compiled in to testcrypt, and
+# also report whether each one can be instantiated at run time.
+
+from testcrypt import *
+
+def get_implementations(alg):
+ return get_implementations_commasep(alg).decode("ASCII").split(",")
+
+def list_implementations(alg, checkfn):
+ print(f"Implementations of {alg}:")
+ for impl in get_implementations(alg):
+ if impl == alg:
+ continue
+ if checkfn(impl):
+ print(f" {impl:<32s} available")
+ else:
+ print(f" {impl:<32s} compiled in, but unavailable at run time")
+
+def list_cipher_implementations(alg):
+ list_implementations(alg, lambda impl: ssh_cipher_new(impl) is not None)
+
+def list_mac_implementations(alg):
+ list_implementations(alg, lambda impl: ssh2_mac_new(impl, None) is not None)
+
+def list_hash_implementations(alg):
+ list_implementations(alg, lambda impl: ssh_hash_new(impl) is not None)
+
+list_cipher_implementations("aes256_cbc")
+list_mac_implementations("aesgcm")
+list_hash_implementations("sha1")
+list_hash_implementations("sha256")
+list_hash_implementations("sha512")
diff --git a/test/primegen.py b/test/primegen.py
index 6964099d..45b340f4 100644
--- a/test/primegen.py
+++ b/test/primegen.py
@@ -18,6 +18,8 @@ def main():
dest='policy', const='provable_fast')
parser.add_argument("--complex", action='store_const',
dest='policy', const='provable_maurer_complex')
+ parser.add_argument("--probabilistic", action='store_const',
+ dest='policy', const='probabilistic')
parser.add_argument("-q", "--quiet", action='store_true')
parser.add_argument("-b", "--binary", action='store_const',
dest='fmt', const='{:b}')
diff --git a/test/sclog/CMakeLists.txt b/test/sclog/CMakeLists.txt
index 6ac0ddfa..8a8ac570 100644
--- a/test/sclog/CMakeLists.txt
+++ b/test/sclog/CMakeLists.txt
@@ -4,6 +4,8 @@
cmake_minimum_required(VERSION 3.5)
+project(sclog LANGUAGES C)
+
find_package(DynamoRIO)
if (NOT DynamoRIO_FOUND)
message(FATAL_ERROR "DynamoRIO not found")
diff --git a/test/sclog/sclog.c b/test/sclog/sclog.c
index 2d2adbf4..d5304a01 100644
--- a/test/sclog/sclog.c
+++ b/test/sclog/sclog.c
@@ -270,6 +270,38 @@ static void wrap_memset_pre(void *wrapctx, void **user_data)
}
/*
+ * Similarly to the above, wrap some versions of memmove.
+ */
+static void wrap_memmove_pre(void *wrapctx, void **user_data)
+{
+ uint was_already_paused = logging_paused++;
+
+ if (outfile == INVALID_FILE || was_already_paused)
+ return;
+
+ const void *daddr = drwrap_get_arg(wrapctx, 0);
+ const void *saddr = drwrap_get_arg(wrapctx, 1);
+ size_t size = (size_t)drwrap_get_arg(wrapctx, 2);
+
+
+ struct allocation *alloc;
+
+ dr_fprintf(outfile, "memmove %"PRIuMAX" ", (uintmax_t)size);
+ if (!(alloc = find_allocation(daddr))) {
+ dr_fprintf(outfile, "to %"PRIxMAX" ", (uintmax_t)daddr);
+ } else {
+ dr_fprintf(outfile, "to allocations[%"PRIuPTR"] + %"PRIxMAX" ",
+ alloc->index, (uintmax_t)(daddr - alloc->start));
+ }
+ if (!(alloc = find_allocation(saddr))) {
+ dr_fprintf(outfile, "from %"PRIxMAX"\n", (uintmax_t)saddr);
+ } else {
+ dr_fprintf(outfile, "from allocations[%"PRIuPTR"] + %"PRIxMAX"\n",
+ alloc->index, (uintmax_t)(saddr - alloc->start));
+ }
+}
+
+/*
* Common post-wrapper function for memset and free, whose entire
* function is to unpause the logging.
*/
@@ -447,20 +479,20 @@ static dr_emit_flags_t instrument_instr(
*/
opnd_t shiftcount = instr_get_src(instr, 0);
if (!opnd_is_immed(shiftcount)) {
- reg_id_t r0;
- drreg_status_t st;
- st = drreg_reserve_register(drcontext, bb, instr, NULL, &r0);
- DR_ASSERT(st == DRREG_SUCCESS);
- opnd_t op_r0 = opnd_create_reg(r0);
- instr_t *movzx = INSTR_CREATE_movzx(drcontext, op_r0, shiftcount);
- instr_set_translation(movzx, instr_get_app_pc(instr));
- instrlist_preinsert(bb, instr, movzx);
- instr_format_location(instr, &loc);
- dr_insert_clean_call(
- drcontext, bb, instr, (void *)log_var_shift, false,
- 2, op_r0, OPND_CREATE_INTPTR(loc));
- st = drreg_unreserve_register(drcontext, bb, instr, r0);
- DR_ASSERT(st == DRREG_SUCCESS);
+ reg_id_t r0;
+ drreg_status_t st;
+ st = drreg_reserve_register(drcontext, bb, instr, NULL, &r0);
+ DR_ASSERT(st == DRREG_SUCCESS);
+ opnd_t op_r0 = opnd_create_reg(r0);
+ instr_t *movzx = INSTR_CREATE_movzx(drcontext, op_r0, shiftcount);
+ instr_set_translation(movzx, instr_get_app_pc(instr));
+ instrlist_preinsert(bb, instr, movzx);
+ instr_format_location(instr, &loc);
+ dr_insert_clean_call(
+ drcontext, bb, instr, (void *)log_var_shift, false,
+ 2, op_r0, OPND_CREATE_INTPTR(loc));
+ st = drreg_unreserve_register(drcontext, bb, instr, r0);
+ DR_ASSERT(st == DRREG_SUCCESS);
}
break;
}
@@ -554,7 +586,7 @@ static void load_module(
#define TRY_WRAP(fn, pre, post) do \
{ \
static bool done_this_one = false; \
- try_wrap_fn(module, fn, pre, post, &done_this_one); \
+ try_wrap_fn(module, fn, pre, post, &done_this_one); \
} while (0)
if (loaded) {
@@ -565,6 +597,7 @@ static void load_module(
TRY_WRAP("realloc", wrap_realloc_pre, wrap_alloc_post);
TRY_WRAP("free", wrap_free_pre, unpause_post);
TRY_WRAP("memset", wrap_memset_pre, unpause_post);
+ TRY_WRAP("memmove", wrap_memmove_pre, unpause_post);
/*
* More strangely named versions of standard C library
@@ -585,6 +618,8 @@ static void load_module(
TRY_WRAP("__GI___libc_free", wrap_free_pre, unpause_post);
TRY_WRAP("__memset_sse2_unaligned", wrap_memset_pre, unpause_post);
TRY_WRAP("__memset_sse2", wrap_memset_pre, unpause_post);
+ TRY_WRAP("__memmove_avx_unaligned_erms", wrap_memmove_pre,
+ unpause_post);
TRY_WRAP("cfree", wrap_free_pre, unpause_post);
}
}
diff --git a/test/ssh.py b/test/ssh.py
index 90eccb7e..53b15183 100644
--- a/test/ssh.py
+++ b/test/ssh.py
@@ -24,6 +24,9 @@ def ssh_byte(n):
def ssh_uint32(n):
return struct.pack(">L", n)
+def ssh_uint64(n):
+ return struct.pack(">Q", n)
+
def ssh_string(s):
return ssh_uint32(len(s)) + s
@@ -54,6 +57,10 @@ def ssh_decode_uint32(s):
return struct.unpack_from(">L", s, 0)[0], 4
@decoder
+def ssh_decode_uint64(s):
+ return struct.unpack_from(">Q", s, 0)[0], 8
+
+@decoder
def ssh_decode_string(s):
length = ssh_decode_uint32(s)
assert length + 4 <= len(s)
@@ -88,6 +95,7 @@ SSH1_AGENTC_REMOVE_RSA_IDENTITY = 8
SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES = 9
SSH_AGENT_FAILURE = 5
SSH_AGENT_SUCCESS = 6
+SSH_AGENT_EXTENSION_FAILURE = 28
SSH2_AGENTC_REQUEST_IDENTITIES = 11
SSH2_AGENT_IDENTITIES_ANSWER = 12
SSH2_AGENTC_SIGN_REQUEST = 13
diff --git a/test/testcrypt-enum.h b/test/testcrypt-enum.h
new file mode 100644
index 00000000..d81f46c7
--- /dev/null
+++ b/test/testcrypt-enum.h
@@ -0,0 +1,178 @@
+BEGIN_ENUM_TYPE(hashalg)
+ ENUM_VALUE("md5", &ssh_md5)
+ ENUM_VALUE("sha1", &ssh_sha1)
+ ENUM_VALUE("sha1_sw", &ssh_sha1_sw)
+ ENUM_VALUE("sha256", &ssh_sha256)
+ ENUM_VALUE("sha384", &ssh_sha384)
+ ENUM_VALUE("sha512", &ssh_sha512)
+ ENUM_VALUE("sha256_sw", &ssh_sha256_sw)
+ ENUM_VALUE("sha384_sw", &ssh_sha384_sw)
+ ENUM_VALUE("sha512_sw", &ssh_sha512_sw)
+#if HAVE_SHA_NI
+ ENUM_VALUE("sha1_ni", &ssh_sha1_ni)
+ ENUM_VALUE("sha256_ni", &ssh_sha256_ni)
+#endif
+#if HAVE_NEON_CRYPTO
+ ENUM_VALUE("sha1_neon", &ssh_sha1_neon)
+ ENUM_VALUE("sha256_neon", &ssh_sha256_neon)
+#endif
+#if HAVE_NEON_SHA512
+ ENUM_VALUE("sha384_neon", &ssh_sha384_neon)
+ ENUM_VALUE("sha512_neon", &ssh_sha512_neon)
+#endif
+ ENUM_VALUE("sha3_224", &ssh_sha3_224)
+ ENUM_VALUE("sha3_256", &ssh_sha3_256)
+ ENUM_VALUE("sha3_384", &ssh_sha3_384)
+ ENUM_VALUE("sha3_512", &ssh_sha3_512)
+ ENUM_VALUE("shake256_114bytes", &ssh_shake256_114bytes)
+ ENUM_VALUE("blake2b", &ssh_blake2b)
+END_ENUM_TYPE(hashalg)
+
+BEGIN_ENUM_TYPE(macalg)
+ ENUM_VALUE("hmac_md5", &ssh_hmac_md5)
+ ENUM_VALUE("hmac_sha1", &ssh_hmac_sha1)
+ ENUM_VALUE("hmac_sha1_buggy", &ssh_hmac_sha1_buggy)
+ ENUM_VALUE("hmac_sha1_96", &ssh_hmac_sha1_96)
+ ENUM_VALUE("hmac_sha1_96_buggy", &ssh_hmac_sha1_96_buggy)
+ ENUM_VALUE("hmac_sha256", &ssh_hmac_sha256)
+ ENUM_VALUE("poly1305", &ssh2_poly1305)
+ ENUM_VALUE("aesgcm", &ssh2_aesgcm_mac)
+ ENUM_VALUE("aesgcm", &ssh2_aesgcm_mac)
+ ENUM_VALUE("aesgcm_sw", &ssh2_aesgcm_mac_sw)
+ ENUM_VALUE("aesgcm_ref_poly", &ssh2_aesgcm_mac_ref_poly)
+#if HAVE_CLMUL
+ ENUM_VALUE("aesgcm_clmul", &ssh2_aesgcm_mac_clmul)
+#endif
+#if HAVE_NEON_PMULL
+ ENUM_VALUE("aesgcm_neon", &ssh2_aesgcm_mac_neon)
+#endif
+END_ENUM_TYPE(macalg)
+
+BEGIN_ENUM_TYPE(keyalg)
+ ENUM_VALUE("dsa", &ssh_dsa)
+ ENUM_VALUE("rsa", &ssh_rsa)
+ ENUM_VALUE("ed25519", &ssh_ecdsa_ed25519)
+ ENUM_VALUE("ed448", &ssh_ecdsa_ed448)
+ ENUM_VALUE("p256", &ssh_ecdsa_nistp256)
+ ENUM_VALUE("p384", &ssh_ecdsa_nistp384)
+ ENUM_VALUE("p521", &ssh_ecdsa_nistp521)
+ ENUM_VALUE("dsa-cert", &opensshcert_ssh_dsa)
+ ENUM_VALUE("rsa-cert", &opensshcert_ssh_rsa)
+ ENUM_VALUE("ed25519-cert", &opensshcert_ssh_ecdsa_ed25519)
+ ENUM_VALUE("p256-cert", &opensshcert_ssh_ecdsa_nistp256)
+ ENUM_VALUE("p384-cert", &opensshcert_ssh_ecdsa_nistp384)
+ ENUM_VALUE("p521-cert", &opensshcert_ssh_ecdsa_nistp521)
+END_ENUM_TYPE(keyalg)
+
+BEGIN_ENUM_TYPE(cipheralg)
+ ENUM_VALUE("3des_ctr", &ssh_3des_ssh2_ctr)
+ ENUM_VALUE("3des_ssh2", &ssh_3des_ssh2)
+ ENUM_VALUE("3des_ssh1", &ssh_3des_ssh1)
+ ENUM_VALUE("des_cbc", &ssh_des)
+ ENUM_VALUE("aes256_ctr", &ssh_aes256_sdctr)
+ ENUM_VALUE("aes256_gcm", &ssh_aes256_gcm)
+ ENUM_VALUE("aes256_cbc", &ssh_aes256_cbc)
+ ENUM_VALUE("aes192_ctr", &ssh_aes192_sdctr)
+ ENUM_VALUE("aes192_gcm", &ssh_aes192_gcm)
+ ENUM_VALUE("aes192_cbc", &ssh_aes192_cbc)
+ ENUM_VALUE("aes128_ctr", &ssh_aes128_sdctr)
+ ENUM_VALUE("aes128_gcm", &ssh_aes128_gcm)
+ ENUM_VALUE("aes128_cbc", &ssh_aes128_cbc)
+ ENUM_VALUE("aes256_ctr_sw", &ssh_aes256_sdctr_sw)
+ ENUM_VALUE("aes256_gcm_sw", &ssh_aes256_gcm_sw)
+ ENUM_VALUE("aes256_cbc_sw", &ssh_aes256_cbc_sw)
+ ENUM_VALUE("aes192_ctr_sw", &ssh_aes192_sdctr_sw)
+ ENUM_VALUE("aes192_gcm_sw", &ssh_aes192_gcm_sw)
+ ENUM_VALUE("aes192_cbc_sw", &ssh_aes192_cbc_sw)
+ ENUM_VALUE("aes128_ctr_sw", &ssh_aes128_sdctr_sw)
+ ENUM_VALUE("aes128_gcm_sw", &ssh_aes128_gcm_sw)
+ ENUM_VALUE("aes128_cbc_sw", &ssh_aes128_cbc_sw)
+#if HAVE_AES_NI
+ ENUM_VALUE("aes256_ctr_ni", &ssh_aes256_sdctr_ni)
+ ENUM_VALUE("aes256_gcm_ni", &ssh_aes256_gcm_ni)
+ ENUM_VALUE("aes256_cbc_ni", &ssh_aes256_cbc_ni)
+ ENUM_VALUE("aes192_ctr_ni", &ssh_aes192_sdctr_ni)
+ ENUM_VALUE("aes192_gcm_ni", &ssh_aes192_gcm_ni)
+ ENUM_VALUE("aes192_cbc_ni", &ssh_aes192_cbc_ni)
+ ENUM_VALUE("aes128_ctr_ni", &ssh_aes128_sdctr_ni)
+ ENUM_VALUE("aes128_gcm_ni", &ssh_aes128_gcm_ni)
+ ENUM_VALUE("aes128_cbc_ni", &ssh_aes128_cbc_ni)
+#endif
+#if HAVE_NEON_CRYPTO
+ ENUM_VALUE("aes256_ctr_neon", &ssh_aes256_sdctr_neon)
+ ENUM_VALUE("aes256_gcm_neon", &ssh_aes256_gcm_neon)
+ ENUM_VALUE("aes256_cbc_neon", &ssh_aes256_cbc_neon)
+ ENUM_VALUE("aes192_ctr_neon", &ssh_aes192_sdctr_neon)
+ ENUM_VALUE("aes192_gcm_neon", &ssh_aes192_gcm_neon)
+ ENUM_VALUE("aes192_cbc_neon", &ssh_aes192_cbc_neon)
+ ENUM_VALUE("aes128_ctr_neon", &ssh_aes128_sdctr_neon)
+ ENUM_VALUE("aes128_gcm_neon", &ssh_aes128_gcm_neon)
+ ENUM_VALUE("aes128_cbc_neon", &ssh_aes128_cbc_neon)
+#endif
+ ENUM_VALUE("blowfish_ctr", &ssh_blowfish_ssh2_ctr)
+ ENUM_VALUE("blowfish_ssh2", &ssh_blowfish_ssh2)
+ ENUM_VALUE("blowfish_ssh1", &ssh_blowfish_ssh1)
+ ENUM_VALUE("arcfour256", &ssh_arcfour256_ssh2)
+ ENUM_VALUE("arcfour128", &ssh_arcfour128_ssh2)
+ ENUM_VALUE("chacha20_poly1305", &ssh2_chacha20_poly1305)
+END_ENUM_TYPE(cipheralg)
+
+BEGIN_ENUM_TYPE(dh_group)
+ ENUM_VALUE("group1", &ssh_diffiehellman_group1_sha1)
+ ENUM_VALUE("group14", &ssh_diffiehellman_group14_sha256)
+ ENUM_VALUE("group15", &ssh_diffiehellman_group15_sha512)
+ ENUM_VALUE("group16", &ssh_diffiehellman_group16_sha512)
+ ENUM_VALUE("group17", &ssh_diffiehellman_group17_sha512)
+ ENUM_VALUE("group18", &ssh_diffiehellman_group18_sha512)
+END_ENUM_TYPE(dh_group)
+
+BEGIN_ENUM_TYPE(ecdh_alg)
+ ENUM_VALUE("curve25519", &ssh_ec_kex_curve25519)
+ ENUM_VALUE("curve448", &ssh_ec_kex_curve448)
+ ENUM_VALUE("nistp256", &ssh_ec_kex_nistp256)
+ ENUM_VALUE("nistp384", &ssh_ec_kex_nistp384)
+ ENUM_VALUE("nistp521", &ssh_ec_kex_nistp521)
+END_ENUM_TYPE(ecdh_alg)
+
+BEGIN_ENUM_TYPE(rsaorder)
+ ENUM_VALUE("exponent_first", RSA_SSH1_EXPONENT_FIRST)
+ ENUM_VALUE("modulus_first", RSA_SSH1_MODULUS_FIRST)
+END_ENUM_TYPE(rsaorder)
+
+BEGIN_ENUM_TYPE(primegenpolicy)
+ ENUM_VALUE("probabilistic", &primegen_probabilistic)
+ ENUM_VALUE("provable_fast", &primegen_provable_fast)
+ ENUM_VALUE("provable_maurer_simple", &primegen_provable_maurer_simple)
+ ENUM_VALUE("provable_maurer_complex", &primegen_provable_maurer_complex)
+END_ENUM_TYPE(primegenpolicy)
+
+BEGIN_ENUM_TYPE(argon2flavour)
+ ENUM_VALUE("d", Argon2d)
+ ENUM_VALUE("i", Argon2i)
+ ENUM_VALUE("id", Argon2id)
+ /* I expect to forget which spelling I chose, so let's support many */
+ ENUM_VALUE("argon2d", Argon2d)
+ ENUM_VALUE("argon2i", Argon2i)
+ ENUM_VALUE("argon2id", Argon2id)
+ ENUM_VALUE("Argon2d", Argon2d)
+ ENUM_VALUE("Argon2i", Argon2i)
+ ENUM_VALUE("Argon2id", Argon2id)
+END_ENUM_TYPE(argon2flavour)
+
+BEGIN_ENUM_TYPE(fptype)
+ ENUM_VALUE("md5", SSH_FPTYPE_MD5)
+ ENUM_VALUE("sha256", SSH_FPTYPE_SHA256)
+ ENUM_VALUE("md5-cert", SSH_FPTYPE_MD5_CERT)
+ ENUM_VALUE("sha256-cert", SSH_FPTYPE_SHA256_CERT)
+END_ENUM_TYPE(fptype)
+
+/*
+ * cproxy.h already has a list macro mapping protocol-specified
+ * strings to the list of HTTP Digest hash functions. Rather than
+ * invent a separate one for testcrypt, reuse the existing names.
+ */
+BEGIN_ENUM_TYPE(httpdigesthash)
+ #define DECL_ARRAY(id, str, alg, bits, accepted) ENUM_VALUE(str, id)
+ HTTP_DIGEST_HASHES(DECL_ARRAY)
+ #undef DECL_ARRAY
+END_ENUM_TYPE(httpdigesthash)
diff --git a/test/testcrypt-func.h b/test/testcrypt-func.h
new file mode 100644
index 00000000..bd007293
--- /dev/null
+++ b/test/testcrypt-func.h
@@ -0,0 +1,582 @@
+/*
+ * List of functions exported by the 'testcrypt' system to provide a
+ * Python API for running unit tests and auxiliary programs.
+ *
+ * Each function definition in this file has the form
+ *
+ * FUNC(return-type, function-name, ...)
+ *
+ * where '...' in turn a variadic list of argument specifications of
+ * the form
+ *
+ * ARG(argument-type, argument-name)
+ *
+ * An empty argument list must be marked by including a
+ * pseudo-argument VOID:
+ *
+ * FUNC(return-type, function-name, VOID)
+ *
+ * Type names are always single identifiers, and they have some
+ * standard prefixes:
+ *
+ * 'val_' means that the type refers to something dynamically
+ * allocated, so that it has a persistent identity, needs to be freed
+ * when finished with (though this is done automatically by the
+ * testcrypt.py system via Python's reference counting), and may also
+ * be mutable. The argument type in C will be a pointer; in Python the
+ * corresponding argument will be an instance of a 'Value' object
+ * defined in testcrypt.py.
+ *
+ * 'opt_val_' is a modification of 'val_' to indicate that the pointer
+ * may be NULL. In Python this is translated by accepting (or
+ * returning) None as an alternative to a Value.
+ *
+ * 'out_' on an argument type indicates an additional output
+ * parameter. The argument type in C has an extra layer of
+ * indirection, e.g. an 'out_val_mpint' is an 'mpint **' instead of an
+ * 'mpint *', identifying a pointer variable where the returned
+ * pointer value will be written. In the Python API, these arguments
+ * do not appear in the argument list of the Python function; instead
+ * they cause the return value to become a tuple, with additional
+ * types appended. For example, a declaration like
+ *
+ * FUNC(val_foo, example, ARG(out_val_bar, bar), ARG(val_baz, baz))
+ *
+ * would identify a function in C with the following prototype, which
+ * returns a 'foo *' directly and a 'bar *' by writing it through the
+ * provided 'bar **' pointer argument:
+ *
+ * foo *example(bar **extra_output, baz *input);
+ *
+ * and in Python this would become a function taking one argument of
+ * type 'baz' and returning a tuple of the form (foo, bar).
+ *
+ * 'out_' and 'opt_' can go together, if a function returns a second
+ * output value but it may in some cases be NULL.
+ *
+ * 'consumed_' on an argument type indicates that the C function
+ * receiving that argument frees it as a side effect.
+ *
+ * Any argument type which does not start 'val_' is plain old data
+ * with no dynamic allocation requirements. Ordinary C integers are
+ * sometimes handled this way (e.g. 'uint'). Other plain-data types
+ * are represented in Python as a string that must be one of a
+ * recognised set of keywords; in C these variously translate into
+ * enumeration types (e.g. argon2flavour, rsaorder) or pointers to
+ * const vtables of one kind or another (e.g. keyalg, hashalg,
+ * primegenpolicy).
+ *
+ * If a function definition begins with FUNC_WRAPPED rather than FUNC,
+ * it means that the underlying C function has a suffix "_wrapper",
+ * e.g. ssh_cipher_setiv_wrapper(). Those wrappers are defined in
+ * testcrypt.c itself, and change the API or semantics in a way that
+ * makes the function more Python-friendly.
+ */
+
+/*
+ * mpint.h functions.
+ */
+FUNC(val_mpint, mp_new, ARG(uint, maxbits))
+FUNC(void, mp_clear, ARG(val_mpint, x))
+FUNC(val_mpint, mp_from_bytes_le, ARG(val_string_ptrlen, bytes))
+FUNC(val_mpint, mp_from_bytes_be, ARG(val_string_ptrlen, bytes))
+FUNC(val_mpint, mp_from_integer, ARG(uint, n))
+FUNC(val_mpint, mp_from_decimal_pl, ARG(val_string_ptrlen, decimal))
+FUNC(val_mpint, mp_from_decimal, ARG(val_string_asciz, decimal))
+FUNC(val_mpint, mp_from_hex_pl, ARG(val_string_ptrlen, hex))
+FUNC(val_mpint, mp_from_hex, ARG(val_string_asciz, hex))
+FUNC(val_mpint, mp_copy, ARG(val_mpint, x))
+FUNC(val_mpint, mp_power_2, ARG(uint, power))
+FUNC(uint, mp_get_byte, ARG(val_mpint, x), ARG(uint, byte))
+FUNC(uint, mp_get_bit, ARG(val_mpint, x), ARG(uint, bit))
+FUNC(void, mp_set_bit, ARG(val_mpint, x), ARG(uint, bit), ARG(uint, val))
+FUNC(uint, mp_max_bytes, ARG(val_mpint, x))
+FUNC(uint, mp_max_bits, ARG(val_mpint, x))
+FUNC(uint, mp_get_nbits, ARG(val_mpint, x))
+FUNC(val_string_asciz, mp_get_decimal, ARG(val_mpint, x))
+FUNC(val_string_asciz, mp_get_hex, ARG(val_mpint, x))
+FUNC(val_string_asciz, mp_get_hex_uppercase, ARG(val_mpint, x))
+FUNC(uint, mp_cmp_hs, ARG(val_mpint, a), ARG(val_mpint, b))
+FUNC(uint, mp_cmp_eq, ARG(val_mpint, a), ARG(val_mpint, b))
+FUNC(uint, mp_hs_integer, ARG(val_mpint, x), ARG(uint, n))
+FUNC(uint, mp_eq_integer, ARG(val_mpint, x), ARG(uint, n))
+FUNC(void, mp_min_into, ARG(val_mpint, dest), ARG(val_mpint, x),
+ ARG(val_mpint, y))
+FUNC(void, mp_max_into, ARG(val_mpint, dest), ARG(val_mpint, x),
+ ARG(val_mpint, y))
+FUNC(val_mpint, mp_min, ARG(val_mpint, x), ARG(val_mpint, y))
+FUNC(val_mpint, mp_max, ARG(val_mpint, x), ARG(val_mpint, y))
+FUNC(void, mp_copy_into, ARG(val_mpint, dest), ARG(val_mpint, src))
+FUNC(void, mp_select_into, ARG(val_mpint, dest), ARG(val_mpint, src0),
+ ARG(val_mpint, src1), ARG(uint, choose_src1))
+FUNC(void, mp_add_into, ARG(val_mpint, dest), ARG(val_mpint, a),
+ ARG(val_mpint, b))
+FUNC(void, mp_sub_into, ARG(val_mpint, dest), ARG(val_mpint, a),
+ ARG(val_mpint, b))
+FUNC(void, mp_mul_into, ARG(val_mpint, dest), ARG(val_mpint, a),
+ ARG(val_mpint, b))
+FUNC(val_mpint, mp_add, ARG(val_mpint, x), ARG(val_mpint, y))
+FUNC(val_mpint, mp_sub, ARG(val_mpint, x), ARG(val_mpint, y))
+FUNC(val_mpint, mp_mul, ARG(val_mpint, x), ARG(val_mpint, y))
+FUNC(void, mp_and_into, ARG(val_mpint, dest), ARG(val_mpint, a),
+ ARG(val_mpint, b))
+FUNC(void, mp_or_into, ARG(val_mpint, dest), ARG(val_mpint, a),
+ ARG(val_mpint, b))
+FUNC(void, mp_xor_into, ARG(val_mpint, dest), ARG(val_mpint, a),
+ ARG(val_mpint, b))
+FUNC(void, mp_bic_into, ARG(val_mpint, dest), ARG(val_mpint, a),
+ ARG(val_mpint, b))
+FUNC(void, mp_copy_integer_into, ARG(val_mpint, dest), ARG(uint, n))
+FUNC(void, mp_add_integer_into, ARG(val_mpint, dest), ARG(val_mpint, a),
+ ARG(uint, n))
+FUNC(void, mp_sub_integer_into, ARG(val_mpint, dest), ARG(val_mpint, a),
+ ARG(uint, n))
+FUNC(void, mp_mul_integer_into, ARG(val_mpint, dest), ARG(val_mpint, a),
+ ARG(uint, n))
+FUNC(void, mp_cond_add_into, ARG(val_mpint, dest), ARG(val_mpint, a),
+ ARG(val_mpint, b), ARG(uint, yes))
+FUNC(void, mp_cond_sub_into, ARG(val_mpint, dest), ARG(val_mpint, a),
+ ARG(val_mpint, b), ARG(uint, yes))
+FUNC(void, mp_cond_swap, ARG(val_mpint, x0), ARG(val_mpint, x1),
+ ARG(uint, swap))
+FUNC(void, mp_cond_clear, ARG(val_mpint, x), ARG(uint, clear))
+FUNC(void, mp_divmod_into, ARG(val_mpint, n), ARG(val_mpint, d),
+ ARG(opt_val_mpint, q), ARG(opt_val_mpint, r))
+FUNC(val_mpint, mp_div, ARG(val_mpint, n), ARG(val_mpint, d))
+FUNC(val_mpint, mp_mod, ARG(val_mpint, x), ARG(val_mpint, modulus))
+FUNC(val_mpint, mp_nthroot, ARG(val_mpint, y), ARG(uint, n),
+ ARG(opt_val_mpint, remainder))
+FUNC(void, mp_reduce_mod_2to, ARG(val_mpint, x), ARG(uint, p))
+FUNC(val_mpint, mp_invert_mod_2to, ARG(val_mpint, x), ARG(uint, p))
+FUNC(val_mpint, mp_invert, ARG(val_mpint, x), ARG(val_mpint, modulus))
+FUNC(void, mp_gcd_into, ARG(val_mpint, a), ARG(val_mpint, b),
+ ARG(opt_val_mpint, gcd_out), ARG(opt_val_mpint, A_out),
+ ARG(opt_val_mpint, B_out))
+FUNC(val_mpint, mp_gcd, ARG(val_mpint, a), ARG(val_mpint, b))
+FUNC(uint, mp_coprime, ARG(val_mpint, a), ARG(val_mpint, b))
+FUNC(val_modsqrt, modsqrt_new, ARG(val_mpint, p),
+ ARG(val_mpint, any_nonsquare_mod_p))
+/* The modsqrt functions' 'success' pointer becomes a second return value */
+FUNC(val_mpint, mp_modsqrt, ARG(val_modsqrt, sc), ARG(val_mpint, x),
+ ARG(out_uint, success))
+FUNC(val_monty, monty_new, ARG(val_mpint, modulus))
+FUNC_WRAPPED(val_mpint, monty_modulus, ARG(val_monty, mc))
+FUNC_WRAPPED(val_mpint, monty_identity, ARG(val_monty, mc))
+FUNC(void, monty_import_into, ARG(val_monty, mc), ARG(val_mpint, dest),
+ ARG(val_mpint, x))
+FUNC(val_mpint, monty_import, ARG(val_monty, mc), ARG(val_mpint, x))
+FUNC(void, monty_export_into, ARG(val_monty, mc), ARG(val_mpint, dest),
+ ARG(val_mpint, x))
+FUNC(val_mpint, monty_export, ARG(val_monty, mc), ARG(val_mpint, x))
+FUNC(void, monty_mul_into, ARG(val_monty, mc), ARG(val_mpint, dest),
+ ARG(val_mpint, x), ARG(val_mpint, y))
+FUNC(val_mpint, monty_add, ARG(val_monty, mc), ARG(val_mpint, x),
+ ARG(val_mpint, y))
+FUNC(val_mpint, monty_sub, ARG(val_monty, mc), ARG(val_mpint, x),
+ ARG(val_mpint, y))
+FUNC(val_mpint, monty_mul, ARG(val_monty, mc), ARG(val_mpint, x),
+ ARG(val_mpint, y))
+FUNC(val_mpint, monty_pow, ARG(val_monty, mc), ARG(val_mpint, base),
+ ARG(val_mpint, exponent))
+FUNC(val_mpint, monty_invert, ARG(val_monty, mc), ARG(val_mpint, x))
+FUNC(val_mpint, monty_modsqrt, ARG(val_modsqrt, sc), ARG(val_mpint, mx),
+ ARG(out_uint, success))
+FUNC(val_mpint, mp_modpow, ARG(val_mpint, base), ARG(val_mpint, exponent),
+ ARG(val_mpint, modulus))
+FUNC(val_mpint, mp_modmul, ARG(val_mpint, x), ARG(val_mpint, y),
+ ARG(val_mpint, modulus))
+FUNC(val_mpint, mp_modadd, ARG(val_mpint, x), ARG(val_mpint, y),
+ ARG(val_mpint, modulus))
+FUNC(val_mpint, mp_modsub, ARG(val_mpint, x), ARG(val_mpint, y),
+ ARG(val_mpint, modulus))
+FUNC(void, mp_lshift_safe_into, ARG(val_mpint, dest), ARG(val_mpint, x),
+ ARG(uint, shift))
+FUNC(void, mp_rshift_safe_into, ARG(val_mpint, dest), ARG(val_mpint, x),
+ ARG(uint, shift))
+FUNC(val_mpint, mp_rshift_safe, ARG(val_mpint, x), ARG(uint, shift))
+FUNC(void, mp_lshift_fixed_into, ARG(val_mpint, dest), ARG(val_mpint, x),
+ ARG(uint, shift))
+FUNC(void, mp_rshift_fixed_into, ARG(val_mpint, dest), ARG(val_mpint, x),
+ ARG(uint, shift))
+FUNC(val_mpint, mp_rshift_fixed, ARG(val_mpint, x), ARG(uint, shift))
+FUNC(val_mpint, mp_random_bits, ARG(uint, bits))
+FUNC(val_mpint, mp_random_in_range, ARG(val_mpint, lo), ARG(val_mpint, hi))
+
+/*
+ * ecc.h functions.
+ */
+FUNC(val_wcurve, ecc_weierstrass_curve, ARG(val_mpint, p), ARG(val_mpint, a),
+ ARG(val_mpint, b), ARG(opt_val_mpint, nonsquare_mod_p))
+FUNC(val_wpoint, ecc_weierstrass_point_new_identity, ARG(val_wcurve, curve))
+FUNC(val_wpoint, ecc_weierstrass_point_new, ARG(val_wcurve, curve),
+ ARG(val_mpint, x), ARG(val_mpint, y))
+FUNC(val_wpoint, ecc_weierstrass_point_new_from_x, ARG(val_wcurve, curve),
+ ARG(val_mpint, x), ARG(uint, desired_y_parity))
+FUNC(val_wpoint, ecc_weierstrass_point_copy, ARG(val_wpoint, orig))
+FUNC(uint, ecc_weierstrass_point_valid, ARG(val_wpoint, P))
+FUNC(val_wpoint, ecc_weierstrass_add_general, ARG(val_wpoint, P),
+ ARG(val_wpoint, Q))
+FUNC(val_wpoint, ecc_weierstrass_add, ARG(val_wpoint, P), ARG(val_wpoint, Q))
+FUNC(val_wpoint, ecc_weierstrass_double, ARG(val_wpoint, P))
+FUNC(val_wpoint, ecc_weierstrass_multiply, ARG(val_wpoint, B),
+ ARG(val_mpint, n))
+FUNC(uint, ecc_weierstrass_is_identity, ARG(val_wpoint, P))
+/* The output pointers in get_affine all become extra output values */
+FUNC(void, ecc_weierstrass_get_affine, ARG(val_wpoint, P),
+ ARG(out_val_mpint, x), ARG(out_val_mpint, y))
+FUNC(val_mcurve, ecc_montgomery_curve, ARG(val_mpint, p), ARG(val_mpint, a),
+ ARG(val_mpint, b))
+FUNC(val_mpoint, ecc_montgomery_point_new, ARG(val_mcurve, curve),
+ ARG(val_mpint, x))
+FUNC(val_mpoint, ecc_montgomery_point_copy, ARG(val_mpoint, orig))
+FUNC(val_mpoint, ecc_montgomery_diff_add, ARG(val_mpoint, P),
+ ARG(val_mpoint, Q), ARG(val_mpoint, PminusQ))
+FUNC(val_mpoint, ecc_montgomery_double, ARG(val_mpoint, P))
+FUNC(val_mpoint, ecc_montgomery_multiply, ARG(val_mpoint, B), ARG(val_mpint, n))
+FUNC(void, ecc_montgomery_get_affine, ARG(val_mpoint, P), ARG(out_val_mpint, x))
+FUNC(boolean, ecc_montgomery_is_identity, ARG(val_mpoint, P))
+FUNC(val_ecurve, ecc_edwards_curve, ARG(val_mpint, p), ARG(val_mpint, d),
+ ARG(val_mpint, a), ARG(opt_val_mpint, nonsquare_mod_p))
+FUNC(val_epoint, ecc_edwards_point_new, ARG(val_ecurve, curve),
+ ARG(val_mpint, x), ARG(val_mpint, y))
+FUNC(val_epoint, ecc_edwards_point_new_from_y, ARG(val_ecurve, curve),
+ ARG(val_mpint, y), ARG(uint, desired_x_parity))
+FUNC(val_epoint, ecc_edwards_point_copy, ARG(val_epoint, orig))
+FUNC(val_epoint, ecc_edwards_add, ARG(val_epoint, P), ARG(val_epoint, Q))
+FUNC(val_epoint, ecc_edwards_multiply, ARG(val_epoint, B), ARG(val_mpint, n))
+FUNC(uint, ecc_edwards_eq, ARG(val_epoint, P), ARG(val_epoint, Q))
+FUNC(void, ecc_edwards_get_affine, ARG(val_epoint, P), ARG(out_val_mpint, x),
+ ARG(out_val_mpint, y))
+
+/*
+ * The ssh_hash abstraction. Note the 'consumed', indicating that
+ * ssh_hash_final puts its input ssh_hash beyond use.
+ *
+ * ssh_hash_update is an invention of testcrypt, handled in the real C
+ * API by the hash object also functioning as a BinarySink.
+ */
+FUNC(opt_val_hash, ssh_hash_new, ARG(hashalg, alg))
+FUNC(void, ssh_hash_reset, ARG(val_hash, h))
+FUNC(val_hash, ssh_hash_copy, ARG(val_hash, orig))
+FUNC_WRAPPED(val_string, ssh_hash_digest, ARG(val_hash, h))
+FUNC_WRAPPED(val_string, ssh_hash_final, ARG(consumed_val_hash, h))
+FUNC(void, ssh_hash_update, ARG(val_hash, h), ARG(val_string_ptrlen, data))
+
+FUNC(opt_val_hash, blake2b_new_general, ARG(uint, hashlen))
+
+/*
+ * The ssh2_mac abstraction. Note the optional ssh_cipher parameter
+ * to ssh2_mac_new. Also, again, I've invented an ssh2_mac_update so
+ * you can put data into the MAC.
+ */
+FUNC(val_mac, ssh2_mac_new, ARG(macalg, alg), ARG(opt_val_cipher, cipher))
+FUNC(void, ssh2_mac_setkey, ARG(val_mac, m), ARG(val_string_ptrlen, key))
+FUNC(void, ssh2_mac_start, ARG(val_mac, m))
+FUNC(void, ssh2_mac_update, ARG(val_mac, m), ARG(val_string_ptrlen, data))
+FUNC(void, ssh2_mac_next_message, ARG(val_mac, m))
+FUNC_WRAPPED(val_string, ssh2_mac_genresult, ARG(val_mac, m))
+FUNC(val_string_asciz_const, ssh2_mac_text_name, ARG(val_mac, m))
+
+FUNC(void, aesgcm_set_prefix_lengths,
+ ARG(val_mac, m), ARG(uint, skip), ARG(uint, aad))
+
+/*
+ * The ssh_key abstraction. All the uses of BinarySink and
+ * BinarySource in parameters are replaced with ordinary strings for
+ * the testing API: new_priv_openssh just takes a string input, and
+ * all the functions that output key and signature blobs do it by
+ * returning a string.
+ */
+FUNC(val_key, ssh_key_new_pub, ARG(keyalg, alg), ARG(val_string_ptrlen, pub))
+FUNC(opt_val_key, ssh_key_new_priv, ARG(keyalg, alg),
+ ARG(val_string_ptrlen, pub), ARG(val_string_ptrlen, priv))
+FUNC(opt_val_key, ssh_key_new_priv_openssh, ARG(keyalg, alg),
+ ARG(val_string_binarysource, src))
+FUNC(opt_val_string_asciz, ssh_key_invalid, ARG(val_key, key), ARG(uint, flags))
+FUNC(void, ssh_key_sign, ARG(val_key, key), ARG(val_string_ptrlen, data),
+ ARG(uint, flags), ARG(out_val_string_binarysink, sig))
+FUNC(boolean, ssh_key_verify, ARG(val_key, key), ARG(val_string_ptrlen, sig),
+ ARG(val_string_ptrlen, data))
+FUNC(void, ssh_key_public_blob, ARG(val_key, key),
+ ARG(out_val_string_binarysink, blob))
+FUNC(void, ssh_key_private_blob, ARG(val_key, key),
+ ARG(out_val_string_binarysink, blob))
+FUNC(void, ssh_key_openssh_blob, ARG(val_key, key),
+ ARG(out_val_string_binarysink, blob))
+FUNC(val_string_asciz, ssh_key_cache_str, ARG(val_key, key))
+FUNC(val_keycomponents, ssh_key_components, ARG(val_key, key))
+FUNC(uint, ssh_key_public_bits, ARG(keyalg, self), ARG(val_string_ptrlen, blob))
+FUNC_WRAPPED(val_key, ssh_key_base_key, ARG(val_key, key))
+FUNC_WRAPPED(void, ssh_key_ca_public_blob, ARG(val_key, key),
+ ARG(out_val_string_binarysink, blob))
+FUNC_WRAPPED(void, ssh_key_cert_id_string, ARG(val_key, key),
+ ARG(out_val_string_binarysink, blob))
+FUNC_WRAPPED(boolean, ssh_key_check_cert, ARG(val_key, key),
+ ARG(boolean, host), ARG(val_string_ptrlen, principal),
+ ARG(uint, time), ARG(val_string_ptrlen, options),
+ ARG(out_val_string_binarysink, error))
+
+/*
+ * Accessors to retrieve the innards of a 'key_components'.
+ */
+FUNC(uint, key_components_count, ARG(val_keycomponents, kc))
+FUNC(opt_val_string_asciz_const, key_components_nth_name,
+ ARG(val_keycomponents, kc), ARG(uint, n))
+FUNC(opt_val_string, key_components_nth_str,
+ ARG(val_keycomponents, kc), ARG(uint, n))
+FUNC(opt_val_mpint, key_components_nth_mp, ARG(val_keycomponents, kc),
+ ARG(uint, n))
+
+/*
+ * The ssh_cipher abstraction. The in-place encrypt and decrypt
+ * functions are wrapped to replace them with versions that take one
+ * string and return a separate string.
+ */
+FUNC(opt_val_cipher, ssh_cipher_new, ARG(cipheralg, alg))
+FUNC_WRAPPED(void, ssh_cipher_setiv, ARG(val_cipher, c),
+ ARG(val_string_ptrlen, iv))
+FUNC_WRAPPED(void, ssh_cipher_setkey, ARG(val_cipher, c),
+ ARG(val_string_ptrlen, key))
+FUNC_WRAPPED(val_string, ssh_cipher_encrypt, ARG(val_cipher, c),
+ ARG(val_string_ptrlen, blk))
+FUNC_WRAPPED(val_string, ssh_cipher_decrypt, ARG(val_cipher, c),
+ ARG(val_string_ptrlen, blk))
+FUNC_WRAPPED(val_string, ssh_cipher_encrypt_length, ARG(val_cipher, c),
+ ARG(val_string_ptrlen, blk), ARG(uint, seq))
+FUNC_WRAPPED(val_string, ssh_cipher_decrypt_length, ARG(val_cipher, c),
+ ARG(val_string_ptrlen, blk), ARG(uint, seq))
+FUNC(void, ssh_cipher_next_message, ARG(val_cipher, c))
+
+/*
+ * Integer Diffie-Hellman.
+ */
+FUNC(val_dh, dh_setup_group, ARG(dh_group, group))
+FUNC(val_dh, dh_setup_gex, ARG(val_mpint, p), ARG(val_mpint, g))
+FUNC(uint, dh_modulus_bit_size, ARG(val_dh, ctx))
+FUNC(val_mpint, dh_create_e, ARG(val_dh, ctx))
+FUNC_WRAPPED(boolean, dh_validate_f, ARG(val_dh, ctx), ARG(val_mpint, f))
+FUNC(val_mpint, dh_find_K, ARG(val_dh, ctx), ARG(val_mpint, f))
+
+/*
+ * Elliptic-curve Diffie-Hellman.
+ */
+FUNC(val_ecdh, ecdh_key_new, ARG(ecdh_alg, alg), ARG(boolean, is_server))
+FUNC(void, ecdh_key_getpublic, ARG(val_ecdh, key),
+ ARG(out_val_string_binarysink, pub))
+FUNC_WRAPPED(opt_val_string, ecdh_key_getkey, ARG(val_ecdh, key),
+ ARG(val_string_ptrlen, pub))
+
+/*
+ * NTRU and its subroutines.
+ */
+FUNC_WRAPPED(int16_list, ntru_ring_multiply, ARG(int16_list, a),
+ ARG(int16_list, b), ARG(uint, p), ARG(uint, q))
+FUNC_WRAPPED(opt_int16_list, ntru_ring_invert, ARG(int16_list, r),
+ ARG(uint, p), ARG(uint, q))
+FUNC_WRAPPED(int16_list, ntru_mod3, ARG(int16_list, r),
+ ARG(uint, p), ARG(uint, q))
+FUNC_WRAPPED(int16_list, ntru_round3, ARG(int16_list, r),
+ ARG(uint, p), ARG(uint, q))
+FUNC_WRAPPED(int16_list, ntru_bias, ARG(int16_list, r),
+ ARG(uint, bias), ARG(uint, p), ARG(uint, q))
+FUNC_WRAPPED(int16_list, ntru_scale, ARG(int16_list, r),
+ ARG(uint, scale), ARG(uint, p), ARG(uint, q))
+FUNC_WRAPPED(val_ntruencodeschedule, ntru_encode_schedule, ARG(int16_list, ms))
+FUNC(uint, ntru_encode_schedule_length, ARG(val_ntruencodeschedule, sched))
+FUNC_WRAPPED(void, ntru_encode, ARG(val_ntruencodeschedule, sched),
+ ARG(int16_list, rs), ARG(out_val_string_binarysink, data))
+FUNC_WRAPPED(opt_int16_list, ntru_decode, ARG(val_ntruencodeschedule, sched),
+ ARG(val_string_ptrlen, data))
+FUNC_WRAPPED(int16_list, ntru_gen_short, ARG(uint, p), ARG(uint, w))
+FUNC(val_ntrukeypair, ntru_keygen, ARG(uint, p), ARG(uint, q), ARG(uint, w))
+FUNC_WRAPPED(int16_list, ntru_pubkey, ARG(val_ntrukeypair, keypair))
+FUNC_WRAPPED(int16_list, ntru_encrypt, ARG(int16_list, plaintext),
+ ARG(int16_list, pubkey), ARG(uint, p), ARG(uint, q))
+FUNC_WRAPPED(int16_list, ntru_decrypt, ARG(int16_list, ciphertext),
+ ARG(val_ntrukeypair, keypair))
+
+/*
+ * RSA key exchange, and also the BinarySource get function
+ * get_ssh1_rsa_priv_agent, which is a convenient way to make an
+ * RSAKey for RSA kex testing purposes.
+ */
+FUNC(val_rsakex, ssh_rsakex_newkey, ARG(val_string_ptrlen, data))
+FUNC(uint, ssh_rsakex_klen, ARG(val_rsakex, key))
+FUNC(val_string, ssh_rsakex_encrypt, ARG(val_rsakex, key), ARG(hashalg, h),
+ ARG(val_string_ptrlen, plaintext))
+FUNC(opt_val_mpint, ssh_rsakex_decrypt, ARG(val_rsakex, key), ARG(hashalg, h),
+ ARG(val_string_ptrlen, ciphertext))
+FUNC(val_rsakex, get_rsa_ssh1_priv_agent, ARG(val_string_binarysource, src))
+
+/*
+ * Bare RSA keys as used in SSH-1. The construction API functions
+ * write into an existing RSAKey object, so I've invented an 'rsa_new'
+ * function to make one in the first place.
+ */
+FUNC(val_rsa, rsa_new, VOID)
+FUNC(void, get_rsa_ssh1_pub, ARG(val_string_binarysource, src),
+ ARG(val_rsa, key), ARG(rsaorder, order))
+FUNC(void, get_rsa_ssh1_priv, ARG(val_string_binarysource, src),
+ ARG(val_rsa, key))
+FUNC_WRAPPED(opt_val_string, rsa_ssh1_encrypt, ARG(val_string_ptrlen, data),
+ ARG(val_rsa, key))
+FUNC(val_mpint, rsa_ssh1_decrypt, ARG(val_mpint, input), ARG(val_rsa, key))
+FUNC_WRAPPED(val_string, rsa_ssh1_decrypt_pkcs1, ARG(val_mpint, input),
+ ARG(val_rsa, key))
+FUNC(val_string_asciz, rsastr_fmt, ARG(val_rsa, key))
+FUNC(val_string_asciz, rsa_ssh1_fingerprint, ARG(val_rsa, key))
+FUNC(void, rsa_ssh1_public_blob, ARG(out_val_string_binarysink, blob),
+ ARG(val_rsa, key), ARG(rsaorder, order))
+FUNC(int, rsa_ssh1_public_blob_len, ARG(val_string_ptrlen, data))
+FUNC(void, rsa_ssh1_private_blob_agent, ARG(out_val_string_binarysink, blob),
+ ARG(val_rsa, key))
+
+/*
+ * The PRNG type. Similarly to hashes and MACs, I've invented an extra
+ * function prng_seed_update for putting seed data into the PRNG's
+ * exposed BinarySink.
+ */
+FUNC(val_prng, prng_new, ARG(hashalg, hashalg))
+FUNC(void, prng_seed_begin, ARG(val_prng, pr))
+FUNC(void, prng_seed_update, ARG(val_prng, pr), ARG(val_string_ptrlen, data))
+FUNC(void, prng_seed_finish, ARG(val_prng, pr))
+FUNC_WRAPPED(val_string, prng_read, ARG(val_prng, pr), ARG(uint, size))
+FUNC(void, prng_add_entropy, ARG(val_prng, pr), ARG(uint, source_id),
+ ARG(val_string_ptrlen, data))
+
+/*
+ * Key load/save functions, or rather, the BinarySource / strbuf API
+ * that sits just inside the file I/O versions.
+ */
+FUNC(boolean, ppk_encrypted_s, ARG(val_string_binarysource, src),
+ ARG(out_opt_val_string_asciz, comment))
+FUNC(boolean, rsa1_encrypted_s, ARG(val_string_binarysource, src),
+ ARG(out_opt_val_string_asciz, comment))
+FUNC(boolean, ppk_loadpub_s, ARG(val_string_binarysource, src),
+ ARG(out_opt_val_string_asciz, algorithm),
+ ARG(out_val_string_binarysink, blob),
+ ARG(out_opt_val_string_asciz, comment),
+ ARG(out_opt_val_string_asciz_const, error))
+FUNC(int, rsa1_loadpub_s, ARG(val_string_binarysource, src),
+ ARG(out_val_string_binarysink, blob),
+ ARG(out_opt_val_string_asciz, comment),
+ ARG(out_opt_val_string_asciz_const, error))
+FUNC_WRAPPED(opt_val_key, ppk_load_s, ARG(val_string_binarysource, src),
+ ARG(out_opt_val_string_asciz, comment),
+ ARG(opt_val_string_asciz, passphrase),
+ ARG(out_opt_val_string_asciz_const, error))
+FUNC_WRAPPED(int, rsa1_load_s, ARG(val_string_binarysource, src),
+ ARG(val_rsa, key), ARG(out_opt_val_string_asciz, comment),
+ ARG(opt_val_string_asciz, passphrase),
+ ARG(out_opt_val_string_asciz_const, error))
+FUNC_WRAPPED(val_string, ppk_save_sb, ARG(val_key, key),
+ ARG(opt_val_string_asciz, comment),
+ ARG(opt_val_string_asciz, passphrase), ARG(uint, fmt_version),
+ ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes),
+ ARG(uint, parallel))
+FUNC_WRAPPED(val_string, rsa1_save_sb, ARG(val_rsa, key),
+ ARG(opt_val_string_asciz, comment),
+ ARG(opt_val_string_asciz, passphrase))
+
+FUNC(val_string_asciz, ssh2_fingerprint_blob, ARG(val_string_ptrlen, blob),
+ ARG(fptype, fptype))
+
+/*
+ * Password hashing.
+ */
+FUNC_WRAPPED(val_string, argon2, ARG(argon2flavour, flavour), ARG(uint, mem),
+ ARG(uint, passes), ARG(uint, parallel), ARG(uint, taglen),
+ ARG(val_string_ptrlen, P), ARG(val_string_ptrlen, S),
+ ARG(val_string_ptrlen, K), ARG(val_string_ptrlen, X))
+FUNC(val_string, argon2_long_hash, ARG(uint, length),
+ ARG(val_string_ptrlen, data))
+FUNC_WRAPPED(val_string, openssh_bcrypt, ARG(val_string_ptrlen, passphrase),
+ ARG(val_string_ptrlen, salt), ARG(uint, rounds),
+ ARG(uint, outbytes))
+
+/*
+ * Key generation functions.
+ */
+FUNC_WRAPPED(val_key, rsa_generate, ARG(uint, bits), ARG(boolean, strong),
+ ARG(val_pgc, pgc))
+FUNC_WRAPPED(val_key, dsa_generate, ARG(uint, bits), ARG(val_pgc, pgc))
+FUNC_WRAPPED(opt_val_key, ecdsa_generate, ARG(uint, bits))
+FUNC_WRAPPED(opt_val_key, eddsa_generate, ARG(uint, bits))
+FUNC(val_rsa, rsa1_generate, ARG(uint, bits), ARG(boolean, strong),
+ ARG(val_pgc, pgc))
+FUNC(val_pgc, primegen_new_context, ARG(primegenpolicy, policy))
+FUNC_WRAPPED(opt_val_mpint, primegen_generate, ARG(val_pgc, ctx),
+ ARG(consumed_val_pcs, pcs))
+FUNC(val_string, primegen_mpu_certificate, ARG(val_pgc, ctx), ARG(val_mpint, p))
+FUNC(val_pcs, pcs_new, ARG(uint, bits))
+FUNC(val_pcs, pcs_new_with_firstbits, ARG(uint, bits), ARG(uint, first),
+ ARG(uint, nfirst))
+FUNC(void, pcs_require_residue, ARG(val_pcs, s), ARG(val_mpint, mod),
+ ARG(val_mpint, res))
+FUNC(void, pcs_require_residue_1, ARG(val_pcs, s), ARG(val_mpint, mod))
+FUNC(void, pcs_require_residue_1_mod_prime, ARG(val_pcs, s),
+ ARG(val_mpint, mod))
+FUNC(void, pcs_avoid_residue_small, ARG(val_pcs, s), ARG(uint, mod),
+ ARG(uint, res))
+FUNC(void, pcs_try_sophie_germain, ARG(val_pcs, s))
+FUNC(void, pcs_set_oneshot, ARG(val_pcs, s))
+FUNC(void, pcs_ready, ARG(val_pcs, s))
+FUNC(void, pcs_inspect, ARG(val_pcs, pcs), ARG(out_val_mpint, limit_out),
+ ARG(out_val_mpint, factor_out), ARG(out_val_mpint, addend_out))
+FUNC(val_mpint, pcs_generate, ARG(val_pcs, s))
+FUNC(val_pockle, pockle_new, VOID)
+FUNC(uint, pockle_mark, ARG(val_pockle, pockle))
+FUNC(void, pockle_release, ARG(val_pockle, pockle), ARG(uint, mark))
+FUNC(pocklestatus, pockle_add_small_prime, ARG(val_pockle, pockle),
+ ARG(val_mpint, p))
+FUNC_WRAPPED(pocklestatus, pockle_add_prime, ARG(val_pockle, pockle),
+ ARG(val_mpint, p), ARG(mpint_list, factors),
+ ARG(val_mpint, witness))
+FUNC(val_string, pockle_mpu, ARG(val_pockle, pockle), ARG(val_mpint, p))
+FUNC(val_millerrabin, miller_rabin_new, ARG(val_mpint, p))
+FUNC(mr_result, miller_rabin_test, ARG(val_millerrabin, mr), ARG(val_mpint, w))
+
+/*
+ * Miscellaneous.
+ */
+FUNC(val_wpoint, ecdsa_public, ARG(val_mpint, private_key), ARG(keyalg, alg))
+FUNC(val_epoint, eddsa_public, ARG(val_mpint, private_key), ARG(keyalg, alg))
+FUNC_WRAPPED(val_string, des_encrypt_xdmauth, ARG(val_string_ptrlen, key),
+ ARG(val_string_ptrlen, blk))
+FUNC_WRAPPED(val_string, des_decrypt_xdmauth, ARG(val_string_ptrlen, key),
+ ARG(val_string_ptrlen, blk))
+FUNC_WRAPPED(val_string, des3_encrypt_pubkey, ARG(val_string_ptrlen, key),
+ ARG(val_string_ptrlen, blk))
+FUNC_WRAPPED(val_string, des3_decrypt_pubkey, ARG(val_string_ptrlen, key),
+ ARG(val_string_ptrlen, blk))
+FUNC_WRAPPED(val_string, des3_encrypt_pubkey_ossh, ARG(val_string_ptrlen, key),
+ ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))
+FUNC_WRAPPED(val_string, des3_decrypt_pubkey_ossh, ARG(val_string_ptrlen, key),
+ ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))
+FUNC_WRAPPED(val_string, aes256_encrypt_pubkey, ARG(val_string_ptrlen, key),
+ ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))
+FUNC_WRAPPED(val_string, aes256_decrypt_pubkey, ARG(val_string_ptrlen, key),
+ ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk))
+FUNC(uint, crc32_rfc1662, ARG(val_string_ptrlen, data))
+FUNC(uint, crc32_ssh1, ARG(val_string_ptrlen, data))
+FUNC(uint, crc32_update, ARG(uint, crc_input), ARG(val_string_ptrlen, data))
+FUNC(boolean, crcda_detect, ARG(val_string_ptrlen, packet),
+ ARG(val_string_ptrlen, iv))
+FUNC(val_string, get_implementations_commasep, ARG(val_string_ptrlen, alg))
+FUNC(void, http_digest_response, ARG(out_val_string_binarysink, response),
+ ARG(val_string_ptrlen, username), ARG(val_string_ptrlen, password),
+ ARG(val_string_ptrlen, realm), ARG(val_string_ptrlen, method),
+ ARG(val_string_ptrlen, uri), ARG(val_string_ptrlen, qop),
+ ARG(val_string_ptrlen, nonce), ARG(val_string_ptrlen, opaque),
+ ARG(uint, nonce_count), ARG(httpdigesthash, hash),
+ ARG(boolean, hash_username))
+
+/*
+ * These functions aren't part of PuTTY's own API, but are additions
+ * by testcrypt itself for administrative purposes.
+ */
+FUNC(void, random_queue, ARG(val_string_ptrlen, data))
+FUNC(uint, random_queue_len, VOID)
+FUNC(void, random_make_prng, ARG(hashalg, hashalg),
+ ARG(val_string_ptrlen, seed))
+FUNC(void, random_clear, VOID)
diff --git a/test/testcrypt.c b/test/testcrypt.c
new file mode 100644
index 00000000..3755ae72
--- /dev/null
+++ b/test/testcrypt.c
@@ -0,0 +1,1718 @@
+/*
+ * 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 <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "defs.h"
+#include "ssh.h"
+#include "sshkeygen.h"
+#include "misc.h"
+#include "mpint.h"
+#include "crypto/ecc.h"
+#include "crypto/ntru.h"
+#include "proxy/cproxy.h"
+
+static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "testcrypt: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ exit(1);
+}
+
+void out_of_memory(void) { fatal_error("out of memory"); }
+
+static bool old_keyfile_warning_given;
+void old_keyfile_warning(void) { old_keyfile_warning_given = true; }
+
+static bufchain random_data_queue;
+static prng *test_prng;
+void random_read(void *buf, size_t size)
+{
+ if (test_prng) {
+ prng_read(test_prng, buf, size);
+ } else {
+ if (!bufchain_try_fetch_consume(&random_data_queue, buf, size))
+ fatal_error("No random data in queue");
+ }
+}
+
+uint64_t prng_reseed_time_ms(void)
+{
+ static uint64_t previous_time = 0;
+ return previous_time += 200;
+}
+
+#define VALUE_TYPES(X) \
+ X(string, strbuf *, strbuf_free(v)) \
+ X(mpint, mp_int *, mp_free(v)) \
+ X(modsqrt, ModsqrtContext *, modsqrt_free(v)) \
+ X(monty, MontyContext *, monty_free(v)) \
+ X(wcurve, WeierstrassCurve *, ecc_weierstrass_curve_free(v)) \
+ X(wpoint, WeierstrassPoint *, ecc_weierstrass_point_free(v)) \
+ X(mcurve, MontgomeryCurve *, ecc_montgomery_curve_free(v)) \
+ X(mpoint, MontgomeryPoint *, ecc_montgomery_point_free(v)) \
+ X(ecurve, EdwardsCurve *, ecc_edwards_curve_free(v)) \
+ X(epoint, EdwardsPoint *, ecc_edwards_point_free(v)) \
+ X(hash, ssh_hash *, ssh_hash_free(v)) \
+ X(key, ssh_key *, ssh_key_free(v)) \
+ X(cipher, ssh_cipher *, ssh_cipher_free(v)) \
+ X(mac, ssh2_mac *, ssh2_mac_free(v)) \
+ X(dh, dh_ctx *, dh_cleanup(v)) \
+ X(ecdh, ecdh_key *, ecdh_key_free(v)) \
+ X(rsakex, RSAKey *, ssh_rsakex_freekey(v)) \
+ X(rsa, RSAKey *, rsa_free(v)) \
+ X(prng, prng *, prng_free(v)) \
+ X(keycomponents, key_components *, key_components_free(v)) \
+ X(pcs, PrimeCandidateSource *, pcs_free(v)) \
+ X(pgc, PrimeGenerationContext *, primegen_free_context(v)) \
+ X(pockle, Pockle *, pockle_free(v)) \
+ X(millerrabin, MillerRabin *, miller_rabin_free(v)) \
+ X(ntrukeypair, NTRUKeyPair *, ntru_keypair_free(v)) \
+ X(ntruencodeschedule, NTRUEncodeSchedule *, ntru_encode_schedule_free(v)) \
+ /* end of list */
+
+typedef struct Value Value;
+
+enum ValueType {
+#define VALTYPE_ENUM(n,t,f) VT_##n,
+ VALUE_TYPES(VALTYPE_ENUM)
+#undef VALTYPE_ENUM
+};
+
+typedef enum ValueType ValueType;
+
+static const char *const type_names[] = {
+#define VALTYPE_NAME(n,t,f) #n,
+ VALUE_TYPES(VALTYPE_NAME)
+#undef VALTYPE_NAME
+};
+
+#define VALTYPE_TYPEDEF(n,t,f) \
+ typedef t TD_val_##n; \
+ typedef t *TD_out_val_##n;
+VALUE_TYPES(VALTYPE_TYPEDEF)
+#undef VALTYPE_TYPEDEF
+
+struct Value {
+ /*
+ * Protocol identifier assigned to this value when it was created.
+ * Lives in the same malloced block as this Value object itself.
+ */
+ ptrlen id;
+
+ /*
+ * Type of the value.
+ */
+ ValueType type;
+
+ /*
+ * Union of all the things it could hold.
+ */
+ union {
+#define VALTYPE_UNION(n,t,f) t vu_##n;
+ VALUE_TYPES(VALTYPE_UNION)
+#undef VALTYPE_UNION
+
+ char *bare_string;
+ };
+};
+
+static int valuecmp(void *av, void *bv)
+{
+ Value *a = (Value *)av, *b = (Value *)bv;
+ return ptrlen_strcmp(a->id, b->id);
+}
+
+static int valuefind(void *av, void *bv)
+{
+ ptrlen *a = (ptrlen *)av;
+ Value *b = (Value *)bv;
+ return ptrlen_strcmp(*a, b->id);
+}
+
+static tree234 *values;
+
+static Value *value_new(ValueType vt)
+{
+ static uint64_t next_index = 0;
+
+ char *name = dupprintf("%s%"PRIu64, type_names[vt], next_index++);
+ size_t namelen = strlen(name);
+
+ Value *val = snew_plus(Value, namelen+1);
+ memcpy(snew_plus_get_aux(val), name, namelen+1);
+ val->id.ptr = snew_plus_get_aux(val);
+ val->id.len = namelen;
+ val->type = vt;
+
+ Value *added = add234(values, val);
+ assert(added == val);
+
+ sfree(name);
+
+ return val;
+}
+
+#define VALTYPE_RETURNFN(n,t,f) \
+ void return_val_##n(strbuf *out, t v) { \
+ Value *val = value_new(VT_##n); \
+ val->vu_##n = v; \
+ put_datapl(out, val->id); \
+ put_byte(out, '\n'); \
+ }
+VALUE_TYPES(VALTYPE_RETURNFN)
+#undef VALTYPE_RETURNFN
+
+static ptrlen get_word(BinarySource *in)
+{
+ ptrlen toret;
+ toret.ptr = get_ptr(in);
+ toret.len = 0;
+ while (get_avail(in) && get_byte(in) != ' ')
+ toret.len++;
+ return toret;
+}
+
+typedef uintmax_t TD_uint;
+typedef bool TD_boolean;
+typedef ptrlen TD_val_string_ptrlen;
+typedef char *TD_val_string_asciz;
+typedef BinarySource *TD_val_string_binarysource;
+typedef unsigned *TD_out_uint;
+typedef BinarySink *TD_out_val_string_binarysink;
+typedef const char *TD_opt_val_string_asciz;
+typedef char **TD_out_val_string_asciz;
+typedef char **TD_out_opt_val_string_asciz;
+typedef const char **TD_out_opt_val_string_asciz_const;
+typedef const ssh_hashalg *TD_hashalg;
+typedef const ssh2_macalg *TD_macalg;
+typedef const ssh_keyalg *TD_keyalg;
+typedef const ssh_cipheralg *TD_cipheralg;
+typedef const ssh_kex *TD_dh_group;
+typedef const ssh_kex *TD_ecdh_alg;
+typedef RsaSsh1Order TD_rsaorder;
+typedef key_components *TD_keycomponents;
+typedef const PrimeGenerationPolicy *TD_primegenpolicy;
+typedef struct mpint_list TD_mpint_list;
+typedef struct int16_list *TD_int16_list;
+typedef PockleStatus TD_pocklestatus;
+typedef struct mr_result TD_mr_result;
+typedef Argon2Flavour TD_argon2flavour;
+typedef FingerprintType TD_fptype;
+typedef HttpDigestHash TD_httpdigesthash;
+
+#define BEGIN_ENUM_TYPE(name) \
+ static bool enum_translate_##name(ptrlen valname, TD_##name *out) { \
+ static const struct { \
+ const char *key; \
+ TD_##name value; \
+ } mapping[] = {
+#define ENUM_VALUE(name, value) {name, value},
+#define END_ENUM_TYPE(name) \
+ }; \
+ for (size_t i = 0; i < lenof(mapping); i++) \
+ if (ptrlen_eq_string(valname, mapping[i].key)) { \
+ if (out) \
+ *out = mapping[i].value; \
+ return true; \
+ } \
+ return false; \
+ } \
+ \
+ static TD_##name get_##name(BinarySource *in) { \
+ ptrlen valname = get_word(in); \
+ TD_##name out; \
+ if (enum_translate_##name(valname, &out)) \
+ return out; \
+ else \
+ fatal_error("%s '%.*s': not found", \
+ #name, PTRLEN_PRINTF(valname)); \
+ }
+#include "testcrypt-enum.h"
+#undef BEGIN_ENUM_TYPE
+#undef ENUM_VALUE
+#undef END_ENUM_TYPE
+
+static uintmax_t get_uint(BinarySource *in)
+{
+ ptrlen word = get_word(in);
+ char *string = mkstr(word);
+ uintmax_t toret = strtoumax(string, NULL, 0);
+ sfree(string);
+ return toret;
+}
+
+static bool get_boolean(BinarySource *in)
+{
+ return ptrlen_eq_string(get_word(in), "true");
+}
+
+static Value *lookup_value(ptrlen word)
+{
+ Value *val = find234(values, &word, valuefind);
+ if (!val)
+ fatal_error("id '%.*s': not found", PTRLEN_PRINTF(word));
+ return val;
+}
+
+static Value *get_value(BinarySource *in)
+{
+ return lookup_value(get_word(in));
+}
+
+typedef void (*finaliser_fn_t)(strbuf *out, void *ctx);
+struct finaliser {
+ finaliser_fn_t fn;
+ void *ctx;
+};
+
+static struct finaliser *finalisers;
+static size_t nfinalisers, finalisersize;
+
+static void add_finaliser(finaliser_fn_t fn, void *ctx)
+{
+ sgrowarray(finalisers, finalisersize, nfinalisers);
+ finalisers[nfinalisers].fn = fn;
+ finalisers[nfinalisers].ctx = ctx;
+ nfinalisers++;
+}
+
+static void run_finalisers(strbuf *out)
+{
+ for (size_t i = 0; i < nfinalisers; i++)
+ finalisers[i].fn(out, finalisers[i].ctx);
+ nfinalisers = 0;
+}
+
+static void finaliser_return_value(strbuf *out, void *ctx)
+{
+ Value *val = (Value *)ctx;
+ put_datapl(out, val->id);
+ put_byte(out, '\n');
+}
+
+static void finaliser_sfree(strbuf *out, void *ctx)
+{
+ sfree(ctx);
+}
+
+#define VALTYPE_GETFN(n,t,f) \
+ static Value *unwrap_value_##n(Value *val) { \
+ ValueType expected = VT_##n; \
+ if (expected != val->type) \
+ fatal_error("id '%.*s': expected %s, got %s", \
+ PTRLEN_PRINTF(val->id), \
+ type_names[expected], type_names[val->type]); \
+ return val; \
+ } \
+ static Value *get_value_##n(BinarySource *in) { \
+ return unwrap_value_##n(get_value(in)); \
+ } \
+ static t get_val_##n(BinarySource *in) { \
+ return get_value_##n(in)->vu_##n; \
+ }
+VALUE_TYPES(VALTYPE_GETFN)
+#undef VALTYPE_GETFN
+
+static ptrlen get_val_string_ptrlen(BinarySource *in)
+{
+ return ptrlen_from_strbuf(get_val_string(in));
+}
+
+static char *get_val_string_asciz(BinarySource *in)
+{
+ return get_val_string(in)->s;
+}
+
+static strbuf *get_opt_val_string(BinarySource *in);
+
+static char *get_opt_val_string_asciz(BinarySource *in)
+{
+ strbuf *sb = get_opt_val_string(in);
+ return sb ? sb->s : NULL;
+}
+
+static mp_int **get_out_val_mpint(BinarySource *in)
+{
+ Value *val = value_new(VT_mpint);
+ add_finaliser(finaliser_return_value, val);
+ return &val->vu_mpint;
+}
+
+struct mpint_list {
+ size_t n;
+ mp_int **integers;
+};
+
+static struct mpint_list get_mpint_list(BinarySource *in)
+{
+ size_t n = get_uint(in);
+
+ struct mpint_list mpl;
+ mpl.n = n;
+
+ mpl.integers = snewn(n, mp_int *);
+ for (size_t i = 0; i < n; i++)
+ mpl.integers[i] = get_val_mpint(in);
+
+ add_finaliser(finaliser_sfree, mpl.integers);
+ return mpl;
+}
+
+typedef struct int16_list {
+ size_t n;
+ uint16_t *integers;
+} int16_list;
+
+static void finaliser_int16_list_free(strbuf *out, void *vlist)
+{
+ int16_list *list = (int16_list *)vlist;
+ sfree(list->integers);
+ sfree(list);
+}
+
+static int16_list *make_int16_list(size_t n)
+{
+ int16_list *list = snew(int16_list);
+ list->n = n;
+ list->integers = snewn(n, uint16_t);
+ add_finaliser(finaliser_int16_list_free, list);
+ return list;
+}
+
+static int16_list *get_int16_list(BinarySource *in)
+{
+ size_t n = get_uint(in);
+ int16_list *list = make_int16_list(n);
+ for (size_t i = 0; i < n; i++)
+ list->integers[i] = get_uint(in);
+ return list;
+}
+
+static void return_int16_list(strbuf *out, int16_list *list)
+{
+ for (size_t i = 0; i < list->n; i++) {
+ if (i > 0)
+ put_byte(out, ',');
+ put_fmt(out, "%d", (int)(int16_t)list->integers[i]);
+ }
+ put_byte(out, '\n');
+}
+
+static void finaliser_return_uint(strbuf *out, void *ctx)
+{
+ unsigned *uval = (unsigned *)ctx;
+ put_fmt(out, "%u\n", *uval);
+ sfree(uval);
+}
+
+static unsigned *get_out_uint(BinarySource *in)
+{
+ unsigned *uval = snew(unsigned);
+ add_finaliser(finaliser_return_uint, uval);
+ return uval;
+}
+
+static BinarySink *get_out_val_string_binarysink(BinarySource *in)
+{
+ Value *val = value_new(VT_string);
+ val->vu_string = strbuf_new();
+ add_finaliser(finaliser_return_value, val);
+ return BinarySink_UPCAST(val->vu_string);
+}
+
+static void return_val_string_asciz_const(strbuf *out, const char *s);
+static void return_val_string_asciz(strbuf *out, char *s);
+
+static void finaliser_return_opt_string_asciz(strbuf *out, void *ctx)
+{
+ char **valp = (char **)ctx;
+ char *val = *valp;
+ sfree(valp);
+ if (!val)
+ put_fmt(out, "NULL\n");
+ else
+ return_val_string_asciz(out, val);
+}
+
+static char **get_out_opt_val_string_asciz(BinarySource *in)
+{
+ char **valp = snew(char *);
+ *valp = NULL;
+ add_finaliser(finaliser_return_opt_string_asciz, valp);
+ return valp;
+}
+
+static void finaliser_return_opt_string_asciz_const(strbuf *out, void *ctx)
+{
+ const char **valp = (const char **)ctx;
+ const char *val = *valp;
+ sfree(valp);
+ if (!val)
+ put_fmt(out, "NULL\n");
+ else
+ return_val_string_asciz_const(out, val);
+}
+
+static const char **get_out_opt_val_string_asciz_const(BinarySource *in)
+{
+ const char **valp = snew(const char *);
+ *valp = NULL;
+ add_finaliser(finaliser_return_opt_string_asciz_const, valp);
+ return valp;
+}
+
+static BinarySource *get_val_string_binarysource(BinarySource *in)
+{
+ strbuf *sb = get_val_string(in);
+ BinarySource *src = snew(BinarySource);
+ BinarySource_BARE_INIT(src, sb->u, sb->len);
+ add_finaliser(finaliser_sfree, src);
+ return src;
+}
+
+#define GET_CONSUMED_FN(type) \
+ typedef TD_val_##type TD_consumed_val_##type; \
+ static TD_val_##type get_consumed_val_##type(BinarySource *in) \
+ { \
+ Value *val = get_value_##type(in); \
+ TD_val_##type toret = val->vu_##type; \
+ del234(values, val); \
+ sfree(val); \
+ return toret; \
+ }
+GET_CONSUMED_FN(hash)
+GET_CONSUMED_FN(pcs)
+
+static void return_int(strbuf *out, intmax_t u)
+{
+ put_fmt(out, "%"PRIdMAX"\n", u);
+}
+
+static void return_uint(strbuf *out, uintmax_t u)
+{
+ put_fmt(out, "0x%"PRIXMAX"\n", u);
+}
+
+static void return_boolean(strbuf *out, bool b)
+{
+ put_fmt(out, "%s\n", b ? "true" : "false");
+}
+
+static void return_pocklestatus(strbuf *out, PockleStatus status)
+{
+ switch (status) {
+ default:
+ put_fmt(out, "POCKLE_BAD_STATUS_VALUE\n");
+ break;
+
+#define STATUS_CASE(id) \
+ case id: \
+ put_fmt(out, "%s\n", #id); \
+ break;
+
+ POCKLE_STATUSES(STATUS_CASE);
+
+#undef STATUS_CASE
+
+ }
+}
+
+static void return_mr_result(strbuf *out, struct mr_result result)
+{
+ if (!result.passed)
+ put_fmt(out, "failed\n");
+ else if (!result.potential_primitive_root)
+ put_fmt(out, "passed\n");
+ else
+ put_fmt(out, "passed+ppr\n");
+}
+
+static void return_val_string_asciz_const(strbuf *out, const char *s)
+{
+ strbuf *sb = strbuf_new();
+ put_data(sb, s, strlen(s));
+ return_val_string(out, sb);
+}
+
+static void return_val_string_asciz(strbuf *out, char *s)
+{
+ return_val_string_asciz_const(out, s);
+ sfree(s);
+}
+
+#define NULLABLE_RETURN_WRAPPER(type_name, c_type) \
+ static void return_opt_##type_name(strbuf *out, c_type ptr) \
+ { \
+ if (!ptr) \
+ put_fmt(out, "NULL\n"); \
+ else \
+ return_##type_name(out, ptr); \
+ }
+
+NULLABLE_RETURN_WRAPPER(val_string, strbuf *)
+NULLABLE_RETURN_WRAPPER(val_string_asciz, char *)
+NULLABLE_RETURN_WRAPPER(val_string_asciz_const, const char *)
+NULLABLE_RETURN_WRAPPER(val_cipher, ssh_cipher *)
+NULLABLE_RETURN_WRAPPER(val_hash, ssh_hash *)
+NULLABLE_RETURN_WRAPPER(val_key, ssh_key *)
+NULLABLE_RETURN_WRAPPER(val_mpint, mp_int *)
+NULLABLE_RETURN_WRAPPER(int16_list, int16_list *)
+
+static void handle_hello(BinarySource *in, strbuf *out)
+{
+ put_fmt(out, "hello, world\n");
+}
+
+static void rsa_free(RSAKey *rsa)
+{
+ freersakey(rsa);
+ sfree(rsa);
+}
+
+static void free_value(Value *val)
+{
+ switch (val->type) {
+#define VALTYPE_FREE(n,t,f) case VT_##n: { t v = val->vu_##n; (f); break; }
+ VALUE_TYPES(VALTYPE_FREE)
+#undef VALTYPE_FREE
+ }
+ sfree(val);
+}
+
+static void handle_free(BinarySource *in, strbuf *out)
+{
+ Value *val = get_value(in);
+ del234(values, val);
+ free_value(val);
+}
+
+static void handle_newstring(BinarySource *in, strbuf *out)
+{
+ strbuf *sb = strbuf_new();
+ while (get_avail(in)) {
+ char c = get_byte(in);
+ if (c == '%') {
+ char hex[3];
+ hex[0] = get_byte(in);
+ if (hex[0] != '%') {
+ hex[1] = get_byte(in);
+ hex[2] = '\0';
+ c = strtoul(hex, NULL, 16);
+ }
+ }
+ put_byte(sb, c);
+ }
+ return_val_string(out, sb);
+}
+
+static void handle_getstring(BinarySource *in, strbuf *out)
+{
+ strbuf *sb = get_val_string(in);
+ for (size_t i = 0; i < sb->len; i++) {
+ char c = sb->s[i];
+ if (c > ' ' && c < 0x7F && c != '%') {
+ put_byte(out, c);
+ } else {
+ put_fmt(out, "%%%02X", 0xFFU & (unsigned)c);
+ }
+ }
+ put_byte(out, '\n');
+}
+
+static void handle_mp_literal(BinarySource *in, strbuf *out)
+{
+ ptrlen pl = get_word(in);
+ char *str = mkstr(pl);
+ mp_int *mp = mp__from_string_literal(str);
+ sfree(str);
+ return_val_mpint(out, mp);
+}
+
+static void handle_mp_dump(BinarySource *in, strbuf *out)
+{
+ mp_int *mp = get_val_mpint(in);
+ for (size_t i = mp_max_bytes(mp); i-- > 0 ;)
+ put_fmt(out, "%02X", mp_get_byte(mp, i));
+ put_byte(out, '\n');
+}
+
+static void handle_checkenum(BinarySource *in, strbuf *out)
+{
+ ptrlen type = get_word(in);
+ ptrlen value = get_word(in);
+ bool ok = false;
+
+ #define BEGIN_ENUM_TYPE(name) \
+ if (ptrlen_eq_string(type, #name)) \
+ ok = enum_translate_##name(value, NULL);
+ #define ENUM_VALUE(name, value)
+ #define END_ENUM_TYPE(name)
+ #include "testcrypt-enum.h"
+ #undef BEGIN_ENUM_TYPE
+ #undef ENUM_VALUE
+ #undef END_ENUM_TYPE
+
+ put_dataz(out, ok ? "ok\n" : "bad\n");
+}
+
+static void random_queue(ptrlen pl)
+{
+ bufchain_add(&random_data_queue, pl.ptr, pl.len);
+}
+
+static size_t random_queue_len(void)
+{
+ return bufchain_size(&random_data_queue);
+}
+
+static void random_clear(void)
+{
+ if (test_prng) {
+ prng_free(test_prng);
+ test_prng = NULL;
+ }
+
+ bufchain_clear(&random_data_queue);
+}
+
+static void random_make_prng(const ssh_hashalg *hashalg, ptrlen seed)
+{
+ random_clear();
+
+ test_prng = prng_new(hashalg);
+ prng_seed_begin(test_prng);
+ put_datapl(test_prng, seed);
+ prng_seed_finish(test_prng);
+}
+
+mp_int *monty_identity_wrapper(MontyContext *mc)
+{
+ return mp_copy(monty_identity(mc));
+}
+
+mp_int *monty_modulus_wrapper(MontyContext *mc)
+{
+ return mp_copy(monty_modulus(mc));
+}
+
+strbuf *ssh_hash_digest_wrapper(ssh_hash *h)
+{
+ strbuf *sb = strbuf_new();
+ void *p = strbuf_append(sb, ssh_hash_alg(h)->hlen);
+ ssh_hash_digest(h, p);
+ return sb;
+}
+
+strbuf *ssh_hash_final_wrapper(ssh_hash *h)
+{
+ strbuf *sb = strbuf_new();
+ void *p = strbuf_append(sb, ssh_hash_alg(h)->hlen);
+ ssh_hash_final(h, p);
+ return sb;
+}
+
+void ssh_cipher_setiv_wrapper(ssh_cipher *c, ptrlen iv)
+{
+ if (iv.len != ssh_cipher_alg(c)->blksize)
+ fatal_error("ssh_cipher_setiv: needs exactly %d bytes",
+ ssh_cipher_alg(c)->blksize);
+ ssh_cipher_setiv(c, iv.ptr);
+}
+
+void ssh_cipher_setkey_wrapper(ssh_cipher *c, ptrlen key)
+{
+ if (key.len != ssh_cipher_alg(c)->padded_keybytes)
+ fatal_error("ssh_cipher_setkey: needs exactly %d bytes",
+ ssh_cipher_alg(c)->padded_keybytes);
+ ssh_cipher_setkey(c, key.ptr);
+}
+
+strbuf *ssh_cipher_encrypt_wrapper(ssh_cipher *c, ptrlen input)
+{
+ if (input.len % ssh_cipher_alg(c)->blksize)
+ fatal_error("ssh_cipher_encrypt: needs a multiple of %d bytes",
+ ssh_cipher_alg(c)->blksize);
+ strbuf *sb = strbuf_dup(input);
+ ssh_cipher_encrypt(c, sb->u, sb->len);
+ return sb;
+}
+
+strbuf *ssh_cipher_decrypt_wrapper(ssh_cipher *c, ptrlen input)
+{
+ if (input.len % ssh_cipher_alg(c)->blksize)
+ fatal_error("ssh_cipher_decrypt: needs a multiple of %d bytes",
+ ssh_cipher_alg(c)->blksize);
+ strbuf *sb = strbuf_dup(input);
+ ssh_cipher_decrypt(c, sb->u, sb->len);
+ return sb;
+}
+
+strbuf *ssh_cipher_encrypt_length_wrapper(ssh_cipher *c, ptrlen input,
+ unsigned long seq)
+{
+ if (input.len != 4)
+ fatal_error("ssh_cipher_encrypt_length: needs exactly 4 bytes");
+ strbuf *sb = strbuf_dup(input);
+ ssh_cipher_encrypt_length(c, sb->u, sb->len, seq);
+ return sb;
+}
+
+strbuf *ssh_cipher_decrypt_length_wrapper(ssh_cipher *c, ptrlen input,
+ unsigned long seq)
+{
+ if (input.len != 4)
+ fatal_error("ssh_cipher_decrypt_length: needs exactly 4 bytes");
+ strbuf *sb = strbuf_dup(input);
+ ssh_cipher_decrypt_length(c, sb->u, sb->len, seq);
+ return sb;
+}
+
+strbuf *ssh2_mac_genresult_wrapper(ssh2_mac *m)
+{
+ strbuf *sb = strbuf_new();
+ void *u = strbuf_append(sb, ssh2_mac_alg(m)->len);
+ ssh2_mac_genresult(m, u);
+ return sb;
+}
+
+ssh_key *ssh_key_base_key_wrapper(ssh_key *key)
+{
+ /* To avoid having to explain the borrowed reference to Python,
+ * just clone the key unconditionally */
+ return ssh_key_clone(ssh_key_base_key(key));
+}
+
+void ssh_key_ca_public_blob_wrapper(ssh_key *key, BinarySink *out)
+{
+ /* Wrap to avoid null-pointer dereference */
+ if (!key->vt->is_certificate)
+ fatal_error("ssh_key_ca_public_blob: needs a certificate");
+ ssh_key_ca_public_blob(key, out);
+}
+
+void ssh_key_cert_id_string_wrapper(ssh_key *key, BinarySink *out)
+{
+ /* Wrap to avoid null-pointer dereference */
+ if (!key->vt->is_certificate)
+ fatal_error("ssh_key_cert_id_string: needs a certificate");
+ ssh_key_cert_id_string(key, out);
+}
+
+static bool ssh_key_check_cert_wrapper(
+ ssh_key *key, bool host, ptrlen principal, uint64_t time, ptrlen optstr,
+ BinarySink *error)
+{
+ /* Wrap to avoid null-pointer dereference */
+ if (!key->vt->is_certificate)
+ fatal_error("ssh_key_cert_id_string: needs a certificate");
+
+ ca_options opts;
+ opts.permit_rsa_sha1 = true;
+ opts.permit_rsa_sha256 = true;
+ opts.permit_rsa_sha512 = true;
+
+ while (optstr.len) {
+ ptrlen word = ptrlen_get_word(&optstr, ",");
+ ptrlen key = word, value = PTRLEN_LITERAL("");
+ const char *comma = memchr(word.ptr, '=', word.len);
+ if (comma) {
+ key.len = comma - (const char *)word.ptr;
+ value.ptr = comma + 1;
+ value.len = word.len - key.len - 1;
+ }
+
+ if (ptrlen_eq_string(key, "permit_rsa_sha1"))
+ opts.permit_rsa_sha1 = ptrlen_eq_string(value, "true");
+ if (ptrlen_eq_string(key, "permit_rsa_sha256"))
+ opts.permit_rsa_sha256 = ptrlen_eq_string(value, "true");
+ if (ptrlen_eq_string(key, "permit_rsa_sha512"))
+ opts.permit_rsa_sha512 = ptrlen_eq_string(value, "true");
+ }
+
+ return ssh_key_check_cert(key, host, principal, time, &opts, error);
+}
+
+bool dh_validate_f_wrapper(dh_ctx *dh, mp_int *f)
+{
+ return dh_validate_f(dh, f) == NULL;
+}
+
+void ssh_hash_update(ssh_hash *h, ptrlen pl)
+{
+ put_datapl(h, pl);
+}
+
+void ssh2_mac_update(ssh2_mac *m, ptrlen pl)
+{
+ put_datapl(m, pl);
+}
+
+static RSAKey *rsa_new(void)
+{
+ RSAKey *rsa = snew(RSAKey);
+ memset(rsa, 0, sizeof(RSAKey));
+ return rsa;
+}
+
+strbuf *ecdh_key_getkey_wrapper(ecdh_key *ek, ptrlen remoteKey)
+{
+ /* Fold the boolean return value in C into the string return value
+ * for this purpose, by returning NULL on failure */
+ strbuf *sb = strbuf_new();
+ if (!ecdh_key_getkey(ek, remoteKey, BinarySink_UPCAST(sb))) {
+ strbuf_free(sb);
+ return NULL;
+ }
+ return sb;
+}
+
+static void int16_list_resize(int16_list *list, unsigned p)
+{
+ list->integers = sresize(list->integers, p, uint16_t);
+ for (size_t i = list->n; i < p; i++)
+ list->integers[i] = 0;
+}
+
+#if 0
+static int16_list ntru_ring_to_list_and_free(uint16_t *out, unsigned p)
+{
+ struct mpint_list mpl;
+ mpl.n = p;
+ mpl->integers = snewn(p, mp_int *);
+ for (unsigned i = 0; i < p; i++)
+ mpl->integers[i] = mp_from_integer((int16_t)out[i]);
+ sfree(out);
+ add_finaliser(finaliser_sfree, mpl->integers);
+ return mpl;
+}
+#endif
+
+int16_list *ntru_ring_multiply_wrapper(
+ int16_list *a, int16_list *b, unsigned p, unsigned q)
+{
+ int16_list_resize(a, p);
+ int16_list_resize(b, p);
+ int16_list *out = make_int16_list(p);
+ ntru_ring_multiply(out->integers, a->integers, b->integers, p, q);
+ return out;
+}
+
+int16_list *ntru_ring_invert_wrapper(int16_list *in, unsigned p, unsigned q)
+{
+ int16_list_resize(in, p);
+ int16_list *out = make_int16_list(p);
+ unsigned success = ntru_ring_invert(out->integers, in->integers, p, q);
+ if (!success)
+ return NULL;
+ return out;
+}
+
+int16_list *ntru_mod3_wrapper(int16_list *in, unsigned p, unsigned q)
+{
+ int16_list_resize(in, p);
+ int16_list *out = make_int16_list(p);
+ ntru_mod3(out->integers, in->integers, p, q);
+ return out;
+}
+
+int16_list *ntru_round3_wrapper(int16_list *in, unsigned p, unsigned q)
+{
+ int16_list_resize(in, p);
+ int16_list *out = make_int16_list(p);
+ ntru_round3(out->integers, in->integers, p, q);
+ return out;
+}
+
+int16_list *ntru_bias_wrapper(int16_list *in, unsigned bias,
+ unsigned p, unsigned q)
+{
+ int16_list_resize(in, p);
+ int16_list *out = make_int16_list(p);
+ ntru_bias(out->integers, in->integers, bias, p, q);
+ return out;
+}
+
+int16_list *ntru_scale_wrapper(int16_list *in, unsigned scale,
+ unsigned p, unsigned q)
+{
+ int16_list_resize(in, p);
+ int16_list *out = make_int16_list(p);
+ ntru_scale(out->integers, in->integers, scale, p, q);
+ return out;
+}
+
+NTRUEncodeSchedule *ntru_encode_schedule_wrapper(int16_list *in)
+{
+ return ntru_encode_schedule(in->integers, in->n);
+}
+
+void ntru_encode_wrapper(NTRUEncodeSchedule *sched, int16_list *rs,
+ BinarySink *bs)
+{
+ ntru_encode(sched, rs->integers, bs);
+}
+
+int16_list *ntru_decode_wrapper(NTRUEncodeSchedule *sched, ptrlen data)
+{
+ int16_list *out = make_int16_list(ntru_encode_schedule_nvals(sched));
+ ntru_decode(sched, out->integers, data);
+ return out;
+}
+
+int16_list *ntru_gen_short_wrapper(unsigned p, unsigned w)
+{
+ int16_list *out = make_int16_list(p);
+ ntru_gen_short(out->integers, p, w);
+ return out;
+}
+
+int16_list *ntru_pubkey_wrapper(NTRUKeyPair *keypair)
+{
+ unsigned p = ntru_keypair_p(keypair);
+ int16_list *out = make_int16_list(p);
+ memcpy(out->integers, ntru_pubkey(keypair), p*sizeof(uint16_t));
+ return out;
+}
+
+int16_list *ntru_encrypt_wrapper(int16_list *plaintext, int16_list *pubkey,
+ unsigned p, unsigned q)
+{
+ int16_list *out = make_int16_list(p);
+ ntru_encrypt(out->integers, plaintext->integers, pubkey->integers, p, q);
+ return out;
+}
+
+int16_list *ntru_decrypt_wrapper(int16_list *ciphertext, NTRUKeyPair *keypair)
+{
+ unsigned p = ntru_keypair_p(keypair);
+ int16_list *out = make_int16_list(p);
+ ntru_decrypt(out->integers, ciphertext->integers, keypair);
+ return out;
+}
+
+strbuf *rsa_ssh1_encrypt_wrapper(ptrlen input, RSAKey *key)
+{
+ /* Fold the boolean return value in C into the string return value
+ * for this purpose, by returning NULL on failure */
+ strbuf *sb = strbuf_new();
+ put_datapl(sb, input);
+ put_padding(sb, key->bytes - input.len, 0);
+ if (!rsa_ssh1_encrypt(sb->u, input.len, key)) {
+ strbuf_free(sb);
+ return NULL;
+ }
+ return sb;
+}
+
+strbuf *rsa_ssh1_decrypt_pkcs1_wrapper(mp_int *input, RSAKey *key)
+{
+ /* Again, return "" on failure */
+ strbuf *sb = strbuf_new();
+ if (!rsa_ssh1_decrypt_pkcs1(input, key, sb))
+ strbuf_clear(sb);
+ return sb;
+}
+
+strbuf *des_encrypt_xdmauth_wrapper(ptrlen key, ptrlen data)
+{
+ if (key.len != 7)
+ fatal_error("des_encrypt_xdmauth: key must be 7 bytes long");
+ if (data.len % 8 != 0)
+ fatal_error("des_encrypt_xdmauth: data must be a multiple of 8 bytes");
+ strbuf *sb = strbuf_dup(data);
+ des_encrypt_xdmauth(key.ptr, sb->u, sb->len);
+ return sb;
+}
+
+strbuf *des_decrypt_xdmauth_wrapper(ptrlen key, ptrlen data)
+{
+ if (key.len != 7)
+ fatal_error("des_decrypt_xdmauth: key must be 7 bytes long");
+ if (data.len % 8 != 0)
+ fatal_error("des_decrypt_xdmauth: data must be a multiple of 8 bytes");
+ strbuf *sb = strbuf_dup(data);
+ des_decrypt_xdmauth(key.ptr, sb->u, sb->len);
+ return sb;
+}
+
+strbuf *des3_encrypt_pubkey_wrapper(ptrlen key, ptrlen data)
+{
+ if (key.len != 16)
+ fatal_error("des3_encrypt_pubkey: key must be 16 bytes long");
+ if (data.len % 8 != 0)
+ fatal_error("des3_encrypt_pubkey: data must be a multiple of 8 bytes");
+ strbuf *sb = strbuf_dup(data);
+ des3_encrypt_pubkey(key.ptr, sb->u, sb->len);
+ return sb;
+}
+
+strbuf *des3_decrypt_pubkey_wrapper(ptrlen key, ptrlen data)
+{
+ if (key.len != 16)
+ fatal_error("des3_decrypt_pubkey: key must be 16 bytes long");
+ if (data.len % 8 != 0)
+ fatal_error("des3_decrypt_pubkey: data must be a multiple of 8 bytes");
+ strbuf *sb = strbuf_dup(data);
+ des3_decrypt_pubkey(key.ptr, sb->u, sb->len);
+ return sb;
+}
+
+strbuf *des3_encrypt_pubkey_ossh_wrapper(ptrlen key, ptrlen iv, ptrlen data)
+{
+ if (key.len != 24)
+ fatal_error("des3_encrypt_pubkey_ossh: key must be 24 bytes long");
+ if (iv.len != 8)
+ fatal_error("des3_encrypt_pubkey_ossh: iv must be 8 bytes long");
+ if (data.len % 8 != 0)
+ fatal_error("des3_encrypt_pubkey_ossh: data must be a multiple of 8 bytes");
+ strbuf *sb = strbuf_dup(data);
+ des3_encrypt_pubkey_ossh(key.ptr, iv.ptr, sb->u, sb->len);
+ return sb;
+}
+
+strbuf *des3_decrypt_pubkey_ossh_wrapper(ptrlen key, ptrlen iv, ptrlen data)
+{
+ if (key.len != 24)
+ fatal_error("des3_decrypt_pubkey_ossh: key must be 24 bytes long");
+ if (iv.len != 8)
+ fatal_error("des3_encrypt_pubkey_ossh: iv must be 8 bytes long");
+ if (data.len % 8 != 0)
+ fatal_error("des3_decrypt_pubkey_ossh: data must be a multiple of 8 bytes");
+ strbuf *sb = strbuf_dup(data);
+ des3_decrypt_pubkey_ossh(key.ptr, iv.ptr, sb->u, sb->len);
+ return sb;
+}
+
+strbuf *aes256_encrypt_pubkey_wrapper(ptrlen key, ptrlen iv, ptrlen data)
+{
+ if (key.len != 32)
+ fatal_error("aes256_encrypt_pubkey: key must be 32 bytes long");
+ if (iv.len != 16)
+ fatal_error("aes256_encrypt_pubkey: iv must be 16 bytes long");
+ if (data.len % 16 != 0)
+ fatal_error("aes256_encrypt_pubkey: data must be a multiple of 16 bytes");
+ strbuf *sb = strbuf_dup(data);
+ aes256_encrypt_pubkey(key.ptr, iv.ptr, sb->u, sb->len);
+ return sb;
+}
+
+strbuf *aes256_decrypt_pubkey_wrapper(ptrlen key, ptrlen iv, ptrlen data)
+{
+ if (key.len != 32)
+ fatal_error("aes256_decrypt_pubkey: key must be 32 bytes long");
+ if (iv.len != 16)
+ fatal_error("aes256_encrypt_pubkey: iv must be 16 bytes long");
+ if (data.len % 16 != 0)
+ fatal_error("aes256_decrypt_pubkey: data must be a multiple of 16 bytes");
+ strbuf *sb = strbuf_dup(data);
+ aes256_decrypt_pubkey(key.ptr, iv.ptr, sb->u, sb->len);
+ return sb;
+}
+
+strbuf *prng_read_wrapper(prng *pr, size_t size)
+{
+ strbuf *sb = strbuf_new();
+ prng_read(pr, strbuf_append(sb, size), size);
+ return sb;
+}
+
+void prng_seed_update(prng *pr, ptrlen data)
+{
+ put_datapl(pr, data);
+}
+
+bool crcda_detect(ptrlen packet, ptrlen iv)
+{
+ if (iv.len != 0 && iv.len != 8)
+ fatal_error("crcda_detect: iv must be empty or 8 bytes long");
+ if (packet.len % 8 != 0)
+ fatal_error("crcda_detect: packet must be a multiple of 8 bytes");
+ struct crcda_ctx *ctx = crcda_make_context();
+ bool toret = detect_attack(ctx, packet.ptr, packet.len,
+ iv.len ? iv.ptr : NULL);
+ crcda_free_context(ctx);
+ return toret;
+}
+
+ssh_key *ppk_load_s_wrapper(BinarySource *src, char **comment,
+ const char *passphrase, const char **errorstr)
+{
+ ssh2_userkey *uk = ppk_load_s(src, passphrase, errorstr);
+ if (uk == SSH2_WRONG_PASSPHRASE) {
+ /* Fudge this special return value */
+ *errorstr = "SSH2_WRONG_PASSPHRASE";
+ return NULL;
+ }
+ if (uk == NULL)
+ return NULL;
+ ssh_key *toret = uk->key;
+ *comment = uk->comment;
+ sfree(uk);
+ return toret;
+}
+
+int rsa1_load_s_wrapper(BinarySource *src, RSAKey *rsa, char **comment,
+ const char *passphrase, const char **errorstr)
+{
+ int toret = rsa1_load_s(src, rsa, passphrase, errorstr);
+ *comment = rsa->comment;
+ rsa->comment = NULL;
+ return toret;
+}
+
+strbuf *ppk_save_sb_wrapper(
+ ssh_key *key, const char *comment, const char *passphrase,
+ unsigned fmt_version, Argon2Flavour flavour,
+ uint32_t mem, uint32_t passes, uint32_t parallel)
+{
+ /*
+ * For repeatable testing purposes, we never want a timing-dependent
+ * choice of password hashing parameters, so this is easy.
+ */
+ ppk_save_parameters save_params;
+ memset(&save_params, 0, sizeof(save_params));
+ save_params.fmt_version = fmt_version;
+ save_params.argon2_flavour = flavour;
+ save_params.argon2_mem = mem;
+ save_params.argon2_passes_auto = false;
+ save_params.argon2_passes = passes;
+ save_params.argon2_parallelism = parallel;
+
+ ssh2_userkey uk;
+ uk.key = key;
+ uk.comment = dupstr(comment);
+ strbuf *toret = ppk_save_sb(&uk, passphrase, &save_params);
+ sfree(uk.comment);
+ return toret;
+}
+
+strbuf *rsa1_save_sb_wrapper(RSAKey *key, const char *comment,
+ const char *passphrase)
+{
+ key->comment = dupstr(comment);
+ strbuf *toret = rsa1_save_sb(key, passphrase);
+ sfree(key->comment);
+ key->comment = NULL;
+ return toret;
+}
+
+#define return_void(out, expression) (expression)
+
+static ProgressReceiver null_progress = { .vt = &null_progress_vt };
+
+mp_int *primegen_generate_wrapper(
+ PrimeGenerationContext *ctx, PrimeCandidateSource *pcs)
+{
+ return primegen_generate(ctx, pcs, &null_progress);
+}
+
+RSAKey *rsa1_generate(int bits, bool strong, PrimeGenerationContext *pgc)
+{
+ RSAKey *rsakey = snew(RSAKey);
+ rsa_generate(rsakey, bits, strong, pgc, &null_progress);
+ rsakey->comment = NULL;
+ return rsakey;
+}
+
+ssh_key *rsa_generate_wrapper(int bits, bool strong,
+ PrimeGenerationContext *pgc)
+{
+ return &rsa1_generate(bits, strong, pgc)->sshk;
+}
+
+ssh_key *dsa_generate_wrapper(int bits, PrimeGenerationContext *pgc)
+{
+ struct dsa_key *dsakey = snew(struct dsa_key);
+ dsa_generate(dsakey, bits, pgc, &null_progress);
+ return &dsakey->sshk;
+}
+
+ssh_key *ecdsa_generate_wrapper(int bits)
+{
+ struct ecdsa_key *ek = snew(struct ecdsa_key);
+ if (!ecdsa_generate(ek, bits)) {
+ sfree(ek);
+ return NULL;
+ }
+ return &ek->sshk;
+}
+
+ssh_key *eddsa_generate_wrapper(int bits)
+{
+ struct eddsa_key *ek = snew(struct eddsa_key);
+ if (!eddsa_generate(ek, bits)) {
+ sfree(ek);
+ return NULL;
+ }
+ return &ek->sshk;
+}
+
+size_t key_components_count(key_components *kc) { return kc->ncomponents; }
+const char *key_components_nth_name(key_components *kc, size_t n)
+{
+ return (n >= kc->ncomponents ? NULL :
+ kc->components[n].name);
+}
+strbuf *key_components_nth_str(key_components *kc, size_t n)
+{
+ if (n >= kc->ncomponents)
+ return NULL;
+ if (kc->components[n].type != KCT_TEXT &&
+ kc->components[n].type != KCT_BINARY)
+ return NULL;
+ return strbuf_dup(ptrlen_from_strbuf(kc->components[n].str));
+}
+mp_int *key_components_nth_mp(key_components *kc, size_t n)
+{
+ return (n >= kc->ncomponents ? NULL :
+ kc->components[n].type != KCT_MPINT ? NULL :
+ mp_copy(kc->components[n].mp));
+}
+
+PockleStatus pockle_add_prime_wrapper(Pockle *pockle, mp_int *p,
+ struct mpint_list mpl, mp_int *witness)
+{
+ return pockle_add_prime(pockle, p, mpl.integers, mpl.n, witness);
+}
+
+strbuf *argon2_wrapper(Argon2Flavour flavour, uint32_t mem, uint32_t passes,
+ uint32_t parallel, uint32_t taglen,
+ ptrlen P, ptrlen S, ptrlen K, ptrlen X)
+{
+ strbuf *out = strbuf_new();
+ argon2(flavour, mem, passes, parallel, taglen, P, S, K, X, out);
+ return out;
+}
+
+strbuf *openssh_bcrypt_wrapper(ptrlen passphrase, ptrlen salt,
+ unsigned rounds, unsigned outbytes)
+{
+ strbuf *out = strbuf_new();
+ openssh_bcrypt(passphrase, salt, rounds,
+ strbuf_append(out, outbytes), outbytes);
+ return out;
+}
+
+strbuf *get_implementations_commasep(ptrlen alg)
+{
+ strbuf *out = strbuf_new();
+ put_datapl(out, alg);
+
+ if (ptrlen_startswith(alg, PTRLEN_LITERAL("aesgcm"), NULL)) {
+ put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg));
+ put_fmt(out, ",%.*s_ref_poly", PTRLEN_PRINTF(alg));
+#if HAVE_CLMUL
+ put_fmt(out, ",%.*s_clmul", PTRLEN_PRINTF(alg));
+#endif
+#if HAVE_NEON_PMULL
+ put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg));
+#endif
+ } else if (ptrlen_startswith(alg, PTRLEN_LITERAL("aes"), NULL)) {
+ put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg));
+#if HAVE_AES_NI
+ put_fmt(out, ",%.*s_ni", PTRLEN_PRINTF(alg));
+#endif
+#if HAVE_NEON_CRYPTO
+ put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg));
+#endif
+ } else if (ptrlen_startswith(alg, PTRLEN_LITERAL("sha256"), NULL) ||
+ ptrlen_startswith(alg, PTRLEN_LITERAL("sha1"), NULL)) {
+ put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg));
+#if HAVE_SHA_NI
+ put_fmt(out, ",%.*s_ni", PTRLEN_PRINTF(alg));
+#endif
+#if HAVE_NEON_CRYPTO
+ put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg));
+#endif
+ } else if (ptrlen_startswith(alg, PTRLEN_LITERAL("sha512"), NULL)) {
+ put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg));
+#if HAVE_NEON_SHA512
+ put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg));
+#endif
+ }
+
+ return out;
+}
+
+#define OPTIONAL_PTR_FUNC(type) \
+ typedef TD_val_##type TD_opt_val_##type; \
+ static TD_opt_val_##type get_opt_val_##type(BinarySource *in) { \
+ ptrlen word = get_word(in); \
+ if (ptrlen_eq_string(word, "NULL")) \
+ return NULL; \
+ return unwrap_value_##type(lookup_value(word))->vu_##type; \
+ }
+OPTIONAL_PTR_FUNC(cipher)
+OPTIONAL_PTR_FUNC(mpint)
+OPTIONAL_PTR_FUNC(string)
+
+/*
+ * HERE BE DRAGONS: the horrible C preprocessor business that reads
+ * testcrypt-func.h and generates a marshalling wrapper for each
+ * exported function.
+ *
+ * In an ideal world, we would start from a specification like this in
+ * testcrypt-func.h
+ *
+ * FUNC(val_foo, example, ARG(val_bar, bar), ARG(uint, n))
+ *
+ * and generate a wrapper function looking like this:
+ *
+ * static void handle_example(BinarySource *in, strbuf *out) {
+ * TD_val_bar bar = get_val_bar(in);
+ * TD_uint n = get_uint(in);
+ * return_val_foo(out, example(bar, n));
+ * }
+ *
+ * which would read the marshalled form of each function argument in
+ * turn from the input BinarySource via the get_<type>() function
+ * family defined in this file; assign each argument to a local
+ * variable; call the underlying C function with all those arguments;
+ * and then call a function of the return_<type>() family to marshal
+ * the output value into the output strbuf to be sent to standard
+ * output.
+ *
+ * With a more general macro processor such as m4, or custom code in
+ * Perl or Python, or a helper program like llvm-tblgen, we could just
+ * do that directly, reading function specifications from
+ * testcrypt-func.h and writing out exactly the above. But we don't
+ * have a fully general macro processor (since everything in that
+ * category introduces an extra build dependency that's awkward on
+ * plain Windows, or requires compiling and running a helper program
+ * which is awkward in a cross-compile). We only have cpp. And in cpp,
+ * a macro can't expand one of its arguments differently in two parts
+ * of its own expansion. So we have to be more clever.
+ *
+ * In place of the above code, I instead generate three successive
+ * declarations for each function. In simplified form they would look
+ * like this:
+ *
+ * typedef struct ARGS_example {
+ * TD_val_bar bar;
+ * TD_uint n;
+ * } ARGS_example;
+ *
+ * static inline ARGS_example get_args_example(BinarySource *in) {
+ * ARGS_example args;
+ * args.bar = get_val_bar(in);
+ * args.n = get_uint(in);
+ * return args;
+ * }
+ *
+ * static void handle_example(BinarySource *in, strbuf *out) {
+ * ARGS_example args = get_args_example(in);
+ * return_val_foo(out, example(args.bar, args.n));
+ * }
+ *
+ * Each of these mentions the arguments and their types just _once_,
+ * so each one can be generated by a single expansion of the FUNC(...)
+ * specification in testcrypt-func.h, with FUNC and ARG and VOID
+ * defined to appropriate values.
+ *
+ * Or ... *nearly*. In fact, I left out several details there, but
+ * it's a good starting point to understand the full version.
+ *
+ * To begin with, several of the variable names shown above are
+ * actually named with an ugly leading underscore, to minimise the
+ * chance of them colliding with real parameter names. (You could
+ * easily imagine 'out' being the name of a parameter to one of the
+ * wrapped functions.) Also, we memset the whole structure to zero at
+ * the start of get_args_example() to avoid compiler warnings about
+ * uninitialised stuff, and insert a precautionary '(void)args;' in
+ * handle_example to avoid a similar warning about _unused_ stuff.
+ *
+ * The big problem is the commas that have to appear between arguments
+ * in the final call to the actual C function. Those can't be
+ * generated by expanding the ARG macro itself, or you'd get one too
+ * many - either a leading comma or a trailing comma. Trailing commas
+ * are legal in a Python function call, but unfortunately C is not yet
+ * so enlightened. (C permits a trailing comma in a struct or array
+ * initialiser, and is coming round to it in enums, but hasn't yet
+ * seen the light about function calls or function prototypes.)
+ *
+ * So the commas must appear _between_ ARG(...) specifiers. And that
+ * means they unavoidably appear in _every_ expansion of FUNC() (or
+ * rather, every expansion that uses the variadic argument list at
+ * all). Therefore, we need to ensure they're harmless in the other
+ * two functions as well.
+ *
+ * In the get_args_example() function above, there's no real problem.
+ * The list of assignments can perfectly well be separated by commas
+ * instead of semicolons, so that it becomes a single expression-
+ * statement instead of a sequence of them; the comma operator still
+ * defines a sequence point, so it's fine.
+ *
+ * But what about the structure definition of ARGS_example?
+ *
+ * To get round that, we fill the structure with pointless extra
+ * cruft, in the form of an extra 'int' field before and after each
+ * actually useful argument field. So the real structure definition
+ * ends up looking more like this:
+ *
+ * typedef struct ARGS_example {
+ * int _predummy_bar;
+ * TD_val_bar bar;
+ * int _postdummy_bar, _predummy_n;
+ * TD_uint n;
+ * int _postdummy_n;
+ * } ARGS_example;
+ *
+ * Those extra 'int' fields are ignored completely at run time. They
+ * might cause a runtime space cost if the struct doesn't get
+ * completely optimised away when get_args_example is inlined into
+ * handle_example, but even if so, that's OK, this is a test program
+ * whose memory usage isn't critical. The real point is that, in
+ * between each pair of real arguments, there's a declaration
+ * containing *two* int variables, and in between them is the vital
+ * comma that we need!
+ *
+ * So in that pass through testcrypt-func.h, the ARG(type, name) macro
+ * has to expand to the weird piece of text
+ *
+ * _predummy_name; // terminating the previous int declaration
+ * TD_type name; // declaring the thing we actually wanted
+ * int _postdummy_name // new declaration ready to see a comma
+ *
+ * so that a comma-separated list of pieces of expansion like that
+ * will fall into just the right form to be the core of the above
+ * expanded structure definition. Then we just need to put in the
+ * 'int' after the open brace, and the ';' before the closing brace,
+ * and we've got everything we need to make it all syntactically legal.
+ *
+ * Finally, what if a wrapped function has _no_ arguments? Two out of
+ * three uses of the argument list here need some kind of special case
+ * for that. That's why you have to write 'VOID' explicitly in an
+ * empty argument list in testcrypt-func.h: we make VOID expand to
+ * whatever is needed to avoid a syntax error in that special case.
+ */
+
+/*
+ * Workarounds for an awkwardness in Visual Studio's preprocessor,
+ * which disagrees with everyone else about what happens if you expand
+ * __VA_ARGS__ into the argument list of another macro. gcc and clang
+ * will treat the commas expanding from __VA_ARGS__ as argument
+ * separators, whereas VS will make them all part of a single argument
+ * to the secondary macro. We want the former behaviour, so we use
+ * the following workaround to enforce it.
+ *
+ * Each of these JUXTAPOSE macros simply places its arguments side by
+ * side. But the arguments are macro-expanded before JUXTAPOSE is
+ * called at all, so we can do this:
+ *
+ * JUXTAPOSE(macroname, (__VA_ARGS__))
+ * -> JUXTAPOSE(macroname, (foo, bar, baz))
+ * -> macroname (foo, bar, baz)
+ *
+ * and this preliminary expansion causes the commas to be treated
+ * normally by the time VS gets round to expanding the inner macro.
+ *
+ * We need two differently named JUXTAPOSE macros, because we have to
+ * do this trick twice: once to turn FUNC and FUNC_WRAPPED in
+ * testcrypt-funcs.h into the underlying common FUNC_INNER, and again
+ * to expand the final function call. And you can't expand a macro
+ * inside text expanded from the _same_ macro, so we have to do the
+ * outer and inner instances of this trick using macros of different
+ * names.
+ */
+#define JUXTAPOSE1(first, second) first second
+#define JUXTAPOSE2(first, second) first second
+
+#define FUNC(outtype, fname, ...) \
+ JUXTAPOSE1(FUNC_INNER, (outtype, fname, fname, __VA_ARGS__))
+#define FUNC_WRAPPED(outtype, fname, ...) \
+ JUXTAPOSE1(FUNC_INNER, (outtype, fname, fname##_wrapper, __VA_ARGS__))
+
+#define ARG(type, arg) _predummy_##arg; TD_##type arg; int _postdummy_##arg
+#define VOID _voiddummy
+#define FUNC_INNER(outtype, fname, realname, ...) \
+ typedef struct ARGS_##fname { \
+ int __VA_ARGS__; \
+ } ARGS_##fname;
+#include "testcrypt-func.h"
+#undef FUNC_INNER
+#undef ARG
+#undef VOID
+
+#define ARG(type, arg) _args.arg = get_##type(_in)
+#define VOID ((void)0)
+#define FUNC_INNER(outtype, fname, realname, ...) \
+ static inline ARGS_##fname get_args_##fname(BinarySource *_in) { \
+ ARGS_##fname _args; \
+ memset(&_args, 0, sizeof(_args)); \
+ __VA_ARGS__; \
+ return _args; \
+ }
+#include "testcrypt-func.h"
+#undef FUNC_INNER
+#undef ARG
+#undef VOID
+
+#define ARG(type, arg) _args.arg
+#define VOID
+#define FUNC_INNER(outtype, fname, realname, ...) \
+ static void handle_##fname(BinarySource *_in, strbuf *_out) { \
+ ARGS_##fname _args = get_args_##fname(_in); \
+ (void)_args; /* suppress warning if no actual arguments */ \
+ return_##outtype(_out, JUXTAPOSE2(realname, (__VA_ARGS__))); \
+ }
+#include "testcrypt-func.h"
+#undef FUNC_INNER
+#undef ARG
+
+static void process_line(BinarySource *in, strbuf *out)
+{
+ ptrlen id = get_word(in);
+
+#define DISPATCH_INTERNAL(cmdname, handler) do { \
+ if (ptrlen_eq_string(id, cmdname)) { \
+ handler(in, out); \
+ return; \
+ } \
+ } while (0)
+
+#define DISPATCH_COMMAND(cmd) DISPATCH_INTERNAL(#cmd, handle_##cmd)
+ DISPATCH_COMMAND(hello);
+ DISPATCH_COMMAND(free);
+ DISPATCH_COMMAND(newstring);
+ DISPATCH_COMMAND(getstring);
+ DISPATCH_COMMAND(mp_literal);
+ DISPATCH_COMMAND(mp_dump);
+ DISPATCH_COMMAND(checkenum);
+#undef DISPATCH_COMMAND
+
+#define FUNC_INNER(outtype, fname, realname, ...) \
+ DISPATCH_INTERNAL(#fname,handle_##fname);
+#define ARG1(type, arg)
+#define ARGN(type, arg)
+#define VOID
+#include "testcrypt-func.h"
+#undef FUNC_INNER
+#undef ARG
+#undef VOID
+
+#undef DISPATCH_INTERNAL
+
+ fatal_error("command '%.*s': unrecognised", PTRLEN_PRINTF(id));
+}
+
+static void free_all_values(void)
+{
+ for (Value *val; (val = delpos234(values, 0)) != NULL ;)
+ free_value(val);
+ freetree234(values);
+}
+
+void dputs(const char *buf)
+{
+ fputs(buf, stderr);
+}
+
+int main(int argc, char **argv)
+{
+ const char *infile = NULL, *outfile = NULL;
+ bool doing_opts = true;
+
+ while (--argc > 0) {
+ char *p = *++argv;
+
+ if (p[0] == '-' && doing_opts) {
+ if (!strcmp(p, "-o")) {
+ if (--argc <= 0) {
+ fprintf(stderr, "'-o' expects a filename\n");
+ return 1;
+ }
+ outfile = *++argv;
+ } else if (!strcmp(p, "--")) {
+ doing_opts = false;
+ } else if (!strcmp(p, "--help")) {
+ printf("usage: testcrypt [INFILE] [-o OUTFILE]\n");
+ printf(" also: testcrypt --help display this text\n");
+ return 0;
+ } else {
+ fprintf(stderr, "unknown command line option '%s'\n", p);
+ return 1;
+ }
+ } else if (!infile) {
+ infile = p;
+ } else {
+ fprintf(stderr, "can only handle one input file name\n");
+ return 1;
+ }
+ }
+
+ FILE *infp = stdin;
+ if (infile) {
+ infp = fopen(infile, "r");
+ if (!infp) {
+ fprintf(stderr, "%s: open: %s\n", infile, strerror(errno));
+ return 1;
+ }
+ }
+
+ FILE *outfp = stdout;
+ if (outfile) {
+ outfp = fopen(outfile, "w");
+ if (!outfp) {
+ fprintf(stderr, "%s: open: %s\n", outfile, strerror(errno));
+ return 1;
+ }
+ }
+
+ values = newtree234(valuecmp);
+
+ atexit(free_all_values);
+
+ for (char *line; (line = chomp(fgetline(infp))) != NULL ;) {
+ BinarySource src[1];
+ BinarySource_BARE_INIT(src, line, strlen(line));
+ strbuf *sb = strbuf_new();
+ process_line(src, sb);
+ run_finalisers(sb);
+ size_t lines = 0;
+ for (size_t i = 0; i < sb->len; i++)
+ if (sb->s[i] == '\n')
+ lines++;
+ fprintf(outfp, "%"SIZEu"\n%s", lines, sb->s);
+ fflush(outfp);
+ strbuf_free(sb);
+ sfree(line);
+ }
+
+ if (infp != stdin)
+ fclose(infp);
+ if (outfp != stdin)
+ fclose(outfp);
+
+ return 0;
+}
diff --git a/test/testcrypt.py b/test/testcrypt.py
index 686302c8..66f63d5c 100644
--- a/test/testcrypt.py
+++ b/test/testcrypt.py
@@ -3,6 +3,7 @@ import os
import numbers
import subprocess
import re
+import string
import struct
from binascii import hexlify
@@ -86,22 +87,25 @@ class ChildProcess(object):
childprocess = ChildProcess()
method_prefixes = {
- 'val_wpoint': 'ecc_weierstrass_',
- 'val_mpoint': 'ecc_montgomery_',
- 'val_epoint': 'ecc_edwards_',
- 'val_hash': 'ssh_hash_',
- 'val_mac': 'ssh2_mac_',
- 'val_key': 'ssh_key_',
- 'val_cipher': 'ssh_cipher_',
- 'val_dh': 'dh_',
- 'val_ecdh': 'ssh_ecdhkex_',
- 'val_rsakex': 'ssh_rsakex_',
- 'val_prng': 'prng_',
- 'val_pcs': 'pcs_',
- 'val_pockle': 'pockle_',
+ 'val_wpoint': ['ecc_weierstrass_'],
+ 'val_mpoint': ['ecc_montgomery_'],
+ 'val_epoint': ['ecc_edwards_'],
+ 'val_hash': ['ssh_hash_'],
+ 'val_mac': ['ssh2_mac_'],
+ 'val_key': ['ssh_key_'],
+ 'val_cipher': ['ssh_cipher_'],
+ 'val_dh': ['dh_'],
+ 'val_ecdh': ['ssh_ecdhkex_'],
+ 'val_rsakex': ['ssh_rsakex_'],
+ 'val_prng': ['prng_'],
+ 'val_pcs': ['pcs_'],
+ 'val_pockle': ['pockle_'],
+ 'val_ntruencodeschedule': ['ntru_encode_schedule_', 'ntru_'],
}
method_lists = {t: [] for t in method_prefixes}
+checked_enum_values = {}
+
class Value(object):
def __init__(self, typename, ident):
self._typename = typename
@@ -148,7 +152,7 @@ def marshal_string(val):
else "%{:02x}".format(b)
for b in val)
-def make_argword(arg, argtype, fnname, argindex, to_preserve):
+def make_argword(arg, argtype, fnname, argindex, argname, to_preserve):
typename, consumed = argtype
if typename.startswith("opt_"):
if arg is None:
@@ -165,8 +169,8 @@ def make_argword(arg, argtype, fnname, argindex, to_preserve):
if isinstance(arg, Value):
if arg._typename != typename:
raise TypeError(
- "{}() argument {:d} should be {} ({} given)".format(
- fnname, argindex, typename, arg._typename))
+ "{}() argument #{:d} ({}) should be {} ({} given)".format(
+ fnname, argindex, argname, typename, arg._typename))
ident = arg._ident
if consumed:
arg._consumed()
@@ -178,20 +182,33 @@ def make_argword(arg, argtype, fnname, argindex, to_preserve):
if typename in {
"hashalg", "macalg", "keyalg", "cipheralg",
"dh_group", "ecdh_alg", "rsaorder", "primegenpolicy",
- "argon2flavour", "fptype"}:
+ "argon2flavour", "fptype", "httpdigesthash"}:
arg = coerce_to_bytes(arg)
if isinstance(arg, bytes) and b" " not in arg:
- return arg
+ dictkey = (typename, arg)
+ if dictkey not in checked_enum_values:
+ retwords = childprocess.funcall("checkenum", [typename, arg])
+ assert len(retwords) == 1
+ checked_enum_values[dictkey] = (retwords[0] == b"ok")
+ if checked_enum_values[dictkey]:
+ return arg
if typename == "mpint_list":
sublist = [make_argword(len(arg), ("uint", False),
- fnname, argindex, to_preserve)]
+ fnname, argindex, argname, to_preserve)]
for val in arg:
sublist.append(make_argword(val, ("val_mpint", False),
- fnname, argindex, to_preserve))
+ fnname, argindex, argname, to_preserve))
+ return b" ".join(coerce_to_bytes(sub) for sub in sublist)
+ if typename == "int16_list":
+ sublist = [make_argword(len(arg), ("uint", False),
+ fnname, argindex, argname, to_preserve)]
+ for val in arg:
+ sublist.append(make_argword(val & 0xFFFF, ("uint", False),
+ fnname, argindex, argname, to_preserve))
return b" ".join(coerce_to_bytes(sub) for sub in sublist)
raise TypeError(
- "Can't convert {}() argument {:d} to {} (value was {!r})".format(
- fnname, argindex, typename, arg))
+ "Can't convert {}() argument #{:d} ({}) to {} (value was {!r})".format(
+ fnname, argindex, argname, typename, arg))
def unpack_string(identifier):
retwords = childprocess.funcall("getstring", [identifier])
@@ -235,8 +252,10 @@ def make_retval(rettype, word, unpack_strings):
elif rettype == "boolean":
assert word == b"true" or word == b"false"
return word == b"true"
- elif rettype == "pocklestatus":
+ elif rettype in {"pocklestatus", "mr_result"}:
return word.decode("ASCII")
+ elif rettype == "int16_list":
+ return list(map(int, word.split(b',')))
raise TypeError("Can't deal with return value {!r} of type {!r}"
.format(word, rettype))
@@ -246,12 +265,20 @@ def make_retvals(rettypes, retwords, unpack_strings=True):
for rettype, word in zip(rettypes, retwords)]
class Function(object):
- def __init__(self, fnname, rettypes, argtypes):
+ def __init__(self, fnname, rettypes, retnames, argtypes, argnames):
self.fnname = fnname
self.rettypes = rettypes
+ self.retnames = retnames
self.argtypes = argtypes
+ self.argnames = argnames
def __repr__(self):
- return "<Function {}>".format(self.fnname)
+ return "<Function {}({}) -> ({})>".format(
+ self.fnname,
+ ", ".join(("consumed " if c else "")+t+" "+n
+ for (t,c),n in zip(self.argtypes, self.argnames)),
+ ", ".join((t+" "+n if n is not None else t)
+ for t,n in zip(self.rettypes, self.retnames)),
+ )
def __call__(self, *args):
if len(args) != len(self.argtypes):
raise TypeError(
@@ -260,7 +287,8 @@ class Function(object):
to_preserve = []
retwords = childprocess.funcall(
self.fnname, [make_argword(args[i], self.argtypes[i],
- self.fnname, i, to_preserve)
+ self.fnname, i, self.argnames[i],
+ to_preserve)
for i in range(len(args))])
retvals = make_retvals(self.rettypes, retwords)
if len(retvals) == 0:
@@ -269,10 +297,94 @@ class Function(object):
return retvals[0]
return tuple(retvals)
-def _setup(scope):
- header_file = os.path.join(putty_srcdir, "testcrypt.h")
+def _lex_testcrypt_header(header):
+ pat = re.compile(
+ # Skip any combination of whitespace and comments
+ '(?:{})*'.format('|'.join((
+ '[ \t\n]', # whitespace
+ '/\\*(?:.|\n)*?\\*/', # C90-style /* ... */ comment, ended eagerly
+ '//[^\n]*\n', # C99-style comment to end-of-line
+ ))) +
+ # And then match a token
+ '({})'.format('|'.join((
+ # Punctuation
+ '\(',
+ '\)',
+ ',',
+ # Identifier
+ '[A-Za-z_][A-Za-z0-9_]*',
+ # End of string
+ '$',
+ )))
+ )
+
+ pos = 0
+ end = len(header)
+ while pos < end:
+ m = pat.match(header, pos)
+ assert m is not None, (
+ "Failed to lex testcrypt-func.h at byte position {:d}".format(pos))
+
+ pos = m.end()
+ tok = m.group(1)
+ if len(tok) == 0:
+ assert pos == end, (
+ "Empty token should only be returned at end of string")
+ yield tok, m.start(1)
+
+def _parse_testcrypt_header(tokens):
+ def is_id(tok):
+ return tok[0] in string.ascii_letters+"_"
+ def expect(what, why, eof_ok=False):
+ tok, pos = next(tokens)
+ if tok == '' and eof_ok:
+ return None
+ if hasattr(what, '__call__'):
+ description = lambda: ""
+ ok = what(tok)
+ elif isinstance(what, set):
+ description = lambda: " or ".join("'"+x+"' " for x in sorted(what))
+ ok = tok in what
+ else:
+ description = lambda: "'"+what+"' "
+ ok = tok == what
+ if not ok:
+ sys.exit("testcrypt-func.h:{:d}: expected {}{}".format(
+ pos, description(), why))
+ return tok
+
+ while True:
+ tok = expect({"FUNC", "FUNC_WRAPPED"},
+ "at start of function specification", eof_ok=True)
+ if tok is None:
+ break
+
+ expect("(", "after FUNC")
+ rettype = expect(is_id, "return type")
+ expect(",", "after return type")
+ funcname = expect(is_id, "function name")
+ expect(",", "after function name")
+ args = []
+ firstargkind = expect({"ARG", "VOID"}, "at start of argument list")
+ if firstargkind == "VOID":
+ expect(")", "after VOID")
+ else:
+ while True:
+ # Every time we come back to the top of this loop, we've
+ # just seen 'ARG'
+ expect("(", "after ARG")
+ argtype = expect(is_id, "argument type")
+ expect(",", "after argument type")
+ argname = expect(is_id, "argument name")
+ args.append((argtype, argname))
+ expect(")", "at end of ARG")
+ punct = expect({",", ")"}, "after argument")
+ if punct == ")":
+ break
+ expect("ARG", "to begin next argument")
+ yield funcname, rettype, args
- linere = re.compile(r'^FUNC\d+\((.*)\)$')
+def _setup(scope):
valprefix = "val_"
outprefix = "out_"
optprefix = "opt_"
@@ -288,36 +400,42 @@ def _setup(scope):
arg = arg[:arg.index("_", len(valprefix))]
return arg
- with open(header_file) as f:
- for line in iter(f.readline, ""):
- line = line.rstrip("\r\n").replace(" ", "")
- m = linere.match(line)
- if m is not None:
- words = m.group(1).split(",")
- function = words[1]
- rettypes = []
- argtypes = []
- argsconsumed = []
- if words[0] != "void":
- rettypes.append(trim_argtype(words[0]))
- for arg in words[2:]:
- if arg.startswith(outprefix):
- rettypes.append(trim_argtype(arg[len(outprefix):]))
- else:
- consumed = False
- if arg.startswith(consprefix):
- arg = arg[len(consprefix):]
- consumed = True
- arg = trim_argtype(arg)
- argtypes.append((arg, consumed))
- func = Function(function, rettypes, argtypes)
- scope[function] = func
- if len(argtypes) > 0:
- t = argtypes[0][0]
- if (t in method_prefixes and
- function.startswith(method_prefixes[t])):
- methodname = function[len(method_prefixes[t]):]
+ with open(os.path.join(putty_srcdir, "test", "testcrypt-func.h")) as f:
+ header = f.read()
+ tokens = _lex_testcrypt_header(header)
+ for function, rettype, arglist in _parse_testcrypt_header(tokens):
+ rettypes = []
+ retnames = []
+ if rettype != "void":
+ rettypes.append(trim_argtype(rettype))
+ retnames.append(None)
+
+ argtypes = []
+ argnames = []
+ argsconsumed = []
+ for arg, argname in arglist:
+ if arg.startswith(outprefix):
+ rettypes.append(trim_argtype(arg[len(outprefix):]))
+ retnames.append(argname)
+ else:
+ consumed = False
+ if arg.startswith(consprefix):
+ arg = arg[len(consprefix):]
+ consumed = True
+ arg = trim_argtype(arg)
+ argtypes.append((arg, consumed))
+ argnames.append(argname)
+ func = Function(function, rettypes, retnames,
+ argtypes, argnames)
+ scope[function] = func
+ if len(argtypes) > 0:
+ t = argtypes[0][0]
+ if t in method_prefixes:
+ for prefix in method_prefixes[t]:
+ if function.startswith(prefix):
+ methodname = function[len(prefix):]
method_lists[t].append((methodname, func))
+ break
_setup(globals())
del _setup
diff --git a/test/testsc.c b/test/testsc.c
new file mode 100644
index 00000000..0a643e97
--- /dev/null
+++ b/test/testsc.c
@@ -0,0 +1,1945 @@
+/*
+ * 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 <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "defs.h"
+#include "putty.h"
+#include "ssh.h"
+#include "sshkeygen.h"
+#include "misc.h"
+#include "mpint.h"
+#include "crypto/ecc.h"
+#include "crypto/ntru.h"
+
+static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "testsc: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ exit(1);
+}
+
+void out_of_memory(void) { fatal_error("out of memory"); }
+
+/*
+ * A simple deterministic PRNG, without any of the Fortuna
+ * complexities, for generating test inputs in a way that's repeatable
+ * between runs of the program, even if only a subset of test cases is
+ * run.
+ */
+static uint64_t random_counter = 0;
+static const char *random_seedstr = NULL;
+static uint8_t random_buf[MAX_HASH_LEN];
+static size_t random_buf_limit = 0;
+static ssh_hash *random_hash;
+
+static void random_seed(const char *seedstr)
+{
+ random_seedstr = seedstr;
+ random_counter = 0;
+ random_buf_limit = 0;
+}
+
+static void random_advance_counter(void)
+{
+ ssh_hash_reset(random_hash);
+ put_asciz(random_hash, random_seedstr);
+ put_uint64(random_hash, random_counter);
+ random_counter++;
+ random_buf_limit = ssh_hash_alg(random_hash)->hlen;
+ ssh_hash_digest(random_hash, random_buf);
+}
+
+void random_read(void *vbuf, size_t size)
+{
+ assert(random_seedstr);
+ uint8_t *buf = (uint8_t *)vbuf;
+ while (size-- > 0) {
+ if (random_buf_limit == 0)
+ random_advance_counter();
+ *buf++ = random_buf[random_buf_limit--];
+ }
+}
+
+struct random_state {
+ const char *seedstr;
+ uint64_t counter;
+ size_t limit;
+ uint8_t buf[MAX_HASH_LEN];
+};
+
+static struct random_state random_get_state(void)
+{
+ struct random_state st;
+ st.seedstr = random_seedstr;
+ st.counter = random_counter;
+ st.limit = random_buf_limit;
+ memcpy(st.buf, random_buf, sizeof(st.buf));
+ return st;
+}
+
+static void random_set_state(struct random_state st)
+{
+ random_seedstr = st.seedstr;
+ random_counter = st.counter;
+ random_buf_limit = st.limit;
+ memcpy(random_buf, st.buf, sizeof(random_buf));
+}
+
+/*
+ * Macro that defines a function, and also a volatile function pointer
+ * pointing to it. Callers indirect through the function pointer
+ * instead of directly calling the function, to ensure that the
+ * compiler doesn't try to get clever by eliminating the call
+ * completely, or inlining it.
+ *
+ * This is used to mark functions that DynamoRIO will look for to
+ * intercept, and also to inhibit inlining and unrolling where they'd
+ * cause a failure of experimental control in the main test.
+ */
+#define VOLATILE_WRAPPED_DEFN(qualifier, rettype, fn, params) \
+ qualifier rettype fn##_real params; \
+ qualifier rettype (*volatile fn) params = fn##_real; \
+ qualifier rettype fn##_real params
+
+VOLATILE_WRAPPED_DEFN(, void, log_to_file, (const char *filename))
+{
+ /*
+ * This function is intercepted by the DynamoRIO side of the
+ * mechanism. We use it to send instructions to the DR wrapper,
+ * namely, 'please start logging to this file' or 'please stop
+ * logging' (if filename == NULL). But we don't have to actually
+ * do anything in _this_ program - all the functionality is in the
+ * DR wrapper.
+ */
+}
+
+static const char *outdir = NULL;
+char *log_filename(const char *basename, size_t index)
+{
+ return dupprintf("%s/%s.%04"SIZEu, outdir, basename, index);
+}
+
+static char *last_filename;
+static const char *test_basename;
+static size_t test_index = 0;
+void log_start(void)
+{
+ last_filename = log_filename(test_basename, test_index++);
+ log_to_file(last_filename);
+}
+void log_end(void)
+{
+ log_to_file(NULL);
+ sfree(last_filename);
+}
+
+static bool test_skipped = false;
+
+VOLATILE_WRAPPED_DEFN(, intptr_t, dry_run, (void))
+{
+ /*
+ * This is another function intercepted by DynamoRIO. In this
+ * case, DR overrides this function to return 0 rather than 1, so
+ * we can use it as a check for whether we're running under
+ * instrumentation, or whether this is just a dry run which goes
+ * through the motions but doesn't expect to find any log files
+ * created.
+ */
+ return 1;
+}
+
+static void mp_random_bits_into(mp_int *r, size_t bits)
+{
+ mp_int *x = mp_random_bits(bits);
+ mp_copy_into(r, x);
+ mp_free(x);
+}
+
+static void mp_random_fill(mp_int *r)
+{
+ mp_random_bits_into(r, mp_max_bits(r));
+}
+
+VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x))
+{
+ /*
+ * looplimit() is the identity function on size_t, but the
+ * compiler isn't allowed to rely on it being that. I use it to
+ * make loops in the test functions look less attractive to
+ * compilers' unrolling heuristics.
+ */
+ return x;
+}
+
+#if HAVE_AES_NI
+#define IF_AES_NI(x) x
+#else
+#define IF_AES_NI(x)
+#endif
+
+#if HAVE_SHA_NI
+#define IF_SHA_NI(x) x
+#else
+#define IF_SHA_NI(x)
+#endif
+
+#if HAVE_CLMUL
+#define IF_CLMUL(x) x
+#else
+#define IF_CLMUL(x)
+#endif
+
+#if HAVE_NEON_CRYPTO
+#define IF_NEON_CRYPTO(x) x
+#else
+#define IF_NEON_CRYPTO(x)
+#endif
+
+#if HAVE_NEON_SHA512
+#define IF_NEON_SHA512(x) x
+#else
+#define IF_NEON_SHA512(x)
+#endif
+
+#if HAVE_NEON_PMULL
+#define IF_NEON_PMULL(x) x
+#else
+#define IF_NEON_PMULL(x)
+#endif
+
+/* Ciphers that we expect to pass this test. Blowfish and Arcfour are
+ * intentionally omitted, because we already know they don't. */
+#define CIPHERS(X, Y) \
+ X(Y, ssh_3des_ssh1) \
+ X(Y, ssh_3des_ssh2_ctr) \
+ X(Y, ssh_3des_ssh2) \
+ X(Y, ssh_des) \
+ X(Y, ssh_des_sshcom_ssh2) \
+ X(Y, ssh_aes256_sdctr) \
+ X(Y, ssh_aes256_gcm) \
+ X(Y, ssh_aes256_cbc) \
+ X(Y, ssh_aes192_sdctr) \
+ X(Y, ssh_aes192_gcm) \
+ X(Y, ssh_aes192_cbc) \
+ X(Y, ssh_aes128_sdctr) \
+ X(Y, ssh_aes128_gcm) \
+ X(Y, ssh_aes128_cbc) \
+ X(Y, ssh_aes256_sdctr_sw) \
+ X(Y, ssh_aes256_gcm_sw) \
+ X(Y, ssh_aes256_cbc_sw) \
+ X(Y, ssh_aes192_sdctr_sw) \
+ X(Y, ssh_aes192_gcm_sw) \
+ X(Y, ssh_aes192_cbc_sw) \
+ X(Y, ssh_aes128_sdctr_sw) \
+ X(Y, ssh_aes128_gcm_sw) \
+ X(Y, ssh_aes128_cbc_sw) \
+ IF_AES_NI(X(Y, ssh_aes256_sdctr_ni)) \
+ IF_AES_NI(X(Y, ssh_aes256_gcm_ni)) \
+ IF_AES_NI(X(Y, ssh_aes256_cbc_ni)) \
+ IF_AES_NI(X(Y, ssh_aes192_sdctr_ni)) \
+ IF_AES_NI(X(Y, ssh_aes192_gcm_ni)) \
+ IF_AES_NI(X(Y, ssh_aes192_cbc_ni)) \
+ IF_AES_NI(X(Y, ssh_aes128_sdctr_ni)) \
+ IF_AES_NI(X(Y, ssh_aes128_gcm_ni)) \
+ IF_AES_NI(X(Y, ssh_aes128_cbc_ni)) \
+ IF_NEON_CRYPTO(X(Y, ssh_aes256_sdctr_neon)) \
+ IF_NEON_CRYPTO(X(Y, ssh_aes256_gcm_neon)) \
+ IF_NEON_CRYPTO(X(Y, ssh_aes256_cbc_neon)) \
+ IF_NEON_CRYPTO(X(Y, ssh_aes192_sdctr_neon)) \
+ IF_NEON_CRYPTO(X(Y, ssh_aes192_gcm_neon)) \
+ IF_NEON_CRYPTO(X(Y, ssh_aes192_cbc_neon)) \
+ IF_NEON_CRYPTO(X(Y, ssh_aes128_sdctr_neon)) \
+ IF_NEON_CRYPTO(X(Y, ssh_aes128_gcm_neon)) \
+ IF_NEON_CRYPTO(X(Y, ssh_aes128_cbc_neon)) \
+ X(Y, ssh2_chacha20_poly1305) \
+ /* end of list */
+
+#define CIPHER_TESTLIST(X, name) X(cipher_ ## name)
+
+#define SIMPLE_MACS(X, Y) \
+ X(Y, ssh_hmac_md5) \
+ X(Y, ssh_hmac_sha1) \
+ X(Y, ssh_hmac_sha1_buggy) \
+ X(Y, ssh_hmac_sha1_96) \
+ X(Y, ssh_hmac_sha1_96_buggy) \
+ X(Y, ssh_hmac_sha256) \
+ /* end of list */
+
+#define ALL_MACS(X, Y) \
+ SIMPLE_MACS(X, Y) \
+ X(Y, poly1305) \
+ X(Y, aesgcm_sw_sw) \
+ X(Y, aesgcm_sw_refpoly) \
+ IF_AES_NI(X(Y, aesgcm_ni_sw)) \
+ IF_NEON_CRYPTO(X(Y, aesgcm_neon_sw)) \
+ IF_CLMUL(X(Y, aesgcm_sw_clmul)) \
+ IF_NEON_PMULL(X(Y, aesgcm_sw_neon)) \
+ IF_AES_NI(IF_CLMUL(X(Y, aesgcm_ni_clmul))) \
+ IF_NEON_CRYPTO(IF_NEON_PMULL(X(Y, aesgcm_neon_neon))) \
+ /* end of list */
+
+#define MAC_TESTLIST(X, name) X(mac_ ## name)
+
+#define HASHES(X, Y) \
+ X(Y, ssh_md5) \
+ X(Y, ssh_sha1) \
+ X(Y, ssh_sha1_sw) \
+ X(Y, ssh_sha256) \
+ X(Y, ssh_sha256_sw) \
+ X(Y, ssh_sha384) \
+ X(Y, ssh_sha512) \
+ X(Y, ssh_sha384_sw) \
+ X(Y, ssh_sha512_sw) \
+ IF_SHA_NI(X(Y, ssh_sha256_ni)) \
+ IF_SHA_NI(X(Y, ssh_sha1_ni)) \
+ IF_NEON_CRYPTO(X(Y, ssh_sha256_neon)) \
+ IF_NEON_CRYPTO(X(Y, ssh_sha1_neon)) \
+ IF_NEON_SHA512(X(Y, ssh_sha384_neon)) \
+ IF_NEON_SHA512(X(Y, ssh_sha512_neon)) \
+ X(Y, ssh_sha3_224) \
+ X(Y, ssh_sha3_256) \
+ X(Y, ssh_sha3_384) \
+ X(Y, ssh_sha3_512) \
+ X(Y, ssh_shake256_114bytes) \
+ X(Y, ssh_blake2b) \
+ /* end of list */
+
+#define HASH_TESTLIST(X, name) X(hash_ ## name)
+
+#define TESTLIST(X) \
+ X(mp_get_nbits) \
+ X(mp_from_decimal) \
+ X(mp_from_hex) \
+ X(mp_get_decimal) \
+ X(mp_get_hex) \
+ X(mp_cmp_hs) \
+ X(mp_cmp_eq) \
+ X(mp_min) \
+ X(mp_max) \
+ X(mp_select_into) \
+ X(mp_cond_swap) \
+ X(mp_cond_clear) \
+ X(mp_add) \
+ X(mp_sub) \
+ X(mp_mul) \
+ X(mp_rshift_safe) \
+ X(mp_divmod) \
+ X(mp_nthroot) \
+ X(mp_modadd) \
+ X(mp_modsub) \
+ X(mp_modmul) \
+ X(mp_modpow) \
+ X(mp_invert_mod_2to) \
+ X(mp_invert) \
+ X(mp_modsqrt) \
+ X(ecc_weierstrass_add) \
+ X(ecc_weierstrass_double) \
+ X(ecc_weierstrass_add_general) \
+ X(ecc_weierstrass_multiply) \
+ X(ecc_weierstrass_is_identity) \
+ X(ecc_weierstrass_get_affine) \
+ X(ecc_weierstrass_decompress) \
+ X(ecc_montgomery_diff_add) \
+ X(ecc_montgomery_double) \
+ X(ecc_montgomery_multiply) \
+ X(ecc_montgomery_get_affine) \
+ X(ecc_edwards_add) \
+ X(ecc_edwards_multiply) \
+ X(ecc_edwards_eq) \
+ X(ecc_edwards_get_affine) \
+ X(ecc_edwards_decompress) \
+ CIPHERS(CIPHER_TESTLIST, X) \
+ ALL_MACS(MAC_TESTLIST, X) \
+ HASHES(HASH_TESTLIST, X) \
+ X(argon2) \
+ X(primegen_probabilistic) \
+ X(ntru) \
+ /* end of list */
+
+static void test_mp_get_nbits(void)
+{
+ mp_int *z = mp_new(512);
+ static const size_t bitposns[] = {
+ 0, 1, 5, 16, 23, 32, 67, 123, 234, 511
+ };
+ mp_int *prev = mp_from_integer(0);
+ for (size_t i = 0; i < looplimit(lenof(bitposns)); i++) {
+ mp_int *x = mp_power_2(bitposns[i]);
+ mp_add_into(z, x, prev);
+ mp_free(prev);
+ prev = x;
+ log_start();
+ mp_get_nbits(z);
+ log_end();
+ }
+ mp_free(prev);
+ mp_free(z);
+}
+
+static void test_mp_from_decimal(void)
+{
+ char dec[64];
+ static const size_t starts[] = { 0, 1, 5, 16, 23, 32, 63, 64 };
+ for (size_t i = 0; i < looplimit(lenof(starts)); i++) {
+ memset(dec, '0', lenof(dec));
+ for (size_t j = starts[i]; j < lenof(dec); j++) {
+ uint8_t r[4];
+ random_read(r, 4);
+ dec[j] = '0' + GET_32BIT_MSB_FIRST(r) % 10;
+ }
+ log_start();
+ mp_int *x = mp_from_decimal_pl(make_ptrlen(dec, lenof(dec)));
+ log_end();
+ mp_free(x);
+ }
+}
+
+static void test_mp_from_hex(void)
+{
+ char hex[64];
+ static const size_t starts[] = { 0, 1, 5, 16, 23, 32, 63, 64 };
+ static const char digits[] = "0123456789abcdefABCDEF";
+ for (size_t i = 0; i < looplimit(lenof(starts)); i++) {
+ memset(hex, '0', lenof(hex));
+ for (size_t j = starts[i]; j < lenof(hex); j++) {
+ uint8_t r[4];
+ random_read(r, 4);
+ hex[j] = digits[GET_32BIT_MSB_FIRST(r) % lenof(digits)];
+ }
+ log_start();
+ mp_int *x = mp_from_hex_pl(make_ptrlen(hex, lenof(hex)));
+ log_end();
+ mp_free(x);
+ }
+}
+
+static void test_mp_string_format(char *(*mp_format)(mp_int *x))
+{
+ mp_int *z = mp_new(512);
+ static const size_t bitposns[] = {
+ 0, 1, 5, 16, 23, 32, 67, 123, 234, 511
+ };
+ for (size_t i = 0; i < looplimit(lenof(bitposns)); i++) {
+ mp_random_bits_into(z, bitposns[i]);
+ log_start();
+ char *formatted = mp_format(z);
+ log_end();
+ sfree(formatted);
+ }
+ mp_free(z);
+}
+
+static void test_mp_get_decimal(void)
+{
+ test_mp_string_format(mp_get_decimal);
+}
+
+static void test_mp_get_hex(void)
+{
+ test_mp_string_format(mp_get_hex);
+}
+
+static void test_mp_cmp(unsigned (*mp_cmp)(mp_int *a, mp_int *b))
+{
+ mp_int *a = mp_new(512), *b = mp_new(512);
+ static const size_t bitposns[] = {
+ 0, 1, 5, 16, 23, 32, 67, 123, 234, 511
+ };
+ for (size_t i = 0; i < looplimit(lenof(bitposns)); i++) {
+ mp_random_fill(b);
+ mp_int *x = mp_random_bits(bitposns[i]);
+ mp_xor_into(a, b, x);
+ mp_free(x);
+ log_start();
+ mp_cmp(a, b);
+ log_end();
+ }
+ mp_free(a);
+ mp_free(b);
+}
+
+static void test_mp_cmp_hs(void)
+{
+ test_mp_cmp(mp_cmp_hs);
+}
+
+static void test_mp_cmp_eq(void)
+{
+ test_mp_cmp(mp_cmp_eq);
+}
+
+static void test_mp_minmax(
+ void (*mp_minmax_into)(mp_int *r, mp_int *x, mp_int *y))
+{
+ mp_int *a = mp_new(256), *b = mp_new(256);
+ for (size_t i = 0; i < looplimit(10); i++) {
+ uint8_t lens[2];
+ random_read(lens, 2);
+ mp_int *x = mp_random_bits(lens[0]);
+ mp_copy_into(a, x);
+ mp_free(x);
+ mp_int *y = mp_random_bits(lens[1]);
+ mp_copy_into(a, y);
+ mp_free(y);
+ log_start();
+ mp_minmax_into(a, a, b);
+ log_end();
+ }
+ mp_free(a);
+ mp_free(b);
+}
+
+static void test_mp_max(void)
+{
+ test_mp_minmax(mp_max_into);
+}
+
+static void test_mp_min(void)
+{
+ test_mp_minmax(mp_min_into);
+}
+
+static void test_mp_select_into(void)
+{
+ mp_int *a = mp_random_bits(256);
+ mp_int *b = mp_random_bits(512);
+ mp_int *r = mp_new(384);
+ for (size_t i = 0; i < looplimit(16); i++) {
+ log_start();
+ mp_select_into(r, a, b, i & 1);
+ log_end();
+ }
+ mp_free(a);
+ mp_free(b);
+ mp_free(r);
+}
+
+static void test_mp_cond_swap(void)
+{
+ mp_int *a = mp_random_bits(512);
+ mp_int *b = mp_random_bits(512);
+ for (size_t i = 0; i < looplimit(16); i++) {
+ log_start();
+ mp_cond_swap(a, b, i & 1);
+ log_end();
+ }
+ mp_free(a);
+ mp_free(b);
+}
+
+static void test_mp_cond_clear(void)
+{
+ mp_int *a = mp_random_bits(512);
+ mp_int *x = mp_copy(a);
+ for (size_t i = 0; i < looplimit(16); i++) {
+ mp_copy_into(x, a);
+ log_start();
+ mp_cond_clear(a, i & 1);
+ log_end();
+ }
+ mp_free(a);
+ mp_free(x);
+}
+
+static void test_mp_arithmetic(mp_int *(*mp_arith)(mp_int *x, mp_int *y))
+{
+ mp_int *a = mp_new(256), *b = mp_new(512);
+ for (size_t i = 0; i < looplimit(16); i++) {
+ mp_random_fill(a);
+ mp_random_fill(b);
+ log_start();
+ mp_int *r = mp_arith(a, b);
+ log_end();
+ mp_free(r);
+ }
+ mp_free(a);
+ mp_free(b);
+}
+
+static void test_mp_add(void)
+{
+ test_mp_arithmetic(mp_add);
+}
+
+static void test_mp_sub(void)
+{
+ test_mp_arithmetic(mp_sub);
+}
+
+static void test_mp_mul(void)
+{
+ test_mp_arithmetic(mp_mul);
+}
+
+static void test_mp_invert(void)
+{
+ test_mp_arithmetic(mp_invert);
+}
+
+static void test_mp_rshift_safe(void)
+{
+ mp_int *x = mp_random_bits(256);
+
+ for (size_t i = 0; i < looplimit(mp_max_bits(x)+1); i++) {
+ log_start();
+ mp_int *r = mp_rshift_safe(x, i);
+ log_end();
+ mp_free(r);
+ }
+
+ mp_free(x);
+}
+
+static void test_mp_divmod(void)
+{
+ mp_int *n = mp_new(256), *d = mp_new(256);
+ mp_int *q = mp_new(256), *r = mp_new(256);
+
+ for (size_t i = 0; i < looplimit(32); i++) {
+ uint8_t sizes[2];
+ random_read(sizes, 2);
+ mp_random_bits_into(n, sizes[0]);
+ mp_random_bits_into(d, sizes[1]);
+ log_start();
+ mp_divmod_into(n, d, q, r);
+ log_end();
+ }
+
+ mp_free(n);
+ mp_free(d);
+ mp_free(q);
+ mp_free(r);
+}
+
+static void test_mp_nthroot(void)
+{
+ mp_int *x = mp_new(256), *remainder = mp_new(256);
+
+ for (size_t i = 0; i < looplimit(32); i++) {
+ uint8_t sizes[1];
+ random_read(sizes, 1);
+ mp_random_bits_into(x, sizes[0]);
+ log_start();
+ mp_free(mp_nthroot(x, 3, remainder));
+ log_end();
+ }
+
+ mp_free(x);
+ mp_free(remainder);
+}
+
+static void test_mp_modarith(
+ mp_int *(*mp_modarith)(mp_int *x, mp_int *y, mp_int *modulus))
+{
+ mp_int *base = mp_new(256);
+ mp_int *exponent = mp_new(256);
+ mp_int *modulus = mp_new(256);
+
+ for (size_t i = 0; i < looplimit(8); i++) {
+ mp_random_fill(base);
+ mp_random_fill(exponent);
+ mp_random_fill(modulus);
+ mp_set_bit(modulus, 0, 1); /* we only support odd moduli */
+
+ log_start();
+ mp_int *out = mp_modarith(base, exponent, modulus);
+ log_end();
+
+ mp_free(out);
+ }
+
+ mp_free(base);
+ mp_free(exponent);
+ mp_free(modulus);
+}
+
+static void test_mp_modadd(void)
+{
+ test_mp_modarith(mp_modadd);
+}
+
+static void test_mp_modsub(void)
+{
+ test_mp_modarith(mp_modsub);
+}
+
+static void test_mp_modmul(void)
+{
+ test_mp_modarith(mp_modmul);
+}
+
+static void test_mp_modpow(void)
+{
+ test_mp_modarith(mp_modpow);
+}
+
+static void test_mp_invert_mod_2to(void)
+{
+ mp_int *x = mp_new(512);
+
+ for (size_t i = 0; i < looplimit(32); i++) {
+ mp_random_fill(x);
+ mp_set_bit(x, 0, 1); /* input should be odd */
+
+ log_start();
+ mp_int *out = mp_invert_mod_2to(x, 511);
+ log_end();
+
+ mp_free(out);
+ }
+
+ mp_free(x);
+}
+
+static void test_mp_modsqrt(void)
+{
+ /* The prime isn't secret in this function (and in any case
+ * finding a non-square on the fly would be prohibitively
+ * annoying), so I hardcode a fixed one, selected to have a lot of
+ * factors of two in p-1 so as to exercise lots of choices in the
+ * algorithm. */
+ mp_int *p =
+ MP_LITERAL(0xb56a517b206a88c73cfa9ec6f704c7030d18212cace82401);
+ mp_int *nonsquare = MP_LITERAL(0x5);
+ size_t bits = mp_max_bits(p);
+ ModsqrtContext *sc = modsqrt_new(p, nonsquare);
+ mp_free(p);
+ mp_free(nonsquare);
+
+ mp_int *x = mp_new(bits);
+ unsigned success;
+
+ /* Do one initial call to cause the lazily initialised sub-context
+ * to be set up. This will take a while, but it can't be helped. */
+ mp_int *unwanted = mp_modsqrt(sc, x, &success);
+ mp_free(unwanted);
+
+ for (size_t i = 0; i < looplimit(8); i++) {
+ mp_random_bits_into(x, bits - 1);
+ log_start();
+ mp_int *out = mp_modsqrt(sc, x, &success);
+ log_end();
+ mp_free(out);
+ }
+
+ mp_free(x);
+ modsqrt_free(sc);
+}
+
+static WeierstrassCurve *wcurve(void)
+{
+ mp_int *p = MP_LITERAL(0xc19337603dc856acf31e01375a696fdf5451);
+ mp_int *a = MP_LITERAL(0x864946f50eecca4cde7abad4865e34be8f67);
+ mp_int *b = MP_LITERAL(0x6a5bf56db3a03ba91cfbf3241916c90feeca);
+ mp_int *nonsquare = mp_from_integer(3);
+ WeierstrassCurve *wc = ecc_weierstrass_curve(p, a, b, nonsquare);
+ mp_free(p);
+ mp_free(a);
+ mp_free(b);
+ mp_free(nonsquare);
+ return wc;
+}
+
+static WeierstrassPoint *wpoint(WeierstrassCurve *wc, size_t index)
+{
+ mp_int *x = NULL, *y = NULL;
+ WeierstrassPoint *wp;
+ switch (index) {
+ case 0:
+ break;
+ case 1:
+ x = MP_LITERAL(0x12345);
+ y = MP_LITERAL(0x3c2c799a365b53d003ef37dab65860bf80ae);
+ break;
+ case 2:
+ x = MP_LITERAL(0x4e1c77e3c00f7c3b15869e6a4e5f86b3ee53);
+ y = MP_LITERAL(0x5bde01693130591400b5c9d257d8325a44a5);
+ break;
+ case 3:
+ x = MP_LITERAL(0xb5f0e722b2f0f7e729f55ba9f15511e3b399);
+ y = MP_LITERAL(0x033d636b855c931cfe679f0b18db164a0d64);
+ break;
+ case 4:
+ x = MP_LITERAL(0xb5f0e722b2f0f7e729f55ba9f15511e3b399);
+ y = MP_LITERAL(0xbe55d3f4b86bc38ff4b6622c418e599546ed);
+ break;
+ default:
+ unreachable("only 5 example Weierstrass points defined");
+ }
+ if (x && y) {
+ wp = ecc_weierstrass_point_new(wc, x, y);
+ } else {
+ wp = ecc_weierstrass_point_new_identity(wc);
+ }
+ if (x)
+ mp_free(x);
+ if (y)
+ mp_free(y);
+ return wp;
+}
+
+static void test_ecc_weierstrass_add(void)
+{
+ WeierstrassCurve *wc = wcurve();
+ WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc);
+ WeierstrassPoint *b = ecc_weierstrass_point_new_identity(wc);
+ for (size_t i = 0; i < looplimit(5); i++) {
+ for (size_t j = 0; j < looplimit(5); j++) {
+ if (i == 0 || j == 0 || i == j ||
+ (i==3 && j==4) || (i==4 && j==3))
+ continue; /* difficult cases */
+
+ WeierstrassPoint *A = wpoint(wc, i), *B = wpoint(wc, j);
+ ecc_weierstrass_point_copy_into(a, A);
+ ecc_weierstrass_point_copy_into(b, B);
+ ecc_weierstrass_point_free(A);
+ ecc_weierstrass_point_free(B);
+
+ log_start();
+ WeierstrassPoint *r = ecc_weierstrass_add(a, b);
+ log_end();
+ ecc_weierstrass_point_free(r);
+ }
+ }
+ ecc_weierstrass_point_free(a);
+ ecc_weierstrass_point_free(b);
+ ecc_weierstrass_curve_free(wc);
+}
+
+static void test_ecc_weierstrass_double(void)
+{
+ WeierstrassCurve *wc = wcurve();
+ WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc);
+ for (size_t i = 0; i < looplimit(5); i++) {
+ WeierstrassPoint *A = wpoint(wc, i);
+ ecc_weierstrass_point_copy_into(a, A);
+ ecc_weierstrass_point_free(A);
+
+ log_start();
+ WeierstrassPoint *r = ecc_weierstrass_double(a);
+ log_end();
+ ecc_weierstrass_point_free(r);
+ }
+ ecc_weierstrass_point_free(a);
+ ecc_weierstrass_curve_free(wc);
+}
+
+static void test_ecc_weierstrass_add_general(void)
+{
+ WeierstrassCurve *wc = wcurve();
+ WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc);
+ WeierstrassPoint *b = ecc_weierstrass_point_new_identity(wc);
+ for (size_t i = 0; i < looplimit(5); i++) {
+ for (size_t j = 0; j < looplimit(5); j++) {
+ WeierstrassPoint *A = wpoint(wc, i), *B = wpoint(wc, j);
+ ecc_weierstrass_point_copy_into(a, A);
+ ecc_weierstrass_point_copy_into(b, B);
+ ecc_weierstrass_point_free(A);
+ ecc_weierstrass_point_free(B);
+
+ log_start();
+ WeierstrassPoint *r = ecc_weierstrass_add_general(a, b);
+ log_end();
+ ecc_weierstrass_point_free(r);
+ }
+ }
+ ecc_weierstrass_point_free(a);
+ ecc_weierstrass_point_free(b);
+ ecc_weierstrass_curve_free(wc);
+}
+
+static void test_ecc_weierstrass_multiply(void)
+{
+ WeierstrassCurve *wc = wcurve();
+ WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc);
+ mp_int *exponent = mp_new(56);
+ for (size_t i = 1; i < looplimit(5); i++) {
+ WeierstrassPoint *A = wpoint(wc, i);
+ ecc_weierstrass_point_copy_into(a, A);
+ ecc_weierstrass_point_free(A);
+ mp_random_fill(exponent);
+
+ log_start();
+ WeierstrassPoint *r = ecc_weierstrass_multiply(a, exponent);
+ log_end();
+
+ ecc_weierstrass_point_free(r);
+ }
+ ecc_weierstrass_point_free(a);
+ ecc_weierstrass_curve_free(wc);
+ mp_free(exponent);
+}
+
+static void test_ecc_weierstrass_is_identity(void)
+{
+ WeierstrassCurve *wc = wcurve();
+ WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc);
+ for (size_t i = 1; i < looplimit(5); i++) {
+ WeierstrassPoint *A = wpoint(wc, i);
+ ecc_weierstrass_point_copy_into(a, A);
+ ecc_weierstrass_point_free(A);
+
+ log_start();
+ ecc_weierstrass_is_identity(a);
+ log_end();
+ }
+ ecc_weierstrass_point_free(a);
+ ecc_weierstrass_curve_free(wc);
+}
+
+static void test_ecc_weierstrass_get_affine(void)
+{
+ WeierstrassCurve *wc = wcurve();
+ WeierstrassPoint *r = ecc_weierstrass_point_new_identity(wc);
+ for (size_t i = 0; i < looplimit(4); i++) {
+ WeierstrassPoint *A = wpoint(wc, i), *B = wpoint(wc, i+1);
+ WeierstrassPoint *R = ecc_weierstrass_add_general(A, B);
+ ecc_weierstrass_point_copy_into(r, R);
+ ecc_weierstrass_point_free(A);
+ ecc_weierstrass_point_free(B);
+ ecc_weierstrass_point_free(R);
+
+ log_start();
+ mp_int *x, *y;
+ ecc_weierstrass_get_affine(r, &x, &y);
+ log_end();
+ mp_free(x);
+ mp_free(y);
+ }
+ ecc_weierstrass_point_free(r);
+ ecc_weierstrass_curve_free(wc);
+}
+
+static void test_ecc_weierstrass_decompress(void)
+{
+ WeierstrassCurve *wc = wcurve();
+
+ /* As in the mp_modsqrt test, prime the lazy initialisation of the
+ * ModsqrtContext */
+ mp_int *x = mp_new(144);
+ WeierstrassPoint *a = ecc_weierstrass_point_new_from_x(wc, x, 0);
+ if (a) /* don't care whether this one succeeded */
+ ecc_weierstrass_point_free(a);
+
+ for (size_t p = 0; p < looplimit(2); p++) {
+ for (size_t i = 1; i < looplimit(5); i++) {
+ WeierstrassPoint *A = wpoint(wc, i);
+ mp_int *X;
+ ecc_weierstrass_get_affine(A, &X, NULL);
+ mp_copy_into(x, X);
+ mp_free(X);
+ ecc_weierstrass_point_free(A);
+
+ log_start();
+ WeierstrassPoint *a = ecc_weierstrass_point_new_from_x(wc, x, p);
+ log_end();
+
+ ecc_weierstrass_point_free(a);
+ }
+ }
+ mp_free(x);
+ ecc_weierstrass_curve_free(wc);
+}
+
+static MontgomeryCurve *mcurve(void)
+{
+ mp_int *p = MP_LITERAL(0xde978eb1db35236a5792e9f0c04d86000659);
+ mp_int *a = MP_LITERAL(0x799b62a612b1b30e1c23cea6d67b2e33c51a);
+ mp_int *b = MP_LITERAL(0x944bf9042b56821a8c9e0b49b636c2502b2b);
+ MontgomeryCurve *mc = ecc_montgomery_curve(p, a, b);
+ mp_free(p);
+ mp_free(a);
+ mp_free(b);
+ return mc;
+}
+
+static MontgomeryPoint *mpoint(MontgomeryCurve *wc, size_t index)
+{
+ mp_int *x = NULL;
+ MontgomeryPoint *mp;
+ switch (index) {
+ case 0:
+ x = MP_LITERAL(31415);
+ break;
+ case 1:
+ x = MP_LITERAL(0x4d352c654c06eecfe19104118857b38398e8);
+ break;
+ case 2:
+ x = MP_LITERAL(0x03fca2a73983bc3434caae3134599cd69cce);
+ break;
+ case 3:
+ x = MP_LITERAL(0xa0fd735ce9b3406498b5f035ee655bda4e15);
+ break;
+ case 4:
+ x = MP_LITERAL(0x7c7f46a00cc286dbe47db39b6d8f5efd920e);
+ break;
+ case 5:
+ x = MP_LITERAL(0x07a6dc30d3b320448e6f8999be417e6b7c6b);
+ break;
+ case 6:
+ x = MP_LITERAL(0x7832da5fc16dfbd358170b2b96896cd3cd06);
+ break;
+ default:
+ unreachable("only 7 example Weierstrass points defined");
+ }
+ mp = ecc_montgomery_point_new(wc, x);
+ mp_free(x);
+ return mp;
+}
+
+static void test_ecc_montgomery_diff_add(void)
+{
+ MontgomeryCurve *wc = mcurve();
+ MontgomeryPoint *a = NULL, *b = NULL, *c = NULL;
+ for (size_t i = 0; i < looplimit(5); i++) {
+ MontgomeryPoint *A = mpoint(wc, i);
+ MontgomeryPoint *B = mpoint(wc, i);
+ MontgomeryPoint *C = mpoint(wc, i);
+ if (!a) {
+ a = A;
+ b = B;
+ c = C;
+ } else {
+ ecc_montgomery_point_copy_into(a, A);
+ ecc_montgomery_point_copy_into(b, B);
+ ecc_montgomery_point_copy_into(c, C);
+ ecc_montgomery_point_free(A);
+ ecc_montgomery_point_free(B);
+ ecc_montgomery_point_free(C);
+ }
+
+ log_start();
+ MontgomeryPoint *r = ecc_montgomery_diff_add(b, c, a);
+ log_end();
+
+ ecc_montgomery_point_free(r);
+ }
+ ecc_montgomery_point_free(a);
+ ecc_montgomery_point_free(b);
+ ecc_montgomery_point_free(c);
+ ecc_montgomery_curve_free(wc);
+}
+
+static void test_ecc_montgomery_double(void)
+{
+ MontgomeryCurve *wc = mcurve();
+ MontgomeryPoint *a = NULL;
+ for (size_t i = 0; i < looplimit(7); i++) {
+ MontgomeryPoint *A = mpoint(wc, i);
+ if (!a) {
+ a = A;
+ } else {
+ ecc_montgomery_point_copy_into(a, A);
+ ecc_montgomery_point_free(A);
+ }
+
+ log_start();
+ MontgomeryPoint *r = ecc_montgomery_double(a);
+ log_end();
+
+ ecc_montgomery_point_free(r);
+ }
+ ecc_montgomery_point_free(a);
+ ecc_montgomery_curve_free(wc);
+}
+
+static void test_ecc_montgomery_multiply(void)
+{
+ MontgomeryCurve *wc = mcurve();
+ MontgomeryPoint *a = NULL;
+ mp_int *exponent = mp_new(56);
+ for (size_t i = 0; i < looplimit(7); i++) {
+ MontgomeryPoint *A = mpoint(wc, i);
+ if (!a) {
+ a = A;
+ } else {
+ ecc_montgomery_point_copy_into(a, A);
+ ecc_montgomery_point_free(A);
+ }
+ mp_random_fill(exponent);
+
+ log_start();
+ MontgomeryPoint *r = ecc_montgomery_multiply(a, exponent);
+ log_end();
+
+ ecc_montgomery_point_free(r);
+ }
+ ecc_montgomery_point_free(a);
+ ecc_montgomery_curve_free(wc);
+ mp_free(exponent);
+}
+
+static void test_ecc_montgomery_get_affine(void)
+{
+ MontgomeryCurve *wc = mcurve();
+ MontgomeryPoint *r = NULL;
+ for (size_t i = 0; i < looplimit(5); i++) {
+ MontgomeryPoint *A = mpoint(wc, i);
+ MontgomeryPoint *B = mpoint(wc, i);
+ MontgomeryPoint *C = mpoint(wc, i);
+ MontgomeryPoint *R = ecc_montgomery_diff_add(B, C, A);
+ ecc_montgomery_point_free(A);
+ ecc_montgomery_point_free(B);
+ ecc_montgomery_point_free(C);
+ if (!r) {
+ r = R;
+ } else {
+ ecc_montgomery_point_copy_into(r, R);
+ ecc_montgomery_point_free(R);
+ }
+
+ log_start();
+ mp_int *x;
+ ecc_montgomery_get_affine(r, &x);
+ log_end();
+
+ mp_free(x);
+ }
+ ecc_montgomery_point_free(r);
+ ecc_montgomery_curve_free(wc);
+}
+
+static EdwardsCurve *ecurve(void)
+{
+ mp_int *p = MP_LITERAL(0xfce2dac1704095de0b5c48876c45063cd475);
+ mp_int *d = MP_LITERAL(0xbd4f77401c3b14ae1742a7d1d367adac8f3e);
+ mp_int *a = MP_LITERAL(0x51d0845da3fa871aaac4341adea53b861919);
+ mp_int *nonsquare = mp_from_integer(2);
+ EdwardsCurve *ec = ecc_edwards_curve(p, d, a, nonsquare);
+ mp_free(p);
+ mp_free(d);
+ mp_free(a);
+ mp_free(nonsquare);
+ return ec;
+}
+
+static EdwardsPoint *epoint(EdwardsCurve *wc, size_t index)
+{
+ mp_int *x, *y;
+ EdwardsPoint *ep;
+ switch (index) {
+ case 0:
+ x = MP_LITERAL(0x0);
+ y = MP_LITERAL(0x1);
+ break;
+ case 1:
+ x = MP_LITERAL(0x3d8aef0294a67c1c7e8e185d987716250d7c);
+ y = MP_LITERAL(0x27184);
+ break;
+ case 2:
+ x = MP_LITERAL(0xf44ed5b8a6debfd3ab24b7874cd2589fd672);
+ y = MP_LITERAL(0xd635d8d15d367881c8a3af472c8fe487bf40);
+ break;
+ case 3:
+ x = MP_LITERAL(0xde114ecc8b944684415ef81126a07269cd30);
+ y = MP_LITERAL(0xbe0fd45ff67ebba047ed0ec5a85d22e688a1);
+ break;
+ case 4:
+ x = MP_LITERAL(0x76bd2f90898d271b492c9c20dd7bbfe39fe5);
+ y = MP_LITERAL(0xbf1c82698b4a5a12c1057631c1ebdc216ae2);
+ break;
+ default:
+ unreachable("only 5 example Edwards points defined");
+ }
+ ep = ecc_edwards_point_new(wc, x, y);
+ mp_free(x);
+ mp_free(y);
+ return ep;
+}
+
+static void test_ecc_edwards_add(void)
+{
+ EdwardsCurve *ec = ecurve();
+ EdwardsPoint *a = NULL, *b = NULL;
+ for (size_t i = 0; i < looplimit(5); i++) {
+ for (size_t j = 0; j < looplimit(5); j++) {
+ EdwardsPoint *A = epoint(ec, i), *B = epoint(ec, j);
+ if (!a) {
+ a = A;
+ b = B;
+ } else {
+ ecc_edwards_point_copy_into(a, A);
+ ecc_edwards_point_copy_into(b, B);
+ ecc_edwards_point_free(A);
+ ecc_edwards_point_free(B);
+ }
+
+ log_start();
+ EdwardsPoint *r = ecc_edwards_add(a, b);
+ log_end();
+
+ ecc_edwards_point_free(r);
+ }
+ }
+ ecc_edwards_point_free(a);
+ ecc_edwards_point_free(b);
+ ecc_edwards_curve_free(ec);
+}
+
+static void test_ecc_edwards_multiply(void)
+{
+ EdwardsCurve *ec = ecurve();
+ EdwardsPoint *a = NULL;
+ mp_int *exponent = mp_new(56);
+ for (size_t i = 1; i < looplimit(5); i++) {
+ EdwardsPoint *A = epoint(ec, i);
+ if (!a) {
+ a = A;
+ } else {
+ ecc_edwards_point_copy_into(a, A);
+ ecc_edwards_point_free(A);
+ }
+ mp_random_fill(exponent);
+
+ log_start();
+ EdwardsPoint *r = ecc_edwards_multiply(a, exponent);
+ log_end();
+
+ ecc_edwards_point_free(r);
+ }
+ ecc_edwards_point_free(a);
+ ecc_edwards_curve_free(ec);
+ mp_free(exponent);
+}
+
+static void test_ecc_edwards_eq(void)
+{
+ EdwardsCurve *ec = ecurve();
+ EdwardsPoint *a = NULL, *b = NULL;
+ for (size_t i = 0; i < looplimit(5); i++) {
+ for (size_t j = 0; j < looplimit(5); j++) {
+ EdwardsPoint *A = epoint(ec, i), *B = epoint(ec, j);
+ if (!a) {
+ a = A;
+ b = B;
+ } else {
+ ecc_edwards_point_copy_into(a, A);
+ ecc_edwards_point_copy_into(b, B);
+ ecc_edwards_point_free(A);
+ ecc_edwards_point_free(B);
+ }
+
+ log_start();
+ ecc_edwards_eq(a, b);
+ log_end();
+ }
+ }
+ ecc_edwards_point_free(a);
+ ecc_edwards_point_free(b);
+ ecc_edwards_curve_free(ec);
+}
+
+static void test_ecc_edwards_get_affine(void)
+{
+ EdwardsCurve *ec = ecurve();
+ EdwardsPoint *r = NULL;
+ for (size_t i = 0; i < looplimit(4); i++) {
+ EdwardsPoint *A = epoint(ec, i), *B = epoint(ec, i+1);
+ EdwardsPoint *R = ecc_edwards_add(A, B);
+ ecc_edwards_point_free(A);
+ ecc_edwards_point_free(B);
+ if (!r) {
+ r = R;
+ } else {
+ ecc_edwards_point_copy_into(r, R);
+ ecc_edwards_point_free(R);
+ }
+
+ log_start();
+ mp_int *x, *y;
+ ecc_edwards_get_affine(r, &x, &y);
+ log_end();
+
+ mp_free(x);
+ mp_free(y);
+ }
+ ecc_edwards_point_free(r);
+ ecc_edwards_curve_free(ec);
+}
+
+static void test_ecc_edwards_decompress(void)
+{
+ EdwardsCurve *ec = ecurve();
+
+ /* As in the mp_modsqrt test, prime the lazy initialisation of the
+ * ModsqrtContext */
+ mp_int *y = mp_new(144);
+ EdwardsPoint *a = ecc_edwards_point_new_from_y(ec, y, 0);
+ if (a) /* don't care whether this one succeeded */
+ ecc_edwards_point_free(a);
+
+ for (size_t p = 0; p < looplimit(2); p++) {
+ for (size_t i = 0; i < looplimit(5); i++) {
+ EdwardsPoint *A = epoint(ec, i);
+ mp_int *Y;
+ ecc_edwards_get_affine(A, NULL, &Y);
+ mp_copy_into(y, Y);
+ mp_free(Y);
+ ecc_edwards_point_free(A);
+
+ log_start();
+ EdwardsPoint *a = ecc_edwards_point_new_from_y(ec, y, p);
+ log_end();
+
+ ecc_edwards_point_free(a);
+ }
+ }
+ mp_free(y);
+ ecc_edwards_curve_free(ec);
+}
+
+static void test_cipher(const ssh_cipheralg *calg)
+{
+ ssh_cipher *c = ssh_cipher_new(calg);
+ if (!c) {
+ test_skipped = true;
+ return;
+ }
+ const ssh2_macalg *malg = calg->required_mac;
+ ssh2_mac *m = NULL;
+ if (malg) {
+ m = ssh2_mac_new(malg, c);
+ if (!m) {
+ ssh_cipher_free(c);
+ test_skipped = true;
+ return;
+ }
+ }
+
+ uint8_t *ckey = snewn(calg->padded_keybytes, uint8_t);
+ uint8_t *civ = snewn(calg->blksize, uint8_t);
+ uint8_t *mkey = malg ? snewn(malg->keylen, uint8_t) : NULL;
+ size_t datalen = calg->blksize * 8;
+ size_t maclen = malg ? malg->len : 0;
+ uint8_t *data = snewn(datalen + maclen, uint8_t);
+ size_t lenlen = 4;
+ uint8_t *lendata = snewn(lenlen, uint8_t);
+
+ for (size_t i = 0; i < looplimit(16); i++) {
+ random_read(ckey, calg->padded_keybytes);
+ if (malg)
+ random_read(mkey, malg->keylen);
+ random_read(data, datalen);
+ random_read(lendata, lenlen);
+ if (i == 0) {
+ /* Ensure one of our test IVs will cause SDCTR wraparound */
+ memset(civ, 0xFF, calg->blksize);
+ } else {
+ random_read(civ, calg->blksize);
+ }
+ uint8_t seqbuf[4];
+ random_read(seqbuf, 4);
+ uint32_t seq = GET_32BIT_MSB_FIRST(seqbuf);
+
+ log_start();
+ ssh_cipher_setkey(c, ckey);
+ ssh_cipher_setiv(c, civ);
+ if (m)
+ ssh2_mac_setkey(m, make_ptrlen(mkey, malg->keylen));
+ if (calg->flags & SSH_CIPHER_SEPARATE_LENGTH)
+ ssh_cipher_encrypt_length(c, data, datalen, seq);
+ ssh_cipher_encrypt(c, data, datalen);
+ if (m) {
+ ssh2_mac_generate(m, data, datalen, seq);
+ ssh2_mac_verify(m, data, datalen, seq);
+ }
+ if (calg->flags & SSH_CIPHER_SEPARATE_LENGTH)
+ ssh_cipher_decrypt_length(c, data, datalen, seq);
+ ssh_cipher_decrypt(c, data, datalen);
+ log_end();
+ }
+
+ sfree(ckey);
+ sfree(civ);
+ sfree(mkey);
+ sfree(data);
+ sfree(lendata);
+ if (m)
+ ssh2_mac_free(m);
+ ssh_cipher_free(c);
+}
+
+#define CIPHER_TESTFN(Y_unused, cipher) \
+ static void test_cipher_##cipher(void) { test_cipher(&cipher); }
+CIPHERS(CIPHER_TESTFN, Y_unused)
+
+static void test_mac(const ssh2_macalg *malg, const ssh_cipheralg *calg)
+{
+ ssh_cipher *c = NULL;
+ if (calg) {
+ c = ssh_cipher_new(calg);
+ if (!c) {
+ test_skipped = true;
+ return;
+ }
+ }
+
+ ssh2_mac *m = ssh2_mac_new(malg, c);
+ if (!m) {
+ test_skipped = true;
+ if (c)
+ ssh_cipher_free(c);
+ return;
+ }
+
+ size_t ckeylen = calg ? calg->padded_keybytes : 0;
+ size_t civlen = calg ? calg->blksize : 0;
+ uint8_t *ckey = snewn(ckeylen, uint8_t);
+ uint8_t *civ = snewn(civlen, uint8_t);
+ uint8_t *mkey = snewn(malg->keylen, uint8_t);
+ size_t datalen = 256;
+ size_t maclen = malg->len;
+ uint8_t *data = snewn(datalen + maclen, uint8_t);
+
+ for (size_t i = 0; i < looplimit(16); i++) {
+ random_read(ckey, ckeylen);
+ random_read(civ, civlen);
+ random_read(mkey, malg->keylen);
+ random_read(data, datalen);
+ uint8_t seqbuf[4];
+ random_read(seqbuf, 4);
+ uint32_t seq = GET_32BIT_MSB_FIRST(seqbuf);
+
+ log_start();
+ if (c) {
+ ssh_cipher_setkey(c, ckey);
+ ssh_cipher_setiv(c, civ);
+ }
+ ssh2_mac_setkey(m, make_ptrlen(mkey, malg->keylen));
+ ssh2_mac_generate(m, data, datalen, seq);
+ ssh2_mac_verify(m, data, datalen, seq);
+ log_end();
+ }
+
+ sfree(ckey);
+ sfree(civ);
+ sfree(mkey);
+ sfree(data);
+ ssh2_mac_free(m);
+ if (c)
+ ssh_cipher_free(c);
+}
+
+#define MAC_TESTFN(Y_unused, mac) \
+ static void test_mac_##mac(void) { test_mac(&mac, NULL); }
+SIMPLE_MACS(MAC_TESTFN, Y_unused)
+
+static void test_mac_poly1305(void)
+{
+ test_mac(&ssh2_poly1305, &ssh2_chacha20_poly1305);
+}
+
+static void test_mac_aesgcm_sw_sw(void)
+{
+ test_mac(&ssh2_aesgcm_mac_sw, &ssh_aes128_gcm_sw);
+}
+
+static void test_mac_aesgcm_sw_refpoly(void)
+{
+ test_mac(&ssh2_aesgcm_mac_ref_poly, &ssh_aes128_gcm_sw);
+}
+
+#if HAVE_AES_NI
+static void test_mac_aesgcm_ni_sw(void)
+{
+ test_mac(&ssh2_aesgcm_mac_sw, &ssh_aes128_gcm_ni);
+}
+#endif
+
+#if HAVE_NEON_CRYPTO
+static void test_mac_aesgcm_neon_sw(void)
+{
+ test_mac(&ssh2_aesgcm_mac_sw, &ssh_aes128_gcm_neon);
+}
+#endif
+
+#if HAVE_CLMUL
+static void test_mac_aesgcm_sw_clmul(void)
+{
+ test_mac(&ssh2_aesgcm_mac_clmul, &ssh_aes128_gcm_sw);
+}
+#endif
+
+#if HAVE_NEON_PMULL
+static void test_mac_aesgcm_sw_neon(void)
+{
+ test_mac(&ssh2_aesgcm_mac_neon, &ssh_aes128_gcm_sw);
+}
+#endif
+
+#if HAVE_AES_NI && HAVE_CLMUL
+static void test_mac_aesgcm_ni_clmul(void)
+{
+ test_mac(&ssh2_aesgcm_mac_clmul, &ssh_aes128_gcm_ni);
+}
+#endif
+
+#if HAVE_NEON_CRYPTO && HAVE_NEON_PMULL
+static void test_mac_aesgcm_neon_neon(void)
+{
+ test_mac(&ssh2_aesgcm_mac_neon, &ssh_aes128_gcm_neon);
+}
+#endif
+
+static void test_hash(const ssh_hashalg *halg)
+{
+ ssh_hash *h = ssh_hash_new(halg);
+ if (!h) {
+ test_skipped = true;
+ return;
+ }
+
+ size_t datalen = 256;
+ uint8_t *data = snewn(datalen, uint8_t);
+ uint8_t *hash = snewn(halg->hlen, uint8_t);
+
+ for (size_t i = 0; i < looplimit(16); i++) {
+ random_read(data, datalen);
+
+ log_start();
+ put_data(h, data, datalen);
+ ssh_hash_final(h, hash);
+ log_end();
+
+ h = ssh_hash_new(halg);
+ }
+
+ sfree(data);
+ sfree(hash);
+ ssh_hash_free(h);
+}
+
+#define HASH_TESTFN(Y_unused, hash) \
+ static void test_hash_##hash(void) { test_hash(&hash); }
+HASHES(HASH_TESTFN, Y_unused)
+
+struct test {
+ const char *testname;
+ void (*testfn)(void);
+};
+
+static void test_argon2(void)
+{
+ /*
+ * We can only expect the Argon2i variant to pass this stringent
+ * test for no data-dependency, because the other two variants of
+ * Argon2 have _deliberate_ data-dependency.
+ */
+ size_t inlen = 48+16+24+8;
+ uint8_t *indata = snewn(inlen, uint8_t);
+ ptrlen password = make_ptrlen(indata, 48);
+ ptrlen salt = make_ptrlen(indata+48, 16);
+ ptrlen secret = make_ptrlen(indata+48+16, 24);
+ ptrlen assoc = make_ptrlen(indata+48+16+24, 8);
+
+ strbuf *outdata = strbuf_new();
+ strbuf_append(outdata, 256);
+
+ for (size_t i = 0; i < looplimit(16); i++) {
+ strbuf_clear(outdata);
+ random_read(indata, inlen);
+
+ log_start();
+ argon2(Argon2i, 32, 2, 2, 144, password, salt, secret, assoc, outdata);
+ log_end();
+ }
+
+ sfree(indata);
+ strbuf_free(outdata);
+}
+
+static void test_primegen(const PrimeGenerationPolicy *policy)
+{
+ static ProgressReceiver null_progress = { .vt = &null_progress_vt };
+
+ PrimeGenerationContext *pgc = primegen_new_context(policy);
+
+ init_smallprimes();
+ mp_int *pcopy = mp_new(128);
+
+ for (size_t i = 0; i < looplimit(2); i++) {
+ while (true) {
+ random_advance_counter();
+ struct random_state st = random_get_state();
+
+ PrimeCandidateSource *pcs = pcs_new(128);
+ pcs_set_oneshot(pcs);
+ pcs_ready(pcs);
+ mp_int *p = primegen_generate(pgc, pcs, &null_progress);
+
+ if (p) {
+ mp_copy_into(pcopy, p);
+ sfree(p);
+
+ random_set_state(st);
+
+ log_start();
+ PrimeCandidateSource *pcs = pcs_new(128);
+ pcs_set_oneshot(pcs);
+ pcs_ready(pcs);
+ mp_int *q = primegen_generate(pgc, pcs, &null_progress);
+ log_end();
+
+ assert(q);
+ assert(mp_cmp_eq(pcopy, q));
+ mp_free(q);
+ break;
+ }
+ }
+ }
+
+ mp_free(pcopy);
+ primegen_free_context(pgc);
+}
+
+static void test_primegen_probabilistic(void)
+{
+ test_primegen(&primegen_probabilistic);
+}
+
+static void test_ntru(void)
+{
+ unsigned p = 11, q = 59, w = 3;
+ uint16_t *pubkey_orig = snewn(p, uint16_t);
+ uint16_t *pubkey_check = snewn(p, uint16_t);
+ uint16_t *pubkey = snewn(p, uint16_t);
+ uint16_t *plaintext = snewn(p, uint16_t);
+ uint16_t *ciphertext = snewn(p, uint16_t);
+
+ strbuf *buffer = strbuf_new();
+ strbuf_append(buffer, 16384);
+ BinarySource src[1];
+
+ for (size_t i = 0; i < looplimit(32); i++) {
+ while (true) {
+ random_advance_counter();
+ struct random_state st = random_get_state();
+
+ NTRUKeyPair *keypair = ntru_keygen_attempt(p, q, w);
+
+ if (keypair) {
+ memcpy(pubkey_orig, ntru_pubkey(keypair),
+ p*sizeof(*pubkey_orig));
+ ntru_keypair_free(keypair);
+
+ random_set_state(st);
+
+ log_start();
+ NTRUKeyPair *keypair = ntru_keygen_attempt(p, q, w);
+ memcpy(pubkey_check, ntru_pubkey(keypair),
+ p*sizeof(*pubkey_check));
+
+ ntru_gen_short(plaintext, p, w);
+ ntru_encrypt(ciphertext, plaintext, pubkey, p, w);
+ ntru_decrypt(plaintext, ciphertext, keypair);
+
+ strbuf_clear(buffer);
+ ntru_encode_pubkey(ntru_pubkey(keypair), p, q,
+ BinarySink_UPCAST(buffer));
+ BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(buffer));
+ ntru_decode_pubkey(pubkey, p, q, src);
+
+ strbuf_clear(buffer);
+ ntru_encode_ciphertext(ciphertext, p, q,
+ BinarySink_UPCAST(buffer));
+ BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(buffer));
+ ntru_decode_ciphertext(ciphertext, keypair, src);
+
+ strbuf_clear(buffer);
+ ntru_encode_plaintext(plaintext, p, BinarySink_UPCAST(buffer));
+ log_end();
+
+ ntru_keypair_free(keypair);
+
+ break;
+ }
+
+ assert(!memcmp(pubkey_orig, pubkey_check,
+ p*sizeof(*pubkey_check)));
+ }
+ }
+
+ sfree(pubkey_orig);
+ sfree(pubkey_check);
+ sfree(pubkey);
+ sfree(plaintext);
+ sfree(ciphertext);
+ strbuf_free(buffer);
+}
+
+static const struct test tests[] = {
+#define STRUCT_TEST(X) { #X, test_##X },
+TESTLIST(STRUCT_TEST)
+#undef STRUCT_TEST
+};
+
+void dputs(const char *buf)
+{
+ fputs(buf, stderr);
+}
+
+int main(int argc, char **argv)
+{
+ bool doing_opts = true;
+ const char *pname = argv[0];
+ uint8_t tests_to_run[lenof(tests)];
+ bool keep_outfiles = false;
+ bool test_names_given = false;
+
+ memset(tests_to_run, 1, sizeof(tests_to_run));
+ random_hash = ssh_hash_new(&ssh_sha256);
+
+ while (--argc > 0) {
+ char *p = *++argv;
+
+ if (p[0] == '-' && doing_opts) {
+ if (!strcmp(p, "-O")) {
+ if (--argc <= 0) {
+ fprintf(stderr, "'-O' expects a directory name\n");
+ return 1;
+ }
+ outdir = *++argv;
+ } else if (!strcmp(p, "-k") || !strcmp(p, "--keep")) {
+ keep_outfiles = true;
+ } else if (!strcmp(p, "--")) {
+ doing_opts = false;
+ } else if (!strcmp(p, "--help")) {
+ printf(" usage: drrun -c test/sclog/libsclog.so -- "
+ "%s -O <outdir>\n", pname);
+ printf("options: -O <outdir> "
+ "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 <outdir> option\n");
+ return 1;
+ }
+ printf("Will write log files to %s\n", outdir);
+ }
+
+ size_t nrun = 0, npass = 0;
+
+ for (size_t i = 0; i < lenof(tests); i++) {
+ bool keep_these_outfiles = true;
+
+ if (!tests_to_run[i])
+ continue;
+ const struct test *test = &tests[i];
+ printf("Running test %s ... ", test->testname);
+ fflush(stdout);
+
+ test_skipped = false;
+ random_seed(test->testname);
+ test_basename = test->testname;
+ test_index = 0;
+
+ test->testfn();
+
+ if (test_skipped) {
+ /* Used for e.g. tests of hardware-accelerated crypto when
+ * the hardware acceleration isn't available */
+ printf("skipped\n");
+ continue;
+ }
+
+ nrun++;
+
+ if (is_dry_run) {
+ printf("dry run done\n");
+ continue; /* test files won't exist anyway */
+ }
+
+ if (test_index < 2) {
+ printf("FAIL: test did not generate multiple output files\n");
+ goto test_done;
+ }
+
+ char *firstfile = log_filename(test_basename, 0);
+ FILE *firstfp = fopen(firstfile, "rb");
+ if (!firstfp) {
+ printf("ERR: %s: open: %s\n", firstfile, strerror(errno));
+ goto test_done;
+ }
+ for (size_t i = 1; i < test_index; i++) {
+ char *nextfile = log_filename(test_basename, i);
+ FILE *nextfp = fopen(nextfile, "rb");
+ if (!nextfp) {
+ printf("ERR: %s: open: %s\n", nextfile, strerror(errno));
+ goto test_done;
+ }
+
+ rewind(firstfp);
+ char buf1[4096], bufn[4096];
+ bool compare_ok = false;
+ while (true) {
+ size_t r1 = fread(buf1, 1, sizeof(buf1), firstfp);
+ size_t rn = fread(bufn, 1, sizeof(bufn), nextfp);
+ if (r1 != rn) {
+ printf("FAIL: %s %s: different lengths\n",
+ firstfile, nextfile);
+ break;
+ }
+ if (r1 == 0) {
+ if (feof(firstfp) && feof(nextfp)) {
+ compare_ok = true;
+ } else {
+ printf("FAIL: %s %s: error at end of file\n",
+ firstfile, nextfile);
+ }
+ break;
+ }
+ if (memcmp(buf1, bufn, r1) != 0) {
+ printf("FAIL: %s %s: different content\n",
+ firstfile, nextfile);
+ break;
+ }
+ }
+ fclose(nextfp);
+ sfree(nextfile);
+ if (!compare_ok) {
+ goto test_done;
+ }
+ }
+ fclose(firstfp);
+ sfree(firstfile);
+
+ printf("pass\n");
+ npass++;
+ keep_these_outfiles = keep_outfiles;
+
+ test_done:
+ if (!keep_these_outfiles) {
+ for (size_t i = 0; i < test_index; i++) {
+ char *file = log_filename(test_basename, i);
+ remove(file);
+ sfree(file);
+ }
+ }
+ }
+
+ ssh_hash_free(random_hash);
+
+ if (npass == nrun) {
+ printf("All tests passed\n");
+ return 0;
+ } else {
+ printf("%"SIZEu" tests failed\n", nrun - npass);
+ return 1;
+ }
+}
diff --git a/test/testzlib.c b/test/testzlib.c
new file mode 100644
index 00000000..0ef4ef19
--- /dev/null
+++ b/test/testzlib.c
@@ -0,0 +1,114 @@
+/*
+ * Main program to compile ssh/zlib.c into a zlib decoding tool.
+ *
+ * This is potentially a handy tool in its own right for picking apart
+ * Zip files or PDFs or PNGs, because it accepts the bare Deflate
+ * format and the zlib wrapper format, unlike 'zcat' which accepts
+ * only the gzip wrapper format.
+ *
+ * It's also useful as a means for a fuzzer to get reasonably direct
+ * access to PuTTY's zlib decompressor.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#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/testback.c b/testback.c
deleted file mode 100644
index 173786ed..00000000
--- a/testback.c
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright (c) 1999 Simon Tatham
- * Copyright (c) 1999 Ben Harris
- * All rights reserved.
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use,
- * copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following
- * conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
- * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
- * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-/* PuTTY test backends */
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#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/testcrypt.c b/testcrypt.c
deleted file mode 100644
index 67627752..00000000
--- a/testcrypt.c
+++ /dev/null
@@ -1,1587 +0,0 @@
-/*
- * testcrypt: a standalone test program that provides direct access to
- * PuTTY's cryptography and mp_int code.
- */
-
-/*
- * This program speaks a line-oriented protocol on standard input and
- * standard output. It's a half-duplex protocol: it expects to read
- * one line of command, and then produce a fixed amount of output
- * (namely a line containing a decimal integer, followed by that many
- * lines each containing one return value).
- *
- * The protocol is human-readable enough to make it debuggable, but
- * verbose enough that you probably wouldn't want to speak it by hand
- * at any great length. The Python program test/testcrypt.py wraps it
- * to give a more useful user-facing API, by invoking this binary as a
- * subprocess.
- *
- * (I decided that was a better idea than making this program an
- * actual Python module, partly because you can rewrap the same binary
- * in another scripting language if you prefer, but mostly because
- * it's easy to attach a debugger to testcrypt or to run it under
- * sanitisers or valgrind or what have you.)
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-#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/testcrypt.h b/testcrypt.h
deleted file mode 100644
index 298abc0f..00000000
--- a/testcrypt.h
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * 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/testsc.c b/testsc.c
deleted file mode 100644
index f04f718c..00000000
--- a/testsc.c
+++ /dev/null
@@ -1,1645 +0,0 @@
-/*
- * testsc: run PuTTY's crypto primitives under instrumentation that
- * checks for cache and timing side channels.
- *
- * The idea is: cryptographic code should avoid leaking secret data
- * through timing information, or through traces of its activity left
- * in the caches.
- *
- * (This property is sometimes called 'constant-time', although really
- * that's a misnomer. It would be impossible to avoid the execution
- * time varying for any number of reasons outside the code's control,
- * such as the prior contents of caches and branch predictors,
- * temperature-based CPU throttling, system load, etc. And in any case
- * you don't _need_ the execution time to be literally constant: you
- * just need it to be independent of your secrets. It can vary as much
- * as it likes based on anything else.)
- *
- * To avoid this, you need to ensure that various aspects of the
- * code's behaviour do not depend on the secret data. The control
- * flow, for a start - no conditional branches based on secrets - and
- * also the memory access pattern (no using secret data as an index
- * into a lookup table). A couple of other kinds of CPU instruction
- * also can't be trusted to run in constant time: we check for
- * register-controlled shifts and hardware divisions. (But, again,
- * it's perfectly fine to _use_ those instructions in the course of
- * crypto code. You just can't use a secret as any time-affecting
- * operand.)
- *
- * This test program works by running the same crypto primitive
- * multiple times, with different secret input data. The relevant
- * details of each run is logged to a file via the DynamoRIO-based
- * instrumentation system living in the subdirectory test/sclog. Then
- * we check over all the files and ensure they're identical.
- *
- * This program itself (testsc) is built by the ordinary PuTTY
- * makefiles. But run by itself, it will do nothing useful: it needs
- * to be run under DynamoRIO, with the sclog instrumentation library.
- *
- * Here's an example of how I built it:
- *
- * Download the DynamoRIO source. I did this by cloning
- * https://github.com/DynamoRIO/dynamorio.git, and at the time of
- * writing this, 259c182a75ce80112bcad329c97ada8d56ba854d was the head
- * commit.
- *
- * In the DynamoRIO checkout:
- *
- * mkdir build
- * cd build
- * cmake -G Ninja ..
- * ninja
- *
- * Now set the shell variable DRBUILD to be the location of the build
- * directory you did that in. (Or not, if you prefer, but the example
- * build commands below will assume that that's where the DynamoRIO
- * libraries, headers and runtime can be found.)
- *
- * Then, in test/sclog:
- *
- * cmake -G Ninja -DCMAKE_PREFIX_PATH=$DRBUILD/cmake .
- * ninja
- *
- * Finally, to run the actual test, set SCTMP to some temp directory
- * you don't mind filling with large temp files (several GB at a
- * time), and in the main PuTTY source directory (assuming that's
- * where testsc has been built):
- *
- * $DRBUILD/bin64/drrun -c test/sclog/libsclog.so -- ./testsc -O $SCTMP
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-
-#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 <outdir>\n", pname);
- printf("options: -O <outdir> "
- "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 <outdir> 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/testzlib.c b/testzlib.c
deleted file mode 100644
index 0e7b424b..00000000
--- a/testzlib.c
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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 <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#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/time.c b/time.c
deleted file mode 100644
index 75b89535..00000000
--- a/time.c
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
- * Portable implementation of ltime() for any ISO-C platform where
- * time_t behaves. (In practice, we've found that platforms such as
- * Windows and Mac have needed their own specialised implementations.)
- */
-
-#include <time.h>
-#include <assert.h>
-
-struct tm ltime(void)
-{
- time_t t;
- time(&t);
- assert (t != ((time_t)-1));
- return *localtime(&t);
-}
diff --git a/tree234.c b/tree234.c
deleted file mode 100644
index a590382b..00000000
--- a/tree234.c
+++ /dev/null
@@ -1,1611 +0,0 @@
-/*
- * tree234.c: reasonably generic counted 2-3-4 tree routines.
- *
- * This file is copyright 1999-2001 Simon Tatham.
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation
- * files (the "Software"), to deal in the Software without
- * restriction, including without limitation the rights to use,
- * copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following
- * conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR
- * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
- * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#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. */
- assert(!(equal_permitted && !e));
-
- if (cmp == NULL)
- cmp = t->cmp;
-
- search234_start(&ss, t);
- while (ss.element) {
- int cmpret;
-
- if (e) {
- cmpret = cmp(e, ss.element);
- } else {
- cmpret = -reldir; /* invent a fixed compare result */
- }
-
- if (cmpret == 0) {
- /*
- * We've found an element that compares exactly equal to
- * the query element.
- */
- if (equal_permitted) {
- /* If our search relation permits equality, we've
- * finished already. */
- if (index)
- *index = ss.index;
- return ss.element;
- } else {
- /* Otherwise, pretend this element was slightly too
- * big/small, according to the direction of search. */
- cmpret = reldir;
- }
- }
-
- search234_step(&ss, cmpret);
- }
-
- /*
- * No element compares equal to the one we were after, but
- * ss.index indicates the index that element would have if it were
- * inserted.
- *
- * So if our search relation is EQ, we must simply return failure.
- */
- if (relation == REL234_EQ)
- return NULL;
-
- /*
- * Otherwise, we must do an index lookup for the previous index
- * (if we're going left - LE or LT) or this index (if we're going
- * right - GE or GT).
- */
- if (relation == REL234_LT || relation == REL234_LE) {
- ss.index--;
- }
-
- /*
- * We know the index of the element we want; just call index234
- * to do the rest. This will return NULL if the index is out of
- * bounds, which is exactly what we want.
- */
- toret = index234(t, ss.index);
- if (toret && index)
- *index = ss.index;
- return toret;
-}
-void *find234(tree234 * t, void *e, cmpfn234 cmp)
-{
- return findrelpos234(t, e, cmp, REL234_EQ, NULL);
-}
-void *findrel234(tree234 * t, void *e, cmpfn234 cmp, int relation)
-{
- return findrelpos234(t, e, cmp, relation, NULL);
-}
-void *findpos234(tree234 * t, void *e, cmpfn234 cmp, int *index)
-{
- return findrelpos234(t, e, cmp, REL234_EQ, index);
-}
-
-void search234_start(search234_state *state, tree234 *t)
-{
- state->_node = t->root;
- state->_base = 0; /* index of first element in this node's subtree */
- state->_last = -1; /* indicate that this node is not previously 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 <stdarg.h>
-#include <string.h>
-
-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/tree234.h b/tree234.h
index 0f2012d0..c7e3f641 100644
--- a/tree234.h
+++ b/tree234.h
@@ -45,13 +45,13 @@ tree234 *newtree234(cmpfn234 cmp);
/*
* Free a 2-3-4 tree (not including freeing the elements).
*/
-void freetree234(tree234 * t);
+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);
+void *add234(tree234 *t, void *e);
/*
* Add an element e to an unsorted 2-3-4 tree t. Returns e on
@@ -61,7 +61,7 @@ void *add234(tree234 * t, void *e);
* Index range can be from 0 to the tree's current element count,
* inclusive.
*/
-void *addpos234(tree234 * t, void *e, int index);
+void *addpos234(tree234 *t, void *e, int index);
/*
* Look up the element at a given numeric index in a 2-3-4 tree.
@@ -81,7 +81,7 @@ void *addpos234(tree234 * t, void *e, int index);
* consume(p);
* }
*/
-void *index234(tree234 * t, int index);
+void *index234(tree234 *t, int index);
/*
* Find an element e in a sorted 2-3-4 tree t. Returns NULL if not
@@ -126,10 +126,10 @@ void *index234(tree234 * t, int index);
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,
+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);
/*
@@ -184,12 +184,12 @@ void search234_step(search234_state *state, int direction);
* 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);
+void *del234(tree234 *t, void *e);
+void *delpos234(tree234 *t, int index);
/*
* Return the total element count of a tree234.
*/
-int count234(tree234 * t);
+int count234(tree234 *t);
#endif /* TREE234_H */
diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt
new file mode 100644
index 00000000..d4de28df
--- /dev/null
+++ b/unix/CMakeLists.txt
@@ -0,0 +1,223 @@
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
+
+add_sources_from_current_dir(utils
+ utils/arm_arch_queries.c
+ utils/block_signal.c
+ utils/cloexec.c
+ utils/dputs.c
+ utils/filename.c
+ utils/fontspec.c
+ utils/getticks.c
+ utils/get_username.c
+ utils/keysym_to_unicode.c
+ utils/make_dir_and_check_ours.c
+ utils/make_dir_path.c
+ utils/make_spr_sw_abort_errno.c
+ utils/nonblock.c
+ utils/open_for_write_would_lose_data.c
+ utils/pgp_fingerprints.c
+ utils/pollwrap.c
+ utils/signal.c
+ utils/x11_ignore_error.c
+ # We want the ISO C implementation of ltime(), because we don't have
+ # a local better alternative
+ ../utils/ltime.c)
+# Compiled icon pixmap files
+add_library(puttyxpms STATIC
+ putty-xpm.c
+ putty-config-xpm.c)
+add_library(ptermxpms STATIC
+ pterm-xpm.c
+ pterm-config-xpm.c)
+add_sources_from_current_dir(eventloop
+ cliloop.c uxsel.c)
+add_sources_from_current_dir(console
+ console.c)
+add_sources_from_current_dir(settings
+ storage.c)
+add_sources_from_current_dir(network
+ network.c fd-socket.c agent-socket.c peerinfo.c local-proxy.c x11.c)
+add_sources_from_current_dir(sshcommon
+ noise.c)
+add_sources_from_current_dir(sshclient
+ gss.c agent-client.c sharing.c)
+add_sources_from_current_dir(sshserver
+ sftpserver.c procnet.c)
+add_sources_from_current_dir(sftpclient
+ sftp.c)
+add_sources_from_current_dir(otherbackends
+ serial.c)
+add_sources_from_current_dir(agent
+ agent-client.c)
+
+add_executable(fuzzterm
+ ${CMAKE_SOURCE_DIR}/test/fuzzterm.c
+ ${CMAKE_SOURCE_DIR}/logging.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-print.c
+ unicode.c
+ no-gtk.c)
+be_list(fuzzterm FuZZterm)
+add_dependencies(fuzzterm generated_licence_h)
+target_link_libraries(fuzzterm
+ guiterminal eventloop charset settings utils)
+
+add_executable(osxlaunch
+ osxlaunch.c)
+
+add_sources_from_current_dir(plink no-gtk.c)
+add_sources_from_current_dir(pscp no-gtk.c)
+add_sources_from_current_dir(psftp no-gtk.c)
+add_sources_from_current_dir(psocks no-gtk.c)
+
+add_executable(psusan
+ psusan.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-gss.c
+ ${CMAKE_SOURCE_DIR}/ssh/scpserver.c
+ no-gtk.c
+ pty.c)
+be_list(psusan psusan)
+target_link_libraries(psusan
+ eventloop sshserver keygen settings network crypto utils)
+installed_program(psusan)
+
+add_library(puttygen-common OBJECT
+ ${CMAKE_SOURCE_DIR}/stubs/no-timing.c
+ keygen-noise.c
+ no-gtk.c
+ noise.c
+ storage.c
+ ${CMAKE_SOURCE_DIR}/sshpubk.c
+ ${CMAKE_SOURCE_DIR}/sshrand.c)
+
+add_executable(puttygen
+ ${CMAKE_SOURCE_DIR}/cmdgen.c
+ $<TARGET_OBJECTS:puttygen-common>)
+target_link_libraries(puttygen keygen console crypto utils)
+installed_program(puttygen)
+
+add_executable(cgtest
+ ${CMAKE_SOURCE_DIR}/cgtest.c
+ $<TARGET_OBJECTS:puttygen-common>)
+target_link_libraries(cgtest keygen console crypto utils)
+
+add_executable(testsc
+ ${CMAKE_SOURCE_DIR}/test/testsc.c)
+target_link_libraries(testsc keygen crypto utils)
+
+add_executable(testzlib
+ ${CMAKE_SOURCE_DIR}/test/testzlib.c
+ ${CMAKE_SOURCE_DIR}/ssh/zlib.c)
+target_link_libraries(testzlib utils)
+
+add_executable(uppity
+ uppity.c
+ ${CMAKE_SOURCE_DIR}/ssh/scpserver.c
+ no-gtk.c
+ pty.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-gss.c)
+be_list(uppity Uppity)
+target_link_libraries(uppity
+ eventloop sshserver keygen settings network crypto utils)
+
+if(GTK_FOUND)
+ add_sources_from_current_dir(utils
+ utils/align_label_left.c
+ utils/buildinfo_gtk_version.c
+ utils/get_label_text_dimensions.c
+ utils/get_x11_display.c
+ utils/our_dialog.c
+ utils/string_width.c
+ columns.c)
+ add_sources_from_current_dir(guiterminal
+ window.c unifont.c dialog.c config-gtk.c gtk-common.c config-unix.c unicode.c printing.c)
+ add_dependencies(guiterminal generated_licence_h) # dialog.c uses licence.h
+
+ add_executable(pterm
+ pterm.c
+ main-gtk-simple.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-gss.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c
+ ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c
+ pty.c)
+ be_list(pterm pterm)
+ target_link_libraries(pterm
+ guiterminal eventloop settings charset utils ptermxpms
+ ${GTK_LIBRARIES} ${X11_LIBRARIES})
+ installed_program(pterm)
+
+ if(GTK_VERSION GREATER_EQUAL 3)
+ add_executable(ptermapp
+ pterm.c
+ main-gtk-application.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-cmdline.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-gss.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c
+ ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c
+ pty.c)
+ be_list(ptermapp pterm)
+ target_link_libraries(ptermapp
+ guiterminal eventloop settings charset utils ptermxpms
+ ${GTK_LIBRARIES} ${X11_LIBRARIES})
+ endif()
+
+ add_executable(putty
+ putty.c
+ main-gtk-simple.c)
+ be_list(putty PuTTY SSH SERIAL OTHERBACKENDS)
+ target_link_libraries(putty
+ guiterminal eventloop sshclient otherbackends settings
+ network crypto charset utils puttyxpms
+ ${GTK_LIBRARIES} ${X11_LIBRARIES})
+ set_target_properties(putty
+ PROPERTIES LINK_INTERFACE_MULTIPLICITY 2)
+ installed_program(putty)
+
+ if(GTK_VERSION GREATER_EQUAL 3)
+ add_executable(puttyapp
+ putty.c
+ main-gtk-application.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-cmdline.c)
+ be_list(puttyapp PuTTY SSH SERIAL OTHERBACKENDS)
+ target_link_libraries(puttyapp
+ guiterminal eventloop sshclient otherbackends settings
+ network crypto charset utils puttyxpms
+ ${GTK_LIBRARIES} ${X11_LIBRARIES})
+ endif()
+
+ add_executable(puttytel
+ putty.c
+ main-gtk-simple.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-gss.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-rand.c
+ ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c
+ ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c)
+ be_list(puttytel PuTTYtel SERIAL OTHERBACKENDS)
+ target_link_libraries(puttytel
+ guiterminal eventloop otherbackends settings network charset utils
+ puttyxpms
+ ${GTK_LIBRARIES} ${X11_LIBRARIES})
+endif()
+
+# Pageant is built whether we have GTK or not; in its absence we
+# degrade to a version that doesn't provide the GTK askpass.
+if(GTK_FOUND)
+ set(pageant_conditional_sources askpass.c)
+ set(pageant_libs ${GTK_LIBRARIES})
+else()
+ set(pageant_conditional_sources noaskpass.c no-gtk.c)
+ set(pageant_libs)
+endif()
+add_executable(pageant
+ pageant.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-gss.c
+ x11.c
+ noise.c
+ ${CMAKE_SOURCE_DIR}/ssh/x11fwd.c
+ ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c
+ ${pageant_conditional_sources})
+be_list(pageant Pageant)
+target_link_libraries(pageant
+ eventloop console agent settings network crypto utils
+ ${pageant_libs})
+installed_program(pageant)
diff --git a/unix/agent-client.c b/unix/agent-client.c
new file mode 100644
index 00000000..6d7a3662
--- /dev/null
+++ b/unix/agent-client.c
@@ -0,0 +1,226 @@
+/*
+ * SSH agent client code.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <fcntl.h>
+
+#include "putty.h"
+#include "misc.h"
+#include "tree234.h"
+#include "puttymem.h"
+
+bool agent_exists(void)
+{
+ const char *p = getenv("SSH_AUTH_SOCK");
+ if (p && *p)
+ return true;
+ return false;
+}
+
+static tree234 *agent_pending_queries;
+struct agent_pending_query {
+ int fd;
+ char *retbuf;
+ char sizebuf[4];
+ int retsize, retlen;
+ void (*callback)(void *, void *, int);
+ void *callback_ctx;
+};
+static int agent_conncmp(void *av, void *bv)
+{
+ agent_pending_query *a = (agent_pending_query *) av;
+ agent_pending_query *b = (agent_pending_query *) bv;
+ if (a->fd < b->fd)
+ return -1;
+ if (a->fd > b->fd)
+ return +1;
+ return 0;
+}
+static int agent_connfind(void *av, void *bv)
+{
+ int afd = *(int *) av;
+ agent_pending_query *b = (agent_pending_query *) bv;
+ if (afd < b->fd)
+ return -1;
+ if (afd > b->fd)
+ return +1;
+ return 0;
+}
+
+/*
+ * Attempt to read from an agent socket fd. Returns false if the
+ * expected response is as yet incomplete; returns true if it's either
+ * complete (conn->retbuf non-NULL and filled with something useful)
+ * or has failed totally (conn->retbuf is NULL).
+ */
+static bool agent_try_read(agent_pending_query *conn)
+{
+ int ret;
+
+ ret = read(conn->fd, conn->retbuf+conn->retlen,
+ conn->retsize-conn->retlen);
+ if (ret <= 0) {
+ if (conn->retbuf != conn->sizebuf) sfree(conn->retbuf);
+ conn->retbuf = NULL;
+ conn->retlen = 0;
+ return true;
+ }
+ conn->retlen += ret;
+ if (conn->retsize == 4 && conn->retlen == 4) {
+ conn->retsize = toint(GET_32BIT_MSB_FIRST(conn->retbuf) + 4);
+ if (conn->retsize <= 0) {
+ conn->retbuf = NULL;
+ conn->retlen = 0;
+ return true; /* way too large */
+ }
+ assert(conn->retbuf == conn->sizebuf);
+ conn->retbuf = snewn(conn->retsize, char);
+ memcpy(conn->retbuf, conn->sizebuf, 4);
+ }
+
+ if (conn->retlen < conn->retsize)
+ return false; /* more data to come */
+
+ return true;
+}
+
+void agent_cancel_query(agent_pending_query *conn)
+{
+ uxsel_del(conn->fd);
+ close(conn->fd);
+ del234(agent_pending_queries, conn);
+ if (conn->retbuf && conn->retbuf != conn->sizebuf)
+ sfree(conn->retbuf);
+ sfree(conn);
+}
+
+static void agent_select_result(int fd, int event)
+{
+ agent_pending_query *conn;
+
+ assert(event == SELECT_R); /* not selecting for anything but R */
+
+ conn = find234(agent_pending_queries, &fd, agent_connfind);
+ if (!conn) {
+ uxsel_del(fd);
+ return;
+ }
+
+ if (!agent_try_read(conn))
+ return; /* more data to come */
+
+ /*
+ * We have now completed the agent query. Do the callback.
+ */
+ conn->callback(conn->callback_ctx, conn->retbuf, conn->retlen);
+ /* Null out conn->retbuf, since ownership of that buffer has
+ * passed to the callback. */
+ conn->retbuf = NULL;
+ agent_cancel_query(conn);
+}
+
+static const char *agent_socket_path(void)
+{
+ return getenv("SSH_AUTH_SOCK");
+}
+
+Socket *agent_connect(Plug *plug)
+{
+ const char *path = agent_socket_path();
+ if (!path)
+ return new_error_socket_fmt(plug, "SSH_AUTH_SOCK not set");
+ return sk_new(unix_sock_addr(path), 0, false, false, false, false, plug);
+}
+
+agent_pending_query *agent_query(
+ strbuf *query, void **out, int *outlen,
+ void (*callback)(void *, void *, int), void *callback_ctx)
+{
+ const char *name;
+ int sock;
+ struct sockaddr_un addr;
+ int done;
+ agent_pending_query *conn;
+
+ name = agent_socket_path();
+ if (!name || strlen(name) >= sizeof(addr.sun_path))
+ goto failure;
+
+ sock = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0) {
+ perror("socket(PF_UNIX)");
+ exit(1);
+ }
+
+ cloexec(sock);
+
+ addr.sun_family = AF_UNIX;
+ strcpy(addr.sun_path, name);
+ if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ close(sock);
+ goto failure;
+ }
+
+ strbuf_finalise_agent_query(query);
+
+ for (done = 0; done < query->len ;) {
+ int ret = write(sock, query->s + done,
+ query->len - done);
+ if (ret <= 0) {
+ close(sock);
+ goto failure;
+ }
+ done += ret;
+ }
+
+ conn = snew(agent_pending_query);
+ conn->fd = sock;
+ conn->retbuf = conn->sizebuf;
+ conn->retsize = 4;
+ conn->retlen = 0;
+ conn->callback = callback;
+ conn->callback_ctx = callback_ctx;
+
+ if (!callback) {
+ /*
+ * Bodge to permit making deliberately synchronous agent
+ * requests. Used by Unix Pageant in command-line client mode,
+ * which is legit because it really is true that no other part
+ * of the program is trying to get anything useful done
+ * simultaneously. But this special case shouldn't be used in
+ * any more general program.
+ */
+ no_nonblock(conn->fd);
+ while (!agent_try_read(conn))
+ /* empty loop body */;
+
+ *out = conn->retbuf;
+ *outlen = conn->retlen;
+ sfree(conn);
+ return NULL;
+ }
+
+ /*
+ * Otherwise do it properly: add conn to the tree of agent
+ * connections currently in flight, return 0 to indicate that the
+ * response hasn't been received yet, and call the callback when
+ * select_result comes back to us.
+ */
+ if (!agent_pending_queries)
+ agent_pending_queries = newtree234(agent_conncmp);
+ add234(agent_pending_queries, conn);
+
+ uxsel_set(sock, SELECT_R, agent_select_result);
+ return conn;
+
+ failure:
+ *out = NULL;
+ *outlen = 0;
+ return NULL;
+}
diff --git a/UNIX/uxagentsock.c b/unix/agent-socket.c
index 7e6187c6..7e6187c6 100644
--- a/UNIX/uxagentsock.c
+++ b/unix/agent-socket.c
diff --git a/unix/askpass.c b/unix/askpass.c
new file mode 100644
index 00000000..b45a1c23
--- /dev/null
+++ b/unix/askpass.c
@@ -0,0 +1,662 @@
+/*
+ * GTK implementation of a GUI password/passphrase prompt.
+ */
+
+#include <assert.h>
+#include <time.h>
+#include <stdlib.h>
+
+#include <unistd.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#if !GTK_CHECK_VERSION(3,0,0)
+#include <gdk/gdkkeysyms.h>
+#endif
+
+#include "defs.h"
+#include "unifont.h"
+#include "gtkcompat.h"
+#include "gtkmisc.h"
+
+#include "putty.h"
+#include "ssh.h"
+#include "misc.h"
+
+#define N_DRAWING_AREAS 3
+
+struct drawing_area_ctx {
+ GtkWidget *area;
+#ifndef DRAW_DEFAULT_CAIRO
+ GdkColor *cols;
+#endif
+ int width, height;
+ enum { NOT_CURRENT, CURRENT, GREYED_OUT } state;
+};
+
+struct askpass_ctx {
+ GtkWidget *dialog, *promptlabel;
+ struct drawing_area_ctx drawingareas[N_DRAWING_AREAS];
+ int active_area;
+#if GTK_CHECK_VERSION(2,0,0)
+ GtkIMContext *imc;
+#endif
+#ifndef DRAW_DEFAULT_CAIRO
+ GdkColormap *colmap;
+ GdkColor cols[3];
+#endif
+ char *error_message; /* if we finish without a passphrase */
+ char *passphrase; /* if we finish with one */
+ int passlen, passsize;
+#if GTK_CHECK_VERSION(3,20,0)
+ GdkSeat *seat; /* for gdk_seat_grab */
+#elif GTK_CHECK_VERSION(3,0,0)
+ GdkDevice *keyboard; /* for gdk_device_grab */
+#endif
+
+ int nattempts;
+};
+
+static prng *keypress_prng = NULL;
+static void feed_keypress_prng(void *data, int size)
+{
+ put_data(keypress_prng, data, size);
+}
+void random_add_noise(NoiseSourceId source, const void *noise, int length)
+{
+ if (keypress_prng)
+ prng_add_entropy(keypress_prng, source, make_ptrlen(noise, length));
+}
+static void setup_keypress_prng(void)
+{
+ keypress_prng = prng_new(&ssh_sha256);
+ prng_seed_begin(keypress_prng);
+ noise_get_heavy(feed_keypress_prng);
+ prng_seed_finish(keypress_prng);
+}
+static void cleanup_keypress_prng(void)
+{
+ prng_free(keypress_prng);
+}
+static uint64_t keypress_prng_value(void)
+{
+ /*
+ * Don't actually put the passphrase keystrokes themselves into
+ * the PRNG; that doesn't seem like the course of wisdom when
+ * that's precisely what the information displayed on the screen
+ * is trying _not_ to be correlated to.
+ */
+ noise_ultralight(NOISE_SOURCE_KEY, 0);
+ uint8_t data[8];
+ prng_read(keypress_prng, data, 8);
+ return GET_64BIT_MSB_FIRST(data);
+}
+static int choose_new_area(int prev_area)
+{
+ int reduced = keypress_prng_value() % (N_DRAWING_AREAS - 1);
+ return (prev_area + 1 + reduced) % N_DRAWING_AREAS;
+}
+
+static void visually_acknowledge_keypress(struct askpass_ctx *ctx)
+{
+ int new_active = choose_new_area(ctx->active_area);
+ ctx->drawingareas[ctx->active_area].state = NOT_CURRENT;
+ gtk_widget_queue_draw(ctx->drawingareas[ctx->active_area].area);
+ ctx->drawingareas[new_active].state = CURRENT;
+ gtk_widget_queue_draw(ctx->drawingareas[new_active].area);
+ ctx->active_area = new_active;
+}
+
+static int last_char_len(struct askpass_ctx *ctx)
+{
+ /*
+ * GTK always encodes in UTF-8, so we can do this in a fixed way.
+ */
+ int i;
+ assert(ctx->passlen > 0);
+ i = ctx->passlen - 1;
+ while ((unsigned)((unsigned char)ctx->passphrase[i] - 0x80) < 0x40) {
+ if (i == 0)
+ break;
+ i--;
+ }
+ return ctx->passlen - i;
+}
+
+static void add_text_to_passphrase(struct askpass_ctx *ctx, gchar *str)
+{
+ int len = strlen(str);
+ if (ctx->passlen + len >= ctx->passsize) {
+ /* Take some care with buffer expansion, because there are
+ * pieces of passphrase in the old buffer so we should ensure
+ * realloc doesn't leave a copy lying around in the address
+ * space. */
+ int oldsize = ctx->passsize;
+ char *newbuf;
+
+ ctx->passsize = (ctx->passlen + len) * 5 / 4 + 1024;
+ newbuf = snewn(ctx->passsize, char);
+ memcpy(newbuf, ctx->passphrase, oldsize);
+ smemclr(ctx->passphrase, oldsize);
+ sfree(ctx->passphrase);
+ ctx->passphrase = newbuf;
+ }
+ strcpy(ctx->passphrase + ctx->passlen, str);
+ ctx->passlen += len;
+ visually_acknowledge_keypress(ctx);
+}
+
+static void cancel_askpass(struct askpass_ctx *ctx, const char *msg)
+{
+ smemclr(ctx->passphrase, ctx->passsize);
+ ctx->passphrase = NULL;
+ ctx->error_message = dupstr(msg);
+ gtk_main_quit();
+}
+
+static gboolean askpass_dialog_closed(GtkWidget *widget, GdkEvent *event,
+ gpointer data)
+{
+ struct askpass_ctx *ctx = (struct askpass_ctx *)data;
+ cancel_askpass(ctx, "passphrase input cancelled");
+ /* Don't destroy dialog yet, so gtk_askpass_cleanup() can do its work */
+ return true;
+}
+
+static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+ struct askpass_ctx *ctx = (struct askpass_ctx *)data;
+
+ if (event->keyval == GDK_KEY_Return &&
+ event->type == GDK_KEY_PRESS) {
+ gtk_main_quit();
+ } else if (event->keyval == GDK_KEY_Escape &&
+ event->type == GDK_KEY_PRESS) {
+ cancel_askpass(ctx, "passphrase input cancelled");
+ } else {
+#if GTK_CHECK_VERSION(2,0,0)
+ if (gtk_im_context_filter_keypress(ctx->imc, event))
+ return true;
+#endif
+
+ if (event->type == GDK_KEY_PRESS) {
+ if (!strcmp(event->string, "\x15")) {
+ /* Ctrl-U. Wipe out the whole line */
+ ctx->passlen = 0;
+ visually_acknowledge_keypress(ctx);
+ } else if (!strcmp(event->string, "\x17")) {
+ /* Ctrl-W. Delete back to the last space->nonspace
+ * boundary. We interpret 'space' in a really simple
+ * way (mimicking terminal drivers), and don't attempt
+ * to second-guess exciting Unicode space
+ * characters. */
+ while (ctx->passlen > 0) {
+ char deleted, prior;
+ ctx->passlen -= last_char_len(ctx);
+ deleted = ctx->passphrase[ctx->passlen];
+ prior = (ctx->passlen == 0 ? ' ' :
+ ctx->passphrase[ctx->passlen-1]);
+ if (!g_ascii_isspace(deleted) && g_ascii_isspace(prior))
+ break;
+ }
+ visually_acknowledge_keypress(ctx);
+ } else if (event->keyval == GDK_KEY_BackSpace) {
+ /* Backspace. Delete one character. */
+ if (ctx->passlen > 0)
+ ctx->passlen -= last_char_len(ctx);
+ visually_acknowledge_keypress(ctx);
+#if !GTK_CHECK_VERSION(2,0,0)
+ } else if (event->string[0]) {
+ add_text_to_passphrase(ctx, event->string);
+#endif
+ }
+ }
+ }
+ return true;
+}
+
+#if GTK_CHECK_VERSION(2,0,0)
+static void input_method_commit_event(GtkIMContext *imc, gchar *str,
+ gpointer data)
+{
+ struct askpass_ctx *ctx = (struct askpass_ctx *)data;
+ add_text_to_passphrase(ctx, str);
+}
+#endif
+
+static gint configure_area(GtkWidget *widget, GdkEventConfigure *event,
+ gpointer data)
+{
+ struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
+ ctx->width = event->width;
+ ctx->height = event->height;
+ gtk_widget_queue_draw(widget);
+ return true;
+}
+
+#ifdef DRAW_DEFAULT_CAIRO
+static void askpass_redraw_cairo(cairo_t *cr, struct drawing_area_ctx *ctx)
+{
+ double rgbval = (ctx->state == CURRENT ? 0 :
+ ctx->state == NOT_CURRENT ? 1 : 0.5);
+ cairo_set_source_rgb(cr, rgbval, rgbval, rgbval);
+ cairo_paint(cr);
+}
+#else
+static void askpass_redraw_gdk(GdkWindow *win, struct drawing_area_ctx *ctx)
+{
+ GdkGC *gc = gdk_gc_new(win);
+ gdk_gc_set_foreground(gc, &ctx->cols[ctx->state]);
+ gdk_draw_rectangle(win, gc, true, 0, 0, ctx->width, ctx->height);
+ gdk_gc_unref(gc);
+}
+#endif
+
+#if GTK_CHECK_VERSION(3,0,0)
+static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
+{
+ struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
+ askpass_redraw_cairo(cr, ctx);
+ return true;
+}
+#else
+static gint expose_area(GtkWidget *widget, GdkEventExpose *event,
+ gpointer data)
+{
+ struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
+
+#ifdef DRAW_DEFAULT_CAIRO
+ cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(ctx->area));
+ askpass_redraw_cairo(cr, ctx);
+ cairo_destroy(cr);
+#else
+ askpass_redraw_gdk(gtk_widget_get_window(ctx->area), ctx);
+#endif
+
+ return true;
+}
+#endif
+
+static gboolean try_grab_keyboard(gpointer vctx)
+{
+ struct askpass_ctx *ctx = (struct askpass_ctx *)vctx;
+ int i, ret;
+
+#if GTK_CHECK_VERSION(3,20,0)
+ /*
+ * Grabbing the keyboard in GTK 3.20 requires the new notion of
+ * GdkSeat.
+ */
+ GdkSeat *seat;
+ GdkWindow *gdkw = gtk_widget_get_window(ctx->dialog);
+ if (!GDK_IS_WINDOW(gdkw) || !gdk_window_is_visible(gdkw))
+ goto fail;
+
+ seat = gdk_display_get_default_seat(
+ gtk_widget_get_display(ctx->dialog));
+ if (!seat)
+ goto fail;
+
+ ctx->seat = seat;
+ ret = gdk_seat_grab(seat, gdkw, GDK_SEAT_CAPABILITY_KEYBOARD,
+ true, NULL, NULL, NULL, NULL);
+
+ /*
+ * For some reason GDK 3.22 hides the GDK window as a side effect
+ * of a failed grab. I've no idea why. But if we're going to retry
+ * the grab, then we need to unhide it again or else we'll just
+ * get GDK_GRAB_NOT_VIEWABLE on every subsequent attempt.
+ */
+ if (ret != GDK_GRAB_SUCCESS)
+ gdk_window_show(gdkw);
+
+#elif GTK_CHECK_VERSION(3,0,0)
+ /*
+ * And it has to be done differently again prior to GTK 3.20.
+ */
+ GdkDeviceManager *dm;
+ GdkDevice *pointer, *keyboard;
+
+ dm = gdk_display_get_device_manager(
+ gtk_widget_get_display(ctx->dialog));
+ if (!dm)
+ goto fail;
+
+ pointer = gdk_device_manager_get_client_pointer(dm);
+ if (!pointer)
+ goto fail;
+ keyboard = gdk_device_get_associated_device(pointer);
+ if (!keyboard)
+ goto fail;
+ if (gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD)
+ goto fail;
+
+ ctx->keyboard = keyboard;
+ ret = gdk_device_grab(ctx->keyboard,
+ gtk_widget_get_window(ctx->dialog),
+ GDK_OWNERSHIP_NONE,
+ true,
+ GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
+ NULL,
+ GDK_CURRENT_TIME);
+#else
+ /*
+ * It's much simpler in GTK 1 and 2!
+ */
+ ret = gdk_keyboard_grab(gtk_widget_get_window(ctx->dialog),
+ false, GDK_CURRENT_TIME);
+#endif
+ if (ret != GDK_GRAB_SUCCESS)
+ goto fail;
+
+ /*
+ * Now that we've got the keyboard grab, connect up our keyboard
+ * handlers.
+ */
+#if GTK_CHECK_VERSION(2,0,0)
+ g_signal_connect(G_OBJECT(ctx->imc), "commit",
+ G_CALLBACK(input_method_commit_event), ctx);
+#endif
+ g_signal_connect(G_OBJECT(ctx->dialog), "key_press_event",
+ G_CALLBACK(key_event), ctx);
+ g_signal_connect(G_OBJECT(ctx->dialog), "key_release_event",
+ G_CALLBACK(key_event), ctx);
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_im_context_set_client_window(ctx->imc,
+ gtk_widget_get_window(ctx->dialog));
+#endif
+
+ /*
+ * And repaint the key-acknowledgment drawing areas as not greyed
+ * out.
+ */
+ ctx->active_area = keypress_prng_value() % N_DRAWING_AREAS;
+ for (i = 0; i < N_DRAWING_AREAS; i++) {
+ ctx->drawingareas[i].state =
+ (i == ctx->active_area ? CURRENT : NOT_CURRENT);
+ gtk_widget_queue_draw(ctx->drawingareas[i].area);
+ }
+
+ return false;
+
+ fail:
+ /*
+ * If we didn't get the grab, reschedule ourself on a timer to try
+ * again later.
+ *
+ * We have to do this rather than just trying once, because there
+ * is at least one important situation in which the grab may fail
+ * the first time: any user who is launching an add-key operation
+ * off some kind of window manager hotkey will almost by
+ * definition be running this script with a keyboard grab already
+ * active, namely the one-key grab that the WM (or whatever) uses
+ * to detect presses of the hotkey. So at the very least we have
+ * to give the user time to release that key.
+ */
+ if (++ctx->nattempts >= 4) {
+ cancel_askpass(ctx, "unable to grab keyboard after 5 seconds");
+ } else {
+ g_timeout_add(1000/8, try_grab_keyboard, ctx);
+ }
+ return false;
+}
+
+void realize(GtkWidget *widget, gpointer vctx)
+{
+ struct askpass_ctx *ctx = (struct askpass_ctx *)vctx;
+
+ gtk_grab_add(ctx->dialog);
+
+ /*
+ * Schedule the first attempt at the keyboard grab.
+ */
+ ctx->nattempts = 0;
+#if GTK_CHECK_VERSION(3,20,0)
+ ctx->seat = NULL;
+#elif GTK_CHECK_VERSION(3,0,0)
+ ctx->keyboard = NULL;
+#endif
+
+ g_idle_add(try_grab_keyboard, ctx);
+}
+
+static const char *gtk_askpass_setup(struct askpass_ctx *ctx,
+ const char *window_title,
+ const char *prompt_text)
+{
+ int i;
+ GtkBox *action_area;
+
+ ctx->passlen = 0;
+ ctx->passsize = 2048;
+ ctx->passphrase = snewn(ctx->passsize, char);
+
+ /*
+ * Create widgets.
+ */
+ ctx->dialog = our_dialog_new();
+ gtk_window_set_title(GTK_WINDOW(ctx->dialog), window_title);
+ gtk_window_set_position(GTK_WINDOW(ctx->dialog), GTK_WIN_POS_CENTER);
+ g_signal_connect(G_OBJECT(ctx->dialog), "delete-event",
+ G_CALLBACK(askpass_dialog_closed), ctx);
+ ctx->promptlabel = gtk_label_new(prompt_text);
+ align_label_left(GTK_LABEL(ctx->promptlabel));
+ gtk_widget_show(ctx->promptlabel);
+ gtk_label_set_line_wrap(GTK_LABEL(ctx->promptlabel), true);
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_label_set_width_chars(GTK_LABEL(ctx->promptlabel), 48);
+#endif
+ int margin = string_width("MM");
+#if GTK_CHECK_VERSION(3,12,0)
+ gtk_widget_set_margin_start(ctx->promptlabel, margin);
+ gtk_widget_set_margin_end(ctx->promptlabel, margin);
+#else
+ gtk_misc_set_padding(GTK_MISC(ctx->promptlabel), margin, 0);
+#endif
+ our_dialog_add_to_content_area(GTK_WINDOW(ctx->dialog),
+ ctx->promptlabel, true, true, 0);
+#if GTK_CHECK_VERSION(2,0,0)
+ ctx->imc = gtk_im_multicontext_new();
+#endif
+#ifndef DRAW_DEFAULT_CAIRO
+ {
+ gboolean success[2];
+ ctx->colmap = gdk_colormap_get_system();
+ ctx->cols[0].red = ctx->cols[0].green = ctx->cols[0].blue = 0xFFFF;
+ ctx->cols[1].red = ctx->cols[1].green = ctx->cols[1].blue = 0;
+ ctx->cols[2].red = ctx->cols[2].green = ctx->cols[2].blue = 0x8000;
+ gdk_colormap_alloc_colors(ctx->colmap, ctx->cols, 2,
+ false, true, success);
+ if (!success[0] || !success[1])
+ return "unable to allocate colours";
+ }
+#endif
+
+ action_area = our_dialog_make_action_hbox(GTK_WINDOW(ctx->dialog));
+
+ for (i = 0; i < N_DRAWING_AREAS; i++) {
+ ctx->drawingareas[i].area = gtk_drawing_area_new();
+#ifndef DRAW_DEFAULT_CAIRO
+ ctx->drawingareas[i].cols = ctx->cols;
+#endif
+ ctx->drawingareas[i].state = GREYED_OUT;
+ ctx->drawingareas[i].width = ctx->drawingareas[i].height = 0;
+ /* It would be nice to choose this size in some more
+ * context-sensitive way, like measuring the size of some
+ * piece of template text. */
+ gtk_widget_set_size_request(ctx->drawingareas[i].area, 32, 32);
+ gtk_box_pack_end(action_area, ctx->drawingareas[i].area,
+ true, true, 5);
+ g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
+ "configure_event",
+ G_CALLBACK(configure_area),
+ &ctx->drawingareas[i]);
+#if GTK_CHECK_VERSION(3,0,0)
+ g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
+ "draw",
+ G_CALLBACK(draw_area),
+ &ctx->drawingareas[i]);
+#else
+ g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
+ "expose_event",
+ G_CALLBACK(expose_area),
+ &ctx->drawingareas[i]);
+#endif
+
+#if GTK_CHECK_VERSION(3,0,0)
+ g_object_set(G_OBJECT(ctx->drawingareas[i].area),
+ "margin-bottom", 8, (const char *)NULL);
+#endif
+
+ gtk_widget_show(ctx->drawingareas[i].area);
+ }
+ ctx->active_area = -1;
+
+ /*
+ * Arrange to receive key events. We don't really need to worry
+ * from a UI perspective about which widget gets the events, as
+ * long as we know which it is so we can catch them. So we'll pick
+ * the prompt label at random, and we'll use gtk_grab_add to
+ * ensure key events go to it.
+ */
+ gtk_widget_set_sensitive(ctx->dialog, true);
+
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_window_set_keep_above(GTK_WINDOW(ctx->dialog), true);
+#endif
+
+ /*
+ * Wait for the key-receiving widget to actually be created, in
+ * order to call gtk_grab_add on it.
+ */
+ g_signal_connect(G_OBJECT(ctx->dialog), "realize",
+ G_CALLBACK(realize), ctx);
+
+ /*
+ * Show the window.
+ */
+ gtk_widget_show(ctx->dialog);
+
+ return NULL;
+}
+
+static void gtk_askpass_cleanup(struct askpass_ctx *ctx)
+{
+#if GTK_CHECK_VERSION(3,20,0)
+ if (ctx->seat)
+ gdk_seat_ungrab(ctx->seat);
+#elif GTK_CHECK_VERSION(3,0,0)
+ if (ctx->keyboard)
+ gdk_device_ungrab(ctx->keyboard, GDK_CURRENT_TIME);
+#else
+ gdk_keyboard_ungrab(GDK_CURRENT_TIME);
+#endif
+ gtk_grab_remove(ctx->promptlabel);
+
+ if (ctx->passphrase) {
+ assert(ctx->passlen < ctx->passsize);
+ ctx->passphrase[ctx->passlen] = '\0';
+ }
+
+ gtk_widget_destroy(ctx->dialog);
+}
+
+static bool setup_gtk(const char *display)
+{
+ static bool gtk_initialised = false;
+ int argc;
+ char *real_argv[3];
+ char **argv = real_argv;
+ bool ret;
+
+ if (gtk_initialised)
+ return true;
+
+ argc = 0;
+ argv[argc++] = dupstr("dummy");
+ argv[argc++] = dupprintf("--display=%s", display);
+ argv[argc] = NULL;
+ ret = gtk_init_check(&argc, &argv);
+ while (argc > 0)
+ sfree(argv[--argc]);
+
+ gtk_initialised = ret;
+ return ret;
+}
+
+const bool buildinfo_gtk_relevant = true;
+
+char *gtk_askpass_main(const char *display, const char *wintitle,
+ const char *prompt, bool *success)
+{
+ struct askpass_ctx ctx[1];
+ const char *err;
+
+ ctx->passphrase = NULL;
+ ctx->error_message = NULL;
+
+ /* In case gtk_init hasn't been called yet by the program */
+ if (!setup_gtk(display)) {
+ *success = false;
+ return dupstr("unable to initialise GTK");
+ }
+
+ if ((err = gtk_askpass_setup(ctx, wintitle, prompt)) != NULL) {
+ *success = false;
+ return dupprintf("%s", err);
+ }
+ setup_keypress_prng();
+ gtk_main();
+ cleanup_keypress_prng();
+ gtk_askpass_cleanup(ctx);
+
+ if (ctx->passphrase) {
+ *success = true;
+ return ctx->passphrase;
+ } else {
+ *success = false;
+ return ctx->error_message;
+ }
+}
+
+#ifdef TEST_ASKPASS
+void modalfatalbox(const char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "FATAL ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ bool success;
+ int exitcode;
+ char *ret;
+
+ gtk_init(&argc, &argv);
+
+ if (argc != 2) {
+ success = false;
+ ret = dupprintf("usage: %s <prompt text>", argv[0]);
+ } else {
+ ret = gtk_askpass_main(NULL, "Enter passphrase", argv[1], &success);
+ }
+
+ if (!success) {
+ fputs(ret, stderr);
+ fputc('\n', stderr);
+ exitcode = 1;
+ } else {
+ fputs(ret, stdout);
+ fputc('\n', stdout);
+ exitcode = 0;
+ }
+
+ smemclr(ret, strlen(ret));
+ return exitcode;
+}
+#endif
diff --git a/UNIX/uxcliloop.c b/unix/cliloop.c
index 23a808da..23a808da 100644
--- a/UNIX/uxcliloop.c
+++ b/unix/cliloop.c
diff --git a/unix/columns.c b/unix/columns.c
new file mode 100644
index 00000000..3dbed9c3
--- /dev/null
+++ b/unix/columns.c
@@ -0,0 +1,1276 @@
+/*
+ * columns.c - implementation of the `Columns' GTK layout container.
+ */
+
+#include <assert.h>
+#include <gtk/gtk.h>
+#include "defs.h"
+#include "gtkcompat.h"
+#include "columns.h"
+
+#if GTK_CHECK_VERSION(2,0,0)
+/* The "focus" method lives in GtkWidget from GTK 2 onwards, but it
+ * was in GtkContainer in GTK 1 */
+#define FOCUS_METHOD_SUPERCLASS GtkWidget
+#define FOCUS_METHOD_LOCATION widget_class /* used in columns_init */
+#define CHILD_FOCUS(cont, dir) gtk_widget_child_focus(GTK_WIDGET(cont), dir)
+#else
+#define FOCUS_METHOD_SUPERCLASS GtkContainer
+#define FOCUS_METHOD_LOCATION container_class
+#define CHILD_FOCUS(cont, dir) gtk_container_focus(GTK_CONTAINER(cont), dir)
+#endif
+
+static void columns_init(Columns *cols);
+static void columns_class_init(ColumnsClass *klass);
+#if !GTK_CHECK_VERSION(2,0,0)
+static void columns_finalize(GtkObject *object);
+#else
+static void columns_finalize(GObject *object);
+#endif
+static void columns_map(GtkWidget *widget);
+static void columns_unmap(GtkWidget *widget);
+#if !GTK_CHECK_VERSION(2,0,0)
+static void columns_draw(GtkWidget *widget, GdkRectangle *area);
+static gint columns_expose(GtkWidget *widget, GdkEventExpose *event);
+#endif
+static void columns_base_add(GtkContainer *container, GtkWidget *widget);
+static void columns_remove(GtkContainer *container, GtkWidget *widget);
+static void columns_forall(GtkContainer *container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data);
+static gint columns_focus(FOCUS_METHOD_SUPERCLASS *container,
+ GtkDirectionType dir);
+static GType columns_child_type(GtkContainer *container);
+#if GTK_CHECK_VERSION(3,0,0)
+static void columns_get_preferred_width(GtkWidget *widget,
+ gint *min, gint *nat);
+static void columns_get_preferred_height(GtkWidget *widget,
+ gint *min, gint *nat);
+static void columns_get_preferred_width_for_height(GtkWidget *widget,
+ gint height,
+ gint *min, gint *nat);
+static void columns_get_preferred_height_for_width(GtkWidget *widget,
+ gint width,
+ gint *min, gint *nat);
+#else
+static void columns_size_request(GtkWidget *widget, GtkRequisition *req);
+#endif
+static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc);
+
+static GtkContainerClass *parent_class = NULL;
+
+#if !GTK_CHECK_VERSION(2,0,0)
+GType columns_get_type(void)
+{
+ static GType columns_type = 0;
+
+ if (!columns_type) {
+ static const GtkTypeInfo columns_info = {
+ "Columns",
+ sizeof(Columns),
+ sizeof(ColumnsClass),
+ (GtkClassInitFunc) columns_class_init,
+ (GtkObjectInitFunc) columns_init,
+ /* reserved_1 */ NULL,
+ /* reserved_2 */ NULL,
+ (GtkClassInitFunc) NULL,
+ };
+
+ columns_type = gtk_type_unique(GTK_TYPE_CONTAINER, &columns_info);
+ }
+
+ return columns_type;
+}
+#else
+GType columns_get_type(void)
+{
+ static GType columns_type = 0;
+
+ if (!columns_type) {
+ static const GTypeInfo columns_info = {
+ sizeof(ColumnsClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) columns_class_init,
+ NULL,
+ NULL,
+ sizeof(Columns),
+ 0,
+ (GInstanceInitFunc)columns_init,
+ };
+
+ columns_type = g_type_register_static(GTK_TYPE_CONTAINER, "Columns",
+ &columns_info, 0);
+ }
+
+ return columns_type;
+}
+#endif
+
+static gint (*columns_inherited_focus)(FOCUS_METHOD_SUPERCLASS *container,
+ GtkDirectionType direction);
+
+static void columns_class_init(ColumnsClass *klass)
+{
+#if !GTK_CHECK_VERSION(2,0,0)
+ GtkObjectClass *object_class = (GtkObjectClass *)klass;
+ GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
+ GtkContainerClass *container_class = (GtkContainerClass *)klass;
+#else
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
+#endif
+
+#if !GTK_CHECK_VERSION(2,0,0)
+ parent_class = gtk_type_class(GTK_TYPE_CONTAINER);
+#else
+ parent_class = g_type_class_peek_parent(klass);
+#endif
+
+ object_class->finalize = columns_finalize;
+ widget_class->map = columns_map;
+ widget_class->unmap = columns_unmap;
+#if !GTK_CHECK_VERSION(2,0,0)
+ widget_class->draw = columns_draw;
+ widget_class->expose_event = columns_expose;
+#endif
+#if GTK_CHECK_VERSION(3,0,0)
+ widget_class->get_preferred_width = columns_get_preferred_width;
+ widget_class->get_preferred_height = columns_get_preferred_height;
+ widget_class->get_preferred_width_for_height =
+ columns_get_preferred_width_for_height;
+ widget_class->get_preferred_height_for_width =
+ columns_get_preferred_height_for_width;
+#else
+ widget_class->size_request = columns_size_request;
+#endif
+ widget_class->size_allocate = columns_size_allocate;
+
+ container_class->add = columns_base_add;
+ container_class->remove = columns_remove;
+ container_class->forall = columns_forall;
+ container_class->child_type = columns_child_type;
+
+ /* Save the previous value of this method. */
+ if (!columns_inherited_focus)
+ columns_inherited_focus = FOCUS_METHOD_LOCATION->focus;
+ FOCUS_METHOD_LOCATION->focus = columns_focus;
+}
+
+static void columns_init(Columns *cols)
+{
+ gtk_widget_set_has_window(GTK_WIDGET(cols), false);
+
+ cols->children = NULL;
+ cols->spacing = 0;
+}
+
+static void columns_child_free(gpointer vchild)
+{
+ ColumnsChild *child = (ColumnsChild *)vchild;
+ if (child->percentages)
+ g_free(child->percentages);
+ g_free(child);
+}
+
+static void columns_finalize(
+#if !GTK_CHECK_VERSION(2,0,0)
+ GtkObject *object
+#else
+ GObject *object
+#endif
+ )
+{
+ Columns *cols;
+
+ g_return_if_fail(object != NULL);
+ g_return_if_fail(IS_COLUMNS(object));
+
+ cols = COLUMNS(object);
+
+#if !GTK_CHECK_VERSION(2,0,0)
+ {
+ GList *node;
+ for (node = cols->children; node; node = node->next)
+ if (node->data)
+ columns_child_free(node->data);
+ }
+ g_list_free(cols->children);
+#else
+ g_list_free_full(cols->children, columns_child_free);
+#endif
+
+ cols->children = NULL;
+
+#if !GTK_CHECK_VERSION(2,0,0)
+ GTK_OBJECT_CLASS(parent_class)->finalize(object);
+#else
+ G_OBJECT_CLASS(parent_class)->finalize(object);
+#endif
+}
+
+/*
+ * These appear to be thoroughly tedious functions; the only reason
+ * we have to reimplement them at all is because we defined our own
+ * format for our GList of children...
+ */
+static void columns_map(GtkWidget *widget)
+{
+ Columns *cols;
+ ColumnsChild *child;
+ GList *children;
+
+ g_return_if_fail(widget != NULL);
+ g_return_if_fail(IS_COLUMNS(widget));
+
+ cols = COLUMNS(widget);
+ gtk_widget_set_mapped(GTK_WIDGET(cols), true);
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ if (child->widget &&
+ gtk_widget_get_visible(child->widget) &&
+ !gtk_widget_get_mapped(child->widget))
+ gtk_widget_map(child->widget);
+ }
+}
+static void columns_unmap(GtkWidget *widget)
+{
+ Columns *cols;
+ ColumnsChild *child;
+ GList *children;
+
+ g_return_if_fail(widget != NULL);
+ g_return_if_fail(IS_COLUMNS(widget));
+
+ cols = COLUMNS(widget);
+ gtk_widget_set_mapped(GTK_WIDGET(cols), false);
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ if (child->widget &&
+ gtk_widget_get_visible(child->widget) &&
+ gtk_widget_get_mapped(child->widget))
+ gtk_widget_unmap(child->widget);
+ }
+}
+#if !GTK_CHECK_VERSION(2,0,0)
+static void columns_draw(GtkWidget *widget, GdkRectangle *area)
+{
+ Columns *cols;
+ ColumnsChild *child;
+ GList *children;
+ GdkRectangle child_area;
+
+ g_return_if_fail(widget != NULL);
+ g_return_if_fail(IS_COLUMNS(widget));
+
+ if (GTK_WIDGET_DRAWABLE(widget)) {
+ cols = COLUMNS(widget);
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ if (child->widget &&
+ GTK_WIDGET_DRAWABLE(child->widget) &&
+ gtk_widget_intersect(child->widget, area, &child_area))
+ gtk_widget_draw(child->widget, &child_area);
+ }
+ }
+}
+static gint columns_expose(GtkWidget *widget, GdkEventExpose *event)
+{
+ Columns *cols;
+ ColumnsChild *child;
+ GList *children;
+ GdkEventExpose child_event;
+
+ g_return_val_if_fail(widget != NULL, false);
+ g_return_val_if_fail(IS_COLUMNS(widget), false);
+ g_return_val_if_fail(event != NULL, false);
+
+ if (GTK_WIDGET_DRAWABLE(widget)) {
+ cols = COLUMNS(widget);
+ child_event = *event;
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ if (child->widget &&
+ GTK_WIDGET_DRAWABLE(child->widget) &&
+ GTK_WIDGET_NO_WINDOW(child->widget) &&
+ gtk_widget_intersect(child->widget, &event->area,
+ &child_event.area))
+ gtk_widget_event(child->widget, (GdkEvent *)&child_event);
+ }
+ }
+ return false;
+}
+#endif
+
+static void columns_base_add(GtkContainer *container, GtkWidget *widget)
+{
+ Columns *cols;
+
+ g_return_if_fail(container != NULL);
+ g_return_if_fail(IS_COLUMNS(container));
+ g_return_if_fail(widget != NULL);
+
+ cols = COLUMNS(container);
+
+ /*
+ * Default is to add a new widget spanning all columns.
+ */
+ columns_add(cols, widget, 0, 0); /* 0 means ncols */
+}
+
+static void columns_remove(GtkContainer *container, GtkWidget *widget)
+{
+ Columns *cols;
+ ColumnsChild *child;
+ GtkWidget *childw;
+ GList *children;
+
+ g_return_if_fail(container != NULL);
+ g_return_if_fail(IS_COLUMNS(container));
+ g_return_if_fail(widget != NULL);
+
+ cols = COLUMNS(container);
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ if (child->widget != widget)
+ continue;
+
+ bool need_layout = false;
+ if (gtk_widget_get_visible(widget))
+ need_layout = true;
+ gtk_widget_unparent(widget);
+ cols->children = g_list_remove_link(cols->children, children);
+ g_list_free(children);
+
+ /* Unlink this widget from its valign list, and if anything
+ * else on the list is still visible, ensure we recompute our
+ * layout */
+ for (ColumnsChild *ch = child->valign_next; ch != child;
+ ch = ch->valign_next)
+ if (gtk_widget_get_visible(ch->widget))
+ need_layout = true;
+ child->valign_next->valign_prev = child->valign_prev;
+ child->valign_prev->valign_next = child->valign_next;
+
+ if (cols->vexpand == child)
+ cols->vexpand = NULL;
+
+ g_free(child);
+ if (need_layout)
+ gtk_widget_queue_resize(GTK_WIDGET(container));
+ break;
+ }
+
+ for (children = cols->taborder;
+ children && (childw = children->data);
+ children = children->next) {
+ if (childw != widget)
+ continue;
+
+ cols->taborder = g_list_remove_link(cols->taborder, children);
+ g_list_free(children);
+ break;
+ }
+}
+
+static void columns_forall(GtkContainer *container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data)
+{
+ Columns *cols;
+ ColumnsChild *child;
+ GList *children, *next;
+
+ g_return_if_fail(container != NULL);
+ g_return_if_fail(IS_COLUMNS(container));
+ g_return_if_fail(callback != NULL);
+
+ cols = COLUMNS(container);
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = next) {
+ /*
+ * We can't wait until after the callback to assign
+ * `children = children->next', because the callback might
+ * be gtk_widget_destroy, which would remove the link
+ * `children' from the list! So instead we must get our
+ * hands on the value of the `next' pointer _before_ the
+ * callback.
+ */
+ next = children->next;
+ if (child->widget)
+ callback(child->widget, callback_data);
+ }
+}
+
+static GType columns_child_type(GtkContainer *container)
+{
+ return GTK_TYPE_WIDGET;
+}
+
+GtkWidget *columns_new(gint spacing)
+{
+ Columns *cols;
+
+#if !GTK_CHECK_VERSION(2,0,0)
+ cols = gtk_type_new(columns_get_type());
+#else
+ cols = g_object_new(TYPE_COLUMNS, NULL);
+#endif
+
+ cols->spacing = spacing;
+
+ return GTK_WIDGET(cols);
+}
+
+void columns_set_cols(Columns *cols, gint ncols, const gint *percentages)
+{
+ ColumnsChild *childdata;
+ gint i;
+
+ g_return_if_fail(cols != NULL);
+ g_return_if_fail(IS_COLUMNS(cols));
+ g_return_if_fail(ncols > 0);
+ g_return_if_fail(percentages != NULL);
+
+ childdata = g_new(ColumnsChild, 1);
+ childdata->widget = NULL;
+ childdata->ncols = ncols;
+ childdata->percentages = g_new(gint, ncols);
+ childdata->force_left = false;
+ for (i = 0; i < ncols; i++)
+ childdata->percentages[i] = percentages[i];
+
+ cols->children = g_list_append(cols->children, childdata);
+}
+
+void columns_add(Columns *cols, GtkWidget *child,
+ gint colstart, gint colspan)
+{
+ ColumnsChild *childdata;
+
+ g_return_if_fail(cols != NULL);
+ g_return_if_fail(IS_COLUMNS(cols));
+ g_return_if_fail(child != NULL);
+ g_return_if_fail(gtk_widget_get_parent(child) == NULL);
+
+ childdata = g_new(ColumnsChild, 1);
+ childdata->widget = child;
+ childdata->colstart = colstart;
+ childdata->colspan = colspan;
+ childdata->force_left = false;
+ childdata->valign_next = childdata;
+ childdata->valign_prev = childdata;
+ childdata->percentages = NULL;
+
+ cols->children = g_list_append(cols->children, childdata);
+ cols->taborder = g_list_append(cols->taborder, child);
+
+ gtk_widget_set_parent(child, GTK_WIDGET(cols));
+
+ if (gtk_widget_get_realized(GTK_WIDGET(cols)))
+ gtk_widget_realize(child);
+
+ if (gtk_widget_get_visible(GTK_WIDGET(cols)) &&
+ gtk_widget_get_visible(child)) {
+ if (gtk_widget_get_mapped(GTK_WIDGET(cols)))
+ gtk_widget_map(child);
+ gtk_widget_queue_resize(child);
+ }
+}
+
+static ColumnsChild *columns_find_child(Columns *cols, GtkWidget *widget)
+{
+ GList *children;
+ ColumnsChild *child;
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+
+ if (child->widget == widget)
+ return child;
+ }
+
+ return NULL;
+}
+
+void columns_force_left_align(Columns *cols, GtkWidget *widget)
+{
+ ColumnsChild *child;
+
+ g_return_if_fail(cols != NULL);
+ g_return_if_fail(IS_COLUMNS(cols));
+ g_return_if_fail(widget != NULL);
+
+ child = columns_find_child(cols, widget);
+ g_return_if_fail(child != NULL);
+
+ child->force_left = true;
+ if (gtk_widget_get_visible(widget))
+ gtk_widget_queue_resize(GTK_WIDGET(cols));
+}
+
+void columns_align_next_to(Columns *cols, GtkWidget *cw1, GtkWidget *cw2)
+{
+ ColumnsChild *child1, *child2;
+
+ g_return_if_fail(cols != NULL);
+ g_return_if_fail(IS_COLUMNS(cols));
+ g_return_if_fail(cw1 != NULL);
+ g_return_if_fail(cw2 != NULL);
+
+ child1 = columns_find_child(cols, cw1);
+ g_return_if_fail(child1 != NULL);
+ child2 = columns_find_child(cols, cw2);
+ g_return_if_fail(child2 != NULL);
+
+ ColumnsChild *child1prev = child1->valign_prev;
+ ColumnsChild *child2prev = child2->valign_prev;
+ child1prev->valign_next = child2;
+ child2->valign_prev = child1prev;
+ child2prev->valign_next = child1;
+ child1->valign_prev = child2prev;
+
+ if (gtk_widget_get_visible(cw1) || gtk_widget_get_visible(cw2))
+ gtk_widget_queue_resize(GTK_WIDGET(cols));
+}
+
+void columns_taborder_last(Columns *cols, GtkWidget *widget)
+{
+ GtkWidget *childw;
+ GList *children;
+
+ g_return_if_fail(cols != NULL);
+ g_return_if_fail(IS_COLUMNS(cols));
+ g_return_if_fail(widget != NULL);
+
+ for (children = cols->taborder;
+ children && (childw = children->data);
+ children = children->next) {
+ if (childw != widget)
+ continue;
+
+ cols->taborder = g_list_remove_link(cols->taborder, children);
+ g_list_free(children);
+ cols->taborder = g_list_append(cols->taborder, widget);
+ break;
+ }
+}
+
+void columns_vexpand(Columns *cols, GtkWidget *widget)
+{
+ g_return_if_fail(cols != NULL);
+ g_return_if_fail(IS_COLUMNS(cols));
+ g_return_if_fail(widget != NULL);
+
+ ColumnsChild *child = columns_find_child(cols, widget);
+ g_return_if_fail(child != NULL);
+
+ cols->vexpand = child;
+}
+
+/*
+ * Override GtkContainer's focus movement so the user can
+ * explicitly specify the tab order.
+ */
+static gint columns_focus(FOCUS_METHOD_SUPERCLASS *super, GtkDirectionType dir)
+{
+ Columns *cols;
+ GList *pos;
+ GtkWidget *focuschild;
+
+ g_return_val_if_fail(super != NULL, false);
+ g_return_val_if_fail(IS_COLUMNS(super), false);
+
+ cols = COLUMNS(super);
+
+ if (!gtk_widget_is_drawable(GTK_WIDGET(cols)) ||
+ !gtk_widget_is_sensitive(GTK_WIDGET(cols)))
+ return false;
+
+ if (!gtk_widget_get_can_focus(GTK_WIDGET(cols)) &&
+ (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
+
+ focuschild = gtk_container_get_focus_child(GTK_CONTAINER(cols));
+ gtk_container_set_focus_child(GTK_CONTAINER(cols), NULL);
+
+ if (dir == GTK_DIR_TAB_FORWARD)
+ pos = cols->taborder;
+ else
+ pos = g_list_last(cols->taborder);
+
+ while (pos) {
+ GtkWidget *child = pos->data;
+
+ if (focuschild) {
+ if (focuschild == child) {
+ focuschild = NULL; /* now we can start looking in here */
+ if (gtk_widget_is_drawable(child) &&
+ GTK_IS_CONTAINER(child) &&
+ !gtk_widget_has_focus(child)) {
+ if (CHILD_FOCUS(child, dir))
+ return true;
+ }
+ }
+ } else if (gtk_widget_is_drawable(child)) {
+ if (GTK_IS_CONTAINER(child)) {
+ if (CHILD_FOCUS(child, dir))
+ return true;
+ } else if (gtk_widget_get_can_focus(child)) {
+ gtk_widget_grab_focus(child);
+ return true;
+ }
+ }
+
+ if (dir == GTK_DIR_TAB_FORWARD)
+ pos = pos->next;
+ else
+ pos = pos->prev;
+ }
+
+ return false;
+ } else
+ return columns_inherited_focus(super, dir);
+}
+
+/*
+ * Underlying parts of the layout algorithm, to compute the Columns
+ * container's width or height given the widths or heights of its
+ * children. These will be called in various ways with different
+ * notions of width and height in use, so we abstract them out and
+ * pass them a 'get width' or 'get height' function pointer.
+ */
+
+typedef gint (*widget_dim_fn_t)(ColumnsChild *child);
+
+static gint columns_compute_width(Columns *cols, widget_dim_fn_t get_width)
+{
+ ColumnsChild *child;
+ GList *children;
+ gint i, ncols, colspan, retwidth, childwidth;
+ const gint *percentages;
+ static const gint onecol[] = { 100 };
+
+#ifdef COLUMNS_WIDTH_DIAGNOSTICS
+ printf("compute_width(%p): start\n", cols);
+#endif
+
+ retwidth = 0;
+
+ ncols = 1;
+ percentages = onecol;
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+
+ if (!child->widget) {
+ /* Column reconfiguration. */
+ ncols = child->ncols;
+ percentages = child->percentages;
+ continue;
+ }
+
+ /* Only take visible widgets into account. */
+ if (!gtk_widget_get_visible(child->widget))
+ continue;
+
+ childwidth = get_width(child);
+ colspan = child->colspan ? child->colspan : ncols-child->colstart;
+ assert(colspan > 0);
+
+#ifdef COLUMNS_WIDTH_DIAGNOSTICS
+ printf("compute_width(%p): ", cols);
+ if (GTK_IS_LABEL(child->widget))
+ printf("label %p '%s' wrap=%s: ", child->widget,
+ gtk_label_get_text(GTK_LABEL(child->widget)),
+ (gtk_label_get_line_wrap(GTK_LABEL(child->widget))
+ ? "true" : "false"));
+ else
+ printf("widget %p: ", child->widget);
+ {
+ gint min, nat;
+ gtk_widget_get_preferred_width(child->widget, &min, &nat);
+ printf("minwidth=%d natwidth=%d ", min, nat);
+ }
+ printf("thiswidth=%d span=%d\n", childwidth, colspan);
+#endif
+
+ /*
+ * To compute width: we know that childwidth + cols->spacing
+ * needs to equal a certain percentage of the full width of
+ * the container. So we work this value out, figure out how
+ * wide the container will need to be to make that percentage
+ * of it equal to that width, and ensure our returned width is
+ * at least that much. Very simple really.
+ */
+ {
+ int percent, thiswid, fullwid;
+
+ percent = 0;
+ for (i = 0; i < colspan; i++)
+ percent += percentages[child->colstart+i];
+
+ thiswid = childwidth + cols->spacing;
+ /*
+ * Since childwidth is (at least sometimes) the _minimum_
+ * size the child needs, we must ensure that it gets _at
+ * least_ that size. Hence, when scaling thiswid up to
+ * fullwid, we must round up, which means adding percent-1
+ * before dividing by percent.
+ */
+ fullwid = (thiswid * 100 + percent - 1) / percent;
+#ifdef COLUMNS_WIDTH_DIAGNOSTICS
+ printf("compute_width(%p): after %p, thiswid=%d fullwid=%d\n",
+ cols, child->widget, thiswid, fullwid);
+#endif
+
+ /*
+ * The above calculation assumes every widget gets
+ * cols->spacing on the right. So we subtract
+ * cols->spacing here to account for the extra load of
+ * spacing on the right.
+ */
+ if (retwidth < fullwid - cols->spacing)
+ retwidth = fullwid - cols->spacing;
+ }
+ }
+
+ retwidth += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
+
+#ifdef COLUMNS_WIDTH_DIAGNOSTICS
+ printf("compute_width(%p): done, returning %d\n", cols, retwidth);
+#endif
+
+ return retwidth;
+}
+
+static void columns_alloc_horiz(Columns *cols, gint ourwidth,
+ widget_dim_fn_t get_width)
+{
+ ColumnsChild *child;
+ GList *children;
+ gint i, ncols, colspan, border, *colxpos, childwidth;
+
+ border = gtk_container_get_border_width(GTK_CONTAINER(cols));
+
+ ncols = 1;
+ /* colxpos gives the starting x position of each column.
+ * We supply n+1 of them, so that we can find the RH edge easily.
+ * All ending x positions are expected to be adjusted afterwards by
+ * subtracting the spacing. */
+ colxpos = g_new(gint, 2);
+ colxpos[0] = 0;
+ colxpos[1] = ourwidth - 2*border + cols->spacing;
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+
+ if (!child->widget) {
+ gint percent;
+
+ /* Column reconfiguration. */
+ ncols = child->ncols;
+ colxpos = g_renew(gint, colxpos, ncols + 1);
+ colxpos[0] = 0;
+ percent = 0;
+ for (i = 0; i < ncols; i++) {
+ percent += child->percentages[i];
+ colxpos[i+1] = (((ourwidth - 2*border) + cols->spacing)
+ * percent / 100);
+ }
+ continue;
+ }
+
+ /* Only take visible widgets into account. */
+ if (!gtk_widget_get_visible(child->widget))
+ continue;
+
+ childwidth = get_width(child);
+ colspan = child->colspan ? child->colspan : ncols-child->colstart;
+
+ /*
+ * Starting x position is cols[colstart].
+ * Ending x position is cols[colstart+colspan] - spacing.
+ *
+ * Unless we're forcing left, in which case the width is
+ * exactly the requisition width.
+ */
+ child->x = colxpos[child->colstart];
+ if (child->force_left)
+ child->w = childwidth;
+ else
+ child->w = (colxpos[child->colstart+colspan] -
+ colxpos[child->colstart] - cols->spacing);
+ }
+
+ g_free(colxpos);
+}
+
+static gint columns_compute_height(Columns *cols, widget_dim_fn_t get_height)
+{
+ ColumnsChild *child;
+ GList *children;
+ gint i, ncols, colspan, *colypos, retheight, childheight;
+
+ retheight = cols->spacing;
+
+ ncols = 1;
+ colypos = g_new(gint, 1);
+ colypos[0] = 0;
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+
+ if (!child->widget) {
+ /* Column reconfiguration. */
+ for (i = 1; i < ncols; i++) {
+ if (colypos[0] < colypos[i])
+ colypos[0] = colypos[i];
+ }
+ ncols = child->ncols;
+ colypos = g_renew(gint, colypos, ncols);
+ for (i = 1; i < ncols; i++)
+ colypos[i] = colypos[0];
+ continue;
+ }
+
+ /* Only take visible widgets into account. */
+ if (!gtk_widget_get_visible(child->widget))
+ continue;
+
+ childheight = get_height(child);
+ for (ColumnsChild *ch = child->valign_next; ch != child;
+ ch = ch->valign_next) {
+ gint childheight2 = get_height(ch);
+ if (childheight < childheight2)
+ childheight = childheight2;
+ }
+ colspan = child->colspan ? child->colspan : ncols-child->colstart;
+
+ /*
+ * To compute height: the widget's top will be positioned at
+ * the largest y value so far reached in any of the columns it
+ * crosses. Then it will go down by childheight plus padding;
+ * and the point it reaches at the bottom is the new y value
+ * in all those columns, and minus the padding it is also a
+ * lower bound on our own height.
+ */
+ {
+ int topy, boty;
+
+ topy = 0;
+ for (i = 0; i < colspan; i++) {
+ if (topy < colypos[child->colstart+i])
+ topy = colypos[child->colstart+i];
+ }
+ boty = topy + childheight + cols->spacing;
+ for (i = 0; i < colspan; i++) {
+ colypos[child->colstart+i] = boty;
+ }
+
+ if (retheight < boty - cols->spacing)
+ retheight = boty - cols->spacing;
+ }
+ }
+
+ retheight += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
+
+ g_free(colypos);
+
+ return retheight;
+}
+
+static void columns_alloc_vert(Columns *cols, gint ourheight,
+ widget_dim_fn_t get_height)
+{
+ ColumnsChild *child;
+ GList *children;
+ gint i, ncols, colspan, *colypos, realheight, fakeheight, vexpand_extra;
+
+ if (cols->vexpand) {
+ gint minheight = columns_compute_height(cols, get_height);
+ vexpand_extra = ourheight - minheight;
+ } else {
+ vexpand_extra = 0;
+ }
+
+ ncols = 1;
+ /* As in size_request, colypos is the lowest y reached in each column. */
+ colypos = g_new(gint, 1);
+ colypos[0] = 0;
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next)
+ child->visited = false;
+
+ /*
+ * Main layout loop. In this loop, vertically aligned controls are
+ * only half dealt with: we assign each one enough _height_ to
+ * match the others in its group, but we don't adjust its y
+ * coordinates yet.
+ */
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ if (!child->widget) {
+ /* Column reconfiguration. */
+ for (i = 1; i < ncols; i++) {
+ if (colypos[0] < colypos[i])
+ colypos[0] = colypos[i];
+ }
+ ncols = child->ncols;
+ colypos = g_renew(gint, colypos, ncols);
+ for (i = 1; i < ncols; i++)
+ colypos[i] = colypos[0];
+ continue;
+ }
+
+ /* Only take visible widgets into account. */
+ if (!gtk_widget_get_visible(child->widget))
+ continue;
+
+ int ymin = 0;
+
+ realheight = get_height(child);
+ if (child == cols->vexpand)
+ realheight += vexpand_extra;
+ fakeheight = realheight;
+ for (ColumnsChild *ch = child->valign_next; ch != child;
+ ch = ch->valign_next) {
+ gint childheight2 = get_height(ch);
+ if (fakeheight < childheight2)
+ fakeheight = childheight2;
+ if (ch->visited && ymin < ch->y)
+ ymin = ch->y;
+ }
+ colspan = child->colspan ? child->colspan : ncols-child->colstart;
+
+ /*
+ * To compute height: the widget's top will be positioned
+ * at the largest y value so far reached in any of the
+ * columns it crosses. Then it will go down by creq.height
+ * plus padding; and the point it reaches at the bottom is
+ * the new y value in all those columns.
+ */
+ {
+ int topy, boty;
+
+ topy = ymin;
+ for (i = 0; i < colspan; i++) {
+ if (topy < colypos[child->colstart+i])
+ topy = colypos[child->colstart+i];
+ }
+ child->y = topy;
+ child->h = realheight;
+ child->visited = true;
+ boty = topy + fakeheight + cols->spacing;
+ for (i = 0; i < colspan; i++) {
+ colypos[child->colstart+i] = boty;
+ }
+ }
+ }
+
+ /*
+ * Now make a separate pass that deals with vertical alignment by
+ * moving controls downwards based on the difference between their
+ * own height and the largest height of anything in their group.
+ */
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ if (!child->widget)
+ continue;
+ if (!gtk_widget_get_visible(child->widget))
+ continue;
+
+ fakeheight = realheight = child->h;
+ for (ColumnsChild *ch = child->valign_next; ch != child;
+ ch = ch->valign_next) {
+ if (fakeheight < ch->h)
+ fakeheight = ch->h;
+ }
+ child->y += fakeheight/2 - realheight/2;
+ }
+
+ g_free(colypos);
+}
+
+/*
+ * Now here comes the interesting bit. The actual layout part is
+ * done in the following two functions:
+ *
+ * columns_size_request() examines the list of widgets held in the
+ * Columns, and returns a requisition stating the absolute minimum
+ * size it can bear to be.
+ *
+ * columns_size_allocate() is given an allocation telling it what
+ * size the whole container is going to be, and it calls
+ * gtk_widget_size_allocate() on all of its (visible) children to
+ * set their size and position relative to the top left of the
+ * container.
+ */
+
+#if !GTK_CHECK_VERSION(3,0,0)
+
+static gint columns_gtk2_get_width(ColumnsChild *child)
+{
+ GtkRequisition creq;
+ gtk_widget_size_request(child->widget, &creq);
+ return creq.width;
+}
+
+static gint columns_gtk2_get_height(ColumnsChild *child)
+{
+ GtkRequisition creq;
+ gtk_widget_size_request(child->widget, &creq);
+ return creq.height;
+}
+
+static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
+{
+ Columns *cols;
+
+ g_return_if_fail(widget != NULL);
+ g_return_if_fail(IS_COLUMNS(widget));
+ g_return_if_fail(req != NULL);
+
+ cols = COLUMNS(widget);
+
+ req->width = columns_compute_width(cols, columns_gtk2_get_width);
+ req->height = columns_compute_height(cols, columns_gtk2_get_height);
+}
+
+static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
+{
+ Columns *cols;
+ ColumnsChild *child;
+ GList *children;
+ gint border;
+
+ g_return_if_fail(widget != NULL);
+ g_return_if_fail(IS_COLUMNS(widget));
+ g_return_if_fail(alloc != NULL);
+
+ cols = COLUMNS(widget);
+ gtk_widget_set_allocation(widget, alloc);
+
+ border = gtk_container_get_border_width(GTK_CONTAINER(cols));
+
+ columns_alloc_horiz(cols, alloc->width, columns_gtk2_get_width);
+ columns_alloc_vert(cols, alloc->height, columns_gtk2_get_height);
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ if (child->widget && gtk_widget_get_visible(child->widget)) {
+ GtkAllocation call;
+ call.x = alloc->x + border + child->x;
+ call.y = alloc->y + border + child->y;
+ call.width = child->w;
+ call.height = child->h;
+ gtk_widget_size_allocate(child->widget, &call);
+ }
+ }
+}
+
+#else /* GTK_CHECK_VERSION(3,0,0) */
+
+static gint columns_gtk3_get_min_width(ColumnsChild *child)
+{
+ gint ret;
+ gtk_widget_get_preferred_width(child->widget, &ret, NULL);
+ return ret;
+}
+
+static gint columns_gtk3_get_nat_width(ColumnsChild *child)
+{
+ gint ret;
+
+ if ((GTK_IS_LABEL(child->widget) &&
+ gtk_label_get_line_wrap(GTK_LABEL(child->widget))) ||
+ GTK_IS_ENTRY(child->widget)) {
+ /*
+ * We treat wrapping GtkLabels as a special case in this
+ * layout class, because the whole point of those is that I
+ * _don't_ want them to take up extra horizontal space for
+ * long text, but instead to wrap it to whatever size is used
+ * by the rest of the layout.
+ *
+ * GtkEntry gets similar treatment, because in OS X GTK I've
+ * found that it requests a natural width regardless of the
+ * output of gtk_entry_set_width_chars.
+ */
+ gtk_widget_get_preferred_width(child->widget, &ret, NULL);
+ } else {
+ gtk_widget_get_preferred_width(child->widget, NULL, &ret);
+ }
+ return ret;
+}
+
+static gint columns_gtk3_get_minfh_width(ColumnsChild *child)
+{
+ gint ret;
+ gtk_widget_get_preferred_width_for_height(child->widget, child->h,
+ &ret, NULL);
+ return ret;
+}
+
+static gint columns_gtk3_get_natfh_width(ColumnsChild *child)
+{
+ gint ret;
+ gtk_widget_get_preferred_width_for_height(child->widget, child->h,
+ NULL, &ret);
+ return ret;
+}
+
+static gint columns_gtk3_get_min_height(ColumnsChild *child)
+{
+ gint ret;
+ gtk_widget_get_preferred_height(child->widget, &ret, NULL);
+ return ret;
+}
+
+static gint columns_gtk3_get_nat_height(ColumnsChild *child)
+{
+ gint ret;
+ gtk_widget_get_preferred_height(child->widget, NULL, &ret);
+ return ret;
+}
+
+static gint columns_gtk3_get_minfw_height(ColumnsChild *child)
+{
+ gint ret;
+ gtk_widget_get_preferred_height_for_width(child->widget, child->w,
+ &ret, NULL);
+ return ret;
+}
+
+static gint columns_gtk3_get_natfw_height(ColumnsChild *child)
+{
+ gint ret;
+ gtk_widget_get_preferred_height_for_width(child->widget, child->w,
+ NULL, &ret);
+ return ret;
+}
+
+static void columns_get_preferred_width(GtkWidget *widget,
+ gint *min, gint *nat)
+{
+ Columns *cols;
+
+ g_return_if_fail(widget != NULL);
+ g_return_if_fail(IS_COLUMNS(widget));
+
+ cols = COLUMNS(widget);
+
+ if (min)
+ *min = columns_compute_width(cols, columns_gtk3_get_min_width);
+ if (nat)
+ *nat = columns_compute_width(cols, columns_gtk3_get_nat_width);
+}
+
+static void columns_get_preferred_height(GtkWidget *widget,
+ gint *min, gint *nat)
+{
+ Columns *cols;
+
+ g_return_if_fail(widget != NULL);
+ g_return_if_fail(IS_COLUMNS(widget));
+
+ cols = COLUMNS(widget);
+
+ if (min)
+ *min = columns_compute_height(cols, columns_gtk3_get_min_height);
+ if (nat)
+ *nat = columns_compute_height(cols, columns_gtk3_get_nat_height);
+}
+
+static void columns_get_preferred_width_for_height(GtkWidget *widget,
+ gint height,
+ gint *min, gint *nat)
+{
+ Columns *cols;
+
+ g_return_if_fail(widget != NULL);
+ g_return_if_fail(IS_COLUMNS(widget));
+
+ cols = COLUMNS(widget);
+
+ /* FIXME: which one should the get-height function here be? */
+ columns_alloc_vert(cols, height, columns_gtk3_get_nat_height);
+
+ if (min)
+ *min = columns_compute_width(cols, columns_gtk3_get_minfh_width);
+ if (nat)
+ *nat = columns_compute_width(cols, columns_gtk3_get_natfh_width);
+}
+
+static void columns_get_preferred_height_for_width(GtkWidget *widget,
+ gint width,
+ gint *min, gint *nat)
+{
+ Columns *cols;
+
+ g_return_if_fail(widget != NULL);
+ g_return_if_fail(IS_COLUMNS(widget));
+
+ cols = COLUMNS(widget);
+
+ /* FIXME: which one should the get-height function here be? */
+ columns_alloc_horiz(cols, width, columns_gtk3_get_nat_width);
+
+ if (min)
+ *min = columns_compute_height(cols, columns_gtk3_get_minfw_height);
+ if (nat)
+ *nat = columns_compute_height(cols, columns_gtk3_get_natfw_height);
+}
+
+static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
+{
+ Columns *cols;
+ ColumnsChild *child;
+ GList *children;
+ gint border;
+
+ g_return_if_fail(widget != NULL);
+ g_return_if_fail(IS_COLUMNS(widget));
+ g_return_if_fail(alloc != NULL);
+
+ cols = COLUMNS(widget);
+ gtk_widget_set_allocation(widget, alloc);
+
+ border = gtk_container_get_border_width(GTK_CONTAINER(cols));
+
+ columns_alloc_horiz(cols, alloc->width, columns_gtk3_get_min_width);
+ columns_alloc_vert(cols, alloc->height, columns_gtk3_get_minfw_height);
+
+ for (children = cols->children;
+ children && (child = children->data);
+ children = children->next) {
+ if (child->widget && gtk_widget_get_visible(child->widget)) {
+ GtkAllocation call;
+ call.x = alloc->x + border + child->x;
+ call.y = alloc->y + border + child->y;
+ call.width = child->w;
+ call.height = child->h;
+ gtk_widget_size_allocate(child->widget, &call);
+ }
+ }
+}
+
+#endif
diff --git a/unix/columns.h b/unix/columns.h
new file mode 100644
index 00000000..bca306b4
--- /dev/null
+++ b/unix/columns.h
@@ -0,0 +1,73 @@
+/*
+ * columns.h - header file for a columns-based widget container
+ * capable of supporting the PuTTY portable dialog box layout
+ * mechanism.
+ */
+
+#ifndef COLUMNS_H
+#define COLUMNS_H
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define TYPE_COLUMNS (columns_get_type())
+#define COLUMNS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_COLUMNS, Columns))
+#define COLUMNS_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_COLUMNS, ColumnsClass))
+#define IS_COLUMNS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_COLUMNS))
+#define IS_COLUMNS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_COLUMNS))
+
+typedef struct Columns_tag Columns;
+typedef struct ColumnsClass_tag ColumnsClass;
+typedef struct ColumnsChild_tag ColumnsChild;
+
+struct Columns_tag {
+ GtkContainer container;
+ /* private after here */
+ GList *children; /* this holds ColumnsChild structures */
+ GList *taborder; /* this just holds GtkWidgets */
+ ColumnsChild *vexpand;
+ gint spacing;
+};
+
+struct ColumnsClass_tag {
+ GtkContainerClass parent_class;
+};
+
+struct ColumnsChild_tag {
+ /* If `widget' is non-NULL, this entry represents an actual widget. */
+ GtkWidget *widget;
+ gint colstart, colspan;
+ bool force_left; /* for recalcitrant GtkLabels */
+ /* Otherwise, this entry represents a change in the column setup. */
+ gint ncols;
+ gint *percentages;
+ gint x, y, w, h; /* used during an individual size computation */
+
+ /* Circularly linked list of children that are vertically aligned
+ * with each other. */
+ ColumnsChild *valign_next, *valign_prev;
+
+ /* Temporary space used within some methods */
+ bool visited;
+};
+
+GType columns_get_type(void);
+GtkWidget *columns_new(gint spacing);
+void columns_set_cols(Columns *cols, gint ncols, const gint *percentages);
+void columns_add(Columns *cols, GtkWidget *child,
+ gint colstart, gint colspan);
+void columns_taborder_last(Columns *cols, GtkWidget *child);
+void columns_force_left_align(Columns *cols, GtkWidget *child);
+void columns_align_next_to(Columns *cols, GtkWidget *ch1, GtkWidget *ch2);
+void columns_vexpand(Columns *cols, GtkWidget *child);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* COLUMNS_H */
diff --git a/unix/config-gtk.c b/unix/config-gtk.c
new file mode 100644
index 00000000..be70f5cb
--- /dev/null
+++ b/unix/config-gtk.c
@@ -0,0 +1,160 @@
+/*
+ * config-gtk.c - the GTK-specific parts of the PuTTY configuration
+ * box.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "dialog.h"
+#include "storage.h"
+
+static void about_handler(dlgcontrol *ctrl, dlgparam *dlg,
+ void *data, int event)
+{
+ if (event == EVENT_ACTION) {
+ about_box(ctrl->context.p);
+ }
+}
+
+void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win)
+{
+ struct controlset *s, *s2;
+ dlgcontrol *c;
+ int i;
+
+ if (!midsession) {
+ /*
+ * Add the About button to the standard panel.
+ */
+ s = ctrl_getset(b, "", "", "");
+ c = ctrl_pushbutton(s, "About", 'a', HELPCTX(no_help),
+ about_handler, P(win));
+ c->column = 0;
+ }
+
+ /*
+ * GTK makes it rather easier to put the scrollbar on the left
+ * than Windows does!
+ */
+ s = ctrl_getset(b, "Window", "scrollback",
+ "Control the scrollback in the window");
+ ctrl_checkbox(s, "Scrollbar on left", 'l',
+ HELPCTX(no_help),
+ conf_checkbox_handler,
+ I(CONF_scrollbar_on_left));
+ /*
+ * Really this wants to go just after `Display scrollbar'. See
+ * if we can find that control, and do some shuffling.
+ */
+ for (i = 0; i < s->ncontrols; i++) {
+ c = s->ctrls[i];
+ if (c->type == CTRL_CHECKBOX &&
+ c->context.i == CONF_scrollbar) {
+ /*
+ * Control i is the scrollbar checkbox.
+ * Control s->ncontrols-1 is the scrollbar-on-left one.
+ */
+ if (i < s->ncontrols-2) {
+ c = s->ctrls[s->ncontrols-1];
+ memmove(s->ctrls+i+2, s->ctrls+i+1,
+ (s->ncontrols-i-2)*sizeof(dlgcontrol *));
+ s->ctrls[i+1] = c;
+ }
+ break;
+ }
+ }
+
+ /*
+ * X requires three more fonts: bold, wide, and wide-bold; also
+ * we need the fiddly shadow-bold-offset control. This would
+ * make the Window/Appearance panel rather unwieldy and large,
+ * so I think the sensible thing here is to _move_ this
+ * controlset into a separate Window/Fonts panel!
+ */
+ s2 = ctrl_getset(b, "Window/Appearance", "font",
+ "Font settings");
+ /* Remove this controlset from b. */
+ for (i = 0; i < b->nctrlsets; i++) {
+ if (b->ctrlsets[i] == s2) {
+ memmove(b->ctrlsets+i, b->ctrlsets+i+1,
+ (b->nctrlsets-i-1) * sizeof(*b->ctrlsets));
+ b->nctrlsets--;
+ ctrl_free_set(s2);
+ break;
+ }
+ }
+ ctrl_settitle(b, "Window/Fonts", "Options controlling font usage");
+ s = ctrl_getset(b, "Window/Fonts", "font",
+ "Fonts for displaying non-bold text");
+ ctrl_fontsel(s, "Font used for ordinary text", 'f',
+ HELPCTX(no_help),
+ conf_fontsel_handler, I(CONF_font));
+ ctrl_fontsel(s, "Font used for wide (CJK) text", 'w',
+ HELPCTX(no_help),
+ conf_fontsel_handler, I(CONF_widefont));
+ s = ctrl_getset(b, "Window/Fonts", "fontbold",
+ "Fonts for displaying bolded text");
+ ctrl_fontsel(s, "Font used for bolded text", 'b',
+ HELPCTX(no_help),
+ conf_fontsel_handler, I(CONF_boldfont));
+ ctrl_fontsel(s, "Font used for bold wide text", 'i',
+ HELPCTX(no_help),
+ conf_fontsel_handler, I(CONF_wideboldfont));
+ ctrl_checkbox(s, "Use shadow bold instead of bold fonts", 'u',
+ HELPCTX(no_help),
+ conf_checkbox_handler,
+ I(CONF_shadowbold));
+ ctrl_text(s, "(Note that bold fonts or shadow bolding are only"
+ " used if you have not requested bolding to be done by"
+ " changing the text colour.)",
+ HELPCTX(no_help));
+ ctrl_editbox(s, "Horizontal offset for shadow bold:", 'z', 20,
+ HELPCTX(no_help), conf_editbox_handler,
+ I(CONF_shadowboldoffset), ED_INT);
+
+ /*
+ * Markus Kuhn feels, not totally unreasonably, that it's good
+ * for all applications to shift into UTF-8 mode if they notice
+ * that they've been started with a LANG setting dictating it,
+ * so that people don't have to keep remembering a separate
+ * UTF-8 option for every application they use. Therefore,
+ * here's an override option in the Translation panel.
+ */
+ s = ctrl_getset(b, "Window/Translation", "trans",
+ "Character set translation on received data");
+ ctrl_checkbox(s, "Override with UTF-8 if locale says so", 'l',
+ HELPCTX(translation_utf8_override),
+ conf_checkbox_handler,
+ I(CONF_utf8_override));
+
+#ifdef OSX_META_KEY_CONFIG
+ /*
+ * On OS X, there are multiple reasonable opinions about whether
+ * Option or Command (or both, or neither) should act as a Meta
+ * key, or whether they should have their normal OS functions.
+ */
+ s = ctrl_getset(b, "Terminal/Keyboard", "meta",
+ "Choose the Meta key:");
+ ctrl_checkbox(s, "Option key acts as Meta", 'p',
+ HELPCTX(no_help),
+ conf_checkbox_handler, I(CONF_osx_option_meta));
+ ctrl_checkbox(s, "Command key acts as Meta", 'm',
+ HELPCTX(no_help),
+ conf_checkbox_handler, I(CONF_osx_command_meta));
+#endif
+
+ if (!midsession) {
+ /*
+ * Allow the user to specify the window class as part of the saved
+ * configuration, so that they can have their window manager treat
+ * different kinds of PuTTY and pterm differently if they want to.
+ */
+ s = ctrl_getset(b, "Window/Behaviour", "x11",
+ "X Window System settings");
+ ctrl_editbox(s, "Window class name:", 'z', 50,
+ HELPCTX(no_help), conf_editbox_handler,
+ I(CONF_winclass), ED_STR);
+ }
+}
diff --git a/unix/config-unix.c b/unix/config-unix.c
new file mode 100644
index 00000000..179d8a9c
--- /dev/null
+++ b/unix/config-unix.c
@@ -0,0 +1,49 @@
+/*
+ * config-unix.c - the Unix-specific parts of the PuTTY configuration
+ * box.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "dialog.h"
+#include "storage.h"
+
+void unix_setup_config_box(struct controlbox *b, bool midsession, int protocol)
+{
+ struct controlset *s;
+ dlgcontrol *c;
+
+ /*
+ * The Conf structure contains two Unix-specific elements which
+ * are not configured in here: stamp_utmp and login_shell. This
+ * is because pterm does not put up a configuration box right at
+ * the start, which is the only time when these elements would
+ * be useful to configure.
+ */
+
+ /*
+ * On Unix, we don't have a drop-down list for the printer
+ * control.
+ */
+ s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing");
+ assert(s->ncontrols == 1 && s->ctrls[0]->type == CTRL_EDITBOX);
+ s->ctrls[0]->editbox.has_list = false;
+
+ /*
+ * Unix supports a local-command proxy.
+ */
+ if (!midsession) {
+ int i;
+ s = ctrl_getset(b, "Connection/Proxy", "basics", NULL);
+ for (i = 0; i < s->ncontrols; i++) {
+ c = s->ctrls[i];
+ if (c->type == CTRL_LISTBOX &&
+ c->handler == proxy_type_handler) {
+ c->context.i |= PROXY_UI_FLAG_LOCAL;
+ break;
+ }
+ }
+ }
+}
diff --git a/unix/configure b/unix/configure
deleted file mode 100755
index b2b033d8..00000000
--- a/unix/configure
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-$(echo "$0" | sed '$s!configure$!../configure!') "$@"
diff --git a/unix/console.c b/unix/console.c
new file mode 100644
index 00000000..286ecf29
--- /dev/null
+++ b/unix/console.c
@@ -0,0 +1,579 @@
+/*
+ * unix/console.c: various interactive-prompt routines shared between
+ * the Unix console PuTTY tools
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <termios.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/time.h>
+
+#include "putty.h"
+#include "storage.h"
+#include "ssh.h"
+#include "console.h"
+
+static struct termios orig_termios_stderr;
+static bool stderr_is_a_tty;
+
+void stderr_tty_init()
+{
+ /* Ensure that if stderr is a tty, we can get it back to a sane state. */
+ if (isatty(STDERR_FILENO)) {
+ stderr_is_a_tty = true;
+ tcgetattr(STDERR_FILENO, &orig_termios_stderr);
+ }
+}
+
+void premsg(struct termios *cf)
+{
+ if (stderr_is_a_tty) {
+ tcgetattr(STDERR_FILENO, cf);
+ tcsetattr(STDERR_FILENO, TCSADRAIN, &orig_termios_stderr);
+ }
+}
+void postmsg(struct termios *cf)
+{
+ if (stderr_is_a_tty)
+ tcsetattr(STDERR_FILENO, TCSADRAIN, cf);
+}
+
+void cleanup_exit(int code)
+{
+ /*
+ * Clean up.
+ */
+ sk_cleanup();
+ random_save_seed();
+ exit(code);
+}
+
+void console_print_error_msg(const char *prefix, const char *msg)
+{
+ struct termios cf;
+ premsg(&cf);
+ fputs(prefix, stderr);
+ fputs(": ", stderr);
+ fputs(msg, stderr);
+ fputc('\n', stderr);
+ fflush(stderr);
+ postmsg(&cf);
+}
+
+/*
+ * Wrapper around Unix read(2), suitable for use on a file descriptor
+ * that's been set into nonblocking mode. Handles EAGAIN/EWOULDBLOCK
+ * by means of doing a one-fd poll and then trying again; all other
+ * errors (including errors from poll) are returned to the caller.
+ */
+static int block_and_read(int fd, void *buf, size_t len)
+{
+ int ret;
+ pollwrapper *pw = pollwrap_new();
+
+ while ((ret = read(fd, buf, len)) < 0 && (
+#ifdef EAGAIN
+ (errno == EAGAIN) ||
+#endif
+#ifdef EWOULDBLOCK
+ (errno == EWOULDBLOCK) ||
+#endif
+ false)) {
+
+ pollwrap_clear(pw);
+ pollwrap_add_fd_rwx(pw, fd, SELECT_R);
+ do {
+ ret = pollwrap_poll_endless(pw);
+ } while (ret < 0 && errno == EINTR);
+ assert(ret != 0);
+ if (ret < 0) {
+ pollwrap_free(pw);
+ return ret;
+ }
+ assert(pollwrap_check_fd_rwx(pw, fd, SELECT_R));
+ }
+
+ pollwrap_free(pw);
+ return ret;
+}
+
+SeatPromptResult console_confirm_ssh_host_key(
+ Seat *seat, const char *host, int port, const char *keytype,
+ char *keystr, SeatDialogText *text, HelpCtx helpctx,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{
+ char line[32];
+ struct termios cf;
+ const char *prompt = NULL;
+
+ stdio_sink errsink[1];
+ stdio_sink_init(errsink, stderr);
+
+ premsg(&cf);
+
+ for (SeatDialogTextItem *item = text->items,
+ *end = item+text->nitems; item < end; item++) {
+ switch (item->type) {
+ case SDT_PARA:
+ wordwrap(BinarySink_UPCAST(errsink),
+ ptrlen_from_asciz(item->text), 60);
+ fputc('\n', stderr);
+ break;
+ case SDT_DISPLAY:
+ fprintf(stderr, " %s\n", item->text);
+ break;
+ case SDT_SCARY_HEADING:
+ /* Can't change font size or weight in this context */
+ fprintf(stderr, "%s\n", item->text);
+ break;
+ case SDT_BATCH_ABORT:
+ if (console_batch_mode) {
+ fprintf(stderr, "%s\n", item->text);
+ fflush(stderr);
+ postmsg(&cf);
+ return SPR_SW_ABORT("Cannot confirm a host key in batch mode");
+ }
+ break;
+ case SDT_PROMPT:
+ prompt = item->text;
+ break;
+ default:
+ break;
+ }
+ }
+ assert(prompt); /* something in the SeatDialogText should have set this */
+
+ while (true) {
+ fprintf(stderr,
+ "%s (y/n, Return cancels connection, i for more info) ",
+ prompt);
+ fflush(stderr);
+
+ struct termios oldmode, newmode;
+ tcgetattr(0, &oldmode);
+ newmode = oldmode;
+ newmode.c_lflag |= ECHO | ISIG | ICANON;
+ tcsetattr(0, TCSANOW, &newmode);
+ line[0] = '\0';
+ if (block_and_read(0, line, sizeof(line) - 1) <= 0)
+ /* handled below */;
+ tcsetattr(0, TCSANOW, &oldmode);
+
+ if (line[0] == 'i' || line[0] == 'I') {
+ for (SeatDialogTextItem *item = text->items,
+ *end = item+text->nitems; item < end; item++) {
+ switch (item->type) {
+ case SDT_MORE_INFO_KEY:
+ fprintf(stderr, "%s", item->text);
+ break;
+ case SDT_MORE_INFO_VALUE_SHORT:
+ fprintf(stderr, ": %s\n", item->text);
+ break;
+ case SDT_MORE_INFO_VALUE_BLOB:
+ fprintf(stderr, ":\n%s\n", item->text);
+ break;
+ default:
+ break;
+ }
+ }
+ } else {
+ break;
+ }
+ }
+
+ /* In case of misplaced reflexes from another program, also recognise 'q'
+ * as 'abandon connection rather than trust this key' */
+ if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n' &&
+ line[0] != 'q' && line[0] != 'Q') {
+ if (line[0] == 'y' || line[0] == 'Y')
+ store_host_key(host, port, keytype, keystr);
+ postmsg(&cf);
+ return SPR_OK;
+ } else {
+ fputs(console_abandoned_msg, stderr);
+ postmsg(&cf);
+ return SPR_USER_ABORT;
+ }
+}
+
+SeatPromptResult console_confirm_weak_crypto_primitive(
+ Seat *seat, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{
+ char line[32];
+ struct termios cf;
+
+ premsg(&cf);
+ fprintf(stderr, weakcrypto_msg_common_fmt, algtype, algname);
+
+ if (console_batch_mode) {
+ fputs(console_abandoned_msg, stderr);
+ postmsg(&cf);
+ return SPR_SW_ABORT("Cannot confirm a weak crypto primitive "
+ "in batch mode");
+ }
+
+ fputs(console_continue_prompt, stderr);
+ fflush(stderr);
+
+ {
+ struct termios oldmode, newmode;
+ tcgetattr(0, &oldmode);
+ newmode = oldmode;
+ newmode.c_lflag |= ECHO | ISIG | ICANON;
+ tcsetattr(0, TCSANOW, &newmode);
+ line[0] = '\0';
+ if (block_and_read(0, line, sizeof(line) - 1) <= 0)
+ /* handled below */;
+ tcsetattr(0, TCSANOW, &oldmode);
+ }
+
+ if (line[0] == 'y' || line[0] == 'Y') {
+ postmsg(&cf);
+ return SPR_OK;
+ } else {
+ fputs(console_abandoned_msg, stderr);
+ postmsg(&cf);
+ return SPR_USER_ABORT;
+ }
+}
+
+SeatPromptResult console_confirm_weak_cached_hostkey(
+ Seat *seat, const char *algname, const char *betteralgs,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{
+ char line[32];
+ struct termios cf;
+
+ premsg(&cf);
+ fprintf(stderr, weakhk_msg_common_fmt, algname, betteralgs);
+
+ if (console_batch_mode) {
+ fputs(console_abandoned_msg, stderr);
+ postmsg(&cf);
+ return SPR_SW_ABORT("Cannot confirm a weak cached host key "
+ "in batch mode");
+ }
+
+ fputs(console_continue_prompt, stderr);
+ fflush(stderr);
+
+ {
+ struct termios oldmode, newmode;
+ tcgetattr(0, &oldmode);
+ newmode = oldmode;
+ newmode.c_lflag |= ECHO | ISIG | ICANON;
+ tcsetattr(0, TCSANOW, &newmode);
+ line[0] = '\0';
+ if (block_and_read(0, line, sizeof(line) - 1) <= 0)
+ /* handled below */;
+ tcsetattr(0, TCSANOW, &oldmode);
+ }
+
+ if (line[0] == 'y' || line[0] == 'Y') {
+ postmsg(&cf);
+ return SPR_OK;
+ } else {
+ fputs(console_abandoned_msg, stderr);
+ postmsg(&cf);
+ return SPR_USER_ABORT;
+ }
+}
+
+/*
+ * Ask whether to wipe a session log file before writing to it.
+ * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
+ */
+int console_askappend(LogPolicy *lp, Filename *filename,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ static const char msgtemplate[] =
+ "The session log file \"%.*s\" already exists.\n"
+ "You can overwrite it with a new session log,\n"
+ "append your session log to the end of it,\n"
+ "or disable session logging for this session.\n"
+ "Enter \"y\" to wipe the file, \"n\" to append to it,\n"
+ "or just press Return to disable logging.\n"
+ "Wipe the log file? (y/n, Return cancels logging) ";
+
+ static const char msgtemplate_batch[] =
+ "The session log file \"%.*s\" already exists.\n"
+ "Logging will not be enabled.\n";
+
+ char line[32];
+ struct termios cf;
+
+ premsg(&cf);
+ if (console_batch_mode) {
+ fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename->path);
+ fflush(stderr);
+ return 0;
+ }
+ fprintf(stderr, msgtemplate, FILENAME_MAX, filename->path);
+ fflush(stderr);
+
+ {
+ struct termios oldmode, newmode;
+ tcgetattr(0, &oldmode);
+ newmode = oldmode;
+ newmode.c_lflag |= ECHO | ISIG | ICANON;
+ tcsetattr(0, TCSANOW, &newmode);
+ line[0] = '\0';
+ if (block_and_read(0, line, sizeof(line) - 1) <= 0)
+ /* handled below */;
+ tcsetattr(0, TCSANOW, &oldmode);
+ }
+
+ postmsg(&cf);
+ if (line[0] == 'y' || line[0] == 'Y')
+ return 2;
+ else if (line[0] == 'n' || line[0] == 'N')
+ return 1;
+ else
+ return 0;
+}
+
+bool console_antispoof_prompt = true;
+
+void console_set_trust_status(Seat *seat, bool trusted)
+{
+ /* Do nothing in response to a change of trust status, because
+ * there's nothing we can do in a console environment. However,
+ * the query function below will make a fiddly decision about
+ * whether to tell the backend to enable fallback handling. */
+}
+
+bool console_can_set_trust_status(Seat *seat)
+{
+ if (console_batch_mode) {
+ /*
+ * In batch mode, we don't need to worry about the server
+ * mimicking our interactive authentication, because the user
+ * already knows not to expect any.
+ */
+ return true;
+ }
+
+ return false;
+}
+
+bool console_has_mixed_input_stream(Seat *seat)
+{
+ if (!is_interactive() || !console_antispoof_prompt) {
+ /*
+ * If standard input isn't connected to a terminal, then even
+ * if the server did send a spoof authentication prompt, the
+ * user couldn't respond to it via the terminal anyway.
+ *
+ * We also pretend this is true if the user has purposely
+ * disabled the antispoof prompt.
+ */
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Warn about the obsolescent key file format.
+ *
+ * Uniquely among these functions, this one does _not_ expect a
+ * frontend handle. This means that if PuTTY is ported to a
+ * platform which requires frontend handles, this function will be
+ * an anomaly. Fortunately, the problem it addresses will not have
+ * been present on that platform, so it can plausibly be
+ * implemented as an empty function.
+ */
+void old_keyfile_warning(void)
+{
+ static const char message[] =
+ "You are loading an SSH-2 private key which has an\n"
+ "old version of the file format. This means your key\n"
+ "file is not fully tamperproof. Future versions of\n"
+ "PuTTY may stop supporting this private key format,\n"
+ "so we recommend you convert your key to the new\n"
+ "format.\n"
+ "\n"
+ "Once the key is loaded into PuTTYgen, you can perform\n"
+ "this conversion simply by saving it again.\n";
+
+ struct termios cf;
+ premsg(&cf);
+ fputs(message, stderr);
+ postmsg(&cf);
+}
+
+void console_logging_error(LogPolicy *lp, const char *string)
+{
+ /* Errors setting up logging are considered important, so they're
+ * displayed to standard error even when not in verbose mode */
+ struct termios cf;
+ premsg(&cf);
+ fprintf(stderr, "%s\n", string);
+ fflush(stderr);
+ postmsg(&cf);
+}
+
+
+void console_eventlog(LogPolicy *lp, const char *string)
+{
+ /* Ordinary Event Log entries are displayed in the same way as
+ * logging errors, but only in verbose mode */
+ if (lp_verbose(lp))
+ console_logging_error(lp, string);
+}
+
+StripCtrlChars *console_stripctrl_new(
+ Seat *seat, BinarySink *bs_out, SeatInteractionContext sic)
+{
+ return stripctrl_new(bs_out, false, 0);
+}
+
+/*
+ * Special functions to read and print to the console for password
+ * prompts and the like. Uses /dev/tty or stdin/stderr, in that order
+ * of preference; also sanitises escape sequences out of the text, on
+ * the basis that it might have been sent by a hostile SSH server
+ * doing malicious keyboard-interactive.
+ */
+static void console_open(FILE **outfp, int *infd)
+{
+ int fd;
+
+ if ((fd = open("/dev/tty", O_RDWR)) >= 0) {
+ *infd = fd;
+ *outfp = fdopen(*infd, "w");
+ } else {
+ *infd = 0;
+ *outfp = stderr;
+ }
+}
+static void console_close(FILE *outfp, int infd)
+{
+ if (outfp != stderr)
+ fclose(outfp); /* will automatically close infd too */
+}
+
+static void console_write(FILE *outfp, ptrlen data)
+{
+ fwrite(data.ptr, 1, data.len, outfp);
+ fflush(outfp);
+}
+
+SeatPromptResult console_get_userpass_input(prompts_t *p)
+{
+ size_t curr_prompt;
+ FILE *outfp = NULL;
+ int infd;
+
+ /*
+ * Zero all the results, in case we abort half-way through.
+ */
+ {
+ int i;
+ for (i = 0; i < p->n_prompts; i++)
+ prompt_set_result(p->prompts[i], "");
+ }
+
+ if (p->n_prompts && console_batch_mode)
+ return SPR_SW_ABORT("Cannot answer interactive prompts "
+ "in batch mode");
+
+ console_open(&outfp, &infd);
+
+ /*
+ * Preamble.
+ */
+ /* We only print the `name' caption if we have to... */
+ if (p->name_reqd && p->name) {
+ ptrlen plname = ptrlen_from_asciz(p->name);
+ console_write(outfp, plname);
+ if (!ptrlen_endswith(plname, PTRLEN_LITERAL("\n"), NULL))
+ console_write(outfp, PTRLEN_LITERAL("\n"));
+ }
+ /* ...but we always print any `instruction'. */
+ if (p->instruction) {
+ ptrlen plinst = ptrlen_from_asciz(p->instruction);
+ console_write(outfp, plinst);
+ if (!ptrlen_endswith(plinst, PTRLEN_LITERAL("\n"), NULL))
+ console_write(outfp, PTRLEN_LITERAL("\n"));
+ }
+
+ for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) {
+
+ struct termios oldmode, newmode;
+ prompt_t *pr = p->prompts[curr_prompt];
+
+ tcgetattr(infd, &oldmode);
+ newmode = oldmode;
+ newmode.c_lflag |= ISIG | ICANON;
+ if (!pr->echo)
+ newmode.c_lflag &= ~ECHO;
+ else
+ newmode.c_lflag |= ECHO;
+ tcsetattr(infd, TCSANOW, &newmode);
+
+ console_write(outfp, ptrlen_from_asciz(pr->prompt));
+
+ bool failed = false;
+ SeatPromptResult spr;
+ while (1) {
+ size_t toread = 65536;
+ size_t prev_result_len = pr->result->len;
+ void *ptr = strbuf_append(pr->result, toread);
+ int ret = read(infd, ptr, toread);
+
+ if (ret == 0) {
+ /* Regard EOF on the terminal as a deliberate user-abort */
+ failed = true;
+ spr = SPR_USER_ABORT;
+ break;
+ }
+
+ if (ret < 0) {
+ /* Any other failure to read from the terminal is treated as
+ * an unexpected error and reported to the user. */
+ failed = true;
+ spr = make_spr_sw_abort_errno(
+ "Error reading from terminal", errno);
+ break;
+ }
+
+ strbuf_shrink_to(pr->result, prev_result_len + ret);
+ if (strbuf_chomp(pr->result, '\n'))
+ break;
+ }
+
+ tcsetattr(infd, TCSANOW, &oldmode);
+
+ if (!pr->echo)
+ console_write(outfp, PTRLEN_LITERAL("\n"));
+
+ if (failed) {
+ console_close(outfp, infd);
+ return spr;
+ }
+ }
+
+ console_close(outfp, infd);
+
+ return SPR_OK;
+}
+
+bool is_interactive(void)
+{
+ return isatty(0);
+}
+
+/*
+ * X11-forwarding-related things suitable for console.
+ */
+
+char *platform_get_x_display(void) {
+ return dupstr(getenv("DISPLAY"));
+}
diff --git a/unix/dialog.c b/unix/dialog.c
new file mode 100644
index 00000000..5846466a
--- /dev/null
+++ b/unix/dialog.c
@@ -0,0 +1,4385 @@
+/*
+ * dialog.c - GTK implementation of the PuTTY configuration box.
+ */
+
+#include <assert.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <time.h>
+
+#include <gtk/gtk.h>
+#if !GTK_CHECK_VERSION(3,0,0)
+#include <gdk/gdkkeysyms.h>
+#endif
+
+#define MAY_REFER_TO_GTK_IN_HEADERS
+
+#include "putty.h"
+#include "gtkcompat.h"
+#include "columns.h"
+#include "unifont.h"
+#include "gtkmisc.h"
+
+#ifndef NOT_X_WINDOWS
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include "x11misc.h"
+#endif
+
+#include "storage.h"
+#include "dialog.h"
+#include "tree234.h"
+#include "licence.h"
+#include "ssh.h"
+
+#if GTK_CHECK_VERSION(2,0,0)
+/* Decide which of GtkFileChooserDialog and GtkFileSelection to use */
+#define USE_GTK_FILE_CHOOSER_DIALOG
+#endif
+
+struct Shortcut {
+ GtkWidget *widget;
+ struct uctrl *uc;
+ int action;
+};
+
+struct Shortcuts {
+ struct Shortcut sc[128];
+};
+
+struct selparam;
+
+struct uctrl {
+ dlgcontrol *ctrl;
+ GtkWidget *toplevel;
+ GtkWidget **buttons; int nbuttons; /* for radio buttons */
+ GtkWidget *entry; /* for editbox, filesel, fontsel */
+ GtkWidget *button; /* for filesel, fontsel */
+#if !GTK_CHECK_VERSION(2,4,0)
+ GtkWidget *list; /* for listbox (in GTK1), combobox (<=GTK2.3) */
+ GtkWidget *menu; /* for optionmenu (==droplist) */
+ GtkWidget *optmenu; /* also for optionmenu */
+#else
+ GtkWidget *combo; /* for combo box (either editable or not) */
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ GtkWidget *treeview; /* for listbox (GTK2), droplist+combo (>=2.4) */
+ GtkListStore *listmodel; /* for all types of list box */
+#endif
+ GtkWidget *text; /* for text */
+ GtkWidget *label; /* for dlg_label_change */
+ GtkAdjustment *adj; /* for the scrollbar in a list box */
+ struct selparam *sp; /* which switchable pane of the box we're in */
+ guint textsig;
+ int nclicks;
+ const char *textvalue; /* temporary, for button-only file selectors */
+};
+
+struct dlgparam {
+ tree234 *byctrl, *bywidget;
+ void *data;
+ struct {
+ unsigned char r, g, b; /* 0-255 */
+ bool ok;
+ } coloursel_result;
+ /* `flags' are set to indicate when a GTK signal handler is being called
+ * due to automatic processing and should not flag a user event. */
+ int flags;
+ struct Shortcuts *shortcuts;
+ GtkWidget *window, *cancelbutton;
+ dlgcontrol *currfocus, *lastfocus;
+#if !GTK_CHECK_VERSION(2,0,0)
+ GtkWidget *currtreeitem, **treeitems;
+ int ntreeitems;
+#else
+ size_t nselparams;
+ struct selparam **selparams;
+#endif
+ struct selparam *curr_panel;
+ struct controlbox *ctrlbox;
+ int retval;
+ post_dialog_fn_t after;
+ void *afterctx;
+};
+#define FLAG_UPDATING_COMBO_LIST 1
+#define FLAG_UPDATING_LISTBOX 2
+
+enum { /* values for Shortcut.action */
+ SHORTCUT_EMPTY, /* no shortcut on this key */
+ SHORTCUT_TREE, /* focus a tree item */
+ SHORTCUT_FOCUS, /* focus the supplied widget */
+ SHORTCUT_UCTRL, /* do something sane with uctrl */
+ SHORTCUT_UCTRL_UP, /* uctrl is a draglist, move Up */
+ SHORTCUT_UCTRL_DOWN, /* uctrl is a draglist, move Down */
+};
+
+#if GTK_CHECK_VERSION(2,0,0)
+enum {
+ TREESTORE_PATH,
+ TREESTORE_PARAMS,
+ TREESTORE_NUM
+};
+#endif
+
+/*
+ * Forward references.
+ */
+static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event,
+ gpointer data);
+static void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
+ int chr, int action, void *ptr);
+static void shortcut_highlight(GtkWidget *label, int chr);
+#if !GTK_CHECK_VERSION(2,0,0)
+static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event,
+ gpointer data);
+static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event,
+ gpointer data);
+static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event,
+ gpointer data);
+static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event,
+ gpointer data);
+#endif
+#if !GTK_CHECK_VERSION(2,4,0)
+static void menuitem_activate(GtkMenuItem *item, gpointer data);
+#endif
+#if GTK_CHECK_VERSION(3,0,0)
+static void colourchoose_response(GtkDialog *dialog,
+ gint response_id, gpointer data);
+#else
+static void coloursel_ok(GtkButton *button, gpointer data);
+static void coloursel_cancel(GtkButton *button, gpointer data);
+#endif
+static void dlgparam_destroy(GtkWidget *widget, gpointer data);
+static int get_listitemheight(GtkWidget *widget);
+
+static int uctrl_cmp_byctrl(void *av, void *bv)
+{
+ struct uctrl *a = (struct uctrl *)av;
+ struct uctrl *b = (struct uctrl *)bv;
+ if (a->ctrl < b->ctrl)
+ return -1;
+ else if (a->ctrl > b->ctrl)
+ return +1;
+ return 0;
+}
+
+static int uctrl_cmp_byctrl_find(void *av, void *bv)
+{
+ dlgcontrol *a = (dlgcontrol *)av;
+ struct uctrl *b = (struct uctrl *)bv;
+ if (a < b->ctrl)
+ return -1;
+ else if (a > b->ctrl)
+ return +1;
+ return 0;
+}
+
+static int uctrl_cmp_bywidget(void *av, void *bv)
+{
+ struct uctrl *a = (struct uctrl *)av;
+ struct uctrl *b = (struct uctrl *)bv;
+ if (a->toplevel < b->toplevel)
+ return -1;
+ else if (a->toplevel > b->toplevel)
+ return +1;
+ return 0;
+}
+
+static int uctrl_cmp_bywidget_find(void *av, void *bv)
+{
+ GtkWidget *a = (GtkWidget *)av;
+ struct uctrl *b = (struct uctrl *)bv;
+ if (a < b->toplevel)
+ return -1;
+ else if (a > b->toplevel)
+ return +1;
+ return 0;
+}
+
+static void dlg_init(struct dlgparam *dp)
+{
+ dp->byctrl = newtree234(uctrl_cmp_byctrl);
+ dp->bywidget = newtree234(uctrl_cmp_bywidget);
+ dp->coloursel_result.ok = false;
+ dp->window = dp->cancelbutton = NULL;
+#if !GTK_CHECK_VERSION(2,0,0)
+ dp->treeitems = NULL;
+ dp->currtreeitem = NULL;
+#endif
+ dp->curr_panel = NULL;
+ dp->flags = 0;
+ dp->currfocus = NULL;
+}
+
+static void dlg_cleanup(struct dlgparam *dp)
+{
+ struct uctrl *uc;
+
+ freetree234(dp->byctrl); /* doesn't free the uctrls inside */
+ dp->byctrl = NULL;
+ while ( (uc = index234(dp->bywidget, 0)) != NULL) {
+ del234(dp->bywidget, uc);
+ sfree(uc->buttons);
+ sfree(uc);
+ }
+ freetree234(dp->bywidget);
+ dp->bywidget = NULL;
+#if !GTK_CHECK_VERSION(2,0,0)
+ sfree(dp->treeitems);
+#endif
+}
+
+static void dlg_add_uctrl(struct dlgparam *dp, struct uctrl *uc)
+{
+ add234(dp->byctrl, uc);
+ add234(dp->bywidget, uc);
+}
+
+static struct uctrl *dlg_find_byctrl(struct dlgparam *dp, dlgcontrol *ctrl)
+{
+ if (!dp->byctrl)
+ return NULL;
+ return find234(dp->byctrl, ctrl, uctrl_cmp_byctrl_find);
+}
+
+static struct uctrl *dlg_find_bywidget(struct dlgparam *dp, GtkWidget *w)
+{
+ struct uctrl *ret = NULL;
+ if (!dp->bywidget)
+ return NULL;
+ do {
+ ret = find234(dp->bywidget, w, uctrl_cmp_bywidget_find);
+ if (ret)
+ return ret;
+ w = gtk_widget_get_parent(w);
+ } while (w);
+ return ret;
+}
+
+dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp)
+{
+ if (dp->currfocus != ctrl)
+ return dp->currfocus;
+ else
+ return dp->lastfocus;
+}
+
+void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int which)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ assert(uc->ctrl->type == CTRL_RADIO);
+ assert(uc->buttons != NULL);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->buttons[which]), true);
+}
+
+int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ int i;
+
+ assert(uc->ctrl->type == CTRL_RADIO);
+ assert(uc->buttons != NULL);
+ for (i = 0; i < uc->nbuttons; i++)
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->buttons[i])))
+ return i;
+ return 0; /* got to return something */
+}
+
+void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ assert(uc->ctrl->type == CTRL_CHECKBOX);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->toplevel), checked);
+}
+
+bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ assert(uc->ctrl->type == CTRL_CHECKBOX);
+ return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->toplevel));
+}
+
+void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ GtkWidget *entry;
+ char *tmpstring;
+ assert(uc->ctrl->type == CTRL_EDITBOX);
+
+#if GTK_CHECK_VERSION(2,4,0)
+ if (uc->combo)
+ entry = gtk_bin_get_child(GTK_BIN(uc->combo));
+ else
+#endif
+ entry = uc->entry;
+
+ assert(entry != NULL);
+
+ /*
+ * GTK 2 implements gtk_entry_set_text by means of two separate
+ * operations: first delete the previous text leaving the empty
+ * string, then insert the new text. This causes two calls to
+ * the "changed" signal.
+ *
+ * The first call to "changed", if allowed to proceed normally,
+ * will cause an EVENT_VALCHANGE event on the edit box, causing
+ * a call to dlg_editbox_get() which will read the empty string
+ * out of the GtkEntry - and promptly write it straight into the
+ * Conf structure, which is precisely where our `text' pointer
+ * is probably pointing, so the second editing operation will
+ * insert that instead of the string we originally asked for.
+ *
+ * Hence, we must take our own copy of the text before we do
+ * this.
+ */
+ tmpstring = dupstr(text);
+ gtk_entry_set_text(GTK_ENTRY(entry), tmpstring);
+ sfree(tmpstring);
+}
+
+char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ assert(uc->ctrl->type == CTRL_EDITBOX);
+
+#if GTK_CHECK_VERSION(2,4,0)
+ if (uc->combo) {
+ return dupstr(gtk_entry_get_text(
+ GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo)))));
+ }
+#endif
+
+ if (uc->entry) {
+ return dupstr(gtk_entry_get_text(GTK_ENTRY(uc->entry)));
+ }
+
+ unreachable("bad control type in editbox_get");
+}
+
+void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp,
+ size_t start, size_t len)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ assert(uc->ctrl->type == CTRL_EDITBOX);
+
+ GtkWidget *entry = NULL;
+
+#if GTK_CHECK_VERSION(2,4,0)
+ if (uc->combo)
+ entry = gtk_bin_get_child(GTK_BIN(uc->combo));
+#endif
+
+ if (uc->entry)
+ entry = uc->entry;
+
+ assert(entry && "we should have a GtkEntry one way or another");
+
+ gtk_editable_select_region(GTK_EDITABLE(entry), start, start + len);
+}
+
+#if !GTK_CHECK_VERSION(2,4,0)
+static void container_remove_and_destroy(GtkWidget *w, gpointer data)
+{
+ GtkContainer *cont = GTK_CONTAINER(data);
+ /* gtk_container_remove will unref the widget for us; we need not. */
+ gtk_container_remove(cont, w);
+}
+#endif
+
+/* The `listbox' functions can also apply to combo boxes. */
+void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ assert(uc->ctrl->type == CTRL_EDITBOX ||
+ uc->ctrl->type == CTRL_LISTBOX);
+
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (uc->menu) {
+ gtk_container_foreach(GTK_CONTAINER(uc->menu),
+ container_remove_and_destroy,
+ GTK_CONTAINER(uc->menu));
+ return;
+ }
+ if (uc->list) {
+ gtk_list_clear_items(GTK_LIST(uc->list), 0, -1);
+ return;
+ }
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ if (uc->listmodel) {
+ gtk_list_store_clear(uc->listmodel);
+ return;
+ }
+#endif
+ unreachable("bad control type in listbox_clear");
+}
+
+void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ assert(uc->ctrl->type == CTRL_EDITBOX ||
+ uc->ctrl->type == CTRL_LISTBOX);
+
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (uc->menu) {
+ gtk_container_remove(
+ GTK_CONTAINER(uc->menu),
+ g_list_nth_data(GTK_MENU_SHELL(uc->menu)->children, index));
+ return;
+ }
+ if (uc->list) {
+ gtk_list_clear_items(GTK_LIST(uc->list), index, index+1);
+ return;
+ }
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ if (uc->listmodel) {
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ assert(uc->listmodel != NULL);
+ path = gtk_tree_path_new_from_indices(index, -1);
+ gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path);
+ gtk_list_store_remove(uc->listmodel, &iter);
+ gtk_tree_path_free(path);
+ return;
+ }
+#endif
+ unreachable("bad control type in listbox_del");
+}
+
+void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text)
+{
+ dlg_listbox_addwithid(ctrl, dp, text, 0);
+}
+
+/*
+ * Each listbox entry may have a numeric id associated with it.
+ * Note that some front ends only permit a string to be stored at
+ * each position, which means that _if_ you put two identical
+ * strings in any listbox then you MUST not assign them different
+ * IDs and expect to get meaningful results back.
+ */
+void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp,
+ char const *text, int id)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ assert(uc->ctrl->type == CTRL_EDITBOX ||
+ uc->ctrl->type == CTRL_LISTBOX);
+
+ /*
+ * This routine is long and complicated in both GTK 1 and 2,
+ * and completely different. Sigh.
+ */
+ dp->flags |= FLAG_UPDATING_COMBO_LIST;
+
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (uc->menu) {
+ /*
+ * List item in a drop-down (but non-combo) list. Tabs are
+ * ignored; we just provide a standard menu item with the
+ * text.
+ */
+ GtkWidget *menuitem = gtk_menu_item_new_with_label(text);
+
+ gtk_container_add(GTK_CONTAINER(uc->menu), menuitem);
+ gtk_widget_show(menuitem);
+
+ g_object_set_data(G_OBJECT(menuitem), "user-data",
+ GINT_TO_POINTER(id));
+ g_signal_connect(G_OBJECT(menuitem), "activate",
+ G_CALLBACK(menuitem_activate), dp);
+ goto done;
+ }
+ if (uc->list && uc->entry) {
+ /*
+ * List item in a combo-box list, which means the sensible
+ * thing to do is make it a perfectly normal label. Hence
+ * tabs are disregarded.
+ */
+ GtkWidget *listitem = gtk_list_item_new_with_label(text);
+
+ gtk_container_add(GTK_CONTAINER(uc->list), listitem);
+ gtk_widget_show(listitem);
+
+ g_object_set_data(G_OBJECT(listitem), "user-data",
+ GINT_TO_POINTER(id));
+ goto done;
+ }
+#endif
+#if !GTK_CHECK_VERSION(2,0,0)
+ if (uc->list) {
+ /*
+ * List item in a non-combo-box list box. We make all of
+ * these Columns containing GtkLabels. This allows us to do
+ * the nasty force_left hack irrespective of whether there
+ * are tabs in the thing.
+ */
+ GtkWidget *listitem = gtk_list_item_new();
+ GtkWidget *cols = columns_new(10);
+ gint *percents;
+ int i, ncols;
+
+ /* Count the tabs in the text, and hence determine # of columns. */
+ ncols = 1;
+ for (i = 0; text[i]; i++)
+ if (text[i] == '\t')
+ ncols++;
+
+ assert(ncols <=
+ (uc->ctrl->listbox.ncols ? uc->ctrl->listbox.ncols : 1));
+ percents = snewn(ncols, gint);
+ percents[ncols-1] = 100;
+ for (i = 0; i < ncols-1; i++) {
+ percents[i] = uc->ctrl->listbox.percentages[i];
+ percents[ncols-1] -= percents[i];
+ }
+ columns_set_cols(COLUMNS(cols), ncols, percents);
+ sfree(percents);
+
+ for (i = 0; i < ncols; i++) {
+ int len = strcspn(text, "\t");
+ char *dup = dupprintf("%.*s", len, text);
+ GtkWidget *label;
+
+ text += len;
+ if (*text) text++;
+ label = gtk_label_new(dup);
+ sfree(dup);
+
+ columns_add(COLUMNS(cols), label, i, 1);
+ columns_force_left_align(COLUMNS(cols), label);
+ gtk_widget_show(label);
+ }
+ gtk_container_add(GTK_CONTAINER(listitem), cols);
+ gtk_widget_show(cols);
+ gtk_container_add(GTK_CONTAINER(uc->list), listitem);
+ gtk_widget_show(listitem);
+
+ if (ctrl->listbox.multisel) {
+ g_signal_connect(G_OBJECT(listitem), "key_press_event",
+ G_CALLBACK(listitem_multi_key), uc->adj);
+ } else {
+ g_signal_connect(G_OBJECT(listitem), "key_press_event",
+ G_CALLBACK(listitem_single_key), uc->adj);
+ }
+ g_signal_connect(G_OBJECT(listitem), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
+ g_signal_connect(G_OBJECT(listitem), "button_press_event",
+ G_CALLBACK(listitem_button_press), dp);
+ g_signal_connect(G_OBJECT(listitem), "button_release_event",
+ G_CALLBACK(listitem_button_release), dp);
+ g_object_set_data(G_OBJECT(listitem), "user-data",
+ GINT_TO_POINTER(id));
+ goto done;
+ }
+#else
+ if (uc->listmodel) {
+ GtkTreeIter iter;
+ int i, cols;
+
+ dp->flags |= FLAG_UPDATING_LISTBOX;/* inhibit drag-list update */
+ gtk_list_store_append(uc->listmodel, &iter);
+ dp->flags &= ~FLAG_UPDATING_LISTBOX;
+ gtk_list_store_set(uc->listmodel, &iter, 0, id, -1);
+
+ /*
+ * Now go through text and divide it into columns at the tabs,
+ * as necessary.
+ */
+ cols = (uc->ctrl->type == CTRL_LISTBOX ? ctrl->listbox.ncols : 1);
+ cols = cols ? cols : 1;
+ for (i = 0; i < cols; i++) {
+ int collen = strcspn(text, "\t");
+ char *tmpstr = snewn(collen+1, char);
+ memcpy(tmpstr, text, collen);
+ tmpstr[collen] = '\0';
+ gtk_list_store_set(uc->listmodel, &iter, i+1, tmpstr, -1);
+ sfree(tmpstr);
+ text += collen;
+ if (*text) text++;
+ }
+ goto done;
+ }
+#endif
+ unreachable("bad control type in listbox_addwithid");
+ done:
+ dp->flags &= ~FLAG_UPDATING_COMBO_LIST;
+}
+
+int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ assert(uc->ctrl->type == CTRL_EDITBOX ||
+ uc->ctrl->type == CTRL_LISTBOX);
+
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (uc->menu || uc->list) {
+ GList *children;
+ GObject *item;
+
+ children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
+ uc->list));
+ item = G_OBJECT(g_list_nth_data(children, index));
+ g_list_free(children);
+
+ return GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "user-data"));
+ }
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ if (uc->listmodel) {
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ int ret;
+
+ path = gtk_tree_path_new_from_indices(index, -1);
+ gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path);
+ gtk_tree_model_get(GTK_TREE_MODEL(uc->listmodel), &iter, 0, &ret, -1);
+ gtk_tree_path_free(path);
+
+ return ret;
+ }
+#endif
+ unreachable("bad control type in listbox_getid");
+ return -1; /* placate dataflow analysis */
+}
+
+/* dlg_listbox_index returns <0 if no single element is selected. */
+int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ assert(uc->ctrl->type == CTRL_EDITBOX ||
+ uc->ctrl->type == CTRL_LISTBOX);
+
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (uc->menu || uc->list) {
+ GList *children;
+ GtkWidget *item, *activeitem;
+ int i;
+ int selected = -1;
+
+ if (uc->menu)
+ activeitem = gtk_menu_get_active(GTK_MENU(uc->menu));
+ else
+ activeitem = NULL; /* unnecessarily placate gcc */
+
+ children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
+ uc->list));
+ for (i = 0; children!=NULL && (item = GTK_WIDGET(children->data))!=NULL;
+ i++, children = children->next) {
+ if (uc->menu ? activeitem == item :
+ GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED) {
+ if (selected == -1)
+ selected = i;
+ else
+ selected = -2;
+ }
+ }
+ g_list_free(children);
+ return selected < 0 ? -1 : selected;
+ }
+#else
+ if (uc->combo) {
+ /*
+ * This API function already does the right thing in the
+ * case of no current selection.
+ */
+ return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo));
+ }
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ if (uc->treeview) {
+ GtkTreeSelection *treesel;
+ GtkTreePath *path;
+ GtkTreeModel *model;
+ GList *sellist;
+ gint *indices;
+ int ret;
+
+ assert(uc->treeview != NULL);
+ treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
+
+ if (gtk_tree_selection_count_selected_rows(treesel) != 1)
+ return -1;
+
+ sellist = gtk_tree_selection_get_selected_rows(treesel, &model);
+
+ assert(sellist && sellist->data);
+ path = sellist->data;
+
+ if (gtk_tree_path_get_depth(path) != 1) {
+ ret = -1;
+ } else {
+ indices = gtk_tree_path_get_indices(path);
+ if (!indices) {
+ ret = -1;
+ } else {
+ ret = indices[0];
+ }
+ }
+
+ g_list_foreach(sellist, (GFunc)gtk_tree_path_free, NULL);
+ g_list_free(sellist);
+
+ return ret;
+ }
+#endif
+ unreachable("bad control type in listbox_index");
+ return -1; /* placate dataflow analysis */
+}
+
+bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ assert(uc->ctrl->type == CTRL_EDITBOX ||
+ uc->ctrl->type == CTRL_LISTBOX);
+
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (uc->menu || uc->list) {
+ GList *children;
+ GtkWidget *item, *activeitem;
+
+ assert(uc->ctrl->type == CTRL_EDITBOX ||
+ uc->ctrl->type == CTRL_LISTBOX);
+ assert(uc->menu != NULL || uc->list != NULL);
+
+ children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
+ uc->list));
+ item = GTK_WIDGET(g_list_nth_data(children, index));
+ g_list_free(children);
+
+ if (uc->menu) {
+ activeitem = gtk_menu_get_active(GTK_MENU(uc->menu));
+ return item == activeitem;
+ } else {
+ return GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED;
+ }
+ }
+#else
+ if (uc->combo) {
+ /*
+ * This API function already does the right thing in the
+ * case of no current selection.
+ */
+ return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo)) == index;
+ }
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ if (uc->treeview) {
+ GtkTreeSelection *treesel;
+ GtkTreePath *path;
+ bool ret;
+
+ assert(uc->treeview != NULL);
+ treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
+
+ path = gtk_tree_path_new_from_indices(index, -1);
+ ret = gtk_tree_selection_path_is_selected(treesel, path);
+ gtk_tree_path_free(path);
+
+ return ret;
+ }
+#endif
+ unreachable("bad control type in listbox_issel");
+ return false; /* placate dataflow analysis */
+}
+
+void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ assert(uc->ctrl->type == CTRL_EDITBOX ||
+ uc->ctrl->type == CTRL_LISTBOX);
+
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (uc->optmenu) {
+ gtk_option_menu_set_history(GTK_OPTION_MENU(uc->optmenu), index);
+ return;
+ }
+ if (uc->list) {
+ int nitems;
+ GList *items;
+ gdouble newtop, newbot;
+
+ gtk_list_select_item(GTK_LIST(uc->list), index);
+
+ /*
+ * Scroll the list box if necessary to ensure the newly
+ * selected item is visible.
+ */
+ items = gtk_container_children(GTK_CONTAINER(uc->list));
+ nitems = g_list_length(items);
+ if (nitems > 0) {
+ bool modified = false;
+ g_list_free(items);
+ newtop = uc->adj->lower +
+ (uc->adj->upper - uc->adj->lower) * index / nitems;
+ newbot = uc->adj->lower +
+ (uc->adj->upper - uc->adj->lower) * (index+1) / nitems;
+ if (uc->adj->value > newtop) {
+ modified = true;
+ uc->adj->value = newtop;
+ } else if (uc->adj->value < newbot - uc->adj->page_size) {
+ modified = true;
+ uc->adj->value = newbot - uc->adj->page_size;
+ }
+ if (modified)
+ gtk_adjustment_value_changed(uc->adj);
+ }
+ return;
+ }
+#else
+ if (uc->combo) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(uc->combo), index);
+ return;
+ }
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ if (uc->treeview) {
+ GtkTreeSelection *treesel;
+ GtkTreePath *path;
+
+ treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
+
+ path = gtk_tree_path_new_from_indices(index, -1);
+ gtk_tree_selection_select_path(treesel, path);
+ gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(uc->treeview),
+ path, NULL, false, 0.0, 0.0);
+ gtk_tree_path_free(path);
+ return;
+ }
+#endif
+ unreachable("bad control type in listbox_select");
+}
+
+void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ assert(uc->ctrl->type == CTRL_TEXT);
+ assert(uc->text != NULL);
+
+ gtk_label_set_text(GTK_LABEL(uc->text), text);
+}
+
+void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ switch (uc->ctrl->type) {
+ case CTRL_BUTTON:
+ gtk_label_set_text(GTK_LABEL(uc->toplevel), text);
+ shortcut_highlight(uc->toplevel, ctrl->button.shortcut);
+ break;
+ case CTRL_CHECKBOX:
+ gtk_label_set_text(GTK_LABEL(uc->toplevel), text);
+ shortcut_highlight(uc->toplevel, ctrl->checkbox.shortcut);
+ break;
+ case CTRL_RADIO:
+ gtk_label_set_text(GTK_LABEL(uc->label), text);
+ shortcut_highlight(uc->label, ctrl->radio.shortcut);
+ break;
+ case CTRL_EDITBOX:
+ gtk_label_set_text(GTK_LABEL(uc->label), text);
+ shortcut_highlight(uc->label, ctrl->editbox.shortcut);
+ break;
+ case CTRL_FILESELECT:
+ if (uc->label) {
+ gtk_label_set_text(GTK_LABEL(uc->label), text);
+ shortcut_highlight(uc->label, ctrl->fileselect.shortcut);
+ }
+ break;
+ case CTRL_FONTSELECT:
+ gtk_label_set_text(GTK_LABEL(uc->label), text);
+ shortcut_highlight(uc->label, ctrl->fontselect.shortcut);
+ break;
+ case CTRL_LISTBOX:
+ gtk_label_set_text(GTK_LABEL(uc->label), text);
+ shortcut_highlight(uc->label, ctrl->listbox.shortcut);
+ break;
+ default:
+ unreachable("bad control type in label_change");
+ }
+}
+
+void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ /* We must copy fn->path before passing it to gtk_entry_set_text.
+ * See comment in dlg_editbox_set() for the reasons. */
+ char *duppath = dupstr(fn->path);
+ assert(uc->ctrl->type == CTRL_FILESELECT);
+ assert(uc->entry != NULL);
+ gtk_entry_set_text(GTK_ENTRY(uc->entry), duppath);
+ sfree(duppath);
+}
+
+Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ assert(uc->ctrl->type == CTRL_FILESELECT);
+ if (!uc->entry) {
+ assert(uc->textvalue);
+ return filename_from_str(uc->textvalue);
+ } else {
+ return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry)));
+ }
+}
+
+void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fs)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ /* We must copy fs->name before passing it to gtk_entry_set_text.
+ * See comment in dlg_editbox_set() for the reasons. */
+ char *dupname = dupstr(fs->name);
+ assert(uc->ctrl->type == CTRL_FONTSELECT);
+ assert(uc->entry != NULL);
+ gtk_entry_set_text(GTK_ENTRY(uc->entry), dupname);
+ sfree(dupname);
+}
+
+FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ assert(uc->ctrl->type == CTRL_FONTSELECT);
+ assert(uc->entry != NULL);
+ return fontspec_new(gtk_entry_get_text(GTK_ENTRY(uc->entry)));
+}
+
+/*
+ * Bracketing a large set of updates in these two functions will
+ * cause the front end (if possible) to delay updating the screen
+ * until it's all complete, thus avoiding flicker.
+ */
+void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp)
+{
+ /*
+ * Apparently we can't do this at all in GTK. GtkCList supports
+ * freeze and thaw, but not GtkList. Bah.
+ */
+}
+
+void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp)
+{
+ /*
+ * Apparently we can't do this at all in GTK. GtkCList supports
+ * freeze and thaw, but not GtkList. Bah.
+ */
+}
+
+void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+ switch (ctrl->type) {
+ case CTRL_CHECKBOX:
+ case CTRL_BUTTON:
+ /* Check boxes and buttons get the focus _and_ get toggled. */
+ gtk_widget_grab_focus(uc->toplevel);
+ break;
+ case CTRL_FILESELECT:
+ case CTRL_FONTSELECT:
+ case CTRL_EDITBOX:
+ if (uc->entry) {
+ /* Anything containing an edit box gets that focused. */
+ gtk_widget_grab_focus(uc->entry);
+ }
+#if GTK_CHECK_VERSION(2,4,0)
+ else if (uc->combo) {
+ /* Failing that, there'll be a combo box. */
+ gtk_widget_grab_focus(uc->combo);
+ }
+#endif
+ break;
+ case CTRL_RADIO:
+ /*
+ * Radio buttons: we find the currently selected button and
+ * focus it.
+ */
+ for (int i = 0; i < ctrl->radio.nbuttons; i++)
+ if (gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(uc->buttons[i]))) {
+ gtk_widget_grab_focus(uc->buttons[i]);
+ }
+ break;
+ case CTRL_LISTBOX:
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (uc->optmenu) {
+ gtk_widget_grab_focus(uc->optmenu);
+ break;
+ }
+#else
+ if (uc->combo) {
+ gtk_widget_grab_focus(uc->combo);
+ break;
+ }
+#endif
+#if !GTK_CHECK_VERSION(2,0,0)
+ if (uc->list) {
+ /*
+ * For GTK-1 style list boxes, we tell it to focus one
+ * of its children, which appears to do the Right
+ * Thing.
+ */
+ gtk_container_focus(GTK_CONTAINER(uc->list), GTK_DIR_TAB_FORWARD);
+ break;
+ }
+#else
+ if (uc->treeview) {
+ gtk_widget_grab_focus(uc->treeview);
+ break;
+ }
+#endif
+ unreachable("bad control type in set_focus");
+ }
+}
+
+/*
+ * During event processing, you might well want to give an error
+ * indication to the user. dlg_beep() is a quick and easy generic
+ * error; dlg_error() puts up a message-box or equivalent.
+ */
+void dlg_beep(dlgparam *dp)
+{
+ gdk_display_beep(gdk_display_get_default());
+}
+
+static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child)
+{
+#if !GTK_CHECK_VERSION(2,0,0)
+ gint x, y, w, h, dx, dy;
+ GtkRequisition req;
+ gtk_window_set_position(GTK_WINDOW(child), GTK_WIN_POS_NONE);
+ gtk_widget_size_request(GTK_WIDGET(child), &req);
+
+ gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(parent)), &x, &y);
+ gdk_window_get_size(gtk_widget_get_window(GTK_WIDGET(parent)), &w, &h);
+
+ /*
+ * One corner of the transient will be offset inwards, by 1/4
+ * of the parent window's size, from the corresponding corner
+ * of the parent window. The corner will be chosen so as to
+ * place the transient closer to the centre of the screen; this
+ * should avoid transients going off the edge of the screen on
+ * a regular basis.
+ */
+ if (x + w/2 < gdk_screen_width() / 2)
+ dx = x + w/4; /* work from left edges */
+ else
+ dx = x + 3*w/4 - req.width; /* work from right edges */
+ if (y + h/2 < gdk_screen_height() / 2)
+ dy = y + h/4; /* work from top edges */
+ else
+ dy = y + 3*h/4 - req.height; /* work from bottom edges */
+ gtk_widget_set_uposition(GTK_WIDGET(child), dx, dy);
+#endif
+}
+
+void trivial_post_dialog_fn(void *vctx, int result)
+{
+}
+
+void dlg_error_msg(dlgparam *dp, const char *msg)
+{
+ create_message_box(
+ dp->window, "Error", msg,
+ string_width("Some sort of text about a config-box error message"),
+ false, &buttons_ok, trivial_post_dialog_fn, NULL);
+}
+
+/*
+ * This function signals to the front end that the dialog's
+ * processing is completed, and passes an integer value (typically
+ * a success status).
+ */
+void dlg_end(dlgparam *dp, int value)
+{
+ dp->retval = value;
+ gtk_widget_destroy(dp->window);
+}
+
+void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct uctrl *uc;
+
+ if (ctrl) {
+ if (ctrl->handler != NULL)
+ ctrl->handler(ctrl, dp, dp->data, EVENT_REFRESH);
+ } else {
+ int i;
+
+ for (i = 0; (uc = index234(dp->byctrl, i)) != NULL; i++) {
+ assert(uc->ctrl != NULL);
+ if (uc->ctrl->handler != NULL)
+ uc->ctrl->handler(uc->ctrl, dp,
+ dp->data, EVENT_REFRESH);
+ }
+ }
+}
+
+void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, int r, int g, int b)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+
+#if GTK_CHECK_VERSION(3,0,0)
+ GtkWidget *coloursel =
+ gtk_color_chooser_dialog_new("Select a colour",
+ GTK_WINDOW(dp->window));
+ gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(coloursel), false);
+#else
+ GtkWidget *okbutton, *cancelbutton;
+ GtkWidget *coloursel =
+ gtk_color_selection_dialog_new("Select a colour");
+ GtkColorSelectionDialog *ccs = GTK_COLOR_SELECTION_DIALOG(coloursel);
+ GtkColorSelection *cs = GTK_COLOR_SELECTION(
+ gtk_color_selection_dialog_get_color_selection(ccs));
+ gtk_color_selection_set_has_opacity_control(cs, false);
+#endif
+
+ dp->coloursel_result.ok = false;
+
+ gtk_window_set_modal(GTK_WINDOW(coloursel), true);
+
+#if GTK_CHECK_VERSION(3,0,0)
+ {
+ GdkRGBA rgba;
+ rgba.red = r / 255.0;
+ rgba.green = g / 255.0;
+ rgba.blue = b / 255.0;
+ rgba.alpha = 1.0; /* fully opaque! */
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(coloursel), &rgba);
+ }
+#elif GTK_CHECK_VERSION(2,0,0)
+ {
+ GdkColor col;
+ col.red = r * 0x0101;
+ col.green = g * 0x0101;
+ col.blue = b * 0x0101;
+ gtk_color_selection_set_current_color(cs, &col);
+ }
+#else
+ {
+ gdouble cvals[4];
+ cvals[0] = r / 255.0;
+ cvals[1] = g / 255.0;
+ cvals[2] = b / 255.0;
+ cvals[3] = 1.0; /* fully opaque! */
+ gtk_color_selection_set_color(cs, cvals);
+ }
+#endif
+
+ g_object_set_data(G_OBJECT(coloursel), "user-data", (gpointer)uc);
+
+#if GTK_CHECK_VERSION(3,0,0)
+ g_signal_connect(G_OBJECT(coloursel), "response",
+ G_CALLBACK(colourchoose_response), (gpointer)dp);
+#else
+
+#if GTK_CHECK_VERSION(2,0,0)
+ g_object_get(G_OBJECT(ccs),
+ "ok-button", &okbutton,
+ "cancel-button", &cancelbutton,
+ (const char *)NULL);
+#else
+ okbutton = ccs->ok_button;
+ cancelbutton = ccs->cancel_button;
+#endif
+ g_object_set_data(G_OBJECT(okbutton), "user-data",
+ (gpointer)coloursel);
+ g_object_set_data(G_OBJECT(cancelbutton), "user-data",
+ (gpointer)coloursel);
+ g_signal_connect(G_OBJECT(okbutton), "clicked",
+ G_CALLBACK(coloursel_ok), (gpointer)dp);
+ g_signal_connect(G_OBJECT(cancelbutton), "clicked",
+ G_CALLBACK(coloursel_cancel), (gpointer)dp);
+ g_signal_connect_swapped(G_OBJECT(okbutton), "clicked",
+ G_CALLBACK(gtk_widget_destroy),
+ (gpointer)coloursel);
+ g_signal_connect_swapped(G_OBJECT(cancelbutton), "clicked",
+ G_CALLBACK(gtk_widget_destroy),
+ (gpointer)coloursel);
+#endif
+ gtk_widget_show(coloursel);
+}
+
+bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp,
+ int *r, int *g, int *b)
+{
+ if (dp->coloursel_result.ok) {
+ *r = dp->coloursel_result.r;
+ *g = dp->coloursel_result.g;
+ *b = dp->coloursel_result.b;
+ return true;
+ } else
+ return false;
+}
+
+/* ----------------------------------------------------------------------
+ * Signal handlers while the dialog box is active.
+ */
+
+static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event,
+ gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, widget);
+ dlgcontrol *focus;
+
+ if (uc && uc->ctrl)
+ focus = uc->ctrl;
+ else
+ focus = NULL;
+
+ if (focus != dp->currfocus) {
+ dp->lastfocus = dp->currfocus;
+ dp->currfocus = focus;
+ }
+
+ return false;
+}
+
+static void button_clicked(GtkButton *button, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
+ uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
+}
+
+static void button_toggled(GtkToggleButton *tb, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tb));
+ uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
+}
+
+static gboolean editbox_key(GtkWidget *widget, GdkEventKey *event,
+ gpointer data)
+{
+ /*
+ * GtkEntry has a nasty habit of eating the Return key, which
+ * is unhelpful since it doesn't actually _do_ anything with it
+ * (it calls gtk_widget_activate, but our edit boxes never need
+ * activating). So I catch Return before GtkEntry sees it, and
+ * pass it straight on to the parent widget. Effect: hitting
+ * Return in an edit box will now activate the default button
+ * in the dialog just like it will everywhere else.
+ */
+ GtkWidget *parent = gtk_widget_get_parent(widget);
+ if (event->keyval == GDK_KEY_Return && parent != NULL) {
+ gboolean return_val;
+ g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
+ g_signal_emit_by_name(G_OBJECT(parent), "key_press_event",
+ event, &return_val);
+ return return_val;
+ }
+ return false;
+}
+
+static void editbox_changed(GtkEditable *ed, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ if (!(dp->flags & FLAG_UPDATING_COMBO_LIST)) {
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed));
+ uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
+ }
+}
+
+static gboolean editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event,
+ gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed));
+ uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_REFRESH);
+ return false;
+}
+
+#if !GTK_CHECK_VERSION(2,0,0)
+
+/*
+ * GTK 1 list box event handlers.
+ */
+
+static gboolean listitem_key(GtkWidget *item, GdkEventKey *event,
+ gpointer data, bool multiple)
+{
+ GtkAdjustment *adj = GTK_ADJUSTMENT(data);
+
+ if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
+ event->keyval == GDK_Down || event->keyval == GDK_KP_Down ||
+ event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up ||
+ event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down) {
+ /*
+ * Up, Down, PgUp or PgDn have been pressed on a ListItem
+ * in a list box. So, if the list box is single-selection:
+ *
+ * - if the list item in question isn't already selected,
+ * we simply select it.
+ * - otherwise, we find the next one (or next
+ * however-far-away) in whichever direction we're going,
+ * and select that.
+ * + in this case, we must also fiddle with the
+ * scrollbar to ensure the newly selected item is
+ * actually visible.
+ *
+ * If it's multiple-selection, we do all of the above
+ * except actually selecting anything, so we move the focus
+ * and fiddle the scrollbar to follow it.
+ */
+ GtkWidget *list = item->parent;
+
+ g_signal_stop_emission_by_name(G_OBJECT(item), "key_press_event");
+
+ if (!multiple &&
+ GTK_WIDGET_STATE(item) != GTK_STATE_SELECTED) {
+ gtk_list_select_child(GTK_LIST(list), item);
+ } else {
+ int direction =
+ (event->keyval==GDK_Up || event->keyval==GDK_KP_Up ||
+ event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up)
+ ? -1 : +1;
+ int step =
+ (event->keyval==GDK_Page_Down ||
+ event->keyval==GDK_KP_Page_Down ||
+ event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up)
+ ? 2 : 1;
+ int i, n;
+ GList *children, *chead;
+
+ chead = children = gtk_container_children(GTK_CONTAINER(list));
+
+ n = g_list_length(children);
+
+ if (step == 2) {
+ /*
+ * Figure out how many list items to a screenful,
+ * and adjust the step appropriately.
+ */
+ step = 0.5 + adj->page_size * n / (adj->upper - adj->lower);
+ step--; /* go by one less than that */
+ }
+
+ i = 0;
+ while (children != NULL) {
+ if (item == children->data)
+ break;
+ children = children->next;
+ i++;
+ }
+
+ while (step > 0) {
+ if (direction < 0 && i > 0)
+ children = children->prev, i--;
+ else if (direction > 0 && i < n-1)
+ children = children->next, i++;
+ step--;
+ }
+
+ if (children && children->data) {
+ if (!multiple)
+ gtk_list_select_child(GTK_LIST(list),
+ GTK_WIDGET(children->data));
+ gtk_widget_grab_focus(GTK_WIDGET(children->data));
+ gtk_adjustment_clamp_page(
+ adj,
+ adj->lower + (adj->upper-adj->lower) * i / n,
+ adj->lower + (adj->upper-adj->lower) * (i+1) / n);
+ }
+
+ g_list_free(chead);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event,
+ gpointer data)
+{
+ return listitem_key(item, event, data, false);
+}
+
+static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event,
+ gpointer data)
+{
+ return listitem_key(item, event, data, true);
+}
+
+static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event,
+ gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item));
+ switch (event->type) {
+ default:
+ case GDK_BUTTON_PRESS: uc->nclicks = 1; break;
+ case GDK_2BUTTON_PRESS: uc->nclicks = 2; break;
+ case GDK_3BUTTON_PRESS: uc->nclicks = 3; break;
+ }
+ return false;
+}
+
+static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event,
+ gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item));
+ if (uc->nclicks>1) {
+ uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
+ return true;
+ }
+ return false;
+}
+
+static void list_selchange(GtkList *list, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(list));
+ if (!uc) return;
+ uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
+}
+
+static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction)
+{
+ int index = dlg_listbox_index(uc->ctrl, dp);
+ GList *children = gtk_container_children(GTK_CONTAINER(uc->list));
+ GtkWidget *child;
+
+ if ((index < 0) ||
+ (index == 0 && direction < 0) ||
+ (index == g_list_length(children)-1 && direction > 0)) {
+ gdk_display_beep(gdk_display_get_default());
+ return;
+ }
+
+ child = g_list_nth_data(children, index);
+ gtk_widget_ref(child);
+ gtk_list_clear_items(GTK_LIST(uc->list), index, index+1);
+ g_list_free(children);
+
+ children = NULL;
+ children = g_list_append(children, child);
+ gtk_list_insert_items(GTK_LIST(uc->list), children, index + direction);
+ gtk_list_select_item(GTK_LIST(uc->list), index + direction);
+ uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
+}
+
+static void draglist_up(GtkButton *button, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
+ draglist_move(dp, uc, -1);
+}
+
+static void draglist_down(GtkButton *button, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
+ draglist_move(dp, uc, +1);
+}
+
+#else /* !GTK_CHECK_VERSION(2,0,0) */
+
+/*
+ * GTK 2 list box event handlers.
+ */
+
+static void listbox_doubleclick(GtkTreeView *treeview, GtkTreePath *path,
+ GtkTreeViewColumn *column, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(treeview));
+ if (uc)
+ uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
+}
+
+static void listbox_selchange(GtkTreeSelection *treeselection,
+ gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ GtkTreeView *tree = gtk_tree_selection_get_tree_view(treeselection);
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tree));
+ if (uc)
+ uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
+}
+
+struct draglist_valchange_ctx {
+ struct uctrl *uc;
+ struct dlgparam *dp;
+};
+
+static gboolean draglist_valchange(gpointer data)
+{
+ struct draglist_valchange_ctx *ctx =
+ (struct draglist_valchange_ctx *)data;
+
+ ctx->uc->ctrl->handler(ctx->uc->ctrl, ctx->dp,
+ ctx->dp->data, EVENT_VALCHANGE);
+
+ sfree(ctx);
+
+ return false;
+}
+
+static void listbox_reorder(GtkTreeModel *treemodel, GtkTreePath *path,
+ GtkTreeIter *iter, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ gpointer tree;
+ struct uctrl *uc;
+
+ if (dp->flags & FLAG_UPDATING_LISTBOX)
+ return; /* not a user drag operation */
+
+ tree = g_object_get_data(G_OBJECT(treemodel), "user-data");
+ uc = dlg_find_bywidget(dp, GTK_WIDGET(tree));
+ if (uc) {
+ /*
+ * We should cause EVENT_VALCHANGE on the list box, now
+ * that its rows have been reordered. However, the GTK 2
+ * docs say that at the point this signal is received the
+ * new row might not have actually been filled in yet.
+ *
+ * (So what smegging use is it then, eh? Don't suppose it
+ * occurred to you at any point that letting the
+ * application know _after_ the reordering was compelete
+ * might be helpful to someone?)
+ *
+ * To get round this, I schedule an idle function, which I
+ * hope won't be called until the main event loop is
+ * re-entered after the drag-and-drop handler has finished
+ * furtling with the list store.
+ */
+ struct draglist_valchange_ctx *ctx =
+ snew(struct draglist_valchange_ctx);
+ ctx->uc = uc;
+ ctx->dp = dp;
+ g_idle_add(draglist_valchange, ctx);
+ }
+}
+
+#endif /* !GTK_CHECK_VERSION(2,0,0) */
+
+#if !GTK_CHECK_VERSION(2,4,0)
+
+static void menuitem_activate(GtkMenuItem *item, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ GtkWidget *menushell = GTK_WIDGET(item)->parent;
+ gpointer optmenu = g_object_get_data(G_OBJECT(menushell), "user-data");
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(optmenu));
+ uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
+}
+
+#else
+
+static void droplist_selchange(GtkComboBox *combo, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(combo));
+ if (uc)
+ uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
+}
+
+#endif /* !GTK_CHECK_VERSION(2,4,0) */
+
+static void filechoose_emit_value(struct dlgparam *dp, struct uctrl *uc,
+ const char *name)
+{
+ if (uc->entry) {
+ gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
+ } else {
+ uc->textvalue = name;
+ uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
+ uc->textvalue = NULL;
+ }
+}
+
+#ifdef USE_GTK_FILE_CHOOSER_DIALOG
+static void filechoose_response(GtkDialog *dialog, gint response,
+ gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data");
+ if (response == GTK_RESPONSE_ACCEPT) {
+ gchar *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+ filechoose_emit_value(dp, uc, name);
+ g_free(name);
+ }
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+#else
+static void filesel_ok(GtkButton *button, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ gpointer filesel = g_object_get_data(G_OBJECT(button), "user-data");
+ struct uctrl *uc = g_object_get_data(G_OBJECT(filesel), "user-data");
+ const char *name = gtk_file_selection_get_filename(
+ GTK_FILE_SELECTION(filesel));
+ filechoose_emit_value(dp, uc, name);
+}
+#endif
+
+static void fontsel_ok(GtkButton *button, gpointer data)
+{
+ /* struct dlgparam *dp = (struct dlgparam *)data; */
+
+#if !GTK_CHECK_VERSION(2,0,0)
+
+ gpointer fontsel = g_object_get_data(G_OBJECT(button), "user-data");
+ struct uctrl *uc = g_object_get_data(G_OBJECT(fontsel), "user-data");
+ const char *name = gtk_font_selection_dialog_get_font_name(
+ GTK_FONT_SELECTION_DIALOG(fontsel));
+ gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
+
+#else
+
+ unifontsel *fontsel = (unifontsel *)g_object_get_data(
+ G_OBJECT(button), "user-data");
+ struct uctrl *uc = (struct uctrl *)fontsel->user_data;
+ char *name = unifontsel_get_name(fontsel);
+ assert(name); /* should always be ok after OK pressed */
+ gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
+ sfree(name);
+
+#endif
+}
+
+#if GTK_CHECK_VERSION(3,0,0)
+
+static void colourchoose_response(GtkDialog *dialog,
+ gint response_id, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data");
+
+ if (response_id == GTK_RESPONSE_OK) {
+ GdkRGBA rgba;
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(dialog), &rgba);
+ dp->coloursel_result.r = (int) (255 * rgba.red);
+ dp->coloursel_result.g = (int) (255 * rgba.green);
+ dp->coloursel_result.b = (int) (255 * rgba.blue);
+ dp->coloursel_result.ok = true;
+ } else {
+ dp->coloursel_result.ok = false;
+ }
+
+ uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
+
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+#else /* GTK 1/2 coloursel response handlers */
+
+static void coloursel_ok(GtkButton *button, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data");
+ struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data");
+
+#if GTK_CHECK_VERSION(2,0,0)
+ {
+ GtkColorSelection *cs = GTK_COLOR_SELECTION(
+ gtk_color_selection_dialog_get_color_selection(
+ GTK_COLOR_SELECTION_DIALOG(coloursel)));
+ GdkColor col;
+ gtk_color_selection_get_current_color(cs, &col);
+ dp->coloursel_result.r = col.red / 0x0100;
+ dp->coloursel_result.g = col.green / 0x0100;
+ dp->coloursel_result.b = col.blue / 0x0100;
+ }
+#else
+ {
+ GtkColorSelection *cs = GTK_COLOR_SELECTION(
+ gtk_color_selection_dialog_get_color_selection(
+ GTK_COLOR_SELECTION_DIALOG(coloursel)));
+ gdouble cvals[4];
+ gtk_color_selection_get_color(cs, cvals);
+ dp->coloursel_result.r = (int) (255 * cvals[0]);
+ dp->coloursel_result.g = (int) (255 * cvals[1]);
+ dp->coloursel_result.b = (int) (255 * cvals[2]);
+ }
+#endif
+ dp->coloursel_result.ok = true;
+ uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
+}
+
+static void coloursel_cancel(GtkButton *button, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data");
+ struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data");
+ dp->coloursel_result.ok = false;
+ uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
+}
+
+#endif /* end of coloursel response handlers */
+
+static void filefont_clicked(GtkButton *button, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
+
+ if (uc->ctrl->type == CTRL_FILESELECT) {
+#ifdef USE_GTK_FILE_CHOOSER_DIALOG
+ GtkWidget *filechoose = gtk_file_chooser_dialog_new(
+ uc->ctrl->fileselect.title, GTK_WINDOW(dp->window),
+ (uc->ctrl->fileselect.for_writing ?
+ GTK_FILE_CHOOSER_ACTION_SAVE :
+ GTK_FILE_CHOOSER_ACTION_OPEN),
+ STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL,
+ STANDARD_OPEN_LABEL, GTK_RESPONSE_ACCEPT,
+ (const gchar *)NULL);
+ gtk_window_set_modal(GTK_WINDOW(filechoose), true);
+ g_object_set_data(G_OBJECT(filechoose), "user-data", (gpointer)uc);
+ g_signal_connect(G_OBJECT(filechoose), "response",
+ G_CALLBACK(filechoose_response), (gpointer)dp);
+ gtk_widget_show(filechoose);
+#else
+ GtkWidget *filesel =
+ gtk_file_selection_new(uc->ctrl->fileselect.title);
+ gtk_window_set_modal(GTK_WINDOW(filesel), true);
+ g_object_set_data(
+ G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data",
+ (gpointer)filesel);
+ g_object_set_data(G_OBJECT(filesel), "user-data", (gpointer)uc);
+ g_signal_connect(
+ G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
+ G_CALLBACK(filesel_ok), (gpointer)dp);
+ g_signal_connect_swapped(
+ G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
+ G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
+ g_signal_connect_swapped(
+ G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked",
+ G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
+ gtk_widget_show(filesel);
+#endif
+ }
+
+ if (uc->ctrl->type == CTRL_FONTSELECT) {
+ const gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry));
+
+#if !GTK_CHECK_VERSION(2,0,0)
+
+ /*
+ * Use the GTK 1 standard font selector.
+ */
+
+ gchar *spacings[] = { "c", "m", NULL };
+ GtkWidget *fontsel =
+ gtk_font_selection_dialog_new("Select a font");
+ gtk_window_set_modal(GTK_WINDOW(fontsel), true);
+ gtk_font_selection_dialog_set_filter(
+ GTK_FONT_SELECTION_DIALOG(fontsel),
+ GTK_FONT_FILTER_BASE, GTK_FONT_ALL,
+ NULL, NULL, NULL, NULL, spacings, NULL);
+ if (!gtk_font_selection_dialog_set_font_name(
+ GTK_FONT_SELECTION_DIALOG(fontsel), fontname)) {
+ /*
+ * If the font name wasn't found as it was, try opening
+ * it and extracting its FONT property. This should
+ * have the effect of mapping short aliases into true
+ * XLFDs.
+ */
+ GdkFont *font = gdk_font_load(fontname);
+ if (font) {
+ XFontStruct *xfs = GDK_FONT_XFONT(font);
+ Display *disp = get_x11_display();
+ Atom fontprop = XInternAtom(disp, "FONT", False);
+ unsigned long ret;
+
+ assert(disp); /* this is GTK1! */
+
+ gdk_font_ref(font);
+ if (XGetFontProperty(xfs, fontprop, &ret)) {
+ char *name = XGetAtomName(disp, (Atom)ret);
+ if (name)
+ gtk_font_selection_dialog_set_font_name(
+ GTK_FONT_SELECTION_DIALOG(fontsel), name);
+ }
+ gdk_font_unref(font);
+ }
+ }
+ g_object_set_data(
+ G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
+ "user-data", (gpointer)fontsel);
+ g_object_set_data(G_OBJECT(fontsel), "user-data", (gpointer)uc);
+ g_signal_connect(
+ G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
+ "clicked", G_CALLBACK(fontsel_ok), (gpointer)dp);
+ g_signal_connect_swapped(
+ G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
+ "clicked", G_CALLBACK(gtk_widget_destroy),
+ (gpointer)fontsel);
+ g_signal_connect_swapped(
+ G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button),
+ "clicked", G_CALLBACK(gtk_widget_destroy),
+ (gpointer)fontsel);
+ gtk_widget_show(fontsel);
+
+#else /* !GTK_CHECK_VERSION(2,0,0) */
+
+ /*
+ * Use the unifontsel code provided in unifont.c.
+ */
+
+ unifontsel *fontsel = unifontsel_new("Select a font");
+
+ gtk_window_set_modal(fontsel->window, true);
+ unifontsel_set_name(fontsel, fontname);
+
+ g_object_set_data(G_OBJECT(fontsel->ok_button),
+ "user-data", (gpointer)fontsel);
+ fontsel->user_data = uc;
+ g_signal_connect(G_OBJECT(fontsel->ok_button), "clicked",
+ G_CALLBACK(fontsel_ok), (gpointer)dp);
+ g_signal_connect_swapped(G_OBJECT(fontsel->ok_button), "clicked",
+ G_CALLBACK(unifontsel_destroy),
+ (gpointer)fontsel);
+ g_signal_connect_swapped(G_OBJECT(fontsel->cancel_button),"clicked",
+ G_CALLBACK(unifontsel_destroy),
+ (gpointer)fontsel);
+
+ gtk_widget_show(GTK_WIDGET(fontsel->window));
+
+#endif /* !GTK_CHECK_VERSION(2,0,0) */
+
+ }
+}
+
+#if !GTK_CHECK_VERSION(3,0,0)
+static void label_sizealloc(GtkWidget *widget, GtkAllocation *alloc,
+ gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ struct uctrl *uc = dlg_find_bywidget(dp, widget);
+
+ gtk_widget_set_size_request(uc->text, alloc->width, -1);
+ gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->label);
+ g_signal_handler_disconnect(G_OBJECT(uc->text), uc->textsig);
+}
+#endif
+
+/* ----------------------------------------------------------------------
+ * This function does the main layout work: it reads a controlset,
+ * it creates the relevant GTK controls, and returns a GtkWidget
+ * containing the result. (This widget might be a title of some
+ * sort, it might be a Columns containing many controls, or it
+ * might be a GtkFrame containing a Columns; whatever it is, it's
+ * definitely a GtkWidget and should probably be added to a
+ * GtkVbox.)
+ *
+ * `win' is required for setting the default button. If it is
+ * non-NULL, all buttons created will be default-capable (so they
+ * have extra space round them for the default highlight).
+ */
+GtkWidget *layout_ctrls(
+ struct dlgparam *dp, struct selparam *sp, struct Shortcuts *scs,
+ struct controlset *s, GtkWindow *win)
+{
+ Columns *cols;
+ GtkWidget *ret;
+ int i;
+
+ if (!s->boxname) {
+ /* This controlset is a panel title. */
+ assert(s->boxtitle);
+ return gtk_label_new(s->boxtitle);
+ }
+
+ /*
+ * Otherwise, we expect to be laying out actual controls, so
+ * we'll start by creating a Columns for the purpose.
+ */
+ cols = COLUMNS(columns_new(4));
+ ret = GTK_WIDGET(cols);
+ gtk_widget_show(ret);
+
+ /*
+ * Create a containing frame if we have a box name.
+ */
+ if (*s->boxname) {
+ ret = gtk_frame_new(s->boxtitle); /* NULL is valid here */
+ gtk_container_set_border_width(GTK_CONTAINER(cols), 4);
+ gtk_container_add(GTK_CONTAINER(ret), GTK_WIDGET(cols));
+ gtk_widget_show(ret);
+ }
+
+ /*
+ * Now iterate through the controls themselves, create them,
+ * and add them to the Columns.
+ */
+ for (i = 0; i < s->ncontrols; i++) {
+ dlgcontrol *ctrl = s->ctrls[i];
+ struct uctrl *uc;
+ bool left = false;
+ GtkWidget *w = NULL;
+
+ switch (ctrl->type) {
+ case CTRL_COLUMNS: {
+ static const int simplecols[1] = { 100 };
+ columns_set_cols(cols, ctrl->columns.ncols,
+ (ctrl->columns.percentages ?
+ ctrl->columns.percentages : simplecols));
+ continue; /* no actual control created */
+ }
+ case CTRL_TABDELAY: {
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl->tabdelay.ctrl);
+ if (uc)
+ columns_taborder_last(cols, uc->toplevel);
+ continue; /* no actual control created */
+ }
+ }
+
+ uc = snew(struct uctrl);
+ uc->sp = sp;
+ uc->ctrl = ctrl;
+ uc->buttons = NULL;
+ uc->entry = NULL;
+#if !GTK_CHECK_VERSION(2,4,0)
+ uc->list = uc->menu = uc->optmenu = NULL;
+#else
+ uc->combo = NULL;
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ uc->treeview = NULL;
+ uc->listmodel = NULL;
+#endif
+ uc->button = uc->text = NULL;
+ uc->label = NULL;
+ uc->nclicks = 0;
+
+ switch (ctrl->type) {
+ case CTRL_BUTTON:
+ w = gtk_button_new_with_label(ctrl->label);
+ if (win) {
+ gtk_widget_set_can_default(w, true);
+ if (ctrl->button.isdefault)
+ gtk_window_set_default(win, w);
+ if (ctrl->button.iscancel)
+ dp->cancelbutton = w;
+ }
+ g_signal_connect(G_OBJECT(w), "clicked",
+ G_CALLBACK(button_clicked), dp);
+ g_signal_connect(G_OBJECT(w), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
+ shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
+ ctrl->button.shortcut, SHORTCUT_UCTRL, uc);
+ break;
+ case CTRL_CHECKBOX:
+ w = gtk_check_button_new_with_label(ctrl->label);
+ g_signal_connect(G_OBJECT(w), "toggled",
+ G_CALLBACK(button_toggled), dp);
+ g_signal_connect(G_OBJECT(w), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
+ shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
+ ctrl->checkbox.shortcut, SHORTCUT_UCTRL, uc);
+ left = true;
+ break;
+ case CTRL_RADIO: {
+ /*
+ * Radio buttons get to go inside their own Columns, no
+ * matter what.
+ */
+ gint i, *percentages;
+ GSList *group;
+
+ w = columns_new(0);
+ if (ctrl->label) {
+ GtkWidget *label = gtk_label_new(ctrl->label);
+ columns_add(COLUMNS(w), label, 0, 1);
+ columns_force_left_align(COLUMNS(w), label);
+ gtk_widget_show(label);
+ shortcut_add(scs, label, ctrl->radio.shortcut,
+ SHORTCUT_UCTRL, uc);
+ uc->label = label;
+ }
+ percentages = g_new(gint, ctrl->radio.ncolumns);
+ for (i = 0; i < ctrl->radio.ncolumns; i++) {
+ percentages[i] =
+ ((100 * (i+1) / ctrl->radio.ncolumns) -
+ 100 * i / ctrl->radio.ncolumns);
+ }
+ columns_set_cols(COLUMNS(w), ctrl->radio.ncolumns,
+ percentages);
+ g_free(percentages);
+ group = NULL;
+
+ uc->nbuttons = ctrl->radio.nbuttons;
+ uc->buttons = snewn(uc->nbuttons, GtkWidget *);
+
+ for (i = 0; i < ctrl->radio.nbuttons; i++) {
+ GtkWidget *b;
+ gint colstart;
+
+ b = gtk_radio_button_new_with_label(
+ group, ctrl->radio.buttons[i]);
+ uc->buttons[i] = b;
+ group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b));
+ colstart = i % ctrl->radio.ncolumns;
+ columns_add(COLUMNS(w), b, colstart,
+ (i == ctrl->radio.nbuttons-1 ?
+ ctrl->radio.ncolumns - colstart : 1));
+ columns_force_left_align(COLUMNS(w), b);
+ gtk_widget_show(b);
+ g_signal_connect(G_OBJECT(b), "toggled",
+ G_CALLBACK(button_toggled), dp);
+ g_signal_connect(G_OBJECT(b), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
+ if (ctrl->radio.shortcuts) {
+ shortcut_add(scs, gtk_bin_get_child(GTK_BIN(b)),
+ ctrl->radio.shortcuts[i],
+ SHORTCUT_UCTRL, uc);
+ }
+ }
+ break;
+ }
+ case CTRL_EDITBOX: {
+ GtkWidget *signalobject;
+
+ if (ctrl->editbox.has_list) {
+#if !GTK_CHECK_VERSION(2,4,0)
+ /*
+ * GTK 1 combo box.
+ */
+ w = gtk_combo_new();
+ gtk_combo_set_value_in_list(GTK_COMBO(w), false, true);
+ uc->entry = GTK_COMBO(w)->entry;
+ uc->list = GTK_COMBO(w)->list;
+ signalobject = uc->entry;
+#else
+ /*
+ * GTK 2 combo box.
+ */
+ uc->listmodel = gtk_list_store_new(2, G_TYPE_INT,
+ G_TYPE_STRING);
+ w = gtk_combo_box_new_with_model_and_entry(
+ GTK_TREE_MODEL(uc->listmodel));
+ g_object_set(G_OBJECT(w), "entry-text-column", 1,
+ (const char *)NULL);
+ /* We cannot support password combo boxes. */
+ assert(!ctrl->editbox.password);
+ uc->combo = w;
+ signalobject = uc->combo;
+#endif
+ } else {
+ w = gtk_entry_new();
+ if (ctrl->editbox.password)
+ gtk_entry_set_visibility(GTK_ENTRY(w), false);
+ uc->entry = w;
+ signalobject = w;
+ }
+ g_signal_connect(G_OBJECT(signalobject), "changed",
+ G_CALLBACK(editbox_changed), dp);
+ g_signal_connect(G_OBJECT(signalobject), "key_press_event",
+ G_CALLBACK(editbox_key), dp);
+ g_signal_connect(G_OBJECT(signalobject), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
+ g_signal_connect(G_OBJECT(signalobject), "focus_out_event",
+ G_CALLBACK(editbox_lostfocus), dp);
+ g_signal_connect(G_OBJECT(signalobject), "focus_out_event",
+ G_CALLBACK(editbox_lostfocus), dp);
+
+#if !GTK_CHECK_VERSION(3,0,0)
+ /*
+ * Edit boxes, for some strange reason, have a minimum
+ * width of 150 in GTK 1.2. We don't want this - we'd
+ * rather the edit boxes acquired their natural width
+ * from the column layout of the rest of the box.
+ */
+ {
+ GtkRequisition req;
+ gtk_widget_size_request(w, &req);
+ gtk_widget_set_size_request(w, 10, req.height);
+ }
+#else
+ /*
+ * In GTK 3, this is still true, but there's a special
+ * method for GtkEntry in particular to fix it.
+ */
+ if (GTK_IS_ENTRY(w))
+ gtk_entry_set_width_chars(GTK_ENTRY(w), 1);
+#endif
+
+ if (ctrl->label) {
+ GtkWidget *label;
+
+ label = gtk_label_new(ctrl->label);
+
+ shortcut_add(scs, label, ctrl->editbox.shortcut,
+ SHORTCUT_FOCUS, uc->entry);
+
+ if (ctrl->editbox.percentwidth == 100) {
+ columns_add(cols, label,
+ COLUMN_START(ctrl->column),
+ COLUMN_SPAN(ctrl->column));
+ columns_force_left_align(cols, label);
+ } else {
+ GtkWidget *container = columns_new(4);
+ gint percentages[2];
+ percentages[1] = ctrl->editbox.percentwidth;
+ percentages[0] = 100 - ctrl->editbox.percentwidth;
+ columns_set_cols(COLUMNS(container), 2, percentages);
+ columns_add(COLUMNS(container), label, 0, 1);
+ columns_force_left_align(COLUMNS(container), label);
+ columns_add(COLUMNS(container), w, 1, 1);
+ columns_align_next_to(COLUMNS(container), label, w);
+ gtk_widget_show(w);
+ w = container;
+ }
+
+ gtk_widget_show(label);
+ uc->label = label;
+ }
+ break;
+ }
+ case CTRL_FILESELECT:
+ case CTRL_FONTSELECT: {
+ GtkWidget *ww;
+
+ bool just_button = (ctrl->type == CTRL_FILESELECT &&
+ ctrl->fileselect.just_button);
+
+ if (!just_button) {
+ const char *browsebtn =
+ (ctrl->type == CTRL_FILESELECT ?
+ "Browse..." : "Change...");
+
+ gint percentages[] = { 75, 25 };
+ w = columns_new(4);
+ columns_set_cols(COLUMNS(w), 2, percentages);
+
+ if (ctrl->label) {
+ ww = gtk_label_new(ctrl->label);
+ columns_add(COLUMNS(w), ww, 0, 2);
+ columns_force_left_align(COLUMNS(w), ww);
+ gtk_widget_show(ww);
+ shortcut_add(scs, ww,
+ (ctrl->type == CTRL_FILESELECT ?
+ ctrl->fileselect.shortcut :
+ ctrl->fontselect.shortcut),
+ SHORTCUT_UCTRL, uc);
+ uc->label = ww;
+ }
+
+ uc->entry = ww = gtk_entry_new();
+#if !GTK_CHECK_VERSION(3,0,0)
+ {
+ GtkRequisition req;
+ gtk_widget_size_request(ww, &req);
+ gtk_widget_set_size_request(ww, 10, req.height);
+ }
+#else
+ gtk_entry_set_width_chars(GTK_ENTRY(ww), 1);
+#endif
+ columns_add(COLUMNS(w), ww, 0, 1);
+ gtk_widget_show(ww);
+
+ uc->button = ww = gtk_button_new_with_label(browsebtn);
+ columns_add(COLUMNS(w), ww, 1, 1);
+ gtk_widget_show(ww);
+
+ columns_align_next_to(COLUMNS(w), uc->entry, uc->button);
+
+ g_signal_connect(G_OBJECT(uc->entry), "key_press_event",
+ G_CALLBACK(editbox_key), dp);
+ g_signal_connect(G_OBJECT(uc->entry), "changed",
+ G_CALLBACK(editbox_changed), dp);
+ g_signal_connect(G_OBJECT(uc->entry), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
+ } else {
+ uc->button = w = gtk_button_new_with_label(ctrl->label);
+ shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
+ ctrl->fileselect.shortcut, SHORTCUT_UCTRL, uc);
+ gtk_widget_show(w);
+
+ }
+ g_signal_connect(G_OBJECT(uc->button), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
+ g_signal_connect(G_OBJECT(uc->button), "clicked",
+ G_CALLBACK(filefont_clicked), dp);
+ break;
+ }
+ case CTRL_LISTBOX:
+
+#if GTK_CHECK_VERSION(2,0,0)
+ /*
+ * First construct the list data store, with the right
+ * number of columns.
+ */
+# if !GTK_CHECK_VERSION(2,4,0)
+ /* (For GTK 2.0 to 2.3, we do this for full listboxes only,
+ * because combo boxes are still done the old GTK1 way.) */
+ if (ctrl->listbox.height > 0)
+# endif
+ {
+ GType *types;
+ int i;
+ int cols;
+
+ cols = ctrl->listbox.ncols;
+ cols = cols ? cols : 1;
+ types = snewn(1 + cols, GType);
+
+ types[0] = G_TYPE_INT;
+ for (i = 0; i < cols; i++)
+ types[i+1] = G_TYPE_STRING;
+
+ uc->listmodel = gtk_list_store_newv(1 + cols, types);
+
+ sfree(types);
+ }
+#endif
+
+ /*
+ * See if it's a drop-down list (non-editable combo
+ * box).
+ */
+ if (ctrl->listbox.height == 0) {
+#if !GTK_CHECK_VERSION(2,4,0)
+ /*
+ * GTK1 and early-GTK2 option-menu style of
+ * drop-down list.
+ */
+ uc->optmenu = w = gtk_option_menu_new();
+ uc->menu = gtk_menu_new();
+ gtk_option_menu_set_menu(GTK_OPTION_MENU(w), uc->menu);
+ g_object_set_data(G_OBJECT(uc->menu), "user-data",
+ (gpointer)uc->optmenu);
+ g_signal_connect(G_OBJECT(uc->optmenu), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
+#else
+ /*
+ * Late-GTK2 style using a GtkComboBox.
+ */
+ GtkCellRenderer *cr;
+
+ /*
+ * Create a non-editable GtkComboBox (that is, not
+ * its subclass GtkComboBoxEntry).
+ */
+ w = gtk_combo_box_new_with_model(
+ GTK_TREE_MODEL(uc->listmodel));
+ uc->combo = w;
+
+ /*
+ * Tell it how to render a list item (i.e. which
+ * column to look at in the list model).
+ */
+ cr = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, true);
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), cr,
+ "text", 1, NULL);
+
+ /*
+ * And tell it to notify us when the selection
+ * changes.
+ */
+ g_signal_connect(G_OBJECT(w), "changed",
+ G_CALLBACK(droplist_selchange), dp);
+
+ g_signal_connect(G_OBJECT(w), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
+#endif
+ } else {
+#if !GTK_CHECK_VERSION(2,0,0)
+ /*
+ * GTK1-style full list box.
+ */
+ uc->list = gtk_list_new();
+ if (ctrl->listbox.multisel == 2) {
+ gtk_list_set_selection_mode(GTK_LIST(uc->list),
+ GTK_SELECTION_EXTENDED);
+ } else if (ctrl->listbox.multisel == 1) {
+ gtk_list_set_selection_mode(GTK_LIST(uc->list),
+ GTK_SELECTION_MULTIPLE);
+ } else {
+ gtk_list_set_selection_mode(GTK_LIST(uc->list),
+ GTK_SELECTION_SINGLE);
+ }
+ w = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(w),
+ uc->list);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ uc->adj = gtk_scrolled_window_get_vadjustment(
+ GTK_SCROLLED_WINDOW(w));
+
+ gtk_widget_show(uc->list);
+ g_signal_connect(G_OBJECT(uc->list), "selection-changed",
+ G_CALLBACK(list_selchange), dp);
+ g_signal_connect(G_OBJECT(uc->list), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
+
+ /*
+ * Adjust the height of the scrolled window to the
+ * minimum given by the height parameter.
+ *
+ * This piece of guesswork is a horrid hack based
+ * on looking inside the GTK 1.2 sources
+ * (specifically gtkviewport.c, which appears to be
+ * the widget which provides the border around the
+ * scrolling area). Anyone lets me know how I can
+ * do this in a way which isn't at risk from GTK
+ * upgrades, I'd be grateful.
+ */
+ {
+ int edge;
+ edge = GTK_WIDGET(uc->list)->style->klass->ythickness;
+ gtk_widget_set_size_request(
+ w, 10, 2*edge + (ctrl->listbox.height *
+ get_listitemheight(w)));
+ }
+
+ if (ctrl->listbox.draglist) {
+ /*
+ * GTK doesn't appear to make it easy to
+ * implement a proper draggable list; so
+ * instead I'm just going to have to put an Up
+ * and a Down button to the right of the actual
+ * list box. Ah well.
+ */
+ GtkWidget *cols, *button;
+ static const gint percentages[2] = { 80, 20 };
+
+ cols = columns_new(4);
+ columns_set_cols(COLUMNS(cols), 2, percentages);
+ columns_add(COLUMNS(cols), w, 0, 1);
+ gtk_widget_show(w);
+ button = gtk_button_new_with_label("Up");
+ columns_add(COLUMNS(cols), button, 1, 1);
+ gtk_widget_show(button);
+ g_signal_connect(G_OBJECT(button), "clicked",
+ G_CALLBACK(draglist_up), dp);
+ g_signal_connect(G_OBJECT(button), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
+ button = gtk_button_new_with_label("Down");
+ columns_add(COLUMNS(cols), button, 1, 1);
+ gtk_widget_show(button);
+ g_signal_connect(G_OBJECT(button), "clicked",
+ G_CALLBACK(draglist_down), dp);
+ g_signal_connect(G_OBJECT(button), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
+
+ w = cols;
+ }
+#else
+ /*
+ * GTK2 treeview-based full list box.
+ */
+ GtkTreeSelection *sel;
+
+ /*
+ * Create the list box itself, its columns, and
+ * its containing scrolled window.
+ */
+ w = gtk_tree_view_new_with_model(
+ GTK_TREE_MODEL(uc->listmodel));
+ g_object_set_data(G_OBJECT(uc->listmodel), "user-data",
+ (gpointer)w);
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false);
+ sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(w));
+ gtk_tree_selection_set_mode(
+ sel, ctrl->listbox.multisel ? GTK_SELECTION_MULTIPLE :
+ GTK_SELECTION_SINGLE);
+ uc->treeview = w;
+ g_signal_connect(G_OBJECT(w), "row-activated",
+ G_CALLBACK(listbox_doubleclick), dp);
+ g_signal_connect(G_OBJECT(w), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
+ g_signal_connect(G_OBJECT(sel), "changed",
+ G_CALLBACK(listbox_selchange), dp);
+
+ if (ctrl->listbox.draglist) {
+ gtk_tree_view_set_reorderable(GTK_TREE_VIEW(w), true);
+ g_signal_connect(G_OBJECT(uc->listmodel), "row-inserted",
+ G_CALLBACK(listbox_reorder), dp);
+ }
+
+ {
+ int i;
+ int cols;
+
+ cols = ctrl->listbox.ncols;
+ cols = cols ? cols : 1;
+ for (i = 0; i < cols; i++) {
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cellrend;
+ /*
+ * It appears that GTK 2 doesn't leave us any
+ * particularly sensible way to honour the
+ * "percentages" specification in the ctrl
+ * structure.
+ */
+ cellrend = gtk_cell_renderer_text_new();
+ if (!ctrl->listbox.hscroll) {
+ g_object_set(G_OBJECT(cellrend),
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "ellipsize-set", true,
+ (const char *)NULL);
+ }
+ column = gtk_tree_view_column_new_with_attributes(
+ "heading", cellrend, "text", i+1, (char *)NULL);
+ gtk_tree_view_column_set_sizing(
+ column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
+ }
+ }
+
+ {
+ GtkWidget *scroll;
+
+ scroll = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_shadow_type(
+ GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN);
+ gtk_widget_show(w);
+ gtk_container_add(GTK_CONTAINER(scroll), w);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_ALWAYS);
+ gtk_widget_set_size_request(
+ scroll, -1,
+ ctrl->listbox.height * get_listitemheight(w));
+
+ w = scroll;
+ }
+#endif
+ }
+
+ if (ctrl->label) {
+ GtkWidget *label, *container;
+
+ label = gtk_label_new(ctrl->label);
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_label_set_width_chars(GTK_LABEL(label), 3);
+#endif
+
+ shortcut_add(scs, label, ctrl->listbox.shortcut,
+ SHORTCUT_UCTRL, uc);
+
+ container = columns_new(4);
+ if (ctrl->listbox.percentwidth == 100) {
+ columns_add(COLUMNS(container), label, 0, 1);
+ columns_force_left_align(COLUMNS(container), label);
+ columns_add(COLUMNS(container), w, 0, 1);
+ } else {
+ gint percentages[2];
+ percentages[1] = ctrl->listbox.percentwidth;
+ percentages[0] = 100 - ctrl->listbox.percentwidth;
+ columns_set_cols(COLUMNS(container), 2, percentages);
+ columns_add(COLUMNS(container), label, 0, 1);
+ columns_force_left_align(COLUMNS(container), label);
+ columns_add(COLUMNS(container), w, 1, 1);
+ columns_align_next_to(COLUMNS(container), label, w);
+ }
+ gtk_widget_show(label);
+ gtk_widget_show(w);
+
+ w = container;
+ uc->label = label;
+ }
+
+ break;
+ case CTRL_TEXT:
+#if !GTK_CHECK_VERSION(3,0,0)
+ /*
+ * Wrapping text widgets don't sit well with the GTK2
+ * layout model, in which widgets state a minimum size
+ * and the whole window then adjusts to the smallest
+ * size it can sensibly take given its contents. A
+ * wrapping text widget _has_ no clear minimum size;
+ * instead it has a range of possibilities. It can be
+ * one line deep but 2000 wide, or two lines deep and
+ * 1000 pixels, or three by 867, or four by 500 and so
+ * on. It can be as short as you like provided you
+ * don't mind it being wide, or as narrow as you like
+ * provided you don't mind it being tall.
+ *
+ * Therefore, it fits very badly into the layout model.
+ * Hence the only thing to do is pick a width and let
+ * it choose its own number of lines. To do this I'm
+ * going to cheat a little. All new wrapping text
+ * widgets will be created with a minimal text content
+ * "X"; then, after the rest of the dialog box is set
+ * up and its size calculated, the text widgets will be
+ * told their width and given their real text, which
+ * will cause the size to be recomputed in the y
+ * direction (because many of them will expand to more
+ * than one line).
+ */
+ uc->text = w = gtk_label_new("X");
+ uc->textsig =
+ g_signal_connect(G_OBJECT(w), "size-allocate",
+ G_CALLBACK(label_sizealloc), dp);
+#else
+ /*
+ * In GTK3, this is all fixed, because the main aim of the
+ * new 'height-for-width' geometry management is to make
+ * wrapping labels behave sensibly. So now we can just do
+ * the obvious thing.
+ */
+ uc->text = w = gtk_label_new(uc->ctrl->label);
+#endif
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_label_set_selectable(GTK_LABEL(w), true);
+ gtk_widget_set_can_focus(w, false);
+#endif
+ align_label_left(GTK_LABEL(w));
+ gtk_label_set_line_wrap(GTK_LABEL(w), ctrl->text.wrap);
+ if (!ctrl->text.wrap) {
+ gtk_widget_show(uc->text);
+ w = gtk_scrolled_window_new(NULL, NULL);
+ gtk_container_set_border_width(GTK_CONTAINER(w), 0);
+ gtk_container_add(GTK_CONTAINER(w), uc->text);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_NEVER);
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_widget_set_can_focus(w, false);
+#endif
+ }
+ break;
+ }
+
+ assert(w != NULL);
+
+ columns_add(cols, w,
+ COLUMN_START(ctrl->column),
+ COLUMN_SPAN(ctrl->column));
+ if (left)
+ columns_force_left_align(cols, w);
+ if (ctrl->align_next_to) {
+ struct uctrl *uc2 = dlg_find_byctrl(
+ dp, ctrl->align_next_to);
+ assert(uc2);
+ columns_align_next_to(cols, w, uc2->toplevel);
+
+#if GTK_CHECK_VERSION(3, 10, 0)
+ /* Slightly nicer to align baselines than just vertically
+ * centring, where the option is available */
+ gtk_widget_set_valign(w, GTK_ALIGN_BASELINE);
+ gtk_widget_set_valign(uc2->toplevel, GTK_ALIGN_BASELINE);
+#endif
+ }
+ gtk_widget_show(w);
+
+ uc->toplevel = w;
+ dlg_add_uctrl(dp, uc);
+ }
+
+ return ret;
+}
+
+struct selparam {
+ struct dlgparam *dp;
+ GtkNotebook *panels;
+ GtkWidget *panel;
+#if !GTK_CHECK_VERSION(2,0,0)
+ GtkWidget *treeitem;
+#else
+ int depth;
+ GtkTreePath *treepath;
+#endif
+ struct Shortcuts shortcuts;
+};
+
+#if GTK_CHECK_VERSION(2,0,0)
+static void treeselection_changed(GtkTreeSelection *treeselection,
+ gpointer data)
+{
+ struct selparam **sps = (struct selparam **)data, *sp;
+ GtkTreeModel *treemodel;
+ GtkTreeIter treeiter;
+ gint spindex;
+ gint page_num;
+
+ if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
+ return;
+
+ gtk_tree_model_get(treemodel, &treeiter, TREESTORE_PARAMS, &spindex, -1);
+ sp = sps[spindex];
+
+ page_num = gtk_notebook_page_num(sp->panels, sp->panel);
+ gtk_notebook_set_current_page(sp->panels, page_num);
+
+ sp->dp->curr_panel = sp;
+ dlg_refresh(NULL, sp->dp);
+
+ sp->dp->shortcuts = &sp->shortcuts;
+}
+#else
+static void treeitem_sel(GtkItem *item, gpointer data)
+{
+ struct selparam *sp = (struct selparam *)data;
+ gint page_num;
+
+ page_num = gtk_notebook_page_num(sp->panels, sp->panel);
+ gtk_notebook_set_page(sp->panels, page_num);
+
+ sp->dp->curr_panel = sp;
+ dlg_refresh(NULL, sp->dp);
+
+ sp->dp->shortcuts = &sp->shortcuts;
+ sp->dp->currtreeitem = sp->treeitem;
+}
+#endif
+
+bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
+ /*
+ * A control is visible if it belongs to _no_ notebook page (i.e.
+ * it's one of the config-box-global buttons like Load or About),
+ * or if it belongs to the currently selected page.
+ */
+ return uc->sp == NULL || uc->sp == dp->curr_panel;
+}
+
+#if !GTK_CHECK_VERSION(2,0,0)
+static bool tree_grab_focus(struct dlgparam *dp)
+{
+ int i, f;
+
+ /*
+ * See if any of the treeitems has the focus.
+ */
+ f = -1;
+ for (i = 0; i < dp->ntreeitems; i++)
+ if (GTK_WIDGET_HAS_FOCUS(dp->treeitems[i])) {
+ f = i;
+ break;
+ }
+
+ if (f >= 0)
+ return false;
+ else {
+ gtk_widget_grab_focus(dp->currtreeitem);
+ return true;
+ }
+}
+
+gint tree_focus(GtkContainer *container, GtkDirectionType direction,
+ gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+
+ g_signal_stop_emission_by_name(G_OBJECT(container), "focus");
+ /*
+ * If there's a focused treeitem, we return false to cause the
+ * focus to move on to some totally other control. If not, we
+ * focus the selected one.
+ */
+ return tree_grab_focus(dp);
+}
+#endif
+
+gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+
+ if (event->keyval == GDK_KEY_Escape && dp->cancelbutton) {
+ g_signal_emit_by_name(G_OBJECT(dp->cancelbutton), "clicked");
+ return true;
+ }
+
+ if ((event->state & GDK_MOD1_MASK) &&
+ (unsigned char)event->string[0] > 0 &&
+ (unsigned char)event->string[0] <= 127) {
+ int schr = (unsigned char)event->string[0];
+ struct Shortcut *sc = &dp->shortcuts->sc[schr];
+
+ switch (sc->action) {
+ case SHORTCUT_TREE:
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_widget_grab_focus(sc->widget);
+#else
+ tree_grab_focus(dp);
+#endif
+ break;
+ case SHORTCUT_FOCUS:
+ gtk_widget_grab_focus(sc->widget);
+ break;
+ case SHORTCUT_UCTRL:
+ /*
+ * We must do something sensible with a uctrl.
+ * Precisely what this is depends on the type of
+ * control.
+ */
+ switch (sc->uc->ctrl->type) {
+ case CTRL_CHECKBOX:
+ case CTRL_BUTTON:
+ /* Check boxes and buttons get the focus _and_ get toggled. */
+ gtk_widget_grab_focus(sc->uc->toplevel);
+ g_signal_emit_by_name(G_OBJECT(sc->uc->toplevel), "clicked");
+ break;
+ case CTRL_FILESELECT:
+ case CTRL_FONTSELECT:
+ /* File/font selectors have their buttons pressed (ooer),
+ * and focus transferred to the edit box. */
+ g_signal_emit_by_name(G_OBJECT(sc->uc->button), "clicked");
+ if (sc->uc->entry)
+ gtk_widget_grab_focus(sc->uc->entry);
+ break;
+ case CTRL_RADIO:
+ /*
+ * Radio buttons are fun, because they have
+ * multiple shortcuts. We must find whether the
+ * activated shortcut is the shortcut for the whole
+ * group, or for a particular button. In the former
+ * case, we find the currently selected button and
+ * focus it; in the latter, we focus-and-click the
+ * button whose shortcut was pressed.
+ */
+ if (schr == sc->uc->ctrl->radio.shortcut) {
+ int i;
+ for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
+ if (gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(sc->uc->buttons[i]))) {
+ gtk_widget_grab_focus(sc->uc->buttons[i]);
+ }
+ } else if (sc->uc->ctrl->radio.shortcuts) {
+ int i;
+ for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
+ if (schr == sc->uc->ctrl->radio.shortcuts[i]) {
+ gtk_widget_grab_focus(sc->uc->buttons[i]);
+ g_signal_emit_by_name(
+ G_OBJECT(sc->uc->buttons[i]), "clicked");
+ }
+ }
+ break;
+ case CTRL_LISTBOX:
+
+#if !GTK_CHECK_VERSION(2,4,0)
+ if (sc->uc->optmenu) {
+ GdkEventButton bev;
+ gint returnval;
+
+ gtk_widget_grab_focus(sc->uc->optmenu);
+ /* Option menus don't work using the "clicked" signal.
+ * We need to manufacture a button press event :-/ */
+ bev.type = GDK_BUTTON_PRESS;
+ bev.button = 1;
+ g_signal_emit_by_name(G_OBJECT(sc->uc->optmenu),
+ "button_press_event",
+ &bev, &returnval);
+ break;
+ }
+#else
+ if (sc->uc->combo) {
+ gtk_widget_grab_focus(sc->uc->combo);
+ gtk_combo_box_popup(GTK_COMBO_BOX(sc->uc->combo));
+ break;
+ }
+#endif
+#if !GTK_CHECK_VERSION(2,0,0)
+ if (sc->uc->list) {
+ /*
+ * For GTK-1 style list boxes, we tell it to
+ * focus one of its children, which appears to
+ * do the Right Thing.
+ */
+ gtk_container_focus(GTK_CONTAINER(sc->uc->list),
+ GTK_DIR_TAB_FORWARD);
+ break;
+ }
+#else
+ if (sc->uc->treeview) {
+ gtk_widget_grab_focus(sc->uc->treeview);
+ break;
+ }
+#endif
+ unreachable("bad listbox type in win_key_press");
+ }
+ break;
+ }
+ }
+
+ return false;
+}
+
+#if !GTK_CHECK_VERSION(2,0,0)
+gint tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+
+ if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
+ event->keyval == GDK_Down || event->keyval == GDK_KP_Down) {
+ int dir, i, j = -1;
+ for (i = 0; i < dp->ntreeitems; i++)
+ if (widget == dp->treeitems[i])
+ break;
+ if (i < dp->ntreeitems) {
+ if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
+ dir = -1;
+ else
+ dir = +1;
+
+ while (1) {
+ i += dir;
+ if (i < 0 || i >= dp->ntreeitems)
+ break; /* nothing in that dir to select */
+ /*
+ * Determine if this tree item is visible.
+ */
+ {
+ GtkWidget *w = dp->treeitems[i];
+ bool vis = true;
+ while (w && (GTK_IS_TREE_ITEM(w) || GTK_IS_TREE(w))) {
+ if (!GTK_WIDGET_VISIBLE(w)) {
+ vis = false;
+ break;
+ }
+ w = w->parent;
+ }
+ if (vis) {
+ j = i; /* got one */
+ break;
+ }
+ }
+ }
+ }
+ g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
+ if (j >= 0) {
+ g_signal_emit_by_name(G_OBJECT(dp->treeitems[j]), "toggle");
+ gtk_widget_grab_focus(dp->treeitems[j]);
+ }
+ return true;
+ }
+
+ /*
+ * It's nice for Left and Right to expand and collapse tree
+ * branches.
+ */
+ if (event->keyval == GDK_Left || event->keyval == GDK_KP_Left) {
+ g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
+ gtk_tree_item_collapse(GTK_TREE_ITEM(widget));
+ return true;
+ }
+ if (event->keyval == GDK_Right || event->keyval == GDK_KP_Right) {
+ g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
+ gtk_tree_item_expand(GTK_TREE_ITEM(widget));
+ return true;
+ }
+
+ return false;
+}
+#endif
+
+static void shortcut_highlight(GtkWidget *labelw, int chr)
+{
+ GtkLabel *label = GTK_LABEL(labelw);
+ const gchar *currstr;
+ gchar *pattern;
+ int i;
+
+#if !GTK_CHECK_VERSION(2,0,0)
+ {
+ gchar *currstr_nonconst;
+ gtk_label_get(label, &currstr_nonconst);
+ currstr = currstr_nonconst;
+ }
+#else
+ currstr = gtk_label_get_text(label);
+#endif
+
+ for (i = 0; currstr[i]; i++)
+ if (tolower((unsigned char)currstr[i]) == chr) {
+ pattern = dupprintf("%*s_", i, "");
+ gtk_label_set_pattern(label, pattern);
+ sfree(pattern);
+ break;
+ }
+}
+
+void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
+ int chr, int action, void *ptr)
+{
+ if (chr == NO_SHORTCUT)
+ return;
+
+ chr = tolower((unsigned char)chr);
+
+ assert(scs->sc[chr].action == SHORTCUT_EMPTY);
+
+ scs->sc[chr].action = action;
+
+ if (action == SHORTCUT_FOCUS || action == SHORTCUT_TREE) {
+ scs->sc[chr].uc = NULL;
+ scs->sc[chr].widget = (GtkWidget *)ptr;
+ } else {
+ scs->sc[chr].widget = NULL;
+ scs->sc[chr].uc = (struct uctrl *)ptr;
+ }
+
+ shortcut_highlight(labelw, chr);
+}
+
+static int get_listitemheight(GtkWidget *w)
+{
+#if !GTK_CHECK_VERSION(2,0,0)
+ GtkWidget *listitem = gtk_list_item_new_with_label("foo");
+ GtkRequisition req;
+ gtk_widget_size_request(listitem, &req);
+ g_object_ref_sink(G_OBJECT(listitem));
+ return req.height;
+#else
+ int height;
+ GtkCellRenderer *cr = gtk_cell_renderer_text_new();
+#if GTK_CHECK_VERSION(3,0,0)
+ {
+ GtkRequisition req;
+ /*
+ * Since none of my list items wraps in this GUI, no
+ * interesting width-for-height behaviour should be happening,
+ * so I don't think it should matter here whether I ask for
+ * the minimum or natural height.
+ */
+ gtk_cell_renderer_get_preferred_size(cr, w, &req, NULL);
+ height = req.height;
+ }
+#else
+ gtk_cell_renderer_get_size(cr, w, NULL, NULL, NULL, NULL, &height);
+#endif
+ g_object_ref(G_OBJECT(cr));
+ g_object_ref_sink(G_OBJECT(cr));
+ g_object_unref(G_OBJECT(cr));
+ return height;
+#endif
+}
+
+#if GTK_CHECK_VERSION(2,0,0)
+void initial_treeview_collapse(struct dlgparam *dp, GtkWidget *tree)
+{
+ /*
+ * Collapse the deeper branches of the treeview into the state we
+ * like them to start off in. See comment below in do_config_box.
+ */
+ int i;
+ for (i = 0; i < dp->nselparams; i++)
+ if (dp->selparams[i]->depth >= 2)
+ gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree),
+ dp->selparams[i]->treepath);
+}
+#endif
+
+#if GTK_CHECK_VERSION(3,0,0)
+void treeview_map_event(GtkWidget *tree, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ GtkAllocation alloc;
+ gtk_widget_get_allocation(tree, &alloc);
+ gtk_widget_set_size_request(tree, alloc.width, -1);
+ initial_treeview_collapse(dp, tree);
+}
+#endif
+
+GtkWidget *create_config_box(const char *title, Conf *conf,
+ bool midsession, int protcfginfo,
+ post_dialog_fn_t after, void *afterctx)
+{
+ GtkWidget *window, *hbox, *vbox, *cols, *label,
+ *tree, *treescroll, *panels, *panelvbox;
+ int index, level, protocol;
+ char *path;
+#if GTK_CHECK_VERSION(2,0,0)
+ GtkTreeStore *treestore;
+ GtkCellRenderer *treerenderer;
+ GtkTreeViewColumn *treecolumn;
+ GtkTreeSelection *treeselection;
+ GtkTreeIter treeiterlevels[8];
+#else
+ GtkTreeItem *treeitemlevels[8];
+ GtkTree *treelevels[8];
+#endif
+ struct dlgparam *dp;
+ struct Shortcuts scs;
+
+ struct selparam **selparams = NULL;
+ size_t nselparams = 0, selparamsize = 0;
+
+ dp = snew(struct dlgparam);
+ dp->after = after;
+ dp->afterctx = afterctx;
+
+ dlg_init(dp);
+
+ for (index = 0; index < lenof(scs.sc); index++) {
+ scs.sc[index].action = SHORTCUT_EMPTY;
+ }
+
+ window = our_dialog_new();
+
+ dp->ctrlbox = ctrl_new_box();
+ protocol = conf_get_int(conf, CONF_protocol);
+ setup_config_box(dp->ctrlbox, midsession, protocol, protcfginfo);
+ unix_setup_config_box(dp->ctrlbox, midsession, protocol);
+ gtk_setup_config_box(dp->ctrlbox, midsession, window);
+
+ gtk_window_set_title(GTK_WINDOW(window), title);
+ hbox = gtk_hbox_new(false, 4);
+ our_dialog_add_to_content_area(GTK_WINDOW(window), hbox, true, true, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(hbox), 10);
+ gtk_widget_show(hbox);
+ vbox = gtk_vbox_new(false, 4);
+ gtk_box_pack_start(GTK_BOX(hbox), vbox, false, false, 0);
+ gtk_widget_show(vbox);
+ cols = columns_new(4);
+ gtk_box_pack_start(GTK_BOX(vbox), cols, false, false, 0);
+ gtk_widget_show(cols);
+ label = gtk_label_new("Category:");
+ columns_add(COLUMNS(cols), label, 0, 1);
+ columns_force_left_align(COLUMNS(cols), label);
+ gtk_widget_show(label);
+ treescroll = gtk_scrolled_window_new(NULL, NULL);
+#if GTK_CHECK_VERSION(2,0,0)
+ treestore = gtk_tree_store_new(
+ TREESTORE_NUM, G_TYPE_STRING, G_TYPE_INT);
+ tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(treestore));
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), false);
+ treerenderer = gtk_cell_renderer_text_new();
+ treecolumn = gtk_tree_view_column_new_with_attributes(
+ "Label", treerenderer, "text", 0, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tree), treecolumn);
+ treeselection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
+ gtk_tree_selection_set_mode(treeselection, GTK_SELECTION_BROWSE);
+ gtk_container_add(GTK_CONTAINER(treescroll), tree);
+#else
+ tree = gtk_tree_new();
+ gtk_tree_set_view_mode(GTK_TREE(tree), GTK_TREE_VIEW_ITEM);
+ gtk_tree_set_selection_mode(GTK_TREE(tree), GTK_SELECTION_BROWSE);
+ g_signal_connect(G_OBJECT(tree), "focus", G_CALLBACK(tree_focus), dp);
+#endif
+ g_signal_connect(G_OBJECT(tree), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
+ shortcut_add(&scs, label, 'g', SHORTCUT_TREE, tree);
+ gtk_widget_show(treescroll);
+ gtk_box_pack_start(GTK_BOX(vbox), treescroll, true, true, 0);
+ panels = gtk_notebook_new();
+ gtk_notebook_set_show_tabs(GTK_NOTEBOOK(panels), false);
+ gtk_notebook_set_show_border(GTK_NOTEBOOK(panels), false);
+ gtk_box_pack_start(GTK_BOX(hbox), panels, true, true, 0);
+ gtk_widget_show(panels);
+
+ panelvbox = NULL;
+ path = NULL;
+ level = 0;
+ for (index = 0; index < dp->ctrlbox->nctrlsets; index++) {
+ struct controlset *s = dp->ctrlbox->ctrlsets[index];
+ GtkWidget *w;
+
+ if (!*s->pathname) {
+ w = layout_ctrls(dp, NULL, &scs, s, GTK_WINDOW(window));
+
+ our_dialog_set_action_area(GTK_WINDOW(window), w);
+ } else {
+ int j = path ? ctrl_path_compare(s->pathname, path) : 0;
+ if (j != INT_MAX) { /* add to treeview, start new panel */
+ char *c;
+#if GTK_CHECK_VERSION(2,0,0)
+ GtkTreeIter treeiter;
+#else
+ GtkWidget *treeitem;
+#endif
+ bool first;
+
+ /*
+ * We expect never to find an implicit path
+ * component. For example, we expect never to see
+ * A/B/C followed by A/D/E, because that would
+ * _implicitly_ create A/D. All our path prefixes
+ * are expected to contain actual controls and be
+ * selectable in the treeview; so we would expect
+ * to see A/D _explicitly_ before encountering
+ * A/D/E.
+ */
+ assert(j == ctrl_path_elements(s->pathname) - 1);
+
+ c = strrchr(s->pathname, '/');
+ if (!c)
+ c = s->pathname;
+ else
+ c++;
+
+ path = s->pathname;
+
+ first = (panelvbox == NULL);
+
+ panelvbox = gtk_vbox_new(false, 4);
+ gtk_widget_show(panelvbox);
+ gtk_notebook_append_page(GTK_NOTEBOOK(panels), panelvbox,
+ NULL);
+
+ struct selparam *sp = snew(struct selparam);
+
+ if (first) {
+ gint page_num;
+
+ page_num = gtk_notebook_page_num(GTK_NOTEBOOK(panels),
+ panelvbox);
+ gtk_notebook_set_current_page(GTK_NOTEBOOK(panels),
+ page_num);
+
+ dp->curr_panel = sp;
+ }
+
+ sgrowarray(selparams, selparamsize, nselparams);
+ selparams[nselparams] = sp;
+ sp->dp = dp;
+ sp->panels = GTK_NOTEBOOK(panels);
+ sp->panel = panelvbox;
+ sp->shortcuts = scs; /* structure copy */
+
+ assert(j-1 < level);
+
+#if GTK_CHECK_VERSION(2,0,0)
+ if (j > 0)
+ /* treeiterlevels[j-1] will always be valid because we
+ * don't allow implicit path components; see above.
+ */
+ gtk_tree_store_append(treestore, &treeiter,
+ &treeiterlevels[j-1]);
+ else
+ gtk_tree_store_append(treestore, &treeiter, NULL);
+ gtk_tree_store_set(treestore, &treeiter,
+ TREESTORE_PATH, c,
+ TREESTORE_PARAMS, nselparams,
+ -1);
+ treeiterlevels[j] = treeiter;
+
+ sp->depth = j;
+ if (j > 0) {
+ sp->treepath = gtk_tree_model_get_path(
+ GTK_TREE_MODEL(treestore), &treeiterlevels[j-1]);
+ /*
+ * We are going to collapse all tree branches
+ * at depth greater than 2, but not _yet_; see
+ * the comment at the call to
+ * gtk_tree_view_collapse_row below.
+ */
+ gtk_tree_view_expand_row(GTK_TREE_VIEW(tree),
+ sp->treepath, false);
+ } else {
+ sp->treepath = NULL;
+ }
+#else
+ treeitem = gtk_tree_item_new_with_label(c);
+ if (j > 0) {
+ if (!treelevels[j-1]) {
+ treelevels[j-1] = GTK_TREE(gtk_tree_new());
+ gtk_tree_item_set_subtree(
+ treeitemlevels[j-1], GTK_WIDGET(treelevels[j-1]));
+ if (j < 2)
+ gtk_tree_item_expand(treeitemlevels[j-1]);
+ else
+ gtk_tree_item_collapse(treeitemlevels[j-1]);
+ }
+ gtk_tree_append(treelevels[j-1], treeitem);
+ } else {
+ gtk_tree_append(GTK_TREE(tree), treeitem);
+ }
+ treeitemlevels[j] = GTK_TREE_ITEM(treeitem);
+ treelevels[j] = NULL;
+
+ g_signal_connect(G_OBJECT(treeitem), "key_press_event",
+ G_CALLBACK(tree_key_press), dp);
+ g_signal_connect(G_OBJECT(treeitem), "focus_in_event",
+ G_CALLBACK(widget_focus), dp);
+
+ gtk_widget_show(treeitem);
+
+ if (first)
+ gtk_tree_select_child(GTK_TREE(tree), treeitem);
+ sp->treeitem = treeitem;
+#endif
+
+ level = j+1;
+ nselparams++;
+ }
+
+ w = layout_ctrls(dp, selparams[nselparams-1],
+ &selparams[nselparams-1]->shortcuts, s, NULL);
+ gtk_box_pack_start(GTK_BOX(panelvbox), w, false, false, 0);
+ gtk_widget_show(w);
+ }
+ }
+
+#if GTK_CHECK_VERSION(2,0,0)
+ /*
+ * We want our tree view to come up with all branches at depth 2
+ * or more collapsed. However, if we start off with those branches
+ * collapsed, then the tree view's size request will be calculated
+ * based on the width of the collapsed tree, and then when the
+ * collapsed branches are expanded later, the tree view will
+ * jarringly change size.
+ *
+ * So instead we start with everything expanded; then, once the
+ * tree view has computed its resulting width requirement, we
+ * collapse the relevant rows, but force the width to be the value
+ * we just retrieved. This arranges that the tree view is wide
+ * enough to have all branches expanded without further resizing.
+ */
+
+ dp->nselparams = nselparams;
+ dp->selparams = selparams;
+
+#if !GTK_CHECK_VERSION(3,0,0)
+ {
+ /*
+ * In GTK2, we can just do the job right now.
+ */
+ GtkRequisition req;
+ gtk_widget_size_request(tree, &req);
+ initial_treeview_collapse(dp, tree);
+ gtk_widget_set_size_request(tree, req.width, -1);
+ }
+#else
+ /*
+ * But in GTK3, we have to wait until the widget is about to be
+ * mapped, because the size computation won't have been done yet.
+ */
+ g_signal_connect(G_OBJECT(tree), "map",
+ G_CALLBACK(treeview_map_event), dp);
+#endif /* GTK 2 vs 3 */
+#endif /* GTK 2+ vs 1 */
+
+#if GTK_CHECK_VERSION(2,0,0)
+ g_signal_connect(G_OBJECT(treeselection), "changed",
+ G_CALLBACK(treeselection_changed), selparams);
+#else
+ dp->ntreeitems = nselparams;
+ dp->treeitems = snewn(dp->ntreeitems, GtkWidget *);
+ for (index = 0; index < nselparams; index++) {
+ g_signal_connect(G_OBJECT(selparams[index]->treeitem), "select",
+ G_CALLBACK(treeitem_sel),
+ selparams[index]);
+ dp->treeitems[index] = selparams[index]->treeitem;
+ }
+#endif
+
+ dp->data = conf;
+ dlg_refresh(NULL, dp);
+
+ dp->shortcuts = &selparams[0]->shortcuts;
+#if !GTK_CHECK_VERSION(2,0,0)
+ dp->currtreeitem = dp->treeitems[0];
+#endif
+ dp->lastfocus = NULL;
+ dp->retval = -1;
+ dp->window = window;
+
+ set_window_icon(window, cfg_icon, n_cfg_icon);
+
+#if !GTK_CHECK_VERSION(2,0,0)
+ gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(treescroll),
+ tree);
+#endif
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(treescroll),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_show(tree);
+
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
+ gtk_widget_show(window);
+
+ /*
+ * Set focus into the first available control.
+ */
+ for (index = 0; index < dp->ctrlbox->nctrlsets; index++) {
+ struct controlset *s = dp->ctrlbox->ctrlsets[index];
+ bool done = false;
+ int j;
+
+ if (*s->pathname) {
+ for (j = 0; j < s->ncontrols; j++)
+ if (s->ctrls[j]->type != CTRL_TABDELAY &&
+ s->ctrls[j]->type != CTRL_COLUMNS &&
+ s->ctrls[j]->type != CTRL_TEXT) {
+ dlg_set_focus(s->ctrls[j], dp);
+ dp->lastfocus = s->ctrls[j];
+ done = true;
+ break;
+ }
+ }
+ if (done)
+ break;
+ }
+
+ g_signal_connect(G_OBJECT(window), "destroy",
+ G_CALLBACK(dlgparam_destroy), dp);
+ g_signal_connect(G_OBJECT(window), "key_press_event",
+ G_CALLBACK(win_key_press), dp);
+
+ return window;
+}
+
+static void dlgparam_destroy(GtkWidget *widget, gpointer data)
+{
+ struct dlgparam *dp = (struct dlgparam *)data;
+ dp->after(dp->afterctx, dp->retval);
+ dlg_cleanup(dp);
+ ctrl_free_box(dp->ctrlbox);
+#if GTK_CHECK_VERSION(2,0,0)
+ if (dp->selparams) {
+ for (size_t i = 0; i < dp->nselparams; i++) {
+ if (dp->selparams[i]->treepath)
+ gtk_tree_path_free(dp->selparams[i]->treepath);
+ sfree(dp->selparams[i]);
+ }
+ sfree(dp->selparams);
+ }
+#endif
+ sfree(dp);
+}
+
+static void messagebox_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ if (event == EVENT_ACTION)
+ dlg_end(dp, ctrl->context.i);
+}
+
+static const struct message_box_button button_array_yn[] = {
+ {"Yes", 'y', +1, 1},
+ {"No", 'n', -1, 0},
+};
+const struct message_box_buttons buttons_yn = {
+ button_array_yn, lenof(button_array_yn),
+};
+static const struct message_box_button button_array_ok[] = {
+ {"OK", 'o', 1, 1},
+};
+const struct message_box_buttons buttons_ok = {
+ button_array_ok, lenof(button_array_ok),
+};
+
+static GtkWidget *create_message_box_general(
+ GtkWidget *parentwin, const char *title, const char *msg, int minwid,
+ bool selectable, const struct message_box_buttons *buttons,
+ post_dialog_fn_t after, void *afterctx,
+ GtkWidget *(*action_postproc)(GtkWidget *, void *), void *postproc_ctx)
+{
+ GtkWidget *window, *w0, *w1;
+ struct controlset *s0, *s1;
+ dlgcontrol *c, *textctrl;
+ struct dlgparam *dp;
+ struct Shortcuts scs;
+ int i, index, ncols, min_type;
+
+ dp = snew(struct dlgparam);
+ dp->after = after;
+ dp->afterctx = afterctx;
+
+ dlg_init(dp);
+
+ for (index = 0; index < lenof(scs.sc); index++) {
+ scs.sc[index].action = SHORTCUT_EMPTY;
+ }
+
+ dp->ctrlbox = ctrl_new_box();
+
+ /*
+ * Count up the number of buttons and find out what kinds there
+ * are.
+ */
+ ncols = 0;
+ min_type = +1;
+ for (i = 0; i < buttons->nbuttons; i++) {
+ const struct message_box_button *button = &buttons->buttons[i];
+ ncols++;
+ if (min_type > button->type)
+ min_type = button->type;
+ assert(button->value >= 0); /* <0 means no return value available */
+ }
+
+ s0 = ctrl_getset(dp->ctrlbox, "", "", "");
+ c = ctrl_columns(s0, 2, 50, 50);
+ c->columns.ncols = s0->ncolumns = ncols;
+ c->columns.percentages = sresize(c->columns.percentages, ncols, int);
+ for (index = 0; index < ncols; index++)
+ c->columns.percentages[index] = (index+1)*100/ncols - index*100/ncols;
+ index = 0;
+ for (i = 0; i < buttons->nbuttons; i++) {
+ const struct message_box_button *button = &buttons->buttons[i];
+ c = ctrl_pushbutton(s0, button->title, button->shortcut,
+ HELPCTX(no_help), messagebox_handler,
+ I(button->value));
+ c->column = index++;
+ if (button->type > 0)
+ c->button.isdefault = true;
+
+ /* We always arrange that _some_ button is labelled as
+ * 'iscancel', so that pressing Escape will always cause
+ * win_key_press to do something. The button we choose is
+ * whichever has the smallest type value: this means that real
+ * cancel buttons (labelled -1) will be picked if one is
+ * there, or in cases where the options are yes/no (1,0) then
+ * no will be picked, and if there's only one option (a box
+ * that really is just showing a _message_ and not even asking
+ * a question) then that will be picked. */
+ if (button->type == min_type)
+ c->button.iscancel = true;
+ }
+
+ s1 = ctrl_getset(dp->ctrlbox, "x", "", "");
+ textctrl = ctrl_text(s1, msg, HELPCTX(no_help));
+
+ window = our_dialog_new();
+ gtk_window_set_title(GTK_WINDOW(window), title);
+ w0 = layout_ctrls(dp, NULL, &scs, s0, GTK_WINDOW(window));
+ if (action_postproc)
+ w0 = action_postproc(w0, postproc_ctx);
+ our_dialog_set_action_area(GTK_WINDOW(window), w0);
+ gtk_widget_show(w0);
+ w1 = layout_ctrls(dp, NULL, &scs, s1, GTK_WINDOW(window));
+ gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
+ gtk_widget_set_size_request(w1, minwid+20, -1);
+ our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0);
+ gtk_widget_show(w1);
+
+ dp->shortcuts = &scs;
+ dp->lastfocus = NULL;
+ dp->retval = 0;
+ dp->window = window;
+
+ if (selectable) {
+#if GTK_CHECK_VERSION(2,0,0)
+ struct uctrl *uc = dlg_find_byctrl(dp, textctrl);
+ gtk_label_set_selectable(GTK_LABEL(uc->text), true);
+
+ /*
+ * GTK selectable labels have a habit of selecting their
+ * entire contents when they gain focus. It's ugly to have
+ * text in a message box start up all selected, so we suppress
+ * this by manually selecting none of it - but we must do this
+ * when the widget _already has_ focus, otherwise our work
+ * will be undone when it gains it shortly.
+ */
+ gtk_widget_grab_focus(uc->text);
+ gtk_label_select_region(GTK_LABEL(uc->text), 0, 0);
+#else
+ (void)textctrl; /* placate warning */
+#endif
+ }
+
+ if (parentwin) {
+ set_transient_window_pos(parentwin, window);
+ gtk_window_set_transient_for(GTK_WINDOW(window),
+ GTK_WINDOW(parentwin));
+ } else
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
+ gtk_container_set_focus_child(GTK_CONTAINER(window), NULL);
+ gtk_widget_show(window);
+ gtk_window_set_focus(GTK_WINDOW(window), NULL);
+
+#if !GTK_CHECK_VERSION(2,0,0)
+ dp->currtreeitem = NULL;
+ dp->treeitems = NULL;
+#else
+ dp->selparams = NULL;
+#endif
+
+ g_signal_connect(G_OBJECT(window), "destroy",
+ G_CALLBACK(dlgparam_destroy), dp);
+ g_signal_connect(G_OBJECT(window), "key_press_event",
+ G_CALLBACK(win_key_press), dp);
+
+ return window;
+}
+
+GtkWidget *create_message_box(
+ GtkWidget *parentwin, const char *title, const char *msg, int minwid,
+ bool selectable, const struct message_box_buttons *buttons,
+ post_dialog_fn_t after, void *afterctx)
+{
+ return create_message_box_general(
+ parentwin, title, msg, minwid, selectable, buttons, after, afterctx,
+ NULL /* action_postproc */, NULL /* postproc_ctx */);
+}
+
+struct confirm_ssh_host_key_dialog_ctx {
+ char *host;
+ int port;
+ char *keytype;
+ char *keystr;
+ char *more_info;
+ void (*callback)(void *callback_ctx, SeatPromptResult result);
+ void *callback_ctx;
+ Seat *seat;
+
+ GtkWidget *main_dialog;
+ GtkWidget *more_info_dialog;
+};
+
+static void confirm_ssh_host_key_result_callback(void *vctx, int result)
+{
+ struct confirm_ssh_host_key_dialog_ctx *ctx =
+ (struct confirm_ssh_host_key_dialog_ctx *)vctx;
+
+ if (result >= 0) {
+ SeatPromptResult logical_result;
+
+ /*
+ * Convert the dialog-box return value (one of three
+ * possibilities) into the return value we pass back to the SSH
+ * code (one of only two possibilities, because the SSH code
+ * doesn't care whether we saved the host key or not).
+ */
+ if (result == 2) {
+ store_host_key(ctx->host, ctx->port, ctx->keytype, ctx->keystr);
+ logical_result = SPR_OK;
+ } else if (result == 1) {
+ logical_result = SPR_OK;
+ } else {
+ logical_result = SPR_USER_ABORT;
+ }
+
+ ctx->callback(ctx->callback_ctx, logical_result);
+ }
+
+ /*
+ * Clean up this context structure, whether or not a result was
+ * ever actually delivered from the dialog box.
+ */
+ unregister_dialog(ctx->seat, DIALOG_SLOT_NETWORK_PROMPT);
+
+ if (ctx->more_info_dialog)
+ gtk_widget_destroy(ctx->more_info_dialog);
+
+ sfree(ctx->host);
+ sfree(ctx->keytype);
+ sfree(ctx->keystr);
+ sfree(ctx->more_info);
+ sfree(ctx);
+}
+
+static GtkWidget *add_more_info_button(GtkWidget *w, void *vctx)
+{
+ GtkWidget *box = gtk_hbox_new(false, 10);
+ gtk_widget_show(box);
+ gtk_box_pack_end(GTK_BOX(box), w, false, true, 0);
+ GtkWidget *button = gtk_button_new_with_label("More info...");
+ gtk_widget_show(button);
+ gtk_box_pack_start(GTK_BOX(box), button, false, true, 0);
+ *(GtkWidget **)vctx = button;
+ return box;
+}
+
+static void more_info_closed(void *vctx, int result)
+{
+ struct confirm_ssh_host_key_dialog_ctx *ctx =
+ (struct confirm_ssh_host_key_dialog_ctx *)vctx;
+
+ ctx->more_info_dialog = NULL;
+}
+
+static void more_info_button_clicked(GtkButton *button, gpointer vctx)
+{
+ struct confirm_ssh_host_key_dialog_ctx *ctx =
+ (struct confirm_ssh_host_key_dialog_ctx *)vctx;
+
+ if (ctx->more_info_dialog)
+ return;
+
+ ctx->more_info_dialog = create_message_box(
+ ctx->main_dialog, "Host key information", ctx->more_info,
+ string_width("SHA256 fingerprint: ecdsa-sha2-nistp521 521 "
+ "abcdefghkmnopqrsuvwxyzABCDEFGHJKLMNOPQRSTUW"), true,
+ &buttons_ok, more_info_closed, ctx);
+}
+
+const SeatDialogPromptDescriptions *gtk_seat_prompt_descriptions(Seat *seat)
+{
+ static const SeatDialogPromptDescriptions descs = {
+ .hk_accept_action = "press \"Accept\"",
+ .hk_connect_once_action = "press \"Connect Once\"",
+ .hk_cancel_action = "press \"Cancel\"",
+ .hk_cancel_action_Participle = "Pressing \"Cancel\"",
+ };
+ return &descs;
+}
+
+SeatPromptResult gtk_seat_confirm_ssh_host_key(
+ Seat *seat, const char *host, int port, const char *keytype,
+ char *keystr, SeatDialogText *text, HelpCtx helpctx,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{
+ static const struct message_box_button button_array_hostkey[] = {
+ {"Accept", 'a', 0, 2},
+ {"Connect Once", 'o', 0, 1},
+ {"Cancel", 'c', -1, 0},
+ };
+ static const struct message_box_buttons buttons_hostkey = {
+ button_array_hostkey, lenof(button_array_hostkey),
+ };
+
+ const char *dlg_title = NULL;
+ strbuf *dlg_text = strbuf_new();
+ int width = string_width("default dialog width determination string");
+
+ for (SeatDialogTextItem *item = text->items,
+ *end = item + text->nitems; item < end; item++) {
+ switch (item->type) {
+ case SDT_PARA:
+ put_fmt(dlg_text, "%s\n\n", item->text);
+ break;
+ case SDT_DISPLAY: {
+ put_fmt(dlg_text, "%s\n\n", item->text);
+ int thiswidth = string_width(item->text);
+ if (width < thiswidth)
+ width = thiswidth;
+ break;
+ }
+ case SDT_SCARY_HEADING:
+ /* Can't change font size or weight in this context */
+ put_fmt(dlg_text, "%s\n\n", item->text);
+ break;
+ case SDT_TITLE:
+ dlg_title = item->text;
+ break;
+ default:
+ break;
+ }
+ }
+ while (strbuf_chomp(dlg_text, '\n'));
+
+ GtkWidget *mainwin, *msgbox;
+
+ struct confirm_ssh_host_key_dialog_ctx *result_ctx =
+ snew(struct confirm_ssh_host_key_dialog_ctx);
+ result_ctx->callback = callback;
+ result_ctx->callback_ctx = ctx;
+ result_ctx->host = dupstr(host);
+ result_ctx->port = port;
+ result_ctx->keytype = dupstr(keytype);
+ result_ctx->keystr = dupstr(keystr);
+ result_ctx->seat = seat;
+
+ mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
+ GtkWidget *more_info_button = NULL;
+ msgbox = create_message_box_general(
+ mainwin, dlg_title, dlg_text->s, width, true,
+ &buttons_hostkey, confirm_ssh_host_key_result_callback, result_ctx,
+ add_more_info_button, &more_info_button);
+
+ result_ctx->main_dialog = msgbox;
+ result_ctx->more_info_dialog = NULL;
+
+ strbuf *moreinfo = strbuf_new();
+ for (SeatDialogTextItem *item = text->items,
+ *end = item + text->nitems; item < end; item++) {
+ switch (item->type) {
+ case SDT_MORE_INFO_KEY:
+ put_fmt(moreinfo, "%s", item->text);
+ break;
+ case SDT_MORE_INFO_VALUE_SHORT:
+ put_fmt(moreinfo, ": %s\n", item->text);
+ break;
+ case SDT_MORE_INFO_VALUE_BLOB:
+ /* We have to manually wrap the public key, or else the GtkLabel
+ * will resize itself to accommodate the longest word, which will
+ * lead to a hilariously wide message box. */
+ put_byte(moreinfo, ':');
+ for (const char *p = item->text, *q = p + strlen(p); p < q ;) {
+ size_t linelen = q-p;
+ if (linelen > 72)
+ linelen = 72;
+ put_byte(moreinfo, '\n');
+ put_data(moreinfo, p, linelen);
+ p += linelen;
+ }
+ put_byte(moreinfo, '\n');
+ break;
+ default:
+ break;
+ }
+ }
+ result_ctx->more_info = strbuf_to_str(moreinfo);
+
+ g_signal_connect(G_OBJECT(more_info_button), "clicked",
+ G_CALLBACK(more_info_button_clicked), result_ctx);
+
+ register_dialog(seat, DIALOG_SLOT_NETWORK_PROMPT, msgbox);
+
+ strbuf_free(dlg_text);
+
+ return SPR_INCOMPLETE; /* dialog still in progress */
+}
+
+struct simple_prompt_result_spr_ctx {
+ void (*callback)(void *callback_ctx, SeatPromptResult spr);
+ void *callback_ctx;
+ Seat *seat;
+ enum DialogSlot dialog_slot;
+};
+
+static void simple_prompt_result_spr_callback(void *vctx, int result)
+{
+ struct simple_prompt_result_spr_ctx *ctx =
+ (struct simple_prompt_result_spr_ctx *)vctx;
+
+ unregister_dialog(ctx->seat, ctx->dialog_slot);
+
+ if (result == 0)
+ ctx->callback(ctx->callback_ctx, SPR_USER_ABORT);
+ else if (result > 0)
+ ctx->callback(ctx->callback_ctx, SPR_OK);
+ /* if <0, we're cleaning up for some other reason */
+
+ /*
+ * Clean up this context structure, whether or not a result was
+ * ever actually delivered from the dialog box.
+ */
+ sfree(ctx);
+}
+
+/*
+ * Ask whether the selected algorithm is acceptable (since it was
+ * below the configured 'warn' threshold).
+ */
+SeatPromptResult gtk_seat_confirm_weak_crypto_primitive(
+ Seat *seat, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{
+ static const char msg[] =
+ "The first %s supported by the server is "
+ "%s, which is below the configured warning threshold.\n"
+ "Continue with connection?";
+
+ char *text;
+ struct simple_prompt_result_spr_ctx *result_ctx;
+ GtkWidget *mainwin, *msgbox;
+
+ text = dupprintf(msg, algtype, algname);
+
+ result_ctx = snew(struct simple_prompt_result_spr_ctx);
+ result_ctx->callback = callback;
+ result_ctx->callback_ctx = ctx;
+ result_ctx->seat = seat;
+ result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT;
+
+ mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
+ msgbox = create_message_box(
+ mainwin, "PuTTY Security Alert", text,
+ string_width("Reasonably long line of text as a width template"),
+ false, &buttons_yn, simple_prompt_result_spr_callback, result_ctx);
+ register_dialog(seat, result_ctx->dialog_slot, msgbox);
+
+ sfree(text);
+
+ return SPR_INCOMPLETE;
+}
+
+SeatPromptResult gtk_seat_confirm_weak_cached_hostkey(
+ Seat *seat, const char *algname, const char *betteralgs,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{
+ 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"
+ "Continue with connection?";
+
+ char *text;
+ struct simple_prompt_result_spr_ctx *result_ctx;
+ GtkWidget *mainwin, *msgbox;
+
+ text = dupprintf(msg, algname, betteralgs);
+
+ result_ctx = snew(struct simple_prompt_result_spr_ctx);
+ result_ctx->callback = callback;
+ result_ctx->callback_ctx = ctx;
+ result_ctx->seat = seat;
+ result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT;
+
+ mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
+ msgbox = create_message_box(
+ mainwin, "PuTTY Security Alert", text,
+ string_width("is ecdsa-nistp521, which is below the configured"
+ " warning threshold."),
+ false, &buttons_yn, simple_prompt_result_spr_callback, result_ctx);
+ register_dialog(seat, result_ctx->dialog_slot, msgbox);
+
+ sfree(text);
+
+ return SPR_INCOMPLETE;
+}
+
+void old_keyfile_warning(void)
+{
+ /*
+ * This should never happen on Unix. We hope.
+ */
+}
+
+void nonfatal_message_box(void *window, const char *msg)
+{
+ char *title = dupcat(appname, " Error");
+ create_message_box(
+ window, title, msg,
+ string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"),
+ false, &buttons_ok, trivial_post_dialog_fn, NULL);
+ sfree(title);
+}
+
+void nonfatal(const char *p, ...)
+{
+ va_list ap;
+ char *msg;
+ va_start(ap, p);
+ msg = dupvprintf(p, ap);
+ va_end(ap);
+ nonfatal_message_box(NULL, msg);
+ sfree(msg);
+}
+
+static GtkWidget *aboutbox = NULL;
+
+static void about_window_destroyed(GtkWidget *widget, gpointer data)
+{
+ aboutbox = NULL;
+}
+
+static void about_close_clicked(GtkButton *button, gpointer data)
+{
+ gtk_widget_destroy(aboutbox);
+ aboutbox = NULL;
+}
+
+static void about_key_press(GtkWidget *widget, GdkEventKey *event,
+ gpointer data)
+{
+ if (event->keyval == GDK_KEY_Escape && aboutbox) {
+ gtk_widget_destroy(aboutbox);
+ aboutbox = NULL;
+ }
+}
+
+static void licence_clicked(GtkButton *button, gpointer data)
+{
+ char *title;
+
+ title = dupcat(appname, " Licence");
+ assert(aboutbox != NULL);
+ create_message_box(aboutbox, title, LICENCE_TEXT("\n\n"),
+ string_width("LONGISH LINE OF TEXT SO THE LICENCE"
+ " BOX ISN'T EXCESSIVELY TALL AND THIN"),
+ true, &buttons_ok, trivial_post_dialog_fn, NULL);
+ sfree(title);
+}
+
+void about_box(void *window)
+{
+ GtkWidget *w;
+ GtkBox *action_area;
+ char *title;
+
+ if (aboutbox) {
+ gtk_widget_grab_focus(aboutbox);
+ return;
+ }
+
+ aboutbox = our_dialog_new();
+ gtk_container_set_border_width(GTK_CONTAINER(aboutbox), 10);
+ title = dupcat("About ", appname);
+ gtk_window_set_title(GTK_WINDOW(aboutbox), title);
+ sfree(title);
+
+ g_signal_connect(G_OBJECT(aboutbox), "destroy",
+ G_CALLBACK(about_window_destroyed), NULL);
+
+ w = gtk_button_new_with_label("Close");
+ gtk_widget_set_can_default(w, true);
+ gtk_window_set_default(GTK_WINDOW(aboutbox), w);
+ action_area = our_dialog_make_action_hbox(GTK_WINDOW(aboutbox));
+ gtk_box_pack_end(action_area, w, false, false, 0);
+ g_signal_connect(G_OBJECT(w), "clicked",
+ G_CALLBACK(about_close_clicked), NULL);
+ gtk_widget_show(w);
+
+ w = gtk_button_new_with_label("View Licence");
+ gtk_widget_set_can_default(w, true);
+ gtk_box_pack_end(action_area, w, false, false, 0);
+ g_signal_connect(G_OBJECT(w), "clicked",
+ G_CALLBACK(licence_clicked), NULL);
+ gtk_widget_show(w);
+
+ {
+ char *buildinfo_text = buildinfo("\n");
+ char *label_text = dupprintf(
+ "%s\n\n%s\n\n%s\n\n%s",
+ appname, ver, buildinfo_text,
+ "Copyright " SHORT_COPYRIGHT_DETAILS ". All rights reserved");
+ w = gtk_label_new(label_text);
+ gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_CENTER);
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_label_set_selectable(GTK_LABEL(w), true);
+#endif
+ sfree(label_text);
+ }
+ our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, false, false, 0);
+#if GTK_CHECK_VERSION(2,0,0)
+ /*
+ * Same precautions against initial select-all as in
+ * create_message_box().
+ */
+ gtk_widget_grab_focus(w);
+ gtk_label_select_region(GTK_LABEL(w), 0, 0);
+#endif
+ gtk_widget_show(w);
+
+ g_signal_connect(G_OBJECT(aboutbox), "key_press_event",
+ G_CALLBACK(about_key_press), NULL);
+
+ set_transient_window_pos(GTK_WIDGET(window), aboutbox);
+ if (window)
+ gtk_window_set_transient_for(GTK_WINDOW(aboutbox),
+ GTK_WINDOW(window));
+ gtk_container_set_focus_child(GTK_CONTAINER(aboutbox), NULL);
+ gtk_widget_show(aboutbox);
+ gtk_window_set_focus(GTK_WINDOW(aboutbox), NULL);
+}
+
+#define LOGEVENT_INITIAL_MAX 128
+#define LOGEVENT_CIRCULAR_MAX 128
+
+struct eventlog_stuff {
+ GtkWidget *parentwin, *window;
+ struct controlbox *eventbox;
+ struct Shortcuts scs;
+ struct dlgparam dp;
+ dlgcontrol *listctrl;
+ char **events_initial;
+ char **events_circular;
+ int ninitial, ncircular, circular_first;
+ strbuf *seldata;
+ int sellen;
+ bool ignore_selchange;
+};
+
+static void eventlog_destroy(GtkWidget *widget, gpointer data)
+{
+ eventlog_stuff *es = (eventlog_stuff *)data;
+
+ es->window = NULL;
+ dlg_cleanup(&es->dp);
+ ctrl_free_box(es->eventbox);
+}
+static void eventlog_ok_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ if (event == EVENT_ACTION)
+ dlg_end(dp, 0);
+}
+static void eventlog_list_handler(dlgcontrol *ctrl, dlgparam *dp,
+ void *data, int event)
+{
+ eventlog_stuff *es = (eventlog_stuff *)data;
+
+ if (event == EVENT_REFRESH) {
+ int i;
+
+ dlg_update_start(ctrl, dp);
+ dlg_listbox_clear(ctrl, dp);
+ for (i = 0; i < es->ninitial; i++) {
+ dlg_listbox_add(ctrl, dp, es->events_initial[i]);
+ }
+ for (i = 0; i < es->ncircular; i++) {
+ dlg_listbox_add(ctrl, dp, es->events_circular[(es->circular_first + i) % LOGEVENT_CIRCULAR_MAX]);
+ }
+ dlg_update_done(ctrl, dp);
+ } else if (event == EVENT_SELCHANGE) {
+ int i;
+
+ /*
+ * If this SELCHANGE event is happening as a result of
+ * deliberate deselection because someone else has grabbed
+ * the selection, the last thing we want to do is pre-empt
+ * them.
+ */
+ if (es->ignore_selchange)
+ return;
+
+ /*
+ * Construct the data to use as the selection.
+ */
+ strbuf_clear(es->seldata);
+ for (i = 0; i < es->ninitial; i++) {
+ if (dlg_listbox_issel(ctrl, dp, i))
+ put_fmt(es->seldata, "%s\n", es->events_initial[i]);
+ }
+ for (i = 0; i < es->ncircular; i++) {
+ if (dlg_listbox_issel(ctrl, dp, es->ninitial + i)) {
+ int j = (es->circular_first + i) % LOGEVENT_CIRCULAR_MAX;
+ put_fmt(es->seldata, "%s\n", es->events_circular[j]);
+ }
+ }
+
+ if (gtk_selection_owner_set(es->window, GDK_SELECTION_PRIMARY,
+ GDK_CURRENT_TIME)) {
+ gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY,
+ GDK_SELECTION_TYPE_STRING, 1);
+ gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY,
+ compound_text_atom, 1);
+ }
+
+ }
+}
+
+void eventlog_selection_get(GtkWidget *widget, GtkSelectionData *seldata,
+ guint info, guint time_stamp, gpointer data)
+{
+ eventlog_stuff *es = (eventlog_stuff *)data;
+
+ gtk_selection_data_set(seldata, gtk_selection_data_get_target(seldata), 8,
+ es->seldata->u, es->seldata->len);
+}
+
+gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
+ gpointer data)
+{
+ eventlog_stuff *es = (eventlog_stuff *)data;
+ struct uctrl *uc;
+
+ /*
+ * Deselect everything in the list box.
+ */
+ uc = dlg_find_byctrl(&es->dp, es->listctrl);
+ es->ignore_selchange = true;
+#if !GTK_CHECK_VERSION(2,0,0)
+ assert(uc->list);
+ gtk_list_unselect_all(GTK_LIST(uc->list));
+#else
+ assert(uc->treeview);
+ gtk_tree_selection_unselect_all(
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)));
+#endif
+ es->ignore_selchange = false;
+
+ return true;
+}
+
+void showeventlog(eventlog_stuff *es, void *parentwin)
+{
+ GtkWidget *window, *w0, *w1;
+ GtkWidget *parent = GTK_WIDGET(parentwin);
+ struct controlset *s0, *s1;
+ dlgcontrol *c;
+ int index;
+ char *title;
+
+ if (es->window) {
+ gtk_widget_grab_focus(es->window);
+ return;
+ }
+
+ dlg_init(&es->dp);
+
+ for (index = 0; index < lenof(es->scs.sc); index++) {
+ es->scs.sc[index].action = SHORTCUT_EMPTY;
+ }
+
+ es->eventbox = ctrl_new_box();
+
+ s0 = ctrl_getset(es->eventbox, "", "", "");
+ ctrl_columns(s0, 3, 33, 34, 33);
+ c = ctrl_pushbutton(s0, "Close", 'c', HELPCTX(no_help),
+ eventlog_ok_handler, P(NULL));
+ c->column = 1;
+ c->button.isdefault = true;
+
+ s1 = ctrl_getset(es->eventbox, "x", "", "");
+ es->listctrl = c = ctrl_listbox(s1, NULL, NO_SHORTCUT, HELPCTX(no_help),
+ eventlog_list_handler, P(es));
+ c->listbox.height = 10;
+ c->listbox.multisel = 2;
+ c->listbox.ncols = 3;
+ c->listbox.percentages = snewn(3, int);
+ c->listbox.percentages[0] = 25;
+ c->listbox.percentages[1] = 10;
+ c->listbox.percentages[2] = 65;
+
+ es->window = window = our_dialog_new();
+ title = dupcat(appname, " Event Log");
+ gtk_window_set_title(GTK_WINDOW(window), title);
+ sfree(title);
+ w0 = layout_ctrls(&es->dp, NULL, &es->scs, s0, GTK_WINDOW(window));
+ our_dialog_set_action_area(GTK_WINDOW(window), w0);
+ gtk_widget_show(w0);
+ w1 = layout_ctrls(&es->dp, NULL, &es->scs, s1, GTK_WINDOW(window));
+ gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
+ gtk_widget_set_size_request(
+ w1, 20 + string_width("LINE OF TEXT GIVING WIDTH OF EVENT LOG IS "
+ "QUITE LONG 'COS SSH LOG ENTRIES ARE WIDE"),
+ -1);
+ our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0);
+ {
+ struct uctrl *uc = dlg_find_byctrl(&es->dp, es->listctrl);
+ columns_vexpand(COLUMNS(w1), uc->toplevel);
+ }
+ gtk_widget_show(w1);
+
+ es->dp.data = es;
+ es->dp.shortcuts = &es->scs;
+ es->dp.lastfocus = NULL;
+ es->dp.retval = 0;
+ es->dp.window = window;
+
+ dlg_refresh(NULL, &es->dp);
+
+ if (parent) {
+ set_transient_window_pos(parent, window);
+ gtk_window_set_transient_for(GTK_WINDOW(window),
+ GTK_WINDOW(parent));
+ } else
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
+ gtk_widget_show(window);
+
+ g_signal_connect(G_OBJECT(window), "destroy",
+ G_CALLBACK(eventlog_destroy), es);
+ g_signal_connect(G_OBJECT(window), "key_press_event",
+ G_CALLBACK(win_key_press), &es->dp);
+ g_signal_connect(G_OBJECT(window), "selection_get",
+ G_CALLBACK(eventlog_selection_get), es);
+ g_signal_connect(G_OBJECT(window), "selection_clear_event",
+ G_CALLBACK(eventlog_selection_clear), es);
+}
+
+eventlog_stuff *eventlogstuff_new(void)
+{
+ eventlog_stuff *es = snew(eventlog_stuff);
+ memset(es, 0, sizeof(*es));
+ es->seldata = strbuf_new();
+ return es;
+}
+
+void eventlogstuff_free(eventlog_stuff *es)
+{
+ int i;
+
+ if (es->events_initial) {
+ for (i = 0; i < LOGEVENT_INITIAL_MAX; i++)
+ sfree(es->events_initial[i]);
+ sfree(es->events_initial);
+ }
+ if (es->events_circular) {
+ for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++)
+ sfree(es->events_circular[i]);
+ sfree(es->events_circular);
+ }
+ strbuf_free(es->seldata);
+
+ sfree(es);
+}
+
+void logevent_dlg(eventlog_stuff *es, const char *string)
+{
+ char timebuf[40];
+ struct tm tm;
+ char **location;
+ size_t i;
+
+ if (es->ninitial == 0) {
+ es->events_initial = sresize(es->events_initial, LOGEVENT_INITIAL_MAX, char *);
+ for (i = 0; i < LOGEVENT_INITIAL_MAX; i++)
+ es->events_initial[i] = NULL;
+ es->events_circular = sresize(es->events_circular, LOGEVENT_CIRCULAR_MAX, char *);
+ for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++)
+ es->events_circular[i] = NULL;
+ }
+
+ if (es->ninitial < LOGEVENT_INITIAL_MAX)
+ location = &es->events_initial[es->ninitial];
+ else
+ location = &es->events_circular[(es->circular_first + es->ncircular) % LOGEVENT_CIRCULAR_MAX];
+
+ tm=ltime();
+ strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
+
+ sfree(*location);
+ *location = dupcat(timebuf, string);
+ if (es->window) {
+ dlg_listbox_add(es->listctrl, &es->dp, *location);
+ }
+ if (es->ninitial < LOGEVENT_INITIAL_MAX) {
+ es->ninitial++;
+ } else if (es->ncircular < LOGEVENT_CIRCULAR_MAX) {
+ es->ncircular++;
+ } else if (es->ncircular == LOGEVENT_CIRCULAR_MAX) {
+ es->circular_first = (es->circular_first + 1) % LOGEVENT_CIRCULAR_MAX;
+ sfree(es->events_circular[es->circular_first]);
+ es->events_circular[es->circular_first] = dupstr("..");
+ }
+}
+
+struct simple_prompt_result_int_ctx {
+ void (*callback)(void *callback_ctx, int result);
+ void *callback_ctx;
+ Seat *seat;
+ enum DialogSlot dialog_slot;
+};
+
+static void simple_prompt_result_int_callback(void *vctx, int result)
+{
+ struct simple_prompt_result_int_ctx *ctx =
+ (struct simple_prompt_result_int_ctx *)vctx;
+
+ unregister_dialog(ctx->seat, ctx->dialog_slot);
+
+ if (result >= 0)
+ ctx->callback(ctx->callback_ctx, result);
+
+ /*
+ * Clean up this context structure, whether or not a result was
+ * ever actually delivered from the dialog box.
+ */
+ sfree(ctx);
+}
+
+int gtkdlg_askappend(Seat *seat, Filename *filename,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ static const char msgtemplate[] =
+ "The session log file \"%.*s\" already exists. "
+ "You can overwrite it with a new session log, "
+ "append your session log to the end of it, "
+ "or disable session logging for this session.";
+ static const struct message_box_button button_array_append[] = {
+ {"Overwrite", 'o', 1, 2},
+ {"Append", 'a', 0, 1},
+ {"Disable", 'd', -1, 0},
+ };
+ static const struct message_box_buttons buttons_append = {
+ button_array_append, lenof(button_array_append),
+ };
+
+ char *message;
+ char *mbtitle;
+ struct simple_prompt_result_int_ctx *result_ctx;
+ GtkWidget *mainwin, *msgbox;
+
+ message = dupprintf(msgtemplate, FILENAME_MAX, filename->path);
+ mbtitle = dupprintf("%s Log to File", appname);
+
+ result_ctx = snew(struct simple_prompt_result_int_ctx);
+ result_ctx->callback = callback;
+ result_ctx->callback_ctx = ctx;
+ result_ctx->seat = seat;
+ result_ctx->dialog_slot = DIALOG_SLOT_LOGFILE_PROMPT;
+
+ mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
+ msgbox = create_message_box(
+ mainwin, mbtitle, message,
+ string_width("LINE OF TEXT SUITABLE FOR THE ASKAPPEND WIDTH"),
+ false, &buttons_append, simple_prompt_result_int_callback, result_ctx);
+ register_dialog(seat, result_ctx->dialog_slot, msgbox);
+
+ sfree(message);
+ sfree(mbtitle);
+
+ return -1; /* dialog still in progress */
+}
+
+struct ca_config_box {
+ GtkWidget *window;
+ struct controlbox *cb;
+ struct Shortcuts scs;
+ bool quit_main;
+ dlgparam dp;
+};
+static struct ca_config_box *cacfg; /* one of these, cross-instance */
+
+static void cacfg_destroy(GtkWidget *widget, gpointer data)
+{
+ cacfg->window = NULL;
+ dlg_cleanup(&cacfg->dp);
+ ctrl_free_box(cacfg->cb);
+ cacfg->cb = NULL;
+ if (cacfg->quit_main)
+ gtk_main_quit();
+}
+static void make_ca_config_box(GtkWidget *spawning_window)
+{
+ if (!cacfg) {
+ cacfg = snew(struct ca_config_box);
+ memset(cacfg, 0, sizeof(*cacfg));
+ }
+
+ if (cacfg->window) {
+ /* This dialog box is already displayed; re-focus it */
+ gtk_widget_grab_focus(cacfg->window);
+ return;
+ }
+
+ dlg_init(&cacfg->dp);
+ for (size_t i = 0; i < lenof(cacfg->scs.sc); i++) {
+ cacfg->scs.sc[i].action = SHORTCUT_EMPTY;
+ }
+
+ cacfg->cb = ctrl_new_box();
+ setup_ca_config_box(cacfg->cb);
+
+ cacfg->window = our_dialog_new();
+ gtk_window_set_title(GTK_WINDOW(cacfg->window),
+ "PuTTY trusted host certification authorities");
+ gtk_widget_set_size_request(
+ cacfg->window, string_width(
+ "ecdsa-sha2-nistp256 256 SHA256:hsO5a8MYGzBoa2gW5"
+ "dLV2vl7bTnCPjw64x3kLkz6BY8"), -1);
+
+ /* Set up everything else */
+ for (int i = 0; i < cacfg->cb->nctrlsets; i++) {
+ struct controlset *s = cacfg->cb->ctrlsets[i];
+ GtkWidget *w = layout_ctrls(&cacfg->dp, NULL, &cacfg->scs, s,
+ GTK_WINDOW(cacfg->window));
+ gtk_container_set_border_width(GTK_CONTAINER(w), 10);
+ gtk_widget_show(w);
+
+ if (!*s->pathname) {
+ our_dialog_set_action_area(GTK_WINDOW(cacfg->window), w);
+ } else {
+ our_dialog_add_to_content_area(GTK_WINDOW(cacfg->window), w,
+ true, true, 0);
+ }
+ }
+
+ cacfg->dp.data = cacfg;
+ cacfg->dp.shortcuts = &cacfg->scs;
+ cacfg->dp.lastfocus = NULL;
+ cacfg->dp.retval = 0;
+ cacfg->dp.window = cacfg->window;
+
+ dlg_refresh(NULL, &cacfg->dp);
+
+ if (spawning_window) {
+ set_transient_window_pos(spawning_window, cacfg->window);
+ } else {
+ gtk_window_set_position(GTK_WINDOW(cacfg->window), GTK_WIN_POS_CENTER);
+ }
+ gtk_widget_show(cacfg->window);
+
+ g_signal_connect(G_OBJECT(cacfg->window), "destroy",
+ G_CALLBACK(cacfg_destroy), NULL);
+ g_signal_connect(G_OBJECT(cacfg->window), "key_press_event",
+ G_CALLBACK(win_key_press), &cacfg->dp);
+}
+
+void show_ca_config_box(dlgparam *dp)
+{
+ make_ca_config_box(dp ? dp->window : NULL);
+}
+
+void show_ca_config_box_synchronously(void)
+{
+ make_ca_config_box(NULL);
+ cacfg->quit_main = true;
+ gtk_main();
+}
diff --git a/unix/fd-socket.c b/unix/fd-socket.c
new file mode 100644
index 00000000..9758a17b
--- /dev/null
+++ b/unix/fd-socket.c
@@ -0,0 +1,417 @@
+/*
+ * fd-socket.c: implementation of Socket that just talks to two
+ * existing input and output file descriptors.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+
+typedef struct FdSocket {
+ int outfd, infd, inerrfd; /* >= 0 if socket is open */
+ DeferredSocketOpener *opener; /* non-NULL if not opened yet */
+
+ bufchain pending_output_data;
+ bufchain pending_input_data;
+ ProxyStderrBuf psb;
+ enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
+
+ int pending_error;
+
+ SockAddr *addr;
+ int port;
+ Plug *plug;
+
+ Socket sock;
+} FdSocket;
+
+static void fdsocket_select_result_input(int fd, int event);
+static void fdsocket_select_result_output(int fd, int event);
+static void fdsocket_select_result_input_error(int fd, int event);
+
+/*
+ * Trees to look up the fds in.
+ */
+static tree234 *fdsocket_by_outfd;
+static tree234 *fdsocket_by_infd;
+static tree234 *fdsocket_by_inerrfd;
+
+static int fdsocket_infd_cmp(void *av, void *bv)
+{
+ FdSocket *a = (FdSocket *)av;
+ FdSocket *b = (FdSocket *)bv;
+ if (a->infd < b->infd)
+ return -1;
+ if (a->infd > b->infd)
+ return +1;
+ return 0;
+}
+static int fdsocket_infd_find(void *av, void *bv)
+{
+ int a = *(int *)av;
+ FdSocket *b = (FdSocket *)bv;
+ if (a < b->infd)
+ return -1;
+ if (a > b->infd)
+ return +1;
+ return 0;
+}
+static int fdsocket_inerrfd_cmp(void *av, void *bv)
+{
+ FdSocket *a = (FdSocket *)av;
+ FdSocket *b = (FdSocket *)bv;
+ if (a->inerrfd < b->inerrfd)
+ return -1;
+ if (a->inerrfd > b->inerrfd)
+ return +1;
+ return 0;
+}
+static int fdsocket_inerrfd_find(void *av, void *bv)
+{
+ int a = *(int *)av;
+ FdSocket *b = (FdSocket *)bv;
+ if (a < b->inerrfd)
+ return -1;
+ if (a > b->inerrfd)
+ return +1;
+ return 0;
+}
+static int fdsocket_outfd_cmp(void *av, void *bv)
+{
+ FdSocket *a = (FdSocket *)av;
+ FdSocket *b = (FdSocket *)bv;
+ if (a->outfd < b->outfd)
+ return -1;
+ if (a->outfd > b->outfd)
+ return +1;
+ return 0;
+}
+static int fdsocket_outfd_find(void *av, void *bv)
+{
+ int a = *(int *)av;
+ FdSocket *b = (FdSocket *)bv;
+ if (a < b->outfd)
+ return -1;
+ if (a > b->outfd)
+ return +1;
+ return 0;
+}
+
+static Plug *fdsocket_plug(Socket *s, Plug *p)
+{
+ FdSocket *fds = container_of(s, FdSocket, sock);
+ Plug *ret = fds->plug;
+ if (p)
+ fds->plug = p;
+ return ret;
+}
+
+static void fdsocket_close(Socket *s)
+{
+ FdSocket *fds = container_of(s, FdSocket, sock);
+
+ if (fds->opener)
+ deferred_socket_opener_free(fds->opener);
+
+ if (fds->outfd >= 0) {
+ del234(fdsocket_by_outfd, fds);
+ uxsel_del(fds->outfd);
+ close(fds->outfd);
+ }
+
+ if (fds->infd >= 0) {
+ del234(fdsocket_by_infd, fds);
+ uxsel_del(fds->infd);
+ close(fds->infd);
+ }
+
+ if (fds->inerrfd >= 0) {
+ del234(fdsocket_by_inerrfd, fds);
+ uxsel_del(fds->inerrfd);
+ close(fds->inerrfd);
+ }
+
+ bufchain_clear(&fds->pending_input_data);
+ bufchain_clear(&fds->pending_output_data);
+
+ if (fds->addr)
+ sk_addr_free(fds->addr);
+
+ delete_callbacks_for_context(fds);
+
+ sfree(fds);
+}
+
+static void fdsocket_error_callback(void *vs)
+{
+ FdSocket *fds = (FdSocket *)vs;
+
+ /*
+ * Just in case other socket work has caused this socket to vanish
+ * or become somehow non-erroneous before this callback arrived...
+ */
+ if (!fds->pending_error)
+ return;
+
+ /*
+ * An error has occurred on this socket. Pass it to the plug.
+ */
+ plug_closing_errno(fds->plug, fds->pending_error);
+}
+
+static int fdsocket_try_send(FdSocket *fds)
+{
+ int sent = 0;
+
+ if (fds->opener)
+ return sent;
+
+ while (bufchain_size(&fds->pending_output_data) > 0) {
+ ssize_t ret;
+
+ ptrlen data = bufchain_prefix(&fds->pending_output_data);
+ ret = write(fds->outfd, data.ptr, data.len);
+ noise_ultralight(NOISE_SOURCE_IOID, ret);
+ if (ret < 0 && errno != EWOULDBLOCK) {
+ if (!fds->pending_error) {
+ fds->pending_error = errno;
+ queue_toplevel_callback(fdsocket_error_callback, fds);
+ }
+ return 0;
+ } else if (ret <= 0) {
+ break;
+ } else {
+ bufchain_consume(&fds->pending_output_data, ret);
+ sent += ret;
+ }
+ }
+
+ if (fds->outgoingeof == EOF_PENDING) {
+ del234(fdsocket_by_outfd, fds);
+ close(fds->outfd);
+ uxsel_del(fds->outfd);
+ fds->outfd = -1;
+ fds->outgoingeof = EOF_SENT;
+ }
+
+ if (bufchain_size(&fds->pending_output_data) == 0)
+ uxsel_del(fds->outfd);
+ else
+ uxsel_set(fds->outfd, SELECT_W, fdsocket_select_result_output);
+
+ return sent;
+}
+
+static size_t fdsocket_write(Socket *s, const void *data, size_t len)
+{
+ FdSocket *fds = container_of(s, FdSocket, sock);
+
+ assert(fds->outgoingeof == EOF_NO);
+
+ bufchain_add(&fds->pending_output_data, data, len);
+
+ fdsocket_try_send(fds);
+
+ return bufchain_size(&fds->pending_output_data);
+}
+
+static size_t fdsocket_write_oob(Socket *s, const void *data, size_t len)
+{
+ /*
+ * oob data is treated as inband; nasty, but nothing really
+ * better we can do
+ */
+ return fdsocket_write(s, data, len);
+}
+
+static void fdsocket_write_eof(Socket *s)
+{
+ FdSocket *fds = container_of(s, FdSocket, sock);
+
+ assert(fds->outgoingeof == EOF_NO);
+ fds->outgoingeof = EOF_PENDING;
+
+ fdsocket_try_send(fds);
+}
+
+static void fdsocket_set_frozen(Socket *s, bool is_frozen)
+{
+ FdSocket *fds = container_of(s, FdSocket, sock);
+
+ if (fds->infd < 0)
+ return;
+
+ if (is_frozen)
+ uxsel_del(fds->infd);
+ else
+ uxsel_set(fds->infd, SELECT_R, fdsocket_select_result_input);
+}
+
+static const char *fdsocket_socket_error(Socket *s)
+{
+ return NULL;
+}
+
+static void fdsocket_select_result_input(int fd, int event)
+{
+ FdSocket *fds;
+ char buf[20480];
+ int retd;
+
+ if (!(fds = find234(fdsocket_by_infd, &fd, fdsocket_infd_find)))
+ return;
+
+ retd = read(fds->infd, buf, sizeof(buf));
+ if (retd > 0) {
+ plug_receive(fds->plug, 0, buf, retd);
+ } else {
+ del234(fdsocket_by_infd, fds);
+ uxsel_del(fds->infd);
+ close(fds->infd);
+ fds->infd = -1;
+
+ if (retd < 0) {
+ plug_closing_errno(fds->plug, errno);
+ } else {
+ plug_closing_normal(fds->plug);
+ }
+ }
+}
+
+static void fdsocket_select_result_output(int fd, int event)
+{
+ FdSocket *fds;
+
+ if (!(fds = find234(fdsocket_by_outfd, &fd, fdsocket_outfd_find)))
+ return;
+
+ if (fdsocket_try_send(fds))
+ plug_sent(fds->plug, bufchain_size(&fds->pending_output_data));
+}
+
+static void fdsocket_select_result_input_error(int fd, int event)
+{
+ FdSocket *fds;
+ char buf[20480];
+ int retd;
+
+ if (!(fds = find234(fdsocket_by_inerrfd, &fd, fdsocket_inerrfd_find)))
+ return;
+
+ retd = read(fd, buf, sizeof(buf));
+ if (retd > 0) {
+ log_proxy_stderr(fds->plug, &fds->psb, buf, retd);
+ } else {
+ del234(fdsocket_by_inerrfd, fds);
+ uxsel_del(fds->inerrfd);
+ close(fds->inerrfd);
+ fds->inerrfd = -1;
+ }
+}
+
+static const SocketVtable FdSocket_sockvt = {
+ .plug = fdsocket_plug,
+ .close = fdsocket_close,
+ .write = fdsocket_write,
+ .write_oob = fdsocket_write_oob,
+ .write_eof = fdsocket_write_eof,
+ .set_frozen = fdsocket_set_frozen,
+ .socket_error = fdsocket_socket_error,
+ .peer_info = NULL,
+};
+
+static void fdsocket_connect_success_callback(void *ctx)
+{
+ FdSocket *fds = (FdSocket *)ctx;
+ plug_log(fds->plug, PLUGLOG_CONNECT_SUCCESS, fds->addr, fds->port,
+ NULL, 0);
+}
+
+void setup_fd_socket(Socket *s, int infd, int outfd, int inerrfd)
+{
+ FdSocket *fds = container_of(s, FdSocket, sock);
+ assert(fds->sock.vt == &FdSocket_sockvt);
+
+ if (fds->opener) {
+ deferred_socket_opener_free(fds->opener);
+ fds->opener = NULL;
+ }
+
+ fds->infd = infd;
+ fds->outfd = outfd;
+ fds->inerrfd = inerrfd;
+
+ if (fds->outfd >= 0) {
+ if (!fdsocket_by_outfd)
+ fdsocket_by_outfd = newtree234(fdsocket_outfd_cmp);
+ add234(fdsocket_by_outfd, fds);
+ }
+
+ if (fds->infd >= 0) {
+ if (!fdsocket_by_infd)
+ fdsocket_by_infd = newtree234(fdsocket_infd_cmp);
+ add234(fdsocket_by_infd, fds);
+ uxsel_set(fds->infd, SELECT_R, fdsocket_select_result_input);
+ }
+
+ if (fds->inerrfd >= 0) {
+ assert(fds->inerrfd != fds->infd);
+ if (!fdsocket_by_inerrfd)
+ fdsocket_by_inerrfd = newtree234(fdsocket_inerrfd_cmp);
+ add234(fdsocket_by_inerrfd, fds);
+ uxsel_set(fds->inerrfd, SELECT_R, fdsocket_select_result_input_error);
+ }
+
+ queue_toplevel_callback(fdsocket_connect_success_callback, fds);
+}
+
+void fd_socket_set_psb_prefix(Socket *s, const char *prefix)
+{
+ FdSocket *fds = container_of(s, FdSocket, sock);
+ assert(fds->sock.vt == &FdSocket_sockvt);
+ psb_set_prefix(&fds->psb, prefix);
+}
+
+static FdSocket *make_fd_socket_internal(SockAddr *addr, int port, Plug *plug)
+{
+ FdSocket *fds;
+
+ fds = snew(FdSocket);
+ fds->sock.vt = &FdSocket_sockvt;
+ fds->addr = addr;
+ fds->port = port;
+ fds->plug = plug;
+ fds->outgoingeof = EOF_NO;
+ fds->pending_error = 0;
+
+ fds->opener = NULL;
+ fds->infd = fds->outfd = fds->inerrfd = -1;
+
+ bufchain_init(&fds->pending_input_data);
+ bufchain_init(&fds->pending_output_data);
+ psb_init(&fds->psb);
+
+ return fds;
+}
+
+Socket *make_fd_socket(int infd, int outfd, int inerrfd,
+ SockAddr *addr, int port, Plug *plug)
+{
+ FdSocket *fds = make_fd_socket_internal(addr, port, plug);
+ setup_fd_socket(&fds->sock, infd, outfd, inerrfd);
+ return &fds->sock;
+}
+
+Socket *make_deferred_fd_socket(DeferredSocketOpener *opener,
+ SockAddr *addr, int port, Plug *plug)
+{
+ FdSocket *fds = make_fd_socket_internal(addr, port, plug);
+ fds->opener = opener;
+ return &fds->sock;
+}
diff --git a/unix/gss.c b/unix/gss.c
new file mode 100644
index 00000000..bd599fcc
--- /dev/null
+++ b/unix/gss.c
@@ -0,0 +1,176 @@
+#include "putty.h"
+#ifndef NO_GSSAPI
+#include "ssh/pgssapi.h"
+#include "ssh/gss.h"
+#include "ssh/gssc.h"
+
+/* Unix code to set up the GSSAPI library list. */
+
+#if !defined NO_LIBDL && !defined STATIC_GSSAPI && !defined NO_GSSAPI
+
+const int ngsslibs = 4;
+const char *const gsslibnames[4] = {
+ "libgssapi (Heimdal)",
+ "libgssapi_krb5 (MIT Kerberos)",
+ "libgss (Sun)",
+ "User-specified GSSAPI library",
+};
+const struct keyvalwhere gsslibkeywords[] = {
+ { "libgssapi", 0, -1, -1 },
+ { "libgssapi_krb5", 1, -1, -1 },
+ { "libgss", 2, -1, -1 },
+ { "custom", 3, -1, -1 },
+};
+
+/*
+ * Run-time binding against a choice of GSSAPI implementations. We
+ * try loading several libraries, and produce an entry in
+ * ssh_gss_libraries[] for each one.
+ */
+
+static void gss_init(struct ssh_gss_library *lib, void *dlhandle,
+ int id, const char *msg)
+{
+ lib->id = id;
+ lib->gsslogmsg = msg;
+ lib->handle = dlhandle;
+
+#define BIND_GSS_FN(name) \
+ lib->u.gssapi.name = (t_gss_##name) dlsym(dlhandle, "gss_" #name)
+
+ BIND_GSS_FN(delete_sec_context);
+ BIND_GSS_FN(display_status);
+ BIND_GSS_FN(get_mic);
+ BIND_GSS_FN(verify_mic);
+ BIND_GSS_FN(import_name);
+ BIND_GSS_FN(init_sec_context);
+ BIND_GSS_FN(release_buffer);
+ BIND_GSS_FN(release_cred);
+ BIND_GSS_FN(release_name);
+ BIND_GSS_FN(acquire_cred);
+ BIND_GSS_FN(inquire_cred_by_mech);
+
+#undef BIND_GSS_FN
+
+ ssh_gssapi_bind_fns(lib);
+}
+
+/* Dynamically load gssapi libs. */
+struct ssh_gss_liblist *ssh_gss_setup(Conf *conf)
+{
+ void *gsslib;
+ char *gsspath;
+ struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist);
+
+ list->libraries = snewn(4, struct ssh_gss_library);
+ list->nlibraries = 0;
+
+ /* Heimdal's GSSAPI Library */
+ if ((gsslib = dlopen("libgssapi.so.2", RTLD_LAZY)) != NULL)
+ gss_init(&list->libraries[list->nlibraries++], gsslib,
+ 0, "Using GSSAPI from libgssapi.so.2");
+
+ /* MIT Kerberos's GSSAPI Library */
+ if ((gsslib = dlopen("libgssapi_krb5.so.2", RTLD_LAZY)) != NULL)
+ gss_init(&list->libraries[list->nlibraries++], gsslib,
+ 1, "Using GSSAPI from libgssapi_krb5.so.2");
+
+ /* Sun's GSSAPI Library */
+ if ((gsslib = dlopen("libgss.so.1", RTLD_LAZY)) != NULL)
+ gss_init(&list->libraries[list->nlibraries++], gsslib,
+ 2, "Using GSSAPI from libgss.so.1");
+
+ /* User-specified GSSAPI library */
+ gsspath = conf_get_filename(conf, CONF_ssh_gss_custom)->path;
+ if (*gsspath && (gsslib = dlopen(gsspath, RTLD_LAZY)) != NULL)
+ gss_init(&list->libraries[list->nlibraries++], gsslib,
+ 3, dupprintf("Using GSSAPI from user-specified"
+ " library '%s'", gsspath));
+
+ return list;
+}
+
+void ssh_gss_cleanup(struct ssh_gss_liblist *list)
+{
+ int i;
+
+ /*
+ * dlopen and dlclose are defined to employ reference counting
+ * in the case where the same library is repeatedly dlopened, so
+ * even in a multiple-sessions-per-process context it's safe to
+ * naively dlclose everything here without worrying about
+ * destroying it under the feet of another SSH instance still
+ * using it.
+ */
+ for (i = 0; i < list->nlibraries; i++) {
+ dlclose(list->libraries[i].handle);
+ if (list->libraries[i].id == 3) {
+ /* The 'custom' id involves a dynamically allocated message.
+ * Note that we must cast away the 'const' to free it. */
+ sfree((char *)list->libraries[i].gsslogmsg);
+ }
+ }
+ sfree(list->libraries);
+ sfree(list);
+}
+
+#elif !defined NO_GSSAPI
+
+const int ngsslibs = 1;
+const char *const gsslibnames[1] = {
+ "static",
+};
+const struct keyvalwhere gsslibkeywords[] = {
+ { "static", 0, -1, -1 },
+};
+
+/*
+ * Link-time binding against GSSAPI. Here we just construct a single
+ * library structure containing pointers to the functions we linked
+ * against.
+ */
+
+#include <gssapi/gssapi.h>
+
+/* Dynamically load gssapi libs. */
+struct ssh_gss_liblist *ssh_gss_setup(Conf *conf)
+{
+ struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist);
+
+ list->libraries = snew(struct ssh_gss_library);
+ list->nlibraries = 1;
+
+ list->libraries[0].id = 0;
+ list->libraries[0].gsslogmsg = "Using statically linked GSSAPI";
+
+#define BIND_GSS_FN(name) \
+ list->libraries[0].u.gssapi.name = (t_gss_##name) gss_##name
+
+ BIND_GSS_FN(delete_sec_context);
+ BIND_GSS_FN(display_status);
+ BIND_GSS_FN(get_mic);
+ BIND_GSS_FN(verify_mic);
+ BIND_GSS_FN(import_name);
+ BIND_GSS_FN(init_sec_context);
+ BIND_GSS_FN(release_buffer);
+ BIND_GSS_FN(release_cred);
+ BIND_GSS_FN(release_name);
+ BIND_GSS_FN(acquire_cred);
+ BIND_GSS_FN(inquire_cred_by_mech);
+
+#undef BIND_GSS_FN
+
+ ssh_gssapi_bind_fns(&list->libraries[0]);
+
+ return list;
+}
+
+void ssh_gss_cleanup(struct ssh_gss_liblist *list)
+{
+ sfree(list->libraries);
+ sfree(list);
+}
+
+#endif /* NO_LIBDL */
+
+#endif /* NO_GSSAPI */
diff --git a/unix/gtk-common.c b/unix/gtk-common.c
new file mode 100644
index 00000000..9d48e882
--- /dev/null
+++ b/unix/gtk-common.c
@@ -0,0 +1,243 @@
+/*
+ * gtk-common.c: machinery in the GTK front end which is common to all
+ * programs that run a session in a terminal window, and also common
+ * across all _sessions_ rather than specific to one session. (Timers,
+ * uxsel etc.)
+ */
+
+#define _GNU_SOURCE
+
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <stdio.h>
+#include <time.h>
+#include <errno.h>
+#include <locale.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <gtk/gtk.h>
+#if !GTK_CHECK_VERSION(3,0,0)
+#include <gdk/gdkkeysyms.h>
+#endif
+
+#if GTK_CHECK_VERSION(2,0,0)
+#include <gtk/gtkimmodule.h>
+#endif
+
+#define MAY_REFER_TO_GTK_IN_HEADERS
+
+#include "putty.h"
+#include "terminal.h"
+#include "gtkcompat.h"
+#include "unifont.h"
+#include "gtkmisc.h"
+
+#ifndef NOT_X_WINDOWS
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#endif
+
+#define ASSERT(x) enum {CAT(assertion_,__LINE__) = 1 / (x)}
+
+#if GTK_CHECK_VERSION(2,0,0)
+ASSERT(sizeof(long) <= sizeof(gsize));
+#define LONG_TO_GPOINTER(l) GSIZE_TO_POINTER(l)
+#define GPOINTER_TO_LONG(p) GPOINTER_TO_SIZE(p)
+#else /* Gtk 1.2 */
+ASSERT(sizeof(long) <= sizeof(gpointer));
+#define LONG_TO_GPOINTER(l) ((gpointer)(long)(l))
+#define GPOINTER_TO_LONG(p) ((long)(p))
+#endif
+
+/* ----------------------------------------------------------------------
+ * File descriptors and uxsel.
+ */
+
+struct uxsel_id {
+#if GTK_CHECK_VERSION(2,0,0)
+ GIOChannel *chan;
+ guint watch_id;
+#else
+ int id;
+#endif
+};
+
+#if GTK_CHECK_VERSION(2,0,0)
+gboolean fd_input_func(GIOChannel *source, GIOCondition condition,
+ gpointer data)
+{
+ int sourcefd = g_io_channel_unix_get_fd(source);
+ /*
+ * We must process exceptional notifications before ordinary
+ * readability ones, or we may go straight past the urgent
+ * marker.
+ */
+ if (condition & G_IO_PRI)
+ select_result(sourcefd, SELECT_X);
+ if (condition & (G_IO_IN | G_IO_HUP))
+ select_result(sourcefd, SELECT_R);
+ if (condition & G_IO_OUT)
+ select_result(sourcefd, SELECT_W);
+
+ run_toplevel_callbacks();
+
+ return true;
+}
+#else
+void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition)
+{
+ if (condition & GDK_INPUT_EXCEPTION)
+ select_result(sourcefd, SELECT_X);
+ if (condition & GDK_INPUT_READ)
+ select_result(sourcefd, SELECT_R);
+ if (condition & GDK_INPUT_WRITE)
+ select_result(sourcefd, SELECT_W);
+
+ run_toplevel_callbacks();
+}
+#endif
+
+uxsel_id *uxsel_input_add(int fd, int rwx) {
+ uxsel_id *id = snew(uxsel_id);
+
+#if GTK_CHECK_VERSION(2,0,0)
+ int flags = 0;
+ if (rwx & SELECT_R) flags |= G_IO_IN | G_IO_HUP;
+ if (rwx & SELECT_W) flags |= G_IO_OUT;
+ if (rwx & SELECT_X) flags |= G_IO_PRI;
+ id->chan = g_io_channel_unix_new(fd);
+ g_io_channel_set_encoding(id->chan, NULL, NULL);
+ id->watch_id = g_io_add_watch_full(id->chan, GDK_PRIORITY_REDRAW+1, flags,
+ fd_input_func, NULL, NULL);
+#else
+ int flags = 0;
+ if (rwx & SELECT_R) flags |= GDK_INPUT_READ;
+ if (rwx & SELECT_W) flags |= GDK_INPUT_WRITE;
+ if (rwx & SELECT_X) flags |= GDK_INPUT_EXCEPTION;
+ assert(flags);
+ id->id = gdk_input_add(fd, flags, fd_input_func, NULL);
+#endif
+
+ return id;
+}
+
+void uxsel_input_remove(uxsel_id *id) {
+#if GTK_CHECK_VERSION(2,0,0)
+ g_source_remove(id->watch_id);
+ g_io_channel_unref(id->chan);
+#else
+ gdk_input_remove(id->id);
+#endif
+ sfree(id);
+}
+
+/* ----------------------------------------------------------------------
+ * Timers.
+ */
+
+static guint timer_id = 0;
+
+static gint timer_trigger(gpointer data)
+{
+ unsigned long now = GPOINTER_TO_LONG(data);
+ unsigned long next, then;
+ long ticks;
+
+ /*
+ * Destroy the timer we got here on.
+ */
+ if (timer_id) {
+ g_source_remove(timer_id);
+ timer_id = 0;
+ }
+
+ /*
+ * run_timers() may cause a call to timer_change_notify, in which
+ * case a new timer will already have been set up and left in
+ * timer_id. If it hasn't, and run_timers reports that some timing
+ * still needs to be done, we do it ourselves.
+ */
+ if (run_timers(now, &next) && !timer_id) {
+ then = now;
+ now = GETTICKCOUNT();
+ if (now - then > next - then)
+ ticks = 0;
+ else
+ ticks = next - now;
+ timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next));
+ }
+
+ /*
+ * Returning false means 'don't call this timer again', which
+ * _should_ be redundant given that we removed it above, but just
+ * in case, return false anyway.
+ */
+ return false;
+}
+
+void timer_change_notify(unsigned long next)
+{
+ long ticks;
+
+ if (timer_id)
+ g_source_remove(timer_id);
+
+ ticks = next - GETTICKCOUNT();
+ if (ticks <= 0)
+ ticks = 1; /* just in case */
+
+ timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next));
+}
+
+/* ----------------------------------------------------------------------
+ * Toplevel callbacks.
+ */
+
+static guint toplevel_callback_idle_id;
+static bool idle_fn_scheduled;
+
+static void notify_toplevel_callback(void *);
+
+static gint idle_toplevel_callback_func(gpointer data)
+{
+ run_toplevel_callbacks();
+
+ /*
+ * If we've emptied our toplevel callback queue, unschedule
+ * ourself. Otherwise, leave ourselves pending so we'll be called
+ * again to deal with more callbacks after another round of the
+ * event loop.
+ */
+ if (!toplevel_callback_pending() && idle_fn_scheduled) {
+ g_source_remove(toplevel_callback_idle_id);
+ idle_fn_scheduled = false;
+ }
+
+ return true;
+}
+
+static void notify_toplevel_callback(void *vctx)
+{
+ if (!idle_fn_scheduled) {
+ toplevel_callback_idle_id =
+ g_idle_add(idle_toplevel_callback_func, NULL);
+ idle_fn_scheduled = true;
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * Setup function. The real main program must call this.
+ */
+
+void gtkcomm_setup(void)
+{
+ uxsel_init();
+ request_callback_notifications(notify_toplevel_callback, NULL);
+}
diff --git a/unix/gtkapp.c b/unix/gtkapp.c
deleted file mode 100644
index e7f49df5..00000000
--- a/unix/gtkapp.c
+++ /dev/null
@@ -1,347 +0,0 @@
-/*
- * gtkapp.c: a top-level front end to GUI PuTTY and pterm, using
- * GtkApplication. Suitable for OS X. Currently unfinished.
- *
- * (You could run it on ordinary Linux GTK too, in principle, but I
- * don't think it would be particularly useful to do so, even once
- * it's fully working.)
- */
-
-/*
-
-To build on OS X, you will need a build environment with GTK 3 and
-gtk-mac-bundler, and also Halibut on the path (to build the man pages,
-without which the standard Makefile will complain). Then, from a clean
-checkout, do this:
-
-./mkfiles.pl -U --with-quartz
-make -C icons icns
-make -C doc
-make
-
-and you should get unix/PuTTY.app and unix/PTerm.app as output.
-
-*/
-
-/*
-
-TODO list for a sensible GTK3 PuTTY/pterm on OS X:
-
-Still to do on the application menu bar: items that have to vary with
-context or user action (saved sessions and mid-session special
-commands), and disabling/enabling the main actions in parallel with
-their counterparts in the Ctrl-rightclick context menu.
-
-Mouse wheel events and trackpad scrolling gestures don't work quite
-right in the terminal drawing area. This seems to be a combination of
-two things, neither of which I completely understand yet. Firstly, on
-OS X GTK my trackpad seems to generate GDK scroll events for which
-gdk_event_get_scroll_deltas returns integers rather than integer
-multiples of 1/30, so we end up scrolling by very large amounts;
-secondly, the window doesn't seem to receive a GTK "draw" event until
-after the entire scroll gesture is complete, which means we don't get
-constant visual feedback on how much we're scrolling by.
-
-There doesn't seem to be a resize handle on terminal windows. Then
-again, they do seem to _be_ resizable; the handle just isn't shown.
-Perhaps that's a feature (certainly in a scrollbarless configuration
-the handle gets in the way of the bottom right character cell in the
-terminal itself), but it would be nice to at least understand _why_ it
-happens and perhaps include an option to put it back again.
-
-A slight oddity with menus that pop up directly under the mouse
-pointer: mousing over the menu items doesn't highlight them initially,
-but if I mouse off the menu and back on (without un-popping-it-up)
-then suddenly that does work. I don't know if this is something I can
-fix, though; it might very well be a quirk of the underlying GTK.
-
-Does OS X have a standard system of online help that I could tie into?
-
-Need to work out what if anything we can do with Pageant on OS X.
-Perhaps it's too much bother and we should just talk to the
-system-provided SSH agent? Or perhaps not.
-
-Nice-to-have: a custom right-click menu from the application's dock
-tile, listing the saved sessions for quick launch. As far as I know
-there's nothing built in to GtkApplication that can produce this, but
-it's possible we might be able to drop a piece of native Cocoa code in
-under ifdef, substituting an application delegate of our own which
-forwards all methods we're not interested in to the GTK-provided one?
-
-At the point where this becomes polished enough to publish pre-built,
-I suppose I'll have to look into OS X code signing.
-https://wiki.gnome.org/Projects/GTK%2B/OSX/Bundling has some links.
-
- */
-
-#include <assert.h>
-#include <stdlib.h>
-
-#include <unistd.h>
-
-#include <gtk/gtk.h>
-
-#define MAY_REFER_TO_GTK_IN_HEADERS
-
-#include "putty.h"
-#include "gtkmisc.h"
-
-char *x_get_default(const char *key) { return NULL; }
-
-const bool buildinfo_gtk_relevant = true;
-
-#if !GTK_CHECK_VERSION(3,0,0)
-/* This front end only works in GTK 3. If that's not what we've got,
- * it's easier to just turn this program into a trivial stub by ifdef
- * in the source than it is to remove it in the makefile edifice. */
-int main(int argc, char **argv)
-{
- fprintf(stderr, "GtkApplication frontend doesn't work pre-GTK3\n");
- return 1;
-}
-GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend) { return NULL; }
-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) {}
-#else /* GTK_CHECK_VERSION(3,0,0) */
-
-static void startup(GApplication *app, gpointer user_data)
-{
- GMenu *menubar, *menu, *section;
-
- menubar = g_menu_new();
-
- menu = g_menu_new();
- g_menu_append_submenu(menubar, "File", G_MENU_MODEL(menu));
-
- section = g_menu_new();
- g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
- g_menu_append(section, "New Window", "app.newwin");
-
- menu = g_menu_new();
- g_menu_append_submenu(menubar, "Edit", G_MENU_MODEL(menu));
-
- section = g_menu_new();
- g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
- g_menu_append(section, "Copy", "win.copy");
- g_menu_append(section, "Paste", "win.paste");
- g_menu_append(section, "Copy All", "win.copyall");
-
- menu = g_menu_new();
- g_menu_append_submenu(menubar, "Window", G_MENU_MODEL(menu));
-
- section = g_menu_new();
- g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
- g_menu_append(section, "Restart Session", "win.restart");
- g_menu_append(section, "Duplicate Session", "win.duplicate");
-
- section = g_menu_new();
- g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
- g_menu_append(section, "Change Settings", "win.changesettings");
-
- if (use_event_log) {
- section = g_menu_new();
- g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
- g_menu_append(section, "Event Log", "win.eventlog");
- }
-
- section = g_menu_new();
- g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
- g_menu_append(section, "Clear Scrollback", "win.clearscrollback");
- g_menu_append(section, "Reset Terminal", "win.resetterm");
-
-#if GTK_CHECK_VERSION(3,12,0)
-#define SET_ACCEL(app, command, accel) do \
- { \
- static const char *const accels[] = { accel, NULL }; \
- gtk_application_set_accels_for_action( \
- GTK_APPLICATION(app), command, accels); \
- } while (0)
-#else
- /* The Gtk function used above was new in 3.12; the one below
- * was deprecated from 3.14. */
-#define SET_ACCEL(app, command, accel) \
- gtk_application_add_accelerator(GTK_APPLICATION(app), accel, \
- command, NULL)
-#endif
-
- SET_ACCEL(app, "app.newwin", "<Primary>n");
- SET_ACCEL(app, "win.copy", "<Primary>c");
- SET_ACCEL(app, "win.paste", "<Primary>v");
-
-#undef SET_ACCEL
-
- gtk_application_set_menubar(GTK_APPLICATION(app),
- G_MENU_MODEL(menubar));
-}
-
-#define WIN_ACTION_LIST(X) \
- X("copy", MA_COPY) \
- X("paste", MA_PASTE) \
- X("copyall", MA_COPY_ALL) \
- X("duplicate", MA_DUPLICATE_SESSION) \
- X("restart", MA_RESTART_SESSION) \
- X("changesettings", MA_CHANGE_SETTINGS) \
- X("clearscrollback", MA_CLEAR_SCROLLBACK) \
- X("resetterm", MA_RESET_TERMINAL) \
- X("eventlog", MA_EVENT_LOG) \
- /* end of list */
-
-#define WIN_ACTION_CALLBACK(name, id) \
-static void win_action_cb_ ## id(GSimpleAction *a, GVariant *p, gpointer d) \
-{ app_menu_action(d, id); }
-WIN_ACTION_LIST(WIN_ACTION_CALLBACK)
-#undef WIN_ACTION_CALLBACK
-
-static const GActionEntry win_actions[] = {
-#define WIN_ACTION_ENTRY(name, id) { name, win_action_cb_ ## id },
-WIN_ACTION_LIST(WIN_ACTION_ENTRY)
-#undef WIN_ACTION_ENTRY
-};
-
-static GtkApplication *app;
-GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend)
-{
- GtkWidget *win = gtk_application_window_new(app);
- g_action_map_add_action_entries(G_ACTION_MAP(win),
- win_actions,
- G_N_ELEMENTS(win_actions),
- frontend);
- return win;
-}
-
-void launch_duplicate_session(Conf *conf)
-{
- assert(!dup_check_launchable || conf_launchable(conf));
- g_application_hold(G_APPLICATION(app));
- new_session_window(conf_copy(conf), NULL);
-}
-
-void session_window_closed(void)
-{
- g_application_release(G_APPLICATION(app));
-}
-
-static void post_initial_config_box(void *vctx, int result)
-{
- Conf *conf = (Conf *)vctx;
-
- if (result > 0) {
- new_session_window(conf, NULL);
- } else if (result == 0) {
- conf_free(conf);
- g_application_release(G_APPLICATION(app));
- }
-}
-
-void launch_saved_session(const char *str)
-{
- Conf *conf = conf_new();
- do_defaults(str, conf);
-
- g_application_hold(G_APPLICATION(app));
-
- if (!conf_launchable(conf)) {
- initial_config_box(conf, post_initial_config_box, conf);
- } else {
- new_session_window(conf, NULL);
- }
-}
-
-void launch_new_session(void)
-{
- /* Same as launch_saved_session except that we pass NULL to
- * do_defaults. */
- launch_saved_session(NULL);
-}
-
-void new_app_win(GtkApplication *app)
-{
- launch_new_session();
-}
-
-static void window_setup_error_callback(void *vctx, int result)
-{
- g_application_release(G_APPLICATION(app));
-}
-
-void window_setup_error(const char *errmsg)
-{
- create_message_box(NULL, "Error creating session window", errmsg,
- string_width("Some sort of fiddly error message that "
- "might be technical"),
- true, &buttons_ok, window_setup_error_callback, NULL);
-}
-
-static void activate(GApplication *app,
- gpointer user_data)
-{
- new_app_win(GTK_APPLICATION(app));
-}
-
-static void newwin_cb(GSimpleAction *action,
- GVariant *parameter,
- gpointer user_data)
-{
- new_app_win(GTK_APPLICATION(user_data));
-}
-
-static void quit_cb(GSimpleAction *action,
- GVariant *parameter,
- gpointer user_data)
-{
- g_application_quit(G_APPLICATION(user_data));
-}
-
-static void about_cb(GSimpleAction *action,
- GVariant *parameter,
- gpointer user_data)
-{
- about_box(NULL);
-}
-
-static const GActionEntry app_actions[] = {
- { "newwin", newwin_cb },
- { "about", about_cb },
- { "quit", quit_cb },
-};
-
-int main(int argc, char **argv)
-{
- int status;
-
- /* Call the function in ux{putty,pterm}.c to do app-type
- * specific setup */
- setup(false); /* false means we are not a one-session process */
-
- if (argc > 1) {
- pty_osx_envrestore_prefix = argv[--argc];
- }
-
- {
- const char *home = getenv("HOME");
- if (home) {
- if (chdir(home)) {}
- }
- }
-
- gtkcomm_setup();
-
- app = gtk_application_new("org.tartarus.projects.putty.macputty",
- G_APPLICATION_FLAGS_NONE);
- g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
- g_signal_connect(app, "startup", G_CALLBACK(startup), NULL);
- g_action_map_add_action_entries(G_ACTION_MAP(app),
- app_actions,
- G_N_ELEMENTS(app_actions),
- app);
-
- status = g_application_run(G_APPLICATION(app), argc, argv);
- g_object_unref(app);
-
- return status;
-}
-
-#endif /* GTK_CHECK_VERSION(3,0,0) */
diff --git a/unix/gtkask.c b/unix/gtkask.c
deleted file mode 100644
index 4e95ba1a..00000000
--- a/unix/gtkask.c
+++ /dev/null
@@ -1,662 +0,0 @@
-/*
- * GTK implementation of a GUI password/passphrase prompt.
- */
-
-#include <assert.h>
-#include <time.h>
-#include <stdlib.h>
-
-#include <unistd.h>
-
-#include <gtk/gtk.h>
-#include <gdk/gdk.h>
-#if !GTK_CHECK_VERSION(3,0,0)
-#include <gdk/gdkkeysyms.h>
-#endif
-
-#include "defs.h"
-#include "gtkfont.h"
-#include "gtkcompat.h"
-#include "gtkmisc.h"
-
-#include "putty.h"
-#include "ssh.h"
-#include "misc.h"
-
-#define N_DRAWING_AREAS 3
-
-struct drawing_area_ctx {
- GtkWidget *area;
-#ifndef DRAW_DEFAULT_CAIRO
- GdkColor *cols;
-#endif
- int width, height;
- enum { NOT_CURRENT, CURRENT, GREYED_OUT } state;
-};
-
-struct askpass_ctx {
- GtkWidget *dialog, *promptlabel;
- struct drawing_area_ctx drawingareas[N_DRAWING_AREAS];
- int active_area;
-#if GTK_CHECK_VERSION(2,0,0)
- GtkIMContext *imc;
-#endif
-#ifndef DRAW_DEFAULT_CAIRO
- GdkColormap *colmap;
- GdkColor cols[3];
-#endif
- char *error_message; /* if we finish without a passphrase */
- char *passphrase; /* if we finish with one */
- int passlen, passsize;
-#if GTK_CHECK_VERSION(3,20,0)
- GdkSeat *seat; /* for gdk_seat_grab */
-#elif GTK_CHECK_VERSION(3,0,0)
- GdkDevice *keyboard; /* for gdk_device_grab */
-#endif
-
- int nattempts;
-};
-
-static prng *keypress_prng = NULL;
-static void feed_keypress_prng(void *data, int size)
-{
- put_data(keypress_prng, data, size);
-}
-void random_add_noise(NoiseSourceId source, const void *noise, int length)
-{
- if (keypress_prng)
- prng_add_entropy(keypress_prng, source, make_ptrlen(noise, length));
-}
-static void setup_keypress_prng(void)
-{
- keypress_prng = prng_new(&ssh_sha256);
- prng_seed_begin(keypress_prng);
- noise_get_heavy(feed_keypress_prng);
- prng_seed_finish(keypress_prng);
-}
-static void cleanup_keypress_prng(void)
-{
- prng_free(keypress_prng);
-}
-static uint64_t keypress_prng_value(void)
-{
- /*
- * Don't actually put the passphrase keystrokes themselves into
- * the PRNG; that doesn't seem like the course of wisdom when
- * that's precisely what the information displayed on the screen
- * is trying _not_ to be correlated to.
- */
- noise_ultralight(NOISE_SOURCE_KEY, 0);
- uint8_t data[8];
- prng_read(keypress_prng, data, 8);
- return GET_64BIT_MSB_FIRST(data);
-}
-static int choose_new_area(int prev_area)
-{
- int reduced = keypress_prng_value() % (N_DRAWING_AREAS - 1);
- return (prev_area + 1 + reduced) % N_DRAWING_AREAS;
-}
-
-static void visually_acknowledge_keypress(struct askpass_ctx *ctx)
-{
- int new_active = choose_new_area(ctx->active_area);
- ctx->drawingareas[ctx->active_area].state = NOT_CURRENT;
- gtk_widget_queue_draw(ctx->drawingareas[ctx->active_area].area);
- ctx->drawingareas[new_active].state = CURRENT;
- gtk_widget_queue_draw(ctx->drawingareas[new_active].area);
- ctx->active_area = new_active;
-}
-
-static int last_char_len(struct askpass_ctx *ctx)
-{
- /*
- * GTK always encodes in UTF-8, so we can do this in a fixed way.
- */
- int i;
- assert(ctx->passlen > 0);
- i = ctx->passlen - 1;
- while ((unsigned)((unsigned char)ctx->passphrase[i] - 0x80) < 0x40) {
- if (i == 0)
- break;
- i--;
- }
- return ctx->passlen - i;
-}
-
-static void add_text_to_passphrase(struct askpass_ctx *ctx, gchar *str)
-{
- int len = strlen(str);
- if (ctx->passlen + len >= ctx->passsize) {
- /* Take some care with buffer expansion, because there are
- * pieces of passphrase in the old buffer so we should ensure
- * realloc doesn't leave a copy lying around in the address
- * space. */
- int oldsize = ctx->passsize;
- char *newbuf;
-
- ctx->passsize = (ctx->passlen + len) * 5 / 4 + 1024;
- newbuf = snewn(ctx->passsize, char);
- memcpy(newbuf, ctx->passphrase, oldsize);
- smemclr(ctx->passphrase, oldsize);
- sfree(ctx->passphrase);
- ctx->passphrase = newbuf;
- }
- strcpy(ctx->passphrase + ctx->passlen, str);
- ctx->passlen += len;
- visually_acknowledge_keypress(ctx);
-}
-
-static void cancel_askpass(struct askpass_ctx *ctx, const char *msg)
-{
- smemclr(ctx->passphrase, ctx->passsize);
- ctx->passphrase = NULL;
- ctx->error_message = dupstr(msg);
- gtk_main_quit();
-}
-
-static gboolean askpass_dialog_closed(GtkWidget *widget, GdkEvent *event,
- gpointer data)
-{
- struct askpass_ctx *ctx = (struct askpass_ctx *)data;
- cancel_askpass(ctx, "passphrase input cancelled");
- /* Don't destroy dialog yet, so gtk_askpass_cleanup() can do its work */
- return true;
-}
-
-static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
-{
- struct askpass_ctx *ctx = (struct askpass_ctx *)data;
-
- if (event->keyval == GDK_KEY_Return &&
- event->type == GDK_KEY_PRESS) {
- gtk_main_quit();
- } else if (event->keyval == GDK_KEY_Escape &&
- event->type == GDK_KEY_PRESS) {
- cancel_askpass(ctx, "passphrase input cancelled");
- } else {
-#if GTK_CHECK_VERSION(2,0,0)
- if (gtk_im_context_filter_keypress(ctx->imc, event))
- return true;
-#endif
-
- if (event->type == GDK_KEY_PRESS) {
- if (!strcmp(event->string, "\x15")) {
- /* Ctrl-U. Wipe out the whole line */
- ctx->passlen = 0;
- visually_acknowledge_keypress(ctx);
- } else if (!strcmp(event->string, "\x17")) {
- /* Ctrl-W. Delete back to the last space->nonspace
- * boundary. We interpret 'space' in a really simple
- * way (mimicking terminal drivers), and don't attempt
- * to second-guess exciting Unicode space
- * characters. */
- while (ctx->passlen > 0) {
- char deleted, prior;
- ctx->passlen -= last_char_len(ctx);
- deleted = ctx->passphrase[ctx->passlen];
- prior = (ctx->passlen == 0 ? ' ' :
- ctx->passphrase[ctx->passlen-1]);
- if (!g_ascii_isspace(deleted) && g_ascii_isspace(prior))
- break;
- }
- visually_acknowledge_keypress(ctx);
- } else if (event->keyval == GDK_KEY_BackSpace) {
- /* Backspace. Delete one character. */
- if (ctx->passlen > 0)
- ctx->passlen -= last_char_len(ctx);
- visually_acknowledge_keypress(ctx);
-#if !GTK_CHECK_VERSION(2,0,0)
- } else if (event->string[0]) {
- add_text_to_passphrase(ctx, event->string);
-#endif
- }
- }
- }
- return true;
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-static void input_method_commit_event(GtkIMContext *imc, gchar *str,
- gpointer data)
-{
- struct askpass_ctx *ctx = (struct askpass_ctx *)data;
- add_text_to_passphrase(ctx, str);
-}
-#endif
-
-static gint configure_area(GtkWidget *widget, GdkEventConfigure *event,
- gpointer data)
-{
- struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
- ctx->width = event->width;
- ctx->height = event->height;
- gtk_widget_queue_draw(widget);
- return true;
-}
-
-#ifdef DRAW_DEFAULT_CAIRO
-static void askpass_redraw_cairo(cairo_t *cr, struct drawing_area_ctx *ctx)
-{
- double rgbval = (ctx->state == CURRENT ? 0 :
- ctx->state == NOT_CURRENT ? 1 : 0.5);
- cairo_set_source_rgb(cr, rgbval, rgbval, rgbval);
- cairo_paint(cr);
-}
-#else
-static void askpass_redraw_gdk(GdkWindow *win, struct drawing_area_ctx *ctx)
-{
- GdkGC *gc = gdk_gc_new(win);
- gdk_gc_set_foreground(gc, &ctx->cols[ctx->state]);
- gdk_draw_rectangle(win, gc, true, 0, 0, ctx->width, ctx->height);
- gdk_gc_unref(gc);
-}
-#endif
-
-#if GTK_CHECK_VERSION(3,0,0)
-static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
-{
- struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
- askpass_redraw_cairo(cr, ctx);
- return true;
-}
-#else
-static gint expose_area(GtkWidget *widget, GdkEventExpose *event,
- gpointer data)
-{
- struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data;
-
-#ifdef DRAW_DEFAULT_CAIRO
- cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(ctx->area));
- askpass_redraw_cairo(cr, ctx);
- cairo_destroy(cr);
-#else
- askpass_redraw_gdk(gtk_widget_get_window(ctx->area), ctx);
-#endif
-
- return true;
-}
-#endif
-
-static gboolean try_grab_keyboard(gpointer vctx)
-{
- struct askpass_ctx *ctx = (struct askpass_ctx *)vctx;
- int i, ret;
-
-#if GTK_CHECK_VERSION(3,20,0)
- /*
- * Grabbing the keyboard in GTK 3.20 requires the new notion of
- * GdkSeat.
- */
- GdkSeat *seat;
- GdkWindow *gdkw = gtk_widget_get_window(ctx->dialog);
- if (!GDK_IS_WINDOW(gdkw) || !gdk_window_is_visible(gdkw))
- goto fail;
-
- seat = gdk_display_get_default_seat
- (gtk_widget_get_display(ctx->dialog));
- if (!seat)
- goto fail;
-
- ctx->seat = seat;
- ret = gdk_seat_grab(seat, gdkw, GDK_SEAT_CAPABILITY_KEYBOARD,
- true, NULL, NULL, NULL, NULL);
-
- /*
- * For some reason GDK 3.22 hides the GDK window as a side effect
- * of a failed grab. I've no idea why. But if we're going to retry
- * the grab, then we need to unhide it again or else we'll just
- * get GDK_GRAB_NOT_VIEWABLE on every subsequent attempt.
- */
- if (ret != GDK_GRAB_SUCCESS)
- gdk_window_show(gdkw);
-
-#elif GTK_CHECK_VERSION(3,0,0)
- /*
- * And it has to be done differently again prior to GTK 3.20.
- */
- GdkDeviceManager *dm;
- GdkDevice *pointer, *keyboard;
-
- dm = gdk_display_get_device_manager
- (gtk_widget_get_display(ctx->dialog));
- if (!dm)
- goto fail;
-
- pointer = gdk_device_manager_get_client_pointer(dm);
- if (!pointer)
- goto fail;
- keyboard = gdk_device_get_associated_device(pointer);
- if (!keyboard)
- goto fail;
- if (gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD)
- goto fail;
-
- ctx->keyboard = keyboard;
- ret = gdk_device_grab(ctx->keyboard,
- gtk_widget_get_window(ctx->dialog),
- GDK_OWNERSHIP_NONE,
- true,
- GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
- NULL,
- GDK_CURRENT_TIME);
-#else
- /*
- * It's much simpler in GTK 1 and 2!
- */
- ret = gdk_keyboard_grab(gtk_widget_get_window(ctx->dialog),
- false, GDK_CURRENT_TIME);
-#endif
- if (ret != GDK_GRAB_SUCCESS)
- goto fail;
-
- /*
- * Now that we've got the keyboard grab, connect up our keyboard
- * handlers.
- */
-#if GTK_CHECK_VERSION(2,0,0)
- g_signal_connect(G_OBJECT(ctx->imc), "commit",
- G_CALLBACK(input_method_commit_event), ctx);
-#endif
- g_signal_connect(G_OBJECT(ctx->dialog), "key_press_event",
- G_CALLBACK(key_event), ctx);
- g_signal_connect(G_OBJECT(ctx->dialog), "key_release_event",
- G_CALLBACK(key_event), ctx);
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_im_context_set_client_window(ctx->imc,
- gtk_widget_get_window(ctx->dialog));
-#endif
-
- /*
- * And repaint the key-acknowledgment drawing areas as not greyed
- * out.
- */
- ctx->active_area = keypress_prng_value() % N_DRAWING_AREAS;
- for (i = 0; i < N_DRAWING_AREAS; i++) {
- ctx->drawingareas[i].state =
- (i == ctx->active_area ? CURRENT : NOT_CURRENT);
- gtk_widget_queue_draw(ctx->drawingareas[i].area);
- }
-
- return false;
-
- fail:
- /*
- * If we didn't get the grab, reschedule ourself on a timer to try
- * again later.
- *
- * We have to do this rather than just trying once, because there
- * is at least one important situation in which the grab may fail
- * the first time: any user who is launching an add-key operation
- * off some kind of window manager hotkey will almost by
- * definition be running this script with a keyboard grab already
- * active, namely the one-key grab that the WM (or whatever) uses
- * to detect presses of the hotkey. So at the very least we have
- * to give the user time to release that key.
- */
- if (++ctx->nattempts >= 4) {
- cancel_askpass(ctx, "unable to grab keyboard after 5 seconds");
- } else {
- g_timeout_add(1000/8, try_grab_keyboard, ctx);
- }
- return false;
-}
-
-void realize(GtkWidget *widget, gpointer vctx)
-{
- struct askpass_ctx *ctx = (struct askpass_ctx *)vctx;
-
- gtk_grab_add(ctx->dialog);
-
- /*
- * Schedule the first attempt at the keyboard grab.
- */
- ctx->nattempts = 0;
-#if GTK_CHECK_VERSION(3,20,0)
- ctx->seat = NULL;
-#elif GTK_CHECK_VERSION(3,0,0)
- ctx->keyboard = NULL;
-#endif
-
- g_idle_add(try_grab_keyboard, ctx);
-}
-
-static const char *gtk_askpass_setup(struct askpass_ctx *ctx,
- const char *window_title,
- const char *prompt_text)
-{
- int i;
- GtkBox *action_area;
-
- ctx->passlen = 0;
- ctx->passsize = 2048;
- ctx->passphrase = snewn(ctx->passsize, char);
-
- /*
- * Create widgets.
- */
- ctx->dialog = our_dialog_new();
- gtk_window_set_title(GTK_WINDOW(ctx->dialog), window_title);
- gtk_window_set_position(GTK_WINDOW(ctx->dialog), GTK_WIN_POS_CENTER);
- g_signal_connect(G_OBJECT(ctx->dialog), "delete-event",
- G_CALLBACK(askpass_dialog_closed), ctx);
- ctx->promptlabel = gtk_label_new(prompt_text);
- align_label_left(GTK_LABEL(ctx->promptlabel));
- gtk_widget_show(ctx->promptlabel);
- gtk_label_set_line_wrap(GTK_LABEL(ctx->promptlabel), true);
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_label_set_width_chars(GTK_LABEL(ctx->promptlabel), 48);
-#endif
- int margin = string_width("MM");
-#if GTK_CHECK_VERSION(3,12,0)
- gtk_widget_set_margin_start(ctx->promptlabel, margin);
- gtk_widget_set_margin_end(ctx->promptlabel, margin);
-#else
- gtk_misc_set_padding(GTK_MISC(ctx->promptlabel), margin, 0);
-#endif
- our_dialog_add_to_content_area(GTK_WINDOW(ctx->dialog),
- ctx->promptlabel, true, true, 0);
-#if GTK_CHECK_VERSION(2,0,0)
- ctx->imc = gtk_im_multicontext_new();
-#endif
-#ifndef DRAW_DEFAULT_CAIRO
- {
- gboolean success[2];
- ctx->colmap = gdk_colormap_get_system();
- ctx->cols[0].red = ctx->cols[0].green = ctx->cols[0].blue = 0xFFFF;
- ctx->cols[1].red = ctx->cols[1].green = ctx->cols[1].blue = 0;
- ctx->cols[2].red = ctx->cols[2].green = ctx->cols[2].blue = 0x8000;
- gdk_colormap_alloc_colors(ctx->colmap, ctx->cols, 2,
- false, true, success);
- if (!success[0] || !success[1])
- return "unable to allocate colours";
- }
-#endif
-
- action_area = our_dialog_make_action_hbox(GTK_WINDOW(ctx->dialog));
-
- for (i = 0; i < N_DRAWING_AREAS; i++) {
- ctx->drawingareas[i].area = gtk_drawing_area_new();
-#ifndef DRAW_DEFAULT_CAIRO
- ctx->drawingareas[i].cols = ctx->cols;
-#endif
- ctx->drawingareas[i].state = GREYED_OUT;
- ctx->drawingareas[i].width = ctx->drawingareas[i].height = 0;
- /* It would be nice to choose this size in some more
- * context-sensitive way, like measuring the size of some
- * piece of template text. */
- gtk_widget_set_size_request(ctx->drawingareas[i].area, 32, 32);
- gtk_box_pack_end(action_area, ctx->drawingareas[i].area,
- true, true, 5);
- g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
- "configure_event",
- G_CALLBACK(configure_area),
- &ctx->drawingareas[i]);
-#if GTK_CHECK_VERSION(3,0,0)
- g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
- "draw",
- G_CALLBACK(draw_area),
- &ctx->drawingareas[i]);
-#else
- g_signal_connect(G_OBJECT(ctx->drawingareas[i].area),
- "expose_event",
- G_CALLBACK(expose_area),
- &ctx->drawingareas[i]);
-#endif
-
-#if GTK_CHECK_VERSION(3,0,0)
- g_object_set(G_OBJECT(ctx->drawingareas[i].area),
- "margin-bottom", 8, (const char *)NULL);
-#endif
-
- gtk_widget_show(ctx->drawingareas[i].area);
- }
- ctx->active_area = -1;
-
- /*
- * Arrange to receive key events. We don't really need to worry
- * from a UI perspective about which widget gets the events, as
- * long as we know which it is so we can catch them. So we'll pick
- * the prompt label at random, and we'll use gtk_grab_add to
- * ensure key events go to it.
- */
- gtk_widget_set_sensitive(ctx->dialog, true);
-
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_window_set_keep_above(GTK_WINDOW(ctx->dialog), true);
-#endif
-
- /*
- * Wait for the key-receiving widget to actually be created, in
- * order to call gtk_grab_add on it.
- */
- g_signal_connect(G_OBJECT(ctx->dialog), "realize",
- G_CALLBACK(realize), ctx);
-
- /*
- * Show the window.
- */
- gtk_widget_show(ctx->dialog);
-
- return NULL;
-}
-
-static void gtk_askpass_cleanup(struct askpass_ctx *ctx)
-{
-#if GTK_CHECK_VERSION(3,20,0)
- if (ctx->seat)
- gdk_seat_ungrab(ctx->seat);
-#elif GTK_CHECK_VERSION(3,0,0)
- if (ctx->keyboard)
- gdk_device_ungrab(ctx->keyboard, GDK_CURRENT_TIME);
-#else
- gdk_keyboard_ungrab(GDK_CURRENT_TIME);
-#endif
- gtk_grab_remove(ctx->promptlabel);
-
- if (ctx->passphrase) {
- assert(ctx->passlen < ctx->passsize);
- ctx->passphrase[ctx->passlen] = '\0';
- }
-
- gtk_widget_destroy(ctx->dialog);
-}
-
-static bool setup_gtk(const char *display)
-{
- static bool gtk_initialised = false;
- int argc;
- char *real_argv[3];
- char **argv = real_argv;
- bool ret;
-
- if (gtk_initialised)
- return true;
-
- argc = 0;
- argv[argc++] = dupstr("dummy");
- argv[argc++] = dupprintf("--display=%s", display);
- argv[argc] = NULL;
- ret = gtk_init_check(&argc, &argv);
- while (argc > 0)
- sfree(argv[--argc]);
-
- gtk_initialised = ret;
- return ret;
-}
-
-const bool buildinfo_gtk_relevant = true;
-
-char *gtk_askpass_main(const char *display, const char *wintitle,
- const char *prompt, bool *success)
-{
- struct askpass_ctx ctx[1];
- const char *err;
-
- ctx->passphrase = NULL;
- ctx->error_message = NULL;
-
- /* In case gtk_init hasn't been called yet by the program */
- if (!setup_gtk(display)) {
- *success = false;
- return dupstr("unable to initialise GTK");
- }
-
- if ((err = gtk_askpass_setup(ctx, wintitle, prompt)) != NULL) {
- *success = false;
- return dupprintf("%s", err);
- }
- setup_keypress_prng();
- gtk_main();
- cleanup_keypress_prng();
- gtk_askpass_cleanup(ctx);
-
- if (ctx->passphrase) {
- *success = true;
- return ctx->passphrase;
- } else {
- *success = false;
- return ctx->error_message;
- }
-}
-
-#ifdef TEST_ASKPASS
-void modalfatalbox(const char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "FATAL ERROR: ");
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
- exit(1);
-}
-
-int main(int argc, char **argv)
-{
- bool success;
- int exitcode;
- char *ret;
-
- gtk_init(&argc, &argv);
-
- if (argc != 2) {
- success = false;
- ret = dupprintf("usage: %s <prompt text>", argv[0]);
- } else {
- ret = gtk_askpass_main(NULL, "Enter passphrase", argv[1], &success);
- }
-
- if (!success) {
- fputs(ret, stderr);
- fputc('\n', stderr);
- exitcode = 1;
- } else {
- fputs(ret, stdout);
- fputc('\n', stdout);
- exitcode = 0;
- }
-
- smemclr(ret, strlen(ret));
- return exitcode;
-}
-#endif
diff --git a/unix/gtkcfg.c b/unix/gtkcfg.c
deleted file mode 100644
index 93c48ce6..00000000
--- a/unix/gtkcfg.c
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * gtkcfg.c - the GTK-specific parts of the PuTTY configuration
- * box.
- */
-
-#include <assert.h>
-#include <stdlib.h>
-
-#include "putty.h"
-#include "dialog.h"
-#include "storage.h"
-
-static void about_handler(union control *ctrl, dlgparam *dlg,
- void *data, int event)
-{
- if (event == EVENT_ACTION) {
- about_box(ctrl->generic.context.p);
- }
-}
-
-void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win)
-{
- struct controlset *s, *s2;
- union control *c;
- int i;
-
- if (!midsession) {
- /*
- * Add the About button to the standard panel.
- */
- s = ctrl_getset(b, "", "", "");
- c = ctrl_pushbutton(s, "About", 'a', HELPCTX(no_help),
- about_handler, P(win));
- c->generic.column = 0;
- }
-
- /*
- * GTK makes it rather easier to put the scrollbar on the left
- * than Windows does!
- */
- s = ctrl_getset(b, "Window", "scrollback",
- "Control the scrollback in the window");
- ctrl_checkbox(s, "Scrollbar on left", 'l',
- HELPCTX(no_help),
- conf_checkbox_handler,
- I(CONF_scrollbar_on_left));
- /*
- * Really this wants to go just after `Display scrollbar'. See
- * if we can find that control, and do some shuffling.
- */
- for (i = 0; i < s->ncontrols; i++) {
- c = s->ctrls[i];
- if (c->generic.type == CTRL_CHECKBOX &&
- c->generic.context.i == CONF_scrollbar) {
- /*
- * Control i is the scrollbar checkbox.
- * Control s->ncontrols-1 is the scrollbar-on-left one.
- */
- if (i < s->ncontrols-2) {
- c = s->ctrls[s->ncontrols-1];
- memmove(s->ctrls+i+2, s->ctrls+i+1,
- (s->ncontrols-i-2)*sizeof(union control *));
- s->ctrls[i+1] = c;
- }
- break;
- }
- }
-
- /*
- * X requires three more fonts: bold, wide, and wide-bold; also
- * we need the fiddly shadow-bold-offset control. This would
- * make the Window/Appearance panel rather unwieldy and large,
- * so I think the sensible thing here is to _move_ this
- * controlset into a separate Window/Fonts panel!
- */
- s2 = ctrl_getset(b, "Window/Appearance", "font",
- "Font settings");
- /* Remove this controlset from b. */
- for (i = 0; i < b->nctrlsets; i++) {
- if (b->ctrlsets[i] == s2) {
- memmove(b->ctrlsets+i, b->ctrlsets+i+1,
- (b->nctrlsets-i-1) * sizeof(*b->ctrlsets));
- b->nctrlsets--;
- ctrl_free_set(s2);
- break;
- }
- }
- ctrl_settitle(b, "Window/Fonts", "Options controlling font usage");
- s = ctrl_getset(b, "Window/Fonts", "font",
- "Fonts for displaying non-bold text");
- ctrl_fontsel(s, "Font used for ordinary text", 'f',
- HELPCTX(no_help),
- conf_fontsel_handler, I(CONF_font));
- ctrl_fontsel(s, "Font used for wide (CJK) text", 'w',
- HELPCTX(no_help),
- conf_fontsel_handler, I(CONF_widefont));
- s = ctrl_getset(b, "Window/Fonts", "fontbold",
- "Fonts for displaying bolded text");
- ctrl_fontsel(s, "Font used for bolded text", 'b',
- HELPCTX(no_help),
- conf_fontsel_handler, I(CONF_boldfont));
- ctrl_fontsel(s, "Font used for bold wide text", 'i',
- HELPCTX(no_help),
- conf_fontsel_handler, I(CONF_wideboldfont));
- ctrl_checkbox(s, "Use shadow bold instead of bold fonts", 'u',
- HELPCTX(no_help),
- conf_checkbox_handler,
- I(CONF_shadowbold));
- ctrl_text(s, "(Note that bold fonts or shadow bolding are only"
- " used if you have not requested bolding to be done by"
- " changing the text colour.)",
- HELPCTX(no_help));
- ctrl_editbox(s, "Horizontal offset for shadow bold:", 'z', 20,
- HELPCTX(no_help), conf_editbox_handler,
- I(CONF_shadowboldoffset), I(-1));
-
- /*
- * Markus Kuhn feels, not totally unreasonably, that it's good
- * for all applications to shift into UTF-8 mode if they notice
- * that they've been started with a LANG setting dictating it,
- * so that people don't have to keep remembering a separate
- * UTF-8 option for every application they use. Therefore,
- * here's an override option in the Translation panel.
- */
- s = ctrl_getset(b, "Window/Translation", "trans",
- "Character set translation on received data");
- ctrl_checkbox(s, "Override with UTF-8 if locale says so", 'l',
- HELPCTX(translation_utf8_override),
- conf_checkbox_handler,
- I(CONF_utf8_override));
-
-#ifdef OSX_META_KEY_CONFIG
- /*
- * On OS X, there are multiple reasonable opinions about whether
- * Option or Command (or both, or neither) should act as a Meta
- * key, or whether they should have their normal OS functions.
- */
- s = ctrl_getset(b, "Terminal/Keyboard", "meta",
- "Choose the Meta key:");
- ctrl_checkbox(s, "Option key acts as Meta", 'p',
- HELPCTX(no_help),
- conf_checkbox_handler, I(CONF_osx_option_meta));
- ctrl_checkbox(s, "Command key acts as Meta", 'm',
- HELPCTX(no_help),
- conf_checkbox_handler, I(CONF_osx_command_meta));
-#endif
-
- if (!midsession) {
- /*
- * Allow the user to specify the window class as part of the saved
- * configuration, so that they can have their window manager treat
- * different kinds of PuTTY and pterm differently if they want to.
- */
- s = ctrl_getset(b, "Window/Behaviour", "x11",
- "X Window System settings");
- ctrl_editbox(s, "Window class name:", 'z', 50,
- HELPCTX(no_help), conf_editbox_handler,
- I(CONF_winclass), I(1));
- }
-}
diff --git a/unix/gtkcols.c b/unix/gtkcols.c
deleted file mode 100644
index 35c02875..00000000
--- a/unix/gtkcols.c
+++ /dev/null
@@ -1,1201 +0,0 @@
-/*
- * gtkcols.c - implementation of the `Columns' GTK layout container.
- */
-
-#include <assert.h>
-#include <gtk/gtk.h>
-#include "defs.h"
-#include "gtkcompat.h"
-#include "gtkcols.h"
-
-#if GTK_CHECK_VERSION(2,0,0)
-/* The "focus" method lives in GtkWidget from GTK 2 onwards, but it
- * was in GtkContainer in GTK 1 */
-#define FOCUS_METHOD_SUPERCLASS GtkWidget
-#define FOCUS_METHOD_LOCATION widget_class /* used in columns_init */
-#define CHILD_FOCUS(cont, dir) gtk_widget_child_focus(GTK_WIDGET(cont), dir)
-#else
-#define FOCUS_METHOD_SUPERCLASS GtkContainer
-#define FOCUS_METHOD_LOCATION container_class
-#define CHILD_FOCUS(cont, dir) gtk_container_focus(GTK_CONTAINER(cont), dir)
-#endif
-
-static void columns_init(Columns *cols);
-static void columns_class_init(ColumnsClass *klass);
-#if !GTK_CHECK_VERSION(2,0,0)
-static void columns_finalize(GtkObject *object);
-#else
-static void columns_finalize(GObject *object);
-#endif
-static void columns_map(GtkWidget *widget);
-static void columns_unmap(GtkWidget *widget);
-#if !GTK_CHECK_VERSION(2,0,0)
-static void columns_draw(GtkWidget *widget, GdkRectangle *area);
-static gint columns_expose(GtkWidget *widget, GdkEventExpose *event);
-#endif
-static void columns_base_add(GtkContainer *container, GtkWidget *widget);
-static void columns_remove(GtkContainer *container, GtkWidget *widget);
-static void columns_forall(GtkContainer *container, gboolean include_internals,
- GtkCallback callback, gpointer callback_data);
-static gint columns_focus(FOCUS_METHOD_SUPERCLASS *container,
- GtkDirectionType dir);
-static GType columns_child_type(GtkContainer *container);
-#if GTK_CHECK_VERSION(3,0,0)
-static void columns_get_preferred_width(GtkWidget *widget,
- gint *min, gint *nat);
-static void columns_get_preferred_height(GtkWidget *widget,
- gint *min, gint *nat);
-static void columns_get_preferred_width_for_height(GtkWidget *widget,
- gint height,
- gint *min, gint *nat);
-static void columns_get_preferred_height_for_width(GtkWidget *widget,
- gint width,
- gint *min, gint *nat);
-#else
-static void columns_size_request(GtkWidget *widget, GtkRequisition *req);
-#endif
-static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc);
-
-static GtkContainerClass *parent_class = NULL;
-
-#if !GTK_CHECK_VERSION(2,0,0)
-GType columns_get_type(void)
-{
- static GType columns_type = 0;
-
- if (!columns_type) {
- static const GtkTypeInfo columns_info = {
- "Columns",
- sizeof(Columns),
- sizeof(ColumnsClass),
- (GtkClassInitFunc) columns_class_init,
- (GtkObjectInitFunc) columns_init,
- /* reserved_1 */ NULL,
- /* reserved_2 */ NULL,
- (GtkClassInitFunc) NULL,
- };
-
- columns_type = gtk_type_unique(GTK_TYPE_CONTAINER, &columns_info);
- }
-
- return columns_type;
-}
-#else
-GType columns_get_type(void)
-{
- static GType columns_type = 0;
-
- if (!columns_type) {
- static const GTypeInfo columns_info = {
- sizeof(ColumnsClass),
- NULL,
- NULL,
- (GClassInitFunc) columns_class_init,
- NULL,
- NULL,
- sizeof(Columns),
- 0,
- (GInstanceInitFunc)columns_init,
- };
-
- columns_type = g_type_register_static(GTK_TYPE_CONTAINER, "Columns",
- &columns_info, 0);
- }
-
- return columns_type;
-}
-#endif
-
-static gint (*columns_inherited_focus)(FOCUS_METHOD_SUPERCLASS *container,
- GtkDirectionType direction);
-
-static void columns_class_init(ColumnsClass *klass)
-{
-#if !GTK_CHECK_VERSION(2,0,0)
- GtkObjectClass *object_class = (GtkObjectClass *)klass;
- GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
- GtkContainerClass *container_class = (GtkContainerClass *)klass;
-#else
- GObjectClass *object_class = G_OBJECT_CLASS(klass);
- GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
- GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
-#endif
-
-#if !GTK_CHECK_VERSION(2,0,0)
- parent_class = gtk_type_class(GTK_TYPE_CONTAINER);
-#else
- parent_class = g_type_class_peek_parent(klass);
-#endif
-
- object_class->finalize = columns_finalize;
- widget_class->map = columns_map;
- widget_class->unmap = columns_unmap;
-#if !GTK_CHECK_VERSION(2,0,0)
- widget_class->draw = columns_draw;
- widget_class->expose_event = columns_expose;
-#endif
-#if GTK_CHECK_VERSION(3,0,0)
- widget_class->get_preferred_width = columns_get_preferred_width;
- widget_class->get_preferred_height = columns_get_preferred_height;
- widget_class->get_preferred_width_for_height =
- columns_get_preferred_width_for_height;
- widget_class->get_preferred_height_for_width =
- columns_get_preferred_height_for_width;
-#else
- widget_class->size_request = columns_size_request;
-#endif
- widget_class->size_allocate = columns_size_allocate;
-
- container_class->add = columns_base_add;
- container_class->remove = columns_remove;
- container_class->forall = columns_forall;
- container_class->child_type = columns_child_type;
-
- /* Save the previous value of this method. */
- if (!columns_inherited_focus)
- columns_inherited_focus = FOCUS_METHOD_LOCATION->focus;
- FOCUS_METHOD_LOCATION->focus = columns_focus;
-}
-
-static void columns_init(Columns *cols)
-{
- gtk_widget_set_has_window(GTK_WIDGET(cols), false);
-
- cols->children = NULL;
- cols->spacing = 0;
-}
-
-static void columns_child_free(gpointer vchild)
-{
- ColumnsChild *child = (ColumnsChild *)vchild;
- if (child->percentages)
- g_free(child->percentages);
- g_free(child);
-}
-
-static void columns_finalize(
-#if !GTK_CHECK_VERSION(2,0,0)
- GtkObject *object
-#else
- GObject *object
-#endif
- )
-{
- Columns *cols;
-
- g_return_if_fail(object != NULL);
- g_return_if_fail(IS_COLUMNS(object));
-
- cols = COLUMNS(object);
-
-#if !GTK_CHECK_VERSION(2,0,0)
- {
- GList *node;
- for (node = cols->children; node; node = node->next)
- if (node->data)
- columns_child_free(node->data);
- }
- g_list_free(cols->children);
-#else
- g_list_free_full(cols->children, columns_child_free);
-#endif
-
- cols->children = NULL;
-
-#if !GTK_CHECK_VERSION(2,0,0)
- GTK_OBJECT_CLASS(parent_class)->finalize(object);
-#else
- G_OBJECT_CLASS(parent_class)->finalize(object);
-#endif
-}
-
-/*
- * These appear to be thoroughly tedious functions; the only reason
- * we have to reimplement them at all is because we defined our own
- * format for our GList of children...
- */
-static void columns_map(GtkWidget *widget)
-{
- Columns *cols;
- ColumnsChild *child;
- GList *children;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
-
- cols = COLUMNS(widget);
- gtk_widget_set_mapped(GTK_WIDGET(cols), true);
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
- if (child->widget &&
- gtk_widget_get_visible(child->widget) &&
- !gtk_widget_get_mapped(child->widget))
- gtk_widget_map(child->widget);
- }
-}
-static void columns_unmap(GtkWidget *widget)
-{
- Columns *cols;
- ColumnsChild *child;
- GList *children;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
-
- cols = COLUMNS(widget);
- gtk_widget_set_mapped(GTK_WIDGET(cols), false);
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
- if (child->widget &&
- gtk_widget_get_visible(child->widget) &&
- gtk_widget_get_mapped(child->widget))
- gtk_widget_unmap(child->widget);
- }
-}
-#if !GTK_CHECK_VERSION(2,0,0)
-static void columns_draw(GtkWidget *widget, GdkRectangle *area)
-{
- Columns *cols;
- ColumnsChild *child;
- GList *children;
- GdkRectangle child_area;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
-
- if (GTK_WIDGET_DRAWABLE(widget)) {
- cols = COLUMNS(widget);
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
- if (child->widget &&
- GTK_WIDGET_DRAWABLE(child->widget) &&
- gtk_widget_intersect(child->widget, area, &child_area))
- gtk_widget_draw(child->widget, &child_area);
- }
- }
-}
-static gint columns_expose(GtkWidget *widget, GdkEventExpose *event)
-{
- Columns *cols;
- ColumnsChild *child;
- GList *children;
- GdkEventExpose child_event;
-
- g_return_val_if_fail(widget != NULL, false);
- g_return_val_if_fail(IS_COLUMNS(widget), false);
- g_return_val_if_fail(event != NULL, false);
-
- if (GTK_WIDGET_DRAWABLE(widget)) {
- cols = COLUMNS(widget);
- child_event = *event;
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
- if (child->widget &&
- GTK_WIDGET_DRAWABLE(child->widget) &&
- GTK_WIDGET_NO_WINDOW(child->widget) &&
- gtk_widget_intersect(child->widget, &event->area,
- &child_event.area))
- gtk_widget_event(child->widget, (GdkEvent *)&child_event);
- }
- }
- return false;
-}
-#endif
-
-static void columns_base_add(GtkContainer *container, GtkWidget *widget)
-{
- Columns *cols;
-
- g_return_if_fail(container != NULL);
- g_return_if_fail(IS_COLUMNS(container));
- g_return_if_fail(widget != NULL);
-
- cols = COLUMNS(container);
-
- /*
- * Default is to add a new widget spanning all columns.
- */
- columns_add(cols, widget, 0, 0); /* 0 means ncols */
-}
-
-static void columns_remove(GtkContainer *container, GtkWidget *widget)
-{
- Columns *cols;
- ColumnsChild *child;
- GtkWidget *childw;
- GList *children;
- bool was_visible;
-
- g_return_if_fail(container != NULL);
- g_return_if_fail(IS_COLUMNS(container));
- g_return_if_fail(widget != NULL);
-
- cols = COLUMNS(container);
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
- if (child->widget != widget)
- continue;
-
- was_visible = gtk_widget_get_visible(widget);
- gtk_widget_unparent(widget);
- cols->children = g_list_remove_link(cols->children, children);
- g_list_free(children);
-
- if (child->same_height_as) {
- g_return_if_fail(child->same_height_as->same_height_as == child);
- child->same_height_as->same_height_as = NULL;
- if (gtk_widget_get_visible(child->same_height_as->widget))
- gtk_widget_queue_resize(GTK_WIDGET(container));
- }
-
- g_free(child);
- if (was_visible)
- gtk_widget_queue_resize(GTK_WIDGET(container));
- break;
- }
-
- for (children = cols->taborder;
- children && (childw = children->data);
- children = children->next) {
- if (childw != widget)
- continue;
-
- cols->taborder = g_list_remove_link(cols->taborder, children);
- g_list_free(children);
- break;
- }
-}
-
-static void columns_forall(GtkContainer *container, gboolean include_internals,
- GtkCallback callback, gpointer callback_data)
-{
- Columns *cols;
- ColumnsChild *child;
- GList *children, *next;
-
- g_return_if_fail(container != NULL);
- g_return_if_fail(IS_COLUMNS(container));
- g_return_if_fail(callback != NULL);
-
- cols = COLUMNS(container);
-
- for (children = cols->children;
- children && (child = children->data);
- children = next) {
- /*
- * We can't wait until after the callback to assign
- * `children = children->next', because the callback might
- * be gtk_widget_destroy, which would remove the link
- * `children' from the list! So instead we must get our
- * hands on the value of the `next' pointer _before_ the
- * callback.
- */
- next = children->next;
- if (child->widget)
- callback(child->widget, callback_data);
- }
-}
-
-static GType columns_child_type(GtkContainer *container)
-{
- return GTK_TYPE_WIDGET;
-}
-
-GtkWidget *columns_new(gint spacing)
-{
- Columns *cols;
-
-#if !GTK_CHECK_VERSION(2,0,0)
- cols = gtk_type_new(columns_get_type());
-#else
- cols = g_object_new(TYPE_COLUMNS, NULL);
-#endif
-
- cols->spacing = spacing;
-
- return GTK_WIDGET(cols);
-}
-
-void columns_set_cols(Columns *cols, gint ncols, const gint *percentages)
-{
- ColumnsChild *childdata;
- gint i;
-
- g_return_if_fail(cols != NULL);
- g_return_if_fail(IS_COLUMNS(cols));
- g_return_if_fail(ncols > 0);
- g_return_if_fail(percentages != NULL);
-
- childdata = g_new(ColumnsChild, 1);
- childdata->widget = NULL;
- childdata->ncols = ncols;
- childdata->percentages = g_new(gint, ncols);
- childdata->force_left = false;
- for (i = 0; i < ncols; i++)
- childdata->percentages[i] = percentages[i];
-
- cols->children = g_list_append(cols->children, childdata);
-}
-
-void columns_add(Columns *cols, GtkWidget *child,
- gint colstart, gint colspan)
-{
- ColumnsChild *childdata;
-
- g_return_if_fail(cols != NULL);
- g_return_if_fail(IS_COLUMNS(cols));
- g_return_if_fail(child != NULL);
- g_return_if_fail(gtk_widget_get_parent(child) == NULL);
-
- childdata = g_new(ColumnsChild, 1);
- childdata->widget = child;
- childdata->colstart = colstart;
- childdata->colspan = colspan;
- childdata->force_left = false;
- childdata->same_height_as = NULL;
- childdata->percentages = NULL;
-
- cols->children = g_list_append(cols->children, childdata);
- cols->taborder = g_list_append(cols->taborder, child);
-
- gtk_widget_set_parent(child, GTK_WIDGET(cols));
-
- if (gtk_widget_get_realized(GTK_WIDGET(cols)))
- gtk_widget_realize(child);
-
- if (gtk_widget_get_visible(GTK_WIDGET(cols)) &&
- gtk_widget_get_visible(child)) {
- if (gtk_widget_get_mapped(GTK_WIDGET(cols)))
- gtk_widget_map(child);
- gtk_widget_queue_resize(child);
- }
-}
-
-static ColumnsChild *columns_find_child(Columns *cols, GtkWidget *widget)
-{
- GList *children;
- ColumnsChild *child;
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
-
- if (child->widget == widget)
- return child;
- }
-
- return NULL;
-}
-
-void columns_force_left_align(Columns *cols, GtkWidget *widget)
-{
- ColumnsChild *child;
-
- g_return_if_fail(cols != NULL);
- g_return_if_fail(IS_COLUMNS(cols));
- g_return_if_fail(widget != NULL);
-
- child = columns_find_child(cols, widget);
- g_return_if_fail(child != NULL);
-
- child->force_left = true;
- if (gtk_widget_get_visible(widget))
- gtk_widget_queue_resize(GTK_WIDGET(cols));
-}
-
-void columns_force_same_height(Columns *cols, GtkWidget *cw1, GtkWidget *cw2)
-{
- ColumnsChild *child1, *child2;
-
- g_return_if_fail(cols != NULL);
- g_return_if_fail(IS_COLUMNS(cols));
- g_return_if_fail(cw1 != NULL);
- g_return_if_fail(cw2 != NULL);
-
- child1 = columns_find_child(cols, cw1);
- g_return_if_fail(child1 != NULL);
- child2 = columns_find_child(cols, cw2);
- g_return_if_fail(child2 != NULL);
-
- child1->same_height_as = child2;
- child2->same_height_as = child1;
- if (gtk_widget_get_visible(cw1) || gtk_widget_get_visible(cw2))
- gtk_widget_queue_resize(GTK_WIDGET(cols));
-}
-
-void columns_taborder_last(Columns *cols, GtkWidget *widget)
-{
- GtkWidget *childw;
- GList *children;
-
- g_return_if_fail(cols != NULL);
- g_return_if_fail(IS_COLUMNS(cols));
- g_return_if_fail(widget != NULL);
-
- for (children = cols->taborder;
- children && (childw = children->data);
- children = children->next) {
- if (childw != widget)
- continue;
-
- cols->taborder = g_list_remove_link(cols->taborder, children);
- g_list_free(children);
- cols->taborder = g_list_append(cols->taborder, widget);
- break;
- }
-}
-
-/*
- * Override GtkContainer's focus movement so the user can
- * explicitly specify the tab order.
- */
-static gint columns_focus(FOCUS_METHOD_SUPERCLASS *super, GtkDirectionType dir)
-{
- Columns *cols;
- GList *pos;
- GtkWidget *focuschild;
-
- g_return_val_if_fail(super != NULL, false);
- g_return_val_if_fail(IS_COLUMNS(super), false);
-
- cols = COLUMNS(super);
-
- if (!gtk_widget_is_drawable(GTK_WIDGET(cols)) ||
- !gtk_widget_is_sensitive(GTK_WIDGET(cols)))
- return false;
-
- if (!gtk_widget_get_can_focus(GTK_WIDGET(cols)) &&
- (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) {
-
- focuschild = gtk_container_get_focus_child(GTK_CONTAINER(cols));
- gtk_container_set_focus_child(GTK_CONTAINER(cols), NULL);
-
- if (dir == GTK_DIR_TAB_FORWARD)
- pos = cols->taborder;
- else
- pos = g_list_last(cols->taborder);
-
- while (pos) {
- GtkWidget *child = pos->data;
-
- if (focuschild) {
- if (focuschild == child) {
- focuschild = NULL; /* now we can start looking in here */
- if (gtk_widget_is_drawable(child) &&
- GTK_IS_CONTAINER(child) &&
- !gtk_widget_has_focus(child)) {
- if (CHILD_FOCUS(child, dir))
- return true;
- }
- }
- } else if (gtk_widget_is_drawable(child)) {
- if (GTK_IS_CONTAINER(child)) {
- if (CHILD_FOCUS(child, dir))
- return true;
- } else if (gtk_widget_get_can_focus(child)) {
- gtk_widget_grab_focus(child);
- return true;
- }
- }
-
- if (dir == GTK_DIR_TAB_FORWARD)
- pos = pos->next;
- else
- pos = pos->prev;
- }
-
- return false;
- } else
- return columns_inherited_focus(super, dir);
-}
-
-/*
- * Underlying parts of the layout algorithm, to compute the Columns
- * container's width or height given the widths or heights of its
- * children. These will be called in various ways with different
- * notions of width and height in use, so we abstract them out and
- * pass them a 'get width' or 'get height' function pointer.
- */
-
-typedef gint (*widget_dim_fn_t)(ColumnsChild *child);
-
-static gint columns_compute_width(Columns *cols, widget_dim_fn_t get_width)
-{
- ColumnsChild *child;
- GList *children;
- gint i, ncols, colspan, retwidth, childwidth;
- const gint *percentages;
- static const gint onecol[] = { 100 };
-
-#ifdef COLUMNS_WIDTH_DIAGNOSTICS
- printf("compute_width(%p): start\n", cols);
-#endif
-
- retwidth = 0;
-
- ncols = 1;
- percentages = onecol;
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
-
- if (!child->widget) {
- /* Column reconfiguration. */
- ncols = child->ncols;
- percentages = child->percentages;
- continue;
- }
-
- /* Only take visible widgets into account. */
- if (!gtk_widget_get_visible(child->widget))
- continue;
-
- childwidth = get_width(child);
- colspan = child->colspan ? child->colspan : ncols-child->colstart;
- assert(colspan > 0);
-
-#ifdef COLUMNS_WIDTH_DIAGNOSTICS
- printf("compute_width(%p): ", cols);
- if (GTK_IS_LABEL(child->widget))
- printf("label %p '%s' wrap=%s: ", child->widget,
- gtk_label_get_text(GTK_LABEL(child->widget)),
- (gtk_label_get_line_wrap(GTK_LABEL(child->widget))
- ? "true" : "false"));
- else
- printf("widget %p: ", child->widget);
- {
- gint min, nat;
- gtk_widget_get_preferred_width(child->widget, &min, &nat);
- printf("minwidth=%d natwidth=%d ", min, nat);
- }
- printf("thiswidth=%d span=%d\n", childwidth, colspan);
-#endif
-
- /*
- * To compute width: we know that childwidth + cols->spacing
- * needs to equal a certain percentage of the full width of
- * the container. So we work this value out, figure out how
- * wide the container will need to be to make that percentage
- * of it equal to that width, and ensure our returned width is
- * at least that much. Very simple really.
- */
- {
- int percent, thiswid, fullwid;
-
- percent = 0;
- for (i = 0; i < colspan; i++)
- percent += percentages[child->colstart+i];
-
- thiswid = childwidth + cols->spacing;
- /*
- * Since childwidth is (at least sometimes) the _minimum_
- * size the child needs, we must ensure that it gets _at
- * least_ that size. Hence, when scaling thiswid up to
- * fullwid, we must round up, which means adding percent-1
- * before dividing by percent.
- */
- fullwid = (thiswid * 100 + percent - 1) / percent;
-#ifdef COLUMNS_WIDTH_DIAGNOSTICS
- printf("compute_width(%p): after %p, thiswid=%d fullwid=%d\n",
- cols, child->widget, thiswid, fullwid);
-#endif
-
- /*
- * The above calculation assumes every widget gets
- * cols->spacing on the right. So we subtract
- * cols->spacing here to account for the extra load of
- * spacing on the right.
- */
- if (retwidth < fullwid - cols->spacing)
- retwidth = fullwid - cols->spacing;
- }
- }
-
- retwidth += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
-
-#ifdef COLUMNS_WIDTH_DIAGNOSTICS
- printf("compute_width(%p): done, returning %d\n", cols, retwidth);
-#endif
-
- return retwidth;
-}
-
-static void columns_alloc_horiz(Columns *cols, gint ourwidth,
- widget_dim_fn_t get_width)
-{
- ColumnsChild *child;
- GList *children;
- gint i, ncols, colspan, border, *colxpos, childwidth;
-
- border = gtk_container_get_border_width(GTK_CONTAINER(cols));
-
- ncols = 1;
- /* colxpos gives the starting x position of each column.
- * We supply n+1 of them, so that we can find the RH edge easily.
- * All ending x positions are expected to be adjusted afterwards by
- * subtracting the spacing. */
- colxpos = g_new(gint, 2);
- colxpos[0] = 0;
- colxpos[1] = ourwidth - 2*border + cols->spacing;
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
-
- if (!child->widget) {
- gint percent;
-
- /* Column reconfiguration. */
- ncols = child->ncols;
- colxpos = g_renew(gint, colxpos, ncols + 1);
- colxpos[0] = 0;
- percent = 0;
- for (i = 0; i < ncols; i++) {
- percent += child->percentages[i];
- colxpos[i+1] = (((ourwidth - 2*border) + cols->spacing)
- * percent / 100);
- }
- continue;
- }
-
- /* Only take visible widgets into account. */
- if (!gtk_widget_get_visible(child->widget))
- continue;
-
- childwidth = get_width(child);
- colspan = child->colspan ? child->colspan : ncols-child->colstart;
-
- /*
- * Starting x position is cols[colstart].
- * Ending x position is cols[colstart+colspan] - spacing.
- *
- * Unless we're forcing left, in which case the width is
- * exactly the requisition width.
- */
- child->x = colxpos[child->colstart];
- if (child->force_left)
- child->w = childwidth;
- else
- child->w = (colxpos[child->colstart+colspan] -
- colxpos[child->colstart] - cols->spacing);
- }
-
- g_free(colxpos);
-}
-
-static gint columns_compute_height(Columns *cols, widget_dim_fn_t get_height)
-{
- ColumnsChild *child;
- GList *children;
- gint i, ncols, colspan, *colypos, retheight, childheight;
-
- retheight = cols->spacing;
-
- ncols = 1;
- colypos = g_new(gint, 1);
- colypos[0] = 0;
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
-
- if (!child->widget) {
- /* Column reconfiguration. */
- for (i = 1; i < ncols; i++) {
- if (colypos[0] < colypos[i])
- colypos[0] = colypos[i];
- }
- ncols = child->ncols;
- colypos = g_renew(gint, colypos, ncols);
- for (i = 1; i < ncols; i++)
- colypos[i] = colypos[0];
- continue;
- }
-
- /* Only take visible widgets into account. */
- if (!gtk_widget_get_visible(child->widget))
- continue;
-
- childheight = get_height(child);
- if (child->same_height_as) {
- gint childheight2 = get_height(child->same_height_as);
- if (childheight < childheight2)
- childheight = childheight2;
- }
- colspan = child->colspan ? child->colspan : ncols-child->colstart;
-
- /*
- * To compute height: the widget's top will be positioned at
- * the largest y value so far reached in any of the columns it
- * crosses. Then it will go down by childheight plus padding;
- * and the point it reaches at the bottom is the new y value
- * in all those columns, and minus the padding it is also a
- * lower bound on our own height.
- */
- {
- int topy, boty;
-
- topy = 0;
- for (i = 0; i < colspan; i++) {
- if (topy < colypos[child->colstart+i])
- topy = colypos[child->colstart+i];
- }
- boty = topy + childheight + cols->spacing;
- for (i = 0; i < colspan; i++) {
- colypos[child->colstart+i] = boty;
- }
-
- if (retheight < boty - cols->spacing)
- retheight = boty - cols->spacing;
- }
- }
-
- retheight += 2*gtk_container_get_border_width(GTK_CONTAINER(cols));
-
- g_free(colypos);
-
- return retheight;
-}
-
-static void columns_alloc_vert(Columns *cols, gint ourheight,
- widget_dim_fn_t get_height)
-{
- ColumnsChild *child;
- GList *children;
- gint i, ncols, colspan, *colypos, realheight, fakeheight;
-
- ncols = 1;
- /* As in size_request, colypos is the lowest y reached in each column. */
- colypos = g_new(gint, 1);
- colypos[0] = 0;
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
- if (!child->widget) {
- /* Column reconfiguration. */
- for (i = 1; i < ncols; i++) {
- if (colypos[0] < colypos[i])
- colypos[0] = colypos[i];
- }
- ncols = child->ncols;
- colypos = g_renew(gint, colypos, ncols);
- for (i = 1; i < ncols; i++)
- colypos[i] = colypos[0];
- continue;
- }
-
- /* Only take visible widgets into account. */
- if (!gtk_widget_get_visible(child->widget))
- continue;
-
- realheight = fakeheight = get_height(child);
- if (child->same_height_as) {
- gint childheight2 = get_height(child->same_height_as);
- if (fakeheight < childheight2)
- fakeheight = childheight2;
- }
- colspan = child->colspan ? child->colspan : ncols-child->colstart;
-
- /*
- * To compute height: the widget's top will be positioned
- * at the largest y value so far reached in any of the
- * columns it crosses. Then it will go down by creq.height
- * plus padding; and the point it reaches at the bottom is
- * the new y value in all those columns.
- */
- {
- int topy, boty;
-
- topy = 0;
- for (i = 0; i < colspan; i++) {
- if (topy < colypos[child->colstart+i])
- topy = colypos[child->colstart+i];
- }
- child->y = topy + fakeheight/2 - realheight/2;
- child->h = realheight;
- boty = topy + fakeheight + cols->spacing;
- for (i = 0; i < colspan; i++) {
- colypos[child->colstart+i] = boty;
- }
- }
- }
-
- g_free(colypos);
-}
-
-/*
- * Now here comes the interesting bit. The actual layout part is
- * done in the following two functions:
- *
- * columns_size_request() examines the list of widgets held in the
- * Columns, and returns a requisition stating the absolute minimum
- * size it can bear to be.
- *
- * columns_size_allocate() is given an allocation telling it what
- * size the whole container is going to be, and it calls
- * gtk_widget_size_allocate() on all of its (visible) children to
- * set their size and position relative to the top left of the
- * container.
- */
-
-#if !GTK_CHECK_VERSION(3,0,0)
-
-static gint columns_gtk2_get_width(ColumnsChild *child)
-{
- GtkRequisition creq;
- gtk_widget_size_request(child->widget, &creq);
- return creq.width;
-}
-
-static gint columns_gtk2_get_height(ColumnsChild *child)
-{
- GtkRequisition creq;
- gtk_widget_size_request(child->widget, &creq);
- return creq.height;
-}
-
-static void columns_size_request(GtkWidget *widget, GtkRequisition *req)
-{
- Columns *cols;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
- g_return_if_fail(req != NULL);
-
- cols = COLUMNS(widget);
-
- req->width = columns_compute_width(cols, columns_gtk2_get_width);
- req->height = columns_compute_height(cols, columns_gtk2_get_height);
-}
-
-static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
-{
- Columns *cols;
- ColumnsChild *child;
- GList *children;
- gint border;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
- g_return_if_fail(alloc != NULL);
-
- cols = COLUMNS(widget);
- gtk_widget_set_allocation(widget, alloc);
-
- border = gtk_container_get_border_width(GTK_CONTAINER(cols));
-
- columns_alloc_horiz(cols, alloc->width, columns_gtk2_get_width);
- columns_alloc_vert(cols, alloc->height, columns_gtk2_get_height);
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
- if (child->widget && gtk_widget_get_visible(child->widget)) {
- GtkAllocation call;
- call.x = alloc->x + border + child->x;
- call.y = alloc->y + border + child->y;
- call.width = child->w;
- call.height = child->h;
- gtk_widget_size_allocate(child->widget, &call);
- }
- }
-}
-
-#else /* GTK_CHECK_VERSION(3,0,0) */
-
-static gint columns_gtk3_get_min_width(ColumnsChild *child)
-{
- gint ret;
- gtk_widget_get_preferred_width(child->widget, &ret, NULL);
- return ret;
-}
-
-static gint columns_gtk3_get_nat_width(ColumnsChild *child)
-{
- gint ret;
-
- if ((GTK_IS_LABEL(child->widget) &&
- gtk_label_get_line_wrap(GTK_LABEL(child->widget))) ||
- GTK_IS_ENTRY(child->widget)) {
- /*
- * We treat wrapping GtkLabels as a special case in this
- * layout class, because the whole point of those is that I
- * _don't_ want them to take up extra horizontal space for
- * long text, but instead to wrap it to whatever size is used
- * by the rest of the layout.
- *
- * GtkEntry gets similar treatment, because in OS X GTK I've
- * found that it requests a natural width regardless of the
- * output of gtk_entry_set_width_chars.
- */
- gtk_widget_get_preferred_width(child->widget, &ret, NULL);
- } else {
- gtk_widget_get_preferred_width(child->widget, NULL, &ret);
- }
- return ret;
-}
-
-static gint columns_gtk3_get_minfh_width(ColumnsChild *child)
-{
- gint ret;
- gtk_widget_get_preferred_width_for_height(child->widget, child->h,
- &ret, NULL);
- return ret;
-}
-
-static gint columns_gtk3_get_natfh_width(ColumnsChild *child)
-{
- gint ret;
- gtk_widget_get_preferred_width_for_height(child->widget, child->h,
- NULL, &ret);
- return ret;
-}
-
-static gint columns_gtk3_get_min_height(ColumnsChild *child)
-{
- gint ret;
- gtk_widget_get_preferred_height(child->widget, &ret, NULL);
- return ret;
-}
-
-static gint columns_gtk3_get_nat_height(ColumnsChild *child)
-{
- gint ret;
- gtk_widget_get_preferred_height(child->widget, NULL, &ret);
- return ret;
-}
-
-static gint columns_gtk3_get_minfw_height(ColumnsChild *child)
-{
- gint ret;
- gtk_widget_get_preferred_height_for_width(child->widget, child->w,
- &ret, NULL);
- return ret;
-}
-
-static gint columns_gtk3_get_natfw_height(ColumnsChild *child)
-{
- gint ret;
- gtk_widget_get_preferred_height_for_width(child->widget, child->w,
- NULL, &ret);
- return ret;
-}
-
-static void columns_get_preferred_width(GtkWidget *widget,
- gint *min, gint *nat)
-{
- Columns *cols;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
-
- cols = COLUMNS(widget);
-
- if (min)
- *min = columns_compute_width(cols, columns_gtk3_get_min_width);
- if (nat)
- *nat = columns_compute_width(cols, columns_gtk3_get_nat_width);
-}
-
-static void columns_get_preferred_height(GtkWidget *widget,
- gint *min, gint *nat)
-{
- Columns *cols;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
-
- cols = COLUMNS(widget);
-
- if (min)
- *min = columns_compute_height(cols, columns_gtk3_get_min_height);
- if (nat)
- *nat = columns_compute_height(cols, columns_gtk3_get_nat_height);
-}
-
-static void columns_get_preferred_width_for_height(GtkWidget *widget,
- gint height,
- gint *min, gint *nat)
-{
- Columns *cols;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
-
- cols = COLUMNS(widget);
-
- /* FIXME: which one should the get-height function here be? */
- columns_alloc_vert(cols, height, columns_gtk3_get_nat_height);
-
- if (min)
- *min = columns_compute_width(cols, columns_gtk3_get_minfh_width);
- if (nat)
- *nat = columns_compute_width(cols, columns_gtk3_get_natfh_width);
-}
-
-static void columns_get_preferred_height_for_width(GtkWidget *widget,
- gint width,
- gint *min, gint *nat)
-{
- Columns *cols;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
-
- cols = COLUMNS(widget);
-
- /* FIXME: which one should the get-height function here be? */
- columns_alloc_horiz(cols, width, columns_gtk3_get_nat_width);
-
- if (min)
- *min = columns_compute_height(cols, columns_gtk3_get_minfw_height);
- if (nat)
- *nat = columns_compute_height(cols, columns_gtk3_get_natfw_height);
-}
-
-static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc)
-{
- Columns *cols;
- ColumnsChild *child;
- GList *children;
- gint border;
-
- g_return_if_fail(widget != NULL);
- g_return_if_fail(IS_COLUMNS(widget));
- g_return_if_fail(alloc != NULL);
-
- cols = COLUMNS(widget);
- gtk_widget_set_allocation(widget, alloc);
-
- border = gtk_container_get_border_width(GTK_CONTAINER(cols));
-
- columns_alloc_horiz(cols, alloc->width, columns_gtk3_get_min_width);
- columns_alloc_vert(cols, alloc->height, columns_gtk3_get_minfw_height);
-
- for (children = cols->children;
- children && (child = children->data);
- children = children->next) {
- if (child->widget && gtk_widget_get_visible(child->widget)) {
- GtkAllocation call;
- call.x = alloc->x + border + child->x;
- call.y = alloc->y + border + child->y;
- call.width = child->w;
- call.height = child->h;
- gtk_widget_size_allocate(child->widget, &call);
- }
- }
-}
-
-#endif
diff --git a/unix/gtkcols.h b/unix/gtkcols.h
deleted file mode 100644
index e589cf79..00000000
--- a/unix/gtkcols.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * gtkcols.h - header file for a columns-based widget container
- * capable of supporting the PuTTY portable dialog box layout
- * mechanism.
- */
-
-#ifndef COLUMNS_H
-#define COLUMNS_H
-
-#include <gdk/gdk.h>
-#include <gtk/gtk.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-#define TYPE_COLUMNS (columns_get_type())
-#define COLUMNS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_COLUMNS, Columns))
-#define COLUMNS_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_COLUMNS, ColumnsClass))
-#define IS_COLUMNS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_COLUMNS))
-#define IS_COLUMNS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_COLUMNS))
-
-typedef struct Columns_tag Columns;
-typedef struct ColumnsClass_tag ColumnsClass;
-typedef struct ColumnsChild_tag ColumnsChild;
-
-struct Columns_tag {
- GtkContainer container;
- /* private after here */
- GList *children; /* this holds ColumnsChild structures */
- GList *taborder; /* this just holds GtkWidgets */
- gint spacing;
-};
-
-struct ColumnsClass_tag {
- GtkContainerClass parent_class;
-};
-
-struct ColumnsChild_tag {
- /* If `widget' is non-NULL, this entry represents an actual widget. */
- GtkWidget *widget;
- gint colstart, colspan;
- bool force_left; /* for recalcitrant GtkLabels */
- ColumnsChild *same_height_as;
- /* Otherwise, this entry represents a change in the column setup. */
- gint ncols;
- gint *percentages;
- gint x, y, w, h; /* used during an individual size computation */
-};
-
-GType columns_get_type(void);
-GtkWidget *columns_new(gint spacing);
-void columns_set_cols(Columns *cols, gint ncols, const gint *percentages);
-void columns_add(Columns *cols, GtkWidget *child,
- gint colstart, gint colspan);
-void columns_taborder_last(Columns *cols, GtkWidget *child);
-void columns_force_left_align(Columns *cols, GtkWidget *child);
-void columns_force_same_height(Columns *cols, GtkWidget *ch1, GtkWidget *ch2);
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
-
-#endif /* COLUMNS_H */
diff --git a/unix/gtkcomm.c b/unix/gtkcomm.c
deleted file mode 100644
index fa52bfb4..00000000
--- a/unix/gtkcomm.c
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * gtkcomm.c: machinery in the GTK front end which is common to all
- * programs that run a session in a terminal window, and also common
- * across all _sessions_ rather than specific to one session. (Timers,
- * uxsel etc.)
- */
-
-#define _GNU_SOURCE
-
-#include <string.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-#include <signal.h>
-#include <stdio.h>
-#include <time.h>
-#include <errno.h>
-#include <locale.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <gtk/gtk.h>
-#if !GTK_CHECK_VERSION(3,0,0)
-#include <gdk/gdkkeysyms.h>
-#endif
-
-#if GTK_CHECK_VERSION(2,0,0)
-#include <gtk/gtkimmodule.h>
-#endif
-
-#define MAY_REFER_TO_GTK_IN_HEADERS
-
-#include "putty.h"
-#include "terminal.h"
-#include "gtkcompat.h"
-#include "gtkfont.h"
-#include "gtkmisc.h"
-
-#ifndef NOT_X_WINDOWS
-#include <gdk/gdkx.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-#endif
-
-#define CAT2(x,y) x ## y
-#define CAT(x,y) CAT2(x,y)
-#define ASSERT(x) enum {CAT(assertion_,__LINE__) = 1 / (x)}
-
-#if GTK_CHECK_VERSION(2,0,0)
-ASSERT(sizeof(long) <= sizeof(gsize));
-#define LONG_TO_GPOINTER(l) GSIZE_TO_POINTER(l)
-#define GPOINTER_TO_LONG(p) GPOINTER_TO_SIZE(p)
-#else /* Gtk 1.2 */
-ASSERT(sizeof(long) <= sizeof(gpointer));
-#define LONG_TO_GPOINTER(l) ((gpointer)(long)(l))
-#define GPOINTER_TO_LONG(p) ((long)(p))
-#endif
-
-/* ----------------------------------------------------------------------
- * File descriptors and uxsel.
- */
-
-struct uxsel_id {
-#if GTK_CHECK_VERSION(2,0,0)
- GIOChannel *chan;
- guint watch_id;
-#else
- int id;
-#endif
-};
-
-#if GTK_CHECK_VERSION(2,0,0)
-gboolean fd_input_func(GIOChannel *source, GIOCondition condition,
- gpointer data)
-{
- int sourcefd = g_io_channel_unix_get_fd(source);
- /*
- * We must process exceptional notifications before ordinary
- * readability ones, or we may go straight past the urgent
- * marker.
- */
- if (condition & G_IO_PRI)
- select_result(sourcefd, SELECT_X);
- if (condition & (G_IO_IN | G_IO_HUP))
- select_result(sourcefd, SELECT_R);
- if (condition & G_IO_OUT)
- select_result(sourcefd, SELECT_W);
-
- return true;
-}
-#else
-void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition)
-{
- if (condition & GDK_INPUT_EXCEPTION)
- select_result(sourcefd, SELECT_X);
- if (condition & GDK_INPUT_READ)
- select_result(sourcefd, SELECT_R);
- if (condition & GDK_INPUT_WRITE)
- select_result(sourcefd, SELECT_W);
-}
-#endif
-
-uxsel_id *uxsel_input_add(int fd, int rwx) {
- uxsel_id *id = snew(uxsel_id);
-
-#if GTK_CHECK_VERSION(2,0,0)
- int flags = 0;
- if (rwx & SELECT_R) flags |= G_IO_IN | G_IO_HUP;
- if (rwx & SELECT_W) flags |= G_IO_OUT;
- if (rwx & SELECT_X) flags |= G_IO_PRI;
- id->chan = g_io_channel_unix_new(fd);
- g_io_channel_set_encoding(id->chan, NULL, NULL);
- id->watch_id = g_io_add_watch_full(id->chan, GDK_PRIORITY_REDRAW+1, flags,
- fd_input_func, NULL, NULL);
-#else
- int flags = 0;
- if (rwx & SELECT_R) flags |= GDK_INPUT_READ;
- if (rwx & SELECT_W) flags |= GDK_INPUT_WRITE;
- if (rwx & SELECT_X) flags |= GDK_INPUT_EXCEPTION;
- assert(flags);
- id->id = gdk_input_add(fd, flags, fd_input_func, NULL);
-#endif
-
- return id;
-}
-
-void uxsel_input_remove(uxsel_id *id) {
-#if GTK_CHECK_VERSION(2,0,0)
- g_source_remove(id->watch_id);
- g_io_channel_unref(id->chan);
-#else
- gdk_input_remove(id->id);
-#endif
- sfree(id);
-}
-
-/* ----------------------------------------------------------------------
- * Timers.
- */
-
-static guint timer_id = 0;
-
-static gint timer_trigger(gpointer data)
-{
- unsigned long now = GPOINTER_TO_LONG(data);
- unsigned long next, then;
- long ticks;
-
- /*
- * Destroy the timer we got here on.
- */
- if (timer_id) {
- g_source_remove(timer_id);
- timer_id = 0;
- }
-
- /*
- * run_timers() may cause a call to timer_change_notify, in which
- * case a new timer will already have been set up and left in
- * timer_id. If it hasn't, and run_timers reports that some timing
- * still needs to be done, we do it ourselves.
- */
- if (run_timers(now, &next) && !timer_id) {
- then = now;
- now = GETTICKCOUNT();
- if (now - then > next - then)
- ticks = 0;
- else
- ticks = next - now;
- timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next));
- }
-
- /*
- * Returning false means 'don't call this timer again', which
- * _should_ be redundant given that we removed it above, but just
- * in case, return false anyway.
- */
- return false;
-}
-
-void timer_change_notify(unsigned long next)
-{
- long ticks;
-
- if (timer_id)
- g_source_remove(timer_id);
-
- ticks = next - GETTICKCOUNT();
- if (ticks <= 0)
- ticks = 1; /* just in case */
-
- timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next));
-}
-
-/* ----------------------------------------------------------------------
- * Toplevel callbacks.
- */
-
-static guint toplevel_callback_idle_id;
-static bool idle_fn_scheduled;
-
-static void notify_toplevel_callback(void *);
-
-static gint idle_toplevel_callback_func(gpointer data)
-{
- run_toplevel_callbacks();
-
- /*
- * If we've emptied our toplevel callback queue, unschedule
- * ourself. Otherwise, leave ourselves pending so we'll be called
- * again to deal with more callbacks after another round of the
- * event loop.
- */
- if (!toplevel_callback_pending() && idle_fn_scheduled) {
- g_source_remove(toplevel_callback_idle_id);
- idle_fn_scheduled = false;
- }
-
- return true;
-}
-
-static void notify_toplevel_callback(void *vctx)
-{
- if (!idle_fn_scheduled) {
- toplevel_callback_idle_id =
- g_idle_add(idle_toplevel_callback_func, NULL);
- idle_fn_scheduled = true;
- }
-}
-
-/* ----------------------------------------------------------------------
- * Setup function. The real main program must call this.
- */
-
-void gtkcomm_setup(void)
-{
- uxsel_init();
- request_callback_notifications(notify_toplevel_callback, NULL);
-}
diff --git a/unix/gtkdlg.c b/unix/gtkdlg.c
deleted file mode 100644
index 8e7421db..00000000
--- a/unix/gtkdlg.c
+++ /dev/null
@@ -1,4195 +0,0 @@
-/*
- * gtkdlg.c - GTK implementation of the PuTTY configuration box.
- */
-
-#include <assert.h>
-#include <stdarg.h>
-#include <ctype.h>
-#include <time.h>
-
-#include <gtk/gtk.h>
-#if !GTK_CHECK_VERSION(3,0,0)
-#include <gdk/gdkkeysyms.h>
-#endif
-
-#define MAY_REFER_TO_GTK_IN_HEADERS
-
-#include "putty.h"
-#include "gtkcompat.h"
-#include "gtkcols.h"
-#include "gtkfont.h"
-#include "gtkmisc.h"
-
-#ifndef NOT_X_WINDOWS
-#include <gdk/gdkx.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include "x11misc.h"
-#endif
-
-#include "storage.h"
-#include "dialog.h"
-#include "tree234.h"
-#include "licence.h"
-#include "ssh.h"
-
-#if GTK_CHECK_VERSION(2,0,0)
-/* Decide which of GtkFileChooserDialog and GtkFileSelection to use */
-#define USE_GTK_FILE_CHOOSER_DIALOG
-#endif
-
-struct Shortcut {
- GtkWidget *widget;
- struct uctrl *uc;
- int action;
-};
-
-struct Shortcuts {
- struct Shortcut sc[128];
-};
-
-struct selparam;
-
-struct uctrl {
- union control *ctrl;
- GtkWidget *toplevel;
- GtkWidget **buttons; int nbuttons; /* for radio buttons */
- GtkWidget *entry; /* for editbox, filesel, fontsel */
- GtkWidget *button; /* for filesel, fontsel */
-#if !GTK_CHECK_VERSION(2,4,0)
- GtkWidget *list; /* for listbox (in GTK1), combobox (<=GTK2.3) */
- GtkWidget *menu; /* for optionmenu (==droplist) */
- GtkWidget *optmenu; /* also for optionmenu */
-#else
- GtkWidget *combo; /* for combo box (either editable or not) */
-#endif
-#if GTK_CHECK_VERSION(2,0,0)
- GtkWidget *treeview; /* for listbox (GTK2), droplist+combo (>=2.4) */
- GtkListStore *listmodel; /* for all types of list box */
-#endif
- GtkWidget *text; /* for text */
- GtkWidget *label; /* for dlg_label_change */
- GtkAdjustment *adj; /* for the scrollbar in a list box */
- struct selparam *sp; /* which switchable pane of the box we're in */
- guint entrysig;
- guint textsig;
- int nclicks;
-};
-
-struct dlgparam {
- tree234 *byctrl, *bywidget;
- void *data;
- struct {
- unsigned char r, g, b; /* 0-255 */
- bool ok;
- } coloursel_result;
- /* `flags' are set to indicate when a GTK signal handler is being called
- * due to automatic processing and should not flag a user event. */
- int flags;
- struct Shortcuts *shortcuts;
- GtkWidget *window, *cancelbutton;
- union control *currfocus, *lastfocus;
-#if !GTK_CHECK_VERSION(2,0,0)
- GtkWidget *currtreeitem, **treeitems;
- int ntreeitems;
-#else
- size_t nselparams;
- struct selparam **selparams;
-#endif
- struct selparam *curr_panel;
- struct controlbox *ctrlbox;
- int retval;
- post_dialog_fn_t after;
- void *afterctx;
-};
-#define FLAG_UPDATING_COMBO_LIST 1
-#define FLAG_UPDATING_LISTBOX 2
-
-enum { /* values for Shortcut.action */
- SHORTCUT_EMPTY, /* no shortcut on this key */
- SHORTCUT_TREE, /* focus a tree item */
- SHORTCUT_FOCUS, /* focus the supplied widget */
- SHORTCUT_UCTRL, /* do something sane with uctrl */
- SHORTCUT_UCTRL_UP, /* uctrl is a draglist, move Up */
- SHORTCUT_UCTRL_DOWN, /* uctrl is a draglist, move Down */
-};
-
-#if GTK_CHECK_VERSION(2,0,0)
-enum {
- TREESTORE_PATH,
- TREESTORE_PARAMS,
- TREESTORE_NUM
-};
-#endif
-
-/*
- * Forward references.
- */
-static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event,
- gpointer data);
-static void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
- int chr, int action, void *ptr);
-static void shortcut_highlight(GtkWidget *label, int chr);
-#if !GTK_CHECK_VERSION(2,0,0)
-static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event,
- gpointer data);
-static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event,
- gpointer data);
-static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event,
- gpointer data);
-static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event,
- gpointer data);
-#endif
-#if !GTK_CHECK_VERSION(2,4,0)
-static void menuitem_activate(GtkMenuItem *item, gpointer data);
-#endif
-#if GTK_CHECK_VERSION(3,0,0)
-static void colourchoose_response(GtkDialog *dialog,
- gint response_id, gpointer data);
-#else
-static void coloursel_ok(GtkButton *button, gpointer data);
-static void coloursel_cancel(GtkButton *button, gpointer data);
-#endif
-static void dlgparam_destroy(GtkWidget *widget, gpointer data);
-static int get_listitemheight(GtkWidget *widget);
-
-static int uctrl_cmp_byctrl(void *av, void *bv)
-{
- struct uctrl *a = (struct uctrl *)av;
- struct uctrl *b = (struct uctrl *)bv;
- if (a->ctrl < b->ctrl)
- return -1;
- else if (a->ctrl > b->ctrl)
- return +1;
- return 0;
-}
-
-static int uctrl_cmp_byctrl_find(void *av, void *bv)
-{
- union control *a = (union control *)av;
- struct uctrl *b = (struct uctrl *)bv;
- if (a < b->ctrl)
- return -1;
- else if (a > b->ctrl)
- return +1;
- return 0;
-}
-
-static int uctrl_cmp_bywidget(void *av, void *bv)
-{
- struct uctrl *a = (struct uctrl *)av;
- struct uctrl *b = (struct uctrl *)bv;
- if (a->toplevel < b->toplevel)
- return -1;
- else if (a->toplevel > b->toplevel)
- return +1;
- return 0;
-}
-
-static int uctrl_cmp_bywidget_find(void *av, void *bv)
-{
- GtkWidget *a = (GtkWidget *)av;
- struct uctrl *b = (struct uctrl *)bv;
- if (a < b->toplevel)
- return -1;
- else if (a > b->toplevel)
- return +1;
- return 0;
-}
-
-static void dlg_init(struct dlgparam *dp)
-{
- dp->byctrl = newtree234(uctrl_cmp_byctrl);
- dp->bywidget = newtree234(uctrl_cmp_bywidget);
- dp->coloursel_result.ok = false;
- dp->window = dp->cancelbutton = NULL;
-#if !GTK_CHECK_VERSION(2,0,0)
- dp->treeitems = NULL;
- dp->currtreeitem = NULL;
-#endif
- dp->curr_panel = NULL;
- dp->flags = 0;
- dp->currfocus = NULL;
-}
-
-static void dlg_cleanup(struct dlgparam *dp)
-{
- struct uctrl *uc;
-
- freetree234(dp->byctrl); /* doesn't free the uctrls inside */
- dp->byctrl = NULL;
- while ( (uc = index234(dp->bywidget, 0)) != NULL) {
- del234(dp->bywidget, uc);
- sfree(uc->buttons);
- sfree(uc);
- }
- freetree234(dp->bywidget);
- dp->bywidget = NULL;
-#if !GTK_CHECK_VERSION(2,0,0)
- sfree(dp->treeitems);
-#endif
-}
-
-static void dlg_add_uctrl(struct dlgparam *dp, struct uctrl *uc)
-{
- add234(dp->byctrl, uc);
- add234(dp->bywidget, uc);
-}
-
-static struct uctrl *dlg_find_byctrl(struct dlgparam *dp, union control *ctrl)
-{
- if (!dp->byctrl)
- return NULL;
- return find234(dp->byctrl, ctrl, uctrl_cmp_byctrl_find);
-}
-
-static struct uctrl *dlg_find_bywidget(struct dlgparam *dp, GtkWidget *w)
-{
- struct uctrl *ret = NULL;
- if (!dp->bywidget)
- return NULL;
- do {
- ret = find234(dp->bywidget, w, uctrl_cmp_bywidget_find);
- if (ret)
- return ret;
- w = gtk_widget_get_parent(w);
- } while (w);
- return ret;
-}
-
-union control *dlg_last_focused(union control *ctrl, dlgparam *dp)
-{
- if (dp->currfocus != ctrl)
- return dp->currfocus;
- else
- return dp->lastfocus;
-}
-
-void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int which)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- assert(uc->ctrl->generic.type == CTRL_RADIO);
- assert(uc->buttons != NULL);
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->buttons[which]), true);
-}
-
-int dlg_radiobutton_get(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- int i;
-
- assert(uc->ctrl->generic.type == CTRL_RADIO);
- assert(uc->buttons != NULL);
- for (i = 0; i < uc->nbuttons; i++)
- if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->buttons[i])))
- return i;
- return 0; /* got to return something */
-}
-
-void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- assert(uc->ctrl->generic.type == CTRL_CHECKBOX);
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->toplevel), checked);
-}
-
-bool dlg_checkbox_get(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- assert(uc->ctrl->generic.type == CTRL_CHECKBOX);
- return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->toplevel));
-}
-
-void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- GtkWidget *entry;
- char *tmpstring;
- assert(uc->ctrl->generic.type == CTRL_EDITBOX);
-
-#if GTK_CHECK_VERSION(2,4,0)
- if (uc->combo)
- entry = gtk_bin_get_child(GTK_BIN(uc->combo));
- else
-#endif
- entry = uc->entry;
-
- assert(entry != NULL);
-
- /*
- * GTK 2 implements gtk_entry_set_text by means of two separate
- * operations: first delete the previous text leaving the empty
- * string, then insert the new text. This causes two calls to
- * the "changed" signal.
- *
- * The first call to "changed", if allowed to proceed normally,
- * will cause an EVENT_VALCHANGE event on the edit box, causing
- * a call to dlg_editbox_get() which will read the empty string
- * out of the GtkEntry - and promptly write it straight into the
- * Conf structure, which is precisely where our `text' pointer
- * is probably pointing, so the second editing operation will
- * insert that instead of the string we originally asked for.
- *
- * Hence, we must take our own copy of the text before we do
- * this.
- */
- tmpstring = dupstr(text);
- gtk_entry_set_text(GTK_ENTRY(entry), tmpstring);
- sfree(tmpstring);
-}
-
-char *dlg_editbox_get(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- assert(uc->ctrl->generic.type == CTRL_EDITBOX);
-
-#if GTK_CHECK_VERSION(2,4,0)
- if (uc->combo) {
- return dupstr(gtk_entry_get_text
- (GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo)))));
- }
-#endif
-
- if (uc->entry) {
- return dupstr(gtk_entry_get_text(GTK_ENTRY(uc->entry)));
- }
-
- unreachable("bad control type in editbox_get");
-}
-
-#if !GTK_CHECK_VERSION(2,4,0)
-static void container_remove_and_destroy(GtkWidget *w, gpointer data)
-{
- GtkContainer *cont = GTK_CONTAINER(data);
- /* gtk_container_remove will unref the widget for us; we need not. */
- gtk_container_remove(cont, w);
-}
-#endif
-
-/* The `listbox' functions can also apply to combo boxes. */
-void dlg_listbox_clear(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
- uc->ctrl->generic.type == CTRL_LISTBOX);
-
-#if !GTK_CHECK_VERSION(2,4,0)
- if (uc->menu) {
- gtk_container_foreach(GTK_CONTAINER(uc->menu),
- container_remove_and_destroy,
- GTK_CONTAINER(uc->menu));
- return;
- }
- if (uc->list) {
- gtk_list_clear_items(GTK_LIST(uc->list), 0, -1);
- return;
- }
-#endif
-#if GTK_CHECK_VERSION(2,0,0)
- if (uc->listmodel) {
- gtk_list_store_clear(uc->listmodel);
- return;
- }
-#endif
- unreachable("bad control type in listbox_clear");
-}
-
-void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
- uc->ctrl->generic.type == CTRL_LISTBOX);
-
-#if !GTK_CHECK_VERSION(2,4,0)
- if (uc->menu) {
- gtk_container_remove
- (GTK_CONTAINER(uc->menu),
- g_list_nth_data(GTK_MENU_SHELL(uc->menu)->children, index));
- return;
- }
- if (uc->list) {
- gtk_list_clear_items(GTK_LIST(uc->list), index, index+1);
- return;
- }
-#endif
-#if GTK_CHECK_VERSION(2,0,0)
- if (uc->listmodel) {
- GtkTreePath *path;
- GtkTreeIter iter;
- assert(uc->listmodel != NULL);
- path = gtk_tree_path_new_from_indices(index, -1);
- gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path);
- gtk_list_store_remove(uc->listmodel, &iter);
- gtk_tree_path_free(path);
- return;
- }
-#endif
- unreachable("bad control type in listbox_del");
-}
-
-void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text)
-{
- dlg_listbox_addwithid(ctrl, dp, text, 0);
-}
-
-/*
- * Each listbox entry may have a numeric id associated with it.
- * Note that some front ends only permit a string to be stored at
- * each position, which means that _if_ you put two identical
- * strings in any listbox then you MUST not assign them different
- * IDs and expect to get meaningful results back.
- */
-void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp,
- char const *text, int id)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
- uc->ctrl->generic.type == CTRL_LISTBOX);
-
- /*
- * This routine is long and complicated in both GTK 1 and 2,
- * and completely different. Sigh.
- */
- dp->flags |= FLAG_UPDATING_COMBO_LIST;
-
-#if !GTK_CHECK_VERSION(2,4,0)
- if (uc->menu) {
- /*
- * List item in a drop-down (but non-combo) list. Tabs are
- * ignored; we just provide a standard menu item with the
- * text.
- */
- GtkWidget *menuitem = gtk_menu_item_new_with_label(text);
-
- gtk_container_add(GTK_CONTAINER(uc->menu), menuitem);
- gtk_widget_show(menuitem);
-
- g_object_set_data(G_OBJECT(menuitem), "user-data",
- GINT_TO_POINTER(id));
- g_signal_connect(G_OBJECT(menuitem), "activate",
- G_CALLBACK(menuitem_activate), dp);
- goto done;
- }
- if (uc->list && uc->entry) {
- /*
- * List item in a combo-box list, which means the sensible
- * thing to do is make it a perfectly normal label. Hence
- * tabs are disregarded.
- */
- GtkWidget *listitem = gtk_list_item_new_with_label(text);
-
- gtk_container_add(GTK_CONTAINER(uc->list), listitem);
- gtk_widget_show(listitem);
-
- g_object_set_data(G_OBJECT(listitem), "user-data",
- GINT_TO_POINTER(id));
- goto done;
- }
-#endif
-#if !GTK_CHECK_VERSION(2,0,0)
- if (uc->list) {
- /*
- * List item in a non-combo-box list box. We make all of
- * these Columns containing GtkLabels. This allows us to do
- * the nasty force_left hack irrespective of whether there
- * are tabs in the thing.
- */
- GtkWidget *listitem = gtk_list_item_new();
- GtkWidget *cols = columns_new(10);
- gint *percents;
- int i, ncols;
-
- /* Count the tabs in the text, and hence determine # of columns. */
- ncols = 1;
- for (i = 0; text[i]; i++)
- if (text[i] == '\t')
- ncols++;
-
- assert(ncols <=
- (uc->ctrl->listbox.ncols ? uc->ctrl->listbox.ncols : 1));
- percents = snewn(ncols, gint);
- percents[ncols-1] = 100;
- for (i = 0; i < ncols-1; i++) {
- percents[i] = uc->ctrl->listbox.percentages[i];
- percents[ncols-1] -= percents[i];
- }
- columns_set_cols(COLUMNS(cols), ncols, percents);
- sfree(percents);
-
- for (i = 0; i < ncols; i++) {
- int len = strcspn(text, "\t");
- char *dup = dupprintf("%.*s", len, text);
- GtkWidget *label;
-
- text += len;
- if (*text) text++;
- label = gtk_label_new(dup);
- sfree(dup);
-
- columns_add(COLUMNS(cols), label, i, 1);
- columns_force_left_align(COLUMNS(cols), label);
- gtk_widget_show(label);
- }
- gtk_container_add(GTK_CONTAINER(listitem), cols);
- gtk_widget_show(cols);
- gtk_container_add(GTK_CONTAINER(uc->list), listitem);
- gtk_widget_show(listitem);
-
- if (ctrl->listbox.multisel) {
- g_signal_connect(G_OBJECT(listitem), "key_press_event",
- G_CALLBACK(listitem_multi_key), uc->adj);
- } else {
- g_signal_connect(G_OBJECT(listitem), "key_press_event",
- G_CALLBACK(listitem_single_key), uc->adj);
- }
- g_signal_connect(G_OBJECT(listitem), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- g_signal_connect(G_OBJECT(listitem), "button_press_event",
- G_CALLBACK(listitem_button_press), dp);
- g_signal_connect(G_OBJECT(listitem), "button_release_event",
- G_CALLBACK(listitem_button_release), dp);
- g_object_set_data(G_OBJECT(listitem), "user-data",
- GINT_TO_POINTER(id));
- goto done;
- }
-#else
- if (uc->listmodel) {
- GtkTreeIter iter;
- int i, cols;
-
- dp->flags |= FLAG_UPDATING_LISTBOX;/* inhibit drag-list update */
- gtk_list_store_append(uc->listmodel, &iter);
- dp->flags &= ~FLAG_UPDATING_LISTBOX;
- gtk_list_store_set(uc->listmodel, &iter, 0, id, -1);
-
- /*
- * Now go through text and divide it into columns at the tabs,
- * as necessary.
- */
- cols = (uc->ctrl->generic.type == CTRL_LISTBOX ? ctrl->listbox.ncols : 1);
- cols = cols ? cols : 1;
- for (i = 0; i < cols; i++) {
- int collen = strcspn(text, "\t");
- char *tmpstr = snewn(collen+1, char);
- memcpy(tmpstr, text, collen);
- tmpstr[collen] = '\0';
- gtk_list_store_set(uc->listmodel, &iter, i+1, tmpstr, -1);
- sfree(tmpstr);
- text += collen;
- if (*text) text++;
- }
- goto done;
- }
-#endif
- unreachable("bad control type in listbox_addwithid");
- done:
- dp->flags &= ~FLAG_UPDATING_COMBO_LIST;
-}
-
-int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
- uc->ctrl->generic.type == CTRL_LISTBOX);
-
-#if !GTK_CHECK_VERSION(2,4,0)
- if (uc->menu || uc->list) {
- GList *children;
- GObject *item;
-
- children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
- uc->list));
- item = G_OBJECT(g_list_nth_data(children, index));
- g_list_free(children);
-
- return GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "user-data"));
- }
-#endif
-#if GTK_CHECK_VERSION(2,0,0)
- if (uc->listmodel) {
- GtkTreePath *path;
- GtkTreeIter iter;
- int ret;
-
- path = gtk_tree_path_new_from_indices(index, -1);
- gtk_tree_model_get_iter(GTK_TREE_MODEL(uc->listmodel), &iter, path);
- gtk_tree_model_get(GTK_TREE_MODEL(uc->listmodel), &iter, 0, &ret, -1);
- gtk_tree_path_free(path);
-
- return ret;
- }
-#endif
- unreachable("bad control type in listbox_getid");
- return -1; /* placate dataflow analysis */
-}
-
-/* dlg_listbox_index returns <0 if no single element is selected. */
-int dlg_listbox_index(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
- uc->ctrl->generic.type == CTRL_LISTBOX);
-
-#if !GTK_CHECK_VERSION(2,4,0)
- if (uc->menu || uc->list) {
- GList *children;
- GtkWidget *item, *activeitem;
- int i;
- int selected = -1;
-
- if (uc->menu)
- activeitem = gtk_menu_get_active(GTK_MENU(uc->menu));
- else
- activeitem = NULL; /* unnecessarily placate gcc */
-
- children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
- uc->list));
- for (i = 0; children!=NULL && (item = GTK_WIDGET(children->data))!=NULL;
- i++, children = children->next) {
- if (uc->menu ? activeitem == item :
- GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED) {
- if (selected == -1)
- selected = i;
- else
- selected = -2;
- }
- }
- g_list_free(children);
- return selected < 0 ? -1 : selected;
- }
-#else
- if (uc->combo) {
- /*
- * This API function already does the right thing in the
- * case of no current selection.
- */
- return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo));
- }
-#endif
-#if GTK_CHECK_VERSION(2,0,0)
- if (uc->treeview) {
- GtkTreeSelection *treesel;
- GtkTreePath *path;
- GtkTreeModel *model;
- GList *sellist;
- gint *indices;
- int ret;
-
- assert(uc->treeview != NULL);
- treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
-
- if (gtk_tree_selection_count_selected_rows(treesel) != 1)
- return -1;
-
- sellist = gtk_tree_selection_get_selected_rows(treesel, &model);
-
- assert(sellist && sellist->data);
- path = sellist->data;
-
- if (gtk_tree_path_get_depth(path) != 1) {
- ret = -1;
- } else {
- indices = gtk_tree_path_get_indices(path);
- if (!indices) {
- ret = -1;
- } else {
- ret = indices[0];
- }
- }
-
- g_list_foreach(sellist, (GFunc)gtk_tree_path_free, NULL);
- g_list_free(sellist);
-
- return ret;
- }
-#endif
- unreachable("bad control type in listbox_index");
- return -1; /* placate dataflow analysis */
-}
-
-bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
- uc->ctrl->generic.type == CTRL_LISTBOX);
-
-#if !GTK_CHECK_VERSION(2,4,0)
- if (uc->menu || uc->list) {
- GList *children;
- GtkWidget *item, *activeitem;
-
- assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
- uc->ctrl->generic.type == CTRL_LISTBOX);
- assert(uc->menu != NULL || uc->list != NULL);
-
- children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu :
- uc->list));
- item = GTK_WIDGET(g_list_nth_data(children, index));
- g_list_free(children);
-
- if (uc->menu) {
- activeitem = gtk_menu_get_active(GTK_MENU(uc->menu));
- return item == activeitem;
- } else {
- return GTK_WIDGET_STATE(item) == GTK_STATE_SELECTED;
- }
- }
-#else
- if (uc->combo) {
- /*
- * This API function already does the right thing in the
- * case of no current selection.
- */
- return gtk_combo_box_get_active(GTK_COMBO_BOX(uc->combo)) == index;
- }
-#endif
-#if GTK_CHECK_VERSION(2,0,0)
- if (uc->treeview) {
- GtkTreeSelection *treesel;
- GtkTreePath *path;
- bool ret;
-
- assert(uc->treeview != NULL);
- treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
-
- path = gtk_tree_path_new_from_indices(index, -1);
- ret = gtk_tree_selection_path_is_selected(treesel, path);
- gtk_tree_path_free(path);
-
- return ret;
- }
-#endif
- unreachable("bad control type in listbox_issel");
- return false; /* placate dataflow analysis */
-}
-
-void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- assert(uc->ctrl->generic.type == CTRL_EDITBOX ||
- uc->ctrl->generic.type == CTRL_LISTBOX);
-
-#if !GTK_CHECK_VERSION(2,4,0)
- if (uc->optmenu) {
- gtk_option_menu_set_history(GTK_OPTION_MENU(uc->optmenu), index);
- return;
- }
- if (uc->list) {
- int nitems;
- GList *items;
- gdouble newtop, newbot;
-
- gtk_list_select_item(GTK_LIST(uc->list), index);
-
- /*
- * Scroll the list box if necessary to ensure the newly
- * selected item is visible.
- */
- items = gtk_container_children(GTK_CONTAINER(uc->list));
- nitems = g_list_length(items);
- if (nitems > 0) {
- bool modified = false;
- g_list_free(items);
- newtop = uc->adj->lower +
- (uc->adj->upper - uc->adj->lower) * index / nitems;
- newbot = uc->adj->lower +
- (uc->adj->upper - uc->adj->lower) * (index+1) / nitems;
- if (uc->adj->value > newtop) {
- modified = true;
- uc->adj->value = newtop;
- } else if (uc->adj->value < newbot - uc->adj->page_size) {
- modified = true;
- uc->adj->value = newbot - uc->adj->page_size;
- }
- if (modified)
- gtk_adjustment_value_changed(uc->adj);
- }
- return;
- }
-#else
- if (uc->combo) {
- gtk_combo_box_set_active(GTK_COMBO_BOX(uc->combo), index);
- return;
- }
-#endif
-#if GTK_CHECK_VERSION(2,0,0)
- if (uc->treeview) {
- GtkTreeSelection *treesel;
- GtkTreePath *path;
-
- treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview));
-
- path = gtk_tree_path_new_from_indices(index, -1);
- gtk_tree_selection_select_path(treesel, path);
- gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(uc->treeview),
- path, NULL, false, 0.0, 0.0);
- gtk_tree_path_free(path);
- return;
- }
-#endif
- unreachable("bad control type in listbox_select");
-}
-
-void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- assert(uc->ctrl->generic.type == CTRL_TEXT);
- assert(uc->text != NULL);
-
- gtk_label_set_text(GTK_LABEL(uc->text), text);
-}
-
-void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- switch (uc->ctrl->generic.type) {
- case CTRL_BUTTON:
- gtk_label_set_text(GTK_LABEL(uc->toplevel), text);
- shortcut_highlight(uc->toplevel, ctrl->button.shortcut);
- break;
- case CTRL_CHECKBOX:
- gtk_label_set_text(GTK_LABEL(uc->toplevel), text);
- shortcut_highlight(uc->toplevel, ctrl->checkbox.shortcut);
- break;
- case CTRL_RADIO:
- gtk_label_set_text(GTK_LABEL(uc->label), text);
- shortcut_highlight(uc->label, ctrl->radio.shortcut);
- break;
- case CTRL_EDITBOX:
- gtk_label_set_text(GTK_LABEL(uc->label), text);
- shortcut_highlight(uc->label, ctrl->editbox.shortcut);
- break;
- case CTRL_FILESELECT:
- gtk_label_set_text(GTK_LABEL(uc->label), text);
- shortcut_highlight(uc->label, ctrl->fileselect.shortcut);
- break;
- case CTRL_FONTSELECT:
- gtk_label_set_text(GTK_LABEL(uc->label), text);
- shortcut_highlight(uc->label, ctrl->fontselect.shortcut);
- break;
- case CTRL_LISTBOX:
- gtk_label_set_text(GTK_LABEL(uc->label), text);
- shortcut_highlight(uc->label, ctrl->listbox.shortcut);
- break;
- default:
- unreachable("bad control type in label_change");
- }
-}
-
-void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- /* We must copy fn->path before passing it to gtk_entry_set_text.
- * See comment in dlg_editbox_set() for the reasons. */
- char *duppath = dupstr(fn->path);
- assert(uc->ctrl->generic.type == CTRL_FILESELECT);
- assert(uc->entry != NULL);
- gtk_entry_set_text(GTK_ENTRY(uc->entry), duppath);
- sfree(duppath);
-}
-
-Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- assert(uc->ctrl->generic.type == CTRL_FILESELECT);
- assert(uc->entry != NULL);
- return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry)));
-}
-
-void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- /* We must copy fs->name before passing it to gtk_entry_set_text.
- * See comment in dlg_editbox_set() for the reasons. */
- char *dupname = dupstr(fs->name);
- assert(uc->ctrl->generic.type == CTRL_FONTSELECT);
- assert(uc->entry != NULL);
- gtk_entry_set_text(GTK_ENTRY(uc->entry), dupname);
- sfree(dupname);
-}
-
-FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- assert(uc->ctrl->generic.type == CTRL_FONTSELECT);
- assert(uc->entry != NULL);
- return fontspec_new(gtk_entry_get_text(GTK_ENTRY(uc->entry)));
-}
-
-/*
- * Bracketing a large set of updates in these two functions will
- * cause the front end (if possible) to delay updating the screen
- * until it's all complete, thus avoiding flicker.
- */
-void dlg_update_start(union control *ctrl, dlgparam *dp)
-{
- /*
- * Apparently we can't do this at all in GTK. GtkCList supports
- * freeze and thaw, but not GtkList. Bah.
- */
-}
-
-void dlg_update_done(union control *ctrl, dlgparam *dp)
-{
- /*
- * Apparently we can't do this at all in GTK. GtkCList supports
- * freeze and thaw, but not GtkList. Bah.
- */
-}
-
-void dlg_set_focus(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
- switch (ctrl->generic.type) {
- case CTRL_CHECKBOX:
- case CTRL_BUTTON:
- /* Check boxes and buttons get the focus _and_ get toggled. */
- gtk_widget_grab_focus(uc->toplevel);
- break;
- case CTRL_FILESELECT:
- case CTRL_FONTSELECT:
- case CTRL_EDITBOX:
- if (uc->entry) {
- /* Anything containing an edit box gets that focused. */
- gtk_widget_grab_focus(uc->entry);
- }
-#if GTK_CHECK_VERSION(2,4,0)
- else if (uc->combo) {
- /* Failing that, there'll be a combo box. */
- gtk_widget_grab_focus(uc->combo);
- }
-#endif
- break;
- case CTRL_RADIO:
- /*
- * Radio buttons: we find the currently selected button and
- * focus it.
- */
- for (int i = 0; i < ctrl->radio.nbuttons; i++)
- if (gtk_toggle_button_get_active
- (GTK_TOGGLE_BUTTON(uc->buttons[i]))) {
- gtk_widget_grab_focus(uc->buttons[i]);
- }
- break;
- case CTRL_LISTBOX:
-#if !GTK_CHECK_VERSION(2,4,0)
- if (uc->optmenu) {
- gtk_widget_grab_focus(uc->optmenu);
- break;
- }
-#else
- if (uc->combo) {
- gtk_widget_grab_focus(uc->combo);
- break;
- }
-#endif
-#if !GTK_CHECK_VERSION(2,0,0)
- if (uc->list) {
- /*
- * For GTK-1 style list boxes, we tell it to focus one
- * of its children, which appears to do the Right
- * Thing.
- */
- gtk_container_focus(GTK_CONTAINER(uc->list), GTK_DIR_TAB_FORWARD);
- break;
- }
-#else
- if (uc->treeview) {
- gtk_widget_grab_focus(uc->treeview);
- break;
- }
-#endif
- unreachable("bad control type in set_focus");
- }
-}
-
-/*
- * During event processing, you might well want to give an error
- * indication to the user. dlg_beep() is a quick and easy generic
- * error; dlg_error() puts up a message-box or equivalent.
- */
-void dlg_beep(dlgparam *dp)
-{
- gdk_display_beep(gdk_display_get_default());
-}
-
-static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child)
-{
-#if !GTK_CHECK_VERSION(2,0,0)
- gint x, y, w, h, dx, dy;
- GtkRequisition req;
- gtk_window_set_position(GTK_WINDOW(child), GTK_WIN_POS_NONE);
- gtk_widget_size_request(GTK_WIDGET(child), &req);
-
- gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(parent)), &x, &y);
- gdk_window_get_size(gtk_widget_get_window(GTK_WIDGET(parent)), &w, &h);
-
- /*
- * One corner of the transient will be offset inwards, by 1/4
- * of the parent window's size, from the corresponding corner
- * of the parent window. The corner will be chosen so as to
- * place the transient closer to the centre of the screen; this
- * should avoid transients going off the edge of the screen on
- * a regular basis.
- */
- if (x + w/2 < gdk_screen_width() / 2)
- dx = x + w/4; /* work from left edges */
- else
- dx = x + 3*w/4 - req.width; /* work from right edges */
- if (y + h/2 < gdk_screen_height() / 2)
- dy = y + h/4; /* work from top edges */
- else
- dy = y + 3*h/4 - req.height; /* work from bottom edges */
- gtk_widget_set_uposition(GTK_WIDGET(child), dx, dy);
-#endif
-}
-
-void trivial_post_dialog_fn(void *vctx, int result)
-{
-}
-
-void dlg_error_msg(dlgparam *dp, const char *msg)
-{
- create_message_box(
- dp->window, "Error", msg,
- string_width("Some sort of text about a config-box error message"),
- false, &buttons_ok, trivial_post_dialog_fn, NULL);
-}
-
-/*
- * This function signals to the front end that the dialog's
- * processing is completed, and passes an integer value (typically
- * a success status).
- */
-void dlg_end(dlgparam *dp, int value)
-{
- dp->retval = value;
- gtk_widget_destroy(dp->window);
-}
-
-void dlg_refresh(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc;
-
- if (ctrl) {
- if (ctrl->generic.handler != NULL)
- ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH);
- } else {
- int i;
-
- for (i = 0; (uc = index234(dp->byctrl, i)) != NULL; i++) {
- assert(uc->ctrl != NULL);
- if (uc->ctrl->generic.handler != NULL)
- uc->ctrl->generic.handler(uc->ctrl, dp,
- dp->data, EVENT_REFRESH);
- }
- }
-}
-
-void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
-
-#if GTK_CHECK_VERSION(3,0,0)
- GtkWidget *coloursel =
- gtk_color_chooser_dialog_new("Select a colour",
- GTK_WINDOW(dp->window));
- gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(coloursel), false);
-#else
- GtkWidget *okbutton, *cancelbutton;
- GtkWidget *coloursel =
- gtk_color_selection_dialog_new("Select a colour");
- GtkColorSelectionDialog *ccs = GTK_COLOR_SELECTION_DIALOG(coloursel);
- GtkColorSelection *cs = GTK_COLOR_SELECTION
- (gtk_color_selection_dialog_get_color_selection(ccs));
- gtk_color_selection_set_has_opacity_control(cs, false);
-#endif
-
- dp->coloursel_result.ok = false;
-
- gtk_window_set_modal(GTK_WINDOW(coloursel), true);
-
-#if GTK_CHECK_VERSION(3,0,0)
- {
- GdkRGBA rgba;
- rgba.red = r / 255.0;
- rgba.green = g / 255.0;
- rgba.blue = b / 255.0;
- rgba.alpha = 1.0; /* fully opaque! */
- gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(coloursel), &rgba);
- }
-#elif GTK_CHECK_VERSION(2,0,0)
- {
- GdkColor col;
- col.red = r * 0x0101;
- col.green = g * 0x0101;
- col.blue = b * 0x0101;
- gtk_color_selection_set_current_color(cs, &col);
- }
-#else
- {
- gdouble cvals[4];
- cvals[0] = r / 255.0;
- cvals[1] = g / 255.0;
- cvals[2] = b / 255.0;
- cvals[3] = 1.0; /* fully opaque! */
- gtk_color_selection_set_color(cs, cvals);
- }
-#endif
-
- g_object_set_data(G_OBJECT(coloursel), "user-data", (gpointer)uc);
-
-#if GTK_CHECK_VERSION(3,0,0)
- g_signal_connect(G_OBJECT(coloursel), "response",
- G_CALLBACK(colourchoose_response), (gpointer)dp);
-#else
-
-#if GTK_CHECK_VERSION(2,0,0)
- g_object_get(G_OBJECT(ccs),
- "ok-button", &okbutton,
- "cancel-button", &cancelbutton,
- (const char *)NULL);
-#else
- okbutton = ccs->ok_button;
- cancelbutton = ccs->cancel_button;
-#endif
- g_object_set_data(G_OBJECT(okbutton), "user-data",
- (gpointer)coloursel);
- g_object_set_data(G_OBJECT(cancelbutton), "user-data",
- (gpointer)coloursel);
- g_signal_connect(G_OBJECT(okbutton), "clicked",
- G_CALLBACK(coloursel_ok), (gpointer)dp);
- g_signal_connect(G_OBJECT(cancelbutton), "clicked",
- G_CALLBACK(coloursel_cancel), (gpointer)dp);
- g_signal_connect_swapped(G_OBJECT(okbutton), "clicked",
- G_CALLBACK(gtk_widget_destroy),
- (gpointer)coloursel);
- g_signal_connect_swapped(G_OBJECT(cancelbutton), "clicked",
- G_CALLBACK(gtk_widget_destroy),
- (gpointer)coloursel);
-#endif
- gtk_widget_show(coloursel);
-}
-
-bool dlg_coloursel_results(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;
-}
-
-/* ----------------------------------------------------------------------
- * Signal handlers while the dialog box is active.
- */
-
-static gboolean widget_focus(GtkWidget *widget, GdkEventFocus *event,
- gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, widget);
- union control *focus;
-
- if (uc && uc->ctrl)
- focus = uc->ctrl;
- else
- focus = NULL;
-
- if (focus != dp->currfocus) {
- dp->lastfocus = dp->currfocus;
- dp->currfocus = focus;
- }
-
- return false;
-}
-
-static void button_clicked(GtkButton *button, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
-}
-
-static void button_toggled(GtkToggleButton *tb, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tb));
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
-}
-
-static gboolean editbox_key(GtkWidget *widget, GdkEventKey *event,
- gpointer data)
-{
- /*
- * GtkEntry has a nasty habit of eating the Return key, which
- * is unhelpful since it doesn't actually _do_ anything with it
- * (it calls gtk_widget_activate, but our edit boxes never need
- * activating). So I catch Return before GtkEntry sees it, and
- * pass it straight on to the parent widget. Effect: hitting
- * Return in an edit box will now activate the default button
- * in the dialog just like it will everywhere else.
- */
- GtkWidget *parent = gtk_widget_get_parent(widget);
- if (event->keyval == GDK_KEY_Return && parent != NULL) {
- gboolean return_val;
- g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
- g_signal_emit_by_name(G_OBJECT(parent), "key_press_event",
- event, &return_val);
- return return_val;
- }
- return false;
-}
-
-static void editbox_changed(GtkEditable *ed, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- if (!(dp->flags & FLAG_UPDATING_COMBO_LIST)) {
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed));
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
- }
-}
-
-static gboolean editbox_lostfocus(GtkWidget *ed, GdkEventFocus *event,
- gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(ed));
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_REFRESH);
- return false;
-}
-
-#if !GTK_CHECK_VERSION(2,0,0)
-
-/*
- * GTK 1 list box event handlers.
- */
-
-static gboolean listitem_key(GtkWidget *item, GdkEventKey *event,
- gpointer data, bool multiple)
-{
- GtkAdjustment *adj = GTK_ADJUSTMENT(data);
-
- if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
- event->keyval == GDK_Down || event->keyval == GDK_KP_Down ||
- event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up ||
- event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down) {
- /*
- * Up, Down, PgUp or PgDn have been pressed on a ListItem
- * in a list box. So, if the list box is single-selection:
- *
- * - if the list item in question isn't already selected,
- * we simply select it.
- * - otherwise, we find the next one (or next
- * however-far-away) in whichever direction we're going,
- * and select that.
- * + in this case, we must also fiddle with the
- * scrollbar to ensure the newly selected item is
- * actually visible.
- *
- * If it's multiple-selection, we do all of the above
- * except actually selecting anything, so we move the focus
- * and fiddle the scrollbar to follow it.
- */
- GtkWidget *list = item->parent;
-
- g_signal_stop_emission_by_name(G_OBJECT(item), "key_press_event");
-
- if (!multiple &&
- GTK_WIDGET_STATE(item) != GTK_STATE_SELECTED) {
- gtk_list_select_child(GTK_LIST(list), item);
- } else {
- int direction =
- (event->keyval==GDK_Up || event->keyval==GDK_KP_Up ||
- event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up)
- ? -1 : +1;
- int step =
- (event->keyval==GDK_Page_Down ||
- event->keyval==GDK_KP_Page_Down ||
- event->keyval==GDK_Page_Up || event->keyval==GDK_KP_Page_Up)
- ? 2 : 1;
- int i, n;
- GList *children, *chead;
-
- chead = children = gtk_container_children(GTK_CONTAINER(list));
-
- n = g_list_length(children);
-
- if (step == 2) {
- /*
- * Figure out how many list items to a screenful,
- * and adjust the step appropriately.
- */
- step = 0.5 + adj->page_size * n / (adj->upper - adj->lower);
- step--; /* go by one less than that */
- }
-
- i = 0;
- while (children != NULL) {
- if (item == children->data)
- break;
- children = children->next;
- i++;
- }
-
- while (step > 0) {
- if (direction < 0 && i > 0)
- children = children->prev, i--;
- else if (direction > 0 && i < n-1)
- children = children->next, i++;
- step--;
- }
-
- if (children && children->data) {
- if (!multiple)
- gtk_list_select_child(GTK_LIST(list),
- GTK_WIDGET(children->data));
- gtk_widget_grab_focus(GTK_WIDGET(children->data));
- gtk_adjustment_clamp_page
- (adj,
- adj->lower + (adj->upper-adj->lower) * i / n,
- adj->lower + (adj->upper-adj->lower) * (i+1) / n);
- }
-
- g_list_free(chead);
- }
- return true;
- }
-
- return false;
-}
-
-static gboolean listitem_single_key(GtkWidget *item, GdkEventKey *event,
- gpointer data)
-{
- return listitem_key(item, event, data, false);
-}
-
-static gboolean listitem_multi_key(GtkWidget *item, GdkEventKey *event,
- gpointer data)
-{
- return listitem_key(item, event, data, true);
-}
-
-static gboolean listitem_button_press(GtkWidget *item, GdkEventButton *event,
- gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item));
- switch (event->type) {
- default:
- case GDK_BUTTON_PRESS: uc->nclicks = 1; break;
- case GDK_2BUTTON_PRESS: uc->nclicks = 2; break;
- case GDK_3BUTTON_PRESS: uc->nclicks = 3; break;
- }
- return false;
-}
-
-static gboolean listitem_button_release(GtkWidget *item, GdkEventButton *event,
- gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(item));
- if (uc->nclicks>1) {
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
- return true;
- }
- return false;
-}
-
-static void list_selchange(GtkList *list, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(list));
- if (!uc) return;
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
-}
-
-static void draglist_move(struct dlgparam *dp, struct uctrl *uc, int direction)
-{
- int index = dlg_listbox_index(uc->ctrl, dp);
- GList *children = gtk_container_children(GTK_CONTAINER(uc->list));
- GtkWidget *child;
-
- if ((index < 0) ||
- (index == 0 && direction < 0) ||
- (index == g_list_length(children)-1 && direction > 0)) {
- gdk_display_beep(gdk_display_get_default());
- return;
- }
-
- child = g_list_nth_data(children, index);
- gtk_widget_ref(child);
- gtk_list_clear_items(GTK_LIST(uc->list), index, index+1);
- g_list_free(children);
-
- children = NULL;
- children = g_list_append(children, child);
- gtk_list_insert_items(GTK_LIST(uc->list), children, index + direction);
- gtk_list_select_item(GTK_LIST(uc->list), index + direction);
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_VALCHANGE);
-}
-
-static void draglist_up(GtkButton *button, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
- draglist_move(dp, uc, -1);
-}
-
-static void draglist_down(GtkButton *button, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
- draglist_move(dp, uc, +1);
-}
-
-#else /* !GTK_CHECK_VERSION(2,0,0) */
-
-/*
- * GTK 2 list box event handlers.
- */
-
-static void listbox_doubleclick(GtkTreeView *treeview, GtkTreePath *path,
- GtkTreeViewColumn *column, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(treeview));
- if (uc)
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_ACTION);
-}
-
-static void listbox_selchange(GtkTreeSelection *treeselection,
- gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- GtkTreeView *tree = gtk_tree_selection_get_tree_view(treeselection);
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(tree));
- if (uc)
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
-}
-
-struct draglist_valchange_ctx {
- struct uctrl *uc;
- struct dlgparam *dp;
-};
-
-static gboolean draglist_valchange(gpointer data)
-{
- struct draglist_valchange_ctx *ctx =
- (struct draglist_valchange_ctx *)data;
-
- ctx->uc->ctrl->generic.handler(ctx->uc->ctrl, ctx->dp,
- ctx->dp->data, EVENT_VALCHANGE);
-
- sfree(ctx);
-
- return false;
-}
-
-static void listbox_reorder(GtkTreeModel *treemodel, GtkTreePath *path,
- GtkTreeIter *iter, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- gpointer tree;
- struct uctrl *uc;
-
- if (dp->flags & FLAG_UPDATING_LISTBOX)
- return; /* not a user drag operation */
-
- tree = g_object_get_data(G_OBJECT(treemodel), "user-data");
- uc = dlg_find_bywidget(dp, GTK_WIDGET(tree));
- if (uc) {
- /*
- * We should cause EVENT_VALCHANGE on the list box, now
- * that its rows have been reordered. However, the GTK 2
- * docs say that at the point this signal is received the
- * new row might not have actually been filled in yet.
- *
- * (So what smegging use is it then, eh? Don't suppose it
- * occurred to you at any point that letting the
- * application know _after_ the reordering was compelete
- * might be helpful to someone?)
- *
- * To get round this, I schedule an idle function, which I
- * hope won't be called until the main event loop is
- * re-entered after the drag-and-drop handler has finished
- * furtling with the list store.
- */
- struct draglist_valchange_ctx *ctx =
- snew(struct draglist_valchange_ctx);
- ctx->uc = uc;
- ctx->dp = dp;
- g_idle_add(draglist_valchange, ctx);
- }
-}
-
-#endif /* !GTK_CHECK_VERSION(2,0,0) */
-
-#if !GTK_CHECK_VERSION(2,4,0)
-
-static void menuitem_activate(GtkMenuItem *item, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- GtkWidget *menushell = GTK_WIDGET(item)->parent;
- gpointer optmenu = g_object_get_data(G_OBJECT(menushell), "user-data");
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(optmenu));
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
-}
-
-#else
-
-static void droplist_selchange(GtkComboBox *combo, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(combo));
- if (uc)
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE);
-}
-
-#endif /* !GTK_CHECK_VERSION(2,4,0) */
-
-#ifdef USE_GTK_FILE_CHOOSER_DIALOG
-static void filechoose_response(GtkDialog *dialog, gint response,
- gpointer data)
-{
- /* struct dlgparam *dp = (struct dlgparam *)data; */
- struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data");
- if (response == GTK_RESPONSE_ACCEPT) {
- gchar *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
- gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
- g_free(name);
- }
- gtk_widget_destroy(GTK_WIDGET(dialog));
-}
-#else
-static void filesel_ok(GtkButton *button, gpointer data)
-{
- /* struct dlgparam *dp = (struct dlgparam *)data; */
- gpointer filesel = g_object_get_data(G_OBJECT(button), "user-data");
- struct uctrl *uc = g_object_get_data(G_OBJECT(filesel), "user-data");
- const char *name = gtk_file_selection_get_filename
- (GTK_FILE_SELECTION(filesel));
- gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
-}
-#endif
-
-static void fontsel_ok(GtkButton *button, gpointer data)
-{
- /* struct dlgparam *dp = (struct dlgparam *)data; */
-
-#if !GTK_CHECK_VERSION(2,0,0)
-
- gpointer fontsel = g_object_get_data(G_OBJECT(button), "user-data");
- struct uctrl *uc = g_object_get_data(G_OBJECT(fontsel), "user-data");
- const char *name = gtk_font_selection_dialog_get_font_name
- (GTK_FONT_SELECTION_DIALOG(fontsel));
- gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
-
-#else
-
- unifontsel *fontsel = (unifontsel *)g_object_get_data
- (G_OBJECT(button), "user-data");
- struct uctrl *uc = (struct uctrl *)fontsel->user_data;
- char *name = unifontsel_get_name(fontsel);
- assert(name); /* should always be ok after OK pressed */
- gtk_entry_set_text(GTK_ENTRY(uc->entry), name);
- sfree(name);
-
-#endif
-}
-
-#if GTK_CHECK_VERSION(3,0,0)
-
-static void colourchoose_response(GtkDialog *dialog,
- gint response_id, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data");
-
- if (response_id == GTK_RESPONSE_OK) {
- GdkRGBA rgba;
- gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(dialog), &rgba);
- dp->coloursel_result.r = (int) (255 * rgba.red);
- dp->coloursel_result.g = (int) (255 * rgba.green);
- dp->coloursel_result.b = (int) (255 * rgba.blue);
- dp->coloursel_result.ok = true;
- } else {
- dp->coloursel_result.ok = false;
- }
-
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
-
- gtk_widget_destroy(GTK_WIDGET(dialog));
-}
-
-#else /* GTK 1/2 coloursel response handlers */
-
-static void coloursel_ok(GtkButton *button, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data");
- struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data");
-
-#if GTK_CHECK_VERSION(2,0,0)
- {
- GtkColorSelection *cs = GTK_COLOR_SELECTION
- (gtk_color_selection_dialog_get_color_selection
- (GTK_COLOR_SELECTION_DIALOG(coloursel)));
- GdkColor col;
- gtk_color_selection_get_current_color(cs, &col);
- dp->coloursel_result.r = col.red / 0x0100;
- dp->coloursel_result.g = col.green / 0x0100;
- dp->coloursel_result.b = col.blue / 0x0100;
- }
-#else
- {
- GtkColorSelection *cs = GTK_COLOR_SELECTION
- (gtk_color_selection_dialog_get_color_selection
- (GTK_COLOR_SELECTION_DIALOG(coloursel)));
- gdouble cvals[4];
- gtk_color_selection_get_color(cs, cvals);
- dp->coloursel_result.r = (int) (255 * cvals[0]);
- dp->coloursel_result.g = (int) (255 * cvals[1]);
- dp->coloursel_result.b = (int) (255 * cvals[2]);
- }
-#endif
- dp->coloursel_result.ok = true;
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
-}
-
-static void coloursel_cancel(GtkButton *button, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data");
- struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data");
- dp->coloursel_result.ok = false;
- uc->ctrl->generic.handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK);
-}
-
-#endif /* end of coloursel response handlers */
-
-static void filefont_clicked(GtkButton *button, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button));
-
- if (uc->ctrl->generic.type == CTRL_FILESELECT) {
-#ifdef USE_GTK_FILE_CHOOSER_DIALOG
- GtkWidget *filechoose = gtk_file_chooser_dialog_new
- (uc->ctrl->fileselect.title, GTK_WINDOW(dp->window),
- (uc->ctrl->fileselect.for_writing ?
- GTK_FILE_CHOOSER_ACTION_SAVE :
- GTK_FILE_CHOOSER_ACTION_OPEN),
- STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL,
- STANDARD_OPEN_LABEL, GTK_RESPONSE_ACCEPT,
- (const gchar *)NULL);
- gtk_window_set_modal(GTK_WINDOW(filechoose), true);
- g_object_set_data(G_OBJECT(filechoose), "user-data", (gpointer)uc);
- g_signal_connect(G_OBJECT(filechoose), "response",
- G_CALLBACK(filechoose_response), (gpointer)dp);
- gtk_widget_show(filechoose);
-#else
- GtkWidget *filesel =
- gtk_file_selection_new(uc->ctrl->fileselect.title);
- gtk_window_set_modal(GTK_WINDOW(filesel), true);
- g_object_set_data
- (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data",
- (gpointer)filesel);
- g_object_set_data(G_OBJECT(filesel), "user-data", (gpointer)uc);
- g_signal_connect
- (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
- G_CALLBACK(filesel_ok), (gpointer)dp);
- g_signal_connect_swapped
- (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
- G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
- g_signal_connect_swapped
- (G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked",
- G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
- gtk_widget_show(filesel);
-#endif
- }
-
- if (uc->ctrl->generic.type == CTRL_FONTSELECT) {
- const gchar *fontname = gtk_entry_get_text(GTK_ENTRY(uc->entry));
-
-#if !GTK_CHECK_VERSION(2,0,0)
-
- /*
- * Use the GTK 1 standard font selector.
- */
-
- gchar *spacings[] = { "c", "m", NULL };
- GtkWidget *fontsel =
- gtk_font_selection_dialog_new("Select a font");
- gtk_window_set_modal(GTK_WINDOW(fontsel), true);
- gtk_font_selection_dialog_set_filter
- (GTK_FONT_SELECTION_DIALOG(fontsel),
- GTK_FONT_FILTER_BASE, GTK_FONT_ALL,
- NULL, NULL, NULL, NULL, spacings, NULL);
- if (!gtk_font_selection_dialog_set_font_name
- (GTK_FONT_SELECTION_DIALOG(fontsel), fontname)) {
- /*
- * If the font name wasn't found as it was, try opening
- * it and extracting its FONT property. This should
- * have the effect of mapping short aliases into true
- * XLFDs.
- */
- GdkFont *font = gdk_font_load(fontname);
- if (font) {
- XFontStruct *xfs = GDK_FONT_XFONT(font);
- Display *disp = get_x11_display();
- Atom fontprop = XInternAtom(disp, "FONT", False);
- unsigned long ret;
-
- assert(disp); /* this is GTK1! */
-
- gdk_font_ref(font);
- if (XGetFontProperty(xfs, fontprop, &ret)) {
- char *name = XGetAtomName(disp, (Atom)ret);
- if (name)
- gtk_font_selection_dialog_set_font_name
- (GTK_FONT_SELECTION_DIALOG(fontsel), name);
- }
- gdk_font_unref(font);
- }
- }
- g_object_set_data
- (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
- "user-data", (gpointer)fontsel);
- g_object_set_data(G_OBJECT(fontsel), "user-data", (gpointer)uc);
- g_signal_connect
- (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
- "clicked", G_CALLBACK(fontsel_ok), (gpointer)dp);
- g_signal_connect_swapped
- (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button),
- "clicked", G_CALLBACK(gtk_widget_destroy),
- (gpointer)fontsel);
- g_signal_connect_swapped
- (G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button),
- "clicked", G_CALLBACK(gtk_widget_destroy),
- (gpointer)fontsel);
- gtk_widget_show(fontsel);
-
-#else /* !GTK_CHECK_VERSION(2,0,0) */
-
- /*
- * Use the unifontsel code provided in gtkfont.c.
- */
-
- unifontsel *fontsel = unifontsel_new("Select a font");
-
- gtk_window_set_modal(fontsel->window, true);
- unifontsel_set_name(fontsel, fontname);
-
- g_object_set_data(G_OBJECT(fontsel->ok_button),
- "user-data", (gpointer)fontsel);
- fontsel->user_data = uc;
- g_signal_connect(G_OBJECT(fontsel->ok_button), "clicked",
- G_CALLBACK(fontsel_ok), (gpointer)dp);
- g_signal_connect_swapped(G_OBJECT(fontsel->ok_button), "clicked",
- G_CALLBACK(unifontsel_destroy),
- (gpointer)fontsel);
- g_signal_connect_swapped(G_OBJECT(fontsel->cancel_button),"clicked",
- G_CALLBACK(unifontsel_destroy),
- (gpointer)fontsel);
-
- gtk_widget_show(GTK_WIDGET(fontsel->window));
-
-#endif /* !GTK_CHECK_VERSION(2,0,0) */
-
- }
-}
-
-#if !GTK_CHECK_VERSION(3,0,0)
-static void label_sizealloc(GtkWidget *widget, GtkAllocation *alloc,
- gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- struct uctrl *uc = dlg_find_bywidget(dp, widget);
-
- gtk_widget_set_size_request(uc->text, alloc->width, -1);
- gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->generic.label);
- g_signal_handler_disconnect(G_OBJECT(uc->text), uc->textsig);
-}
-#endif
-
-/* ----------------------------------------------------------------------
- * This function does the main layout work: it reads a controlset,
- * it creates the relevant GTK controls, and returns a GtkWidget
- * containing the result. (This widget might be a title of some
- * sort, it might be a Columns containing many controls, or it
- * might be a GtkFrame containing a Columns; whatever it is, it's
- * definitely a GtkWidget and should probably be added to a
- * GtkVbox.)
- *
- * `win' is required for setting the default button. If it is
- * non-NULL, all buttons created will be default-capable (so they
- * have extra space round them for the default highlight).
- */
-GtkWidget *layout_ctrls(
- struct dlgparam *dp, struct selparam *sp, struct Shortcuts *scs,
- struct controlset *s, GtkWindow *win)
-{
- Columns *cols;
- GtkWidget *ret;
- int i;
-
- if (!s->boxname) {
- /* This controlset is a panel title. */
- assert(s->boxtitle);
- return gtk_label_new(s->boxtitle);
- }
-
- /*
- * Otherwise, we expect to be laying out actual controls, so
- * we'll start by creating a Columns for the purpose.
- */
- cols = COLUMNS(columns_new(4));
- ret = GTK_WIDGET(cols);
- gtk_widget_show(ret);
-
- /*
- * Create a containing frame if we have a box name.
- */
- if (*s->boxname) {
- ret = gtk_frame_new(s->boxtitle); /* NULL is valid here */
- gtk_container_set_border_width(GTK_CONTAINER(cols), 4);
- gtk_container_add(GTK_CONTAINER(ret), GTK_WIDGET(cols));
- gtk_widget_show(ret);
- }
-
- /*
- * Now iterate through the controls themselves, create them,
- * and add them to the Columns.
- */
- for (i = 0; i < s->ncontrols; i++) {
- union control *ctrl = s->ctrls[i];
- struct uctrl *uc;
- bool left = false;
- GtkWidget *w = NULL;
-
- switch (ctrl->generic.type) {
- case CTRL_COLUMNS: {
- static const int simplecols[1] = { 100 };
- columns_set_cols(cols, ctrl->columns.ncols,
- (ctrl->columns.percentages ?
- ctrl->columns.percentages : simplecols));
- continue; /* no actual control created */
- }
- case CTRL_TABDELAY: {
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl->tabdelay.ctrl);
- if (uc)
- columns_taborder_last(cols, uc->toplevel);
- continue; /* no actual control created */
- }
- }
-
- uc = snew(struct uctrl);
- uc->sp = sp;
- uc->ctrl = ctrl;
- uc->buttons = NULL;
- uc->entry = NULL;
-#if !GTK_CHECK_VERSION(2,4,0)
- uc->list = uc->menu = uc->optmenu = NULL;
-#else
- uc->combo = NULL;
-#endif
-#if GTK_CHECK_VERSION(2,0,0)
- uc->treeview = NULL;
- uc->listmodel = NULL;
-#endif
- uc->button = uc->text = NULL;
- uc->label = NULL;
- uc->nclicks = 0;
-
- switch (ctrl->generic.type) {
- case CTRL_BUTTON:
- w = gtk_button_new_with_label(ctrl->generic.label);
- if (win) {
- gtk_widget_set_can_default(w, true);
- if (ctrl->button.isdefault)
- gtk_window_set_default(win, w);
- if (ctrl->button.iscancel)
- dp->cancelbutton = w;
- }
- g_signal_connect(G_OBJECT(w), "clicked",
- G_CALLBACK(button_clicked), dp);
- g_signal_connect(G_OBJECT(w), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
- ctrl->button.shortcut, SHORTCUT_UCTRL, uc);
- break;
- case CTRL_CHECKBOX:
- w = gtk_check_button_new_with_label(ctrl->generic.label);
- g_signal_connect(G_OBJECT(w), "toggled",
- G_CALLBACK(button_toggled), dp);
- g_signal_connect(G_OBJECT(w), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)),
- ctrl->checkbox.shortcut, SHORTCUT_UCTRL, uc);
- left = true;
- break;
- case CTRL_RADIO: {
- /*
- * Radio buttons get to go inside their own Columns, no
- * matter what.
- */
- gint i, *percentages;
- GSList *group;
-
- w = columns_new(0);
- if (ctrl->generic.label) {
- GtkWidget *label = gtk_label_new(ctrl->generic.label);
- columns_add(COLUMNS(w), label, 0, 1);
- columns_force_left_align(COLUMNS(w), label);
- gtk_widget_show(label);
- shortcut_add(scs, label, ctrl->radio.shortcut,
- SHORTCUT_UCTRL, uc);
- uc->label = label;
- }
- percentages = g_new(gint, ctrl->radio.ncolumns);
- for (i = 0; i < ctrl->radio.ncolumns; i++) {
- percentages[i] =
- ((100 * (i+1) / ctrl->radio.ncolumns) -
- 100 * i / ctrl->radio.ncolumns);
- }
- columns_set_cols(COLUMNS(w), ctrl->radio.ncolumns,
- percentages);
- g_free(percentages);
- group = NULL;
-
- uc->nbuttons = ctrl->radio.nbuttons;
- uc->buttons = snewn(uc->nbuttons, GtkWidget *);
-
- for (i = 0; i < ctrl->radio.nbuttons; i++) {
- GtkWidget *b;
- gint colstart;
-
- b = (gtk_radio_button_new_with_label
- (group, ctrl->radio.buttons[i]));
- uc->buttons[i] = b;
- group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(b));
- colstart = i % ctrl->radio.ncolumns;
- columns_add(COLUMNS(w), b, colstart,
- (i == ctrl->radio.nbuttons-1 ?
- ctrl->radio.ncolumns - colstart : 1));
- columns_force_left_align(COLUMNS(w), b);
- gtk_widget_show(b);
- g_signal_connect(G_OBJECT(b), "toggled",
- G_CALLBACK(button_toggled), dp);
- g_signal_connect(G_OBJECT(b), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- if (ctrl->radio.shortcuts) {
- shortcut_add(scs, gtk_bin_get_child(GTK_BIN(b)),
- ctrl->radio.shortcuts[i],
- SHORTCUT_UCTRL, uc);
- }
- }
- break;
- }
- case CTRL_EDITBOX: {
- GtkWidget *signalobject;
-
- if (ctrl->editbox.has_list) {
-#if !GTK_CHECK_VERSION(2,4,0)
- /*
- * GTK 1 combo box.
- */
- w = gtk_combo_new();
- gtk_combo_set_value_in_list(GTK_COMBO(w), false, true);
- uc->entry = GTK_COMBO(w)->entry;
- uc->list = GTK_COMBO(w)->list;
- signalobject = uc->entry;
-#else
- /*
- * GTK 2 combo box.
- */
- uc->listmodel = gtk_list_store_new(2, G_TYPE_INT,
- G_TYPE_STRING);
- w = gtk_combo_box_new_with_model_and_entry
- (GTK_TREE_MODEL(uc->listmodel));
- g_object_set(G_OBJECT(w), "entry-text-column", 1,
- (const char *)NULL);
- /* We cannot support password combo boxes. */
- assert(!ctrl->editbox.password);
- uc->combo = w;
- signalobject = uc->combo;
-#endif
- } else {
- w = gtk_entry_new();
- if (ctrl->editbox.password)
- gtk_entry_set_visibility(GTK_ENTRY(w), false);
- uc->entry = w;
- signalobject = w;
- }
- uc->entrysig =
- g_signal_connect(G_OBJECT(signalobject), "changed",
- G_CALLBACK(editbox_changed), dp);
- g_signal_connect(G_OBJECT(signalobject), "key_press_event",
- G_CALLBACK(editbox_key), dp);
- g_signal_connect(G_OBJECT(signalobject), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- g_signal_connect(G_OBJECT(signalobject), "focus_out_event",
- G_CALLBACK(editbox_lostfocus), dp);
- g_signal_connect(G_OBJECT(signalobject), "focus_out_event",
- G_CALLBACK(editbox_lostfocus), dp);
-
-#if !GTK_CHECK_VERSION(3,0,0)
- /*
- * Edit boxes, for some strange reason, have a minimum
- * width of 150 in GTK 1.2. We don't want this - we'd
- * rather the edit boxes acquired their natural width
- * from the column layout of the rest of the box.
- */
- {
- GtkRequisition req;
- gtk_widget_size_request(w, &req);
- gtk_widget_set_size_request(w, 10, req.height);
- }
-#else
- /*
- * In GTK 3, this is still true, but there's a special
- * method for GtkEntry in particular to fix it.
- */
- if (GTK_IS_ENTRY(w))
- gtk_entry_set_width_chars(GTK_ENTRY(w), 1);
-#endif
-
- if (ctrl->generic.label) {
- GtkWidget *label, *container;
-
- label = gtk_label_new(ctrl->generic.label);
-
- shortcut_add(scs, label, ctrl->editbox.shortcut,
- SHORTCUT_FOCUS, uc->entry);
-
- container = columns_new(4);
- if (ctrl->editbox.percentwidth == 100) {
- columns_add(COLUMNS(container), label, 0, 1);
- columns_force_left_align(COLUMNS(container), label);
- columns_add(COLUMNS(container), w, 0, 1);
- } else {
- gint percentages[2];
- percentages[1] = ctrl->editbox.percentwidth;
- percentages[0] = 100 - ctrl->editbox.percentwidth;
- columns_set_cols(COLUMNS(container), 2, percentages);
- columns_add(COLUMNS(container), label, 0, 1);
- columns_force_left_align(COLUMNS(container), label);
- columns_add(COLUMNS(container), w, 1, 1);
- columns_force_same_height(COLUMNS(container),
- label, w);
- }
- gtk_widget_show(label);
- gtk_widget_show(w);
-
- w = container;
- uc->label = label;
- }
- break;
- }
- case CTRL_FILESELECT:
- case CTRL_FONTSELECT: {
- GtkWidget *ww;
- const char *browsebtn =
- (ctrl->generic.type == CTRL_FILESELECT ?
- "Browse..." : "Change...");
-
- gint percentages[] = { 75, 25 };
- w = columns_new(4);
- columns_set_cols(COLUMNS(w), 2, percentages);
-
- if (ctrl->generic.label) {
- ww = gtk_label_new(ctrl->generic.label);
- columns_add(COLUMNS(w), ww, 0, 2);
- columns_force_left_align(COLUMNS(w), ww);
- gtk_widget_show(ww);
- shortcut_add(scs, ww,
- (ctrl->generic.type == CTRL_FILESELECT ?
- ctrl->fileselect.shortcut :
- ctrl->fontselect.shortcut),
- SHORTCUT_UCTRL, uc);
- uc->label = ww;
- }
-
- uc->entry = ww = gtk_entry_new();
-#if !GTK_CHECK_VERSION(3,0,0)
- {
- GtkRequisition req;
- gtk_widget_size_request(ww, &req);
- gtk_widget_set_size_request(ww, 10, req.height);
- }
-#else
- gtk_entry_set_width_chars(GTK_ENTRY(ww), 1);
-#endif
- columns_add(COLUMNS(w), ww, 0, 1);
- gtk_widget_show(ww);
-
- uc->button = ww = gtk_button_new_with_label(browsebtn);
- columns_add(COLUMNS(w), ww, 1, 1);
- gtk_widget_show(ww);
-
- columns_force_same_height(COLUMNS(w), uc->entry, uc->button);
-
- g_signal_connect(G_OBJECT(uc->entry), "key_press_event",
- G_CALLBACK(editbox_key), dp);
- uc->entrysig =
- g_signal_connect(G_OBJECT(uc->entry), "changed",
- G_CALLBACK(editbox_changed), dp);
- g_signal_connect(G_OBJECT(uc->entry), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- g_signal_connect(G_OBJECT(uc->button), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- g_signal_connect(G_OBJECT(ww), "clicked",
- G_CALLBACK(filefont_clicked), dp);
- break;
- }
- case CTRL_LISTBOX:
-
-#if GTK_CHECK_VERSION(2,0,0)
- /*
- * First construct the list data store, with the right
- * number of columns.
- */
-# if !GTK_CHECK_VERSION(2,4,0)
- /* (For GTK 2.0 to 2.3, we do this for full listboxes only,
- * because combo boxes are still done the old GTK1 way.) */
- if (ctrl->listbox.height > 0)
-# endif
- {
- GType *types;
- int i;
- int cols;
-
- cols = ctrl->listbox.ncols;
- cols = cols ? cols : 1;
- types = snewn(1 + cols, GType);
-
- types[0] = G_TYPE_INT;
- for (i = 0; i < cols; i++)
- types[i+1] = G_TYPE_STRING;
-
- uc->listmodel = gtk_list_store_newv(1 + cols, types);
-
- sfree(types);
- }
-#endif
-
- /*
- * See if it's a drop-down list (non-editable combo
- * box).
- */
- if (ctrl->listbox.height == 0) {
-#if !GTK_CHECK_VERSION(2,4,0)
- /*
- * GTK1 and early-GTK2 option-menu style of
- * drop-down list.
- */
- uc->optmenu = w = gtk_option_menu_new();
- uc->menu = gtk_menu_new();
- gtk_option_menu_set_menu(GTK_OPTION_MENU(w), uc->menu);
- g_object_set_data(G_OBJECT(uc->menu), "user-data",
- (gpointer)uc->optmenu);
- g_signal_connect(G_OBJECT(uc->optmenu), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
-#else
- /*
- * Late-GTK2 style using a GtkComboBox.
- */
- GtkCellRenderer *cr;
-
- /*
- * Create a non-editable GtkComboBox (that is, not
- * its subclass GtkComboBoxEntry).
- */
- w = gtk_combo_box_new_with_model
- (GTK_TREE_MODEL(uc->listmodel));
- uc->combo = w;
-
- /*
- * Tell it how to render a list item (i.e. which
- * column to look at in the list model).
- */
- cr = gtk_cell_renderer_text_new();
- gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, true);
- gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), cr,
- "text", 1, NULL);
-
- /*
- * And tell it to notify us when the selection
- * changes.
- */
- g_signal_connect(G_OBJECT(w), "changed",
- G_CALLBACK(droplist_selchange), dp);
-
- g_signal_connect(G_OBJECT(w), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
-#endif
- } else {
-#if !GTK_CHECK_VERSION(2,0,0)
- /*
- * GTK1-style full list box.
- */
- uc->list = gtk_list_new();
- if (ctrl->listbox.multisel == 2) {
- gtk_list_set_selection_mode(GTK_LIST(uc->list),
- GTK_SELECTION_EXTENDED);
- } else if (ctrl->listbox.multisel == 1) {
- gtk_list_set_selection_mode(GTK_LIST(uc->list),
- GTK_SELECTION_MULTIPLE);
- } else {
- gtk_list_set_selection_mode(GTK_LIST(uc->list),
- GTK_SELECTION_SINGLE);
- }
- w = gtk_scrolled_window_new(NULL, NULL);
- gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(w),
- uc->list);
- gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
- GTK_POLICY_NEVER,
- GTK_POLICY_AUTOMATIC);
- uc->adj = gtk_scrolled_window_get_vadjustment
- (GTK_SCROLLED_WINDOW(w));
-
- gtk_widget_show(uc->list);
- g_signal_connect(G_OBJECT(uc->list), "selection-changed",
- G_CALLBACK(list_selchange), dp);
- g_signal_connect(G_OBJECT(uc->list), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
-
- /*
- * Adjust the height of the scrolled window to the
- * minimum given by the height parameter.
- *
- * This piece of guesswork is a horrid hack based
- * on looking inside the GTK 1.2 sources
- * (specifically gtkviewport.c, which appears to be
- * the widget which provides the border around the
- * scrolling area). Anyone lets me know how I can
- * do this in a way which isn't at risk from GTK
- * upgrades, I'd be grateful.
- */
- {
- int edge;
- edge = GTK_WIDGET(uc->list)->style->klass->ythickness;
- gtk_widget_set_size_request(w, 10,
- 2*edge + (ctrl->listbox.height *
- get_listitemheight(w)));
- }
-
- if (ctrl->listbox.draglist) {
- /*
- * GTK doesn't appear to make it easy to
- * implement a proper draggable list; so
- * instead I'm just going to have to put an Up
- * and a Down button to the right of the actual
- * list box. Ah well.
- */
- GtkWidget *cols, *button;
- static const gint percentages[2] = { 80, 20 };
-
- cols = columns_new(4);
- columns_set_cols(COLUMNS(cols), 2, percentages);
- columns_add(COLUMNS(cols), w, 0, 1);
- gtk_widget_show(w);
- button = gtk_button_new_with_label("Up");
- columns_add(COLUMNS(cols), button, 1, 1);
- gtk_widget_show(button);
- g_signal_connect(G_OBJECT(button), "clicked",
- G_CALLBACK(draglist_up), dp);
- g_signal_connect(G_OBJECT(button), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- button = gtk_button_new_with_label("Down");
- columns_add(COLUMNS(cols), button, 1, 1);
- gtk_widget_show(button);
- g_signal_connect(G_OBJECT(button), "clicked",
- G_CALLBACK(draglist_down), dp);
- g_signal_connect(G_OBJECT(button), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
-
- w = cols;
- }
-#else
- /*
- * GTK2 treeview-based full list box.
- */
- GtkTreeSelection *sel;
-
- /*
- * Create the list box itself, its columns, and
- * its containing scrolled window.
- */
- w = gtk_tree_view_new_with_model
- (GTK_TREE_MODEL(uc->listmodel));
- g_object_set_data(G_OBJECT(uc->listmodel), "user-data",
- (gpointer)w);
- gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false);
- sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(w));
- gtk_tree_selection_set_mode
- (sel, ctrl->listbox.multisel ? GTK_SELECTION_MULTIPLE :
- GTK_SELECTION_SINGLE);
- uc->treeview = w;
- g_signal_connect(G_OBJECT(w), "row-activated",
- G_CALLBACK(listbox_doubleclick), dp);
- g_signal_connect(G_OBJECT(w), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- g_signal_connect(G_OBJECT(sel), "changed",
- G_CALLBACK(listbox_selchange), dp);
-
- if (ctrl->listbox.draglist) {
- gtk_tree_view_set_reorderable(GTK_TREE_VIEW(w), true);
- g_signal_connect(G_OBJECT(uc->listmodel), "row-inserted",
- G_CALLBACK(listbox_reorder), dp);
- }
-
- {
- int i;
- int cols;
-
- cols = ctrl->listbox.ncols;
- cols = cols ? cols : 1;
- for (i = 0; i < cols; i++) {
- GtkTreeViewColumn *column;
- GtkCellRenderer *cellrend;
- /*
- * It appears that GTK 2 doesn't leave us any
- * particularly sensible way to honour the
- * "percentages" specification in the ctrl
- * structure.
- */
- cellrend = gtk_cell_renderer_text_new();
- if (!ctrl->listbox.hscroll) {
- g_object_set(G_OBJECT(cellrend),
- "ellipsize", PANGO_ELLIPSIZE_END,
- "ellipsize-set", true,
- (const char *)NULL);
- }
- column = gtk_tree_view_column_new_with_attributes
- ("heading", cellrend, "text", i+1, (char *)NULL);
- gtk_tree_view_column_set_sizing
- (column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
- gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
- }
- }
-
- {
- GtkWidget *scroll;
-
- scroll = gtk_scrolled_window_new(NULL, NULL);
- gtk_scrolled_window_set_shadow_type
- (GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN);
- gtk_widget_show(w);
- gtk_container_add(GTK_CONTAINER(scroll), w);
- gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
- GTK_POLICY_AUTOMATIC,
- GTK_POLICY_ALWAYS);
- gtk_widget_set_size_request
- (scroll, -1,
- ctrl->listbox.height * get_listitemheight(w));
-
- w = scroll;
- }
-#endif
- }
-
- if (ctrl->generic.label) {
- GtkWidget *label, *container;
-
- label = gtk_label_new(ctrl->generic.label);
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_label_set_width_chars(GTK_LABEL(label), 3);
-#endif
-
- shortcut_add(scs, label, ctrl->listbox.shortcut,
- SHORTCUT_UCTRL, uc);
-
- container = columns_new(4);
- if (ctrl->listbox.percentwidth == 100) {
- columns_add(COLUMNS(container), label, 0, 1);
- columns_force_left_align(COLUMNS(container), label);
- columns_add(COLUMNS(container), w, 0, 1);
- } else {
- gint percentages[2];
- percentages[1] = ctrl->listbox.percentwidth;
- percentages[0] = 100 - ctrl->listbox.percentwidth;
- columns_set_cols(COLUMNS(container), 2, percentages);
- columns_add(COLUMNS(container), label, 0, 1);
- columns_force_left_align(COLUMNS(container), label);
- columns_add(COLUMNS(container), w, 1, 1);
- columns_force_same_height(COLUMNS(container),
- label, w);
- }
- gtk_widget_show(label);
- gtk_widget_show(w);
-
- w = container;
- uc->label = label;
- }
-
- break;
- case CTRL_TEXT:
-#if !GTK_CHECK_VERSION(3,0,0)
- /*
- * Wrapping text widgets don't sit well with the GTK2
- * layout model, in which widgets state a minimum size
- * and the whole window then adjusts to the smallest
- * size it can sensibly take given its contents. A
- * wrapping text widget _has_ no clear minimum size;
- * instead it has a range of possibilities. It can be
- * one line deep but 2000 wide, or two lines deep and
- * 1000 pixels, or three by 867, or four by 500 and so
- * on. It can be as short as you like provided you
- * don't mind it being wide, or as narrow as you like
- * provided you don't mind it being tall.
- *
- * Therefore, it fits very badly into the layout model.
- * Hence the only thing to do is pick a width and let
- * it choose its own number of lines. To do this I'm
- * going to cheat a little. All new wrapping text
- * widgets will be created with a minimal text content
- * "X"; then, after the rest of the dialog box is set
- * up and its size calculated, the text widgets will be
- * told their width and given their real text, which
- * will cause the size to be recomputed in the y
- * direction (because many of them will expand to more
- * than one line).
- */
- uc->text = w = gtk_label_new("X");
- uc->textsig =
- g_signal_connect(G_OBJECT(w), "size-allocate",
- G_CALLBACK(label_sizealloc), dp);
-#else
- /*
- * In GTK3, this is all fixed, because the main aim of the
- * new 'height-for-width' geometry management is to make
- * wrapping labels behave sensibly. So now we can just do
- * the obvious thing.
- */
- uc->text = w = gtk_label_new(uc->ctrl->generic.label);
-#endif
- align_label_left(GTK_LABEL(w));
- gtk_label_set_line_wrap(GTK_LABEL(w), true);
- break;
- }
-
- assert(w != NULL);
-
- columns_add(cols, w,
- COLUMN_START(ctrl->generic.column),
- COLUMN_SPAN(ctrl->generic.column));
- if (left)
- columns_force_left_align(cols, w);
- if (ctrl->generic.align_next_to) {
- /*
- * Implement align_next_to by simply forcing the two
- * controls to have the same height of size allocation. At
- * least for the controls we're currently doing this with,
- * the GTK layout system will automatically vertically
- * centre each control within its allocation, which will
- * get the two controls aligned alongside each other
- * reasonably well.
- */
- struct uctrl *uc2 = dlg_find_byctrl(
- dp, ctrl->generic.align_next_to);
- assert(uc2);
- columns_force_same_height(cols, w, uc2->toplevel);
-
-#if GTK_CHECK_VERSION(3, 10, 0)
- /* Slightly nicer to align baselines than just vertically
- * centring, where the option is available */
- gtk_widget_set_valign(w, GTK_ALIGN_BASELINE);
- gtk_widget_set_valign(uc2->toplevel, GTK_ALIGN_BASELINE);
-#endif
- }
- gtk_widget_show(w);
-
- uc->toplevel = w;
- dlg_add_uctrl(dp, uc);
- }
-
- return ret;
-}
-
-struct selparam {
- struct dlgparam *dp;
- GtkNotebook *panels;
- GtkWidget *panel;
-#if !GTK_CHECK_VERSION(2,0,0)
- GtkWidget *treeitem;
-#else
- int depth;
- GtkTreePath *treepath;
-#endif
- struct Shortcuts shortcuts;
-};
-
-#if GTK_CHECK_VERSION(2,0,0)
-static void treeselection_changed(GtkTreeSelection *treeselection,
- gpointer data)
-{
- struct selparam **sps = (struct selparam **)data, *sp;
- GtkTreeModel *treemodel;
- GtkTreeIter treeiter;
- gint spindex;
- gint page_num;
-
- if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
- return;
-
- gtk_tree_model_get(treemodel, &treeiter, TREESTORE_PARAMS, &spindex, -1);
- sp = sps[spindex];
-
- page_num = gtk_notebook_page_num(sp->panels, sp->panel);
- gtk_notebook_set_current_page(sp->panels, page_num);
-
- sp->dp->curr_panel = sp;
- dlg_refresh(NULL, sp->dp);
-
- sp->dp->shortcuts = &sp->shortcuts;
-}
-#else
-static void treeitem_sel(GtkItem *item, gpointer data)
-{
- struct selparam *sp = (struct selparam *)data;
- gint page_num;
-
- page_num = gtk_notebook_page_num(sp->panels, sp->panel);
- gtk_notebook_set_page(sp->panels, page_num);
-
- sp->dp->curr_panel = sp;
- dlg_refresh(NULL, sp->dp);
-
- sp->dp->shortcuts = &sp->shortcuts;
- sp->dp->currtreeitem = sp->treeitem;
-}
-#endif
-
-bool dlg_is_visible(union control *ctrl, dlgparam *dp)
-{
- struct uctrl *uc = dlg_find_byctrl(dp, ctrl);
- /*
- * A control is visible if it belongs to _no_ notebook page (i.e.
- * it's one of the config-box-global buttons like Load or About),
- * or if it belongs to the currently selected page.
- */
- return uc->sp == NULL || uc->sp == dp->curr_panel;
-}
-
-#if !GTK_CHECK_VERSION(2,0,0)
-static bool tree_grab_focus(struct dlgparam *dp)
-{
- int i, f;
-
- /*
- * See if any of the treeitems has the focus.
- */
- f = -1;
- for (i = 0; i < dp->ntreeitems; i++)
- if (GTK_WIDGET_HAS_FOCUS(dp->treeitems[i])) {
- f = i;
- break;
- }
-
- if (f >= 0)
- return false;
- else {
- gtk_widget_grab_focus(dp->currtreeitem);
- return true;
- }
-}
-
-gint tree_focus(GtkContainer *container, GtkDirectionType direction,
- gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
-
- g_signal_stop_emission_by_name(G_OBJECT(container), "focus");
- /*
- * If there's a focused treeitem, we return false to cause the
- * focus to move on to some totally other control. If not, we
- * focus the selected one.
- */
- return tree_grab_focus(dp);
-}
-#endif
-
-gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
-
- if (event->keyval == GDK_KEY_Escape && dp->cancelbutton) {
- g_signal_emit_by_name(G_OBJECT(dp->cancelbutton), "clicked");
- return true;
- }
-
- if ((event->state & GDK_MOD1_MASK) &&
- (unsigned char)event->string[0] > 0 &&
- (unsigned char)event->string[0] <= 127) {
- int schr = (unsigned char)event->string[0];
- struct Shortcut *sc = &dp->shortcuts->sc[schr];
-
- switch (sc->action) {
- case SHORTCUT_TREE:
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_widget_grab_focus(sc->widget);
-#else
- tree_grab_focus(dp);
-#endif
- break;
- case SHORTCUT_FOCUS:
- gtk_widget_grab_focus(sc->widget);
- break;
- case SHORTCUT_UCTRL:
- /*
- * We must do something sensible with a uctrl.
- * Precisely what this is depends on the type of
- * control.
- */
- switch (sc->uc->ctrl->generic.type) {
- case CTRL_CHECKBOX:
- case CTRL_BUTTON:
- /* Check boxes and buttons get the focus _and_ get toggled. */
- gtk_widget_grab_focus(sc->uc->toplevel);
- g_signal_emit_by_name(G_OBJECT(sc->uc->toplevel), "clicked");
- break;
- case CTRL_FILESELECT:
- case CTRL_FONTSELECT:
- /* File/font selectors have their buttons pressed (ooer),
- * and focus transferred to the edit box. */
- g_signal_emit_by_name(G_OBJECT(sc->uc->button), "clicked");
- gtk_widget_grab_focus(sc->uc->entry);
- break;
- case CTRL_RADIO:
- /*
- * Radio buttons are fun, because they have
- * multiple shortcuts. We must find whether the
- * activated shortcut is the shortcut for the whole
- * group, or for a particular button. In the former
- * case, we find the currently selected button and
- * focus it; in the latter, we focus-and-click the
- * button whose shortcut was pressed.
- */
- if (schr == sc->uc->ctrl->radio.shortcut) {
- int i;
- for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
- if (gtk_toggle_button_get_active
- (GTK_TOGGLE_BUTTON(sc->uc->buttons[i]))) {
- gtk_widget_grab_focus(sc->uc->buttons[i]);
- }
- } else if (sc->uc->ctrl->radio.shortcuts) {
- int i;
- for (i = 0; i < sc->uc->ctrl->radio.nbuttons; i++)
- if (schr == sc->uc->ctrl->radio.shortcuts[i]) {
- gtk_widget_grab_focus(sc->uc->buttons[i]);
- g_signal_emit_by_name
- (G_OBJECT(sc->uc->buttons[i]), "clicked");
- }
- }
- break;
- case CTRL_LISTBOX:
-
-#if !GTK_CHECK_VERSION(2,4,0)
- if (sc->uc->optmenu) {
- GdkEventButton bev;
- gint returnval;
-
- gtk_widget_grab_focus(sc->uc->optmenu);
- /* Option menus don't work using the "clicked" signal.
- * We need to manufacture a button press event :-/ */
- bev.type = GDK_BUTTON_PRESS;
- bev.button = 1;
- g_signal_emit_by_name(G_OBJECT(sc->uc->optmenu),
- "button_press_event",
- &bev, &returnval);
- break;
- }
-#else
- if (sc->uc->combo) {
- gtk_widget_grab_focus(sc->uc->combo);
- gtk_combo_box_popup(GTK_COMBO_BOX(sc->uc->combo));
- break;
- }
-#endif
-#if !GTK_CHECK_VERSION(2,0,0)
- if (sc->uc->list) {
- /*
- * For GTK-1 style list boxes, we tell it to
- * focus one of its children, which appears to
- * do the Right Thing.
- */
- gtk_container_focus(GTK_CONTAINER(sc->uc->list),
- GTK_DIR_TAB_FORWARD);
- break;
- }
-#else
- if (sc->uc->treeview) {
- gtk_widget_grab_focus(sc->uc->treeview);
- break;
- }
-#endif
- unreachable("bad listbox type in win_key_press");
- }
- break;
- }
- }
-
- return false;
-}
-
-#if !GTK_CHECK_VERSION(2,0,0)
-gint tree_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
-
- if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up ||
- event->keyval == GDK_Down || event->keyval == GDK_KP_Down) {
- int dir, i, j = -1;
- for (i = 0; i < dp->ntreeitems; i++)
- if (widget == dp->treeitems[i])
- break;
- if (i < dp->ntreeitems) {
- if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
- dir = -1;
- else
- dir = +1;
-
- while (1) {
- i += dir;
- if (i < 0 || i >= dp->ntreeitems)
- break; /* nothing in that dir to select */
- /*
- * Determine if this tree item is visible.
- */
- {
- GtkWidget *w = dp->treeitems[i];
- bool vis = true;
- while (w && (GTK_IS_TREE_ITEM(w) || GTK_IS_TREE(w))) {
- if (!GTK_WIDGET_VISIBLE(w)) {
- vis = false;
- break;
- }
- w = w->parent;
- }
- if (vis) {
- j = i; /* got one */
- break;
- }
- }
- }
- }
- g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
- if (j >= 0) {
- g_signal_emit_by_name(G_OBJECT(dp->treeitems[j]), "toggle");
- gtk_widget_grab_focus(dp->treeitems[j]);
- }
- return true;
- }
-
- /*
- * It's nice for Left and Right to expand and collapse tree
- * branches.
- */
- if (event->keyval == GDK_Left || event->keyval == GDK_KP_Left) {
- g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
- gtk_tree_item_collapse(GTK_TREE_ITEM(widget));
- return true;
- }
- if (event->keyval == GDK_Right || event->keyval == GDK_KP_Right) {
- g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
- gtk_tree_item_expand(GTK_TREE_ITEM(widget));
- return true;
- }
-
- return false;
-}
-#endif
-
-static void shortcut_highlight(GtkWidget *labelw, int chr)
-{
- GtkLabel *label = GTK_LABEL(labelw);
- const gchar *currstr;
- gchar *pattern;
- int i;
-
-#if !GTK_CHECK_VERSION(2,0,0)
- {
- gchar *currstr_nonconst;
- gtk_label_get(label, &currstr_nonconst);
- currstr = currstr_nonconst;
- }
-#else
- currstr = gtk_label_get_text(label);
-#endif
-
- for (i = 0; currstr[i]; i++)
- if (tolower((unsigned char)currstr[i]) == chr) {
- pattern = dupprintf("%*s_", i, "");
- gtk_label_set_pattern(label, pattern);
- sfree(pattern);
- break;
- }
-}
-
-void shortcut_add(struct Shortcuts *scs, GtkWidget *labelw,
- int chr, int action, void *ptr)
-{
- if (chr == NO_SHORTCUT)
- return;
-
- chr = tolower((unsigned char)chr);
-
- assert(scs->sc[chr].action == SHORTCUT_EMPTY);
-
- scs->sc[chr].action = action;
-
- if (action == SHORTCUT_FOCUS || action == SHORTCUT_TREE) {
- scs->sc[chr].uc = NULL;
- scs->sc[chr].widget = (GtkWidget *)ptr;
- } else {
- scs->sc[chr].widget = NULL;
- scs->sc[chr].uc = (struct uctrl *)ptr;
- }
-
- shortcut_highlight(labelw, chr);
-}
-
-static int get_listitemheight(GtkWidget *w)
-{
-#if !GTK_CHECK_VERSION(2,0,0)
- GtkWidget *listitem = gtk_list_item_new_with_label("foo");
- GtkRequisition req;
- gtk_widget_size_request(listitem, &req);
- g_object_ref_sink(G_OBJECT(listitem));
- return req.height;
-#else
- int height;
- GtkCellRenderer *cr = gtk_cell_renderer_text_new();
-#if GTK_CHECK_VERSION(3,0,0)
- {
- GtkRequisition req;
- /*
- * Since none of my list items wraps in this GUI, no
- * interesting width-for-height behaviour should be happening,
- * so I don't think it should matter here whether I ask for
- * the minimum or natural height.
- */
- gtk_cell_renderer_get_preferred_size(cr, w, &req, NULL);
- height = req.height;
- }
-#else
- gtk_cell_renderer_get_size(cr, w, NULL, NULL, NULL, NULL, &height);
-#endif
- g_object_ref(G_OBJECT(cr));
- g_object_ref_sink(G_OBJECT(cr));
- g_object_unref(G_OBJECT(cr));
- return height;
-#endif
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-void initial_treeview_collapse(struct dlgparam *dp, GtkWidget *tree)
-{
- /*
- * Collapse the deeper branches of the treeview into the state we
- * like them to start off in. See comment below in do_config_box.
- */
- int i;
- for (i = 0; i < dp->nselparams; i++)
- if (dp->selparams[i]->depth >= 2)
- gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree),
- dp->selparams[i]->treepath);
-}
-#endif
-
-#if GTK_CHECK_VERSION(3,0,0)
-void treeview_map_event(GtkWidget *tree, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- GtkAllocation alloc;
- gtk_widget_get_allocation(tree, &alloc);
- gtk_widget_set_size_request(tree, alloc.width, -1);
- initial_treeview_collapse(dp, tree);
-}
-#endif
-
-GtkWidget *create_config_box(const char *title, Conf *conf,
- bool midsession, int protcfginfo,
- post_dialog_fn_t after, void *afterctx)
-{
- GtkWidget *window, *hbox, *vbox, *cols, *label,
- *tree, *treescroll, *panels, *panelvbox;
- int index, level, protocol;
- char *path;
-#if GTK_CHECK_VERSION(2,0,0)
- GtkTreeStore *treestore;
- GtkCellRenderer *treerenderer;
- GtkTreeViewColumn *treecolumn;
- GtkTreeSelection *treeselection;
- GtkTreeIter treeiterlevels[8];
-#else
- GtkTreeItem *treeitemlevels[8];
- GtkTree *treelevels[8];
-#endif
- struct dlgparam *dp;
- struct Shortcuts scs;
-
- struct selparam **selparams = NULL;
- size_t nselparams = 0, selparamsize = 0;
-
- dp = snew(struct dlgparam);
- dp->after = after;
- dp->afterctx = afterctx;
-
- dlg_init(dp);
-
- for (index = 0; index < lenof(scs.sc); index++) {
- scs.sc[index].action = SHORTCUT_EMPTY;
- }
-
- window = our_dialog_new();
-
- dp->ctrlbox = ctrl_new_box();
- protocol = conf_get_int(conf, CONF_protocol);
- setup_config_box(dp->ctrlbox, midsession, protocol, protcfginfo);
- unix_setup_config_box(dp->ctrlbox, midsession, protocol);
- gtk_setup_config_box(dp->ctrlbox, midsession, window);
-
- gtk_window_set_title(GTK_WINDOW(window), title);
- hbox = gtk_hbox_new(false, 4);
- our_dialog_add_to_content_area(GTK_WINDOW(window), hbox, true, true, 0);
- gtk_container_set_border_width(GTK_CONTAINER(hbox), 10);
- gtk_widget_show(hbox);
- vbox = gtk_vbox_new(false, 4);
- gtk_box_pack_start(GTK_BOX(hbox), vbox, false, false, 0);
- gtk_widget_show(vbox);
- cols = columns_new(4);
- gtk_box_pack_start(GTK_BOX(vbox), cols, false, false, 0);
- gtk_widget_show(cols);
- label = gtk_label_new("Category:");
- columns_add(COLUMNS(cols), label, 0, 1);
- columns_force_left_align(COLUMNS(cols), label);
- gtk_widget_show(label);
- treescroll = gtk_scrolled_window_new(NULL, NULL);
-#if GTK_CHECK_VERSION(2,0,0)
- treestore = gtk_tree_store_new
- (TREESTORE_NUM, G_TYPE_STRING, G_TYPE_INT);
- tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(treestore));
- gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), false);
- treerenderer = gtk_cell_renderer_text_new();
- treecolumn = gtk_tree_view_column_new_with_attributes
- ("Label", treerenderer, "text", 0, NULL);
- gtk_tree_view_append_column(GTK_TREE_VIEW(tree), treecolumn);
- treeselection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
- gtk_tree_selection_set_mode(treeselection, GTK_SELECTION_BROWSE);
- gtk_container_add(GTK_CONTAINER(treescroll), tree);
-#else
- tree = gtk_tree_new();
- gtk_tree_set_view_mode(GTK_TREE(tree), GTK_TREE_VIEW_ITEM);
- gtk_tree_set_selection_mode(GTK_TREE(tree), GTK_SELECTION_BROWSE);
- g_signal_connect(G_OBJECT(tree), "focus", G_CALLBACK(tree_focus), dp);
-#endif
- g_signal_connect(G_OBJECT(tree), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
- shortcut_add(&scs, label, 'g', SHORTCUT_TREE, tree);
- gtk_widget_show(treescroll);
- gtk_box_pack_start(GTK_BOX(vbox), treescroll, true, true, 0);
- panels = gtk_notebook_new();
- gtk_notebook_set_show_tabs(GTK_NOTEBOOK(panels), false);
- gtk_notebook_set_show_border(GTK_NOTEBOOK(panels), false);
- gtk_box_pack_start(GTK_BOX(hbox), panels, true, true, 0);
- gtk_widget_show(panels);
-
- panelvbox = NULL;
- path = NULL;
- level = 0;
- for (index = 0; index < dp->ctrlbox->nctrlsets; index++) {
- struct controlset *s = dp->ctrlbox->ctrlsets[index];
- GtkWidget *w;
-
- if (!*s->pathname) {
- w = layout_ctrls(dp, NULL, &scs, s, GTK_WINDOW(window));
-
- our_dialog_set_action_area(GTK_WINDOW(window), w);
- } else {
- int j = path ? ctrl_path_compare(s->pathname, path) : 0;
- if (j != INT_MAX) { /* add to treeview, start new panel */
- char *c;
-#if GTK_CHECK_VERSION(2,0,0)
- GtkTreeIter treeiter;
-#else
- GtkWidget *treeitem;
-#endif
- bool first;
-
- /*
- * We expect never to find an implicit path
- * component. For example, we expect never to see
- * A/B/C followed by A/D/E, because that would
- * _implicitly_ create A/D. All our path prefixes
- * are expected to contain actual controls and be
- * selectable in the treeview; so we would expect
- * to see A/D _explicitly_ before encountering
- * A/D/E.
- */
- assert(j == ctrl_path_elements(s->pathname) - 1);
-
- c = strrchr(s->pathname, '/');
- if (!c)
- c = s->pathname;
- else
- c++;
-
- path = s->pathname;
-
- first = (panelvbox == NULL);
-
- panelvbox = gtk_vbox_new(false, 4);
- gtk_widget_show(panelvbox);
- gtk_notebook_append_page(GTK_NOTEBOOK(panels), panelvbox,
- NULL);
-
- struct selparam *sp = snew(struct selparam);
-
- if (first) {
- gint page_num;
-
- page_num = gtk_notebook_page_num(GTK_NOTEBOOK(panels),
- panelvbox);
- gtk_notebook_set_current_page(GTK_NOTEBOOK(panels),
- page_num);
-
- dp->curr_panel = sp;
- }
-
- sgrowarray(selparams, selparamsize, nselparams);
- selparams[nselparams] = sp;
- sp->dp = dp;
- sp->panels = GTK_NOTEBOOK(panels);
- sp->panel = panelvbox;
- sp->shortcuts = scs; /* structure copy */
-
- assert(j-1 < level);
-
-#if GTK_CHECK_VERSION(2,0,0)
- if (j > 0)
- /* treeiterlevels[j-1] will always be valid because we
- * don't allow implicit path components; see above.
- */
- gtk_tree_store_append(treestore, &treeiter,
- &treeiterlevels[j-1]);
- else
- gtk_tree_store_append(treestore, &treeiter, NULL);
- gtk_tree_store_set(treestore, &treeiter,
- TREESTORE_PATH, c,
- TREESTORE_PARAMS, nselparams,
- -1);
- treeiterlevels[j] = treeiter;
-
- sp->depth = j;
- if (j > 0) {
- sp->treepath = gtk_tree_model_get_path(
- GTK_TREE_MODEL(treestore), &treeiterlevels[j-1]);
- /*
- * We are going to collapse all tree branches
- * at depth greater than 2, but not _yet_; see
- * the comment at the call to
- * gtk_tree_view_collapse_row below.
- */
- gtk_tree_view_expand_row(GTK_TREE_VIEW(tree),
- sp->treepath, false);
- } else {
- sp->treepath = NULL;
- }
-#else
- treeitem = gtk_tree_item_new_with_label(c);
- if (j > 0) {
- if (!treelevels[j-1]) {
- treelevels[j-1] = GTK_TREE(gtk_tree_new());
- gtk_tree_item_set_subtree
- (treeitemlevels[j-1],
- GTK_WIDGET(treelevels[j-1]));
- if (j < 2)
- gtk_tree_item_expand(treeitemlevels[j-1]);
- else
- gtk_tree_item_collapse(treeitemlevels[j-1]);
- }
- gtk_tree_append(treelevels[j-1], treeitem);
- } else {
- gtk_tree_append(GTK_TREE(tree), treeitem);
- }
- treeitemlevels[j] = GTK_TREE_ITEM(treeitem);
- treelevels[j] = NULL;
-
- g_signal_connect(G_OBJECT(treeitem), "key_press_event",
- G_CALLBACK(tree_key_press), dp);
- g_signal_connect(G_OBJECT(treeitem), "focus_in_event",
- G_CALLBACK(widget_focus), dp);
-
- gtk_widget_show(treeitem);
-
- if (first)
- gtk_tree_select_child(GTK_TREE(tree), treeitem);
- sp->treeitem = treeitem;
-#endif
-
- level = j+1;
- nselparams++;
- }
-
- w = layout_ctrls(dp, selparams[nselparams-1],
- &selparams[nselparams-1]->shortcuts, s, NULL);
- gtk_box_pack_start(GTK_BOX(panelvbox), w, false, false, 0);
- gtk_widget_show(w);
- }
- }
-
-#if GTK_CHECK_VERSION(2,0,0)
- /*
- * We want our tree view to come up with all branches at depth 2
- * or more collapsed. However, if we start off with those branches
- * collapsed, then the tree view's size request will be calculated
- * based on the width of the collapsed tree, and then when the
- * collapsed branches are expanded later, the tree view will
- * jarringly change size.
- *
- * So instead we start with everything expanded; then, once the
- * tree view has computed its resulting width requirement, we
- * collapse the relevant rows, but force the width to be the value
- * we just retrieved. This arranges that the tree view is wide
- * enough to have all branches expanded without further resizing.
- */
-
- dp->nselparams = nselparams;
- dp->selparams = selparams;
-
-#if !GTK_CHECK_VERSION(3,0,0)
- {
- /*
- * In GTK2, we can just do the job right now.
- */
- GtkRequisition req;
- gtk_widget_size_request(tree, &req);
- initial_treeview_collapse(dp, tree);
- gtk_widget_set_size_request(tree, req.width, -1);
- }
-#else
- /*
- * But in GTK3, we have to wait until the widget is about to be
- * mapped, because the size computation won't have been done yet.
- */
- g_signal_connect(G_OBJECT(tree), "map",
- G_CALLBACK(treeview_map_event), dp);
-#endif /* GTK 2 vs 3 */
-#endif /* GTK 2+ vs 1 */
-
-#if GTK_CHECK_VERSION(2,0,0)
- g_signal_connect(G_OBJECT(treeselection), "changed",
- G_CALLBACK(treeselection_changed), selparams);
-#else
- dp->ntreeitems = nselparams;
- dp->treeitems = snewn(dp->ntreeitems, GtkWidget *);
- for (index = 0; index < nselparams; index++) {
- g_signal_connect(G_OBJECT(selparams[index]->treeitem), "select",
- G_CALLBACK(treeitem_sel),
- selparams[index]);
- dp->treeitems[index] = selparams[index]->treeitem;
- }
-#endif
-
- dp->data = conf;
- dlg_refresh(NULL, dp);
-
- dp->shortcuts = &selparams[0]->shortcuts;
-#if !GTK_CHECK_VERSION(2,0,0)
- dp->currtreeitem = dp->treeitems[0];
-#endif
- dp->lastfocus = NULL;
- dp->retval = -1;
- dp->window = window;
-
- set_window_icon(window, cfg_icon, n_cfg_icon);
-
-#if !GTK_CHECK_VERSION(2,0,0)
- gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(treescroll),
- tree);
-#endif
- gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(treescroll),
- GTK_POLICY_NEVER,
- GTK_POLICY_AUTOMATIC);
- gtk_widget_show(tree);
-
- gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
- gtk_widget_show(window);
-
- /*
- * Set focus into the first available control.
- */
- for (index = 0; index < dp->ctrlbox->nctrlsets; index++) {
- struct controlset *s = dp->ctrlbox->ctrlsets[index];
- bool done = false;
- int j;
-
- if (*s->pathname) {
- for (j = 0; j < s->ncontrols; j++)
- if (s->ctrls[j]->generic.type != CTRL_TABDELAY &&
- s->ctrls[j]->generic.type != CTRL_COLUMNS &&
- s->ctrls[j]->generic.type != CTRL_TEXT) {
- dlg_set_focus(s->ctrls[j], dp);
- dp->lastfocus = s->ctrls[j];
- done = true;
- break;
- }
- }
- if (done)
- break;
- }
-
- g_signal_connect(G_OBJECT(window), "destroy",
- G_CALLBACK(dlgparam_destroy), dp);
- g_signal_connect(G_OBJECT(window), "key_press_event",
- G_CALLBACK(win_key_press), dp);
-
- return window;
-}
-
-static void dlgparam_destroy(GtkWidget *widget, gpointer data)
-{
- struct dlgparam *dp = (struct dlgparam *)data;
- dp->after(dp->afterctx, dp->retval);
- dlg_cleanup(dp);
- ctrl_free_box(dp->ctrlbox);
-#if GTK_CHECK_VERSION(2,0,0)
- if (dp->selparams) {
- for (size_t i = 0; i < dp->nselparams; i++) {
- if (dp->selparams[i]->treepath)
- gtk_tree_path_free(dp->selparams[i]->treepath);
- sfree(dp->selparams[i]);
- }
- sfree(dp->selparams);
- }
-#endif
- sfree(dp);
-}
-
-static void messagebox_handler(union control *ctrl, dlgparam *dp,
- void *data, int event)
-{
- if (event == EVENT_ACTION)
- dlg_end(dp, ctrl->generic.context.i);
-}
-
-static const struct message_box_button button_array_yn[] = {
- {"Yes", 'y', +1, 1},
- {"No", 'n', -1, 0},
-};
-const struct message_box_buttons buttons_yn = {
- button_array_yn, lenof(button_array_yn),
-};
-static const struct message_box_button button_array_ok[] = {
- {"OK", 'o', 1, 1},
-};
-const struct message_box_buttons buttons_ok = {
- button_array_ok, lenof(button_array_ok),
-};
-
-static GtkWidget *create_message_box_general(
- GtkWidget *parentwin, const char *title, const char *msg, int minwid,
- bool selectable, const struct message_box_buttons *buttons,
- post_dialog_fn_t after, void *afterctx,
- GtkWidget *(*action_postproc)(GtkWidget *, void *), void *postproc_ctx)
-{
- GtkWidget *window, *w0, *w1;
- struct controlset *s0, *s1;
- union control *c, *textctrl;
- struct dlgparam *dp;
- struct Shortcuts scs;
- int i, index, ncols, min_type;
-
- dp = snew(struct dlgparam);
- dp->after = after;
- dp->afterctx = afterctx;
-
- dlg_init(dp);
-
- for (index = 0; index < lenof(scs.sc); index++) {
- scs.sc[index].action = SHORTCUT_EMPTY;
- }
-
- dp->ctrlbox = ctrl_new_box();
-
- /*
- * Count up the number of buttons and find out what kinds there
- * are.
- */
- ncols = 0;
- min_type = +1;
- for (i = 0; i < buttons->nbuttons; i++) {
- const struct message_box_button *button = &buttons->buttons[i];
- ncols++;
- if (min_type > button->type)
- min_type = button->type;
- assert(button->value >= 0); /* <0 means no return value available */
- }
-
- s0 = ctrl_getset(dp->ctrlbox, "", "", "");
- c = ctrl_columns(s0, 2, 50, 50);
- c->columns.ncols = s0->ncolumns = ncols;
- c->columns.percentages = sresize(c->columns.percentages, ncols, int);
- for (index = 0; index < ncols; index++)
- c->columns.percentages[index] = (index+1)*100/ncols - index*100/ncols;
- index = 0;
- for (i = 0; i < buttons->nbuttons; i++) {
- const struct message_box_button *button = &buttons->buttons[i];
- c = ctrl_pushbutton(s0, button->title, button->shortcut,
- HELPCTX(no_help), messagebox_handler,
- I(button->value));
- c->generic.column = index++;
- if (button->type > 0)
- c->button.isdefault = true;
-
- /* We always arrange that _some_ button is labelled as
- * 'iscancel', so that pressing Escape will always cause
- * win_key_press to do something. The button we choose is
- * whichever has the smallest type value: this means that real
- * cancel buttons (labelled -1) will be picked if one is
- * there, or in cases where the options are yes/no (1,0) then
- * no will be picked, and if there's only one option (a box
- * that really is just showing a _message_ and not even asking
- * a question) then that will be picked. */
- if (button->type == min_type)
- c->button.iscancel = true;
- }
-
- s1 = ctrl_getset(dp->ctrlbox, "x", "", "");
- textctrl = ctrl_text(s1, msg, HELPCTX(no_help));
-
- window = our_dialog_new();
- gtk_window_set_title(GTK_WINDOW(window), title);
- w0 = layout_ctrls(dp, NULL, &scs, s0, GTK_WINDOW(window));
- if (action_postproc)
- w0 = action_postproc(w0, postproc_ctx);
- our_dialog_set_action_area(GTK_WINDOW(window), w0);
- gtk_widget_show(w0);
- w1 = layout_ctrls(dp, NULL, &scs, s1, GTK_WINDOW(window));
- gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
- gtk_widget_set_size_request(w1, minwid+20, -1);
- our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0);
- gtk_widget_show(w1);
-
- dp->shortcuts = &scs;
- dp->lastfocus = NULL;
- dp->retval = 0;
- dp->window = window;
-
- if (selectable) {
-#if GTK_CHECK_VERSION(2,0,0)
- struct uctrl *uc = dlg_find_byctrl(dp, textctrl);
- gtk_label_set_selectable(GTK_LABEL(uc->text), true);
-
- /*
- * GTK selectable labels have a habit of selecting their
- * entire contents when they gain focus. It's ugly to have
- * text in a message box start up all selected, so we suppress
- * this by manually selecting none of it - but we must do this
- * when the widget _already has_ focus, otherwise our work
- * will be undone when it gains it shortly.
- */
- gtk_widget_grab_focus(uc->text);
- gtk_label_select_region(GTK_LABEL(uc->text), 0, 0);
-#else
- (void)textctrl; /* placate warning */
-#endif
- }
-
- if (parentwin) {
- set_transient_window_pos(parentwin, window);
- gtk_window_set_transient_for(GTK_WINDOW(window),
- GTK_WINDOW(parentwin));
- } else
- gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
- gtk_container_set_focus_child(GTK_CONTAINER(window), NULL);
- gtk_widget_show(window);
- gtk_window_set_focus(GTK_WINDOW(window), NULL);
-
-#if !GTK_CHECK_VERSION(2,0,0)
- dp->currtreeitem = NULL;
- dp->treeitems = NULL;
-#else
- dp->selparams = NULL;
-#endif
-
- g_signal_connect(G_OBJECT(window), "destroy",
- G_CALLBACK(dlgparam_destroy), dp);
- g_signal_connect(G_OBJECT(window), "key_press_event",
- G_CALLBACK(win_key_press), dp);
-
- return window;
-}
-
-GtkWidget *create_message_box(
- GtkWidget *parentwin, const char *title, const char *msg, int minwid,
- bool selectable, const struct message_box_buttons *buttons,
- post_dialog_fn_t after, void *afterctx)
-{
- return create_message_box_general(
- parentwin, title, msg, minwid, selectable, buttons, after, afterctx,
- NULL /* action_postproc */, NULL /* postproc_ctx */);
-}
-
-struct verify_ssh_host_key_dialog_ctx {
- char *host;
- int port;
- char *keytype;
- char *keystr;
- char *more_info;
- void (*callback)(void *callback_ctx, int result);
- void *callback_ctx;
- Seat *seat;
-
- GtkWidget *main_dialog;
- GtkWidget *more_info_dialog;
-};
-
-static void verify_ssh_host_key_result_callback(void *vctx, int result)
-{
- struct verify_ssh_host_key_dialog_ctx *ctx =
- (struct verify_ssh_host_key_dialog_ctx *)vctx;
-
- if (result >= 0) {
- int logical_result;
-
- /*
- * Convert the dialog-box return value (one of three
- * possibilities) into the return value we pass back to the SSH
- * code (one of only two possibilities, because the SSH code
- * doesn't care whether we saved the host key or not).
- */
- if (result == 2) {
- store_host_key(ctx->host, ctx->port, ctx->keytype, ctx->keystr);
- logical_result = 1; /* continue with connection */
- } else if (result == 1) {
- logical_result = 1; /* continue with connection */
- } else {
- logical_result = 0; /* do not continue with connection */
- }
-
- ctx->callback(ctx->callback_ctx, logical_result);
- }
-
- /*
- * Clean up this context structure, whether or not a result was
- * ever actually delivered from the dialog box.
- */
- unregister_dialog(ctx->seat, DIALOG_SLOT_NETWORK_PROMPT);
-
- if (ctx->more_info_dialog)
- gtk_widget_destroy(ctx->more_info_dialog);
-
- sfree(ctx->host);
- sfree(ctx->keytype);
- sfree(ctx->keystr);
- sfree(ctx->more_info);
- sfree(ctx);
-}
-
-static GtkWidget *add_more_info_button(GtkWidget *w, void *vctx)
-{
- GtkWidget *box = gtk_hbox_new(false, 10);
- gtk_widget_show(box);
- gtk_box_pack_end(GTK_BOX(box), w, false, true, 0);
- GtkWidget *button = gtk_button_new_with_label("More info...");
- gtk_widget_show(button);
- gtk_box_pack_start(GTK_BOX(box), button, false, true, 0);
- *(GtkWidget **)vctx = button;
- return box;
-}
-
-static void more_info_closed(void *vctx, int result)
-{
- struct verify_ssh_host_key_dialog_ctx *ctx =
- (struct verify_ssh_host_key_dialog_ctx *)vctx;
-
- ctx->more_info_dialog = NULL;
-}
-
-static void more_info_button_clicked(GtkButton *button, gpointer vctx)
-{
- struct verify_ssh_host_key_dialog_ctx *ctx =
- (struct verify_ssh_host_key_dialog_ctx *)vctx;
-
- if (ctx->more_info_dialog)
- return;
-
- ctx->more_info_dialog = create_message_box(
- ctx->main_dialog, "Host key information", ctx->more_info,
- string_width("SHA256 fingerprint: ecdsa-sha2-nistp521 521 "
- "abcdefghkmnopqrsuvwxyzABCDEFGHJKLMNOPQRSTUW"), true,
- &buttons_ok, more_info_closed, ctx);
-}
-
-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)
-{
- static const char absenttxt[] =
- "The server's host key is not cached. You have no guarantee "
- "that the server is the computer you think it is.\n"
- "The server's %s key fingerprint is:\n"
- "%s\n"
- "If you trust this host, press \"Accept\" to add the key to "
- "PuTTY's cache and carry on connecting.\n"
- "If you want to carry on connecting just once, without "
- "adding the key to the cache, press \"Connect Once\".\n"
- "If you do not trust this host, press \"Cancel\" to abandon the "
- "connection.";
- static const char wrongtxt[] =
- "WARNING - POTENTIAL SECURITY BREACH!\n"
- "The server's host key does not match the one PuTTY has "
- "cached. This means that either the server administrator "
- "has changed the host key, or you have actually connected "
- "to another computer pretending to be the server.\n"
- "The new %s key fingerprint is:\n"
- "%s\n"
- "If you were expecting this change and trust the new key, "
- "press \"Accept\" to update PuTTY's cache and continue connecting.\n"
- "If you want to carry on connecting but without updating "
- "the cache, press \"Connect Once\".\n"
- "If you want to abandon the connection completely, press "
- "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed "
- "safe choice.";
- static const struct message_box_button button_array_hostkey[] = {
- {"Accept", 'a', 0, 2},
- {"Connect Once", 'o', 0, 1},
- {"Cancel", 'c', -1, 0},
- };
- static const struct message_box_buttons buttons_hostkey = {
- button_array_hostkey, lenof(button_array_hostkey),
- };
-
- char *text;
- int ret;
- struct verify_ssh_host_key_dialog_ctx *result_ctx;
- GtkWidget *mainwin, *msgbox;
-
- /*
- * Verify the key.
- */
- ret = verify_host_key(host, port, keytype, keystr);
-
- if (ret == 0) /* success - key matched OK */
- return 1;
-
- FingerprintType fptype_default =
- ssh2_pick_default_fingerprint(fingerprints);
-
- text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype,
- fingerprints[fptype_default]);
-
- result_ctx = snew(struct verify_ssh_host_key_dialog_ctx);
- result_ctx->callback = callback;
- result_ctx->callback_ctx = ctx;
- result_ctx->host = dupstr(host);
- result_ctx->port = port;
- result_ctx->keytype = dupstr(keytype);
- result_ctx->keystr = dupstr(keystr);
- result_ctx->seat = seat;
-
- mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
- GtkWidget *more_info_button = NULL;
- msgbox = create_message_box_general(
- mainwin, "PuTTY Security Alert", text,
- string_width(fingerprints[fptype_default]), true,
- &buttons_hostkey, verify_ssh_host_key_result_callback, result_ctx,
- add_more_info_button, &more_info_button);
-
- result_ctx->main_dialog = msgbox;
- result_ctx->more_info_dialog = NULL;
-
- strbuf *sb = strbuf_new();
- if (fingerprints[SSH_FPTYPE_SHA256])
- strbuf_catf(sb, "SHA256 fingerprint: %s\n",
- fingerprints[SSH_FPTYPE_SHA256]);
- if (fingerprints[SSH_FPTYPE_MD5])
- strbuf_catf(sb, "MD5 fingerprint: %s\n",
- fingerprints[SSH_FPTYPE_MD5]);
- strbuf_catf(sb, "Full text of host's public key:");
- /* We have to manually wrap the public key, or else the GtkLabel
- * will resize itself to accommodate the longest word, which will
- * lead to a hilariously wide message box. */
- for (const char *p = keydisp, *q = p + strlen(p); p < q ;) {
- size_t linelen = q-p;
- if (linelen > 72)
- linelen = 72;
- put_byte(sb, '\n');
- put_data(sb, p, linelen);
- p += linelen;
- }
- result_ctx->more_info = strbuf_to_str(sb);
-
- g_signal_connect(G_OBJECT(more_info_button), "clicked",
- G_CALLBACK(more_info_button_clicked), result_ctx);
-
- register_dialog(seat, DIALOG_SLOT_NETWORK_PROMPT, msgbox);
-
- sfree(text);
-
- return -1; /* dialog still in progress */
-}
-
-struct simple_prompt_result_ctx {
- void (*callback)(void *callback_ctx, int result);
- void *callback_ctx;
- Seat *seat;
- enum DialogSlot dialog_slot;
-};
-
-static void simple_prompt_result_callback(void *vctx, int result)
-{
- struct simple_prompt_result_ctx *ctx =
- (struct simple_prompt_result_ctx *)vctx;
-
- unregister_dialog(ctx->seat, ctx->dialog_slot);
-
- if (result >= 0)
- ctx->callback(ctx->callback_ctx, result);
-
- /*
- * Clean up this context structure, whether or not a result was
- * ever actually delivered from the dialog box.
- */
- sfree(ctx);
-}
-
-/*
- * Ask whether the selected algorithm is acceptable (since it was
- * below the configured 'warn' threshold).
- */
-int gtk_seat_confirm_weak_crypto_primitive(
- Seat *seat, const char *algtype, const char *algname,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- static const char msg[] =
- "The first %s supported by the server is "
- "%s, which is below the configured warning threshold.\n"
- "Continue with connection?";
-
- char *text;
- struct simple_prompt_result_ctx *result_ctx;
- GtkWidget *mainwin, *msgbox;
-
- text = dupprintf(msg, algtype, algname);
-
- result_ctx = snew(struct simple_prompt_result_ctx);
- result_ctx->callback = callback;
- result_ctx->callback_ctx = ctx;
- result_ctx->seat = seat;
- result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT;
-
- mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
- msgbox = create_message_box(
- mainwin, "PuTTY Security Alert", text,
- string_width("Reasonably long line of text as a width template"),
- false, &buttons_yn, simple_prompt_result_callback, result_ctx);
- register_dialog(seat, result_ctx->dialog_slot, msgbox);
-
- sfree(text);
-
- return -1; /* dialog still in progress */
-}
-
-int gtk_seat_confirm_weak_cached_hostkey(
- Seat *seat, const char *algname, const char *betteralgs,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- 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"
- "Continue with connection?";
-
- char *text;
- struct simple_prompt_result_ctx *result_ctx;
- GtkWidget *mainwin, *msgbox;
-
- text = dupprintf(msg, algname, betteralgs);
-
- result_ctx = snew(struct simple_prompt_result_ctx);
- result_ctx->callback = callback;
- result_ctx->callback_ctx = ctx;
- result_ctx->seat = seat;
- result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT;
-
- mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
- msgbox = create_message_box(
- mainwin, "PuTTY Security Alert", text,
- string_width("is ecdsa-nistp521, which is below the configured"
- " warning threshold."),
- false, &buttons_yn, simple_prompt_result_callback, result_ctx);
- register_dialog(seat, result_ctx->dialog_slot, msgbox);
-
- sfree(text);
-
- return -1; /* dialog still in progress */
-}
-
-void old_keyfile_warning(void)
-{
- /*
- * This should never happen on Unix. We hope.
- */
-}
-
-void nonfatal_message_box(void *window, const char *msg)
-{
- char *title = dupcat(appname, " Error");
- create_message_box(
- window, title, msg,
- string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"),
- false, &buttons_ok, trivial_post_dialog_fn, NULL);
- sfree(title);
-}
-
-void nonfatal(const char *p, ...)
-{
- va_list ap;
- char *msg;
- va_start(ap, p);
- msg = dupvprintf(p, ap);
- va_end(ap);
- nonfatal_message_box(NULL, msg);
- sfree(msg);
-}
-
-static GtkWidget *aboutbox = NULL;
-
-static void about_window_destroyed(GtkWidget *widget, gpointer data)
-{
- aboutbox = NULL;
-}
-
-static void about_close_clicked(GtkButton *button, gpointer data)
-{
- gtk_widget_destroy(aboutbox);
- aboutbox = NULL;
-}
-
-static void about_key_press(GtkWidget *widget, GdkEventKey *event,
- gpointer data)
-{
- if (event->keyval == GDK_KEY_Escape && aboutbox) {
- gtk_widget_destroy(aboutbox);
- aboutbox = NULL;
- }
-}
-
-static void licence_clicked(GtkButton *button, gpointer data)
-{
- char *title;
-
- title = dupcat(appname, " Licence");
- assert(aboutbox != NULL);
- create_message_box(aboutbox, title, LICENCE_TEXT("\n\n"),
- string_width("LONGISH LINE OF TEXT SO THE LICENCE"
- " BOX ISN'T EXCESSIVELY TALL AND THIN"),
- true, &buttons_ok, trivial_post_dialog_fn, NULL);
- sfree(title);
-}
-
-void about_box(void *window)
-{
- GtkWidget *w;
- GtkBox *action_area;
- char *title;
-
- if (aboutbox) {
- gtk_widget_grab_focus(aboutbox);
- return;
- }
-
- aboutbox = our_dialog_new();
- gtk_container_set_border_width(GTK_CONTAINER(aboutbox), 10);
- title = dupcat("About ", appname);
- gtk_window_set_title(GTK_WINDOW(aboutbox), title);
- sfree(title);
-
- g_signal_connect(G_OBJECT(aboutbox), "destroy",
- G_CALLBACK(about_window_destroyed), NULL);
-
- w = gtk_button_new_with_label("Close");
- gtk_widget_set_can_default(w, true);
- gtk_window_set_default(GTK_WINDOW(aboutbox), w);
- action_area = our_dialog_make_action_hbox(GTK_WINDOW(aboutbox));
- gtk_box_pack_end(action_area, w, false, false, 0);
- g_signal_connect(G_OBJECT(w), "clicked",
- G_CALLBACK(about_close_clicked), NULL);
- gtk_widget_show(w);
-
- w = gtk_button_new_with_label("View Licence");
- gtk_widget_set_can_default(w, true);
- gtk_box_pack_end(action_area, w, false, false, 0);
- g_signal_connect(G_OBJECT(w), "clicked",
- G_CALLBACK(licence_clicked), NULL);
- gtk_widget_show(w);
-
- {
- char *buildinfo_text = buildinfo("\n");
- char *label_text = dupprintf
- ("%s\n\n%s\n\n%s\n\n%s",
- appname, ver, buildinfo_text,
- "Copyright " SHORT_COPYRIGHT_DETAILS ". All rights reserved");
- w = gtk_label_new(label_text);
- gtk_label_set_justify(GTK_LABEL(w), GTK_JUSTIFY_CENTER);
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_label_set_selectable(GTK_LABEL(w), true);
-#endif
- sfree(label_text);
- }
- our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, false, false, 0);
-#if GTK_CHECK_VERSION(2,0,0)
- /*
- * Same precautions against initial select-all as in
- * create_message_box().
- */
- gtk_widget_grab_focus(w);
- gtk_label_select_region(GTK_LABEL(w), 0, 0);
-#endif
- gtk_widget_show(w);
-
- g_signal_connect(G_OBJECT(aboutbox), "key_press_event",
- G_CALLBACK(about_key_press), NULL);
-
- set_transient_window_pos(GTK_WIDGET(window), aboutbox);
- if (window)
- gtk_window_set_transient_for(GTK_WINDOW(aboutbox),
- GTK_WINDOW(window));
- gtk_container_set_focus_child(GTK_CONTAINER(aboutbox), NULL);
- gtk_widget_show(aboutbox);
- gtk_window_set_focus(GTK_WINDOW(aboutbox), NULL);
-}
-
-#define LOGEVENT_INITIAL_MAX 128
-#define LOGEVENT_CIRCULAR_MAX 128
-
-struct eventlog_stuff {
- GtkWidget *parentwin, *window;
- struct controlbox *eventbox;
- struct Shortcuts scs;
- struct dlgparam dp;
- union control *listctrl;
- char **events_initial;
- char **events_circular;
- int ninitial, ncircular, circular_first;
- strbuf *seldata;
- int sellen;
- bool ignore_selchange;
-};
-
-static void eventlog_destroy(GtkWidget *widget, gpointer data)
-{
- eventlog_stuff *es = (eventlog_stuff *)data;
-
- es->window = NULL;
- dlg_cleanup(&es->dp);
- ctrl_free_box(es->eventbox);
-}
-static void eventlog_ok_handler(union control *ctrl, dlgparam *dp,
- void *data, int event)
-{
- if (event == EVENT_ACTION)
- dlg_end(dp, 0);
-}
-static void eventlog_list_handler(union control *ctrl, dlgparam *dp,
- void *data, int event)
-{
- eventlog_stuff *es = (eventlog_stuff *)data;
-
- if (event == EVENT_REFRESH) {
- int i;
-
- dlg_update_start(ctrl, dp);
- dlg_listbox_clear(ctrl, dp);
- for (i = 0; i < es->ninitial; i++) {
- dlg_listbox_add(ctrl, dp, es->events_initial[i]);
- }
- for (i = 0; i < es->ncircular; i++) {
- dlg_listbox_add(ctrl, dp, es->events_circular[(es->circular_first + i) % LOGEVENT_CIRCULAR_MAX]);
- }
- dlg_update_done(ctrl, dp);
- } else if (event == EVENT_SELCHANGE) {
- int i;
-
- /*
- * If this SELCHANGE event is happening as a result of
- * deliberate deselection because someone else has grabbed
- * the selection, the last thing we want to do is pre-empt
- * them.
- */
- if (es->ignore_selchange)
- return;
-
- /*
- * Construct the data to use as the selection.
- */
- strbuf_clear(es->seldata);
- for (i = 0; i < es->ninitial; i++) {
- if (dlg_listbox_issel(ctrl, dp, i))
- strbuf_catf(es->seldata, "%s\n", es->events_initial[i]);
- }
- for (i = 0; i < es->ncircular; i++) {
- if (dlg_listbox_issel(ctrl, dp, es->ninitial + i)) {
- int j = (es->circular_first + i) % LOGEVENT_CIRCULAR_MAX;
- strbuf_catf(es->seldata, "%s\n", es->events_circular[j]);
- }
- }
-
- if (gtk_selection_owner_set(es->window, GDK_SELECTION_PRIMARY,
- GDK_CURRENT_TIME)) {
- gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY,
- GDK_SELECTION_TYPE_STRING, 1);
- gtk_selection_add_target(es->window, GDK_SELECTION_PRIMARY,
- compound_text_atom, 1);
- }
-
- }
-}
-
-void eventlog_selection_get(GtkWidget *widget, GtkSelectionData *seldata,
- guint info, guint time_stamp, gpointer data)
-{
- eventlog_stuff *es = (eventlog_stuff *)data;
-
- gtk_selection_data_set(seldata, gtk_selection_data_get_target(seldata), 8,
- es->seldata->u, es->seldata->len);
-}
-
-gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
- gpointer data)
-{
- eventlog_stuff *es = (eventlog_stuff *)data;
- struct uctrl *uc;
-
- /*
- * Deselect everything in the list box.
- */
- uc = dlg_find_byctrl(&es->dp, es->listctrl);
- es->ignore_selchange = true;
-#if !GTK_CHECK_VERSION(2,0,0)
- assert(uc->list);
- gtk_list_unselect_all(GTK_LIST(uc->list));
-#else
- assert(uc->treeview);
- gtk_tree_selection_unselect_all
- (gtk_tree_view_get_selection(GTK_TREE_VIEW(uc->treeview)));
-#endif
- es->ignore_selchange = false;
-
- return true;
-}
-
-void showeventlog(eventlog_stuff *es, void *parentwin)
-{
- GtkWidget *window, *w0, *w1;
- GtkWidget *parent = GTK_WIDGET(parentwin);
- struct controlset *s0, *s1;
- union control *c;
- int index;
- char *title;
-
- if (es->window) {
- gtk_widget_grab_focus(es->window);
- return;
- }
-
- dlg_init(&es->dp);
-
- for (index = 0; index < lenof(es->scs.sc); index++) {
- es->scs.sc[index].action = SHORTCUT_EMPTY;
- }
-
- es->eventbox = ctrl_new_box();
-
- s0 = ctrl_getset(es->eventbox, "", "", "");
- ctrl_columns(s0, 3, 33, 34, 33);
- c = ctrl_pushbutton(s0, "Close", 'c', HELPCTX(no_help),
- eventlog_ok_handler, P(NULL));
- c->button.column = 1;
- c->button.isdefault = true;
-
- s1 = ctrl_getset(es->eventbox, "x", "", "");
- es->listctrl = c = ctrl_listbox(s1, NULL, NO_SHORTCUT, HELPCTX(no_help),
- eventlog_list_handler, P(es));
- c->listbox.height = 10;
- c->listbox.multisel = 2;
- c->listbox.ncols = 3;
- c->listbox.percentages = snewn(3, int);
- c->listbox.percentages[0] = 25;
- c->listbox.percentages[1] = 10;
- c->listbox.percentages[2] = 65;
-
- es->window = window = our_dialog_new();
- title = dupcat(appname, " Event Log");
- gtk_window_set_title(GTK_WINDOW(window), title);
- sfree(title);
- w0 = layout_ctrls(&es->dp, NULL, &es->scs, s0, GTK_WINDOW(window));
- our_dialog_set_action_area(GTK_WINDOW(window), w0);
- gtk_widget_show(w0);
- w1 = layout_ctrls(&es->dp, NULL, &es->scs, s1, GTK_WINDOW(window));
- gtk_container_set_border_width(GTK_CONTAINER(w1), 10);
- gtk_widget_set_size_request(w1, 20 + string_width
- ("LINE OF TEXT GIVING WIDTH OF EVENT LOG IS "
- "QUITE LONG 'COS SSH LOG ENTRIES ARE WIDE"),
- -1);
- our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0);
- gtk_widget_show(w1);
-
- es->dp.data = es;
- es->dp.shortcuts = &es->scs;
- es->dp.lastfocus = NULL;
- es->dp.retval = 0;
- es->dp.window = window;
-
- dlg_refresh(NULL, &es->dp);
-
- if (parent) {
- set_transient_window_pos(parent, window);
- gtk_window_set_transient_for(GTK_WINDOW(window),
- GTK_WINDOW(parent));
- } else
- gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
- gtk_widget_show(window);
-
- g_signal_connect(G_OBJECT(window), "destroy",
- G_CALLBACK(eventlog_destroy), es);
- g_signal_connect(G_OBJECT(window), "key_press_event",
- G_CALLBACK(win_key_press), &es->dp);
- g_signal_connect(G_OBJECT(window), "selection_get",
- G_CALLBACK(eventlog_selection_get), es);
- g_signal_connect(G_OBJECT(window), "selection_clear_event",
- G_CALLBACK(eventlog_selection_clear), es);
-}
-
-eventlog_stuff *eventlogstuff_new(void)
-{
- eventlog_stuff *es = snew(eventlog_stuff);
- memset(es, 0, sizeof(*es));
- es->seldata = strbuf_new();
- return es;
-}
-
-void eventlogstuff_free(eventlog_stuff *es)
-{
- int i;
-
- if (es->events_initial) {
- for (i = 0; i < LOGEVENT_INITIAL_MAX; i++)
- sfree(es->events_initial[i]);
- sfree(es->events_initial);
- }
- if (es->events_circular) {
- for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++)
- sfree(es->events_circular[i]);
- sfree(es->events_circular);
- }
- strbuf_free(es->seldata);
-
- sfree(es);
-}
-
-void logevent_dlg(eventlog_stuff *es, const char *string)
-{
- char timebuf[40];
- struct tm tm;
- char **location;
- size_t i;
-
- if (es->ninitial == 0) {
- es->events_initial = sresize(es->events_initial, LOGEVENT_INITIAL_MAX, char *);
- for (i = 0; i < LOGEVENT_INITIAL_MAX; i++)
- es->events_initial[i] = NULL;
- es->events_circular = sresize(es->events_circular, LOGEVENT_CIRCULAR_MAX, char *);
- for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++)
- es->events_circular[i] = NULL;
- }
-
- if (es->ninitial < LOGEVENT_INITIAL_MAX)
- location = &es->events_initial[es->ninitial];
- else
- location = &es->events_circular[(es->circular_first + es->ncircular) % LOGEVENT_CIRCULAR_MAX];
-
- tm=ltime();
- strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
-
- sfree(*location);
- *location = dupcat(timebuf, string);
- if (es->window) {
- dlg_listbox_add(es->listctrl, &es->dp, *location);
- }
- if (es->ninitial < LOGEVENT_INITIAL_MAX) {
- es->ninitial++;
- } else if (es->ncircular < LOGEVENT_CIRCULAR_MAX) {
- es->ncircular++;
- } else if (es->ncircular == LOGEVENT_CIRCULAR_MAX) {
- es->circular_first = (es->circular_first + 1) % LOGEVENT_CIRCULAR_MAX;
- sfree(es->events_circular[es->circular_first]);
- es->events_circular[es->circular_first] = dupstr("..");
- }
-}
-
-int gtkdlg_askappend(Seat *seat, Filename *filename,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- static const char msgtemplate[] =
- "The session log file \"%.*s\" already exists. "
- "You can overwrite it with a new session log, "
- "append your session log to the end of it, "
- "or disable session logging for this session.";
- static const struct message_box_button button_array_append[] = {
- {"Overwrite", 'o', 1, 2},
- {"Append", 'a', 0, 1},
- {"Disable", 'd', -1, 0},
- };
- static const struct message_box_buttons buttons_append = {
- button_array_append, lenof(button_array_append),
- };
-
- char *message;
- char *mbtitle;
- struct simple_prompt_result_ctx *result_ctx;
- GtkWidget *mainwin, *msgbox;
-
- message = dupprintf(msgtemplate, FILENAME_MAX, filename->path);
- mbtitle = dupprintf("%s Log to File", appname);
-
- result_ctx = snew(struct simple_prompt_result_ctx);
- result_ctx->callback = callback;
- result_ctx->callback_ctx = ctx;
- result_ctx->seat = seat;
- result_ctx->dialog_slot = DIALOG_SLOT_LOGFILE_PROMPT;
-
- mainwin = GTK_WIDGET(gtk_seat_get_window(seat));
- msgbox = create_message_box(
- mainwin, mbtitle, message,
- string_width("LINE OF TEXT SUITABLE FOR THE ASKAPPEND WIDTH"),
- false, &buttons_append, simple_prompt_result_callback, result_ctx);
- register_dialog(seat, result_ctx->dialog_slot, msgbox);
-
- sfree(message);
- sfree(mbtitle);
-
- return -1; /* dialog still in progress */
-}
diff --git a/unix/gtkfont.c b/unix/gtkfont.c
deleted file mode 100644
index b910d14b..00000000
--- a/unix/gtkfont.c
+++ /dev/null
@@ -1,3808 +0,0 @@
-/*
- * Unified font management for GTK.
- *
- * PuTTY is willing to use both old-style X server-side bitmap
- * fonts _and_ GTK2/Pango client-side fonts. This requires us to
- * do a bit of work to wrap the two wildly different APIs into
- * forms the rest of the code can switch between seamlessly, and
- * also requires a custom font selector capable of handling both
- * types of font.
- */
-
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <gtk/gtk.h>
-#if !GTK_CHECK_VERSION(3,0,0)
-#include <gdk/gdkkeysyms.h>
-#endif
-
-#define MAY_REFER_TO_GTK_IN_HEADERS
-
-#include "putty.h"
-#include "gtkfont.h"
-#include "gtkcompat.h"
-#include "gtkmisc.h"
-#include "tree234.h"
-
-#ifndef NOT_X_WINDOWS
-#include <gdk/gdkx.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-#include "x11misc.h"
-#endif
-
-/*
- * Future work:
- *
- * - it would be nice to have a display of the current font name,
- * and in particular whether it's client- or server-side,
- * during the progress of the font selector.
- */
-
-#if !GLIB_CHECK_VERSION(1,3,7)
-#define g_ascii_strcasecmp g_strcasecmp
-#define g_ascii_strncasecmp g_strncasecmp
-#endif
-
-/*
- * Ad-hoc vtable mechanism to allow font structures to be
- * polymorphic.
- *
- * Any instance of `unifont' used in the vtable functions will
- * actually be an element of a larger structure containing data
- * specific to the subtype.
- */
-
-#define FONTFLAG_CLIENTSIDE 0x0001
-#define FONTFLAG_SERVERSIDE 0x0002
-#define FONTFLAG_SERVERALIAS 0x0004
-#define FONTFLAG_NONMONOSPACED 0x0008
-
-#define FONTFLAG_SORT_MASK 0x0007 /* used to disambiguate font families */
-
-typedef void (*fontsel_add_entry)(void *ctx, const char *realfontname,
- const char *family, const char *charset,
- const char *style, const char *stylekey,
- int size, int flags,
- const struct UnifontVtable *fontclass);
-
-struct UnifontVtable {
- /*
- * `Methods' of the `class'.
- */
- unifont *(*create)(GtkWidget *widget, const char *name, bool wide,
- bool bold, int shadowoffset, bool shadowalways);
- unifont *(*create_fallback)(GtkWidget *widget, int height, bool wide,
- bool bold, int shadowoffset,
- bool shadowalways);
- void (*destroy)(unifont *font);
- bool (*has_glyph)(unifont *font, wchar_t glyph);
- void (*draw_text)(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth);
- void (*draw_combining)(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth);
- void (*enum_fonts)(GtkWidget *widget,
- fontsel_add_entry callback, void *callback_ctx);
- char *(*canonify_fontname)(GtkWidget *widget, const char *name, int *size,
- int *flags, bool resolve_aliases);
- char *(*scale_fontname)(GtkWidget *widget, const char *name, int size);
- char *(*size_increment)(unifont *font, int increment);
-
- /*
- * `Static data members' of the `class'.
- */
- const char *prefix;
-};
-
-#ifndef NOT_X_WINDOWS
-
-/* ----------------------------------------------------------------------
- * X11 font implementation, directly using Xlib calls. Conditioned out
- * if X11 fonts aren't available at all (e.g. building with GTK3 for a
- * back end other than X).
- */
-
-static bool x11font_has_glyph(unifont *font, wchar_t glyph);
-static void x11font_draw_text(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth);
-static void x11font_draw_combining(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string,
- int len, bool wide, bool bold,
- int cellwidth);
-static unifont *x11font_create(GtkWidget *widget, const char *name,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways);
-static void x11font_destroy(unifont *font);
-static void x11font_enum_fonts(GtkWidget *widget,
- fontsel_add_entry callback, void *callback_ctx);
-static char *x11font_canonify_fontname(GtkWidget *widget, const char *name,
- int *size, int *flags,
- bool resolve_aliases);
-static char *x11font_scale_fontname(GtkWidget *widget, const char *name,
- int size);
-static char *x11font_size_increment(unifont *font, int increment);
-
-#ifdef DRAW_TEXT_CAIRO
-struct cairo_cached_glyph {
- cairo_surface_t *surface;
- unsigned char *bitmap;
-};
-#endif
-
-/*
- * Structure storing a single physical XFontStruct, plus associated
- * data.
- */
-typedef struct x11font_individual {
- /* The XFontStruct itself. */
- XFontStruct *xfs;
-
- /*
- * The `allocated' flag indicates whether we've tried to fetch
- * this subfont already (thus distinguishing xfs==NULL because we
- * haven't tried yet from xfs==NULL because we tried and failed,
- * so that we don't keep trying and failing subsequently).
- */
- bool allocated;
-
-#ifdef DRAW_TEXT_CAIRO
- /*
- * A cache of glyph bitmaps downloaded from the X server when
- * we're in Cairo rendering mode. If glyphcache itself is
- * non-NULL, then entries in [0,nglyphs) are expected to be
- * initialised to either NULL or a bitmap pointer.
- */
- struct cairo_cached_glyph *glyphcache;
- int nglyphs;
-
- /*
- * X server paraphernalia for actually downloading the glyphs.
- */
- Pixmap pixmap;
- GC gc;
- int pixwidth, pixheight, pixoriginx, pixoriginy;
-
- /*
- * Paraphernalia for loading the resulting bitmaps into Cairo.
- */
- int rowsize, allsize, indexflip;
-#endif
-
-} x11font_individual;
-
-struct x11font {
- /*
- * Copy of the X display handle, so we don't have to keep
- * extracting it from GDK.
- */
- Display *disp;
- /*
- * Individual physical X fonts. We store a number of these, for
- * automatically guessed bold and wide variants.
- */
- x11font_individual fonts[4];
- /*
- * `sixteen_bit' is true iff the font object is indexed by
- * values larger than a byte. That is, this flag tells us
- * whether we use XDrawString or XDrawString16, etc.
- */
- bool sixteen_bit;
- /*
- * `variable' is true iff the font is non-fixed-pitch. This
- * enables some code which takes greater care over character
- * positioning during text drawing.
- */
- bool variable;
- /*
- * real_charset is the charset used when translating text into the
- * font's internal encoding inside draw_text(). This need not be
- * the same as the public_charset provided to the client; for
- * example, public_charset might be CS_ISO8859_1 while
- * real_charset is CS_ISO8859_1_X11.
- */
- int real_charset;
- /*
- * Data passed in to unifont_create().
- */
- int shadowoffset;
- bool wide, bold, shadowalways;
-
- unifont u;
-};
-
-static const UnifontVtable x11font_vtable = {
- .create = x11font_create,
- .create_fallback = NULL, /* no fallback fonts in X11 */
- .destroy = x11font_destroy,
- .has_glyph = x11font_has_glyph,
- .draw_text = x11font_draw_text,
- .draw_combining = x11font_draw_combining,
- .enum_fonts = x11font_enum_fonts,
- .canonify_fontname = x11font_canonify_fontname,
- .scale_fontname = x11font_scale_fontname,
- .size_increment = x11font_size_increment,
- .prefix = "server",
-};
-
-#define XLFD_STRING_PARTS_LIST(S,I) \
- S(foundry) \
- S(family_name) \
- S(weight_name) \
- S(slant) \
- S(setwidth_name) \
- S(add_style_name) \
- I(pixel_size) \
- I(point_size) \
- I(resolution_x) \
- I(resolution_y) \
- S(spacing) \
- I(average_width) \
- S(charset_registry) \
- S(charset_encoding) \
- /* end of list */
-
-/* Special value for int fields that xlfd_recompose will render as "*" */
-#define XLFD_INT_WILDCARD INT_MIN
-
-struct xlfd_decomposed {
-#define STR_FIELD(f) const char *f;
-#define INT_FIELD(f) int f;
- XLFD_STRING_PARTS_LIST(STR_FIELD, INT_FIELD)
-#undef STR_FIELD
-#undef INT_FIELD
-};
-
-static struct xlfd_decomposed *xlfd_decompose(const char *xlfd)
-{
- char *p, *components[14];
- struct xlfd_decomposed *dec;
- int i;
-
- if (!xlfd)
- return NULL;
-
- dec = snew_plus(struct xlfd_decomposed, strlen(xlfd) + 1);
- p = snew_plus_get_aux(dec);
- strcpy(p, xlfd);
-
- for (i = 0; i < 14; i++) {
- if (*p != '-') {
- /* Malformed XLFD: not enough '-' */
- sfree(dec);
- return NULL;
- }
- *p++ = '\0';
- components[i] = p;
- p += strcspn(p, "-");
- }
- if (*p) {
- /* Malformed XLFD: too many '-' */
- sfree(dec);
- return NULL;
- }
-
- i = 0;
-#define STORE_STR(f) dec->f = components[i++];
-#define STORE_INT(f) dec->f = atoi(components[i++]);
- XLFD_STRING_PARTS_LIST(STORE_STR, STORE_INT)
-#undef STORE_STR
-#undef STORE_INT
-
- return dec;
-}
-
-static char *xlfd_recompose(const struct xlfd_decomposed *dec)
-{
-#define FMT_STR(f) "-%s"
-#define ARG_STR(f) , dec->f
-#define FMT_INT(f) "%s%.*d"
-#define ARG_INT(f) \
- , dec->f == XLFD_INT_WILDCARD ? "-*" : "-" \
- , dec->f == XLFD_INT_WILDCARD ? 0 : 1 \
- , dec->f == XLFD_INT_WILDCARD ? 0 : dec->f
- return dupprintf(XLFD_STRING_PARTS_LIST(FMT_STR, FMT_INT)
- XLFD_STRING_PARTS_LIST(ARG_STR, ARG_INT));
-#undef FMT_STR
-#undef ARG_STR
-#undef FMT_INT
-#undef ARG_INT
-}
-
-static char *x11_guess_derived_font_name(Display *disp, XFontStruct *xfs,
- bool bold, bool wide)
-{
- Atom fontprop = XInternAtom(disp, "FONT", False);
- unsigned long ret;
- if (XGetFontProperty(xfs, fontprop, &ret)) {
- char *name = XGetAtomName(disp, (Atom)ret);
- struct xlfd_decomposed *xlfd = xlfd_decompose(name);
- if (!xlfd)
- return NULL;
-
- if (bold)
- xlfd->weight_name = "bold";
-
- if (wide) {
- /* Width name obviously may have changed. */
- /* Additional style may now become e.g. `ja' or `ko'. */
- xlfd->setwidth_name = xlfd->add_style_name = "*";
-
- /* Expect to double the average width. */
- xlfd->average_width *= 2;
- }
-
- {
- char *ret = xlfd_recompose(xlfd);
- sfree(xlfd);
- return ret;
- }
- }
- return NULL;
-}
-
-static int x11_font_width(XFontStruct *xfs, bool sixteen_bit)
-{
- if (sixteen_bit) {
- XChar2b space;
- space.byte1 = 0;
- space.byte2 = '0';
- return XTextWidth16(xfs, &space, 1);
- } else {
- return XTextWidth(xfs, "0", 1);
- }
-}
-
-static const XCharStruct *x11_char_struct(
- XFontStruct *xfs, unsigned char byte1, unsigned char byte2)
-{
- int index;
-
- /*
- * The man page for XQueryFont is rather confusing about how the
- * per_char array in the XFontStruct is laid out, because it gives
- * formulae for determining the two-byte X character code _from_
- * an index into the per_char array. Going the other way, it's
- * rather simpler:
- *
- * The valid character codes have byte1 between min_byte1 and
- * max_byte1 inclusive, and byte2 between min_char_or_byte2 and
- * max_char_or_byte2 inclusive. This gives a rectangle of size
- * (max_byte2-min_byte1+1) by
- * (max_char_or_byte2-min_char_or_byte2+1), which is precisely the
- * rectangle encoded in the per_char array. Hence, given a
- * character code which is valid in the sense that it falls
- * somewhere in that rectangle, its index in per_char is given by
- * setting
- *
- * x = byte2 - min_char_or_byte2
- * y = byte1 - min_byte1
- * index = y * (max_char_or_byte2-min_char_or_byte2+1) + x
- *
- * If min_byte1 and min_byte2 are both zero, that's a special case
- * which can be treated as if min_byte2 was 1 instead, i.e. the
- * per_char array just runs from min_char_or_byte2 to
- * max_char_or_byte2 inclusive, and byte1 should always be zero.
- */
-
- if (byte2 < xfs->min_char_or_byte2 || byte2 > xfs->max_char_or_byte2)
- return NULL;
-
- if (xfs->min_byte1 == 0 && xfs->max_byte1 == 0) {
- index = byte2 - xfs->min_char_or_byte2;
- } else {
- if (byte1 < xfs->min_byte1 || byte1 > xfs->max_byte1)
- return NULL;
- index = ((byte2 - xfs->min_char_or_byte2) +
- ((byte1 - xfs->min_byte1) *
- (xfs->max_char_or_byte2 - xfs->min_char_or_byte2 + 1)));
- }
-
- if (!xfs->per_char) /* per_char NULL => everything in range exists */
- return &xfs->max_bounds;
-
- return &xfs->per_char[index];
-}
-
-static bool x11_font_has_glyph(
- XFontStruct *xfs, unsigned char byte1, unsigned char byte2)
-{
- /*
- * Not to be confused with x11font_has_glyph, which is a method of
- * the x11font 'class' and hence takes a unifont as argument. This
- * is the low-level function which grubs about in an actual
- * XFontStruct to see if a given glyph exists.
- *
- * We must do this ourselves rather than letting Xlib's
- * XTextExtents16 do the job, because XTextExtents will helpfully
- * substitute the font's default_char for any missing glyph and
- * not tell us it did so, which precisely won't help us find out
- * which glyphs _are_ missing.
- */
- const XCharStruct *xcs = x11_char_struct(xfs, byte1, byte2);
- return xcs && (xcs->ascent + xcs->descent > 0 || xcs->width > 0);
-}
-
-static unifont *x11font_create(GtkWidget *widget, const char *name,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways)
-{
- struct x11font *xfont;
- XFontStruct *xfs;
- Display *disp;
- Atom charset_registry, charset_encoding, spacing;
- unsigned long registry_ret, encoding_ret, spacing_ret;
- int pubcs, realcs;
- bool sixteen_bit, variable;
- int i;
-
- if ((disp = get_x11_display()) == NULL)
- return NULL;
-
- xfs = XLoadQueryFont(disp, name);
- if (!xfs)
- return NULL;
-
- charset_registry = XInternAtom(disp, "CHARSET_REGISTRY", False);
- charset_encoding = XInternAtom(disp, "CHARSET_ENCODING", False);
-
- pubcs = realcs = CS_NONE;
- sixteen_bit = false;
- variable = true;
-
- if (XGetFontProperty(xfs, charset_registry, &registry_ret) &&
- XGetFontProperty(xfs, charset_encoding, &encoding_ret)) {
- char *reg, *enc;
- reg = XGetAtomName(disp, (Atom)registry_ret);
- enc = XGetAtomName(disp, (Atom)encoding_ret);
- if (reg && enc) {
- char *encoding = dupcat(reg, "-", enc);
- pubcs = realcs = charset_from_xenc(encoding);
-
- /*
- * iso10646-1 is the only wide font encoding we
- * support. In this case, we expect clients to give us
- * UTF-8, which this module must internally convert
- * into 16-bit Unicode.
- */
- if (!strcasecmp(encoding, "iso10646-1")) {
- sixteen_bit = true;
- pubcs = realcs = CS_UTF8;
- }
-
- /*
- * Hack for X line-drawing characters: if the primary font
- * is encoded as ISO-8859-1, and has valid glyphs in the
- * low character positions, it is assumed that those
- * glyphs are the VT100 line-drawing character set.
- */
- if (pubcs == CS_ISO8859_1) {
- int ch;
- for (ch = 1; ch < 32; ch++)
- if (!x11_font_has_glyph(xfs, 0, ch))
- break;
- if (ch == 32)
- realcs = CS_ISO8859_1_X11;
- }
-
- sfree(encoding);
- }
- }
-
- spacing = XInternAtom(disp, "SPACING", False);
- if (XGetFontProperty(xfs, spacing, &spacing_ret)) {
- char *spc;
- spc = XGetAtomName(disp, (Atom)spacing_ret);
-
- if (spc && strchr("CcMm", spc[0]))
- variable = false;
- }
-
- xfont = snew(struct x11font);
- xfont->u.vt = &x11font_vtable;
- xfont->u.width = x11_font_width(xfs, sixteen_bit);
- xfont->u.ascent = xfs->ascent;
- xfont->u.descent = xfs->descent;
- xfont->u.height = xfont->u.ascent + xfont->u.descent;
- xfont->u.public_charset = pubcs;
- xfont->u.want_fallback = true;
- xfont->u.strikethrough_y = xfont->u.ascent - (xfont->u.ascent * 3 / 8);
-#ifdef DRAW_TEXT_GDK
- xfont->u.preferred_drawtype = DRAWTYPE_GDK;
-#elif defined DRAW_TEXT_CAIRO
- xfont->u.preferred_drawtype = DRAWTYPE_CAIRO;
-#else
-#error No drawtype available at all
-#endif
- xfont->disp = disp;
- xfont->real_charset = realcs;
- xfont->sixteen_bit = sixteen_bit;
- xfont->variable = variable;
- xfont->wide = wide;
- xfont->bold = bold;
- xfont->shadowoffset = shadowoffset;
- xfont->shadowalways = shadowalways;
-
- for (i = 0; i < lenof(xfont->fonts); i++) {
- xfont->fonts[i].xfs = NULL;
- xfont->fonts[i].allocated = false;
-#ifdef DRAW_TEXT_CAIRO
- xfont->fonts[i].glyphcache = NULL;
- xfont->fonts[i].nglyphs = 0;
- xfont->fonts[i].pixmap = None;
- xfont->fonts[i].gc = None;
-#endif
- }
- xfont->fonts[0].xfs = xfs;
- xfont->fonts[0].allocated = true;
-
- return &xfont->u;
-}
-
-static void x11font_destroy(unifont *font)
-{
- struct x11font *xfont = container_of(font, struct x11font, u);
- Display *disp = xfont->disp;
- int i;
-
- for (i = 0; i < lenof(xfont->fonts); i++) {
- if (xfont->fonts[i].xfs)
- XFreeFont(disp, xfont->fonts[i].xfs);
-#ifdef DRAW_TEXT_CAIRO
- if (xfont->fonts[i].gc != None)
- XFreeGC(disp, xfont->fonts[i].gc);
- if (xfont->fonts[i].pixmap != None)
- XFreePixmap(disp, xfont->fonts[i].pixmap);
- if (xfont->fonts[i].glyphcache) {
- int j;
- for (j = 0; j < xfont->fonts[i].nglyphs; j++) {
- cairo_surface_destroy(xfont->fonts[i].glyphcache[j].surface);
- sfree(xfont->fonts[i].glyphcache[j].bitmap);
- }
- sfree(xfont->fonts[i].glyphcache);
- }
-#endif
- }
- sfree(xfont);
-}
-
-static void x11_alloc_subfont(struct x11font *xfont, int sfid)
-{
- Display *disp = xfont->disp;
- char *derived_name = x11_guess_derived_font_name
- (disp, xfont->fonts[0].xfs, sfid & 1, !!(sfid & 2));
- xfont->fonts[sfid].xfs = XLoadQueryFont(disp, derived_name);
- xfont->fonts[sfid].allocated = true;
- sfree(derived_name);
- /* Note that xfont->fonts[sfid].xfs may still be NULL, if XLQF failed. */
-}
-
-static bool x11font_has_glyph(unifont *font, wchar_t glyph)
-{
- struct x11font *xfont = container_of(font, struct x11font, u);
-
- if (xfont->sixteen_bit) {
- /*
- * This X font has 16-bit character indices, which means
- * we can directly use our Unicode input value.
- */
- return x11_font_has_glyph(xfont->fonts[0].xfs,
- glyph >> 8, glyph & 0xFF);
- } else {
- /*
- * This X font has 8-bit indices, so we must convert to the
- * appropriate character set.
- */
- char sbstring[2];
- int sblen = wc_to_mb(xfont->real_charset, 0, &glyph, 1,
- sbstring, 2, "", NULL);
- if (sblen == 0 || !sbstring[0])
- return false; /* not even in the charset */
-
- return x11_font_has_glyph(xfont->fonts[0].xfs, 0,
- (unsigned char)sbstring[0]);
- }
-}
-
-#if !GTK_CHECK_VERSION(2,0,0)
-#define GDK_DRAWABLE_XID(d) GDK_WINDOW_XWINDOW(d) /* GTK1's name for this */
-#elif GTK_CHECK_VERSION(3,0,0)
-#define GDK_DRAWABLE_XID(d) GDK_WINDOW_XID(d) /* GTK3's name for this */
-#endif
-
-static int x11font_width_16(unifont_drawctx *ctx, x11font_individual *xfi,
- const void *vstring, int start, int length)
-{
- const XChar2b *string = (const XChar2b *)vstring;
- return XTextWidth16(xfi->xfs, string+start, length);
-}
-
-static int x11font_width_8(unifont_drawctx *ctx, x11font_individual *xfi,
- const void *vstring, int start, int length)
-{
- const char *string = (const char *)vstring;
- return XTextWidth(xfi->xfs, string+start, length);
-}
-
-#ifdef DRAW_TEXT_GDK
-static void x11font_gdk_setup(unifont_drawctx *ctx, x11font_individual *xfi,
- Display *disp)
-{
- XSetFont(disp, GDK_GC_XGC(ctx->u.gdk.gc), xfi->xfs->fid);
-}
-
-static void x11font_gdk_draw_16(unifont_drawctx *ctx, x11font_individual *xfi,
- Display *disp, int x, int y,
- const void *vstring, int start, int length)
-{
- const XChar2b *string = (const XChar2b *)vstring;
- XDrawString16(disp, GDK_DRAWABLE_XID(ctx->u.gdk.target),
- GDK_GC_XGC(ctx->u.gdk.gc), x, y, string+start, length);
-}
-
-static void x11font_gdk_draw_8(unifont_drawctx *ctx, x11font_individual *xfi,
- Display *disp, int x, int y,
- const void *vstring, int start, int length)
-{
- const char *string = (const char *)vstring;
- XDrawString(disp, GDK_DRAWABLE_XID(ctx->u.gdk.target),
- GDK_GC_XGC(ctx->u.gdk.gc), x, y, string+start, length);
-}
-#endif
-
-#ifdef DRAW_TEXT_CAIRO
-static void x11font_cairo_setup(
- unifont_drawctx *ctx, x11font_individual *xfi, Display *disp)
-{
- if (xfi->pixmap == None) {
- XGCValues gcvals;
- GdkWindow *widgetwin = gtk_widget_get_window(ctx->u.cairo.widget);
- int widgetscr = GDK_SCREEN_XNUMBER(gdk_window_get_screen(widgetwin));
-
- xfi->pixwidth =
- xfi->xfs->max_bounds.rbearing - xfi->xfs->min_bounds.lbearing;
- xfi->pixheight =
- xfi->xfs->max_bounds.ascent + xfi->xfs->max_bounds.descent;
- xfi->pixoriginx = -xfi->xfs->min_bounds.lbearing;
- xfi->pixoriginy = xfi->xfs->max_bounds.ascent;
-
- xfi->rowsize = cairo_format_stride_for_width(CAIRO_FORMAT_A1,
- xfi->pixwidth);
- xfi->allsize = xfi->rowsize * xfi->pixheight;
-
- {
- /*
- * Test host endianness and use it to set xfi->indexflip,
- * which is XORed into our left-shift counts in order to
- * implement the CAIRO_FORMAT_A1 specification, in which
- * each bitmap byte is oriented LSB-first on little-endian
- * platforms and MSB-first on big-endian ones.
- *
- * This is the same technique Cairo itself uses to test
- * endianness, so hopefully it'll work in any situation
- * where Cairo is usable at all.
- */
- static const int endianness_test = 1;
- xfi->indexflip = (*((char *) &endianness_test) == 1) ? 0 : 7;
- }
-
- xfi->pixmap = XCreatePixmap
- (disp,
- GDK_DRAWABLE_XID(gtk_widget_get_window(ctx->u.cairo.widget)),
- xfi->pixwidth, xfi->pixheight, 1);
- gcvals.foreground = WhitePixel(disp, widgetscr);
- gcvals.background = BlackPixel(disp, widgetscr);
- gcvals.font = xfi->xfs->fid;
- xfi->gc = XCreateGC(disp, xfi->pixmap,
- GCForeground | GCBackground | GCFont, &gcvals);
- }
-}
-
-static void x11font_cairo_cache_glyph(
- Display *disp, x11font_individual *xfi, int glyphindex)
-{
- XImage *image;
- int x, y;
- unsigned char *bitmap;
- const XCharStruct *xcs = x11_char_struct(xfi->xfs, glyphindex >> 8,
- glyphindex & 0xFF);
-
- bitmap = snewn(xfi->allsize, unsigned char);
- memset(bitmap, 0, xfi->allsize);
-
- image = XGetImage(disp, xfi->pixmap, 0, 0,
- xfi->pixwidth, xfi->pixheight, AllPlanes, XYPixmap);
- for (y = xfi->pixoriginy - xcs->ascent;
- y < xfi->pixoriginy + xcs->descent; y++) {
- for (x = xfi->pixoriginx + xcs->lbearing;
- x < xfi->pixoriginx + xcs->rbearing; x++) {
- unsigned long pixel = XGetPixel(image, x, y);
- if (pixel) {
- int byteindex = y * xfi->rowsize + x/8;
- int bitindex = (x & 7) ^ xfi->indexflip;
- bitmap[byteindex] |= 1U << bitindex;
- }
- }
- }
- XDestroyImage(image);
-
- if (xfi->nglyphs <= glyphindex) {
- /* Round up to the next multiple of 256 on the general
- * principle that Unicode characters come in contiguous blocks
- * often used together */
- int old_nglyphs = xfi->nglyphs;
- xfi->nglyphs = (glyphindex + 0x100) & ~0xFF;
- xfi->glyphcache = sresize(xfi->glyphcache, xfi->nglyphs,
- struct cairo_cached_glyph);
-
- while (old_nglyphs < xfi->nglyphs) {
- xfi->glyphcache[old_nglyphs].surface = NULL;
- xfi->glyphcache[old_nglyphs].bitmap = NULL;
- old_nglyphs++;
- }
- }
- xfi->glyphcache[glyphindex].bitmap = bitmap;
- xfi->glyphcache[glyphindex].surface = cairo_image_surface_create_for_data
- (bitmap, CAIRO_FORMAT_A1, xfi->pixwidth, xfi->pixheight, xfi->rowsize);
-}
-
-static void x11font_cairo_draw_glyph(unifont_drawctx *ctx,
- x11font_individual *xfi, int x, int y,
- int glyphindex)
-{
- if (xfi->glyphcache[glyphindex].surface) {
- cairo_mask_surface(ctx->u.cairo.cr,
- xfi->glyphcache[glyphindex].surface,
- x - xfi->pixoriginx, y - xfi->pixoriginy);
- }
-}
-
-static void x11font_cairo_draw_16(
- unifont_drawctx *ctx, x11font_individual *xfi, Display *disp,
- int x, int y, const void *vstring, int start, int length)
-{
- const XChar2b *string = (const XChar2b *)vstring + start;
- int i;
- for (i = 0; i < length; i++) {
- if (x11_font_has_glyph(xfi->xfs, string[i].byte1, string[i].byte2)) {
- int glyphindex = (256 * (unsigned char)string[i].byte1 +
- (unsigned char)string[i].byte2);
- if (glyphindex >= xfi->nglyphs ||
- !xfi->glyphcache[glyphindex].surface) {
- XDrawImageString16(disp, xfi->pixmap, xfi->gc,
- xfi->pixoriginx, xfi->pixoriginy,
- string+i, 1);
- x11font_cairo_cache_glyph(disp, xfi, glyphindex);
- }
- x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex);
- x += XTextWidth16(xfi->xfs, string+i, 1);
- }
- }
-}
-
-static void x11font_cairo_draw_8(
- unifont_drawctx *ctx, x11font_individual *xfi, Display *disp,
- int x, int y, const void *vstring, int start, int length)
-{
- const char *string = (const char *)vstring + start;
- int i;
- for (i = 0; i < length; i++) {
- if (x11_font_has_glyph(xfi->xfs, 0, string[i])) {
- int glyphindex = (unsigned char)string[i];
- if (glyphindex >= xfi->nglyphs ||
- !xfi->glyphcache[glyphindex].surface) {
- XDrawImageString(disp, xfi->pixmap, xfi->gc,
- xfi->pixoriginx, xfi->pixoriginy,
- string+i, 1);
- x11font_cairo_cache_glyph(disp, xfi, glyphindex);
- }
- x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex);
- x += XTextWidth(xfi->xfs, string+i, 1);
- }
- }
-}
-#endif /* DRAW_TEXT_CAIRO */
-
-struct x11font_drawfuncs {
- int (*width)(unifont_drawctx *ctx, x11font_individual *xfi,
- const void *vstring, int start, int length);
- void (*setup)(unifont_drawctx *ctx, x11font_individual *xfi,
- Display *disp);
- void (*draw)(unifont_drawctx *ctx, x11font_individual *xfi, Display *disp,
- int x, int y, const void *vstring, int start, int length);
-};
-
-/*
- * This array has two entries per compiled-in drawtype; of each pair,
- * the first is for an 8-bit font and the second for 16-bit.
- */
-static const struct x11font_drawfuncs x11font_drawfuncs[2*DRAWTYPE_NTYPES] = {
-#ifdef DRAW_TEXT_GDK
- /* gdk, 8-bit */
- {
- x11font_width_8,
- x11font_gdk_setup,
- x11font_gdk_draw_8,
- },
- /* gdk, 16-bit */
- {
- x11font_width_16,
- x11font_gdk_setup,
- x11font_gdk_draw_16,
- },
-#endif
-#ifdef DRAW_TEXT_CAIRO
- /* cairo, 8-bit */
- {
- x11font_width_8,
- x11font_cairo_setup,
- x11font_cairo_draw_8,
- },
- /* [3] cairo, 16-bit */
- {
- x11font_width_16,
- x11font_cairo_setup,
- x11font_cairo_draw_16,
- },
-#endif
-};
-
-static void x11font_really_draw_text(
- const struct x11font_drawfuncs *dfns, unifont_drawctx *ctx,
- x11font_individual *xfi, Display *disp,
- int x, int y, const void *string, int nchars,
- int shadowoffset, bool fontvariable, int cellwidth)
-{
- int start = 0, step, nsteps;
- bool centre;
-
- if (fontvariable) {
- /*
- * In a variable-pitch font, we draw one character at a
- * time, and centre it in the character cell.
- */
- step = 1;
- nsteps = nchars;
- centre = true;
- } else {
- /*
- * In a fixed-pitch font, we can draw the whole lot in one go.
- */
- step = nchars;
- nsteps = 1;
- centre = false;
- }
-
- dfns->setup(ctx, xfi, disp);
-
- while (nsteps-- > 0) {
- int X = x;
- if (centre)
- X += (cellwidth - dfns->width(ctx, xfi, string, start, step)) / 2;
-
- dfns->draw(ctx, xfi, disp, X, y, string, start, step);
- if (shadowoffset)
- dfns->draw(ctx, xfi, disp, X + shadowoffset, y,
- string, start, step);
-
- x += cellwidth;
- start += step;
- }
-}
-
-static void x11font_draw_text(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth)
-{
- struct x11font *xfont = container_of(font, struct x11font, u);
- int sfid;
- int shadowoffset = 0;
- int mult = (wide ? 2 : 1);
- int index = 2 * (int)ctx->type;
-
- wide = wide && !xfont->wide;
- bold = bold && !xfont->bold;
-
- /*
- * Decide which subfont we're using, and whether we have to
- * use shadow bold.
- */
- if (xfont->shadowalways && bold) {
- shadowoffset = xfont->shadowoffset;
- bold = false;
- }
- sfid = 2 * wide + bold;
- if (!xfont->fonts[sfid].allocated)
- x11_alloc_subfont(xfont, sfid);
- if (bold && !xfont->fonts[sfid].xfs) {
- bold = false;
- shadowoffset = xfont->shadowoffset;
- sfid = 2 * wide + bold;
- if (!xfont->fonts[sfid].allocated)
- x11_alloc_subfont(xfont, sfid);
- }
-
- if (!xfont->fonts[sfid].xfs)
- return; /* we've tried our best, but no luck */
-
- if (xfont->sixteen_bit) {
- /*
- * This X font has 16-bit character indices, which means
- * we can directly use our Unicode input string.
- */
- XChar2b *xcs;
- int i;
-
- xcs = snewn(len, XChar2b);
- for (i = 0; i < len; i++) {
- xcs[i].byte1 = string[i] >> 8;
- xcs[i].byte2 = string[i];
- }
-
- x11font_really_draw_text(x11font_drawfuncs + index + 1, ctx,
- &xfont->fonts[sfid], xfont->disp, x, y,
- xcs, len, shadowoffset,
- xfont->variable, cellwidth * mult);
- sfree(xcs);
- } else {
- /*
- * This X font has 8-bit indices, so we must convert to the
- * appropriate character set.
- */
- char *sbstring = snewn(len+1, char);
- int sblen = wc_to_mb(xfont->real_charset, 0, string, len,
- sbstring, len+1, ".", NULL);
- x11font_really_draw_text(x11font_drawfuncs + index + 0, ctx,
- &xfont->fonts[sfid], xfont->disp, x, y,
- sbstring, sblen, shadowoffset,
- xfont->variable, cellwidth * mult);
- sfree(sbstring);
- }
-}
-
-static void x11font_draw_combining(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string,
- int len, bool wide, bool bold,
- int cellwidth)
-{
- /*
- * For server-side fonts, there's no sophisticated system for
- * combining characters intelligently, so the best we can do is to
- * overprint them on each other in the obvious way.
- */
- int i;
- for (i = 0; i < len; i++)
- x11font_draw_text(ctx, font, x, y, string+i, 1, wide, bold, cellwidth);
-}
-
-static void x11font_enum_fonts(GtkWidget *widget,
- fontsel_add_entry callback, void *callback_ctx)
-{
- Display *disp;
- char **fontnames;
- char *tmp = NULL;
- int nnames, i, max, tmpsize;
-
- if ((disp = get_x11_display()) == NULL)
- return;
-
- max = 32768;
- while (1) {
- fontnames = XListFonts(disp, "*", max, &nnames);
- if (nnames >= max) {
- XFreeFontNames(fontnames);
- max *= 2;
- } else
- break;
- }
-
- tmpsize = 0;
-
- for (i = 0; i < nnames; i++) {
- struct xlfd_decomposed *xlfd = xlfd_decompose(fontnames[i]);
- if (xlfd) {
- char *p, *font, *style, *stylekey, *charset;
- int weightkey, slantkey, setwidthkey;
- int thistmpsize;
-
- /*
- * Convert a dismembered XLFD into the format we'll be
- * using in the font selector.
- */
-
- thistmpsize = 4 * strlen(fontnames[i]) + 256;
- if (tmpsize < thistmpsize) {
- tmpsize = thistmpsize;
- tmp = sresize(tmp, tmpsize, char);
- }
- p = tmp;
-
- /*
- * Font name is in the form "family (foundry)". (This is
- * what the GTK 1.2 X font selector does, and it seems to
- * come out looking reasonably sensible.)
- */
- font = p;
- p += 1 + sprintf(p, "%s (%s)", xlfd->family_name, xlfd->foundry);
-
- /*
- * Character set.
- */
- charset = p;
- p += 1 + sprintf(p, "%s-%s", xlfd->charset_registry,
- xlfd->charset_encoding);
-
- /*
- * Style is a mixture of quite a lot of the fields,
- * with some strange formatting.
- */
- style = p;
- p += sprintf(p, "%s", xlfd->weight_name[0] ? xlfd->weight_name :
- "regular");
- if (!g_ascii_strcasecmp(xlfd->slant, "i"))
- p += sprintf(p, " italic");
- else if (!g_ascii_strcasecmp(xlfd->slant, "o"))
- p += sprintf(p, " oblique");
- else if (!g_ascii_strcasecmp(xlfd->slant, "ri"))
- p += sprintf(p, " reverse italic");
- else if (!g_ascii_strcasecmp(xlfd->slant, "ro"))
- p += sprintf(p, " reverse oblique");
- else if (!g_ascii_strcasecmp(xlfd->slant, "ot"))
- p += sprintf(p, " other-slant");
- if (xlfd->setwidth_name[0] &&
- g_ascii_strcasecmp(xlfd->setwidth_name, "normal"))
- p += sprintf(p, " %s", xlfd->setwidth_name);
- if (!g_ascii_strcasecmp(xlfd->spacing, "m"))
- p += sprintf(p, " [M]");
- if (!g_ascii_strcasecmp(xlfd->spacing, "c"))
- p += sprintf(p, " [C]");
- if (xlfd->add_style_name[0])
- p += sprintf(p, " %s", xlfd->add_style_name);
-
- /*
- * Style key is the same stuff as above, but with a
- * couple of transformations done on it to make it
- * sort more sensibly.
- */
- p++;
- stylekey = p;
- if (!g_ascii_strcasecmp(xlfd->weight_name, "medium") ||
- !g_ascii_strcasecmp(xlfd->weight_name, "regular") ||
- !g_ascii_strcasecmp(xlfd->weight_name, "normal") ||
- !g_ascii_strcasecmp(xlfd->weight_name, "book"))
- weightkey = 0;
- else if (!g_ascii_strncasecmp(xlfd->weight_name, "demi", 4) ||
- !g_ascii_strncasecmp(xlfd->weight_name, "semi", 4))
- weightkey = 1;
- else
- weightkey = 2;
- if (!g_ascii_strcasecmp(xlfd->slant, "r"))
- slantkey = 0;
- else if (!g_ascii_strncasecmp(xlfd->slant, "r", 1))
- slantkey = 2;
- else
- slantkey = 1;
- if (!g_ascii_strcasecmp(xlfd->setwidth_name, "normal"))
- setwidthkey = 0;
- else
- setwidthkey = 1;
-
- p += sprintf(
- p, "%04d%04d%s%04d%04d%s%04d%04d%s%04d%s%04d%s",
- weightkey, (int)strlen(xlfd->weight_name), xlfd->weight_name,
- slantkey, (int)strlen(xlfd->slant), xlfd->slant,
- setwidthkey,
- (int)strlen(xlfd->setwidth_name), xlfd->setwidth_name,
- (int)strlen(xlfd->spacing), xlfd->spacing,
- (int)strlen(xlfd->add_style_name), xlfd->add_style_name);
-
- assert(p - tmp < thistmpsize);
-
- /*
- * Flags: we need to know whether this is a monospaced
- * font, which we do by examining the spacing field
- * again.
- */
- int flags = FONTFLAG_SERVERSIDE;
- if (!strchr("CcMm", xlfd->spacing[0]))
- flags |= FONTFLAG_NONMONOSPACED;
-
- /*
- * Some fonts have a pixel size of zero, meaning they're
- * treated as scalable. For these purposes, we only want
- * fonts whose pixel size we actually know, so filter
- * those out.
- */
- if (xlfd->pixel_size)
- callback(callback_ctx, fontnames[i], font, charset,
- style, stylekey, xlfd->pixel_size, flags,
- &x11font_vtable);
-
- sfree(xlfd);
- } else {
- /*
- * This isn't an XLFD, so it must be an alias.
- * Transmit it with mostly null data.
- *
- * It would be nice to work out if it's monospaced
- * here, but at the moment I can't see that being
- * anything but computationally hideous. Ah well.
- */
- callback(callback_ctx, fontnames[i], fontnames[i], NULL,
- NULL, NULL, 0, FONTFLAG_SERVERALIAS, &x11font_vtable);
-
- sfree(xlfd);
- }
- }
- XFreeFontNames(fontnames);
- sfree(tmp);
-}
-
-static char *x11font_canonify_fontname(GtkWidget *widget, const char *name,
- int *size, int *flags,
- bool resolve_aliases)
-{
- /*
- * When given an X11 font name to try to make sense of for a
- * font selector, we must attempt to load it (to see if it
- * exists), and then canonify it by extracting its FONT
- * property, which should give its full XLFD even if what we
- * originally had was a wildcard.
- *
- * However, we must carefully avoid canonifying font
- * _aliases_, unless specifically asked to, because the font
- * selector treats them as worthwhile in their own right.
- */
- XFontStruct *xfs;
- Display *disp;
- Atom fontprop, fontprop2;
- unsigned long ret;
-
- if ((disp = get_x11_display()) == NULL)
- return NULL;
-
- xfs = XLoadQueryFont(disp, name);
-
- if (!xfs)
- return NULL; /* didn't make sense to us, sorry */
-
- fontprop = XInternAtom(disp, "FONT", False);
-
- if (XGetFontProperty(xfs, fontprop, &ret)) {
- char *newname = XGetAtomName(disp, (Atom)ret);
- if (newname) {
- unsigned long fsize = 12;
-
- fontprop2 = XInternAtom(disp, "PIXEL_SIZE", False);
- if (XGetFontProperty(xfs, fontprop2, &fsize) && fsize > 0) {
- *size = fsize;
- XFreeFont(disp, xfs);
- if (flags) {
- if (name[0] == '-' || resolve_aliases)
- *flags = FONTFLAG_SERVERSIDE;
- else
- *flags = FONTFLAG_SERVERALIAS;
- }
- return dupstr(name[0] == '-' || resolve_aliases ?
- newname : name);
- }
- }
- }
-
- XFreeFont(disp, xfs);
-
- return NULL; /* something went wrong */
-}
-
-static char *x11font_scale_fontname(GtkWidget *widget, const char *name,
- int size)
-{
- return NULL; /* shan't */
-}
-
-static char *x11font_size_increment(unifont *font, int increment)
-{
- struct x11font *xfont = container_of(font, struct x11font, u);
- Display *disp = xfont->disp;
- Atom fontprop = XInternAtom(disp, "FONT", False);
- char *returned_name = NULL;
- unsigned long ret;
- if (XGetFontProperty(xfont->fonts[0].xfs, fontprop, &ret)) {
- struct xlfd_decomposed *xlfd;
- struct xlfd_decomposed *xlfd_best;
- char *wc;
- char **fontnames;
- int nnames, i, max;
-
- xlfd = xlfd_decompose(XGetAtomName(disp, (Atom)ret));
- if (!xlfd)
- return NULL;
-
- /*
- * Form a wildcard consisting of everything in the
- * original XLFD except for the size-related fields.
- */
- {
- struct xlfd_decomposed xlfd_wc = *xlfd; /* structure copy */
- xlfd_wc.pixel_size = XLFD_INT_WILDCARD;
- xlfd_wc.point_size = XLFD_INT_WILDCARD;
- xlfd_wc.average_width = XLFD_INT_WILDCARD;
- wc = xlfd_recompose(&xlfd_wc);
- }
-
- /*
- * Fetch all the font names matching that wildcard.
- */
- max = 32768;
- while (1) {
- fontnames = XListFonts(disp, wc, max, &nnames);
- if (nnames >= max) {
- XFreeFontNames(fontnames);
- max *= 2;
- } else
- break;
- }
-
- sfree(wc);
-
- /*
- * Iterate over those to find the one closest in size to the
- * original font, in the correct direction.
- */
-
-#define FLIPPED_SIZE(xlfd) \
- (((xlfd)->pixel_size + (xlfd)->point_size) * \
- (increment < 0 ? -1 : +1))
-
- xlfd_best = NULL;
- for (i = 0; i < nnames; i++) {
- struct xlfd_decomposed *xlfd2 = xlfd_decompose(fontnames[i]);
- if (!xlfd2)
- continue;
-
- if (xlfd2->pixel_size != 0 &&
- FLIPPED_SIZE(xlfd2) > FLIPPED_SIZE(xlfd) &&
- (!xlfd_best || FLIPPED_SIZE(xlfd2)<FLIPPED_SIZE(xlfd_best))) {
- sfree(xlfd_best);
- xlfd_best = xlfd2;
- xlfd2 = NULL;
- }
-
- sfree(xlfd2);
- }
-
-#undef FLIPPED_SIZE
-
- if (xlfd_best) {
- char *bare_returned_name = xlfd_recompose(xlfd_best);
- returned_name = dupcat(
- xfont->u.vt->prefix, ":", bare_returned_name);
- sfree(bare_returned_name);
- }
-
- XFreeFontNames(fontnames);
- sfree(xlfd);
- sfree(xlfd_best);
- }
- return returned_name;
-}
-
-#endif /* NOT_X_WINDOWS */
-
-#if GTK_CHECK_VERSION(2,0,0)
-
-/* ----------------------------------------------------------------------
- * Pango font implementation (for GTK 2 only).
- */
-
-#if defined PANGO_PRE_1POINT4 && !defined PANGO_PRE_1POINT6
-#define PANGO_PRE_1POINT6 /* make life easier for pre-1.4 folk */
-#endif
-
-static bool pangofont_has_glyph(unifont *font, wchar_t glyph);
-static void pangofont_draw_text(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth);
-static void pangofont_draw_combining(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string,
- int len, bool wide, bool bold,
- int cellwidth);
-static unifont *pangofont_create(GtkWidget *widget, const char *name,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways);
-static unifont *pangofont_create_fallback(GtkWidget *widget, int height,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways);
-static void pangofont_destroy(unifont *font);
-static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback,
- void *callback_ctx);
-static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name,
- int *size, int *flags,
- bool resolve_aliases);
-static char *pangofont_scale_fontname(GtkWidget *widget, const char *name,
- int size);
-static char *pangofont_size_increment(unifont *font, int increment);
-
-struct pangofont {
- /*
- * Pango objects.
- */
- PangoFontDescription *desc;
- PangoFontset *fset;
- /*
- * The containing widget.
- */
- GtkWidget *widget;
- /*
- * Data passed in to unifont_create().
- */
- int shadowoffset;
- bool bold, shadowalways;
- /*
- * Cache of character widths, indexed by Unicode code point. In
- * pixels; -1 means we haven't asked Pango about this character
- * before.
- */
- int *widthcache;
- unsigned nwidthcache;
-
- struct unifont u;
-};
-
-static const UnifontVtable pangofont_vtable = {
- .create = pangofont_create,
- .create_fallback = pangofont_create_fallback,
- .destroy = pangofont_destroy,
- .has_glyph = pangofont_has_glyph,
- .draw_text = pangofont_draw_text,
- .draw_combining = pangofont_draw_combining,
- .enum_fonts = pangofont_enum_fonts,
- .canonify_fontname = pangofont_canonify_fontname,
- .scale_fontname = pangofont_scale_fontname,
- .size_increment = pangofont_size_increment,
- .prefix = "client",
-};
-
-/*
- * This function is used to rigorously validate a
- * PangoFontDescription. Later versions of Pango have a nasty
- * habit of accepting _any_ old string as input to
- * pango_font_description_from_string and returning a font
- * description which can actually be used to display text, even if
- * they have to do it by falling back to their most default font.
- * This is doubtless helpful in some situations, but not here,
- * because we need to know if a Pango font string actually _makes
- * sense_ in order to fall back to treating it as an X font name
- * if it doesn't. So we check that the font family is actually one
- * supported by Pango.
- */
-static bool pangofont_check_desc_makes_sense(PangoContext *ctx,
- PangoFontDescription *desc)
-{
-#ifndef PANGO_PRE_1POINT6
- PangoFontMap *map;
-#endif
- PangoFontFamily **families;
- int i, nfamilies;
- bool matched;
-
- /*
- * Ask Pango for a list of font families, and iterate through
- * them to see if one of them matches the family in the
- * PangoFontDescription.
- */
-#ifndef PANGO_PRE_1POINT6
- map = pango_context_get_font_map(ctx);
- if (!map)
- return false;
- pango_font_map_list_families(map, &families, &nfamilies);
-#else
- pango_context_list_families(ctx, &families, &nfamilies);
-#endif
-
- matched = false;
- for (i = 0; i < nfamilies; i++) {
- if (!g_ascii_strcasecmp(pango_font_family_get_name(families[i]),
- pango_font_description_get_family(desc))) {
- matched = true;
- break;
- }
- }
- g_free(families);
-
- return matched;
-}
-
-static unifont *pangofont_create_internal(GtkWidget *widget,
- PangoContext *ctx,
- PangoFontDescription *desc,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways)
-{
- struct pangofont *pfont;
-#ifndef PANGO_PRE_1POINT6
- PangoFontMap *map;
-#endif
- PangoFontset *fset;
- PangoFontMetrics *metrics;
-
-#ifndef PANGO_PRE_1POINT6
- map = pango_context_get_font_map(ctx);
- if (!map) {
- pango_font_description_free(desc);
- return NULL;
- }
- fset = pango_font_map_load_fontset(map, ctx, desc,
- pango_context_get_language(ctx));
-#else
- fset = pango_context_load_fontset(ctx, desc,
- pango_context_get_language(ctx));
-#endif
- if (!fset) {
- pango_font_description_free(desc);
- return NULL;
- }
- metrics = pango_fontset_get_metrics(fset);
- if (!metrics ||
- pango_font_metrics_get_approximate_digit_width(metrics) == 0) {
- pango_font_description_free(desc);
- g_object_unref(fset);
- return NULL;
- }
-
- pfont = snew(struct pangofont);
- pfont->u.vt = &pangofont_vtable;
- pfont->u.width =
- PANGO_PIXELS(pango_font_metrics_get_approximate_digit_width(metrics));
- pfont->u.ascent =
- PANGO_PIXELS_CEIL(pango_font_metrics_get_ascent(metrics));
- pfont->u.descent =
- PANGO_PIXELS_CEIL(pango_font_metrics_get_descent(metrics));
- pfont->u.height = pfont->u.ascent + pfont->u.descent;
- pfont->u.strikethrough_y =
- PANGO_PIXELS(pango_font_metrics_get_ascent(metrics) -
- pango_font_metrics_get_strikethrough_position(metrics));
- pfont->u.want_fallback = false;
-#ifdef DRAW_TEXT_CAIRO
- pfont->u.preferred_drawtype = DRAWTYPE_CAIRO;
-#elif defined DRAW_TEXT_GDK
- pfont->u.preferred_drawtype = DRAWTYPE_GDK;
-#else
-#error No drawtype available at all
-#endif
- /* The Pango API is hardwired to UTF-8 */
- pfont->u.public_charset = CS_UTF8;
- pfont->desc = desc;
- pfont->fset = fset;
- pfont->widget = widget;
- pfont->bold = bold;
- pfont->shadowoffset = shadowoffset;
- pfont->shadowalways = shadowalways;
- pfont->widthcache = NULL;
- pfont->nwidthcache = 0;
-
- pango_font_metrics_unref(metrics);
-
- return &pfont->u;
-}
-
-static unifont *pangofont_create(GtkWidget *widget, const char *name,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways)
-{
- PangoContext *ctx;
- PangoFontDescription *desc;
-
- desc = pango_font_description_from_string(name);
- if (!desc)
- return NULL;
- ctx = gtk_widget_get_pango_context(widget);
- if (!ctx) {
- pango_font_description_free(desc);
- return NULL;
- }
- if (!pangofont_check_desc_makes_sense(ctx, desc)) {
- pango_font_description_free(desc);
- return NULL;
- }
- return pangofont_create_internal(widget, ctx, desc, wide, bold,
- shadowoffset, shadowalways);
-}
-
-static unifont *pangofont_create_fallback(GtkWidget *widget, int height,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways)
-{
- PangoContext *ctx;
- PangoFontDescription *desc;
-
- desc = pango_font_description_from_string("Monospace");
- if (!desc)
- return NULL;
- ctx = gtk_widget_get_pango_context(widget);
- if (!ctx) {
- pango_font_description_free(desc);
- return NULL;
- }
- pango_font_description_set_absolute_size(desc, height * PANGO_SCALE);
- return pangofont_create_internal(widget, ctx, desc, wide, bold,
- shadowoffset, shadowalways);
-}
-
-static void pangofont_destroy(unifont *font)
-{
- struct pangofont *pfont = container_of(font, struct pangofont, u);
- pango_font_description_free(pfont->desc);
- sfree(pfont->widthcache);
- g_object_unref(pfont->fset);
- sfree(pfont);
-}
-
-static int pangofont_char_width(PangoLayout *layout, struct pangofont *pfont,
- wchar_t uchr, const char *utfchr, int utflen)
-{
- /*
- * Here we check whether a character has the same width as the
- * character cell it'll be drawn in. Because profiling showed that
- * asking Pango for text sizes was a huge bottleneck when we were
- * calling it every time we needed to know this, we instead call
- * it only on characters we don't already know about, and cache
- * the results.
- */
-
- if ((unsigned)uchr >= pfont->nwidthcache) {
- unsigned newsize = ((int)uchr + 0x100) & ~0xFF;
- pfont->widthcache = sresize(pfont->widthcache, newsize, int);
- while (pfont->nwidthcache < newsize)
- pfont->widthcache[pfont->nwidthcache++] = -1;
- }
-
- if (pfont->widthcache[uchr] < 0) {
- PangoRectangle rect;
- pango_layout_set_text(layout, utfchr, utflen);
- pango_layout_get_extents(layout, NULL, &rect);
- pfont->widthcache[uchr] = rect.width;
- }
-
- return pfont->widthcache[uchr];
-}
-
-static bool pangofont_has_glyph(unifont *font, wchar_t glyph)
-{
- /* Pango implements font fallback, so assume it has everything */
- return true;
-}
-
-#ifdef DRAW_TEXT_GDK
-static void pango_gdk_draw_layout(unifont_drawctx *ctx,
- gint x, gint y, PangoLayout *layout)
-{
- gdk_draw_layout(ctx->u.gdk.target, ctx->u.gdk.gc, x, y, layout);
-}
-#endif
-
-#ifdef DRAW_TEXT_CAIRO
-static void pango_cairo_draw_layout(unifont_drawctx *ctx,
- gint x, gint y, PangoLayout *layout)
-{
- cairo_move_to(ctx->u.cairo.cr, x, y);
- pango_cairo_show_layout(ctx->u.cairo.cr, layout);
-}
-#endif
-
-static void pangofont_draw_internal(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string,
- int len, bool wide, bool bold,
- int cellwidth, bool combining)
-{
- struct pangofont *pfont = container_of(font, struct pangofont, u);
- PangoLayout *layout;
- PangoRectangle rect;
- char *utfstring, *utfptr;
- int utflen;
- bool shadowbold = false;
- void (*draw_layout)(unifont_drawctx *ctx,
- gint x, gint y, PangoLayout *layout) = NULL;
-
-#ifdef DRAW_TEXT_GDK
- if (ctx->type == DRAWTYPE_GDK) {
- draw_layout = pango_gdk_draw_layout;
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (ctx->type == DRAWTYPE_CAIRO) {
- draw_layout = pango_cairo_draw_layout;
- }
-#endif
- assert(draw_layout);
-
- if (wide)
- cellwidth *= 2;
-
- y -= pfont->u.ascent;
-
- layout = pango_layout_new(gtk_widget_get_pango_context(pfont->widget));
- pango_layout_set_font_description(layout, pfont->desc);
- if (bold && !pfont->bold) {
- if (pfont->shadowalways)
- shadowbold = true;
- else {
- PangoFontDescription *desc2 =
- pango_font_description_copy_static(pfont->desc);
- pango_font_description_set_weight(desc2, PANGO_WEIGHT_BOLD);
- pango_layout_set_font_description(layout, desc2);
- }
- }
-
- /*
- * Pango always expects UTF-8, so convert the input wide character
- * string to UTF-8.
- */
- utfstring = snewn(len*6+1, char); /* UTF-8 has max 6 bytes/char */
- utflen = wc_to_mb(CS_UTF8, 0, string, len,
- utfstring, len*6+1, ".", NULL);
-
- utfptr = utfstring;
- while (utflen > 0) {
- int clen, n;
- int desired = cellwidth * PANGO_SCALE;
-
- /*
- * We want to display every character from this string in
- * the centre of its own character cell. In the worst case,
- * this requires a separate text-drawing call for each
- * character; but in the common case where the font is
- * properly fixed-width, we can draw many characters in one
- * go which is much faster.
- *
- * This still isn't really ideal. If you look at what
- * happens in the X protocol as a result of all of this, you
- * find - naturally enough - that each call to
- * gdk_draw_layout() generates a separate set of X RENDER
- * operations involving creating a picture, setting a clip
- * rectangle, doing some drawing and undoing the whole lot.
- * In an ideal world, we should _always_ be able to turn the
- * contents of this loop into a single RenderCompositeGlyphs
- * operation which internally specifies inter-character
- * deltas to get the spacing right, which would give us full
- * speed _even_ in the worst case of a non-fixed-width font.
- * However, Pango's architecture and documentation are so
- * unhelpful that I have no idea how if at all to persuade
- * them to do that.
- */
-
- if (combining) {
- /*
- * For a character with combining stuff, we just dump the
- * whole lot in one go, and expect it to take up just one
- * character cell.
- */
- clen = utflen;
- n = 1;
- } else {
- /*
- * Start by extracting a single UTF-8 character from the
- * string.
- */
- clen = 1;
- while (clen < utflen &&
- (unsigned char)utfptr[clen] >= 0x80 &&
- (unsigned char)utfptr[clen] < 0xC0)
- clen++;
- n = 1;
-
- if (is_rtl(string[0]) ||
- pangofont_char_width(layout, pfont, string[n-1],
- utfptr, clen) != desired) {
- /*
- * If this character is a right-to-left one, or has an
- * unusual width, then we must display it on its own.
- */
- } else {
- /*
- * Try to amalgamate a contiguous string of characters
- * with the expected sensible width, for the common case
- * in which we're using a monospaced font and everything
- * works as expected.
- */
- while (clen < utflen) {
- int oldclen = clen;
- clen++; /* skip UTF-8 introducer byte */
- while (clen < utflen &&
- (unsigned char)utfptr[clen] >= 0x80 &&
- (unsigned char)utfptr[clen] < 0xC0)
- clen++;
- n++;
- if (is_rtl(string[n-1]) ||
- pangofont_char_width(layout, pfont,
- string[n-1], utfptr + oldclen,
- clen - oldclen) != desired) {
- clen = oldclen;
- n--;
- break;
- }
- }
- }
- }
-
- pango_layout_set_text(layout, utfptr, clen);
- pango_layout_get_pixel_extents(layout, NULL, &rect);
-
- draw_layout(ctx,
- x + (n*cellwidth - rect.width)/2,
- y + (pfont->u.height - rect.height)/2, layout);
- if (shadowbold)
- draw_layout(ctx,
- x + (n*cellwidth - rect.width)/2 + pfont->shadowoffset,
- y + (pfont->u.height - rect.height)/2, layout);
-
- utflen -= clen;
- utfptr += clen;
- string += n;
- x += n * cellwidth;
- }
-
- sfree(utfstring);
-
- g_object_unref(layout);
-}
-
-static void pangofont_draw_text(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth)
-{
- pangofont_draw_internal(ctx, font, x, y, string, len, wide, bold,
- cellwidth, false);
-}
-
-static void pangofont_draw_combining(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string,
- int len, bool wide, bool bold,
- int cellwidth)
-{
- wchar_t *tmpstring = NULL;
- if (mk_wcwidth(string[0]) == 0) {
- /*
- * If we've been told to draw a sequence of _only_ combining
- * characters, prefix a space so that they have something to
- * combine with.
- */
- tmpstring = snewn(len+1, wchar_t);
- memcpy(tmpstring+1, string, len * sizeof(wchar_t));
- tmpstring[0] = L' ';
- string = tmpstring;
- len++;
- }
- pangofont_draw_internal(ctx, font, x, y, string, len, wide, bold,
- cellwidth, true);
- sfree(tmpstring);
-}
-
-/*
- * Dummy size value to be used when converting a
- * PangoFontDescription of a scalable font to a string for
- * internal use.
- */
-#define PANGO_DUMMY_SIZE 12
-
-static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback,
- void *callback_ctx)
-{
- PangoContext *ctx;
-#ifndef PANGO_PRE_1POINT6
- PangoFontMap *map;
-#endif
- PangoFontFamily **families;
- int i, nfamilies;
-
- ctx = gtk_widget_get_pango_context(widget);
- if (!ctx)
- return;
-
- /*
- * Ask Pango for a list of font families, and iterate through
- * them.
- */
-#ifndef PANGO_PRE_1POINT6
- map = pango_context_get_font_map(ctx);
- if (!map)
- return;
- pango_font_map_list_families(map, &families, &nfamilies);
-#else
- pango_context_list_families(ctx, &families, &nfamilies);
-#endif
- for (i = 0; i < nfamilies; i++) {
- PangoFontFamily *family = families[i];
- const char *familyname;
- int flags;
- PangoFontFace **faces;
- int j, nfaces;
-
- /*
- * Set up our flags for this font family, and get the name
- * string.
- */
- flags = FONTFLAG_CLIENTSIDE;
-#ifndef PANGO_PRE_1POINT4
- /*
- * In very early versions of Pango, we can't tell
- * monospaced fonts from non-monospaced.
- */
- if (!pango_font_family_is_monospace(family))
- flags |= FONTFLAG_NONMONOSPACED;
-#endif
- familyname = pango_font_family_get_name(family);
-
- /*
- * Go through the available font faces in this family.
- */
- pango_font_family_list_faces(family, &faces, &nfaces);
- for (j = 0; j < nfaces; j++) {
- PangoFontFace *face = faces[j];
- PangoFontDescription *desc;
- const char *facename;
- int *sizes;
- int k, nsizes, dummysize;
-
- /*
- * Get the face name string.
- */
- facename = pango_font_face_get_face_name(face);
-
- /*
- * Set up a font description with what we've got so
- * far. We'll fill in the size field manually and then
- * call pango_font_description_to_string() to give the
- * full real name of the specific font.
- */
- desc = pango_font_face_describe(face);
-
- /*
- * See if this font has a list of specific sizes.
- */
-#ifndef PANGO_PRE_1POINT4
- pango_font_face_list_sizes(face, &sizes, &nsizes);
-#else
- /*
- * In early versions of Pango, that call wasn't
- * supported; we just have to assume everything is
- * scalable.
- */
- sizes = NULL;
-#endif
- if (!sizes) {
- /*
- * Write a single entry with a dummy size.
- */
- dummysize = PANGO_DUMMY_SIZE * PANGO_SCALE;
- sizes = &dummysize;
- nsizes = 1;
- }
-
- /*
- * If so, go through them one by one.
- */
- for (k = 0; k < nsizes; k++) {
- char *fullname, *stylekey;
-
- pango_font_description_set_size(desc, sizes[k]);
-
- fullname = pango_font_description_to_string(desc);
-
- /*
- * Construct the sorting key for font styles.
- */
- {
- strbuf *buf = strbuf_new();
-
- int weight = pango_font_description_get_weight(desc);
- /* Weight: normal, then lighter, then bolder */
- if (weight <= PANGO_WEIGHT_NORMAL)
- weight = PANGO_WEIGHT_NORMAL - weight;
- strbuf_catf(buf, "%4d", weight);
-
- strbuf_catf(buf, " %2d",
- pango_font_description_get_style(desc));
-
- int stretch = pango_font_description_get_stretch(desc);
- /* Stretch: closer to normal sorts earlier */
- stretch = 2 * abs(PANGO_STRETCH_NORMAL - stretch) +
- (stretch < PANGO_STRETCH_NORMAL);
- strbuf_catf(buf, " %2d", stretch);
-
- strbuf_catf(buf, " %2d",
- pango_font_description_get_variant(desc));
-
- stylekey = strbuf_to_str(buf);
- }
-
- /*
- * Got everything. Hand off to the callback.
- * (The charset string is NULL, because only
- * server-side X fonts use it.)
- */
- callback(callback_ctx, fullname, familyname, NULL, facename,
- stylekey,
- (sizes == &dummysize ? 0 : PANGO_PIXELS(sizes[k])),
- flags, &pangofont_vtable);
-
- sfree(stylekey);
- g_free(fullname);
- }
- if (sizes != &dummysize)
- g_free(sizes);
-
- pango_font_description_free(desc);
- }
- g_free(faces);
- }
- g_free(families);
-}
-
-static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name,
- int *size, int *flags,
- bool resolve_aliases)
-{
- /*
- * When given a Pango font name to try to make sense of for a
- * font selector, we must normalise it to PANGO_DUMMY_SIZE and
- * extract its original size (in pixels) into the `size' field.
- */
- PangoContext *ctx;
-#ifndef PANGO_PRE_1POINT6
- PangoFontMap *map;
-#endif
- PangoFontDescription *desc;
- PangoFontset *fset;
- PangoFontMetrics *metrics;
- char *newname, *retname;
-
- desc = pango_font_description_from_string(name);
- if (!desc)
- return NULL;
- ctx = gtk_widget_get_pango_context(widget);
- if (!ctx) {
- pango_font_description_free(desc);
- return NULL;
- }
- if (!pangofont_check_desc_makes_sense(ctx, desc)) {
- pango_font_description_free(desc);
- return NULL;
- }
-#ifndef PANGO_PRE_1POINT6
- map = pango_context_get_font_map(ctx);
- if (!map) {
- pango_font_description_free(desc);
- return NULL;
- }
- fset = pango_font_map_load_fontset(map, ctx, desc,
- pango_context_get_language(ctx));
-#else
- fset = pango_context_load_fontset(ctx, desc,
- pango_context_get_language(ctx));
-#endif
- if (!fset) {
- pango_font_description_free(desc);
- return NULL;
- }
- metrics = pango_fontset_get_metrics(fset);
- if (!metrics ||
- pango_font_metrics_get_approximate_digit_width(metrics) == 0) {
- pango_font_description_free(desc);
- g_object_unref(fset);
- return NULL;
- }
-
- *size = PANGO_PIXELS(pango_font_description_get_size(desc));
- *flags = FONTFLAG_CLIENTSIDE;
- pango_font_description_set_size(desc, PANGO_DUMMY_SIZE * PANGO_SCALE);
- newname = pango_font_description_to_string(desc);
- retname = dupstr(newname);
- g_free(newname);
-
- pango_font_metrics_unref(metrics);
- pango_font_description_free(desc);
- g_object_unref(fset);
-
- return retname;
-}
-
-static char *pangofont_scale_fontname(GtkWidget *widget, const char *name,
- int size)
-{
- PangoFontDescription *desc;
- char *newname, *retname;
-
- desc = pango_font_description_from_string(name);
- if (!desc)
- return NULL;
- pango_font_description_set_size(desc, size * PANGO_SCALE);
- newname = pango_font_description_to_string(desc);
- retname = dupstr(newname);
- g_free(newname);
- pango_font_description_free(desc);
-
- return retname;
-}
-
-static char *pangofont_size_increment(unifont *font, int increment)
-{
- struct pangofont *pfont = container_of(font, struct pangofont, u);
- PangoFontDescription *desc;
- int size;
- char *newname, *retname;
-
- desc = pango_font_description_copy_static(pfont->desc);
-
- size = pango_font_description_get_size(desc);
- size += PANGO_SCALE * increment;
-
- if (size <= 0) {
- retname = NULL;
- } else {
- pango_font_description_set_size(desc, size);
- newname = pango_font_description_to_string(desc);
- retname = dupcat(pfont->u.vt->prefix, ":", newname);
- g_free(newname);
- }
-
- pango_font_description_free(desc);
- return retname;
-}
-
-#endif /* GTK_CHECK_VERSION(2,0,0) */
-
-/* ----------------------------------------------------------------------
- * Outermost functions which do the vtable dispatch.
- */
-
-/*
- * Complete list of font-type subclasses. Listed in preference
- * order for unifont_create(). (That is, in the extremely unlikely
- * event that the same font name is valid as both a Pango and an
- * X11 font, it will be interpreted as the former in the absence
- * of an explicit type-disambiguating prefix.)
- *
- * The 'multifont' subclass is omitted here, as discussed above.
- */
-static const struct UnifontVtable *unifont_types[] = {
-#if GTK_CHECK_VERSION(2,0,0)
- &pangofont_vtable,
-#endif
-#ifndef NOT_X_WINDOWS
- &x11font_vtable,
-#endif
-};
-
-/*
- * Function which takes a font name and processes the optional
- * scheme prefix. Returns the tail of the font name suitable for
- * passing to individual font scheme functions, and also provides
- * a subrange of the unifont_types[] array above.
- *
- * The return values `start' and `end' denote a half-open interval
- * in unifont_types[]; that is, the correct way to iterate over
- * them is
- *
- * for (i = start; i < end; i++) {...}
- */
-static const char *unifont_do_prefix(const char *name, int *start, int *end)
-{
- int colonpos = strcspn(name, ":");
- int i;
-
- if (name[colonpos]) {
- /*
- * There's a colon prefix on the font name. Use it to work
- * out which subclass to use.
- */
- for (i = 0; i < lenof(unifont_types); i++) {
- if (strlen(unifont_types[i]->prefix) == colonpos &&
- !strncmp(unifont_types[i]->prefix, name, colonpos)) {
- *start = i;
- *end = i+1;
- return name + colonpos + 1;
- }
- }
- /*
- * None matched, so return an empty scheme list to prevent
- * any scheme from being called at all.
- */
- *start = *end = 0;
- return name + colonpos + 1;
- } else {
- /*
- * No colon prefix, so just use all the subclasses.
- */
- *start = 0;
- *end = lenof(unifont_types);
- return name;
- }
-}
-
-unifont *unifont_create(GtkWidget *widget, const char *name, bool wide,
- bool bold, int shadowoffset, bool shadowalways)
-{
- int i, start, end;
-
- name = unifont_do_prefix(name, &start, &end);
-
- for (i = start; i < end; i++) {
- unifont *ret = unifont_types[i]->create(widget, name, wide, bold,
- shadowoffset, shadowalways);
- if (ret)
- return ret;
- }
- return NULL; /* font not found in any scheme */
-}
-
-void unifont_destroy(unifont *font)
-{
- font->vt->destroy(font);
-}
-
-void unifont_draw_text(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth)
-{
- font->vt->draw_text(ctx, font, x, y, string, len, wide, bold, cellwidth);
-}
-
-void unifont_draw_combining(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth)
-{
- font->vt->draw_combining(ctx, font, x, y, string, len, wide, bold,
- cellwidth);
-}
-
-char *unifont_size_increment(unifont *font, int increment)
-{
- return font->vt->size_increment(font, increment);
-}
-
-/* ----------------------------------------------------------------------
- * Multiple-font wrapper. This is a type of unifont which encapsulates
- * up to two other unifonts, permitting missing glyphs in the main
- * font to be filled in by a fallback font.
- *
- * This is a type of unifont just like the previous two, but it has a
- * separate constructor which is manually called by the client, so it
- * doesn't appear in the list of available font types enumerated by
- * unifont_create. This means it's not used by unifontsel either, so
- * it doesn't need to support any methods except draw_text and
- * destroy.
- */
-
-static void multifont_draw_text(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth);
-static void multifont_draw_combining(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string,
- int len, bool wide, bool bold,
- int cellwidth);
-static void multifont_destroy(unifont *font);
-static char *multifont_size_increment(unifont *font, int increment);
-
-struct multifont {
- unifont *main;
- unifont *fallback;
-
- struct unifont u;
-};
-
-static const UnifontVtable multifont_vtable = {
- .create = NULL, /* creation is done specially */
- .create_fallback = NULL,
- .destroy = multifont_destroy,
- .has_glyph = NULL,
- .draw_text = multifont_draw_text,
- .draw_combining = multifont_draw_combining,
- .enum_fonts = NULL,
- .canonify_fontname = NULL,
- .scale_fontname = NULL,
- .size_increment = multifont_size_increment,
- .prefix = "client",
-};
-
-unifont *multifont_create(GtkWidget *widget, const char *name,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways)
-{
- int i;
- unifont *font, *fallback;
- struct multifont *mfont;
-
- font = unifont_create(widget, name, wide, bold,
- shadowoffset, shadowalways);
- if (!font)
- return NULL;
-
- fallback = NULL;
- if (font->want_fallback) {
- for (i = 0; i < lenof(unifont_types); i++) {
- if (unifont_types[i]->create_fallback) {
- fallback = unifont_types[i]->create_fallback
- (widget, font->height, wide, bold,
- shadowoffset, shadowalways);
- if (fallback)
- break;
- }
- }
- }
-
- /*
- * Construct our multifont. Public members are all copied from the
- * primary font we're wrapping.
- */
- mfont = snew(struct multifont);
- mfont->u.vt = &multifont_vtable;
- mfont->u.width = font->width;
- mfont->u.ascent = font->ascent;
- mfont->u.descent = font->descent;
- mfont->u.height = font->height;
- mfont->u.strikethrough_y = font->strikethrough_y;
- mfont->u.public_charset = font->public_charset;
- mfont->u.want_fallback = false; /* shouldn't be needed, but just in case */
- mfont->u.preferred_drawtype = font->preferred_drawtype;
- mfont->main = font;
- mfont->fallback = fallback;
-
- return &mfont->u;
-}
-
-static void multifont_destroy(unifont *font)
-{
- struct multifont *mfont = container_of(font, struct multifont, u);
- unifont_destroy(mfont->main);
- if (mfont->fallback)
- unifont_destroy(mfont->fallback);
- sfree(mfont);
-}
-
-typedef void (*unifont_draw_func_t)(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string,
- int len, bool wide, bool bold,
- int cellwidth);
-
-static void multifont_draw_main(unifont_drawctx *ctx, unifont *font, int x,
- int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth,
- int cellinc, unifont_draw_func_t draw)
-{
- struct multifont *mfont = container_of(font, struct multifont, u);
- unifont *f;
- bool ok;
- int i;
-
- while (len > 0) {
- /*
- * Find a maximal sequence of characters which are, or are
- * not, supported by our main font.
- */
- ok = mfont->main->vt->has_glyph(mfont->main, string[0]);
- for (i = 1;
- i < len &&
- !mfont->main->vt->has_glyph(mfont->main, string[i]) == !ok;
- i++);
-
- /*
- * Now display it.
- */
- f = ok ? mfont->main : mfont->fallback;
- if (f)
- draw(ctx, f, x, y, string, i, wide, bold, cellwidth);
- string += i;
- len -= i;
- x += i * cellinc;
- }
-}
-
-static void multifont_draw_text(unifont_drawctx *ctx, unifont *font, int x,
- int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth)
-{
- multifont_draw_main(ctx, font, x, y, string, len, wide, bold,
- cellwidth, cellwidth, unifont_draw_text);
-}
-
-static void multifont_draw_combining(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string,
- int len, bool wide, bool bold,
- int cellwidth)
-{
- multifont_draw_main(ctx, font, x, y, string, len, wide, bold,
- cellwidth, 0, unifont_draw_combining);
-}
-
-static char *multifont_size_increment(unifont *font, int increment)
-{
- struct multifont *mfont = container_of(font, struct multifont, u);
- return unifont_size_increment(mfont->main, increment);
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-
-/* ----------------------------------------------------------------------
- * Implementation of a unified font selector. Used on GTK 2 only;
- * for GTK 1 we still use the standard font selector.
- */
-
-typedef struct fontinfo fontinfo;
-
-typedef struct unifontsel_internal {
- GtkListStore *family_model, *style_model, *size_model;
- GtkWidget *family_list, *style_list, *size_entry, *size_list;
- GtkWidget *filter_buttons[4];
- int n_filter_buttons;
- GtkWidget *preview_area;
-#ifndef NO_BACKING_PIXMAPS
- GdkPixmap *preview_pixmap;
-#endif
- int preview_width, preview_height;
- GdkColor preview_fg, preview_bg;
- int filter_flags;
- tree234 *fonts_by_realname, *fonts_by_selorder;
- fontinfo *selected;
- int selsize, intendedsize;
- bool inhibit_response; /* inhibit callbacks when we change GUI controls */
-
- unifontsel u;
-} unifontsel_internal;
-
-/*
- * The structure held in the tree234s. All the string members are
- * part of the same allocated area, so don't need freeing
- * separately.
- */
-struct fontinfo {
- char *realname;
- char *family, *charset, *style, *stylekey;
- int size, flags;
- /*
- * Fallback sorting key, to permit multiple identical entries
- * to exist in the selorder tree.
- */
- int index;
- /*
- * Indices mapping fontinfo structures to indices in the list
- * boxes. sizeindex is irrelevant if the font is scalable
- * (size==0).
- */
- int familyindex, styleindex, sizeindex;
- /*
- * The class of font.
- */
- const struct UnifontVtable *fontclass;
-};
-
-struct fontinfo_realname_find {
- const char *realname;
- int flags;
-};
-
-static int strnullcasecmp(const char *a, const char *b)
-{
- int i;
-
- /*
- * If exactly one of the inputs is NULL, it compares before
- * the other one.
- */
- if ((i = (!b) - (!a)) != 0)
- return i;
-
- /*
- * NULL compares equal.
- */
- if (!a)
- return 0;
-
- /*
- * Otherwise, ordinary strcasecmp.
- */
- return g_ascii_strcasecmp(a, b);
-}
-
-static int fontinfo_realname_compare(void *av, void *bv)
-{
- fontinfo *a = (fontinfo *)av;
- fontinfo *b = (fontinfo *)bv;
- int i;
-
- if ((i = strnullcasecmp(a->realname, b->realname)) != 0)
- return i;
- if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK))
- return ((a->flags & FONTFLAG_SORT_MASK) <
- (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1);
- return 0;
-}
-
-static int fontinfo_realname_find(void *av, void *bv)
-{
- struct fontinfo_realname_find *a = (struct fontinfo_realname_find *)av;
- fontinfo *b = (fontinfo *)bv;
- int i;
-
- if ((i = strnullcasecmp(a->realname, b->realname)) != 0)
- return i;
- if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK))
- return ((a->flags & FONTFLAG_SORT_MASK) <
- (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1);
- return 0;
-}
-
-static int fontinfo_selorder_compare(void *av, void *bv)
-{
- fontinfo *a = (fontinfo *)av;
- fontinfo *b = (fontinfo *)bv;
- int i;
- if ((i = strnullcasecmp(a->family, b->family)) != 0)
- return i;
- /*
- * Font class comes immediately after family, so that fonts
- * from different classes with the same family
- */
- if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK))
- return ((a->flags & FONTFLAG_SORT_MASK) <
- (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1);
- if ((i = strnullcasecmp(a->charset, b->charset)) != 0)
- return i;
- if ((i = strnullcasecmp(a->stylekey, b->stylekey)) != 0)
- return i;
- if ((i = strnullcasecmp(a->style, b->style)) != 0)
- return i;
- if (a->size != b->size)
- return (a->size < b->size ? -1 : +1);
- if (a->index != b->index)
- return (a->index < b->index ? -1 : +1);
- return 0;
-}
-
-static void unifontsel_draw_preview_text(unifontsel_internal *fs);
-
-static void unifontsel_deselect(unifontsel_internal *fs)
-{
- fs->selected = NULL;
- gtk_list_store_clear(fs->style_model);
- gtk_list_store_clear(fs->size_model);
- gtk_widget_set_sensitive(fs->u.ok_button, false);
- gtk_widget_set_sensitive(fs->size_entry, false);
- unifontsel_draw_preview_text(fs);
-}
-
-static void unifontsel_setup_familylist(unifontsel_internal *fs)
-{
- GtkTreeIter iter;
- int i, listindex, minpos = -1, maxpos = -1;
- char *currfamily = NULL;
- int currflags = -1;
- fontinfo *info;
-
- fs->inhibit_response = true;
-
- gtk_list_store_clear(fs->family_model);
- listindex = 0;
-
- /*
- * Search through the font tree for anything matching our
- * current filter criteria. When we find one, add its font
- * name to the list box.
- */
- for (i = 0 ;; i++) {
- info = (fontinfo *)index234(fs->fonts_by_selorder, i);
- /*
- * info may be NULL if we've just run off the end of the
- * tree. We must still do a processing pass in that
- * situation, in case we had an unfinished font record in
- * progress.
- */
- if (info && (info->flags &~ fs->filter_flags)) {
- info->familyindex = -1;
- continue; /* we're filtering out this font */
- }
- if (!info || strnullcasecmp(currfamily, info->family) ||
- currflags != (info->flags & FONTFLAG_SORT_MASK)) {
- /*
- * We've either finished a family, or started a new
- * one, or both.
- */
- if (currfamily) {
- gtk_list_store_append(fs->family_model, &iter);
- gtk_list_store_set(fs->family_model, &iter,
- 0, currfamily, 1, minpos, 2, maxpos+1, -1);
- listindex++;
- }
- if (info) {
- minpos = i;
- currfamily = info->family;
- currflags = info->flags & FONTFLAG_SORT_MASK;
- }
- }
- if (!info)
- break; /* now we're done */
- info->familyindex = listindex;
- maxpos = i;
- }
-
- /*
- * If we've just filtered out the previously selected font,
- * deselect it thoroughly.
- */
- if (fs->selected && fs->selected->familyindex < 0)
- unifontsel_deselect(fs);
-
- fs->inhibit_response = false;
-}
-
-static void unifontsel_setup_stylelist(unifontsel_internal *fs,
- int start, int end)
-{
- GtkTreeIter iter;
- int i, listindex, minpos = -1, maxpos = -1;
- bool started = false;
- char *currcs = NULL, *currstyle = NULL;
- fontinfo *info;
-
- gtk_list_store_clear(fs->style_model);
- listindex = 0;
- started = false;
-
- /*
- * Search through the font tree for anything matching our
- * current filter criteria. When we find one, add its charset
- * and/or style name to the list box.
- */
- for (i = start; i <= end; i++) {
- if (i == end)
- info = NULL;
- else
- info = (fontinfo *)index234(fs->fonts_by_selorder, i);
- /*
- * info may be NULL if we've just run off the end of the
- * relevant data. We must still do a processing pass in
- * that situation, in case we had an unfinished font
- * record in progress.
- */
- if (info && (info->flags &~ fs->filter_flags)) {
- info->styleindex = -1;
- continue; /* we're filtering out this font */
- }
- if (!info || !started || strnullcasecmp(currcs, info->charset) ||
- strnullcasecmp(currstyle, info->style)) {
- /*
- * We've either finished a style/charset, or started a
- * new one, or both.
- */
- started = true;
- if (currstyle) {
- gtk_list_store_append(fs->style_model, &iter);
- gtk_list_store_set(fs->style_model, &iter,
- 0, currstyle, 1, minpos, 2, maxpos+1,
- 3, true, 4, PANGO_WEIGHT_NORMAL, -1);
- listindex++;
- }
- if (info) {
- minpos = i;
- if (info->charset && strnullcasecmp(currcs, info->charset)) {
- gtk_list_store_append(fs->style_model, &iter);
- gtk_list_store_set(fs->style_model, &iter,
- 0, info->charset, 1, -1, 2, -1,
- 3, false, 4, PANGO_WEIGHT_BOLD, -1);
- listindex++;
- }
- currcs = info->charset;
- currstyle = info->style;
- }
- }
- if (!info)
- break; /* now we're done */
- info->styleindex = listindex;
- maxpos = i;
- }
-}
-
-static const int unifontsel_default_sizes[] = { 10, 12, 14, 16, 20, 24, 32 };
-
-static void unifontsel_setup_sizelist(unifontsel_internal *fs,
- int start, int end)
-{
- GtkTreeIter iter;
- int i, listindex;
- char sizetext[40];
- fontinfo *info;
-
- gtk_list_store_clear(fs->size_model);
- listindex = 0;
-
- /*
- * Search through the font tree for anything matching our
- * current filter criteria. When we find one, add its font
- * name to the list box.
- */
- for (i = start; i < end; i++) {
- info = (fontinfo *)index234(fs->fonts_by_selorder, i);
- if (!info) {
- /* _shouldn't_ happen unless font list is completely funted */
- break;
- }
- if (info->flags &~ fs->filter_flags) {
- info->sizeindex = -1;
- continue; /* we're filtering out this font */
- }
- if (info->size) {
- sprintf(sizetext, "%d", info->size);
- info->sizeindex = listindex;
- gtk_list_store_append(fs->size_model, &iter);
- gtk_list_store_set(fs->size_model, &iter,
- 0, sizetext, 1, i, 2, info->size, -1);
- listindex++;
- } else {
- int j;
-
- assert(i == start);
- assert(i+1 == end);
-
- for (j = 0; j < lenof(unifontsel_default_sizes); j++) {
- sprintf(sizetext, "%d", unifontsel_default_sizes[j]);
- gtk_list_store_append(fs->size_model, &iter);
- gtk_list_store_set(fs->size_model, &iter, 0, sizetext, 1, i,
- 2, unifontsel_default_sizes[j], -1);
- listindex++;
- }
- }
- }
-}
-
-static void unifontsel_set_filter_buttons(unifontsel_internal *fs)
-{
- int i;
-
- for (i = 0; i < fs->n_filter_buttons; i++) {
- int flagbit = GPOINTER_TO_INT(g_object_get_data
- (G_OBJECT(fs->filter_buttons[i]),
- "user-data"));
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fs->filter_buttons[i]),
- !!(fs->filter_flags & flagbit));
- }
-}
-
-static void unifontsel_draw_preview_text_inner(unifont_drawctx *dctx,
- unifontsel_internal *fs)
-{
- unifont *font;
- char *sizename = NULL;
- fontinfo *info = fs->selected;
-
- if (info) {
- sizename = info->fontclass->scale_fontname
- (GTK_WIDGET(fs->u.window), info->realname, fs->selsize);
- font = info->fontclass->create(GTK_WIDGET(fs->u.window),
- sizename ? sizename : info->realname,
- false, false, 0, 0);
- } else
- font = NULL;
-
-#ifdef DRAW_TEXT_GDK
- if (dctx->type == DRAWTYPE_GDK) {
- gdk_gc_set_foreground(dctx->u.gdk.gc, &fs->preview_bg);
- gdk_draw_rectangle(dctx->u.gdk.target, dctx->u.gdk.gc, 1, 0, 0,
- fs->preview_width, fs->preview_height);
- gdk_gc_set_foreground(dctx->u.gdk.gc, &fs->preview_fg);
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (dctx->type == DRAWTYPE_CAIRO) {
- cairo_set_source_rgb(dctx->u.cairo.cr,
- fs->preview_bg.red / 65535.0,
- fs->preview_bg.green / 65535.0,
- fs->preview_bg.blue / 65535.0);
- cairo_paint(dctx->u.cairo.cr);
- cairo_set_source_rgb(dctx->u.cairo.cr,
- fs->preview_fg.red / 65535.0,
- fs->preview_fg.green / 65535.0,
- fs->preview_fg.blue / 65535.0);
- }
-#endif
-
- if (font) {
- /*
- * The pangram used here is rather carefully
- * constructed: it contains a sequence of very narrow
- * letters (`jil') and a pair of adjacent very wide
- * letters (`wm').
- *
- * If the user selects a proportional font, it will be
- * coerced into fixed-width character cells when used
- * in the actual terminal window. We therefore display
- * it the same way in the preview pane, so as to show
- * it the way it will actually be displayed - and we
- * deliberately pick a pangram which will show the
- * resulting miskerning at its worst.
- *
- * We aren't trying to sell people these fonts; we're
- * trying to let them make an informed choice. Better
- * that they find out the problems with using
- * proportional fonts in terminal windows here than
- * that they go to the effort of selecting their font
- * and _then_ realise it was a mistake.
- */
- info->fontclass->draw_text(dctx, font,
- 0, font->ascent,
- L"bankrupt jilted showmen quiz convex fogey",
- 41, false, false, font->width);
- info->fontclass->draw_text(dctx, font,
- 0, font->ascent + font->height,
- L"BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY",
- 41, false, false, font->width);
- /*
- * The ordering of punctuation here is also selected
- * with some specific aims in mind. I put ` and '
- * together because some software (and people) still
- * use them as matched quotes no matter what Unicode
- * might say on the matter, so people can quickly
- * check whether they look silly in a candidate font.
- * The sequence #_@ is there to let people judge the
- * suitability of the underscore as an effectively
- * alphabetic character (since that's how it's often
- * used in practice, at least by programmers).
- */
- info->fontclass->draw_text(dctx, font,
- 0, font->ascent + font->height * 2,
- L"0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$",
- 42, false, false, font->width);
-
- info->fontclass->destroy(font);
- }
-
- sfree(sizename);
-}
-
-static void unifontsel_draw_preview_text(unifontsel_internal *fs)
-{
- unifont_drawctx dctx;
- GdkWindow *target;
-
-#ifndef NO_BACKING_PIXMAPS
- target = fs->preview_pixmap;
-#else
- target = gtk_widget_get_window(fs->preview_area);
-#endif
- if (!target) /* we may be called when we haven't created everything yet */
- return;
-
- dctx.type = DRAWTYPE_DEFAULT;
-#ifdef DRAW_TEXT_GDK
- if (dctx.type == DRAWTYPE_GDK) {
- dctx.u.gdk.target = target;
- dctx.u.gdk.gc = gdk_gc_new(target);
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (dctx.type == DRAWTYPE_CAIRO) {
-#if GTK_CHECK_VERSION(3,22,0)
- cairo_region_t *region;
-#endif
-
- dctx.u.cairo.widget = GTK_WIDGET(fs->preview_area);
-
-#if GTK_CHECK_VERSION(3,22,0)
- dctx.u.cairo.gdkwin = gtk_widget_get_window(dctx.u.cairo.widget);
- region = gdk_window_get_clip_region(dctx.u.cairo.gdkwin);
- dctx.u.cairo.drawctx = gdk_window_begin_draw_frame(
- dctx.u.cairo.gdkwin, region);
- dctx.u.cairo.cr = gdk_drawing_context_get_cairo_context(
- dctx.u.cairo.drawctx);
- cairo_region_destroy(region);
-#else
- dctx.u.cairo.cr = gdk_cairo_create(target);
-#endif
- }
-#endif
-
- unifontsel_draw_preview_text_inner(&dctx, fs);
-
-#ifdef DRAW_TEXT_GDK
- if (dctx.type == DRAWTYPE_GDK) {
- gdk_gc_unref(dctx.u.gdk.gc);
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (dctx.type == DRAWTYPE_CAIRO) {
-#if GTK_CHECK_VERSION(3,22,0)
- gdk_window_end_draw_frame(dctx.u.cairo.gdkwin, dctx.u.cairo.drawctx);
-#else
- cairo_destroy(dctx.u.cairo.cr);
-#endif
- }
-#endif
-
- gdk_window_invalidate_rect(gtk_widget_get_window(fs->preview_area),
- NULL, false);
-}
-
-static void unifontsel_select_font(unifontsel_internal *fs,
- fontinfo *info, int size, int leftlist,
- bool size_is_explicit)
-{
- int index;
- int minval, maxval;
- gboolean success;
- GtkTreePath *treepath;
- GtkTreeIter iter;
-
- fs->inhibit_response = true;
-
- fs->selected = info;
- fs->selsize = size;
- if (size_is_explicit)
- fs->intendedsize = size;
-
- gtk_widget_set_sensitive(fs->u.ok_button, true);
-
- /*
- * Find the index of this fontinfo in the selorder list.
- */
- index = -1;
- findpos234(fs->fonts_by_selorder, info, NULL, &index);
- assert(index >= 0);
-
- /*
- * Adjust the font selector flags and redo the font family
- * list box, if necessary.
- */
- if (leftlist <= 0 &&
- (fs->filter_flags | info->flags) != fs->filter_flags) {
- fs->filter_flags |= info->flags;
- unifontsel_set_filter_buttons(fs);
- unifontsel_setup_familylist(fs);
- }
-
- /*
- * Find the appropriate family name and select it in the list.
- */
- assert(info->familyindex >= 0);
- treepath = gtk_tree_path_new_from_indices(info->familyindex, -1);
- gtk_tree_selection_select_path
- (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->family_list)),
- treepath);
- gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->family_list),
- treepath, NULL, false, 0.0, 0.0);
- success = gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model),
- &iter, treepath);
- assert(success);
- gtk_tree_path_free(treepath);
-
- /*
- * Now set up the font style list.
- */
- gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter,
- 1, &minval, 2, &maxval, -1);
- if (leftlist <= 1)
- unifontsel_setup_stylelist(fs, minval, maxval);
-
- /*
- * Find the appropriate style name and select it in the list.
- */
- if (info->style) {
- assert(info->styleindex >= 0);
- treepath = gtk_tree_path_new_from_indices(info->styleindex, -1);
- gtk_tree_selection_select_path
- (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->style_list)),
- treepath);
- gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->style_list),
- treepath, NULL, false, 0.0, 0.0);
- gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->style_model),
- &iter, treepath);
- gtk_tree_path_free(treepath);
-
- /*
- * And set up the size list.
- */
- gtk_tree_model_get(GTK_TREE_MODEL(fs->style_model), &iter,
- 1, &minval, 2, &maxval, -1);
- if (leftlist <= 2)
- unifontsel_setup_sizelist(fs, minval, maxval);
-
- /*
- * Find the appropriate size, and select it in the list.
- */
- if (info->size) {
- assert(info->sizeindex >= 0);
- treepath = gtk_tree_path_new_from_indices(info->sizeindex, -1);
- gtk_tree_selection_select_path
- (gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->size_list)),
- treepath);
- gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list),
- treepath, NULL, false, 0.0, 0.0);
- gtk_tree_path_free(treepath);
- size = info->size;
- } else {
- int j;
- for (j = 0; j < lenof(unifontsel_default_sizes); j++)
- if (unifontsel_default_sizes[j] == size) {
- treepath = gtk_tree_path_new_from_indices(j, -1);
- gtk_tree_view_set_cursor(GTK_TREE_VIEW(fs->size_list),
- treepath, NULL, false);
- gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list),
- treepath, NULL, false, 0.0,
- 0.0);
- gtk_tree_path_free(treepath);
- }
- }
-
- /*
- * And set up the font size text entry box.
- */
- {
- char sizetext[40];
- sprintf(sizetext, "%d", size);
- gtk_entry_set_text(GTK_ENTRY(fs->size_entry), sizetext);
- }
- } else {
- if (leftlist <= 2)
- unifontsel_setup_sizelist(fs, 0, 0);
- gtk_entry_set_text(GTK_ENTRY(fs->size_entry), "");
- }
-
- /*
- * Grey out the font size edit box if we're not using a
- * scalable font.
- */
- gtk_editable_set_editable(GTK_EDITABLE(fs->size_entry),
- fs->selected->size == 0);
- gtk_widget_set_sensitive(fs->size_entry, fs->selected->size == 0);
-
- unifontsel_draw_preview_text(fs);
-
- fs->inhibit_response = false;
-}
-
-static void unifontsel_button_toggled(GtkToggleButton *tb, gpointer data)
-{
- unifontsel_internal *fs = (unifontsel_internal *)data;
- bool newstate = gtk_toggle_button_get_active(tb);
- int newflags;
- int flagbit = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(tb),
- "user-data"));
-
- if (newstate)
- newflags = fs->filter_flags | flagbit;
- else
- newflags = fs->filter_flags & ~flagbit;
-
- if (fs->filter_flags != newflags) {
- fs->filter_flags = newflags;
- unifontsel_setup_familylist(fs);
- }
-}
-
-static void unifontsel_add_entry(void *ctx, const char *realfontname,
- const char *family, const char *charset,
- const char *style, const char *stylekey,
- int size, int flags,
- const struct UnifontVtable *fontclass)
-{
- unifontsel_internal *fs = (unifontsel_internal *)ctx;
- fontinfo *info;
- int totalsize;
- char *p;
-
- totalsize = sizeof(fontinfo) + strlen(realfontname) +
- (family ? strlen(family) : 0) + (charset ? strlen(charset) : 0) +
- (style ? strlen(style) : 0) + (stylekey ? strlen(stylekey) : 0) + 10;
- info = (fontinfo *)smalloc(totalsize);
- info->fontclass = fontclass;
- p = (char *)info + sizeof(fontinfo);
- info->realname = p;
- strcpy(p, realfontname);
- p += 1+strlen(p);
- if (family) {
- info->family = p;
- strcpy(p, family);
- p += 1+strlen(p);
- } else
- info->family = NULL;
- if (charset) {
- info->charset = p;
- strcpy(p, charset);
- p += 1+strlen(p);
- } else
- info->charset = NULL;
- if (style) {
- info->style = p;
- strcpy(p, style);
- p += 1+strlen(p);
- } else
- info->style = NULL;
- if (stylekey) {
- info->stylekey = p;
- strcpy(p, stylekey);
- p += 1+strlen(p);
- } else
- info->stylekey = NULL;
- assert(p - (char *)info <= totalsize);
- info->size = size;
- info->flags = flags;
- info->index = count234(fs->fonts_by_selorder);
-
- /*
- * It's just conceivable that a misbehaving font enumerator
- * might tell us about the same font real name more than once,
- * in which case we should silently drop the new one.
- */
- if (add234(fs->fonts_by_realname, info) != info) {
- sfree(info);
- return;
- }
- /*
- * However, we should never get a duplicate key in the
- * selorder tree, because the index field carefully
- * disambiguates otherwise identical records.
- */
- add234(fs->fonts_by_selorder, info);
-}
-
-static fontinfo *update_for_intended_size(unifontsel_internal *fs,
- fontinfo *info)
-{
- fontinfo info2, *below, *above;
- int pos;
-
- /*
- * Copy the info structure. This doesn't copy its dynamic
- * string fields, but that's unimportant because all we're
- * going to do is to adjust the size field and use it in one
- * tree search.
- */
- info2 = *info;
- info2.size = fs->intendedsize;
-
- /*
- * Search in the tree to find the fontinfo structure which
- * best approximates the size the user last requested.
- */
- below = findrelpos234(fs->fonts_by_selorder, &info2, NULL,
- REL234_LE, &pos);
- if (!below)
- pos = -1;
- above = index234(fs->fonts_by_selorder, pos+1);
-
- /*
- * See if we've found it exactly, which is an easy special
- * case. If we have, it'll be in `below' and not `above',
- * because we did a REL234_LE rather than REL234_LT search.
- */
- if (below && !fontinfo_selorder_compare(&info2, below))
- return below;
-
- /*
- * Now we've either found two suitable fonts, one smaller and
- * one larger, or we're at one or other extreme end of the
- * scale. Find out which, by NULLing out either of below and
- * above if it differs from this one in any respect but size
- * (and the disambiguating index field). Bear in mind, also,
- * that either one might _already_ be NULL if we're at the
- * extreme ends of the font list.
- */
- if (below) {
- info2.size = below->size;
- info2.index = below->index;
- if (fontinfo_selorder_compare(&info2, below))
- below = NULL;
- }
- if (above) {
- info2.size = above->size;
- info2.index = above->index;
- if (fontinfo_selorder_compare(&info2, above))
- above = NULL;
- }
-
- /*
- * Now return whichever of above and below is non-NULL, if
- * that's unambiguous.
- */
- if (!above)
- return below;
- if (!below)
- return above;
-
- /*
- * And now we really do have to make a choice about whether to
- * round up or down. We'll do it by rounding to nearest,
- * breaking ties by rounding up.
- */
- if (above->size - fs->intendedsize <= fs->intendedsize - below->size)
- return above;
- else
- return below;
-}
-
-static void family_changed(GtkTreeSelection *treeselection, gpointer data)
-{
- unifontsel_internal *fs = (unifontsel_internal *)data;
- GtkTreeModel *treemodel;
- GtkTreeIter treeiter;
- int minval;
- fontinfo *info;
-
- if (fs->inhibit_response) /* we made this change ourselves */
- return;
-
- if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
- return;
-
- gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1);
- info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
- if (!info)
- return; /* _shouldn't_ happen unless font list is completely funted */
- info = update_for_intended_size(fs, info);
- if (!info)
- return; /* similarly shouldn't happen */
- if (!info->size)
- fs->selsize = fs->intendedsize; /* font is scalable */
- unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize,
- 1, false);
-}
-
-static void style_changed(GtkTreeSelection *treeselection, gpointer data)
-{
- unifontsel_internal *fs = (unifontsel_internal *)data;
- GtkTreeModel *treemodel;
- GtkTreeIter treeiter;
- int minval;
- fontinfo *info;
-
- if (fs->inhibit_response) /* we made this change ourselves */
- return;
-
- if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
- return;
-
- gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1);
- if (minval < 0)
- return; /* somehow a charset heading got clicked */
- info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
- if (!info)
- return; /* _shouldn't_ happen unless font list is completely funted */
- info = update_for_intended_size(fs, info);
- if (!info)
- return; /* similarly shouldn't happen */
- if (!info->size)
- fs->selsize = fs->intendedsize; /* font is scalable */
- unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize,
- 2, false);
-}
-
-static void size_changed(GtkTreeSelection *treeselection, gpointer data)
-{
- unifontsel_internal *fs = (unifontsel_internal *)data;
- GtkTreeModel *treemodel;
- GtkTreeIter treeiter;
- int minval, size;
- fontinfo *info;
-
- if (fs->inhibit_response) /* we made this change ourselves */
- return;
-
- if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
- return;
-
- gtk_tree_model_get(treemodel, &treeiter, 1, &minval, 2, &size, -1);
- info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
- if (!info)
- return; /* _shouldn't_ happen unless font list is completely funted */
- unifontsel_select_font(fs, info, info->size ? info->size : size, 3, true);
-}
-
-static void size_entry_changed(GtkEditable *ed, gpointer data)
-{
- unifontsel_internal *fs = (unifontsel_internal *)data;
- const char *text;
- int size;
-
- if (fs->inhibit_response) /* we made this change ourselves */
- return;
-
- text = gtk_entry_get_text(GTK_ENTRY(ed));
- size = atoi(text);
-
- if (size > 0) {
- assert(fs->selected->size == 0);
- unifontsel_select_font(fs, fs->selected, size, 3, true);
- }
-}
-
-static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path,
- GtkTreeViewColumn *column, gpointer data)
-{
- unifontsel_internal *fs = (unifontsel_internal *)data;
- GtkTreeIter iter;
- int minval, newsize;
- fontinfo *info, *newinfo;
- char *newname;
-
- if (fs->inhibit_response) /* we made this change ourselves */
- return;
-
- gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, path);
- gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter, 1,&minval, -1);
- info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
- if (info) {
- int flags;
- struct fontinfo_realname_find f;
-
- newname = info->fontclass->canonify_fontname
- (GTK_WIDGET(fs->u.window), info->realname, &newsize, &flags, true);
-
- f.realname = newname;
- f.flags = flags;
- newinfo = find234(fs->fonts_by_realname, &f, fontinfo_realname_find);
-
- sfree(newname);
- if (!newinfo)
- return; /* font name not in our index */
- if (newinfo == info)
- return; /* didn't change under canonification => not an alias */
- unifontsel_select_font(fs, newinfo,
- newinfo->size ? newinfo->size : newsize,
- 1, true);
- }
-}
-
-#if GTK_CHECK_VERSION(3,0,0)
-static gint unifontsel_draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
-{
- unifontsel_internal *fs = (unifontsel_internal *)data;
- unifont_drawctx dctx;
-
- dctx.type = DRAWTYPE_CAIRO;
- dctx.u.cairo.widget = widget;
- dctx.u.cairo.cr = cr;
- unifontsel_draw_preview_text_inner(&dctx, fs);
-
- return true;
-}
-#else
-static gint unifontsel_expose_area(GtkWidget *widget, GdkEventExpose *event,
- gpointer data)
-{
- unifontsel_internal *fs = (unifontsel_internal *)data;
-
-#ifndef NO_BACKING_PIXMAPS
- if (fs->preview_pixmap) {
- gdk_draw_pixmap(gtk_widget_get_window(widget),
- (gtk_widget_get_style(widget)->fg_gc
- [gtk_widget_get_state(widget)]),
- fs->preview_pixmap,
- event->area.x, event->area.y,
- event->area.x, event->area.y,
- event->area.width, event->area.height);
- }
-#else
- unifontsel_draw_preview_text(fs);
-#endif
-
- return true;
-}
-#endif
-
-static gint unifontsel_configure_area(GtkWidget *widget,
- GdkEventConfigure *event, gpointer data)
-{
-#ifndef NO_BACKING_PIXMAPS
- unifontsel_internal *fs = (unifontsel_internal *)data;
- int ox, oy, nx, ny, x, y;
-
- /*
- * Enlarge the pixmap, but never shrink it.
- */
- ox = fs->preview_width;
- oy = fs->preview_height;
- x = event->width;
- y = event->height;
- if (x > ox || y > oy) {
- if (fs->preview_pixmap)
- gdk_pixmap_unref(fs->preview_pixmap);
-
- nx = (x > ox ? x : ox);
- ny = (y > oy ? y : oy);
- fs->preview_pixmap = gdk_pixmap_new(gtk_widget_get_window(widget),
- nx, ny, -1);
- fs->preview_width = nx;
- fs->preview_height = ny;
-
- unifontsel_draw_preview_text(fs);
- }
-#endif
-
- gdk_window_invalidate_rect(gtk_widget_get_window(widget), NULL, false);
-
- return true;
-}
-
-unifontsel *unifontsel_new(const char *wintitle)
-{
- unifontsel_internal *fs = snew(unifontsel_internal);
- GtkWidget *table, *label, *w, *ww, *scroll;
- GtkListStore *model;
- GtkTreeViewColumn *column;
- int lists_height, preview_height, font_width, style_width, size_width;
- int i;
-
- fs->inhibit_response = false;
- fs->selected = NULL;
-
- {
- int width, height;
-
- /*
- * Invent some magic size constants.
- */
- get_label_text_dimensions("Quite Long Font Name (Foundry)",
- &width, &height);
- font_width = width;
- lists_height = 14 * height;
- preview_height = 5 * height;
-
- get_label_text_dimensions("Italic Extra Condensed", &width, &height);
- style_width = width;
-
- get_label_text_dimensions("48000", &width, &height);
- size_width = width;
- }
-
- /*
- * Create the dialog box and initialise the user-visible
- * fields in the returned structure.
- */
- fs->u.user_data = NULL;
- fs->u.window = GTK_WINDOW(gtk_dialog_new());
- gtk_window_set_title(fs->u.window, wintitle);
- fs->u.cancel_button = gtk_dialog_add_button
- (GTK_DIALOG(fs->u.window), STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL);
- fs->u.ok_button = gtk_dialog_add_button
- (GTK_DIALOG(fs->u.window), STANDARD_OK_LABEL, GTK_RESPONSE_OK);
- gtk_widget_grab_default(fs->u.ok_button);
-
- /*
- * Now set up the internal fields, including in particular all
- * the controls that actually allow the user to select fonts.
- */
-#if GTK_CHECK_VERSION(3,0,0)
- table = gtk_grid_new();
- gtk_grid_set_column_spacing(GTK_GRID(table), 8);
-#else
- table = gtk_table_new(8, 3, false);
- gtk_table_set_col_spacings(GTK_TABLE(table), 8);
-#endif
- gtk_widget_show(table);
-
-#if GTK_CHECK_VERSION(3,0,0)
- /* GtkAlignment has become deprecated and we use the "margin"
- * property */
- w = table;
- g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL);
-#elif GTK_CHECK_VERSION(2,4,0)
- /* GtkAlignment seems to be the simplest way to put padding round things */
- w = gtk_alignment_new(0, 0, 1, 1);
- gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8);
- gtk_container_add(GTK_CONTAINER(w), table);
- gtk_widget_show(w);
-#else
- /* In GTK < 2.4, even that isn't available */
- w = table;
-#endif
-
- gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area
- (GTK_DIALOG(fs->u.window))),
- w, true, true, 0);
-
- label = gtk_label_new_with_mnemonic("_Font:");
- gtk_widget_show(label);
- align_label_left(GTK_LABEL(label));
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1);
- g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
-#endif
-
- /*
- * The Font list box displays only a string, but additionally
- * stores two integers which give the limits within the
- * tree234 of the font entries covered by this list entry.
- */
- model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
- w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
- gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false);
- gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
- gtk_widget_show(w);
- column = gtk_tree_view_column_new_with_attributes
- ("Font", gtk_cell_renderer_text_new(),
- "text", 0, (char *)NULL);
- gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
- gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
- g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
- "changed", G_CALLBACK(family_changed), fs);
- g_signal_connect(G_OBJECT(w), "row-activated",
- G_CALLBACK(alias_resolve), fs);
-
- scroll = gtk_scrolled_window_new(NULL, NULL);
- gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
- GTK_SHADOW_IN);
- gtk_container_add(GTK_CONTAINER(scroll), w);
- gtk_widget_show(scroll);
- gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
- GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
- gtk_widget_set_size_request(scroll, font_width, lists_height);
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), scroll, 0, 1, 1, 2);
- g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL,
- GTK_EXPAND | GTK_FILL, 0, 0);
-#endif
- fs->family_model = model;
- fs->family_list = w;
-
- label = gtk_label_new_with_mnemonic("_Style:");
- gtk_widget_show(label);
- align_label_left(GTK_LABEL(label));
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), label, 1, 0, 1, 1);
- g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
-#endif
-
- /*
- * The Style list box can contain insensitive elements (character
- * set headings for server-side fonts), so we add an extra column
- * to the list store to hold that information. Also, since GTK3 at
- * least doesn't seem to display insensitive elements differently
- * by default, we add a further column to change their style.
- */
- model = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT,
- G_TYPE_BOOLEAN, G_TYPE_INT);
- w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
- gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false);
- gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
- gtk_widget_show(w);
- column = gtk_tree_view_column_new_with_attributes
- ("Style", gtk_cell_renderer_text_new(),
- "text", 0, "sensitive", 3, "weight", 4, (char *)NULL);
- gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
- gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
- g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
- "changed", G_CALLBACK(style_changed), fs);
-
- scroll = gtk_scrolled_window_new(NULL, NULL);
- gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
- GTK_SHADOW_IN);
- gtk_container_add(GTK_CONTAINER(scroll), w);
- gtk_widget_show(scroll);
- gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
- GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
- gtk_widget_set_size_request(scroll, style_width, lists_height);
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), scroll, 1, 1, 1, 2);
- g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL,
- GTK_EXPAND | GTK_FILL, 0, 0);
-#endif
- fs->style_model = model;
- fs->style_list = w;
-
- label = gtk_label_new_with_mnemonic("Si_ze:");
- gtk_widget_show(label);
- align_label_left(GTK_LABEL(label));
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), label, 2, 0, 1, 1);
- g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0);
-#endif
-
- /*
- * The Size label attaches primarily to a text input box so
- * that the user can select a size of their choice. The list
- * of available sizes is secondary.
- */
- fs->size_entry = w = gtk_entry_new();
- gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
- gtk_widget_set_size_request(w, size_width, -1);
- gtk_widget_show(w);
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), w, 2, 1, 1, 1);
- g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), w, 2, 3, 1, 2, GTK_FILL, 0, 0, 0);
-#endif
- g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(size_entry_changed),
- fs);
-
- model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
- w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
- gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false);
- gtk_widget_show(w);
- column = gtk_tree_view_column_new_with_attributes
- ("Size", gtk_cell_renderer_text_new(),
- "text", 0, (char *)NULL);
- gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
- gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
- g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
- "changed", G_CALLBACK(size_changed), fs);
-
- scroll = gtk_scrolled_window_new(NULL, NULL);
- gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
- GTK_SHADOW_IN);
- gtk_container_add(GTK_CONTAINER(scroll), w);
- gtk_widget_show(scroll);
- gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
- GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), scroll, 2, 2, 1, 1);
- g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), scroll, 2, 3, 2, 3, GTK_FILL,
- GTK_EXPAND | GTK_FILL, 0, 0);
-#endif
- fs->size_model = model;
- fs->size_list = w;
-
- /*
- * Preview widget.
- */
- fs->preview_area = gtk_drawing_area_new();
-#ifndef NO_BACKING_PIXMAPS
- fs->preview_pixmap = NULL;
-#endif
- fs->preview_width = 0;
- fs->preview_height = 0;
- fs->preview_fg.pixel = fs->preview_bg.pixel = 0;
- fs->preview_fg.red = fs->preview_fg.green = fs->preview_fg.blue = 0x0000;
- fs->preview_bg.red = fs->preview_bg.green = fs->preview_bg.blue = 0xFFFF;
-#if !GTK_CHECK_VERSION(3,0,0)
- gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_fg,
- false, false);
- gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_bg,
- false, false);
-#endif
-#if GTK_CHECK_VERSION(3,0,0)
- g_signal_connect(G_OBJECT(fs->preview_area), "draw",
- G_CALLBACK(unifontsel_draw_area), fs);
-#else
- g_signal_connect(G_OBJECT(fs->preview_area), "expose_event",
- G_CALLBACK(unifontsel_expose_area), fs);
-#endif
- g_signal_connect(G_OBJECT(fs->preview_area), "configure_event",
- G_CALLBACK(unifontsel_configure_area), fs);
- gtk_widget_set_size_request(fs->preview_area, 1, preview_height);
- gtk_widget_show(fs->preview_area);
- ww = fs->preview_area;
- w = gtk_frame_new(NULL);
- gtk_container_add(GTK_CONTAINER(w), ww);
- gtk_widget_show(w);
-
-#if GTK_CHECK_VERSION(3,0,0)
- /* GtkAlignment has become deprecated and we use the "margin"
- * property */
- g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL);
-#elif GTK_CHECK_VERSION(2,4,0)
- ww = w;
- /* GtkAlignment seems to be the simplest way to put padding round things */
- w = gtk_alignment_new(0, 0, 1, 1);
- gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8);
- gtk_container_add(GTK_CONTAINER(w), ww);
- gtk_widget_show(w);
-#endif
-
- ww = w;
- w = gtk_frame_new("Preview of font");
- gtk_container_add(GTK_CONTAINER(w), ww);
- gtk_widget_show(w);
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), w, 0, 3, 3, 1);
- g_object_set(G_OBJECT(w), "expand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), w, 0, 3, 3, 4,
- GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 8);
-#endif
-
- /*
- * We only provide the checkboxes for client- and server-side
- * fonts if we have the X11 back end available, because that's the
- * only situation in which more than one class of font is
- * available anyway.
- */
- fs->n_filter_buttons = 0;
-#ifndef NOT_X_WINDOWS
- w = gtk_check_button_new_with_label("Show client-side fonts");
- g_object_set_data(G_OBJECT(w), "user-data",
- GINT_TO_POINTER(FONTFLAG_CLIENTSIDE));
- g_signal_connect(G_OBJECT(w), "toggled",
- G_CALLBACK(unifontsel_button_toggled), fs);
- gtk_widget_show(w);
- fs->filter_buttons[fs->n_filter_buttons++] = w;
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), w, 0, 4, 3, 1);
- g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), w, 0, 3, 4, 5, GTK_FILL, 0, 0, 0);
-#endif
- w = gtk_check_button_new_with_label("Show server-side fonts");
- g_object_set_data(G_OBJECT(w), "user-data",
- GINT_TO_POINTER(FONTFLAG_SERVERSIDE));
- g_signal_connect(G_OBJECT(w), "toggled",
- G_CALLBACK(unifontsel_button_toggled), fs);
- gtk_widget_show(w);
- fs->filter_buttons[fs->n_filter_buttons++] = w;
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), w, 0, 5, 3, 1);
- g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), w, 0, 3, 5, 6, GTK_FILL, 0, 0, 0);
-#endif
- w = gtk_check_button_new_with_label("Show server-side font aliases");
- g_object_set_data(G_OBJECT(w), "user-data",
- GINT_TO_POINTER(FONTFLAG_SERVERALIAS));
- g_signal_connect(G_OBJECT(w), "toggled",
- G_CALLBACK(unifontsel_button_toggled), fs);
- gtk_widget_show(w);
- fs->filter_buttons[fs->n_filter_buttons++] = w;
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), w, 0, 6, 3, 1);
- g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), w, 0, 3, 6, 7, GTK_FILL, 0, 0, 0);
-#endif
-#endif /* NOT_X_WINDOWS */
- w = gtk_check_button_new_with_label("Show non-monospaced fonts");
- g_object_set_data(G_OBJECT(w), "user-data",
- GINT_TO_POINTER(FONTFLAG_NONMONOSPACED));
- g_signal_connect(G_OBJECT(w), "toggled",
- G_CALLBACK(unifontsel_button_toggled), fs);
- gtk_widget_show(w);
- fs->filter_buttons[fs->n_filter_buttons++] = w;
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_grid_attach(GTK_GRID(table), w, 0, 7, 3, 1);
- g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
-#else
- gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0);
-#endif
-
- assert(fs->n_filter_buttons <= lenof(fs->filter_buttons));
- fs->filter_flags = FONTFLAG_CLIENTSIDE | FONTFLAG_SERVERSIDE |
- FONTFLAG_SERVERALIAS;
- unifontsel_set_filter_buttons(fs);
-
- /*
- * Go and find all the font names, and set up our master font
- * list.
- */
- fs->fonts_by_realname = newtree234(fontinfo_realname_compare);
- fs->fonts_by_selorder = newtree234(fontinfo_selorder_compare);
- for (i = 0; i < lenof(unifont_types); i++)
- unifont_types[i]->enum_fonts(GTK_WIDGET(fs->u.window),
- unifontsel_add_entry, fs);
-
- /*
- * And set up the initial font names list.
- */
- unifontsel_setup_familylist(fs);
-
- fs->selsize = fs->intendedsize = 13; /* random default */
- gtk_widget_set_sensitive(fs->u.ok_button, false);
-
- return &fs->u;
-}
-
-void unifontsel_destroy(unifontsel *fontsel)
-{
- unifontsel_internal *fs = container_of(fontsel, unifontsel_internal, u);
- fontinfo *info;
-
-#ifndef NO_BACKING_PIXMAPS
- if (fs->preview_pixmap)
- gdk_pixmap_unref(fs->preview_pixmap);
-#endif
-
- freetree234(fs->fonts_by_selorder);
- while ((info = delpos234(fs->fonts_by_realname, 0)) != NULL)
- sfree(info);
- freetree234(fs->fonts_by_realname);
-
- gtk_widget_destroy(GTK_WIDGET(fs->u.window));
- sfree(fs);
-}
-
-void unifontsel_set_name(unifontsel *fontsel, const char *fontname)
-{
- unifontsel_internal *fs = container_of(fontsel, unifontsel_internal, u);
- int i, start, end, size, flags;
- const char *fontname2 = NULL;
- fontinfo *info;
-
- /*
- * Provide a default if given an empty or null font name.
- */
- if (!fontname || !*fontname)
- fontname = DEFAULT_GTK_FONT;
-
- /*
- * Call the canonify_fontname function.
- */
- fontname = unifont_do_prefix(fontname, &start, &end);
- for (i = start; i < end; i++) {
- fontname2 = unifont_types[i]->canonify_fontname
- (GTK_WIDGET(fs->u.window), fontname, &size, &flags, false);
- if (fontname2)
- break;
- }
- if (i == end)
- return; /* font name not recognised */
-
- /*
- * Now look up the canonified font name in our index.
- */
- {
- struct fontinfo_realname_find f;
- f.realname = fontname2;
- f.flags = flags;
- info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find);
- }
-
- /*
- * If we've found the font, and its size field is either
- * correct or zero (the latter indicating a scalable font),
- * then we're done. Otherwise, try looking up the original
- * font name instead.
- */
- if (!info || (info->size != size && info->size != 0)) {
- struct fontinfo_realname_find f;
- f.realname = fontname;
- f.flags = flags;
-
- info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find);
- if (!info || info->size != size)
- return; /* font name not in our index */
- }
-
- /*
- * Now we've got a fontinfo structure and a font size, so we
- * know everything we need to fill in all the fields in the
- * dialog.
- */
- unifontsel_select_font(fs, info, size, 0, true);
-}
-
-char *unifontsel_get_name(unifontsel *fontsel)
-{
- unifontsel_internal *fs = container_of(fontsel, unifontsel_internal, u);
- char *name;
-
- if (!fs->selected)
- return NULL;
-
- if (fs->selected->size == 0) {
- name = fs->selected->fontclass->scale_fontname
- (GTK_WIDGET(fs->u.window), fs->selected->realname, fs->selsize);
- if (name) {
- char *ret = dupcat(fs->selected->fontclass->prefix, ":", name);
- sfree(name);
- return ret;
- }
- }
-
- return dupcat(fs->selected->fontclass->prefix, ":",
- fs->selected->realname);
-}
-
-#endif /* GTK_CHECK_VERSION(2,0,0) */
diff --git a/unix/gtkfont.h b/unix/gtkfont.h
deleted file mode 100644
index e43a748d..00000000
--- a/unix/gtkfont.h
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Header file for gtkfont.c. Has to be separate from unix.h
- * because it depends on GTK data types, hence can't be included
- * from cross-platform code (which doesn't go near GTK).
- */
-
-#ifndef PUTTY_GTKFONT_H
-#define PUTTY_GTKFONT_H
-
-/*
- * We support two entirely different drawing systems: the old
- * GDK1/GDK2 one which works on server-side X drawables, and the
- * new-style Cairo one. GTK1 only supports GDK drawing; GTK3 only
- * supports Cairo; GTK2 supports both, but deprecates GTK, so we only
- * enable it if we aren't trying on purpose to compile without the
- * deprecated functions.
- *
- * Our different font classes may prefer different drawing systems: X
- * server-side fonts are a lot faster to draw with GDK, but for
- * everything else we prefer Cairo, on general grounds of modernness
- * and also in particular because its matrix-based scaling system
- * gives much nicer results for double-width and double-height text
- * when a scalable font is in use.
- */
-#if !GTK_CHECK_VERSION(3,0,0) && !defined GDK_DISABLE_DEPRECATED
-#define DRAW_TEXT_GDK
-#endif
-#if GTK_CHECK_VERSION(2,8,0)
-#define DRAW_TEXT_CAIRO
-#endif
-
-#if GTK_CHECK_VERSION(3,0,0) || defined GDK_DISABLE_DEPRECATED
-/*
- * Where the facility is available, we prefer to render text on to a
- * persistent server-side pixmap, and redraw windows by simply
- * blitting rectangles of that pixmap into them as needed. This is
- * better for performance since we avoid expensive font rendering
- * calls where possible, and it's particularly good over a non-local X
- * connection because the response to an expose event can now be a
- * very simple rectangle-copy operation rather than a lot of fiddly
- * drawing or bitmap transfer.
- *
- * However, GTK is deprecating the use of server-side pixmaps, so we
- * have to disable this mode under some circumstances.
- */
-#define NO_BACKING_PIXMAPS
-#endif
-
-/*
- * Exports from gtkfont.c.
- */
-typedef struct UnifontVtable UnifontVtable; /* contents internal to
- * gtkfont.c */
-typedef struct unifont {
- const struct UnifontVtable *vt;
- /*
- * `Non-static data members' of the `class', accessible to
- * external code.
- */
-
- /*
- * public_charset is the charset used when the user asks for
- * `Use font encoding'.
- */
- int public_charset;
-
- /*
- * Font dimensions needed by clients.
- */
- int width, height, ascent, descent, strikethrough_y;
-
- /*
- * Indicates whether this font is capable of handling all glyphs
- * (Pango fonts can do this because Pango automatically supplies
- * missing glyphs from other fonts), or whether it would like a
- * fallback font to cope with missing glyphs.
- */
- bool want_fallback;
-
- /*
- * Preferred drawing API to use when this class of font is active.
- * (See the enum below, in unifont_drawctx.)
- */
- int preferred_drawtype;
-} unifont;
-
-/* A default drawtype, for the case where no font exists to make the
- * decision with. */
-#ifdef DRAW_TEXT_CAIRO
-#define DRAW_DEFAULT_CAIRO
-#define DRAWTYPE_DEFAULT DRAWTYPE_CAIRO
-#elif defined DRAW_TEXT_GDK
-#define DRAW_DEFAULT_GDK
-#define DRAWTYPE_DEFAULT DRAWTYPE_GDK
-#else
-#error No drawtype available at all
-#endif
-
-/*
- * Drawing context passed in to unifont_draw_text, which contains
- * everything required to know where and how to draw the requested
- * text.
- */
-typedef struct unifont_drawctx {
- enum {
-#ifdef DRAW_TEXT_GDK
- DRAWTYPE_GDK,
-#endif
-#ifdef DRAW_TEXT_CAIRO
- DRAWTYPE_CAIRO,
-#endif
- DRAWTYPE_NTYPES
- } type;
- union {
-#ifdef DRAW_TEXT_GDK
- struct {
- GdkDrawable *target;
- GdkGC *gc;
- } gdk;
-#endif
-#ifdef DRAW_TEXT_CAIRO
- struct {
- /* Need an actual widget, in order to backtrack to its X
- * screen number when creating server-side pixmaps */
- GtkWidget *widget;
- cairo_t *cr;
- cairo_matrix_t origmatrix;
-#if GTK_CHECK_VERSION(3,22,0)
- GdkWindow *gdkwin;
- GdkDrawingContext *drawctx;
-#endif
- } cairo;
-#endif
- } u;
-} unifont_drawctx;
-
-unifont *unifont_create(GtkWidget *widget, const char *name,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways);
-void unifont_destroy(unifont *font);
-void unifont_draw_text(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth);
-/* Same as unifont_draw_text, but expects 'string' to contain one
- * normal char plus combining chars, and overdraws them all in the
- * same character cell. */
-void unifont_draw_combining(unifont_drawctx *ctx, unifont *font,
- int x, int y, const wchar_t *string, int len,
- bool wide, bool bold, int cellwidth);
-/* Return a name that will select a bigger/smaller font than this one,
- * or NULL if no such name is available. */
-char *unifont_size_increment(unifont *font, int increment);
-
-/*
- * This function behaves exactly like the low-level unifont_create,
- * except that as well as the requested font it also allocates (if
- * necessary) a fallback font for filling in replacement glyphs.
- *
- * Return value is usable with unifont_destroy and unifont_draw_text
- * as if it were an ordinary unifont.
- */
-unifont *multifont_create(GtkWidget *widget, const char *name,
- bool wide, bool bold,
- int shadowoffset, bool shadowalways);
-
-/*
- * Unified font selector dialog. I can't be bothered to do a
- * proper GTK subclassing today, so this will just be an ordinary
- * data structure with some useful members.
- *
- * (Of course, these aren't the only members; this structure is
- * contained within a bigger one which holds data visible only to
- * the implementation.)
- */
-typedef struct unifontsel {
- void *user_data; /* settable by the user */
- GtkWindow *window;
- GtkWidget *ok_button, *cancel_button;
-} unifontsel;
-
-unifontsel *unifontsel_new(const char *wintitle);
-void unifontsel_destroy(unifontsel *fontsel);
-void unifontsel_set_name(unifontsel *fontsel, const char *fontname);
-char *unifontsel_get_name(unifontsel *fontsel);
-
-#endif /* PUTTY_GTKFONT_H */
diff --git a/unix/gtkmain.c b/unix/gtkmain.c
deleted file mode 100644
index ec8f7da4..00000000
--- a/unix/gtkmain.c
+++ /dev/null
@@ -1,673 +0,0 @@
-/*
- * gtkmain.c: the common main-program code between the straight-up
- * Unix PuTTY and pterm, which they do not share with the
- * multi-session gtkapp.c.
- */
-
-#define _GNU_SOURCE
-
-#include <string.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-#include <signal.h>
-#include <stdio.h>
-#include <time.h>
-#include <errno.h>
-#include <locale.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <gtk/gtk.h>
-#if !GTK_CHECK_VERSION(3,0,0)
-#include <gdk/gdkkeysyms.h>
-#endif
-
-#if GTK_CHECK_VERSION(2,0,0)
-#include <gtk/gtkimmodule.h>
-#endif
-
-#define MAY_REFER_TO_GTK_IN_HEADERS
-
-#include "putty.h"
-#include "terminal.h"
-#include "gtkcompat.h"
-#include "gtkfont.h"
-#include "gtkmisc.h"
-
-#ifndef NOT_X_WINDOWS
-#include <gdk/gdkx.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-#include "x11misc.h"
-#endif
-
-static char *progname, **gtkargvstart;
-static int ngtkargs;
-
-static const char *app_name = "pterm";
-
-char *x_get_default(const char *key)
-{
-#ifndef NOT_X_WINDOWS
- Display *disp;
- if ((disp = get_x11_display()) == NULL)
- return NULL;
- return XGetDefault(disp, app_name, key);
-#else
- return NULL;
-#endif
-}
-
-void fork_and_exec_self(int fd_to_close, ...)
-{
- /*
- * Re-execing ourself is not an exact science under Unix. I do
- * the best I can by using /proc/self/exe if available and by
- * assuming argv[0] can be found on $PATH if not.
- *
- * Note that we also have to reconstruct the elements of the
- * original argv which gtk swallowed, since the user wants the
- * new session to appear on the same X display as the old one.
- */
- char **args;
- va_list ap;
- int i, n;
- int pid;
-
- /*
- * Collect the arguments with which to re-exec ourself.
- */
- va_start(ap, fd_to_close);
- n = 2; /* progname and terminating NULL */
- n += ngtkargs;
- while (va_arg(ap, char *) != NULL)
- n++;
- va_end(ap);
-
- args = snewn(n, char *);
- args[0] = progname;
- args[n-1] = NULL;
- for (i = 0; i < ngtkargs; i++)
- args[i+1] = gtkargvstart[i];
-
- i++;
- va_start(ap, fd_to_close);
- while ((args[i++] = va_arg(ap, char *)) != NULL);
- va_end(ap);
-
- assert(i == n);
-
- /*
- * Do the double fork.
- */
- pid = fork();
- if (pid < 0) {
- perror("fork");
- sfree(args);
- return;
- }
-
- if (pid == 0) {
- int pid2 = fork();
- if (pid2 < 0) {
- perror("fork");
- _exit(1);
- } else if (pid2 > 0) {
- /*
- * First child has successfully forked second child. My
- * Work Here Is Done. Note the use of _exit rather than
- * exit: the latter appears to cause destroy messages
- * to be sent to the X server. I suspect gtk uses
- * atexit.
- */
- _exit(0);
- }
-
- /*
- * If we reach here, we are the second child, so we now
- * actually perform the exec.
- */
- if (fd_to_close >= 0)
- close(fd_to_close);
-
- execv("/proc/self/exe", args);
- execvp(progname, args);
- perror("exec");
- _exit(127);
-
- } else {
- int status;
- sfree(args);
- waitpid(pid, &status, 0);
- }
-
-}
-
-void launch_duplicate_session(Conf *conf)
-{
- /*
- * For this feature we must marshal conf and (possibly) pty_argv
- * into a byte stream, create a pipe, and send this byte stream
- * to the child through the pipe.
- */
- int i, ret;
- strbuf *serialised;
- char option[80];
- int pipefd[2];
-
- if (pipe(pipefd) < 0) {
- perror("pipe");
- return;
- }
-
- serialised = strbuf_new();
-
- conf_serialise(BinarySink_UPCAST(serialised), conf);
- if (use_pty_argv && pty_argv)
- for (i = 0; pty_argv[i]; i++)
- put_asciz(serialised, pty_argv[i]);
-
- sprintf(option, "---[%d,%zu]", pipefd[0], serialised->len);
- noncloexec(pipefd[0]);
- fork_and_exec_self(pipefd[1], option, NULL);
- close(pipefd[0]);
-
- i = ret = 0;
- while (i < serialised->len &&
- (ret = write(pipefd[1], serialised->s + i,
- serialised->len - i)) > 0)
- i += ret;
- if (ret < 0)
- perror("write to pipe");
- close(pipefd[1]);
- strbuf_free(serialised);
-}
-
-void launch_new_session(void)
-{
- fork_and_exec_self(-1, NULL);
-}
-
-void launch_saved_session(const char *str)
-{
- fork_and_exec_self(-1, "-load", str, NULL);
-}
-
-int read_dupsession_data(Conf *conf, char *arg)
-{
- int fd, i, ret, size;
- char *data;
- BinarySource src[1];
-
- if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) {
- fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg);
- exit(1);
- }
-
- data = snewn(size, char);
- i = ret = 0;
- while (i < size && (ret = read(fd, data + i, size - i)) > 0)
- i += ret;
- if (ret < 0) {
- perror("read from pipe");
- exit(1);
- } else if (i < size) {
- fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n",
- appname);
- exit(1);
- }
-
- BinarySource_BARE_INIT(src, data, size);
- if (!conf_deserialise(conf, src)) {
- fprintf(stderr, "%s: malformed Duplicate Session data\n", appname);
- exit(1);
- }
- if (use_pty_argv) {
- int pty_argc = 0;
- size_t argv_startpos = src->pos;
-
- while (get_asciz(src), !get_err(src))
- pty_argc++;
-
- src->err = BSE_NO_ERROR;
-
- if (pty_argc > 0) {
- src->pos = argv_startpos;
-
- pty_argv = snewn(pty_argc + 1, char *);
- pty_argv[pty_argc] = NULL;
- for (i = 0; i < pty_argc; i++)
- pty_argv[i] = dupstr(get_asciz(src));
- }
- }
-
- if (get_err(src) || get_avail(src) > 0) {
- fprintf(stderr, "%s: malformed Duplicate Session data\n", appname);
- exit(1);
- }
-
- sfree(data);
- return 0;
-}
-
-static void help(FILE *fp) {
- if(fprintf(fp,
-"pterm option summary:\n"
-"\n"
-" --display DISPLAY Specify X display to use (note '--')\n"
-" -name PREFIX Prefix when looking up resources (default: pterm)\n"
-" -fn FONT Normal text font\n"
-" -fb FONT Bold text font\n"
-" -geometry GEOMETRY Position and size of window (size in characters)\n"
-" -sl LINES Number of lines of scrollback\n"
-" -fg COLOUR, -bg COLOUR Foreground/background colour\n"
-" -bfg COLOUR, -bbg COLOUR Foreground/background bold colour\n"
-" -cfg COLOUR, -bfg COLOUR Foreground/background cursor colour\n"
-" -T TITLE Window title\n"
-" -ut, +ut Do(default) or do not update utmp\n"
-" -ls, +ls Do(default) or do not make shell a login shell\n"
-" -sb, +sb Do(default) or do not display a scrollbar\n"
-" -log PATH, -sessionlog PATH Log all output to a file\n"
-" -nethack Map numeric keypad to hjklyubn direction keys\n"
-" -xrm RESOURCE-STRING Set an X resource\n"
-" -e COMMAND [ARGS...] Execute command (consumes all remaining args)\n"
- ) < 0 || fflush(fp) < 0) {
- perror("output error");
- exit(1);
- }
-}
-
-static void version(FILE *fp) {
- char *buildinfo_text = buildinfo("\n");
- if(fprintf(fp, "%s: %s\n%s\n", appname, ver, buildinfo_text) < 0 ||
- fflush(fp) < 0) {
- perror("output error");
- exit(1);
- }
- sfree(buildinfo_text);
-}
-
-static const char *geometry_string;
-
-void cmdline_error(const char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "%s: ", appname);
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
- exit(1);
-}
-
-void window_setup_error(const char *errmsg)
-{
- fprintf(stderr, "%s: %s\n", appname, errmsg);
- exit(1);
-}
-
-bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf)
-{
- bool err = false;
- char *val;
-
- /*
- * Macros to make argument handling easier.
- *
- * Note that because they need to call `continue', they cannot be
- * contained in the usual do {...} while (0) wrapper to make them
- * syntactically single statements. I use the alternative if (1)
- * {...} else ((void)0).
- */
-#define EXPECTS_ARG if (1) { \
- if (--argc <= 0) { \
- err = true; \
- fprintf(stderr, "%s: %s expects an argument\n", appname, p); \
- continue; \
- } else \
- val = *++argv; \
- } else ((void)0)
-#define SECOND_PASS_ONLY if (1) { \
- if (!do_everything) \
- continue; \
- } else ((void)0)
-
- while (--argc > 0) {
- const char *p = *++argv;
- int ret;
-
- /*
- * Shameless cheating. Debian requires all X terminal
- * emulators to support `-T title'; but
- * cmdline_process_param will eat -T (it means no-pty) and
- * complain that pterm doesn't support it. So, in pterm
- * only, we convert -T into -title.
- */
- if ((cmdline_tooltype & TOOLTYPE_NONNETWORK) &&
- !strcmp(p, "-T"))
- p = "-title";
-
- ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
- do_everything ? 1 : -1, conf);
-
- if (ret == -2) {
- cmdline_error("option \"%s\" requires an argument", p);
- } else if (ret == 2) {
- --argc, ++argv; /* skip next argument */
- continue;
- } else if (ret == 1) {
- continue;
- }
-
- if (!strcmp(p, "-fn") || !strcmp(p, "-font")) {
- FontSpec *fs;
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- fs = fontspec_new(val);
- conf_set_fontspec(conf, CONF_font, fs);
- fontspec_free(fs);
-
- } else if (!strcmp(p, "-fb")) {
- FontSpec *fs;
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- fs = fontspec_new(val);
- conf_set_fontspec(conf, CONF_boldfont, fs);
- fontspec_free(fs);
-
- } else if (!strcmp(p, "-fw")) {
- FontSpec *fs;
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- fs = fontspec_new(val);
- conf_set_fontspec(conf, CONF_widefont, fs);
- fontspec_free(fs);
-
- } else if (!strcmp(p, "-fwb")) {
- FontSpec *fs;
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- fs = fontspec_new(val);
- conf_set_fontspec(conf, CONF_wideboldfont, fs);
- fontspec_free(fs);
-
- } else if (!strcmp(p, "-cs")) {
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- conf_set_str(conf, CONF_line_codepage, val);
-
- } else if (!strcmp(p, "-geometry")) {
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- geometry_string = val;
- } else if (!strcmp(p, "-sl")) {
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- conf_set_int(conf, CONF_savelines, atoi(val));
-
- } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") ||
- !strcmp(p, "-bfg") || !strcmp(p, "-bbg") ||
- !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) {
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
-
- {
-#if GTK_CHECK_VERSION(3,0,0)
- GdkRGBA rgba;
- bool success = gdk_rgba_parse(&rgba, val);
-#else
- GdkColor col;
- bool success = gdk_color_parse(val, &col);
-#endif
-
- if (!success) {
- err = true;
- fprintf(stderr, "%s: unable to parse colour \"%s\"\n",
- appname, val);
- } else {
-#if GTK_CHECK_VERSION(3,0,0)
- int r = rgba.red * 255;
- int g = rgba.green * 255;
- int b = rgba.blue * 255;
-#else
- int r = col.red / 256;
- int g = col.green / 256;
- int b = col.blue / 256;
-#endif
-
- int index;
- index = (!strcmp(p, "-fg") ? 0 :
- !strcmp(p, "-bg") ? 2 :
- !strcmp(p, "-bfg") ? 1 :
- !strcmp(p, "-bbg") ? 3 :
- !strcmp(p, "-cfg") ? 4 :
- !strcmp(p, "-cbg") ? 5 : -1);
- assert(index != -1);
-
- conf_set_int_int(conf, CONF_colours, index*3+0, r);
- conf_set_int_int(conf, CONF_colours, index*3+1, g);
- conf_set_int_int(conf, CONF_colours, index*3+2, b);
- }
- }
-
- } else if (use_pty_argv && !strcmp(p, "-e")) {
- /* This option swallows all further arguments. */
- if (!do_everything)
- break;
-
- if (--argc > 0) {
- int i;
- pty_argv = snewn(argc+1, char *);
- ++argv;
- for (i = 0; i < argc; i++)
- pty_argv[i] = argv[i];
- pty_argv[argc] = NULL;
- break; /* finished command-line processing */
- } else
- err = true, fprintf(stderr, "%s: -e expects an argument\n",
- appname);
-
- } else if (!strcmp(p, "-title")) {
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- conf_set_str(conf, CONF_wintitle, val);
-
- } else if (!strcmp(p, "-log")) {
- Filename *fn;
- EXPECTS_ARG;
- SECOND_PASS_ONLY;
- fn = filename_from_str(val);
- conf_set_filename(conf, CONF_logfilename, fn);
- conf_set_int(conf, CONF_logtype, LGTYP_DEBUG);
- filename_free(fn);
-
- } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) {
- SECOND_PASS_ONLY;
- conf_set_bool(conf, CONF_stamp_utmp, false);
-
- } else if (!strcmp(p, "-ut")) {
- SECOND_PASS_ONLY;
- conf_set_bool(conf, CONF_stamp_utmp, true);
-
- } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) {
- SECOND_PASS_ONLY;
- conf_set_bool(conf, CONF_login_shell, false);
-
- } else if (!strcmp(p, "-ls")) {
- SECOND_PASS_ONLY;
- conf_set_bool(conf, CONF_login_shell, true);
-
- } else if (!strcmp(p, "-nethack")) {
- SECOND_PASS_ONLY;
- conf_set_bool(conf, CONF_nethack_keypad, true);
-
- } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) {
- SECOND_PASS_ONLY;
- conf_set_bool(conf, CONF_scrollbar, false);
-
- } else if (!strcmp(p, "-sb")) {
- SECOND_PASS_ONLY;
- conf_set_bool(conf, CONF_scrollbar, true);
-
- } else if (!strcmp(p, "-name")) {
- EXPECTS_ARG;
- app_name = val;
-
- } else if (!strcmp(p, "-xrm")) {
- EXPECTS_ARG;
- provide_xrm_string(val, appname);
-
- } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) {
- help(stdout);
- exit(0);
-
- } else if(!strcmp(p, "-version") || !strcmp(p, "--version")) {
- version(stdout);
- exit(0);
-
- } else if (!strcmp(p, "-pgpfp")) {
- pgp_fingerprints();
- exit(1);
-
- } else if (p[0] != '-') {
- /* Non-option arguments not handled by cmdline.c are errors. */
- if (do_everything) {
- err = true;
- fprintf(stderr, "%s: unexpected non-option argument '%s'\n",
- appname, p);
- }
-
- } else {
- err = true;
- fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p);
- }
- }
-
- return err;
-}
-
-GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend)
-{
- return gtk_window_new(GTK_WINDOW_TOPLEVEL);
-}
-
-const bool buildinfo_gtk_relevant = true;
-
-struct post_initial_config_box_ctx {
- Conf *conf;
- const char *geometry_string;
-};
-
-static void post_initial_config_box(void *vctx, int result)
-{
- struct post_initial_config_box_ctx ctx =
- *(struct post_initial_config_box_ctx *)vctx;
- sfree(vctx);
-
- if (result > 0) {
- new_session_window(ctx.conf, ctx.geometry_string);
- } else {
- /* In this main(), which only runs one session in total, a
- * negative result from the initial config box means we simply
- * terminate. */
- conf_free(ctx.conf);
- gtk_main_quit();
- }
-}
-
-void session_window_closed(void)
-{
- gtk_main_quit();
-}
-
-int main(int argc, char **argv)
-{
- Conf *conf;
- bool need_config_box;
-
- setlocale(LC_CTYPE, "");
-
- /* Call the function in ux{putty,pterm}.c to do app-type
- * specific setup */
- setup(true); /* true means we are a one-session process */
-
- progname = argv[0];
-
- /*
- * Copy the original argv before letting gtk_init fiddle with
- * it. It will be required later.
- */
- {
- int i, oldargc;
- gtkargvstart = snewn(argc-1, char *);
- for (i = 1; i < argc; i++)
- gtkargvstart[i-1] = dupstr(argv[i]);
- oldargc = argc;
- gtk_init(&argc, &argv);
- ngtkargs = oldargc - argc;
- }
-
- conf = conf_new();
-
- gtkcomm_setup();
-
- /*
- * Block SIGPIPE: if we attempt Duplicate Session or similar and
- * it falls over in some way, we certainly don't want SIGPIPE
- * terminating the main pterm/PuTTY. However, we'll have to
- * unblock it again when pterm forks.
- */
- block_signal(SIGPIPE, true);
-
- if (argc > 1 && !strncmp(argv[1], "---", 3)) {
- read_dupsession_data(conf, argv[1]);
- /* Splatter this argument so it doesn't clutter a ps listing */
- smemclr(argv[1], strlen(argv[1]));
-
- assert(!dup_check_launchable || conf_launchable(conf));
- need_config_box = false;
- } else {
- if (do_cmdline(argc, argv, false, conf))
- exit(1); /* pre-defaults pass to get -class */
- do_defaults(NULL, conf);
- if (do_cmdline(argc, argv, true, conf))
- exit(1); /* post-defaults, do everything */
-
- cmdline_run_saved(conf);
-
- if (cmdline_tooltype & TOOLTYPE_HOST_ARG)
- need_config_box = !cmdline_host_ok(conf);
- else
- need_config_box = false;
- }
-
- if (need_config_box) {
- /*
- * Put up the initial config box, which will pass the provided
- * parameters (with conf updated) to new_session_window() when
- * (if) the user selects Open. Or it might close without
- * creating a session window, if the user selects Cancel. Or
- * it might just create the session window immediately if this
- * is a pterm-style app which doesn't have an initial config
- * box at all.
- */
- struct post_initial_config_box_ctx *ctx =
- snew(struct post_initial_config_box_ctx);
- ctx->conf = conf;
- ctx->geometry_string = geometry_string;
- initial_config_box(conf, post_initial_config_box, ctx);
- } else {
- /*
- * No initial config needed; just create the session window
- * now.
- */
- new_session_window(conf, geometry_string);
- }
-
- gtk_main();
-
- return 0;
-}
diff --git a/unix/gtkmisc.c b/unix/gtkmisc.c
deleted file mode 100644
index c0c9682a..00000000
--- a/unix/gtkmisc.c
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Miscellaneous GTK helper functions.
- */
-
-#include <assert.h>
-#include <stdarg.h>
-#include <ctype.h>
-#include <time.h>
-
-#include <gtk/gtk.h>
-#if !GTK_CHECK_VERSION(3,0,0)
-#include <gdk/gdkkeysyms.h>
-#endif
-
-#include "putty.h"
-#include "gtkcompat.h"
-
-#ifndef NOT_X_WINDOWS
-#include <gdk/gdkx.h>
-#include <X11/Xlib.h>
-#endif
-
-void get_label_text_dimensions(const char *text, int *width, int *height)
-{
- /*
- * Determine the dimensions of a piece of text in the standard
- * font used in GTK interface elements like labels. We do this by
- * instantiating an actual GtkLabel, and then querying its size.
- *
- * But GTK2 and GTK3 require us to query the size completely
- * differently. I'm sure there ought to be an easier approach than
- * the way I'm doing this in GTK3, too!
- */
- GtkWidget *label = gtk_label_new(text);
-
-#if GTK_CHECK_VERSION(3,0,0)
- PangoLayout *layout = gtk_label_get_layout(GTK_LABEL(label));
- PangoRectangle logrect;
- pango_layout_get_extents(layout, NULL, &logrect);
- if (width)
- *width = logrect.width / PANGO_SCALE;
- if (height)
- *height = logrect.height / PANGO_SCALE;
-#else
- GtkRequisition req;
- gtk_widget_size_request(label, &req);
- if (width)
- *width = req.width;
- if (height)
- *height = req.height;
-#endif
-
- g_object_ref_sink(G_OBJECT(label));
-#if GTK_CHECK_VERSION(2,10,0)
- g_object_unref(label);
-#endif
-}
-
-int string_width(const char *text)
-{
- int ret;
- get_label_text_dimensions(text, &ret, NULL);
- return ret;
-}
-
-void align_label_left(GtkLabel *label)
-{
-#if GTK_CHECK_VERSION(3,16,0)
- gtk_label_set_xalign(label, 0.0);
-#elif GTK_CHECK_VERSION(3,14,0)
- gtk_widget_set_halign(GTK_WIDGET(label), GTK_ALIGN_START);
-#else
- gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-#endif
-}
-
-/* ----------------------------------------------------------------------
- * Functions to arrange controls in a basically dialog-like window.
- *
- * The best method for doing this has varied wildly with versions of
- * GTK, hence the set of wrapper functions here.
- *
- * In GTK 1, a GtkDialog has an 'action_area' at the bottom, which is
- * a GtkHBox which stretches to cover the full width of the dialog. So
- * we can either add buttons or other widgets to that box directly, or
- * alternatively we can fill the hbox with some layout class of our
- * own such as a Columns widget.
- *
- * In GTK 2, the action area has become a GtkHButtonBox, and its
- * layout behaviour seems to be different and not what we want. So
- * instead we abandon the dialog's action area completely: we
- * gtk_widget_hide() it in the below code, and we also call
- * gtk_dialog_set_has_separator() to remove the separator above it. We
- * then insert our own action area into the end of the dialog's main
- * vbox, and add our own separator above that.
- *
- * In GTK 3, we typically don't even want to use GtkDialog at all,
- * because GTK 3 has become a lot more restrictive about what you can
- * sensibly use GtkDialog for - it deprecates direct access to the
- * action area in favour of making you provide nothing but
- * dialog-ending buttons in the form of (text, response code) pairs,
- * so you can't put any other kind of control in there, or fiddle with
- * alignment and positioning, or even have a button that _doesn't_ end
- * the dialog (e.g. 'View Licence' in our About box). So instead of
- * GtkDialog, we use a straight-up GtkWindow and have it contain a
- * vbox as its (unique) child widget; and we implement the action area
- * by adding a separator and another widget at the bottom of that
- * vbox.
- */
-
-GtkWidget *our_dialog_new(void)
-{
-#if GTK_CHECK_VERSION(3,0,0)
- /*
- * See comment in our_dialog_set_action_area(): in GTK 3, we use
- * GtkWindow in place of GtkDialog for most purposes.
- */
- GtkWidget *w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
- GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
- gtk_container_add(GTK_CONTAINER(w), vbox);
- gtk_widget_show(vbox);
- return w;
-#else
- return gtk_dialog_new();
-#endif
-}
-
-void our_dialog_set_action_area(GtkWindow *dlg, GtkWidget *w)
-{
-#if !GTK_CHECK_VERSION(2,0,0)
-
- gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area),
- w, true, true, 0);
-
-#elif !GTK_CHECK_VERSION(3,0,0)
-
- GtkWidget *align;
- align = gtk_alignment_new(0, 0, 1, 1);
- gtk_container_add(GTK_CONTAINER(align), w);
- /*
- * The purpose of this GtkAlignment is to provide padding
- * around the buttons. The padding we use is twice the padding
- * used in our GtkColumns, because we nest two GtkColumns most
- * of the time (one separating the tree view from the main
- * controls, and another for the main controls themselves).
- */
-#if GTK_CHECK_VERSION(2,4,0)
- gtk_alignment_set_padding(GTK_ALIGNMENT(align), 8, 8, 8, 8);
-#endif
- gtk_widget_show(align);
- gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))),
- align, false, true, 0);
-
- w = gtk_hseparator_new();
- gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))),
- w, false, true, 0);
- gtk_widget_show(w);
- gtk_widget_hide(gtk_dialog_get_action_area(GTK_DIALOG(dlg)));
- g_object_set(G_OBJECT(dlg), "has-separator", true, (const char *)NULL);
-
-#else /* GTK 3 */
-
- /* GtkWindow is a GtkBin, hence contains exactly one child, which
- * here we always expect to be a vbox */
- GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(dlg)));
- GtkWidget *sep;
-
- g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL);
- gtk_box_pack_end(vbox, w, false, true, 0);
-
- sep = gtk_hseparator_new();
- gtk_box_pack_end(vbox, sep, false, true, 0);
- gtk_widget_show(sep);
-
-#endif
-}
-
-GtkBox *our_dialog_make_action_hbox(GtkWindow *dlg)
-{
-#if GTK_CHECK_VERSION(3,0,0)
- GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
- our_dialog_set_action_area(dlg, hbox);
- g_object_set(G_OBJECT(hbox), "margin", 0, (const char *)NULL);
- g_object_set(G_OBJECT(hbox), "spacing", 8, (const char *)NULL);
- gtk_widget_show(hbox);
- return GTK_BOX(hbox);
-#else /* not GTK 3 */
- return GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(dlg)));
-#endif
-}
-
-void our_dialog_add_to_content_area(GtkWindow *dlg, GtkWidget *w,
- bool expand, bool fill, guint padding)
-{
-#if GTK_CHECK_VERSION(3,0,0)
- /* GtkWindow is a GtkBin, hence contains exactly one child, which
- * here we always expect to be a vbox */
- GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(dlg)));
-
- gtk_box_pack_start(vbox, w, expand, fill, padding);
-#else
- gtk_box_pack_start
- (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))),
- w, expand, fill, padding);
-#endif
-}
-
-char *buildinfo_gtk_version(void)
-{
- return dupprintf("%d.%d.%d",
- GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION);
-}
-
-#ifndef NOT_X_WINDOWS
-Display *get_x11_display(void)
-{
-#if GTK_CHECK_VERSION(3,0,0)
- if (!GDK_IS_X11_DISPLAY(gdk_display_get_default()))
- return NULL;
-#endif
- return GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
-}
-#endif
diff --git a/unix/gtkwin.c b/unix/gtkwin.c
deleted file mode 100644
index 61e77187..00000000
--- a/unix/gtkwin.c
+++ /dev/null
@@ -1,5431 +0,0 @@
-/*
- * gtkwin.c: the main code that runs a PuTTY terminal emulator and
- * backend in a GTK window.
- */
-
-#define _GNU_SOURCE
-
-#include <string.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-#include <signal.h>
-#include <stdio.h>
-#include <time.h>
-#include <errno.h>
-#include <locale.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <gtk/gtk.h>
-#if !GTK_CHECK_VERSION(3,0,0)
-#include <gdk/gdkkeysyms.h>
-#endif
-
-#if GTK_CHECK_VERSION(2,0,0)
-#include <gtk/gtkimmodule.h>
-#endif
-
-#define MAY_REFER_TO_GTK_IN_HEADERS
-
-#include "putty.h"
-#include "terminal.h"
-#include "gtkcompat.h"
-#include "gtkfont.h"
-#include "gtkmisc.h"
-
-#ifndef NOT_X_WINDOWS
-#include <gdk/gdkx.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-#endif
-
-#include "x11misc.h"
-
-GdkAtom compound_text_atom, utf8_string_atom;
-static GdkAtom clipboard_atom
-#if GTK_CHECK_VERSION(2,0,0) /* GTK1 will have to fill this in at startup */
- = GDK_SELECTION_CLIPBOARD
-#endif
- ;
-
-#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
-/*
- * Because calling gtk_clipboard_set_with_data triggers a call to the
- * clipboard_clear function from the last time, we need to arrange a
- * way to distinguish a real call to clipboard_clear for the _new_
- * instance of the clipboard data from the leftover call for the
- * outgoing one. We do this by setting the user data field in our
- * gtk_clipboard_set_with_data() call, instead of the obvious pointer
- * to 'inst', to one of these.
- */
-struct clipboard_data_instance {
- char *pasteout_data_utf8;
- int pasteout_data_utf8_len;
- struct clipboard_state *state;
- struct clipboard_data_instance *next, *prev;
-};
-#endif
-
-struct clipboard_state {
- GtkFrontend *inst;
- int clipboard;
- GdkAtom atom;
-#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
- GtkClipboard *gtkclipboard;
- struct clipboard_data_instance *current_cdi;
-#else
- char *pasteout_data, *pasteout_data_ctext, *pasteout_data_utf8;
- int pasteout_data_len, pasteout_data_ctext_len, pasteout_data_utf8_len;
-#endif
-};
-
-typedef struct XpmHolder XpmHolder; /* only used for GTK 1 */
-
-struct GtkFrontend {
- GtkWidget *window, *area, *sbar;
- gboolean sbar_visible;
- gboolean drawing_area_got_size, drawing_area_realised;
- gboolean drawing_area_setup_needed;
- GtkBox *hbox;
- GtkAdjustment *sbar_adjust;
- GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2,
- *restartitem;
- GtkWidget *sessionsmenu;
-#ifndef NOT_X_WINDOWS
- Display *disp;
-#endif
-#ifndef NO_BACKING_PIXMAPS
- /*
- * Server-side pixmap which we use to cache the terminal window's
- * contents. When we draw text in the terminal, we draw it to this
- * pixmap first, and then blit from there to the actual window;
- * this way, X expose events can be handled with an absolute
- * minimum of network traffic, by just sending a command to
- * re-blit an appropriate rectangle from this pixmap.
- */
- GdkPixmap *pixmap;
-#endif
-#ifdef DRAW_TEXT_CAIRO
- /*
- * If we're drawing using Cairo, we cache the same image on the
- * client side in a Cairo surface.
- *
- * In GTK2+Cairo, this happens _as well_ as having the server-side
- * pixmap cache above; in GTK3+Cairo, server-side pixmaps are
- * deprecated, so we _just_ have this client-side cache. In the
- * latter case that means we have to transmit a big wodge of
- * bitmap data over the X connection on every expose event; but
- * GTK3 apparently deliberately provides no way to avoid that
- * inefficiency, and at least this way we don't _also_ have to
- * redo any font rendering just because the window was temporarily
- * covered.
- */
- cairo_surface_t *surface;
-#endif
- int backing_w, backing_h;
-#if GTK_CHECK_VERSION(2,0,0)
- GtkIMContext *imc;
-#endif
- unifont *fonts[4]; /* normal, bold, wide, widebold */
- int xpos, ypos, gravity;
- bool gotpos;
- GdkCursor *rawcursor, *textcursor, *blankcursor, *waitcursor, *currcursor;
- GdkColor cols[OSC4_NCOLOURS]; /* indexed by xterm colour indices */
-#if !GTK_CHECK_VERSION(3,0,0)
- GdkColormap *colmap;
-#endif
- bool direct_to_font;
- struct clipboard_state clipstates[N_CLIPBOARDS];
-#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
- /* Remember all clipboard_data_instance structures currently
- * associated with this GtkFrontend, in case they're still around
- * when it gets destroyed */
- struct clipboard_data_instance cdi_headtail;
-#endif
- int clipboard_ctrlshiftins, clipboard_ctrlshiftcv;
- int font_width, font_height;
- int width, height, scale;
- bool ignore_sbar;
- bool mouseptr_visible;
- BusyStatus busy_status;
- int alt_keycode;
- int alt_digits;
- char *wintitle;
- char *icontitle;
- int master_fd, master_func_id;
- Ldisc *ldisc;
- Backend *backend;
- Terminal *term;
- LogContext *logctx;
- bool exited;
- struct unicode_data ucsdata;
- Conf *conf;
- eventlog_stuff *eventlogstuff;
- guint32 input_event_time; /* Timestamp of the most recent input event. */
- GtkWidget *dialogs[DIALOG_SLOT_LIMIT];
-#if GTK_CHECK_VERSION(3,4,0)
- gdouble cumulative_scroll;
-#endif
- /* Cached things out of conf that we refer to a lot */
- int bold_style;
- int window_border;
- int cursor_type;
- int drawtype;
- int meta_mod_mask;
-#ifdef OSX_META_KEY_CONFIG
- int system_mod_mask;
-#endif
- bool send_raw_mouse;
- bool pointer_indicates_raw_mouse;
- unifont_drawctx uctx;
-#if GTK_CHECK_VERSION(2,0,0)
- GdkPixbuf *trust_sigil_pb;
-#else
- GdkPixmap *trust_sigil_pm;
-#endif
- int trust_sigil_w, trust_sigil_h;
-
- Seat seat;
- TermWin termwin;
- LogPolicy logpolicy;
-};
-
-static void cache_conf_values(GtkFrontend *inst)
-{
- inst->bold_style = conf_get_int(inst->conf, CONF_bold_style);
- inst->window_border = conf_get_int(inst->conf, CONF_window_border);
- inst->cursor_type = conf_get_int(inst->conf, CONF_cursor_type);
-#ifdef OSX_META_KEY_CONFIG
- inst->meta_mod_mask = 0;
- if (conf_get_bool(inst->conf, CONF_osx_option_meta))
- inst->meta_mod_mask |= GDK_MOD1_MASK;
- if (conf_get_bool(inst->conf, CONF_osx_command_meta))
- inst->meta_mod_mask |= GDK_MOD2_MASK;
- inst->system_mod_mask = GDK_MOD2_MASK & ~inst->meta_mod_mask;
-#else
- inst->meta_mod_mask = GDK_MOD1_MASK;
-#endif
-}
-
-static void start_backend(GtkFrontend *inst);
-static void exit_callback(void *vinst);
-static void destroy_inst_connection(GtkFrontend *inst);
-static void delete_inst(GtkFrontend *inst);
-
-static void post_fatal_message_box_toplevel(void *vctx)
-{
- GtkFrontend *inst = (GtkFrontend *)vctx;
- gtk_widget_destroy(inst->window);
-}
-
-static void post_fatal_message_box(void *vctx, int result)
-{
- GtkFrontend *inst = (GtkFrontend *)vctx;
- unregister_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL);
- queue_toplevel_callback(post_fatal_message_box_toplevel, inst);
-}
-
-static void common_connfatal_message_box(
- GtkFrontend *inst, const char *msg, post_dialog_fn_t postfn)
-{
- char *title = dupcat(appname, " Fatal Error");
- GtkWidget *dialog = create_message_box(
- inst->window, title, msg,
- string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"),
- false, &buttons_ok, postfn, inst);
- register_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL, dialog);
- sfree(title);
-}
-
-void fatal_message_box(GtkFrontend *inst, const char *msg)
-{
- common_connfatal_message_box(inst, msg, post_fatal_message_box);
-}
-
-static void connection_fatal_callback(void *vctx)
-{
- GtkFrontend *inst = (GtkFrontend *)vctx;
- destroy_inst_connection(inst);
-}
-
-static void post_nonfatal_message_box(void *vctx, int result)
-{
- GtkFrontend *inst = (GtkFrontend *)vctx;
- unregister_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL);
-}
-
-static void gtk_seat_connection_fatal(Seat *seat, const char *msg)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- if (conf_get_int(inst->conf, CONF_close_on_exit) == FORCE_ON) {
- fatal_message_box(inst, msg);
- } else {
- common_connfatal_message_box(inst, msg, post_nonfatal_message_box);
- }
-
- inst->exited = true; /* suppress normal exit handling */
- queue_toplevel_callback(connection_fatal_callback, inst);
-}
-
-/*
- * Default settings that are specific to pterm.
- */
-FontSpec *platform_default_fontspec(const char *name)
-{
- if (!strcmp(name, "Font"))
- return fontspec_new(DEFAULT_GTK_FONT);
- else
- return fontspec_new("");
-}
-
-Filename *platform_default_filename(const char *name)
-{
- if (!strcmp(name, "LogFileName"))
- return filename_from_str("putty.log");
- else
- return filename_from_str("");
-}
-
-char *platform_default_s(const char *name)
-{
- if (!strcmp(name, "SerialLine"))
- return dupstr("/dev/ttyS0");
- return NULL;
-}
-
-bool platform_default_b(const char *name, bool def)
-{
- if (!strcmp(name, "WinNameAlways")) {
- /* X natively supports icon titles, so use 'em by default */
- return false;
- }
- return def;
-}
-
-int platform_default_i(const char *name, int def)
-{
- if (!strcmp(name, "CloseOnExit"))
- return 2; /* maps to FORCE_ON after painful rearrangement :-( */
- return def;
-}
-
-static char *gtk_seat_get_ttymode(Seat *seat, const char *mode)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- return term_get_ttymode(inst->term, mode);
-}
-
-static size_t gtk_seat_output(Seat *seat, bool is_stderr,
- const void *data, size_t len)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- return term_data(inst->term, is_stderr, data, len);
-}
-
-static bool gtk_seat_eof(Seat *seat)
-{
- /* GtkFrontend *inst = container_of(seat, GtkFrontend, seat); */
- return true; /* do respond to incoming EOF with outgoing */
-}
-
-static int gtk_seat_get_userpass_input(Seat *seat, prompts_t *p,
- bufchain *input)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- int ret;
- ret = cmdline_get_passwd_input(p);
- if (ret == -1)
- ret = term_get_userpass_input(inst->term, p, input);
- return ret;
-}
-
-static bool gtk_seat_is_utf8(Seat *seat)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- return inst->ucsdata.line_codepage == CS_UTF8;
-}
-
-static void get_window_pixel_size(GtkFrontend *inst, int *w, int *h)
-{
- /*
- * I assume that when the GTK version of this call is available
- * we should use it. Not sure how it differs from the GDK one,
- * though.
- */
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_window_get_size(GTK_WINDOW(inst->window), w, h);
-#else
- gdk_window_get_size(gtk_widget_get_window(inst->window), w, h);
-#endif
-}
-
-static bool gtk_seat_get_window_pixel_size(Seat *seat, int *w, int *h)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- get_window_pixel_size(inst, w, h);
- return true;
-}
-
-StripCtrlChars *gtk_seat_stripctrl_new(
- Seat *seat, BinarySink *bs_out, SeatInteractionContext sic)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- return stripctrl_new_term(bs_out, false, 0, inst->term);
-}
-
-static void gtk_seat_notify_remote_exit(Seat *seat);
-static void gtk_seat_update_specials_menu(Seat *seat);
-static void gtk_seat_set_busy_status(Seat *seat, BusyStatus status);
-static const char *gtk_seat_get_x_display(Seat *seat);
-#ifndef NOT_X_WINDOWS
-static bool gtk_seat_get_windowid(Seat *seat, long *id);
-#endif
-static bool gtk_seat_set_trust_status(Seat *seat, bool trusted);
-static bool gtk_seat_get_cursor_position(Seat *seat, int *x, int *y);
-
-static const SeatVtable gtk_seat_vt = {
- .output = gtk_seat_output,
- .eof = gtk_seat_eof,
- .get_userpass_input = gtk_seat_get_userpass_input,
- .notify_remote_exit = gtk_seat_notify_remote_exit,
- .connection_fatal = gtk_seat_connection_fatal,
- .update_specials_menu = gtk_seat_update_specials_menu,
- .get_ttymode = gtk_seat_get_ttymode,
- .set_busy_status = gtk_seat_set_busy_status,
- .verify_ssh_host_key = gtk_seat_verify_ssh_host_key,
- .confirm_weak_crypto_primitive = gtk_seat_confirm_weak_crypto_primitive,
- .confirm_weak_cached_hostkey = gtk_seat_confirm_weak_cached_hostkey,
- .is_utf8 = gtk_seat_is_utf8,
- .echoedit_update = nullseat_echoedit_update,
- .get_x_display = gtk_seat_get_x_display,
-#ifdef NOT_X_WINDOWS
- .get_windowid = nullseat_get_windowid,
-#else
- .get_windowid = gtk_seat_get_windowid,
-#endif
- .get_window_pixel_size = gtk_seat_get_window_pixel_size,
- .stripctrl_new = gtk_seat_stripctrl_new,
- .set_trust_status = gtk_seat_set_trust_status,
- .verbose = nullseat_verbose_yes,
- .interactive = nullseat_interactive_yes,
- .get_cursor_position = gtk_seat_get_cursor_position,
-};
-
-static void gtk_eventlog(LogPolicy *lp, const char *string)
-{
- GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy);
- logevent_dlg(inst->eventlogstuff, string);
-}
-
-static int gtk_askappend(LogPolicy *lp, Filename *filename,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy);
- return gtkdlg_askappend(&inst->seat, filename, callback, ctx);
-}
-
-static void gtk_logging_error(LogPolicy *lp, const char *event)
-{
- GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy);
-
- /* Send 'can't open log file' errors to the terminal window.
- * (Marked as stderr, although terminal.c won't care.) */
- seat_stderr_pl(&inst->seat, ptrlen_from_asciz(event));
- seat_stderr_pl(&inst->seat, PTRLEN_LITERAL("\r\n"));
-}
-
-static const LogPolicyVtable gtk_logpolicy_vt = {
- .eventlog = gtk_eventlog,
- .askappend = gtk_askappend,
- .logging_error = gtk_logging_error,
- .verbose = null_lp_verbose_yes,
-};
-
-/*
- * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
- * into a cooked one (SELECT, EXTEND, PASTE).
- *
- * In Unix, this is not configurable; the X button arrangement is
- * rock-solid across all applications, everyone has a three-button
- * mouse or a means of faking it, and there is no need to switch
- * buttons around at all.
- */
-static Mouse_Button translate_button(Mouse_Button button)
-{
- if (button == MBT_LEFT)
- return MBT_SELECT;
- if (button == MBT_MIDDLE)
- return MBT_PASTE;
- if (button == MBT_RIGHT)
- return MBT_EXTEND;
- return 0; /* shouldn't happen */
-}
-
-/*
- * Return the top-level GtkWindow associated with a particular
- * front end instance.
- */
-GtkWidget *gtk_seat_get_window(Seat *seat)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- return inst->window;
-}
-
-/*
- * Set and clear a pointer to a dialog box created as a result of the
- * network code wanting to ask an asynchronous user question (e.g.
- * 'what about this dodgy host key, then?').
- */
-void register_dialog(Seat *seat, enum DialogSlot slot, GtkWidget *dialog)
-{
- GtkFrontend *inst;
- assert(seat->vt == &gtk_seat_vt);
- inst = container_of(seat, GtkFrontend, seat);
- assert(slot < DIALOG_SLOT_LIMIT);
- assert(!inst->dialogs[slot]);
- inst->dialogs[slot] = dialog;
-}
-void unregister_dialog(Seat *seat, enum DialogSlot slot)
-{
- GtkFrontend *inst;
- assert(seat->vt == &gtk_seat_vt);
- inst = container_of(seat, GtkFrontend, seat);
- assert(slot < DIALOG_SLOT_LIMIT);
- assert(inst->dialogs[slot]);
- inst->dialogs[slot] = NULL;
-}
-
-/*
- * Minimise or restore the window in response to a server-side
- * request.
- */
-static void gtkwin_set_minimised(TermWin *tw, bool minimised)
-{
- /*
- * GTK 1.2 doesn't know how to do this.
- */
-#if GTK_CHECK_VERSION(2,0,0)
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- if (minimised)
- gtk_window_iconify(GTK_WINDOW(inst->window));
- else
- gtk_window_deiconify(GTK_WINDOW(inst->window));
-#endif
-}
-
-/*
- * Move the window in response to a server-side request.
- */
-static void gtkwin_move(TermWin *tw, int x, int y)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- /*
- * I assume that when the GTK version of this call is available
- * we should use it. Not sure how it differs from the GDK one,
- * though.
- */
-#if GTK_CHECK_VERSION(2,0,0)
- /* in case we reset this at startup due to a geometry string */
- gtk_window_set_gravity(GTK_WINDOW(inst->window), GDK_GRAVITY_NORTH_EAST);
- gtk_window_move(GTK_WINDOW(inst->window), x, y);
-#else
- gdk_window_move(gtk_widget_get_window(inst->window), x, y);
-#endif
-}
-
-/*
- * Move the window to the top or bottom of the z-order in response
- * to a server-side request.
- */
-static void gtkwin_set_zorder(TermWin *tw, bool top)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- if (top)
- gdk_window_raise(gtk_widget_get_window(inst->window));
- else
- gdk_window_lower(gtk_widget_get_window(inst->window));
-}
-
-/*
- * Refresh the window in response to a server-side request.
- */
-static void gtkwin_refresh(TermWin *tw)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- term_invalidate(inst->term);
-}
-
-/*
- * Maximise or restore the window in response to a server-side
- * request.
- */
-static void gtkwin_set_maximised(TermWin *tw, bool maximised)
-{
- /*
- * GTK 1.2 doesn't know how to do this.
- */
-#if GTK_CHECK_VERSION(2,0,0)
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- if (maximised)
- gtk_window_maximize(GTK_WINDOW(inst->window));
- else
- gtk_window_unmaximize(GTK_WINDOW(inst->window));
-#endif
-}
-
-/*
- * Find out whether a dialog box already exists for this window in a
- * particular DialogSlot. If it does, uniconify it (if we can) and
- * raise it, so that the user realises they've already been asked this
- * question.
- */
-static bool find_and_raise_dialog(GtkFrontend *inst, enum DialogSlot slot)
-{
- GtkWidget *dialog = inst->dialogs[slot];
- if (!dialog)
- return false;
-
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_window_deiconify(GTK_WINDOW(dialog));
-#endif
- gdk_window_raise(gtk_widget_get_window(dialog));
- return true;
-}
-
-static void warn_on_close_callback(void *vctx, int result)
-{
- GtkFrontend *inst = (GtkFrontend *)vctx;
- unregister_dialog(&inst->seat, DIALOG_SLOT_WARN_ON_CLOSE);
- if (result)
- gtk_widget_destroy(inst->window);
-}
-
-/*
- * Handle the 'delete window' event (e.g. user clicking the WM close
- * button). The return value false means the window should close, and
- * true means it shouldn't.
- *
- * (That's counterintuitive, but really, in GTK terms, true means 'I
- * have done everything necessary to handle this event, so the default
- * handler need not do anything', i.e. 'suppress default handler',
- * i.e. 'do not close the window'.)
- */
-gint delete_window(GtkWidget *widget, GdkEvent *event, GtkFrontend *inst)
-{
- if (!inst->exited && conf_get_bool(inst->conf, CONF_warn_on_close)) {
- /*
- * We're not going to exit right now. We must put up a
- * warn-on-close dialog, unless one already exists, in which
- * case we'll just re-emphasise that one.
- */
- if (!find_and_raise_dialog(inst, DIALOG_SLOT_WARN_ON_CLOSE)) {
- char *title = dupcat(appname, " Exit Confirmation");
- char *msg, *additional = NULL;
- if (inst->backend && inst->backend->vt->close_warn_text) {
- additional = inst->backend->vt->close_warn_text(inst->backend);
- }
- msg = dupprintf("Are you sure you want to close this session?%s%s",
- additional ? "\n" : "",
- additional ? additional : "");
- GtkWidget *dialog = create_message_box(
- inst->window, title, msg,
- string_width("Most of the width of the above text"),
- false, &buttons_yn, warn_on_close_callback, inst);
- register_dialog(&inst->seat, DIALOG_SLOT_WARN_ON_CLOSE, dialog);
- sfree(title);
- sfree(msg);
- sfree(additional);
- }
- return true;
- }
- return false;
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-static void window_state_event(GtkWidget *widget, GdkEventWindowState *event,
- gpointer user_data)
-{
- GtkFrontend *inst = (GtkFrontend *)user_data;
- term_notify_minimised(
- inst->term, event->new_window_state & GDK_WINDOW_STATE_ICONIFIED);
-}
-#endif
-
-static void update_mouseptr(GtkFrontend *inst)
-{
- switch (inst->busy_status) {
- case BUSY_NOT:
- if (!inst->mouseptr_visible) {
- gdk_window_set_cursor(gtk_widget_get_window(inst->area),
- inst->blankcursor);
- } else if (inst->pointer_indicates_raw_mouse) {
- gdk_window_set_cursor(gtk_widget_get_window(inst->area),
- inst->rawcursor);
- } else {
- gdk_window_set_cursor(gtk_widget_get_window(inst->area),
- inst->textcursor);
- }
- break;
- case BUSY_WAITING: /* XXX can we do better? */
- case BUSY_CPU:
- /* We always display these cursors. */
- gdk_window_set_cursor(gtk_widget_get_window(inst->area),
- inst->waitcursor);
- break;
- default:
- unreachable("Bad busy_status");
- }
-}
-
-static void show_mouseptr(GtkFrontend *inst, bool show)
-{
- if (!conf_get_bool(inst->conf, CONF_hide_mouseptr))
- show = true;
- inst->mouseptr_visible = show;
- update_mouseptr(inst);
-}
-
-static void draw_backing_rect(GtkFrontend *inst);
-
-static void drawing_area_setup(GtkFrontend *inst, int width, int height)
-{
- int w, h, new_scale;
- bool need_size = false;
-
- /*
- * See if the terminal size has changed.
- */
- w = (width - 2*inst->window_border) / inst->font_width;
- h = (height - 2*inst->window_border) / inst->font_height;
- if (w != inst->width || h != inst->height) {
- /*
- * Update conf.
- */
- inst->width = w;
- inst->height = h;
- conf_set_int(inst->conf, CONF_width, inst->width);
- conf_set_int(inst->conf, CONF_height, inst->height);
- /*
- * We'll need to tell terminal.c about the resize below.
- */
- need_size = true;
- /*
- * And we must refresh the window's backing image.
- */
- inst->drawing_area_setup_needed = true;
- }
-
-#if GTK_CHECK_VERSION(3,10,0)
- new_scale = gtk_widget_get_scale_factor(inst->area);
- if (new_scale != inst->scale)
- inst->drawing_area_setup_needed = true;
-#else
- new_scale = 1;
-#endif
-
- int new_backing_w = w * inst->font_width + 2*inst->window_border;
- int new_backing_h = h * inst->font_height + 2*inst->window_border;
- new_backing_w *= new_scale;
- new_backing_h *= new_scale;
-
- if (inst->backing_w != new_backing_w || inst->backing_h != new_backing_h)
- inst->drawing_area_setup_needed = true;
-
- /*
- * This event might be spurious; some GTK setups have been known
- * to call it when nothing at all has changed. Check if we have
- * any reason to proceed.
- */
- if (!inst->drawing_area_setup_needed)
- return;
-
- inst->drawing_area_setup_needed = false;
- inst->scale = new_scale;
- inst->backing_w = new_backing_w;
- inst->backing_h = new_backing_h;
-
-#ifndef NO_BACKING_PIXMAPS
- if (inst->pixmap) {
- gdk_pixmap_unref(inst->pixmap);
- inst->pixmap = NULL;
- }
-
- inst->pixmap = gdk_pixmap_new(gtk_widget_get_window(inst->area),
- inst->backing_w, inst->backing_h, -1);
-#endif
-
-#ifdef DRAW_TEXT_CAIRO
- if (inst->surface) {
- cairo_surface_destroy(inst->surface);
- inst->surface = NULL;
- }
-
- inst->surface = cairo_image_surface_create(
- CAIRO_FORMAT_ARGB32, inst->backing_w, inst->backing_h);
-#endif
-
- draw_backing_rect(inst);
-
- if (need_size && inst->term) {
- term_size(inst->term, h, w, conf_get_int(inst->conf, CONF_savelines));
- }
-
- if (inst->term)
- term_invalidate(inst->term);
-
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_im_context_set_client_window(
- inst->imc, gtk_widget_get_window(inst->area));
-#endif
-}
-
-static void drawing_area_setup_simple(GtkFrontend *inst)
-{
- /*
- * Wrapper on drawing_area_setup which fetches the width and
- * height of the drawing area. We go directly to the inner version
- * in the case where a new size allocation comes in (just in case
- * GTK hasn't installed it in the normal place yet).
- */
-#if GTK_CHECK_VERSION(2,0,0)
- GdkRectangle alloc;
- gtk_widget_get_allocation(inst->area, &alloc);
-#else
- GtkAllocation alloc = inst->area->allocation;
-#endif
- drawing_area_setup(inst, alloc.width, alloc.height);
-}
-
-static void area_realised(GtkWidget *widget, GtkFrontend *inst)
-{
- inst->drawing_area_realised = true;
- if (inst->drawing_area_realised && inst->drawing_area_got_size &&
- inst->drawing_area_setup_needed)
- drawing_area_setup_simple(inst);
-}
-
-static void area_size_allocate(
- GtkWidget *widget, GdkRectangle *alloc, GtkFrontend *inst)
-{
- inst->drawing_area_got_size = true;
- if (inst->drawing_area_realised && inst->drawing_area_got_size)
- drawing_area_setup(inst, alloc->width, alloc->height);
-}
-
-#if GTK_CHECK_VERSION(3,10,0)
-static void area_check_scale(GtkFrontend *inst)
-{
- if (!inst->drawing_area_setup_needed &&
- inst->scale != gtk_widget_get_scale_factor(inst->area)) {
- drawing_area_setup_simple(inst);
- if (inst->term) {
- term_invalidate(inst->term);
- term_update(inst->term);
- }
- }
-}
-#endif
-
-static gboolean window_configured(
- GtkWidget *widget, GdkEventConfigure *event, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- if (inst->term) {
- term_notify_window_pos(inst->term, event->x, event->y);
- term_notify_window_size_pixels(
- inst->term, event->width, event->height);
- }
- return false;
-}
-
-#if GTK_CHECK_VERSION(3,10,0)
-static gboolean area_configured(
- GtkWidget *widget, GdkEventConfigure *event, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- area_check_scale(inst);
- return false;
-}
-#endif
-
-#ifdef DRAW_TEXT_CAIRO
-static void cairo_setup_draw_ctx(GtkFrontend *inst)
-{
- cairo_get_matrix(inst->uctx.u.cairo.cr,
- &inst->uctx.u.cairo.origmatrix);
- cairo_set_line_width(inst->uctx.u.cairo.cr, 1.0);
- cairo_set_line_cap(inst->uctx.u.cairo.cr, CAIRO_LINE_CAP_SQUARE);
- cairo_set_line_join(inst->uctx.u.cairo.cr, CAIRO_LINE_JOIN_MITER);
- /* This antialiasing setting appears to be ignored for Pango
- * font rendering but honoured for stroking and filling paths;
- * I don't quite understand the logic of that, but I won't
- * complain since it's exactly what I happen to want */
- cairo_set_antialias(inst->uctx.u.cairo.cr, CAIRO_ANTIALIAS_NONE);
-}
-#endif
-
-#if GTK_CHECK_VERSION(3,0,0)
-static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
-
-#if GTK_CHECK_VERSION(3,10,0)
- /*
- * This may be the first we hear of the window scale having
- * changed, in which case we must hastily reconstruct our backing
- * surface before we copy the wrong one into the newly resized
- * real window.
- */
- area_check_scale(inst);
-#endif
-
- /*
- * GTK3 window redraw: we always expect Cairo to be enabled, so
- * that inst->surface exists, and pixmaps to be disabled, so that
- * inst->pixmap does not exist. Hence, we just blit from
- * inst->surface to the window.
- */
- if (inst->surface) {
- GdkRectangle dirtyrect;
- cairo_surface_t *target_surface;
- double orig_sx, orig_sy;
- cairo_matrix_t m;
-
- /*
- * Furtle around in the Cairo setup to force the device scale
- * back to 1, so that when we blit a collection of pixels from
- * our backing surface into the window, they really are
- * _pixels_ and not some confusing antialiased slightly-offset
- * 2x2 rectangle of pixeloids.
- *
- * I have no idea whether GTK expects me not to mess with the
- * device scale in the cairo_surface_t backing its window, so
- * I carefully put it back when I've finished.
- *
- * In some GTK setups, the Cairo context we're given may not
- * have a zero translation offset in its matrix, in which case
- * we have to adjust that to compensate for the change of
- * scale, or else the old translation offset (designed for the
- * old scale) will be multiplied by the new scale instead and
- * put everything in the wrong place.
- */
- target_surface = cairo_get_target(cr);
- cairo_get_matrix(cr, &m);
- cairo_surface_get_device_scale(target_surface, &orig_sx, &orig_sy);
- cairo_surface_set_device_scale(target_surface, 1.0, 1.0);
- cairo_translate(cr, m.x0 * (orig_sx - 1.0), m.y0 * (orig_sy - 1.0));
-
- gdk_cairo_get_clip_rectangle(cr, &dirtyrect);
-
- cairo_set_source_surface(cr, inst->surface, 0, 0);
- cairo_rectangle(cr, dirtyrect.x, dirtyrect.y,
- dirtyrect.width, dirtyrect.height);
- cairo_fill(cr);
-
- cairo_surface_set_device_scale(target_surface, orig_sx, orig_sy);
- }
-
- return true;
-}
-#else
-gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
-
-#ifndef NO_BACKING_PIXMAPS
- /*
- * Draw to the exposed part of the window from the server-side
- * backing pixmap.
- */
- if (inst->pixmap) {
- gdk_draw_pixmap(gtk_widget_get_window(widget),
- (gtk_widget_get_style(widget)->fg_gc
- [gtk_widget_get_state(widget)]),
- inst->pixmap,
- event->area.x, event->area.y,
- event->area.x, event->area.y,
- event->area.width, event->area.height);
- }
-#else
- /*
- * Failing that, draw from the client-side Cairo surface. (We
- * should never be compiled in a context where we have _neither_
- * inst->surface nor inst->pixmap.)
- */
- if (inst->surface) {
- cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(widget));
- cairo_set_source_surface(cr, inst->surface, 0, 0);
- cairo_rectangle(cr, event->area.x, event->area.y,
- event->area.width, event->area.height);
- cairo_fill(cr);
- cairo_destroy(cr);
- }
-#endif
-
- return true;
-}
-#endif
-
-#define KEY_PRESSED(k) \
- (inst->keystate[(k) / 32] & (1 << ((k) % 32)))
-
-#ifdef KEY_EVENT_DIAGNOSTICS
-char *dup_keyval_name(guint keyval)
-{
- const char *name = gdk_keyval_name(keyval);
- if (name)
- return dupstr(name);
- else
- return dupprintf("UNKNOWN[%u]", (unsigned)keyval);
-}
-#endif
-
-static void change_font_size(GtkFrontend *inst, int increment);
-static void key_pressed(GtkFrontend *inst);
-
-/* Subroutine used in key_event */
-static int return_key(GtkFrontend *inst, char *output, bool *special)
-{
- int end;
-
- /* Ugly label so we can come here as a fallback from
- * numeric keypad Enter handling */
- if (inst->term->cr_lf_return) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Return in cr_lf_return mode, translating as 0d 0a\n");
-#endif
- output[1] = '\015';
- output[2] = '\012';
- end = 3;
- } else {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Return special case, translating as 0d + special\n");
-#endif
- output[1] = '\015';
- end = 2;
- *special = true;
- }
-
- return end;
-}
-
-gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- char output[256];
- wchar_t ucsoutput[2];
- int ucsval, start, end, output_charset;
- bool special, use_ucsoutput;
- bool force_format_numeric_keypad = false;
- bool generated_something = false;
- char num_keypad_key = '\0';
- const char *event_string = event->string ? event->string : "";
-
- noise_ultralight(NOISE_SOURCE_KEY, event->keyval);
-
-#ifdef OSX_META_KEY_CONFIG
- if (event->state & inst->system_mod_mask)
- return false; /* let GTK process OS X Command key */
-#endif
-
- /* Remember the timestamp. */
- inst->input_event_time = event->time;
-
- /* By default, nothing is generated. */
- end = start = 0;
- special = use_ucsoutput = false;
- output_charset = CS_ISO8859_1;
-
-#ifdef KEY_EVENT_DIAGNOSTICS
- {
- char *type_string, *state_string, *keyval_string, *string_string;
-
- type_string = (event->type == GDK_KEY_PRESS ? dupstr("PRESS") :
- event->type == GDK_KEY_RELEASE ? dupstr("RELEASE") :
- dupprintf("UNKNOWN[%d]", (int)event->type));
-
- {
- static const struct {
- int mod_bit;
- const char *name;
- } mod_bits[] = {
- {GDK_SHIFT_MASK, "SHIFT"},
- {GDK_LOCK_MASK, "LOCK"},
- {GDK_CONTROL_MASK, "CONTROL"},
- {GDK_MOD1_MASK, "MOD1"},
- {GDK_MOD2_MASK, "MOD2"},
- {GDK_MOD3_MASK, "MOD3"},
- {GDK_MOD4_MASK, "MOD4"},
- {GDK_MOD5_MASK, "MOD5"},
- {GDK_SUPER_MASK, "SUPER"},
- {GDK_HYPER_MASK, "HYPER"},
- {GDK_META_MASK, "META"},
- };
- int i;
- int val = event->state;
-
- state_string = dupstr("");
-
- for (i = 0; i < lenof(mod_bits); i++) {
- if (val & mod_bits[i].mod_bit) {
- char *old = state_string;
- state_string = dupcat(state_string,
- state_string[0] ? "|" : "",
- mod_bits[i].name);
- sfree(old);
-
- val &= ~mod_bits[i].mod_bit;
- }
- }
-
- if (val || !state_string[0]) {
- char *old = state_string;
- state_string = dupprintf("%s%s%d", state_string,
- state_string[0] ? "|" : "", val);
- sfree(old);
- }
- }
-
- keyval_string = dup_keyval_name(event->keyval);
-
- string_string = dupstr("");
- {
- int i;
- for (i = 0; event_string[i]; i++) {
- char *old = string_string;
- string_string = dupprintf("%s%s%02x", string_string,
- string_string[0] ? " " : "",
- (unsigned)event_string[i] & 0xFF);
- sfree(old);
- }
- }
-
- debug("key_event: type=%s keyval=%s state=%s "
- "hardware_keycode=%d is_modifier=%s string=[%s]\n",
- type_string, keyval_string, state_string,
- (int)event->hardware_keycode,
- event->is_modifier ? "true" : "false",
- string_string);
-
- sfree(type_string);
- sfree(state_string);
- sfree(keyval_string);
- sfree(string_string);
- }
-#endif /* KEY_EVENT_DIAGNOSTICS */
-
- /*
- * If Alt is being released after typing an Alt+numberpad
- * sequence, we should generate the code that was typed.
- *
- * Note that we only do this if more than one key was actually
- * pressed - I don't think Alt+NumPad4 should be ^D or that
- * Alt+NumPad3 should be ^C, for example. There's no serious
- * inconvenience in having to type a zero before a single-digit
- * character code.
- */
- if (event->type == GDK_KEY_RELEASE) {
- if ((event->keyval == GDK_KEY_Meta_L ||
- event->keyval == GDK_KEY_Meta_R ||
- event->keyval == GDK_KEY_Alt_L ||
- event->keyval == GDK_KEY_Alt_R) &&
- inst->alt_keycode >= 0 && inst->alt_digits > 1) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - modifier release terminates Alt+numberpad input, "
- "keycode = %d\n", inst->alt_keycode);
-#endif
- /*
- * FIXME: we might usefully try to do something clever here
- * about interpreting the generated key code in a way that's
- * appropriate to the line code page.
- */
- output[0] = inst->alt_keycode;
- end = 1;
- goto done;
- }
-#if GTK_CHECK_VERSION(2,0,0)
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - key release, passing to IM\n");
-#endif
- if (gtk_im_context_filter_keypress(inst->imc, event)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - key release accepted by IM\n");
-#endif
- return true;
- } else {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - key release not accepted by IM\n");
-#endif
- }
-#endif
- }
-
- if (event->type == GDK_KEY_PRESS) {
- /*
- * If Alt has just been pressed, we start potentially
- * accumulating an Alt+numberpad code. We do this by
- * setting alt_keycode to -1 (nothing yet but plausible).
- */
- if ((event->keyval == GDK_KEY_Meta_L ||
- event->keyval == GDK_KEY_Meta_R ||
- event->keyval == GDK_KEY_Alt_L ||
- event->keyval == GDK_KEY_Alt_R)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - modifier press potentially begins Alt+numberpad "
- "input\n");
-#endif
- inst->alt_keycode = -1;
- inst->alt_digits = 0;
- goto done; /* this generates nothing else */
- }
-
- /*
- * If we're seeing a numberpad key press with Meta down,
- * consider adding it to alt_keycode if that's sensible.
- * Anything _else_ with Meta down cancels any possibility
- * of an ALT keycode: we set alt_keycode to -2.
- */
- if ((event->state & inst->meta_mod_mask) && inst->alt_keycode != -2) {
- int digit = -1;
- switch (event->keyval) {
- case GDK_KEY_KP_0: case GDK_KEY_KP_Insert: digit = 0; break;
- case GDK_KEY_KP_1: case GDK_KEY_KP_End: digit = 1; break;
- case GDK_KEY_KP_2: case GDK_KEY_KP_Down: digit = 2; break;
- case GDK_KEY_KP_3: case GDK_KEY_KP_Page_Down: digit = 3; break;
- case GDK_KEY_KP_4: case GDK_KEY_KP_Left: digit = 4; break;
- case GDK_KEY_KP_5: case GDK_KEY_KP_Begin: digit = 5; break;
- case GDK_KEY_KP_6: case GDK_KEY_KP_Right: digit = 6; break;
- case GDK_KEY_KP_7: case GDK_KEY_KP_Home: digit = 7; break;
- case GDK_KEY_KP_8: case GDK_KEY_KP_Up: digit = 8; break;
- case GDK_KEY_KP_9: case GDK_KEY_KP_Page_Up: digit = 9; break;
- }
- if (digit < 0)
- inst->alt_keycode = -2; /* it's invalid */
- else {
-#if defined(DEBUG) && defined(KEY_EVENT_DIAGNOSTICS)
- int old_keycode = inst->alt_keycode;
-#endif
- if (inst->alt_keycode == -1)
- inst->alt_keycode = digit; /* one-digit code */
- else
- inst->alt_keycode = inst->alt_keycode * 10 + digit;
- inst->alt_digits++;
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Alt+numberpad digit %d added to keycode %d"
- " gives %d\n", digit, old_keycode, inst->alt_keycode);
-#endif
- /* Having used this digit, we now do nothing more with it. */
- goto done;
- }
- }
-
- if (event->keyval == GDK_KEY_greater &&
- (event->state & GDK_CONTROL_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl->: increase font size\n");
-#endif
- change_font_size(inst, +1);
- return true;
- }
- if (event->keyval == GDK_KEY_less &&
- (event->state & GDK_CONTROL_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-<: increase font size\n");
-#endif
- change_font_size(inst, -1);
- return true;
- }
-
- /*
- * Shift-PgUp and Shift-PgDn don't even generate keystrokes
- * at all.
- */
- if (event->keyval == GDK_KEY_Page_Up &&
- ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) ==
- (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Shift-PgUp scroll\n");
-#endif
- term_scroll(inst->term, 1, 0);
- return true;
- }
- if (event->keyval == GDK_KEY_Page_Up &&
- (event->state & GDK_SHIFT_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Shift-PgUp scroll\n");
-#endif
- term_scroll(inst->term, 0, -inst->height/2);
- return true;
- }
- if (event->keyval == GDK_KEY_Page_Up &&
- (event->state & GDK_CONTROL_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-PgUp scroll\n");
-#endif
- term_scroll(inst->term, 0, -1);
- return true;
- }
- if (event->keyval == GDK_KEY_Page_Down &&
- ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) ==
- (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-shift-PgDn scroll\n");
-#endif
- term_scroll(inst->term, -1, 0);
- return true;
- }
- if (event->keyval == GDK_KEY_Page_Down &&
- (event->state & GDK_SHIFT_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Shift-PgDn scroll\n");
-#endif
- term_scroll(inst->term, 0, +inst->height/2);
- return true;
- }
- if (event->keyval == GDK_KEY_Page_Down &&
- (event->state & GDK_CONTROL_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-PgDn scroll\n");
-#endif
- term_scroll(inst->term, 0, +1);
- return true;
- }
-
- /*
- * Neither do Shift-Ins or Ctrl-Ins (if enabled).
- */
- if (event->keyval == GDK_KEY_Insert &&
- (event->state & GDK_SHIFT_MASK)) {
- int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftins);
-
- switch (cfgval) {
- case CLIPUI_IMPLICIT:
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Shift-Insert: paste from PRIMARY\n");
-#endif
- term_request_paste(inst->term, CLIP_PRIMARY);
- return true;
- case CLIPUI_EXPLICIT:
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Shift-Insert: paste from CLIPBOARD\n");
-#endif
- term_request_paste(inst->term, CLIP_CLIPBOARD);
- return true;
- case CLIPUI_CUSTOM:
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Shift-Insert: paste from custom clipboard\n");
-#endif
- term_request_paste(inst->term, inst->clipboard_ctrlshiftins);
- return true;
- default:
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Shift-Insert: no paste action\n");
-#endif
- break;
- }
- }
- if (event->keyval == GDK_KEY_Insert &&
- (event->state & GDK_CONTROL_MASK)) {
- static const int clips_clipboard[] = { CLIP_CLIPBOARD };
- int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftins);
-
- switch (cfgval) {
- case CLIPUI_IMPLICIT:
- /* do nothing; re-copy to PRIMARY is not needed */
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Insert: non-copy to PRIMARY\n");
-#endif
- return true;
- case CLIPUI_EXPLICIT:
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Insert: copy to CLIPBOARD\n");
-#endif
- term_request_copy(inst->term,
- clips_clipboard, lenof(clips_clipboard));
- return true;
- case CLIPUI_CUSTOM:
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Insert: copy to custom clipboard\n");
-#endif
- term_request_copy(inst->term,
- &inst->clipboard_ctrlshiftins, 1);
- return true;
- default:
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Insert: no copy action\n");
-#endif
- break;
- }
- }
-
- /*
- * Another pair of copy-paste keys.
- */
- if ((event->state & GDK_SHIFT_MASK) &&
- (event->state & GDK_CONTROL_MASK) &&
- (event->keyval == GDK_KEY_C || event->keyval == GDK_KEY_c ||
- event->keyval == GDK_KEY_V || event->keyval == GDK_KEY_v)) {
- int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftcv);
- bool paste = (event->keyval == GDK_KEY_V ||
- event->keyval == GDK_KEY_v);
-
- switch (cfgval) {
- case CLIPUI_IMPLICIT:
- if (paste) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Shift-V: paste from PRIMARY\n");
-#endif
- term_request_paste(inst->term, CLIP_PRIMARY);
- } else {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Shift-C: non-copy to PRIMARY\n");
-#endif
- }
- return true;
- case CLIPUI_EXPLICIT:
- if (paste) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Shift-V: paste from CLIPBOARD\n");
-#endif
- term_request_paste(inst->term, CLIP_CLIPBOARD);
- } else {
- static const int clips[] = { CLIP_CLIPBOARD };
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Shift-C: copy to CLIPBOARD\n");
-#endif
- term_request_copy(inst->term, clips, lenof(clips));
- }
- return true;
- case CLIPUI_CUSTOM:
- if (paste) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Shift-V: paste from custom clipboard\n");
-#endif
- term_request_paste(inst->term,
- inst->clipboard_ctrlshiftcv);
- } else {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Shift-C: copy to custom clipboard\n");
-#endif
- term_request_copy(inst->term,
- &inst->clipboard_ctrlshiftcv, 1);
- }
- return true;
- }
- }
-
- special = false;
- use_ucsoutput = false;
-
- /* ALT+things gives leading Escape. */
- output[0] = '\033';
-#if !GTK_CHECK_VERSION(2,0,0)
- /*
- * In vanilla X, and hence also GDK 1.2, the string received
- * as part of a keyboard event is assumed to be in
- * ISO-8859-1. (Seems woefully shortsighted in i18n terms,
- * but it's true: see the man page for XLookupString(3) for
- * confirmation.)
- */
- output_charset = CS_ISO8859_1;
- strncpy(output+1, event_string, lenof(output)-1);
-#else /* !GTK_CHECK_VERSION(2,0,0) */
- /*
- * Most things can now be passed to
- * gtk_im_context_filter_keypress without breaking anything
- * below this point. An exception is the numeric keypad if
- * we're in Nethack or application mode: the IM will eat
- * numeric keypad presses if Num Lock is on, but we don't want
- * it to.
- */
- bool numeric = false;
- bool nethack_mode = conf_get_bool(inst->conf, CONF_nethack_keypad);
- bool app_keypad_mode = (inst->term->app_keypad_keys &&
- !conf_get_bool(inst->conf, CONF_no_applic_k));
-
- switch (event->keyval) {
- case GDK_KEY_Num_Lock: num_keypad_key = 'G'; break;
- case GDK_KEY_KP_Divide: num_keypad_key = '/'; break;
- case GDK_KEY_KP_Multiply: num_keypad_key = '*'; break;
- case GDK_KEY_KP_Subtract: num_keypad_key = '-'; break;
- case GDK_KEY_KP_Add: num_keypad_key = '+'; break;
- case GDK_KEY_KP_Enter: num_keypad_key = '\r'; break;
- case GDK_KEY_KP_0: num_keypad_key = '0'; numeric = true; break;
- case GDK_KEY_KP_Insert: num_keypad_key = '0'; break;
- case GDK_KEY_KP_1: num_keypad_key = '1'; numeric = true; break;
- case GDK_KEY_KP_End: num_keypad_key = '1'; break;
- case GDK_KEY_KP_2: num_keypad_key = '2'; numeric = true; break;
- case GDK_KEY_KP_Down: num_keypad_key = '2'; break;
- case GDK_KEY_KP_3: num_keypad_key = '3'; numeric = true; break;
- case GDK_KEY_KP_Page_Down: num_keypad_key = '3'; break;
- case GDK_KEY_KP_4: num_keypad_key = '4'; numeric = true; break;
- case GDK_KEY_KP_Left: num_keypad_key = '4'; break;
- case GDK_KEY_KP_5: num_keypad_key = '5'; numeric = true; break;
- case GDK_KEY_KP_Begin: num_keypad_key = '5'; break;
- case GDK_KEY_KP_6: num_keypad_key = '6'; numeric = true; break;
- case GDK_KEY_KP_Right: num_keypad_key = '6'; break;
- case GDK_KEY_KP_7: num_keypad_key = '7'; numeric = true; break;
- case GDK_KEY_KP_Home: num_keypad_key = '7'; break;
- case GDK_KEY_KP_8: num_keypad_key = '8'; numeric = true; break;
- case GDK_KEY_KP_Up: num_keypad_key = '8'; break;
- case GDK_KEY_KP_9: num_keypad_key = '9'; numeric = true; break;
- case GDK_KEY_KP_Page_Up: num_keypad_key = '9'; break;
- case GDK_KEY_KP_Decimal: num_keypad_key = '.'; numeric = true; break;
- case GDK_KEY_KP_Delete: num_keypad_key = '.'; break;
- }
- if ((app_keypad_mode && num_keypad_key &&
- (numeric || inst->term->funky_type != FUNKY_XTERM)) ||
- (nethack_mode && num_keypad_key >= '1' && num_keypad_key <= '9')) {
- /* In these modes, we override the keypad handling:
- * regardless of Num Lock, the keys are handled by
- * format_numeric_keypad_key below. */
- force_format_numeric_keypad = true;
- } else {
- bool try_filter = true;
-
-#ifdef META_MANUAL_MASK
- if (event->state & META_MANUAL_MASK & inst->meta_mod_mask) {
- /*
- * If this key event had a Meta modifier bit set which
- * is also in META_MANUAL_MASK, that means passing
- * such an event to the GtkIMContext will be unhelpful
- * (it will eat the keystroke and turn it into
- * something not what we wanted).
- */
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Meta modifier requiring manual intervention, "
- "suppressing IM filtering\n");
-#endif
- try_filter = false;
- }
-#endif
-
- if (try_filter) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - general key press, passing to IM\n");
-#endif
- if (gtk_im_context_filter_keypress(inst->imc, event)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - key press accepted by IM\n");
-#endif
- return true;
- } else {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - key press not accepted by IM\n");
-#endif
- }
- }
- }
-
- /*
- * GDK 2.0 arranges to have done some translation for us: in
- * GDK 2.0, event->string is encoded in the current locale.
- *
- * So we use the standard C library function mbstowcs() to
- * convert from the current locale into Unicode; from there
- * we can convert to whatever PuTTY is currently working in.
- * (In fact I convert straight back to UTF-8 from
- * wide-character Unicode, for the sake of simplicity: that
- * way we can still use exactly the same code to manipulate
- * the string, such as prefixing ESC.)
- */
- output_charset = CS_UTF8;
- {
- wchar_t widedata[32];
- const wchar_t *wp;
- int wlen;
- int ulen;
-
- wlen = mb_to_wc(DEFAULT_CODEPAGE, 0,
- event_string, strlen(event_string),
- widedata, lenof(widedata)-1);
-
-#ifdef KEY_EVENT_DIAGNOSTICS
- {
- char *string_string = dupstr("");
- int i;
-
- for (i = 0; i < wlen; i++) {
- char *old = string_string;
- string_string = dupprintf("%s%s%04x", string_string,
- string_string[0] ? " " : "",
- (unsigned)widedata[i]);
- sfree(old);
- }
- debug(" - string translated into Unicode = [%s]\n",
- string_string);
- sfree(string_string);
- }
-#endif
-
- wp = widedata;
- ulen = charset_from_unicode(&wp, &wlen, output+1, lenof(output)-2,
- CS_UTF8, NULL, NULL, 0);
-
-#ifdef KEY_EVENT_DIAGNOSTICS
- {
- char *string_string = dupstr("");
- int i;
-
- for (i = 0; i < ulen; i++) {
- char *old = string_string;
- string_string = dupprintf("%s%s%02x", string_string,
- string_string[0] ? " " : "",
- (unsigned)output[i+1] & 0xFF);
- sfree(old);
- }
- debug(" - string translated into UTF-8 = [%s]\n",
- string_string);
- sfree(string_string);
- }
-#endif
-
- output[1+ulen] = '\0';
- }
-#endif /* !GTK_CHECK_VERSION(2,0,0) */
-
- if (!output[1] &&
- (ucsval = keysym_to_unicode(event->keyval)) >= 0) {
- ucsoutput[0] = '\033';
- ucsoutput[1] = ucsval;
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - keysym_to_unicode gave %04x\n",
- (unsigned)ucsoutput[1]);
-#endif
- use_ucsoutput = true;
- end = 2;
- } else {
- output[lenof(output)-1] = '\0';
- end = strlen(output);
- }
- if (event->state & inst->meta_mod_mask) {
- start = 0;
- if (end == 1) end = 0;
-
-#ifdef META_MANUAL_MASK
- if (event->state & META_MANUAL_MASK) {
- /*
- * Key events which have a META_MANUAL_MASK meta bit
- * set may have a keyval reflecting that, e.g. on OS X
- * the Option key acts as an AltGr-like modifier and
- * causes different Unicode characters to be output.
- *
- * To work around this, we clear the dangerous
- * modifier bit and retranslate from the hardware
- * keycode as if the key had been pressed without that
- * modifier. Then we prefix Esc to *that*.
- */
- guint new_keyval;
- GdkModifierType consumed;
- if (gdk_keymap_translate_keyboard_state
- (gdk_keymap_get_for_display(gdk_display_get_default()),
- event->hardware_keycode, event->state & ~META_MANUAL_MASK,
- 0, &new_keyval, NULL, NULL, &consumed)) {
- ucsoutput[0] = '\033';
- ucsoutput[1] = gdk_keyval_to_unicode(new_keyval);
-#ifdef KEY_EVENT_DIAGNOSTICS
- {
- char *keyval_name = dup_keyval_name(new_keyval);
- debug(" - retranslation for manual Meta: "
- "new keyval = %s, Unicode = %04x\n",
- keyval_name, (unsigned)ucsoutput[1]);
- sfree(keyval_name);
- }
-#endif
- use_ucsoutput = true;
- end = 2;
- }
- }
-#endif
- } else
- start = 1;
-
- /* Control-` is the same as Control-\ (unless gtk has a better idea) */
- if (!output[1] && event->keyval == '`' &&
- (event->state & GDK_CONTROL_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-` special case, translating as 1c\n");
-#endif
- output[1] = '\x1C';
- use_ucsoutput = false;
- end = 2;
- }
-
- /* Some GTK backends (e.g. Quartz) do not change event->string
- * in response to the Control modifier. So we do it ourselves
- * here, if it's not already happened.
- *
- * The translations below are in line with X11 policy as far
- * as I know. */
- if ((event->state & GDK_CONTROL_MASK) && end == 2) {
- int orig = use_ucsoutput ? ucsoutput[1] : output[1];
- int new = orig;
-
- if (new >= '3' && new <= '7') {
- /* ^3,...,^7 map to 0x1B,...,0x1F */
- new += '\x1B' - '3';
- } else if (new == '2' || new == ' ') {
- /* ^2 and ^Space are both ^@, i.e. \0 */
- new = '\0';
- } else if (new == '8') {
- /* ^8 is DEL */
- new = '\x7F';
- } else if (new == '/') {
- /* ^/ is the same as ^_ */
- new = '\x1F';
- } else if (new >= 0x40 && new < 0x7F) {
- /* Everything anywhere near the alphabetics just gets
- * masked. */
- new &= 0x1F;
- }
- /* Anything else, e.g. '0', is unchanged. */
-
- if (orig == new) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - manual Ctrl key handling did nothing\n");
-#endif
- } else {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - manual Ctrl key handling: %02x -> %02x\n",
- (unsigned)orig, (unsigned)new);
-#endif
- output[1] = new;
- use_ucsoutput = false;
- }
- }
-
- /* Control-Break sends a Break special to the backend */
- if (event->keyval == GDK_KEY_Break &&
- (event->state & GDK_CONTROL_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Break special case, sending SS_BRK\n");
-#endif
- if (inst->backend)
- backend_special(inst->backend, SS_BRK, 0);
- return true;
- }
-
- /* We handle Return ourselves, because it needs to be flagged as
- * special to ldisc. */
- if (event->keyval == GDK_KEY_Return) {
- end = return_key(inst, output, &special);
- use_ucsoutput = false;
- }
-
- /* Control-2, Control-Space and Control-@ are NUL */
- if (!output[1] &&
- (event->keyval == ' ' || event->keyval == '2' ||
- event->keyval == '@') &&
- (event->state & (GDK_SHIFT_MASK |
- GDK_CONTROL_MASK)) == GDK_CONTROL_MASK) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-{space,2,@} special case, translating as 00\n");
-#endif
- output[1] = '\0';
- use_ucsoutput = false;
- end = 2;
- }
-
- /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */
- if (!output[1] && event->keyval == ' ' &&
- (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) ==
- (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Ctrl-Shift-space special case, translating as 00a0\n");
-#endif
- output[1] = '\240';
- output_charset = CS_ISO8859_1;
- use_ucsoutput = false;
- end = 2;
- }
-
- /* We don't let GTK tell us what Backspace is! We know better. */
- if (event->keyval == GDK_KEY_BackSpace &&
- !(event->state & GDK_SHIFT_MASK)) {
- output[1] = conf_get_bool(inst->conf, CONF_bksp_is_delete) ?
- '\x7F' : '\x08';
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Backspace, translating as %02x\n",
- (unsigned)output[1]);
-#endif
- use_ucsoutput = false;
- end = 2;
- special = true;
- }
- /* For Shift Backspace, do opposite of what is configured. */
- if (event->keyval == GDK_KEY_BackSpace &&
- (event->state & GDK_SHIFT_MASK)) {
- output[1] = conf_get_bool(inst->conf, CONF_bksp_is_delete) ?
- '\x08' : '\x7F';
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Shift-Backspace, translating as %02x\n",
- (unsigned)output[1]);
-#endif
- use_ucsoutput = false;
- end = 2;
- special = true;
- }
-
- /* Shift-Tab is ESC [ Z */
- if (event->keyval == GDK_KEY_ISO_Left_Tab ||
- (event->keyval == GDK_KEY_Tab &&
- (event->state & GDK_SHIFT_MASK))) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Shift-Tab, translating as ESC [ Z\n");
-#endif
- end = 1 + sprintf(output+1, "\033[Z");
- use_ucsoutput = false;
- }
- /* And normal Tab is Tab, if the keymap hasn't already told us.
- * (Curiously, at least one version of the MacOS 10.5 X server
- * doesn't translate Tab for us. */
- if (event->keyval == GDK_KEY_Tab && end <= 1) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - Tab, translating as 09\n");
-#endif
- output[1] = '\t';
- end = 2;
- }
-
- if (num_keypad_key && force_format_numeric_keypad) {
- end = 1 + format_numeric_keypad_key(
- output+1, inst->term, num_keypad_key,
- event->state & GDK_SHIFT_MASK,
- event->state & GDK_CONTROL_MASK);
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - numeric keypad key");
-#endif
- use_ucsoutput = false;
- goto done;
- }
-
- switch (event->keyval) {
- int fkey_number;
- case GDK_KEY_F1: fkey_number = 1; goto numbered_function_key;
- case GDK_KEY_F2: fkey_number = 2; goto numbered_function_key;
- case GDK_KEY_F3: fkey_number = 3; goto numbered_function_key;
- case GDK_KEY_F4: fkey_number = 4; goto numbered_function_key;
- case GDK_KEY_F5: fkey_number = 5; goto numbered_function_key;
- case GDK_KEY_F6: fkey_number = 6; goto numbered_function_key;
- case GDK_KEY_F7: fkey_number = 7; goto numbered_function_key;
- case GDK_KEY_F8: fkey_number = 8; goto numbered_function_key;
- case GDK_KEY_F9: fkey_number = 9; goto numbered_function_key;
- case GDK_KEY_F10: fkey_number = 10; goto numbered_function_key;
- case GDK_KEY_F11: fkey_number = 11; goto numbered_function_key;
- case GDK_KEY_F12: fkey_number = 12; goto numbered_function_key;
- case GDK_KEY_F13: fkey_number = 13; goto numbered_function_key;
- case GDK_KEY_F14: fkey_number = 14; goto numbered_function_key;
- case GDK_KEY_F15: fkey_number = 15; goto numbered_function_key;
- case GDK_KEY_F16: fkey_number = 16; goto numbered_function_key;
- case GDK_KEY_F17: fkey_number = 17; goto numbered_function_key;
- case GDK_KEY_F18: fkey_number = 18; goto numbered_function_key;
- case GDK_KEY_F19: fkey_number = 19; goto numbered_function_key;
- case GDK_KEY_F20: fkey_number = 20; goto numbered_function_key;
- numbered_function_key:
- end = 1 + format_function_key(output+1, inst->term, fkey_number,
- event->state & GDK_SHIFT_MASK,
- event->state & GDK_CONTROL_MASK);
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - function key F%d", fkey_number);
-#endif
- use_ucsoutput = false;
- goto done;
-
- SmallKeypadKey sk_key;
- case GDK_KEY_Home: case GDK_KEY_KP_Home:
- sk_key = SKK_HOME; goto small_keypad_key;
- case GDK_KEY_Insert: case GDK_KEY_KP_Insert:
- sk_key = SKK_INSERT; goto small_keypad_key;
- case GDK_KEY_Delete: case GDK_KEY_KP_Delete:
- sk_key = SKK_DELETE; goto small_keypad_key;
- case GDK_KEY_End: case GDK_KEY_KP_End:
- sk_key = SKK_END; goto small_keypad_key;
- case GDK_KEY_Page_Up: case GDK_KEY_KP_Page_Up:
- sk_key = SKK_PGUP; goto small_keypad_key;
- case GDK_KEY_Page_Down: case GDK_KEY_KP_Page_Down:
- sk_key = SKK_PGDN; goto small_keypad_key;
- small_keypad_key:
- /* These keys don't generate terminal input with Ctrl */
- if (event->state & GDK_CONTROL_MASK)
- break;
-
- end = 1 + format_small_keypad_key(output+1, inst->term, sk_key);
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - small keypad key");
-#endif
- use_ucsoutput = false;
- goto done;
-
- int xkey;
- case GDK_KEY_Up: case GDK_KEY_KP_Up:
- xkey = 'A'; goto arrow_key;
- case GDK_KEY_Down: case GDK_KEY_KP_Down:
- xkey = 'B'; goto arrow_key;
- case GDK_KEY_Right: case GDK_KEY_KP_Right:
- xkey = 'C'; goto arrow_key;
- case GDK_KEY_Left: case GDK_KEY_KP_Left:
- xkey = 'D'; goto arrow_key;
- case GDK_KEY_Begin: case GDK_KEY_KP_Begin:
- xkey = 'G'; goto arrow_key;
- arrow_key:
- end = 1 + format_arrow_key(output+1, inst->term, xkey,
- event->state & GDK_CONTROL_MASK);
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - arrow key");
-#endif
- use_ucsoutput = false;
- goto done;
- }
-
- if (num_keypad_key) {
- end = 1 + format_numeric_keypad_key(
- output+1, inst->term, num_keypad_key,
- event->state & GDK_SHIFT_MASK,
- event->state & GDK_CONTROL_MASK);
-#ifdef KEY_EVENT_DIAGNOSTICS
- debug(" - numeric keypad key");
-#endif
-
- if (end == 1 && num_keypad_key == '\r') {
- /* Keypad Enter, lacking any other translation,
- * becomes the same special Return code as normal
- * Return. */
- end = return_key(inst, output, &special);
- use_ucsoutput = false;
- }
-
- use_ucsoutput = false;
- goto done;
- }
-
- goto done;
- }
-
- done:
-
- if (end-start > 0) {
- if (special) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- char *string_string = dupstr("");
- int i;
-
- for (i = start; i < end; i++) {
- char *old = string_string;
- string_string = dupprintf("%s%s%02x", string_string,
- string_string[0] ? " " : "",
- (unsigned)output[i] & 0xFF);
- sfree(old);
- }
- debug(" - final output, special, generic encoding = [%s]\n",
- string_string);
- sfree(string_string);
-#endif
- /*
- * For special control characters, the character set
- * should never matter.
- */
- output[end] = '\0'; /* NUL-terminate */
- generated_something = true;
- term_keyinput(inst->term, -1, output+start, -2);
- } else if (!inst->direct_to_font) {
- if (!use_ucsoutput) {
-#ifdef KEY_EVENT_DIAGNOSTICS
- char *string_string = dupstr("");
- int i;
-
- for (i = start; i < end; i++) {
- char *old = string_string;
- string_string = dupprintf("%s%s%02x", string_string,
- string_string[0] ? " " : "",
- (unsigned)output[i] & 0xFF);
- sfree(old);
- }
- debug(" - final output in %s = [%s]\n",
- charset_to_localenc(output_charset), string_string);
- sfree(string_string);
-#endif
- generated_something = true;
- term_keyinput(inst->term, output_charset,
- output+start, end-start);
- } else {
-#ifdef KEY_EVENT_DIAGNOSTICS
- char *string_string = dupstr("");
- int i;
-
- for (i = start; i < end; i++) {
- char *old = string_string;
- string_string = dupprintf("%s%s%04x", string_string,
- string_string[0] ? " " : "",
- (unsigned)ucsoutput[i]);
- sfree(old);
- }
- debug(" - final output in Unicode = [%s]\n",
- string_string);
- sfree(string_string);
-#endif
-
- /*
- * We generated our own Unicode key data from the
- * keysym, so use that instead.
- */
- generated_something = true;
- term_keyinputw(inst->term, ucsoutput+start, end-start);
- }
- } else {
- /*
- * In direct-to-font mode, we just send the string
- * exactly as we received it.
- */
-#ifdef KEY_EVENT_DIAGNOSTICS
- char *string_string = dupstr("");
- int i;
-
- for (i = start; i < end; i++) {
- char *old = string_string;
- string_string = dupprintf("%s%s%02x", string_string,
- string_string[0] ? " " : "",
- (unsigned)output[i] & 0xFF);
- sfree(old);
- }
- debug(" - final output in direct-to-font encoding = [%s]\n",
- string_string);
- sfree(string_string);
-#endif
- generated_something = true;
- term_keyinput(inst->term, -1, output+start, end-start);
- }
-
- show_mouseptr(inst, false);
- }
-
- if (generated_something)
- key_pressed(inst);
- return true;
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-void input_method_commit_event(GtkIMContext *imc, gchar *str, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
-
-#ifdef KEY_EVENT_DIAGNOSTICS
- char *string_string = dupstr("");
- int i;
-
- for (i = 0; str[i]; i++) {
- char *old = string_string;
- string_string = dupprintf("%s%s%02x", string_string,
- string_string[0] ? " " : "",
- (unsigned)str[i] & 0xFF);
- sfree(old);
- }
- debug(" - IM commit event in UTF-8 = [%s]\n", string_string);
- sfree(string_string);
-#endif
-
- term_keyinput(inst->term, CS_UTF8, str, strlen(str));
- show_mouseptr(inst, false);
- key_pressed(inst);
-}
-#endif
-
-#define SCROLL_INCREMENT_LINES 5
-
-#if GTK_CHECK_VERSION(3,4,0)
-gboolean scroll_internal(GtkFrontend *inst, gdouble delta, guint state,
- gdouble ex, gdouble ey)
-{
- int x, y;
- bool shift, ctrl, alt, raw_mouse_mode;
-
- show_mouseptr(inst, true);
-
- shift = state & GDK_SHIFT_MASK;
- ctrl = state & GDK_CONTROL_MASK;
- alt = state & inst->meta_mod_mask;
-
- x = (ex - inst->window_border) / inst->font_width;
- y = (ey - inst->window_border) / inst->font_height;
-
- raw_mouse_mode = (inst->send_raw_mouse &&
- !(shift && conf_get_bool(inst->conf,
- CONF_mouse_override)));
-
- inst->cumulative_scroll += delta * SCROLL_INCREMENT_LINES;
-
- if (!raw_mouse_mode) {
- int scroll_lines = (int)inst->cumulative_scroll; /* rounds toward 0 */
- if (scroll_lines) {
- term_scroll(inst->term, 0, scroll_lines);
- inst->cumulative_scroll -= scroll_lines;
- }
- return true;
- } else {
- int scroll_events = (int)(inst->cumulative_scroll /
- SCROLL_INCREMENT_LINES);
- if (scroll_events) {
- int button;
-
- inst->cumulative_scroll -= scroll_events * SCROLL_INCREMENT_LINES;
-
- if (scroll_events > 0) {
- button = MBT_WHEEL_DOWN;
- } else {
- button = MBT_WHEEL_UP;
- scroll_events = -scroll_events;
- }
-
- while (scroll_events-- > 0) {
- term_mouse(inst->term, button, translate_button(button),
- MA_CLICK, x, y, shift, ctrl, alt);
- }
- }
- return true;
- }
-}
-#endif
-
-static gboolean button_internal(GtkFrontend *inst, GdkEventButton *event)
-{
- bool shift, ctrl, alt, raw_mouse_mode;
- int x, y, button, act;
-
- /* Remember the timestamp. */
- inst->input_event_time = event->time;
-
- noise_ultralight(NOISE_SOURCE_MOUSEBUTTON, event->button);
-
- show_mouseptr(inst, true);
-
- shift = event->state & GDK_SHIFT_MASK;
- ctrl = event->state & GDK_CONTROL_MASK;
- alt = event->state & inst->meta_mod_mask;
-
- raw_mouse_mode = (inst->send_raw_mouse &&
- !(shift && conf_get_bool(inst->conf,
- CONF_mouse_override)));
-
- if (!raw_mouse_mode) {
- if (event->button == 4 && event->type == GDK_BUTTON_PRESS) {
- term_scroll(inst->term, 0, -SCROLL_INCREMENT_LINES);
- return true;
- }
- if (event->button == 5 && event->type == GDK_BUTTON_PRESS) {
- term_scroll(inst->term, 0, +SCROLL_INCREMENT_LINES);
- return true;
- }
- }
-
- if (event->button == 3 && ctrl) {
-#if GTK_CHECK_VERSION(3,22,0)
- gtk_menu_popup_at_pointer(GTK_MENU(inst->menu), (GdkEvent *)event);
-#else
- gtk_menu_popup(GTK_MENU(inst->menu), NULL, NULL, NULL, NULL,
- event->button, event->time);
-#endif
- return true;
- }
-
- if (event->button == 1)
- button = MBT_LEFT;
- else if (event->button == 2)
- button = MBT_MIDDLE;
- else if (event->button == 3)
- button = MBT_RIGHT;
- else if (event->button == 4)
- button = MBT_WHEEL_UP;
- else if (event->button == 5)
- button = MBT_WHEEL_DOWN;
- else
- return false; /* don't even know what button! */
-
- switch (event->type) {
- case GDK_BUTTON_PRESS: act = MA_CLICK; break;
- case GDK_BUTTON_RELEASE: act = MA_RELEASE; break;
- case GDK_2BUTTON_PRESS: act = MA_2CLK; break;
- case GDK_3BUTTON_PRESS: act = MA_3CLK; break;
- default: return false; /* don't know this event type */
- }
-
- if (raw_mouse_mode && act != MA_CLICK && act != MA_RELEASE)
- return true; /* we ignore these in raw mouse mode */
-
- x = (event->x - inst->window_border) / inst->font_width;
- y = (event->y - inst->window_border) / inst->font_height;
-
- term_mouse(inst->term, button, translate_button(button), act,
- x, y, shift, ctrl, alt);
-
- return true;
-}
-
-gboolean button_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- return button_internal(inst, event);
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-/*
- * In GTK 2, mouse wheel events have become a new type of event.
- * This handler translates them back into button-4 and button-5
- * presses so that I don't have to change my old code too much :-)
- */
-gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- GdkScrollDirection dir;
-
-#if GTK_CHECK_VERSION(3,4,0)
- gdouble dx, dy;
- if (gdk_event_get_scroll_deltas((GdkEvent *)event, &dx, &dy)) {
- return scroll_internal(inst, dy, event->state, event->x, event->y);
- } else if (!gdk_event_get_scroll_direction((GdkEvent *)event, &dir)) {
- return false;
- }
-#else
- dir = event->direction;
-#endif
-
- guint button;
- GdkEventButton *event_button;
- gboolean ret;
-
- if (dir == GDK_SCROLL_UP)
- button = 4;
- else if (dir == GDK_SCROLL_DOWN)
- button = 5;
- else
- return false;
-
- event_button = (GdkEventButton *)gdk_event_new(GDK_BUTTON_PRESS);
- event_button->window = g_object_ref(event->window);
- event_button->send_event = event->send_event;
- event_button->time = event->time;
- event_button->x = event->x;
- event_button->y = event->y;
- event_button->axes = NULL;
- event_button->state = event->state;
- event_button->button = button;
- event_button->device = g_object_ref(event->device);
- event_button->x_root = event->x_root;
- event_button->y_root = event->y_root;
- ret = button_internal(inst, event_button);
- gdk_event_free((GdkEvent *)event_button);
- return ret;
-}
-#endif
-
-gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- bool shift, ctrl, alt;
- int x, y, button;
-
- /* Remember the timestamp. */
- inst->input_event_time = event->time;
-
- noise_ultralight(NOISE_SOURCE_MOUSEPOS,
- ((uint32_t)event->x << 16) | (uint32_t)event->y);
-
- show_mouseptr(inst, true);
-
- shift = event->state & GDK_SHIFT_MASK;
- ctrl = event->state & GDK_CONTROL_MASK;
- alt = event->state & inst->meta_mod_mask;
- if (event->state & GDK_BUTTON1_MASK)
- button = MBT_LEFT;
- else if (event->state & GDK_BUTTON2_MASK)
- button = MBT_MIDDLE;
- else if (event->state & GDK_BUTTON3_MASK)
- button = MBT_RIGHT;
- else
- return false; /* don't even know what button! */
-
- x = (event->x - inst->window_border) / inst->font_width;
- y = (event->y - inst->window_border) / inst->font_height;
-
- term_mouse(inst->term, button, translate_button(button), MA_DRAG,
- x, y, shift, ctrl, alt);
-
- return true;
-}
-
-static void key_pressed(GtkFrontend *inst)
-{
- /*
- * If our child process has exited but not closed, terminate on
- * any keypress.
- *
- * This is a UI feature specific to GTK PuTTY, because GTK PuTTY
- * will (at least sometimes) be running under X, and under X the
- * window manager is sometimes absent (very occasionally on
- * purpose, more usually temporarily because it's crashed). So
- * it's useful to have a way to close an application window
- * without depending on protocols like WM_DELETE_WINDOW that are
- * typically generated by the WM (e.g. in response to a close
- * button in the window frame).
- */
- if (inst->exited)
- gtk_widget_destroy(inst->window);
-}
-
-static void exit_callback(void *vctx)
-{
- GtkFrontend *inst = (GtkFrontend *)vctx;
- int exitcode, close_on_exit;
-
- if (!inst->exited &&
- (exitcode = backend_exitcode(inst->backend)) >= 0) {
- destroy_inst_connection(inst);
-
- close_on_exit = conf_get_int(inst->conf, CONF_close_on_exit);
- if (close_on_exit == FORCE_ON ||
- (close_on_exit == AUTO && exitcode == 0)) {
- gtk_widget_destroy(inst->window);
- }
- }
-}
-
-static void gtk_seat_notify_remote_exit(Seat *seat)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- queue_toplevel_callback(exit_callback, inst);
-}
-
-static void destroy_inst_connection(GtkFrontend *inst)
-{
- inst->exited = true;
- if (inst->ldisc) {
- ldisc_free(inst->ldisc);
- inst->ldisc = NULL;
- }
- if (inst->backend) {
- backend_free(inst->backend);
- inst->backend = NULL;
- }
- if (inst->term)
- term_provide_backend(inst->term, NULL);
- if (inst->menu) {
- seat_update_specials_menu(&inst->seat);
- gtk_widget_set_sensitive(inst->restartitem, true);
- }
-}
-
-static void delete_inst(GtkFrontend *inst)
-{
- int dialog_slot;
- for (dialog_slot = 0; dialog_slot < DIALOG_SLOT_LIMIT; dialog_slot++) {
- if (inst->dialogs[dialog_slot]) {
- gtk_widget_destroy(inst->dialogs[dialog_slot]);
- inst->dialogs[dialog_slot] = NULL;
- }
- }
- if (inst->window) {
- gtk_widget_destroy(inst->window);
- inst->window = NULL;
- }
- if (inst->menu) {
- gtk_widget_destroy(inst->menu);
- inst->menu = NULL;
- }
- destroy_inst_connection(inst);
- if (inst->term) {
- term_free(inst->term);
- inst->term = NULL;
- }
- if (inst->conf) {
- conf_free(inst->conf);
- inst->conf = NULL;
- }
- if (inst->logctx) {
- log_free(inst->logctx);
- inst->logctx = NULL;
- }
-#if GTK_CHECK_VERSION(2,0,0)
- if (inst->trust_sigil_pb) {
- g_object_unref(G_OBJECT(inst->trust_sigil_pb));
- inst->trust_sigil_pb = NULL;
- }
-#else
- if (inst->trust_sigil_pm) {
- gdk_pixmap_unref(inst->trust_sigil_pm);
- inst->trust_sigil_pm = NULL;
- }
-#endif
-
-#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
- /*
- * Clear up any in-flight clipboard_data_instances. We can't
- * actually _free_ them, but we detach them from the inst that's
- * about to be destroyed.
- */
- while (inst->cdi_headtail.next != &inst->cdi_headtail) {
- struct clipboard_data_instance *cdi = inst->cdi_headtail.next;
- cdi->state = NULL;
- cdi->next->prev = cdi->prev;
- cdi->prev->next = cdi->next;
- cdi->next = cdi->prev = cdi;
- }
-#endif
-
- /*
- * Delete any top-level callbacks associated with inst, which
- * would otherwise become stale-pointer dereferences waiting to
- * happen. We do this last, because some of the above cleanups
- * (notably shutting down the backend) might themelves queue such
- * callbacks, so we need to make sure they don't do that _after_
- * we're supposed to have cleaned everything up.
- */
- delete_callbacks_for_context(inst);
-
- eventlogstuff_free(inst->eventlogstuff);
-
- sfree(inst);
-}
-
-void destroy(GtkWidget *widget, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- inst->window = NULL;
- delete_inst(inst);
- session_window_closed();
-}
-
-gint focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- term_set_focus(inst->term, event->in);
- term_update(inst->term);
- show_mouseptr(inst, true);
- return false;
-}
-
-static void gtk_seat_set_busy_status(Seat *seat, BusyStatus status)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- inst->busy_status = status;
- update_mouseptr(inst);
-}
-
-static void gtkwin_set_raw_mouse_mode(TermWin *tw, bool activate)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- inst->send_raw_mouse = activate;
-}
-
-static void gtkwin_set_raw_mouse_mode_pointer(TermWin *tw, bool activate)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- inst->pointer_indicates_raw_mouse = activate;
- update_mouseptr(inst);
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-static void compute_whole_window_size(GtkFrontend *inst,
- int wchars, int hchars,
- int *wpix, int *hpix);
-#endif
-
-static void gtkwin_request_resize(TermWin *tw, int w, int h)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
-
-#if !GTK_CHECK_VERSION(3,0,0)
-
- int large_x, large_y;
- int offset_x, offset_y;
- int area_x, area_y;
- GtkRequisition inner, outer;
-
- /*
- * This is a heinous hack dreamed up by the gnome-terminal
- * people to get around a limitation in gtk. The problem is
- * that in order to set the size correctly we really need to be
- * calling gtk_window_resize - but that needs to know the size
- * of the _whole window_, not the drawing area. So what we do
- * is to set an artificially huge size request on the drawing
- * area, recompute the resulting size request on the window,
- * and look at the difference between the two. That gives us
- * the x and y offsets we need to translate drawing area size
- * into window size for real, and then we call
- * gtk_window_resize.
- */
-
- /*
- * We start by retrieving the current size of the whole window.
- * Adding a bit to _that_ will give us a value we can use as a
- * bogus size request which guarantees to be bigger than the
- * current size of the drawing area.
- */
- get_window_pixel_size(inst, &large_x, &large_y);
- large_x += 32;
- large_y += 32;
-
- gtk_widget_set_size_request(inst->area, large_x, large_y);
- gtk_widget_size_request(inst->area, &inner);
- gtk_widget_size_request(inst->window, &outer);
-
- offset_x = outer.width - inner.width;
- offset_y = outer.height - inner.height;
-
- area_x = inst->font_width * w + 2*inst->window_border;
- area_y = inst->font_height * h + 2*inst->window_border;
-
- /*
- * Now we must set the size request on the drawing area back to
- * something sensible before we commit the real resize. Best
- * way to do this, I think, is to set it to what the size is
- * really going to end up being.
- */
- gtk_widget_set_size_request(inst->area, area_x, area_y);
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_window_resize(GTK_WINDOW(inst->window),
- area_x + offset_x, area_y + offset_y);
-#else
- gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), area_x, area_y);
- /*
- * I can no longer remember what this call to
- * gtk_container_dequeue_resize_handler is for. It was
- * introduced in r3092 with no comment, and the commit log
- * message was uninformative. I'm _guessing_ its purpose is to
- * prevent gratuitous resize processing on the window given
- * that we're about to resize it anyway, but I have no idea
- * why that's so incredibly vital.
- *
- * I've tried removing the call, and nothing seems to go
- * wrong. I've backtracked to r3092 and tried removing the
- * call there, and still nothing goes wrong. So I'm going to
- * adopt the working hypothesis that it's superfluous; I won't
- * actually remove it from the GTK 1.2 code, but I won't
- * attempt to replicate its functionality in the GTK 2 code
- * above.
- */
- gtk_container_dequeue_resize_handler(GTK_CONTAINER(inst->window));
- gdk_window_resize(gtk_widget_get_window(inst->window),
- area_x + offset_x, area_y + offset_y);
-#endif
-
-#else /* GTK_CHECK_VERSION(3,0,0) */
-
- int wp, hp;
- compute_whole_window_size(inst, w, h, &wp, &hp);
- gtk_window_resize(GTK_WINDOW(inst->window), wp, hp);
-
-#endif
-
-}
-
-#if GTK_CHECK_VERSION(3,0,0)
-char *colour_to_css(const GdkColor *col)
-{
- GdkRGBA rgba;
- rgba.red = col->red / 65535.0;
- rgba.green = col->green / 65535.0;
- rgba.blue = col->blue / 65535.0;
- rgba.alpha = 1.0;
- return gdk_rgba_to_string(&rgba);
-}
-#endif
-
-void set_gtk_widget_background(GtkWidget *widget, const GdkColor *col)
-{
-#if GTK_CHECK_VERSION(3,0,0)
- GtkCssProvider *provider = gtk_css_provider_new();
- char *col_css = colour_to_css(col);
- char *data = dupprintf(
- "#drawing-area, #top-level { background-color: %s; }\n", col_css);
- gtk_css_provider_load_from_data(provider, data, -1, NULL);
- GtkStyleContext *context = gtk_widget_get_style_context(widget);
- gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider),
- GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
- free(data);
- free(col_css);
-#else
- if (gtk_widget_get_window(widget)) {
- /* For GTK1, which doesn't have a 'const' on
- * gdk_window_set_background's second parameter type. */
- GdkColor col_mutable = *col;
- gdk_window_set_background(gtk_widget_get_window(widget), &col_mutable);
- }
-#endif
-}
-
-void set_window_background(GtkFrontend *inst)
-{
- if (inst->area)
- set_gtk_widget_background(GTK_WIDGET(inst->area), &inst->cols[258]);
- if (inst->window)
- set_gtk_widget_background(GTK_WIDGET(inst->window), &inst->cols[258]);
-}
-
-static void gtkwin_palette_set(TermWin *tw, unsigned start, unsigned ncolours,
- const rgb *colours)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
-
- assert(start <= OSC4_NCOLOURS);
- assert(ncolours <= OSC4_NCOLOURS - start);
-
-#if !GTK_CHECK_VERSION(3,0,0)
- if (!inst->colmap) {
- inst->colmap = gdk_colormap_get_system();
- } else {
- gdk_colormap_free_colors(inst->colmap, inst->cols, OSC4_NCOLOURS);
- }
-#endif
-
- for (unsigned i = 0; i < ncolours; i++) {
- const rgb *in = &colours[i];
- GdkColor *out = &inst->cols[start + i];
-
- out->red = in->r * 0x0101;
- out->green = in->g * 0x0101;
- out->blue = in->b * 0x0101;
- }
-
-#if !GTK_CHECK_VERSION(3,0,0)
- {
- gboolean success[OSC4_NCOLOURS];
- gdk_colormap_alloc_colors(inst->colmap, inst->cols + start,
- ncolours, false, true, success);
- for (unsigned i = 0; i < ncolours; i++) {
- if (!success[i])
- g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n",
- appname, start + i,
- conf_get_int_int(inst->conf, CONF_colours, i*3+0),
- conf_get_int_int(inst->conf, CONF_colours, i*3+1),
- conf_get_int_int(inst->conf, CONF_colours, i*3+2));
- }
- }
-#endif
-
- if (start <= OSC4_COLOUR_bg && OSC4_COLOUR_bg < start + ncolours) {
- /* Default Background has changed, so ensure that space between text
- * area and window border is refreshed. */
- set_window_background(inst);
- if (inst->area && gtk_widget_get_window(inst->area)) {
- draw_backing_rect(inst);
- gtk_widget_queue_draw(inst->area);
- }
- }
-}
-
-static void gtkwin_palette_get_overrides(TermWin *tw, Terminal *term)
-{
- /* GTK has no analogue of Windows's 'standard system colours', so GTK PuTTY
- * has no config option to override the normally configured colours from
- * it */
-}
-
-static struct clipboard_state *clipboard_from_atom(
- GtkFrontend *inst, GdkAtom atom)
-{
- int i;
-
- for (i = 0; i < N_CLIPBOARDS; i++) {
- struct clipboard_state *state = &inst->clipstates[i];
- if (state->inst == inst && state->atom == atom)
- return state;
- }
-
- return NULL;
-}
-
-#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
-
-/* ----------------------------------------------------------------------
- * Clipboard handling, using the high-level GtkClipboard interface in
- * as hands-off a way as possible. We write and read the clipboard as
- * UTF-8 text, and let GTK deal with converting to any other text
- * formats it feels like.
- */
-
-void set_clipboard_atom(GtkFrontend *inst, int clipboard, GdkAtom atom)
-{
- struct clipboard_state *state = &inst->clipstates[clipboard];
-
- state->inst = inst;
- state->clipboard = clipboard;
- state->atom = atom;
-
- if (state->atom != GDK_NONE) {
- state->gtkclipboard = gtk_clipboard_get_for_display(
- gdk_display_get_default(), state->atom);
- g_object_set_data(G_OBJECT(state->gtkclipboard), "user-data", state);
- } else {
- state->gtkclipboard = NULL;
- }
-}
-
-int init_clipboard(GtkFrontend *inst)
-{
- set_clipboard_atom(inst, CLIP_PRIMARY, GDK_SELECTION_PRIMARY);
- set_clipboard_atom(inst, CLIP_CLIPBOARD, clipboard_atom);
- return true;
-}
-
-static void clipboard_provide_data(GtkClipboard *clipboard,
- GtkSelectionData *selection_data,
- guint info, gpointer data)
-{
- struct clipboard_data_instance *cdi =
- (struct clipboard_data_instance *)data;
-
- if (cdi->state && cdi->state->current_cdi == cdi) {
- gtk_selection_data_set_text(selection_data, cdi->pasteout_data_utf8,
- cdi->pasteout_data_utf8_len);
- }
-}
-
-static void clipboard_clear(GtkClipboard *clipboard, gpointer data)
-{
- struct clipboard_data_instance *cdi =
- (struct clipboard_data_instance *)data;
-
- if (cdi->state && cdi->state->current_cdi == cdi) {
- if (cdi->state->inst && cdi->state->inst->term) {
- term_lost_clipboard_ownership(cdi->state->inst->term,
- cdi->state->clipboard);
- }
- cdi->state->current_cdi = NULL;
- }
- sfree(cdi->pasteout_data_utf8);
- cdi->next->prev = cdi->prev;
- cdi->prev->next = cdi->next;
- sfree(cdi);
-}
-
-static void gtkwin_clip_write(
- TermWin *tw, int clipboard, wchar_t *data, int *attr,
- truecolour *truecolour, int len, bool must_deselect)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- struct clipboard_state *state = &inst->clipstates[clipboard];
- struct clipboard_data_instance *cdi;
-
- if (inst->direct_to_font) {
- /* In this clipboard mode, we just can't paste if we're in
- * direct-to-font mode. Fortunately, that shouldn't be
- * important, because we'll only use this clipboard handling
- * code on systems where that kind of font doesn't exist
- * anyway. */
- return;
- }
-
- if (!state->gtkclipboard)
- return;
-
- cdi = snew(struct clipboard_data_instance);
- cdi->state = state;
- state->current_cdi = cdi;
- cdi->pasteout_data_utf8 = snewn(len*6, char);
- cdi->prev = inst->cdi_headtail.prev;
- cdi->next = &inst->cdi_headtail;
- cdi->next->prev = cdi;
- cdi->prev->next = cdi;
- {
- const wchar_t *tmp = data;
- int tmplen = len;
- cdi->pasteout_data_utf8_len =
- charset_from_unicode(&tmp, &tmplen, cdi->pasteout_data_utf8,
- len*6, CS_UTF8, NULL, NULL, 0);
- }
-
- /*
- * It would be nice to just call gtk_clipboard_set_text() in place
- * of all of the faffing below. Unfortunately, that won't give me
- * access to the clipboard-clear event, which we use to visually
- * deselect text in the terminal.
- */
- {
- GtkTargetList *targetlist;
- GtkTargetEntry *targettable;
- gint n_targets;
-
- targetlist = gtk_target_list_new(NULL, 0);
- gtk_target_list_add_text_targets(targetlist, 0);
- targettable = gtk_target_table_new_from_list(targetlist, &n_targets);
- gtk_clipboard_set_with_data(state->gtkclipboard, targettable,
- n_targets, clipboard_provide_data,
- clipboard_clear, cdi);
- gtk_target_table_free(targettable, n_targets);
- gtk_target_list_unref(targetlist);
- }
-}
-
-static void clipboard_text_received(GtkClipboard *clipboard,
- const gchar *text, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- wchar_t *paste;
- int paste_len;
- int length;
-
- if (!text)
- return;
-
- length = strlen(text);
-
- paste = snewn(length, wchar_t);
- paste_len = mb_to_wc(CS_UTF8, 0, text, length, paste, length);
-
- term_do_paste(inst->term, paste, paste_len);
-
- sfree(paste);
-}
-
-static void gtkwin_clip_request_paste(TermWin *tw, int clipboard)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- struct clipboard_state *state = &inst->clipstates[clipboard];
-
- if (!state->gtkclipboard)
- return;
-
- gtk_clipboard_request_text(state->gtkclipboard,
- clipboard_text_received, inst);
-}
-
-#else /* JUST_USE_GTK_CLIPBOARD_UTF8 */
-
-/* ----------------------------------------------------------------------
- * Clipboard handling for X, using the low-level gtk_selection_*
- * interface, handling conversions to fiddly things like compound text
- * ourselves, and storing in X cut buffers too.
- *
- * This version of the clipboard code has to be kept around for GTK1,
- * which doesn't have the higher-level GtkClipboard interface at all.
- * And since it works on GTK2 and GTK3 too and has had a good few
- * years of shakedown and bug fixing, we might as well keep using it
- * where it's applicable.
- *
- * It's _possible_ that we might be able to replicate all the
- * important wrinkles of this code in GtkClipboard. (In particular,
- * cut buffers or local analogue look as if they might be accessible
- * via gtk_clipboard_set_can_store(), and delivering text in
- * non-Unicode formats only in the direct-to-font case ought to be
- * possible if we can figure out the right set of things to put in the
- * GtkTargetList.) But that work can wait until there's a need for it!
- */
-
-#ifndef NOT_X_WINDOWS
-
-/* Store the data in a cut-buffer. */
-static void store_cutbuffer(GtkFrontend *inst, char *ptr, int len)
-{
- if (inst->disp) {
- /* ICCCM says we must rotate the buffers before storing to buffer 0. */
- XRotateBuffers(inst->disp, 1);
- XStoreBytes(inst->disp, ptr, len);
- }
-}
-
-/* Retrieve data from a cut-buffer.
- * Returned data needs to be freed with XFree().
- */
-static char *retrieve_cutbuffer(GtkFrontend *inst, int *nbytes)
-{
- char *ptr;
- if (!inst->disp) {
- *nbytes = 0;
- return NULL;
- }
- ptr = XFetchBytes(inst->disp, nbytes);
- if (*nbytes <= 0 && ptr != 0) {
- XFree(ptr);
- ptr = 0;
- }
- return ptr;
-}
-
-#endif /* NOT_X_WINDOWS */
-
-static void gtkwin_clip_write(
- TermWin *tw, int clipboard, wchar_t *data, int *attr,
- truecolour *truecolour, int len, bool must_deselect)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- struct clipboard_state *state = &inst->clipstates[clipboard];
-
- if (state->pasteout_data)
- sfree(state->pasteout_data);
- if (state->pasteout_data_ctext)
- sfree(state->pasteout_data_ctext);
- if (state->pasteout_data_utf8)
- sfree(state->pasteout_data_utf8);
-
- /*
- * Set up UTF-8 and compound text paste data. This only happens
- * if we aren't in direct-to-font mode using the D800 hack.
- */
- if (!inst->direct_to_font) {
- const wchar_t *tmp = data;
- int tmplen = len;
-#ifndef NOT_X_WINDOWS
- XTextProperty tp;
- char *list[1];
-#endif
-
- state->pasteout_data_utf8 = snewn(len*6, char);
- state->pasteout_data_utf8_len = len*6;
- state->pasteout_data_utf8_len =
- charset_from_unicode(&tmp, &tmplen, state->pasteout_data_utf8,
- state->pasteout_data_utf8_len,
- CS_UTF8, NULL, NULL, 0);
- if (state->pasteout_data_utf8_len == 0) {
- sfree(state->pasteout_data_utf8);
- state->pasteout_data_utf8 = NULL;
- } else {
- state->pasteout_data_utf8 =
- sresize(state->pasteout_data_utf8,
- state->pasteout_data_utf8_len + 1, char);
- state->pasteout_data_utf8[state->pasteout_data_utf8_len] = '\0';
- }
-
- /*
- * Now let Xlib convert our UTF-8 data into compound text.
- */
-#ifndef NOT_X_WINDOWS
- list[0] = state->pasteout_data_utf8;
- if (inst->disp && Xutf8TextListToTextProperty(
- inst->disp, list, 1, XCompoundTextStyle, &tp) == 0) {
- state->pasteout_data_ctext = snewn(tp.nitems+1, char);
- memcpy(state->pasteout_data_ctext, tp.value, tp.nitems);
- state->pasteout_data_ctext_len = tp.nitems;
- XFree(tp.value);
- } else
-#endif
- {
- state->pasteout_data_ctext = NULL;
- state->pasteout_data_ctext_len = 0;
- }
- } else {
- state->pasteout_data_utf8 = NULL;
- state->pasteout_data_utf8_len = 0;
- state->pasteout_data_ctext = NULL;
- state->pasteout_data_ctext_len = 0;
- }
-
- state->pasteout_data = snewn(len*6, char);
- state->pasteout_data_len = len*6;
- state->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0,
- data, len, state->pasteout_data,
- state->pasteout_data_len,
- NULL, NULL);
- if (state->pasteout_data_len == 0) {
- sfree(state->pasteout_data);
- state->pasteout_data = NULL;
- } else {
- state->pasteout_data =
- sresize(state->pasteout_data, state->pasteout_data_len, char);
- }
-
-#ifndef NOT_X_WINDOWS
- /* The legacy X cut buffers go with PRIMARY, not any other clipboard */
- if (state->atom == GDK_SELECTION_PRIMARY)
- store_cutbuffer(inst, state->pasteout_data, state->pasteout_data_len);
-#endif
-
- if (gtk_selection_owner_set(inst->area, state->atom,
- inst->input_event_time)) {
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_selection_clear_targets(inst->area, state->atom);
-#endif
- gtk_selection_add_target(inst->area, state->atom,
- GDK_SELECTION_TYPE_STRING, 1);
- if (state->pasteout_data_ctext)
- gtk_selection_add_target(inst->area, state->atom,
- compound_text_atom, 1);
- if (state->pasteout_data_utf8)
- gtk_selection_add_target(inst->area, state->atom,
- utf8_string_atom, 1);
- }
-
- if (must_deselect)
- term_lost_clipboard_ownership(inst->term, clipboard);
-}
-
-static void selection_get(GtkWidget *widget, GtkSelectionData *seldata,
- guint info, guint time_stamp, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- GdkAtom target = gtk_selection_data_get_target(seldata);
- struct clipboard_state *state = clipboard_from_atom(
- inst, gtk_selection_data_get_selection(seldata));
-
- if (!state)
- return;
-
- if (target == utf8_string_atom)
- gtk_selection_data_set(seldata, target, 8,
- (unsigned char *)state->pasteout_data_utf8,
- state->pasteout_data_utf8_len);
- else if (target == compound_text_atom)
- gtk_selection_data_set(seldata, target, 8,
- (unsigned char *)state->pasteout_data_ctext,
- state->pasteout_data_ctext_len);
- else
- gtk_selection_data_set(seldata, target, 8,
- (unsigned char *)state->pasteout_data,
- state->pasteout_data_len);
-}
-
-static gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
- gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- struct clipboard_state *state = clipboard_from_atom(
- inst, seldata->selection);
-
- if (!state)
- return true;
-
- term_lost_clipboard_ownership(inst->term, state->clipboard);
- if (state->pasteout_data)
- sfree(state->pasteout_data);
- if (state->pasteout_data_ctext)
- sfree(state->pasteout_data_ctext);
- if (state->pasteout_data_utf8)
- sfree(state->pasteout_data_utf8);
- state->pasteout_data = NULL;
- state->pasteout_data_len = 0;
- state->pasteout_data_ctext = NULL;
- state->pasteout_data_ctext_len = 0;
- state->pasteout_data_utf8 = NULL;
- state->pasteout_data_utf8_len = 0;
- return true;
-}
-
-static void gtkwin_clip_request_paste(TermWin *tw, int clipboard)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- struct clipboard_state *state = &inst->clipstates[clipboard];
-
- /*
- * In Unix, pasting is asynchronous: all we can do at the
- * moment is to call gtk_selection_convert(), and when the data
- * comes back _then_ we can call term_do_paste().
- */
-
- if (!inst->direct_to_font) {
- /*
- * First we attempt to retrieve the selection as a UTF-8
- * string (which we will convert to the correct code page
- * before sending to the session, of course). If that
- * fails, selection_received() will be informed and will
- * fall back to an ordinary string.
- */
- gtk_selection_convert(inst->area, state->atom, utf8_string_atom,
- inst->input_event_time);
- } else {
- /*
- * If we're in direct-to-font mode, we disable UTF-8
- * pasting, and go straight to ordinary string data.
- */
- gtk_selection_convert(inst->area, state->atom,
- GDK_SELECTION_TYPE_STRING,
- inst->input_event_time);
- }
-}
-
-static void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
- guint time, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- char *text;
- int length;
-#ifndef NOT_X_WINDOWS
- char **list;
- bool free_list_required = false;
- bool free_required = false;
-#endif
- int charset;
- GdkAtom seldata_target = gtk_selection_data_get_target(seldata);
- GdkAtom seldata_type = gtk_selection_data_get_data_type(seldata);
- const guchar *seldata_data = gtk_selection_data_get_data(seldata);
- gint seldata_length = gtk_selection_data_get_length(seldata);
- wchar_t *paste;
- int paste_len;
- struct clipboard_state *state = clipboard_from_atom(
- inst, gtk_selection_data_get_selection(seldata));
-
- if (!state)
- return;
-
- if (seldata_target == utf8_string_atom && seldata_length <= 0) {
- /*
- * Failed to get a UTF-8 selection string. Try compound
- * text next.
- */
- gtk_selection_convert(inst->area, state->atom,
- compound_text_atom,
- inst->input_event_time);
- return;
- }
-
- if (seldata_target == compound_text_atom && seldata_length <= 0) {
- /*
- * Failed to get UTF-8 or compound text. Try an ordinary
- * string.
- */
- gtk_selection_convert(inst->area, state->atom,
- GDK_SELECTION_TYPE_STRING,
- inst->input_event_time);
- return;
- }
-
- /*
- * If we have data, but it's not of a type we can deal with,
- * we have to ignore the data.
- */
- if (seldata_length > 0 &&
- seldata_type != GDK_SELECTION_TYPE_STRING &&
- seldata_type != compound_text_atom &&
- seldata_type != utf8_string_atom)
- return;
-
- /*
- * If we have no data, try looking in a cut buffer.
- */
- if (seldata_length <= 0) {
-#ifndef NOT_X_WINDOWS
- text = retrieve_cutbuffer(inst, &length);
- if (length == 0)
- return;
- /* Xterm is rumoured to expect Latin-1, though I havn't checked the
- * source, so use that as a de-facto standard. */
- charset = CS_ISO8859_1;
- free_required = true;
-#else
- return;
-#endif
- } else {
- /*
- * Convert COMPOUND_TEXT into UTF-8.
- */
- if (seldata_type == compound_text_atom) {
-#ifndef NOT_X_WINDOWS
- XTextProperty tp;
- int ret, count;
-
- tp.value = (unsigned char *)seldata_data;
- tp.encoding = (Atom) seldata_type;
- tp.format = gtk_selection_data_get_format(seldata);
- tp.nitems = seldata_length;
- ret = inst->disp == NULL ? -1 :
- Xutf8TextPropertyToTextList(inst->disp, &tp, &list, &count);
- if (ret == 0 && count == 1) {
- text = list[0];
- length = strlen(list[0]);
- charset = CS_UTF8;
- free_list_required = true;
- } else
-#endif
- {
- /*
- * Compound text failed; fall back to STRING.
- */
- gtk_selection_convert(inst->area, state->atom,
- GDK_SELECTION_TYPE_STRING,
- inst->input_event_time);
- return;
- }
- } else {
- text = (char *)seldata_data;
- length = seldata_length;
- charset = (seldata_type == utf8_string_atom ?
- CS_UTF8 : inst->ucsdata.line_codepage);
- }
- }
-
- paste = snewn(length, wchar_t);
- paste_len = mb_to_wc(charset, 0, text, length, paste, length);
-
- term_do_paste(inst->term, paste, paste_len);
-
- sfree(paste);
-
-#ifndef NOT_X_WINDOWS
- if (free_list_required)
- XFreeStringList(list);
- if (free_required)
- XFree(text);
-#endif
-}
-
-static void init_one_clipboard(GtkFrontend *inst, int clipboard)
-{
- struct clipboard_state *state = &inst->clipstates[clipboard];
-
- state->inst = inst;
- state->clipboard = clipboard;
-}
-
-void set_clipboard_atom(GtkFrontend *inst, int clipboard, GdkAtom atom)
-{
- struct clipboard_state *state = &inst->clipstates[clipboard];
-
- state->inst = inst;
- state->clipboard = clipboard;
-
- state->atom = atom;
-}
-
-void init_clipboard(GtkFrontend *inst)
-{
-#ifndef NOT_X_WINDOWS
- /*
- * Ensure that all the cut buffers exist - according to the ICCCM,
- * we must do this before we start using cut buffers.
- */
- if (inst->disp) {
- unsigned char empty[] = "";
- x11_ignore_error(inst->disp, BadMatch);
- XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER0,
- XA_STRING, 8, PropModeAppend, empty, 0);
- x11_ignore_error(inst->disp, BadMatch);
- XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER1,
- XA_STRING, 8, PropModeAppend, empty, 0);
- x11_ignore_error(inst->disp, BadMatch);
- XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER2,
- XA_STRING, 8, PropModeAppend, empty, 0);
- x11_ignore_error(inst->disp, BadMatch);
- XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER3,
- XA_STRING, 8, PropModeAppend, empty, 0);
- x11_ignore_error(inst->disp, BadMatch);
- XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER4,
- XA_STRING, 8, PropModeAppend, empty, 0);
- x11_ignore_error(inst->disp, BadMatch);
- XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER5,
- XA_STRING, 8, PropModeAppend, empty, 0);
- x11_ignore_error(inst->disp, BadMatch);
- XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER6,
- XA_STRING, 8, PropModeAppend, empty, 0);
- x11_ignore_error(inst->disp, BadMatch);
- XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER7,
- XA_STRING, 8, PropModeAppend, empty, 0);
- }
-#endif
-
- inst->clipstates[CLIP_PRIMARY].atom = GDK_SELECTION_PRIMARY;
- inst->clipstates[CLIP_CLIPBOARD].atom = clipboard_atom;
- init_one_clipboard(inst, CLIP_PRIMARY);
- init_one_clipboard(inst, CLIP_CLIPBOARD);
-
- g_signal_connect(G_OBJECT(inst->area), "selection_received",
- G_CALLBACK(selection_received), inst);
- g_signal_connect(G_OBJECT(inst->area), "selection_get",
- G_CALLBACK(selection_get), inst);
- g_signal_connect(G_OBJECT(inst->area), "selection_clear_event",
- G_CALLBACK(selection_clear), inst);
-}
-
-/*
- * End of selection/clipboard handling.
- * ----------------------------------------------------------------------
- */
-
-#endif /* JUST_USE_GTK_CLIPBOARD_UTF8 */
-
-static void set_window_titles(GtkFrontend *inst)
-{
- /*
- * We must always call set_icon_name after calling set_title,
- * since set_title will write both names. Irritating, but such
- * is life.
- */
- gtk_window_set_title(GTK_WINDOW(inst->window), inst->wintitle);
- if (!conf_get_bool(inst->conf, CONF_win_name_always))
- gdk_window_set_icon_name(gtk_widget_get_window(inst->window),
- inst->icontitle);
-}
-
-static void gtkwin_set_title(TermWin *tw, const char *title)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- sfree(inst->wintitle);
- inst->wintitle = dupstr(title);
- set_window_titles(inst);
-}
-
-static void gtkwin_set_icon_title(TermWin *tw, const char *title)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- sfree(inst->icontitle);
- inst->icontitle = dupstr(title);
- set_window_titles(inst);
-}
-
-static void gtkwin_set_scrollbar(TermWin *tw, int total, int start, int page)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- if (!conf_get_bool(inst->conf, CONF_scrollbar))
- return;
- inst->ignore_sbar = true;
- gtk_adjustment_set_lower(inst->sbar_adjust, 0);
- gtk_adjustment_set_upper(inst->sbar_adjust, total);
- gtk_adjustment_set_value(inst->sbar_adjust, start);
- gtk_adjustment_set_page_size(inst->sbar_adjust, page);
- gtk_adjustment_set_step_increment(inst->sbar_adjust, 1);
- gtk_adjustment_set_page_increment(inst->sbar_adjust, page/2);
-#if !GTK_CHECK_VERSION(3,18,0)
- gtk_adjustment_changed(inst->sbar_adjust);
-#endif
- inst->ignore_sbar = false;
-}
-
-void scrollbar_moved(GtkAdjustment *adj, GtkFrontend *inst)
-{
- if (!conf_get_bool(inst->conf, CONF_scrollbar))
- return;
- if (!inst->ignore_sbar)
- term_scroll(inst->term, 1, (int)gtk_adjustment_get_value(adj));
-}
-
-static void show_scrollbar(GtkFrontend *inst, gboolean visible)
-{
- inst->sbar_visible = visible;
- if (visible)
- gtk_widget_show(inst->sbar);
- else
- gtk_widget_hide(inst->sbar);
-}
-
-static void gtkwin_set_cursor_pos(TermWin *tw, int x, int y)
-{
- /*
- * This is meaningless under X.
- */
-}
-
-/*
- * This is still called when mode==BELL_VISUAL, even though the
- * visual bell is handled entirely within terminal.c, because we
- * may want to perform additional actions on any kind of bell (for
- * example, taskbar flashing in Windows).
- */
-static void gtkwin_bell(TermWin *tw, int mode)
-{
- /* GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); */
- if (mode == BELL_DEFAULT)
- gdk_display_beep(gdk_display_get_default());
-}
-
-static int gtkwin_char_width(TermWin *tw, int uc)
-{
- /*
- * In this front end, double-width characters are handled using a
- * separate font, so this can safely just return 1 always.
- */
- return 1;
-}
-
-static bool gtkwin_setup_draw_ctx(TermWin *tw)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
-
- if (!gtk_widget_get_window(inst->area))
- return false;
-
- inst->uctx.type = inst->drawtype;
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
- /* If we're doing GDK-based drawing, then we also expect
- * inst->pixmap to exist. */
- inst->uctx.u.gdk.target = inst->pixmap;
- inst->uctx.u.gdk.gc = gdk_gc_new(gtk_widget_get_window(inst->area));
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- inst->uctx.u.cairo.widget = GTK_WIDGET(inst->area);
- /* If we're doing Cairo drawing, we expect inst->surface to
- * exist, and we draw to that first, regardless of whether we
- * subsequently copy the results to inst->pixmap. */
- inst->uctx.u.cairo.cr = cairo_create(inst->surface);
- cairo_scale(inst->uctx.u.cairo.cr, inst->scale, inst->scale);
- cairo_setup_draw_ctx(inst);
- }
-#endif
- return true;
-}
-
-static void gtkwin_free_draw_ctx(TermWin *tw)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
- gdk_gc_unref(inst->uctx.u.gdk.gc);
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_destroy(inst->uctx.u.cairo.cr);
- }
-#endif
-}
-
-
-static void draw_update(GtkFrontend *inst, int x, int y, int w, int h)
-{
-#if defined DRAW_TEXT_CAIRO && !defined NO_BACKING_PIXMAPS
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- /*
- * If inst->surface and inst->pixmap both exist, then we've
- * just drawn new content to the former which we must copy to
- * the latter.
- */
- cairo_t *cr = gdk_cairo_create(inst->pixmap);
- cairo_set_source_surface(cr, inst->surface, 0, 0);
- cairo_rectangle(cr, x, y, w, h);
- cairo_fill(cr);
- cairo_destroy(cr);
- }
-#endif
-
- /*
- * Now we just queue a window redraw, which will cause
- * inst->surface or inst->pixmap (whichever is appropriate for our
- * compile mode) to be copied to the real window when we receive
- * the resulting "expose" or "draw" event.
- *
- * Amazingly, this one API call is actually valid in all versions
- * of GTK :-)
- */
- gtk_widget_queue_draw_area(inst->area, x, y, w, h);
-}
-
-#ifdef DRAW_TEXT_CAIRO
-static void cairo_set_source_rgb_dim(cairo_t *cr, double r, double g, double b,
- bool dim)
-{
- if (dim)
- cairo_set_source_rgb(cr, r * 2 / 3, g * 2 / 3, b * 2 / 3);
- else
- cairo_set_source_rgb(cr, r, g, b);
-}
-#endif
-
-static void draw_set_colour(GtkFrontend *inst, int col, bool dim)
-{
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
- if (dim) {
-#if GTK_CHECK_VERSION(2,0,0)
- GdkColor color;
- color.red = inst->cols[col].red * 2 / 3;
- color.green = inst->cols[col].green * 2 / 3;
- color.blue = inst->cols[col].blue * 2 / 3;
- gdk_gc_set_rgb_fg_color(inst->uctx.u.gdk.gc, &color);
-#else
- /* Poor GTK1 fallback */
- gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[col]);
-#endif
- } else {
- gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[col]);
- }
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_set_source_rgb_dim(inst->uctx.u.cairo.cr,
- inst->cols[col].red / 65535.0,
- inst->cols[col].green / 65535.0,
- inst->cols[col].blue / 65535.0, dim);
- }
-#endif
-}
-
-static void draw_set_colour_rgb(GtkFrontend *inst, optionalrgb orgb, bool dim)
-{
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
-#if GTK_CHECK_VERSION(2,0,0)
- GdkColor color;
- color.red = orgb.r * 256;
- color.green = orgb.g * 256;
- color.blue = orgb.b * 256;
- if (dim) {
- color.red = color.red * 2 / 3;
- color.green = color.green * 2 / 3;
- color.blue = color.blue * 2 / 3;
- }
- gdk_gc_set_rgb_fg_color(inst->uctx.u.gdk.gc, &color);
-#else
- /* Poor GTK1 fallback */
- gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[256]);
-#endif
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_set_source_rgb_dim(inst->uctx.u.cairo.cr, orgb.r / 255.0,
- orgb.g / 255.0, orgb.b / 255.0, dim);
- }
-#endif
-}
-
-static void draw_rectangle(GtkFrontend *inst, bool filled,
- int x, int y, int w, int h)
-{
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
- gdk_draw_rectangle(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc,
- filled, x, y, w, h);
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_new_path(inst->uctx.u.cairo.cr);
- if (filled) {
- cairo_rectangle(inst->uctx.u.cairo.cr, x, y, w, h);
- cairo_fill(inst->uctx.u.cairo.cr);
- } else {
- cairo_rectangle(inst->uctx.u.cairo.cr,
- x + 0.5, y + 0.5, w, h);
- cairo_close_path(inst->uctx.u.cairo.cr);
- cairo_stroke(inst->uctx.u.cairo.cr);
- }
- }
-#endif
-}
-
-static void draw_clip(GtkFrontend *inst, int x, int y, int w, int h)
-{
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
- GdkRectangle r;
-
- r.x = x;
- r.y = y;
- r.width = w;
- r.height = h;
-
- gdk_gc_set_clip_rectangle(inst->uctx.u.gdk.gc, &r);
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_reset_clip(inst->uctx.u.cairo.cr);
- cairo_new_path(inst->uctx.u.cairo.cr);
- cairo_rectangle(inst->uctx.u.cairo.cr, x, y, w, h);
- cairo_clip(inst->uctx.u.cairo.cr);
- }
-#endif
-}
-
-static void draw_point(GtkFrontend *inst, int x, int y)
-{
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
- gdk_draw_point(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, x, y);
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_new_path(inst->uctx.u.cairo.cr);
- cairo_rectangle(inst->uctx.u.cairo.cr, x, y, 1, 1);
- cairo_fill(inst->uctx.u.cairo.cr);
- }
-#endif
-}
-
-static void draw_line(GtkFrontend *inst, int x0, int y0, int x1, int y1)
-{
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
- gdk_draw_line(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc,
- x0, y0, x1, y1);
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_new_path(inst->uctx.u.cairo.cr);
- cairo_move_to(inst->uctx.u.cairo.cr, x0 + 0.5, y0 + 0.5);
- cairo_line_to(inst->uctx.u.cairo.cr, x1 + 0.5, y1 + 0.5);
- cairo_stroke(inst->uctx.u.cairo.cr);
- }
-#endif
-}
-
-static void draw_stretch_before(GtkFrontend *inst, int x, int y,
- int w, bool wdouble,
- int h, bool hdouble, bool hbothalf)
-{
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_matrix_t matrix;
-
- matrix.xy = 0;
- matrix.yx = 0;
-
- if (wdouble) {
- matrix.xx = 2;
- matrix.x0 = -x;
- } else {
- matrix.xx = 1;
- matrix.x0 = 0;
- }
-
- if (hdouble) {
- matrix.yy = 2;
- if (hbothalf) {
- matrix.y0 = -(y+h);
- } else {
- matrix.y0 = -y;
- }
- } else {
- matrix.yy = 1;
- matrix.y0 = 0;
- }
- cairo_transform(inst->uctx.u.cairo.cr, &matrix);
- }
-#endif
-}
-
-static void draw_stretch_after(GtkFrontend *inst, int x, int y,
- int w, bool wdouble,
- int h, bool hdouble, bool hbothalf)
-{
-#ifdef DRAW_TEXT_GDK
-#ifndef NO_BACKING_PIXMAPS
- if (inst->uctx.type == DRAWTYPE_GDK) {
- /*
- * I can't find any plausible StretchBlt equivalent in the X
- * server, so I'm going to do this the slow and painful way.
- * This will involve repeated calls to gdk_draw_pixmap() to
- * stretch the text horizontally. It's O(N^2) in time and O(N)
- * in network bandwidth, but you try thinking of a better way.
- * :-(
- */
- int i;
- if (wdouble) {
- for (i = 0; i < w; i++) {
- gdk_draw_pixmap(inst->uctx.u.gdk.target,
- inst->uctx.u.gdk.gc,
- inst->uctx.u.gdk.target,
- x + 2*i, y,
- x + 2*i+1, y,
- w - i, h);
- }
- w *= 2;
- }
-
- if (hdouble) {
- int dt, db;
- /* Now stretch vertically, in the same way. */
- if (hbothalf)
- dt = 0, db = 1;
- else
- dt = 1, db = 0;
- for (i = 0; i < h; i += 2) {
- gdk_draw_pixmap(inst->uctx.u.gdk.target,
- inst->uctx.u.gdk.gc,
- inst->uctx.u.gdk.target,
- x, y + dt*i + db,
- x, y + dt*(i+1),
- w, h-i-1);
- }
- }
- }
-#else
-#error No way to implement stretching in GDK without a reliable backing pixmap
-#endif
-#endif /* DRAW_TEXT_GDK */
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- cairo_set_matrix(inst->uctx.u.cairo.cr,
- &inst->uctx.u.cairo.origmatrix);
- }
-#endif
-}
-
-static void draw_backing_rect(GtkFrontend *inst)
-{
- int w, h;
-
- if (!win_setup_draw_ctx(&inst->termwin))
- return;
-
- w = inst->width * inst->font_width + 2*inst->window_border;
- h = inst->height * inst->font_height + 2*inst->window_border;
- draw_set_colour(inst, 258, false);
- draw_rectangle(inst, true, 0, 0, w, h);
- draw_update(inst, 0, 0, w, h);
- win_free_draw_ctx(&inst->termwin);
-}
-
-/*
- * Draw a line of text in the window, at given character
- * coordinates, in given attributes.
- *
- * We are allowed to fiddle with the contents of `text'.
- */
-static void do_text_internal(
- GtkFrontend *inst, int x, int y, wchar_t *text, int len,
- unsigned long attr, int lattr, truecolour truecolour)
-{
- int ncombining;
- int nfg, nbg, t, fontid, rlen, widefactor;
- bool bold;
- bool monochrome =
- gdk_visual_get_depth(gtk_widget_get_visual(inst->area)) == 1;
-
- if (attr & TATTR_COMBINING) {
- ncombining = len;
- len = 1;
- } else
- ncombining = 1;
-
- if (monochrome)
- truecolour.fg = truecolour.bg = optionalrgb_none;
-
- nfg = ((monochrome ? ATTR_DEFFG : (attr & ATTR_FGMASK)) >> ATTR_FGSHIFT);
- nbg = ((monochrome ? ATTR_DEFBG : (attr & ATTR_BGMASK)) >> ATTR_BGSHIFT);
- if (!!(attr & ATTR_REVERSE) ^ (monochrome && (attr & TATTR_ACTCURS))) {
- struct optionalrgb trgb;
-
- t = nfg;
- nfg = nbg;
- nbg = t;
-
- trgb = truecolour.fg;
- truecolour.fg = truecolour.bg;
- truecolour.bg = trgb;
- }
- if ((inst->bold_style & 2) && (attr & ATTR_BOLD)) {
- if (nfg < 16) nfg |= 8;
- else if (nfg >= 256) nfg |= 1;
- }
- if ((inst->bold_style & 2) && (attr & ATTR_BLINK)) {
- if (nbg < 16) nbg |= 8;
- else if (nbg >= 256) nbg |= 1;
- }
- if ((attr & TATTR_ACTCURS) && !monochrome) {
- truecolour.fg = truecolour.bg = optionalrgb_none;
- nfg = 260;
- nbg = 261;
- attr &= ~ATTR_DIM; /* don't dim the cursor */
- }
-
- fontid = 0;
-
- if (attr & ATTR_WIDE) {
- widefactor = 2;
- fontid |= 2;
- } else {
- widefactor = 1;
- }
-
- if ((attr & ATTR_BOLD) && (inst->bold_style & 1)) {
- bold = true;
- fontid |= 1;
- } else {
- bold = false;
- }
-
- if (!inst->fonts[fontid]) {
- int i;
- /*
- * Fall back through font ids with subsets of this one's
- * set bits, in order.
- */
- for (i = fontid; i-- > 0 ;) {
- if (i & ~fontid)
- continue; /* some other bit is set */
- if (inst->fonts[i]) {
- fontid = i;
- break;
- }
- }
- assert(inst->fonts[fontid]); /* we should at least have hit zero */
- }
-
- if ((lattr & LATTR_MODE) != LATTR_NORM) {
- x *= 2;
- if (x >= inst->term->cols)
- return;
- if (x + len*2*widefactor > inst->term->cols) {
- len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */
- if (len == 0)
- return; /* rounded down half a double-width char to zero */
- }
- rlen = len * 2;
- } else
- rlen = len;
-
- draw_clip(inst,
- x*inst->font_width+inst->window_border,
- y*inst->font_height+inst->window_border,
- rlen*widefactor*inst->font_width,
- inst->font_height);
-
- if ((lattr & LATTR_MODE) != LATTR_NORM) {
- draw_stretch_before(inst,
- x*inst->font_width+inst->window_border,
- y*inst->font_height+inst->window_border,
- rlen*widefactor*inst->font_width, true,
- inst->font_height,
- ((lattr & LATTR_MODE) != LATTR_WIDE),
- ((lattr & LATTR_MODE) == LATTR_BOT));
- }
-
- if (truecolour.bg.enabled)
- draw_set_colour_rgb(inst, truecolour.bg, attr & ATTR_DIM);
- else
- draw_set_colour(inst, nbg, attr & ATTR_DIM);
- draw_rectangle(inst, true,
- x*inst->font_width+inst->window_border,
- y*inst->font_height+inst->window_border,
- rlen*widefactor*inst->font_width, inst->font_height);
-
- if (truecolour.fg.enabled)
- draw_set_colour_rgb(inst, truecolour.fg, attr & ATTR_DIM);
- else
- draw_set_colour(inst, nfg, attr & ATTR_DIM);
- if (ncombining > 1) {
- assert(len == 1);
- unifont_draw_combining(&inst->uctx, inst->fonts[fontid],
- x*inst->font_width+inst->window_border,
- (y*inst->font_height+inst->window_border+
- inst->fonts[0]->ascent),
- text, ncombining, widefactor > 1,
- bold, inst->font_width);
- } else {
- unifont_draw_text(&inst->uctx, inst->fonts[fontid],
- x*inst->font_width+inst->window_border,
- (y*inst->font_height+inst->window_border+
- inst->fonts[0]->ascent),
- text, len, widefactor > 1,
- bold, inst->font_width);
- }
-
- if (attr & ATTR_UNDER) {
- int uheight = inst->fonts[0]->ascent + 1;
- if (uheight >= inst->font_height)
- uheight = inst->font_height - 1;
- draw_line(inst, x*inst->font_width+inst->window_border,
- y*inst->font_height + uheight + inst->window_border,
- (x+len)*widefactor*inst->font_width-1+inst->window_border,
- y*inst->font_height + uheight + inst->window_border);
- }
-
- if (attr & ATTR_STRIKE) {
- int sheight = inst->fonts[fontid]->strikethrough_y;
- draw_line(inst, x*inst->font_width+inst->window_border,
- y*inst->font_height + sheight + inst->window_border,
- (x+len)*widefactor*inst->font_width-1+inst->window_border,
- y*inst->font_height + sheight + inst->window_border);
- }
-
- if ((lattr & LATTR_MODE) != LATTR_NORM) {
- draw_stretch_after(inst,
- x*inst->font_width+inst->window_border,
- y*inst->font_height+inst->window_border,
- rlen*widefactor*inst->font_width, true,
- inst->font_height,
- ((lattr & LATTR_MODE) != LATTR_WIDE),
- ((lattr & LATTR_MODE) == LATTR_BOT));
- }
-}
-
-static void gtkwin_draw_text(
- TermWin *tw, int x, int y, wchar_t *text, int len,
- unsigned long attr, int lattr, truecolour truecolour)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- int widefactor;
-
- do_text_internal(inst, x, y, text, len, attr, lattr, truecolour);
-
- if (attr & ATTR_WIDE) {
- widefactor = 2;
- } else {
- widefactor = 1;
- }
-
- if ((lattr & LATTR_MODE) != LATTR_NORM) {
- x *= 2;
- if (x >= inst->term->cols)
- return;
- if (x + len*2*widefactor > inst->term->cols)
- len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */
- len *= 2;
- }
-
- draw_update(inst,
- x*inst->font_width+inst->window_border,
- y*inst->font_height+inst->window_border,
- len*widefactor*inst->font_width, inst->font_height);
-}
-
-static void gtkwin_draw_cursor(
- TermWin *tw, int x, int y, wchar_t *text, int len,
- unsigned long attr, int lattr, truecolour truecolour)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
- bool active, passive;
- int widefactor;
-
- if (attr & TATTR_PASCURS) {
- attr &= ~TATTR_PASCURS;
- passive = true;
- } else
- passive = false;
- if ((attr & TATTR_ACTCURS) && inst->cursor_type != 0) {
- attr &= ~TATTR_ACTCURS;
- active = true;
- } else
- active = false;
- do_text_internal(inst, x, y, text, len, attr, lattr, truecolour);
-
- if (attr & TATTR_COMBINING)
- len = 1;
-
- if (attr & ATTR_WIDE) {
- widefactor = 2;
- } else {
- widefactor = 1;
- }
-
- if ((lattr & LATTR_MODE) != LATTR_NORM) {
- x *= 2;
- if (x >= inst->term->cols)
- return;
- if (x + len*2*widefactor > inst->term->cols)
- len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */
- len *= 2;
- }
-
- if (inst->cursor_type == 0) {
- /*
- * An active block cursor will already have been done by
- * the above do_text call, so we only need to do anything
- * if it's passive.
- */
- if (passive) {
- draw_set_colour(inst, 261, false);
- draw_rectangle(inst, false,
- x*inst->font_width+inst->window_border,
- y*inst->font_height+inst->window_border,
- len*widefactor*inst->font_width-1,
- inst->font_height-1);
- }
- } else {
- int uheight;
- int startx, starty, dx, dy, length, i;
-
- int char_width;
-
- if ((attr & ATTR_WIDE) || (lattr & LATTR_MODE) != LATTR_NORM)
- char_width = 2*inst->font_width;
- else
- char_width = inst->font_width;
-
- if (inst->cursor_type == 1) {
- uheight = inst->fonts[0]->ascent + 1;
- if (uheight >= inst->font_height)
- uheight = inst->font_height - 1;
-
- startx = x * inst->font_width + inst->window_border;
- starty = y * inst->font_height + inst->window_border + uheight;
- dx = 1;
- dy = 0;
- length = len * widefactor * char_width;
- } else {
- int xadjust = 0;
- if (attr & TATTR_RIGHTCURS)
- xadjust = char_width - 1;
- startx = x * inst->font_width + inst->window_border + xadjust;
- starty = y * inst->font_height + inst->window_border;
- dx = 0;
- dy = 1;
- length = inst->font_height;
- }
-
- draw_set_colour(inst, 261, false);
- if (passive) {
- for (i = 0; i < length; i++) {
- if (i % 2 == 0) {
- draw_point(inst, startx, starty);
- }
- startx += dx;
- starty += dy;
- }
- } else if (active) {
- draw_line(inst, startx, starty,
- startx + (length-1) * dx, starty + (length-1) * dy);
- } /* else no cursor (e.g., blinked off) */
- }
-
- draw_update(inst,
- x*inst->font_width+inst->window_border,
- y*inst->font_height+inst->window_border,
- len*widefactor*inst->font_width, inst->font_height);
-
-#if GTK_CHECK_VERSION(2,0,0)
- {
- GdkRectangle cursorrect;
- cursorrect.x = x*inst->font_width+inst->window_border;
- cursorrect.y = y*inst->font_height+inst->window_border;
- cursorrect.width = len*widefactor*inst->font_width;
- cursorrect.height = inst->font_height;
- gtk_im_context_set_cursor_location(inst->imc, &cursorrect);
- }
-#endif
-}
-
-#if !GTK_CHECK_VERSION(2,0,0)
-/*
- * For GTK 1, manual code to scale an in-memory XPM, producing a new
- * one as output. It will be ugly, but good enough to use as a trust
- * sigil.
- */
-struct XpmHolder {
- char **strings;
- size_t nstrings;
-};
-
-static void xpmholder_free(XpmHolder *xh)
-{
- for (size_t i = 0; i < xh->nstrings; i++)
- sfree(xh->strings[i]);
- sfree(xh->strings);
- sfree(xh);
-}
-
-static XpmHolder *xpm_scale(const char *const *xpm, int wo, int ho)
-{
- /* Get image dimensions, # colours, and chars-per-pixel */
- int wi = 0, hi = 0, nc = 0, cpp = 0;
- int retd = sscanf(xpm[0], "%d %d %d %d", &wi, &hi, &nc, &cpp);
- assert(retd == 4);
-
- /* Make output XpmHolder */
- XpmHolder *xh = snew(XpmHolder);
- xh->nstrings = 1 + nc + ho;
- xh->strings = snewn(xh->nstrings, char *);
-
- /* Set up header */
- xh->strings[0] = dupprintf("%d %d %d %d", wo, ho, nc, cpp);
- for (int i = 0; i < nc; i++)
- xh->strings[1 + i] = dupstr(xpm[1 + i]);
-
- /* Scale image */
- for (int yo = 0; yo < ho; yo++) {
- int yi = yo * hi / ho;
- char *ro = snewn(cpp * wo + 1, char);
- ro[cpp * wo] = '\0';
- xh->strings[1 + nc + yo] = ro;
- const char *ri = xpm[1 + nc + yi];
-
- for (int xo = 0; xo < wo; xo++) {
- int xi = xo * wi / wo;
- memcpy(ro + cpp * xo, ri + cpp * xi, cpp);
- }
- }
-
- return xh;
-}
-#endif /* !GTK_CHECK_VERSION(2,0,0) */
-
-static void gtkwin_draw_trust_sigil(TermWin *tw, int cx, int cy)
-{
- GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
-
- int x = cx * inst->font_width + inst->window_border;
- int y = cy * inst->font_height + inst->window_border;
- int w = 2*inst->font_width, h = inst->font_height;
-
- if (inst->trust_sigil_w != w || inst->trust_sigil_h != h ||
-#if GTK_CHECK_VERSION(2,0,0)
- !inst->trust_sigil_pb
-#else
- !inst->trust_sigil_pm
-#endif
- ) {
-
-#if GTK_CHECK_VERSION(2,0,0)
- if (inst->trust_sigil_pb)
- g_object_unref(G_OBJECT(inst->trust_sigil_pb));
-#else
- if (inst->trust_sigil_pm)
- gdk_pixmap_unref(inst->trust_sigil_pm);
-#endif
-
- int best_icon_index = 0;
- unsigned score = UINT_MAX;
- for (int i = 0; i < n_main_icon; i++) {
- int iw, ih;
- if (sscanf(main_icon[i][0], "%d %d", &iw, &ih) == 2) {
- int this_excess = (iw + ih) - (w + h);
- unsigned this_score = (abs(this_excess) |
- (this_excess > 0 ? 0 : 0x80000000U));
- if (this_score < score) {
- best_icon_index = i;
- score = this_score;
- }
- }
- }
-
-#if GTK_CHECK_VERSION(2,0,0)
- GdkPixbuf *icon_unscaled = gdk_pixbuf_new_from_xpm_data(
- (const gchar **)main_icon[best_icon_index]);
- inst->trust_sigil_pb = gdk_pixbuf_scale_simple(
- icon_unscaled, w, h, GDK_INTERP_BILINEAR);
- g_object_unref(G_OBJECT(icon_unscaled));
-#else
- XpmHolder *xh = xpm_scale(main_icon[best_icon_index], w, h);
- inst->trust_sigil_pm = gdk_pixmap_create_from_xpm_d(
- gtk_widget_get_window(inst->window), NULL,
- &inst->cols[258], xh->strings);
- xpmholder_free(xh);
-#endif
-
- inst->trust_sigil_w = w;
- inst->trust_sigil_h = h;
- }
-
-#ifdef DRAW_TEXT_GDK
- if (inst->uctx.type == DRAWTYPE_GDK) {
-#if GTK_CHECK_VERSION(2,0,0)
- gdk_draw_pixbuf(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc,
- inst->trust_sigil_pb, 0, 0, x, y, w, h,
- GDK_RGB_DITHER_NORMAL, 0, 0);
-#else
- gdk_draw_pixmap(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc,
- inst->trust_sigil_pm, 0, 0, x, y, w, h);
-#endif
- }
-#endif
-#ifdef DRAW_TEXT_CAIRO
- if (inst->uctx.type == DRAWTYPE_CAIRO) {
- inst->uctx.u.cairo.widget = GTK_WIDGET(inst->area);
- cairo_save(inst->uctx.u.cairo.cr);
- cairo_translate(inst->uctx.u.cairo.cr, x, y);
- gdk_cairo_set_source_pixbuf(inst->uctx.u.cairo.cr,
- inst->trust_sigil_pb, 0, 0);
- cairo_rectangle(inst->uctx.u.cairo.cr, 0, 0, w, h);
- cairo_fill(inst->uctx.u.cairo.cr);
- cairo_restore(inst->uctx.u.cairo.cr);
- }
-#endif
-
- draw_update(inst, x, y, w, h);
-}
-
-GdkCursor *make_mouse_ptr(GtkFrontend *inst, int cursor_val)
-{
- if (cursor_val == -1) {
-#if GTK_CHECK_VERSION(2,16,0)
- cursor_val = GDK_BLANK_CURSOR;
-#else
- /*
- * Work around absence of GDK_BLANK_CURSOR by inventing a
- * blank pixmap.
- */
- GdkCursor *ret;
- GdkColor bg = { 0, 0, 0, 0 };
- GdkPixmap *pm = gdk_pixmap_new(NULL, 1, 1, 1);
- GdkGC *gc = gdk_gc_new(pm);
- gdk_gc_set_foreground(gc, &bg);
- gdk_draw_rectangle(pm, gc, 1, 0, 0, 1, 1);
- gdk_gc_unref(gc);
- ret = gdk_cursor_new_from_pixmap(pm, pm, &bg, &bg, 1, 1);
- gdk_pixmap_unref(pm);
- return ret;
-#endif
- }
-
- return gdk_cursor_new(cursor_val);
-}
-
-void modalfatalbox(const char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "FATAL ERROR: ");
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
- exit(1);
-}
-
-static const char *gtk_seat_get_x_display(Seat *seat)
-{
- return gdk_get_display();
-}
-
-#ifndef NOT_X_WINDOWS
-static bool gtk_seat_get_windowid(Seat *seat, long *id)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- GdkWindow *window = gtk_widget_get_window(inst->area);
- if (!GDK_IS_X11_WINDOW(window))
- return false;
- *id = GDK_WINDOW_XID(window);
- return true;
-}
-#endif
-
-char *setup_fonts_ucs(GtkFrontend *inst)
-{
- bool shadowbold = conf_get_bool(inst->conf, CONF_shadowbold);
- int shadowboldoffset = conf_get_int(inst->conf, CONF_shadowboldoffset);
- FontSpec *fs;
- unifont *fonts[4];
- int i;
-
- fs = conf_get_fontspec(inst->conf, CONF_font);
- fonts[0] = multifont_create(inst->area, fs->name, false, false,
- shadowboldoffset, shadowbold);
- if (!fonts[0]) {
- return dupprintf("unable to load font \"%s\"", fs->name);
- }
-
- fs = conf_get_fontspec(inst->conf, CONF_boldfont);
- if (shadowbold || !fs->name[0]) {
- fonts[1] = NULL;
- } else {
- fonts[1] = multifont_create(inst->area, fs->name, false, true,
- shadowboldoffset, shadowbold);
- if (!fonts[1]) {
- if (fonts[0])
- unifont_destroy(fonts[0]);
- return dupprintf("unable to load bold font \"%s\"", fs->name);
- }
- }
-
- fs = conf_get_fontspec(inst->conf, CONF_widefont);
- if (fs->name[0]) {
- fonts[2] = multifont_create(inst->area, fs->name, true, false,
- shadowboldoffset, shadowbold);
- if (!fonts[2]) {
- for (i = 0; i < 2; i++)
- if (fonts[i])
- unifont_destroy(fonts[i]);
- return dupprintf("unable to load wide font \"%s\"", fs->name);
- }
- } else {
- fonts[2] = NULL;
- }
-
- fs = conf_get_fontspec(inst->conf, CONF_wideboldfont);
- if (shadowbold || !fs->name[0]) {
- fonts[3] = NULL;
- } else {
- fonts[3] = multifont_create(inst->area, fs->name, true, true,
- shadowboldoffset, shadowbold);
- if (!fonts[3]) {
- for (i = 0; i < 3; i++)
- if (fonts[i])
- unifont_destroy(fonts[i]);
- return dupprintf("unable to load wide bold font \"%s\"", fs->name);
- }
- }
-
- /*
- * Now we've got past all the possible error conditions, we can
- * actually update our state.
- */
-
- for (i = 0; i < 4; i++) {
- if (inst->fonts[i])
- unifont_destroy(inst->fonts[i]);
- inst->fonts[i] = fonts[i];
- }
-
- if (inst->font_width != inst->fonts[0]->width ||
- inst->font_height != inst->fonts[0]->height) {
-
- inst->font_width = inst->fonts[0]->width;
- inst->font_height = inst->fonts[0]->height;
-
- /*
- * The font size has changed, so force the next call to
- * drawing_area_setup to regenerate the backing surface.
- */
- inst->drawing_area_setup_needed = true;
- }
-
- inst->direct_to_font = init_ucs(&inst->ucsdata,
- conf_get_str(inst->conf, CONF_line_codepage),
- conf_get_bool(inst->conf, CONF_utf8_override),
- inst->fonts[0]->public_charset,
- conf_get_int(inst->conf, CONF_vtmode));
-
- inst->drawtype = inst->fonts[0]->preferred_drawtype;
-
- return NULL;
-}
-
-#if GTK_CHECK_VERSION(3,0,0)
-struct find_app_menu_bar_ctx {
- GtkWidget *area, *menubar;
-};
-static void find_app_menu_bar(GtkWidget *widget, gpointer data)
-{
- struct find_app_menu_bar_ctx *ctx = (struct find_app_menu_bar_ctx *)data;
- if (widget != ctx->area && GTK_IS_MENU_BAR(widget))
- ctx->menubar = widget;
-}
-#endif
-
-static void compute_geom_hints(GtkFrontend *inst, GdkGeometry *geom)
-{
- /*
- * Unused fields in geom.
- */
- geom->max_width = geom->max_height = -1;
- geom->min_aspect = geom->max_aspect = 0;
-
- /*
- * Set up the geometry fields we care about, with reference to
- * just the drawing area. We'll correct for other widgets in a
- * moment.
- */
- geom->min_width = inst->font_width + 2*inst->window_border;
- geom->min_height = inst->font_height + 2*inst->window_border;
- geom->base_width = 2*inst->window_border;
- geom->base_height = 2*inst->window_border;
- geom->width_inc = inst->font_width;
- geom->height_inc = inst->font_height;
-
- /*
- * If we've got a scrollbar visible, then we must include its
- * width as part of the base and min width, and also ensure that
- * our window's minimum height is at least the height required by
- * the scrollbar.
- *
- * In the latter case, we must also take care to arrange that
- * (geom->min_height - geom->base_height) is an integer multiple of
- * geom->height_inc, because if it's not, then some window managers
- * (we know of xfwm4) get confused, with the effect that they
- * resize our window to a height based on min_height instead of
- * base_height, which we then round down and the window ends up
- * too short.
- */
- if (inst->sbar_visible) {
- GtkRequisition req;
- int min_sb_height;
-
-#if GTK_CHECK_VERSION(3,0,0)
- gtk_widget_get_preferred_size(inst->sbar, &req, NULL);
-#else
- gtk_widget_size_request(inst->sbar, &req);
-#endif
-
- /* Compute rounded-up scrollbar height. */
- min_sb_height = req.height;
- min_sb_height += geom->height_inc - 1;
- min_sb_height -= ((min_sb_height - geom->base_height%geom->height_inc)
- % geom->height_inc);
-
- geom->min_width += req.width;
- geom->base_width += req.width;
- if (geom->min_height < min_sb_height)
- geom->min_height = min_sb_height;
- }
-
-#if GTK_CHECK_VERSION(3,0,0)
- /*
- * And if we're running a gtkapp.c based program and
- * GtkApplicationWindow has given us a menu bar inside the window,
- * then we must take that into account as well.
- *
- * In its unbounded wisdom, GtkApplicationWindow doesn't actually
- * give us a direct function call to _find_ the menu bar widget.
- * Fortunately, we can find it by enumerating the children of the
- * top-level window and looking for one we didn't put there
- * ourselves.
- */
- {
- struct find_app_menu_bar_ctx ctx[1];
- ctx->area = inst->area;
- ctx->menubar = NULL;
- gtk_container_foreach(GTK_CONTAINER(inst->window),
- find_app_menu_bar, ctx);
-
- if (ctx->menubar) {
- GtkRequisition req;
- int min_menu_width;
- gtk_widget_get_preferred_size(ctx->menubar, NULL, &req);
-
- /*
- * This time, the height adjustment is easy (the menu bar
- * sits above everything), but we have to take care with
- * the _width_ to ensure we keep min_width and base_width
- * congruent modulo width_inc.
- */
- geom->min_height += req.height;
- geom->base_height += req.height;
-
- min_menu_width = req.width;
- min_menu_width += geom->width_inc - 1;
- min_menu_width -=
- ((min_menu_width - geom->base_width%geom->width_inc)
- % geom->width_inc);
- if (geom->min_width < min_menu_width)
- geom->min_width = min_menu_width;
- }
- }
-#endif
-}
-
-void set_geom_hints(GtkFrontend *inst)
-{
- const struct BackendVtable *vt;
- GdkGeometry geom;
- gint flags = GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE | GDK_HINT_RESIZE_INC;
- compute_geom_hints(inst, &geom);
-#if GTK_CHECK_VERSION(2,0,0)
- if (inst->gotpos)
- flags |= GDK_HINT_USER_POS;
-#endif
- vt = backend_vt_from_proto(conf_get_int(inst->conf, CONF_protocol));
- if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) {
- /* Window resizing forbidden. Set both minimum and maximum
- * dimensions to be the initial size. */
- geom.min_width = inst->width*inst->font_width + 2*inst->window_border;
- geom.min_height = inst->height*inst->font_height + 2*inst->window_border;
- geom.max_width = geom.min_width;
- geom.max_height = geom.min_height;
- flags |= GDK_HINT_MAX_SIZE;
- }
- gtk_window_set_geometry_hints(GTK_WINDOW(inst->window),
- NULL, &geom, flags);
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-static void compute_whole_window_size(GtkFrontend *inst,
- int wchars, int hchars,
- int *wpix, int *hpix)
-{
- GdkGeometry geom;
- compute_geom_hints(inst, &geom);
- if (wpix) *wpix = geom.base_width + wchars * geom.width_inc;
- if (hpix) *hpix = geom.base_height + hchars * geom.height_inc;
-}
-#endif
-
-void clear_scrollback_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- term_clrsb(inst->term);
-}
-
-void reset_terminal_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- term_pwron(inst->term, true);
- if (inst->ldisc)
- ldisc_echoedit_update(inst->ldisc);
-}
-
-void copy_clipboard_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- static const int clips[] = { MENU_CLIPBOARD };
- term_request_copy(inst->term, clips, lenof(clips));
-}
-
-void paste_clipboard_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- term_request_paste(inst->term, MENU_CLIPBOARD);
-}
-
-void copy_all_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- static const int clips[] = { COPYALL_CLIPBOARDS };
- term_copyall(inst->term, clips, lenof(clips));
-}
-
-void special_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- SessionSpecial *sc = g_object_get_data(G_OBJECT(item), "user-data");
-
- if (inst->backend)
- backend_special(inst->backend, sc->code, sc->arg);
-}
-
-void about_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- about_box(inst->window);
-}
-
-void event_log_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- showeventlog(inst->eventlogstuff, inst->window);
-}
-
-void setup_clipboards(GtkFrontend *inst, Terminal *term, Conf *conf)
-{
- assert(term->mouse_select_clipboards[0] == CLIP_LOCAL);
-
- term->n_mouse_select_clipboards = 1;
- term->mouse_select_clipboards[
- term->n_mouse_select_clipboards++] = MOUSE_SELECT_CLIPBOARD;
-
- if (conf_get_bool(conf, CONF_mouseautocopy)) {
- term->mouse_select_clipboards[
- term->n_mouse_select_clipboards++] = CLIP_CLIPBOARD;
- }
-
- set_clipboard_atom(inst, CLIP_CUSTOM_1, GDK_NONE);
- set_clipboard_atom(inst, CLIP_CUSTOM_2, GDK_NONE);
- set_clipboard_atom(inst, CLIP_CUSTOM_3, GDK_NONE);
-
- switch (conf_get_int(conf, CONF_mousepaste)) {
- case CLIPUI_IMPLICIT:
- term->mouse_paste_clipboard = MOUSE_PASTE_CLIPBOARD;
- break;
- case CLIPUI_EXPLICIT:
- term->mouse_paste_clipboard = CLIP_CLIPBOARD;
- break;
- case CLIPUI_CUSTOM:
- term->mouse_paste_clipboard = CLIP_CUSTOM_1;
- set_clipboard_atom(inst, CLIP_CUSTOM_1,
- gdk_atom_intern(
- conf_get_str(conf, CONF_mousepaste_custom),
- false));
- break;
- default:
- term->mouse_paste_clipboard = CLIP_NULL;
- break;
- }
-
- if (conf_get_int(conf, CONF_ctrlshiftins) == CLIPUI_CUSTOM) {
- GdkAtom atom = gdk_atom_intern(
- conf_get_str(conf, CONF_ctrlshiftins_custom), false);
- struct clipboard_state *state = clipboard_from_atom(inst, atom);
- if (state) {
- inst->clipboard_ctrlshiftins = state->clipboard;
- } else {
- inst->clipboard_ctrlshiftins = CLIP_CUSTOM_2;
- set_clipboard_atom(inst, CLIP_CUSTOM_2, atom);
- }
- }
-
- if (conf_get_int(conf, CONF_ctrlshiftcv) == CLIPUI_CUSTOM) {
- GdkAtom atom = gdk_atom_intern(
- conf_get_str(conf, CONF_ctrlshiftcv_custom), false);
- struct clipboard_state *state = clipboard_from_atom(inst, atom);
- if (state) {
- inst->clipboard_ctrlshiftins = state->clipboard;
- } else {
- inst->clipboard_ctrlshiftcv = CLIP_CUSTOM_3;
- set_clipboard_atom(inst, CLIP_CUSTOM_3, atom);
- }
- }
-}
-
-struct after_change_settings_dialog_ctx {
- GtkFrontend *inst;
- Conf *newconf;
-};
-
-static void after_change_settings_dialog(void *vctx, int retval);
-
-void change_settings_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- struct after_change_settings_dialog_ctx *ctx;
- GtkWidget *dialog;
- char *title;
-
- if (find_and_raise_dialog(inst, DIALOG_SLOT_RECONFIGURE))
- return;
-
- title = dupcat(appname, " Reconfiguration");
-
- ctx = snew(struct after_change_settings_dialog_ctx);
- ctx->inst = inst;
- ctx->newconf = conf_copy(inst->conf);
-
- term_pre_reconfig(inst->term, ctx->newconf);
-
- dialog = create_config_box(
- title, ctx->newconf, true,
- inst->backend ? backend_cfg_info(inst->backend) : 0,
- after_change_settings_dialog, ctx);
- register_dialog(&inst->seat, DIALOG_SLOT_RECONFIGURE, dialog);
-
- sfree(title);
-}
-
-static void after_change_settings_dialog(void *vctx, int retval)
-{
- struct after_change_settings_dialog_ctx ctx =
- *(struct after_change_settings_dialog_ctx *)vctx;
- GtkFrontend *inst = ctx.inst;
- Conf *oldconf = inst->conf, *newconf = ctx.newconf;
- bool need_size;
-
- sfree(vctx); /* we've copied this already */
-
- unregister_dialog(&inst->seat, DIALOG_SLOT_RECONFIGURE);
-
- if (retval > 0) {
- inst->conf = newconf;
-
- /* Pass new config data to the logging module */
- log_reconfig(inst->logctx, inst->conf);
- /*
- * Flush the line discipline's edit buffer in the case
- * where local editing has just been disabled.
- */
- if (inst->ldisc) {
- ldisc_configure(inst->ldisc, inst->conf);
- ldisc_echoedit_update(inst->ldisc);
- }
- /* Pass new config data to the terminal */
- term_reconfig(inst->term, inst->conf);
- setup_clipboards(inst, inst->term, inst->conf);
- /* Pass new config data to the back end */
- if (inst->backend)
- backend_reconfig(inst->backend, inst->conf);
-
- cache_conf_values(inst);
-
- need_size = false;
-
- /*
- * If the scrollbar needs to be shown, hidden, or moved
- * from one end to the other of the window, do so now.
- */
- if (conf_get_bool(oldconf, CONF_scrollbar) !=
- conf_get_bool(newconf, CONF_scrollbar)) {
- show_scrollbar(inst, conf_get_bool(newconf, CONF_scrollbar));
- need_size = true;
- }
- if (conf_get_bool(oldconf, CONF_scrollbar_on_left) !=
- conf_get_bool(newconf, CONF_scrollbar_on_left)) {
- gtk_box_reorder_child(inst->hbox, inst->sbar,
- conf_get_bool(newconf, CONF_scrollbar_on_left)
- ? 0 : 1);
- }
-
- /*
- * Redo the whole tangled fonts and Unicode mess if
- * necessary.
- */
- if (strcmp(conf_get_fontspec(oldconf, CONF_font)->name,
- conf_get_fontspec(newconf, CONF_font)->name) ||
- strcmp(conf_get_fontspec(oldconf, CONF_boldfont)->name,
- conf_get_fontspec(newconf, CONF_boldfont)->name) ||
- strcmp(conf_get_fontspec(oldconf, CONF_widefont)->name,
- conf_get_fontspec(newconf, CONF_widefont)->name) ||
- strcmp(conf_get_fontspec(oldconf, CONF_wideboldfont)->name,
- conf_get_fontspec(newconf, CONF_wideboldfont)->name) ||
- strcmp(conf_get_str(oldconf, CONF_line_codepage),
- conf_get_str(newconf, CONF_line_codepage)) ||
- conf_get_bool(oldconf, CONF_utf8_override) !=
- conf_get_bool(newconf, CONF_utf8_override) ||
- conf_get_int(oldconf, CONF_vtmode) !=
- conf_get_int(newconf, CONF_vtmode) ||
- conf_get_bool(oldconf, CONF_shadowbold) !=
- conf_get_bool(newconf, CONF_shadowbold) ||
- conf_get_int(oldconf, CONF_shadowboldoffset) !=
- conf_get_int(newconf, CONF_shadowboldoffset)) {
- char *errmsg = setup_fonts_ucs(inst);
- if (errmsg) {
- char *msgboxtext =
- dupprintf("Could not change fonts in terminal window: %s\n",
- errmsg);
- create_message_box(
- inst->window, "Font setup error", msgboxtext,
- string_width("Could not change fonts in terminal window:"),
- false, &buttons_ok, trivial_post_dialog_fn, NULL);
- sfree(msgboxtext);
- sfree(errmsg);
- } else {
- need_size = true;
- }
- }
-
- /*
- * Resize the window.
- */
- if (conf_get_int(oldconf, CONF_width) !=
- conf_get_int(newconf, CONF_width) ||
- conf_get_int(oldconf, CONF_height) !=
- conf_get_int(newconf, CONF_height) ||
- conf_get_int(oldconf, CONF_window_border) !=
- conf_get_int(newconf, CONF_window_border) ||
- need_size) {
- set_geom_hints(inst);
- win_request_resize(&inst->termwin,
- conf_get_int(newconf, CONF_width),
- conf_get_int(newconf, CONF_height));
- } else {
- /*
- * The above will have caused a call to term_size() for
- * us if it happened. If the user has fiddled with only
- * the scrollback size, the above will not have
- * happened and we will need an explicit term_size()
- * here.
- */
- if (conf_get_int(oldconf, CONF_savelines) !=
- conf_get_int(newconf, CONF_savelines))
- term_size(inst->term, inst->term->rows, inst->term->cols,
- conf_get_int(newconf, CONF_savelines));
- }
-
- term_invalidate(inst->term);
-
- /*
- * We do an explicit full redraw here to ensure the window
- * border has been redrawn as well as the text area.
- */
- gtk_widget_queue_draw(inst->area);
-
- conf_free(oldconf);
- } else {
- conf_free(newconf);
- }
-}
-
-static void change_font_size(GtkFrontend *inst, int increment)
-{
- static const int conf_keys[lenof(inst->fonts)] = {
- CONF_font, CONF_boldfont, CONF_widefont, CONF_wideboldfont,
- };
- FontSpec *oldfonts[lenof(inst->fonts)];
- FontSpec *newfonts[lenof(inst->fonts)];
- char *errmsg = NULL;
- int i;
-
- for (i = 0; i < lenof(newfonts); i++)
- oldfonts[i] = newfonts[i] = NULL;
-
- for (i = 0; i < lenof(inst->fonts); i++) {
- if (inst->fonts[i]) {
- char *newname = unifont_size_increment(inst->fonts[i], increment);
- if (!newname)
- goto cleanup;
- newfonts[i] = fontspec_new(newname);
- sfree(newname);
- }
- }
-
- for (i = 0; i < lenof(newfonts); i++) {
- if (newfonts[i]) {
- oldfonts[i] = fontspec_copy(
- conf_get_fontspec(inst->conf, conf_keys[i]));
- conf_set_fontspec(inst->conf, conf_keys[i], newfonts[i]);
- }
- }
-
- errmsg = setup_fonts_ucs(inst);
- if (errmsg)
- goto cleanup;
-
- /* Success, so suppress putting everything back */
- for (i = 0; i < lenof(newfonts); i++) {
- if (oldfonts[i]) {
- fontspec_free(oldfonts[i]);
- oldfonts[i] = NULL;
- }
- }
-
- set_geom_hints(inst);
- win_request_resize(&inst->termwin, conf_get_int(inst->conf, CONF_width),
- conf_get_int(inst->conf, CONF_height));
- term_invalidate(inst->term);
- gtk_widget_queue_draw(inst->area);
-
- cleanup:
- for (i = 0; i < lenof(oldfonts); i++) {
- if (oldfonts[i]) {
- conf_set_fontspec(inst->conf, conf_keys[i], oldfonts[i]);
- fontspec_free(oldfonts[i]);
- }
- if (newfonts[i])
- fontspec_free(newfonts[i]);
- }
- sfree(errmsg);
-}
-
-void dup_session_menuitem(GtkMenuItem *item, gpointer gdata)
-{
- GtkFrontend *inst = (GtkFrontend *)gdata;
-
- launch_duplicate_session(inst->conf);
-}
-
-void new_session_menuitem(GtkMenuItem *item, gpointer data)
-{
- launch_new_session();
-}
-
-void restart_session_menuitem(GtkMenuItem *item, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
-
- if (!inst->backend) {
- logevent(inst->logctx, "----- Session restarted -----");
- term_pwron(inst->term, false);
- start_backend(inst);
- inst->exited = false;
- }
-}
-
-void saved_session_menuitem(GtkMenuItem *item, gpointer data)
-{
- char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data");
-
- launch_saved_session(str);
-}
-
-void saved_session_freedata(GtkMenuItem *item, gpointer data)
-{
- char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data");
-
- sfree(str);
-}
-
-void app_menu_action(GtkFrontend *frontend, enum MenuAction action)
-{
- GtkFrontend *inst = (GtkFrontend *)frontend;
- switch (action) {
- case MA_COPY:
- copy_clipboard_menuitem(NULL, inst);
- break;
- case MA_PASTE:
- paste_clipboard_menuitem(NULL, inst);
- break;
- case MA_COPY_ALL:
- copy_all_menuitem(NULL, inst);
- break;
- case MA_DUPLICATE_SESSION:
- dup_session_menuitem(NULL, inst);
- break;
- case MA_RESTART_SESSION:
- restart_session_menuitem(NULL, inst);
- break;
- case MA_CHANGE_SETTINGS:
- change_settings_menuitem(NULL, inst);
- break;
- case MA_CLEAR_SCROLLBACK:
- clear_scrollback_menuitem(NULL, inst);
- break;
- case MA_RESET_TERMINAL:
- reset_terminal_menuitem(NULL, inst);
- break;
- case MA_EVENT_LOG:
- event_log_menuitem(NULL, inst);
- break;
- }
-}
-
-static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data)
-{
- GtkFrontend *inst = (GtkFrontend *)data;
- struct sesslist sesslist;
- int i;
-
- gtk_container_foreach(GTK_CONTAINER(inst->sessionsmenu),
- (GtkCallback)gtk_widget_destroy, NULL);
-
- get_sesslist(&sesslist, true);
- /* skip sesslist.sessions[0] == Default Settings */
- for (i = 1; i < sesslist.nsessions; i++) {
- GtkWidget *menuitem =
- gtk_menu_item_new_with_label(sesslist.sessions[i]);
- gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem);
- gtk_widget_show(menuitem);
- g_object_set_data(G_OBJECT(menuitem), "user-data",
- dupstr(sesslist.sessions[i]));
- g_signal_connect(G_OBJECT(menuitem), "activate",
- G_CALLBACK(saved_session_menuitem),
- inst);
- g_signal_connect(G_OBJECT(menuitem), "destroy",
- G_CALLBACK(saved_session_freedata),
- inst);
- }
- if (sesslist.nsessions <= 1) {
- GtkWidget *menuitem =
- gtk_menu_item_new_with_label("(No sessions)");
- gtk_widget_set_sensitive(menuitem, false);
- gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem);
- gtk_widget_show(menuitem);
- }
- get_sesslist(&sesslist, false); /* free up */
-}
-
-void set_window_icon(GtkWidget *window, const char *const *const *icon,
- int n_icon)
-{
-#if GTK_CHECK_VERSION(2,0,0)
- GList *iconlist;
- int n;
-#else
- GdkPixmap *iconpm;
- GdkBitmap *iconmask;
-#endif
-
- if (!n_icon)
- return;
-
- gtk_widget_realize(window);
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_window_set_icon(GTK_WINDOW(window),
- gdk_pixbuf_new_from_xpm_data((const gchar **)icon[0]));
-#else
- iconpm = gdk_pixmap_create_from_xpm_d(gtk_widget_get_window(window),
- &iconmask, NULL, (gchar **)icon[0]);
- gdk_window_set_icon(gtk_widget_get_window(window), NULL, iconpm, iconmask);
-#endif
-
-#if GTK_CHECK_VERSION(2,0,0)
- iconlist = NULL;
- for (n = 0; n < n_icon; n++) {
- iconlist =
- g_list_append(iconlist,
- gdk_pixbuf_new_from_xpm_data((const gchar **)
- icon[n]));
- }
- gtk_window_set_icon_list(GTK_WINDOW(window), iconlist);
-#endif
-}
-
-static void free_special_cmd(gpointer data) { sfree(data); }
-
-static void gtk_seat_update_specials_menu(Seat *seat)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- const SessionSpecial *specials;
-
- if (inst->backend)
- specials = backend_get_specials(inst->backend);
- else
- specials = NULL;
-
- /* I believe this disposes of submenus too. */
- gtk_container_foreach(GTK_CONTAINER(inst->specialsmenu),
- (GtkCallback)gtk_widget_destroy, NULL);
- if (specials) {
- int i;
- GtkWidget *menu = inst->specialsmenu;
- /* A lame "stack" for submenus that will do for now. */
- GtkWidget *saved_menu = NULL;
- int nesting = 1;
- for (i = 0; nesting > 0; i++) {
- GtkWidget *menuitem = NULL;
- switch (specials[i].code) {
- case SS_SUBMENU:
- assert (nesting < 2);
- saved_menu = menu; /* XXX lame stacking */
- menu = gtk_menu_new();
- menuitem = gtk_menu_item_new_with_label(specials[i].name);
- gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
- gtk_container_add(GTK_CONTAINER(saved_menu), menuitem);
- gtk_widget_show(menuitem);
- menuitem = NULL;
- nesting++;
- break;
- case SS_EXITMENU:
- nesting--;
- if (nesting) {
- menu = saved_menu; /* XXX lame stacking */
- saved_menu = NULL;
- }
- break;
- case SS_SEP:
- menuitem = gtk_menu_item_new();
- break;
- default: {
- menuitem = gtk_menu_item_new_with_label(specials[i].name);
- SessionSpecial *sc = snew(SessionSpecial);
- *sc = specials[i]; /* structure copy */
- g_object_set_data_full(G_OBJECT(menuitem), "user-data",
- sc, free_special_cmd);
- g_signal_connect(G_OBJECT(menuitem), "activate",
- G_CALLBACK(special_menuitem), inst);
- break;
- }
- }
- if (menuitem) {
- gtk_container_add(GTK_CONTAINER(menu), menuitem);
- gtk_widget_show(menuitem);
- }
- }
- gtk_widget_show(inst->specialsitem1);
- gtk_widget_show(inst->specialsitem2);
- } else {
- gtk_widget_hide(inst->specialsitem1);
- gtk_widget_hide(inst->specialsitem2);
- }
-}
-
-static void start_backend(GtkFrontend *inst)
-{
- const struct BackendVtable *vt;
- char *error, *realhost;
-
- vt = select_backend(inst->conf);
-
- seat_set_trust_status(&inst->seat, true);
- error = backend_init(vt, &inst->seat, &inst->backend,
- inst->logctx, inst->conf,
- conf_get_str(inst->conf, CONF_host),
- conf_get_int(inst->conf, CONF_port),
- &realhost,
- conf_get_bool(inst->conf, CONF_tcp_nodelay),
- conf_get_bool(inst->conf, CONF_tcp_keepalives));
-
- if (error) {
- seat_connection_fatal(&inst->seat,
- "Unable to open connection to %s:\n%s",
- conf_dest(inst->conf), error);
- sfree(error);
- inst->exited = true;
- return;
- }
-
- term_setup_window_titles(inst->term, realhost);
- sfree(realhost);
-
- term_provide_backend(inst->term, inst->backend);
-
- inst->ldisc = ldisc_create(inst->conf, inst->term, inst->backend,
- &inst->seat);
-
- gtk_widget_set_sensitive(inst->restartitem, false);
-}
-
-#if GTK_CHECK_VERSION(2,0,0)
-static void get_monitor_geometry(GtkWidget *widget, GdkRectangle *geometry)
-{
-#if GTK_CHECK_VERSION(3,4,0)
- GdkDisplay *display = gtk_widget_get_display(widget);
- GdkWindow *gdkwindow = gtk_widget_get_window(widget);
-# if GTK_CHECK_VERSION(3,22,0)
- GdkMonitor *monitor;
- if (gdkwindow)
- monitor = gdk_display_get_monitor_at_window(display, gdkwindow);
- else
- monitor = gdk_display_get_monitor(display, 0);
- gdk_monitor_get_geometry(monitor, geometry);
-# else
- GdkScreen *screen = gdk_display_get_default_screen(display);
- gint monitor_num = gdk_screen_get_monitor_at_window(screen, gdkwindow);
- gdk_screen_get_monitor_geometry(screen, monitor_num, geometry);
-# endif
-#else
- geometry->x = geometry->y = 0;
- geometry->width = gdk_screen_width();
- geometry->height = gdk_screen_height();
-#endif
-}
-#endif
-
-static const TermWinVtable gtk_termwin_vt = {
- .setup_draw_ctx = gtkwin_setup_draw_ctx,
- .draw_text = gtkwin_draw_text,
- .draw_cursor = gtkwin_draw_cursor,
- .draw_trust_sigil = gtkwin_draw_trust_sigil,
- .char_width = gtkwin_char_width,
- .free_draw_ctx = gtkwin_free_draw_ctx,
- .set_cursor_pos = gtkwin_set_cursor_pos,
- .set_raw_mouse_mode = gtkwin_set_raw_mouse_mode,
- .set_raw_mouse_mode_pointer = gtkwin_set_raw_mouse_mode_pointer,
- .set_scrollbar = gtkwin_set_scrollbar,
- .bell = gtkwin_bell,
- .clip_write = gtkwin_clip_write,
- .clip_request_paste = gtkwin_clip_request_paste,
- .refresh = gtkwin_refresh,
- .request_resize = gtkwin_request_resize,
- .set_title = gtkwin_set_title,
- .set_icon_title = gtkwin_set_icon_title,
- .set_minimised = gtkwin_set_minimised,
- .set_maximised = gtkwin_set_maximised,
- .move = gtkwin_move,
- .set_zorder = gtkwin_set_zorder,
- .palette_set = gtkwin_palette_set,
- .palette_get_overrides = gtkwin_palette_get_overrides,
-};
-
-void new_session_window(Conf *conf, const char *geometry_string)
-{
- GtkFrontend *inst;
-
- prepare_session(conf);
-
- /*
- * Create an instance structure and initialise to zeroes
- */
- inst = snew(GtkFrontend);
- memset(inst, 0, sizeof(*inst));
-#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
- inst->cdi_headtail.next = inst->cdi_headtail.prev = &inst->cdi_headtail;
-#endif
- inst->alt_keycode = -1; /* this one needs _not_ to be zero */
- inst->busy_status = BUSY_NOT;
- inst->conf = conf;
- inst->wintitle = inst->icontitle = NULL;
- inst->drawtype = DRAWTYPE_DEFAULT;
-#if GTK_CHECK_VERSION(3,4,0)
- inst->cumulative_scroll = 0.0;
-#endif
- inst->drawing_area_setup_needed = true;
-
- inst->termwin.vt = &gtk_termwin_vt;
- inst->seat.vt = &gtk_seat_vt;
- inst->logpolicy.vt = &gtk_logpolicy_vt;
-
-#ifndef NOT_X_WINDOWS
- inst->disp = get_x11_display();
- if (geometry_string) {
- int flags, x, y;
- unsigned int w, h;
- flags = XParseGeometry(geometry_string, &x, &y, &w, &h);
- if (flags & WidthValue)
- conf_set_int(conf, CONF_width, w);
- if (flags & HeightValue)
- conf_set_int(conf, CONF_height, h);
-
- if (flags & (XValue | YValue)) {
- inst->xpos = x;
- inst->ypos = y;
- inst->gotpos = true;
- inst->gravity = ((flags & XNegative ? 1 : 0) |
- (flags & YNegative ? 2 : 0));
- }
- }
-#endif
-
- if (!compound_text_atom)
- compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", false);
- if (!utf8_string_atom)
- utf8_string_atom = gdk_atom_intern("UTF8_STRING", false);
- if (!clipboard_atom)
- clipboard_atom = gdk_atom_intern("CLIPBOARD", false);
-
- inst->area = gtk_drawing_area_new();
- gtk_widget_set_name(GTK_WIDGET(inst->area), "drawing-area");
-
- {
- char *errmsg = setup_fonts_ucs(inst);
- if (errmsg) {
- window_setup_error(errmsg);
- sfree(errmsg);
- gtk_widget_destroy(inst->area);
- sfree(inst);
- return;
- }
- }
-
-#if GTK_CHECK_VERSION(2,0,0)
- inst->imc = gtk_im_multicontext_new();
-#endif
-
- inst->window = make_gtk_toplevel_window(inst);
- gtk_widget_set_name(GTK_WIDGET(inst->window), "top-level");
- {
- const char *winclass = conf_get_str(inst->conf, CONF_winclass);
- if (*winclass) {
-#if GTK_CHECK_VERSION(3,22,0)
-#ifndef NOT_X_WINDOWS
- GdkWindow *gdkwin;
- gtk_widget_realize(GTK_WIDGET(inst->window));
- gdkwin = gtk_widget_get_window(GTK_WIDGET(inst->window));
- if (inst->disp && gdk_window_ensure_native(gdkwin)) {
- XClassHint *xch = XAllocClassHint();
- xch->res_name = (char *)winclass;
- xch->res_class = (char *)winclass;
- XSetClassHint(inst->disp, GDK_WINDOW_XID(gdkwin), xch);
- XFree(xch);
- }
-#endif
- /*
- * If we do have NOT_X_WINDOWS set, then we don't have any
- * function in GTK 3.22 equivalent to the above. But then,
- * surely in that situation the deprecated
- * gtk_window_set_wmclass wouldn't have done anything
- * meaningful in previous GTKs either.
- */
-#else
- gtk_window_set_wmclass(GTK_WINDOW(inst->window),
- winclass, winclass);
-#endif
- }
- }
-
- inst->width = conf_get_int(inst->conf, CONF_width);
- inst->height = conf_get_int(inst->conf, CONF_height);
- cache_conf_values(inst);
-
- init_clipboard(inst);
-
- inst->sbar_adjust = GTK_ADJUSTMENT(gtk_adjustment_new(0,0,0,0,0,0));
- inst->sbar = gtk_vscrollbar_new(inst->sbar_adjust);
- inst->hbox = GTK_BOX(gtk_hbox_new(false, 0));
- /*
- * We always create the scrollbar; it remains invisible if
- * unwanted, so we can pop it up quickly if it suddenly becomes
- * desirable.
- */
- if (conf_get_bool(inst->conf, CONF_scrollbar_on_left))
- gtk_box_pack_start(inst->hbox, inst->sbar, false, false, 0);
- gtk_box_pack_start(inst->hbox, inst->area, true, true, 0);
- if (!conf_get_bool(inst->conf, CONF_scrollbar_on_left))
- gtk_box_pack_start(inst->hbox, inst->sbar, false, false, 0);
-
- gtk_container_add(GTK_CONTAINER(inst->window), GTK_WIDGET(inst->hbox));
-
- gtk_widget_show(inst->area);
- show_scrollbar(inst, conf_get_bool(inst->conf, CONF_scrollbar));
- gtk_widget_show(GTK_WIDGET(inst->hbox));
-
- /*
- * We must call gtk_widget_realize before setting up the geometry
- * hints, so that GtkApplicationWindow will have actually created
- * its menu bar (if it's going to) and hence compute_geom_hints
- * can find it to take its size into account.
- */
- gtk_widget_realize(inst->window);
- set_geom_hints(inst);
-
-#if GTK_CHECK_VERSION(3,0,0)
- {
- int wp, hp;
- compute_whole_window_size(inst, inst->width, inst->height, &wp, &hp);
- gtk_window_set_default_size(GTK_WINDOW(inst->window), wp, hp);
- }
-#else
- {
- int w = inst->font_width * inst->width + 2*inst->window_border;
- int h = inst->font_height * inst->height + 2*inst->window_border;
-#if GTK_CHECK_VERSION(2,0,0)
- gtk_widget_set_size_request(inst->area, w, h);
-#else
- gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), w, h);
-#endif
- }
-#endif
-
-#if GTK_CHECK_VERSION(2,0,0)
- if (inst->gotpos) {
- static const GdkGravity gravities[] = {
- GDK_GRAVITY_NORTH_WEST,
- GDK_GRAVITY_NORTH_EAST,
- GDK_GRAVITY_SOUTH_WEST,
- GDK_GRAVITY_SOUTH_EAST,
- };
- int x = inst->xpos, y = inst->ypos;
- int wp, hp;
- GdkRectangle monitor_geometry;
- compute_whole_window_size(inst, inst->width, inst->height, &wp, &hp);
- get_monitor_geometry(GTK_WIDGET(inst->window), &monitor_geometry);
- if (inst->gravity & 1) x += (monitor_geometry.width - wp);
- if (inst->gravity & 2) y += (monitor_geometry.height - hp);
- gtk_window_set_gravity(GTK_WINDOW(inst->window),
- gravities[inst->gravity & 3]);
- gtk_window_move(GTK_WINDOW(inst->window), x, y);
- }
-#else
- if (inst->gotpos) {
- int x = inst->xpos, y = inst->ypos;
- GtkRequisition req;
- gtk_widget_size_request(GTK_WIDGET(inst->window), &req);
- if (inst->gravity & 1) x += gdk_screen_width() - req.width;
- if (inst->gravity & 2) y += gdk_screen_height() - req.height;
- gtk_window_set_position(GTK_WINDOW(inst->window), GTK_WIN_POS_NONE);
- gtk_widget_set_uposition(GTK_WIDGET(inst->window), x, y);
- }
-#endif
-
- g_signal_connect(G_OBJECT(inst->window), "destroy",
- G_CALLBACK(destroy), inst);
- g_signal_connect(G_OBJECT(inst->window), "delete_event",
- G_CALLBACK(delete_window), inst);
- g_signal_connect(G_OBJECT(inst->window), "key_press_event",
- G_CALLBACK(key_event), inst);
- g_signal_connect(G_OBJECT(inst->window), "key_release_event",
- G_CALLBACK(key_event), inst);
- g_signal_connect(G_OBJECT(inst->window), "focus_in_event",
- G_CALLBACK(focus_event), inst);
- g_signal_connect(G_OBJECT(inst->window), "focus_out_event",
- G_CALLBACK(focus_event), inst);
- g_signal_connect(G_OBJECT(inst->area), "realize",
- G_CALLBACK(area_realised), inst);
- g_signal_connect(G_OBJECT(inst->area), "size_allocate",
- G_CALLBACK(area_size_allocate), inst);
- g_signal_connect(G_OBJECT(inst->window), "configure_event",
- G_CALLBACK(window_configured), inst);
-#if GTK_CHECK_VERSION(3,10,0)
- g_signal_connect(G_OBJECT(inst->area), "configure_event",
- G_CALLBACK(area_configured), inst);
-#endif
-#if GTK_CHECK_VERSION(3,0,0)
- g_signal_connect(G_OBJECT(inst->area), "draw",
- G_CALLBACK(draw_area), inst);
-#else
- g_signal_connect(G_OBJECT(inst->area), "expose_event",
- G_CALLBACK(expose_area), inst);
-#endif
- g_signal_connect(G_OBJECT(inst->area), "button_press_event",
- G_CALLBACK(button_event), inst);
- g_signal_connect(G_OBJECT(inst->area), "button_release_event",
- G_CALLBACK(button_event), inst);
-#if GTK_CHECK_VERSION(2,0,0)
- g_signal_connect(G_OBJECT(inst->area), "scroll_event",
- G_CALLBACK(scroll_event), inst);
-#endif
- g_signal_connect(G_OBJECT(inst->area), "motion_notify_event",
- G_CALLBACK(motion_event), inst);
-#if GTK_CHECK_VERSION(2,0,0)
- g_signal_connect(G_OBJECT(inst->imc), "commit",
- G_CALLBACK(input_method_commit_event), inst);
-#endif
- if (conf_get_bool(inst->conf, CONF_scrollbar))
- g_signal_connect(G_OBJECT(inst->sbar_adjust), "value_changed",
- G_CALLBACK(scrollbar_moved), inst);
- gtk_widget_add_events(GTK_WIDGET(inst->area),
- GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
- GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
- GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK
-#if GTK_CHECK_VERSION(3,4,0)
- | GDK_SMOOTH_SCROLL_MASK
-#endif
- );
-
- set_window_icon(inst->window, main_icon, n_main_icon);
-
- gtk_widget_show(inst->window);
-
- set_window_background(inst);
-
- /*
- * Set up the Ctrl+rightclick context menu.
- */
- {
- GtkWidget *menuitem;
- char *s;
-
- inst->menu = gtk_menu_new();
-
-#define MKMENUITEM(title, func) do \
- { \
- menuitem = gtk_menu_item_new_with_label(title); \
- gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \
- gtk_widget_show(menuitem); \
- g_signal_connect(G_OBJECT(menuitem), "activate", \
- G_CALLBACK(func), inst); \
- } while (0)
-
-#define MKSUBMENU(title) do \
- { \
- menuitem = gtk_menu_item_new_with_label(title); \
- gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \
- gtk_widget_show(menuitem); \
- } while (0)
-
-#define MKSEP() do \
- { \
- menuitem = gtk_menu_item_new(); \
- gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \
- gtk_widget_show(menuitem); \
- } while (0)
-
- if (new_session)
- MKMENUITEM("New Session...", new_session_menuitem);
- MKMENUITEM("Restart Session", restart_session_menuitem);
- inst->restartitem = menuitem;
- gtk_widget_set_sensitive(inst->restartitem, false);
- MKMENUITEM("Duplicate Session", dup_session_menuitem);
- if (saved_sessions) {
- inst->sessionsmenu = gtk_menu_new();
- /* sessionsmenu will be updated when it's invoked */
- /* XXX is this the right way to do dynamic menus in Gtk? */
- MKMENUITEM("Saved Sessions", update_savedsess_menu);
- gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem),
- inst->sessionsmenu);
- }
- MKSEP();
- MKMENUITEM("Change Settings...", change_settings_menuitem);
- MKSEP();
- if (use_event_log)
- MKMENUITEM("Event Log", event_log_menuitem);
- MKSUBMENU("Special Commands");
- inst->specialsmenu = gtk_menu_new();
- gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), inst->specialsmenu);
- inst->specialsitem1 = menuitem;
- MKSEP();
- inst->specialsitem2 = menuitem;
- gtk_widget_hide(inst->specialsitem1);
- gtk_widget_hide(inst->specialsitem2);
- MKMENUITEM("Clear Scrollback", clear_scrollback_menuitem);
- MKMENUITEM("Reset Terminal", reset_terminal_menuitem);
- MKSEP();
- MKMENUITEM("Copy to " CLIPNAME_EXPLICIT_OBJECT,
- copy_clipboard_menuitem);
- MKMENUITEM("Paste from " CLIPNAME_EXPLICIT_OBJECT,
- paste_clipboard_menuitem);
- MKMENUITEM("Copy All", copy_all_menuitem);
- MKSEP();
- s = dupcat("About ", appname);
- MKMENUITEM(s, about_menuitem);
- sfree(s);
-#undef MKMENUITEM
-#undef MKSUBMENU
-#undef MKSEP
- }
-
- inst->textcursor = make_mouse_ptr(inst, GDK_XTERM);
- inst->rawcursor = make_mouse_ptr(inst, GDK_LEFT_PTR);
- inst->waitcursor = make_mouse_ptr(inst, GDK_WATCH);
- inst->blankcursor = make_mouse_ptr(inst, -1);
- inst->currcursor = inst->textcursor;
- show_mouseptr(inst, true);
-
- inst->eventlogstuff = eventlogstuff_new();
-
- inst->term = term_init(inst->conf, &inst->ucsdata, &inst->termwin);
- setup_clipboards(inst, inst->term, inst->conf);
- inst->logctx = log_init(&inst->logpolicy, inst->conf);
- term_provide_logctx(inst->term, inst->logctx);
-
- term_size(inst->term, inst->height, inst->width,
- conf_get_int(inst->conf, CONF_savelines));
-
-#if GTK_CHECK_VERSION(2,0,0)
- /* Delay this signal connection until after inst->term exists */
- g_signal_connect(G_OBJECT(inst->window), "window_state_event",
- G_CALLBACK(window_state_event), inst);
-#endif
-
- inst->exited = false;
-
- start_backend(inst);
-
- if (inst->ldisc) /* early backend failure might make this NULL already */
- ldisc_echoedit_update(inst->ldisc); /* cause ldisc to notice changes */
-}
-
-static bool gtk_seat_set_trust_status(Seat *seat, bool trusted)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- term_set_trust_status(inst->term, trusted);
- return true;
-}
-
-static bool gtk_seat_get_cursor_position(Seat *seat, int *x, int *y)
-{
- GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
- if (inst->term) {
- term_get_cursor_position(inst->term, x, y);
- return true;
- }
- return false;
-}
diff --git a/unix/keygen-noise.c b/unix/keygen-noise.c
new file mode 100644
index 00000000..ab7de68d
--- /dev/null
+++ b/unix/keygen-noise.c
@@ -0,0 +1,64 @@
+/*
+ * keygen-noise.c: Unix implementation of get_heavy_noise() from cmdgen.c.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "putty.h"
+
+char *get_random_data(int len, const char *device)
+{
+ char *buf = snewn(len, char);
+ int fd;
+ int ngot, ret;
+
+ if (!device) {
+ static const char *const default_devices[] = {
+ "/dev/urandom", "/dev/random"
+ };
+ size_t i;
+
+ for (i = 0; i < lenof(default_devices); i++) {
+ if (access(default_devices[i], R_OK) == 0) {
+ device = default_devices[i];
+ break;
+ }
+ }
+
+ if (!device) {
+ sfree(buf);
+ fprintf(stderr, "puttygen: cannot find a readable "
+ "random number source; use --random-device\n");
+ return NULL;
+ }
+ }
+
+ fd = open(device, O_RDONLY);
+ if (fd < 0) {
+ sfree(buf);
+ fprintf(stderr, "puttygen: %s: open: %s\n",
+ device, strerror(errno));
+ return NULL;
+ }
+
+ ngot = 0;
+ while (ngot < len) {
+ ret = read(fd, buf+ngot, len-ngot);
+ if (ret < 0) {
+ close(fd);
+ sfree(buf);
+ fprintf(stderr, "puttygen: %s: read: %s\n",
+ device, strerror(errno));
+ return NULL;
+ }
+ ngot += ret;
+ }
+
+ close(fd);
+
+ return buf;
+}
diff --git a/unix/local-proxy.c b/unix/local-proxy.c
new file mode 100644
index 00000000..157f9207
--- /dev/null
+++ b/unix/local-proxy.c
@@ -0,0 +1,116 @@
+/*
+ * local-proxy.c: Unix implementation of platform_new_connection(),
+ * supporting an OpenSSH-like proxy command.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+#include "proxy/proxy.h"
+
+char *platform_setup_local_proxy(Socket *socket, const char *cmd)
+{
+ /*
+ * Create the pipes to the proxy command, and spawn the proxy
+ * command process.
+ */
+ int to_cmd_pipe[2], from_cmd_pipe[2], cmd_err_pipe[2];
+ if (pipe(to_cmd_pipe) < 0 ||
+ pipe(from_cmd_pipe) < 0 ||
+ pipe(cmd_err_pipe) < 0) {
+ return dupprintf("pipe: %s", strerror(errno));
+ }
+ cloexec(to_cmd_pipe[1]);
+ cloexec(from_cmd_pipe[0]);
+ cloexec(cmd_err_pipe[0]);
+
+ int pid = fork();
+ if (pid == 0) {
+ close(0);
+ close(1);
+ dup2(to_cmd_pipe[0], 0);
+ dup2(from_cmd_pipe[1], 1);
+ close(to_cmd_pipe[0]);
+ close(from_cmd_pipe[1]);
+ dup2(cmd_err_pipe[1], 2);
+ noncloexec(0);
+ noncloexec(1);
+ execl("/bin/sh", "sh", "-c", cmd, (void *)NULL);
+ _exit(255);
+ }
+
+ if (pid < 0) {
+ return dupprintf("fork: %s", strerror(errno));
+ }
+
+ close(to_cmd_pipe[0]);
+ close(from_cmd_pipe[1]);
+ close(cmd_err_pipe[1]);
+
+ setup_fd_socket(socket, from_cmd_pipe[0], to_cmd_pipe[1], cmd_err_pipe[0]);
+
+ return NULL;
+}
+
+Socket *platform_new_connection(SockAddr *addr, const char *hostname,
+ int port, bool privport,
+ bool oobinline, bool nodelay, bool keepalive,
+ Plug *plug, Conf *conf, Interactor *itr)
+{
+ switch (conf_get_int(conf, CONF_proxy_type)) {
+ case PROXY_CMD: {
+ DeferredSocketOpener *opener = local_proxy_opener(
+ addr, port, plug, conf, itr);
+ Socket *socket = make_deferred_fd_socket(opener, addr, port, plug);
+ local_proxy_opener_set_socket(opener, socket);
+ return socket;
+ }
+
+ case PROXY_FUZZ: {
+ char *cmd = format_telnet_command(addr, port, conf, NULL);
+ int outfd = open("/dev/null", O_WRONLY);
+ if (outfd == -1) {
+ sfree(cmd);
+ return new_error_socket_fmt(
+ plug, "/dev/null: %s", strerror(errno));
+ }
+ int infd = open(cmd, O_RDONLY);
+ if (infd == -1) {
+ Socket *toret = new_error_socket_fmt(
+ plug, "%s: %s", cmd, strerror(errno));
+ sfree(cmd);
+ close(outfd);
+ return toret;
+ }
+ sfree(cmd);
+ return make_fd_socket(infd, outfd, -1, addr, port, plug);
+ }
+
+ default:
+ return NULL;
+ }
+}
+
+Socket *platform_start_subprocess(const char *cmd, Plug *plug,
+ const char *prefix)
+{
+ Socket *socket = make_deferred_fd_socket(
+ null_deferred_socket_opener(),
+ sk_nonamelookup("<local command>"), 0, plug);
+ char *err = platform_setup_local_proxy(socket, cmd);
+ fd_socket_set_psb_prefix(socket, prefix);
+
+ if (err) {
+ sk_close(socket);
+ socket = new_error_socket_fmt(plug, "%s", err);
+ sfree(err);
+ }
+
+ return socket;
+}
diff --git a/unix/main-gtk-application.c b/unix/main-gtk-application.c
new file mode 100644
index 00000000..963c93fc
--- /dev/null
+++ b/unix/main-gtk-application.c
@@ -0,0 +1,326 @@
+/*
+ * main-gtk-application.c: a top-level front end to GUI PuTTY and
+ * pterm, using GtkApplication. Suitable for OS X. Currently
+ * unfinished.
+ *
+ * (You could run it on ordinary Linux GTK too, in principle, but I
+ * don't think it would be particularly useful to do so, even once
+ * it's fully working.)
+ */
+
+/*
+
+Building this for OS X is currently broken, because the new
+CMake-based build system doesn't support it yet. Probably what needs
+doing is to add it back in to unix/CMakeLists.txt under a condition
+like if(CMAKE_SYSTEM_NAME MATCHES "Darwin").
+
+*/
+
+/*
+
+TODO list for a sensible GTK3 PuTTY/pterm on OS X:
+
+Still to do on the application menu bar: items that have to vary with
+context or user action (saved sessions and mid-session special
+commands), and disabling/enabling the main actions in parallel with
+their counterparts in the Ctrl-rightclick context menu.
+
+Mouse wheel events and trackpad scrolling gestures don't work quite
+right in the terminal drawing area. This seems to be a combination of
+two things, neither of which I completely understand yet. Firstly, on
+OS X GTK my trackpad seems to generate GDK scroll events for which
+gdk_event_get_scroll_deltas returns integers rather than integer
+multiples of 1/30, so we end up scrolling by very large amounts;
+secondly, the window doesn't seem to receive a GTK "draw" event until
+after the entire scroll gesture is complete, which means we don't get
+constant visual feedback on how much we're scrolling by.
+
+There doesn't seem to be a resize handle on terminal windows. Then
+again, they do seem to _be_ resizable; the handle just isn't shown.
+Perhaps that's a feature (certainly in a scrollbarless configuration
+the handle gets in the way of the bottom right character cell in the
+terminal itself), but it would be nice to at least understand _why_ it
+happens and perhaps include an option to put it back again.
+
+A slight oddity with menus that pop up directly under the mouse
+pointer: mousing over the menu items doesn't highlight them initially,
+but if I mouse off the menu and back on (without un-popping-it-up)
+then suddenly that does work. I don't know if this is something I can
+fix, though; it might very well be a quirk of the underlying GTK.
+
+Does OS X have a standard system of online help that I could tie into?
+
+Need to work out what if anything we can do with Pageant on OS X.
+Perhaps it's too much bother and we should just talk to the
+system-provided SSH agent? Or perhaps not.
+
+Nice-to-have: a custom right-click menu from the application's dock
+tile, listing the saved sessions for quick launch. As far as I know
+there's nothing built in to GtkApplication that can produce this, but
+it's possible we might be able to drop a piece of native Cocoa code in
+under ifdef, substituting an application delegate of our own which
+forwards all methods we're not interested in to the GTK-provided one?
+
+At the point where this becomes polished enough to publish pre-built,
+I suppose I'll have to look into OS X code signing.
+https://wiki.gnome.org/Projects/GTK%2B/OSX/Bundling has some links.
+
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <unistd.h>
+
+#include <gtk/gtk.h>
+
+#define MAY_REFER_TO_GTK_IN_HEADERS
+
+#include "putty.h"
+#include "gtkmisc.h"
+
+char *x_get_default(const char *key) { return NULL; }
+
+const bool buildinfo_gtk_relevant = true;
+
+#if !GTK_CHECK_VERSION(3,0,0)
+#error This front end only works in GTK 3
+#endif
+
+static void startup(GApplication *app, gpointer user_data)
+{
+ GMenu *menubar, *menu, *section;
+
+ menubar = g_menu_new();
+
+ menu = g_menu_new();
+ g_menu_append_submenu(menubar, "File", G_MENU_MODEL(menu));
+
+ section = g_menu_new();
+ g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
+ g_menu_append(section, "New Window", "app.newwin");
+
+ menu = g_menu_new();
+ g_menu_append_submenu(menubar, "Edit", G_MENU_MODEL(menu));
+
+ section = g_menu_new();
+ g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
+ g_menu_append(section, "Copy", "win.copy");
+ g_menu_append(section, "Paste", "win.paste");
+ g_menu_append(section, "Copy All", "win.copyall");
+
+ menu = g_menu_new();
+ g_menu_append_submenu(menubar, "Window", G_MENU_MODEL(menu));
+
+ section = g_menu_new();
+ g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
+ g_menu_append(section, "Restart Session", "win.restart");
+ g_menu_append(section, "Duplicate Session", "win.duplicate");
+
+ section = g_menu_new();
+ g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
+ g_menu_append(section, "Change Settings", "win.changesettings");
+
+ if (use_event_log) {
+ section = g_menu_new();
+ g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
+ g_menu_append(section, "Event Log", "win.eventlog");
+ }
+
+ section = g_menu_new();
+ g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
+ g_menu_append(section, "Clear Scrollback", "win.clearscrollback");
+ g_menu_append(section, "Reset Terminal", "win.resetterm");
+
+#if GTK_CHECK_VERSION(3,12,0)
+#define SET_ACCEL(app, command, accel) do \
+ { \
+ static const char *const accels[] = { accel, NULL }; \
+ gtk_application_set_accels_for_action( \
+ GTK_APPLICATION(app), command, accels); \
+ } while (0)
+#else
+ /* The Gtk function used above was new in 3.12; the one below
+ * was deprecated from 3.14. */
+#define SET_ACCEL(app, command, accel) \
+ gtk_application_add_accelerator(GTK_APPLICATION(app), accel, \
+ command, NULL)
+#endif
+
+ SET_ACCEL(app, "app.newwin", "<Primary>n");
+ SET_ACCEL(app, "win.copy", "<Primary>c");
+ SET_ACCEL(app, "win.paste", "<Primary>v");
+
+#undef SET_ACCEL
+
+ gtk_application_set_menubar(GTK_APPLICATION(app),
+ G_MENU_MODEL(menubar));
+}
+
+#define WIN_ACTION_LIST(X) \
+ X("copy", MA_COPY) \
+ X("paste", MA_PASTE) \
+ X("copyall", MA_COPY_ALL) \
+ X("duplicate", MA_DUPLICATE_SESSION) \
+ X("restart", MA_RESTART_SESSION) \
+ X("changesettings", MA_CHANGE_SETTINGS) \
+ X("clearscrollback", MA_CLEAR_SCROLLBACK) \
+ X("resetterm", MA_RESET_TERMINAL) \
+ X("eventlog", MA_EVENT_LOG) \
+ /* end of list */
+
+#define WIN_ACTION_CALLBACK(name, id) \
+static void win_action_cb_ ## id(GSimpleAction *a, GVariant *p, gpointer d) \
+{ app_menu_action(d, id); }
+WIN_ACTION_LIST(WIN_ACTION_CALLBACK)
+#undef WIN_ACTION_CALLBACK
+
+static const GActionEntry win_actions[] = {
+#define WIN_ACTION_ENTRY(name, id) { name, win_action_cb_ ## id },
+WIN_ACTION_LIST(WIN_ACTION_ENTRY)
+#undef WIN_ACTION_ENTRY
+};
+
+static GtkApplication *app;
+GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend)
+{
+ GtkWidget *win = gtk_application_window_new(app);
+ g_action_map_add_action_entries(G_ACTION_MAP(win),
+ win_actions,
+ G_N_ELEMENTS(win_actions),
+ frontend);
+ return win;
+}
+
+void launch_duplicate_session(Conf *conf)
+{
+ assert(!dup_check_launchable || conf_launchable(conf));
+ g_application_hold(G_APPLICATION(app));
+ new_session_window(conf_copy(conf), NULL);
+}
+
+void session_window_closed(void)
+{
+ g_application_release(G_APPLICATION(app));
+}
+
+static void post_initial_config_box(void *vctx, int result)
+{
+ Conf *conf = (Conf *)vctx;
+
+ if (result > 0) {
+ new_session_window(conf, NULL);
+ } else if (result == 0) {
+ conf_free(conf);
+ g_application_release(G_APPLICATION(app));
+ }
+}
+
+void launch_saved_session(const char *str)
+{
+ Conf *conf = conf_new();
+ do_defaults(str, conf);
+
+ g_application_hold(G_APPLICATION(app));
+
+ if (!conf_launchable(conf)) {
+ initial_config_box(conf, post_initial_config_box, conf);
+ } else {
+ new_session_window(conf, NULL);
+ }
+}
+
+void launch_new_session(void)
+{
+ /* Same as launch_saved_session except that we pass NULL to
+ * do_defaults. */
+ launch_saved_session(NULL);
+}
+
+void new_app_win(GtkApplication *app)
+{
+ launch_new_session();
+}
+
+static void window_setup_error_callback(void *vctx, int result)
+{
+ g_application_release(G_APPLICATION(app));
+}
+
+void window_setup_error(const char *errmsg)
+{
+ create_message_box(NULL, "Error creating session window", errmsg,
+ string_width("Some sort of fiddly error message that "
+ "might be technical"),
+ true, &buttons_ok, window_setup_error_callback, NULL);
+}
+
+static void activate(GApplication *app,
+ gpointer user_data)
+{
+ new_app_win(GTK_APPLICATION(app));
+}
+
+static void newwin_cb(GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ new_app_win(GTK_APPLICATION(user_data));
+}
+
+static void quit_cb(GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ g_application_quit(G_APPLICATION(user_data));
+}
+
+static void about_cb(GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ about_box(NULL);
+}
+
+static const GActionEntry app_actions[] = {
+ { "newwin", newwin_cb },
+ { "about", about_cb },
+ { "quit", quit_cb },
+};
+
+int main(int argc, char **argv)
+{
+ int status;
+
+ /* Call the function in ux{putty,pterm}.c to do app-type
+ * specific setup */
+ setup(false); /* false means we are not a one-session process */
+
+ if (argc > 1) {
+ pty_osx_envrestore_prefix = argv[--argc];
+ }
+
+ {
+ const char *home = getenv("HOME");
+ if (home) {
+ if (chdir(home)) {}
+ }
+ }
+
+ gtkcomm_setup();
+
+ app = gtk_application_new("org.tartarus.projects.putty.macputty",
+ G_APPLICATION_FLAGS_NONE);
+ g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
+ g_signal_connect(app, "startup", G_CALLBACK(startup), NULL);
+ g_action_map_add_action_entries(G_ACTION_MAP(app),
+ app_actions,
+ G_N_ELEMENTS(app_actions),
+ app);
+
+ status = g_application_run(G_APPLICATION(app), argc, argv);
+ g_object_unref(app);
+
+ return status;
+}
diff --git a/unix/main-gtk-simple.c b/unix/main-gtk-simple.c
new file mode 100644
index 00000000..4cbb5a31
--- /dev/null
+++ b/unix/main-gtk-simple.c
@@ -0,0 +1,679 @@
+/*
+ * main-gtk-simple.c: the common main-program code between the
+ * straight-up Unix PuTTY and pterm, which they do not share with the
+ * multi-session main-gtk-application.c.
+ */
+
+#define _GNU_SOURCE
+
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <stdio.h>
+#include <time.h>
+#include <errno.h>
+#include <locale.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <gtk/gtk.h>
+#if !GTK_CHECK_VERSION(3,0,0)
+#include <gdk/gdkkeysyms.h>
+#endif
+
+#if GTK_CHECK_VERSION(2,0,0)
+#include <gtk/gtkimmodule.h>
+#endif
+
+#define MAY_REFER_TO_GTK_IN_HEADERS
+
+#include "putty.h"
+#include "terminal.h"
+#include "gtkcompat.h"
+#include "unifont.h"
+#include "gtkmisc.h"
+
+#ifndef NOT_X_WINDOWS
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include "x11misc.h"
+#endif
+
+static char *progname, **gtkargvstart;
+static int ngtkargs;
+
+static const char *app_name = "pterm";
+
+char *x_get_default(const char *key)
+{
+#ifndef NOT_X_WINDOWS
+ Display *disp;
+ if ((disp = get_x11_display()) == NULL)
+ return NULL;
+ return XGetDefault(disp, app_name, key);
+#else
+ return NULL;
+#endif
+}
+
+void fork_and_exec_self(int fd_to_close, ...)
+{
+ /*
+ * Re-execing ourself is not an exact science under Unix. I do
+ * the best I can by using /proc/self/exe if available and by
+ * assuming argv[0] can be found on $PATH if not.
+ *
+ * Note that we also have to reconstruct the elements of the
+ * original argv which gtk swallowed, since the user wants the
+ * new session to appear on the same X display as the old one.
+ */
+ char **args;
+ va_list ap;
+ int i, n;
+ int pid;
+
+ /*
+ * Collect the arguments with which to re-exec ourself.
+ */
+ va_start(ap, fd_to_close);
+ n = 2; /* progname and terminating NULL */
+ n += ngtkargs;
+ while (va_arg(ap, char *) != NULL)
+ n++;
+ va_end(ap);
+
+ args = snewn(n, char *);
+ args[0] = progname;
+ args[n-1] = NULL;
+ for (i = 0; i < ngtkargs; i++)
+ args[i+1] = gtkargvstart[i];
+
+ i++;
+ va_start(ap, fd_to_close);
+ while ((args[i++] = va_arg(ap, char *)) != NULL);
+ va_end(ap);
+
+ assert(i == n);
+
+ /*
+ * Do the double fork.
+ */
+ pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ sfree(args);
+ return;
+ }
+
+ if (pid == 0) {
+ int pid2 = fork();
+ if (pid2 < 0) {
+ perror("fork");
+ _exit(1);
+ } else if (pid2 > 0) {
+ /*
+ * First child has successfully forked second child. My
+ * Work Here Is Done. Note the use of _exit rather than
+ * exit: the latter appears to cause destroy messages
+ * to be sent to the X server. I suspect gtk uses
+ * atexit.
+ */
+ _exit(0);
+ }
+
+ /*
+ * If we reach here, we are the second child, so we now
+ * actually perform the exec.
+ */
+ if (fd_to_close >= 0)
+ close(fd_to_close);
+
+ execv("/proc/self/exe", args);
+ execvp(progname, args);
+ perror("exec");
+ _exit(127);
+
+ } else {
+ int status;
+ sfree(args);
+ waitpid(pid, &status, 0);
+ }
+
+}
+
+void launch_duplicate_session(Conf *conf)
+{
+ /*
+ * For this feature we must marshal conf and (possibly) pty_argv
+ * into a byte stream, create a pipe, and send this byte stream
+ * to the child through the pipe.
+ */
+ int i, ret;
+ strbuf *serialised;
+ char option[80];
+ int pipefd[2];
+
+ if (pipe(pipefd) < 0) {
+ perror("pipe");
+ return;
+ }
+
+ serialised = strbuf_new();
+
+ conf_serialise(BinarySink_UPCAST(serialised), conf);
+ if (use_pty_argv && pty_argv)
+ for (i = 0; pty_argv[i]; i++)
+ put_asciz(serialised, pty_argv[i]);
+
+ sprintf(option, "---[%d,%zu]", pipefd[0], serialised->len);
+ noncloexec(pipefd[0]);
+ fork_and_exec_self(pipefd[1], option, NULL);
+ close(pipefd[0]);
+
+ i = ret = 0;
+ while (i < serialised->len &&
+ (ret = write(pipefd[1], serialised->s + i,
+ serialised->len - i)) > 0)
+ i += ret;
+ if (ret < 0)
+ perror("write to pipe");
+ close(pipefd[1]);
+ strbuf_free(serialised);
+}
+
+void launch_new_session(void)
+{
+ fork_and_exec_self(-1, NULL);
+}
+
+void launch_saved_session(const char *str)
+{
+ fork_and_exec_self(-1, "-load", str, NULL);
+}
+
+int read_dupsession_data(Conf *conf, char *arg)
+{
+ int fd, i, ret, size;
+ char *data;
+ BinarySource src[1];
+
+ if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) {
+ fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg);
+ exit(1);
+ }
+
+ data = snewn(size, char);
+ i = ret = 0;
+ while (i < size && (ret = read(fd, data + i, size - i)) > 0)
+ i += ret;
+ if (ret < 0) {
+ perror("read from pipe");
+ exit(1);
+ } else if (i < size) {
+ fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n",
+ appname);
+ exit(1);
+ }
+
+ BinarySource_BARE_INIT(src, data, size);
+ if (!conf_deserialise(conf, src)) {
+ fprintf(stderr, "%s: malformed Duplicate Session data\n", appname);
+ exit(1);
+ }
+ if (use_pty_argv) {
+ int pty_argc = 0;
+ size_t argv_startpos = src->pos;
+
+ while (get_asciz(src), !get_err(src))
+ pty_argc++;
+
+ src->err = BSE_NO_ERROR;
+
+ if (pty_argc > 0) {
+ src->pos = argv_startpos;
+
+ pty_argv = snewn(pty_argc + 1, char *);
+ pty_argv[pty_argc] = NULL;
+ for (i = 0; i < pty_argc; i++)
+ pty_argv[i] = dupstr(get_asciz(src));
+ }
+ }
+
+ if (get_err(src) || get_avail(src) > 0) {
+ fprintf(stderr, "%s: malformed Duplicate Session data\n", appname);
+ exit(1);
+ }
+
+ sfree(data);
+ return 0;
+}
+
+static void help(FILE *fp) {
+ if(fprintf(fp,
+"pterm option summary:\n"
+"\n"
+" --display DISPLAY Specify X display to use (note '--')\n"
+" -name PREFIX Prefix when looking up resources (default: pterm)\n"
+" -fn FONT Normal text font\n"
+" -fb FONT Bold text font\n"
+" -geometry GEOMETRY Position and size of window (size in characters)\n"
+" -sl LINES Number of lines of scrollback\n"
+" -fg COLOUR, -bg COLOUR Foreground/background colour\n"
+" -bfg COLOUR, -bbg COLOUR Foreground/background bold colour\n"
+" -cfg COLOUR, -bfg COLOUR Foreground/background cursor colour\n"
+" -T TITLE Window title\n"
+" -ut, +ut Do(default) or do not update utmp\n"
+" -ls, +ls Do(default) or do not make shell a login shell\n"
+" -sb, +sb Do(default) or do not display a scrollbar\n"
+" -log PATH, -sessionlog PATH Log all output to a file\n"
+" -nethack Map numeric keypad to hjklyubn direction keys\n"
+" -xrm RESOURCE-STRING Set an X resource\n"
+" -e COMMAND [ARGS...] Execute command (consumes all remaining args)\n"
+ ) < 0 || fflush(fp) < 0) {
+ perror("output error");
+ exit(1);
+ }
+}
+
+static void version(FILE *fp) {
+ char *buildinfo_text = buildinfo("\n");
+ if(fprintf(fp, "%s: %s\n%s\n", appname, ver, buildinfo_text) < 0 ||
+ fflush(fp) < 0) {
+ perror("output error");
+ exit(1);
+ }
+ sfree(buildinfo_text);
+}
+
+static const char *geometry_string;
+
+void cmdline_error(const char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "%s: ", appname);
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ exit(1);
+}
+
+void window_setup_error(const char *errmsg)
+{
+ fprintf(stderr, "%s: %s\n", appname, errmsg);
+ exit(1);
+}
+
+bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf)
+{
+ bool err = false;
+ char *val;
+
+ /*
+ * Macros to make argument handling easier.
+ *
+ * Note that because they need to call `continue', they cannot be
+ * contained in the usual do {...} while (0) wrapper to make them
+ * syntactically single statements. I use the alternative if (1)
+ * {...} else ((void)0).
+ */
+#define EXPECTS_ARG if (1) { \
+ if (--argc <= 0) { \
+ err = true; \
+ fprintf(stderr, "%s: %s expects an argument\n", appname, p); \
+ continue; \
+ } else \
+ val = *++argv; \
+ } else ((void)0)
+#define SECOND_PASS_ONLY if (1) { \
+ if (!do_everything) \
+ continue; \
+ } else ((void)0)
+
+ while (--argc > 0) {
+ const char *p = *++argv;
+ int ret;
+
+ /*
+ * Shameless cheating. Debian requires all X terminal
+ * emulators to support `-T title'; but
+ * cmdline_process_param will eat -T (it means no-pty) and
+ * complain that pterm doesn't support it. So, in pterm
+ * only, we convert -T into -title.
+ */
+ if ((cmdline_tooltype & TOOLTYPE_NONNETWORK) &&
+ !strcmp(p, "-T"))
+ p = "-title";
+
+ ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
+ do_everything ? 1 : -1, conf);
+
+ if (ret == -2) {
+ cmdline_error("option \"%s\" requires an argument", p);
+ } else if (ret == 2) {
+ --argc, ++argv; /* skip next argument */
+ continue;
+ } else if (ret == 1) {
+ continue;
+ }
+
+ if (!strcmp(p, "-fn") || !strcmp(p, "-font")) {
+ FontSpec *fs;
+ EXPECTS_ARG;
+ SECOND_PASS_ONLY;
+ fs = fontspec_new(val);
+ conf_set_fontspec(conf, CONF_font, fs);
+ fontspec_free(fs);
+
+ } else if (!strcmp(p, "-fb")) {
+ FontSpec *fs;
+ EXPECTS_ARG;
+ SECOND_PASS_ONLY;
+ fs = fontspec_new(val);
+ conf_set_fontspec(conf, CONF_boldfont, fs);
+ fontspec_free(fs);
+
+ } else if (!strcmp(p, "-fw")) {
+ FontSpec *fs;
+ EXPECTS_ARG;
+ SECOND_PASS_ONLY;
+ fs = fontspec_new(val);
+ conf_set_fontspec(conf, CONF_widefont, fs);
+ fontspec_free(fs);
+
+ } else if (!strcmp(p, "-fwb")) {
+ FontSpec *fs;
+ EXPECTS_ARG;
+ SECOND_PASS_ONLY;
+ fs = fontspec_new(val);
+ conf_set_fontspec(conf, CONF_wideboldfont, fs);
+ fontspec_free(fs);
+
+ } else if (!strcmp(p, "-cs")) {
+ EXPECTS_ARG;
+ SECOND_PASS_ONLY;
+ conf_set_str(conf, CONF_line_codepage, val);
+
+ } else if (!strcmp(p, "-geometry")) {
+ EXPECTS_ARG;
+ SECOND_PASS_ONLY;
+ geometry_string = val;
+ } else if (!strcmp(p, "-sl")) {
+ EXPECTS_ARG;
+ SECOND_PASS_ONLY;
+ conf_set_int(conf, CONF_savelines, atoi(val));
+
+ } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") ||
+ !strcmp(p, "-bfg") || !strcmp(p, "-bbg") ||
+ !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) {
+ EXPECTS_ARG;
+ SECOND_PASS_ONLY;
+
+ {
+#if GTK_CHECK_VERSION(3,0,0)
+ GdkRGBA rgba;
+ bool success = gdk_rgba_parse(&rgba, val);
+#else
+ GdkColor col;
+ bool success = gdk_color_parse(val, &col);
+#endif
+
+ if (!success) {
+ err = true;
+ fprintf(stderr, "%s: unable to parse colour \"%s\"\n",
+ appname, val);
+ } else {
+#if GTK_CHECK_VERSION(3,0,0)
+ int r = rgba.red * 255;
+ int g = rgba.green * 255;
+ int b = rgba.blue * 255;
+#else
+ int r = col.red / 256;
+ int g = col.green / 256;
+ int b = col.blue / 256;
+#endif
+
+ int index;
+ index = (!strcmp(p, "-fg") ? 0 :
+ !strcmp(p, "-bg") ? 2 :
+ !strcmp(p, "-bfg") ? 1 :
+ !strcmp(p, "-bbg") ? 3 :
+ !strcmp(p, "-cfg") ? 4 :
+ !strcmp(p, "-cbg") ? 5 : -1);
+ assert(index != -1);
+
+ conf_set_int_int(conf, CONF_colours, index*3+0, r);
+ conf_set_int_int(conf, CONF_colours, index*3+1, g);
+ conf_set_int_int(conf, CONF_colours, index*3+2, b);
+ }
+ }
+
+ } else if (use_pty_argv && !strcmp(p, "-e")) {
+ /* This option swallows all further arguments. */
+ if (!do_everything)
+ break;
+
+ if (--argc > 0) {
+ int i;
+ pty_argv = snewn(argc+1, char *);
+ ++argv;
+ for (i = 0; i < argc; i++)
+ pty_argv[i] = argv[i];
+ pty_argv[argc] = NULL;
+ break; /* finished command-line processing */
+ } else
+ err = true, fprintf(stderr, "%s: -e expects an argument\n",
+ appname);
+
+ } else if (!strcmp(p, "-title")) {
+ EXPECTS_ARG;
+ SECOND_PASS_ONLY;
+ conf_set_str(conf, CONF_wintitle, val);
+
+ } else if (!strcmp(p, "-log")) {
+ Filename *fn;
+ EXPECTS_ARG;
+ SECOND_PASS_ONLY;
+ fn = filename_from_str(val);
+ conf_set_filename(conf, CONF_logfilename, fn);
+ conf_set_int(conf, CONF_logtype, LGTYP_DEBUG);
+ filename_free(fn);
+
+ } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) {
+ SECOND_PASS_ONLY;
+ conf_set_bool(conf, CONF_stamp_utmp, false);
+
+ } else if (!strcmp(p, "-ut")) {
+ SECOND_PASS_ONLY;
+ conf_set_bool(conf, CONF_stamp_utmp, true);
+
+ } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) {
+ SECOND_PASS_ONLY;
+ conf_set_bool(conf, CONF_login_shell, false);
+
+ } else if (!strcmp(p, "-ls")) {
+ SECOND_PASS_ONLY;
+ conf_set_bool(conf, CONF_login_shell, true);
+
+ } else if (!strcmp(p, "-nethack")) {
+ SECOND_PASS_ONLY;
+ conf_set_bool(conf, CONF_nethack_keypad, true);
+
+ } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) {
+ SECOND_PASS_ONLY;
+ conf_set_bool(conf, CONF_scrollbar, false);
+
+ } else if (!strcmp(p, "-sb")) {
+ SECOND_PASS_ONLY;
+ conf_set_bool(conf, CONF_scrollbar, true);
+
+ } else if (!strcmp(p, "-name")) {
+ EXPECTS_ARG;
+ app_name = val;
+
+ } else if (!strcmp(p, "-xrm")) {
+ EXPECTS_ARG;
+ provide_xrm_string(val, appname);
+
+ } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) {
+ help(stdout);
+ exit(0);
+
+ } else if(!strcmp(p, "-version") || !strcmp(p, "--version")) {
+ version(stdout);
+ exit(0);
+
+ } else if (!strcmp(p, "-pgpfp")) {
+ pgp_fingerprints();
+ exit(1);
+
+ } else if (has_ca_config_box &&
+ (!strcmp(p, "-host-ca") || !strcmp(p, "--host-ca") ||
+ !strcmp(p, "-host_ca") || !strcmp(p, "--host_ca"))) {
+ show_ca_config_box_synchronously();
+ exit(0);
+
+ } else if (p[0] != '-') {
+ /* Non-option arguments not handled by cmdline.c are errors. */
+ if (do_everything) {
+ err = true;
+ fprintf(stderr, "%s: unexpected non-option argument '%s'\n",
+ appname, p);
+ }
+
+ } else {
+ err = true;
+ fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p);
+ }
+ }
+
+ return err;
+}
+
+GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend)
+{
+ return gtk_window_new(GTK_WINDOW_TOPLEVEL);
+}
+
+const bool buildinfo_gtk_relevant = true;
+
+struct post_initial_config_box_ctx {
+ Conf *conf;
+ const char *geometry_string;
+};
+
+static void post_initial_config_box(void *vctx, int result)
+{
+ struct post_initial_config_box_ctx ctx =
+ *(struct post_initial_config_box_ctx *)vctx;
+ sfree(vctx);
+
+ if (result > 0) {
+ new_session_window(ctx.conf, ctx.geometry_string);
+ } else {
+ /* In this main(), which only runs one session in total, a
+ * negative result from the initial config box means we simply
+ * terminate. */
+ conf_free(ctx.conf);
+ gtk_main_quit();
+ }
+}
+
+void session_window_closed(void)
+{
+ gtk_main_quit();
+}
+
+int main(int argc, char **argv)
+{
+ Conf *conf;
+ bool need_config_box;
+
+ setlocale(LC_CTYPE, "");
+
+ /* Call the function in ux{putty,pterm}.c to do app-type
+ * specific setup */
+ setup(true); /* true means we are a one-session process */
+
+ progname = argv[0];
+
+ /*
+ * Copy the original argv before letting gtk_init fiddle with
+ * it. It will be required later.
+ */
+ {
+ int i, oldargc;
+ gtkargvstart = snewn(argc-1, char *);
+ for (i = 1; i < argc; i++)
+ gtkargvstart[i-1] = dupstr(argv[i]);
+ oldargc = argc;
+ gtk_init(&argc, &argv);
+ ngtkargs = oldargc - argc;
+ }
+
+ conf = conf_new();
+
+ gtkcomm_setup();
+
+ /*
+ * Block SIGPIPE: if we attempt Duplicate Session or similar and
+ * it falls over in some way, we certainly don't want SIGPIPE
+ * terminating the main pterm/PuTTY. However, we'll have to
+ * unblock it again when pterm forks.
+ */
+ block_signal(SIGPIPE, true);
+
+ if (argc > 1 && !strncmp(argv[1], "---", 3)) {
+ read_dupsession_data(conf, argv[1]);
+ /* Splatter this argument so it doesn't clutter a ps listing */
+ smemclr(argv[1], strlen(argv[1]));
+
+ assert(!dup_check_launchable || conf_launchable(conf));
+ need_config_box = false;
+ } else {
+ if (do_cmdline(argc, argv, false, conf))
+ exit(1); /* pre-defaults pass to get -class */
+ do_defaults(NULL, conf);
+ if (do_cmdline(argc, argv, true, conf))
+ exit(1); /* post-defaults, do everything */
+
+ cmdline_run_saved(conf);
+
+ if (cmdline_tooltype & TOOLTYPE_HOST_ARG)
+ need_config_box = !cmdline_host_ok(conf);
+ else
+ need_config_box = false;
+ }
+
+ if (need_config_box) {
+ /*
+ * Put up the initial config box, which will pass the provided
+ * parameters (with conf updated) to new_session_window() when
+ * (if) the user selects Open. Or it might close without
+ * creating a session window, if the user selects Cancel. Or
+ * it might just create the session window immediately if this
+ * is a pterm-style app which doesn't have an initial config
+ * box at all.
+ */
+ struct post_initial_config_box_ctx *ctx =
+ snew(struct post_initial_config_box_ctx);
+ ctx->conf = conf;
+ ctx->geometry_string = geometry_string;
+ initial_config_box(conf, post_initial_config_box, ctx);
+ } else {
+ /*
+ * No initial config needed; just create the session window
+ * now.
+ */
+ new_session_window(conf, geometry_string);
+ }
+
+ gtk_main();
+
+ return 0;
+}
diff --git a/unix/network.c b/unix/network.c
new file mode 100644
index 00000000..31c754bd
--- /dev/null
+++ b/unix/network.c
@@ -0,0 +1,1755 @@
+/*
+ * Unix networking abstraction.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#include <sys/un.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "putty.h"
+#include "network.h"
+#include "tree234.h"
+
+/* Solaris needs <sys/sockio.h> for SIOCATMARK. */
+#ifndef SIOCATMARK
+#include <sys/sockio.h>
+#endif
+
+#ifndef X11_UNIX_PATH
+# define X11_UNIX_PATH "/tmp/.X11-unix/X"
+#endif
+
+/*
+ * Access to sockaddr types without breaking C strict aliasing rules.
+ */
+union sockaddr_union {
+ struct sockaddr_storage storage;
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+#ifndef NO_IPV6
+ struct sockaddr_in6 sin6;
+#endif
+ struct sockaddr_un su;
+};
+
+/*
+ * Mutable state that goes with a SockAddr: stores information
+ * about where in the list of candidate IP(v*) addresses we've
+ * currently got to.
+ */
+typedef struct SockAddrStep_tag SockAddrStep;
+struct SockAddrStep_tag {
+#ifndef NO_IPV6
+ struct addrinfo *ai; /* steps along addr->ais */
+#endif
+ int curraddr;
+};
+
+typedef struct NetSocket NetSocket;
+struct NetSocket {
+ const char *error;
+ int s;
+ Plug *plug;
+ bufchain output_data;
+ bool connected; /* irrelevant for listening sockets */
+ bool writable;
+ bool frozen; /* this causes readability notifications to be ignored */
+ bool localhost_only; /* for listening sockets */
+ char oobdata[1];
+ size_t sending_oob;
+ bool oobpending; /* is there OOB data available to read? */
+ bool oobinline;
+ enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
+ bool incomingeof;
+ int pending_error; /* in case send() returns error */
+ bool listener;
+ bool nodelay, keepalive; /* for connect()-type sockets */
+ bool privport;
+ int port; /* and again */
+ SockAddr *addr;
+ SockAddrStep step;
+ /*
+ * We sometimes need pairs of Socket structures to be linked:
+ * if we are listening on the same IPv6 and v4 port, for
+ * example. So here we define `parent' and `child' pointers to
+ * track this link.
+ */
+ NetSocket *parent, *child;
+
+ Socket sock;
+};
+
+struct SockAddr {
+ int refcount;
+ const char *error;
+ enum { UNRESOLVED, UNIX, IP } superfamily;
+#ifndef NO_IPV6
+ struct addrinfo *ais; /* Addresses IPv6 style. */
+#else
+ unsigned long *addresses; /* Addresses IPv4 style. */
+ int naddresses;
+#endif
+ char hostname[512]; /* Store an unresolved host name. */
+};
+
+/*
+ * Which address family this address belongs to. AF_INET for IPv4;
+ * AF_INET6 for IPv6; AF_UNSPEC indicates that name resolution has
+ * not been done and a simple host name is held in this SockAddr
+ * structure.
+ */
+#ifndef NO_IPV6
+#define SOCKADDR_FAMILY(addr, step) \
+ ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \
+ (addr)->superfamily == UNIX ? AF_UNIX : \
+ (step).ai ? (step).ai->ai_family : AF_INET)
+#else
+/* Here we gratuitously reference 'step' to avoid gcc warnings about
+ * 'set but not used' when compiling -DNO_IPV6 */
+#define SOCKADDR_FAMILY(addr, step) \
+ ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \
+ (addr)->superfamily == UNIX ? AF_UNIX : \
+ (step).curraddr ? AF_INET : AF_INET)
+#endif
+
+/*
+ * Start a SockAddrStep structure to step through multiple
+ * addresses.
+ */
+#ifndef NO_IPV6
+#define START_STEP(addr, step) \
+ ((step).ai = (addr)->ais, (step).curraddr = 0)
+#else
+#define START_STEP(addr, step) \
+ ((step).curraddr = 0)
+#endif
+
+static tree234 *sktree;
+
+static void uxsel_tell(NetSocket *s);
+
+static int cmpfortree(void *av, void *bv)
+{
+ NetSocket *a = (NetSocket *) av, *b = (NetSocket *) bv;
+ int as = a->s, bs = b->s;
+ if (as < bs)
+ return -1;
+ if (as > bs)
+ return +1;
+ if (a < b)
+ return -1;
+ if (a > b)
+ return +1;
+ return 0;
+}
+
+static int cmpforsearch(void *av, void *bv)
+{
+ NetSocket *b = (NetSocket *) bv;
+ int as = *(int *)av, bs = b->s;
+ if (as < bs)
+ return -1;
+ if (as > bs)
+ return +1;
+ return 0;
+}
+
+void sk_init(void)
+{
+ sktree = newtree234(cmpfortree);
+}
+
+void sk_cleanup(void)
+{
+ NetSocket *s;
+ int i;
+
+ if (sktree) {
+ for (i = 0; (s = index234(sktree, i)) != NULL; i++) {
+ close(s->s);
+ }
+ }
+}
+
+SockAddr *sk_namelookup(const char *host, char **canonicalname,
+ int address_family)
+{
+ *canonicalname = NULL;
+
+ if (host[0] == '/') {
+ *canonicalname = dupstr(host);
+ return unix_sock_addr(host);
+ }
+
+ SockAddr *addr = snew(SockAddr);
+ memset(addr, 0, sizeof(SockAddr));
+ addr->superfamily = UNRESOLVED;
+ addr->refcount = 1;
+
+#ifndef NO_IPV6
+ /*
+ * Use getaddrinfo, as long as it's available. This should handle
+ * both IPv4 and IPv6 address literals, and hostnames, in one
+ * unified API.
+ */
+ {
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET :
+ address_family == ADDRTYPE_IPV6 ? AF_INET6 :
+ AF_UNSPEC);
+ hints.ai_flags = AI_CANONNAME;
+ hints.ai_socktype = SOCK_STREAM;
+
+ /* strip [] on IPv6 address literals */
+ char *trimmed_host = host_strduptrim(host);
+ int err = getaddrinfo(trimmed_host, NULL, &hints, &addr->ais);
+ sfree(trimmed_host);
+
+ if (addr->ais) {
+ addr->superfamily = IP;
+ if (addr->ais->ai_canonname)
+ *canonicalname = dupstr(addr->ais->ai_canonname);
+ else
+ *canonicalname = dupstr(host);
+ } else {
+ addr->error = gai_strerror(err);
+ }
+ return addr;
+ }
+
+#else
+ /*
+ * Failing that (if IPv6 support was not compiled in), try the
+ * old-fashioned approach, which is to start by manually checking
+ * for an IPv4 literal and then use gethostbyname.
+ */
+ unsigned long a = inet_addr(host);
+ if (a != (unsigned long) INADDR_NONE) {
+ addr->addresses = snew(unsigned long);
+ addr->naddresses = 1;
+ addr->addresses[0] = ntohl(a);
+ addr->superfamily = IP;
+ *canonicalname = dupstr(host);
+ return addr;
+ }
+
+ struct hostent *h = gethostbyname(host);
+ if (h) {
+ addr->superfamily = IP;
+
+ size_t n;
+ for (n = 0; h->h_addr_list[n]; n++);
+ addr->addresses = snewn(n, unsigned long);
+ addr->naddresses = n;
+ for (n = 0; n < addr->naddresses; n++) {
+ uint32_t a;
+ memcpy(&a, h->h_addr_list[n], sizeof(a));
+ addr->addresses[n] = ntohl(a);
+ }
+
+ *canonicalname = dupstr(h->h_name);
+ } else {
+ addr->error = hstrerror(h_errno);
+ }
+ return addr;
+#endif
+}
+
+SockAddr *sk_nonamelookup(const char *host)
+{
+ SockAddr *ret = snew(SockAddr);
+ ret->error = NULL;
+ ret->superfamily = UNRESOLVED;
+ strncpy(ret->hostname, host, lenof(ret->hostname));
+ ret->hostname[lenof(ret->hostname)-1] = '\0';
+#ifndef NO_IPV6
+ ret->ais = NULL;
+#else
+ ret->addresses = NULL;
+#endif
+ ret->refcount = 1;
+ return ret;
+}
+
+static bool sk_nextaddr(SockAddr *addr, SockAddrStep *step)
+{
+#ifndef NO_IPV6
+ if (step->ai && step->ai->ai_next) {
+ step->ai = step->ai->ai_next;
+ return true;
+ } else
+ return false;
+#else
+ if (step->curraddr+1 < addr->naddresses) {
+ step->curraddr++;
+ return true;
+ } else {
+ return false;
+ }
+#endif
+}
+
+void sk_getaddr(SockAddr *addr, char *buf, int buflen)
+{
+ if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) {
+ strncpy(buf, addr->hostname, buflen);
+ buf[buflen-1] = '\0';
+ } else {
+#ifndef NO_IPV6
+ if (getnameinfo(addr->ais->ai_addr, addr->ais->ai_addrlen, buf, buflen,
+ NULL, 0, NI_NUMERICHOST) != 0) {
+ buf[0] = '\0';
+ strncat(buf, "<unknown>", buflen - 1);
+ }
+#else
+ struct in_addr a;
+ SockAddrStep step;
+ START_STEP(addr, step);
+ assert(SOCKADDR_FAMILY(addr, step) == AF_INET);
+ a.s_addr = htonl(addr->addresses[0]);
+ strncpy(buf, inet_ntoa(a), buflen);
+ buf[buflen-1] = '\0';
+#endif
+ }
+}
+
+/*
+ * This constructs a SockAddr that points at one specific sub-address
+ * of a parent SockAddr. The returned SockAddr does not own all its
+ * own memory: it points into the old one's data structures, so it
+ * MUST NOT be used after the old one is freed, and it MUST NOT be
+ * passed to sk_addr_free. (The latter is why it's returned by value
+ * rather than dynamically allocated - that should clue in anyone
+ * writing a call to it that something is weird about it.)
+ */
+static SockAddr sk_extractaddr_tmp(
+ SockAddr *addr, const SockAddrStep *step)
+{
+ SockAddr toret;
+ toret = *addr; /* structure copy */
+ toret.refcount = 1;
+
+ if (addr->superfamily == IP) {
+#ifndef NO_IPV6
+ toret.ais = step->ai;
+#else
+ assert(SOCKADDR_FAMILY(addr, *step) == AF_INET);
+ toret.addresses += step->curraddr;
+#endif
+ }
+
+ return toret;
+}
+
+bool sk_addr_needs_port(SockAddr *addr)
+{
+ if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool sk_hostname_is_local(const char *name)
+{
+ return !strcmp(name, "localhost") ||
+ !strcmp(name, "::1") ||
+ !strncmp(name, "127.", 4);
+}
+
+#define ipv4_is_loopback(addr) \
+ (((addr).s_addr & htonl(0xff000000)) == htonl(0x7f000000))
+
+static bool sockaddr_is_loopback(struct sockaddr *sa)
+{
+ union sockaddr_union *u = (union sockaddr_union *)sa;
+ switch (u->sa.sa_family) {
+ case AF_INET:
+ return ipv4_is_loopback(u->sin.sin_addr);
+#ifndef NO_IPV6
+ case AF_INET6:
+ return IN6_IS_ADDR_LOOPBACK(&u->sin6.sin6_addr);
+#endif
+ case AF_UNIX:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool sk_address_is_local(SockAddr *addr)
+{
+ if (addr->superfamily == UNRESOLVED)
+ return false; /* we don't know; assume not */
+ else if (addr->superfamily == UNIX)
+ return true;
+ else {
+#ifndef NO_IPV6
+ return sockaddr_is_loopback(addr->ais->ai_addr);
+#else
+ struct in_addr a;
+ SockAddrStep step;
+ START_STEP(addr, step);
+ assert(SOCKADDR_FAMILY(addr, step) == AF_INET);
+ a.s_addr = htonl(addr->addresses[0]);
+ return ipv4_is_loopback(a);
+#endif
+ }
+}
+
+bool sk_address_is_special_local(SockAddr *addr)
+{
+ return addr->superfamily == UNIX;
+}
+
+int sk_addrtype(SockAddr *addr)
+{
+ SockAddrStep step;
+ int family;
+ START_STEP(addr, step);
+ family = SOCKADDR_FAMILY(addr, step);
+
+ return (family == AF_INET ? ADDRTYPE_IPV4 :
+#ifndef NO_IPV6
+ family == AF_INET6 ? ADDRTYPE_IPV6 :
+#endif
+ ADDRTYPE_NAME);
+}
+
+void sk_addrcopy(SockAddr *addr, char *buf)
+{
+ SockAddrStep step;
+ int family;
+ START_STEP(addr, step);
+ family = SOCKADDR_FAMILY(addr, step);
+
+#ifndef NO_IPV6
+ if (family == AF_INET)
+ memcpy(buf, &((struct sockaddr_in *)step.ai->ai_addr)->sin_addr,
+ sizeof(struct in_addr));
+ else if (family == AF_INET6)
+ memcpy(buf, &((struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr,
+ sizeof(struct in6_addr));
+ else
+ unreachable("bad address family in sk_addrcopy");
+#else
+ struct in_addr a;
+
+ assert(family == AF_INET);
+ a.s_addr = htonl(addr->addresses[step.curraddr]);
+ memcpy(buf, (char*) &a.s_addr, 4);
+#endif
+}
+
+void sk_addr_free(SockAddr *addr)
+{
+ if (--addr->refcount > 0)
+ return;
+#ifndef NO_IPV6
+ if (addr->ais != NULL)
+ freeaddrinfo(addr->ais);
+#else
+ sfree(addr->addresses);
+#endif
+ sfree(addr);
+}
+
+SockAddr *sk_addr_dup(SockAddr *addr)
+{
+ addr->refcount++;
+ return addr;
+}
+
+static Plug *sk_net_plug(Socket *sock, Plug *p)
+{
+ NetSocket *s = container_of(sock, NetSocket, sock);
+ Plug *ret = s->plug;
+ if (p)
+ s->plug = p;
+ return ret;
+}
+
+static void sk_net_close(Socket *s);
+static size_t sk_net_write(Socket *s, const void *data, size_t len);
+static size_t sk_net_write_oob(Socket *s, const void *data, size_t len);
+static void sk_net_write_eof(Socket *s);
+static void sk_net_set_frozen(Socket *s, bool is_frozen);
+static SocketPeerInfo *sk_net_peer_info(Socket *s);
+static const char *sk_net_socket_error(Socket *s);
+
+static const SocketVtable NetSocket_sockvt = {
+ .plug = sk_net_plug,
+ .close = sk_net_close,
+ .write = sk_net_write,
+ .write_oob = sk_net_write_oob,
+ .write_eof = sk_net_write_eof,
+ .set_frozen = sk_net_set_frozen,
+ .socket_error = sk_net_socket_error,
+ .peer_info = sk_net_peer_info,
+};
+
+static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug)
+{
+ int sockfd = ctx.i;
+ NetSocket *ret;
+
+ /*
+ * Create NetSocket structure.
+ */
+ ret = snew(NetSocket);
+ ret->sock.vt = &NetSocket_sockvt;
+ ret->error = NULL;
+ ret->plug = plug;
+ bufchain_init(&ret->output_data);
+ ret->writable = true; /* to start with */
+ ret->sending_oob = 0;
+ ret->frozen = true;
+ ret->localhost_only = false; /* unused, but best init anyway */
+ ret->pending_error = 0;
+ ret->oobpending = false;
+ ret->outgoingeof = EOF_NO;
+ ret->incomingeof = false;
+ ret->listener = false;
+ ret->parent = ret->child = NULL;
+ ret->addr = NULL;
+ ret->connected = true;
+
+ ret->s = sockfd;
+
+ if (ret->s < 0) {
+ ret->error = strerror(errno);
+ return &ret->sock;
+ }
+
+ ret->oobinline = false;
+
+ uxsel_tell(ret);
+ add234(sktree, ret);
+
+ return &ret->sock;
+}
+
+static int try_connect(NetSocket *sock)
+{
+ int s;
+ union sockaddr_union u;
+ const union sockaddr_union *sa;
+ int err = 0;
+ short localport;
+ int salen, family;
+
+ /*
+ * Remove the socket from the tree before we overwrite its
+ * internal socket id, because that forms part of the tree's
+ * sorting criterion. We'll add it back before exiting this
+ * function, whether we changed anything or not.
+ */
+ del234(sktree, sock);
+
+ if (sock->s >= 0)
+ close(sock->s);
+
+ {
+ SockAddr thisaddr = sk_extractaddr_tmp(
+ sock->addr, &sock->step);
+ plug_log(sock->plug, PLUGLOG_CONNECT_TRYING,
+ &thisaddr, sock->port, NULL, 0);
+ }
+
+ /*
+ * Open socket.
+ */
+ family = SOCKADDR_FAMILY(sock->addr, sock->step);
+ assert(family != AF_UNSPEC);
+ s = socket(family, SOCK_STREAM, 0);
+ sock->s = s;
+
+ if (s < 0) {
+ err = errno;
+ goto ret;
+ }
+
+ cloexec(s);
+
+ if (sock->oobinline) {
+ int b = 1;
+ if (setsockopt(s, SOL_SOCKET, SO_OOBINLINE,
+ (void *) &b, sizeof(b)) < 0) {
+ err = errno;
+ close(s);
+ goto ret;
+ }
+ }
+
+ if (sock->nodelay && family != AF_UNIX) {
+ int b = 1;
+ if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY,
+ (void *) &b, sizeof(b)) < 0) {
+ err = errno;
+ close(s);
+ goto ret;
+ }
+ }
+
+ if (sock->keepalive) {
+ int b = 1;
+ if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
+ (void *) &b, sizeof(b)) < 0) {
+ err = errno;
+ close(s);
+ goto ret;
+ }
+ }
+
+ /*
+ * Bind to local address.
+ */
+ if (sock->privport)
+ localport = 1023; /* count from 1023 downwards */
+ else
+ localport = 0; /* just use port 0 (ie kernel picks) */
+
+ /* BSD IP stacks need sockaddr_in zeroed before filling in */
+ memset(&u,'\0',sizeof(u));
+
+ /* We don't try to bind to a local address for UNIX domain sockets. (Why
+ * do we bother doing the bind when localport == 0 anyway?) */
+ if (family != AF_UNIX) {
+ /* Loop round trying to bind */
+ while (1) {
+ int retcode;
+
+#ifndef NO_IPV6
+ if (family == AF_INET6) {
+ /* XXX use getaddrinfo to get a local address? */
+ u.sin6.sin6_family = AF_INET6;
+ u.sin6.sin6_addr = in6addr_any;
+ u.sin6.sin6_port = htons(localport);
+ retcode = bind(s, &u.sa, sizeof(u.sin6));
+ } else
+#endif
+ {
+ assert(family == AF_INET);
+ u.sin.sin_family = AF_INET;
+ u.sin.sin_addr.s_addr = htonl(INADDR_ANY);
+ u.sin.sin_port = htons(localport);
+ retcode = bind(s, &u.sa, sizeof(u.sin));
+ }
+ if (retcode >= 0) {
+ err = 0;
+ break; /* done */
+ } else {
+ err = errno;
+ if (err != EADDRINUSE) /* failed, for a bad reason */
+ break;
+ }
+
+ if (localport == 0)
+ break; /* we're only looping once */
+ localport--;
+ if (localport == 0)
+ break; /* we might have got to the end */
+ }
+
+ if (err)
+ goto ret;
+ }
+
+ /*
+ * Connect to remote address.
+ */
+ switch(family) {
+#ifndef NO_IPV6
+ case AF_INET:
+ /* XXX would be better to have got getaddrinfo() to fill in the port. */
+ ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port =
+ htons(sock->port);
+ sa = (const union sockaddr_union *)sock->step.ai->ai_addr;
+ salen = sock->step.ai->ai_addrlen;
+ break;
+ case AF_INET6:
+ ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port =
+ htons(sock->port);
+ sa = (const union sockaddr_union *)sock->step.ai->ai_addr;
+ salen = sock->step.ai->ai_addrlen;
+ break;
+#else
+ case AF_INET:
+ u.sin.sin_family = AF_INET;
+ u.sin.sin_addr.s_addr = htonl(sock->addr->addresses[sock->step.curraddr]);
+ u.sin.sin_port = htons((short) sock->port);
+ sa = &u;
+ salen = sizeof u.sin;
+ break;
+#endif
+ case AF_UNIX:
+ assert(strlen(sock->addr->hostname) < sizeof u.su.sun_path);
+ u.su.sun_family = AF_UNIX;
+ strcpy(u.su.sun_path, sock->addr->hostname);
+ sa = &u;
+ salen = sizeof u.su;
+ break;
+
+ default:
+ unreachable("unknown address family");
+ exit(1); /* XXX: GCC doesn't understand assert() on some systems. */
+ }
+
+ nonblock(s);
+
+ if ((connect(s, &(sa->sa), salen)) < 0) {
+ if ( errno != EINPROGRESS ) {
+ err = errno;
+ goto ret;
+ }
+ } else {
+ /*
+ * If we _don't_ get EWOULDBLOCK, the connect has completed
+ * and we should set the socket as connected and writable.
+ */
+ sock->connected = true;
+ sock->writable = true;
+
+ SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step);
+ plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS,
+ &thisaddr, sock->port, NULL, 0);
+ }
+
+ uxsel_tell(sock);
+
+ ret:
+
+ /*
+ * No matter what happened, put the socket back in the tree.
+ */
+ add234(sktree, sock);
+
+ if (err) {
+ SockAddr thisaddr = sk_extractaddr_tmp(
+ sock->addr, &sock->step);
+ plug_log(sock->plug, PLUGLOG_CONNECT_FAILED,
+ &thisaddr, sock->port, strerror(err), err);
+ }
+ return err;
+}
+
+Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline,
+ bool nodelay, bool keepalive, Plug *plug)
+{
+ NetSocket *ret;
+ int 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 */
+ ret->writable = false; /* to start with */
+ ret->sending_oob = 0;
+ ret->frozen = false;
+ ret->localhost_only = false; /* unused, but best init anyway */
+ ret->pending_error = 0;
+ ret->parent = ret->child = NULL;
+ ret->oobpending = false;
+ ret->outgoingeof = EOF_NO;
+ ret->incomingeof = false;
+ ret->listener = false;
+ ret->addr = addr;
+ START_STEP(ret->addr, ret->step);
+ ret->s = -1;
+ ret->oobinline = oobinline;
+ ret->nodelay = nodelay;
+ ret->keepalive = keepalive;
+ ret->privport = privport;
+ ret->port = port;
+
+ do {
+ err = try_connect(ret);
+ } while (err && sk_nextaddr(ret->addr, &ret->step));
+
+ if (err)
+ ret->error = strerror(err);
+
+ return &ret->sock;
+}
+
+Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
+ bool local_host_only, int orig_address_family)
+{
+ int s;
+#ifndef NO_IPV6
+ struct addrinfo hints, *ai = NULL;
+ char portstr[6];
+#endif
+ union sockaddr_union u;
+ union sockaddr_union *addr;
+ int addrlen;
+ NetSocket *ret;
+ int retcode;
+ int address_family;
+ int on = 1;
+
+ /*
+ * Create NetSocket structure.
+ */
+ ret = snew(NetSocket);
+ ret->sock.vt = &NetSocket_sockvt;
+ ret->error = NULL;
+ ret->plug = plug;
+ bufchain_init(&ret->output_data);
+ ret->writable = false; /* to start with */
+ ret->sending_oob = 0;
+ ret->frozen = false;
+ ret->localhost_only = local_host_only;
+ ret->pending_error = 0;
+ ret->parent = ret->child = NULL;
+ ret->oobpending = false;
+ ret->outgoingeof = EOF_NO;
+ ret->incomingeof = false;
+ ret->listener = true;
+ ret->addr = NULL;
+ ret->s = -1;
+
+ /*
+ * Translate address_family from platform-independent constants
+ * into local reality.
+ */
+ address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET :
+#ifndef NO_IPV6
+ orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 :
+#endif
+ AF_UNSPEC);
+
+#ifndef NO_IPV6
+ /* Let's default to IPv6.
+ * If the stack doesn't support IPv6, we will fall back to IPv4. */
+ if (address_family == AF_UNSPEC) address_family = AF_INET6;
+#else
+ /* No other choice, default to IPv4 */
+ if (address_family == AF_UNSPEC) address_family = AF_INET;
+#endif
+
+ /*
+ * Open socket.
+ */
+ s = socket(address_family, SOCK_STREAM, 0);
+
+#ifndef NO_IPV6
+ /* If the host doesn't support IPv6 try fallback to IPv4. */
+ if (s < 0 && address_family == AF_INET6) {
+ address_family = AF_INET;
+ s = socket(address_family, SOCK_STREAM, 0);
+ }
+#endif
+
+ if (s < 0) {
+ ret->error = strerror(errno);
+ return &ret->sock;
+ }
+
+ cloexec(s);
+
+ ret->oobinline = false;
+
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+ (const char *)&on, sizeof(on)) < 0) {
+ ret->error = strerror(errno);
+ close(s);
+ return &ret->sock;
+ }
+
+ retcode = -1;
+ addr = NULL; addrlen = -1; /* placate optimiser */
+
+ if (srcaddr != NULL) {
+#ifndef NO_IPV6
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = address_family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+ hints.ai_addrlen = 0;
+ hints.ai_addr = NULL;
+ hints.ai_canonname = NULL;
+ hints.ai_next = NULL;
+ assert(port >= 0 && port <= 99999);
+ sprintf(portstr, "%d", port);
+ {
+ char *trimmed_addr = host_strduptrim(srcaddr);
+ retcode = getaddrinfo(trimmed_addr, portstr, &hints, &ai);
+ sfree(trimmed_addr);
+ }
+ if (retcode == 0) {
+ addr = (union sockaddr_union *)ai->ai_addr;
+ addrlen = ai->ai_addrlen;
+ }
+#else
+ memset(&u,'\0',sizeof u);
+ u.sin.sin_family = AF_INET;
+ u.sin.sin_port = htons(port);
+ u.sin.sin_addr.s_addr = inet_addr(srcaddr);
+ if (u.sin.sin_addr.s_addr != (in_addr_t)(-1)) {
+ /* Override localhost_only with specified listen addr. */
+ ret->localhost_only = ipv4_is_loopback(u.sin.sin_addr);
+ }
+ addr = &u;
+ addrlen = sizeof(u.sin);
+ retcode = 0;
+#endif
+ }
+
+ if (retcode != 0) {
+ memset(&u,'\0',sizeof u);
+#ifndef NO_IPV6
+ if (address_family == AF_INET6) {
+ u.sin6.sin6_family = AF_INET6;
+ u.sin6.sin6_port = htons(port);
+ if (local_host_only)
+ u.sin6.sin6_addr = in6addr_loopback;
+ else
+ u.sin6.sin6_addr = in6addr_any;
+ addr = &u;
+ addrlen = sizeof(u.sin6);
+ } else
+#endif
+ {
+ u.sin.sin_family = AF_INET;
+ u.sin.sin_port = htons(port);
+ if (local_host_only)
+ u.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ else
+ u.sin.sin_addr.s_addr = htonl(INADDR_ANY);
+ addr = &u;
+ addrlen = sizeof(u.sin);
+ }
+ }
+
+ retcode = bind(s, &addr->sa, addrlen);
+
+#ifndef NO_IPV6
+ if (ai)
+ freeaddrinfo(ai);
+#endif
+
+ if (retcode < 0) {
+ close(s);
+ ret->error = strerror(errno);
+ return &ret->sock;
+ }
+
+ if (listen(s, SOMAXCONN) < 0) {
+ close(s);
+ ret->error = strerror(errno);
+ return &ret->sock;
+ }
+
+#ifndef NO_IPV6
+ /*
+ * If we were given ADDRTYPE_UNSPEC, we must also create an
+ * IPv4 listening socket and link it to this one.
+ */
+ if (address_family == AF_INET6 && orig_address_family == ADDRTYPE_UNSPEC) {
+ NetSocket *other;
+
+ other = container_of(
+ sk_newlistener(srcaddr, port, plug,
+ local_host_only, ADDRTYPE_IPV4),
+ NetSocket, sock);
+
+ if (other) {
+ if (!other->error) {
+ other->parent = ret;
+ ret->child = other;
+ } else {
+ /* If we couldn't create a listening socket on IPv4 as well
+ * as IPv6, we must return an error overall. */
+ close(s);
+ sfree(ret);
+ return &other->sock;
+ }
+ }
+ }
+#endif
+
+ ret->s = s;
+
+ uxsel_tell(ret);
+ add234(sktree, ret);
+
+ return &ret->sock;
+}
+
+static void sk_net_close(Socket *sock)
+{
+ NetSocket *s = container_of(sock, NetSocket, sock);
+
+ if (s->child)
+ sk_net_close(&s->child->sock);
+
+ bufchain_clear(&s->output_data);
+
+ del234(sktree, s);
+ if (s->s >= 0) {
+ uxsel_del(s->s);
+ close(s->s);
+ }
+ if (s->addr)
+ sk_addr_free(s->addr);
+ delete_callbacks_for_context(s);
+ sfree(s);
+}
+
+void *sk_getxdmdata(Socket *sock, int *lenp)
+{
+ NetSocket *s;
+ union sockaddr_union u;
+ socklen_t addrlen;
+ char *buf;
+ static unsigned int unix_addr = 0xFFFFFFFF;
+
+ /*
+ * We must check that this socket really _is_ a NetSocket before
+ * downcasting it.
+ */
+ if (sock->vt != &NetSocket_sockvt)
+ return NULL; /* failure */
+ s = container_of(sock, NetSocket, sock);
+
+ addrlen = sizeof(u);
+ if (getsockname(s->s, &u.sa, &addrlen) < 0)
+ return NULL;
+ switch(u.sa.sa_family) {
+ case AF_INET:
+ *lenp = 6;
+ buf = snewn(*lenp, char);
+ PUT_32BIT_MSB_FIRST(buf, ntohl(u.sin.sin_addr.s_addr));
+ PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin.sin_port));
+ break;
+#ifndef NO_IPV6
+ case AF_INET6:
+ *lenp = 6;
+ buf = snewn(*lenp, char);
+ if (IN6_IS_ADDR_V4MAPPED(&u.sin6.sin6_addr)) {
+ memcpy(buf, u.sin6.sin6_addr.s6_addr + 12, 4);
+ PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin6.sin6_port));
+ } else
+ /* This is stupid, but it's what XLib does. */
+ memset(buf, 0, 6);
+ break;
+#endif
+ case AF_UNIX:
+ *lenp = 6;
+ buf = snewn(*lenp, char);
+ PUT_32BIT_MSB_FIRST(buf, unix_addr--);
+ PUT_16BIT_MSB_FIRST(buf+4, getpid());
+ break;
+
+ /* XXX IPV6 */
+
+ default:
+ return NULL;
+ }
+
+ return buf;
+}
+
+void plug_closing_errno(Plug *plug, int error)
+{
+ PlugCloseType type = PLUGCLOSE_ERROR;
+ if (error == EPIPE)
+ type = PLUGCLOSE_BROKEN_PIPE;
+ plug_closing(plug, type, strerror(error));
+}
+
+/*
+ * Deal with socket errors detected in try_send().
+ */
+static void socket_error_callback(void *vs)
+{
+ NetSocket *s = (NetSocket *)vs;
+
+ /*
+ * Just in case other socket work has caused this socket to vanish
+ * or become somehow non-erroneous before this callback arrived...
+ */
+ if (!find234(sktree, s, NULL) || !s->pending_error)
+ return;
+
+ /*
+ * An error has occurred on this socket. Pass it to the plug.
+ */
+ plug_closing_errno(s->plug, s->pending_error);
+}
+
+/*
+ * The function which tries to send on a socket once it's deemed
+ * writable.
+ */
+void try_send(NetSocket *s)
+{
+ while (s->sending_oob || bufchain_size(&s->output_data) > 0) {
+ int nsent;
+ int err;
+ const void *data;
+ size_t len;
+ int urgentflag;
+
+ if (s->sending_oob) {
+ urgentflag = MSG_OOB;
+ len = s->sending_oob;
+ data = &s->oobdata;
+ } else {
+ urgentflag = 0;
+ ptrlen bufdata = bufchain_prefix(&s->output_data);
+ data = bufdata.ptr;
+ len = bufdata.len;
+ }
+ nsent = send(s->s, data, len, urgentflag);
+ noise_ultralight(NOISE_SOURCE_IOLEN, nsent);
+ if (nsent <= 0) {
+ err = (nsent < 0 ? errno : 0);
+ if (err == EWOULDBLOCK) {
+ /*
+ * Perfectly normal: we've sent all we can for the moment.
+ */
+ s->writable = false;
+ return;
+ } else {
+ /*
+ * We unfortunately can't just call plug_closing(),
+ * because it's quite likely that we're currently
+ * _in_ a call from the code we'd be calling back
+ * to, so we'd have to make half the SSH code
+ * reentrant. Instead we flag a pending error on
+ * the socket, to be dealt with (by calling
+ * plug_closing()) at some suitable future moment.
+ */
+ s->pending_error = err;
+ /*
+ * Immediately cease selecting on this socket, so that
+ * we don't tight-loop repeatedly trying to do
+ * whatever it was that went wrong.
+ */
+ uxsel_tell(s);
+ /*
+ * Arrange to be called back from the top level to
+ * deal with the error condition on this socket.
+ */
+ queue_toplevel_callback(socket_error_callback, s);
+ return;
+ }
+ } else {
+ if (s->sending_oob) {
+ if (nsent < len) {
+ memmove(s->oobdata, s->oobdata+nsent, len-nsent);
+ s->sending_oob = len - nsent;
+ } else {
+ s->sending_oob = 0;
+ }
+ } else {
+ bufchain_consume(&s->output_data, nsent);
+ }
+ }
+ }
+
+ /*
+ * If we reach here, we've finished sending everything we might
+ * have needed to send. Send EOF, if we need to.
+ */
+ if (s->outgoingeof == EOF_PENDING) {
+ shutdown(s->s, SHUT_WR);
+ s->outgoingeof = EOF_SENT;
+ }
+
+ /*
+ * Also update the select status, because we don't need to select
+ * for writing any more.
+ */
+ uxsel_tell(s);
+}
+
+static size_t sk_net_write(Socket *sock, const void *buf, size_t len)
+{
+ NetSocket *s = container_of(sock, NetSocket, sock);
+
+ assert(s->outgoingeof == EOF_NO);
+
+ /*
+ * Add the data to the buffer list on the socket.
+ */
+ bufchain_add(&s->output_data, buf, len);
+
+ /*
+ * Now try sending from the start of the buffer list.
+ */
+ if (s->writable)
+ try_send(s);
+
+ /*
+ * Update the select() status to correctly reflect whether or
+ * not we should be selecting for write.
+ */
+ uxsel_tell(s);
+
+ return bufchain_size(&s->output_data);
+}
+
+static size_t sk_net_write_oob(Socket *sock, const void *buf, size_t len)
+{
+ NetSocket *s = container_of(sock, NetSocket, sock);
+
+ assert(s->outgoingeof == EOF_NO);
+
+ /*
+ * Replace the buffer list on the socket with the data.
+ */
+ bufchain_clear(&s->output_data);
+ assert(len <= sizeof(s->oobdata));
+ memcpy(s->oobdata, buf, len);
+ s->sending_oob = len;
+
+ /*
+ * Now try sending from the start of the buffer list.
+ */
+ if (s->writable)
+ try_send(s);
+
+ /*
+ * Update the select() status to correctly reflect whether or
+ * not we should be selecting for write.
+ */
+ uxsel_tell(s);
+
+ return s->sending_oob;
+}
+
+static void sk_net_write_eof(Socket *sock)
+{
+ NetSocket *s = container_of(sock, NetSocket, sock);
+
+ assert(s->outgoingeof == EOF_NO);
+
+ /*
+ * Mark the socket as pending outgoing EOF.
+ */
+ s->outgoingeof = EOF_PENDING;
+
+ /*
+ * Now try sending from the start of the buffer list.
+ */
+ if (s->writable)
+ try_send(s);
+
+ /*
+ * Update the select() status to correctly reflect whether or
+ * not we should be selecting for write.
+ */
+ uxsel_tell(s);
+}
+
+static void net_select_result(int fd, int event)
+{
+ int ret;
+ char buf[20480]; /* nice big buffer for plenty of speed */
+ NetSocket *s;
+ bool atmark = true;
+
+ /* Find the Socket structure */
+ s = find234(sktree, &fd, cmpforsearch);
+ if (!s)
+ return; /* boggle */
+
+ noise_ultralight(NOISE_SOURCE_IOID, fd);
+
+ switch (event) {
+ case SELECT_X: /* exceptional */
+ if (!s->oobinline) {
+ /*
+ * On a non-oobinline socket, this indicates that we
+ * can immediately perform an OOB read and get back OOB
+ * data, which we will send to the back end with
+ * type==2 (urgent data).
+ */
+ ret = recv(s->s, buf, sizeof(buf), MSG_OOB);
+ noise_ultralight(NOISE_SOURCE_IOLEN, ret);
+ if (ret == 0) {
+ plug_closing_error(s->plug, "Internal networking trouble");
+ } else if (ret < 0) {
+ plug_closing_errno(s->plug, errno);
+ } else {
+ /*
+ * Receiving actual data on a socket means we can
+ * stop falling back through the candidate
+ * addresses to connect to.
+ */
+ if (s->addr) {
+ sk_addr_free(s->addr);
+ s->addr = NULL;
+ }
+ plug_receive(s->plug, 2, buf, ret);
+ }
+ break;
+ }
+
+ /*
+ * If we reach here, this is an oobinline socket, which
+ * means we should set s->oobpending and then deal with it
+ * when we get called for the readability event (which
+ * should also occur).
+ */
+ s->oobpending = true;
+ break;
+ case SELECT_R: /* readable; also acceptance */
+ if (s->listener) {
+ /*
+ * On a listening socket, the readability event means a
+ * connection is ready to be accepted.
+ */
+ union sockaddr_union su;
+ socklen_t addrlen = sizeof(su);
+ accept_ctx_t actx;
+ int t; /* socket of connection */
+
+ memset(&su, 0, addrlen);
+ t = accept(s->s, &su.sa, &addrlen);
+ if (t < 0) {
+ break;
+ }
+
+ nonblock(t);
+ actx.i = t;
+
+ if ((!s->addr || s->addr->superfamily != UNIX) &&
+ s->localhost_only && !sockaddr_is_loopback(&su.sa)) {
+ close(t); /* someone let nonlocal through?! */
+ } else if (plug_accepting(s->plug, sk_net_accept, actx)) {
+ close(t); /* denied or error */
+ }
+ break;
+ }
+
+ /*
+ * If we reach here, this is not a listening socket, so
+ * readability really means readability.
+ */
+
+ /* In the case the socket is still frozen, we don't even bother */
+ if (s->frozen)
+ break;
+
+ /*
+ * We have received data on the socket. For an oobinline
+ * socket, this might be data _before_ an urgent pointer,
+ * in which case we send it to the back end with type==1
+ * (data prior to urgent).
+ */
+ if (s->oobinline && s->oobpending) {
+ int atmark_from_ioctl;
+ if (ioctl(s->s, SIOCATMARK, &atmark_from_ioctl) == 0) {
+ atmark = atmark_from_ioctl;
+ if (atmark)
+ s->oobpending = false; /* clear this indicator */
+ }
+ } else
+ atmark = true;
+
+ ret = recv(s->s, buf, s->oobpending ? 1 : sizeof(buf), 0);
+ noise_ultralight(NOISE_SOURCE_IOLEN, ret);
+ if (ret < 0) {
+ if (errno == EWOULDBLOCK) {
+ break;
+ }
+ }
+ if (ret < 0) {
+ plug_closing_errno(s->plug, errno);
+ } else if (0 == ret) {
+ s->incomingeof = true; /* stop trying to read now */
+ uxsel_tell(s);
+ plug_closing_normal(s->plug);
+ } else {
+ /*
+ * Receiving actual data on a socket means we can
+ * stop falling back through the candidate
+ * addresses to connect to.
+ */
+ if (s->addr) {
+ sk_addr_free(s->addr);
+ s->addr = NULL;
+ }
+ plug_receive(s->plug, atmark ? 0 : 1, buf, ret);
+ }
+ break;
+ case SELECT_W: /* writable */
+ if (!s->connected) {
+ /*
+ * select/poll reports a socket as _writable_ when an
+ * asynchronous connect() attempt either completes or
+ * fails. So first we must find out which.
+ */
+ {
+ int err;
+ socklen_t errlen = sizeof(err);
+ char *errmsg = NULL;
+ if (getsockopt(s->s, SOL_SOCKET, SO_ERROR, &err, &errlen)<0) {
+ errmsg = dupprintf("getsockopt(SO_ERROR): %s",
+ strerror(errno));
+ err = errno; /* got to put something in here */
+ } else if (err != 0) {
+ errmsg = dupstr(strerror(err));
+ }
+ if (errmsg) {
+ /*
+ * The asynchronous connection attempt failed.
+ * Report the problem via plug_log, and try again
+ * with the next candidate address, if we have
+ * more than one.
+ */
+ SockAddr thisaddr;
+ assert(s->addr);
+
+ thisaddr = sk_extractaddr_tmp(s->addr, &s->step);
+ plug_log(s->plug, PLUGLOG_CONNECT_FAILED,
+ &thisaddr, s->port, errmsg, err);
+
+ while (err && s->addr && sk_nextaddr(s->addr, &s->step)) {
+ err = try_connect(s);
+ }
+ if (err) {
+ plug_closing_errno(s->plug, err);
+ return; /* socket is now presumably defunct */
+ }
+ if (!s->connected)
+ return; /* another async attempt in progress */
+ } else {
+ /*
+ * The connection attempt succeeded.
+ */
+ SockAddr thisaddr = sk_extractaddr_tmp(s->addr, &s->step);
+ plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS,
+ &thisaddr, s->port, NULL, 0);
+ }
+ }
+
+ /*
+ * If we get here, we've managed to make a connection.
+ */
+ if (s->addr) {
+ sk_addr_free(s->addr);
+ s->addr = NULL;
+ }
+ s->connected = true;
+ s->writable = true;
+ uxsel_tell(s);
+ } else {
+ size_t bufsize_before, bufsize_after;
+ s->writable = true;
+ bufsize_before = s->sending_oob + bufchain_size(&s->output_data);
+ try_send(s);
+ bufsize_after = s->sending_oob + bufchain_size(&s->output_data);
+ if (bufsize_after < bufsize_before)
+ plug_sent(s->plug, bufsize_after);
+ }
+ break;
+ }
+}
+
+/*
+ * Special error values are returned from sk_namelookup and sk_new
+ * if there's a problem. These functions extract an error message,
+ * or return NULL if there's no problem.
+ */
+const char *sk_addr_error(SockAddr *addr)
+{
+ return addr->error;
+}
+static const char *sk_net_socket_error(Socket *sock)
+{
+ NetSocket *s = container_of(sock, NetSocket, sock);
+ return s->error;
+}
+
+static void sk_net_set_frozen(Socket *sock, bool is_frozen)
+{
+ NetSocket *s = container_of(sock, NetSocket, sock);
+ if (s->frozen == is_frozen)
+ return;
+ s->frozen = is_frozen;
+ uxsel_tell(s);
+}
+
+static SocketPeerInfo *sk_net_peer_info(Socket *sock)
+{
+ NetSocket *s = container_of(sock, NetSocket, sock);
+ union sockaddr_union addr;
+ socklen_t addrlen = sizeof(addr);
+#ifndef NO_IPV6
+ char buf[INET6_ADDRSTRLEN];
+#endif
+ SocketPeerInfo *pi;
+
+ if (getpeername(s->s, &addr.sa, &addrlen) < 0)
+ return NULL;
+
+ pi = snew(SocketPeerInfo);
+ pi->addressfamily = ADDRTYPE_UNSPEC;
+ pi->addr_text = NULL;
+ pi->port = -1;
+ pi->log_text = NULL;
+
+ if (addr.storage.ss_family == AF_INET) {
+ pi->addressfamily = ADDRTYPE_IPV4;
+ memcpy(pi->addr_bin.ipv4, &addr.sin.sin_addr, 4);
+ pi->port = ntohs(addr.sin.sin_port);
+ pi->addr_text = dupstr(inet_ntoa(addr.sin.sin_addr));
+ pi->log_text = dupprintf("%s:%d", pi->addr_text, pi->port);
+
+#ifndef NO_IPV6
+ } else if (addr.storage.ss_family == AF_INET6) {
+ pi->addressfamily = ADDRTYPE_IPV6;
+ memcpy(pi->addr_bin.ipv6, &addr.sin6.sin6_addr, 16);
+ pi->port = ntohs(addr.sin6.sin6_port);
+ pi->addr_text = dupstr(
+ inet_ntop(AF_INET6, &addr.sin6.sin6_addr, buf, sizeof(buf)));
+ pi->log_text = dupprintf("[%s]:%d", pi->addr_text, pi->port);
+#endif
+
+ } else if (addr.storage.ss_family == AF_UNIX) {
+ pi->addressfamily = ADDRTYPE_LOCAL;
+
+ /*
+ * For Unix sockets, the source address is unlikely to be
+ * helpful, so we leave addr_txt NULL (and we certainly can't
+ * fill in port, obviously). Instead, we try SO_PEERCRED and
+ * try to get the source pid, and put that in the log text.
+ */
+ int pid, uid, gid;
+ if (so_peercred(s->s, &pid, &uid, &gid)) {
+ char uidbuf[64], gidbuf[64];
+ sprintf(uidbuf, "%d", uid);
+ sprintf(gidbuf, "%d", gid);
+ struct passwd *pw = getpwuid(uid);
+ struct group *gr = getgrgid(gid);
+ pi->log_text = dupprintf("pid %d (%s:%s)", pid,
+ pw ? pw->pw_name : uidbuf,
+ gr ? gr->gr_name : gidbuf);
+ }
+ } else {
+ sfree(pi);
+ return NULL;
+ }
+
+ return pi;
+}
+
+int sk_net_get_fd(Socket *sock)
+{
+ /* This function is not fully general: it only works on NetSocket */
+ if (sock->vt != &NetSocket_sockvt)
+ return -1; /* failure */
+ NetSocket *s = container_of(sock, NetSocket, sock);
+ return s->s;
+}
+
+static void uxsel_tell(NetSocket *s)
+{
+ int rwx = 0;
+ if (!s->pending_error) {
+ if (s->listener) {
+ rwx |= SELECT_R; /* read == accept */
+ } else {
+ if (!s->connected)
+ rwx |= SELECT_W; /* write == connect */
+ if (s->connected && !s->frozen && !s->incomingeof)
+ rwx |= SELECT_R | SELECT_X;
+ if (bufchain_size(&s->output_data))
+ rwx |= SELECT_W;
+ }
+ }
+ uxsel_set(s->s, rwx, net_select_result);
+}
+
+int net_service_lookup(const char *service)
+{
+ struct servent *se;
+ se = getservbyname(service, NULL);
+ if (se != NULL)
+ return ntohs(se->s_port);
+ else
+ return 0;
+}
+
+char *get_hostname(void)
+{
+ size_t size = 0;
+ char *hostname = NULL;
+ do {
+ sgrowarray(hostname, size, size);
+ if ((gethostname(hostname, size) < 0) && (errno != ENAMETOOLONG)) {
+ sfree(hostname);
+ hostname = NULL;
+ break;
+ }
+ } while (strlen(hostname) >= size-1);
+ return hostname;
+}
+
+SockAddr *platform_get_x11_unix_address(const char *sockpath, int displaynum)
+{
+ SockAddr *ret = snew(SockAddr);
+ int n;
+
+ memset(ret, 0, sizeof *ret);
+ ret->superfamily = UNIX;
+ /*
+ * In special circumstances (notably Mac OS X Leopard), we'll
+ * have been passed an explicit Unix socket path.
+ */
+ if (sockpath) {
+ n = snprintf(ret->hostname, sizeof ret->hostname,
+ "%s", sockpath);
+ } else {
+ n = snprintf(ret->hostname, sizeof ret->hostname,
+ "%s%d", X11_UNIX_PATH, displaynum);
+ }
+
+ if (n < 0)
+ ret->error = "snprintf failed";
+ else if (n >= sizeof ret->hostname)
+ ret->error = "X11 UNIX name too long";
+
+#ifndef NO_IPV6
+ ret->ais = NULL;
+#else
+ ret->addresses = NULL;
+ ret->naddresses = 0;
+#endif
+ ret->refcount = 1;
+ return ret;
+}
+
+SockAddr *unix_sock_addr(const char *path)
+{
+ SockAddr *ret = snew(SockAddr);
+ int n;
+
+ memset(ret, 0, sizeof *ret);
+ ret->superfamily = UNIX;
+ n = snprintf(ret->hostname, sizeof ret->hostname, "%s", path);
+
+ if (n < 0)
+ ret->error = "snprintf failed";
+ else if (n >= sizeof ret->hostname ||
+ n >= sizeof(((struct sockaddr_un *)0)->sun_path))
+ ret->error = "socket pathname too long";
+
+#ifndef NO_IPV6
+ ret->ais = NULL;
+#else
+ ret->addresses = NULL;
+ ret->naddresses = 0;
+#endif
+ ret->refcount = 1;
+ return ret;
+}
+
+Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug)
+{
+ int s;
+ union sockaddr_union u;
+ union sockaddr_union *addr;
+ int addrlen;
+ NetSocket *ret;
+ int retcode;
+
+ /*
+ * Create NetSocket structure.
+ */
+ ret = snew(NetSocket);
+ ret->sock.vt = &NetSocket_sockvt;
+ ret->error = NULL;
+ ret->plug = plug;
+ bufchain_init(&ret->output_data);
+ ret->writable = false; /* to start with */
+ ret->sending_oob = 0;
+ ret->frozen = false;
+ ret->localhost_only = true;
+ ret->pending_error = 0;
+ ret->parent = ret->child = NULL;
+ ret->oobpending = false;
+ ret->outgoingeof = EOF_NO;
+ ret->incomingeof = false;
+ ret->listener = true;
+ ret->addr = listenaddr;
+ ret->s = -1;
+
+ assert(listenaddr->superfamily == UNIX);
+
+ /*
+ * Open socket.
+ */
+ s = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (s < 0) {
+ ret->error = strerror(errno);
+ return &ret->sock;
+ }
+
+ cloexec(s);
+
+ ret->oobinline = false;
+
+ memset(&u, '\0', sizeof(u));
+ u.su.sun_family = AF_UNIX;
+#if __GNUC__ >= 8
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wstringop-truncation"
+#endif // __GNUC__ >= 8
+ strncpy(u.su.sun_path, listenaddr->hostname, sizeof(u.su.sun_path)-1);
+#if __GNUC__ >= 8
+# pragma GCC diagnostic pop
+#endif // __GNUC__ >= 8
+ addr = &u;
+ addrlen = sizeof(u.su);
+
+ if (unlink(u.su.sun_path) < 0 && errno != ENOENT) {
+ close(s);
+ ret->error = strerror(errno);
+ return &ret->sock;
+ }
+
+ retcode = bind(s, &addr->sa, addrlen);
+ if (retcode < 0) {
+ close(s);
+ ret->error = strerror(errno);
+ return &ret->sock;
+ }
+
+ if (listen(s, SOMAXCONN) < 0) {
+ close(s);
+ ret->error = strerror(errno);
+ return &ret->sock;
+ }
+
+ ret->s = s;
+
+ uxsel_tell(ret);
+ add234(sktree, ret);
+
+ return &ret->sock;
+}
diff --git a/unix/no-gtk.c b/unix/no-gtk.c
new file mode 100644
index 00000000..12565a1f
--- /dev/null
+++ b/unix/no-gtk.c
@@ -0,0 +1,11 @@
+/*
+ * no-gtk.c: link into non-GUI Unix programs so that they can tell
+ * buildinfo about a lack of GTK.
+ */
+
+#include "putty.h"
+
+char *buildinfo_gtk_version(void)
+{
+ return NULL;
+}
diff --git a/unix/noaskpass.c b/unix/noaskpass.c
new file mode 100644
index 00000000..de646c55
--- /dev/null
+++ b/unix/noaskpass.c
@@ -0,0 +1,19 @@
+/*
+ * Dummy (lack-of-)implementation of a GUI password/passphrase prompt.
+ */
+
+#include "putty.h"
+
+void random_add_noise(NoiseSourceId source, const void *noise, int length)
+{
+ /* We have no keypress_prng here, so no need to implement this */
+}
+
+const bool buildinfo_gtk_relevant = false;
+
+char *gtk_askpass_main(const char *display, const char *wintitle,
+ const char *prompt, bool *success)
+{
+ *success = false;
+ return dupstr("this Pageant was built without GTK");
+}
diff --git a/UNIX/UXNOISE.C b/unix/noise.c
index 0fbf8c4d..0fbf8c4d 100644
--- a/UNIX/UXNOISE.C
+++ b/unix/noise.c
diff --git a/unix/pageant.c b/unix/pageant.c
new file mode 100644
index 00000000..4558cd37
--- /dev/null
+++ b/unix/pageant.c
@@ -0,0 +1,1533 @@
+/*
+ * Unix Pageant, more or less similar to ssh-agent.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <signal.h>
+#include <ctype.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <termios.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "misc.h"
+#include "pageant.h"
+
+void cmdline_error(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ console_print_error_msg_fmt_v("pageant", fmt, ap);
+ va_end(ap);
+ exit(1);
+}
+
+static void setup_sigchld_handler(void);
+
+typedef enum RuntimePromptType {
+ RTPROMPT_UNAVAILABLE,
+ RTPROMPT_DEBUG,
+ RTPROMPT_GUI,
+} RuntimePromptType;
+
+static const char *progname;
+
+struct uxpgnt_client {
+ FILE *logfp;
+ strbuf *prompt_buf;
+ RuntimePromptType prompt_type;
+ bool prompt_active;
+ PageantClientDialogId *dlgid;
+ int passphrase_fd;
+ int termination_pid;
+
+ PageantListenerClient plc;
+};
+
+static void uxpgnt_log(PageantListenerClient *plc, const char *fmt, va_list ap)
+{
+ struct uxpgnt_client *upc = container_of(plc, struct uxpgnt_client, plc);
+
+ if (!upc->logfp)
+ return;
+
+ fprintf(upc->logfp, "pageant: ");
+ vfprintf(upc->logfp, fmt, ap);
+ fprintf(upc->logfp, "\n");
+}
+
+static int make_pipe_to_askpass(const char *msg)
+{
+ int pipefds[2];
+
+ setup_sigchld_handler();
+
+ if (pipe(pipefds) < 0)
+ return -1;
+
+ pid_t pid = fork();
+ if (pid < 0) {
+ close(pipefds[0]);
+ close(pipefds[1]);
+ return -1;
+ }
+
+ if (pid == 0) {
+ const char *args[5] = {
+ progname, "--gui-prompt", "--askpass", msg, NULL
+ };
+
+ dup2(pipefds[1], 1);
+ cloexec(pipefds[0]);
+ cloexec(pipefds[1]);
+
+ /*
+ * See comment in fork_and_exec_self() in main-gtk-simple.c.
+ */
+ execv("/proc/self/exe", (char **)args);
+ execvp(progname, (char **)args);
+ perror("exec");
+ _exit(127);
+ }
+
+ close(pipefds[1]);
+ return pipefds[0];
+}
+
+static bool uxpgnt_ask_passphrase(
+ PageantListenerClient *plc, PageantClientDialogId *dlgid,
+ const char *comment)
+{
+ struct uxpgnt_client *upc = container_of(plc, struct uxpgnt_client, plc);
+
+ assert(!upc->dlgid); /* Pageant core should be serialising requests */
+
+ char *msg = dupprintf(
+ "A client of Pageant wants to use the following encrypted key:\n"
+ "%s\n"
+ "If you intended this, enter the passphrase to decrypt the key.",
+ comment);
+
+ switch (upc->prompt_type) {
+ case RTPROMPT_UNAVAILABLE:
+ sfree(msg);
+ return false;
+
+ case RTPROMPT_GUI:
+ upc->passphrase_fd = make_pipe_to_askpass(msg);
+ sfree(msg);
+ if (upc->passphrase_fd < 0)
+ return false; /* something went wrong */
+ break;
+
+ case RTPROMPT_DEBUG:
+ fprintf(upc->logfp, "pageant passphrase request: %s\n", msg);
+ sfree(msg);
+ break;
+ }
+
+ upc->prompt_active = true;
+ upc->dlgid = dlgid;
+ return true;
+}
+
+static void passphrase_done(struct uxpgnt_client *upc, bool success)
+{
+ PageantClientDialogId *dlgid = upc->dlgid;
+ upc->dlgid = NULL;
+ upc->prompt_active = false;
+
+ if (upc->logfp)
+ fprintf(upc->logfp, "pageant passphrase response: %s\n",
+ success ? "success" : "failure");
+
+ if (success)
+ pageant_passphrase_request_success(
+ dlgid, ptrlen_from_strbuf(upc->prompt_buf));
+ else
+ pageant_passphrase_request_refused(dlgid);
+
+ strbuf_free(upc->prompt_buf);
+ upc->prompt_buf = strbuf_new_nm();
+}
+
+static const PageantListenerClientVtable uxpgnt_vtable = {
+ .log = uxpgnt_log,
+ .ask_passphrase = uxpgnt_ask_passphrase,
+};
+
+/*
+ * More stubs.
+ */
+void random_save_seed(void) {}
+void random_destroy_seed(void) {}
+char *platform_default_s(const char *name) { return NULL; }
+bool platform_default_b(const char *name, bool def) { return def; }
+int platform_default_i(const char *name, int def) { return def; }
+FontSpec *platform_default_fontspec(const char *name) { return fontspec_new(""); }
+Filename *platform_default_filename(const char *name) { return filename_from_str(""); }
+char *x_get_default(const char *key) { return NULL; }
+
+/*
+ * Short description of parameters.
+ */
+static void usage(void)
+{
+ printf("Pageant: SSH agent\n");
+ printf("%s\n", ver);
+ printf("Usage: pageant <lifetime> [[--encrypted] key files]\n");
+ printf(" pageant [[--encrypted] key files] --exec <command> [args]\n");
+ printf(" pageant -a [--encrypted] [key files]\n");
+ printf(" pageant -d [key identifiers]\n");
+ printf(" pageant -D\n");
+ printf(" pageant -r [key identifiers]\n");
+ printf(" pageant -R\n");
+ printf(" pageant --public [key identifiers]\n");
+ printf(" pageant ( --public-openssh | -L ) [key identifiers]\n");
+ printf(" pageant -l [-E fptype]\n");
+ printf("Lifetime options, for running Pageant as an agent:\n");
+ printf(" -X run with the lifetime of the X server\n");
+ printf(" -T run with the lifetime of the controlling tty\n");
+ printf(" --permanent run permanently\n");
+ printf(" --debug run in debugging mode, without forking\n");
+ printf(" --exec <command> run with the lifetime of that command\n");
+ printf("Client options, for talking to an existing agent:\n");
+ printf(" -a add key(s) to the existing agent\n");
+ printf(" -l list currently loaded key fingerprints and comments\n");
+ printf(" --public print public keys in RFC 4716 format\n");
+ printf(" --public-openssh, -L print public keys in OpenSSH format\n");
+ printf(" -d delete key(s) from the agent\n");
+ printf(" -D delete all keys from the agent\n");
+ printf(" -r re-encrypt keys in the agent (forget cleartext)\n");
+ printf(" -R re-encrypt all possible keys in the agent\n");
+ printf("Other options:\n");
+ printf(" -v verbose mode (in agent mode)\n");
+ printf(" -s -c force POSIX or C shell syntax (in agent mode)\n");
+ printf(" --symlink path create symlink to socket (in agent mode)\n");
+ printf(" --encrypted when adding keys, don't decrypt\n");
+ printf(" -E alg, --fptype alg fingerprint type for -l (sha256, md5)\n");
+ printf(" --tty-prompt force tty-based passphrase prompt\n");
+ printf(" --gui-prompt force GUI-based passphrase prompt\n");
+ printf(" --askpass <prompt> behave like a standalone askpass program\n");
+ exit(1);
+}
+
+static void version(void)
+{
+ char *buildinfo_text = buildinfo("\n");
+ printf("pageant: %s\n%s\n", ver, buildinfo_text);
+ sfree(buildinfo_text);
+ exit(0);
+}
+
+void keylist_update(void)
+{
+ /* Nothing needs doing in Unix Pageant */
+}
+
+#define PAGEANT_DIR_PREFIX "/tmp/pageant"
+
+static bool time_to_die = false;
+
+/*
+ * These functions are part of the plug for our connection to the X
+ * display, so they do get called. They needn't actually do anything,
+ * except that x11_closing has to signal back to the main loop that
+ * it's time to terminate.
+ */
+static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port,
+ const char *error_msg, int error_code) {}
+static void x11_receive(Plug *plug, int urgent, const char *data, size_t len) {}
+static void x11_sent(Plug *plug, size_t bufsize) {}
+static void x11_closing(Plug *plug, PlugCloseType type, const char *error_msg)
+{
+ time_to_die = true;
+}
+struct X11Connection {
+ Plug plug;
+};
+
+static char *socketname;
+static enum { SHELL_AUTO, SHELL_SH, SHELL_CSH } shell_type = SHELL_AUTO;
+void pageant_print_env(int pid)
+{
+ if (shell_type == SHELL_AUTO) {
+ /* Same policy as OpenSSH: if $SHELL ends in "csh" then assume
+ * it's csh-shaped. */
+ const char *shell = getenv("SHELL");
+ if (shell && strlen(shell) >= 3 &&
+ !strcmp(shell + strlen(shell) - 3, "csh"))
+ shell_type = SHELL_CSH;
+ else
+ shell_type = SHELL_SH;
+ }
+
+ /*
+ * These shell snippets could usefully pay some attention to
+ * escaping of interesting characters. I don't think it causes a
+ * problem at the moment, because the pathnames we use are so
+ * utterly boring, but it's a lurking bug waiting to happen once
+ * a bit more flexibility turns up.
+ */
+
+ switch (shell_type) {
+ case SHELL_SH:
+ printf("SSH_AUTH_SOCK=%s; export SSH_AUTH_SOCK;\n"
+ "SSH_AGENT_PID=%d; export SSH_AGENT_PID;\n",
+ socketname, pid);
+ break;
+ case SHELL_CSH:
+ printf("setenv SSH_AUTH_SOCK %s;\n"
+ "setenv SSH_AGENT_PID %d;\n",
+ socketname, pid);
+ break;
+ case SHELL_AUTO:
+ unreachable("SHELL_AUTO should have been eliminated by now");
+ break;
+ }
+}
+
+void pageant_fork_and_print_env(bool retain_tty)
+{
+ pid_t pid = fork();
+ if (pid == -1) {
+ perror("fork");
+ exit(1);
+ } else if (pid != 0) {
+ pageant_print_env(pid);
+ exit(0);
+ }
+
+ /*
+ * Having forked off, we now daemonise ourselves as best we can.
+ * It's good practice in general to setsid() ourself out of any
+ * process group we didn't want to be part of, and to chdir("/")
+ * to avoid holding any directories open that we don't need in
+ * case someone wants to umount them; also, we should definitely
+ * close standard output (because it will very likely be pointing
+ * at a pipe from which some parent process is trying to read our
+ * environment variable dump, so if we hold open another copy of
+ * it then that process will never finish reading). We close
+ * standard input too on general principles, but not standard
+ * error, since we might need to shout a panicky error message
+ * down that one.
+ */
+ if (chdir("/") < 0) {
+ /* should there be an error condition, nothing we can do about
+ * it anyway */
+ }
+ close(0);
+ close(1);
+ if (retain_tty) {
+ /* Get out of our previous process group, to avoid being
+ * blasted by passing signals. But keep our controlling tty,
+ * so we can keep checking to see if we still have one. */
+#if HAVE_NULLARY_SETPGRP
+ setpgrp();
+#elif HAVE_BINARY_SETPGRP
+ setpgrp(0, 0);
+#endif
+ } else {
+ /* Do that, but also leave our entire session and detach from
+ * the controlling tty (if any). */
+ setsid();
+ }
+}
+
+static int signalpipe[2] = { -1, -1 };
+
+static void sigchld(int signum)
+{
+ if (write(signalpipe[1], "x", 1) <= 0)
+ /* not much we can do about it */;
+}
+
+static void setup_sigchld_handler(void)
+{
+ if (signalpipe[0] >= 0)
+ return;
+
+ /*
+ * Set up the pipe we'll use to tell us about SIGCHLD.
+ */
+ if (pipe(signalpipe) < 0) {
+ perror("pipe");
+ exit(1);
+ }
+ putty_signal(SIGCHLD, sigchld);
+}
+
+#define TTY_LIFE_POLL_INTERVAL (TICKSPERSEC * 30)
+static void *dummy_timer_ctx;
+static void tty_life_timer(void *ctx, unsigned long now)
+{
+ schedule_timer(TTY_LIFE_POLL_INTERVAL, tty_life_timer, &dummy_timer_ctx);
+}
+
+typedef enum {
+ KEYACT_AGENT_LOAD,
+ KEYACT_AGENT_LOAD_ENCRYPTED,
+ KEYACT_CLIENT_BASE,
+ KEYACT_CLIENT_ADD = KEYACT_CLIENT_BASE,
+ KEYACT_CLIENT_ADD_ENCRYPTED,
+ KEYACT_CLIENT_DEL,
+ KEYACT_CLIENT_DEL_ALL,
+ KEYACT_CLIENT_LIST,
+ KEYACT_CLIENT_PUBLIC_OPENSSH,
+ KEYACT_CLIENT_PUBLIC,
+ KEYACT_CLIENT_SIGN,
+ KEYACT_CLIENT_REENCRYPT,
+ KEYACT_CLIENT_REENCRYPT_ALL,
+} keyact;
+struct cmdline_key_action {
+ struct cmdline_key_action *next;
+ keyact action;
+ const char *filename;
+};
+
+bool is_agent_action(keyact action)
+{
+ return action < KEYACT_CLIENT_BASE;
+}
+
+static struct cmdline_key_action *keyact_head = NULL, *keyact_tail = NULL;
+static uint32_t sign_flags = 0;
+
+void add_keyact(keyact action, const char *filename)
+{
+ struct cmdline_key_action *a = snew(struct cmdline_key_action);
+ a->action = action;
+ a->filename = filename;
+ a->next = NULL;
+ if (keyact_tail)
+ keyact_tail->next = a;
+ else
+ keyact_head = a;
+ keyact_tail = a;
+}
+
+bool have_controlling_tty(void)
+{
+ int fd = open("/dev/tty", O_RDONLY);
+ if (fd < 0) {
+ if (errno != ENXIO) {
+ perror("/dev/tty: open");
+ exit(1);
+ }
+ return false;
+ } else {
+ close(fd);
+ return true;
+ }
+}
+
+static char **exec_args = NULL;
+static enum {
+ LIFE_UNSPEC, LIFE_X11, LIFE_TTY, LIFE_DEBUG, LIFE_PERM, LIFE_EXEC
+} life = LIFE_UNSPEC;
+static const char *display = NULL;
+static enum {
+ PROMPT_UNSPEC, PROMPT_TTY, PROMPT_GUI
+} prompt_type = PROMPT_UNSPEC;
+static FingerprintType key_list_fptype = SSH_FPTYPE_DEFAULT;
+
+static char *askpass_tty(const char *prompt)
+{
+ prompts_t *p = new_prompts();
+ p->to_server = false;
+ p->from_server = false;
+ p->name = dupstr("Pageant passphrase prompt");
+ add_prompt(p, dupcat(prompt, ": "), false);
+ SeatPromptResult spr = console_get_userpass_input(p);
+ assert(spr.kind != SPRK_INCOMPLETE);
+
+ if (spr.kind == SPRK_USER_ABORT) {
+ free_prompts(p);
+ return NULL;
+ } else if (spr.kind == SPRK_SW_ABORT) {
+ free_prompts(p);
+ char *err = spr_get_error_message(spr);
+ fprintf(stderr, "pageant: unable to read passphrase: %s", err);
+ sfree(err);
+ return NULL;
+ } else {
+ char *passphrase = prompt_get_result(p->prompts[0]);
+ free_prompts(p);
+ return passphrase;
+ }
+}
+
+static char *askpass_gui(const char *prompt)
+{
+ char *passphrase;
+ bool success;
+
+ passphrase = gtk_askpass_main(
+ display, "Pageant passphrase prompt", prompt, &success);
+ if (!success) {
+ /* return value is error message */
+ fprintf(stderr, "%s\n", passphrase);
+ sfree(passphrase);
+ passphrase = NULL;
+ }
+ return passphrase;
+}
+
+static char *askpass(const char *prompt)
+{
+ if (prompt_type == PROMPT_TTY) {
+ if (!have_controlling_tty()) {
+ fprintf(stderr, "no controlling terminal available "
+ "for passphrase prompt\n");
+ return NULL;
+ }
+ return askpass_tty(prompt);
+ }
+
+ if (prompt_type == PROMPT_GUI) {
+ if (!display) {
+ fprintf(stderr, "no graphical display available "
+ "for passphrase prompt\n");
+ return NULL;
+ }
+ return askpass_gui(prompt);
+ }
+
+ if (have_controlling_tty()) {
+ return askpass_tty(prompt);
+ } else if (display) {
+ return askpass_gui(prompt);
+ } else {
+ fprintf(stderr, "no way to read a passphrase without tty or "
+ "X display\n");
+ return NULL;
+ }
+}
+
+static bool unix_add_keyfile(const char *filename_str, bool add_encrypted)
+{
+ Filename *filename = filename_from_str(filename_str);
+ int status;
+ bool ret;
+ char *err;
+
+ ret = true;
+
+ /*
+ * Try without a passphrase.
+ */
+ status = pageant_add_keyfile(filename, NULL, &err, add_encrypted);
+ if (status == PAGEANT_ACTION_OK) {
+ goto cleanup;
+ } else if (status == PAGEANT_ACTION_FAILURE) {
+ fprintf(stderr, "pageant: %s: %s\n", filename_str, err);
+ ret = false;
+ goto cleanup;
+ }
+
+ /*
+ * And now try prompting for a passphrase.
+ */
+ while (1) {
+ char *prompt = dupprintf(
+ "Enter passphrase to load key '%s'", err);
+ char *passphrase = askpass(prompt);
+ sfree(err);
+ sfree(prompt);
+ err = NULL;
+ if (!passphrase)
+ break;
+
+ status = pageant_add_keyfile(filename, passphrase, &err,
+ add_encrypted);
+
+ smemclr(passphrase, strlen(passphrase));
+ sfree(passphrase);
+ passphrase = NULL;
+
+ if (status == PAGEANT_ACTION_OK) {
+ goto cleanup;
+ } else if (status == PAGEANT_ACTION_FAILURE) {
+ fprintf(stderr, "pageant: %s: %s\n", filename_str, err);
+ ret = false;
+ goto cleanup;
+ }
+ }
+
+ cleanup:
+ sfree(err);
+ filename_free(filename);
+ return ret;
+}
+
+void key_list_callback(void *ctx, char **fingerprints, const char *comment,
+ uint32_t ext_flags, struct pageant_pubkey *key)
+{
+ const char *mode = "";
+ if (ext_flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY)
+ mode = " (encrypted)";
+ else if (ext_flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE)
+ mode = " (re-encryptable)";
+
+ FingerprintType this_type =
+ ssh2_pick_fingerprint(fingerprints, key_list_fptype);
+ printf("%s %s%s\n", fingerprints[this_type], comment, mode);
+}
+
+struct key_find_ctx {
+ const char *string;
+ bool match_fp, match_comment;
+ bool match_fptypes[SSH_N_FPTYPES];
+ struct pageant_pubkey *found;
+ int nfound;
+};
+
+static bool match_fingerprint_string(
+ const char *string_orig, char **fingerprints,
+ const struct key_find_ctx *ctx)
+{
+ const char *hash;
+
+ for (unsigned fptype = 0; fptype < SSH_N_FPTYPES; fptype++) {
+ if (!ctx->match_fptypes[fptype])
+ continue;
+
+ const char *fingerprint = fingerprints[fptype];
+ if (!fingerprint)
+ continue;
+
+ /* Find the hash in the fingerprint string. It'll be the word
+ * at the end. */
+ hash = strrchr(fingerprint, ' ');
+ assert(hash);
+ hash++;
+
+ const char *string = string_orig;
+ bool case_sensitive;
+ const char *ignore_chars = "";
+
+ switch (fptype) {
+ case SSH_FPTYPE_MD5:
+ case SSH_FPTYPE_MD5_CERT:
+ /* MD5 fingerprints are in hex, so disregard case differences. */
+ case_sensitive = false;
+ /* And we don't really need to force the user to type the
+ * colons in between the digits, which are always the
+ * same. */
+ ignore_chars = ":";
+ break;
+ case SSH_FPTYPE_SHA256:
+ case SSH_FPTYPE_SHA256_CERT:
+ /* Skip over the "SHA256:" prefix, which we don't really
+ * want to force the user to type. On the other hand,
+ * tolerate it on the input string. */
+ assert(strstartswith(hash, "SHA256:"));
+ hash += 7;
+ if (strstartswith(string, "SHA256:"))
+ string += 7;
+ /* SHA256 fingerprints are base64, which is intrinsically
+ * case sensitive. */
+ case_sensitive = true;
+ break;
+ }
+
+ /* Now see if the search string is a prefix of the full hash,
+ * neglecting colons and (where appropriate) case differences. */
+ while (1) {
+ string += strspn(string, ignore_chars);
+ hash += strspn(hash, ignore_chars);
+ if (!*string)
+ return true;
+ char sc = *string, hc = *hash;
+ if (!case_sensitive) {
+ sc = tolower((unsigned char)sc);
+ hc = tolower((unsigned char)hc);
+ }
+ if (sc != hc)
+ break;
+ string++;
+ hash++;
+ }
+ }
+
+ return false;
+}
+
+void key_find_callback(void *vctx, char **fingerprints,
+ const char *comment, uint32_t ext_flags,
+ struct pageant_pubkey *key)
+{
+ struct key_find_ctx *ctx = (struct key_find_ctx *)vctx;
+
+ if ((ctx->match_comment && !strcmp(ctx->string, comment)) ||
+ (ctx->match_fp && match_fingerprint_string(ctx->string, fingerprints,
+ ctx)))
+ {
+ if (!ctx->found)
+ ctx->found = pageant_pubkey_copy(key);
+ ctx->nfound++;
+ }
+}
+
+struct pageant_pubkey *find_key(const char *string, char **retstr)
+{
+ struct key_find_ctx ctx[1];
+ struct pageant_pubkey key_in, *key_ret;
+ bool try_file = true, try_fp = true, try_comment = true;
+ bool file_errors = false;
+ bool try_all_fptypes = true;
+ FingerprintType fptype = SSH_FPTYPE_DEFAULT;
+
+ /*
+ * Trim off disambiguating prefixes telling us how to interpret
+ * the provided string.
+ */
+ if (!strncmp(string, "file:", 5)) {
+ string += 5;
+ try_fp = false;
+ try_comment = false;
+ file_errors = true; /* also report failure to load the file */
+ } else if (!strncmp(string, "comment:", 8)) {
+ string += 8;
+ try_file = false;
+ try_fp = false;
+ } else if (!strncmp(string, "fp:", 3)) {
+ string += 3;
+ try_file = false;
+ try_comment = false;
+ } else if (!strncmp(string, "fingerprint:", 12)) {
+ string += 12;
+ try_file = false;
+ try_comment = false;
+ } else if (!strnicmp(string, "md5:", 4)) {
+ string += 4;
+ try_file = false;
+ try_comment = false;
+ try_all_fptypes = false;
+ fptype = SSH_FPTYPE_MD5;
+ } else if (!strncmp(string, "sha256:", 7)) {
+ string += 7;
+ try_file = false;
+ try_comment = false;
+ try_all_fptypes = false;
+ fptype = SSH_FPTYPE_SHA256;
+ } else if (!strnicmp(string, "md5-cert:", 9)) {
+ string += 9;
+ try_file = false;
+ try_comment = false;
+ try_all_fptypes = false;
+ fptype = SSH_FPTYPE_MD5_CERT;
+ } else if (!strncmp(string, "sha256-cert:", 12)) {
+ string += 12;
+ try_file = false;
+ try_comment = false;
+ try_all_fptypes = false;
+ fptype = SSH_FPTYPE_SHA256_CERT;
+ }
+
+ /*
+ * Try interpreting the string as a key file name.
+ */
+ if (try_file) {
+ Filename *fn = filename_from_str(string);
+ int keytype = key_type(fn);
+ if (keytype == SSH_KEYTYPE_SSH1 ||
+ keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
+ const char *error;
+
+ key_in.blob = strbuf_new();
+ if (!rsa1_loadpub_f(fn, BinarySink_UPCAST(key_in.blob),
+ NULL, &error)) {
+ strbuf_free(key_in.blob);
+ key_in.blob = NULL;
+ if (file_errors) {
+ *retstr = dupprintf("unable to load file '%s': %s",
+ string, error);
+ filename_free(fn);
+ return NULL;
+ }
+ } else {
+ /*
+ * If we've successfully loaded the file, stop here - we
+ * already have a key blob and need not go to the agent to
+ * list things.
+ */
+ key_in.ssh_version = 1;
+ key_in.comment = NULL;
+ key_ret = pageant_pubkey_copy(&key_in);
+ strbuf_free(key_in.blob);
+ key_in.blob = NULL;
+ filename_free(fn);
+ return key_ret;
+ }
+ } else if (keytype == SSH_KEYTYPE_SSH2 ||
+ keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
+ keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
+ const char *error;
+
+ key_in.blob = strbuf_new();
+ if (!ppk_loadpub_f(fn, NULL, BinarySink_UPCAST(key_in.blob),
+ NULL, &error)) {
+ strbuf_free(key_in.blob);
+ key_in.blob = NULL;
+ if (file_errors) {
+ *retstr = dupprintf("unable to load file '%s': %s",
+ string, error);
+ filename_free(fn);
+ return NULL;
+ }
+ } else {
+ /*
+ * If we've successfully loaded the file, stop here - we
+ * already have a key blob and need not go to the agent to
+ * list things.
+ */
+ key_in.ssh_version = 2;
+ key_in.comment = NULL;
+ key_ret = pageant_pubkey_copy(&key_in);
+ strbuf_free(key_in.blob);
+ key_in.blob = NULL;
+ filename_free(fn);
+ return key_ret;
+ }
+ } else {
+ if (file_errors) {
+ *retstr = dupprintf("unable to load key file '%s': %s",
+ string, key_type_to_str(keytype));
+ filename_free(fn);
+ return NULL;
+ }
+ }
+ filename_free(fn);
+ }
+
+ /*
+ * Failing that, go through the keys in the agent, and match
+ * against fingerprints and comments as appropriate.
+ */
+ ctx->string = string;
+ ctx->match_fp = try_fp;
+ ctx->match_comment = try_comment;
+ for (unsigned i = 0; i < SSH_N_FPTYPES; i++)
+ ctx->match_fptypes[i] = (try_all_fptypes || i == fptype);
+ ctx->found = NULL;
+ ctx->nfound = 0;
+ if (pageant_enum_keys(key_find_callback, ctx, retstr) ==
+ PAGEANT_ACTION_FAILURE)
+ return NULL;
+
+ if (ctx->nfound == 0) {
+ *retstr = dupstr("no key matched");
+ assert(!ctx->found);
+ return NULL;
+ } else if (ctx->nfound > 1) {
+ *retstr = dupstr("multiple keys matched");
+ assert(ctx->found);
+ pageant_pubkey_free(ctx->found);
+ return NULL;
+ }
+
+ assert(ctx->found);
+ return ctx->found;
+}
+
+void run_client(void)
+{
+ const struct cmdline_key_action *act;
+ struct pageant_pubkey *key;
+ bool errors = false;
+ char *retstr;
+ LoadedFile *message = lf_new(AGENT_MAX_MSGLEN);
+ bool message_loaded = false, message_ok = false;
+ strbuf *signature = strbuf_new();
+
+ if (!agent_exists()) {
+ fprintf(stderr, "pageant: no agent running to talk to\n");
+ exit(1);
+ }
+
+ for (act = keyact_head; act; act = act->next) {
+ switch (act->action) {
+ case KEYACT_CLIENT_ADD:
+ case KEYACT_CLIENT_ADD_ENCRYPTED:
+ if (!unix_add_keyfile(act->filename,
+ act->action == KEYACT_CLIENT_ADD_ENCRYPTED))
+ errors = true;
+ break;
+ case KEYACT_CLIENT_LIST:
+ if (pageant_enum_keys(key_list_callback, NULL, &retstr) ==
+ PAGEANT_ACTION_FAILURE) {
+ fprintf(stderr, "pageant: listing keys: %s\n", retstr);
+ sfree(retstr);
+ errors = true;
+ }
+ break;
+ case KEYACT_CLIENT_DEL:
+ key = NULL;
+ if (!(key = find_key(act->filename, &retstr)) ||
+ pageant_delete_key(key, &retstr) == PAGEANT_ACTION_FAILURE) {
+ fprintf(stderr, "pageant: deleting key '%s': %s\n",
+ act->filename, retstr);
+ sfree(retstr);
+ errors = true;
+ }
+ if (key)
+ pageant_pubkey_free(key);
+ break;
+ case KEYACT_CLIENT_REENCRYPT:
+ key = NULL;
+ if (!(key = find_key(act->filename, &retstr)) ||
+ pageant_reencrypt_key(key, &retstr) == PAGEANT_ACTION_FAILURE) {
+ fprintf(stderr, "pageant: re-encrypting key '%s': %s\n",
+ act->filename, retstr);
+ sfree(retstr);
+ errors = true;
+ }
+ if (key)
+ pageant_pubkey_free(key);
+ break;
+ case KEYACT_CLIENT_PUBLIC_OPENSSH:
+ case KEYACT_CLIENT_PUBLIC:
+ key = NULL;
+ if (!(key = find_key(act->filename, &retstr))) {
+ fprintf(stderr, "pageant: finding key '%s': %s\n",
+ act->filename, retstr);
+ sfree(retstr);
+ errors = true;
+ } else {
+ FILE *fp = stdout; /* FIXME: add a -o option? */
+
+ if (key->ssh_version == 1) {
+ BinarySource src[1];
+ RSAKey rkey;
+
+ BinarySource_BARE_INIT(src, key->blob->u, key->blob->len);
+ memset(&rkey, 0, sizeof(rkey));
+ rkey.comment = dupstr(key->comment);
+ get_rsa_ssh1_pub(src, &rkey, RSA_SSH1_EXPONENT_FIRST);
+ ssh1_write_pubkey(fp, &rkey);
+ freersakey(&rkey);
+ } else {
+ ssh2_write_pubkey(fp, key->comment,
+ key->blob->u,
+ key->blob->len,
+ (act->action == KEYACT_CLIENT_PUBLIC ?
+ SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 :
+ SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH));
+ }
+ pageant_pubkey_free(key);
+ }
+ break;
+ case KEYACT_CLIENT_DEL_ALL:
+ if (pageant_delete_all_keys(&retstr) == PAGEANT_ACTION_FAILURE) {
+ fprintf(stderr, "pageant: deleting all keys: %s\n", retstr);
+ sfree(retstr);
+ errors = true;
+ }
+ break;
+ case KEYACT_CLIENT_REENCRYPT_ALL: {
+ int status = pageant_reencrypt_all_keys(&retstr);
+ if (status == PAGEANT_ACTION_FAILURE) {
+ fprintf(stderr, "pageant: re-encrypting all keys: "
+ "%s\n", retstr);
+ sfree(retstr);
+ errors = true;
+ } else if (status == PAGEANT_ACTION_WARNING) {
+ fprintf(stderr, "pageant: re-encrypting all keys: "
+ "warning: %s\n", retstr);
+ sfree(retstr);
+ }
+ break;
+ }
+ case KEYACT_CLIENT_SIGN:
+ key = NULL;
+ if (!message_loaded) {
+ message_loaded = true;
+ switch(lf_load_fp(message, stdin)) {
+ case LF_TOO_BIG:
+ fprintf(stderr, "pageant: message to sign is too big\n");
+ errors = true;
+ break;
+ case LF_ERROR:
+ fprintf(stderr, "pageant: reading message to sign: %s\n",
+ strerror(errno));
+ errors = true;
+ break;
+ case LF_OK:
+ message_ok = true;
+ break;
+ }
+ }
+ if (!message_ok)
+ break;
+ strbuf_clear(signature);
+ if (!(key = find_key(act->filename, &retstr)) ||
+ pageant_sign(key, ptrlen_from_lf(message), signature,
+ sign_flags, &retstr) == PAGEANT_ACTION_FAILURE) {
+ fprintf(stderr, "pageant: signing with key '%s': %s\n",
+ act->filename, retstr);
+ sfree(retstr);
+ errors = true;
+ } else {
+ fwrite(signature->s, 1, signature->len, stdout);
+ }
+ if (key)
+ pageant_pubkey_free(key);
+ break;
+ default:
+ unreachable("Invalid client action found");
+ }
+ }
+
+ lf_free(message);
+ strbuf_free(signature);
+
+ if (errors)
+ exit(1);
+}
+
+static const PlugVtable X11Connection_plugvt = {
+ .log = x11_log,
+ .closing = x11_closing,
+ .receive = x11_receive,
+ .sent = x11_sent,
+};
+
+
+static bool agent_loop_pw_setup(void *vctx, pollwrapper *pw)
+{
+ struct uxpgnt_client *upc = (struct uxpgnt_client *)vctx;
+
+ if (signalpipe[0] >= 0) {
+ pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R);
+ }
+
+ if (upc->prompt_active)
+ pollwrap_add_fd_rwx(pw, upc->passphrase_fd, SELECT_R);
+
+ return true;
+}
+
+static void agent_loop_pw_check(void *vctx, pollwrapper *pw)
+{
+ struct uxpgnt_client *upc = (struct uxpgnt_client *)vctx;
+
+ if (life == LIFE_TTY) {
+ /*
+ * Every time we wake up (whether it was due to tty_timer
+ * elapsing or for any other reason), poll to see if we still
+ * have a controlling terminal. If we don't, then our
+ * containing tty session has ended, so it's time to clean up
+ * and leave.
+ */
+ if (!have_controlling_tty()) {
+ time_to_die = true;
+ return;
+ }
+ }
+
+ if (signalpipe[0] >= 0 &&
+ pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) {
+ char c[1];
+ if (read(signalpipe[0], c, 1) <= 0)
+ /* ignore error */;
+ /* ignore its value; it'll be `x' */
+ while (1) {
+ int status;
+ pid_t pid;
+ pid = waitpid(-1, &status, WNOHANG);
+ if (pid <= 0)
+ break;
+ if (pid == upc->termination_pid)
+ time_to_die = true;
+ }
+ }
+
+ if (upc->prompt_active &&
+ pollwrap_check_fd_rwx(pw, upc->passphrase_fd, SELECT_R)) {
+ char c;
+ int retd = read(upc->passphrase_fd, &c, 1);
+
+ switch (upc->prompt_type) {
+ case RTPROMPT_GUI:
+ if (retd <= 0) {
+ close(upc->passphrase_fd);
+ upc->passphrase_fd = -1;
+ bool ok = (retd == 0);
+ if (!strbuf_chomp(upc->prompt_buf, '\n'))
+ ok = false;
+ passphrase_done(upc, ok);
+ } else {
+ put_byte(upc->prompt_buf, c);
+ }
+ break;
+ case RTPROMPT_DEBUG:
+ if (retd <= 0) {
+ passphrase_done(upc, false);
+ /* Now never try to read from stdin again */
+ upc->prompt_type = RTPROMPT_UNAVAILABLE;
+ break;
+ }
+
+ switch (c) {
+ case '\n':
+ case '\r':
+ passphrase_done(upc, true);
+ break;
+ case '\004':
+ passphrase_done(upc, false);
+ break;
+ case '\b':
+ case '\177':
+ strbuf_shrink_by(upc->prompt_buf, 1);
+ break;
+ case '\025':
+ strbuf_clear(upc->prompt_buf);
+ break;
+ default:
+ put_byte(upc->prompt_buf, c);
+ break;
+ }
+ break;
+ case RTPROMPT_UNAVAILABLE:
+ unreachable("Should never have started a prompt at all");
+ }
+ }
+}
+
+static bool agent_loop_continue(void *vctx, bool fd, bool cb)
+{
+ return !time_to_die;
+}
+
+void run_agent(FILE *logfp, const char *symlink_path)
+{
+ const char *err;
+ char *errw;
+ struct pageant_listen_state *pl;
+ Plug *pl_plug;
+ Socket *sock;
+ bool errors = false;
+ Conf *conf;
+ const struct cmdline_key_action *act;
+
+ pageant_init();
+
+ /*
+ * Start by loading any keys provided on the command line.
+ */
+ for (act = keyact_head; act; act = act->next) {
+ assert(act->action == KEYACT_AGENT_LOAD ||
+ act->action == KEYACT_AGENT_LOAD_ENCRYPTED);
+ if (!unix_add_keyfile(act->filename,
+ act->action == KEYACT_AGENT_LOAD_ENCRYPTED))
+ errors = true;
+ }
+ if (errors)
+ exit(1);
+
+ /*
+ * Set up a listening socket and run Pageant on it.
+ */
+ struct uxpgnt_client upc[1];
+ memset(upc, 0, sizeof(upc));
+ upc->plc.vt = &uxpgnt_vtable;
+ upc->logfp = logfp;
+ upc->passphrase_fd = -1;
+ upc->termination_pid = -1;
+ upc->prompt_buf = strbuf_new_nm();
+ upc->prompt_type = display ? RTPROMPT_GUI : RTPROMPT_UNAVAILABLE;
+ pl = pageant_listener_new(&pl_plug, &upc->plc);
+ sock = platform_make_agent_socket(pl_plug, PAGEANT_DIR_PREFIX,
+ &errw, &socketname);
+ if (!sock) {
+ fprintf(stderr, "pageant: %s\n", errw);
+ sfree(errw);
+ exit(1);
+ }
+ pageant_listener_got_socket(pl, sock);
+
+ if (symlink_path) {
+ /*
+ * Try to make a symlink to the Unix socket, in a location of
+ * the user's choosing.
+ *
+ * If the link already exists, we want to replace it. There
+ * are two ways we could do this: either make it under another
+ * name and then rename it over the top, or remove the old
+ * link first. The former is what 'ln -sf' does, on the
+ * grounds that it's more atomic. But I think in this case,
+ * where the expected use case is that the previous agent has
+ * long since shut down, atomicity isn't a critical concern
+ * compared to not accidentally overwriting some non-symlink
+ * that might have important data in it!
+ */
+ struct stat st;
+ if (lstat(symlink_path, &st) == 0 && S_ISLNK(st.st_mode))
+ unlink(symlink_path);
+ if (symlink(socketname, symlink_path) < 0)
+ fprintf(stderr, "pageant: making symlink %s: %s\n",
+ symlink_path, strerror(errno));
+ }
+
+ conf = conf_new();
+ conf_set_int(conf, CONF_proxy_type, PROXY_NONE);
+
+ /*
+ * Lifetime preparations.
+ */
+ if (life == LIFE_X11) {
+ struct X11Display *disp;
+ void *greeting;
+ int greetinglen;
+ Socket *s;
+ struct X11Connection *conn;
+ char *x11_setup_err;
+
+ if (!display) {
+ fprintf(stderr, "pageant: no DISPLAY for -X mode\n");
+ exit(1);
+ }
+ disp = x11_setup_display(display, conf, &x11_setup_err);
+ if (!disp) {
+ fprintf(stderr, "pageant: unable to connect to X server: %s\n",
+ x11_setup_err);
+ sfree(x11_setup_err);
+ exit(1);
+ }
+
+ conn = snew(struct X11Connection);
+ conn->plug.vt = &X11Connection_plugvt;
+ s = new_connection(sk_addr_dup(disp->addr),
+ disp->realhost, disp->port,
+ false, true, false, false, &conn->plug, conf,
+ NULL);
+ if ((err = sk_socket_error(s)) != NULL) {
+ fprintf(stderr, "pageant: unable to connect to X server: %s", err);
+ exit(1);
+ }
+ greeting = x11_make_greeting('B', 11, 0, disp->localauthproto,
+ disp->localauthdata,
+ disp->localauthdatalen,
+ NULL, 0, &greetinglen);
+ sk_write(s, greeting, greetinglen);
+ smemclr(greeting, greetinglen);
+ sfree(greeting);
+
+ pageant_fork_and_print_env(false);
+ } else if (life == LIFE_TTY) {
+ schedule_timer(TTY_LIFE_POLL_INTERVAL,
+ tty_life_timer, &dummy_timer_ctx);
+ pageant_fork_and_print_env(true);
+ } else if (life == LIFE_PERM) {
+ pageant_fork_and_print_env(false);
+ } else if (life == LIFE_DEBUG) {
+ pageant_print_env(getpid());
+ upc->logfp = stdout;
+
+ struct termios orig_termios;
+ upc->passphrase_fd = fileno(stdin);
+ if (tcgetattr(upc->passphrase_fd, &orig_termios) == 0) {
+ struct termios new_termios = orig_termios;
+ new_termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON);
+
+ /*
+ * Try to set up a watchdog process that will restore
+ * termios if we crash or are killed. If successful, turn
+ * off echo, for runtime passphrase prompts.
+ */
+ int pipefd[2];
+ if (pipe(pipefd) == 0) {
+ pid_t pid = fork();
+ if (pid == 0) {
+ tcsetattr(upc->passphrase_fd, TCSADRAIN, &new_termios);
+ close(pipefd[1]);
+ char buf[4096];
+ while (read(pipefd[0], buf, sizeof(buf)) > 0);
+ tcsetattr(upc->passphrase_fd, TCSADRAIN, &new_termios);
+ _exit(0);
+ } else if (pid > 0) {
+ upc->prompt_type = RTPROMPT_DEBUG;
+ }
+
+ close(pipefd[0]);
+ if (pid < 0)
+ close(pipefd[1]);
+ }
+ }
+ } else if (life == LIFE_EXEC) {
+ pid_t agentpid, pid;
+
+ agentpid = getpid();
+ setup_sigchld_handler();
+
+ pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ exit(1);
+ } else if (pid == 0) {
+ setenv("SSH_AUTH_SOCK", socketname, true);
+ setenv("SSH_AGENT_PID", dupprintf("%d", (int)agentpid), true);
+ execvp(exec_args[0], exec_args);
+ perror("exec");
+ _exit(127);
+ } else {
+ upc->termination_pid = pid;
+ }
+ }
+
+ if (!upc->logfp)
+ upc->plc.suppress_logging = true;
+
+ cli_main_loop(agent_loop_pw_setup, agent_loop_pw_check,
+ agent_loop_continue, upc);
+
+ /*
+ * Before terminating, clean up our Unix socket file if possible.
+ */
+ if (unlink(socketname) < 0) {
+ fprintf(stderr, "pageant: %s: %s\n", socketname, strerror(errno));
+ exit(1);
+ }
+
+ strbuf_free(upc->prompt_buf);
+ conf_free(conf);
+}
+
+int main(int argc, char **argv)
+{
+ bool doing_opts = true;
+ keyact curr_keyact = KEYACT_AGENT_LOAD;
+ const char *standalone_askpass_prompt = NULL;
+ const char *symlink_path = NULL;
+ FILE *logfp = NULL;
+
+ progname = argv[0];
+
+ /*
+ * Process the command line.
+ */
+ while (--argc > 0) {
+ char *p = *++argv;
+ if (*p == '-' && doing_opts) {
+ if (!strcmp(p, "-V") || !strcmp(p, "--version")) {
+ version();
+ } else if (!strcmp(p, "--help")) {
+ usage();
+ exit(0);
+ } else if (!strcmp(p, "-v")) {
+ logfp = stderr;
+ } else if (!strcmp(p, "-a")) {
+ curr_keyact = KEYACT_CLIENT_ADD;
+ } else if (!strcmp(p, "-d")) {
+ curr_keyact = KEYACT_CLIENT_DEL;
+ } else if (!strcmp(p, "-r")) {
+ curr_keyact = KEYACT_CLIENT_REENCRYPT;
+ } else if (!strcmp(p, "-s")) {
+ shell_type = SHELL_SH;
+ } else if (!strcmp(p, "-c")) {
+ shell_type = SHELL_CSH;
+ } else if (!strcmp(p, "-D")) {
+ add_keyact(KEYACT_CLIENT_DEL_ALL, NULL);
+ } else if (!strcmp(p, "-R")) {
+ add_keyact(KEYACT_CLIENT_REENCRYPT_ALL, NULL);
+ } else if (!strcmp(p, "-l")) {
+ add_keyact(KEYACT_CLIENT_LIST, NULL);
+ } else if (!strcmp(p, "--public")) {
+ curr_keyact = KEYACT_CLIENT_PUBLIC;
+ } else if (!strcmp(p, "--public-openssh") || !strcmp(p, "-L")) {
+ curr_keyact = KEYACT_CLIENT_PUBLIC_OPENSSH;
+ } else if (!strcmp(p, "-X")) {
+ life = LIFE_X11;
+ } else if (!strcmp(p, "-T")) {
+ life = LIFE_TTY;
+ } else if (!strcmp(p, "--no-decrypt") ||
+ !strcmp(p, "-no-decrypt") ||
+ !strcmp(p, "--no_decrypt") ||
+ !strcmp(p, "-no_decrypt") ||
+ !strcmp(p, "--nodecrypt") ||
+ !strcmp(p, "-nodecrypt") ||
+ !strcmp(p, "--encrypted") ||
+ !strcmp(p, "-encrypted")) {
+ if (curr_keyact == KEYACT_AGENT_LOAD)
+ curr_keyact = KEYACT_AGENT_LOAD_ENCRYPTED;
+ else if (curr_keyact == KEYACT_CLIENT_ADD)
+ curr_keyact = KEYACT_CLIENT_ADD_ENCRYPTED;
+ else {
+ fprintf(stderr, "pageant: unexpected -E while not adding "
+ "keys\n");
+ exit(1);
+ }
+ } else if (!strcmp(p, "--debug")) {
+ life = LIFE_DEBUG;
+ } else if (!strcmp(p, "--test-sign")) {
+ curr_keyact = KEYACT_CLIENT_SIGN;
+ sign_flags = 0;
+ } else if (strstartswith(p, "--test-sign-with-flags=")) {
+ curr_keyact = KEYACT_CLIENT_SIGN;
+ sign_flags = atoi(p + strlen("--test-sign-with-flags="));
+ } else if (!strcmp(p, "--permanent")) {
+ life = LIFE_PERM;
+ } else if (!strcmp(p, "--exec")) {
+ life = LIFE_EXEC;
+ /* Now all subsequent arguments go to the exec command. */
+ if (--argc > 0) {
+ exec_args = ++argv;
+ argc = 0; /* force end of option processing */
+ } else {
+ fprintf(stderr, "pageant: expected a command "
+ "after --exec\n");
+ exit(1);
+ }
+ } else if (!strcmp(p, "--tty-prompt")) {
+ prompt_type = PROMPT_TTY;
+ } else if (!strcmp(p, "--gui-prompt")) {
+ prompt_type = PROMPT_GUI;
+ } else if (!strcmp(p, "--askpass")) {
+ if (--argc > 0) {
+ standalone_askpass_prompt = *++argv;
+ } else {
+ fprintf(stderr, "pageant: expected a prompt message "
+ "after --askpass\n");
+ exit(1);
+ }
+ } else if (!strcmp(p, "--symlink")) {
+ if (--argc > 0) {
+ symlink_path = *++argv;
+ } else {
+ fprintf(stderr, "pageant: expected a pathname "
+ "after --symlink\n");
+ exit(1);
+ }
+ } else if (!strcmp(p, "-E") || !strcmp(p, "--fptype")) {
+ const char *keyword;
+ if (--argc > 0) {
+ keyword = *++argv;
+ } else {
+ fprintf(stderr, "pageant: expected a type string "
+ "after %s\n", p);
+ exit(1);
+ }
+ if (!strcmp(keyword, "md5"))
+ key_list_fptype = SSH_FPTYPE_MD5;
+ else if (!strcmp(keyword, "sha256"))
+ key_list_fptype = SSH_FPTYPE_SHA256;
+ else if (!strcmp(keyword, "md5-cert"))
+ key_list_fptype = SSH_FPTYPE_MD5_CERT;
+ else if (!strcmp(keyword, "sha256-cert"))
+ key_list_fptype = SSH_FPTYPE_SHA256_CERT;
+ else {
+ fprintf(stderr, "pageant: unknown fingerprint type `%s'\n",
+ keyword);
+ exit(1);
+ }
+ } else if (!strcmp(p, "--")) {
+ doing_opts = false;
+ } else {
+ fprintf(stderr, "pageant: unrecognised option '%s'\n", p);
+ exit(1);
+ }
+ } else {
+ /*
+ * Non-option arguments (apart from those after --exec,
+ * which are treated specially above) are interpreted as
+ * the names of private key files to either add or delete
+ * from an agent.
+ */
+ add_keyact(curr_keyact, p);
+ }
+ }
+
+ if (life == LIFE_EXEC && !exec_args) {
+ fprintf(stderr, "pageant: expected a command with --exec\n");
+ exit(1);
+ }
+
+ if (!display) {
+ display = getenv("DISPLAY");
+ if (display && !*display)
+ display = NULL;
+ }
+
+ /*
+ * Deal with standalone-askpass mode.
+ */
+ if (standalone_askpass_prompt) {
+ char *passphrase = askpass(standalone_askpass_prompt);
+
+ if (!passphrase)
+ return 1;
+
+ puts(passphrase);
+ fflush(stdout);
+
+ smemclr(passphrase, strlen(passphrase));
+ sfree(passphrase);
+ return 0;
+ }
+
+ /*
+ * Block SIGPIPE, so that we'll get EPIPE individually on
+ * particular network connections that go wrong.
+ */
+ putty_signal(SIGPIPE, SIG_IGN);
+
+ sk_init();
+ uxsel_init();
+
+ /*
+ * Now distinguish our two main running modes. Either we're
+ * actually starting up an agent, in which case we should have a
+ * lifetime mode, and no key actions of KEYACT_CLIENT_* type; or
+ * else we're contacting an existing agent to add or remove keys,
+ * in which case we should have no lifetime mode, and no key
+ * actions of KEYACT_AGENT_* type.
+ */
+ {
+ bool has_agent_actions = false;
+ bool has_client_actions = false;
+ bool has_lifetime = false;
+ const struct cmdline_key_action *act;
+
+ for (act = keyact_head; act; act = act->next) {
+ if (is_agent_action(act->action))
+ has_agent_actions = true;
+ else
+ has_client_actions = true;
+ }
+ if (life != LIFE_UNSPEC)
+ has_lifetime = true;
+
+ if (has_lifetime && has_client_actions) {
+ fprintf(stderr, "pageant: client key actions (-a, -d, -D, -r, -R, "
+ "-l, -L) do not go with an agent lifetime option\n");
+ exit(1);
+ }
+ if (!has_lifetime && has_agent_actions) {
+ fprintf(stderr, "pageant: expected an agent lifetime option with"
+ " bare key file arguments\n");
+ exit(1);
+ }
+ if (!has_lifetime && !has_client_actions) {
+ fprintf(stderr, "pageant: expected an agent lifetime option"
+ " or a client key action\n");
+ exit(1);
+ }
+
+ if (has_lifetime) {
+ run_agent(logfp, symlink_path);
+ } else if (has_client_actions) {
+ run_client();
+ }
+ }
+
+ return 0;
+}
diff --git a/unix/peerinfo.c b/unix/peerinfo.c
new file mode 100644
index 00000000..11f03291
--- /dev/null
+++ b/unix/peerinfo.c
@@ -0,0 +1,32 @@
+/*
+ * Unix: wrapper for getsockopt(SO_PEERCRED), conditionalised on
+ * appropriate autoconfery.
+ */
+
+#if HAVE_CMAKE_H
+#include "cmake.h"
+#endif
+
+#if HAVE_SO_PEERCRED
+#define _GNU_SOURCE
+#include <features.h>
+#endif
+
+#include <sys/socket.h>
+
+#include "putty.h"
+
+bool so_peercred(int fd, int *pid, int *uid, int *gid)
+{
+#if HAVE_SO_PEERCRED
+ struct ucred cr;
+ socklen_t crlen = sizeof(cr);
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &crlen) == 0) {
+ *pid = cr.pid;
+ *uid = cr.uid;
+ *gid = cr.gid;
+ return true;
+ }
+#endif
+ return false;
+}
diff --git a/unix/platform.h b/unix/platform.h
new file mode 100644
index 00000000..3b1db9ba
--- /dev/null
+++ b/unix/platform.h
@@ -0,0 +1,474 @@
+/*
+ * unix/platform.h: Unix-specific inter-module stuff.
+ */
+
+#ifndef PUTTY_UNIX_PLATFORM_H
+#define PUTTY_UNIX_PLATFORM_H
+
+#include <stdio.h> /* for FILENAME_MAX */
+#include <stdint.h> /* C99 int types */
+#ifndef NO_LIBDL
+#include <dlfcn.h> /* Dynamic library loading */
+#endif /* NO_LIBDL */
+#include "charset.h"
+#include <sys/types.h> /* 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 ssh/sharing.c */
+
+/*
+ * Under GTK, we send MA_CLICK _and_ MA_2CLK, or MA_CLICK _and_
+ * MA_3CLK, when a button is pressed for the second or third time.
+ */
+#define MULTICLICK_ONLY_EVENT 0
+
+/*
+ * Under GTK, there is no context help available.
+ */
+typedef void *HelpCtx;
+#define NULL_HELPCTX ((HelpCtx)NULL)
+#define HELPCTX(x) NULL
+#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, "<custom#1>") \
+ X(CLIP_CUSTOM_2, "<custom#2>") \
+ X(CLIP_CUSTOM_3, "<custom#3>") \
+ /* end of list */
+
+#ifdef OSX_GTK
+/* OS X has no PRIMARY selection */
+#define MOUSE_SELECT_CLIPBOARD CLIP_NULL
+#define MOUSE_PASTE_CLIPBOARD CLIP_LOCAL
+#define CLIPNAME_IMPLICIT "Last selected text"
+#define CLIPNAME_EXPLICIT "System clipboard"
+#define CLIPNAME_EXPLICIT_OBJECT "system clipboard"
+/* These defaults are the ones that more or less comply with the OS X
+ * Human Interface Guidelines, i.e. copy/paste to the system clipboard
+ * is _not_ implicit but requires a specific UI action. This is at
+ * odds with all other PuTTY front ends' defaults, but on OS X there
+ * is no multi-decade precedent for PuTTY working the other way. */
+#define CLIPUI_DEFAULT_AUTOCOPY false
+#define CLIPUI_DEFAULT_MOUSE CLIPUI_IMPLICIT
+#define CLIPUI_DEFAULT_INS CLIPUI_EXPLICIT
+#define MENU_CLIPBOARD CLIP_CLIPBOARD
+#define COPYALL_CLIPBOARDS CLIP_CLIPBOARD
+#else
+#define MOUSE_SELECT_CLIPBOARD CLIP_PRIMARY
+#define MOUSE_PASTE_CLIPBOARD CLIP_PRIMARY
+#define CLIPNAME_IMPLICIT "PRIMARY"
+#define CLIPNAME_EXPLICIT "CLIPBOARD"
+#define CLIPNAME_EXPLICIT_OBJECT "CLIPBOARD"
+/* These defaults are the ones Unix PuTTY has historically had since
+ * it was first thought of in 2002 */
+#define CLIPUI_DEFAULT_AUTOCOPY false
+#define CLIPUI_DEFAULT_MOUSE CLIPUI_IMPLICIT
+#define CLIPUI_DEFAULT_INS CLIPUI_IMPLICIT
+#define MENU_CLIPBOARD CLIP_CLIPBOARD
+#define COPYALL_CLIPBOARDS CLIP_PRIMARY, CLIP_CLIPBOARD
+/* X11 supports arbitrary named clipboards */
+#define NAMED_CLIPBOARDS
+#endif
+
+/* The per-session frontend structure managed by window.c */
+typedef struct GtkFrontend GtkFrontend;
+
+/* Callback when a dialog box finishes, and a no-op implementation of it */
+typedef void (*post_dialog_fn_t)(void *ctx, int result);
+void trivial_post_dialog_fn(void *vctx, int result);
+
+/* Start up a session window, with or without a preliminary config box */
+void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx);
+void new_session_window(Conf *conf, const char *geometry_string);
+
+/* Defined in main-gtk-*.c */
+void launch_duplicate_session(Conf *conf);
+void launch_new_session(void);
+void launch_saved_session(const char *str);
+void session_window_closed(void);
+void window_setup_error(const char *errmsg);
+#ifdef MAY_REFER_TO_GTK_IN_HEADERS
+GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend);
+#endif
+
+const struct BackendVtable *select_backend(Conf *conf);
+
+/* Defined in gtk-common.c */
+void gtkcomm_setup(void);
+
+/* Used to pass application-menu operations from
+ * main-gtk-application.c to window.c */
+enum MenuAction {
+ MA_COPY, MA_PASTE, MA_COPY_ALL, MA_DUPLICATE_SESSION,
+ MA_RESTART_SESSION, MA_CHANGE_SETTINGS, MA_CLEAR_SCROLLBACK,
+ MA_RESET_TERMINAL, MA_EVENT_LOG
+};
+void app_menu_action(GtkFrontend *frontend, enum MenuAction);
+
+/* Arrays of pixmap data used for GTK window icons. (main_icon is for
+ * the process's main window; cfg_icon is the modified icon used for
+ * its config box.) */
+extern const char *const *const main_icon[];
+extern const char *const *const cfg_icon[];
+extern const int n_main_icon, n_cfg_icon;
+
+/* Things dialog.c needs from window.c */
+#ifdef MAY_REFER_TO_GTK_IN_HEADERS
+enum DialogSlot {
+ DIALOG_SLOT_RECONFIGURE,
+ DIALOG_SLOT_NETWORK_PROMPT,
+ DIALOG_SLOT_LOGFILE_PROMPT,
+ DIALOG_SLOT_WARN_ON_CLOSE,
+ DIALOG_SLOT_CONNECTION_FATAL,
+ DIALOG_SLOT_LIMIT /* must remain last */
+};
+GtkWidget *gtk_seat_get_window(Seat *seat);
+void register_dialog(Seat *seat, enum DialogSlot slot, GtkWidget *dialog);
+void unregister_dialog(Seat *seat, enum DialogSlot slot);
+void set_window_icon(GtkWidget *window, const char *const *const *icon,
+ int n_icon);
+extern GdkAtom compound_text_atom;
+#endif
+
+/* Things window.c needs from dialog.c */
+#ifdef MAY_REFER_TO_GTK_IN_HEADERS
+GtkWidget *create_config_box(const char *title, Conf *conf,
+ bool midsession, int protcfginfo,
+ post_dialog_fn_t after, void *afterctx);
+#endif
+void nonfatal_message_box(void *window, const char *msg);
+void about_box(void *window);
+typedef struct eventlog_stuff eventlog_stuff;
+eventlog_stuff *eventlogstuff_new(void);
+void eventlogstuff_free(eventlog_stuff *);
+void showeventlog(eventlog_stuff *estuff, void *parentwin);
+void logevent_dlg(eventlog_stuff *estuff, const char *string);
+int gtkdlg_askappend(Seat *seat, Filename *filename,
+ void (*callback)(void *ctx, int result), void *ctx);
+SeatPromptResult gtk_seat_confirm_ssh_host_key(
+ Seat *seat, const char *host, int port, const char *keytype,
+ char *keystr, SeatDialogText *text, HelpCtx helpctx,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+SeatPromptResult gtk_seat_confirm_weak_crypto_primitive(
+ Seat *seat, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+SeatPromptResult gtk_seat_confirm_weak_cached_hostkey(
+ Seat *seat, const char *algname, const char *betteralgs,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+const SeatDialogPromptDescriptions *gtk_seat_prompt_descriptions(Seat *seat);
+#ifdef MAY_REFER_TO_GTK_IN_HEADERS
+struct message_box_button {
+ const char *title;
+ char shortcut;
+ int type; /* more negative means more appropriate to be the Esc action */
+ int value; /* message box's return value if this is pressed */
+};
+struct message_box_buttons {
+ const struct message_box_button *buttons;
+ int nbuttons;
+};
+extern const struct message_box_buttons buttons_yn, buttons_ok;
+GtkWidget *create_message_box(
+ GtkWidget *parentwin, const char *title, const char *msg, int minwid,
+ bool selectable, const struct message_box_buttons *buttons,
+ post_dialog_fn_t after, void *afterctx);
+#endif
+void show_ca_config_box_synchronously(void);
+
+/* window.c needs this special function in utils */
+int keysym_to_unicode(int keysym);
+
+/* Things storage.c needs from window.c */
+char *x_get_default(const char *key);
+
+/* Things storage.c provides to window.c */
+void provide_xrm_string(const char *string, const char *progname);
+
+/* Function that main-gtk-*.c needs from {pterm,putty}.c. Does
+ * early process setup that varies between applications (e.g.
+ * pty_pre_init or sk_init), and is passed a boolean by the caller
+ * indicating whether this is an OS X style multi-session monolithic
+ * process or an ordinary Unix one-shot. */
+void setup(bool single_session_in_this_process);
+
+/*
+ * Per-application constants that affect behaviour of shared modules.
+ */
+/* Do we need an Event Log menu item? (yes for PuTTY, no for pterm) */
+extern const bool use_event_log;
+/* Do we need a New Session menu item? (yes for PuTTY, no for pterm) */
+extern const bool new_session;
+/* Do we need a Saved Sessions menu item? (yes for PuTTY, no for pterm) */
+extern const bool saved_sessions;
+/* When we Duplicate Session, do we need to double-check that the Conf
+ * is in a launchable state? (no for pterm, because conf_launchable
+ * returns an irrelevant answer, since we'll force use of the pty
+ * backend which ignores all the relevant settings) */
+extern const bool dup_check_launchable;
+/* In the Duplicate Session serialised data, do we send/receive an
+ * argv array after the main Conf? (yes for pterm, no for PuTTY) */
+extern const bool use_pty_argv;
+
+/*
+ * OS X environment munging: this is the prefix we expect to find on
+ * environment variable names that were changed by osxlaunch.
+ * Extracted from the command line of the OS X pterm main binary, and
+ * used in pty.c to restore the original environment before
+ * launching its subprocess.
+ */
+extern char *pty_osx_envrestore_prefix;
+
+/* Things provided by console.c */
+struct termios;
+void stderr_tty_init(void); /* call at startup if stderr might be a tty */
+void premsg(struct termios *);
+void postmsg(struct termios *);
+
+/* The interface used by uxsel.c */
+typedef struct uxsel_id uxsel_id;
+void uxsel_init(void);
+typedef void (*uxsel_callback_fn)(int fd, int event);
+void uxsel_set(int fd, int rwx, uxsel_callback_fn callback);
+void uxsel_del(int fd);
+enum { SELECT_R = 1, SELECT_W = 2, SELECT_X = 4 };
+void select_result(int fd, int event);
+int first_fd(int *state, int *rwx);
+int next_fd(int *state, int *rwx);
+/* The following are expected to be provided _to_ uxsel.c by the frontend */
+uxsel_id *uxsel_input_add(int fd, int rwx); /* returns an id */
+void uxsel_input_remove(uxsel_id *id);
+
+/* config-unix.c */
+struct controlbox;
+void unix_setup_config_box(
+ struct controlbox *b, bool midsession, int protocol);
+
+/* config-gtk.c */
+void gtk_setup_config_box(
+ struct controlbox *b, bool midsession, void *window);
+
+/*
+ * In the Unix Unicode layer, DEFAULT_CODEPAGE is a special value
+ * which causes mb_to_wc and wc_to_mb to call _libc_ rather than
+ * libcharset. That way, we can interface the various charsets
+ * supported by libcharset with the one supported by mbstowcs and
+ * wcstombs (which will be the character set in which stuff read
+ * from the command line or config files is assumed to be encoded).
+ */
+#define DEFAULT_CODEPAGE 0xFFFF
+#define CP_UTF8 CS_UTF8 /* from libcharset */
+
+#define strnicmp strncasecmp
+#define stricmp strcasecmp
+
+/* BSD-semantics version of signal(), and another helpful function */
+void (*putty_signal(int sig, void (*func)(int)))(int);
+void block_signal(int sig, bool block_it);
+
+/* utils */
+void cloexec(int);
+void noncloexec(int);
+bool nonblock(int);
+bool no_nonblock(int);
+char *make_dir_and_check_ours(const char *dirname);
+char *make_dir_path(const char *path, mode_t mode);
+
+/*
+ * Exports from unicode.c.
+ */
+bool init_ucs(struct unicode_data *ucsdata, char *line_codepage,
+ bool utf8_override, int font_charset, int vtmode);
+
+/*
+ * Spare functions exported directly from network.c.
+ */
+void *sk_getxdmdata(Socket *sock, int *lenp);
+int sk_net_get_fd(Socket *sock);
+SockAddr *unix_sock_addr(const char *path);
+Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug);
+
+/*
+ * General helpful Unix stuff: more helpful version of the FD_SET
+ * macro, which also handles maxfd.
+ */
+#define FD_SET_MAX(fd, max, set) do { \
+ FD_SET(fd, &set); \
+ if (max < fd + 1) max = fd + 1; \
+} while (0)
+
+/*
+ * Exports from serial.c.
+ */
+extern const struct BackendVtable serial_backend;
+
+/*
+ * peerinfo.c, wrapping getsockopt(SO_PEERCRED).
+ */
+bool so_peercred(int fd, int *pid, int *uid, int *gid);
+
+/*
+ * fd-socket.c.
+ */
+Socket *make_fd_socket(int infd, int outfd, int inerrfd,
+ SockAddr *addr, int port, Plug *plug);
+Socket *make_deferred_fd_socket(DeferredSocketOpener *opener,
+ SockAddr *addr, int port, Plug *plug);
+void setup_fd_socket(Socket *s, int infd, int outfd, int inerrfd);
+void fd_socket_set_psb_prefix(Socket *s, const char *prefix);
+
+/*
+ * Default font setting, which can vary depending on NOT_X_WINDOWS.
+ */
+#ifdef NOT_X_WINDOWS
+#define DEFAULT_GTK_FONT "client:Monospace 12"
+#else
+#define DEFAULT_GTK_FONT "server:fixed"
+#endif
+
+/*
+ * pty.c.
+ */
+void pty_pre_init(void); /* pty+utmp setup before dropping privilege */
+/* Pass in the argv[] for an instance of the pty backend created by
+ * the standard vtable constructor. Only called from (non-OSX) pterm,
+ * which will construct exactly one such instance, and initialises
+ * this from the command line. */
+extern char **pty_argv;
+
+/*
+ * askpass.c.
+ */
+char *gtk_askpass_main(const char *display, const char *wintitle,
+ const char *prompt, bool *success);
+
+/*
+ * procnet.c.
+ */
+bool socket_peer_is_same_user(int fd);
+static inline bool sk_peer_trusted(Socket *sock)
+{
+ int fd = sk_net_get_fd(sock);
+ return fd >= 0 && socket_peer_is_same_user(fd);
+}
+
+/*
+ * sftpserver.c.
+ */
+extern const SftpServerVtable unix_live_sftpserver_vt;
+
+/*
+ * utils/pollwrap.c.
+ */
+typedef struct pollwrapper pollwrapper;
+pollwrapper *pollwrap_new(void);
+void pollwrap_free(pollwrapper *pw);
+void pollwrap_clear(pollwrapper *pw);
+void pollwrap_add_fd_events(pollwrapper *pw, int fd, int events);
+void pollwrap_add_fd_rwx(pollwrapper *pw, int fd, int rwx);
+int pollwrap_poll_instant(pollwrapper *pw);
+int pollwrap_poll_endless(pollwrapper *pw);
+int pollwrap_poll_timeout(pollwrapper *pw, int milliseconds);
+int pollwrap_get_fd_events(pollwrapper *pw, int fd);
+int pollwrap_get_fd_rwx(pollwrapper *pw, int fd);
+static inline bool pollwrap_check_fd_rwx(pollwrapper *pw, int fd, int rwx)
+{
+ return (pollwrap_get_fd_rwx(pw, fd) & rwx) != 0;
+}
+
+/*
+ * cliloop.c.
+ */
+typedef bool (*cliloop_pw_setup_t)(void *ctx, pollwrapper *pw);
+typedef void (*cliloop_pw_check_t)(void *ctx, pollwrapper *pw);
+typedef bool (*cliloop_continue_t)(void *ctx, bool found_any_fd,
+ bool ran_any_callback);
+
+void cli_main_loop(cliloop_pw_setup_t pw_setup,
+ cliloop_pw_check_t pw_check,
+ cliloop_continue_t cont, void *ctx);
+
+bool cliloop_no_pw_setup(void *ctx, pollwrapper *pw);
+void cliloop_no_pw_check(void *ctx, pollwrapper *pw);
+bool cliloop_always_continue(void *ctx, bool, bool);
+
+/* network.c: network error reporting helper taking an OS error code */
+void plug_closing_errno(Plug *plug, int error);
+
+SeatPromptResult make_spr_sw_abort_errno(const char *prefix, int errno_value);
+
+#endif /* PUTTY_UNIX_PLATFORM_H */
diff --git a/unix/plink.c b/unix/plink.c
new file mode 100644
index 00000000..f0c73754
--- /dev/null
+++ b/unix/plink.c
@@ -0,0 +1,984 @@
+/*
+ * PLink - a command-line (stdin/stdout) variant of PuTTY.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <pwd.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "storage.h"
+#include "tree234.h"
+
+#define MAX_STDIN_BACKLOG 4096
+
+static LogContext *logctx;
+
+static struct termios orig_termios;
+
+void cmdline_error(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ console_print_error_msg_fmt_v("plink", fmt, ap);
+ va_end(ap);
+ exit(1);
+}
+
+static bool local_tty = false; /* do we have a local tty? */
+
+static Backend *backend;
+static Conf *conf;
+
+/*
+ * Default settings that are specific to Unix plink.
+ */
+char *platform_default_s(const char *name)
+{
+ if (!strcmp(name, "TermType"))
+ return dupstr(getenv("TERM"));
+ if (!strcmp(name, "SerialLine"))
+ return dupstr("/dev/ttyS0");
+ return NULL;
+}
+
+bool platform_default_b(const char *name, bool def)
+{
+ return def;
+}
+
+int platform_default_i(const char *name, int def)
+{
+ return def;
+}
+
+FontSpec *platform_default_fontspec(const char *name)
+{
+ return fontspec_new("");
+}
+
+Filename *platform_default_filename(const char *name)
+{
+ if (!strcmp(name, "LogFileName"))
+ return filename_from_str("putty.log");
+ else
+ return filename_from_str("");
+}
+
+char *x_get_default(const char *key)
+{
+ return NULL; /* this is a stub */
+}
+static void plink_echoedit_update(Seat *seat, bool echo, bool edit)
+{
+ /* Update stdin read mode to reflect changes in line discipline. */
+ struct termios mode;
+
+ if (!local_tty) return;
+
+ mode = orig_termios;
+
+ if (echo)
+ mode.c_lflag |= ECHO;
+ else
+ mode.c_lflag &= ~ECHO;
+
+ if (edit) {
+ mode.c_iflag |= ICRNL;
+ mode.c_lflag |= ISIG | ICANON;
+ mode.c_oflag |= OPOST;
+ } else {
+ mode.c_iflag &= ~ICRNL;
+ mode.c_lflag &= ~(ISIG | ICANON);
+ mode.c_oflag &= ~OPOST;
+ /* Solaris sets these to unhelpful values */
+ mode.c_cc[VMIN] = 1;
+ mode.c_cc[VTIME] = 0;
+ /* FIXME: perhaps what we do with IXON/IXOFF should be an
+ * argument to the echoedit_update() method, to allow
+ * implementation of SSH-2 "xon-xoff" and Rlogin's
+ * equivalent? */
+ mode.c_iflag &= ~IXON;
+ mode.c_iflag &= ~IXOFF;
+ }
+ /*
+ * Mark parity errors and (more important) BREAK on input. This
+ * is more complex than it need be because POSIX-2001 suggests
+ * that escaping of valid 0xff in the input stream is dependent on
+ * IGNPAR being clear even though marking of BREAK isn't. NetBSD
+ * 2.0 goes one worse and makes it dependent on INPCK too. We
+ * deal with this by forcing these flags into a useful state and
+ * then faking the state in which we found them in from_tty() if
+ * we get passed a parity or framing error.
+ */
+ mode.c_iflag = (mode.c_iflag | INPCK | PARMRK) & ~IGNPAR;
+
+ tcsetattr(STDIN_FILENO, TCSANOW, &mode);
+}
+
+/* Helper function to extract a special character from a termios. */
+static char *get_ttychar(struct termios *t, int index)
+{
+ cc_t c = t->c_cc[index];
+#if defined(_POSIX_VDISABLE)
+ if (c == _POSIX_VDISABLE)
+ return dupstr("");
+#endif
+ return dupprintf("^<%d>", c);
+}
+
+static char *plink_get_ttymode(Seat *seat, const char *mode)
+{
+ /*
+ * Propagate appropriate terminal modes from the local terminal,
+ * if any.
+ */
+ if (!local_tty) return NULL;
+
+#define GET_CHAR(ourname, uxname) \
+ do { \
+ if (strcmp(mode, ourname) == 0) \
+ return get_ttychar(&orig_termios, uxname); \
+ } while(0)
+#define GET_BOOL(ourname, uxname, uxmemb, transform) \
+ do { \
+ if (strcmp(mode, ourname) == 0) { \
+ bool b = (orig_termios.uxmemb & uxname) != 0; \
+ transform; \
+ return dupprintf("%d", b); \
+ } \
+ } while (0)
+
+ /*
+ * Modes that want to be the same on all terminal devices involved.
+ */
+ /* All the special characters supported by SSH */
+#if defined(VINTR)
+ GET_CHAR("INTR", VINTR);
+#endif
+#if defined(VQUIT)
+ GET_CHAR("QUIT", VQUIT);
+#endif
+#if defined(VERASE)
+ GET_CHAR("ERASE", VERASE);
+#endif
+#if defined(VKILL)
+ GET_CHAR("KILL", VKILL);
+#endif
+#if defined(VEOF)
+ GET_CHAR("EOF", VEOF);
+#endif
+#if defined(VEOL)
+ GET_CHAR("EOL", VEOL);
+#endif
+#if defined(VEOL2)
+ GET_CHAR("EOL2", VEOL2);
+#endif
+#if defined(VSTART)
+ GET_CHAR("START", VSTART);
+#endif
+#if defined(VSTOP)
+ GET_CHAR("STOP", VSTOP);
+#endif
+#if defined(VSUSP)
+ GET_CHAR("SUSP", VSUSP);
+#endif
+#if defined(VDSUSP)
+ GET_CHAR("DSUSP", VDSUSP);
+#endif
+#if defined(VREPRINT)
+ GET_CHAR("REPRINT", VREPRINT);
+#endif
+#if defined(VWERASE)
+ GET_CHAR("WERASE", VWERASE);
+#endif
+#if defined(VLNEXT)
+ GET_CHAR("LNEXT", VLNEXT);
+#endif
+#if defined(VFLUSH)
+ GET_CHAR("FLUSH", VFLUSH);
+#endif
+#if defined(VSWTCH)
+ GET_CHAR("SWTCH", VSWTCH);
+#endif
+#if defined(VSTATUS)
+ GET_CHAR("STATUS", VSTATUS);
+#endif
+#if defined(VDISCARD)
+ GET_CHAR("DISCARD", VDISCARD);
+#endif
+ /* Modes that "configure" other major modes. These should probably be
+ * considered as user preferences. */
+ /* Configuration of ICANON */
+#if defined(ECHOK)
+ GET_BOOL("ECHOK", ECHOK, c_lflag, );
+#endif
+#if defined(ECHOKE)
+ GET_BOOL("ECHOKE", ECHOKE, c_lflag, );
+#endif
+#if defined(ECHOE)
+ GET_BOOL("ECHOE", ECHOE, c_lflag, );
+#endif
+#if defined(ECHONL)
+ GET_BOOL("ECHONL", ECHONL, c_lflag, );
+#endif
+#if defined(XCASE)
+ GET_BOOL("XCASE", XCASE, c_lflag, );
+#endif
+#if defined(IUTF8)
+ GET_BOOL("IUTF8", IUTF8, c_iflag, );
+#endif
+ /* Configuration of ECHO */
+#if defined(ECHOCTL)
+ GET_BOOL("ECHOCTL", ECHOCTL, c_lflag, );
+#endif
+ /* Configuration of IXON/IXOFF */
+#if defined(IXANY)
+ GET_BOOL("IXANY", IXANY, c_iflag, );
+#endif
+ /* Configuration of OPOST */
+#if defined(OLCUC)
+ GET_BOOL("OLCUC", OLCUC, c_oflag, );
+#endif
+#if defined(ONLCR)
+ GET_BOOL("ONLCR", ONLCR, c_oflag, );
+#endif
+#if defined(OCRNL)
+ GET_BOOL("OCRNL", OCRNL, c_oflag, );
+#endif
+#if defined(ONOCR)
+ GET_BOOL("ONOCR", ONOCR, c_oflag, );
+#endif
+#if defined(ONLRET)
+ GET_BOOL("ONLRET", ONLRET, c_oflag, );
+#endif
+
+ /*
+ * Modes that want to be set in only one place, and that we have
+ * squashed locally.
+ */
+#if defined(ISIG)
+ GET_BOOL("ISIG", ISIG, c_lflag, );
+#endif
+#if defined(ICANON)
+ GET_BOOL("ICANON", ICANON, c_lflag, );
+#endif
+#if defined(ECHO)
+ GET_BOOL("ECHO", ECHO, c_lflag, );
+#endif
+#if defined(IXON)
+ GET_BOOL("IXON", IXON, c_iflag, );
+#endif
+#if defined(IXOFF)
+ GET_BOOL("IXOFF", IXOFF, c_iflag, );
+#endif
+#if defined(OPOST)
+ GET_BOOL("OPOST", OPOST, c_oflag, );
+#endif
+
+ /*
+ * We do not propagate the following modes:
+ * - Parity/serial settings, which are a local affair and don't
+ * make sense propagated over SSH's 8-bit byte-stream.
+ * IGNPAR PARMRK INPCK CS7 CS8 PARENB PARODD
+ * - Things that want to be enabled in one place that we don't
+ * squash locally.
+ * IUCLC
+ * - Status bits.
+ * PENDIN
+ * - Things I don't know what to do with. (FIXME)
+ * ISTRIP IMAXBEL NOFLSH TOSTOP IEXTEN
+ * INLCR IGNCR ICRNL
+ */
+
+#undef GET_CHAR
+#undef GET_BOOL
+
+ /* Fall through to here for unrecognised names, or ones that are
+ * unsupported on this platform */
+ return NULL;
+}
+
+void cleanup_termios(void)
+{
+ if (local_tty)
+ tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
+}
+
+static bufchain stdout_data, stderr_data;
+static bufchain_sink stdout_bcs, stderr_bcs;
+static StripCtrlChars *stdout_scc, *stderr_scc;
+static BinarySink *stdout_bs, *stderr_bs;
+
+static enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
+
+static size_t output_backlog(void)
+{
+ return bufchain_size(&stdout_data) + bufchain_size(&stderr_data);
+}
+
+void try_output(bool is_stderr)
+{
+ bufchain *chain = (is_stderr ? &stderr_data : &stdout_data);
+ int fd = (is_stderr ? STDERR_FILENO : STDOUT_FILENO);
+ ssize_t ret;
+
+ if (bufchain_size(chain) > 0) {
+ bool prev_nonblock = nonblock(fd);
+ ptrlen senddata;
+ do {
+ senddata = bufchain_prefix(chain);
+ ret = write(fd, senddata.ptr, senddata.len);
+ if (ret > 0)
+ bufchain_consume(chain, ret);
+ } while (ret == senddata.len && bufchain_size(chain) != 0);
+ if (!prev_nonblock)
+ no_nonblock(fd);
+ if (ret < 0 && errno != EAGAIN) {
+ perror(is_stderr ? "stderr: write" : "stdout: write");
+ exit(1);
+ }
+
+ backend_unthrottle(backend, output_backlog());
+ }
+ if (outgoingeof == EOF_PENDING && bufchain_size(&stdout_data) == 0) {
+ close(STDOUT_FILENO);
+ outgoingeof = EOF_SENT;
+ }
+}
+
+static size_t plink_output(
+ Seat *seat, SeatOutputType type, const void *data, size_t len)
+{
+ bool is_stderr = type != SEAT_OUTPUT_STDOUT;
+ assert(is_stderr || outgoingeof == EOF_NO);
+
+ BinarySink *bs = is_stderr ? stderr_bs : stdout_bs;
+ put_data(bs, data, len);
+
+ try_output(is_stderr);
+ return output_backlog();
+}
+
+static bool plink_eof(Seat *seat)
+{
+ assert(outgoingeof == EOF_NO);
+ outgoingeof = EOF_PENDING;
+ try_output(false);
+ return false; /* do not respond to incoming EOF with outgoing */
+}
+
+static SeatPromptResult plink_get_userpass_input(Seat *seat, prompts_t *p)
+{
+ /* Plink doesn't support Restart Session, so we can just have a
+ * single static cmdline_get_passwd_input_state that's never reset */
+ static cmdline_get_passwd_input_state cmdline_state =
+ CMDLINE_GET_PASSWD_INPUT_STATE_INIT;
+
+ SeatPromptResult spr;
+ spr = cmdline_get_passwd_input(p, &cmdline_state, false);
+ if (spr.kind == SPRK_INCOMPLETE)
+ spr = console_get_userpass_input(p);
+ return spr;
+}
+
+static bool plink_seat_interactive(Seat *seat)
+{
+ return (!*conf_get_str(conf, CONF_remote_cmd) &&
+ !*conf_get_str(conf, CONF_remote_cmd2) &&
+ !*conf_get_str(conf, CONF_ssh_nc_host));
+}
+
+static const SeatVtable plink_seat_vt = {
+ .output = plink_output,
+ .eof = plink_eof,
+ .sent = nullseat_sent,
+ .banner = nullseat_banner_to_stderr,
+ .get_userpass_input = plink_get_userpass_input,
+ .notify_session_started = nullseat_notify_session_started,
+ .notify_remote_exit = nullseat_notify_remote_exit,
+ .notify_remote_disconnect = nullseat_notify_remote_disconnect,
+ .connection_fatal = console_connection_fatal,
+ .update_specials_menu = nullseat_update_specials_menu,
+ .get_ttymode = plink_get_ttymode,
+ .set_busy_status = nullseat_set_busy_status,
+ .confirm_ssh_host_key = console_confirm_ssh_host_key,
+ .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive,
+ .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey,
+ .prompt_descriptions = console_prompt_descriptions,
+ .is_utf8 = nullseat_is_never_utf8,
+ .echoedit_update = plink_echoedit_update,
+ .get_x_display = nullseat_get_x_display,
+ .get_windowid = nullseat_get_windowid,
+ .get_window_pixel_size = nullseat_get_window_pixel_size,
+ .stripctrl_new = console_stripctrl_new,
+ .set_trust_status = console_set_trust_status,
+ .can_set_trust_status = console_can_set_trust_status,
+ .has_mixed_input_stream = console_has_mixed_input_stream,
+ .verbose = cmdline_seat_verbose,
+ .interactive = plink_seat_interactive,
+ .get_cursor_position = nullseat_get_cursor_position,
+};
+static Seat plink_seat[1] = {{ &plink_seat_vt }};
+
+/*
+ * Handle data from a local tty in PARMRK format.
+ */
+static void from_tty(void *vbuf, unsigned len)
+{
+ char *p, *q, *end, *buf = vbuf;
+ static enum {NORMAL, FF, FF00} state = NORMAL;
+
+ p = buf; end = buf + len;
+ while (p < end) {
+ switch (state) {
+ case NORMAL:
+ if (*p == '\xff') {
+ p++;
+ state = FF;
+ } else {
+ q = memchr(p, '\xff', end - p);
+ if (q == NULL) q = end;
+ backend_send(backend, p, q - p);
+ p = q;
+ }
+ break;
+ case FF:
+ if (*p == '\xff') {
+ backend_send(backend, p, 1);
+ p++;
+ state = NORMAL;
+ } else if (*p == '\0') {
+ p++;
+ state = FF00;
+ } else abort();
+ break;
+ case FF00:
+ if (*p == '\0') {
+ backend_special(backend, SS_BRK, 0);
+ } else {
+ /*
+ * Pretend that PARMRK wasn't set. This involves
+ * faking what INPCK and IGNPAR would have done if
+ * we hadn't overridden them. Unfortunately, we
+ * can't do this entirely correctly because INPCK
+ * distinguishes between framing and parity
+ * errors, but PARMRK format represents both in
+ * the same way. We assume that parity errors are
+ * more common than framing errors, and hence
+ * treat all input errors as being subject to
+ * INPCK.
+ */
+ if (orig_termios.c_iflag & INPCK) {
+ /* If IGNPAR is set, we throw away the character. */
+ if (!(orig_termios.c_iflag & IGNPAR)) {
+ /* PE/FE get passed on as NUL. */
+ *p = 0;
+ backend_send(backend, p, 1);
+ }
+ } else {
+ /* INPCK not set. Assume we got a parity error. */
+ backend_send(backend, p, 1);
+ }
+ }
+ p++;
+ state = NORMAL;
+ }
+ }
+}
+
+static int signalpipe[2];
+
+void sigwinch(int signum)
+{
+ if (write(signalpipe[1], "x", 1) <= 0)
+ /* not much we can do about it */;
+}
+
+/*
+ * Short description of parameters.
+ */
+static void usage(void)
+{
+ printf("Plink: command-line connection utility\n");
+ printf("%s\n", ver);
+ printf("Usage: plink [options] [user@]host [command]\n");
+ printf(" (\"host\" can also be a PuTTY saved session name)\n");
+ printf("Options:\n");
+ printf(" -V print version information and exit\n");
+ printf(" -pgpfp print PGP key fingerprints and exit\n");
+ printf(" -v show verbose messages\n");
+ printf(" -load sessname Load settings from saved session\n");
+ printf(" -ssh -telnet -rlogin -raw -serial\n");
+ printf(" force use of a particular protocol\n");
+ printf(" -ssh-connection\n");
+ printf(" force use of the bare ssh-connection protocol\n");
+ printf(" -P port connect to specified port\n");
+ printf(" -l user connect with specified username\n");
+ printf(" -batch disable all interactive prompts\n");
+ printf(" -proxycmd command\n");
+ printf(" use 'command' as local proxy\n");
+ printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n");
+ printf(" Specify the serial configuration (serial only)\n");
+ printf("The following options only apply to SSH connections:\n");
+ printf(" -pwfile file login with password read from specified file\n");
+ printf(" -D [listen-IP:]listen-port\n");
+ printf(" Dynamic SOCKS-based port forwarding\n");
+ printf(" -L [listen-IP:]listen-port:host:port\n");
+ printf(" Forward local port to remote address\n");
+ printf(" -R [listen-IP:]listen-port:host:port\n");
+ printf(" Forward remote port to local address\n");
+ printf(" -X -x enable / disable X11 forwarding\n");
+ printf(" -A -a enable / disable agent forwarding\n");
+ printf(" -t -T enable / disable pty allocation\n");
+ printf(" -1 -2 force use of particular SSH protocol version\n");
+ printf(" -4 -6 force use of IPv4 or IPv6\n");
+ printf(" -C enable compression\n");
+ printf(" -i key private key file for user authentication\n");
+ printf(" -noagent disable use of Pageant\n");
+ printf(" -agent enable use of Pageant\n");
+ printf(" -no-trivial-auth\n");
+ printf(" disconnect if SSH authentication succeeds trivially\n");
+ printf(" -noshare disable use of connection sharing\n");
+ printf(" -share enable use of connection sharing\n");
+ printf(" -hostkey keyid\n");
+ printf(" manually specify a host key (may be repeated)\n");
+ printf(" -sanitise-stderr, -sanitise-stdout, "
+ "-no-sanitise-stderr, -no-sanitise-stdout\n");
+ printf(" do/don't strip control chars from standard "
+ "output/error\n");
+ printf(" -no-antispoof omit anti-spoofing prompt after "
+ "authentication\n");
+ printf(" -m file read remote command(s) from file\n");
+ printf(" -s remote command is an SSH subsystem (SSH-2 only)\n");
+ printf(" -N don't start a shell/command (SSH-2 only)\n");
+ printf(" -nc host:port\n");
+ printf(" open tunnel in place of session (SSH-2 only)\n");
+ printf(" -sshlog file\n");
+ printf(" -sshrawlog file\n");
+ printf(" log protocol details to a file\n");
+ printf(" -logoverwrite\n");
+ printf(" -logappend\n");
+ printf(" control what happens when a log file already exists\n");
+ printf(" -shareexists\n");
+ printf(" test whether a connection-sharing upstream exists\n");
+ exit(1);
+}
+
+static void version(void)
+{
+ char *buildinfo_text = buildinfo("\n");
+ printf("plink: %s\n%s\n", ver, buildinfo_text);
+ sfree(buildinfo_text);
+ exit(0);
+}
+
+void frontend_net_error_pending(void) {}
+
+const bool share_can_be_downstream = true;
+const bool share_can_be_upstream = true;
+
+const bool buildinfo_gtk_relevant = false;
+
+const unsigned cmdline_tooltype =
+ TOOLTYPE_HOST_ARG |
+ TOOLTYPE_HOST_ARG_CAN_BE_SESSION |
+ TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX |
+ TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD;
+
+static bool seen_stdin_eof = false;
+
+static bool plink_pw_setup(void *vctx, pollwrapper *pw)
+{
+ pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R);
+
+ if (!seen_stdin_eof &&
+ backend_connected(backend) &&
+ backend_sendok(backend) &&
+ backend_sendbuffer(backend) < MAX_STDIN_BACKLOG) {
+ /* If we're OK to send, then try to read from stdin. */
+ pollwrap_add_fd_rwx(pw, STDIN_FILENO, SELECT_R);
+ }
+
+ if (bufchain_size(&stdout_data) > 0) {
+ /* If we have data for stdout, try to write to stdout. */
+ pollwrap_add_fd_rwx(pw, STDOUT_FILENO, SELECT_W);
+ }
+
+ if (bufchain_size(&stderr_data) > 0) {
+ /* If we have data for stderr, try to write to stderr. */
+ pollwrap_add_fd_rwx(pw, STDERR_FILENO, SELECT_W);
+ }
+
+ return true;
+}
+
+static void plink_pw_check(void *vctx, pollwrapper *pw)
+{
+ if (pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) {
+ char c[1];
+ struct winsize size;
+ if (read(signalpipe[0], c, 1) <= 0)
+ /* ignore error */;
+ /* ignore its value; it'll be `x' */
+ if (ioctl(STDIN_FILENO, TIOCGWINSZ, (void *)&size) >= 0)
+ backend_size(backend, size.ws_col, size.ws_row);
+ }
+
+ if (pollwrap_check_fd_rwx(pw, STDIN_FILENO, SELECT_R)) {
+ char buf[4096];
+ int ret;
+
+ if (backend_connected(backend)) {
+ ret = read(STDIN_FILENO, buf, sizeof(buf));
+ noise_ultralight(NOISE_SOURCE_IOLEN, ret);
+ if (ret < 0) {
+ perror("stdin: read");
+ exit(1);
+ } else if (ret == 0) {
+ backend_special(backend, SS_EOF, 0);
+ seen_stdin_eof = true;
+ } else {
+ if (local_tty)
+ from_tty(buf, ret);
+ else
+ backend_send(backend, buf, ret);
+ }
+ }
+ }
+
+ if (pollwrap_check_fd_rwx(pw, STDOUT_FILENO, SELECT_W))
+ try_output(false);
+
+ if (pollwrap_check_fd_rwx(pw, STDERR_FILENO, SELECT_W))
+ try_output(true);
+}
+
+static bool plink_continue(void *vctx, bool found_any_fd,
+ bool ran_any_callback)
+{
+ if (!backend_connected(backend) &&
+ bufchain_size(&stdout_data) == 0 && bufchain_size(&stderr_data) == 0)
+ return false; /* terminate main loop */
+ return true;
+}
+
+int main(int argc, char **argv)
+{
+ int exitcode;
+ bool errors;
+ enum TriState sanitise_stdout = AUTO, sanitise_stderr = AUTO;
+ bool use_subsystem = false;
+ bool just_test_share_exists = false;
+ struct winsize size;
+ const struct BackendVtable *backvt;
+
+ /*
+ * Initialise port and protocol to sensible defaults. (These
+ * will be overridden by more or less anything.)
+ */
+ settings_set_default_protocol(PROT_SSH);
+ settings_set_default_port(22);
+
+ bufchain_init(&stdout_data);
+ bufchain_init(&stderr_data);
+ bufchain_sink_init(&stdout_bcs, &stdout_data);
+ bufchain_sink_init(&stderr_bcs, &stderr_data);
+ stdout_bs = BinarySink_UPCAST(&stdout_bcs);
+ stderr_bs = BinarySink_UPCAST(&stderr_bcs);
+ outgoingeof = EOF_NO;
+
+ stderr_tty_init();
+ /*
+ * Process the command line.
+ */
+ conf = conf_new();
+ do_defaults(NULL, conf);
+ settings_set_default_protocol(conf_get_int(conf, CONF_protocol));
+ settings_set_default_port(conf_get_int(conf, CONF_port));
+ errors = false;
+ {
+ /*
+ * Override the default protocol if PLINK_PROTOCOL is set.
+ */
+ char *p = getenv("PLINK_PROTOCOL");
+ if (p) {
+ const struct BackendVtable *vt = backend_vt_from_name(p);
+ if (vt) {
+ settings_set_default_protocol(vt->protocol);
+ settings_set_default_port(vt->default_port);
+ conf_set_int(conf, CONF_protocol, vt->protocol);
+ conf_set_int(conf, CONF_port, vt->default_port);
+ }
+ }
+ }
+ while (--argc) {
+ char *p = *++argv;
+ int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
+ 1, conf);
+ if (ret == -2) {
+ fprintf(stderr,
+ "plink: option \"%s\" requires an argument\n", p);
+ errors = true;
+ } else if (ret == 2) {
+ --argc, ++argv;
+ } else if (ret == 1) {
+ continue;
+ } else if (!strcmp(p, "-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();
+ exit(0);
+ } else if (!strcmp(p, "-pgpfp")) {
+ pgp_fingerprints();
+ exit(1);
+ } else if (!strcmp(p, "-o")) {
+ if (argc <= 1) {
+ fprintf(stderr,
+ "plink: option \"-o\" requires an argument\n");
+ errors = true;
+ } else {
+ --argc;
+ /* Explicitly pass "plink" in place of appname for
+ * error reporting purposes. appname will have been
+ * set by be_list.c to something more generic, probably
+ * "PuTTY". */
+ provide_xrm_string(*++argv, "plink");
+ }
+ } else if (!strcmp(p, "-shareexists")) {
+ just_test_share_exists = true;
+ } else if (!strcmp(p, "-fuzznet")) {
+ conf_set_int(conf, CONF_proxy_type, PROXY_FUZZ);
+ conf_set_str(conf, CONF_proxy_telnet_command, "%host");
+ } else if (!strcmp(p, "-sanitise-stdout") ||
+ !strcmp(p, "-sanitize-stdout")) {
+ sanitise_stdout = FORCE_ON;
+ } else if (!strcmp(p, "-no-sanitise-stdout") ||
+ !strcmp(p, "-no-sanitize-stdout")) {
+ sanitise_stdout = FORCE_OFF;
+ } else if (!strcmp(p, "-sanitise-stderr") ||
+ !strcmp(p, "-sanitize-stderr")) {
+ sanitise_stderr = FORCE_ON;
+ } else if (!strcmp(p, "-no-sanitise-stderr") ||
+ !strcmp(p, "-no-sanitize-stderr")) {
+ sanitise_stderr = FORCE_OFF;
+ } else if (!strcmp(p, "-no-antispoof")) {
+ console_antispoof_prompt = false;
+ } else if (*p != '-') {
+ strbuf *cmdbuf = strbuf_new();
+
+ while (argc > 0) {
+ if (cmdbuf->len > 0)
+ put_byte(cmdbuf, ' '); /* add space separator */
+ put_dataz(cmdbuf, p);
+ if (--argc > 0)
+ p = *++argv;
+ }
+
+ conf_set_str(conf, CONF_remote_cmd, cmdbuf->s);
+ conf_set_str(conf, CONF_remote_cmd2, "");
+ conf_set_bool(conf, CONF_nopty, true); /* command => no tty */
+
+ strbuf_free(cmdbuf);
+ break; /* done with cmdline */
+ } else {
+ fprintf(stderr, "plink: unknown option \"%s\"\n", p);
+ errors = true;
+ }
+ }
+
+ if (errors)
+ return 1;
+
+ if (!cmdline_host_ok(conf)) {
+ usage();
+ }
+
+ prepare_session(conf);
+
+ /*
+ * Perform command-line overrides on session configuration.
+ */
+ cmdline_run_saved(conf);
+
+ /*
+ * If we have no better ideas for the remote username, use the local
+ * one, as 'ssh' does.
+ */
+ if (conf_get_str(conf, CONF_username)[0] == '\0') {
+ char *user = get_username();
+ if (user) {
+ conf_set_str(conf, CONF_username, user);
+ sfree(user);
+ }
+ }
+
+ /*
+ * Apply subsystem status.
+ */
+ if (use_subsystem)
+ conf_set_bool(conf, CONF_ssh_subsys, true);
+
+ /*
+ * Select protocol. This is farmed out into a table in a
+ * separate file to enable an ssh-free variant.
+ */
+ backvt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol));
+ if (!backvt) {
+ fprintf(stderr,
+ "Internal fault: Unsupported protocol found\n");
+ return 1;
+ }
+
+ if (backvt->flags & BACKEND_NEEDS_TERMINAL) {
+ fprintf(stderr,
+ "Plink doesn't support %s, which needs terminal emulation\n",
+ backvt->displayname_lc);
+ return 1;
+ }
+
+ /*
+ * Block SIGPIPE, so that we'll get EPIPE individually on
+ * particular network connections that go wrong.
+ */
+ putty_signal(SIGPIPE, SIG_IGN);
+
+ /*
+ * Set up the pipe we'll use to tell us about SIGWINCH.
+ */
+ if (pipe(signalpipe) < 0) {
+ perror("pipe");
+ exit(1);
+ }
+ /* We don't want the signal handler to block if the pipe's full. */
+ nonblock(signalpipe[0]);
+ nonblock(signalpipe[1]);
+ cloexec(signalpipe[0]);
+ cloexec(signalpipe[1]);
+ putty_signal(SIGWINCH, sigwinch);
+
+ /*
+ * Now that we've got the SIGWINCH handler installed, try to find
+ * out the initial terminal size.
+ */
+ if (ioctl(STDIN_FILENO, TIOCGWINSZ, &size) >= 0) {
+ conf_set_int(conf, CONF_width, size.ws_col);
+ conf_set_int(conf, CONF_height, size.ws_row);
+ }
+
+ /*
+ * Decide whether to sanitise control sequences out of standard
+ * output and standard error.
+ *
+ * If we weren't given a command-line override, we do this if (a)
+ * the fd in question is pointing at a terminal, and (b) we aren't
+ * trying to allocate a terminal as part of the session.
+ *
+ * (Rationale: the risk of control sequences is that they cause
+ * confusion when sent to a local terminal, so if there isn't one,
+ * no problem. Also, if we allocate a remote terminal, then we
+ * sent a terminal type, i.e. we told it what kind of escape
+ * sequences we _like_, i.e. we were expecting to receive some.)
+ */
+ if (sanitise_stdout == FORCE_ON ||
+ (sanitise_stdout == AUTO && isatty(STDOUT_FILENO) &&
+ conf_get_bool(conf, CONF_nopty))) {
+ stdout_scc = stripctrl_new(stdout_bs, true, L'\0');
+ stdout_bs = BinarySink_UPCAST(stdout_scc);
+ }
+ if (sanitise_stderr == FORCE_ON ||
+ (sanitise_stderr == AUTO && isatty(STDERR_FILENO) &&
+ conf_get_bool(conf, CONF_nopty))) {
+ stderr_scc = stripctrl_new(stderr_bs, true, L'\0');
+ stderr_bs = BinarySink_UPCAST(stderr_scc);
+ }
+
+ sk_init();
+ uxsel_init();
+
+ /*
+ * Plink doesn't provide any way to add forwardings after the
+ * connection is set up, so if there are none now, we can safely set
+ * the "simple" flag.
+ */
+ if (conf_get_int(conf, CONF_protocol) == PROT_SSH &&
+ !conf_get_bool(conf, CONF_x11_forward) &&
+ !conf_get_bool(conf, CONF_agentfwd) &&
+ !conf_get_str_nthstrkey(conf, CONF_portfwd, 0))
+ conf_set_bool(conf, CONF_ssh_simple, true);
+
+ if (just_test_share_exists) {
+ if (!backvt->test_for_upstream) {
+ fprintf(stderr, "Connection sharing not supported for this "
+ "connection type (%s)'\n", backvt->displayname_lc);
+ return 1;
+ }
+ if (backvt->test_for_upstream(conf_get_str(conf, CONF_host),
+ conf_get_int(conf, CONF_port), conf))
+ return 0;
+ else
+ return 1;
+ }
+
+ /*
+ * Start up the connection.
+ */
+ logctx = log_init(console_cli_logpolicy, conf);
+ {
+ char *error, *realhost;
+ /* nodelay is only useful if stdin is a terminal device */
+ bool nodelay = conf_get_bool(conf, CONF_tcp_nodelay) && isatty(0);
+
+ /* This is a good place for a fuzzer to fork us. */
+#ifdef __AFL_HAVE_MANUAL_CONTROL
+ __AFL_INIT();
+#endif
+
+ error = backend_init(backvt, plink_seat, &backend, logctx, conf,
+ conf_get_str(conf, CONF_host),
+ conf_get_int(conf, CONF_port),
+ &realhost, nodelay,
+ conf_get_bool(conf, CONF_tcp_keepalives));
+ if (error) {
+ fprintf(stderr, "Unable to open connection:\n%s\n", error);
+ sfree(error);
+ return 1;
+ }
+ ldisc_create(conf, NULL, backend, plink_seat);
+ sfree(realhost);
+ }
+
+ /*
+ * Set up the initial console mode. We don't care if this call
+ * fails, because we know we aren't necessarily running in a
+ * console.
+ */
+ local_tty = (tcgetattr(STDIN_FILENO, &orig_termios) == 0);
+ atexit(cleanup_termios);
+ seat_echoedit_update(plink_seat, 1, 1);
+
+ cli_main_loop(plink_pw_setup, plink_pw_check, plink_continue, NULL);
+
+ exitcode = backend_exitcode(backend);
+ if (exitcode < 0) {
+ fprintf(stderr, "Remote process exit code unavailable\n");
+ exitcode = 1; /* this is an error condition */
+ }
+ cleanup_exit(exitcode);
+ return exitcode; /* shouldn't happen, but placates gcc */
+}
diff --git a/unix/printing.c b/unix/printing.c
new file mode 100644
index 00000000..416db396
--- /dev/null
+++ b/unix/printing.c
@@ -0,0 +1,58 @@
+/*
+ * Printing interface for PuTTY.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include "putty.h"
+
+struct printer_job_tag {
+ FILE *fp;
+};
+
+printer_job *printer_start_job(char *printer)
+{
+ printer_job *ret = snew(printer_job);
+ /*
+ * On Unix, we treat the printer string as the name of a
+ * command to pipe to - typically lpr, of course.
+ */
+ ret->fp = popen(printer, "w");
+ if (!ret->fp) {
+ sfree(ret);
+ ret = NULL;
+ }
+ return ret;
+}
+
+void printer_job_data(printer_job *pj, const void *data, size_t len)
+{
+ if (!pj)
+ return;
+
+ if (fwrite(data, 1, len, pj->fp) < len)
+ /* ignore */;
+}
+
+void printer_finish_job(printer_job *pj)
+{
+ if (!pj)
+ return;
+
+ pclose(pj->fp);
+ sfree(pj);
+}
+
+/*
+ * There's no sensible way to enumerate printers under Unix, since
+ * practically any valid Unix command is a valid printer :-) So
+ * these are useless stub functions, and config-unix.c will disable
+ * the drop-down list in the printer configurer.
+ */
+printer_enum *printer_start_enum(int *nprinters_ptr) {
+ *nprinters_ptr = 0;
+ return NULL;
+}
+char *printer_get_name(printer_enum *pe, int i) { return NULL;
+}
+void printer_finish_enum(printer_enum *pe) { }
diff --git a/unix/procnet.c b/unix/procnet.c
index 43b1f055..09373956 100644
--- a/unix/procnet.c
+++ b/unix/procnet.c
@@ -170,8 +170,8 @@ static char *format_sockaddr(const void *addr, int family)
const uint32_t *addrwords = (const uint32_t *)a->sin6_addr.s6_addr;
for (int i = 0; i < 4; i++)
- strbuf_catf(sb, "%08X", addrwords[i]);
- strbuf_catf(sb, ":%04X", ntohs(a->sin6_port));
+ put_fmt(sb, "%08X", addrwords[i]);
+ put_fmt(sb, ":%04X", ntohs(a->sin6_port));
return strbuf_to_str(sb);
} else {
diff --git a/unix/psocks.c b/unix/psocks.c
new file mode 100644
index 00000000..748790b8
--- /dev/null
+++ b/unix/psocks.c
@@ -0,0 +1,176 @@
+/*
+ * Main program for Unix psocks.
+ */
+
+#include <string.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "psocks.h"
+
+typedef struct PsocksDataSinkPopen {
+ stdio_sink sink[2];
+ PsocksDataSink pds;
+} PsocksDataSinkPopen;
+
+static void popen_free(PsocksDataSink *pds)
+{
+ PsocksDataSinkPopen *pdsp = container_of(pds, PsocksDataSinkPopen, pds);
+ for (size_t i = 0; i < 2; i++)
+ pclose(pdsp->sink[i].fp);
+ sfree(pdsp);
+}
+
+static PsocksDataSink *open_pipes(
+ const char *cmd, const char *const *direction_args,
+ const char *index_arg, char **err)
+{
+ FILE *fp[2];
+ char *errmsg = NULL;
+
+ for (size_t i = 0; i < 2; i++) {
+ /* No escaping needed: the provided command is already
+ * shell-quoted, and our extra arguments are simple */
+ char *command = dupprintf("%s %s %s", cmd,
+ direction_args[i], index_arg);
+
+ fp[i] = popen(command, "w");
+ sfree(command);
+
+ if (!fp[i]) {
+ if (!errmsg)
+ errmsg = dupprintf("%s", strerror(errno));
+ }
+ }
+
+ if (errmsg) {
+ for (size_t i = 0; i < 2; i++)
+ if (fp[i])
+ pclose(fp[i]);
+ *err = errmsg;
+ return NULL;
+ }
+
+ PsocksDataSinkPopen *pdsp = snew(PsocksDataSinkPopen);
+
+ for (size_t i = 0; i < 2; i++) {
+ setvbuf(fp[i], NULL, _IONBF, 0);
+ stdio_sink_init(&pdsp->sink[i], fp[i]);
+ pdsp->pds.s[i] = BinarySink_UPCAST(&pdsp->sink[i]);
+ }
+
+ pdsp->pds.free = popen_free;
+
+ return &pdsp->pds;
+}
+
+static int signalpipe[2] = { -1, -1 };
+static void sigchld(int signum)
+{
+ if (write(signalpipe[1], "x", 1) <= 0)
+ /* not much we can do about it */;
+}
+
+static pid_t subcommand_pid = -1;
+
+static bool still_running = true;
+
+static void start_subcommand(strbuf *args)
+{
+ pid_t pid;
+
+ /*
+ * Set up the pipe we'll use to tell us about SIGCHLD.
+ */
+ if (pipe(signalpipe) < 0) {
+ perror("pipe");
+ exit(1);
+ }
+ putty_signal(SIGCHLD, sigchld);
+
+ /*
+ * Make an array of argument pointers that execvp will like.
+ */
+ size_t nargs = 0;
+ for (size_t i = 0; i < args->len; i++)
+ if (args->s[i] == '\0')
+ nargs++;
+
+ char **exec_args = snewn(nargs + 1, char *);
+ char *p = args->s;
+ for (size_t a = 0; a < nargs; a++) {
+ exec_args[a] = p;
+ size_t len = strlen(p);
+ assert(len < args->len - (p - args->s));
+ p += 1 + len;
+ }
+ exec_args[nargs] = NULL;
+
+ pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ exit(1);
+ } else if (pid == 0) {
+ execvp(exec_args[0], exec_args);
+ perror("exec");
+ _exit(127);
+ } else {
+ subcommand_pid = pid;
+ sfree(exec_args);
+ }
+}
+
+static const PsocksPlatform platform = {
+ open_pipes,
+ start_subcommand,
+};
+
+static bool psocks_pw_setup(void *ctx, pollwrapper *pw)
+{
+ if (signalpipe[0] >= 0)
+ pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R);
+ return true;
+}
+
+static void psocks_pw_check(void *ctx, pollwrapper *pw)
+{
+ if (signalpipe[0] >= 0 &&
+ pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) {
+ while (true) {
+ int status;
+ pid_t pid = waitpid(-1, &status, WNOHANG);
+ if (pid <= 0)
+ break;
+ if (pid == subcommand_pid)
+ still_running = false;
+ }
+ }
+}
+
+static bool psocks_continue(void *ctx, bool found_any_fd,
+ bool ran_any_callback)
+{
+ return still_running;
+}
+
+typedef bool (*cliloop_continue_t)(void *ctx, bool found_any_fd,
+ bool ran_any_callback);
+
+int main(int argc, char **argv)
+{
+ psocks_state *ps = psocks_new(&platform);
+ psocks_cmdline(ps, argc, argv);
+
+ sk_init();
+ uxsel_init();
+ psocks_start(ps);
+
+ cli_main_loop(psocks_pw_setup, psocks_pw_check, psocks_continue, NULL);
+}
diff --git a/unix/psusan.c b/unix/psusan.c
new file mode 100644
index 00000000..1d5816d5
--- /dev/null
+++ b/unix/psusan.c
@@ -0,0 +1,421 @@
+/*
+ * 'psusan': Pseudo Ssh for Untappable, Separately Authenticated Networks
+ *
+ * This is a standalone application that speaks on its standard I/O
+ * (or a listening Unix-domain socket) the server end of the bare
+ * ssh-connection protocol used by PuTTY's connection sharing.
+ *
+ * The idea of this tool is that you can use it to communicate across
+ * any 8-bit-clean data channel between two inconveniently separated
+ * domains, provided the channel is already (as the name suggests)
+ * adequately secured against eavesdropping and modification and
+ * already authenticated as the right user.
+ *
+ * If you're sitting at one end of such a channel and want to type
+ * commands into the other end, the most obvious thing to do is to run
+ * a terminal session directly over it. But if you run psusan at one
+ * end, and a PuTTY (or compatible) client at the other end, then you
+ * not only get a single terminal session: you get all the other SSH
+ * amenities, like the ability to spawn extra terminal sessions,
+ * forward ports or X11 connections, even forward an SSH agent.
+ *
+ * There are a surprising number of channels of that kind; see the man
+ * page for some examples.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <pwd.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+
+#include "putty.h"
+#include "mpint.h"
+#include "ssh.h"
+#include "ssh/server.h"
+
+void modalfatalbox(const char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "FATAL ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ exit(1);
+}
+void nonfatal(const char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+}
+
+char *platform_default_s(const char *name)
+{
+ return NULL;
+}
+
+bool platform_default_b(const char *name, bool def)
+{
+ return def;
+}
+
+int platform_default_i(const char *name, int def)
+{
+ return def;
+}
+
+FontSpec *platform_default_fontspec(const char *name)
+{
+ return fontspec_new("");
+}
+
+Filename *platform_default_filename(const char *name)
+{
+ return filename_from_str("");
+}
+
+char *x_get_default(const char *key)
+{
+ return NULL; /* this is a stub */
+}
+
+void old_keyfile_warning(void) { }
+
+void timer_change_notify(unsigned long next)
+{
+}
+
+char *platform_get_x_display(void) { return NULL; }
+
+void make_unix_sftp_filehandle_key(void *vdata, size_t size)
+{
+ /* psusan runs without a random number generator, so we can't make
+ * this up by random_read. Fortunately, psusan is also
+ * non-adversarial, so it's safe to generate this trivially. */
+ unsigned char *data = (unsigned char *)vdata;
+ for (size_t i = 0; i < size; i++)
+ data[i] = (unsigned)rand() / ((unsigned)RAND_MAX / 256);
+}
+
+static bool verbose;
+
+struct server_instance {
+ unsigned id;
+ LogPolicy logpolicy;
+};
+
+static void log_to_stderr(unsigned id, const char *msg)
+{
+ if (!verbose)
+ return;
+ if (id != (unsigned)-1)
+ fprintf(stderr, "#%u: ", id);
+ fputs(msg, stderr);
+ fputc('\n', stderr);
+ fflush(stderr);
+}
+
+static void server_eventlog(LogPolicy *lp, const char *event)
+{
+ struct server_instance *inst = container_of(
+ lp, struct server_instance, logpolicy);
+ if (verbose)
+ log_to_stderr(inst->id, event);
+}
+
+static void server_logging_error(LogPolicy *lp, const char *event)
+{
+ struct server_instance *inst = container_of(
+ lp, struct server_instance, logpolicy);
+ log_to_stderr(inst->id, event); /* unconditional */
+}
+
+static int server_askappend(
+ LogPolicy *lp, Filename *filename,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ return 2; /* always overwrite (FIXME: could make this a cmdline option) */
+}
+
+static const LogPolicyVtable server_logpolicy_vt = {
+ .eventlog = server_eventlog,
+ .askappend = server_askappend,
+ .logging_error = server_logging_error,
+ .verbose = null_lp_verbose_no,
+};
+
+static void show_help(FILE *fp)
+{
+ fputs("usage: psusan [options]\n"
+ "options: --listen SOCKETPATH listen for connections on a Unix-domain socket\n"
+ " --listen-once (with --listen) stop after one connection\n"
+ " --verbose print log messages to standard error\n"
+ " --sessiondir DIR cwd for session subprocess (default $HOME)\n"
+ " --sshlog FILE write ssh-connection packet log to FILE\n"
+ " --sshrawlog FILE write packets and raw data log to FILE\n"
+ "also: psusan --help show this text\n"
+ " psusan --version show version information\n", fp);
+}
+
+static void show_version_and_exit(void)
+{
+ char *buildinfo_text = buildinfo("\n");
+ printf("%s: %s\n%s\n", appname, ver, buildinfo_text);
+ sfree(buildinfo_text);
+ exit(0);
+}
+
+const bool buildinfo_gtk_relevant = false;
+
+static bool listening = false, listen_once = false;
+static bool finished = false;
+void server_instance_terminated(LogPolicy *lp)
+{
+ struct server_instance *inst = container_of(
+ lp, struct server_instance, logpolicy);
+
+ if (listening && !listen_once) {
+ log_to_stderr(inst->id, "connection terminated");
+ } else {
+ finished = true;
+ }
+
+ sfree(inst);
+}
+
+bool psusan_continue(void *ctx, bool fd, bool cb)
+{
+ return !finished;
+}
+
+static bool longoptarg(const char *arg, const char *expected,
+ const char **val, int *argcp, char ***argvp)
+{
+ int len = strlen(expected);
+ if (memcmp(arg, expected, len))
+ return false;
+ if (arg[len] == '=') {
+ *val = arg + len + 1;
+ return true;
+ } else if (arg[len] == '\0') {
+ if (--*argcp > 0) {
+ *val = *++*argvp;
+ return true;
+ } else {
+ fprintf(stderr, "%s: option %s expects an argument\n",
+ appname, expected);
+ exit(1);
+ }
+ }
+ return false;
+}
+
+static bool longoptnoarg(const char *arg, const char *expected)
+{
+ int len = strlen(expected);
+ if (memcmp(arg, expected, len))
+ return false;
+ if (arg[len] == '=') {
+ fprintf(stderr, "%s: option %s expects no argument\n",
+ appname, expected);
+ exit(1);
+ } else if (arg[len] == '\0') {
+ return true;
+ }
+ return false;
+}
+
+struct server_config {
+ Conf *conf;
+ const SshServerConfig *ssc;
+
+ unsigned next_id;
+
+ Socket *listening_socket;
+ Plug listening_plug;
+};
+
+static Plug *server_conn_plug(
+ struct server_config *cfg, struct server_instance **inst_out)
+{
+ struct server_instance *inst = snew(struct server_instance);
+
+ memset(inst, 0, sizeof(*inst));
+
+ inst->id = cfg->next_id++;
+ inst->logpolicy.vt = &server_logpolicy_vt;
+
+ if (inst_out)
+ *inst_out = inst;
+
+ return ssh_server_plug(
+ cfg->conf, cfg->ssc, NULL, 0, NULL, NULL,
+ &inst->logpolicy, &unix_live_sftpserver_vt);
+}
+
+static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
+ const char *error_msg, int error_code)
+{
+ log_to_stderr(-1, error_msg);
+}
+
+static void server_closing(Plug *plug, PlugCloseType type,
+ const char *error_msg)
+{
+ if (type != PLUGCLOSE_NORMAL)
+ log_to_stderr(-1, error_msg);
+}
+
+static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
+{
+ struct server_config *cfg = container_of(
+ p, struct server_config, listening_plug);
+ Socket *s;
+ const char *err;
+
+ struct server_instance *inst;
+
+ if (listen_once) {
+ if (!cfg->listening_socket) /* in case of rapid double-accept */
+ return 1;
+ sk_close(cfg->listening_socket);
+ cfg->listening_socket = NULL;
+ }
+
+ Plug *plug = server_conn_plug(cfg, &inst);
+ s = constructor(ctx, plug);
+ if ((err = sk_socket_error(s)) != NULL)
+ return 1;
+
+ SocketPeerInfo *pi = sk_peer_info(s);
+
+ char *msg = dupprintf("new connection from %s", pi->log_text);
+ log_to_stderr(inst->id, msg);
+ sfree(msg);
+ sk_free_peer_info(pi);
+
+ sk_set_frozen(s, false);
+ ssh_server_start(plug, s);
+ return 0;
+}
+
+static const PlugVtable server_plugvt = {
+ .log = server_log,
+ .closing = server_closing,
+ .accepting = server_accepting,
+};
+
+unsigned auth_methods(AuthPolicy *ap)
+{ return 0; }
+bool auth_none(AuthPolicy *ap, ptrlen username)
+{ return false; }
+int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password,
+ ptrlen *new_password_opt)
+{ return 0; }
+bool auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob)
+{ return false; }
+RSAKey *auth_publickey_ssh1(
+ AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus)
+{ return NULL; }
+AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username)
+{ return NULL; }
+int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses)
+{ return -1; }
+char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username)
+{ return NULL; }
+bool auth_ssh1int_response(AuthPolicy *ap, ptrlen response)
+{ return false; }
+bool auth_successful(AuthPolicy *ap, ptrlen username, unsigned method)
+{ return false; }
+
+int main(int argc, char **argv)
+{
+ const char *listen_socket = NULL;
+
+ SshServerConfig ssc;
+
+ Conf *conf = make_ssh_server_conf();
+
+ memset(&ssc, 0, sizeof(ssc));
+
+ ssc.application_name = "PSUSAN";
+ ssc.session_starting_dir = getenv("HOME");
+ ssc.bare_connection = true;
+
+ while (--argc > 0) {
+ const char *arg = *++argv;
+ const char *val;
+
+ if (longoptnoarg(arg, "--help")) {
+ show_help(stdout);
+ exit(0);
+ } else if (longoptnoarg(arg, "--version")) {
+ show_version_and_exit();
+ } else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) {
+ verbose = true;
+ } else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) {
+ ssc.session_starting_dir = val;
+ } else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) ||
+ longoptarg(arg, "-sshlog", &val, &argc, &argv)) {
+ Filename *logfile = filename_from_str(val);
+ conf_set_filename(conf, CONF_logfilename, logfile);
+ filename_free(logfile);
+ conf_set_int(conf, CONF_logtype, LGTYP_PACKETS);
+ conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
+ } else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) ||
+ longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) {
+ Filename *logfile = filename_from_str(val);
+ conf_set_filename(conf, CONF_logfilename, logfile);
+ filename_free(logfile);
+ conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW);
+ conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
+ } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) {
+ listen_socket = val;
+ } else if (!strcmp(arg, "--listen-once")) {
+ listen_once = true;
+ } else {
+ fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg);
+ exit(1);
+ }
+ }
+
+ sk_init();
+ uxsel_init();
+
+ struct server_config scfg;
+ scfg.conf = conf;
+ scfg.ssc = &ssc;
+ scfg.next_id = 0;
+
+ if (listen_socket) {
+ listening = true;
+ scfg.listening_plug.vt = &server_plugvt;
+ SockAddr *addr = unix_sock_addr(listen_socket);
+ scfg.listening_socket = new_unix_listener(addr, &scfg.listening_plug);
+ char *msg = dupprintf("listening on Unix socket %s", listen_socket);
+ log_to_stderr(-1, msg);
+ sfree(msg);
+ } else {
+ struct server_instance *inst;
+ Plug *plug = server_conn_plug(&scfg, &inst);
+ ssh_server_start(plug, make_fd_socket(0, 1, -1, NULL, 0, plug));
+ log_to_stderr(inst->id, "running directly on stdio");
+ }
+
+ cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check,
+ psusan_continue, NULL);
+
+ return 0;
+}
diff --git a/UNIX/XPMPTCFG.C b/unix/pterm-config-xpm.c
index 92835c15..92835c15 100644
--- a/UNIX/XPMPTCFG.C
+++ b/unix/pterm-config-xpm.c
diff --git a/UNIX/XPMPTERM.C b/unix/pterm-xpm.c
index aea5e4e2..aea5e4e2 100644
--- a/UNIX/XPMPTERM.C
+++ b/unix/pterm-xpm.c
diff --git a/unix/pterm.c b/unix/pterm.c
new file mode 100644
index 00000000..87dd3e99
--- /dev/null
+++ b/unix/pterm.c
@@ -0,0 +1,49 @@
+/*
+ * pterm main program.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+
+const bool use_event_log = false; /* pterm doesn't need it */
+const bool new_session = false, saved_sessions = false; /* or these */
+const bool dup_check_launchable = false; /* no need to check host name
+ * in conf */
+const bool use_pty_argv = true;
+
+const unsigned cmdline_tooltype = TOOLTYPE_NONNETWORK;
+
+/* window.c will call this, and in pterm it's not needed */
+void noise_ultralight(NoiseSourceId id, unsigned long data) { }
+
+const struct BackendVtable *select_backend(Conf *conf)
+{
+ return &pty_backend;
+}
+
+void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx)
+{
+ /*
+ * This is a no-op in pterm, except that we'll ensure the protocol
+ * is set to -1 to inhibit the useless Connection panel in the
+ * config box. So we do that and then just immediately call the
+ * post-dialog function with a positive result.
+ */
+ conf_set_int(conf, CONF_protocol, -1);
+ after(afterctx, 1);
+}
+
+void cleanup_exit(int code)
+{
+ exit(code);
+}
+
+void setup(bool single)
+{
+ settings_set_default_protocol(-1);
+
+ if (single)
+ pty_pre_init();
+}
diff --git a/unix/pty.c b/unix/pty.c
new file mode 100644
index 00000000..05c58929
--- /dev/null
+++ b/unix/pty.c
@@ -0,0 +1,1615 @@
+/*
+ * Pseudo-tty backend for pterm.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <grp.h>
+#if HAVE_UTMP_H
+#include <utmp.h>
+#endif
+#include <pwd.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#include <termios.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "ssh/server.h" /* to check the prototypes of server-needed things */
+#include "tree234.h"
+
+#ifndef OMIT_UTMP
+#include <utmpx.h>
+#endif
+
+/* updwtmpx() needs the name of the wtmp file. Try to find it. */
+#ifndef WTMPX_FILE
+#ifdef _PATH_WTMPX
+#define WTMPX_FILE _PATH_WTMPX
+#else
+#define WTMPX_FILE "/var/log/wtmpx"
+#endif
+#endif
+
+#ifndef LASTLOG_FILE
+#ifdef _PATH_LASTLOG
+#define LASTLOG_FILE _PATH_LASTLOG
+#else
+#define LASTLOG_FILE "/var/log/lastlog"
+#endif
+#endif
+
+typedef struct Pty Pty;
+
+/*
+ * The pty_signal_pipe, along with the SIGCHLD handler, must be
+ * process-global rather than session-specific.
+ */
+static int pty_signal_pipe[2] = { -1, -1 }; /* obviously bogus initial val */
+
+typedef struct PtyFd {
+ int fd;
+ Pty *pty;
+} PtyFd;
+
+struct Pty {
+ Conf *conf;
+
+ int master_fd, slave_fd;
+ int pipefds[6];
+ PtyFd fds[3];
+ int master_i, master_o, master_e;
+
+ Seat *seat;
+ size_t output_backlog;
+ char name[FILENAME_MAX];
+ pid_t child_pid;
+ int term_width, term_height;
+ bool child_dead, finished;
+ int exit_code;
+ bufchain output_data;
+ bool pending_eof;
+ Backend backend;
+};
+
+#define PTY_MAX_BACKLOG 32768
+
+/*
+ * We store all the (active) PtyFd structures in a tree sorted by fd,
+ * so that when we get an uxsel notification we know which backend
+ * instance is the owner of the pty that caused it, and then we can
+ * find out which fd is the relevant one too.
+ */
+static int ptyfd_compare(void *av, void *bv)
+{
+ PtyFd *a = (PtyFd *)av;
+ PtyFd *b = (PtyFd *)bv;
+
+ if (a->fd < b->fd)
+ return -1;
+ else if (a->fd > b->fd)
+ return +1;
+ return 0;
+}
+
+static int ptyfd_find(void *av, void *bv)
+{
+ int a = *(int *)av;
+ PtyFd *b = (PtyFd *)bv;
+
+ if (a < b->fd)
+ return -1;
+ else if (a > b->fd)
+ return +1;
+ return 0;
+}
+
+static tree234 *ptyfds = NULL;
+
+/*
+ * We also have a tree of Pty structures themselves, sorted by child
+ * pid, so that when we wait() in response to the signal we know which
+ * backend instance is the owner of the process that caused the
+ * signal.
+ */
+static int pty_compare_by_pid(void *av, void *bv)
+{
+ Pty *a = (Pty *)av;
+ Pty *b = (Pty *)bv;
+
+ if (a->child_pid < b->child_pid)
+ return -1;
+ else if (a->child_pid > b->child_pid)
+ return +1;
+ return 0;
+}
+
+static int pty_find_by_pid(void *av, void *bv)
+{
+ pid_t a = *(pid_t *)av;
+ Pty *b = (Pty *)bv;
+
+ if (a < b->child_pid)
+ return -1;
+ else if (a > b->child_pid)
+ return +1;
+ return 0;
+}
+
+static tree234 *ptys_by_pid = NULL;
+
+/*
+ * If we are using pty_pre_init(), it will need to have already
+ * allocated a pty structure, which we must then return from
+ * pty_init() rather than allocating a new one. Here we store that
+ * structure between allocation and use.
+ *
+ * Note that although most of this module is entirely capable of
+ * handling multiple ptys in a single process, pty_pre_init() is
+ * fundamentally _dependent_ on there being at most one pty per
+ * process, so the normal static-data constraints don't apply.
+ *
+ * Likewise, since utmp is only used via pty_pre_init, it too must
+ * be single-instance, so we can declare utmp-related variables
+ * here.
+ */
+static Pty *single_pty = NULL;
+
+#ifndef OMIT_UTMP
+static pid_t pty_utmp_helper_pid = -1;
+static int pty_utmp_helper_pipe = -1;
+static bool pty_stamped_utmp;
+static struct utmpx utmp_entry;
+#endif
+
+/*
+ * pty_argv is a grievous hack to allow a proper argv to be passed
+ * through from the Unix command line. Again, it doesn't really
+ * make sense outside a one-pty-per-process setup.
+ */
+char **pty_argv;
+
+char *pty_osx_envrestore_prefix;
+
+static void pty_close(Pty *pty);
+static void pty_try_write(Pty *pty);
+
+#ifndef OMIT_UTMP
+static void setup_utmp(char *ttyname, char *location)
+{
+#if HAVE_LASTLOG
+ struct lastlog lastlog_entry;
+ FILE *lastlog;
+#endif
+ struct passwd *pw;
+ struct timeval tv;
+
+ pw = getpwuid(getuid());
+ if (!pw)
+ return; /* can't stamp utmp if we don't have a username */
+ memset(&utmp_entry, 0, sizeof(utmp_entry));
+ utmp_entry.ut_type = USER_PROCESS;
+ utmp_entry.ut_pid = getpid();
+#if __GNUC__ >= 8
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wstringop-truncation"
+#endif // __GNUC__ >= 8
+ strncpy(utmp_entry.ut_line, ttyname+5, lenof(utmp_entry.ut_line));
+ strncpy(utmp_entry.ut_id, ttyname+8, lenof(utmp_entry.ut_id));
+ strncpy(utmp_entry.ut_user, pw->pw_name, lenof(utmp_entry.ut_user));
+ strncpy(utmp_entry.ut_host, location, lenof(utmp_entry.ut_host));
+#if __GNUC__ >= 8
+# pragma GCC diagnostic pop
+#endif // __GNUC__ >= 8
+ /*
+ * Apparently there are some architectures where (struct
+ * utmpx).ut_tv is not essentially struct timeval (e.g. Linux
+ * amd64). Hence the temporary.
+ */
+ gettimeofday(&tv, NULL);
+ utmp_entry.ut_tv.tv_sec = tv.tv_sec;
+ utmp_entry.ut_tv.tv_usec = tv.tv_usec;
+
+ setutxent();
+ pututxline(&utmp_entry);
+ endutxent();
+
+#if HAVE_UPDWTMPX
+ /* Reportedly, AIX 5.1 has <utmpx.h> and pututxline(), but no
+ * updwtmpx(). */
+ updwtmpx(WTMPX_FILE, &utmp_entry);
+#endif
+
+#if HAVE_LASTLOG
+ memset(&lastlog_entry, 0, sizeof(lastlog_entry));
+ strncpy(lastlog_entry.ll_line, ttyname+5, lenof(lastlog_entry.ll_line));
+ strncpy(lastlog_entry.ll_host, location, lenof(lastlog_entry.ll_host));
+ time(&lastlog_entry.ll_time);
+ if ((lastlog = fopen(LASTLOG_FILE, "r+")) != NULL) {
+ fseek(lastlog, sizeof(lastlog_entry) * getuid(), SEEK_SET);
+ fwrite(&lastlog_entry, 1, sizeof(lastlog_entry), lastlog);
+ fclose(lastlog);
+ }
+#endif
+
+ pty_stamped_utmp = true;
+
+}
+
+static void cleanup_utmp(void)
+{
+ struct timeval tv;
+
+ if (!pty_stamped_utmp)
+ return;
+
+ utmp_entry.ut_type = DEAD_PROCESS;
+ memset(utmp_entry.ut_user, 0, lenof(utmp_entry.ut_user));
+ gettimeofday(&tv, NULL);
+ utmp_entry.ut_tv.tv_sec = tv.tv_sec;
+ utmp_entry.ut_tv.tv_usec = tv.tv_usec;
+
+#if HAVE_UPDWTMPX
+ updwtmpx(WTMPX_FILE, &utmp_entry);
+#endif
+
+ memset(utmp_entry.ut_line, 0, lenof(utmp_entry.ut_line));
+ utmp_entry.ut_tv.tv_sec = 0;
+ utmp_entry.ut_tv.tv_usec = 0;
+
+ setutxent();
+ pututxline(&utmp_entry);
+ endutxent();
+
+ pty_stamped_utmp = false; /* ensure we never double-cleanup */
+}
+#endif
+
+static void sigchld_handler(int signum)
+{
+ if (write(pty_signal_pipe[1], "x", 1) <= 0)
+ /* not much we can do about it */;
+}
+
+static void pty_setup_sigchld_handler(void)
+{
+ static bool setup = false;
+ if (!setup) {
+ putty_signal(SIGCHLD, sigchld_handler);
+ setup = true;
+ }
+}
+
+#ifndef OMIT_UTMP
+static void fatal_sig_handler(int signum)
+{
+ putty_signal(signum, SIG_DFL);
+ cleanup_utmp();
+ raise(signum);
+}
+#endif
+
+static int pty_open_slave(Pty *pty)
+{
+ if (pty->slave_fd < 0) {
+ pty->slave_fd = open(pty->name, O_RDWR);
+ cloexec(pty->slave_fd);
+ }
+
+ return pty->slave_fd;
+}
+
+static void pty_open_master(Pty *pty)
+{
+#ifdef BSD_PTYS
+ const char chars1[] = "pqrstuvwxyz";
+ const char chars2[] = "0123456789abcdef";
+ const char *p1, *p2;
+ char master_name[20];
+ struct group *gp;
+
+ for (p1 = chars1; *p1; p1++)
+ for (p2 = chars2; *p2; p2++) {
+ sprintf(master_name, "/dev/pty%c%c", *p1, *p2);
+ pty->master_fd = open(master_name, O_RDWR);
+ if (pty->master_fd >= 0) {
+ if (geteuid() == 0 ||
+ access(master_name, R_OK | W_OK) == 0) {
+ /*
+ * We must also check at this point that we are
+ * able to open the slave side of the pty. We
+ * wouldn't want to allocate the wrong master,
+ * get all the way down to forking, and _then_
+ * find we're unable to open the slave.
+ */
+ strcpy(pty->name, master_name);
+ pty->name[5] = 't'; /* /dev/ptyXX -> /dev/ttyXX */
+
+ cloexec(pty->master_fd);
+
+ if (pty_open_slave(pty) >= 0 &&
+ access(pty->name, R_OK | W_OK) == 0)
+ goto got_one;
+ if (pty->slave_fd > 0)
+ close(pty->slave_fd);
+ pty->slave_fd = -1;
+ }
+ close(pty->master_fd);
+ }
+ }
+
+ /* If we get here, we couldn't get a tty at all. */
+ fprintf(stderr, "pterm: unable to open a pseudo-terminal device\n");
+ exit(1);
+
+ got_one:
+
+ /* We need to chown/chmod the /dev/ttyXX device. */
+ gp = getgrnam("tty");
+ chown(pty->name, getuid(), gp ? gp->gr_gid : -1);
+ chmod(pty->name, 0600);
+#else
+
+ const int flags = O_RDWR
+#ifdef O_NOCTTY
+ | O_NOCTTY
+#endif
+ ;
+
+#if HAVE_POSIX_OPENPT
+#ifdef SET_NONBLOCK_VIA_OPENPT
+ /*
+ * OS X, as of 10.10 at least, doesn't permit me to set O_NONBLOCK
+ * on pty master fds via the usual fcntl mechanism. Fortunately,
+ * it does let me work around this by adding O_NONBLOCK to the
+ * posix_openpt flags parameter, which isn't a documented use of
+ * the API but seems to work. So we'll do that for now.
+ */
+ pty->master_fd = posix_openpt(flags | O_NONBLOCK);
+#else
+ pty->master_fd = posix_openpt(flags);
+#endif
+
+ if (pty->master_fd < 0) {
+ perror("posix_openpt");
+ exit(1);
+ }
+#else
+ pty->master_fd = open("/dev/ptmx", flags);
+
+ if (pty->master_fd < 0) {
+ perror("/dev/ptmx: open");
+ exit(1);
+ }
+#endif
+
+ if (grantpt(pty->master_fd) < 0) {
+ perror("grantpt");
+ exit(1);
+ }
+
+ if (unlockpt(pty->master_fd) < 0) {
+ perror("unlockpt");
+ exit(1);
+ }
+
+ cloexec(pty->master_fd);
+
+ pty->name[FILENAME_MAX-1] = '\0';
+ strncpy(pty->name, ptsname(pty->master_fd), FILENAME_MAX-1);
+#endif
+
+#ifndef SET_NONBLOCK_VIA_OPENPT
+ nonblock(pty->master_fd);
+#endif
+}
+
+static Pty *new_pty_struct(void)
+{
+ Pty *pty = snew(Pty);
+ memset(pty, 0, sizeof(Pty));
+ pty->conf = NULL;
+ pty->pending_eof = false;
+ bufchain_init(&pty->output_data);
+ return pty;
+}
+
+/*
+ * Pre-initialisation. This is here to get around the fact that GTK
+ * doesn't like being run in setuid/setgid programs (probably
+ * sensibly). So before we initialise GTK - and therefore before we
+ * even process the command line - we check to see if we're running
+ * set[ug]id. If so, we open our pty master _now_, chown it as
+ * necessary, and drop privileges. We can always close it again
+ * later. If we're potentially going to be doing utmp as well, we
+ * also fork off a utmp helper process and communicate with it by
+ * means of a pipe; the utmp helper will keep privileges in order
+ * to clean up utmp when we exit (i.e. when its end of our pipe
+ * closes).
+ */
+void pty_pre_init(void)
+{
+#ifndef NO_PTY_PRE_INIT
+
+ Pty *pty;
+
+#ifndef OMIT_UTMP
+ pid_t pid;
+ int pipefd[2];
+#endif
+
+ pty = single_pty = new_pty_struct();
+
+ /* set the child signal handler straight away; it needs to be set
+ * before we ever fork. */
+ pty_setup_sigchld_handler();
+ pty->master_fd = pty->slave_fd = -1;
+#ifndef OMIT_UTMP
+ pty_stamped_utmp = false;
+#endif
+
+ if (geteuid() != getuid() || getegid() != getgid()) {
+ pty_open_master(pty);
+
+#ifndef OMIT_UTMP
+ /*
+ * Fork off the utmp helper.
+ */
+ if (pipe(pipefd) < 0) {
+ perror("pterm: pipe");
+ exit(1);
+ }
+ cloexec(pipefd[0]);
+ cloexec(pipefd[1]);
+ pid = fork();
+ if (pid < 0) {
+ perror("pterm: fork");
+ exit(1);
+ } else if (pid == 0) {
+ char display[128], buffer[128];
+ int dlen, ret;
+
+ close(pipefd[1]);
+ /*
+ * Now sit here until we receive a display name from the
+ * other end of the pipe, and then stamp utmp. Unstamp utmp
+ * again, and exit, when the pipe closes.
+ */
+
+ dlen = 0;
+ while (1) {
+
+ ret = read(pipefd[0], buffer, lenof(buffer));
+ if (ret <= 0) {
+ cleanup_utmp();
+ _exit(0);
+ } else if (!pty_stamped_utmp) {
+ if (dlen < lenof(display))
+ memcpy(display+dlen, buffer,
+ min(ret, lenof(display)-dlen));
+ if (buffer[ret-1] == '\0') {
+ /*
+ * Now we have a display name. NUL-terminate
+ * it, and stamp utmp.
+ */
+ display[lenof(display)-1] = '\0';
+ /*
+ * Trap as many fatal signals as we can in the
+ * hope of having the best possible chance to
+ * clean up utmp before termination. We are
+ * unfortunately unprotected against SIGKILL,
+ * but that's life.
+ */
+ putty_signal(SIGHUP, fatal_sig_handler);
+ putty_signal(SIGINT, fatal_sig_handler);
+ putty_signal(SIGQUIT, fatal_sig_handler);
+ putty_signal(SIGILL, fatal_sig_handler);
+ putty_signal(SIGABRT, fatal_sig_handler);
+ putty_signal(SIGFPE, fatal_sig_handler);
+ putty_signal(SIGPIPE, fatal_sig_handler);
+ putty_signal(SIGALRM, fatal_sig_handler);
+ putty_signal(SIGTERM, fatal_sig_handler);
+ putty_signal(SIGSEGV, fatal_sig_handler);
+ putty_signal(SIGUSR1, fatal_sig_handler);
+ putty_signal(SIGUSR2, fatal_sig_handler);
+#ifdef SIGBUS
+ putty_signal(SIGBUS, fatal_sig_handler);
+#endif
+#ifdef SIGPOLL
+ putty_signal(SIGPOLL, fatal_sig_handler);
+#endif
+#ifdef SIGPROF
+ putty_signal(SIGPROF, fatal_sig_handler);
+#endif
+#ifdef SIGSYS
+ putty_signal(SIGSYS, fatal_sig_handler);
+#endif
+#ifdef SIGTRAP
+ putty_signal(SIGTRAP, fatal_sig_handler);
+#endif
+#ifdef SIGVTALRM
+ putty_signal(SIGVTALRM, fatal_sig_handler);
+#endif
+#ifdef SIGXCPU
+ putty_signal(SIGXCPU, fatal_sig_handler);
+#endif
+#ifdef SIGXFSZ
+ putty_signal(SIGXFSZ, fatal_sig_handler);
+#endif
+#ifdef SIGIO
+ putty_signal(SIGIO, fatal_sig_handler);
+#endif
+ setup_utmp(pty->name, display);
+ }
+ }
+ }
+ } else {
+ close(pipefd[0]);
+ pty_utmp_helper_pid = pid;
+ pty_utmp_helper_pipe = pipefd[1];
+ }
+#endif
+ }
+
+ /* Drop privs. */
+ {
+#if HAVE_SETRESUID && HAVE_SETRESGID
+ int gid = getgid(), uid = getuid();
+ int setresgid(gid_t, gid_t, gid_t);
+ int setresuid(uid_t, uid_t, uid_t);
+ if (setresgid(gid, gid, gid) < 0) {
+ perror("setresgid");
+ exit(1);
+ }
+ if (setresuid(uid, uid, uid) < 0) {
+ perror("setresuid");
+ exit(1);
+ }
+#else
+ if (setgid(getgid()) < 0) {
+ perror("setgid");
+ exit(1);
+ }
+ if (setuid(getuid()) < 0) {
+ perror("setuid");
+ exit(1);
+ }
+#endif
+ }
+
+#endif /* NO_PTY_PRE_INIT */
+
+}
+
+static void pty_try_wait(void);
+static void pty_uxsel_setup(Pty *pty);
+
+static void pty_real_select_result(Pty *pty, int fd, int event, int status)
+{
+ char buf[4096];
+ int ret;
+ bool finished = false;
+
+ if (event < 0) {
+ /*
+ * We've been called because our child process did
+ * something. `status' tells us what.
+ */
+ if ((WIFEXITED(status) || WIFSIGNALED(status))) {
+ /*
+ * The primary child process died.
+ */
+ pty->child_dead = true;
+ del234(ptys_by_pid, pty);
+ pty->exit_code = status;
+
+ /*
+ * If this is an ordinary pty session, this is also the
+ * moment to terminate the whole backend.
+ *
+ * We _could_ instead keep the terminal open for remaining
+ * subprocesses to output to, but conventional wisdom
+ * seems to feel that that's the Wrong Thing for an
+ * xterm-alike, so we bail out now (though we don't
+ * necessarily _close_ the window, depending on the state
+ * of Close On Exit). This would be easy enough to change
+ * or make configurable if necessary.
+ */
+ if (pty->master_fd >= 0)
+ finished = true;
+ }
+ } else {
+ if (event == SELECT_R) {
+ bool is_stdout = (fd == pty->master_o);
+
+ ret = read(fd, buf, sizeof(buf));
+
+ /*
+ * Treat EIO on a pty master as equivalent to EOF (because
+ * that's how the kernel seems to report the event where
+ * the last process connected to the other end of the pty
+ * went away).
+ */
+ if (fd == pty->master_fd && ret < 0 && errno == EIO)
+ ret = 0;
+
+ if (ret == 0) {
+ /*
+ * EOF on this input fd, so to begin with, we may as
+ * well close it, and remove all references to it in
+ * the pty's fd fields.
+ */
+ uxsel_del(fd);
+ close(fd);
+ if (pty->master_fd == fd)
+ pty->master_fd = -1;
+ if (pty->master_o == fd)
+ pty->master_o = -1;
+ if (pty->master_e == fd)
+ pty->master_e = -1;
+
+ if (is_stdout) {
+ /*
+ * We assume a clean exit if the pty (or stdout
+ * pipe) has closed, but the actual child process
+ * hasn't. The only way I can imagine this
+ * happening is if it detaches itself from the pty
+ * and goes daemonic - in which case the expected
+ * usage model would precisely _not_ be for the
+ * pterm window to hang around!
+ */
+ finished = true;
+ pty_try_wait(); /* one last effort to collect exit code */
+ if (!pty->child_dead)
+ pty->exit_code = 0;
+ }
+ } else if (ret < 0) {
+ perror("read pty master");
+ exit(1);
+ } else if (ret > 0) {
+ pty->output_backlog = seat_output(
+ pty->seat, !is_stdout, buf, ret);
+ pty_uxsel_setup(pty);
+ }
+ } else if (event == SELECT_W) {
+ /*
+ * Attempt to send data down the pty.
+ */
+ pty_try_write(pty);
+ }
+ }
+
+ if (finished && !pty->finished) {
+ int close_on_exit;
+ int i;
+
+ for (i = 0; i < 3; i++)
+ if (pty->fds[i].fd >= 0)
+ uxsel_del(pty->fds[i].fd);
+
+ pty_close(pty);
+
+ pty->finished = true;
+
+ /*
+ * This is a slight layering-violation sort of hack: only
+ * if we're not closing on exit (COE is set to Never, or to
+ * Only On Clean and it wasn't a clean exit) do we output a
+ * `terminated' message.
+ */
+ close_on_exit = conf_get_int(pty->conf, CONF_close_on_exit);
+ if (close_on_exit == FORCE_OFF ||
+ (close_on_exit == AUTO && pty->exit_code != 0)) {
+ char *message;
+ if (WIFEXITED(pty->exit_code)) {
+ message = dupprintf(
+ "\r\n[pterm: process terminated with exit code %d]\r\n",
+ WEXITSTATUS(pty->exit_code));
+ } else if (WIFSIGNALED(pty->exit_code)) {
+#if !HAVE_STRSIGNAL
+ message = dupprintf(
+ "\r\n[pterm: process terminated on signal %d]\r\n",
+ WTERMSIG(pty->exit_code));
+#else
+ message = dupprintf(
+ "\r\n[pterm: process terminated on signal %d (%s)]\r\n",
+ WTERMSIG(pty->exit_code),
+ strsignal(WTERMSIG(pty->exit_code)));
+#endif
+ } else {
+ /* _Shouldn't_ happen, but if it does, a vague message
+ * is better than no message at all */
+ message = dupprintf("\r\n[pterm: process terminated]\r\n");
+ }
+ seat_stdout_pl(pty->seat, ptrlen_from_asciz(message));
+ sfree(message);
+ }
+
+ seat_eof(pty->seat);
+ seat_notify_remote_exit(pty->seat);
+ }
+}
+
+static void pty_try_wait(void)
+{
+ Pty *pty;
+ pid_t pid;
+ int status;
+
+ do {
+ pid = waitpid(-1, &status, WNOHANG);
+
+ pty = find234(ptys_by_pid, &pid, pty_find_by_pid);
+
+ if (pty)
+ pty_real_select_result(pty, -1, -1, status);
+ } while (pid > 0);
+}
+
+void pty_select_result(int fd, int event)
+{
+ if (fd == pty_signal_pipe[0]) {
+ char c[1];
+
+ if (read(pty_signal_pipe[0], c, 1) <= 0)
+ /* ignore error */;
+ /* ignore its value; it'll be `x' */
+
+ pty_try_wait();
+ } else {
+ PtyFd *ptyfd = find234(ptyfds, &fd, ptyfd_find);
+
+ if (ptyfd)
+ pty_real_select_result(ptyfd->pty, fd, event, 0);
+ }
+}
+
+static void pty_uxsel_setup_fd(Pty *pty, int fd)
+{
+ int rwx = 0;
+
+ if (fd < 0)
+ return;
+
+ /* read from standard output and standard error pipes, assuming
+ * we're not too backlogged */
+ if ((pty->master_o == fd || pty->master_e == fd) &&
+ pty->output_backlog < PTY_MAX_BACKLOG)
+ rwx |= SELECT_R;
+ /* write to standard input pipe if we have any data */
+ if (pty->master_i == fd && bufchain_size(&pty->output_data))
+ rwx |= SELECT_W;
+
+ uxsel_set(fd, rwx, pty_select_result);
+}
+
+static void pty_uxsel_setup(Pty *pty)
+{
+ /*
+ * We potentially have three separate fds here, but on the other
+ * hand, some of them might be the same (if they're a pty master).
+ * So we can't just call uxsel_set(master_o, SELECT_R) and then
+ * uxsel_set(master_i, SELECT_W), without the latter potentially
+ * undoing the work of the former if master_o == master_i.
+ *
+ * Instead, here we call a single uxsel on each one of these fds
+ * (if it exists at all), and for each one, check it against all
+ * three to see which bits to set.
+ */
+ pty_uxsel_setup_fd(pty, pty->master_o);
+ pty_uxsel_setup_fd(pty, pty->master_e);
+ pty_uxsel_setup_fd(pty, pty->master_i);
+
+ /*
+ * In principle this only needs calling once for all pty
+ * backend instances, but it's simplest just to call it every
+ * time; uxsel won't mind.
+ */
+ uxsel_set(pty_signal_pipe[0], SELECT_R, pty_select_result);
+}
+
+static void copy_ttymodes_into_termios(
+ struct termios *attrs, struct ssh_ttymodes modes)
+{
+#define TTYMODE_CHAR(name, ssh_opcode, cc_index) { \
+ if (modes.have_mode[ssh_opcode]) { \
+ unsigned value = modes.mode_val[ssh_opcode]; \
+ /* normalise wire value of 255 to local _POSIX_VDISABLE */ \
+ attrs->c_cc[cc_index] = (value == 255 ? \
+ _POSIX_VDISABLE : value); \
+ } \
+ }
+
+#define TTYMODE_FLAG(flagval, ssh_opcode, field, flagmask) { \
+ if (modes.have_mode[ssh_opcode]) { \
+ attrs->c_##field##flag &= ~flagmask; \
+ if (modes.mode_val[ssh_opcode]) \
+ attrs->c_##field##flag |= flagval; \
+ } \
+ }
+
+#define TTYMODES_LOCAL_ONLY /* omit any that this platform doesn't know */
+#include "ssh/ttymode-list.h"
+
+#undef TTYMODES_LOCAL_ONLY
+#undef TTYMODE_CHAR
+#undef TTYMODE_FLAG
+
+ if (modes.have_mode[TTYMODE_ISPEED])
+ cfsetispeed(attrs, modes.mode_val[TTYMODE_ISPEED]);
+ if (modes.have_mode[TTYMODE_OSPEED])
+ cfsetospeed(attrs, modes.mode_val[TTYMODE_OSPEED]);
+}
+
+/*
+ * The main setup function for the pty back end. This doesn't match
+ * the signature of backend_init(), partly because it has to be able
+ * to take extra arguments such as an argv array, and also because
+ * once we're changing the type signature _anyway_ we can discard the
+ * stuff that's not really applicable to this backend like host names
+ * and port numbers.
+ */
+Backend *pty_backend_create(
+ Seat *seat, LogContext *logctx, Conf *conf, char **argv, const char *cmd,
+ struct ssh_ttymodes ttymodes, bool pipes_instead, const char *dir,
+ const char *const *env_vars_to_unset)
+{
+ int slavefd;
+ pid_t pid, pgrp;
+#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */
+ bool got_windowid;
+ long windowid;
+#endif
+ Pty *pty;
+ int i;
+
+ /* No local authentication phase in this protocol */
+ seat_set_trust_status(seat, false);
+
+ if (single_pty) {
+ pty = single_pty;
+ assert(pty->conf == NULL);
+ } else {
+ pty = new_pty_struct();
+ pty->master_fd = pty->slave_fd = -1;
+#ifndef OMIT_UTMP
+ pty_stamped_utmp = false;
+#endif
+ }
+ for (i = 0; i < 6; i++)
+ pty->pipefds[i] = -1;
+ for (i = 0; i < 3; i++) {
+ pty->fds[i].fd = -1;
+ pty->fds[i].pty = pty;
+ }
+
+ if (pty_signal_pipe[0] < 0) {
+ if (pipe(pty_signal_pipe) < 0) {
+ perror("pipe");
+ exit(1);
+ }
+ cloexec(pty_signal_pipe[0]);
+ cloexec(pty_signal_pipe[1]);
+ }
+
+ pty->seat = seat;
+ pty->backend.vt = &pty_backend;
+
+ pty->conf = conf_copy(conf);
+ pty->term_width = conf_get_int(conf, CONF_width);
+ pty->term_height = conf_get_int(conf, CONF_height);
+
+ if (!ptyfds)
+ ptyfds = newtree234(ptyfd_compare);
+
+ if (pipes_instead) {
+ if (pty->master_fd >= 0) {
+ /* If somehow we've got a pty master already and don't
+ * need it, throw it away! */
+ close(pty->master_fd);
+#ifndef OMIT_UTMP
+ if (pty_utmp_helper_pipe >= 0) {
+ close(pty_utmp_helper_pipe); /* don't need this either */
+ pty_utmp_helper_pipe = -1;
+ }
+#endif
+ }
+
+
+ for (i = 0; i < 6; i += 2) {
+ if (pipe(pty->pipefds + i) < 0) {
+ backend_free(&pty->backend);
+ return NULL;
+ }
+ }
+
+ pty->fds[0].fd = pty->master_i = pty->pipefds[1];
+ pty->fds[1].fd = pty->master_o = pty->pipefds[2];
+ pty->fds[2].fd = pty->master_e = pty->pipefds[4];
+
+ add234(ptyfds, &pty->fds[0]);
+ add234(ptyfds, &pty->fds[1]);
+ add234(ptyfds, &pty->fds[2]);
+ } else {
+ if (pty->master_fd < 0)
+ pty_open_master(pty);
+
+#ifndef OMIT_UTMP
+ /*
+ * Stamp utmp (that is, tell the utmp helper process to do so),
+ * or not.
+ */
+ if (pty_utmp_helper_pipe >= 0) { /* if it's < 0, we can't anyway */
+ if (!conf_get_bool(conf, CONF_stamp_utmp)) {
+ /* We're not stamping utmp, so just let the child
+ * process die that was waiting to unstamp it later. */
+ close(pty_utmp_helper_pipe);
+ pty_utmp_helper_pipe = -1;
+ } else {
+ const char *location = seat_get_x_display(pty->seat);
+ int len = strlen(location)+1, pos = 0; /* +1 to include NUL */
+ while (pos < len) {
+ int ret = write(pty_utmp_helper_pipe,
+ location + pos, len - pos);
+ if (ret < 0) {
+ perror("pterm: writing to utmp helper process");
+ close(pty_utmp_helper_pipe); /* arrgh, just give up */
+ pty_utmp_helper_pipe = -1;
+ break;
+ }
+ pos += ret;
+ }
+ }
+ }
+#endif
+
+ pty->master_i = pty->master_fd;
+ pty->master_o = pty->master_fd;
+ pty->master_e = -1;
+
+ pty->fds[0].fd = pty->master_fd;
+ add234(ptyfds, &pty->fds[0]);
+ }
+
+#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */
+ got_windowid = seat_get_windowid(pty->seat, &windowid);
+#endif
+
+ /*
+ * Set up the signal handler to catch SIGCHLD, if pty_pre_init
+ * didn't already do it.
+ */
+ pty_setup_sigchld_handler();
+
+ /*
+ * Fork and execute the command.
+ */
+ pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ exit(1);
+ }
+
+ if (pid == 0) {
+ struct termios attrs;
+
+ /*
+ * We are the child.
+ */
+
+ if (pty_osx_envrestore_prefix) {
+ int plen = strlen(pty_osx_envrestore_prefix);
+ extern char **environ;
+ char **ep;
+
+ restart_osx_env_restore:
+ for (ep = environ; *ep; ep++) {
+ char *e = *ep;
+
+ if (!strncmp(e, pty_osx_envrestore_prefix, plen)) {
+ bool unset = (e[plen] == 'u');
+ char *pname = dupprintf("%.*s", (int)strcspn(e, "="), e);
+ char *name = pname + plen + 1;
+ char *value = e + strcspn(e, "=");
+ if (*value) value++;
+ value = dupstr(value);
+ if (unset)
+ unsetenv(name);
+ else
+ setenv(name, value, 1);
+ unsetenv(pname);
+ sfree(pname);
+ sfree(value);
+ goto restart_osx_env_restore;
+ }
+ }
+ }
+
+ pgrp = getpid();
+
+ if (pipes_instead) {
+ int i;
+ dup2(pty->pipefds[0], 0);
+ dup2(pty->pipefds[3], 1);
+ dup2(pty->pipefds[5], 2);
+ for (i = 0; i < 6; i++)
+ close(pty->pipefds[i]);
+
+ setsid();
+ } else {
+ slavefd = pty_open_slave(pty);
+ if (slavefd < 0) {
+ perror("slave pty: open");
+ _exit(1);
+ }
+
+ close(pty->master_fd);
+ noncloexec(slavefd);
+ dup2(slavefd, 0);
+ dup2(slavefd, 1);
+ dup2(slavefd, 2);
+ close(slavefd);
+ setsid();
+#ifdef TIOCSCTTY
+ ioctl(0, TIOCSCTTY, 1);
+#endif
+ tcsetpgrp(0, pgrp);
+
+ /*
+ * Set up configuration-dependent termios settings on the new
+ * pty. Linux would have let us do this on the pty master
+ * before we forked, but that fails on OS X, so we do it here
+ * instead.
+ */
+ if (tcgetattr(0, &attrs) == 0) {
+ /*
+ * Set the backspace character to be whichever of ^H and
+ * ^? is specified by bksp_is_delete.
+ */
+ attrs.c_cc[VERASE] = conf_get_bool(conf, CONF_bksp_is_delete)
+ ? '\177' : '\010';
+
+ /*
+ * Set the IUTF8 bit iff the character set is UTF-8.
+ */
+#ifdef IUTF8
+ if (seat_is_utf8(seat))
+ attrs.c_iflag |= IUTF8;
+ else
+ attrs.c_iflag &= ~IUTF8;
+#endif
+
+ copy_ttymodes_into_termios(&attrs, ttymodes);
+
+ tcsetattr(0, TCSANOW, &attrs);
+ }
+ }
+
+ setpgid(pgrp, pgrp);
+ if (!pipes_instead) {
+ int ptyfd = open(pty->name, O_WRONLY, 0);
+ if (ptyfd >= 0)
+ close(ptyfd);
+ }
+ setpgid(pgrp, pgrp);
+
+ if (env_vars_to_unset)
+ for (const char *const *p = env_vars_to_unset; *p; p++)
+ unsetenv(*p);
+
+ if (!pipes_instead) {
+ char *term_env_var = dupprintf("TERM=%s",
+ conf_get_str(conf, CONF_termtype));
+ putenv(term_env_var);
+ /* We mustn't free term_env_var, as putenv links it into the
+ * environment in place.
+ */
+ }
+#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */
+ if (got_windowid) {
+ char *windowid_env_var = dupprintf("WINDOWID=%ld", windowid);
+ putenv(windowid_env_var);
+ /* We mustn't free windowid_env_var, as putenv links it into the
+ * environment in place.
+ */
+ }
+ {
+ /*
+ * In case we were invoked with a --display argument that
+ * doesn't match DISPLAY in our actual environment, we
+ * should set DISPLAY for processes running inside the
+ * terminal to match the display the terminal itself is
+ * on.
+ */
+ const char *x_display = seat_get_x_display(pty->seat);
+ if (x_display) {
+ char *x_display_env_var = dupprintf("DISPLAY=%s", x_display);
+ putenv(x_display_env_var);
+ /* As above, we don't free this. */
+ } else {
+ unsetenv("DISPLAY");
+ }
+ }
+#endif
+ {
+ char *key, *val;
+
+ for (val = conf_get_str_strs(conf, CONF_environmt, NULL, &key);
+ val != NULL;
+ val = conf_get_str_strs(conf, CONF_environmt, key, &key)) {
+ char *varval = dupcat(key, "=", val);
+ putenv(varval);
+ /*
+ * We must not free varval, since putenv links it
+ * into the environment _in place_. Weird, but
+ * there we go. Memory usage will be rationalised
+ * as soon as we exec anyway.
+ */
+ }
+ }
+
+ if (dir) {
+ if (chdir(dir) < 0) {
+ /* Ignore the error - nothing we can sensibly do about it,
+ * and our existing cwd is as good a fallback as any. */
+ }
+ }
+
+ /*
+ * SIGINT, SIGQUIT and SIGPIPE may have been set to ignored by
+ * our parent, particularly by things like sh -c 'pterm &' and
+ * some window or session managers. SIGPIPE was also
+ * (potentially) blocked by us during startup. Reverse all
+ * this for our child process.
+ */
+ putty_signal(SIGINT, SIG_DFL);
+ putty_signal(SIGQUIT, SIG_DFL);
+ putty_signal(SIGPIPE, SIG_DFL);
+ block_signal(SIGPIPE, false);
+ if (argv || cmd) {
+ /*
+ * If we were given a separated argument list, try to exec
+ * it.
+ */
+ if (argv) {
+ execvp(argv[0], argv);
+ }
+ /*
+ * Otherwise, if we were given a single command string,
+ * try passing that to $SHELL -c.
+ *
+ * In the case of pterm, this system of fallbacks arranges
+ * that we can _either_ follow 'pterm -e' with a list of
+ * argv elements to be fed directly to exec, _or_ with a
+ * single argument containing a command to be parsed by a
+ * shell (but, in cases of doubt, the former is more
+ * reliable). We arrange this by setting argv to the full
+ * argument list, and also setting cmd to the single
+ * element of argv if it's a length-1 list.
+ *
+ * A quick survey of other terminal emulators' -e options
+ * (as of Debian squeeze) suggests that:
+ *
+ * - xterm supports both modes, more or less like this
+ * - gnome-terminal will only accept a one-string shell command
+ * - Eterm, kterm and rxvt will only accept a list of
+ * argv elements (as did older versions of pterm).
+ *
+ * It therefore seems important to support both usage
+ * modes in order to be a drop-in replacement for either
+ * xterm or gnome-terminal, and hence for anyone's
+ * plausible uses of the Debian-style alias
+ * 'x-terminal-emulator'.
+ *
+ * In other use cases, a caller can set only one of argv
+ * and cmd to get a fixed handling of the input.
+ */
+ if (cmd) {
+ char *shell = getenv("SHELL");
+ if (shell)
+ execl(shell, shell, "-c", cmd, (void *)NULL);
+ }
+ } else {
+ const char *shell = getenv("SHELL");
+ if (!shell)
+ shell = "/bin/sh";
+ char *shellname;
+ if (conf_get_bool(conf, CONF_login_shell)) {
+ const char *p = strrchr(shell, '/');
+ shellname = snewn(2+strlen(shell), char);
+ p = p ? p+1 : shell;
+ sprintf(shellname, "-%s", p);
+ } else
+ shellname = (char *)shell;
+ execl(shell, shellname, (void *)NULL);
+ }
+
+ /*
+ * If we're here, exec has gone badly foom.
+ */
+ perror("exec");
+ _exit(127);
+ } else {
+ pty->child_pid = pid;
+ pty->child_dead = false;
+ pty->finished = false;
+ if (pty->slave_fd > 0)
+ close(pty->slave_fd);
+ if (!ptys_by_pid)
+ ptys_by_pid = newtree234(pty_compare_by_pid);
+ if (pty->pipefds[0] >= 0) {
+ close(pty->pipefds[0]);
+ pty->pipefds[0] = -1;
+ }
+ if (pty->pipefds[3] >= 0) {
+ close(pty->pipefds[3]);
+ pty->pipefds[3] = -1;
+ }
+ if (pty->pipefds[5] >= 0) {
+ close(pty->pipefds[5]);
+ pty->pipefds[5] = -1;
+ }
+ add234(ptys_by_pid, pty);
+ }
+
+ pty_uxsel_setup(pty);
+
+ return &pty->backend;
+}
+
+/*
+ * This is the pty backend's _official_ init method, for BackendVtable
+ * purposes. Its job is just to be an API converter, ignoring the
+ * irrelevant input parameters and making up auxiliary outputs. Also
+ * it gets the argv array from the global variable pty_argv, expecting
+ * that it will have been invoked by pterm.
+ */
+static char *pty_init(const BackendVtable *vt, Seat *seat,
+ Backend **backend_handle, LogContext *logctx,
+ Conf *conf, const char *host, int port,
+ char **realhost, bool nodelay, bool keepalive)
+{
+ const char *cmd = NULL;
+ struct ssh_ttymodes modes;
+
+ memset(&modes, 0, sizeof(modes));
+
+ if (pty_argv && pty_argv[0] && !pty_argv[1])
+ cmd = pty_argv[0];
+
+ assert(vt == &pty_backend);
+ *backend_handle = pty_backend_create(
+ seat, logctx, conf, pty_argv, cmd, modes, false, NULL, NULL);
+ *realhost = dupstr("");
+ return NULL;
+}
+
+static void pty_reconfig(Backend *be, Conf *conf)
+{
+ Pty *pty = container_of(be, Pty, backend);
+ /*
+ * We don't have much need to reconfigure this backend, but
+ * unfortunately we do need to pick up the setting of Close On
+ * Exit so we know whether to give a `terminated' message.
+ */
+ conf_copy_into(pty->conf, conf);
+}
+
+/*
+ * Stub routine (never called in pterm).
+ */
+static void pty_free(Backend *be)
+{
+ Pty *pty = container_of(be, Pty, backend);
+ int i;
+
+ pty_close(pty);
+
+ /* Either of these may fail `not found'. That's fine with us. */
+ del234(ptys_by_pid, pty);
+ for (i = 0; i < 3; i++)
+ if (pty->fds[i].fd >= 0)
+ del234(ptyfds, &pty->fds[i]);
+
+ bufchain_clear(&pty->output_data);
+
+ conf_free(pty->conf);
+ pty->conf = NULL;
+
+ if (pty == single_pty) {
+ /*
+ * Leave this structure around in case we need to Restart
+ * Session.
+ */
+ } else {
+ sfree(pty);
+ }
+}
+
+static void pty_try_write(Pty *pty)
+{
+ ssize_t ret;
+
+ assert(pty->master_i >= 0);
+
+ while (bufchain_size(&pty->output_data) > 0) {
+ ptrlen data = bufchain_prefix(&pty->output_data);
+ ret = write(pty->master_i, data.ptr, data.len);
+
+ if (ret < 0 && (errno == EWOULDBLOCK)) {
+ /*
+ * We've sent all we can for the moment.
+ */
+ break;
+ }
+ if (ret < 0) {
+ perror("write pty master");
+ exit(1);
+ }
+ bufchain_consume(&pty->output_data, ret);
+ }
+
+ if (pty->pending_eof && bufchain_size(&pty->output_data) == 0) {
+ /* This should only happen if pty->master_i is a pipe that
+ * doesn't alias either output fd */
+ assert(pty->master_i != pty->master_o);
+ assert(pty->master_i != pty->master_e);
+ uxsel_del(pty->master_i);
+ close(pty->master_i);
+ pty->master_i = -1;
+ pty->pending_eof = false;
+ }
+
+ pty_uxsel_setup(pty);
+}
+
+/*
+ * Called to send data down the pty.
+ */
+static void pty_send(Backend *be, const char *buf, size_t len)
+{
+ Pty *pty = container_of(be, Pty, backend);
+
+ if (pty->master_i < 0 || pty->pending_eof)
+ return; /* ignore all writes if fd closed */
+
+ bufchain_add(&pty->output_data, buf, len);
+ pty_try_write(pty);
+}
+
+static void pty_close(Pty *pty)
+{
+ int i;
+
+ if (pty->master_o >= 0)
+ uxsel_del(pty->master_o);
+ if (pty->master_e >= 0)
+ uxsel_del(pty->master_e);
+ if (pty->master_i >= 0)
+ uxsel_del(pty->master_i);
+
+ if (pty->master_fd >= 0) {
+ close(pty->master_fd);
+ pty->master_fd = -1;
+ }
+ for (i = 0; i < 6; i++) {
+ if (pty->pipefds[i] >= 0)
+ close(pty->pipefds[i]);
+ pty->pipefds[i] = -1;
+ }
+ pty->master_i = pty->master_o = pty->master_e = -1;
+#ifndef OMIT_UTMP
+ if (pty_utmp_helper_pipe >= 0) {
+ close(pty_utmp_helper_pipe); /* this causes utmp to be cleaned up */
+ pty_utmp_helper_pipe = -1;
+ }
+#endif
+}
+
+/*
+ * Called to query the current socket sendability status.
+ */
+static size_t pty_sendbuffer(Backend *be)
+{
+ Pty *pty = container_of(be, Pty, backend);
+ return bufchain_size(&pty->output_data);
+}
+
+/*
+ * Called to set the size of the window
+ */
+static void pty_size(Backend *be, int width, int height)
+{
+ Pty *pty = container_of(be, Pty, backend);
+ struct winsize size;
+ int xpixel = 0, ypixel = 0;
+
+ pty->term_width = width;
+ pty->term_height = height;
+
+ if (pty->master_fd < 0)
+ return;
+
+ seat_get_window_pixel_size(pty->seat, &xpixel, &ypixel);
+
+ size.ws_row = (unsigned short)pty->term_height;
+ size.ws_col = (unsigned short)pty->term_width;
+ size.ws_xpixel = (unsigned short)xpixel;
+ size.ws_ypixel = (unsigned short)ypixel;
+ ioctl(pty->master_fd, TIOCSWINSZ, (void *)&size);
+ return;
+}
+
+/*
+ * Send special codes.
+ */
+static void pty_special(Backend *be, SessionSpecialCode code, int arg)
+{
+ Pty *pty = container_of(be, Pty, backend);
+
+ if (code == SS_BRK) {
+ if (pty->master_fd >= 0)
+ tcsendbreak(pty->master_fd, 0);
+ return;
+ }
+
+ if (code == SS_EOF) {
+ if (pty->master_i >= 0 && pty->master_i != pty->master_fd) {
+ pty->pending_eof = true;
+ pty_try_write(pty);
+ }
+ return;
+ }
+
+ {
+ int sig = -1;
+
+ #define SIGNAL_SUB(name) if (code == SS_SIG ## name) sig = SIG ## name;
+ #define SIGNAL_MAIN(name, text) SIGNAL_SUB(name)
+ #define SIGNALS_LOCAL_ONLY
+ #include "ssh/signal-list.h"
+ #undef SIGNAL_SUB
+ #undef SIGNAL_MAIN
+ #undef SIGNALS_LOCAL_ONLY
+
+ if (sig != -1) {
+ if (!pty->child_dead)
+ kill(pty->child_pid, sig);
+ return;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const SessionSpecial *pty_get_specials(Backend *be)
+{
+ /* Pty *pty = container_of(be, Pty, backend); */
+ /*
+ * Hmm. When I get round to having this actually usable, it
+ * might be quite nice to have the ability to deliver a few
+ * well chosen signals to the child process - SIGINT, SIGTERM,
+ * SIGKILL at least.
+ */
+ return NULL;
+}
+
+static bool pty_connected(Backend *be)
+{
+ /* Pty *pty = container_of(be, Pty, backend); */
+ return true;
+}
+
+static bool pty_sendok(Backend *be)
+{
+ /* Pty *pty = container_of(be, Pty, backend); */
+ return true;
+}
+
+static void pty_unthrottle(Backend *be, size_t backlog)
+{
+ Pty *pty = container_of(be, Pty, backend);
+ pty->output_backlog = backlog;
+ pty_uxsel_setup(pty);
+}
+
+static bool pty_ldisc(Backend *be, int option)
+{
+ /* Pty *pty = container_of(be, Pty, backend); */
+ return false; /* neither editing nor echoing */
+}
+
+static void pty_provide_ldisc(Backend *be, Ldisc *ldisc)
+{
+ /* Pty *pty = container_of(be, Pty, backend); */
+ /* This is a stub. */
+}
+
+static int pty_exitcode(Backend *be)
+{
+ Pty *pty = container_of(be, Pty, backend);
+ if (!pty->finished)
+ return -1; /* not dead yet */
+ else if (WIFSIGNALED(pty->exit_code))
+ return 128 + WTERMSIG(pty->exit_code);
+ else
+ return WEXITSTATUS(pty->exit_code);
+}
+
+int pty_backend_exit_signum(Backend *be)
+{
+ Pty *pty = container_of(be, Pty, backend);
+
+ if (!pty->finished || !WIFSIGNALED(pty->exit_code))
+ return -1;
+
+ return WTERMSIG(pty->exit_code);
+}
+
+ptrlen pty_backend_exit_signame(Backend *be, char **aux_msg)
+{
+ *aux_msg = NULL;
+
+ int sig = pty_backend_exit_signum(be);
+ if (sig < 0)
+ return PTRLEN_LITERAL("");
+
+ #define SIGNAL_SUB(s) { \
+ if (sig == SIG ## s) \
+ return PTRLEN_LITERAL(#s); \
+ }
+ #define SIGNAL_MAIN(s, desc) SIGNAL_SUB(s)
+ #define SIGNALS_LOCAL_ONLY
+ #include "ssh/signal-list.h"
+ #undef SIGNAL_MAIN
+ #undef SIGNAL_SUB
+ #undef SIGNALS_LOCAL_ONLY
+
+ *aux_msg = dupprintf("untranslatable signal number %d: %s",
+ sig, strsignal(sig));
+ return PTRLEN_LITERAL("HUP"); /* need some kind of default */
+}
+
+static int pty_cfg_info(Backend *be)
+{
+ /* Pty *pty = container_of(be, Pty, backend); */
+ return 0;
+}
+
+const BackendVtable pty_backend = {
+ .init = pty_init,
+ .free = pty_free,
+ .reconfig = pty_reconfig,
+ .send = pty_send,
+ .sendbuffer = pty_sendbuffer,
+ .size = pty_size,
+ .special = pty_special,
+ .get_specials = pty_get_specials,
+ .connected = pty_connected,
+ .exitcode = pty_exitcode,
+ .sendok = pty_sendok,
+ .ldisc_option_state = pty_ldisc,
+ .provide_ldisc = pty_provide_ldisc,
+ .unthrottle = pty_unthrottle,
+ .cfg_info = pty_cfg_info,
+ .id = "pty",
+ .displayname_tc = "pty",
+ .displayname_lc = "pty",
+ .protocol = -1,
+};
diff --git a/UNIX/XPMPUCFG.C b/unix/putty-config-xpm.c
index 34c5d491..34c5d491 100644
--- a/UNIX/XPMPUCFG.C
+++ b/unix/putty-config-xpm.c
diff --git a/UNIX/XPMPUTTY.C b/unix/putty-xpm.c
index 56d16bee..56d16bee 100644
--- a/UNIX/XPMPUTTY.C
+++ b/unix/putty-xpm.c
diff --git a/unix/putty.c b/unix/putty.c
new file mode 100644
index 00000000..a96217e3
--- /dev/null
+++ b/unix/putty.c
@@ -0,0 +1,92 @@
+/*
+ * Unix PuTTY main program.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+
+#define MAY_REFER_TO_GTK_IN_HEADERS
+
+#include "putty.h"
+#include "ssh.h"
+#include "storage.h"
+
+#include "gtkcompat.h"
+
+/*
+ * Stubs to avoid pty.c needing to be linked in.
+ */
+const bool use_pty_argv = false;
+char **pty_argv; /* never used */
+char *pty_osx_envrestore_prefix;
+
+/*
+ * Clean up and exit.
+ */
+void cleanup_exit(int code)
+{
+ /*
+ * Clean up.
+ */
+ sk_cleanup();
+ random_save_seed();
+ exit(code);
+}
+
+const struct BackendVtable *select_backend(Conf *conf)
+{
+ const struct BackendVtable *vt =
+ backend_vt_from_proto(conf_get_int(conf, CONF_protocol));
+ assert(vt != NULL);
+ return vt;
+}
+
+void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx)
+{
+ char *title = dupcat(appname, " Configuration");
+ create_config_box(title, conf, false, 0, after, afterctx);
+ sfree(title);
+}
+
+const bool use_event_log = true, new_session = true, saved_sessions = true;
+const bool dup_check_launchable = true;
+
+/*
+ * X11-forwarding-related things suitable for Gtk app.
+ */
+
+char *platform_get_x_display(void) {
+ const char *display;
+ /* Try to take account of --display and what have you. */
+ if (!(display = gdk_get_display()))
+ /* fall back to traditional method */
+ display = getenv("DISPLAY");
+ return dupstr(display);
+}
+
+const bool share_can_be_downstream = true;
+const bool share_can_be_upstream = true;
+
+const unsigned cmdline_tooltype =
+ TOOLTYPE_HOST_ARG |
+ TOOLTYPE_PORT_ARG |
+ TOOLTYPE_NO_VERBOSE_OPTION;
+
+void setup(bool single)
+{
+ sk_init();
+ settings_set_default_protocol(be_default_protocol);
+ /* Find the appropriate default port. */
+ {
+ const struct BackendVtable *vt =
+ backend_vt_from_proto(be_default_protocol);
+ settings_set_default_port(0); /* illegal */
+ if (vt)
+ settings_set_default_port(vt->default_port);
+ }
+}
diff --git a/unix/serial.c b/unix/serial.c
new file mode 100644
index 00000000..1e20dd20
--- /dev/null
+++ b/unix/serial.c
@@ -0,0 +1,596 @@
+/*
+ * Serial back end (Unix-specific).
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <limits.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+
+#include "putty.h"
+#include "tree234.h"
+
+#define SERIAL_MAX_BACKLOG 4096
+
+typedef struct Serial Serial;
+struct Serial {
+ Seat *seat;
+ LogContext *logctx;
+ int fd;
+ bool finished;
+ size_t inbufsize;
+ bufchain output_data;
+ Backend backend;
+};
+
+/*
+ * We store our serial backends in a tree sorted by fd, so that
+ * when we get an uxsel notification we know which backend instance
+ * is the owner of the serial port that caused it.
+ */
+static int serial_compare_by_fd(void *av, void *bv)
+{
+ Serial *a = (Serial *)av;
+ Serial *b = (Serial *)bv;
+
+ if (a->fd < b->fd)
+ return -1;
+ else if (a->fd > b->fd)
+ return +1;
+ return 0;
+}
+
+static int serial_find_by_fd(void *av, void *bv)
+{
+ int a = *(int *)av;
+ Serial *b = (Serial *)bv;
+
+ if (a < b->fd)
+ return -1;
+ else if (a > b->fd)
+ return +1;
+ return 0;
+}
+
+static tree234 *serial_by_fd = NULL;
+
+static void serial_select_result(int fd, int event);
+static void serial_uxsel_setup(Serial *serial);
+static void serial_try_write(Serial *serial);
+
+static char *serial_configure(Serial *serial, Conf *conf)
+{
+ struct termios options;
+ int bflag, bval, speed, flow, parity;
+ const char *str;
+
+ if (serial->fd < 0)
+ return dupstr("Unable to reconfigure already-closed "
+ "serial connection");
+
+ tcgetattr(serial->fd, &options);
+
+ /*
+ * Find the appropriate baud rate flag.
+ */
+ speed = conf_get_int(conf, CONF_serspeed);
+#define SETBAUD(x) (bflag = B ## x, bval = x)
+#define CHECKBAUD(x) do { if (speed >= x) SETBAUD(x); } while (0)
+ SETBAUD(50);
+#ifdef B75
+ CHECKBAUD(75);
+#endif
+#ifdef B110
+ CHECKBAUD(110);
+#endif
+#ifdef B134
+ CHECKBAUD(134);
+#endif
+#ifdef B150
+ CHECKBAUD(150);
+#endif
+#ifdef B200
+ CHECKBAUD(200);
+#endif
+#ifdef B300
+ CHECKBAUD(300);
+#endif
+#ifdef B600
+ CHECKBAUD(600);
+#endif
+#ifdef B1200
+ CHECKBAUD(1200);
+#endif
+#ifdef B1800
+ CHECKBAUD(1800);
+#endif
+#ifdef B2400
+ CHECKBAUD(2400);
+#endif
+#ifdef B4800
+ CHECKBAUD(4800);
+#endif
+#ifdef B9600
+ CHECKBAUD(9600);
+#endif
+#ifdef B19200
+ CHECKBAUD(19200);
+#endif
+#ifdef B38400
+ CHECKBAUD(38400);
+#endif
+#ifdef B57600
+ CHECKBAUD(57600);
+#endif
+#ifdef B76800
+ CHECKBAUD(76800);
+#endif
+#ifdef B115200
+ CHECKBAUD(115200);
+#endif
+#ifdef B153600
+ CHECKBAUD(153600);
+#endif
+#ifdef B230400
+ CHECKBAUD(230400);
+#endif
+#ifdef B307200
+ CHECKBAUD(307200);
+#endif
+#ifdef B460800
+ CHECKBAUD(460800);
+#endif
+#ifdef B500000
+ CHECKBAUD(500000);
+#endif
+#ifdef B576000
+ CHECKBAUD(576000);
+#endif
+#ifdef B921600
+ CHECKBAUD(921600);
+#endif
+#ifdef B1000000
+ CHECKBAUD(1000000);
+#endif
+#ifdef B1152000
+ CHECKBAUD(1152000);
+#endif
+#ifdef B1500000
+ CHECKBAUD(1500000);
+#endif
+#ifdef B2000000
+ CHECKBAUD(2000000);
+#endif
+#ifdef B2500000
+ CHECKBAUD(2500000);
+#endif
+#ifdef B3000000
+ CHECKBAUD(3000000);
+#endif
+#ifdef B3500000
+ CHECKBAUD(3500000);
+#endif
+#ifdef B4000000
+ CHECKBAUD(4000000);
+#endif
+#undef CHECKBAUD
+#undef SETBAUD
+ cfsetispeed(&options, bflag);
+ cfsetospeed(&options, bflag);
+ logeventf(serial->logctx, "Configuring baud rate %d", bval);
+
+ options.c_cflag &= ~CSIZE;
+ switch (conf_get_int(conf, CONF_serdatabits)) {
+ case 5: options.c_cflag |= CS5; break;
+ case 6: options.c_cflag |= CS6; break;
+ case 7: options.c_cflag |= CS7; break;
+ case 8: options.c_cflag |= CS8; break;
+ default: return dupstr("Invalid number of data bits "
+ "(need 5, 6, 7 or 8)");
+ }
+ logeventf(serial->logctx, "Configuring %d data bits",
+ conf_get_int(conf, CONF_serdatabits));
+
+ if (conf_get_int(conf, CONF_serstopbits) >= 4) {
+ options.c_cflag |= CSTOPB;
+ } else {
+ options.c_cflag &= ~CSTOPB;
+ }
+ logeventf(serial->logctx, "Configuring %s",
+ (options.c_cflag & CSTOPB ? "2 stop bits" : "1 stop bit"));
+
+ options.c_iflag &= ~(IXON|IXOFF);
+#ifdef CRTSCTS
+ options.c_cflag &= ~CRTSCTS;
+#endif
+#ifdef CNEW_RTSCTS
+ options.c_cflag &= ~CNEW_RTSCTS;
+#endif
+ flow = conf_get_int(conf, CONF_serflow);
+ if (flow == SER_FLOW_XONXOFF) {
+ options.c_iflag |= IXON | IXOFF;
+ str = "XON/XOFF";
+ } else if (flow == SER_FLOW_RTSCTS) {
+#ifdef CRTSCTS
+ options.c_cflag |= CRTSCTS;
+#endif
+#ifdef CNEW_RTSCTS
+ options.c_cflag |= CNEW_RTSCTS;
+#endif
+ str = "RTS/CTS";
+ } else
+ str = "no";
+ logeventf(serial->logctx, "Configuring %s flow control", str);
+
+ /* Parity */
+ parity = conf_get_int(conf, CONF_serparity);
+ if (parity == SER_PAR_ODD) {
+ options.c_cflag |= PARENB;
+ options.c_cflag |= PARODD;
+ str = "odd";
+ } else if (parity == SER_PAR_EVEN) {
+ options.c_cflag |= PARENB;
+ options.c_cflag &= ~PARODD;
+ str = "even";
+ } else {
+ options.c_cflag &= ~PARENB;
+ str = "no";
+ }
+ logeventf(serial->logctx, "Configuring %s parity", str);
+
+ options.c_cflag |= CLOCAL | CREAD;
+ options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
+ options.c_iflag &= ~(ISTRIP | IGNCR | INLCR | ICRNL
+#ifdef IUCLC
+ | IUCLC
+#endif
+ );
+ options.c_oflag &= ~(OPOST
+#ifdef ONLCR
+ | ONLCR
+#endif
+#ifdef OCRNL
+ | OCRNL
+#endif
+#ifdef ONOCR
+ | ONOCR
+#endif
+#ifdef ONLRET
+ | ONLRET
+#endif
+ );
+ options.c_cc[VMIN] = 1;
+ options.c_cc[VTIME] = 0;
+
+ if (tcsetattr(serial->fd, TCSANOW, &options) < 0)
+ return dupprintf("Configuring serial port: %s", strerror(errno));
+
+ return NULL;
+}
+
+/*
+ * Called to set up the serial connection.
+ *
+ * Returns an error message, or NULL on success.
+ *
+ * Also places the canonical host name into `realhost'. It must be
+ * freed by the caller.
+ */
+static char *serial_init(const BackendVtable *vt, Seat *seat,
+ Backend **backend_handle, LogContext *logctx,
+ Conf *conf, const char *host, int port,
+ char **realhost, bool nodelay, bool keepalive)
+{
+ Serial *serial;
+ char *err;
+ char *line;
+
+ /* No local authentication phase in this protocol */
+ seat_set_trust_status(seat, false);
+
+ serial = snew(Serial);
+ memset(serial, 0, sizeof(Serial));
+ serial->backend.vt = vt;
+ *backend_handle = &serial->backend;
+
+ serial->seat = seat;
+ serial->logctx = logctx;
+ serial->finished = false;
+ serial->inbufsize = 0;
+ bufchain_init(&serial->output_data);
+
+ line = conf_get_str(conf, CONF_serline);
+ logeventf(serial->logctx, "Opening serial device %s", line);
+
+ serial->fd = open(line, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
+ if (serial->fd < 0)
+ return dupprintf("Opening serial port '%s': %s",
+ line, strerror(errno));
+
+ cloexec(serial->fd);
+
+ err = serial_configure(serial, conf);
+ if (err)
+ return err;
+
+ *realhost = dupstr(line);
+
+ if (!serial_by_fd)
+ serial_by_fd = newtree234(serial_compare_by_fd);
+ add234(serial_by_fd, serial);
+
+ serial_uxsel_setup(serial);
+
+ /*
+ * Specials are always available.
+ */
+ seat_update_specials_menu(serial->seat);
+
+ return NULL;
+}
+
+static void serial_close(Serial *serial)
+{
+ if (serial->fd >= 0) {
+ uxsel_del(serial->fd);
+ close(serial->fd);
+ serial->fd = -1;
+ }
+}
+
+static void serial_free(Backend *be)
+{
+ Serial *serial = container_of(be, Serial, backend);
+
+ serial_close(serial);
+
+ bufchain_clear(&serial->output_data);
+
+ sfree(serial);
+}
+
+static void serial_reconfig(Backend *be, Conf *conf)
+{
+ Serial *serial = container_of(be, Serial, backend);
+
+ char *err = serial_configure(serial, conf);
+ if (err) {
+ /*
+ * FIXME: apart from freeing the dynamically allocated
+ * message, what should we do if this returns an error?
+ */
+ sfree(err);
+ }
+}
+
+static void serial_select_result(int fd, int event)
+{
+ Serial *serial;
+ char buf[4096];
+ int ret;
+ bool finished = false;
+
+ serial = find234(serial_by_fd, &fd, serial_find_by_fd);
+
+ if (!serial)
+ return; /* spurious event; keep going */
+
+ if (event == 1) {
+ ret = read(serial->fd, buf, sizeof(buf));
+
+ if (ret == 0) {
+ /*
+ * Shouldn't happen on a real serial port, but I'm open
+ * to the idea that there might be two-way devices we
+ * can treat _like_ serial ports which can return EOF.
+ */
+ finished = true;
+ } else if (ret < 0) {
+#ifdef EAGAIN
+ if (errno == EAGAIN)
+ return; /* spurious */
+#endif
+#ifdef EWOULDBLOCK
+ if (errno == EWOULDBLOCK)
+ return; /* spurious */
+#endif
+ perror("read serial port");
+ exit(1);
+ } else if (ret > 0) {
+ serial->inbufsize = seat_stdout(serial->seat, buf, ret);
+ serial_uxsel_setup(serial); /* might acquire backlog and freeze */
+ }
+ } else if (event == 2) {
+ /*
+ * Attempt to send data down the pty.
+ */
+ serial_try_write(serial);
+ }
+
+ if (finished) {
+ serial_close(serial);
+
+ serial->finished = true;
+
+ seat_notify_remote_exit(serial->seat);
+ }
+}
+
+static void serial_uxsel_setup(Serial *serial)
+{
+ int rwx = 0;
+
+ if (serial->inbufsize <= SERIAL_MAX_BACKLOG)
+ rwx |= SELECT_R;
+ if (bufchain_size(&serial->output_data))
+ rwx |= SELECT_W; /* might also want to write to it */
+ uxsel_set(serial->fd, rwx, serial_select_result);
+}
+
+static void serial_try_write(Serial *serial)
+{
+ ssize_t ret;
+
+ assert(serial->fd >= 0);
+
+ while (bufchain_size(&serial->output_data) > 0) {
+ ptrlen data = bufchain_prefix(&serial->output_data);
+ ret = write(serial->fd, data.ptr, data.len);
+
+ if (ret < 0 && (errno == EWOULDBLOCK)) {
+ /*
+ * We've sent all we can for the moment.
+ */
+ break;
+ }
+ if (ret < 0) {
+ perror("write serial port");
+ exit(1);
+ }
+ bufchain_consume(&serial->output_data, ret);
+ }
+
+ seat_sent(serial->seat, bufchain_size(&serial->output_data));
+ serial_uxsel_setup(serial);
+}
+
+/*
+ * Called to send data down the serial connection.
+ */
+static void serial_send(Backend *be, const char *buf, size_t len)
+{
+ Serial *serial = container_of(be, Serial, backend);
+
+ if (serial->fd < 0)
+ return;
+
+ bufchain_add(&serial->output_data, buf, len);
+ serial_try_write(serial);
+}
+
+/*
+ * Called to query the current sendability status.
+ */
+static size_t serial_sendbuffer(Backend *be)
+{
+ Serial *serial = container_of(be, Serial, backend);
+ return bufchain_size(&serial->output_data);
+}
+
+/*
+ * Called to set the size of the window
+ */
+static void serial_size(Backend *be, int width, int height)
+{
+ /* Do nothing! */
+ return;
+}
+
+/*
+ * Send serial special codes.
+ */
+static void serial_special(Backend *be, SessionSpecialCode code, int arg)
+{
+ Serial *serial = container_of(be, Serial, backend);
+
+ if (serial->fd >= 0 && code == SS_BRK) {
+ tcsendbreak(serial->fd, 0);
+ logevent(serial->logctx, "Sending serial break at user request");
+ }
+
+ return;
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const SessionSpecial *serial_get_specials(Backend *be)
+{
+ static const struct SessionSpecial specials[] = {
+ {"Break", SS_BRK},
+ {NULL, SS_EXITMENU}
+ };
+ return specials;
+}
+
+static bool serial_connected(Backend *be)
+{
+ return true; /* always connected */
+}
+
+static bool serial_sendok(Backend *be)
+{
+ return true;
+}
+
+static void serial_unthrottle(Backend *be, size_t backlog)
+{
+ Serial *serial = container_of(be, Serial, backend);
+ serial->inbufsize = backlog;
+ serial_uxsel_setup(serial);
+}
+
+static bool serial_ldisc(Backend *be, int option)
+{
+ /*
+ * Local editing and local echo are off by default.
+ */
+ return false;
+}
+
+static void serial_provide_ldisc(Backend *be, Ldisc *ldisc)
+{
+ /* This is a stub. */
+}
+
+static int serial_exitcode(Backend *be)
+{
+ Serial *serial = container_of(be, Serial, backend);
+ if (serial->fd >= 0)
+ return -1; /* still connected */
+ else
+ /* Exit codes are a meaningless concept with serial ports */
+ return INT_MAX;
+}
+
+/*
+ * cfg_info for Serial does nothing at all.
+ */
+static int serial_cfg_info(Backend *be)
+{
+ return 0;
+}
+
+const BackendVtable serial_backend = {
+ .init = serial_init,
+ .free = serial_free,
+ .reconfig = serial_reconfig,
+ .send = serial_send,
+ .sendbuffer = serial_sendbuffer,
+ .size = serial_size,
+ .special = serial_special,
+ .get_specials = serial_get_specials,
+ .connected = serial_connected,
+ .exitcode = serial_exitcode,
+ .sendok = serial_sendok,
+ .ldisc_option_state = serial_ldisc,
+ .provide_ldisc = serial_provide_ldisc,
+ .unthrottle = serial_unthrottle,
+ .cfg_info = serial_cfg_info,
+ .id = "serial",
+ .displayname_tc = "Serial",
+ .displayname_lc = "serial",
+ .protocol = PROT_SERIAL,
+ .serial_parity_mask = ((1 << SER_PAR_NONE) |
+ (1 << SER_PAR_ODD) |
+ (1 << SER_PAR_EVEN)),
+ .serial_flow_mask = ((1 << SER_FLOW_NONE) |
+ (1 << SER_FLOW_XONXOFF) |
+ (1 << SER_FLOW_RTSCTS)),
+};
diff --git a/unix/sftp.c b/unix/sftp.c
new file mode 100644
index 00000000..023f41ee
--- /dev/null
+++ b/unix/sftp.c
@@ -0,0 +1,584 @@
+/*
+ * sftp.c: the Unix-specific parts of PSFTP and PSCP.
+ */
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <utime.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "psftp.h"
+
+#if HAVE_GLOB_H
+#include <glob.h>
+#endif
+
+char *x_get_default(const char *key)
+{
+ return NULL; /* this is a stub */
+}
+
+void platform_get_x11_auth(struct X11Display *display, Conf *conf)
+{
+ /* Do nothing, therefore no auth. */
+}
+const bool platform_uses_x11_unix_by_default = true;
+
+/*
+ * Default settings that are specific to PSFTP.
+ */
+char *platform_default_s(const char *name)
+{
+ return NULL;
+}
+
+bool platform_default_b(const char *name, bool def)
+{
+ return def;
+}
+
+int platform_default_i(const char *name, int def)
+{
+ return def;
+}
+
+FontSpec *platform_default_fontspec(const char *name)
+{
+ return fontspec_new("");
+}
+
+Filename *platform_default_filename(const char *name)
+{
+ if (!strcmp(name, "LogFileName"))
+ return filename_from_str("putty.log");
+ else
+ return filename_from_str("");
+}
+
+SeatPromptResult filexfer_get_userpass_input(Seat *seat, prompts_t *p)
+{
+ /* The file transfer tools don't support Restart Session, so we
+ * can just have a single static cmdline_get_passwd_input_state
+ * that's never reset */
+ static cmdline_get_passwd_input_state cmdline_state =
+ CMDLINE_GET_PASSWD_INPUT_STATE_INIT;
+
+ SeatPromptResult spr;
+ spr = cmdline_get_passwd_input(p, &cmdline_state, false);
+ if (spr.kind == SPRK_INCOMPLETE)
+ spr = console_get_userpass_input(p);
+ return spr;
+}
+
+/*
+ * Set local current directory. Returns NULL on success, or else an
+ * error message which must be freed after printing.
+ */
+char *psftp_lcd(char *dir)
+{
+ if (chdir(dir) < 0)
+ return dupprintf("%s: chdir: %s", dir, strerror(errno));
+ else
+ return NULL;
+}
+
+/*
+ * Get local current directory. Returns a string which must be
+ * freed.
+ */
+char *psftp_getcwd(void)
+{
+ char *buffer, *ret;
+ size_t size = 256;
+
+ buffer = snewn(size, char);
+ while (1) {
+ ret = getcwd(buffer, size);
+ if (ret != NULL)
+ return ret;
+ if (errno != ERANGE) {
+ sfree(buffer);
+ return dupprintf("[cwd unavailable: %s]", strerror(errno));
+ }
+ /*
+ * Otherwise, ERANGE was returned, meaning the buffer
+ * wasn't big enough.
+ */
+ sgrowarray(buffer, size, size);
+ }
+}
+
+struct RFile {
+ int fd;
+};
+
+RFile *open_existing_file(const char *name, uint64_t *size,
+ unsigned long *mtime, unsigned long *atime,
+ long *perms)
+{
+ int fd;
+ RFile *ret;
+
+ fd = open(name, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+
+ ret = snew(RFile);
+ ret->fd = fd;
+
+ if (size || mtime || atime || perms) {
+ struct stat statbuf;
+ if (fstat(fd, &statbuf) < 0) {
+ fprintf(stderr, "%s: stat: %s\n", name, strerror(errno));
+ memset(&statbuf, 0, sizeof(statbuf));
+ }
+
+ if (size)
+ *size = statbuf.st_size;
+
+ if (mtime)
+ *mtime = statbuf.st_mtime;
+
+ if (atime)
+ *atime = statbuf.st_atime;
+
+ if (perms)
+ *perms = statbuf.st_mode;
+ }
+
+ return ret;
+}
+
+int read_from_file(RFile *f, void *buffer, int length)
+{
+ return read(f->fd, buffer, length);
+}
+
+void close_rfile(RFile *f)
+{
+ close(f->fd);
+ sfree(f);
+}
+
+struct WFile {
+ int fd;
+ char *name;
+};
+
+WFile *open_new_file(const char *name, long perms)
+{
+ int fd;
+ WFile *ret;
+
+ fd = open(name, O_CREAT | O_TRUNC | O_WRONLY,
+ (mode_t)(perms ? perms : 0666));
+ if (fd < 0)
+ return NULL;
+
+ ret = snew(WFile);
+ ret->fd = fd;
+ ret->name = dupstr(name);
+
+ return ret;
+}
+
+
+WFile *open_existing_wfile(const char *name, uint64_t *size)
+{
+ int fd;
+ WFile *ret;
+
+ fd = open(name, O_APPEND | O_WRONLY);
+ if (fd < 0)
+ return NULL;
+
+ ret = snew(WFile);
+ ret->fd = fd;
+ ret->name = dupstr(name);
+
+ if (size) {
+ struct stat statbuf;
+ if (fstat(fd, &statbuf) < 0) {
+ fprintf(stderr, "%s: stat: %s\n", name, strerror(errno));
+ memset(&statbuf, 0, sizeof(statbuf));
+ }
+
+ *size = statbuf.st_size;
+ }
+
+ return ret;
+}
+
+int write_to_file(WFile *f, void *buffer, int length)
+{
+ char *p = (char *)buffer;
+ int so_far = 0;
+
+ /* Keep trying until we've really written as much as we can. */
+ while (length > 0) {
+ int ret = write(f->fd, p, length);
+
+ if (ret < 0)
+ return ret;
+
+ if (ret == 0)
+ break;
+
+ p += ret;
+ length -= ret;
+ so_far += ret;
+ }
+
+ return so_far;
+}
+
+void set_file_times(WFile *f, unsigned long mtime, unsigned long atime)
+{
+ struct utimbuf ut;
+
+ ut.actime = atime;
+ ut.modtime = mtime;
+
+ utime(f->name, &ut);
+}
+
+/* Closes and frees the WFile */
+void close_wfile(WFile *f)
+{
+ close(f->fd);
+ sfree(f->name);
+ sfree(f);
+}
+
+/* Seek offset bytes through file, from whence, where whence is
+ FROM_START, FROM_CURRENT, or FROM_END */
+int seek_file(WFile *f, uint64_t offset, int whence)
+{
+ int lseek_whence;
+
+ switch (whence) {
+ case FROM_START:
+ lseek_whence = SEEK_SET;
+ break;
+ case FROM_CURRENT:
+ lseek_whence = SEEK_CUR;
+ break;
+ case FROM_END:
+ lseek_whence = SEEK_END;
+ break;
+ default:
+ return -1;
+ }
+
+ return lseek(f->fd, offset, lseek_whence) >= 0 ? 0 : -1;
+}
+
+uint64_t get_file_posn(WFile *f)
+{
+ return lseek(f->fd, (off_t) 0, SEEK_CUR);
+}
+
+int file_type(const char *name)
+{
+ struct stat statbuf;
+
+ if (stat(name, &statbuf) < 0) {
+ if (errno != ENOENT)
+ fprintf(stderr, "%s: stat: %s\n", name, strerror(errno));
+ return FILE_TYPE_NONEXISTENT;
+ }
+
+ if (S_ISREG(statbuf.st_mode))
+ return FILE_TYPE_FILE;
+
+ if (S_ISDIR(statbuf.st_mode))
+ return FILE_TYPE_DIRECTORY;
+
+ return FILE_TYPE_WEIRD;
+}
+
+struct DirHandle {
+ DIR *dir;
+};
+
+DirHandle *open_directory(const char *name, const char **errmsg)
+{
+ DIR *dir;
+ DirHandle *ret;
+
+ dir = opendir(name);
+ if (!dir) {
+ *errmsg = strerror(errno);
+ return NULL;
+ }
+
+ ret = snew(DirHandle);
+ ret->dir = dir;
+ return ret;
+}
+
+char *read_filename(DirHandle *dir)
+{
+ struct dirent *de;
+
+ do {
+ de = readdir(dir->dir);
+ if (de == NULL)
+ return NULL;
+ } while ((de->d_name[0] == '.' &&
+ (de->d_name[1] == '\0' ||
+ (de->d_name[1] == '.' && de->d_name[2] == '\0'))));
+
+ return dupstr(de->d_name);
+}
+
+void close_directory(DirHandle *dir)
+{
+ closedir(dir->dir);
+ sfree(dir);
+}
+
+int test_wildcard(const char *name, bool cmdline)
+{
+ struct stat statbuf;
+
+ if (stat(name, &statbuf) == 0) {
+ return WCTYPE_FILENAME;
+ } else if (cmdline) {
+ /*
+ * On Unix, we never need to parse wildcards coming from
+ * the command line, because the shell will have expanded
+ * them into a filename list already.
+ */
+ return WCTYPE_NONEXISTENT;
+ } else {
+#if HAVE_GLOB_H
+ glob_t globbed;
+ int ret = WCTYPE_NONEXISTENT;
+
+ if (glob(name, GLOB_ERR, NULL, &globbed) == 0) {
+ if (globbed.gl_pathc > 0)
+ ret = WCTYPE_WILDCARD;
+ globfree(&globbed);
+ }
+
+ return ret;
+#else
+ /* On a system without glob.h, we just have to return a
+ * failure code */
+ return WCTYPE_NONEXISTENT;
+#endif
+ }
+}
+
+/*
+ * Actually return matching file names for a local wildcard.
+ */
+#if HAVE_GLOB_H
+struct WildcardMatcher {
+ glob_t globbed;
+ int i;
+};
+WildcardMatcher *begin_wildcard_matching(const char *name) {
+ WildcardMatcher *ret = snew(WildcardMatcher);
+
+ if (glob(name, 0, NULL, &ret->globbed) < 0) {
+ sfree(ret);
+ return NULL;
+ }
+
+ ret->i = 0;
+
+ return ret;
+}
+char *wildcard_get_filename(WildcardMatcher *dir) {
+ if (dir->i < dir->globbed.gl_pathc) {
+ return dupstr(dir->globbed.gl_pathv[dir->i++]);
+ } else
+ return NULL;
+}
+void finish_wildcard_matching(WildcardMatcher *dir) {
+ globfree(&dir->globbed);
+ sfree(dir);
+}
+#else
+WildcardMatcher *begin_wildcard_matching(const char *name)
+{
+ return NULL;
+}
+char *wildcard_get_filename(WildcardMatcher *dir)
+{
+ unreachable("Can't construct a valid WildcardMatcher without <glob.h>");
+}
+void finish_wildcard_matching(WildcardMatcher *dir)
+{
+ unreachable("Can't construct a valid WildcardMatcher without <glob.h>");
+}
+#endif
+
+char *stripslashes(const char *str, bool local)
+{
+ char *p;
+
+ /*
+ * On Unix, we do the same thing regardless of the 'local'
+ * parameter.
+ */
+ p = strrchr(str, '/');
+ if (p) str = p+1;
+
+ return (char *)str;
+}
+
+bool vet_filename(const char *name)
+{
+ if (strchr(name, '/'))
+ return false;
+
+ if (name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2])))
+ return false;
+
+ return true;
+}
+
+bool create_directory(const char *name)
+{
+ return mkdir(name, 0777) == 0;
+}
+
+char *dir_file_cat(const char *dir, const char *file)
+{
+ ptrlen dir_pl = ptrlen_from_asciz(dir);
+ return dupcat(
+ dir, ptrlen_endswith(dir_pl, PTRLEN_LITERAL("/"), NULL) ? "" : "/",
+ file);
+}
+
+/*
+ * Do a select() between all currently active network fds and
+ * optionally stdin, using cli_main_loop.
+ */
+
+struct ssh_sftp_mainloop_ctx {
+ bool include_stdin, no_fds_ok;
+ int toret;
+};
+static bool ssh_sftp_pw_setup(void *vctx, pollwrapper *pw)
+{
+ struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx;
+ int fdstate, rwx;
+
+ if (!ctx->no_fds_ok && !toplevel_callback_pending() &&
+ first_fd(&fdstate, &rwx) < 0) {
+ ctx->toret = -1;
+ return false; /* terminate cli_main_loop */
+ }
+
+ if (ctx->include_stdin)
+ pollwrap_add_fd_rwx(pw, 0, SELECT_R);
+
+ return true;
+}
+static void ssh_sftp_pw_check(void *vctx, pollwrapper *pw)
+{
+ struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx;
+
+ if (ctx->include_stdin && pollwrap_check_fd_rwx(pw, 0, SELECT_R))
+ ctx->toret = 1;
+}
+static bool ssh_sftp_mainloop_continue(void *vctx, bool found_any_fd,
+ bool ran_any_callback)
+{
+ struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx;
+ if (ctx->toret != 0 || found_any_fd || ran_any_callback)
+ return false; /* finish the loop */
+ return true;
+}
+static int ssh_sftp_do_select(bool include_stdin, bool no_fds_ok)
+{
+ struct ssh_sftp_mainloop_ctx ctx[1];
+ ctx->include_stdin = include_stdin;
+ ctx->no_fds_ok = no_fds_ok;
+ ctx->toret = 0;
+
+ cli_main_loop(ssh_sftp_pw_setup, ssh_sftp_pw_check,
+ ssh_sftp_mainloop_continue, ctx);
+
+ return ctx->toret;
+}
+
+/*
+ * Wait for some network data and process it.
+ */
+int ssh_sftp_loop_iteration(void)
+{
+ return ssh_sftp_do_select(false, false);
+}
+
+/*
+ * Read a PSFTP command line from stdin.
+ */
+char *ssh_sftp_get_cmdline(const char *prompt, bool no_fds_ok)
+{
+ char *buf;
+ size_t buflen, bufsize;
+ int ret;
+
+ fputs(prompt, stdout);
+ fflush(stdout);
+
+ buf = NULL;
+ buflen = bufsize = 0;
+
+ while (1) {
+ ret = ssh_sftp_do_select(true, no_fds_ok);
+ if (ret < 0) {
+ printf("connection died\n");
+ sfree(buf);
+ return NULL; /* woop woop */
+ }
+ if (ret > 0) {
+ sgrowarray(buf, bufsize, buflen);
+ ret = read(0, buf+buflen, 1);
+ if (ret < 0) {
+ perror("read");
+ sfree(buf);
+ return NULL;
+ }
+ if (ret == 0) {
+ /* eof on stdin; no error, but no answer either */
+ sfree(buf);
+ return NULL;
+ }
+
+ if (buf[buflen++] == '\n') {
+ /* we have a full line */
+ return buf;
+ }
+ }
+ }
+}
+
+void frontend_net_error_pending(void) {}
+
+void platform_psftp_pre_conn_setup(LogPolicy *lp) {}
+
+const bool buildinfo_gtk_relevant = false;
+
+/*
+ * Main program: do platform-specific initialisation and then call
+ * psftp_main().
+ */
+int main(int argc, char *argv[])
+{
+ uxsel_init();
+ return psftp_main(argc, argv);
+}
diff --git a/unix/sftpserver.c b/unix/sftpserver.c
new file mode 100644
index 00000000..453a3ee0
--- /dev/null
+++ b/unix/sftpserver.c
@@ -0,0 +1,703 @@
+/*
+ * Implement the SftpServer abstraction, in the 'live' form (i.e.
+ * really operating on the Unix filesystem).
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+#include <utime.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "ssh/server.h"
+#include "ssh/sftp.h"
+#include "tree234.h"
+
+typedef struct UnixSftpServer UnixSftpServer;
+
+struct UnixSftpServer {
+ unsigned *fdseqs;
+ bool *fdsopen;
+ size_t fdsize;
+
+ tree234 *dirhandles;
+ int last_dirhandle_index;
+
+ char handlekey[8];
+
+ SftpServer srv;
+};
+
+struct uss_dirhandle {
+ int index;
+ DIR *dp;
+};
+
+#define USS_DIRHANDLE_SEQ (0xFFFFFFFFU)
+
+static int uss_dirhandle_cmp(void *av, void *bv)
+{
+ struct uss_dirhandle *a = (struct uss_dirhandle *)av;
+ struct uss_dirhandle *b = (struct uss_dirhandle *)bv;
+ if (a->index < b->index)
+ return -1;
+ if (a->index > b->index)
+ return +1;
+ return 0;
+}
+
+static SftpServer *uss_new(const SftpServerVtable *vt)
+{
+ UnixSftpServer *uss = snew(UnixSftpServer);
+
+ memset(uss, 0, sizeof(UnixSftpServer));
+
+ uss->dirhandles = newtree234(uss_dirhandle_cmp);
+ uss->srv.vt = vt;
+
+ make_unix_sftp_filehandle_key(uss->handlekey, sizeof(uss->handlekey));
+
+ return &uss->srv;
+}
+
+static void uss_free(SftpServer *srv)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+ struct uss_dirhandle *udh;
+
+ for (size_t i = 0; i < uss->fdsize; i++)
+ if (uss->fdsopen[i])
+ close(i);
+ sfree(uss->fdseqs);
+
+ while ((udh = delpos234(uss->dirhandles, 0)) != NULL) {
+ closedir(udh->dp);
+ sfree(udh);
+ }
+
+ sfree(uss);
+}
+
+static void uss_return_handle_raw(
+ UnixSftpServer *uss, SftpReplyBuilder *reply, int index, unsigned seq)
+{
+ unsigned char handlebuf[8];
+ PUT_32BIT_MSB_FIRST(handlebuf, index);
+ PUT_32BIT_MSB_FIRST(handlebuf + 4, seq);
+ des_encrypt_xdmauth(uss->handlekey, handlebuf, 8);
+ fxp_reply_handle(reply, make_ptrlen(handlebuf, 8));
+}
+
+static bool uss_decode_handle(
+ UnixSftpServer *uss, ptrlen handle, int *index, unsigned *seq)
+{
+ unsigned char handlebuf[8];
+
+ if (handle.len != 8)
+ return false;
+ memcpy(handlebuf, handle.ptr, 8);
+ des_decrypt_xdmauth(uss->handlekey, handlebuf, 8);
+ *index = toint(GET_32BIT_MSB_FIRST(handlebuf));
+ *seq = GET_32BIT_MSB_FIRST(handlebuf + 4);
+ return true;
+}
+
+static void uss_return_new_handle(
+ UnixSftpServer *uss, SftpReplyBuilder *reply, int fd)
+{
+ assert(fd >= 0);
+ if (fd >= uss->fdsize) {
+ size_t old_size = uss->fdsize;
+ sgrowarray(uss->fdseqs, uss->fdsize, fd);
+ uss->fdsopen = sresize(uss->fdsopen, uss->fdsize, bool);
+ while (old_size < uss->fdsize) {
+ uss->fdseqs[old_size] = 0;
+ uss->fdsopen[old_size] = false;
+ old_size++;
+ }
+ }
+ assert(!uss->fdsopen[fd]);
+ uss->fdsopen[fd] = true;
+ if (++uss->fdseqs[fd] == USS_DIRHANDLE_SEQ)
+ uss->fdseqs[fd] = 0;
+ uss_return_handle_raw(uss, reply, fd, uss->fdseqs[fd]);
+}
+
+static int uss_try_lookup_fd(UnixSftpServer *uss, ptrlen handle)
+{
+ int fd;
+ unsigned seq;
+ if (!uss_decode_handle(uss, handle, &fd, &seq) ||
+ fd < 0 || fd >= uss->fdsize ||
+ !uss->fdsopen[fd] || uss->fdseqs[fd] != seq)
+ return -1;
+
+ return fd;
+}
+
+static int uss_lookup_fd(UnixSftpServer *uss, SftpReplyBuilder *reply,
+ ptrlen handle)
+{
+ int fd = uss_try_lookup_fd(uss, handle);
+ if (fd < 0)
+ fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle");
+ return fd;
+}
+
+static void uss_return_new_dirhandle(
+ UnixSftpServer *uss, SftpReplyBuilder *reply, DIR *dp)
+{
+ struct uss_dirhandle *udh = snew(struct uss_dirhandle);
+ udh->index = uss->last_dirhandle_index++;
+ udh->dp = dp;
+ struct uss_dirhandle *added = add234(uss->dirhandles, udh);
+ assert(added == udh);
+ uss_return_handle_raw(uss, reply, udh->index, USS_DIRHANDLE_SEQ);
+}
+
+static struct uss_dirhandle *uss_try_lookup_dirhandle(
+ UnixSftpServer *uss, ptrlen handle)
+{
+ struct uss_dirhandle key, *udh;
+ unsigned seq;
+
+ if (!uss_decode_handle(uss, handle, &key.index, &seq) ||
+ seq != USS_DIRHANDLE_SEQ ||
+ (udh = find234(uss->dirhandles, &key, NULL)) == NULL)
+ return NULL;
+
+ return udh;
+}
+
+static struct uss_dirhandle *uss_lookup_dirhandle(
+ UnixSftpServer *uss, SftpReplyBuilder *reply, ptrlen handle)
+{
+ struct uss_dirhandle *udh = uss_try_lookup_dirhandle(uss, handle);
+ if (!udh)
+ fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle");
+ return udh;
+}
+
+static void uss_error(UnixSftpServer *uss, SftpReplyBuilder *reply)
+{
+ unsigned code = SSH_FX_FAILURE;
+ switch (errno) {
+ case ENOENT:
+ code = SSH_FX_NO_SUCH_FILE;
+ break;
+ case EPERM:
+ code = SSH_FX_PERMISSION_DENIED;
+ break;
+ }
+ fxp_reply_error(reply, code, strerror(errno));
+}
+
+static void uss_realpath(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+
+ char *inpath = mkstr(path);
+ char *outpath = realpath(inpath, NULL);
+ free(inpath);
+
+ if (!outpath) {
+ uss_error(uss, reply);
+ } else {
+ fxp_reply_simple_name(reply, ptrlen_from_asciz(outpath));
+ free(outpath);
+ }
+}
+
+static void uss_open(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path, unsigned flags, struct fxp_attrs attrs)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+
+ int openflags = 0;
+ if (!((SSH_FXF_READ | SSH_FXF_WRITE) &~ flags))
+ openflags |= O_RDWR;
+ else if (flags & SSH_FXF_WRITE)
+ openflags |= O_WRONLY;
+ else if (flags & SSH_FXF_READ)
+ openflags |= O_RDONLY;
+ if (flags & SSH_FXF_APPEND)
+ openflags |= O_APPEND;
+ if (flags & SSH_FXF_CREAT)
+ openflags |= O_CREAT;
+ if (flags & SSH_FXF_TRUNC)
+ openflags |= O_TRUNC;
+ if (flags & SSH_FXF_EXCL)
+ openflags |= O_EXCL;
+
+ char *pathstr = mkstr(path);
+ int fd = open(pathstr, openflags, GET_PERMISSIONS(attrs, 0777));
+ free(pathstr);
+
+ if (fd < 0) {
+ uss_error(uss, reply);
+ } else {
+ uss_return_new_handle(uss, reply, fd);
+ }
+}
+
+static void uss_opendir(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+
+ char *pathstr = mkstr(path);
+ DIR *dp = opendir(pathstr);
+ free(pathstr);
+
+ if (!dp) {
+ uss_error(uss, reply);
+ } else {
+ uss_return_new_dirhandle(uss, reply, dp);
+ }
+}
+
+static void uss_close(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+ int fd;
+ struct uss_dirhandle *udh;
+
+ if ((udh = uss_try_lookup_dirhandle(uss, handle)) != NULL) {
+ closedir(udh->dp);
+ del234(uss->dirhandles, udh);
+ sfree(udh);
+ fxp_reply_ok(reply);
+ } else if ((fd = uss_lookup_fd(uss, reply, handle)) >= 0) {
+ close(fd);
+ assert(0 <= fd && fd <= uss->fdsize);
+ uss->fdsopen[fd] = false;
+ fxp_reply_ok(reply);
+ }
+ /* if both failed, uss_lookup_fd will have filled in an error response */
+}
+
+static void uss_mkdir(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path, struct fxp_attrs attrs)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+
+ char *pathstr = mkstr(path);
+ int status = mkdir(pathstr, GET_PERMISSIONS(attrs, 0777));
+ free(pathstr);
+
+ if (status < 0) {
+ uss_error(uss, reply);
+ } else {
+ fxp_reply_ok(reply);
+ }
+}
+
+static void uss_rmdir(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+
+ char *pathstr = mkstr(path);
+ int status = rmdir(pathstr);
+ free(pathstr);
+
+ if (status < 0) {
+ uss_error(uss, reply);
+ } else {
+ fxp_reply_ok(reply);
+ }
+}
+
+static void uss_remove(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+
+ char *pathstr = mkstr(path);
+ int status = unlink(pathstr);
+ free(pathstr);
+
+ if (status < 0) {
+ uss_error(uss, reply);
+ } else {
+ fxp_reply_ok(reply);
+ }
+}
+
+static void uss_rename(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen srcpath, ptrlen dstpath)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+
+ char *srcstr = mkstr(srcpath), *dststr = mkstr(dstpath);
+ int status = rename(srcstr, dststr);
+ free(srcstr);
+ free(dststr);
+
+ if (status < 0) {
+ uss_error(uss, reply);
+ } else {
+ fxp_reply_ok(reply);
+ }
+}
+
+static struct fxp_attrs uss_translate_struct_stat(const struct stat *st)
+{
+ struct fxp_attrs attrs;
+
+ attrs.flags = (SSH_FILEXFER_ATTR_SIZE |
+ SSH_FILEXFER_ATTR_PERMISSIONS |
+ SSH_FILEXFER_ATTR_UIDGID |
+ SSH_FILEXFER_ATTR_ACMODTIME);
+
+ attrs.size = st->st_size;
+ attrs.permissions = st->st_mode;
+ attrs.uid = st->st_uid;
+ attrs.gid = st->st_gid;
+ attrs.atime = st->st_atime;
+ attrs.mtime = st->st_mtime;
+
+ return attrs;
+}
+
+static void uss_reply_struct_stat(SftpReplyBuilder *reply,
+ const struct stat *st)
+{
+ fxp_reply_attrs(reply, uss_translate_struct_stat(st));
+}
+
+static void uss_stat(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path, bool follow_symlinks)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+ struct stat st;
+
+ char *pathstr = mkstr(path);
+ int status = (follow_symlinks ? stat : lstat) (pathstr, &st);
+ free(pathstr);
+
+ if (status < 0) {
+ uss_error(uss, reply);
+ } else {
+ uss_reply_struct_stat(reply, &st);
+ }
+}
+
+static void uss_fstat(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+ struct stat st;
+ int fd;
+
+ if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
+ return;
+ int status = fstat(fd, &st);
+
+ if (status < 0) {
+ uss_error(uss, reply);
+ } else {
+ uss_reply_struct_stat(reply, &st);
+ }
+}
+
+#if !HAVE_FUTIMES
+static inline int futimes(int fd, const struct timeval tv[2])
+{
+ /* If the OS doesn't support futimes(3) then we have to pretend it
+ * always returns failure */
+ errno = EINVAL;
+ return -1;
+}
+#endif
+
+/*
+ * The guts of setstat and fsetstat, macroised so that they can call
+ * fchown(fd,...) or chown(path,...) depending on parameters.
+ */
+#define SETSTAT_GUTS(api_prefix, api_arg, attrs, success) do \
+ { \
+ if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) \
+ if (api_prefix(truncate)(api_arg, attrs.size) < 0) \
+ success = false; \
+ if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) \
+ if (api_prefix(chown)(api_arg, attrs.uid, attrs.gid) < 0) \
+ success = false; \
+ if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) \
+ if (api_prefix(chmod)(api_arg, attrs.permissions) < 0) \
+ success = false; \
+ if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) { \
+ struct timeval tv[2]; \
+ tv[0].tv_sec = attrs.atime; \
+ tv[1].tv_sec = attrs.mtime; \
+ tv[0].tv_usec = tv[1].tv_usec = 0; \
+ if (api_prefix(utimes)(api_arg, tv) < 0) \
+ success = false; \
+ } \
+ } while (0)
+
+#define PATH_PREFIX(func) func
+#define FD_PREFIX(func) f ## func
+
+static void uss_setstat(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen path, struct fxp_attrs attrs)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+
+ char *pathstr = mkstr(path);
+ bool success = true;
+ SETSTAT_GUTS(PATH_PREFIX, pathstr, attrs, success);
+ free(pathstr);
+
+ if (!success) {
+ uss_error(uss, reply);
+ } else {
+ fxp_reply_ok(reply);
+ }
+}
+
+static void uss_fsetstat(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle, struct fxp_attrs attrs)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+ int fd;
+
+ if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
+ return;
+
+ bool success = true;
+ SETSTAT_GUTS(FD_PREFIX, fd, attrs, success);
+
+ if (!success) {
+ uss_error(uss, reply);
+ } else {
+ fxp_reply_ok(reply);
+ }
+}
+
+static void uss_read(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle, uint64_t offset, unsigned length)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+ int fd;
+ char *buf;
+
+ if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
+ return;
+
+ if ((buf = malloc(length)) == NULL) {
+ /* A rare case in which I bother to check malloc failure,
+ * because in this case we can localise the problem easily by
+ * turning it into a failure response from this one sftp
+ * request */
+ fxp_reply_error(reply, SSH_FX_FAILURE,
+ "Out of memory for read buffer");
+ return;
+ }
+
+ char *p = buf;
+
+ int status = lseek(fd, offset, SEEK_SET);
+ if (status >= 0 || errno == ESPIPE) {
+ bool seekable = (status >= 0);
+ while (length > 0) {
+ status = read(fd, p, length);
+ if (status <= 0)
+ break;
+
+ unsigned bytes_read = status;
+ assert(bytes_read <= length);
+ length -= bytes_read;
+ p += bytes_read;
+
+ if (!seekable) {
+ /*
+ * If the seek failed because the file is fundamentally
+ * not a seekable kind of thing, abandon this loop after
+ * one attempt, i.e. we just read whatever we could get
+ * and we don't mind returning a short buffer.
+ */
+ }
+ }
+ }
+
+ if (status < 0) {
+ uss_error(uss, reply);
+ } else if (p == buf) {
+ fxp_reply_error(reply, SSH_FX_EOF, "End of file");
+ } else {
+ fxp_reply_data(reply, make_ptrlen(buf, p - buf));
+ }
+
+ free(buf);
+}
+
+static void uss_write(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle, uint64_t offset, ptrlen data)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+ int fd;
+
+ if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
+ return;
+
+ const char *p = data.ptr;
+ unsigned length = data.len;
+
+ int status = lseek(fd, offset, SEEK_SET);
+ if (status >= 0 || errno == ESPIPE) {
+
+ while (length > 0) {
+ status = write(fd, p, length);
+ assert(status != 0);
+ if (status < 0)
+ break;
+
+ unsigned bytes_written = status;
+ assert(bytes_written <= length);
+ length -= bytes_written;
+ p += bytes_written;
+ }
+ }
+
+ if (status < 0) {
+ uss_error(uss, reply);
+ } else {
+ fxp_reply_ok(reply);
+ }
+}
+
+static void uss_readdir(SftpServer *srv, SftpReplyBuilder *reply,
+ ptrlen handle, int max_entries, bool omit_longname)
+{
+ UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
+ struct dirent *de;
+ struct uss_dirhandle *udh;
+
+ if ((udh = uss_lookup_dirhandle(uss, reply, handle)) == NULL)
+ return;
+
+ errno = 0;
+ de = readdir(udh->dp);
+ if (!de) {
+ if (errno == 0) {
+ fxp_reply_error(reply, SSH_FX_EOF, "End of directory");
+ } else {
+ uss_error(uss, reply);
+ }
+ } else {
+ ptrlen longname = PTRLEN_LITERAL("");
+ char *longnamebuf = NULL;
+ struct fxp_attrs attrs = no_attrs;
+
+#if defined HAVE_FSTATAT && defined HAVE_DIRFD
+ struct stat st;
+ if (!fstatat(dirfd(udh->dp), de->d_name, &st, AT_SYMLINK_NOFOLLOW)) {
+ char perms[11], *uidbuf = NULL, *gidbuf = NULL;
+ struct passwd *pwd;
+ struct group *grp;
+ const char *user, *group;
+ struct tm tm;
+
+ attrs = uss_translate_struct_stat(&st);
+
+ if (!omit_longname) {
+
+ strcpy(perms, "----------");
+ switch (st.st_mode & S_IFMT) {
+ case S_IFBLK: perms[0] = 'b'; break;
+ case S_IFCHR: perms[0] = 'c'; break;
+ case S_IFDIR: perms[0] = 'd'; break;
+ case S_IFIFO: perms[0] = 'p'; break;
+ case S_IFLNK: perms[0] = 'l'; break;
+ case S_IFSOCK: perms[0] = 's'; break;
+ }
+ if (st.st_mode & S_IRUSR)
+ perms[1] = 'r';
+ if (st.st_mode & S_IWUSR)
+ perms[2] = 'w';
+ if (st.st_mode & S_IXUSR)
+ perms[3] = (st.st_mode & S_ISUID ? 's' : 'x');
+ else
+ perms[3] = (st.st_mode & S_ISUID ? 'S' : '-');
+ if (st.st_mode & S_IRGRP)
+ perms[4] = 'r';
+ if (st.st_mode & S_IWGRP)
+ perms[5] = 'w';
+ if (st.st_mode & S_IXGRP)
+ perms[6] = (st.st_mode & S_ISGID ? 's' : 'x');
+ else
+ perms[6] = (st.st_mode & S_ISGID ? 'S' : '-');
+ if (st.st_mode & S_IROTH)
+ perms[7] = 'r';
+ if (st.st_mode & S_IWOTH)
+ perms[8] = 'w';
+ if (st.st_mode & S_IXOTH)
+ perms[9] = 'x';
+
+ if ((pwd = getpwuid(st.st_uid)) != NULL)
+ user = pwd->pw_name;
+ else
+ user = uidbuf = dupprintf("%u", (unsigned)st.st_uid);
+ if ((grp = getgrgid(st.st_gid)) != NULL)
+ group = grp->gr_name;
+ else
+ group = gidbuf = dupprintf("%u", (unsigned)st.st_gid);
+
+ tm = *localtime(&st.st_mtime);
+
+ longnamebuf = dupprintf(
+ "%s %3u %-8s %-8s %8"PRIuMAX" %.3s %2d %02d:%02d %s",
+ perms, (unsigned)st.st_nlink, user, group,
+ (uintmax_t)st.st_size,
+ (&"JanFebMarAprMayJunJulAugSepOctNovDec"[3*tm.tm_mon]),
+ tm.tm_mday, tm.tm_hour, tm.tm_min, de->d_name);
+ longname = ptrlen_from_asciz(longnamebuf);
+
+ sfree(uidbuf);
+ sfree(gidbuf);
+ }
+ }
+#endif
+
+ /* FIXME: be able to return more than one, in which case we
+ * must also check max_entries */
+ fxp_reply_name_count(reply, 1);
+ fxp_reply_full_name(reply, ptrlen_from_asciz(de->d_name),
+ longname, attrs);
+
+ sfree(longnamebuf);
+ }
+}
+
+const SftpServerVtable unix_live_sftpserver_vt = {
+ .new = uss_new,
+ .free = uss_free,
+ .realpath = uss_realpath,
+ .open = uss_open,
+ .opendir = uss_opendir,
+ .close = uss_close,
+ .mkdir = uss_mkdir,
+ .rmdir = uss_rmdir,
+ .remove = uss_remove,
+ .rename = uss_rename,
+ .stat = uss_stat,
+ .fstat = uss_fstat,
+ .setstat = uss_setstat,
+ .fsetstat = uss_fsetstat,
+ .read = uss_read,
+ .write = uss_write,
+ .readdir = uss_readdir,
+};
diff --git a/unix/sharing.c b/unix/sharing.c
new file mode 100644
index 00000000..8db2d71e
--- /dev/null
+++ b/unix/sharing.c
@@ -0,0 +1,370 @@
+/*
+ * Unix implementation of SSH connection-sharing IPC setup.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/file.h>
+
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+#include "proxy/proxy.h"
+#include "ssh.h"
+
+#define CONNSHARE_SOCKETDIR_PREFIX "/tmp/putty-connshare"
+#define SALT_FILENAME "salt"
+#define SALT_SIZE 64
+#ifndef PIPE_BUF
+#define PIPE_BUF _POSIX_PIPE_BUF
+#endif
+
+static char *make_parentdir_name(void)
+{
+ char *username, *parent;
+
+ username = get_username();
+ parent = dupprintf("%s.%s", CONNSHARE_SOCKETDIR_PREFIX, username);
+ sfree(username);
+ assert(*parent == '/');
+
+ return parent;
+}
+
+static char *make_dirname(const char *pi_name, char **logtext)
+{
+ char *name, *parentdirname, *dirname, *err;
+
+ /*
+ * First, create the top-level directory for all shared PuTTY
+ * connections owned by this user.
+ */
+ parentdirname = make_parentdir_name();
+ if ((err = make_dir_and_check_ours(parentdirname)) != NULL) {
+ *logtext = err;
+ sfree(parentdirname);
+ return NULL;
+ }
+
+ /*
+ * Transform the platform-independent version of the connection
+ * identifier into the name we'll actually use for the directory
+ * containing the Unix socket.
+ *
+ * We do this by hashing the identifier with some user-specific
+ * secret information, to avoid the privacy leak of having
+ * "user@host" strings show up in 'netstat -x'. (Irritatingly, the
+ * full pathname of a Unix-domain socket _does_ show up in the
+ * 'netstat -x' output, at least on Linux, even if that socket is
+ * in a directory not readable to the user running netstat. You'd
+ * think putting things inside an 0700 directory would hide their
+ * names from other users, but no.)
+ *
+ * The secret information we use to salt the hash lives in a file
+ * inside the top-level directory we just created, so we must
+ * first create that file (with some fresh random data in it) if
+ * it's not already been done by a previous PuTTY.
+ */
+ {
+ unsigned char saltbuf[SALT_SIZE];
+ char *saltname;
+ int saltfd, i, ret;
+
+ saltname = dupprintf("%s/%s", parentdirname, SALT_FILENAME);
+ saltfd = open(saltname, O_RDONLY);
+ if (saltfd < 0) {
+ char *tmpname;
+ int pid;
+
+ if (errno != ENOENT) {
+ *logtext = dupprintf("%s: open: %s", saltname,
+ strerror(errno));
+ sfree(saltname);
+ sfree(parentdirname);
+ return NULL;
+ }
+
+ /*
+ * The salt file doesn't already exist, so try to create
+ * it. Another process may be attempting the same thing
+ * simultaneously, so we must do this carefully: we write
+ * a salt file under a different name, then hard-link it
+ * into place, which guarantees that we won't change the
+ * contents of an existing salt file.
+ */
+ pid = getpid();
+ for (i = 0;; i++) {
+ tmpname = dupprintf("%s/%s.tmp.%d.%d",
+ parentdirname, SALT_FILENAME, pid, i);
+ saltfd = open(tmpname, O_WRONLY | O_EXCL | O_CREAT, 0400);
+ if (saltfd >= 0)
+ break;
+ if (errno != EEXIST) {
+ *logtext = dupprintf("%s: open: %s", tmpname,
+ strerror(errno));
+ sfree(tmpname);
+ sfree(saltname);
+ sfree(parentdirname);
+ return NULL;
+ }
+ sfree(tmpname); /* go round and try again with i+1 */
+ }
+ /*
+ * Invent some random data.
+ */
+ random_read(saltbuf, SALT_SIZE);
+ ret = write(saltfd, saltbuf, SALT_SIZE);
+ /* POSIX atomicity guarantee: because we wrote less than
+ * PIPE_BUF bytes, the write either completed in full or
+ * failed. */
+ assert(SALT_SIZE < PIPE_BUF);
+ assert(ret < 0 || ret == SALT_SIZE);
+ if (ret < 0) {
+ close(saltfd);
+ *logtext = dupprintf("%s: write: %s", tmpname,
+ strerror(errno));
+ sfree(tmpname);
+ sfree(saltname);
+ sfree(parentdirname);
+ return NULL;
+ }
+ if (close(saltfd) < 0) {
+ *logtext = dupprintf("%s: close: %s", tmpname,
+ strerror(errno));
+ sfree(tmpname);
+ sfree(saltname);
+ sfree(parentdirname);
+ return NULL;
+ }
+
+ /*
+ * Now attempt to hard-link our temp file into place. We
+ * tolerate EEXIST as an outcome, because that just means
+ * another PuTTY got their attempt in before we did (and
+ * we only care that there is a valid salt file we can
+ * agree on, no matter who created it).
+ */
+ if (link(tmpname, saltname) < 0 && errno != EEXIST) {
+ *logtext = dupprintf("%s: link: %s", saltname,
+ strerror(errno));
+ sfree(tmpname);
+ sfree(saltname);
+ sfree(parentdirname);
+ return NULL;
+ }
+
+ /*
+ * Whether that succeeded or not, get rid of our temp file.
+ */
+ if (unlink(tmpname) < 0) {
+ *logtext = dupprintf("%s: unlink: %s", tmpname,
+ strerror(errno));
+ sfree(tmpname);
+ sfree(saltname);
+ sfree(parentdirname);
+ return NULL;
+ }
+
+ /*
+ * And now we've arranged for there to be a salt file, so
+ * we can try to open it for reading again and this time
+ * expect it to work.
+ */
+ sfree(tmpname);
+
+ saltfd = open(saltname, O_RDONLY);
+ if (saltfd < 0) {
+ *logtext = dupprintf("%s: open: %s", saltname,
+ strerror(errno));
+ sfree(saltname);
+ sfree(parentdirname);
+ return NULL;
+ }
+ }
+
+ for (i = 0; i < SALT_SIZE; i++) {
+ ret = read(saltfd, saltbuf, SALT_SIZE);
+ if (ret <= 0) {
+ close(saltfd);
+ *logtext = dupprintf("%s: read: %s", saltname,
+ ret == 0 ? "unexpected EOF" :
+ strerror(errno));
+ sfree(saltname);
+ sfree(parentdirname);
+ return NULL;
+ }
+ assert(0 < ret && ret <= SALT_SIZE - i);
+ i += ret;
+ }
+
+ close(saltfd);
+ sfree(saltname);
+
+ /*
+ * Now we've got our salt, hash it with the connection
+ * identifier to produce our actual socket name.
+ */
+ {
+ unsigned char digest[32];
+ char retbuf[65];
+
+ ssh_hash *h = ssh_hash_new(&ssh_sha256);
+ put_string(h, saltbuf, SALT_SIZE);
+ put_stringz(h, pi_name);
+ ssh_hash_final(h, digest);
+
+ /*
+ * And make it printable.
+ */
+ for (i = 0; i < 32; i++) {
+ sprintf(retbuf + 2*i, "%02x", digest[i]);
+ /* the last of those will also write the trailing NUL */
+ }
+
+ name = dupstr(retbuf);
+ }
+
+ smemclr(saltbuf, sizeof(saltbuf));
+ }
+
+ dirname = dupprintf("%s/%s", parentdirname, name);
+ sfree(parentdirname);
+ sfree(name);
+
+ return dirname;
+}
+
+int platform_ssh_share(const char *pi_name, Conf *conf,
+ Plug *downplug, Plug *upplug, Socket **sock,
+ char **logtext, char **ds_err, char **us_err,
+ bool can_upstream, bool can_downstream)
+{
+ char *dirname, *lockname, *sockname, *err;
+ int lockfd;
+ Socket *retsock;
+
+ /*
+ * Sort out what we're going to call the directory in which we
+ * keep the socket. This has the side effect of potentially
+ * creating its top-level containing dir and/or the salt file
+ * within that, if they don't already exist.
+ */
+ dirname = make_dirname(pi_name, logtext);
+ if (!dirname) {
+ return SHARE_NONE;
+ }
+
+ /*
+ * Now make sure the subdirectory exists.
+ */
+ if ((err = make_dir_and_check_ours(dirname)) != NULL) {
+ *logtext = err;
+ sfree(dirname);
+ return SHARE_NONE;
+ }
+
+ /*
+ * Acquire a lock on a file in that directory.
+ */
+ lockname = dupcat(dirname, "/lock");
+ lockfd = open(lockname, O_CREAT | O_RDWR | O_TRUNC, 0600);
+ if (lockfd < 0) {
+ *logtext = dupprintf("%s: open: %s", lockname, strerror(errno));
+ sfree(dirname);
+ sfree(lockname);
+ return SHARE_NONE;
+ }
+ if (flock(lockfd, LOCK_EX) < 0) {
+ *logtext = dupprintf("%s: flock(LOCK_EX): %s",
+ lockname, strerror(errno));
+ sfree(dirname);
+ sfree(lockname);
+ close(lockfd);
+ return SHARE_NONE;
+ }
+
+ sockname = dupprintf("%s/socket", dirname);
+
+ *logtext = NULL;
+
+ if (can_downstream) {
+ retsock = new_connection(unix_sock_addr(sockname),
+ "", 0, false, true, false, false,
+ downplug, conf, NULL);
+ if (sk_socket_error(retsock) == NULL) {
+ sfree(*logtext);
+ *logtext = sockname;
+ *sock = retsock;
+ sfree(dirname);
+ sfree(lockname);
+ close(lockfd);
+ return SHARE_DOWNSTREAM;
+ }
+ sfree(*ds_err);
+ *ds_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
+ sk_close(retsock);
+ }
+
+ if (can_upstream) {
+ retsock = new_unix_listener(unix_sock_addr(sockname), upplug);
+ if (sk_socket_error(retsock) == NULL) {
+ sfree(*logtext);
+ *logtext = sockname;
+ *sock = retsock;
+ sfree(dirname);
+ sfree(lockname);
+ close(lockfd);
+ return SHARE_UPSTREAM;
+ }
+ sfree(*us_err);
+ *us_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
+ sk_close(retsock);
+ }
+
+ /* One of the above clauses ought to have happened. */
+ assert(*logtext || *ds_err || *us_err);
+
+ sfree(dirname);
+ sfree(lockname);
+ sfree(sockname);
+ close(lockfd);
+ return SHARE_NONE;
+}
+
+void platform_ssh_share_cleanup(const char *name)
+{
+ char *dirname, *filename, *logtext;
+
+ dirname = make_dirname(name, &logtext);
+ if (!dirname) {
+ sfree(logtext); /* we can't do much with this */
+ return;
+ }
+
+ filename = dupcat(dirname, "/socket");
+ remove(filename);
+ sfree(filename);
+
+ filename = dupcat(dirname, "/lock");
+ remove(filename);
+ sfree(filename);
+
+ rmdir(dirname);
+
+ /*
+ * We deliberately _don't_ clean up the parent directory
+ * /tmp/putty-connshare.<username>, because if we leave it around
+ * then it reduces the ability for other users to be a nuisance by
+ * putting their own directory in the way of it. Also, the salt
+ * file in it can be reused.
+ */
+
+ sfree(dirname);
+}
diff --git a/unix/storage.c b/unix/storage.c
new file mode 100644
index 00000000..ca225732
--- /dev/null
+++ b/unix/storage.c
@@ -0,0 +1,976 @@
+/*
+ * storage.c: Unix-specific implementation of the interface defined
+ * in storage.h.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include <ctype.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include "putty.h"
+#include "storage.h"
+#include "tree234.h"
+
+#ifdef PATH_MAX
+#define FNLEN PATH_MAX
+#else
+#define FNLEN 1024 /* XXX */
+#endif
+
+enum {
+ INDEX_DIR, INDEX_HOSTKEYS, INDEX_HOSTKEYS_TMP, INDEX_RANDSEED,
+ INDEX_SESSIONDIR, INDEX_SESSION, INDEX_HOSTCADIR, INDEX_HOSTCA
+};
+
+static const char hex[16] = "0123456789ABCDEF";
+
+static void make_session_filename(const char *in, strbuf *out)
+{
+ if (!in || !*in)
+ in = "Default Settings";
+
+ while (*in) {
+ /*
+ * There are remarkably few punctuation characters that
+ * aren't shell-special in some way or likely to be used as
+ * separators in some file format or another! Hence we use
+ * opt-in for safe characters rather than opt-out for
+ * specific unsafe ones...
+ */
+ if (*in!='+' && *in!='-' && *in!='.' && *in!='@' && *in!='_' &&
+ !(*in >= '0' && *in <= '9') &&
+ !(*in >= 'A' && *in <= 'Z') &&
+ !(*in >= 'a' && *in <= 'z')) {
+ put_byte(out, '%');
+ put_byte(out, hex[((unsigned char) *in) >> 4]);
+ put_byte(out, hex[((unsigned char) *in) & 15]);
+ } else
+ put_byte(out, *in);
+ in++;
+ }
+}
+
+static void decode_session_filename(const char *in, strbuf *out)
+{
+ while (*in) {
+ if (*in == '%' && in[1] && in[2]) {
+ int i, j;
+
+ i = in[1] - '0';
+ i -= (i > 9 ? 7 : 0);
+ j = in[2] - '0';
+ j -= (j > 9 ? 7 : 0);
+
+ put_byte(out, (i << 4) + j);
+ in += 3;
+ } else {
+ put_byte(out, *in++);
+ }
+ }
+}
+
+static char *make_filename(int index, const char *subname)
+{
+ char *env, *tmp, *ret;
+
+ /*
+ * Allow override of the PuTTY configuration location, and of
+ * specific subparts of it, by means of environment variables.
+ */
+ if (index == INDEX_DIR) {
+ struct passwd *pwd;
+ char *xdg_dir, *old_dir, *old_dir2, *old_dir3, *home, *pwd_home;
+
+ env = getenv("PUTTYDIR");
+ if (env)
+ return dupstr(env);
+
+ home = getenv("HOME");
+ pwd = getpwuid(getuid());
+ if (pwd && pwd->pw_dir) {
+ pwd_home = pwd->pw_dir;
+ } else {
+ pwd_home = NULL;
+ }
+
+ xdg_dir = NULL;
+ env = getenv("XDG_CONFIG_HOME");
+ if (env && *env) {
+ xdg_dir = dupprintf("%s/putty", env);
+ }
+ if (!xdg_dir) {
+ if (home) {
+ tmp = home;
+ } else if (pwd_home) {
+ tmp = pwd_home;
+ } else {
+ tmp = "";
+ }
+ xdg_dir = dupprintf("%s/.config/putty", tmp);
+ }
+ if (xdg_dir && access(xdg_dir, F_OK) == 0) {
+ return xdg_dir;
+ }
+
+ old_dir = old_dir2 = old_dir3 = NULL;
+ if (home) {
+ old_dir = dupprintf("%s/.putty", home);
+ }
+ if (pwd_home) {
+ old_dir2 = dupprintf("%s/.putty", pwd_home);
+ }
+ old_dir3 = dupstr("/.putty");
+
+ if (old_dir && access(old_dir, F_OK) == 0) {
+ ret = old_dir;
+ goto out;
+ }
+ if (old_dir2 && access(old_dir2, F_OK) == 0) {
+ ret = old_dir2;
+ goto out;
+ }
+ if (access(old_dir3, F_OK) == 0) {
+ ret = old_dir3;
+ goto out;
+ }
+#ifdef XDG_DEFAULT
+ if (xdg_dir) {
+ ret = xdg_dir;
+ goto out;
+ }
+#endif
+ ret = old_dir ? old_dir : (old_dir2 ? old_dir2 : old_dir3);
+
+ out:
+ if (ret != old_dir)
+ sfree(old_dir);
+ if (ret != old_dir2)
+ sfree(old_dir2);
+ if (ret != old_dir3)
+ sfree(old_dir3);
+ if (ret != xdg_dir)
+ sfree(xdg_dir);
+ return ret;
+ }
+ if (index == INDEX_SESSIONDIR) {
+ env = getenv("PUTTYSESSIONS");
+ if (env)
+ return dupstr(env);
+ tmp = make_filename(INDEX_DIR, NULL);
+ ret = dupprintf("%s/sessions", tmp);
+ sfree(tmp);
+ return ret;
+ }
+ if (index == INDEX_SESSION) {
+ strbuf *sb = strbuf_new();
+ tmp = make_filename(INDEX_SESSIONDIR, NULL);
+ put_fmt(sb, "%s/", tmp);
+ sfree(tmp);
+ make_session_filename(subname, sb);
+ return strbuf_to_str(sb);
+ }
+ if (index == INDEX_HOSTKEYS) {
+ env = getenv("PUTTYSSHHOSTKEYS");
+ if (env)
+ return dupstr(env);
+ tmp = make_filename(INDEX_DIR, NULL);
+ ret = dupprintf("%s/sshhostkeys", tmp);
+ sfree(tmp);
+ return ret;
+ }
+ if (index == INDEX_HOSTKEYS_TMP) {
+ tmp = make_filename(INDEX_HOSTKEYS, NULL);
+ ret = dupprintf("%s.tmp", tmp);
+ sfree(tmp);
+ return ret;
+ }
+ if (index == INDEX_RANDSEED) {
+ env = getenv("PUTTYRANDOMSEED");
+ if (env)
+ return dupstr(env);
+ tmp = make_filename(INDEX_DIR, NULL);
+ ret = dupprintf("%s/randomseed", tmp);
+ sfree(tmp);
+ return ret;
+ }
+ if (index == INDEX_HOSTCADIR) {
+ env = getenv("PUTTYSSHHOSTCAS");
+ if (env)
+ return dupstr(env);
+ tmp = make_filename(INDEX_DIR, NULL);
+ ret = dupprintf("%s/sshhostcas", tmp);
+ sfree(tmp);
+ return ret;
+ }
+ if (index == INDEX_HOSTCA) {
+ strbuf *sb = strbuf_new();
+ tmp = make_filename(INDEX_HOSTCADIR, NULL);
+ put_fmt(sb, "%s/", tmp);
+ sfree(tmp);
+ make_session_filename(subname, sb);
+ return strbuf_to_str(sb);
+ }
+ tmp = make_filename(INDEX_DIR, NULL);
+ ret = dupprintf("%s/ERROR", tmp);
+ sfree(tmp);
+ return ret;
+}
+
+struct settings_w {
+ FILE *fp;
+};
+
+settings_w *open_settings_w(const char *sessionname, char **errmsg)
+{
+ char *filename, *err;
+ FILE *fp;
+
+ *errmsg = NULL;
+
+ /*
+ * Start by making sure the .putty directory and its sessions
+ * subdir actually exist.
+ */
+ filename = make_filename(INDEX_DIR, NULL);
+ if ((err = make_dir_path(filename, 0700)) != NULL) {
+ *errmsg = dupprintf("Unable to save session: %s", err);
+ sfree(err);
+ sfree(filename);
+ return NULL;
+ }
+ sfree(filename);
+
+ filename = make_filename(INDEX_SESSIONDIR, NULL);
+ if ((err = make_dir_path(filename, 0700)) != NULL) {
+ *errmsg = dupprintf("Unable to save session: %s", err);
+ sfree(err);
+ sfree(filename);
+ return NULL;
+ }
+ sfree(filename);
+
+ filename = make_filename(INDEX_SESSION, sessionname);
+ fp = fopen(filename, "w");
+ if (!fp) {
+ *errmsg = dupprintf("Unable to save session: open(\"%s\") "
+ "returned '%s'", filename, strerror(errno));
+ sfree(filename);
+ return NULL; /* can't open */
+ }
+ sfree(filename);
+
+ settings_w *toret = snew(settings_w);
+ toret->fp = fp;
+ return toret;
+}
+
+void write_setting_s(settings_w *handle, const char *key, const char *value)
+{
+ fprintf(handle->fp, "%s=%s\n", key, value);
+}
+
+void write_setting_i(settings_w *handle, const char *key, int value)
+{
+ fprintf(handle->fp, "%s=%d\n", key, value);
+}
+
+void close_settings_w(settings_w *handle)
+{
+ fclose(handle->fp);
+ sfree(handle);
+}
+
+/* ----------------------------------------------------------------------
+ * System for treating X resources as a fallback source of defaults,
+ * after data read from a saved-session disk file.
+ *
+ * The read_setting_* functions will call get_setting(key) as a
+ * fallback if the setting isn't in the file they loaded. That in turn
+ * will hand on to x_get_default, which the front end application
+ * provides, and which actually reads resources from the X server (if
+ * appropriate). In between, there's a tree234 of X-resource shaped
+ * settings living locally in this file: the front end can call
+ * provide_xrm_string() to insert a setting into this tree (typically
+ * in response to an -xrm command line option or similar), and those
+ * will override the actual X resources.
+ */
+
+struct skeyval {
+ const char *key;
+ const char *value;
+};
+
+static tree234 *xrmtree = NULL;
+
+static int keycmp(void *av, void *bv)
+{
+ struct skeyval *a = (struct skeyval *)av;
+ struct skeyval *b = (struct skeyval *)bv;
+ return strcmp(a->key, b->key);
+}
+
+void provide_xrm_string(const char *string, const char *progname)
+{
+ const char *p, *q;
+ char *key;
+ struct skeyval *xrms, *ret;
+
+ p = q = strchr(string, ':');
+ if (!q) {
+ fprintf(stderr, "%s: expected a colon in resource string"
+ " \"%s\"\n", progname, string);
+ return;
+ }
+ q++;
+ while (p > string && p[-1] != '.' && p[-1] != '*')
+ p--;
+ xrms = snew(struct skeyval);
+ key = snewn(q-p, char);
+ memcpy(key, p, q-p);
+ key[q-p-1] = '\0';
+ xrms->key = key;
+ while (*q && isspace((unsigned char)*q))
+ q++;
+ xrms->value = dupstr(q);
+
+ if (!xrmtree)
+ xrmtree = newtree234(keycmp);
+
+ ret = add234(xrmtree, xrms);
+ if (ret) {
+ /* Override an existing string. */
+ del234(xrmtree, ret);
+ add234(xrmtree, xrms);
+ }
+}
+
+static const char *get_setting(const char *key)
+{
+ struct skeyval tmp, *ret;
+ tmp.key = key;
+ if (xrmtree) {
+ ret = find234(xrmtree, &tmp, NULL);
+ if (ret)
+ return ret->value;
+ }
+ return x_get_default(key);
+}
+
+/* ----------------------------------------------------------------------
+ * Main code for reading settings from a disk file, calling the above
+ * get_setting() as a fallback if necessary.
+ */
+
+struct settings_r {
+ tree234 *t;
+};
+
+settings_r *open_settings_r(const char *sessionname)
+{
+ char *filename;
+ FILE *fp;
+ char *line;
+ settings_r *toret;
+
+ filename = make_filename(INDEX_SESSION, sessionname);
+ fp = fopen(filename, "r");
+ sfree(filename);
+ if (!fp)
+ return NULL; /* can't open */
+
+ toret = snew(settings_r);
+ toret->t = newtree234(keycmp);
+
+ while ( (line = fgetline(fp)) ) {
+ char *value = strchr(line, '=');
+ struct skeyval *kv;
+
+ if (!value) {
+ sfree(line);
+ continue;
+ }
+ *value++ = '\0';
+ value[strcspn(value, "\r\n")] = '\0'; /* trim trailing NL */
+
+ kv = snew(struct skeyval);
+ kv->key = dupstr(line);
+ kv->value = dupstr(value);
+ add234(toret->t, kv);
+
+ sfree(line);
+ }
+
+ fclose(fp);
+
+ return toret;
+}
+
+char *read_setting_s(settings_r *handle, const char *key)
+{
+ const char *val;
+ struct skeyval tmp, *kv;
+
+ tmp.key = key;
+ if (handle != NULL &&
+ (kv = find234(handle->t, &tmp, NULL)) != NULL) {
+ val = kv->value;
+ assert(val != NULL);
+ } else
+ val = get_setting(key);
+
+ if (!val)
+ return NULL;
+ else
+ return dupstr(val);
+}
+
+int read_setting_i(settings_r *handle, const char *key, int defvalue)
+{
+ const char *val;
+ struct skeyval tmp, *kv;
+
+ tmp.key = key;
+ if (handle != NULL &&
+ (kv = find234(handle->t, &tmp, NULL)) != NULL) {
+ val = kv->value;
+ assert(val != NULL);
+ } else
+ val = get_setting(key);
+
+ if (!val)
+ return defvalue;
+ else
+ return atoi(val);
+}
+
+FontSpec *read_setting_fontspec(settings_r *handle, const char *name)
+{
+ /*
+ * In GTK1-only PuTTY, we used to store font names simply as a
+ * valid X font description string (logical or alias), under a
+ * bare key such as "Font".
+ *
+ * In GTK2 PuTTY, we have a prefix system where "client:"
+ * indicates a Pango font and "server:" an X one; existing
+ * configuration needs to be reinterpreted as having the
+ * "server:" prefix, so we change the storage key from the
+ * provided name string (e.g. "Font") to a suffixed one
+ * ("FontName").
+ */
+ char *suffname = dupcat(name, "Name");
+ char *tmp;
+
+ if ((tmp = read_setting_s(handle, suffname)) != NULL) {
+ FontSpec *fs = fontspec_new(tmp);
+ sfree(suffname);
+ sfree(tmp);
+ return fs; /* got new-style name */
+ }
+ sfree(suffname);
+
+ /* Fall back to old-style name. */
+ tmp = read_setting_s(handle, name);
+ if (tmp && *tmp) {
+ char *tmp2 = dupcat("server:", tmp);
+ FontSpec *fs = fontspec_new(tmp2);
+ sfree(tmp2);
+ sfree(tmp);
+ return fs;
+ } else {
+ sfree(tmp);
+ return NULL;
+ }
+}
+Filename *read_setting_filename(settings_r *handle, const char *name)
+{
+ char *tmp = read_setting_s(handle, name);
+ if (tmp) {
+ Filename *ret = filename_from_str(tmp);
+ sfree(tmp);
+ return ret;
+ } else
+ return NULL;
+}
+
+void write_setting_fontspec(settings_w *handle, const char *name, FontSpec *fs)
+{
+ /*
+ * read_setting_fontspec had to handle two cases, but when
+ * writing our settings back out we simply always generate the
+ * new-style name.
+ */
+ char *suffname = dupcat(name, "Name");
+ write_setting_s(handle, suffname, fs->name);
+ sfree(suffname);
+}
+void write_setting_filename(settings_w *handle,
+ const char *name, Filename *result)
+{
+ write_setting_s(handle, name, result->path);
+}
+
+void close_settings_r(settings_r *handle)
+{
+ struct skeyval *kv;
+
+ if (!handle)
+ return;
+
+ while ( (kv = index234(handle->t, 0)) != NULL) {
+ del234(handle->t, kv);
+ sfree((char *)kv->key);
+ sfree((char *)kv->value);
+ sfree(kv);
+ }
+
+ freetree234(handle->t);
+ sfree(handle);
+}
+
+void del_settings(const char *sessionname)
+{
+ char *filename;
+ filename = make_filename(INDEX_SESSION, sessionname);
+ unlink(filename);
+ sfree(filename);
+}
+
+struct settings_e {
+ DIR *dp;
+};
+
+settings_e *enum_settings_start(void)
+{
+ DIR *dp;
+ char *filename;
+
+ filename = make_filename(INDEX_SESSIONDIR, NULL);
+ dp = opendir(filename);
+ sfree(filename);
+
+ settings_e *toret = snew(settings_e);
+ toret->dp = dp;
+ return toret;
+}
+
+static bool enum_dir_next(DIR *dp, int index, strbuf *out)
+{
+ struct dirent *de;
+ struct stat st;
+ strbuf *fullpath;
+
+ if (!dp)
+ return false;
+
+ fullpath = strbuf_new();
+
+ char *sessiondir = make_filename(index, NULL);
+ put_dataz(fullpath, sessiondir);
+ sfree(sessiondir);
+ put_byte(fullpath, '/');
+
+ size_t baselen = fullpath->len;
+
+ while ( (de = readdir(dp)) != NULL ) {
+ strbuf_shrink_to(fullpath, baselen);
+ put_dataz(fullpath, de->d_name);
+
+ if (stat(fullpath->s, &st) < 0 || !S_ISREG(st.st_mode))
+ continue; /* try another one */
+
+ decode_session_filename(de->d_name, out);
+ strbuf_free(fullpath);
+ return true;
+ }
+
+ strbuf_free(fullpath);
+ return false;
+}
+
+bool enum_settings_next(settings_e *handle, strbuf *out)
+{
+ return enum_dir_next(handle->dp, INDEX_SESSIONDIR, out);
+}
+
+void enum_settings_finish(settings_e *handle)
+{
+ if (handle->dp)
+ closedir(handle->dp);
+ sfree(handle);
+}
+
+struct host_ca_enum {
+ DIR *dp;
+};
+
+host_ca_enum *enum_host_ca_start(void)
+{
+ host_ca_enum *handle = snew(host_ca_enum);
+
+ char *filename = make_filename(INDEX_HOSTCADIR, NULL);
+ handle->dp = opendir(filename);
+ sfree(filename);
+
+ return handle;
+}
+
+bool enum_host_ca_next(host_ca_enum *handle, strbuf *out)
+{
+ return enum_dir_next(handle->dp, INDEX_HOSTCADIR, out);
+}
+
+void enum_host_ca_finish(host_ca_enum *handle)
+{
+ if (handle->dp)
+ closedir(handle->dp);
+ sfree(handle);
+}
+
+host_ca *host_ca_load(const char *name)
+{
+ char *filename = make_filename(INDEX_HOSTCA, name);
+ FILE *fp = fopen(filename, "r");
+ sfree(filename);
+ if (!fp)
+ return NULL;
+
+ host_ca *hca = host_ca_new();
+ hca->name = dupstr(name);
+
+ char *line;
+ CertExprBuilder *eb = NULL;
+
+ while ( (line = fgetline(fp)) ) {
+ char *value = strchr(line, '=');
+
+ if (!value) {
+ sfree(line);
+ continue;
+ }
+ *value++ = '\0';
+ value[strcspn(value, "\r\n")] = '\0'; /* trim trailing NL */
+
+ if (!strcmp(line, "PublicKey")) {
+ hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(value));
+ } else if (!strcmp(line, "MatchHosts")) {
+ if (!eb)
+ eb = cert_expr_builder_new();
+ cert_expr_builder_add(eb, value);
+ } else if (!strcmp(line, "Validity")) {
+ hca->validity_expression = strbuf_to_str(
+ percent_decode_sb(ptrlen_from_asciz(value)));
+ } else if (!strcmp(line, "PermitRSASHA1")) {
+ hca->opts.permit_rsa_sha1 = atoi(value);
+ } else if (!strcmp(line, "PermitRSASHA256")) {
+ hca->opts.permit_rsa_sha256 = atoi(value);
+ } else if (!strcmp(line, "PermitRSASHA512")) {
+ hca->opts.permit_rsa_sha512 = atoi(value);
+ }
+
+ sfree(line);
+ }
+
+ fclose(fp);
+
+ if (eb) {
+ if (!hca->validity_expression) {
+ hca->validity_expression = cert_expr_expression(eb);
+ }
+ cert_expr_builder_free(eb);
+ }
+
+ return hca;
+}
+
+char *host_ca_save(host_ca *hca)
+{
+ if (!*hca->name)
+ return dupstr("CA record must have a name");
+
+ char *filename = make_filename(INDEX_HOSTCA, hca->name);
+ FILE *fp = fopen(filename, "w");
+ if (!fp)
+ return dupprintf("Unable to open file '%s'", filename);
+
+ fprintf(fp, "PublicKey=");
+ base64_encode_fp(fp, ptrlen_from_strbuf(hca->ca_public_key), 0);
+ fprintf(fp, "\n");
+
+ fprintf(fp, "Validity=");
+ percent_encode_fp(fp, ptrlen_from_asciz(hca->validity_expression), NULL);
+ fprintf(fp, "\n");
+
+ fprintf(fp, "PermitRSASHA1=%d\n", (int)hca->opts.permit_rsa_sha1);
+ fprintf(fp, "PermitRSASHA256=%d\n", (int)hca->opts.permit_rsa_sha256);
+ fprintf(fp, "PermitRSASHA512=%d\n", (int)hca->opts.permit_rsa_sha512);
+
+ bool bad = ferror(fp);
+ if (fclose(fp) < 0)
+ bad = true;
+
+ char *err = NULL;
+ if (bad)
+ err = dupprintf("Unable to write file '%s'", filename);
+
+ sfree(filename);
+ return err;
+}
+
+char *host_ca_delete(const char *name)
+{
+ if (!*name)
+ return dupstr("CA record must have a name");
+ char *filename = make_filename(INDEX_HOSTCA, name);
+ bool bad = remove(filename) < 0;
+
+ char *err = NULL;
+ if (bad)
+ err = dupprintf("Unable to delete file '%s'", filename);
+
+ sfree(filename);
+ return err;
+}
+
+/*
+ * Lines in the host keys file are of the form
+ *
+ * type@port:hostname keydata
+ *
+ * e.g.
+ *
+ * rsa@22:foovax.example.org 0x23,0x293487364395345345....2343
+ */
+int check_stored_host_key(const char *hostname, int port,
+ const char *keytype, const char *key)
+{
+ FILE *fp;
+ char *filename;
+ char *line;
+ int ret;
+
+ filename = make_filename(INDEX_HOSTKEYS, NULL);
+ fp = fopen(filename, "r");
+ sfree(filename);
+ if (!fp)
+ return 1; /* key does not exist */
+
+ ret = 1;
+ while ( (line = fgetline(fp)) ) {
+ int i;
+ char *p = line;
+ char porttext[20];
+
+ line[strcspn(line, "\n")] = '\0'; /* strip trailing newline */
+
+ i = strlen(keytype);
+ if (strncmp(p, keytype, i))
+ goto done;
+ p += i;
+
+ if (*p != '@')
+ goto done;
+ p++;
+
+ sprintf(porttext, "%d", port);
+ i = strlen(porttext);
+ if (strncmp(p, porttext, i))
+ goto done;
+ p += i;
+
+ if (*p != ':')
+ goto done;
+ p++;
+
+ i = strlen(hostname);
+ if (strncmp(p, hostname, i))
+ goto done;
+ p += i;
+
+ if (*p != ' ')
+ goto done;
+ p++;
+
+ /*
+ * Found the key. Now just work out whether it's the right
+ * one or not.
+ */
+ if (!strcmp(p, key))
+ ret = 0; /* key matched OK */
+ else
+ ret = 2; /* key mismatch */
+
+ done:
+ sfree(line);
+ if (ret != 1)
+ break;
+ }
+
+ fclose(fp);
+ return ret;
+}
+
+bool have_ssh_host_key(const char *hostname, int port,
+ const char *keytype)
+{
+ /*
+ * If we have a host key, check_stored_host_key will return 0 or 2.
+ * If we don't have one, it'll return 1.
+ */
+ return check_stored_host_key(hostname, port, keytype, "") != 1;
+}
+
+void store_host_key(const char *hostname, int port,
+ const char *keytype, const char *key)
+{
+ FILE *rfp, *wfp;
+ char *newtext, *line;
+ int headerlen;
+ char *filename, *tmpfilename;
+
+ /*
+ * Open both the old file and a new file.
+ */
+ tmpfilename = make_filename(INDEX_HOSTKEYS_TMP, NULL);
+ wfp = fopen(tmpfilename, "w");
+ if (!wfp && errno == ENOENT) {
+ char *dir, *errmsg;
+
+ dir = make_filename(INDEX_DIR, NULL);
+ if ((errmsg = make_dir_path(dir, 0700)) != NULL) {
+ nonfatal("Unable to store host key: %s", errmsg);
+ sfree(errmsg);
+ sfree(dir);
+ sfree(tmpfilename);
+ return;
+ }
+ sfree(dir);
+
+ wfp = fopen(tmpfilename, "w");
+ }
+ if (!wfp) {
+ nonfatal("Unable to store host key: open(\"%s\") "
+ "returned '%s'", tmpfilename, strerror(errno));
+ sfree(tmpfilename);
+ return;
+ }
+ filename = make_filename(INDEX_HOSTKEYS, NULL);
+ rfp = fopen(filename, "r");
+
+ newtext = dupprintf("%s@%d:%s %s\n", keytype, port, hostname, key);
+ headerlen = 1 + strcspn(newtext, " "); /* count the space too */
+
+ /*
+ * Copy all lines from the old file to the new one that _don't_
+ * involve the same host key identifier as the one we're adding.
+ */
+ if (rfp) {
+ while ( (line = fgetline(rfp)) ) {
+ if (strncmp(line, newtext, headerlen))
+ fputs(line, wfp);
+ sfree(line);
+ }
+ fclose(rfp);
+ }
+
+ /*
+ * Now add the new line at the end.
+ */
+ fputs(newtext, wfp);
+
+ fclose(wfp);
+
+ if (rename(tmpfilename, filename) < 0) {
+ nonfatal("Unable to store host key: rename(\"%s\",\"%s\")"
+ " returned '%s'", tmpfilename, filename,
+ strerror(errno));
+ }
+
+ sfree(tmpfilename);
+ sfree(filename);
+ sfree(newtext);
+}
+
+void read_random_seed(noise_consumer_t consumer)
+{
+ int fd;
+ char *fname;
+
+ fname = make_filename(INDEX_RANDSEED, NULL);
+ fd = open(fname, O_RDONLY);
+ sfree(fname);
+ if (fd >= 0) {
+ char buf[512];
+ int ret;
+ while ( (ret = read(fd, buf, sizeof(buf))) > 0)
+ consumer(buf, ret);
+ close(fd);
+ }
+}
+
+void write_random_seed(void *data, int len)
+{
+ int fd;
+ char *fname;
+
+ fname = make_filename(INDEX_RANDSEED, NULL);
+ /*
+ * Don't truncate the random seed file if it already exists; if
+ * something goes wrong half way through writing it, it would
+ * be better to leave the old data there than to leave it empty.
+ */
+ fd = open(fname, O_CREAT | O_WRONLY, 0600);
+ if (fd < 0) {
+ if (errno != ENOENT) {
+ nonfatal("Unable to write random seed: open(\"%s\") "
+ "returned '%s'", fname, strerror(errno));
+ sfree(fname);
+ return;
+ }
+ char *dir, *errmsg;
+
+ dir = make_filename(INDEX_DIR, NULL);
+ if ((errmsg = make_dir_path(dir, 0700)) != NULL) {
+ nonfatal("Unable to write random seed: %s", errmsg);
+ sfree(errmsg);
+ sfree(fname);
+ sfree(dir);
+ return;
+ }
+ sfree(dir);
+
+ fd = open(fname, O_CREAT | O_WRONLY, 0600);
+ if (fd < 0) {
+ nonfatal("Unable to write random seed: open(\"%s\") "
+ "returned '%s'", fname, strerror(errno));
+ sfree(fname);
+ return;
+ }
+ }
+
+ while (len > 0) {
+ int ret = write(fd, data, len);
+ if (ret < 0) {
+ nonfatal("Unable to write random seed: write "
+ "returned '%s'", strerror(errno));
+ break;
+ }
+ len -= ret;
+ data = (char *)data + len;
+ }
+
+ close(fd);
+ sfree(fname);
+}
+
+void cleanup_all(void)
+{
+}
diff --git a/unix/unicode.c b/unix/unicode.c
new file mode 100644
index 00000000..a98c8d3b
--- /dev/null
+++ b/unix/unicode.c
@@ -0,0 +1,271 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <locale.h>
+#include <limits.h>
+#include <wchar.h>
+
+#include <time.h>
+
+#include "putty.h"
+#include "charset.h"
+#include "terminal.h"
+#include "misc.h"
+
+/*
+ * Unix Unicode-handling routines.
+ */
+
+bool is_dbcs_leadbyte(int codepage, char byte)
+{
+ return false; /* we don't do DBCS */
+}
+
+int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen,
+ wchar_t *wcstr, int wclen)
+{
+ if (codepage == DEFAULT_CODEPAGE) {
+ int n = 0;
+ mbstate_t state;
+
+ memset(&state, 0, sizeof state);
+
+ while (mblen > 0) {
+ if (n >= wclen)
+ return n;
+ size_t i = mbrtowc(wcstr+n, mbstr, (size_t)mblen, &state);
+ if (i == (size_t)-1 || i == (size_t)-2)
+ break;
+ n++;
+ mbstr += i;
+ mblen -= i;
+ }
+
+ return n;
+ } else if (codepage == CS_NONE) {
+ int n = 0;
+
+ while (mblen > 0) {
+ if (n >= wclen)
+ return n;
+ wcstr[n] = 0xD800 | (mbstr[0] & 0xFF);
+ n++;
+ mbstr++;
+ mblen--;
+ }
+
+ return n;
+ } else
+ return charset_to_unicode(&mbstr, &mblen, wcstr, wclen, codepage,
+ NULL, NULL, 0);
+}
+
+int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen,
+ char *mbstr, int mblen, const char *defchr)
+{
+ if (codepage == DEFAULT_CODEPAGE) {
+ char output[MB_LEN_MAX];
+ mbstate_t state;
+ int n = 0;
+
+ memset(&state, 0, sizeof state);
+
+ while (wclen > 0) {
+ size_t i = wcrtomb(output, wcstr[0], &state);
+ if (i == (size_t)-1 || i > n - mblen)
+ break;
+ memcpy(mbstr+n, output, i);
+ n += i;
+ wcstr++;
+ wclen--;
+ }
+
+ return n;
+ } else if (codepage == CS_NONE) {
+ int n = 0;
+ while (wclen > 0 && n < mblen) {
+ if (*wcstr >= 0xD800 && *wcstr < 0xD900)
+ mbstr[n++] = (*wcstr & 0xFF);
+ else if (defchr)
+ mbstr[n++] = *defchr;
+ wcstr++;
+ wclen--;
+ }
+ return n;
+ } else {
+ return charset_from_unicode(&wcstr, &wclen, mbstr, mblen, codepage,
+ NULL, defchr?defchr:NULL, defchr?1:0);
+ }
+}
+
+/*
+ * Return value is true if pterm is to run in direct-to-font mode.
+ */
+bool init_ucs(struct unicode_data *ucsdata, char *linecharset,
+ bool utf8_override, int font_charset, int vtmode)
+{
+ int i;
+ bool ret = false;
+
+ /*
+ * In the platform-independent parts of the code, font_codepage
+ * is used only for system DBCS support - which we don't
+ * support at all. So we set this to something which will never
+ * be used.
+ */
+ ucsdata->font_codepage = -1;
+
+ /*
+ * If utf8_override is set and the POSIX locale settings
+ * dictate a UTF-8 character set, then just go straight for
+ * UTF-8.
+ */
+ ucsdata->line_codepage = CS_NONE;
+ if (utf8_override) {
+ const char *s;
+ if (((s = getenv("LC_ALL")) && *s) ||
+ ((s = getenv("LC_CTYPE")) && *s) ||
+ ((s = getenv("LANG")) && *s)) {
+ if (strstr(s, "UTF-8"))
+ ucsdata->line_codepage = CS_UTF8;
+ }
+ }
+
+ /*
+ * Failing that, line_codepage should be decoded from the
+ * specification in conf.
+ */
+ if (ucsdata->line_codepage == CS_NONE)
+ ucsdata->line_codepage = decode_codepage(linecharset);
+
+ /*
+ * If line_codepage is _still_ CS_NONE, we assume we're using
+ * the font's own encoding. This has been passed in to us, so
+ * we use that. If it's still CS_NONE after _that_ - i.e. the
+ * font we were given had an incomprehensible charset - then we
+ * fall back to using the D800 page.
+ */
+ if (ucsdata->line_codepage == CS_NONE)
+ ucsdata->line_codepage = font_charset;
+
+ if (ucsdata->line_codepage == CS_NONE)
+ ret = true;
+
+ /*
+ * Set up unitab_line, by translating each individual character
+ * in the line codepage into Unicode.
+ */
+ for (i = 0; i < 256; i++) {
+ char c[1];
+ const char *p;
+ wchar_t wc[1];
+ int len;
+ c[0] = i;
+ p = c;
+ len = 1;
+ if (ucsdata->line_codepage == CS_NONE)
+ ucsdata->unitab_line[i] = 0xD800 | i;
+ else if (1 == charset_to_unicode(&p, &len, wc, 1,
+ ucsdata->line_codepage,
+ NULL, L"", 0))
+ ucsdata->unitab_line[i] = wc[0];
+ else
+ ucsdata->unitab_line[i] = 0xFFFD;
+ }
+
+ /*
+ * Set up unitab_xterm. This is the same as unitab_line except
+ * in the line-drawing regions, where it follows the Unicode
+ * encoding.
+ *
+ * (Note that the strange X encoding of line-drawing characters
+ * in the bottom 32 glyphs of ISO8859-1 fonts is taken care of
+ * by the font encoding, which will spot such a font and act as
+ * if it were in a variant encoding of ISO8859-1.)
+ */
+ for (i = 0; i < 256; i++) {
+ static const wchar_t unitab_xterm_std[32] = {
+ 0x2666, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1,
+ 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba,
+ 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c,
+ 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x0020
+ };
+ static const wchar_t unitab_xterm_poorman[32] =
+ L"*#****o~**+++++-----++++|****L. ";
+
+ const wchar_t *ptr;
+
+ if (vtmode == VT_POORMAN)
+ ptr = unitab_xterm_poorman;
+ else
+ ptr = unitab_xterm_std;
+
+ if (i >= 0x5F && i < 0x7F)
+ ucsdata->unitab_xterm[i] = ptr[i & 0x1F];
+ else
+ ucsdata->unitab_xterm[i] = ucsdata->unitab_line[i];
+ }
+
+ /*
+ * Set up unitab_scoacs. The SCO Alternate Character Set is
+ * simply CP437.
+ */
+ for (i = 0; i < 256; i++) {
+ char c[1];
+ const char *p;
+ wchar_t wc[1];
+ int len;
+ c[0] = i;
+ p = c;
+ len = 1;
+ if (1 == charset_to_unicode(&p, &len, wc, 1, CS_CP437, NULL, L"", 0))
+ ucsdata->unitab_scoacs[i] = wc[0];
+ else
+ ucsdata->unitab_scoacs[i] = 0xFFFD;
+ }
+
+ /*
+ * Find the control characters in the line codepage. For
+ * direct-to-font mode using the D800 hack, we assume 00-1F and
+ * 7F are controls, but allow 80-9F through. (It's as good a
+ * guess as anything; and my bet is that half the weird fonts
+ * used in this way will be IBM or MS code pages anyway.)
+ */
+ for (i = 0; i < 256; i++) {
+ int lineval = ucsdata->unitab_line[i];
+ if (lineval < ' ' || (lineval >= 0x7F && lineval < 0xA0) ||
+ (lineval >= 0xD800 && lineval < 0xD820) || (lineval == 0xD87F))
+ ucsdata->unitab_ctrl[i] = i;
+ else
+ ucsdata->unitab_ctrl[i] = 0xFF;
+ }
+
+ return ret;
+}
+
+const char *cp_name(int codepage)
+{
+ if (codepage == CS_NONE)
+ return "Use font encoding";
+ return charset_to_localenc(codepage);
+}
+
+const char *cp_enumerate(int index)
+{
+ int charset;
+ charset = charset_localenc_nth(index);
+ if (charset == CS_NONE) {
+ /* "Use font encoding" comes after all the named charsets */
+ if (charset_localenc_nth(index-1) != CS_NONE)
+ return "Use font encoding";
+ return NULL;
+ }
+ return charset_to_localenc(charset);
+}
+
+int decode_codepage(const char *cp_name)
+{
+ if (!cp_name || !*cp_name)
+ return CS_UTF8;
+ return charset_from_localenc(cp_name);
+}
diff --git a/unix/unifont.c b/unix/unifont.c
new file mode 100644
index 00000000..e9f8623a
--- /dev/null
+++ b/unix/unifont.c
@@ -0,0 +1,3806 @@
+/*
+ * Unified font management for GTK.
+ *
+ * PuTTY is willing to use both old-style X server-side bitmap
+ * fonts _and_ GTK2/Pango client-side fonts. This requires us to
+ * do a bit of work to wrap the two wildly different APIs into
+ * forms the rest of the code can switch between seamlessly, and
+ * also requires a custom font selector capable of handling both
+ * types of font.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+#if !GTK_CHECK_VERSION(3,0,0)
+#include <gdk/gdkkeysyms.h>
+#endif
+
+#define MAY_REFER_TO_GTK_IN_HEADERS
+
+#include "putty.h"
+#include "unifont.h"
+#include "gtkcompat.h"
+#include "gtkmisc.h"
+#include "tree234.h"
+
+#ifndef NOT_X_WINDOWS
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include "x11misc.h"
+#endif
+
+/*
+ * Future work:
+ *
+ * - it would be nice to have a display of the current font name,
+ * and in particular whether it's client- or server-side,
+ * during the progress of the font selector.
+ */
+
+#if !GLIB_CHECK_VERSION(1,3,7)
+#define g_ascii_strcasecmp g_strcasecmp
+#define g_ascii_strncasecmp g_strncasecmp
+#endif
+
+/*
+ * Ad-hoc vtable mechanism to allow font structures to be
+ * polymorphic.
+ *
+ * Any instance of `unifont' used in the vtable functions will
+ * actually be an element of a larger structure containing data
+ * specific to the subtype.
+ */
+
+#define FONTFLAG_CLIENTSIDE 0x0001
+#define FONTFLAG_SERVERSIDE 0x0002
+#define FONTFLAG_SERVERALIAS 0x0004
+#define FONTFLAG_NONMONOSPACED 0x0008
+
+#define FONTFLAG_SORT_MASK 0x0007 /* used to disambiguate font families */
+
+typedef void (*fontsel_add_entry)(void *ctx, const char *realfontname,
+ const char *family, const char *charset,
+ const char *style, const char *stylekey,
+ int size, int flags,
+ const struct UnifontVtable *fontclass);
+
+struct UnifontVtable {
+ /*
+ * `Methods' of the `class'.
+ */
+ unifont *(*create)(GtkWidget *widget, const char *name, bool wide,
+ bool bold, int shadowoffset, bool shadowalways);
+ unifont *(*create_fallback)(GtkWidget *widget, int height, bool wide,
+ bool bold, int shadowoffset,
+ bool shadowalways);
+ void (*destroy)(unifont *font);
+ bool (*has_glyph)(unifont *font, wchar_t glyph);
+ void (*draw_text)(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string, int len,
+ bool wide, bool bold, int cellwidth);
+ void (*draw_combining)(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string, int len,
+ bool wide, bool bold, int cellwidth);
+ void (*enum_fonts)(GtkWidget *widget,
+ fontsel_add_entry callback, void *callback_ctx);
+ char *(*canonify_fontname)(GtkWidget *widget, const char *name, int *size,
+ int *flags, bool resolve_aliases);
+ char *(*scale_fontname)(GtkWidget *widget, const char *name, int size);
+ char *(*size_increment)(unifont *font, int increment);
+
+ /*
+ * `Static data members' of the `class'.
+ */
+ const char *prefix;
+};
+
+#ifndef NOT_X_WINDOWS
+
+/* ----------------------------------------------------------------------
+ * X11 font implementation, directly using Xlib calls. Conditioned out
+ * if X11 fonts aren't available at all (e.g. building with GTK3 for a
+ * back end other than X).
+ */
+
+static bool x11font_has_glyph(unifont *font, wchar_t glyph);
+static void x11font_draw_text(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string, int len,
+ bool wide, bool bold, int cellwidth);
+static void x11font_draw_combining(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string,
+ int len, bool wide, bool bold,
+ int cellwidth);
+static unifont *x11font_create(GtkWidget *widget, const char *name,
+ bool wide, bool bold,
+ int shadowoffset, bool shadowalways);
+static void x11font_destroy(unifont *font);
+static void x11font_enum_fonts(GtkWidget *widget,
+ fontsel_add_entry callback, void *callback_ctx);
+static char *x11font_canonify_fontname(GtkWidget *widget, const char *name,
+ int *size, int *flags,
+ bool resolve_aliases);
+static char *x11font_scale_fontname(GtkWidget *widget, const char *name,
+ int size);
+static char *x11font_size_increment(unifont *font, int increment);
+
+#ifdef DRAW_TEXT_CAIRO
+struct cairo_cached_glyph {
+ cairo_surface_t *surface;
+ unsigned char *bitmap;
+};
+#endif
+
+/*
+ * Structure storing a single physical XFontStruct, plus associated
+ * data.
+ */
+typedef struct x11font_individual {
+ /* The XFontStruct itself. */
+ XFontStruct *xfs;
+
+ /*
+ * The `allocated' flag indicates whether we've tried to fetch
+ * this subfont already (thus distinguishing xfs==NULL because we
+ * haven't tried yet from xfs==NULL because we tried and failed,
+ * so that we don't keep trying and failing subsequently).
+ */
+ bool allocated;
+
+#ifdef DRAW_TEXT_CAIRO
+ /*
+ * A cache of glyph bitmaps downloaded from the X server when
+ * we're in Cairo rendering mode. If glyphcache itself is
+ * non-NULL, then entries in [0,nglyphs) are expected to be
+ * initialised to either NULL or a bitmap pointer.
+ */
+ struct cairo_cached_glyph *glyphcache;
+ int nglyphs;
+
+ /*
+ * X server paraphernalia for actually downloading the glyphs.
+ */
+ Pixmap pixmap;
+ GC gc;
+ int pixwidth, pixheight, pixoriginx, pixoriginy;
+
+ /*
+ * Paraphernalia for loading the resulting bitmaps into Cairo.
+ */
+ int rowsize, allsize, indexflip;
+#endif
+
+} x11font_individual;
+
+struct x11font {
+ /*
+ * Copy of the X display handle, so we don't have to keep
+ * extracting it from GDK.
+ */
+ Display *disp;
+ /*
+ * Individual physical X fonts. We store a number of these, for
+ * automatically guessed bold and wide variants.
+ */
+ x11font_individual fonts[4];
+ /*
+ * `sixteen_bit' is true iff the font object is indexed by
+ * values larger than a byte. That is, this flag tells us
+ * whether we use XDrawString or XDrawString16, etc.
+ */
+ bool sixteen_bit;
+ /*
+ * `variable' is true iff the font is non-fixed-pitch. This
+ * enables some code which takes greater care over character
+ * positioning during text drawing.
+ */
+ bool variable;
+ /*
+ * real_charset is the charset used when translating text into the
+ * font's internal encoding inside draw_text(). This need not be
+ * the same as the public_charset provided to the client; for
+ * example, public_charset might be CS_ISO8859_1 while
+ * real_charset is CS_ISO8859_1_X11.
+ */
+ int real_charset;
+ /*
+ * Data passed in to unifont_create().
+ */
+ int shadowoffset;
+ bool wide, bold, shadowalways;
+
+ unifont u;
+};
+
+static const UnifontVtable x11font_vtable = {
+ .create = x11font_create,
+ .create_fallback = NULL, /* no fallback fonts in X11 */
+ .destroy = x11font_destroy,
+ .has_glyph = x11font_has_glyph,
+ .draw_text = x11font_draw_text,
+ .draw_combining = x11font_draw_combining,
+ .enum_fonts = x11font_enum_fonts,
+ .canonify_fontname = x11font_canonify_fontname,
+ .scale_fontname = x11font_scale_fontname,
+ .size_increment = x11font_size_increment,
+ .prefix = "server",
+};
+
+#define XLFD_STRING_PARTS_LIST(S,I) \
+ S(foundry) \
+ S(family_name) \
+ S(weight_name) \
+ S(slant) \
+ S(setwidth_name) \
+ S(add_style_name) \
+ I(pixel_size) \
+ I(point_size) \
+ I(resolution_x) \
+ I(resolution_y) \
+ S(spacing) \
+ I(average_width) \
+ S(charset_registry) \
+ S(charset_encoding) \
+ /* end of list */
+
+/* Special value for int fields that xlfd_recompose will render as "*" */
+#define XLFD_INT_WILDCARD INT_MIN
+
+struct xlfd_decomposed {
+#define STR_FIELD(f) const char *f;
+#define INT_FIELD(f) int f;
+ XLFD_STRING_PARTS_LIST(STR_FIELD, INT_FIELD)
+#undef STR_FIELD
+#undef INT_FIELD
+};
+
+static struct xlfd_decomposed *xlfd_decompose(const char *xlfd)
+{
+ char *p, *components[14];
+ struct xlfd_decomposed *dec;
+ int i;
+
+ if (!xlfd)
+ return NULL;
+
+ dec = snew_plus(struct xlfd_decomposed, strlen(xlfd) + 1);
+ p = snew_plus_get_aux(dec);
+ strcpy(p, xlfd);
+
+ for (i = 0; i < 14; i++) {
+ if (*p != '-') {
+ /* Malformed XLFD: not enough '-' */
+ sfree(dec);
+ return NULL;
+ }
+ *p++ = '\0';
+ components[i] = p;
+ p += strcspn(p, "-");
+ }
+ if (*p) {
+ /* Malformed XLFD: too many '-' */
+ sfree(dec);
+ return NULL;
+ }
+
+ i = 0;
+#define STORE_STR(f) dec->f = components[i++];
+#define STORE_INT(f) dec->f = atoi(components[i++]);
+ XLFD_STRING_PARTS_LIST(STORE_STR, STORE_INT)
+#undef STORE_STR
+#undef STORE_INT
+
+ return dec;
+}
+
+static char *xlfd_recompose(const struct xlfd_decomposed *dec)
+{
+#define FMT_STR(f) "-%s"
+#define ARG_STR(f) , dec->f
+#define FMT_INT(f) "%s%.*d"
+#define ARG_INT(f) \
+ , dec->f == XLFD_INT_WILDCARD ? "-*" : "-" \
+ , dec->f == XLFD_INT_WILDCARD ? 0 : 1 \
+ , dec->f == XLFD_INT_WILDCARD ? 0 : dec->f
+ return dupprintf(XLFD_STRING_PARTS_LIST(FMT_STR, FMT_INT)
+ XLFD_STRING_PARTS_LIST(ARG_STR, ARG_INT));
+#undef FMT_STR
+#undef ARG_STR
+#undef FMT_INT
+#undef ARG_INT
+}
+
+static char *x11_guess_derived_font_name(Display *disp, XFontStruct *xfs,
+ bool bold, bool wide)
+{
+ Atom fontprop = XInternAtom(disp, "FONT", False);
+ unsigned long ret;
+ if (XGetFontProperty(xfs, fontprop, &ret)) {
+ char *name = XGetAtomName(disp, (Atom)ret);
+ struct xlfd_decomposed *xlfd = xlfd_decompose(name);
+ if (!xlfd)
+ return NULL;
+
+ if (bold)
+ xlfd->weight_name = "bold";
+
+ if (wide) {
+ /* Width name obviously may have changed. */
+ /* Additional style may now become e.g. `ja' or `ko'. */
+ xlfd->setwidth_name = xlfd->add_style_name = "*";
+
+ /* Expect to double the average width. */
+ xlfd->average_width *= 2;
+ }
+
+ {
+ char *ret = xlfd_recompose(xlfd);
+ sfree(xlfd);
+ return ret;
+ }
+ }
+ return NULL;
+}
+
+static int x11_font_width(XFontStruct *xfs, bool sixteen_bit)
+{
+ if (sixteen_bit) {
+ XChar2b space;
+ space.byte1 = 0;
+ space.byte2 = '0';
+ return XTextWidth16(xfs, &space, 1);
+ } else {
+ return XTextWidth(xfs, "0", 1);
+ }
+}
+
+static const XCharStruct *x11_char_struct(
+ XFontStruct *xfs, unsigned char byte1, unsigned char byte2)
+{
+ int index;
+
+ /*
+ * The man page for XQueryFont is rather confusing about how the
+ * per_char array in the XFontStruct is laid out, because it gives
+ * formulae for determining the two-byte X character code _from_
+ * an index into the per_char array. Going the other way, it's
+ * rather simpler:
+ *
+ * The valid character codes have byte1 between min_byte1 and
+ * max_byte1 inclusive, and byte2 between min_char_or_byte2 and
+ * max_char_or_byte2 inclusive. This gives a rectangle of size
+ * (max_byte2-min_byte1+1) by
+ * (max_char_or_byte2-min_char_or_byte2+1), which is precisely the
+ * rectangle encoded in the per_char array. Hence, given a
+ * character code which is valid in the sense that it falls
+ * somewhere in that rectangle, its index in per_char is given by
+ * setting
+ *
+ * x = byte2 - min_char_or_byte2
+ * y = byte1 - min_byte1
+ * index = y * (max_char_or_byte2-min_char_or_byte2+1) + x
+ *
+ * If min_byte1 and min_byte2 are both zero, that's a special case
+ * which can be treated as if min_byte2 was 1 instead, i.e. the
+ * per_char array just runs from min_char_or_byte2 to
+ * max_char_or_byte2 inclusive, and byte1 should always be zero.
+ */
+
+ if (byte2 < xfs->min_char_or_byte2 || byte2 > xfs->max_char_or_byte2)
+ return NULL;
+
+ if (xfs->min_byte1 == 0 && xfs->max_byte1 == 0) {
+ index = byte2 - xfs->min_char_or_byte2;
+ } else {
+ if (byte1 < xfs->min_byte1 || byte1 > xfs->max_byte1)
+ return NULL;
+ index = ((byte2 - xfs->min_char_or_byte2) +
+ ((byte1 - xfs->min_byte1) *
+ (xfs->max_char_or_byte2 - xfs->min_char_or_byte2 + 1)));
+ }
+
+ if (!xfs->per_char) /* per_char NULL => everything in range exists */
+ return &xfs->max_bounds;
+
+ return &xfs->per_char[index];
+}
+
+static bool x11_font_has_glyph(
+ XFontStruct *xfs, unsigned char byte1, unsigned char byte2)
+{
+ /*
+ * Not to be confused with x11font_has_glyph, which is a method of
+ * the x11font 'class' and hence takes a unifont as argument. This
+ * is the low-level function which grubs about in an actual
+ * XFontStruct to see if a given glyph exists.
+ *
+ * We must do this ourselves rather than letting Xlib's
+ * XTextExtents16 do the job, because XTextExtents will helpfully
+ * substitute the font's default_char for any missing glyph and
+ * not tell us it did so, which precisely won't help us find out
+ * which glyphs _are_ missing.
+ */
+ const XCharStruct *xcs = x11_char_struct(xfs, byte1, byte2);
+ return xcs && (xcs->ascent + xcs->descent > 0 || xcs->width > 0);
+}
+
+static unifont *x11font_create(GtkWidget *widget, const char *name,
+ bool wide, bool bold,
+ int shadowoffset, bool shadowalways)
+{
+ struct x11font *xfont;
+ XFontStruct *xfs;
+ Display *disp;
+ Atom charset_registry, charset_encoding, spacing;
+ unsigned long registry_ret, encoding_ret, spacing_ret;
+ int pubcs, realcs;
+ bool sixteen_bit, variable;
+ int i;
+
+ if ((disp = get_x11_display()) == NULL)
+ return NULL;
+
+ xfs = XLoadQueryFont(disp, name);
+ if (!xfs)
+ return NULL;
+
+ charset_registry = XInternAtom(disp, "CHARSET_REGISTRY", False);
+ charset_encoding = XInternAtom(disp, "CHARSET_ENCODING", False);
+
+ pubcs = realcs = CS_NONE;
+ sixteen_bit = false;
+ variable = true;
+
+ if (XGetFontProperty(xfs, charset_registry, &registry_ret) &&
+ XGetFontProperty(xfs, charset_encoding, &encoding_ret)) {
+ char *reg, *enc;
+ reg = XGetAtomName(disp, (Atom)registry_ret);
+ enc = XGetAtomName(disp, (Atom)encoding_ret);
+ if (reg && enc) {
+ char *encoding = dupcat(reg, "-", enc);
+ pubcs = realcs = charset_from_xenc(encoding);
+
+ /*
+ * iso10646-1 is the only wide font encoding we
+ * support. In this case, we expect clients to give us
+ * UTF-8, which this module must internally convert
+ * into 16-bit Unicode.
+ */
+ if (!strcasecmp(encoding, "iso10646-1")) {
+ sixteen_bit = true;
+ pubcs = realcs = CS_UTF8;
+ }
+
+ /*
+ * Hack for X line-drawing characters: if the primary font
+ * is encoded as ISO-8859-1, and has valid glyphs in the
+ * low character positions, it is assumed that those
+ * glyphs are the VT100 line-drawing character set.
+ */
+ if (pubcs == CS_ISO8859_1) {
+ int ch;
+ for (ch = 1; ch < 32; ch++)
+ if (!x11_font_has_glyph(xfs, 0, ch))
+ break;
+ if (ch == 32)
+ realcs = CS_ISO8859_1_X11;
+ }
+
+ sfree(encoding);
+ }
+ }
+
+ spacing = XInternAtom(disp, "SPACING", False);
+ if (XGetFontProperty(xfs, spacing, &spacing_ret)) {
+ char *spc;
+ spc = XGetAtomName(disp, (Atom)spacing_ret);
+
+ if (spc && strchr("CcMm", spc[0]))
+ variable = false;
+ }
+
+ xfont = snew(struct x11font);
+ xfont->u.vt = &x11font_vtable;
+ xfont->u.width = x11_font_width(xfs, sixteen_bit);
+ xfont->u.ascent = xfs->ascent;
+ xfont->u.descent = xfs->descent;
+ xfont->u.height = xfont->u.ascent + xfont->u.descent;
+ xfont->u.public_charset = pubcs;
+ xfont->u.want_fallback = true;
+ xfont->u.strikethrough_y = xfont->u.ascent - (xfont->u.ascent * 3 / 8);
+#ifdef DRAW_TEXT_GDK
+ xfont->u.preferred_drawtype = DRAWTYPE_GDK;
+#elif defined DRAW_TEXT_CAIRO
+ xfont->u.preferred_drawtype = DRAWTYPE_CAIRO;
+#else
+#error No drawtype available at all
+#endif
+ xfont->disp = disp;
+ xfont->real_charset = realcs;
+ xfont->sixteen_bit = sixteen_bit;
+ xfont->variable = variable;
+ xfont->wide = wide;
+ xfont->bold = bold;
+ xfont->shadowoffset = shadowoffset;
+ xfont->shadowalways = shadowalways;
+
+ for (i = 0; i < lenof(xfont->fonts); i++) {
+ xfont->fonts[i].xfs = NULL;
+ xfont->fonts[i].allocated = false;
+#ifdef DRAW_TEXT_CAIRO
+ xfont->fonts[i].glyphcache = NULL;
+ xfont->fonts[i].nglyphs = 0;
+ xfont->fonts[i].pixmap = None;
+ xfont->fonts[i].gc = None;
+#endif
+ }
+ xfont->fonts[0].xfs = xfs;
+ xfont->fonts[0].allocated = true;
+
+ return &xfont->u;
+}
+
+static void x11font_destroy(unifont *font)
+{
+ struct x11font *xfont = container_of(font, struct x11font, u);
+ Display *disp = xfont->disp;
+ int i;
+
+ for (i = 0; i < lenof(xfont->fonts); i++) {
+ if (xfont->fonts[i].xfs)
+ XFreeFont(disp, xfont->fonts[i].xfs);
+#ifdef DRAW_TEXT_CAIRO
+ if (xfont->fonts[i].gc != None)
+ XFreeGC(disp, xfont->fonts[i].gc);
+ if (xfont->fonts[i].pixmap != None)
+ XFreePixmap(disp, xfont->fonts[i].pixmap);
+ if (xfont->fonts[i].glyphcache) {
+ int j;
+ for (j = 0; j < xfont->fonts[i].nglyphs; j++) {
+ cairo_surface_destroy(xfont->fonts[i].glyphcache[j].surface);
+ sfree(xfont->fonts[i].glyphcache[j].bitmap);
+ }
+ sfree(xfont->fonts[i].glyphcache);
+ }
+#endif
+ }
+ sfree(xfont);
+}
+
+static void x11_alloc_subfont(struct x11font *xfont, int sfid)
+{
+ Display *disp = xfont->disp;
+ char *derived_name = x11_guess_derived_font_name(
+ disp, xfont->fonts[0].xfs, sfid & 1, !!(sfid & 2));
+ xfont->fonts[sfid].xfs = XLoadQueryFont(disp, derived_name);
+ xfont->fonts[sfid].allocated = true;
+ sfree(derived_name);
+ /* Note that xfont->fonts[sfid].xfs may still be NULL, if XLQF failed. */
+}
+
+static bool x11font_has_glyph(unifont *font, wchar_t glyph)
+{
+ struct x11font *xfont = container_of(font, struct x11font, u);
+
+ if (xfont->sixteen_bit) {
+ /*
+ * This X font has 16-bit character indices, which means
+ * we can directly use our Unicode input value.
+ */
+ return x11_font_has_glyph(xfont->fonts[0].xfs,
+ glyph >> 8, glyph & 0xFF);
+ } else {
+ /*
+ * This X font has 8-bit indices, so we must convert to the
+ * appropriate character set.
+ */
+ char sbstring[2];
+ int sblen = wc_to_mb(xfont->real_charset, 0, &glyph, 1,
+ sbstring, 2, "");
+ if (sblen == 0 || !sbstring[0])
+ return false; /* not even in the charset */
+
+ return x11_font_has_glyph(xfont->fonts[0].xfs, 0,
+ (unsigned char)sbstring[0]);
+ }
+}
+
+#if !GTK_CHECK_VERSION(2,0,0)
+#define GDK_DRAWABLE_XID(d) GDK_WINDOW_XWINDOW(d) /* GTK1's name for this */
+#elif GTK_CHECK_VERSION(3,0,0)
+#define GDK_DRAWABLE_XID(d) GDK_WINDOW_XID(d) /* GTK3's name for this */
+#endif
+
+static int x11font_width_16(unifont_drawctx *ctx, x11font_individual *xfi,
+ const void *vstring, int start, int length)
+{
+ const XChar2b *string = (const XChar2b *)vstring;
+ return XTextWidth16(xfi->xfs, string+start, length);
+}
+
+static int x11font_width_8(unifont_drawctx *ctx, x11font_individual *xfi,
+ const void *vstring, int start, int length)
+{
+ const char *string = (const char *)vstring;
+ return XTextWidth(xfi->xfs, string+start, length);
+}
+
+#ifdef DRAW_TEXT_GDK
+static void x11font_gdk_setup(unifont_drawctx *ctx, x11font_individual *xfi,
+ Display *disp)
+{
+ XSetFont(disp, GDK_GC_XGC(ctx->u.gdk.gc), xfi->xfs->fid);
+}
+
+static void x11font_gdk_draw_16(unifont_drawctx *ctx, x11font_individual *xfi,
+ Display *disp, int x, int y,
+ const void *vstring, int start, int length)
+{
+ const XChar2b *string = (const XChar2b *)vstring;
+ XDrawString16(disp, GDK_DRAWABLE_XID(ctx->u.gdk.target),
+ GDK_GC_XGC(ctx->u.gdk.gc), x, y, string+start, length);
+}
+
+static void x11font_gdk_draw_8(unifont_drawctx *ctx, x11font_individual *xfi,
+ Display *disp, int x, int y,
+ const void *vstring, int start, int length)
+{
+ const char *string = (const char *)vstring;
+ XDrawString(disp, GDK_DRAWABLE_XID(ctx->u.gdk.target),
+ GDK_GC_XGC(ctx->u.gdk.gc), x, y, string+start, length);
+}
+#endif
+
+#ifdef DRAW_TEXT_CAIRO
+static void x11font_cairo_setup(
+ unifont_drawctx *ctx, x11font_individual *xfi, Display *disp)
+{
+ if (xfi->pixmap == None) {
+ XGCValues gcvals;
+ GdkWindow *widgetwin = gtk_widget_get_window(ctx->u.cairo.widget);
+ int widgetscr = GDK_SCREEN_XNUMBER(gdk_window_get_screen(widgetwin));
+
+ xfi->pixwidth =
+ xfi->xfs->max_bounds.rbearing - xfi->xfs->min_bounds.lbearing;
+ xfi->pixheight =
+ xfi->xfs->max_bounds.ascent + xfi->xfs->max_bounds.descent;
+ xfi->pixoriginx = -xfi->xfs->min_bounds.lbearing;
+ xfi->pixoriginy = xfi->xfs->max_bounds.ascent;
+
+ xfi->rowsize = cairo_format_stride_for_width(CAIRO_FORMAT_A1,
+ xfi->pixwidth);
+ xfi->allsize = xfi->rowsize * xfi->pixheight;
+
+ {
+ /*
+ * Test host endianness and use it to set xfi->indexflip,
+ * which is XORed into our left-shift counts in order to
+ * implement the CAIRO_FORMAT_A1 specification, in which
+ * each bitmap byte is oriented LSB-first on little-endian
+ * platforms and MSB-first on big-endian ones.
+ *
+ * This is the same technique Cairo itself uses to test
+ * endianness, so hopefully it'll work in any situation
+ * where Cairo is usable at all.
+ */
+ static const int endianness_test = 1;
+ xfi->indexflip = (*((char *) &endianness_test) == 1) ? 0 : 7;
+ }
+
+ xfi->pixmap = XCreatePixmap(
+ disp, GDK_DRAWABLE_XID(gtk_widget_get_window(ctx->u.cairo.widget)),
+ xfi->pixwidth, xfi->pixheight, 1);
+ gcvals.foreground = WhitePixel(disp, widgetscr);
+ gcvals.background = BlackPixel(disp, widgetscr);
+ gcvals.font = xfi->xfs->fid;
+ xfi->gc = XCreateGC(disp, xfi->pixmap,
+ GCForeground | GCBackground | GCFont, &gcvals);
+ }
+}
+
+static void x11font_cairo_cache_glyph(
+ Display *disp, x11font_individual *xfi, int glyphindex)
+{
+ XImage *image;
+ int x, y;
+ unsigned char *bitmap;
+ const XCharStruct *xcs = x11_char_struct(xfi->xfs, glyphindex >> 8,
+ glyphindex & 0xFF);
+
+ bitmap = snewn(xfi->allsize, unsigned char);
+ memset(bitmap, 0, xfi->allsize);
+
+ image = XGetImage(disp, xfi->pixmap, 0, 0,
+ xfi->pixwidth, xfi->pixheight, AllPlanes, XYPixmap);
+ for (y = xfi->pixoriginy - xcs->ascent;
+ y < xfi->pixoriginy + xcs->descent; y++) {
+ for (x = xfi->pixoriginx + xcs->lbearing;
+ x < xfi->pixoriginx + xcs->rbearing; x++) {
+ unsigned long pixel = XGetPixel(image, x, y);
+ if (pixel) {
+ int byteindex = y * xfi->rowsize + x/8;
+ int bitindex = (x & 7) ^ xfi->indexflip;
+ bitmap[byteindex] |= 1U << bitindex;
+ }
+ }
+ }
+ XDestroyImage(image);
+
+ if (xfi->nglyphs <= glyphindex) {
+ /* Round up to the next multiple of 256 on the general
+ * principle that Unicode characters come in contiguous blocks
+ * often used together */
+ int old_nglyphs = xfi->nglyphs;
+ xfi->nglyphs = (glyphindex + 0x100) & ~0xFF;
+ xfi->glyphcache = sresize(xfi->glyphcache, xfi->nglyphs,
+ struct cairo_cached_glyph);
+
+ while (old_nglyphs < xfi->nglyphs) {
+ xfi->glyphcache[old_nglyphs].surface = NULL;
+ xfi->glyphcache[old_nglyphs].bitmap = NULL;
+ old_nglyphs++;
+ }
+ }
+ xfi->glyphcache[glyphindex].bitmap = bitmap;
+ xfi->glyphcache[glyphindex].surface = cairo_image_surface_create_for_data(
+ bitmap, CAIRO_FORMAT_A1, xfi->pixwidth, xfi->pixheight, xfi->rowsize);
+}
+
+static void x11font_cairo_draw_glyph(unifont_drawctx *ctx,
+ x11font_individual *xfi, int x, int y,
+ int glyphindex)
+{
+ if (xfi->glyphcache[glyphindex].surface) {
+ cairo_mask_surface(ctx->u.cairo.cr,
+ xfi->glyphcache[glyphindex].surface,
+ x - xfi->pixoriginx, y - xfi->pixoriginy);
+ }
+}
+
+static void x11font_cairo_draw_16(
+ unifont_drawctx *ctx, x11font_individual *xfi, Display *disp,
+ int x, int y, const void *vstring, int start, int length)
+{
+ const XChar2b *string = (const XChar2b *)vstring + start;
+ int i;
+ for (i = 0; i < length; i++) {
+ if (x11_font_has_glyph(xfi->xfs, string[i].byte1, string[i].byte2)) {
+ int glyphindex = (256 * (unsigned char)string[i].byte1 +
+ (unsigned char)string[i].byte2);
+ if (glyphindex >= xfi->nglyphs ||
+ !xfi->glyphcache[glyphindex].surface) {
+ XDrawImageString16(disp, xfi->pixmap, xfi->gc,
+ xfi->pixoriginx, xfi->pixoriginy,
+ string+i, 1);
+ x11font_cairo_cache_glyph(disp, xfi, glyphindex);
+ }
+ x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex);
+ x += XTextWidth16(xfi->xfs, string+i, 1);
+ }
+ }
+}
+
+static void x11font_cairo_draw_8(
+ unifont_drawctx *ctx, x11font_individual *xfi, Display *disp,
+ int x, int y, const void *vstring, int start, int length)
+{
+ const char *string = (const char *)vstring + start;
+ int i;
+ for (i = 0; i < length; i++) {
+ if (x11_font_has_glyph(xfi->xfs, 0, string[i])) {
+ int glyphindex = (unsigned char)string[i];
+ if (glyphindex >= xfi->nglyphs ||
+ !xfi->glyphcache[glyphindex].surface) {
+ XDrawImageString(disp, xfi->pixmap, xfi->gc,
+ xfi->pixoriginx, xfi->pixoriginy,
+ string+i, 1);
+ x11font_cairo_cache_glyph(disp, xfi, glyphindex);
+ }
+ x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex);
+ x += XTextWidth(xfi->xfs, string+i, 1);
+ }
+ }
+}
+#endif /* DRAW_TEXT_CAIRO */
+
+struct x11font_drawfuncs {
+ int (*width)(unifont_drawctx *ctx, x11font_individual *xfi,
+ const void *vstring, int start, int length);
+ void (*setup)(unifont_drawctx *ctx, x11font_individual *xfi,
+ Display *disp);
+ void (*draw)(unifont_drawctx *ctx, x11font_individual *xfi, Display *disp,
+ int x, int y, const void *vstring, int start, int length);
+};
+
+/*
+ * This array has two entries per compiled-in drawtype; of each pair,
+ * the first is for an 8-bit font and the second for 16-bit.
+ */
+static const struct x11font_drawfuncs x11font_drawfuncs[2*DRAWTYPE_NTYPES] = {
+#ifdef DRAW_TEXT_GDK
+ /* gdk, 8-bit */
+ {
+ x11font_width_8,
+ x11font_gdk_setup,
+ x11font_gdk_draw_8,
+ },
+ /* gdk, 16-bit */
+ {
+ x11font_width_16,
+ x11font_gdk_setup,
+ x11font_gdk_draw_16,
+ },
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ /* cairo, 8-bit */
+ {
+ x11font_width_8,
+ x11font_cairo_setup,
+ x11font_cairo_draw_8,
+ },
+ /* [3] cairo, 16-bit */
+ {
+ x11font_width_16,
+ x11font_cairo_setup,
+ x11font_cairo_draw_16,
+ },
+#endif
+};
+
+static void x11font_really_draw_text(
+ const struct x11font_drawfuncs *dfns, unifont_drawctx *ctx,
+ x11font_individual *xfi, Display *disp,
+ int x, int y, const void *string, int nchars,
+ int shadowoffset, bool fontvariable, int cellwidth)
+{
+ int start = 0, step, nsteps;
+ bool centre;
+
+ if (fontvariable) {
+ /*
+ * In a variable-pitch font, we draw one character at a
+ * time, and centre it in the character cell.
+ */
+ step = 1;
+ nsteps = nchars;
+ centre = true;
+ } else {
+ /*
+ * In a fixed-pitch font, we can draw the whole lot in one go.
+ */
+ step = nchars;
+ nsteps = 1;
+ centre = false;
+ }
+
+ dfns->setup(ctx, xfi, disp);
+
+ while (nsteps-- > 0) {
+ int X = x;
+ if (centre)
+ X += (cellwidth - dfns->width(ctx, xfi, string, start, step)) / 2;
+
+ dfns->draw(ctx, xfi, disp, X, y, string, start, step);
+ if (shadowoffset)
+ dfns->draw(ctx, xfi, disp, X + shadowoffset, y,
+ string, start, step);
+
+ x += cellwidth;
+ start += step;
+ }
+}
+
+static void x11font_draw_text(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string, int len,
+ bool wide, bool bold, int cellwidth)
+{
+ struct x11font *xfont = container_of(font, struct x11font, u);
+ int sfid;
+ int shadowoffset = 0;
+ int mult = (wide ? 2 : 1);
+ int index = 2 * (int)ctx->type;
+
+ wide = wide && !xfont->wide;
+ bold = bold && !xfont->bold;
+
+ /*
+ * Decide which subfont we're using, and whether we have to
+ * use shadow bold.
+ */
+ if (xfont->shadowalways && bold) {
+ shadowoffset = xfont->shadowoffset;
+ bold = false;
+ }
+ sfid = 2 * wide + bold;
+ if (!xfont->fonts[sfid].allocated)
+ x11_alloc_subfont(xfont, sfid);
+ if (bold && !xfont->fonts[sfid].xfs) {
+ bold = false;
+ shadowoffset = xfont->shadowoffset;
+ sfid = 2 * wide + bold;
+ if (!xfont->fonts[sfid].allocated)
+ x11_alloc_subfont(xfont, sfid);
+ }
+
+ if (!xfont->fonts[sfid].xfs)
+ return; /* we've tried our best, but no luck */
+
+ if (xfont->sixteen_bit) {
+ /*
+ * This X font has 16-bit character indices, which means
+ * we can directly use our Unicode input string.
+ */
+ XChar2b *xcs;
+ int i;
+
+ xcs = snewn(len, XChar2b);
+ for (i = 0; i < len; i++) {
+ xcs[i].byte1 = string[i] >> 8;
+ xcs[i].byte2 = string[i];
+ }
+
+ x11font_really_draw_text(x11font_drawfuncs + index + 1, ctx,
+ &xfont->fonts[sfid], xfont->disp, x, y,
+ xcs, len, shadowoffset,
+ xfont->variable, cellwidth * mult);
+ sfree(xcs);
+ } else {
+ /*
+ * This X font has 8-bit indices, so we must convert to the
+ * appropriate character set.
+ */
+ char *sbstring = snewn(len+1, char);
+ int sblen = wc_to_mb(xfont->real_charset, 0, string, len,
+ sbstring, len+1, ".");
+ x11font_really_draw_text(x11font_drawfuncs + index + 0, ctx,
+ &xfont->fonts[sfid], xfont->disp, x, y,
+ sbstring, sblen, shadowoffset,
+ xfont->variable, cellwidth * mult);
+ sfree(sbstring);
+ }
+}
+
+static void x11font_draw_combining(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string,
+ int len, bool wide, bool bold,
+ int cellwidth)
+{
+ /*
+ * For server-side fonts, there's no sophisticated system for
+ * combining characters intelligently, so the best we can do is to
+ * overprint them on each other in the obvious way.
+ */
+ int i;
+ for (i = 0; i < len; i++)
+ x11font_draw_text(ctx, font, x, y, string+i, 1, wide, bold, cellwidth);
+}
+
+static void x11font_enum_fonts(GtkWidget *widget,
+ fontsel_add_entry callback, void *callback_ctx)
+{
+ Display *disp;
+ char **fontnames;
+ char *tmp = NULL;
+ int nnames, i, max, tmpsize;
+
+ if ((disp = get_x11_display()) == NULL)
+ return;
+
+ max = 32768;
+ while (1) {
+ fontnames = XListFonts(disp, "*", max, &nnames);
+ if (nnames >= max) {
+ XFreeFontNames(fontnames);
+ max *= 2;
+ } else
+ break;
+ }
+
+ tmpsize = 0;
+
+ for (i = 0; i < nnames; i++) {
+ struct xlfd_decomposed *xlfd = xlfd_decompose(fontnames[i]);
+ if (xlfd) {
+ char *p, *font, *style, *stylekey, *charset;
+ int weightkey, slantkey, setwidthkey;
+ int thistmpsize;
+
+ /*
+ * Convert a dismembered XLFD into the format we'll be
+ * using in the font selector.
+ */
+
+ thistmpsize = 4 * strlen(fontnames[i]) + 256;
+ if (tmpsize < thistmpsize) {
+ tmpsize = thistmpsize;
+ tmp = sresize(tmp, tmpsize, char);
+ }
+ p = tmp;
+
+ /*
+ * Font name is in the form "family (foundry)". (This is
+ * what the GTK 1.2 X font selector does, and it seems to
+ * come out looking reasonably sensible.)
+ */
+ font = p;
+ p += 1 + sprintf(p, "%s (%s)", xlfd->family_name, xlfd->foundry);
+
+ /*
+ * Character set.
+ */
+ charset = p;
+ p += 1 + sprintf(p, "%s-%s", xlfd->charset_registry,
+ xlfd->charset_encoding);
+
+ /*
+ * Style is a mixture of quite a lot of the fields,
+ * with some strange formatting.
+ */
+ style = p;
+ p += sprintf(p, "%s", xlfd->weight_name[0] ? xlfd->weight_name :
+ "regular");
+ if (!g_ascii_strcasecmp(xlfd->slant, "i"))
+ p += sprintf(p, " italic");
+ else if (!g_ascii_strcasecmp(xlfd->slant, "o"))
+ p += sprintf(p, " oblique");
+ else if (!g_ascii_strcasecmp(xlfd->slant, "ri"))
+ p += sprintf(p, " reverse italic");
+ else if (!g_ascii_strcasecmp(xlfd->slant, "ro"))
+ p += sprintf(p, " reverse oblique");
+ else if (!g_ascii_strcasecmp(xlfd->slant, "ot"))
+ p += sprintf(p, " other-slant");
+ if (xlfd->setwidth_name[0] &&
+ g_ascii_strcasecmp(xlfd->setwidth_name, "normal"))
+ p += sprintf(p, " %s", xlfd->setwidth_name);
+ if (!g_ascii_strcasecmp(xlfd->spacing, "m"))
+ p += sprintf(p, " [M]");
+ if (!g_ascii_strcasecmp(xlfd->spacing, "c"))
+ p += sprintf(p, " [C]");
+ if (xlfd->add_style_name[0])
+ p += sprintf(p, " %s", xlfd->add_style_name);
+
+ /*
+ * Style key is the same stuff as above, but with a
+ * couple of transformations done on it to make it
+ * sort more sensibly.
+ */
+ p++;
+ stylekey = p;
+ if (!g_ascii_strcasecmp(xlfd->weight_name, "medium") ||
+ !g_ascii_strcasecmp(xlfd->weight_name, "regular") ||
+ !g_ascii_strcasecmp(xlfd->weight_name, "normal") ||
+ !g_ascii_strcasecmp(xlfd->weight_name, "book"))
+ weightkey = 0;
+ else if (!g_ascii_strncasecmp(xlfd->weight_name, "demi", 4) ||
+ !g_ascii_strncasecmp(xlfd->weight_name, "semi", 4))
+ weightkey = 1;
+ else
+ weightkey = 2;
+ if (!g_ascii_strcasecmp(xlfd->slant, "r"))
+ slantkey = 0;
+ else if (!g_ascii_strncasecmp(xlfd->slant, "r", 1))
+ slantkey = 2;
+ else
+ slantkey = 1;
+ if (!g_ascii_strcasecmp(xlfd->setwidth_name, "normal"))
+ setwidthkey = 0;
+ else
+ setwidthkey = 1;
+
+ p += sprintf(
+ p, "%04d%04d%s%04d%04d%s%04d%04d%s%04d%s%04d%s",
+ weightkey, (int)strlen(xlfd->weight_name), xlfd->weight_name,
+ slantkey, (int)strlen(xlfd->slant), xlfd->slant,
+ setwidthkey,
+ (int)strlen(xlfd->setwidth_name), xlfd->setwidth_name,
+ (int)strlen(xlfd->spacing), xlfd->spacing,
+ (int)strlen(xlfd->add_style_name), xlfd->add_style_name);
+
+ assert(p - tmp < thistmpsize);
+
+ /*
+ * Flags: we need to know whether this is a monospaced
+ * font, which we do by examining the spacing field
+ * again.
+ */
+ int flags = FONTFLAG_SERVERSIDE;
+ if (!strchr("CcMm", xlfd->spacing[0]))
+ flags |= FONTFLAG_NONMONOSPACED;
+
+ /*
+ * Some fonts have a pixel size of zero, meaning they're
+ * treated as scalable. For these purposes, we only want
+ * fonts whose pixel size we actually know, so filter
+ * those out.
+ */
+ if (xlfd->pixel_size)
+ callback(callback_ctx, fontnames[i], font, charset,
+ style, stylekey, xlfd->pixel_size, flags,
+ &x11font_vtable);
+
+ sfree(xlfd);
+ } else {
+ /*
+ * This isn't an XLFD, so it must be an alias.
+ * Transmit it with mostly null data.
+ *
+ * It would be nice to work out if it's monospaced
+ * here, but at the moment I can't see that being
+ * anything but computationally hideous. Ah well.
+ */
+ callback(callback_ctx, fontnames[i], fontnames[i], NULL,
+ NULL, NULL, 0, FONTFLAG_SERVERALIAS, &x11font_vtable);
+
+ sfree(xlfd);
+ }
+ }
+ XFreeFontNames(fontnames);
+ sfree(tmp);
+}
+
+static char *x11font_canonify_fontname(GtkWidget *widget, const char *name,
+ int *size, int *flags,
+ bool resolve_aliases)
+{
+ /*
+ * When given an X11 font name to try to make sense of for a
+ * font selector, we must attempt to load it (to see if it
+ * exists), and then canonify it by extracting its FONT
+ * property, which should give its full XLFD even if what we
+ * originally had was a wildcard.
+ *
+ * However, we must carefully avoid canonifying font
+ * _aliases_, unless specifically asked to, because the font
+ * selector treats them as worthwhile in their own right.
+ */
+ XFontStruct *xfs;
+ Display *disp;
+ Atom fontprop, fontprop2;
+ unsigned long ret;
+
+ if ((disp = get_x11_display()) == NULL)
+ return NULL;
+
+ xfs = XLoadQueryFont(disp, name);
+
+ if (!xfs)
+ return NULL; /* didn't make sense to us, sorry */
+
+ fontprop = XInternAtom(disp, "FONT", False);
+
+ if (XGetFontProperty(xfs, fontprop, &ret)) {
+ char *newname = XGetAtomName(disp, (Atom)ret);
+ if (newname) {
+ unsigned long fsize = 12;
+
+ fontprop2 = XInternAtom(disp, "PIXEL_SIZE", False);
+ if (XGetFontProperty(xfs, fontprop2, &fsize) && fsize > 0) {
+ *size = fsize;
+ XFreeFont(disp, xfs);
+ if (flags) {
+ if (name[0] == '-' || resolve_aliases)
+ *flags = FONTFLAG_SERVERSIDE;
+ else
+ *flags = FONTFLAG_SERVERALIAS;
+ }
+ return dupstr(name[0] == '-' || resolve_aliases ?
+ newname : name);
+ }
+ }
+ }
+
+ XFreeFont(disp, xfs);
+
+ return NULL; /* something went wrong */
+}
+
+static char *x11font_scale_fontname(GtkWidget *widget, const char *name,
+ int size)
+{
+ return NULL; /* shan't */
+}
+
+static char *x11font_size_increment(unifont *font, int increment)
+{
+ struct x11font *xfont = container_of(font, struct x11font, u);
+ Display *disp = xfont->disp;
+ Atom fontprop = XInternAtom(disp, "FONT", False);
+ char *returned_name = NULL;
+ unsigned long ret;
+ if (XGetFontProperty(xfont->fonts[0].xfs, fontprop, &ret)) {
+ struct xlfd_decomposed *xlfd;
+ struct xlfd_decomposed *xlfd_best;
+ char *wc;
+ char **fontnames;
+ int nnames, i, max;
+
+ xlfd = xlfd_decompose(XGetAtomName(disp, (Atom)ret));
+ if (!xlfd)
+ return NULL;
+
+ /*
+ * Form a wildcard consisting of everything in the
+ * original XLFD except for the size-related fields.
+ */
+ {
+ struct xlfd_decomposed xlfd_wc = *xlfd; /* structure copy */
+ xlfd_wc.pixel_size = XLFD_INT_WILDCARD;
+ xlfd_wc.point_size = XLFD_INT_WILDCARD;
+ xlfd_wc.average_width = XLFD_INT_WILDCARD;
+ wc = xlfd_recompose(&xlfd_wc);
+ }
+
+ /*
+ * Fetch all the font names matching that wildcard.
+ */
+ max = 32768;
+ while (1) {
+ fontnames = XListFonts(disp, wc, max, &nnames);
+ if (nnames >= max) {
+ XFreeFontNames(fontnames);
+ max *= 2;
+ } else
+ break;
+ }
+
+ sfree(wc);
+
+ /*
+ * Iterate over those to find the one closest in size to the
+ * original font, in the correct direction.
+ */
+
+#define FLIPPED_SIZE(xlfd) \
+ (((xlfd)->pixel_size + (xlfd)->point_size) * \
+ (increment < 0 ? -1 : +1))
+
+ xlfd_best = NULL;
+ for (i = 0; i < nnames; i++) {
+ struct xlfd_decomposed *xlfd2 = xlfd_decompose(fontnames[i]);
+ if (!xlfd2)
+ continue;
+
+ if (xlfd2->pixel_size != 0 &&
+ FLIPPED_SIZE(xlfd2) > FLIPPED_SIZE(xlfd) &&
+ (!xlfd_best || FLIPPED_SIZE(xlfd2)<FLIPPED_SIZE(xlfd_best))) {
+ sfree(xlfd_best);
+ xlfd_best = xlfd2;
+ xlfd2 = NULL;
+ }
+
+ sfree(xlfd2);
+ }
+
+#undef FLIPPED_SIZE
+
+ if (xlfd_best) {
+ char *bare_returned_name = xlfd_recompose(xlfd_best);
+ returned_name = dupcat(
+ xfont->u.vt->prefix, ":", bare_returned_name);
+ sfree(bare_returned_name);
+ }
+
+ XFreeFontNames(fontnames);
+ sfree(xlfd);
+ sfree(xlfd_best);
+ }
+ return returned_name;
+}
+
+#endif /* NOT_X_WINDOWS */
+
+#if GTK_CHECK_VERSION(2,0,0)
+
+/* ----------------------------------------------------------------------
+ * Pango font implementation (for GTK 2 only).
+ */
+
+#if defined PANGO_PRE_1POINT4 && !defined PANGO_PRE_1POINT6
+#define PANGO_PRE_1POINT6 /* make life easier for pre-1.4 folk */
+#endif
+
+static bool pangofont_has_glyph(unifont *font, wchar_t glyph);
+static void pangofont_draw_text(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string, int len,
+ bool wide, bool bold, int cellwidth);
+static void pangofont_draw_combining(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string,
+ int len, bool wide, bool bold,
+ int cellwidth);
+static unifont *pangofont_create(GtkWidget *widget, const char *name,
+ bool wide, bool bold,
+ int shadowoffset, bool shadowalways);
+static unifont *pangofont_create_fallback(GtkWidget *widget, int height,
+ bool wide, bool bold,
+ int shadowoffset, bool shadowalways);
+static void pangofont_destroy(unifont *font);
+static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback,
+ void *callback_ctx);
+static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name,
+ int *size, int *flags,
+ bool resolve_aliases);
+static char *pangofont_scale_fontname(GtkWidget *widget, const char *name,
+ int size);
+static char *pangofont_size_increment(unifont *font, int increment);
+
+struct pangofont {
+ /*
+ * Pango objects.
+ */
+ PangoFontDescription *desc;
+ PangoFontset *fset;
+ /*
+ * The containing widget.
+ */
+ GtkWidget *widget;
+ /*
+ * Data passed in to unifont_create().
+ */
+ int shadowoffset;
+ bool bold, shadowalways;
+ /*
+ * Cache of character widths, indexed by Unicode code point. In
+ * pixels; -1 means we haven't asked Pango about this character
+ * before.
+ */
+ int *widthcache;
+ unsigned nwidthcache;
+
+ struct unifont u;
+};
+
+static const UnifontVtable pangofont_vtable = {
+ .create = pangofont_create,
+ .create_fallback = pangofont_create_fallback,
+ .destroy = pangofont_destroy,
+ .has_glyph = pangofont_has_glyph,
+ .draw_text = pangofont_draw_text,
+ .draw_combining = pangofont_draw_combining,
+ .enum_fonts = pangofont_enum_fonts,
+ .canonify_fontname = pangofont_canonify_fontname,
+ .scale_fontname = pangofont_scale_fontname,
+ .size_increment = pangofont_size_increment,
+ .prefix = "client",
+};
+
+/*
+ * This function is used to rigorously validate a
+ * PangoFontDescription. Later versions of Pango have a nasty
+ * habit of accepting _any_ old string as input to
+ * pango_font_description_from_string and returning a font
+ * description which can actually be used to display text, even if
+ * they have to do it by falling back to their most default font.
+ * This is doubtless helpful in some situations, but not here,
+ * because we need to know if a Pango font string actually _makes
+ * sense_ in order to fall back to treating it as an X font name
+ * if it doesn't. So we check that the font family is actually one
+ * supported by Pango.
+ */
+static bool pangofont_check_desc_makes_sense(PangoContext *ctx,
+ PangoFontDescription *desc)
+{
+#ifndef PANGO_PRE_1POINT6
+ PangoFontMap *map;
+#endif
+ PangoFontFamily **families;
+ int i, nfamilies;
+ bool matched;
+
+ /*
+ * Ask Pango for a list of font families, and iterate through
+ * them to see if one of them matches the family in the
+ * PangoFontDescription.
+ */
+#ifndef PANGO_PRE_1POINT6
+ map = pango_context_get_font_map(ctx);
+ if (!map)
+ return false;
+ pango_font_map_list_families(map, &families, &nfamilies);
+#else
+ pango_context_list_families(ctx, &families, &nfamilies);
+#endif
+
+ matched = false;
+ for (i = 0; i < nfamilies; i++) {
+ if (!g_ascii_strcasecmp(pango_font_family_get_name(families[i]),
+ pango_font_description_get_family(desc))) {
+ matched = true;
+ break;
+ }
+ }
+ g_free(families);
+
+ return matched;
+}
+
+static unifont *pangofont_create_internal(GtkWidget *widget,
+ PangoContext *ctx,
+ PangoFontDescription *desc,
+ bool wide, bool bold,
+ int shadowoffset, bool shadowalways)
+{
+ struct pangofont *pfont;
+#ifndef PANGO_PRE_1POINT6
+ PangoFontMap *map;
+#endif
+ PangoFontset *fset;
+ PangoFontMetrics *metrics;
+
+#ifndef PANGO_PRE_1POINT6
+ map = pango_context_get_font_map(ctx);
+ if (!map) {
+ pango_font_description_free(desc);
+ return NULL;
+ }
+ fset = pango_font_map_load_fontset(map, ctx, desc,
+ pango_context_get_language(ctx));
+#else
+ fset = pango_context_load_fontset(ctx, desc,
+ pango_context_get_language(ctx));
+#endif
+ if (!fset) {
+ pango_font_description_free(desc);
+ return NULL;
+ }
+ metrics = pango_fontset_get_metrics(fset);
+ if (!metrics ||
+ pango_font_metrics_get_approximate_digit_width(metrics) == 0) {
+ pango_font_description_free(desc);
+ g_object_unref(fset);
+ return NULL;
+ }
+
+ pfont = snew(struct pangofont);
+ pfont->u.vt = &pangofont_vtable;
+ pfont->u.width =
+ PANGO_PIXELS(pango_font_metrics_get_approximate_digit_width(metrics));
+ pfont->u.ascent =
+ PANGO_PIXELS_CEIL(pango_font_metrics_get_ascent(metrics));
+ pfont->u.descent =
+ PANGO_PIXELS_CEIL(pango_font_metrics_get_descent(metrics));
+ pfont->u.height = pfont->u.ascent + pfont->u.descent;
+ pfont->u.strikethrough_y =
+ PANGO_PIXELS(pango_font_metrics_get_ascent(metrics) -
+ pango_font_metrics_get_strikethrough_position(metrics));
+ pfont->u.want_fallback = false;
+#ifdef DRAW_TEXT_CAIRO
+ pfont->u.preferred_drawtype = DRAWTYPE_CAIRO;
+#elif defined DRAW_TEXT_GDK
+ pfont->u.preferred_drawtype = DRAWTYPE_GDK;
+#else
+#error No drawtype available at all
+#endif
+ /* The Pango API is hardwired to UTF-8 */
+ pfont->u.public_charset = CS_UTF8;
+ pfont->desc = desc;
+ pfont->fset = fset;
+ pfont->widget = widget;
+ pfont->bold = bold;
+ pfont->shadowoffset = shadowoffset;
+ pfont->shadowalways = shadowalways;
+ pfont->widthcache = NULL;
+ pfont->nwidthcache = 0;
+
+ pango_font_metrics_unref(metrics);
+
+ return &pfont->u;
+}
+
+static unifont *pangofont_create(GtkWidget *widget, const char *name,
+ bool wide, bool bold,
+ int shadowoffset, bool shadowalways)
+{
+ PangoContext *ctx;
+ PangoFontDescription *desc;
+
+ desc = pango_font_description_from_string(name);
+ if (!desc)
+ return NULL;
+ ctx = gtk_widget_get_pango_context(widget);
+ if (!ctx) {
+ pango_font_description_free(desc);
+ return NULL;
+ }
+ if (!pangofont_check_desc_makes_sense(ctx, desc)) {
+ pango_font_description_free(desc);
+ return NULL;
+ }
+ return pangofont_create_internal(widget, ctx, desc, wide, bold,
+ shadowoffset, shadowalways);
+}
+
+static unifont *pangofont_create_fallback(GtkWidget *widget, int height,
+ bool wide, bool bold,
+ int shadowoffset, bool shadowalways)
+{
+ PangoContext *ctx;
+ PangoFontDescription *desc;
+
+ desc = pango_font_description_from_string("Monospace");
+ if (!desc)
+ return NULL;
+ ctx = gtk_widget_get_pango_context(widget);
+ if (!ctx) {
+ pango_font_description_free(desc);
+ return NULL;
+ }
+ pango_font_description_set_absolute_size(desc, height * PANGO_SCALE);
+ return pangofont_create_internal(widget, ctx, desc, wide, bold,
+ shadowoffset, shadowalways);
+}
+
+static void pangofont_destroy(unifont *font)
+{
+ struct pangofont *pfont = container_of(font, struct pangofont, u);
+ pango_font_description_free(pfont->desc);
+ sfree(pfont->widthcache);
+ g_object_unref(pfont->fset);
+ sfree(pfont);
+}
+
+static int pangofont_char_width(PangoLayout *layout, struct pangofont *pfont,
+ wchar_t uchr, const char *utfchr, int utflen)
+{
+ /*
+ * Here we check whether a character has the same width as the
+ * character cell it'll be drawn in. Because profiling showed that
+ * asking Pango for text sizes was a huge bottleneck when we were
+ * calling it every time we needed to know this, we instead call
+ * it only on characters we don't already know about, and cache
+ * the results.
+ */
+
+ if ((unsigned)uchr >= pfont->nwidthcache) {
+ unsigned newsize = ((int)uchr + 0x100) & ~0xFF;
+ pfont->widthcache = sresize(pfont->widthcache, newsize, int);
+ while (pfont->nwidthcache < newsize)
+ pfont->widthcache[pfont->nwidthcache++] = -1;
+ }
+
+ if (pfont->widthcache[uchr] < 0) {
+ PangoRectangle rect;
+ pango_layout_set_text(layout, utfchr, utflen);
+ pango_layout_get_extents(layout, NULL, &rect);
+ pfont->widthcache[uchr] = rect.width;
+ }
+
+ return pfont->widthcache[uchr];
+}
+
+static bool pangofont_has_glyph(unifont *font, wchar_t glyph)
+{
+ /* Pango implements font fallback, so assume it has everything */
+ return true;
+}
+
+#ifdef DRAW_TEXT_GDK
+static void pango_gdk_draw_layout(unifont_drawctx *ctx,
+ gint x, gint y, PangoLayout *layout)
+{
+ gdk_draw_layout(ctx->u.gdk.target, ctx->u.gdk.gc, x, y, layout);
+}
+#endif
+
+#ifdef DRAW_TEXT_CAIRO
+static void pango_cairo_draw_layout(unifont_drawctx *ctx,
+ gint x, gint y, PangoLayout *layout)
+{
+ cairo_move_to(ctx->u.cairo.cr, x, y);
+ pango_cairo_show_layout(ctx->u.cairo.cr, layout);
+}
+#endif
+
+static void pangofont_draw_internal(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string,
+ int len, bool wide, bool bold,
+ int cellwidth, bool combining)
+{
+ struct pangofont *pfont = container_of(font, struct pangofont, u);
+ PangoLayout *layout;
+ PangoRectangle rect;
+ char *utfstring, *utfptr;
+ int utflen;
+ bool shadowbold = false;
+ void (*draw_layout)(unifont_drawctx *ctx,
+ gint x, gint y, PangoLayout *layout) = NULL;
+
+#ifdef DRAW_TEXT_GDK
+ if (ctx->type == DRAWTYPE_GDK) {
+ draw_layout = pango_gdk_draw_layout;
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (ctx->type == DRAWTYPE_CAIRO) {
+ draw_layout = pango_cairo_draw_layout;
+ }
+#endif
+ assert(draw_layout);
+
+ if (wide)
+ cellwidth *= 2;
+
+ y -= pfont->u.ascent;
+
+ layout = pango_layout_new(gtk_widget_get_pango_context(pfont->widget));
+ pango_layout_set_font_description(layout, pfont->desc);
+ if (bold && !pfont->bold) {
+ if (pfont->shadowalways)
+ shadowbold = true;
+ else {
+ PangoFontDescription *desc2 =
+ pango_font_description_copy_static(pfont->desc);
+ pango_font_description_set_weight(desc2, PANGO_WEIGHT_BOLD);
+ pango_layout_set_font_description(layout, desc2);
+ }
+ }
+
+ /*
+ * Pango always expects UTF-8, so convert the input wide character
+ * string to UTF-8.
+ */
+ utfstring = snewn(len*6+1, char); /* UTF-8 has max 6 bytes/char */
+ utflen = wc_to_mb(CS_UTF8, 0, string, len, utfstring, len*6+1, ".");
+
+ utfptr = utfstring;
+ while (utflen > 0) {
+ int clen, n;
+ int desired = cellwidth * PANGO_SCALE;
+
+ /*
+ * We want to display every character from this string in
+ * the centre of its own character cell. In the worst case,
+ * this requires a separate text-drawing call for each
+ * character; but in the common case where the font is
+ * properly fixed-width, we can draw many characters in one
+ * go which is much faster.
+ *
+ * This still isn't really ideal. If you look at what
+ * happens in the X protocol as a result of all of this, you
+ * find - naturally enough - that each call to
+ * gdk_draw_layout() generates a separate set of X RENDER
+ * operations involving creating a picture, setting a clip
+ * rectangle, doing some drawing and undoing the whole lot.
+ * In an ideal world, we should _always_ be able to turn the
+ * contents of this loop into a single RenderCompositeGlyphs
+ * operation which internally specifies inter-character
+ * deltas to get the spacing right, which would give us full
+ * speed _even_ in the worst case of a non-fixed-width font.
+ * However, Pango's architecture and documentation are so
+ * unhelpful that I have no idea how if at all to persuade
+ * them to do that.
+ */
+
+ if (combining) {
+ /*
+ * For a character with combining stuff, we just dump the
+ * whole lot in one go, and expect it to take up just one
+ * character cell.
+ */
+ clen = utflen;
+ n = 1;
+ } else {
+ /*
+ * Start by extracting a single UTF-8 character from the
+ * string.
+ */
+ clen = 1;
+ while (clen < utflen &&
+ (unsigned char)utfptr[clen] >= 0x80 &&
+ (unsigned char)utfptr[clen] < 0xC0)
+ clen++;
+ n = 1;
+
+ if (is_rtl(string[0]) ||
+ pangofont_char_width(layout, pfont, string[n-1],
+ utfptr, clen) != desired) {
+ /*
+ * If this character is a right-to-left one, or has an
+ * unusual width, then we must display it on its own.
+ */
+ } else {
+ /*
+ * Try to amalgamate a contiguous string of characters
+ * with the expected sensible width, for the common case
+ * in which we're using a monospaced font and everything
+ * works as expected.
+ */
+ while (clen < utflen) {
+ int oldclen = clen;
+ clen++; /* skip UTF-8 introducer byte */
+ while (clen < utflen &&
+ (unsigned char)utfptr[clen] >= 0x80 &&
+ (unsigned char)utfptr[clen] < 0xC0)
+ clen++;
+ n++;
+ if (is_rtl(string[n-1]) ||
+ pangofont_char_width(layout, pfont,
+ string[n-1], utfptr + oldclen,
+ clen - oldclen) != desired) {
+ clen = oldclen;
+ n--;
+ break;
+ }
+ }
+ }
+ }
+
+ pango_layout_set_text(layout, utfptr, clen);
+ pango_layout_get_pixel_extents(layout, NULL, &rect);
+
+ draw_layout(ctx,
+ x + (n*cellwidth - rect.width)/2,
+ y + (pfont->u.height - rect.height)/2, layout);
+ if (shadowbold)
+ draw_layout(ctx,
+ x + (n*cellwidth - rect.width)/2 + pfont->shadowoffset,
+ y + (pfont->u.height - rect.height)/2, layout);
+
+ utflen -= clen;
+ utfptr += clen;
+ string += n;
+ x += n * cellwidth;
+ }
+
+ sfree(utfstring);
+
+ g_object_unref(layout);
+}
+
+static void pangofont_draw_text(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string, int len,
+ bool wide, bool bold, int cellwidth)
+{
+ pangofont_draw_internal(ctx, font, x, y, string, len, wide, bold,
+ cellwidth, false);
+}
+
+static void pangofont_draw_combining(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string,
+ int len, bool wide, bool bold,
+ int cellwidth)
+{
+ wchar_t *tmpstring = NULL;
+ if (mk_wcwidth(string[0]) == 0) {
+ /*
+ * If we've been told to draw a sequence of _only_ combining
+ * characters, prefix a space so that they have something to
+ * combine with.
+ */
+ tmpstring = snewn(len+1, wchar_t);
+ memcpy(tmpstring+1, string, len * sizeof(wchar_t));
+ tmpstring[0] = L' ';
+ string = tmpstring;
+ len++;
+ }
+ pangofont_draw_internal(ctx, font, x, y, string, len, wide, bold,
+ cellwidth, true);
+ sfree(tmpstring);
+}
+
+/*
+ * Dummy size value to be used when converting a
+ * PangoFontDescription of a scalable font to a string for
+ * internal use.
+ */
+#define PANGO_DUMMY_SIZE 12
+
+static void pangofont_enum_fonts(GtkWidget *widget, fontsel_add_entry callback,
+ void *callback_ctx)
+{
+ PangoContext *ctx;
+#ifndef PANGO_PRE_1POINT6
+ PangoFontMap *map;
+#endif
+ PangoFontFamily **families;
+ int i, nfamilies;
+
+ ctx = gtk_widget_get_pango_context(widget);
+ if (!ctx)
+ return;
+
+ /*
+ * Ask Pango for a list of font families, and iterate through
+ * them.
+ */
+#ifndef PANGO_PRE_1POINT6
+ map = pango_context_get_font_map(ctx);
+ if (!map)
+ return;
+ pango_font_map_list_families(map, &families, &nfamilies);
+#else
+ pango_context_list_families(ctx, &families, &nfamilies);
+#endif
+ for (i = 0; i < nfamilies; i++) {
+ PangoFontFamily *family = families[i];
+ const char *familyname;
+ int flags;
+ PangoFontFace **faces;
+ int j, nfaces;
+
+ /*
+ * Set up our flags for this font family, and get the name
+ * string.
+ */
+ flags = FONTFLAG_CLIENTSIDE;
+#ifndef PANGO_PRE_1POINT4
+ /*
+ * In very early versions of Pango, we can't tell
+ * monospaced fonts from non-monospaced.
+ */
+ if (!pango_font_family_is_monospace(family))
+ flags |= FONTFLAG_NONMONOSPACED;
+#endif
+ familyname = pango_font_family_get_name(family);
+
+ /*
+ * Go through the available font faces in this family.
+ */
+ pango_font_family_list_faces(family, &faces, &nfaces);
+ for (j = 0; j < nfaces; j++) {
+ PangoFontFace *face = faces[j];
+ PangoFontDescription *desc;
+ const char *facename;
+ int *sizes;
+ int k, nsizes, dummysize;
+
+ /*
+ * Get the face name string.
+ */
+ facename = pango_font_face_get_face_name(face);
+
+ /*
+ * Set up a font description with what we've got so
+ * far. We'll fill in the size field manually and then
+ * call pango_font_description_to_string() to give the
+ * full real name of the specific font.
+ */
+ desc = pango_font_face_describe(face);
+
+ /*
+ * See if this font has a list of specific sizes.
+ */
+#ifndef PANGO_PRE_1POINT4
+ pango_font_face_list_sizes(face, &sizes, &nsizes);
+#else
+ /*
+ * In early versions of Pango, that call wasn't
+ * supported; we just have to assume everything is
+ * scalable.
+ */
+ sizes = NULL;
+#endif
+ if (!sizes) {
+ /*
+ * Write a single entry with a dummy size.
+ */
+ dummysize = PANGO_DUMMY_SIZE * PANGO_SCALE;
+ sizes = &dummysize;
+ nsizes = 1;
+ }
+
+ /*
+ * If so, go through them one by one.
+ */
+ for (k = 0; k < nsizes; k++) {
+ char *fullname, *stylekey;
+
+ pango_font_description_set_size(desc, sizes[k]);
+
+ fullname = pango_font_description_to_string(desc);
+
+ /*
+ * Construct the sorting key for font styles.
+ */
+ {
+ strbuf *buf = strbuf_new();
+
+ int weight = pango_font_description_get_weight(desc);
+ /* Weight: normal, then lighter, then bolder */
+ if (weight <= PANGO_WEIGHT_NORMAL)
+ weight = PANGO_WEIGHT_NORMAL - weight;
+ put_fmt(buf, "%4d", weight);
+
+ put_fmt(buf, " %2d",
+ pango_font_description_get_style(desc));
+
+ int stretch = pango_font_description_get_stretch(desc);
+ /* Stretch: closer to normal sorts earlier */
+ stretch = 2 * abs(PANGO_STRETCH_NORMAL - stretch) +
+ (stretch < PANGO_STRETCH_NORMAL);
+ put_fmt(buf, " %2d", stretch);
+
+ put_fmt(buf, " %2d",
+ pango_font_description_get_variant(desc));
+
+ stylekey = strbuf_to_str(buf);
+ }
+
+ /*
+ * Got everything. Hand off to the callback.
+ * (The charset string is NULL, because only
+ * server-side X fonts use it.)
+ */
+ callback(callback_ctx, fullname, familyname, NULL, facename,
+ stylekey,
+ (sizes == &dummysize ? 0 : PANGO_PIXELS(sizes[k])),
+ flags, &pangofont_vtable);
+
+ sfree(stylekey);
+ g_free(fullname);
+ }
+ if (sizes != &dummysize)
+ g_free(sizes);
+
+ pango_font_description_free(desc);
+ }
+ g_free(faces);
+ }
+ g_free(families);
+}
+
+static char *pangofont_canonify_fontname(GtkWidget *widget, const char *name,
+ int *size, int *flags,
+ bool resolve_aliases)
+{
+ /*
+ * When given a Pango font name to try to make sense of for a
+ * font selector, we must normalise it to PANGO_DUMMY_SIZE and
+ * extract its original size (in pixels) into the `size' field.
+ */
+ PangoContext *ctx;
+#ifndef PANGO_PRE_1POINT6
+ PangoFontMap *map;
+#endif
+ PangoFontDescription *desc;
+ PangoFontset *fset;
+ PangoFontMetrics *metrics;
+ char *newname, *retname;
+
+ desc = pango_font_description_from_string(name);
+ if (!desc)
+ return NULL;
+ ctx = gtk_widget_get_pango_context(widget);
+ if (!ctx) {
+ pango_font_description_free(desc);
+ return NULL;
+ }
+ if (!pangofont_check_desc_makes_sense(ctx, desc)) {
+ pango_font_description_free(desc);
+ return NULL;
+ }
+#ifndef PANGO_PRE_1POINT6
+ map = pango_context_get_font_map(ctx);
+ if (!map) {
+ pango_font_description_free(desc);
+ return NULL;
+ }
+ fset = pango_font_map_load_fontset(map, ctx, desc,
+ pango_context_get_language(ctx));
+#else
+ fset = pango_context_load_fontset(ctx, desc,
+ pango_context_get_language(ctx));
+#endif
+ if (!fset) {
+ pango_font_description_free(desc);
+ return NULL;
+ }
+ metrics = pango_fontset_get_metrics(fset);
+ if (!metrics ||
+ pango_font_metrics_get_approximate_digit_width(metrics) == 0) {
+ pango_font_description_free(desc);
+ g_object_unref(fset);
+ return NULL;
+ }
+
+ *size = PANGO_PIXELS(pango_font_description_get_size(desc));
+ *flags = FONTFLAG_CLIENTSIDE;
+ pango_font_description_set_size(desc, PANGO_DUMMY_SIZE * PANGO_SCALE);
+ newname = pango_font_description_to_string(desc);
+ retname = dupstr(newname);
+ g_free(newname);
+
+ pango_font_metrics_unref(metrics);
+ pango_font_description_free(desc);
+ g_object_unref(fset);
+
+ return retname;
+}
+
+static char *pangofont_scale_fontname(GtkWidget *widget, const char *name,
+ int size)
+{
+ PangoFontDescription *desc;
+ char *newname, *retname;
+
+ desc = pango_font_description_from_string(name);
+ if (!desc)
+ return NULL;
+ pango_font_description_set_size(desc, size * PANGO_SCALE);
+ newname = pango_font_description_to_string(desc);
+ retname = dupstr(newname);
+ g_free(newname);
+ pango_font_description_free(desc);
+
+ return retname;
+}
+
+static char *pangofont_size_increment(unifont *font, int increment)
+{
+ struct pangofont *pfont = container_of(font, struct pangofont, u);
+ PangoFontDescription *desc;
+ int size;
+ char *newname, *retname;
+
+ desc = pango_font_description_copy_static(pfont->desc);
+
+ size = pango_font_description_get_size(desc);
+ size += PANGO_SCALE * increment;
+
+ if (size <= 0) {
+ retname = NULL;
+ } else {
+ pango_font_description_set_size(desc, size);
+ newname = pango_font_description_to_string(desc);
+ retname = dupcat(pfont->u.vt->prefix, ":", newname);
+ g_free(newname);
+ }
+
+ pango_font_description_free(desc);
+ return retname;
+}
+
+#endif /* GTK_CHECK_VERSION(2,0,0) */
+
+/* ----------------------------------------------------------------------
+ * Outermost functions which do the vtable dispatch.
+ */
+
+/*
+ * Complete list of font-type subclasses. Listed in preference
+ * order for unifont_create(). (That is, in the extremely unlikely
+ * event that the same font name is valid as both a Pango and an
+ * X11 font, it will be interpreted as the former in the absence
+ * of an explicit type-disambiguating prefix.)
+ *
+ * The 'multifont' subclass is omitted here, as discussed above.
+ */
+static const struct UnifontVtable *unifont_types[] = {
+#if GTK_CHECK_VERSION(2,0,0)
+ &pangofont_vtable,
+#endif
+#ifndef NOT_X_WINDOWS
+ &x11font_vtable,
+#endif
+};
+
+/*
+ * Function which takes a font name and processes the optional
+ * scheme prefix. Returns the tail of the font name suitable for
+ * passing to individual font scheme functions, and also provides
+ * a subrange of the unifont_types[] array above.
+ *
+ * The return values `start' and `end' denote a half-open interval
+ * in unifont_types[]; that is, the correct way to iterate over
+ * them is
+ *
+ * for (i = start; i < end; i++) {...}
+ */
+static const char *unifont_do_prefix(const char *name, int *start, int *end)
+{
+ int colonpos = strcspn(name, ":");
+ int i;
+
+ if (name[colonpos]) {
+ /*
+ * There's a colon prefix on the font name. Use it to work
+ * out which subclass to use.
+ */
+ for (i = 0; i < lenof(unifont_types); i++) {
+ if (strlen(unifont_types[i]->prefix) == colonpos &&
+ !strncmp(unifont_types[i]->prefix, name, colonpos)) {
+ *start = i;
+ *end = i+1;
+ return name + colonpos + 1;
+ }
+ }
+ /*
+ * None matched, so return an empty scheme list to prevent
+ * any scheme from being called at all.
+ */
+ *start = *end = 0;
+ return name + colonpos + 1;
+ } else {
+ /*
+ * No colon prefix, so just use all the subclasses.
+ */
+ *start = 0;
+ *end = lenof(unifont_types);
+ return name;
+ }
+}
+
+unifont *unifont_create(GtkWidget *widget, const char *name, bool wide,
+ bool bold, int shadowoffset, bool shadowalways)
+{
+ int i, start, end;
+
+ name = unifont_do_prefix(name, &start, &end);
+
+ for (i = start; i < end; i++) {
+ unifont *ret = unifont_types[i]->create(widget, name, wide, bold,
+ shadowoffset, shadowalways);
+ if (ret)
+ return ret;
+ }
+ return NULL; /* font not found in any scheme */
+}
+
+void unifont_destroy(unifont *font)
+{
+ font->vt->destroy(font);
+}
+
+void unifont_draw_text(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string, int len,
+ bool wide, bool bold, int cellwidth)
+{
+ font->vt->draw_text(ctx, font, x, y, string, len, wide, bold, cellwidth);
+}
+
+void unifont_draw_combining(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string, int len,
+ bool wide, bool bold, int cellwidth)
+{
+ font->vt->draw_combining(ctx, font, x, y, string, len, wide, bold,
+ cellwidth);
+}
+
+char *unifont_size_increment(unifont *font, int increment)
+{
+ return font->vt->size_increment(font, increment);
+}
+
+/* ----------------------------------------------------------------------
+ * Multiple-font wrapper. This is a type of unifont which encapsulates
+ * up to two other unifonts, permitting missing glyphs in the main
+ * font to be filled in by a fallback font.
+ *
+ * This is a type of unifont just like the previous two, but it has a
+ * separate constructor which is manually called by the client, so it
+ * doesn't appear in the list of available font types enumerated by
+ * unifont_create. This means it's not used by unifontsel either, so
+ * it doesn't need to support any methods except draw_text and
+ * destroy.
+ */
+
+static void multifont_draw_text(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string, int len,
+ bool wide, bool bold, int cellwidth);
+static void multifont_draw_combining(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string,
+ int len, bool wide, bool bold,
+ int cellwidth);
+static void multifont_destroy(unifont *font);
+static char *multifont_size_increment(unifont *font, int increment);
+
+struct multifont {
+ unifont *main;
+ unifont *fallback;
+
+ struct unifont u;
+};
+
+static const UnifontVtable multifont_vtable = {
+ .create = NULL, /* creation is done specially */
+ .create_fallback = NULL,
+ .destroy = multifont_destroy,
+ .has_glyph = NULL,
+ .draw_text = multifont_draw_text,
+ .draw_combining = multifont_draw_combining,
+ .enum_fonts = NULL,
+ .canonify_fontname = NULL,
+ .scale_fontname = NULL,
+ .size_increment = multifont_size_increment,
+ .prefix = "client",
+};
+
+unifont *multifont_create(GtkWidget *widget, const char *name,
+ bool wide, bool bold,
+ int shadowoffset, bool shadowalways)
+{
+ int i;
+ unifont *font, *fallback;
+ struct multifont *mfont;
+
+ font = unifont_create(widget, name, wide, bold,
+ shadowoffset, shadowalways);
+ if (!font)
+ return NULL;
+
+ fallback = NULL;
+ if (font->want_fallback) {
+ for (i = 0; i < lenof(unifont_types); i++) {
+ if (unifont_types[i]->create_fallback) {
+ fallback = unifont_types[i]->create_fallback(
+ widget, font->height, wide, bold,
+ shadowoffset, shadowalways);
+ if (fallback)
+ break;
+ }
+ }
+ }
+
+ /*
+ * Construct our multifont. Public members are all copied from the
+ * primary font we're wrapping.
+ */
+ mfont = snew(struct multifont);
+ mfont->u.vt = &multifont_vtable;
+ mfont->u.width = font->width;
+ mfont->u.ascent = font->ascent;
+ mfont->u.descent = font->descent;
+ mfont->u.height = font->height;
+ mfont->u.strikethrough_y = font->strikethrough_y;
+ mfont->u.public_charset = font->public_charset;
+ mfont->u.want_fallback = false; /* shouldn't be needed, but just in case */
+ mfont->u.preferred_drawtype = font->preferred_drawtype;
+ mfont->main = font;
+ mfont->fallback = fallback;
+
+ return &mfont->u;
+}
+
+static void multifont_destroy(unifont *font)
+{
+ struct multifont *mfont = container_of(font, struct multifont, u);
+ unifont_destroy(mfont->main);
+ if (mfont->fallback)
+ unifont_destroy(mfont->fallback);
+ sfree(mfont);
+}
+
+typedef void (*unifont_draw_func_t)(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string,
+ int len, bool wide, bool bold,
+ int cellwidth);
+
+static void multifont_draw_main(unifont_drawctx *ctx, unifont *font, int x,
+ int y, const wchar_t *string, int len,
+ bool wide, bool bold, int cellwidth,
+ int cellinc, unifont_draw_func_t draw)
+{
+ struct multifont *mfont = container_of(font, struct multifont, u);
+ unifont *f;
+ bool ok;
+ int i;
+
+ while (len > 0) {
+ /*
+ * Find a maximal sequence of characters which are, or are
+ * not, supported by our main font.
+ */
+ ok = mfont->main->vt->has_glyph(mfont->main, string[0]);
+ for (i = 1;
+ i < len &&
+ !mfont->main->vt->has_glyph(mfont->main, string[i]) == !ok;
+ i++);
+
+ /*
+ * Now display it.
+ */
+ f = ok ? mfont->main : mfont->fallback;
+ if (f)
+ draw(ctx, f, x, y, string, i, wide, bold, cellwidth);
+ string += i;
+ len -= i;
+ x += i * cellinc;
+ }
+}
+
+static void multifont_draw_text(unifont_drawctx *ctx, unifont *font, int x,
+ int y, const wchar_t *string, int len,
+ bool wide, bool bold, int cellwidth)
+{
+ multifont_draw_main(ctx, font, x, y, string, len, wide, bold,
+ cellwidth, cellwidth, unifont_draw_text);
+}
+
+static void multifont_draw_combining(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string,
+ int len, bool wide, bool bold,
+ int cellwidth)
+{
+ multifont_draw_main(ctx, font, x, y, string, len, wide, bold,
+ cellwidth, 0, unifont_draw_combining);
+}
+
+static char *multifont_size_increment(unifont *font, int increment)
+{
+ struct multifont *mfont = container_of(font, struct multifont, u);
+ return unifont_size_increment(mfont->main, increment);
+}
+
+#if GTK_CHECK_VERSION(2,0,0)
+
+/* ----------------------------------------------------------------------
+ * Implementation of a unified font selector. Used on GTK 2 only;
+ * for GTK 1 we still use the standard font selector.
+ */
+
+typedef struct fontinfo fontinfo;
+
+typedef struct unifontsel_internal {
+ GtkListStore *family_model, *style_model, *size_model;
+ GtkWidget *family_list, *style_list, *size_entry, *size_list;
+ GtkWidget *filter_buttons[4];
+ int n_filter_buttons;
+ GtkWidget *preview_area;
+#ifndef NO_BACKING_PIXMAPS
+ GdkPixmap *preview_pixmap;
+#endif
+ int preview_width, preview_height;
+ GdkColor preview_fg, preview_bg;
+ int filter_flags;
+ tree234 *fonts_by_realname, *fonts_by_selorder;
+ fontinfo *selected;
+ int selsize, intendedsize;
+ bool inhibit_response; /* inhibit callbacks when we change GUI controls */
+
+ unifontsel u;
+} unifontsel_internal;
+
+/*
+ * The structure held in the tree234s. All the string members are
+ * part of the same allocated area, so don't need freeing
+ * separately.
+ */
+struct fontinfo {
+ char *realname;
+ char *family, *charset, *style, *stylekey;
+ int size, flags;
+ /*
+ * Fallback sorting key, to permit multiple identical entries
+ * to exist in the selorder tree.
+ */
+ int index;
+ /*
+ * Indices mapping fontinfo structures to indices in the list
+ * boxes. sizeindex is irrelevant if the font is scalable
+ * (size==0).
+ */
+ int familyindex, styleindex, sizeindex;
+ /*
+ * The class of font.
+ */
+ const struct UnifontVtable *fontclass;
+};
+
+struct fontinfo_realname_find {
+ const char *realname;
+ int flags;
+};
+
+static int strnullcasecmp(const char *a, const char *b)
+{
+ int i;
+
+ /*
+ * If exactly one of the inputs is NULL, it compares before
+ * the other one.
+ */
+ if ((i = (!b) - (!a)) != 0)
+ return i;
+
+ /*
+ * NULL compares equal.
+ */
+ if (!a)
+ return 0;
+
+ /*
+ * Otherwise, ordinary strcasecmp.
+ */
+ return g_ascii_strcasecmp(a, b);
+}
+
+static int fontinfo_realname_compare(void *av, void *bv)
+{
+ fontinfo *a = (fontinfo *)av;
+ fontinfo *b = (fontinfo *)bv;
+ int i;
+
+ if ((i = strnullcasecmp(a->realname, b->realname)) != 0)
+ return i;
+ if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK))
+ return ((a->flags & FONTFLAG_SORT_MASK) <
+ (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1);
+ return 0;
+}
+
+static int fontinfo_realname_find(void *av, void *bv)
+{
+ struct fontinfo_realname_find *a = (struct fontinfo_realname_find *)av;
+ fontinfo *b = (fontinfo *)bv;
+ int i;
+
+ if ((i = strnullcasecmp(a->realname, b->realname)) != 0)
+ return i;
+ if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK))
+ return ((a->flags & FONTFLAG_SORT_MASK) <
+ (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1);
+ return 0;
+}
+
+static int fontinfo_selorder_compare(void *av, void *bv)
+{
+ fontinfo *a = (fontinfo *)av;
+ fontinfo *b = (fontinfo *)bv;
+ int i;
+ if ((i = strnullcasecmp(a->family, b->family)) != 0)
+ return i;
+ /*
+ * Font class comes immediately after family, so that fonts
+ * from different classes with the same family
+ */
+ if ((a->flags & FONTFLAG_SORT_MASK) != (b->flags & FONTFLAG_SORT_MASK))
+ return ((a->flags & FONTFLAG_SORT_MASK) <
+ (b->flags & FONTFLAG_SORT_MASK) ? -1 : +1);
+ if ((i = strnullcasecmp(a->charset, b->charset)) != 0)
+ return i;
+ if ((i = strnullcasecmp(a->stylekey, b->stylekey)) != 0)
+ return i;
+ if ((i = strnullcasecmp(a->style, b->style)) != 0)
+ return i;
+ if (a->size != b->size)
+ return (a->size < b->size ? -1 : +1);
+ if (a->index != b->index)
+ return (a->index < b->index ? -1 : +1);
+ return 0;
+}
+
+static void unifontsel_draw_preview_text(unifontsel_internal *fs);
+
+static void unifontsel_deselect(unifontsel_internal *fs)
+{
+ fs->selected = NULL;
+ gtk_list_store_clear(fs->style_model);
+ gtk_list_store_clear(fs->size_model);
+ gtk_widget_set_sensitive(fs->u.ok_button, false);
+ gtk_widget_set_sensitive(fs->size_entry, false);
+ unifontsel_draw_preview_text(fs);
+}
+
+static void unifontsel_setup_familylist(unifontsel_internal *fs)
+{
+ GtkTreeIter iter;
+ int i, listindex, minpos = -1, maxpos = -1;
+ char *currfamily = NULL;
+ int currflags = -1;
+ fontinfo *info;
+
+ fs->inhibit_response = true;
+
+ gtk_list_store_clear(fs->family_model);
+ listindex = 0;
+
+ /*
+ * Search through the font tree for anything matching our
+ * current filter criteria. When we find one, add its font
+ * name to the list box.
+ */
+ for (i = 0 ;; i++) {
+ info = (fontinfo *)index234(fs->fonts_by_selorder, i);
+ /*
+ * info may be NULL if we've just run off the end of the
+ * tree. We must still do a processing pass in that
+ * situation, in case we had an unfinished font record in
+ * progress.
+ */
+ if (info && (info->flags &~ fs->filter_flags)) {
+ info->familyindex = -1;
+ continue; /* we're filtering out this font */
+ }
+ if (!info || strnullcasecmp(currfamily, info->family) ||
+ currflags != (info->flags & FONTFLAG_SORT_MASK)) {
+ /*
+ * We've either finished a family, or started a new
+ * one, or both.
+ */
+ if (currfamily) {
+ gtk_list_store_append(fs->family_model, &iter);
+ gtk_list_store_set(fs->family_model, &iter,
+ 0, currfamily, 1, minpos, 2, maxpos+1, -1);
+ listindex++;
+ }
+ if (info) {
+ minpos = i;
+ currfamily = info->family;
+ currflags = info->flags & FONTFLAG_SORT_MASK;
+ }
+ }
+ if (!info)
+ break; /* now we're done */
+ info->familyindex = listindex;
+ maxpos = i;
+ }
+
+ /*
+ * If we've just filtered out the previously selected font,
+ * deselect it thoroughly.
+ */
+ if (fs->selected && fs->selected->familyindex < 0)
+ unifontsel_deselect(fs);
+
+ fs->inhibit_response = false;
+}
+
+static void unifontsel_setup_stylelist(unifontsel_internal *fs,
+ int start, int end)
+{
+ GtkTreeIter iter;
+ int i, listindex, minpos = -1, maxpos = -1;
+ bool started = false;
+ char *currcs = NULL, *currstyle = NULL;
+ fontinfo *info;
+
+ gtk_list_store_clear(fs->style_model);
+ listindex = 0;
+ started = false;
+
+ /*
+ * Search through the font tree for anything matching our
+ * current filter criteria. When we find one, add its charset
+ * and/or style name to the list box.
+ */
+ for (i = start; i <= end; i++) {
+ if (i == end)
+ info = NULL;
+ else
+ info = (fontinfo *)index234(fs->fonts_by_selorder, i);
+ /*
+ * info may be NULL if we've just run off the end of the
+ * relevant data. We must still do a processing pass in
+ * that situation, in case we had an unfinished font
+ * record in progress.
+ */
+ if (info && (info->flags &~ fs->filter_flags)) {
+ info->styleindex = -1;
+ continue; /* we're filtering out this font */
+ }
+ if (!info || !started || strnullcasecmp(currcs, info->charset) ||
+ strnullcasecmp(currstyle, info->style)) {
+ /*
+ * We've either finished a style/charset, or started a
+ * new one, or both.
+ */
+ started = true;
+ if (currstyle) {
+ gtk_list_store_append(fs->style_model, &iter);
+ gtk_list_store_set(fs->style_model, &iter,
+ 0, currstyle, 1, minpos, 2, maxpos+1,
+ 3, true, 4, PANGO_WEIGHT_NORMAL, -1);
+ listindex++;
+ }
+ if (info) {
+ minpos = i;
+ if (info->charset && strnullcasecmp(currcs, info->charset)) {
+ gtk_list_store_append(fs->style_model, &iter);
+ gtk_list_store_set(fs->style_model, &iter,
+ 0, info->charset, 1, -1, 2, -1,
+ 3, false, 4, PANGO_WEIGHT_BOLD, -1);
+ listindex++;
+ }
+ currcs = info->charset;
+ currstyle = info->style;
+ }
+ }
+ if (!info)
+ break; /* now we're done */
+ info->styleindex = listindex;
+ maxpos = i;
+ }
+}
+
+static const int unifontsel_default_sizes[] = { 10, 12, 14, 16, 20, 24, 32 };
+
+static void unifontsel_setup_sizelist(unifontsel_internal *fs,
+ int start, int end)
+{
+ GtkTreeIter iter;
+ int i, listindex;
+ char sizetext[40];
+ fontinfo *info;
+
+ gtk_list_store_clear(fs->size_model);
+ listindex = 0;
+
+ /*
+ * Search through the font tree for anything matching our
+ * current filter criteria. When we find one, add its font
+ * name to the list box.
+ */
+ for (i = start; i < end; i++) {
+ info = (fontinfo *)index234(fs->fonts_by_selorder, i);
+ if (!info) {
+ /* _shouldn't_ happen unless font list is completely funted */
+ break;
+ }
+ if (info->flags &~ fs->filter_flags) {
+ info->sizeindex = -1;
+ continue; /* we're filtering out this font */
+ }
+ if (info->size) {
+ sprintf(sizetext, "%d", info->size);
+ info->sizeindex = listindex;
+ gtk_list_store_append(fs->size_model, &iter);
+ gtk_list_store_set(fs->size_model, &iter,
+ 0, sizetext, 1, i, 2, info->size, -1);
+ listindex++;
+ } else {
+ int j;
+
+ assert(i == start);
+ assert(i+1 == end);
+
+ for (j = 0; j < lenof(unifontsel_default_sizes); j++) {
+ sprintf(sizetext, "%d", unifontsel_default_sizes[j]);
+ gtk_list_store_append(fs->size_model, &iter);
+ gtk_list_store_set(fs->size_model, &iter, 0, sizetext, 1, i,
+ 2, unifontsel_default_sizes[j], -1);
+ listindex++;
+ }
+ }
+ }
+}
+
+static void unifontsel_set_filter_buttons(unifontsel_internal *fs)
+{
+ int i;
+
+ for (i = 0; i < fs->n_filter_buttons; i++) {
+ int flagbit = GPOINTER_TO_INT(g_object_get_data(
+ G_OBJECT(fs->filter_buttons[i]),
+ "user-data"));
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fs->filter_buttons[i]),
+ !!(fs->filter_flags & flagbit));
+ }
+}
+
+static void unifontsel_draw_preview_text_inner(unifont_drawctx *dctx,
+ unifontsel_internal *fs)
+{
+ unifont *font;
+ char *sizename = NULL;
+ fontinfo *info = fs->selected;
+
+ if (info) {
+ sizename = info->fontclass->scale_fontname(
+ GTK_WIDGET(fs->u.window), info->realname, fs->selsize);
+ font = info->fontclass->create(GTK_WIDGET(fs->u.window),
+ sizename ? sizename : info->realname,
+ false, false, 0, 0);
+ } else
+ font = NULL;
+
+#ifdef DRAW_TEXT_GDK
+ if (dctx->type == DRAWTYPE_GDK) {
+ gdk_gc_set_foreground(dctx->u.gdk.gc, &fs->preview_bg);
+ gdk_draw_rectangle(dctx->u.gdk.target, dctx->u.gdk.gc, 1, 0, 0,
+ fs->preview_width, fs->preview_height);
+ gdk_gc_set_foreground(dctx->u.gdk.gc, &fs->preview_fg);
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (dctx->type == DRAWTYPE_CAIRO) {
+ cairo_set_source_rgb(dctx->u.cairo.cr,
+ fs->preview_bg.red / 65535.0,
+ fs->preview_bg.green / 65535.0,
+ fs->preview_bg.blue / 65535.0);
+ cairo_paint(dctx->u.cairo.cr);
+ cairo_set_source_rgb(dctx->u.cairo.cr,
+ fs->preview_fg.red / 65535.0,
+ fs->preview_fg.green / 65535.0,
+ fs->preview_fg.blue / 65535.0);
+ }
+#endif
+
+ if (font) {
+ /*
+ * The pangram used here is rather carefully
+ * constructed: it contains a sequence of very narrow
+ * letters (`jil') and a pair of adjacent very wide
+ * letters (`wm').
+ *
+ * If the user selects a proportional font, it will be
+ * coerced into fixed-width character cells when used
+ * in the actual terminal window. We therefore display
+ * it the same way in the preview pane, so as to show
+ * it the way it will actually be displayed - and we
+ * deliberately pick a pangram which will show the
+ * resulting miskerning at its worst.
+ *
+ * We aren't trying to sell people these fonts; we're
+ * trying to let them make an informed choice. Better
+ * that they find out the problems with using
+ * proportional fonts in terminal windows here than
+ * that they go to the effort of selecting their font
+ * and _then_ realise it was a mistake.
+ */
+ info->fontclass->draw_text(dctx, font,
+ 0, font->ascent,
+ L"bankrupt jilted showmen quiz convex fogey",
+ 41, false, false, font->width);
+ info->fontclass->draw_text(dctx, font,
+ 0, font->ascent + font->height,
+ L"BANKRUPT JILTED SHOWMEN QUIZ CONVEX FOGEY",
+ 41, false, false, font->width);
+ /*
+ * The ordering of punctuation here is also selected
+ * with some specific aims in mind. I put ` and '
+ * together because some software (and people) still
+ * use them as matched quotes no matter what Unicode
+ * might say on the matter, so people can quickly
+ * check whether they look silly in a candidate font.
+ * The sequence #_@ is there to let people judge the
+ * suitability of the underscore as an effectively
+ * alphabetic character (since that's how it's often
+ * used in practice, at least by programmers).
+ */
+ info->fontclass->draw_text(dctx, font,
+ 0, font->ascent + font->height * 2,
+ L"0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$",
+ 42, false, false, font->width);
+
+ info->fontclass->destroy(font);
+ }
+
+ sfree(sizename);
+}
+
+static void unifontsel_draw_preview_text(unifontsel_internal *fs)
+{
+ unifont_drawctx dctx;
+ GdkWindow *target;
+
+#ifndef NO_BACKING_PIXMAPS
+ target = fs->preview_pixmap;
+#else
+ target = gtk_widget_get_window(fs->preview_area);
+#endif
+ if (!target) /* we may be called when we haven't created everything yet */
+ return;
+
+ dctx.type = DRAWTYPE_DEFAULT;
+#ifdef DRAW_TEXT_GDK
+ if (dctx.type == DRAWTYPE_GDK) {
+ dctx.u.gdk.target = target;
+ dctx.u.gdk.gc = gdk_gc_new(target);
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (dctx.type == DRAWTYPE_CAIRO) {
+#if GTK_CHECK_VERSION(3,22,0)
+ cairo_region_t *region;
+#endif
+
+ dctx.u.cairo.widget = GTK_WIDGET(fs->preview_area);
+
+#if GTK_CHECK_VERSION(3,22,0)
+ dctx.u.cairo.gdkwin = gtk_widget_get_window(dctx.u.cairo.widget);
+ region = gdk_window_get_clip_region(dctx.u.cairo.gdkwin);
+ dctx.u.cairo.drawctx = gdk_window_begin_draw_frame(
+ dctx.u.cairo.gdkwin, region);
+ dctx.u.cairo.cr = gdk_drawing_context_get_cairo_context(
+ dctx.u.cairo.drawctx);
+ cairo_region_destroy(region);
+#else
+ dctx.u.cairo.cr = gdk_cairo_create(target);
+#endif
+ }
+#endif
+
+ unifontsel_draw_preview_text_inner(&dctx, fs);
+
+#ifdef DRAW_TEXT_GDK
+ if (dctx.type == DRAWTYPE_GDK) {
+ gdk_gc_unref(dctx.u.gdk.gc);
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (dctx.type == DRAWTYPE_CAIRO) {
+#if GTK_CHECK_VERSION(3,22,0)
+ gdk_window_end_draw_frame(dctx.u.cairo.gdkwin, dctx.u.cairo.drawctx);
+#else
+ cairo_destroy(dctx.u.cairo.cr);
+#endif
+ }
+#endif
+
+ gdk_window_invalidate_rect(gtk_widget_get_window(fs->preview_area),
+ NULL, false);
+}
+
+static void unifontsel_select_font(unifontsel_internal *fs,
+ fontinfo *info, int size, int leftlist,
+ bool size_is_explicit)
+{
+ int index;
+ int minval, maxval;
+ gboolean success;
+ GtkTreePath *treepath;
+ GtkTreeIter iter;
+
+ fs->inhibit_response = true;
+
+ fs->selected = info;
+ fs->selsize = size;
+ if (size_is_explicit)
+ fs->intendedsize = size;
+
+ gtk_widget_set_sensitive(fs->u.ok_button, true);
+
+ /*
+ * Find the index of this fontinfo in the selorder list.
+ */
+ index = -1;
+ findpos234(fs->fonts_by_selorder, info, NULL, &index);
+ assert(index >= 0);
+
+ /*
+ * Adjust the font selector flags and redo the font family
+ * list box, if necessary.
+ */
+ if (leftlist <= 0 &&
+ (fs->filter_flags | info->flags) != fs->filter_flags) {
+ fs->filter_flags |= info->flags;
+ unifontsel_set_filter_buttons(fs);
+ unifontsel_setup_familylist(fs);
+ }
+
+ /*
+ * Find the appropriate family name and select it in the list.
+ */
+ assert(info->familyindex >= 0);
+ treepath = gtk_tree_path_new_from_indices(info->familyindex, -1);
+ gtk_tree_selection_select_path(
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->family_list)),
+ treepath);
+ gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->family_list),
+ treepath, NULL, false, 0.0, 0.0);
+ success = gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model),
+ &iter, treepath);
+ assert(success);
+ gtk_tree_path_free(treepath);
+
+ /*
+ * Now set up the font style list.
+ */
+ gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter,
+ 1, &minval, 2, &maxval, -1);
+ if (leftlist <= 1)
+ unifontsel_setup_stylelist(fs, minval, maxval);
+
+ /*
+ * Find the appropriate style name and select it in the list.
+ */
+ if (info->style) {
+ assert(info->styleindex >= 0);
+ treepath = gtk_tree_path_new_from_indices(info->styleindex, -1);
+ gtk_tree_selection_select_path(
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->style_list)),
+ treepath);
+ gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->style_list),
+ treepath, NULL, false, 0.0, 0.0);
+ gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->style_model),
+ &iter, treepath);
+ gtk_tree_path_free(treepath);
+
+ /*
+ * And set up the size list.
+ */
+ gtk_tree_model_get(GTK_TREE_MODEL(fs->style_model), &iter,
+ 1, &minval, 2, &maxval, -1);
+ if (leftlist <= 2)
+ unifontsel_setup_sizelist(fs, minval, maxval);
+
+ /*
+ * Find the appropriate size, and select it in the list.
+ */
+ if (info->size) {
+ assert(info->sizeindex >= 0);
+ treepath = gtk_tree_path_new_from_indices(info->sizeindex, -1);
+ gtk_tree_selection_select_path(
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(fs->size_list)),
+ treepath);
+ gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list),
+ treepath, NULL, false, 0.0, 0.0);
+ gtk_tree_path_free(treepath);
+ size = info->size;
+ } else {
+ int j;
+ for (j = 0; j < lenof(unifontsel_default_sizes); j++)
+ if (unifontsel_default_sizes[j] == size) {
+ treepath = gtk_tree_path_new_from_indices(j, -1);
+ gtk_tree_view_set_cursor(GTK_TREE_VIEW(fs->size_list),
+ treepath, NULL, false);
+ gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(fs->size_list),
+ treepath, NULL, false, 0.0,
+ 0.0);
+ gtk_tree_path_free(treepath);
+ }
+ }
+
+ /*
+ * And set up the font size text entry box.
+ */
+ {
+ char sizetext[40];
+ sprintf(sizetext, "%d", size);
+ gtk_entry_set_text(GTK_ENTRY(fs->size_entry), sizetext);
+ }
+ } else {
+ if (leftlist <= 2)
+ unifontsel_setup_sizelist(fs, 0, 0);
+ gtk_entry_set_text(GTK_ENTRY(fs->size_entry), "");
+ }
+
+ /*
+ * Grey out the font size edit box if we're not using a
+ * scalable font.
+ */
+ gtk_editable_set_editable(GTK_EDITABLE(fs->size_entry),
+ fs->selected->size == 0);
+ gtk_widget_set_sensitive(fs->size_entry, fs->selected->size == 0);
+
+ unifontsel_draw_preview_text(fs);
+
+ fs->inhibit_response = false;
+}
+
+static void unifontsel_button_toggled(GtkToggleButton *tb, gpointer data)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+ bool newstate = gtk_toggle_button_get_active(tb);
+ int newflags;
+ int flagbit = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(tb),
+ "user-data"));
+
+ if (newstate)
+ newflags = fs->filter_flags | flagbit;
+ else
+ newflags = fs->filter_flags & ~flagbit;
+
+ if (fs->filter_flags != newflags) {
+ fs->filter_flags = newflags;
+ unifontsel_setup_familylist(fs);
+ }
+}
+
+static void unifontsel_add_entry(void *ctx, const char *realfontname,
+ const char *family, const char *charset,
+ const char *style, const char *stylekey,
+ int size, int flags,
+ const struct UnifontVtable *fontclass)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)ctx;
+ fontinfo *info;
+ int totalsize;
+ char *p;
+
+ totalsize = sizeof(fontinfo) + strlen(realfontname) +
+ (family ? strlen(family) : 0) + (charset ? strlen(charset) : 0) +
+ (style ? strlen(style) : 0) + (stylekey ? strlen(stylekey) : 0) + 10;
+ info = (fontinfo *)smalloc(totalsize);
+ info->fontclass = fontclass;
+ p = (char *)info + sizeof(fontinfo);
+ info->realname = p;
+ strcpy(p, realfontname);
+ p += 1+strlen(p);
+ if (family) {
+ info->family = p;
+ strcpy(p, family);
+ p += 1+strlen(p);
+ } else
+ info->family = NULL;
+ if (charset) {
+ info->charset = p;
+ strcpy(p, charset);
+ p += 1+strlen(p);
+ } else
+ info->charset = NULL;
+ if (style) {
+ info->style = p;
+ strcpy(p, style);
+ p += 1+strlen(p);
+ } else
+ info->style = NULL;
+ if (stylekey) {
+ info->stylekey = p;
+ strcpy(p, stylekey);
+ p += 1+strlen(p);
+ } else
+ info->stylekey = NULL;
+ assert(p - (char *)info <= totalsize);
+ info->size = size;
+ info->flags = flags;
+ info->index = count234(fs->fonts_by_selorder);
+
+ /*
+ * It's just conceivable that a misbehaving font enumerator
+ * might tell us about the same font real name more than once,
+ * in which case we should silently drop the new one.
+ */
+ if (add234(fs->fonts_by_realname, info) != info) {
+ sfree(info);
+ return;
+ }
+ /*
+ * However, we should never get a duplicate key in the
+ * selorder tree, because the index field carefully
+ * disambiguates otherwise identical records.
+ */
+ add234(fs->fonts_by_selorder, info);
+}
+
+static fontinfo *update_for_intended_size(unifontsel_internal *fs,
+ fontinfo *info)
+{
+ fontinfo info2, *below, *above;
+ int pos;
+
+ /*
+ * Copy the info structure. This doesn't copy its dynamic
+ * string fields, but that's unimportant because all we're
+ * going to do is to adjust the size field and use it in one
+ * tree search.
+ */
+ info2 = *info;
+ info2.size = fs->intendedsize;
+
+ /*
+ * Search in the tree to find the fontinfo structure which
+ * best approximates the size the user last requested.
+ */
+ below = findrelpos234(fs->fonts_by_selorder, &info2, NULL,
+ REL234_LE, &pos);
+ if (!below)
+ pos = -1;
+ above = index234(fs->fonts_by_selorder, pos+1);
+
+ /*
+ * See if we've found it exactly, which is an easy special
+ * case. If we have, it'll be in `below' and not `above',
+ * because we did a REL234_LE rather than REL234_LT search.
+ */
+ if (below && !fontinfo_selorder_compare(&info2, below))
+ return below;
+
+ /*
+ * Now we've either found two suitable fonts, one smaller and
+ * one larger, or we're at one or other extreme end of the
+ * scale. Find out which, by NULLing out either of below and
+ * above if it differs from this one in any respect but size
+ * (and the disambiguating index field). Bear in mind, also,
+ * that either one might _already_ be NULL if we're at the
+ * extreme ends of the font list.
+ */
+ if (below) {
+ info2.size = below->size;
+ info2.index = below->index;
+ if (fontinfo_selorder_compare(&info2, below))
+ below = NULL;
+ }
+ if (above) {
+ info2.size = above->size;
+ info2.index = above->index;
+ if (fontinfo_selorder_compare(&info2, above))
+ above = NULL;
+ }
+
+ /*
+ * Now return whichever of above and below is non-NULL, if
+ * that's unambiguous.
+ */
+ if (!above)
+ return below;
+ if (!below)
+ return above;
+
+ /*
+ * And now we really do have to make a choice about whether to
+ * round up or down. We'll do it by rounding to nearest,
+ * breaking ties by rounding up.
+ */
+ if (above->size - fs->intendedsize <= fs->intendedsize - below->size)
+ return above;
+ else
+ return below;
+}
+
+static void family_changed(GtkTreeSelection *treeselection, gpointer data)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+ GtkTreeModel *treemodel;
+ GtkTreeIter treeiter;
+ int minval;
+ fontinfo *info;
+
+ if (fs->inhibit_response) /* we made this change ourselves */
+ return;
+
+ if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
+ return;
+
+ gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1);
+ info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
+ if (!info)
+ return; /* _shouldn't_ happen unless font list is completely funted */
+ info = update_for_intended_size(fs, info);
+ if (!info)
+ return; /* similarly shouldn't happen */
+ if (!info->size)
+ fs->selsize = fs->intendedsize; /* font is scalable */
+ unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize,
+ 1, false);
+}
+
+static void style_changed(GtkTreeSelection *treeselection, gpointer data)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+ GtkTreeModel *treemodel;
+ GtkTreeIter treeiter;
+ int minval;
+ fontinfo *info;
+
+ if (fs->inhibit_response) /* we made this change ourselves */
+ return;
+
+ if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
+ return;
+
+ gtk_tree_model_get(treemodel, &treeiter, 1, &minval, -1);
+ if (minval < 0)
+ return; /* somehow a charset heading got clicked */
+ info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
+ if (!info)
+ return; /* _shouldn't_ happen unless font list is completely funted */
+ info = update_for_intended_size(fs, info);
+ if (!info)
+ return; /* similarly shouldn't happen */
+ if (!info->size)
+ fs->selsize = fs->intendedsize; /* font is scalable */
+ unifontsel_select_font(fs, info, info->size ? info->size : fs->selsize,
+ 2, false);
+}
+
+static void size_changed(GtkTreeSelection *treeselection, gpointer data)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+ GtkTreeModel *treemodel;
+ GtkTreeIter treeiter;
+ int minval, size;
+ fontinfo *info;
+
+ if (fs->inhibit_response) /* we made this change ourselves */
+ return;
+
+ if (!gtk_tree_selection_get_selected(treeselection, &treemodel, &treeiter))
+ return;
+
+ gtk_tree_model_get(treemodel, &treeiter, 1, &minval, 2, &size, -1);
+ info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
+ if (!info)
+ return; /* _shouldn't_ happen unless font list is completely funted */
+ unifontsel_select_font(fs, info, info->size ? info->size : size, 3, true);
+}
+
+static void size_entry_changed(GtkEditable *ed, gpointer data)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+ const char *text;
+ int size;
+
+ if (fs->inhibit_response) /* we made this change ourselves */
+ return;
+
+ text = gtk_entry_get_text(GTK_ENTRY(ed));
+ size = atoi(text);
+
+ if (size > 0) {
+ assert(fs->selected->size == 0);
+ unifontsel_select_font(fs, fs->selected, size, 3, true);
+ }
+}
+
+static void alias_resolve(GtkTreeView *treeview, GtkTreePath *path,
+ GtkTreeViewColumn *column, gpointer data)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+ GtkTreeIter iter;
+ int minval, newsize;
+ fontinfo *info, *newinfo;
+ char *newname;
+
+ if (fs->inhibit_response) /* we made this change ourselves */
+ return;
+
+ gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), &iter, path);
+ gtk_tree_model_get(GTK_TREE_MODEL(fs->family_model), &iter, 1,&minval, -1);
+ info = (fontinfo *)index234(fs->fonts_by_selorder, minval);
+ if (info) {
+ int flags;
+ struct fontinfo_realname_find f;
+
+ newname = info->fontclass->canonify_fontname(
+ GTK_WIDGET(fs->u.window), info->realname, &newsize, &flags, true);
+
+ f.realname = newname;
+ f.flags = flags;
+ newinfo = find234(fs->fonts_by_realname, &f, fontinfo_realname_find);
+
+ sfree(newname);
+ if (!newinfo)
+ return; /* font name not in our index */
+ if (newinfo == info)
+ return; /* didn't change under canonification => not an alias */
+ unifontsel_select_font(fs, newinfo,
+ newinfo->size ? newinfo->size : newsize,
+ 1, true);
+ }
+}
+
+#if GTK_CHECK_VERSION(3,0,0)
+static gint unifontsel_draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+ unifont_drawctx dctx;
+
+ dctx.type = DRAWTYPE_CAIRO;
+ dctx.u.cairo.widget = widget;
+ dctx.u.cairo.cr = cr;
+ unifontsel_draw_preview_text_inner(&dctx, fs);
+
+ return true;
+}
+#else
+static gint unifontsel_expose_area(GtkWidget *widget, GdkEventExpose *event,
+ gpointer data)
+{
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+
+#ifndef NO_BACKING_PIXMAPS
+ if (fs->preview_pixmap) {
+ gdk_draw_pixmap(gtk_widget_get_window(widget),
+ (gtk_widget_get_style(widget)->fg_gc
+ [gtk_widget_get_state(widget)]),
+ fs->preview_pixmap,
+ event->area.x, event->area.y,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height);
+ }
+#else
+ unifontsel_draw_preview_text(fs);
+#endif
+
+ return true;
+}
+#endif
+
+static gint unifontsel_configure_area(GtkWidget *widget,
+ GdkEventConfigure *event, gpointer data)
+{
+#ifndef NO_BACKING_PIXMAPS
+ unifontsel_internal *fs = (unifontsel_internal *)data;
+ int ox, oy, nx, ny, x, y;
+
+ /*
+ * Enlarge the pixmap, but never shrink it.
+ */
+ ox = fs->preview_width;
+ oy = fs->preview_height;
+ x = event->width;
+ y = event->height;
+ if (x > ox || y > oy) {
+ if (fs->preview_pixmap)
+ gdk_pixmap_unref(fs->preview_pixmap);
+
+ nx = (x > ox ? x : ox);
+ ny = (y > oy ? y : oy);
+ fs->preview_pixmap = gdk_pixmap_new(gtk_widget_get_window(widget),
+ nx, ny, -1);
+ fs->preview_width = nx;
+ fs->preview_height = ny;
+
+ unifontsel_draw_preview_text(fs);
+ }
+#endif
+
+ gdk_window_invalidate_rect(gtk_widget_get_window(widget), NULL, false);
+
+ return true;
+}
+
+unifontsel *unifontsel_new(const char *wintitle)
+{
+ unifontsel_internal *fs = snew(unifontsel_internal);
+ GtkWidget *table, *label, *w, *ww, *scroll;
+ GtkListStore *model;
+ GtkTreeViewColumn *column;
+ int lists_height, preview_height, font_width, style_width, size_width;
+ int i;
+
+ fs->inhibit_response = false;
+ fs->selected = NULL;
+
+ {
+ int width, height;
+
+ /*
+ * Invent some magic size constants.
+ */
+ get_label_text_dimensions("Quite Long Font Name (Foundry)",
+ &width, &height);
+ font_width = width;
+ lists_height = 14 * height;
+ preview_height = 5 * height;
+
+ get_label_text_dimensions("Italic Extra Condensed", &width, &height);
+ style_width = width;
+
+ get_label_text_dimensions("48000", &width, &height);
+ size_width = width;
+ }
+
+ /*
+ * Create the dialog box and initialise the user-visible
+ * fields in the returned structure.
+ */
+ fs->u.user_data = NULL;
+ fs->u.window = GTK_WINDOW(gtk_dialog_new());
+ gtk_window_set_title(fs->u.window, wintitle);
+ fs->u.cancel_button = gtk_dialog_add_button(
+ GTK_DIALOG(fs->u.window), STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL);
+ fs->u.ok_button = gtk_dialog_add_button(
+ GTK_DIALOG(fs->u.window), STANDARD_OK_LABEL, GTK_RESPONSE_OK);
+ gtk_widget_grab_default(fs->u.ok_button);
+
+ /*
+ * Now set up the internal fields, including in particular all
+ * the controls that actually allow the user to select fonts.
+ */
+#if GTK_CHECK_VERSION(3,0,0)
+ table = gtk_grid_new();
+ gtk_grid_set_column_spacing(GTK_GRID(table), 8);
+#else
+ table = gtk_table_new(8, 3, false);
+ gtk_table_set_col_spacings(GTK_TABLE(table), 8);
+#endif
+ gtk_widget_show(table);
+
+#if GTK_CHECK_VERSION(3,0,0)
+ /* GtkAlignment has become deprecated and we use the "margin"
+ * property */
+ w = table;
+ g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL);
+#elif GTK_CHECK_VERSION(2,4,0)
+ /* GtkAlignment seems to be the simplest way to put padding round things */
+ w = gtk_alignment_new(0, 0, 1, 1);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8);
+ gtk_container_add(GTK_CONTAINER(w), table);
+ gtk_widget_show(w);
+#else
+ /* In GTK < 2.4, even that isn't available */
+ w = table;
+#endif
+
+ gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(
+ GTK_DIALOG(fs->u.window))),
+ w, true, true, 0);
+
+ label = gtk_label_new_with_mnemonic("_Font:");
+ gtk_widget_show(label);
+ align_label_left(GTK_LABEL(label));
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1);
+ g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL);
+#else
+ gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+#endif
+
+ /*
+ * The Font list box displays only a string, but additionally
+ * stores two integers which give the limits within the
+ * tree234 of the font entries covered by this list entry.
+ */
+ model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
+ w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
+ gtk_widget_show(w);
+ column = gtk_tree_view_column_new_with_attributes(
+ "Font", gtk_cell_renderer_text_new(),
+ "text", 0, (char *)NULL);
+ gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
+ g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
+ "changed", G_CALLBACK(family_changed), fs);
+ g_signal_connect(G_OBJECT(w), "row-activated",
+ G_CALLBACK(alias_resolve), fs);
+
+ scroll = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
+ GTK_SHADOW_IN);
+ gtk_container_add(GTK_CONTAINER(scroll), w);
+ gtk_widget_show(scroll);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+ gtk_widget_set_size_request(scroll, font_width, lists_height);
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_grid_attach(GTK_GRID(table), scroll, 0, 1, 1, 2);
+ g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL);
+#else
+ gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL,
+ GTK_EXPAND | GTK_FILL, 0, 0);
+#endif
+ fs->family_model = model;
+ fs->family_list = w;
+
+ label = gtk_label_new_with_mnemonic("_Style:");
+ gtk_widget_show(label);
+ align_label_left(GTK_LABEL(label));
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_grid_attach(GTK_GRID(table), label, 1, 0, 1, 1);
+ g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL);
+#else
+ gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
+#endif
+
+ /*
+ * The Style list box can contain insensitive elements (character
+ * set headings for server-side fonts), so we add an extra column
+ * to the list store to hold that information. Also, since GTK3 at
+ * least doesn't seem to display insensitive elements differently
+ * by default, we add a further column to change their style.
+ */
+ model = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT,
+ G_TYPE_BOOLEAN, G_TYPE_INT);
+ w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
+ gtk_widget_show(w);
+ column = gtk_tree_view_column_new_with_attributes(
+ "Style", gtk_cell_renderer_text_new(),
+ "text", 0, "sensitive", 3, "weight", 4, (char *)NULL);
+ gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
+ g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
+ "changed", G_CALLBACK(style_changed), fs);
+
+ scroll = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
+ GTK_SHADOW_IN);
+ gtk_container_add(GTK_CONTAINER(scroll), w);
+ gtk_widget_show(scroll);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+ gtk_widget_set_size_request(scroll, style_width, lists_height);
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_grid_attach(GTK_GRID(table), scroll, 1, 1, 1, 2);
+ g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL);
+#else
+ gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL,
+ GTK_EXPAND | GTK_FILL, 0, 0);
+#endif
+ fs->style_model = model;
+ fs->style_list = w;
+
+ label = gtk_label_new_with_mnemonic("Si_ze:");
+ gtk_widget_show(label);
+ align_label_left(GTK_LABEL(label));
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_grid_attach(GTK_GRID(table), label, 2, 0, 1, 1);
+ g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL);
+#else
+ gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0);
+#endif
+
+ /*
+ * The Size label attaches primarily to a text input box so
+ * that the user can select a size of their choice. The list
+ * of available sizes is secondary.
+ */
+ fs->size_entry = w = gtk_entry_new();
+ gtk_label_set_mnemonic_widget(GTK_LABEL(label), w);
+ gtk_widget_set_size_request(w, size_width, -1);
+ gtk_widget_show(w);
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_grid_attach(GTK_GRID(table), w, 2, 1, 1, 1);
+ g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
+#else
+ gtk_table_attach(GTK_TABLE(table), w, 2, 3, 1, 2, GTK_FILL, 0, 0, 0);
+#endif
+ g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(size_entry_changed),
+ fs);
+
+ model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
+ w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false);
+ gtk_widget_show(w);
+ column = gtk_tree_view_column_new_with_attributes(
+ "Size", gtk_cell_renderer_text_new(),
+ "text", 0, (char *)NULL);
+ gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
+ g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(w))),
+ "changed", G_CALLBACK(size_changed), fs);
+
+ scroll = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
+ GTK_SHADOW_IN);
+ gtk_container_add(GTK_CONTAINER(scroll), w);
+ gtk_widget_show(scroll);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_grid_attach(GTK_GRID(table), scroll, 2, 2, 1, 1);
+ g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL);
+#else
+ gtk_table_attach(GTK_TABLE(table), scroll, 2, 3, 2, 3, GTK_FILL,
+ GTK_EXPAND | GTK_FILL, 0, 0);
+#endif
+ fs->size_model = model;
+ fs->size_list = w;
+
+ /*
+ * Preview widget.
+ */
+ fs->preview_area = gtk_drawing_area_new();
+#ifndef NO_BACKING_PIXMAPS
+ fs->preview_pixmap = NULL;
+#endif
+ fs->preview_width = 0;
+ fs->preview_height = 0;
+ fs->preview_fg.pixel = fs->preview_bg.pixel = 0;
+ fs->preview_fg.red = fs->preview_fg.green = fs->preview_fg.blue = 0x0000;
+ fs->preview_bg.red = fs->preview_bg.green = fs->preview_bg.blue = 0xFFFF;
+#if !GTK_CHECK_VERSION(3,0,0)
+ gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_fg,
+ false, false);
+ gdk_colormap_alloc_color(gdk_colormap_get_system(), &fs->preview_bg,
+ false, false);
+#endif
+#if GTK_CHECK_VERSION(3,0,0)
+ g_signal_connect(G_OBJECT(fs->preview_area), "draw",
+ G_CALLBACK(unifontsel_draw_area), fs);
+#else
+ g_signal_connect(G_OBJECT(fs->preview_area), "expose_event",
+ G_CALLBACK(unifontsel_expose_area), fs);
+#endif
+ g_signal_connect(G_OBJECT(fs->preview_area), "configure_event",
+ G_CALLBACK(unifontsel_configure_area), fs);
+ gtk_widget_set_size_request(fs->preview_area, 1, preview_height);
+ gtk_widget_show(fs->preview_area);
+ ww = fs->preview_area;
+ w = gtk_frame_new(NULL);
+ gtk_container_add(GTK_CONTAINER(w), ww);
+ gtk_widget_show(w);
+
+#if GTK_CHECK_VERSION(3,0,0)
+ /* GtkAlignment has become deprecated and we use the "margin"
+ * property */
+ g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL);
+#elif GTK_CHECK_VERSION(2,4,0)
+ ww = w;
+ /* GtkAlignment seems to be the simplest way to put padding round things */
+ w = gtk_alignment_new(0, 0, 1, 1);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(w), 8, 8, 8, 8);
+ gtk_container_add(GTK_CONTAINER(w), ww);
+ gtk_widget_show(w);
+#endif
+
+ ww = w;
+ w = gtk_frame_new("Preview of font");
+ gtk_container_add(GTK_CONTAINER(w), ww);
+ gtk_widget_show(w);
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_grid_attach(GTK_GRID(table), w, 0, 3, 3, 1);
+ g_object_set(G_OBJECT(w), "expand", true, (const char *)NULL);
+#else
+ gtk_table_attach(GTK_TABLE(table), w, 0, 3, 3, 4,
+ GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 8);
+#endif
+
+ /*
+ * We only provide the checkboxes for client- and server-side
+ * fonts if we have the X11 back end available, because that's the
+ * only situation in which more than one class of font is
+ * available anyway.
+ */
+ fs->n_filter_buttons = 0;
+#ifndef NOT_X_WINDOWS
+ w = gtk_check_button_new_with_label("Show client-side fonts");
+ g_object_set_data(G_OBJECT(w), "user-data",
+ GINT_TO_POINTER(FONTFLAG_CLIENTSIDE));
+ g_signal_connect(G_OBJECT(w), "toggled",
+ G_CALLBACK(unifontsel_button_toggled), fs);
+ gtk_widget_show(w);
+ fs->filter_buttons[fs->n_filter_buttons++] = w;
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_grid_attach(GTK_GRID(table), w, 0, 4, 3, 1);
+ g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
+#else
+ gtk_table_attach(GTK_TABLE(table), w, 0, 3, 4, 5, GTK_FILL, 0, 0, 0);
+#endif
+ w = gtk_check_button_new_with_label("Show server-side fonts");
+ g_object_set_data(G_OBJECT(w), "user-data",
+ GINT_TO_POINTER(FONTFLAG_SERVERSIDE));
+ g_signal_connect(G_OBJECT(w), "toggled",
+ G_CALLBACK(unifontsel_button_toggled), fs);
+ gtk_widget_show(w);
+ fs->filter_buttons[fs->n_filter_buttons++] = w;
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_grid_attach(GTK_GRID(table), w, 0, 5, 3, 1);
+ g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
+#else
+ gtk_table_attach(GTK_TABLE(table), w, 0, 3, 5, 6, GTK_FILL, 0, 0, 0);
+#endif
+ w = gtk_check_button_new_with_label("Show server-side font aliases");
+ g_object_set_data(G_OBJECT(w), "user-data",
+ GINT_TO_POINTER(FONTFLAG_SERVERALIAS));
+ g_signal_connect(G_OBJECT(w), "toggled",
+ G_CALLBACK(unifontsel_button_toggled), fs);
+ gtk_widget_show(w);
+ fs->filter_buttons[fs->n_filter_buttons++] = w;
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_grid_attach(GTK_GRID(table), w, 0, 6, 3, 1);
+ g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
+#else
+ gtk_table_attach(GTK_TABLE(table), w, 0, 3, 6, 7, GTK_FILL, 0, 0, 0);
+#endif
+#endif /* NOT_X_WINDOWS */
+ w = gtk_check_button_new_with_label("Show non-monospaced fonts");
+ g_object_set_data(G_OBJECT(w), "user-data",
+ GINT_TO_POINTER(FONTFLAG_NONMONOSPACED));
+ g_signal_connect(G_OBJECT(w), "toggled",
+ G_CALLBACK(unifontsel_button_toggled), fs);
+ gtk_widget_show(w);
+ fs->filter_buttons[fs->n_filter_buttons++] = w;
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_grid_attach(GTK_GRID(table), w, 0, 7, 3, 1);
+ g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL);
+#else
+ gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0);
+#endif
+
+ assert(fs->n_filter_buttons <= lenof(fs->filter_buttons));
+ fs->filter_flags = FONTFLAG_CLIENTSIDE | FONTFLAG_SERVERSIDE |
+ FONTFLAG_SERVERALIAS;
+ unifontsel_set_filter_buttons(fs);
+
+ /*
+ * Go and find all the font names, and set up our master font
+ * list.
+ */
+ fs->fonts_by_realname = newtree234(fontinfo_realname_compare);
+ fs->fonts_by_selorder = newtree234(fontinfo_selorder_compare);
+ for (i = 0; i < lenof(unifont_types); i++)
+ unifont_types[i]->enum_fonts(GTK_WIDGET(fs->u.window),
+ unifontsel_add_entry, fs);
+
+ /*
+ * And set up the initial font names list.
+ */
+ unifontsel_setup_familylist(fs);
+
+ fs->selsize = fs->intendedsize = 13; /* random default */
+ gtk_widget_set_sensitive(fs->u.ok_button, false);
+
+ return &fs->u;
+}
+
+void unifontsel_destroy(unifontsel *fontsel)
+{
+ unifontsel_internal *fs = container_of(fontsel, unifontsel_internal, u);
+ fontinfo *info;
+
+#ifndef NO_BACKING_PIXMAPS
+ if (fs->preview_pixmap)
+ gdk_pixmap_unref(fs->preview_pixmap);
+#endif
+
+ freetree234(fs->fonts_by_selorder);
+ while ((info = delpos234(fs->fonts_by_realname, 0)) != NULL)
+ sfree(info);
+ freetree234(fs->fonts_by_realname);
+
+ gtk_widget_destroy(GTK_WIDGET(fs->u.window));
+ sfree(fs);
+}
+
+void unifontsel_set_name(unifontsel *fontsel, const char *fontname)
+{
+ unifontsel_internal *fs = container_of(fontsel, unifontsel_internal, u);
+ int i, start, end, size, flags;
+ const char *fontname2 = NULL;
+ fontinfo *info;
+
+ /*
+ * Provide a default if given an empty or null font name.
+ */
+ if (!fontname || !*fontname)
+ fontname = DEFAULT_GTK_FONT;
+
+ /*
+ * Call the canonify_fontname function.
+ */
+ fontname = unifont_do_prefix(fontname, &start, &end);
+ for (i = start; i < end; i++) {
+ fontname2 = unifont_types[i]->canonify_fontname(
+ GTK_WIDGET(fs->u.window), fontname, &size, &flags, false);
+ if (fontname2)
+ break;
+ }
+ if (i == end)
+ return; /* font name not recognised */
+
+ /*
+ * Now look up the canonified font name in our index.
+ */
+ {
+ struct fontinfo_realname_find f;
+ f.realname = fontname2;
+ f.flags = flags;
+ info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find);
+ }
+
+ /*
+ * If we've found the font, and its size field is either
+ * correct or zero (the latter indicating a scalable font),
+ * then we're done. Otherwise, try looking up the original
+ * font name instead.
+ */
+ if (!info || (info->size != size && info->size != 0)) {
+ struct fontinfo_realname_find f;
+ f.realname = fontname;
+ f.flags = flags;
+
+ info = find234(fs->fonts_by_realname, &f, fontinfo_realname_find);
+ if (!info || info->size != size)
+ return; /* font name not in our index */
+ }
+
+ /*
+ * Now we've got a fontinfo structure and a font size, so we
+ * know everything we need to fill in all the fields in the
+ * dialog.
+ */
+ unifontsel_select_font(fs, info, size, 0, true);
+}
+
+char *unifontsel_get_name(unifontsel *fontsel)
+{
+ unifontsel_internal *fs = container_of(fontsel, unifontsel_internal, u);
+ char *name;
+
+ if (!fs->selected)
+ return NULL;
+
+ if (fs->selected->size == 0) {
+ name = fs->selected->fontclass->scale_fontname(
+ GTK_WIDGET(fs->u.window), fs->selected->realname, fs->selsize);
+ if (name) {
+ char *ret = dupcat(fs->selected->fontclass->prefix, ":", name);
+ sfree(name);
+ return ret;
+ }
+ }
+
+ return dupcat(fs->selected->fontclass->prefix, ":",
+ fs->selected->realname);
+}
+
+#endif /* GTK_CHECK_VERSION(2,0,0) */
diff --git a/unix/unifont.h b/unix/unifont.h
new file mode 100644
index 00000000..c0f01121
--- /dev/null
+++ b/unix/unifont.h
@@ -0,0 +1,186 @@
+/*
+ * Header file for unifont.c. Has to be separate from unix.h
+ * because it depends on GTK data types, hence can't be included
+ * from cross-platform code (which doesn't go near GTK).
+ */
+
+#ifndef PUTTY_GTKFONT_H
+#define PUTTY_GTKFONT_H
+
+/*
+ * We support two entirely different drawing systems: the old
+ * GDK1/GDK2 one which works on server-side X drawables, and the
+ * new-style Cairo one. GTK1 only supports GDK drawing; GTK3 only
+ * supports Cairo; GTK2 supports both, but deprecates GTK, so we only
+ * enable it if we aren't trying on purpose to compile without the
+ * deprecated functions.
+ *
+ * Our different font classes may prefer different drawing systems: X
+ * server-side fonts are a lot faster to draw with GDK, but for
+ * everything else we prefer Cairo, on general grounds of modernness
+ * and also in particular because its matrix-based scaling system
+ * gives much nicer results for double-width and double-height text
+ * when a scalable font is in use.
+ */
+#if !GTK_CHECK_VERSION(3,0,0) && !defined GDK_DISABLE_DEPRECATED
+#define DRAW_TEXT_GDK
+#endif
+#if GTK_CHECK_VERSION(2,8,0)
+#define DRAW_TEXT_CAIRO
+#endif
+
+#if GTK_CHECK_VERSION(3,0,0) || defined GDK_DISABLE_DEPRECATED
+/*
+ * Where the facility is available, we prefer to render text on to a
+ * persistent server-side pixmap, and redraw windows by simply
+ * blitting rectangles of that pixmap into them as needed. This is
+ * better for performance since we avoid expensive font rendering
+ * calls where possible, and it's particularly good over a non-local X
+ * connection because the response to an expose event can now be a
+ * very simple rectangle-copy operation rather than a lot of fiddly
+ * drawing or bitmap transfer.
+ *
+ * However, GTK is deprecating the use of server-side pixmaps, so we
+ * have to disable this mode under some circumstances.
+ */
+#define NO_BACKING_PIXMAPS
+#endif
+
+/*
+ * Exports from unifont.c.
+ */
+typedef struct UnifontVtable UnifontVtable; /* contents internal to
+ * unifont.c */
+typedef struct unifont {
+ const struct UnifontVtable *vt;
+ /*
+ * `Non-static data members' of the `class', accessible to
+ * external code.
+ */
+
+ /*
+ * public_charset is the charset used when the user asks for
+ * `Use font encoding'.
+ */
+ int public_charset;
+
+ /*
+ * Font dimensions needed by clients.
+ */
+ int width, height, ascent, descent, strikethrough_y;
+
+ /*
+ * Indicates whether this font is capable of handling all glyphs
+ * (Pango fonts can do this because Pango automatically supplies
+ * missing glyphs from other fonts), or whether it would like a
+ * fallback font to cope with missing glyphs.
+ */
+ bool want_fallback;
+
+ /*
+ * Preferred drawing API to use when this class of font is active.
+ * (See the enum below, in unifont_drawctx.)
+ */
+ int preferred_drawtype;
+} unifont;
+
+/* A default drawtype, for the case where no font exists to make the
+ * decision with. */
+#ifdef DRAW_TEXT_CAIRO
+#define DRAW_DEFAULT_CAIRO
+#define DRAWTYPE_DEFAULT DRAWTYPE_CAIRO
+#elif defined DRAW_TEXT_GDK
+#define DRAW_DEFAULT_GDK
+#define DRAWTYPE_DEFAULT DRAWTYPE_GDK
+#else
+#error No drawtype available at all
+#endif
+
+/*
+ * Drawing context passed in to unifont_draw_text, which contains
+ * everything required to know where and how to draw the requested
+ * text.
+ */
+typedef struct unifont_drawctx {
+ enum {
+#ifdef DRAW_TEXT_GDK
+ DRAWTYPE_GDK,
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ DRAWTYPE_CAIRO,
+#endif
+ DRAWTYPE_NTYPES
+ } type;
+ union {
+#ifdef DRAW_TEXT_GDK
+ struct {
+ GdkDrawable *target;
+ GdkGC *gc;
+ } gdk;
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ struct {
+ /* Need an actual widget, in order to backtrack to its X
+ * screen number when creating server-side pixmaps */
+ GtkWidget *widget;
+ cairo_t *cr;
+ cairo_matrix_t origmatrix;
+#if GTK_CHECK_VERSION(3,22,0)
+ GdkWindow *gdkwin;
+ GdkDrawingContext *drawctx;
+#endif
+ } cairo;
+#endif
+ } u;
+} unifont_drawctx;
+
+unifont *unifont_create(GtkWidget *widget, const char *name,
+ bool wide, bool bold,
+ int shadowoffset, bool shadowalways);
+void unifont_destroy(unifont *font);
+void unifont_draw_text(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string, int len,
+ bool wide, bool bold, int cellwidth);
+/* Same as unifont_draw_text, but expects 'string' to contain one
+ * normal char plus combining chars, and overdraws them all in the
+ * same character cell. */
+void unifont_draw_combining(unifont_drawctx *ctx, unifont *font,
+ int x, int y, const wchar_t *string, int len,
+ bool wide, bool bold, int cellwidth);
+/* Return a name that will select a bigger/smaller font than this one,
+ * or NULL if no such name is available. */
+char *unifont_size_increment(unifont *font, int increment);
+
+/*
+ * This function behaves exactly like the low-level unifont_create,
+ * except that as well as the requested font it also allocates (if
+ * necessary) a fallback font for filling in replacement glyphs.
+ *
+ * Return value is usable with unifont_destroy and unifont_draw_text
+ * as if it were an ordinary unifont.
+ */
+unifont *multifont_create(GtkWidget *widget, const char *name,
+ bool wide, bool bold,
+ int shadowoffset, bool shadowalways);
+
+/*
+ * Unified font selector dialog. I can't be bothered to do a
+ * proper GTK subclassing today, so this will just be an ordinary
+ * data structure with some useful members.
+ *
+ * (Of course, these aren't the only members; this structure is
+ * contained within a bigger one which holds data visible only to
+ * the implementation.)
+ */
+typedef struct unifontsel {
+ void *user_data; /* settable by the user */
+ GtkWindow *window;
+ GtkWidget *ok_button, *cancel_button;
+} unifontsel;
+
+unifontsel *unifontsel_new(const char *wintitle);
+void unifontsel_destroy(unifontsel *fontsel);
+void unifontsel_set_name(unifontsel *fontsel, const char *fontname);
+char *unifontsel_get_name(unifontsel *fontsel);
+
+#endif /* PUTTY_GTKFONT_H */
diff --git a/unix/unix.h b/unix/unix.h
deleted file mode 100644
index 2c0664b2..00000000
--- a/unix/unix.h
+++ /dev/null
@@ -1,460 +0,0 @@
-#ifndef PUTTY_UNIX_H
-#define PUTTY_UNIX_H
-
-#ifdef HAVE_CONFIG_H
-# include "uxconfig.h" /* Space to hide it from mkfiles.pl */
-#endif
-
-#include <stdio.h> /* for FILENAME_MAX */
-#include <stdint.h> /* C99 int types */
-#ifndef NO_LIBDL
-#include <dlfcn.h> /* Dynamic library loading */
-#endif /* NO_LIBDL */
-#include "charset.h"
-#include <sys/types.h> /* 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, "<custom#1>") \
- X(CLIP_CUSTOM_2, "<custom#2>") \
- X(CLIP_CUSTOM_3, "<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/unix/uppity.c b/unix/uppity.c
new file mode 100644
index 00000000..11fead95
--- /dev/null
+++ b/unix/uppity.c
@@ -0,0 +1,969 @@
+/*
+ * SSH server for Unix: main program.
+ *
+ * ======================================================================
+ *
+ * This server is NOT SECURE!
+ *
+ * DO NOT DEPLOY IT IN A HOSTILE-FACING ENVIRONMENT!
+ *
+ * Its purpose is to speak the server end of everything PuTTY speaks
+ * on the client side, so that I can test that I haven't broken PuTTY
+ * when I reorganise its code, even things like RSA key exchange or
+ * chained auth methods which it's hard to find a server that speaks
+ * at all.
+ *
+ * It has no interaction with the OS's authentication system: the
+ * authentications it will accept are configurable by command-line
+ * option, and once you authenticate, it will run the connection
+ * protocol - including all subprocesses and shells - under the same
+ * Unix user id you started it under.
+ *
+ * It really is only suitable for testing the actual SSH protocol.
+ * Don't use it for anything more serious!
+ *
+ * ======================================================================
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <pwd.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+
+#include "putty.h"
+#include "mpint.h"
+#include "ssh.h"
+#include "ssh/server.h"
+
+void modalfatalbox(const char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "FATAL ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ exit(1);
+}
+void nonfatal(const char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+}
+
+char *platform_default_s(const char *name)
+{
+ return NULL;
+}
+
+bool platform_default_b(const char *name, bool def)
+{
+ return def;
+}
+
+int platform_default_i(const char *name, int def)
+{
+ return def;
+}
+
+FontSpec *platform_default_fontspec(const char *name)
+{
+ return fontspec_new("");
+}
+
+Filename *platform_default_filename(const char *name)
+{
+ return filename_from_str("");
+}
+
+char *x_get_default(const char *key)
+{
+ return NULL; /* this is a stub */
+}
+
+void old_keyfile_warning(void) { }
+
+void timer_change_notify(unsigned long next)
+{
+}
+
+char *platform_get_x_display(void) { return NULL; }
+
+void make_unix_sftp_filehandle_key(void *data, size_t size)
+{
+ random_read(data, size);
+}
+
+static bool verbose;
+
+struct server_config;
+
+struct AuthPolicyShared {
+ struct AuthPolicy_ssh1_pubkey *ssh1keys;
+ struct AuthPolicy_ssh2_pubkey *ssh2keys;
+};
+
+struct AuthPolicy {
+ struct AuthPolicyShared *shared;
+ int kbdint_state;
+};
+
+struct server_instance {
+ unsigned id;
+ AuthPolicy ap;
+ LogPolicy logpolicy;
+ struct server_config *cfg;
+};
+
+struct server_config {
+ unsigned config_id;
+
+ Conf *conf;
+ const SshServerConfig *ssc;
+
+ ssh_key **hostkeys;
+ int nhostkeys;
+
+ RSAKey *hostkey1;
+
+ unsigned auth_methods;
+
+ struct AuthPolicyShared *ap_shared;
+
+ Socket *listening_socket;
+ Plug listening_plug;
+};
+
+static unsigned next_id = 0;
+
+static void log_to_stderr(unsigned id, const char *msg)
+{
+ if (id != (unsigned)-1)
+ fprintf(stderr, "conn#%u: ", id);
+ fputs(msg, stderr);
+ fputc('\n', stderr);
+ fflush(stderr);
+}
+
+static void server_eventlog(LogPolicy *lp, const char *event)
+{
+ struct server_instance *inst = container_of(
+ lp, struct server_instance, logpolicy);
+ if (verbose)
+ log_to_stderr(inst->id, event);
+}
+
+static void server_logging_error(LogPolicy *lp, const char *event)
+{
+ struct server_instance *inst = container_of(
+ lp, struct server_instance, logpolicy);
+ log_to_stderr(inst->id, event); /* unconditional */
+}
+
+static int server_askappend(
+ LogPolicy *lp, Filename *filename,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ return 2; /* always overwrite (FIXME: could make this a cmdline option) */
+}
+
+static const LogPolicyVtable server_logpolicy_vt = {
+ .eventlog = server_eventlog,
+ .askappend = server_askappend,
+ .logging_error = server_logging_error,
+ .verbose = null_lp_verbose_no,
+};
+
+struct AuthPolicy_ssh1_pubkey {
+ RSAKey key;
+ struct AuthPolicy_ssh1_pubkey *next;
+};
+struct AuthPolicy_ssh2_pubkey {
+ ptrlen public_blob;
+ struct AuthPolicy_ssh2_pubkey *next;
+};
+
+unsigned auth_methods(AuthPolicy *ap)
+{
+ struct server_instance *inst = container_of(
+ ap, struct server_instance, ap);
+ return inst->cfg->auth_methods;
+}
+bool auth_none(AuthPolicy *ap, ptrlen username)
+{
+ struct server_instance *inst = container_of(
+ ap, struct server_instance, ap);
+ if (inst->cfg->auth_methods & AUTHMETHOD_NONE)
+ return true;
+ return false;
+}
+int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password,
+ ptrlen *new_password_opt)
+{
+ const char *PHONY_GOOD_PASSWORD = "weasel";
+ const char *PHONY_BAD_PASSWORD = "ferret";
+
+ if (!new_password_opt) {
+ /* Accept login with our preconfigured good password */
+ if (ptrlen_eq_string(password, PHONY_GOOD_PASSWORD))
+ return 1;
+ /* Don't outright reject the bad password, but insist on a change */
+ if (ptrlen_eq_string(password, PHONY_BAD_PASSWORD))
+ return 2;
+ /* Reject anything else */
+ return 0;
+ } else {
+ /* In a password-change request, expect the bad password as input */
+ if (!ptrlen_eq_string(password, PHONY_BAD_PASSWORD))
+ return 0;
+ /* Accept a request to change it to the good password */
+ if (ptrlen_eq_string(*new_password_opt, PHONY_GOOD_PASSWORD))
+ return 1;
+ /* Outright reject a request to change it to the same password
+ * as it already 'was' */
+ if (ptrlen_eq_string(*new_password_opt, PHONY_BAD_PASSWORD))
+ return 0;
+ /* Anything else, pretend the new pw wasn't good enough, and
+ * re-request a change */
+ return 2;
+ }
+}
+bool auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob)
+{
+ struct AuthPolicy_ssh2_pubkey *iter;
+ for (iter = ap->shared->ssh2keys; iter; iter = iter->next) {
+ if (ptrlen_eq_ptrlen(public_blob, iter->public_blob))
+ return true;
+ }
+ return false;
+}
+RSAKey *auth_publickey_ssh1(
+ AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus)
+{
+ struct AuthPolicy_ssh1_pubkey *iter;
+ for (iter = ap->shared->ssh1keys; iter; iter = iter->next) {
+ if (mp_cmp_eq(rsa_modulus, iter->key.modulus))
+ return &iter->key;
+ }
+ return NULL;
+}
+AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username)
+{
+ AuthKbdInt *aki;
+
+ switch (ap->kbdint_state) {
+ case 0:
+ aki = snew(AuthKbdInt);
+ aki->title = dupstr("Initial double prompt");
+ aki->instruction =
+ dupstr("First prompt should echo, second should not");
+ aki->nprompts = 2;
+ aki->prompts = snewn(aki->nprompts, AuthKbdIntPrompt);
+ aki->prompts[0].prompt = dupstr("Echoey prompt: ");
+ aki->prompts[0].echo = true;
+ aki->prompts[1].prompt = dupstr("Silent prompt: ");
+ aki->prompts[1].echo = false;
+ return aki;
+ case 1: {
+ struct server_instance *inst = container_of(
+ ap, struct server_instance, ap);
+ aki = snew(AuthKbdInt);
+ if (inst->cfg->ssc->stunt_allow_trivial_ki_auth) {
+ aki->title = dupstr("");
+ aki->instruction = dupstr("");
+ } else {
+ aki->title = dupstr("Zero-prompt step");
+ aki->instruction = dupstr("Shouldn't see any prompts this time");
+ }
+ aki->nprompts = 0;
+ aki->prompts = NULL;
+ return aki;
+ }
+ default:
+ ap->kbdint_state = 0;
+ return NULL;
+ }
+}
+int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses)
+{
+ switch (ap->kbdint_state) {
+ case 0:
+ if (ptrlen_eq_string(responses[0], "stoat") &&
+ ptrlen_eq_string(responses[1], "weasel")) {
+ ap->kbdint_state++;
+ return 0; /* those are the expected responses */
+ } else {
+ ap->kbdint_state = 0;
+ return -1;
+ }
+ break;
+ case 1:
+ return +1; /* succeed after the zero-prompt step */
+ default:
+ ap->kbdint_state = 0;
+ return -1;
+ }
+}
+char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username)
+{
+ /* FIXME: test returning a challenge string without \n, and ensure
+ * it gets printed as a prompt in its own right, without PuTTY
+ * making up a "Response: " prompt to follow it */
+ return dupprintf("This is a dummy %s challenge!\n",
+ (method == AUTHMETHOD_TIS ? "TIS" : "CryptoCard"));
+}
+bool auth_ssh1int_response(AuthPolicy *ap, ptrlen response)
+{
+ return ptrlen_eq_string(response, "otter");
+}
+bool auth_successful(AuthPolicy *ap, ptrlen username, unsigned method)
+{
+ return true;
+}
+
+static void safety_warning(FILE *fp)
+{
+ fputs(" =================================================\n"
+ " THIS SSH SERVER IS NOT WRITTEN TO BE SECURE!\n"
+ " DO NOT DEPLOY IT IN A HOSTILE-FACING ENVIRONMENT!\n"
+ " =================================================\n", fp);
+}
+
+static void show_help(FILE *fp)
+{
+ safety_warning(fp);
+ fputs("\n"
+ "usage: uppity [options] [--and <options>...]\n"
+ "options: --listen [PORT|PATH] listen to a port on localhost, or Unix socket\n"
+ " --listen-once (with --listen) stop after one "
+ "connection\n"
+ " --hostkey KEY SSH host key (need at least one)\n"
+ " --rsakexkey KEY key for SSH-2 RSA key exchange "
+ "(in SSH-1 format)\n"
+ " --userkey KEY public key"
+ " acceptable for user authentication\n"
+ " --sessiondir DIR cwd for session subprocess (default $HOME)\n"
+ " --bannertext TEXT send TEXT as SSH-2 auth banner\n"
+ " --bannerfile FILE send contents of FILE as SSH-2 auth "
+ "banner\n"
+ " --kexinit-kex STR override list of SSH-2 KEX methods\n"
+ " --kexinit-hostkey STR override list of SSH-2 host key "
+ "types\n"
+ " --kexinit-cscipher STR override list of SSH-2 "
+ "client->server ciphers\n"
+ " --kexinit-sccipher STR override list of SSH-2 "
+ "server->client ciphers\n"
+ " --kexinit-csmac STR override list of SSH-2 "
+ "client->server MACs\n"
+ " --kexinit-scmac STR override list of SSH-2 "
+ "server->client MACs\n"
+ " --kexinit-cscomp STR override list of SSH-2 "
+ "c->s compression types\n"
+ " --kexinit-sccomp STR override list of SSH-2 "
+ "s->c compression types\n"
+ " --ssh1-ciphers STR override list of SSH-1 ciphers\n"
+ " --ssh1-no-compression forbid compression in SSH-1\n"
+ " --deny-auth METHOD forbid a userauth method\n"
+ " --allow-auth METHOD allow a userauth method\n"
+ " (METHOD = none/password/publickey/kbdint/tis/"
+ "cryptocard)\n"
+ " --exitsignum send buggy numeric \"exit-signal\" "
+ "message\n"
+ " --verbose print event log messages to standard "
+ "error\n"
+ " --sshlog FILE write SSH packet log to FILE\n"
+ " --sshrawlog FILE write SSH packets + raw data log"
+ " to FILE\n"
+ " --and run a separate server on another port\n"
+ "also: uppity --help show this text\n"
+ " uppity --version show version information\n"
+ "\n", fp);
+ safety_warning(fp);
+}
+
+static void show_version_and_exit(void)
+{
+ char *buildinfo_text = buildinfo("\n");
+ printf("%s: %s\n%s\n", appname, ver, buildinfo_text);
+ sfree(buildinfo_text);
+ exit(0);
+}
+
+const bool buildinfo_gtk_relevant = false;
+
+static bool listening = false, listen_once = false;
+static bool finished = false;
+void server_instance_terminated(LogPolicy *lp)
+{
+ struct server_instance *inst = container_of(
+ lp, struct server_instance, logpolicy);
+
+ if (listening && !listen_once) {
+ log_to_stderr(inst->id, "connection terminated");
+ } else {
+ finished = true;
+ }
+
+ sfree(inst);
+}
+
+static bool longoptarg(const char *arg, const char *expected,
+ const char **val, int *argcp, char ***argvp)
+{
+ int len = strlen(expected);
+ if (memcmp(arg, expected, len))
+ return false;
+ if (arg[len] == '=') {
+ *val = arg + len + 1;
+ return true;
+ } else if (arg[len] == '\0') {
+ if (--*argcp > 0) {
+ *val = *++*argvp;
+ return true;
+ } else {
+ fprintf(stderr, "%s: option %s expects an argument\n",
+ appname, expected);
+ exit(1);
+ }
+ }
+ return false;
+}
+
+static bool longoptnoarg(const char *arg, const char *expected)
+{
+ int len = strlen(expected);
+ if (memcmp(arg, expected, len))
+ return false;
+ if (arg[len] == '=') {
+ fprintf(stderr, "%s: option %s expects no argument\n",
+ appname, expected);
+ exit(1);
+ } else if (arg[len] == '\0') {
+ return true;
+ }
+ return false;
+}
+
+static Plug *server_conn_plug(
+ struct server_config *cfg, struct server_instance **inst_out)
+{
+ struct server_instance *inst = snew(struct server_instance);
+
+ memset(inst, 0, sizeof(*inst));
+
+ inst->id = next_id++;
+ inst->ap.shared = cfg->ap_shared;
+ if (cfg->ssc->stunt_allow_trivial_ki_auth)
+ inst->ap.kbdint_state = 1;
+ inst->logpolicy.vt = &server_logpolicy_vt;
+ inst->cfg = cfg;
+
+ if (inst_out)
+ *inst_out = inst;
+
+ return ssh_server_plug(
+ cfg->conf, cfg->ssc, cfg->hostkeys, cfg->nhostkeys, cfg->hostkey1,
+ &inst->ap, &inst->logpolicy, &unix_live_sftpserver_vt);
+}
+
+static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
+ const char *error_msg, int error_code)
+{
+ log_to_stderr((unsigned)-1, error_msg);
+}
+
+static void server_closing(Plug *plug, PlugCloseType type,
+ const char *error_msg)
+{
+ if (type != PLUGCLOSE_NORMAL)
+ log_to_stderr((unsigned)-1, error_msg);
+}
+
+static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
+{
+ struct server_config *cfg = container_of(
+ p, struct server_config, listening_plug);
+ Socket *s;
+ const char *err;
+
+ struct server_instance *inst;
+
+ if (listen_once) {
+ if (!cfg->listening_socket) /* in case of rapid double-accept */
+ return 1;
+ sk_close(cfg->listening_socket);
+ cfg->listening_socket = NULL;
+ }
+
+ unsigned old_next_id = next_id;
+
+ Plug *plug = server_conn_plug(cfg, &inst);
+ s = constructor(ctx, plug);
+ if ((err = sk_socket_error(s)) != NULL)
+ return 1;
+
+ SocketPeerInfo *pi = sk_peer_info(s);
+
+ if (pi->addressfamily != ADDRTYPE_LOCAL && !sk_peer_trusted(s)) {
+ fprintf(stderr, "rejected connection to serv#%u "
+ "from %s (untrustworthy peer)\n",
+ cfg->config_id, pi->log_text);
+ sk_free_peer_info(pi);
+ sk_close(s);
+ next_id = old_next_id;
+ return 1;
+ }
+
+ char *msg = dupprintf("new connection to serv#%u from %s",
+ cfg->config_id, pi->log_text);
+ log_to_stderr(inst->id, msg);
+ sfree(msg);
+ sk_free_peer_info(pi);
+
+ sk_set_frozen(s, false);
+ ssh_server_start(plug, s);
+ return 0;
+}
+
+static const PlugVtable server_plugvt = {
+ .log = server_log,
+ .closing = server_closing,
+ .accepting = server_accepting,
+};
+
+static unsigned auth_method_from_name(const char *name)
+{
+ if (!strcmp(name, "none"))
+ return AUTHMETHOD_NONE;
+ if (!strcmp(name, "tis"))
+ return AUTHMETHOD_TIS;
+ if (!strcmp(name, "cryptocard") || !strcmp(name, "ccard"))
+ return AUTHMETHOD_CRYPTOCARD;
+ if (!strcmp(name, "keyboard-interactive") || !strcmp(name, "k-i") ||
+ !strcmp(name, "kbdint") || !strcmp(name, "ki"))
+ return AUTHMETHOD_KBDINT;
+ if (!strcmp(name, "publickey") || !strcmp(name, "pubkey") ||
+ !strcmp(name, "pk"))
+ return AUTHMETHOD_PUBLICKEY;
+ if (!strcmp(name, "password") || !strcmp(name, "pw"))
+ return AUTHMETHOD_PASSWORD;
+ return 0;
+}
+
+struct cmdline_instance {
+ int listen_port;
+ const char *listen_socket;
+ ssh_key **hostkeys;
+ size_t nhostkeys, hostkeysize;
+ RSAKey *hostkey1;
+ unsigned auth_methods;
+ struct AuthPolicyShared aps;
+ SshServerConfig ssc;
+ Conf *conf;
+};
+
+static void init_cmdline_instance(struct cmdline_instance *ci)
+{
+ ci->listen_port = -1;
+ ci->listen_socket = NULL;
+
+ ci->hostkeys = NULL;
+ ci->nhostkeys = ci->hostkeysize = 0;
+ ci->hostkey1 = NULL;
+
+ ci->conf = make_ssh_server_conf();
+
+ ci->aps.ssh1keys = NULL;
+ ci->aps.ssh2keys = NULL;
+
+ memset(&ci->ssc, 0, sizeof(ci->ssc));
+
+ ci->ssc.application_name = "Uppity";
+ ci->ssc.session_starting_dir = getenv("HOME");
+ ci->ssc.ssh1_cipher_mask = SSH1_SUPPORTED_CIPHER_MASK;
+ ci->ssc.ssh1_allow_compression = true;
+
+ ci->auth_methods = (AUTHMETHOD_PUBLICKEY | AUTHMETHOD_PASSWORD |
+ AUTHMETHOD_KBDINT | AUTHMETHOD_TIS |
+ AUTHMETHOD_CRYPTOCARD);
+}
+
+static void cmdline_instance_start(struct cmdline_instance *ci)
+{
+ static unsigned next_server_config_id = 0;
+
+ struct server_config *scfg = snew(struct server_config);
+ scfg->config_id = next_server_config_id++;
+ scfg->conf = ci->conf;
+ scfg->ssc = &ci->ssc;
+ scfg->hostkeys = ci->hostkeys;
+ scfg->nhostkeys = ci->nhostkeys;
+ scfg->hostkey1 = ci->hostkey1;
+ scfg->ap_shared = &ci->aps;
+ scfg->auth_methods = ci->auth_methods;
+
+ if (ci->listen_port >= 0 || ci->listen_socket) {
+ listening = true;
+ scfg->listening_plug.vt = &server_plugvt;
+ char *msg;
+ if (ci->listen_port >= 0) {
+ scfg->listening_socket = sk_newlistener(
+ NULL, ci->listen_port, &scfg->listening_plug, true,
+ ADDRTYPE_UNSPEC);
+ msg = dupprintf("serv#%u: listening on port %d",
+ scfg->config_id, ci->listen_port);
+ } else {
+ SockAddr *addr = unix_sock_addr(ci->listen_socket);
+ scfg->listening_socket = new_unix_listener(
+ addr, &scfg->listening_plug);
+ msg = dupprintf("serv#%u: listening on Unix socket %s",
+ scfg->config_id, ci->listen_socket);
+ }
+
+ log_to_stderr(-1, msg);
+ sfree(msg);
+ } else {
+ struct server_instance *inst;
+ Plug *plug = server_conn_plug(scfg, &inst);
+ ssh_server_start(plug, make_fd_socket(0, 1, -1, NULL, 0, plug));
+ log_to_stderr(inst->id, "speaking SSH on stdio");
+ }
+}
+
+int main(int argc, char **argv)
+{
+ size_t ninstances = 0, instancessize = 8;
+ struct cmdline_instance *instances = snewn(
+ instancessize, struct cmdline_instance);
+ struct cmdline_instance *ci = &instances[ninstances++];
+ init_cmdline_instance(ci);
+
+ if (argc <= 1) {
+ /*
+ * We're going to terminate with an error message below,
+ * because there are no host keys. But we'll display the help
+ * as additional standard-error output, if nothing else so
+ * that people see the giant safety warning.
+ */
+ show_help(stderr);
+ fputc('\n', stderr);
+ }
+
+ while (--argc > 0) {
+ const char *arg = *++argv;
+ const char *val;
+
+ if (!strcmp(arg, "--help")) {
+ show_help(stdout);
+ exit(0);
+ } else if (longoptnoarg(arg, "--version")) {
+ show_version_and_exit();
+ } else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) {
+ verbose = true;
+ } else if (longoptnoarg(arg, "--and")) {
+ sgrowarray(instances, instancessize, ninstances);
+ ci = &instances[ninstances++];
+ init_cmdline_instance(ci);
+ } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) {
+ if (val[0] == '/') {
+ ci->listen_port = -1;
+ ci->listen_socket = val;
+ } else {
+ ci->listen_port = atoi(val);
+ ci->listen_socket = NULL;
+ }
+ } else if (!strcmp(arg, "--listen-once")) {
+ listen_once = true;
+ } else if (longoptarg(arg, "--hostkey", &val, &argc, &argv)) {
+ Filename *keyfile;
+ int keytype;
+ const char *error;
+
+ keyfile = filename_from_str(val);
+ keytype = key_type(keyfile);
+
+ if (keytype == SSH_KEYTYPE_SSH2) {
+ ssh2_userkey *uk;
+ ssh_key *key;
+ uk = ppk_load_f(keyfile, NULL, &error);
+ filename_free(keyfile);
+ if (!uk || !uk->key) {
+ fprintf(stderr, "%s: unable to load host key '%s': "
+ "%s\n", appname, val, error);
+ exit(1);
+ }
+ char *invalid = ssh_key_invalid(uk->key, 0);
+ if (invalid) {
+ fprintf(stderr, "%s: host key '%s' is unusable: "
+ "%s\n", appname, val, invalid);
+ exit(1);
+ }
+ key = uk->key;
+ sfree(uk->comment);
+ sfree(uk);
+
+ for (int i = 0; i < ci->nhostkeys; i++)
+ if (ssh_key_alg(ci->hostkeys[i]) == ssh_key_alg(key)) {
+ fprintf(stderr, "%s: host key '%s' duplicates key "
+ "type %s\n", appname, val,
+ ssh_key_alg(key)->ssh_id);
+ exit(1);
+ }
+
+ sgrowarray(ci->hostkeys, ci->hostkeysize, ci->nhostkeys);
+ ci->hostkeys[ci->nhostkeys++] = key;
+ } else if (keytype == SSH_KEYTYPE_SSH1) {
+ if (ci->hostkey1) {
+ fprintf(stderr, "%s: host key '%s' is a redundant "
+ "SSH-1 host key\n", appname, val);
+ exit(1);
+ }
+ ci->hostkey1 = snew(RSAKey);
+ if (!rsa1_load_f(keyfile, ci->hostkey1, NULL, &error)) {
+ fprintf(stderr, "%s: unable to load host key '%s': "
+ "%s\n", appname, val, error);
+ exit(1);
+ }
+ } else {
+ fprintf(stderr, "%s: '%s' is not loadable as a "
+ "private key (%s)", appname, val,
+ key_type_to_str(keytype));
+ exit(1);
+ }
+ } else if (longoptarg(arg, "--rsakexkey", &val, &argc, &argv)) {
+ Filename *keyfile;
+ int keytype;
+ const char *error;
+
+ keyfile = filename_from_str(val);
+ keytype = key_type(keyfile);
+
+ if (keytype != SSH_KEYTYPE_SSH1) {
+ fprintf(stderr, "%s: '%s' is not loadable as an SSH-1 format "
+ "private key (%s)", appname, val,
+ key_type_to_str(keytype));
+ exit(1);
+ }
+
+ if (ci->ssc.rsa_kex_key) {
+ freersakey(ci->ssc.rsa_kex_key);
+ } else {
+ ci->ssc.rsa_kex_key = snew(RSAKey);
+ }
+
+ if (!rsa1_load_f(keyfile, ci->ssc.rsa_kex_key, NULL, &error)) {
+ fprintf(stderr, "%s: unable to load RSA kex key '%s': "
+ "%s\n", appname, val, error);
+ exit(1);
+ }
+
+ ci->ssc.rsa_kex_key->sshk.vt = &ssh_rsa;
+ } else if (longoptarg(arg, "--userkey", &val, &argc, &argv)) {
+ Filename *keyfile;
+ int keytype;
+ const char *error;
+
+ keyfile = filename_from_str(val);
+ keytype = key_type(keyfile);
+
+ if (keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
+ keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
+ strbuf *sb = strbuf_new();
+ struct AuthPolicy_ssh2_pubkey *node;
+ void *blob;
+
+ if (!ppk_loadpub_f(keyfile, NULL, BinarySink_UPCAST(sb),
+ NULL, &error)) {
+ fprintf(stderr, "%s: unable to load user key '%s': "
+ "%s\n", appname, val, error);
+ exit(1);
+ }
+
+ node = snew_plus(struct AuthPolicy_ssh2_pubkey, sb->len);
+ blob = snew_plus_get_aux(node);
+ memcpy(blob, sb->u, sb->len);
+ node->public_blob = make_ptrlen(blob, sb->len);
+
+ node->next = ci->aps.ssh2keys;
+ ci->aps.ssh2keys = node;
+
+ strbuf_free(sb);
+ } else if (keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
+ strbuf *sb = strbuf_new();
+ BinarySource src[1];
+ struct AuthPolicy_ssh1_pubkey *node;
+
+ if (!rsa1_loadpub_f(keyfile, BinarySink_UPCAST(sb),
+ NULL, &error)) {
+ fprintf(stderr, "%s: unable to load user key '%s': "
+ "%s\n", appname, val, error);
+ exit(1);
+ }
+
+ node = snew(struct AuthPolicy_ssh1_pubkey);
+ BinarySource_BARE_INIT(src, sb->u, sb->len);
+ get_rsa_ssh1_pub(src, &node->key, RSA_SSH1_EXPONENT_FIRST);
+
+ node->next = ci->aps.ssh1keys;
+ ci->aps.ssh1keys = node;
+
+ strbuf_free(sb);
+ } else {
+ fprintf(stderr, "%s: '%s' is not loadable as a public key "
+ "(%s)\n", appname, val, key_type_to_str(keytype));
+ exit(1);
+ }
+ } else if (longoptarg(arg, "--bannerfile", &val, &argc, &argv)) {
+ FILE *fp = fopen(val, "r");
+ if (!fp) {
+ fprintf(stderr, "%s: %s: open: %s\n", appname,
+ val, strerror(errno));
+ exit(1);
+ }
+ strbuf *sb = strbuf_new();
+ if (!read_file_into(BinarySink_UPCAST(sb), fp)) {
+ fprintf(stderr, "%s: %s: read: %s\n", appname,
+ val, strerror(errno));
+ exit(1);
+ }
+ fclose(fp);
+ ci->ssc.banner = ptrlen_from_strbuf(sb);
+ } else if (longoptarg(arg, "--bannertext", &val, &argc, &argv)) {
+ ci->ssc.banner = ptrlen_from_asciz(val);
+ } else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) {
+ ci->ssc.session_starting_dir = val;
+ } else if (longoptarg(arg, "--kexinit-kex", &val, &argc, &argv)) {
+ ci->ssc.kex_override[KEXLIST_KEX] = ptrlen_from_asciz(val);
+ } else if (longoptarg(arg, "--kexinit-hostkey", &val, &argc, &argv)) {
+ ci->ssc.kex_override[KEXLIST_HOSTKEY] = ptrlen_from_asciz(val);
+ } else if (longoptarg(arg, "--kexinit-cscipher", &val, &argc, &argv)) {
+ ci->ssc.kex_override[KEXLIST_CSCIPHER] = ptrlen_from_asciz(val);
+ } else if (longoptarg(arg, "--kexinit-csmac", &val, &argc, &argv)) {
+ ci->ssc.kex_override[KEXLIST_CSMAC] = ptrlen_from_asciz(val);
+ } else if (longoptarg(arg, "--kexinit-cscomp", &val, &argc, &argv)) {
+ ci->ssc.kex_override[KEXLIST_CSCOMP] = ptrlen_from_asciz(val);
+ } else if (longoptarg(arg, "--kexinit-sccipher", &val, &argc, &argv)) {
+ ci->ssc.kex_override[KEXLIST_SCCIPHER] = ptrlen_from_asciz(val);
+ } else if (longoptarg(arg, "--kexinit-scmac", &val, &argc, &argv)) {
+ ci->ssc.kex_override[KEXLIST_SCMAC] = ptrlen_from_asciz(val);
+ } else if (longoptarg(arg, "--kexinit-sccomp", &val, &argc, &argv)) {
+ ci->ssc.kex_override[KEXLIST_SCCOMP] = ptrlen_from_asciz(val);
+ } else if (longoptarg(arg, "--allow-auth", &val, &argc, &argv)) {
+ unsigned method = auth_method_from_name(val);
+ if (!method) {
+ fprintf(stderr, "%s: unrecognised auth method '%s'\n",
+ appname, val);
+ exit(1);
+ }
+ ci->auth_methods |= method;
+ } else if (longoptarg(arg, "--deny-auth", &val, &argc, &argv)) {
+ unsigned method = auth_method_from_name(val);
+ if (!method) {
+ fprintf(stderr, "%s: unrecognised auth method '%s'\n",
+ appname, val);
+ exit(1);
+ }
+ ci->auth_methods &= ~method;
+ } else if (longoptarg(arg, "--ssh1-ciphers", &val, &argc, &argv)) {
+ ptrlen list = ptrlen_from_asciz(val);
+ ptrlen word;
+ unsigned long mask = 0;
+ while (word = ptrlen_get_word(&list, ","), word.len != 0) {
+
+#define SSH1_CIPHER_CASE(bitpos, name) \
+ if (ptrlen_eq_string(word, name)) { \
+ mask |= 1U << bitpos; \
+ continue; \
+ }
+ SSH1_SUPPORTED_CIPHER_LIST(SSH1_CIPHER_CASE);
+#undef SSH1_CIPHER_CASE
+
+ fprintf(stderr, "%s: unrecognised SSH-1 cipher '%.*s'\n",
+ appname, PTRLEN_PRINTF(word));
+ exit(1);
+ }
+ ci->ssc.ssh1_cipher_mask = mask;
+ } else if (longoptnoarg(arg, "--ssh1-no-compression")) {
+ ci->ssc.ssh1_allow_compression = false;
+ } else if (longoptnoarg(arg, "--exitsignum")) {
+ ci->ssc.exit_signal_numeric = true;
+ } else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) ||
+ longoptarg(arg, "-sshlog", &val, &argc, &argv)) {
+ Filename *logfile = filename_from_str(val);
+ conf_set_filename(ci->conf, CONF_logfilename, logfile);
+ filename_free(logfile);
+ conf_set_int(ci->conf, CONF_logtype, LGTYP_PACKETS);
+ conf_set_int(ci->conf, CONF_logxfovr, LGXF_OVR);
+ } else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) ||
+ longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) {
+ Filename *logfile = filename_from_str(val);
+ conf_set_filename(ci->conf, CONF_logfilename, logfile);
+ filename_free(logfile);
+ conf_set_int(ci->conf, CONF_logtype, LGTYP_SSHRAW);
+ conf_set_int(ci->conf, CONF_logxfovr, LGXF_OVR);
+ } else if (!strcmp(arg, "--pretend-to-accept-any-pubkey")) {
+ ci->ssc.stunt_pretend_to_accept_any_pubkey = true;
+ } else if (!strcmp(arg, "--open-unconditional-agent-socket")) {
+ ci->ssc.stunt_open_unconditional_agent_socket = true;
+ } else if (!strcmp(arg, "--allow-none-auth")) {
+ /* backwards-compatibility synonym for --allow-auth=none */
+ ci->auth_methods |= AUTHMETHOD_NONE;
+ } else if (!strcmp(arg, "--allow-trivial-ki-auth")) {
+ ci->ssc.stunt_allow_trivial_ki_auth = true;
+ } else if (!strcmp(arg, "--return-success-to-pubkey-offer")) {
+ ci->ssc.stunt_return_success_to_pubkey_offer = true;
+ } else {
+ fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg);
+ exit(1);
+ }
+ }
+
+ if (ninstances > 1 && listen_once) {
+ fprintf(stderr, "%s: cannot listen once only with multiple server "
+ "instances\n", appname);
+ exit(1);
+ }
+ for (size_t i = 0; i < ninstances; i++) {
+ ci = &instances[i];
+ if (ci->nhostkeys == 0 && !ci->hostkey1) {
+ fprintf(stderr, "%s: specify at least one host key\n", appname);
+ exit(1);
+ }
+ if (ninstances > 1 && !(ci->listen_port >= 0 || ci->listen_socket)) {
+ fprintf(stderr, "%s: cannot talk to stdio with multiple server "
+ "instances\n", appname);
+ exit(1);
+ }
+ }
+
+ random_ref();
+
+ /*
+ * Block SIGPIPE, so that we'll get EPIPE individually on
+ * particular network connections that go wrong.
+ */
+ putty_signal(SIGPIPE, SIG_IGN);
+
+ sk_init();
+ uxsel_init();
+
+ for (size_t i = 0; i < ninstances; i++)
+ cmdline_instance_start(&instances[i]);
+
+ cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check,
+ cliloop_always_continue, NULL);
+
+ return 0;
+}
diff --git a/unix/utils/align_label_left.c b/unix/utils/align_label_left.c
new file mode 100644
index 00000000..eaae01ab
--- /dev/null
+++ b/unix/utils/align_label_left.c
@@ -0,0 +1,20 @@
+/*
+ * Helper function to align the text in a GtkLabel to the left, which
+ * has to be done in several different ways depending on GTK version.
+ */
+
+#include <gtk/gtk.h>
+#include "putty.h"
+#include "gtkcompat.h"
+#include "gtkmisc.h"
+
+void align_label_left(GtkLabel *label)
+{
+#if GTK_CHECK_VERSION(3,16,0)
+ gtk_label_set_xalign(label, 0.0);
+#elif GTK_CHECK_VERSION(3,14,0)
+ gtk_widget_set_halign(GTK_WIDGET(label), GTK_ALIGN_START);
+#else
+ gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
+#endif
+}
diff --git a/unix/utils/arm_arch_queries.c b/unix/utils/arm_arch_queries.c
new file mode 100644
index 00000000..c3dc286b
--- /dev/null
+++ b/unix/utils/arm_arch_queries.c
@@ -0,0 +1,105 @@
+/*
+ * Unix implementation of the OS query functions that detect Arm
+ * architecture extensions.
+ */
+
+#include "putty.h"
+#include "ssh.h"
+
+#include "utils/arm_arch_queries.h"
+
+#if defined __arm__ || defined __aarch64__
+
+bool platform_aes_neon_available(void)
+{
+#if defined HWCAP_AES
+ return getauxval(AT_HWCAP) & HWCAP_AES;
+#elif defined HWCAP2_AES
+ return getauxval(AT_HWCAP2) & HWCAP2_AES;
+#elif defined __APPLE__
+ SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_AES");
+ /* Older M1 macOS didn't provide this flag, but as far as I know
+ * implemented the crypto extension anyway, so treat 'feature
+ * missing' as 'implemented' */
+ return res != SYSCTL_OFF;
+#else
+ return false;
+#endif
+}
+
+bool platform_pmull_neon_available(void)
+{
+#if defined HWCAP_PMULL
+ return getauxval(AT_HWCAP) & HWCAP_PMULL;
+#elif defined HWCAP2_PMULL
+ return getauxval(AT_HWCAP2) & HWCAP2_PMULL;
+#elif defined __APPLE__
+ SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_PMULL");
+ /* As above, treat 'missing' as enabled */
+ return res != SYSCTL_OFF;
+#else
+ return false;
+#endif
+}
+
+bool platform_sha256_neon_available(void)
+{
+#if defined HWCAP_SHA2
+ return getauxval(AT_HWCAP) & HWCAP_SHA2;
+#elif defined HWCAP2_SHA2
+ return getauxval(AT_HWCAP2) & HWCAP2_SHA2;
+#elif defined __APPLE__
+ SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_SHA256");
+ /* As above, treat 'missing' as enabled */
+ return res != SYSCTL_OFF;
+#else
+ return false;
+#endif
+}
+
+bool platform_sha1_neon_available(void)
+{
+#if defined HWCAP_SHA1
+ return getauxval(AT_HWCAP) & HWCAP_SHA1;
+#elif defined HWCAP2_SHA1
+ return getauxval(AT_HWCAP2) & HWCAP2_SHA1;
+#elif defined __APPLE__
+ SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_SHA1");
+ /* As above, treat 'missing' as enabled */
+ return res != SYSCTL_OFF;
+#else
+ return false;
+#endif
+}
+
+bool platform_sha512_neon_available(void)
+{
+#if defined HWCAP_SHA512
+ return getauxval(AT_HWCAP) & HWCAP_SHA512;
+#elif defined HWCAP2_SHA512
+ return getauxval(AT_HWCAP2) & HWCAP2_SHA512;
+#elif defined __APPLE__
+ /* There are two sysctl flags for this, apparently invented at
+ * different times. Try both, falling back to the older one. */
+ SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_SHA512");
+ if (res != SYSCTL_MISSING)
+ return res == SYSCTL_ON;
+
+ res = test_sysctl_flag("hw.optional.armv8_2_sha512");
+ return res == SYSCTL_ON;
+#else
+ return false;
+#endif
+}
+
+#else /* defined __arm__ || defined __aarch64__ */
+
+/*
+ * Include _something_ in this file to prevent an annoying compiler
+ * warning, and to avoid having to condition out this file in
+ * CMakeLists. It's in a library, so this variable shouldn't end up in
+ * any actual program, because nothing will refer to it.
+ */
+const int arm_arch_queries_dummy_variable = 0;
+
+#endif /* defined __arm__ || defined __aarch64__ */
diff --git a/unix/utils/arm_arch_queries.h b/unix/utils/arm_arch_queries.h
new file mode 100644
index 00000000..fa46c622
--- /dev/null
+++ b/unix/utils/arm_arch_queries.h
@@ -0,0 +1,69 @@
+/*
+ * Header included only by arm_arch_queries.c.
+ *
+ * The only reason this is a header file instead of a source file is
+ * so that I can define 'static inline' functions which may or may not
+ * be used, without provoking a compiler warning when I turn out not
+ * to use them in the subsequent source file.
+ */
+
+#ifndef PUTTY_ARM_ARCH_QUERIES_H
+#define PUTTY_ARM_ARCH_QUERIES_H
+
+#if defined __APPLE__
+#if HAVE_SYS_SYSCTL_H
+#include <sys/sysctl.h>
+#endif
+#endif /* defined __APPLE__ */
+
+#if defined __arm__ || defined __aarch64__
+
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#if HAVE_SYS_AUXV_H
+#include <sys/auxv.h>
+#endif
+
+#if HAVE_ASM_HWCAP_H
+#include <asm/hwcap.h>
+#endif
+
+#if HAVE_GETAUXVAL
+/* No code needed: getauxval has just the API we want already */
+#elif HAVE_ELF_AUX_INFO
+/* Implement the simple getauxval API in terms of FreeBSD elf_aux_info */
+static inline u_long getauxval(int which)
+{
+ u_long toret;
+ if (elf_aux_info(which, &toret, sizeof(toret)) != 0)
+ return 0; /* elf_aux_info didn't work */
+ return toret;
+}
+#else
+/* Implement a stub getauxval which returns no capabilities */
+static inline u_long getauxval(int which) { return 0; }
+#endif
+
+#endif /* defined __arm__ || defined __aarch64__ */
+
+#if defined __APPLE__
+typedef enum { SYSCTL_MISSING, SYSCTL_OFF, SYSCTL_ON } SysctlResult;
+
+static inline SysctlResult test_sysctl_flag(const char *flagname)
+{
+#if HAVE_SYSCTLBYNAME
+ int value;
+ size_t size = sizeof(value);
+ if (sysctlbyname(flagname, &value, &size, NULL, 0) == 0 &&
+ size == sizeof(value)) {
+ return value != 0 ? SYSCTL_ON : SYSCTL_OFF;
+ }
+#else /* HAVE_SYSCTLBYNAME */
+ return SYSCTL_MISSING;
+#endif /* HAVE_SYSCTLBYNAME */
+}
+#endif /* defined __APPLE__ */
+
+#endif /* PUTTY_ARM_ARCH_QUERIES_H */
diff --git a/unix/utils/block_signal.c b/unix/utils/block_signal.c
new file mode 100644
index 00000000..918e63a4
--- /dev/null
+++ b/unix/utils/block_signal.c
@@ -0,0 +1,21 @@
+/*
+ * Handy function to block or unblock a signal, which does all the
+ * messing about with sigset_t for you.
+ */
+
+#include <signal.h>
+#include <stdlib.h>
+
+#include "defs.h"
+
+void block_signal(int sig, bool block_it)
+{
+ sigset_t ss;
+
+ sigemptyset(&ss);
+ sigaddset(&ss, sig);
+ if(sigprocmask(block_it ? SIG_BLOCK : SIG_UNBLOCK, &ss, 0) < 0) {
+ perror("sigprocmask");
+ exit(1);
+ }
+}
diff --git a/unix/utils/buildinfo_gtk_version.c b/unix/utils/buildinfo_gtk_version.c
new file mode 100644
index 00000000..a2ec187e
--- /dev/null
+++ b/unix/utils/buildinfo_gtk_version.c
@@ -0,0 +1,14 @@
+/*
+ * Return the version of GTK we were compiled against, for buildinfo.
+ */
+
+#include <gtk/gtk.h>
+#include "putty.h"
+#include "gtkcompat.h"
+#include "gtkmisc.h"
+
+char *buildinfo_gtk_version(void)
+{
+ return dupprintf("%d.%d.%d",
+ GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION);
+}
diff --git a/unix/utils/cloexec.c b/unix/utils/cloexec.c
new file mode 100644
index 00000000..2fc22289
--- /dev/null
+++ b/unix/utils/cloexec.c
@@ -0,0 +1,49 @@
+/*
+ * Set and clear the FD_CLOEXEC fcntl option on a file descriptor.
+ *
+ * We don't realistically expect these operations to fail (the most
+ * plausible error condition is EBADF, but we always believe ourselves
+ * to be passing a valid fd so even that's an assertion-fail sort of
+ * response), so we don't make any effort to return sensible error
+ * codes to the caller - we just log to standard error and die
+ * unceremoniously.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <fcntl.h>
+
+#include "putty.h"
+
+void cloexec(int fd)
+{
+ int fdflags;
+
+ fdflags = fcntl(fd, F_GETFD);
+ if (fdflags < 0) {
+ fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno));
+ exit(1);
+ }
+ if (fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC) < 0) {
+ fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno));
+ exit(1);
+ }
+}
+
+void noncloexec(int fd)
+{
+ int fdflags;
+
+ fdflags = fcntl(fd, F_GETFD);
+ if (fdflags < 0) {
+ fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno));
+ exit(1);
+ }
+ if (fcntl(fd, F_SETFD, fdflags & ~FD_CLOEXEC) < 0) {
+ fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno));
+ exit(1);
+ }
+}
diff --git a/unix/utils/dputs.c b/unix/utils/dputs.c
new file mode 100644
index 00000000..97a275e0
--- /dev/null
+++ b/unix/utils/dputs.c
@@ -0,0 +1,24 @@
+/*
+ * Implementation of dputs() for Unix.
+ *
+ * The debug messages are written to standard output, and also into a
+ * file called debug.log.
+ */
+
+#include <unistd.h>
+
+#include "putty.h"
+
+static FILE *debug_fp = NULL;
+
+void dputs(const char *buf)
+{
+ if (!debug_fp) {
+ debug_fp = fopen("debug.log", "w");
+ }
+
+ if (write(1, buf, strlen(buf)) < 0) {} /* 'error check' to placate gcc */
+
+ fputs(buf, debug_fp);
+ fflush(debug_fp);
+}
diff --git a/unix/utils/filename.c b/unix/utils/filename.c
new file mode 100644
index 00000000..208483d2
--- /dev/null
+++ b/unix/utils/filename.c
@@ -0,0 +1,72 @@
+/*
+ * Implementation of Filename for Unix, including f_open().
+ */
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "putty.h"
+
+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[0];
+}
+
+void filename_free(Filename *fn)
+{
+ sfree(fn->path);
+ sfree(fn);
+}
+
+void filename_serialise(BinarySink *bs, const Filename *f)
+{
+ put_asciz(bs, f->path);
+}
+Filename *filename_deserialise(BinarySource *src)
+{
+ return filename_from_str(get_asciz(src));
+}
+
+char filename_char_sanitise(char c)
+{
+ if (c == '/')
+ return '.';
+ return c;
+}
+
+FILE *f_open(const Filename *filename, char const *mode, bool is_private)
+{
+ if (!is_private) {
+ return fopen(filename->path, mode);
+ } else {
+ int fd;
+ assert(mode[0] == 'w'); /* is_private is meaningless for read,
+ and tricky for append */
+ fd = open(filename->path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (fd < 0)
+ return NULL;
+ return fdopen(fd, mode);
+ }
+}
diff --git a/unix/utils/fontspec.c b/unix/utils/fontspec.c
new file mode 100644
index 00000000..7c5a0a2f
--- /dev/null
+++ b/unix/utils/fontspec.c
@@ -0,0 +1,35 @@
+/*
+ * Implementation of FontSpec for Unix. This is more or less
+ * degenerate - on this platform a font specification is just a
+ * string.
+ */
+
+#include "putty.h"
+
+FontSpec *fontspec_new(const char *name)
+{
+ FontSpec *f = snew(FontSpec);
+ f->name = dupstr(name);
+ return f;
+}
+
+FontSpec *fontspec_copy(const FontSpec *f)
+{
+ return fontspec_new(f->name);
+}
+
+void fontspec_free(FontSpec *f)
+{
+ sfree(f->name);
+ sfree(f);
+}
+
+void fontspec_serialise(BinarySink *bs, FontSpec *f)
+{
+ put_asciz(bs, f->name);
+}
+
+FontSpec *fontspec_deserialise(BinarySource *src)
+{
+ return fontspec_new(get_asciz(src));
+}
diff --git a/unix/utils/get_label_text_dimensions.c b/unix/utils/get_label_text_dimensions.c
new file mode 100644
index 00000000..fe1c13af
--- /dev/null
+++ b/unix/utils/get_label_text_dimensions.c
@@ -0,0 +1,42 @@
+/*
+ * Determine the dimensions of a piece of text in the standard
+ * font used in GTK interface elements like labels. We do this by
+ * instantiating an actual GtkLabel, and then querying its size.
+ */
+
+#include <gtk/gtk.h>
+#include "putty.h"
+#include "gtkcompat.h"
+#include "gtkmisc.h"
+
+void get_label_text_dimensions(const char *text, int *width, int *height)
+{
+ GtkWidget *label = gtk_label_new(text);
+
+ /*
+ * GTK2 and GTK3 require us to query the size completely
+ * differently. I'm sure there ought to be an easier approach than
+ * the way I'm doing this in GTK3, too!
+ */
+#if GTK_CHECK_VERSION(3,0,0)
+ PangoLayout *layout = gtk_label_get_layout(GTK_LABEL(label));
+ PangoRectangle logrect;
+ pango_layout_get_extents(layout, NULL, &logrect);
+ if (width)
+ *width = logrect.width / PANGO_SCALE;
+ if (height)
+ *height = logrect.height / PANGO_SCALE;
+#else
+ GtkRequisition req;
+ gtk_widget_size_request(label, &req);
+ if (width)
+ *width = req.width;
+ if (height)
+ *height = req.height;
+#endif
+
+ g_object_ref_sink(G_OBJECT(label));
+#if GTK_CHECK_VERSION(2,10,0)
+ g_object_unref(label);
+#endif
+}
diff --git a/unix/utils/get_username.c b/unix/utils/get_username.c
new file mode 100644
index 00000000..61e259c0
--- /dev/null
+++ b/unix/utils/get_username.c
@@ -0,0 +1,52 @@
+/*
+ * Implementation of get_username() for Unix.
+ */
+
+#include <unistd.h>
+#include <pwd.h>
+
+#include "putty.h"
+
+char *get_username(void)
+{
+ struct passwd *p;
+ uid_t uid = getuid();
+ char *user, *ret = NULL;
+
+ /*
+ * First, find who we think we are using getlogin. If this
+ * agrees with our uid, we'll go along with it. This should
+ * allow sharing of uids between several login names whilst
+ * coping correctly with people who have su'ed.
+ */
+ user = getlogin();
+#if HAVE_SETPWENT
+ setpwent();
+#endif
+ if (user)
+ p = getpwnam(user);
+ else
+ p = NULL;
+ if (p && p->pw_uid == uid) {
+ /*
+ * The result of getlogin() really does correspond to
+ * our uid. Fine.
+ */
+ ret = user;
+ } else {
+ /*
+ * If that didn't work, for whatever reason, we'll do
+ * the simpler version: look up our uid in the password
+ * file and map it straight to a name.
+ */
+ p = getpwuid(uid);
+ if (!p)
+ return NULL;
+ ret = p->pw_name;
+ }
+#if HAVE_ENDPWENT
+ endpwent();
+#endif
+
+ return dupstr(ret);
+}
diff --git a/unix/utils/get_x11_display.c b/unix/utils/get_x11_display.c
new file mode 100644
index 00000000..d9462c27
--- /dev/null
+++ b/unix/utils/get_x11_display.c
@@ -0,0 +1,34 @@
+/*
+ * Return the Xlib 'Display *' underlying our GTK environment, if any.
+ */
+
+#include <gtk/gtk.h>
+#include "putty.h"
+#include "gtkcompat.h"
+#include "gtkmisc.h"
+
+#ifndef NOT_X_WINDOWS
+
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+
+Display *get_x11_display(void)
+{
+#if GTK_CHECK_VERSION(3,0,0)
+ if (!GDK_IS_X11_DISPLAY(gdk_display_get_default()))
+ return NULL;
+#endif
+ return GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+}
+
+#else
+
+/*
+ * Include _something_ in this file to prevent an annoying compiler
+ * warning, and to avoid having to condition out this file in
+ * CMakeLists. It's in a library, so this variable shouldn't end up in
+ * any actual program, because nothing will refer to it.
+ */
+const int get_x11_display_dummy_variable = 0;
+
+#endif
diff --git a/unix/utils/getticks.c b/unix/utils/getticks.c
new file mode 100644
index 00000000..9d169816
--- /dev/null
+++ b/unix/utils/getticks.c
@@ -0,0 +1,33 @@
+/*
+ * Implement getticks() for Unix.
+ */
+
+#include <time.h>
+#include <sys/time.h>
+
+#include "putty.h"
+
+unsigned long getticks(void)
+{
+ /*
+ * We want to use milliseconds rather than the microseconds or
+ * nanoseconds given by the underlying clock functions, because we
+ * need a decent number of them to fit into a 32-bit word so it
+ * can be used for keepalives.
+ */
+#if HAVE_CLOCK_GETTIME && HAVE_CLOCK_MONOTONIC
+ {
+ /* Use CLOCK_MONOTONIC if available, so as to be unconfused if
+ * the system clock changes. */
+ struct timespec ts;
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
+ return ts.tv_sec * TICKSPERSEC +
+ ts.tv_nsec / (1000000000 / TICKSPERSEC);
+ }
+#endif
+ {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ return tv.tv_sec * TICKSPERSEC + tv.tv_usec / (1000000 / TICKSPERSEC);
+ }
+}
diff --git a/unix/utils/keysym_to_unicode.c b/unix/utils/keysym_to_unicode.c
new file mode 100644
index 00000000..37e419fe
--- /dev/null
+++ b/unix/utils/keysym_to_unicode.c
@@ -0,0 +1,1011 @@
+/*
+ * Map X keysyms to Unicode values.
+ *
+ * The basic idea of this is shamelessly cribbed from xterm. The
+ * actual character data is generated from Markus Kuhn's proposed
+ * redraft of the X11 keysym mapping table, using the following
+ * piece of Perl/sh code:
+
+wget -q -O - http://www.cl.cam.ac.uk/~mgk25/ucs/X11.keysyms | \
+perl -ne '/^(\d+)\s+(\d+)\s+[\d\/]+\s+U\+([\dA-Fa-f]+)/ and' \
+ -e ' do { $a{$1 * 256+ $2} = hex $3; };' \
+ -e 'END { foreach $i (sort {$a <=> $b} keys %a) {' \
+ -e ' printf " {0x%x, 0x%x},\n", $i, $a{$i} } }' \
+ -e 'BEGIN { $a{0x13a4} = 0x20ac }'
+
+ * (The BEGIN clause inserts a mapping for the Euro sign which for
+ * some reason isn't in the list but xterm supports. *shrug*.)
+ */
+
+#include "misc.h"
+
+struct keysym {
+ /*
+ * Currently nothing in here is above 0xFFFF, so I'll use
+ * `unsigned short' to save space.
+ */
+ unsigned short keysym;
+ unsigned short unicode;
+};
+
+static struct keysym keysyms[] = {
+ {0x20, 0x20},
+ {0x21, 0x21},
+ {0x22, 0x22},
+ {0x23, 0x23},
+ {0x24, 0x24},
+ {0x25, 0x25},
+ {0x26, 0x26},
+ {0x27, 0x27},
+ {0x28, 0x28},
+ {0x29, 0x29},
+ {0x2a, 0x2a},
+ {0x2b, 0x2b},
+ {0x2c, 0x2c},
+ {0x2d, 0x2d},
+ {0x2e, 0x2e},
+ {0x2f, 0x2f},
+ {0x30, 0x30},
+ {0x31, 0x31},
+ {0x32, 0x32},
+ {0x33, 0x33},
+ {0x34, 0x34},
+ {0x35, 0x35},
+ {0x36, 0x36},
+ {0x37, 0x37},
+ {0x38, 0x38},
+ {0x39, 0x39},
+ {0x3a, 0x3a},
+ {0x3b, 0x3b},
+ {0x3c, 0x3c},
+ {0x3d, 0x3d},
+ {0x3e, 0x3e},
+ {0x3f, 0x3f},
+ {0x40, 0x40},
+ {0x41, 0x41},
+ {0x42, 0x42},
+ {0x43, 0x43},
+ {0x44, 0x44},
+ {0x45, 0x45},
+ {0x46, 0x46},
+ {0x47, 0x47},
+ {0x48, 0x48},
+ {0x49, 0x49},
+ {0x4a, 0x4a},
+ {0x4b, 0x4b},
+ {0x4c, 0x4c},
+ {0x4d, 0x4d},
+ {0x4e, 0x4e},
+ {0x4f, 0x4f},
+ {0x50, 0x50},
+ {0x51, 0x51},
+ {0x52, 0x52},
+ {0x53, 0x53},
+ {0x54, 0x54},
+ {0x55, 0x55},
+ {0x56, 0x56},
+ {0x57, 0x57},
+ {0x58, 0x58},
+ {0x59, 0x59},
+ {0x5a, 0x5a},
+ {0x5b, 0x5b},
+ {0x5c, 0x5c},
+ {0x5d, 0x5d},
+ {0x5e, 0x5e},
+ {0x5f, 0x5f},
+ {0x60, 0x60},
+ {0x61, 0x61},
+ {0x62, 0x62},
+ {0x63, 0x63},
+ {0x64, 0x64},
+ {0x65, 0x65},
+ {0x66, 0x66},
+ {0x67, 0x67},
+ {0x68, 0x68},
+ {0x69, 0x69},
+ {0x6a, 0x6a},
+ {0x6b, 0x6b},
+ {0x6c, 0x6c},
+ {0x6d, 0x6d},
+ {0x6e, 0x6e},
+ {0x6f, 0x6f},
+ {0x70, 0x70},
+ {0x71, 0x71},
+ {0x72, 0x72},
+ {0x73, 0x73},
+ {0x74, 0x74},
+ {0x75, 0x75},
+ {0x76, 0x76},
+ {0x77, 0x77},
+ {0x78, 0x78},
+ {0x79, 0x79},
+ {0x7a, 0x7a},
+ {0x7b, 0x7b},
+ {0x7c, 0x7c},
+ {0x7d, 0x7d},
+ {0x7e, 0x7e},
+ {0xa0, 0xa0},
+ {0xa1, 0xa1},
+ {0xa2, 0xa2},
+ {0xa3, 0xa3},
+ {0xa4, 0xa4},
+ {0xa5, 0xa5},
+ {0xa6, 0xa6},
+ {0xa7, 0xa7},
+ {0xa8, 0xa8},
+ {0xa9, 0xa9},
+ {0xaa, 0xaa},
+ {0xab, 0xab},
+ {0xac, 0xac},
+ {0xad, 0xad},
+ {0xae, 0xae},
+ {0xaf, 0xaf},
+ {0xb0, 0xb0},
+ {0xb1, 0xb1},
+ {0xb2, 0xb2},
+ {0xb3, 0xb3},
+ {0xb4, 0xb4},
+ {0xb5, 0xb5},
+ {0xb6, 0xb6},
+ {0xb7, 0xb7},
+ {0xb8, 0xb8},
+ {0xb9, 0xb9},
+ {0xba, 0xba},
+ {0xbb, 0xbb},
+ {0xbc, 0xbc},
+ {0xbd, 0xbd},
+ {0xbe, 0xbe},
+ {0xbf, 0xbf},
+ {0xc0, 0xc0},
+ {0xc1, 0xc1},
+ {0xc2, 0xc2},
+ {0xc3, 0xc3},
+ {0xc4, 0xc4},
+ {0xc5, 0xc5},
+ {0xc6, 0xc6},
+ {0xc7, 0xc7},
+ {0xc8, 0xc8},
+ {0xc9, 0xc9},
+ {0xca, 0xca},
+ {0xcb, 0xcb},
+ {0xcc, 0xcc},
+ {0xcd, 0xcd},
+ {0xce, 0xce},
+ {0xcf, 0xcf},
+ {0xd0, 0xd0},
+ {0xd1, 0xd1},
+ {0xd2, 0xd2},
+ {0xd3, 0xd3},
+ {0xd4, 0xd4},
+ {0xd5, 0xd5},
+ {0xd6, 0xd6},
+ {0xd7, 0xd7},
+ {0xd8, 0xd8},
+ {0xd9, 0xd9},
+ {0xda, 0xda},
+ {0xdb, 0xdb},
+ {0xdc, 0xdc},
+ {0xdd, 0xdd},
+ {0xde, 0xde},
+ {0xdf, 0xdf},
+ {0xe0, 0xe0},
+ {0xe1, 0xe1},
+ {0xe2, 0xe2},
+ {0xe3, 0xe3},
+ {0xe4, 0xe4},
+ {0xe5, 0xe5},
+ {0xe6, 0xe6},
+ {0xe7, 0xe7},
+ {0xe8, 0xe8},
+ {0xe9, 0xe9},
+ {0xea, 0xea},
+ {0xeb, 0xeb},
+ {0xec, 0xec},
+ {0xed, 0xed},
+ {0xee, 0xee},
+ {0xef, 0xef},
+ {0xf0, 0xf0},
+ {0xf1, 0xf1},
+ {0xf2, 0xf2},
+ {0xf3, 0xf3},
+ {0xf4, 0xf4},
+ {0xf5, 0xf5},
+ {0xf6, 0xf6},
+ {0xf7, 0xf7},
+ {0xf8, 0xf8},
+ {0xf9, 0xf9},
+ {0xfa, 0xfa},
+ {0xfb, 0xfb},
+ {0xfc, 0xfc},
+ {0xfd, 0xfd},
+ {0xfe, 0xfe},
+ {0xff, 0xff},
+ {0x1a1, 0x104},
+ {0x1a2, 0x2d8},
+ {0x1a3, 0x141},
+ {0x1a5, 0x13d},
+ {0x1a6, 0x15a},
+ {0x1a9, 0x160},
+ {0x1aa, 0x15e},
+ {0x1ab, 0x164},
+ {0x1ac, 0x179},
+ {0x1ae, 0x17d},
+ {0x1af, 0x17b},
+ {0x1b1, 0x105},
+ {0x1b2, 0x2db},
+ {0x1b3, 0x142},
+ {0x1b5, 0x13e},
+ {0x1b6, 0x15b},
+ {0x1b7, 0x2c7},
+ {0x1b9, 0x161},
+ {0x1ba, 0x15f},
+ {0x1bb, 0x165},
+ {0x1bc, 0x17a},
+ {0x1bd, 0x2dd},
+ {0x1be, 0x17e},
+ {0x1bf, 0x17c},
+ {0x1c0, 0x154},
+ {0x1c3, 0x102},
+ {0x1c5, 0x139},
+ {0x1c6, 0x106},
+ {0x1c8, 0x10c},
+ {0x1ca, 0x118},
+ {0x1cc, 0x11a},
+ {0x1cf, 0x10e},
+ {0x1d0, 0x110},
+ {0x1d1, 0x143},
+ {0x1d2, 0x147},
+ {0x1d5, 0x150},
+ {0x1d8, 0x158},
+ {0x1d9, 0x16e},
+ {0x1db, 0x170},
+ {0x1de, 0x162},
+ {0x1e0, 0x155},
+ {0x1e3, 0x103},
+ {0x1e5, 0x13a},
+ {0x1e6, 0x107},
+ {0x1e8, 0x10d},
+ {0x1ea, 0x119},
+ {0x1ec, 0x11b},
+ {0x1ef, 0x10f},
+ {0x1f0, 0x111},
+ {0x1f1, 0x144},
+ {0x1f2, 0x148},
+ {0x1f5, 0x151},
+ {0x1f8, 0x159},
+ {0x1f9, 0x16f},
+ {0x1fb, 0x171},
+ {0x1fe, 0x163},
+ {0x1ff, 0x2d9},
+ {0x2a1, 0x126},
+ {0x2a6, 0x124},
+ {0x2a9, 0x130},
+ {0x2ab, 0x11e},
+ {0x2ac, 0x134},
+ {0x2b1, 0x127},
+ {0x2b6, 0x125},
+ {0x2b9, 0x131},
+ {0x2bb, 0x11f},
+ {0x2bc, 0x135},
+ {0x2c5, 0x10a},
+ {0x2c6, 0x108},
+ {0x2d5, 0x120},
+ {0x2d8, 0x11c},
+ {0x2dd, 0x16c},
+ {0x2de, 0x15c},
+ {0x2e5, 0x10b},
+ {0x2e6, 0x109},
+ {0x2f5, 0x121},
+ {0x2f8, 0x11d},
+ {0x2fd, 0x16d},
+ {0x2fe, 0x15d},
+ {0x3a2, 0x138},
+ {0x3a3, 0x156},
+ {0x3a5, 0x128},
+ {0x3a6, 0x13b},
+ {0x3aa, 0x112},
+ {0x3ab, 0x122},
+ {0x3ac, 0x166},
+ {0x3b3, 0x157},
+ {0x3b5, 0x129},
+ {0x3b6, 0x13c},
+ {0x3ba, 0x113},
+ {0x3bb, 0x123},
+ {0x3bc, 0x167},
+ {0x3bd, 0x14a},
+ {0x3bf, 0x14b},
+ {0x3c0, 0x100},
+ {0x3c7, 0x12e},
+ {0x3cc, 0x116},
+ {0x3cf, 0x12a},
+ {0x3d1, 0x145},
+ {0x3d2, 0x14c},
+ {0x3d3, 0x136},
+ {0x3d9, 0x172},
+ {0x3dd, 0x168},
+ {0x3de, 0x16a},
+ {0x3e0, 0x101},
+ {0x3e7, 0x12f},
+ {0x3ec, 0x117},
+ {0x3ef, 0x12b},
+ {0x3f1, 0x146},
+ {0x3f2, 0x14d},
+ {0x3f3, 0x137},
+ {0x3f9, 0x173},
+ {0x3fd, 0x169},
+ {0x3fe, 0x16b},
+ {0x47e, 0x203e},
+ {0x4a1, 0x3002},
+ {0x4a2, 0x300c},
+ {0x4a3, 0x300d},
+ {0x4a4, 0x3001},
+ {0x4a5, 0x30fb},
+ {0x4a6, 0x30f2},
+ {0x4a7, 0x30a1},
+ {0x4a8, 0x30a3},
+ {0x4a9, 0x30a5},
+ {0x4aa, 0x30a7},
+ {0x4ab, 0x30a9},
+ {0x4ac, 0x30e3},
+ {0x4ad, 0x30e5},
+ {0x4ae, 0x30e7},
+ {0x4af, 0x30c3},
+ {0x4b0, 0x30fc},
+ {0x4b1, 0x30a2},
+ {0x4b2, 0x30a4},
+ {0x4b3, 0x30a6},
+ {0x4b4, 0x30a8},
+ {0x4b5, 0x30aa},
+ {0x4b6, 0x30ab},
+ {0x4b7, 0x30ad},
+ {0x4b8, 0x30af},
+ {0x4b9, 0x30b1},
+ {0x4ba, 0x30b3},
+ {0x4bb, 0x30b5},
+ {0x4bc, 0x30b7},
+ {0x4bd, 0x30b9},
+ {0x4be, 0x30bb},
+ {0x4bf, 0x30bd},
+ {0x4c0, 0x30bf},
+ {0x4c1, 0x30c1},
+ {0x4c2, 0x30c4},
+ {0x4c3, 0x30c6},
+ {0x4c4, 0x30c8},
+ {0x4c5, 0x30ca},
+ {0x4c6, 0x30cb},
+ {0x4c7, 0x30cc},
+ {0x4c8, 0x30cd},
+ {0x4c9, 0x30ce},
+ {0x4ca, 0x30cf},
+ {0x4cb, 0x30d2},
+ {0x4cc, 0x30d5},
+ {0x4cd, 0x30d8},
+ {0x4ce, 0x30db},
+ {0x4cf, 0x30de},
+ {0x4d0, 0x30df},
+ {0x4d1, 0x30e0},
+ {0x4d2, 0x30e1},
+ {0x4d3, 0x30e2},
+ {0x4d4, 0x30e4},
+ {0x4d5, 0x30e6},
+ {0x4d6, 0x30e8},
+ {0x4d7, 0x30e9},
+ {0x4d8, 0x30ea},
+ {0x4d9, 0x30eb},
+ {0x4da, 0x30ec},
+ {0x4db, 0x30ed},
+ {0x4dc, 0x30ef},
+ {0x4dd, 0x30f3},
+ {0x4de, 0x309b},
+ {0x4df, 0x309c},
+ {0x5ac, 0x60c},
+ {0x5bb, 0x61b},
+ {0x5bf, 0x61f},
+ {0x5c1, 0x621},
+ {0x5c2, 0x622},
+ {0x5c3, 0x623},
+ {0x5c4, 0x624},
+ {0x5c5, 0x625},
+ {0x5c6, 0x626},
+ {0x5c7, 0x627},
+ {0x5c8, 0x628},
+ {0x5c9, 0x629},
+ {0x5ca, 0x62a},
+ {0x5cb, 0x62b},
+ {0x5cc, 0x62c},
+ {0x5cd, 0x62d},
+ {0x5ce, 0x62e},
+ {0x5cf, 0x62f},
+ {0x5d0, 0x630},
+ {0x5d1, 0x631},
+ {0x5d2, 0x632},
+ {0x5d3, 0x633},
+ {0x5d4, 0x634},
+ {0x5d5, 0x635},
+ {0x5d6, 0x636},
+ {0x5d7, 0x637},
+ {0x5d8, 0x638},
+ {0x5d9, 0x639},
+ {0x5da, 0x63a},
+ {0x5e0, 0x640},
+ {0x5e1, 0x641},
+ {0x5e2, 0x642},
+ {0x5e3, 0x643},
+ {0x5e4, 0x644},
+ {0x5e5, 0x645},
+ {0x5e6, 0x646},
+ {0x5e7, 0x647},
+ {0x5e8, 0x648},
+ {0x5e9, 0x649},
+ {0x5ea, 0x64a},
+ {0x5eb, 0x64b},
+ {0x5ec, 0x64c},
+ {0x5ed, 0x64d},
+ {0x5ee, 0x64e},
+ {0x5ef, 0x64f},
+ {0x5f0, 0x650},
+ {0x5f1, 0x651},
+ {0x5f2, 0x652},
+ {0x6a1, 0x452},
+ {0x6a2, 0x453},
+ {0x6a3, 0x451},
+ {0x6a4, 0x454},
+ {0x6a5, 0x455},
+ {0x6a6, 0x456},
+ {0x6a7, 0x457},
+ {0x6a8, 0x458},
+ {0x6a9, 0x459},
+ {0x6aa, 0x45a},
+ {0x6ab, 0x45b},
+ {0x6ac, 0x45c},
+ {0x6ae, 0x45e},
+ {0x6af, 0x45f},
+ {0x6b0, 0x2116},
+ {0x6b1, 0x402},
+ {0x6b2, 0x403},
+ {0x6b3, 0x401},
+ {0x6b4, 0x404},
+ {0x6b5, 0x405},
+ {0x6b6, 0x406},
+ {0x6b7, 0x407},
+ {0x6b8, 0x408},
+ {0x6b9, 0x409},
+ {0x6ba, 0x40a},
+ {0x6bb, 0x40b},
+ {0x6bc, 0x40c},
+ {0x6be, 0x40e},
+ {0x6bf, 0x40f},
+ {0x6c0, 0x44e},
+ {0x6c1, 0x430},
+ {0x6c2, 0x431},
+ {0x6c3, 0x446},
+ {0x6c4, 0x434},
+ {0x6c5, 0x435},
+ {0x6c6, 0x444},
+ {0x6c7, 0x433},
+ {0x6c8, 0x445},
+ {0x6c9, 0x438},
+ {0x6ca, 0x439},
+ {0x6cb, 0x43a},
+ {0x6cc, 0x43b},
+ {0x6cd, 0x43c},
+ {0x6ce, 0x43d},
+ {0x6cf, 0x43e},
+ {0x6d0, 0x43f},
+ {0x6d1, 0x44f},
+ {0x6d2, 0x440},
+ {0x6d3, 0x441},
+ {0x6d4, 0x442},
+ {0x6d5, 0x443},
+ {0x6d6, 0x436},
+ {0x6d7, 0x432},
+ {0x6d8, 0x44c},
+ {0x6d9, 0x44b},
+ {0x6da, 0x437},
+ {0x6db, 0x448},
+ {0x6dc, 0x44d},
+ {0x6dd, 0x449},
+ {0x6de, 0x447},
+ {0x6df, 0x44a},
+ {0x6e0, 0x42e},
+ {0x6e1, 0x410},
+ {0x6e2, 0x411},
+ {0x6e3, 0x426},
+ {0x6e4, 0x414},
+ {0x6e5, 0x415},
+ {0x6e6, 0x424},
+ {0x6e7, 0x413},
+ {0x6e8, 0x425},
+ {0x6e9, 0x418},
+ {0x6ea, 0x419},
+ {0x6eb, 0x41a},
+ {0x6ec, 0x41b},
+ {0x6ed, 0x41c},
+ {0x6ee, 0x41d},
+ {0x6ef, 0x41e},
+ {0x6f0, 0x41f},
+ {0x6f1, 0x42f},
+ {0x6f2, 0x420},
+ {0x6f3, 0x421},
+ {0x6f4, 0x422},
+ {0x6f5, 0x423},
+ {0x6f6, 0x416},
+ {0x6f7, 0x412},
+ {0x6f8, 0x42c},
+ {0x6f9, 0x42b},
+ {0x6fa, 0x417},
+ {0x6fb, 0x428},
+ {0x6fc, 0x42d},
+ {0x6fd, 0x429},
+ {0x6fe, 0x427},
+ {0x6ff, 0x42a},
+ {0x7a1, 0x386},
+ {0x7a2, 0x388},
+ {0x7a3, 0x389},
+ {0x7a4, 0x38a},
+ {0x7a5, 0x3aa},
+ {0x7a7, 0x38c},
+ {0x7a8, 0x38e},
+ {0x7a9, 0x3ab},
+ {0x7ab, 0x38f},
+ {0x7ae, 0x385},
+ {0x7af, 0x2015},
+ {0x7b1, 0x3ac},
+ {0x7b2, 0x3ad},
+ {0x7b3, 0x3ae},
+ {0x7b4, 0x3af},
+ {0x7b5, 0x3ca},
+ {0x7b6, 0x390},
+ {0x7b7, 0x3cc},
+ {0x7b8, 0x3cd},
+ {0x7b9, 0x3cb},
+ {0x7ba, 0x3b0},
+ {0x7bb, 0x3ce},
+ {0x7c1, 0x391},
+ {0x7c2, 0x392},
+ {0x7c3, 0x393},
+ {0x7c4, 0x394},
+ {0x7c5, 0x395},
+ {0x7c6, 0x396},
+ {0x7c7, 0x397},
+ {0x7c8, 0x398},
+ {0x7c9, 0x399},
+ {0x7ca, 0x39a},
+ {0x7cb, 0x39b},
+ {0x7cc, 0x39c},
+ {0x7cd, 0x39d},
+ {0x7ce, 0x39e},
+ {0x7cf, 0x39f},
+ {0x7d0, 0x3a0},
+ {0x7d1, 0x3a1},
+ {0x7d2, 0x3a3},
+ {0x7d4, 0x3a4},
+ {0x7d5, 0x3a5},
+ {0x7d6, 0x3a6},
+ {0x7d7, 0x3a7},
+ {0x7d8, 0x3a8},
+ {0x7d9, 0x3a9},
+ {0x7e1, 0x3b1},
+ {0x7e2, 0x3b2},
+ {0x7e3, 0x3b3},
+ {0x7e4, 0x3b4},
+ {0x7e5, 0x3b5},
+ {0x7e6, 0x3b6},
+ {0x7e7, 0x3b7},
+ {0x7e8, 0x3b8},
+ {0x7e9, 0x3b9},
+ {0x7ea, 0x3ba},
+ {0x7eb, 0x3bb},
+ {0x7ec, 0x3bc},
+ {0x7ed, 0x3bd},
+ {0x7ee, 0x3be},
+ {0x7ef, 0x3bf},
+ {0x7f0, 0x3c0},
+ {0x7f1, 0x3c1},
+ {0x7f2, 0x3c3},
+ {0x7f3, 0x3c2},
+ {0x7f4, 0x3c4},
+ {0x7f5, 0x3c5},
+ {0x7f6, 0x3c6},
+ {0x7f7, 0x3c7},
+ {0x7f8, 0x3c8},
+ {0x7f9, 0x3c9},
+ {0x8a1, 0x23b7},
+ {0x8a2, 0x250c},
+ {0x8a3, 0x2500},
+ {0x8a4, 0x2320},
+ {0x8a5, 0x2321},
+ {0x8a6, 0x2502},
+ {0x8a7, 0x23a1},
+ {0x8a8, 0x23a3},
+ {0x8a9, 0x23a4},
+ {0x8aa, 0x23a6},
+ {0x8ab, 0x239b},
+ {0x8ac, 0x239d},
+ {0x8ad, 0x239e},
+ {0x8ae, 0x23a0},
+ {0x8af, 0x23a8},
+ {0x8b0, 0x23ac},
+ {0x8bc, 0x2264},
+ {0x8bd, 0x2260},
+ {0x8be, 0x2265},
+ {0x8bf, 0x222b},
+ {0x8c0, 0x2234},
+ {0x8c1, 0x221d},
+ {0x8c2, 0x221e},
+ {0x8c5, 0x2207},
+ {0x8c8, 0x223c},
+ {0x8c9, 0x2243},
+ {0x8cd, 0x21d4},
+ {0x8ce, 0x21d2},
+ {0x8cf, 0x2261},
+ {0x8d6, 0x221a},
+ {0x8da, 0x2282},
+ {0x8db, 0x2283},
+ {0x8dc, 0x2229},
+ {0x8dd, 0x222a},
+ {0x8de, 0x2227},
+ {0x8df, 0x2228},
+ {0x8ef, 0x2202},
+ {0x8f6, 0x192},
+ {0x8fb, 0x2190},
+ {0x8fc, 0x2191},
+ {0x8fd, 0x2192},
+ {0x8fe, 0x2193},
+ {0x9e0, 0x25c6},
+ {0x9e1, 0x2592},
+ {0x9e2, 0x2409},
+ {0x9e3, 0x240c},
+ {0x9e4, 0x240d},
+ {0x9e5, 0x240a},
+ {0x9e8, 0x2424},
+ {0x9e9, 0x240b},
+ {0x9ea, 0x2518},
+ {0x9eb, 0x2510},
+ {0x9ec, 0x250c},
+ {0x9ed, 0x2514},
+ {0x9ee, 0x253c},
+ {0x9ef, 0x23ba},
+ {0x9f0, 0x23bb},
+ {0x9f1, 0x2500},
+ {0x9f2, 0x23bc},
+ {0x9f3, 0x23bd},
+ {0x9f4, 0x251c},
+ {0x9f5, 0x2524},
+ {0x9f6, 0x2534},
+ {0x9f7, 0x252c},
+ {0x9f8, 0x2502},
+ {0xaa1, 0x2003},
+ {0xaa2, 0x2002},
+ {0xaa3, 0x2004},
+ {0xaa4, 0x2005},
+ {0xaa5, 0x2007},
+ {0xaa6, 0x2008},
+ {0xaa7, 0x2009},
+ {0xaa8, 0x200a},
+ {0xaa9, 0x2014},
+ {0xaaa, 0x2013},
+ {0xaae, 0x2026},
+ {0xaaf, 0x2025},
+ {0xab0, 0x2153},
+ {0xab1, 0x2154},
+ {0xab2, 0x2155},
+ {0xab3, 0x2156},
+ {0xab4, 0x2157},
+ {0xab5, 0x2158},
+ {0xab6, 0x2159},
+ {0xab7, 0x215a},
+ {0xab8, 0x2105},
+ {0xabb, 0x2012},
+ {0xabc, 0x2329},
+ {0xabe, 0x232a},
+ {0xac3, 0x215b},
+ {0xac4, 0x215c},
+ {0xac5, 0x215d},
+ {0xac6, 0x215e},
+ {0xac9, 0x2122},
+ {0xaca, 0x2613},
+ {0xacc, 0x25c1},
+ {0xacd, 0x25b7},
+ {0xace, 0x25cb},
+ {0xacf, 0x25af},
+ {0xad0, 0x2018},
+ {0xad1, 0x2019},
+ {0xad2, 0x201c},
+ {0xad3, 0x201d},
+ {0xad4, 0x211e},
+ {0xad6, 0x2032},
+ {0xad7, 0x2033},
+ {0xad9, 0x271d},
+ {0xadb, 0x25ac},
+ {0xadc, 0x25c0},
+ {0xadd, 0x25b6},
+ {0xade, 0x25cf},
+ {0xadf, 0x25ae},
+ {0xae0, 0x25e6},
+ {0xae1, 0x25ab},
+ {0xae2, 0x25ad},
+ {0xae3, 0x25b3},
+ {0xae4, 0x25bd},
+ {0xae5, 0x2606},
+ {0xae6, 0x2022},
+ {0xae7, 0x25aa},
+ {0xae8, 0x25b2},
+ {0xae9, 0x25bc},
+ {0xaea, 0x261c},
+ {0xaeb, 0x261e},
+ {0xaec, 0x2663},
+ {0xaed, 0x2666},
+ {0xaee, 0x2665},
+ {0xaf0, 0x2720},
+ {0xaf1, 0x2020},
+ {0xaf2, 0x2021},
+ {0xaf3, 0x2713},
+ {0xaf4, 0x2717},
+ {0xaf5, 0x266f},
+ {0xaf6, 0x266d},
+ {0xaf7, 0x2642},
+ {0xaf8, 0x2640},
+ {0xaf9, 0x260e},
+ {0xafa, 0x2315},
+ {0xafb, 0x2117},
+ {0xafc, 0x2038},
+ {0xafd, 0x201a},
+ {0xafe, 0x201e},
+ {0xba3, 0x3c},
+ {0xba6, 0x3e},
+ {0xba8, 0x2228},
+ {0xba9, 0x2227},
+ {0xbc0, 0xaf},
+ {0xbc2, 0x22a5},
+ {0xbc3, 0x2229},
+ {0xbc4, 0x230a},
+ {0xbc6, 0x5f},
+ {0xbca, 0x2218},
+ {0xbcc, 0x2395},
+ {0xbce, 0x22a4},
+ {0xbcf, 0x25cb},
+ {0xbd3, 0x2308},
+ {0xbd6, 0x222a},
+ {0xbd8, 0x2283},
+ {0xbda, 0x2282},
+ {0xbdc, 0x22a2},
+ {0xbfc, 0x22a3},
+ {0xcdf, 0x2017},
+ {0xce0, 0x5d0},
+ {0xce1, 0x5d1},
+ {0xce2, 0x5d2},
+ {0xce3, 0x5d3},
+ {0xce4, 0x5d4},
+ {0xce5, 0x5d5},
+ {0xce6, 0x5d6},
+ {0xce7, 0x5d7},
+ {0xce8, 0x5d8},
+ {0xce9, 0x5d9},
+ {0xcea, 0x5da},
+ {0xceb, 0x5db},
+ {0xcec, 0x5dc},
+ {0xced, 0x5dd},
+ {0xcee, 0x5de},
+ {0xcef, 0x5df},
+ {0xcf0, 0x5e0},
+ {0xcf1, 0x5e1},
+ {0xcf2, 0x5e2},
+ {0xcf3, 0x5e3},
+ {0xcf4, 0x5e4},
+ {0xcf5, 0x5e5},
+ {0xcf6, 0x5e6},
+ {0xcf7, 0x5e7},
+ {0xcf8, 0x5e8},
+ {0xcf9, 0x5e9},
+ {0xcfa, 0x5ea},
+ {0xda1, 0xe01},
+ {0xda2, 0xe02},
+ {0xda3, 0xe03},
+ {0xda4, 0xe04},
+ {0xda5, 0xe05},
+ {0xda6, 0xe06},
+ {0xda7, 0xe07},
+ {0xda8, 0xe08},
+ {0xda9, 0xe09},
+ {0xdaa, 0xe0a},
+ {0xdab, 0xe0b},
+ {0xdac, 0xe0c},
+ {0xdad, 0xe0d},
+ {0xdae, 0xe0e},
+ {0xdaf, 0xe0f},
+ {0xdb0, 0xe10},
+ {0xdb1, 0xe11},
+ {0xdb2, 0xe12},
+ {0xdb3, 0xe13},
+ {0xdb4, 0xe14},
+ {0xdb5, 0xe15},
+ {0xdb6, 0xe16},
+ {0xdb7, 0xe17},
+ {0xdb8, 0xe18},
+ {0xdb9, 0xe19},
+ {0xdba, 0xe1a},
+ {0xdbb, 0xe1b},
+ {0xdbc, 0xe1c},
+ {0xdbd, 0xe1d},
+ {0xdbe, 0xe1e},
+ {0xdbf, 0xe1f},
+ {0xdc0, 0xe20},
+ {0xdc1, 0xe21},
+ {0xdc2, 0xe22},
+ {0xdc3, 0xe23},
+ {0xdc4, 0xe24},
+ {0xdc5, 0xe25},
+ {0xdc6, 0xe26},
+ {0xdc7, 0xe27},
+ {0xdc8, 0xe28},
+ {0xdc9, 0xe29},
+ {0xdca, 0xe2a},
+ {0xdcb, 0xe2b},
+ {0xdcc, 0xe2c},
+ {0xdcd, 0xe2d},
+ {0xdce, 0xe2e},
+ {0xdcf, 0xe2f},
+ {0xdd0, 0xe30},
+ {0xdd1, 0xe31},
+ {0xdd2, 0xe32},
+ {0xdd3, 0xe33},
+ {0xdd4, 0xe34},
+ {0xdd5, 0xe35},
+ {0xdd6, 0xe36},
+ {0xdd7, 0xe37},
+ {0xdd8, 0xe38},
+ {0xdd9, 0xe39},
+ {0xdda, 0xe3a},
+ {0xddf, 0xe3f},
+ {0xde0, 0xe40},
+ {0xde1, 0xe41},
+ {0xde2, 0xe42},
+ {0xde3, 0xe43},
+ {0xde4, 0xe44},
+ {0xde5, 0xe45},
+ {0xde6, 0xe46},
+ {0xde7, 0xe47},
+ {0xde8, 0xe48},
+ {0xde9, 0xe49},
+ {0xdea, 0xe4a},
+ {0xdeb, 0xe4b},
+ {0xdec, 0xe4c},
+ {0xded, 0xe4d},
+ {0xdf0, 0xe50},
+ {0xdf1, 0xe51},
+ {0xdf2, 0xe52},
+ {0xdf3, 0xe53},
+ {0xdf4, 0xe54},
+ {0xdf5, 0xe55},
+ {0xdf6, 0xe56},
+ {0xdf7, 0xe57},
+ {0xdf8, 0xe58},
+ {0xdf9, 0xe59},
+ {0xea1, 0x3131},
+ {0xea2, 0x3132},
+ {0xea3, 0x3133},
+ {0xea4, 0x3134},
+ {0xea5, 0x3135},
+ {0xea6, 0x3136},
+ {0xea7, 0x3137},
+ {0xea8, 0x3138},
+ {0xea9, 0x3139},
+ {0xeaa, 0x313a},
+ {0xeab, 0x313b},
+ {0xeac, 0x313c},
+ {0xead, 0x313d},
+ {0xeae, 0x313e},
+ {0xeaf, 0x313f},
+ {0xeb0, 0x3140},
+ {0xeb1, 0x3141},
+ {0xeb2, 0x3142},
+ {0xeb3, 0x3143},
+ {0xeb4, 0x3144},
+ {0xeb5, 0x3145},
+ {0xeb6, 0x3146},
+ {0xeb7, 0x3147},
+ {0xeb8, 0x3148},
+ {0xeb9, 0x3149},
+ {0xeba, 0x314a},
+ {0xebb, 0x314b},
+ {0xebc, 0x314c},
+ {0xebd, 0x314d},
+ {0xebe, 0x314e},
+ {0xebf, 0x314f},
+ {0xec0, 0x3150},
+ {0xec1, 0x3151},
+ {0xec2, 0x3152},
+ {0xec3, 0x3153},
+ {0xec4, 0x3154},
+ {0xec5, 0x3155},
+ {0xec6, 0x3156},
+ {0xec7, 0x3157},
+ {0xec8, 0x3158},
+ {0xec9, 0x3159},
+ {0xeca, 0x315a},
+ {0xecb, 0x315b},
+ {0xecc, 0x315c},
+ {0xecd, 0x315d},
+ {0xece, 0x315e},
+ {0xecf, 0x315f},
+ {0xed0, 0x3160},
+ {0xed1, 0x3161},
+ {0xed2, 0x3162},
+ {0xed3, 0x3163},
+ {0xed4, 0x11a8},
+ {0xed5, 0x11a9},
+ {0xed6, 0x11aa},
+ {0xed7, 0x11ab},
+ {0xed8, 0x11ac},
+ {0xed9, 0x11ad},
+ {0xeda, 0x11ae},
+ {0xedb, 0x11af},
+ {0xedc, 0x11b0},
+ {0xedd, 0x11b1},
+ {0xede, 0x11b2},
+ {0xedf, 0x11b3},
+ {0xee0, 0x11b4},
+ {0xee1, 0x11b5},
+ {0xee2, 0x11b6},
+ {0xee3, 0x11b7},
+ {0xee4, 0x11b8},
+ {0xee5, 0x11b9},
+ {0xee6, 0x11ba},
+ {0xee7, 0x11bb},
+ {0xee8, 0x11bc},
+ {0xee9, 0x11bd},
+ {0xeea, 0x11be},
+ {0xeeb, 0x11bf},
+ {0xeec, 0x11c0},
+ {0xeed, 0x11c1},
+ {0xeee, 0x11c2},
+ {0xeef, 0x316d},
+ {0xef0, 0x3171},
+ {0xef1, 0x3178},
+ {0xef2, 0x317f},
+ {0xef3, 0x3181},
+ {0xef4, 0x3184},
+ {0xef5, 0x3186},
+ {0xef6, 0x318d},
+ {0xef7, 0x318e},
+ {0xef8, 0x11eb},
+ {0xef9, 0x11f0},
+ {0xefa, 0x11f9},
+ {0xeff, 0x20a9},
+ {0x13a4, 0x20ac},
+ {0x13bc, 0x152},
+ {0x13bd, 0x153},
+ {0x13be, 0x178},
+ {0x20a0, 0x20a0},
+ {0x20a1, 0x20a1},
+ {0x20a2, 0x20a2},
+ {0x20a3, 0x20a3},
+ {0x20a4, 0x20a4},
+ {0x20a5, 0x20a5},
+ {0x20a6, 0x20a6},
+ {0x20a7, 0x20a7},
+ {0x20a8, 0x20a8},
+ {0x20aa, 0x20aa},
+ {0x20ab, 0x20ab},
+ {0x20ac, 0x20ac},
+};
+
+int keysym_to_unicode(int keysym)
+{
+ int i, j, k;
+
+ i = -1;
+ j = lenof(keysyms);
+
+ while (j - i >= 2) {
+ k = (j + i) / 2;
+ if (keysyms[k].keysym == keysym)
+ return keysyms[k].unicode;
+ else if (keysyms[k].keysym < keysym)
+ i = k;
+ else
+ j = k;
+ }
+ return -1;
+}
diff --git a/unix/utils/make_dir_and_check_ours.c b/unix/utils/make_dir_and_check_ours.c
new file mode 100644
index 00000000..cab4dc20
--- /dev/null
+++ b/unix/utils/make_dir_and_check_ours.c
@@ -0,0 +1,60 @@
+/*
+ * Create a directory accessible only to us, and then check afterwards
+ * that we really did end up with a directory with the right ownership
+ * and permissions.
+ *
+ * The idea is that this is a directory in which we're about to create
+ * something sensitive, like a listening Unix-domain socket for SSH
+ * connection sharing or an SSH agent. We want to be protected against
+ * somebody else previously having created the directory in a way
+ * that's writable to us, and thus manipulating us into creating the
+ * actual socket in a directory they can see so that they can connect
+ * to it and (say) use our authenticated SSH sessions.
+ *
+ * NOTE: The strategy used in this function is not safe if the enemy
+ * has unrestricted write access to the containing directory. In that
+ * case, they could move our directory out of the way and make a new
+ * one, after this function returns and before we create our socket
+ * (or whatever) inside it.
+ *
+ * But this should be OK for temp directories (which modify the
+ * default world-write behaviour by also setting the 't' bit,
+ * preventing people from renaming or deleting things in there that
+ * they don't own). And of course it's also safe if the directory is
+ * writable only by our _own_ uid.
+ */
+
+#include <errno.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "putty.h"
+
+char *make_dir_and_check_ours(const char *dirname)
+{
+ struct stat st;
+
+ /*
+ * Create the directory. We might have created it before, so
+ * EEXIST is an OK error; but anything else is doom.
+ */
+ if (mkdir(dirname, 0700) < 0 && errno != EEXIST)
+ return dupprintf("%s: mkdir: %s", dirname, strerror(errno));
+
+ /*
+ * Stat the directory and check its ownership and permissions.
+ */
+ if (stat(dirname, &st) < 0)
+ return dupprintf("%s: stat: %s", dirname, strerror(errno));
+ if (st.st_uid != getuid())
+ return dupprintf("%s: directory owned by uid %d, not by us",
+ dirname, st.st_uid);
+ if ((st.st_mode & 077) != 0)
+ return dupprintf("%s: directory has overgenerous permissions %03o"
+ " (expected 700)", dirname, st.st_mode & 0777);
+
+ return NULL;
+}
diff --git a/unix/utils/make_dir_path.c b/unix/utils/make_dir_path.c
new file mode 100644
index 00000000..4d212fe4
--- /dev/null
+++ b/unix/utils/make_dir_path.c
@@ -0,0 +1,39 @@
+/*
+ * Make a path of subdirectories, tolerating EEXIST at every step.
+ */
+
+#include <errno.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "putty.h"
+
+char *make_dir_path(const char *path, mode_t mode)
+{
+ int pos = 0;
+ char *prefix;
+
+ while (1) {
+ pos += strcspn(path + pos, "/");
+
+ if (pos > 0) {
+ prefix = dupprintf("%.*s", pos, path);
+
+ if (mkdir(prefix, mode) < 0 && errno != EEXIST) {
+ char *ret = dupprintf("%s: mkdir: %s",
+ prefix, strerror(errno));
+ sfree(prefix);
+ return ret;
+ }
+
+ sfree(prefix);
+ }
+
+ if (!path[pos])
+ return NULL;
+ pos += strspn(path + pos, "/");
+ }
+}
diff --git a/unix/utils/make_spr_sw_abort_errno.c b/unix/utils/make_spr_sw_abort_errno.c
new file mode 100644
index 00000000..57f58c2f
--- /dev/null
+++ b/unix/utils/make_spr_sw_abort_errno.c
@@ -0,0 +1,22 @@
+/*
+ * Constructor function for a SeatPromptResult of the 'software abort'
+ * category, whose error message includes the translation of an OS
+ * error code.
+ */
+
+#include "putty.h"
+
+static void spr_errno_errfn(SeatPromptResult spr, BinarySink *bs)
+{
+ put_fmt(bs, "%s: %s", spr.errdata_lit, strerror(spr.errdata_u));
+}
+
+SeatPromptResult make_spr_sw_abort_errno(const char *prefix, int errno_value)
+{
+ SeatPromptResult spr;
+ spr.kind = SPRK_SW_ABORT;
+ spr.errfn = spr_errno_errfn;
+ spr.errdata_lit = prefix;
+ spr.errdata_u = errno_value;
+ return spr;
+}
diff --git a/unix/utils/nonblock.c b/unix/utils/nonblock.c
new file mode 100644
index 00000000..cece206c
--- /dev/null
+++ b/unix/utils/nonblock.c
@@ -0,0 +1,55 @@
+/*
+ * Set and clear the O_NONBLOCK fcntl option on an open file.
+ *
+ * We don't realistically expect these operations to fail (the most
+ * plausible error condition is EBADF, but we always believe ourselves
+ * to be passing a valid fd so even that's an assertion-fail sort of
+ * response), so we don't make any effort to return sensible error
+ * codes to the caller - we just log to standard error and die
+ * unceremoniously.
+ *
+ * Returns the previous state of O_NONBLOCK.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <fcntl.h>
+
+#include "putty.h"
+
+bool nonblock(int fd)
+{
+ int fdflags;
+
+ fdflags = fcntl(fd, F_GETFL);
+ if (fdflags < 0) {
+ fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno));
+ exit(1);
+ }
+ if (fcntl(fd, F_SETFL, fdflags | O_NONBLOCK) < 0) {
+ fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno));
+ exit(1);
+ }
+
+ return fdflags & O_NONBLOCK;
+}
+
+bool no_nonblock(int fd)
+{
+ int fdflags;
+
+ fdflags = fcntl(fd, F_GETFL);
+ if (fdflags < 0) {
+ fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno));
+ exit(1);
+ }
+ if (fcntl(fd, F_SETFL, fdflags & ~O_NONBLOCK) < 0) {
+ fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno));
+ exit(1);
+ }
+
+ return fdflags & O_NONBLOCK;
+}
diff --git a/unix/utils/open_for_write_would_lose_data.c b/unix/utils/open_for_write_would_lose_data.c
new file mode 100644
index 00000000..46e695fd
--- /dev/null
+++ b/unix/utils/open_for_write_would_lose_data.c
@@ -0,0 +1,44 @@
+/*
+ * Unix implementation of open_for_write_would_lose_data().
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "putty.h"
+
+bool open_for_write_would_lose_data(const Filename *fn)
+{
+ struct stat st;
+
+ if (stat(fn->path, &st) < 0) {
+ /*
+ * If the file doesn't even exist, we obviously want to return
+ * false. If we failed to stat it for any other reason,
+ * ignoring the precise error code and returning false still
+ * doesn't seem too unreasonable, because then we'll try to
+ * open the file for writing and report _that_ error, which is
+ * likely to be more to the point.
+ */
+ return false;
+ }
+
+ /*
+ * OK, something exists at this pathname and we've found out
+ * something about it. But an open-for-write will only
+ * destructively truncate it if it's a regular file with nonzero
+ * size. If it's empty, or some other kind of special thing like a
+ * character device (e.g. /dev/tty) or a named pipe, then opening
+ * it for write is already non-destructive and it's pointless and
+ * annoying to warn about it just because the same file can be
+ * opened for reading. (Indeed, if it's a named pipe, opening it
+ * for reading actually _causes inconvenience_ in its own right,
+ * even before the question of whether it gives misleading
+ * information.)
+ */
+ if (S_ISREG(st.st_mode) && st.st_size > 0) {
+ return true;
+ }
+
+ return false;
+}
diff --git a/unix/utils/our_dialog.c b/unix/utils/our_dialog.c
new file mode 100644
index 00000000..0ca8a7d2
--- /dev/null
+++ b/unix/utils/our_dialog.c
@@ -0,0 +1,135 @@
+/*
+ * Functions to arrange controls in a basically dialog-like window.
+ *
+ * The best method for doing this has varied wildly with versions of
+ * GTK, hence the set of wrapper functions here.
+ *
+ * In GTK 1, a GtkDialog has an 'action_area' at the bottom, which is
+ * a GtkHBox which stretches to cover the full width of the dialog. So
+ * we can either add buttons or other widgets to that box directly, or
+ * alternatively we can fill the hbox with some layout class of our
+ * own such as a Columns widget.
+ *
+ * In GTK 2, the action area has become a GtkHButtonBox, and its
+ * layout behaviour seems to be different and not what we want. So
+ * instead we abandon the dialog's action area completely: we
+ * gtk_widget_hide() it in the below code, and we also call
+ * gtk_dialog_set_has_separator() to remove the separator above it. We
+ * then insert our own action area into the end of the dialog's main
+ * vbox, and add our own separator above that.
+ *
+ * In GTK 3, we typically don't even want to use GtkDialog at all,
+ * because GTK 3 has become a lot more restrictive about what you can
+ * sensibly use GtkDialog for - it deprecates direct access to the
+ * action area in favour of making you provide nothing but
+ * dialog-ending buttons in the form of (text, response code) pairs,
+ * so you can't put any other kind of control in there, or fiddle with
+ * alignment and positioning, or even have a button that _doesn't_ end
+ * the dialog (e.g. 'View Licence' in our About box). So instead of
+ * GtkDialog, we use a straight-up GtkWindow and have it contain a
+ * vbox as its (unique) child widget; and we implement the action area
+ * by adding a separator and another widget at the bottom of that
+ * vbox.
+ */
+
+#include <gtk/gtk.h>
+#include "putty.h"
+#include "gtkcompat.h"
+#include "gtkmisc.h"
+
+GtkWidget *our_dialog_new(void)
+{
+#if GTK_CHECK_VERSION(3,0,0)
+ /*
+ * See comment in our_dialog_set_action_area(): in GTK 3, we use
+ * GtkWindow in place of GtkDialog for most purposes.
+ */
+ GtkWidget *w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
+ gtk_container_add(GTK_CONTAINER(w), vbox);
+ gtk_widget_show(vbox);
+ return w;
+#else
+ return gtk_dialog_new();
+#endif
+}
+
+void our_dialog_set_action_area(GtkWindow *dlg, GtkWidget *w)
+{
+#if !GTK_CHECK_VERSION(2,0,0)
+
+ gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area),
+ w, true, true, 0);
+
+#elif !GTK_CHECK_VERSION(3,0,0)
+
+ GtkWidget *align;
+ align = gtk_alignment_new(0, 0, 1, 1);
+ gtk_container_add(GTK_CONTAINER(align), w);
+ /*
+ * The purpose of this GtkAlignment is to provide padding
+ * around the buttons. The padding we use is twice the padding
+ * used in our GtkColumns, because we nest two GtkColumns most
+ * of the time (one separating the tree view from the main
+ * controls, and another for the main controls themselves).
+ */
+#if GTK_CHECK_VERSION(2,4,0)
+ gtk_alignment_set_padding(GTK_ALIGNMENT(align), 8, 8, 8, 8);
+#endif
+ gtk_widget_show(align);
+ gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))),
+ align, false, true, 0);
+
+ w = gtk_hseparator_new();
+ gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))),
+ w, false, true, 0);
+ gtk_widget_show(w);
+ gtk_widget_hide(gtk_dialog_get_action_area(GTK_DIALOG(dlg)));
+ g_object_set(G_OBJECT(dlg), "has-separator", true, (const char *)NULL);
+
+#else /* GTK 3 */
+
+ /* GtkWindow is a GtkBin, hence contains exactly one child, which
+ * here we always expect to be a vbox */
+ GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(dlg)));
+ GtkWidget *sep;
+
+ g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL);
+ gtk_box_pack_end(vbox, w, false, true, 0);
+
+ sep = gtk_hseparator_new();
+ gtk_box_pack_end(vbox, sep, false, true, 0);
+ gtk_widget_show(sep);
+
+#endif
+}
+
+GtkBox *our_dialog_make_action_hbox(GtkWindow *dlg)
+{
+#if GTK_CHECK_VERSION(3,0,0)
+ GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ our_dialog_set_action_area(dlg, hbox);
+ g_object_set(G_OBJECT(hbox), "margin", 0, (const char *)NULL);
+ g_object_set(G_OBJECT(hbox), "spacing", 8, (const char *)NULL);
+ gtk_widget_show(hbox);
+ return GTK_BOX(hbox);
+#else /* not GTK 3 */
+ return GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(dlg)));
+#endif
+}
+
+void our_dialog_add_to_content_area(GtkWindow *dlg, GtkWidget *w,
+ bool expand, bool fill, guint padding)
+{
+#if GTK_CHECK_VERSION(3,0,0)
+ /* GtkWindow is a GtkBin, hence contains exactly one child, which
+ * here we always expect to be a vbox */
+ GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(dlg)));
+
+ gtk_box_pack_start(vbox, w, expand, fill, padding);
+#else
+ gtk_box_pack_start(
+ GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))),
+ w, expand, fill, padding);
+#endif
+}
diff --git a/unix/utils/pgp_fingerprints.c b/unix/utils/pgp_fingerprints.c
new file mode 100644
index 00000000..9fb6bf42
--- /dev/null
+++ b/unix/utils/pgp_fingerprints.c
@@ -0,0 +1,23 @@
+/*
+ * Display the fingerprints of the PGP Master Keys to the user.
+ *
+ * (This is in its own file rather than in console.c, because it's
+ * appropriate even for Unix GUI apps.)
+ */
+
+#include "putty.h"
+
+void pgp_fingerprints(void)
+{
+ fputs("These are the fingerprints of the PuTTY PGP Master Keys. They can\n"
+ "be used to establish a trust path from this executable to another\n"
+ "one. See the manual for more information.\n"
+ "(Note: these fingerprints have nothing to do with SSH!)\n"
+ "\n"
+ "PuTTY Master Key as of " PGP_MASTER_KEY_YEAR
+ " (" PGP_MASTER_KEY_DETAILS "):\n"
+ " " PGP_MASTER_KEY_FP "\n\n"
+ "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR
+ ", " PGP_PREV_MASTER_KEY_DETAILS "):\n"
+ " " PGP_PREV_MASTER_KEY_FP "\n", stdout);
+}
diff --git a/unix/utils/pollwrap.c b/unix/utils/pollwrap.c
new file mode 100644
index 00000000..51121e36
--- /dev/null
+++ b/unix/utils/pollwrap.c
@@ -0,0 +1,190 @@
+/*
+ * Wrapper system around poll() that lets me treat it more or less
+ * like select(), but avoiding the inherent limitation of select()
+ * that it can't handle the full range of fds that are capable of
+ * existing.
+ *
+ * The pollwrapper structure contains the 'struct pollfd' array passed
+ * to poll() itself, and also a tree234 that maps each fd to its
+ * location in the list, which makes it convenient to add or remove
+ * individual fds from the system or change what events you're
+ * watching for on them. So the API is _shaped_ basically like select,
+ * even if none of the details are identical: from outside this
+ * module, a pollwrapper can be used wherever you'd otherwise have had
+ * an fd_set.
+ *
+ * Also, this module translate between the simple select r/w/x
+ * classification and the richer poll flags. We have to stick to r/w/x
+ * in this code base, because it ports to other systems where that's
+ * all you get.
+ */
+
+/* On some systems this is needed to get poll.h to define eg.. POLLRDNORM */
+#define _XOPEN_SOURCE
+
+#include <poll.h>
+
+#include "putty.h"
+#include "tree234.h"
+
+struct pollwrapper {
+ struct pollfd *fds;
+ size_t nfd, fdsize;
+ tree234 *fdtopos;
+};
+
+typedef struct pollwrap_fdtopos pollwrap_fdtopos;
+struct pollwrap_fdtopos {
+ int fd;
+ size_t pos;
+};
+
+static int pollwrap_fd_cmp(void *av, void *bv)
+{
+ pollwrap_fdtopos *a = (pollwrap_fdtopos *)av;
+ pollwrap_fdtopos *b = (pollwrap_fdtopos *)bv;
+ return a->fd < b->fd ? -1 : a->fd > b->fd ? +1 : 0;
+}
+
+pollwrapper *pollwrap_new(void)
+{
+ pollwrapper *pw = snew(pollwrapper);
+ pw->fdsize = 16;
+ pw->nfd = 0;
+ pw->fds = snewn(pw->fdsize, struct pollfd);
+ pw->fdtopos = newtree234(pollwrap_fd_cmp);
+ return pw;
+}
+
+void pollwrap_free(pollwrapper *pw)
+{
+ pollwrap_clear(pw);
+ freetree234(pw->fdtopos);
+ sfree(pw->fds);
+ sfree(pw);
+}
+
+void pollwrap_clear(pollwrapper *pw)
+{
+ pw->nfd = 0;
+ for (pollwrap_fdtopos *f2p;
+ (f2p = delpos234(pw->fdtopos, 0)) != NULL ;)
+ sfree(f2p);
+}
+
+void pollwrap_add_fd_events(pollwrapper *pw, int fd, int events)
+{
+ pollwrap_fdtopos *f2p, f2p_find;
+
+ assert(fd >= 0);
+
+ f2p_find.fd = fd;
+ f2p = find234(pw->fdtopos, &f2p_find, NULL);
+ if (!f2p) {
+ sgrowarray(pw->fds, pw->fdsize, pw->nfd);
+ size_t index = pw->nfd++;
+ pw->fds[index].fd = fd;
+ pw->fds[index].events = pw->fds[index].revents = 0;
+
+ f2p = snew(pollwrap_fdtopos);
+ f2p->fd = fd;
+ f2p->pos = index;
+ pollwrap_fdtopos *added = add234(pw->fdtopos, f2p);
+ assert(added == f2p);
+ }
+
+ pw->fds[f2p->pos].events |= events;
+}
+
+/* Omit any of the POLL{RD,WR}{NORM,BAND} flag values that are still
+ * not defined by poll.h, just in case */
+#ifndef POLLRDNORM
+#define POLLRDNORM 0
+#endif
+#ifndef POLLRDBAND
+#define POLLRDBAND 0
+#endif
+#ifndef POLLWRNORM
+#define POLLWRNORM 0
+#endif
+#ifndef POLLWRBAND
+#define POLLWRBAND 0
+#endif
+
+#define SELECT_R_IN (POLLIN | POLLRDNORM | POLLRDBAND)
+#define SELECT_W_IN (POLLOUT | POLLWRNORM | POLLWRBAND)
+#define SELECT_X_IN (POLLPRI)
+
+#define SELECT_R_OUT (SELECT_R_IN | POLLERR | POLLHUP)
+#define SELECT_W_OUT (SELECT_W_IN | POLLERR)
+#define SELECT_X_OUT (SELECT_X_IN)
+
+void pollwrap_add_fd_rwx(pollwrapper *pw, int fd, int rwx)
+{
+ int events = 0;
+ if (rwx & SELECT_R)
+ events |= SELECT_R_IN;
+ if (rwx & SELECT_W)
+ events |= SELECT_W_IN;
+ if (rwx & SELECT_X)
+ events |= SELECT_X_IN;
+ pollwrap_add_fd_events(pw, fd, events);
+}
+
+int pollwrap_poll_instant(pollwrapper *pw)
+{
+ return poll(pw->fds, pw->nfd, 0);
+}
+
+int pollwrap_poll_endless(pollwrapper *pw)
+{
+ return poll(pw->fds, pw->nfd, -1);
+}
+
+int pollwrap_poll_timeout(pollwrapper *pw, int milliseconds)
+{
+ assert(milliseconds >= 0);
+ return poll(pw->fds, pw->nfd, milliseconds);
+}
+
+static void pollwrap_get_fd_events_revents(pollwrapper *pw, int fd,
+ int *events_p, int *revents_p)
+{
+ pollwrap_fdtopos *f2p, f2p_find;
+ int events = 0, revents = 0;
+
+ assert(fd >= 0);
+
+ f2p_find.fd = fd;
+ f2p = find234(pw->fdtopos, &f2p_find, NULL);
+ if (f2p) {
+ events = pw->fds[f2p->pos].events;
+ revents = pw->fds[f2p->pos].revents;
+ }
+
+ if (events_p)
+ *events_p = events;
+ if (revents_p)
+ *revents_p = revents;
+}
+
+int pollwrap_get_fd_events(pollwrapper *pw, int fd)
+{
+ int revents;
+ pollwrap_get_fd_events_revents(pw, fd, NULL, &revents);
+ return revents;
+}
+
+int pollwrap_get_fd_rwx(pollwrapper *pw, int fd)
+{
+ int events, revents;
+ pollwrap_get_fd_events_revents(pw, fd, &events, &revents);
+ int rwx = 0;
+ if ((events & POLLIN) && (revents & SELECT_R_OUT))
+ rwx |= SELECT_R;
+ if ((events & POLLOUT) && (revents & SELECT_W_OUT))
+ rwx |= SELECT_W;
+ if ((events & POLLPRI) && (revents & SELECT_X_OUT))
+ rwx |= SELECT_X;
+ return rwx;
+}
diff --git a/unix/utils/signal.c b/unix/utils/signal.c
new file mode 100644
index 00000000..a56fe6be
--- /dev/null
+++ b/unix/utils/signal.c
@@ -0,0 +1,30 @@
+/*
+ * PuTTY's wrapper on signal(2).
+ *
+ * Calling signal() is non-portable, as it varies in meaning between
+ * platforms and depending on feature macros, and has stupid semantics
+ * at least some of the time.
+ *
+ * This function provides the same interface as the libc function, but
+ * provides consistent semantics. It assumes POSIX semantics for
+ * sigaction() (so you might need to do some more work if you port to
+ * something ancient like SunOS 4).
+ */
+
+#include <signal.h>
+
+#include "defs.h"
+
+void (*putty_signal(int sig, void (*func)(int)))(int)
+{
+ struct sigaction sa;
+ struct sigaction old;
+
+ sa.sa_handler = func;
+ if(sigemptyset(&sa.sa_mask) < 0)
+ return SIG_ERR;
+ sa.sa_flags = SA_RESTART;
+ if(sigaction(sig, &sa, &old) < 0)
+ return SIG_ERR;
+ return old.sa_handler;
+}
diff --git a/unix/utils/string_width.c b/unix/utils/string_width.c
new file mode 100644
index 00000000..c944308f
--- /dev/null
+++ b/unix/utils/string_width.c
@@ -0,0 +1,18 @@
+/*
+ * Return the width of a string in the font used in GTK controls. Used
+ * as a means of picking a sensible size for dialog boxes and pieces
+ * of them, in a way that should adapt sensibly to changes in font and
+ * resolution.
+ */
+
+#include <gtk/gtk.h>
+#include "putty.h"
+#include "gtkcompat.h"
+#include "gtkmisc.h"
+
+int string_width(const char *text)
+{
+ int ret;
+ get_label_text_dimensions(text, &ret, NULL);
+ return ret;
+}
diff --git a/unix/utils/x11_ignore_error.c b/unix/utils/x11_ignore_error.c
new file mode 100644
index 00000000..a4165ab5
--- /dev/null
+++ b/unix/utils/x11_ignore_error.c
@@ -0,0 +1,88 @@
+/*
+ * Error handling mechanism which permits us to ignore specific X11
+ * errors from particular requests. We maintain a list of upcoming
+ * potential error events that we want to not treat as fatal errors.
+ */
+
+#include <ctype.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "putty.h"
+
+#ifndef NOT_X_WINDOWS
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+#include "x11misc.h"
+
+static int (*orig_x11_error_handler)(Display *thisdisp, XErrorEvent *err);
+
+struct x11_err_to_ignore {
+ Display *display;
+ unsigned char error_code;
+ unsigned long serial;
+};
+
+static struct x11_err_to_ignore *errs;
+static size_t nerrs, errsize;
+
+static int x11_error_handler(Display *thisdisp, XErrorEvent *err)
+{
+ for (size_t i = 0; i < nerrs; i++) {
+ if (thisdisp == errs[i].display &&
+ err->serial == errs[i].serial &&
+ err->error_code == errs[i].error_code) {
+ /* Ok, this is an error we're happy to ignore */
+ return 0;
+ }
+ }
+
+ return (*orig_x11_error_handler)(thisdisp, err);
+}
+
+void x11_ignore_error(Display *disp, unsigned char errcode)
+{
+ /*
+ * Install our error handler, if we haven't already.
+ */
+ if (!orig_x11_error_handler)
+ orig_x11_error_handler = XSetErrorHandler(x11_error_handler);
+
+ /*
+ * This is as good a moment as any to winnow the ignore list based
+ * on requests we know to have been processed.
+ */
+ {
+ unsigned long last = LastKnownRequestProcessed(disp);
+ size_t i, j;
+ for (i = j = 0; i < nerrs; i++) {
+ if (errs[i].display == disp && errs[i].serial <= last)
+ continue;
+ errs[j++] = errs[i];
+ }
+ nerrs = j;
+ }
+
+ sgrowarray(errs, errsize, nerrs);
+ errs[nerrs].display = disp;
+ errs[nerrs].error_code = errcode;
+ errs[nerrs].serial = NextRequest(disp);
+ nerrs++;
+}
+
+#else /* NOT_X_WINDOWS */
+
+/*
+ * Include _something_ in this file to prevent an annoying compiler
+ * warning, and to avoid having to condition out this file in
+ * CMakeLists. It's in a library, so this variable shouldn't end up in
+ * any actual program, because nothing will refer to it.
+ */
+const int x11_ignore_error_dummy_variable = 0;
+
+#endif /* NOT_X_WINDOWS */
diff --git a/unix/ux_x11.c b/unix/ux_x11.c
deleted file mode 100644
index 7a0c2218..00000000
--- a/unix/ux_x11.c
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * ux_x11.c: fetch local auth data for X forwarding.
- */
-
-#include <ctype.h>
-#include <unistd.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "network.h"
-
-void platform_get_x11_auth(struct X11Display *disp, Conf *conf)
-{
- char *xauthfile;
- bool needs_free;
-
- /*
- * Find the .Xauthority file.
- */
- needs_free = false;
- xauthfile = getenv("XAUTHORITY");
- if (!xauthfile) {
- xauthfile = getenv("HOME");
- if (xauthfile) {
- xauthfile = dupcat(xauthfile, "/.Xauthority");
- needs_free = true;
- }
- }
-
- if (xauthfile) {
- x11_get_auth_from_authfile(disp, xauthfile);
- if (needs_free)
- sfree(xauthfile);
- }
-}
-
-const bool platform_uses_x11_unix_by_default = true;
-
-int platform_make_x11_server(Plug *plug, const char *progname, int mindisp,
- const char *screen_number_suffix,
- ptrlen authproto, ptrlen authdata,
- Socket **sockets, Conf *conf)
-{
- char *tmpdir;
- char *authfilename = NULL;
- strbuf *authfiledata = NULL;
- char *unix_path = NULL;
-
- SockAddr *a_tcp = NULL, *a_unix = NULL;
-
- int authfd;
- FILE *authfp;
-
- int displayno;
-
- authfiledata = strbuf_new_nm();
-
- int nsockets = 0;
-
- /*
- * Look for a free TCP port to run our server on.
- */
- for (displayno = mindisp;; displayno++) {
- const char *err;
- int tcp_port = displayno + 6000;
- int addrtype = ADDRTYPE_IPV4;
-
- sockets[nsockets] = new_listener(
- NULL, tcp_port, plug, false, conf, addrtype);
-
- err = sk_socket_error(sockets[nsockets]);
- if (!err) {
- char *hostname = get_hostname();
- if (hostname) {
- char *canonicalname = NULL;
- a_tcp = sk_namelookup(hostname, &canonicalname, addrtype);
- sfree(canonicalname);
- }
- sfree(hostname);
- nsockets++;
- break; /* success! */
- } else {
- sk_close(sockets[nsockets]);
- }
-
- if (!strcmp(err, strerror(EADDRINUSE))) /* yuck! */
- goto out;
- }
-
- if (a_tcp) {
- x11_format_auth_for_authfile(
- BinarySink_UPCAST(authfiledata),
- a_tcp, displayno, authproto, authdata);
- }
-
- /*
- * Try to establish the Unix-domain analogue. That may or may not
- * work - file permissions in /tmp may prevent it, for example -
- * but it's worth a try, and we don't consider it a fatal error if
- * it doesn't work.
- */
- unix_path = dupprintf("/tmp/.X11-unix/X%d", displayno);
- a_unix = unix_sock_addr(unix_path);
-
- sockets[nsockets] = new_unix_listener(a_unix, plug);
- if (!sk_socket_error(sockets[nsockets])) {
- x11_format_auth_for_authfile(
- BinarySink_UPCAST(authfiledata),
- a_unix, displayno, authproto, authdata);
- nsockets++;
- } else {
- sk_close(sockets[nsockets]);
- sfree(unix_path);
- unix_path = NULL;
- }
-
- /*
- * Decide where the authority data will be written.
- */
-
- tmpdir = getenv("TMPDIR");
- if (!tmpdir || !*tmpdir)
- tmpdir = "/tmp";
-
- authfilename = dupcat(tmpdir, "/", progname, "-Xauthority-XXXXXX");
-
- {
- int oldumask = umask(077);
- authfd = mkstemp(authfilename);
- umask(oldumask);
- }
- if (authfd < 0) {
- while (nsockets-- > 0)
- sk_close(sockets[nsockets]);
- goto out;
- }
-
- /*
- * Spawn a subprocess which will try to reliably delete our
- * auth file when we terminate, in case we die unexpectedly.
- */
- {
- int cleanup_pipe[2];
- pid_t pid;
-
- /* Don't worry if pipe or fork fails; it's not _that_ critical. */
- if (!pipe(cleanup_pipe)) {
- if ((pid = fork()) == 0) {
- int buf[1024];
- /*
- * Our parent process holds the writing end of
- * this pipe, and writes nothing to it. Hence,
- * we expect read() to return EOF as soon as
- * that process terminates.
- */
-
- close(0);
- close(1);
- close(2);
-
- setpgid(0, 0);
- close(cleanup_pipe[1]);
- close(authfd);
- while (read(cleanup_pipe[0], buf, sizeof(buf)) > 0);
- unlink(authfilename);
- if (unix_path)
- unlink(unix_path);
- _exit(0);
- } else if (pid < 0) {
- close(cleanup_pipe[0]);
- close(cleanup_pipe[1]);
- } else {
- close(cleanup_pipe[0]);
- cloexec(cleanup_pipe[1]);
- }
- }
- }
-
- authfp = fdopen(authfd, "wb");
- fwrite(authfiledata->u, 1, authfiledata->len, authfp);
- fclose(authfp);
-
- {
- char *display = dupprintf(":%d%s", displayno, screen_number_suffix);
- conf_set_str_str(conf, CONF_environmt, "DISPLAY", display);
- sfree(display);
- }
- conf_set_str_str(conf, CONF_environmt, "XAUTHORITY", authfilename);
-
- /*
- * FIXME: return at least the DISPLAY and XAUTHORITY env settings,
- * and perhaps also the display number
- */
-
- out:
- if (a_tcp)
- sk_addr_free(a_tcp);
- /* a_unix doesn't need freeing, because new_unix_listener took it over */
- sfree(authfilename);
- strbuf_free(authfiledata);
- sfree(unix_path);
- return nsockets;
-}
diff --git a/unix/uxagentc.c b/unix/uxagentc.c
deleted file mode 100644
index e4d671d2..00000000
--- a/unix/uxagentc.c
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * SSH agent client code.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <unistd.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <fcntl.h>
-
-#include "putty.h"
-#include "misc.h"
-#include "tree234.h"
-#include "puttymem.h"
-
-bool agent_exists(void)
-{
- const char *p = getenv("SSH_AUTH_SOCK");
- if (p && *p)
- return true;
- return false;
-}
-
-static tree234 *agent_pending_queries;
-struct agent_pending_query {
- int fd;
- char *retbuf;
- char sizebuf[4];
- int retsize, retlen;
- void (*callback)(void *, void *, int);
- void *callback_ctx;
-};
-static int agent_conncmp(void *av, void *bv)
-{
- agent_pending_query *a = (agent_pending_query *) av;
- agent_pending_query *b = (agent_pending_query *) bv;
- if (a->fd < b->fd)
- return -1;
- if (a->fd > b->fd)
- return +1;
- return 0;
-}
-static int agent_connfind(void *av, void *bv)
-{
- int afd = *(int *) av;
- agent_pending_query *b = (agent_pending_query *) bv;
- if (afd < b->fd)
- return -1;
- if (afd > b->fd)
- return +1;
- return 0;
-}
-
-/*
- * Attempt to read from an agent socket fd. Returns false if the
- * expected response is as yet incomplete; returns true if it's either
- * complete (conn->retbuf non-NULL and filled with something useful)
- * or has failed totally (conn->retbuf is NULL).
- */
-static bool agent_try_read(agent_pending_query *conn)
-{
- int ret;
-
- ret = read(conn->fd, conn->retbuf+conn->retlen,
- conn->retsize-conn->retlen);
- if (ret <= 0) {
- if (conn->retbuf != conn->sizebuf) sfree(conn->retbuf);
- conn->retbuf = NULL;
- conn->retlen = 0;
- return true;
- }
- conn->retlen += ret;
- if (conn->retsize == 4 && conn->retlen == 4) {
- conn->retsize = toint(GET_32BIT_MSB_FIRST(conn->retbuf) + 4);
- if (conn->retsize <= 0) {
- conn->retbuf = NULL;
- conn->retlen = 0;
- return true; /* way too large */
- }
- assert(conn->retbuf == conn->sizebuf);
- conn->retbuf = snewn(conn->retsize, char);
- memcpy(conn->retbuf, conn->sizebuf, 4);
- }
-
- if (conn->retlen < conn->retsize)
- return false; /* more data to come */
-
- return true;
-}
-
-void agent_cancel_query(agent_pending_query *conn)
-{
- uxsel_del(conn->fd);
- close(conn->fd);
- del234(agent_pending_queries, conn);
- if (conn->retbuf && conn->retbuf != conn->sizebuf)
- sfree(conn->retbuf);
- sfree(conn);
-}
-
-static void agent_select_result(int fd, int event)
-{
- agent_pending_query *conn;
-
- assert(event == SELECT_R); /* not selecting for anything but R */
-
- conn = find234(agent_pending_queries, &fd, agent_connfind);
- if (!conn) {
- uxsel_del(fd);
- return;
- }
-
- if (!agent_try_read(conn))
- return; /* more data to come */
-
- /*
- * We have now completed the agent query. Do the callback.
- */
- conn->callback(conn->callback_ctx, conn->retbuf, conn->retlen);
- /* Null out conn->retbuf, since ownership of that buffer has
- * passed to the callback. */
- conn->retbuf = NULL;
- agent_cancel_query(conn);
-}
-
-static const char *agent_socket_path(void)
-{
- return getenv("SSH_AUTH_SOCK");
-}
-
-Socket *agent_connect(Plug *plug)
-{
- const char *path = agent_socket_path();
- if (!path)
- return new_error_socket_fmt(plug, "SSH_AUTH_SOCK not set");
- return sk_new(unix_sock_addr(path), 0, false, false, false, false, plug);
-}
-
-agent_pending_query *agent_query(
- strbuf *query, void **out, int *outlen,
- void (*callback)(void *, void *, int), void *callback_ctx)
-{
- const char *name;
- int sock;
- struct sockaddr_un addr;
- int done;
- agent_pending_query *conn;
-
- name = agent_socket_path();
- if (!name || strlen(name) >= sizeof(addr.sun_path))
- goto failure;
-
- sock = socket(PF_UNIX, SOCK_STREAM, 0);
- if (sock < 0) {
- perror("socket(PF_UNIX)");
- exit(1);
- }
-
- cloexec(sock);
-
- addr.sun_family = AF_UNIX;
- strcpy(addr.sun_path, name);
- if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
- close(sock);
- goto failure;
- }
-
- strbuf_finalise_agent_query(query);
-
- for (done = 0; done < query->len ;) {
- int ret = write(sock, query->s + done,
- query->len - done);
- if (ret <= 0) {
- close(sock);
- goto failure;
- }
- done += ret;
- }
-
- conn = snew(agent_pending_query);
- conn->fd = sock;
- conn->retbuf = conn->sizebuf;
- conn->retsize = 4;
- conn->retlen = 0;
- conn->callback = callback;
- conn->callback_ctx = callback_ctx;
-
- if (!callback) {
- /*
- * Bodge to permit making deliberately synchronous agent
- * requests. Used by Unix Pageant in command-line client mode,
- * which is legit because it really is true that no other part
- * of the program is trying to get anything useful done
- * simultaneously. But this special case shouldn't be used in
- * any more general program.
- */
- no_nonblock(conn->fd);
- while (!agent_try_read(conn))
- /* empty loop body */;
-
- *out = conn->retbuf;
- *outlen = conn->retlen;
- sfree(conn);
- return NULL;
- }
-
- /*
- * Otherwise do it properly: add conn to the tree of agent
- * connections currently in flight, return 0 to indicate that the
- * response hasn't been received yet, and call the callback when
- * select_result comes back to us.
- */
- if (!agent_pending_queries)
- agent_pending_queries = newtree234(agent_conncmp);
- add234(agent_pending_queries, conn);
-
- uxsel_set(sock, SELECT_R, agent_select_result);
- return conn;
-
- failure:
- *out = NULL;
- *outlen = 0;
- return NULL;
-}
diff --git a/unix/uxagentsock.c b/unix/uxagentsock.c
deleted file mode 100644
index 7e6187c6..00000000
--- a/unix/uxagentsock.c
+++ /dev/null
@@ -1,91 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <assert.h>
-#include <signal.h>
-#include <ctype.h>
-
-#include <unistd.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "misc.h"
-#include "pageant.h"
-
-Socket *platform_make_agent_socket(
- Plug *plug, const char *dirprefix, char **error, char **name)
-{
- char *username, *socketdir, *socketname, *errw;
- const char *err;
- Socket *sock;
-
- *name = NULL;
-
- username = get_username();
- socketdir = dupprintf("%s.%s", dirprefix, username);
- sfree(username);
-
- assert(*socketdir == '/');
- if ((errw = make_dir_and_check_ours(socketdir)) != NULL) {
- *error = dupprintf("%s: %s\n", socketdir, errw);
- sfree(errw);
- sfree(socketdir);
- return NULL;
- }
-
- socketname = dupprintf("%s/pageant.%d", socketdir, (int)getpid());
- sock = new_unix_listener(unix_sock_addr(socketname), plug);
- if ((err = sk_socket_error(sock)) != NULL) {
- *error = dupprintf("%s: %s\n", socketname, err);
- sk_close(sock);
- sfree(socketname);
- rmdir(socketdir);
- sfree(socketdir);
- return NULL;
- }
-
- /*
- * Spawn a subprocess which will try to reliably delete our socket
- * and its containing directory when we terminate, in case we die
- * unexpectedly.
- */
- {
- int cleanup_pipe[2];
- pid_t pid;
-
- /* Don't worry if pipe or fork fails; it's not _that_ critical. */
- if (!pipe(cleanup_pipe)) {
- if ((pid = fork()) == 0) {
- int buf[1024];
- /*
- * Our parent process holds the writing end of
- * this pipe, and writes nothing to it. Hence,
- * we expect read() to return EOF as soon as
- * that process terminates.
- */
-
- close(0);
- close(1);
- close(2);
-
- setpgid(0, 0);
- close(cleanup_pipe[1]);
- while (read(cleanup_pipe[0], buf, sizeof(buf)) > 0);
- unlink(socketname);
- rmdir(socketdir);
- _exit(0);
- } else if (pid < 0) {
- close(cleanup_pipe[0]);
- close(cleanup_pipe[1]);
- } else {
- close(cleanup_pipe[0]);
- cloexec(cleanup_pipe[1]);
- }
- }
- }
-
- *name = socketname;
- *error = NULL;
- sfree(socketdir);
- return sock;
-}
diff --git a/unix/uxcfg.c b/unix/uxcfg.c
deleted file mode 100644
index 8397a0ac..00000000
--- a/unix/uxcfg.c
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * uxcfg.c - the Unix-specific parts of the PuTTY configuration
- * box.
- */
-
-#include <assert.h>
-#include <stdlib.h>
-
-#include "putty.h"
-#include "dialog.h"
-#include "storage.h"
-
-void unix_setup_config_box(struct controlbox *b, bool midsession, int protocol)
-{
- struct controlset *s;
- union control *c;
-
- /*
- * The Conf structure contains two Unix-specific elements which
- * are not configured in here: stamp_utmp and login_shell. This
- * is because pterm does not put up a configuration box right at
- * the start, which is the only time when these elements would
- * be useful to configure.
- */
-
- /*
- * On Unix, we don't have a drop-down list for the printer
- * control.
- */
- s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing");
- assert(s->ncontrols == 1 && s->ctrls[0]->generic.type == CTRL_EDITBOX);
- s->ctrls[0]->editbox.has_list = false;
-
- /*
- * Unix supports a local-command proxy. This also means we must
- * adjust the text on the `Telnet command' control.
- */
- if (!midsession) {
- int i;
- s = ctrl_getset(b, "Connection/Proxy", "basics", NULL);
- for (i = 0; i < s->ncontrols; i++) {
- c = s->ctrls[i];
- if (c->generic.type == CTRL_RADIO &&
- c->generic.context.i == CONF_proxy_type) {
- assert(c->generic.handler == conf_radiobutton_handler);
- c->radio.nbuttons++;
- c->radio.buttons =
- sresize(c->radio.buttons, c->radio.nbuttons, char *);
- c->radio.buttons[c->radio.nbuttons-1] =
- dupstr("Local");
- c->radio.buttondata =
- sresize(c->radio.buttondata, c->radio.nbuttons, intorptr);
- c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_CMD);
- break;
- }
- }
-
- for (i = 0; i < s->ncontrols; i++) {
- c = s->ctrls[i];
- if (c->generic.type == CTRL_EDITBOX &&
- c->generic.context.i == CONF_proxy_telnet_command) {
- assert(c->generic.handler == conf_editbox_handler);
- sfree(c->generic.label);
- c->generic.label = dupstr("Telnet command, or local"
- " proxy command");
- break;
- }
- }
- }
-}
diff --git a/unix/uxcliloop.c b/unix/uxcliloop.c
deleted file mode 100644
index 23a808da..00000000
--- a/unix/uxcliloop.c
+++ /dev/null
@@ -1,125 +0,0 @@
-#include <errno.h>
-
-#include "putty.h"
-
-void cli_main_loop(cliloop_pw_setup_t pw_setup,
- cliloop_pw_check_t pw_check,
- cliloop_continue_t cont, void *ctx)
-{
- unsigned long now = GETTICKCOUNT();
-
- int *fdlist = NULL;
- size_t fdsize = 0;
-
- pollwrapper *pw = pollwrap_new();
-
- while (true) {
- int rwx;
- int ret;
- int fdstate;
- unsigned long next;
-
- pollwrap_clear(pw);
-
- if (!pw_setup(ctx, pw))
- break; /* our client signalled emergency exit */
-
- /* Count the currently active fds. */
- size_t nfds = 0;
- for (int fd = first_fd(&fdstate, &rwx); fd >= 0;
- fd = next_fd(&fdstate, &rwx))
- nfds++;
-
- /* Expand the fdlist buffer if necessary. */
- sgrowarray(fdlist, fdsize, nfds);
-
- /*
- * Add all currently open uxsel fds to pw, and store them in
- * fdlist as well.
- */
- size_t fdcount = 0;
- for (int fd = first_fd(&fdstate, &rwx); fd >= 0;
- fd = next_fd(&fdstate, &rwx)) {
- fdlist[fdcount++] = fd;
- pollwrap_add_fd_rwx(pw, fd, rwx);
- }
-
- if (toplevel_callback_pending()) {
- ret = pollwrap_poll_instant(pw);
- } else if (run_timers(now, &next)) {
- do {
- unsigned long then;
- long ticks;
-
- then = now;
- now = GETTICKCOUNT();
- if (now - then > next - then)
- ticks = 0;
- else
- ticks = next - now;
-
- bool overflow = false;
- if (ticks > INT_MAX) {
- ticks = INT_MAX;
- overflow = true;
- }
-
- ret = pollwrap_poll_timeout(pw, ticks);
- if (ret == 0 && !overflow)
- now = next;
- else
- now = GETTICKCOUNT();
- } while (ret < 0 && errno == EINTR);
- } else {
- ret = pollwrap_poll_endless(pw);
- }
-
- if (ret < 0 && errno == EINTR)
- continue;
-
- if (ret < 0) {
- perror("poll");
- exit(1);
- }
-
- bool found_fd = (ret > 0);
-
- for (size_t i = 0; i < fdcount; i++) {
- int fd = fdlist[i];
- int rwx = pollwrap_get_fd_rwx(pw, fd);
- /*
- * We must process exceptional notifications before
- * ordinary readability ones, or we may go straight
- * past the urgent marker.
- */
- if (rwx & SELECT_X)
- select_result(fd, SELECT_X);
- if (rwx & SELECT_R)
- select_result(fd, SELECT_R);
- if (rwx & SELECT_W)
- select_result(fd, SELECT_W);
- }
-
- pw_check(ctx, pw);
-
- bool ran_callback = run_toplevel_callbacks();
-
- if (!cont(ctx, found_fd, ran_callback))
- break;
- }
-
- pollwrap_free(pw);
- sfree(fdlist);
-}
-
-bool cliloop_no_pw_setup(void *ctx, pollwrapper *pw) { return true; }
-void cliloop_no_pw_check(void *ctx, pollwrapper *pw) {}
-bool cliloop_always_continue(void *ctx, bool fd, bool cb) { return true; }
-
-/*
- * Any application using this main loop doesn't need to do anything
- * when uxsel adds or removes an fd, because we synchronously re-check
- * the current list every time we go round the main loop above.
- */
-uxsel_id *uxsel_input_add(int fd, int rwx) { return NULL; }
-void uxsel_input_remove(uxsel_id *id) { }
diff --git a/unix/uxcons.c b/unix/uxcons.c
deleted file mode 100644
index 90e73a98..00000000
--- a/unix/uxcons.c
+++ /dev/null
@@ -1,530 +0,0 @@
-/*
- * uxcons.c: various interactive-prompt routines shared between the
- * Unix console PuTTY tools
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <errno.h>
-
-#include <termios.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <sys/time.h>
-
-#include "putty.h"
-#include "storage.h"
-#include "ssh.h"
-#include "console.h"
-
-static struct termios orig_termios_stderr;
-static bool stderr_is_a_tty;
-
-void stderr_tty_init()
-{
- /* Ensure that if stderr is a tty, we can get it back to a sane state. */
- if (isatty(STDERR_FILENO)) {
- stderr_is_a_tty = true;
- tcgetattr(STDERR_FILENO, &orig_termios_stderr);
- }
-}
-
-void premsg(struct termios *cf)
-{
- if (stderr_is_a_tty) {
- tcgetattr(STDERR_FILENO, cf);
- tcsetattr(STDERR_FILENO, TCSADRAIN, &orig_termios_stderr);
- }
-}
-void postmsg(struct termios *cf)
-{
- if (stderr_is_a_tty)
- tcsetattr(STDERR_FILENO, TCSADRAIN, cf);
-}
-
-void cleanup_exit(int code)
-{
- /*
- * Clean up.
- */
- sk_cleanup();
- random_save_seed();
- exit(code);
-}
-
-void console_print_error_msg(const char *prefix, const char *msg)
-{
- struct termios cf;
- premsg(&cf);
- fputs(prefix, stderr);
- fputs(": ", stderr);
- fputs(msg, stderr);
- fputc('\n', stderr);
- fflush(stderr);
- postmsg(&cf);
-}
-
-/*
- * Wrapper around Unix read(2), suitable for use on a file descriptor
- * that's been set into nonblocking mode. Handles EAGAIN/EWOULDBLOCK
- * by means of doing a one-fd poll and then trying again; all other
- * errors (including errors from poll) are returned to the caller.
- */
-static int block_and_read(int fd, void *buf, size_t len)
-{
- int ret;
- pollwrapper *pw = pollwrap_new();
-
- while ((ret = read(fd, buf, len)) < 0 && (
-#ifdef EAGAIN
- (errno == EAGAIN) ||
-#endif
-#ifdef EWOULDBLOCK
- (errno == EWOULDBLOCK) ||
-#endif
- false)) {
-
- pollwrap_clear(pw);
- pollwrap_add_fd_rwx(pw, fd, SELECT_R);
- do {
- ret = pollwrap_poll_endless(pw);
- } while (ret < 0 && errno == EINTR);
- assert(ret != 0);
- if (ret < 0) {
- pollwrap_free(pw);
- return ret;
- }
- assert(pollwrap_check_fd_rwx(pw, fd, SELECT_R));
- }
-
- pollwrap_free(pw);
- return ret;
-}
-
-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;
-
- char line[32];
- struct termios cf;
- const char *common_fmt, *intro, *prompt;
-
- /*
- * Verify the key.
- */
- ret = verify_host_key(host, port, keytype, keystr);
-
- if (ret == 0) /* success - key matched OK */
- return 1;
-
- premsg(&cf);
- if (ret == 2) { /* key was different */
- 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]);
- if (console_batch_mode) {
- fputs(console_abandoned_msg, stderr);
- return 0;
- }
-
- fputs(intro, stderr);
- fflush(stderr);
- while (true) {
- fputs(prompt, stderr);
- fflush(stderr);
-
- struct termios oldmode, newmode;
- tcgetattr(0, &oldmode);
- newmode = oldmode;
- newmode.c_lflag |= ECHO | ISIG | ICANON;
- tcsetattr(0, TCSANOW, &newmode);
- line[0] = '\0';
- if (block_and_read(0, line, sizeof(line) - 1) <= 0)
- /* handled below */;
- tcsetattr(0, TCSANOW, &oldmode);
-
- if (line[0] == 'i' || line[0] == 'I') {
- 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);
- postmsg(&cf);
- return 1;
- } else {
- fputs(console_abandoned_msg, stderr);
- postmsg(&cf);
- return 0;
- }
-}
-
-int console_confirm_weak_crypto_primitive(
- Seat *seat, const char *algtype, const char *algname,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- char line[32];
- struct termios cf;
-
- premsg(&cf);
- fprintf(stderr, weakcrypto_msg_common_fmt, algtype, algname);
-
- if (console_batch_mode) {
- fputs(console_abandoned_msg, stderr);
- postmsg(&cf);
- return 0;
- }
-
- fputs(console_continue_prompt, stderr);
- fflush(stderr);
-
- {
- struct termios oldmode, newmode;
- tcgetattr(0, &oldmode);
- newmode = oldmode;
- newmode.c_lflag |= ECHO | ISIG | ICANON;
- tcsetattr(0, TCSANOW, &newmode);
- line[0] = '\0';
- if (block_and_read(0, line, sizeof(line) - 1) <= 0)
- /* handled below */;
- tcsetattr(0, TCSANOW, &oldmode);
- }
-
- if (line[0] == 'y' || line[0] == 'Y') {
- postmsg(&cf);
- return 1;
- } else {
- fputs(console_abandoned_msg, stderr);
- postmsg(&cf);
- return 0;
- }
-}
-
-int console_confirm_weak_cached_hostkey(
- Seat *seat, const char *algname, const char *betteralgs,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- char line[32];
- struct termios cf;
-
- premsg(&cf);
- fprintf(stderr, weakhk_msg_common_fmt, algname, betteralgs);
-
- if (console_batch_mode) {
- fputs(console_abandoned_msg, stderr);
- postmsg(&cf);
- return 0;
- }
-
- fputs(console_continue_prompt, stderr);
- fflush(stderr);
-
- {
- struct termios oldmode, newmode;
- tcgetattr(0, &oldmode);
- newmode = oldmode;
- newmode.c_lflag |= ECHO | ISIG | ICANON;
- tcsetattr(0, TCSANOW, &newmode);
- line[0] = '\0';
- if (block_and_read(0, line, sizeof(line) - 1) <= 0)
- /* handled below */;
- tcsetattr(0, TCSANOW, &oldmode);
- }
-
- if (line[0] == 'y' || line[0] == 'Y') {
- postmsg(&cf);
- return 1;
- } else {
- fputs(console_abandoned_msg, stderr);
- postmsg(&cf);
- return 0;
- }
-}
-
-/*
- * Ask whether to wipe a session log file before writing to it.
- * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
- */
-int console_askappend(LogPolicy *lp, Filename *filename,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- static const char msgtemplate[] =
- "The session log file \"%.*s\" already exists.\n"
- "You can overwrite it with a new session log,\n"
- "append your session log to the end of it,\n"
- "or disable session logging for this session.\n"
- "Enter \"y\" to wipe the file, \"n\" to append to it,\n"
- "or just press Return to disable logging.\n"
- "Wipe the log file? (y/n, Return cancels logging) ";
-
- static const char msgtemplate_batch[] =
- "The session log file \"%.*s\" already exists.\n"
- "Logging will not be enabled.\n";
-
- char line[32];
- struct termios cf;
-
- premsg(&cf);
- if (console_batch_mode) {
- fprintf(stderr, msgtemplate_batch, FILENAME_MAX, filename->path);
- fflush(stderr);
- return 0;
- }
- fprintf(stderr, msgtemplate, FILENAME_MAX, filename->path);
- fflush(stderr);
-
- {
- struct termios oldmode, newmode;
- tcgetattr(0, &oldmode);
- newmode = oldmode;
- newmode.c_lflag |= ECHO | ISIG | ICANON;
- tcsetattr(0, TCSANOW, &newmode);
- line[0] = '\0';
- if (block_and_read(0, line, sizeof(line) - 1) <= 0)
- /* handled below */;
- tcsetattr(0, TCSANOW, &oldmode);
- }
-
- postmsg(&cf);
- if (line[0] == 'y' || line[0] == 'Y')
- return 2;
- else if (line[0] == 'n' || line[0] == 'N')
- return 1;
- else
- return 0;
-}
-
-bool console_antispoof_prompt = true;
-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;
-}
-
-/*
- * Warn about the obsolescent key file format.
- *
- * Uniquely among these functions, this one does _not_ expect a
- * frontend handle. This means that if PuTTY is ported to a
- * platform which requires frontend handles, this function will be
- * an anomaly. Fortunately, the problem it addresses will not have
- * been present on that platform, so it can plausibly be
- * implemented as an empty function.
- */
-void old_keyfile_warning(void)
-{
- static const char message[] =
- "You are loading an SSH-2 private key which has an\n"
- "old version of the file format. This means your key\n"
- "file is not fully tamperproof. Future versions of\n"
- "PuTTY may stop supporting this private key format,\n"
- "so we recommend you convert your key to the new\n"
- "format.\n"
- "\n"
- "Once the key is loaded into PuTTYgen, you can perform\n"
- "this conversion simply by saving it again.\n";
-
- struct termios cf;
- premsg(&cf);
- fputs(message, stderr);
- postmsg(&cf);
-}
-
-void console_logging_error(LogPolicy *lp, const char *string)
-{
- /* Errors setting up logging are considered important, so they're
- * displayed to standard error even when not in verbose mode */
- struct termios cf;
- premsg(&cf);
- fprintf(stderr, "%s\n", string);
- fflush(stderr);
- postmsg(&cf);
-}
-
-
-void console_eventlog(LogPolicy *lp, const char *string)
-{
- /* Ordinary Event Log entries are displayed in the same way as
- * logging errors, but only in verbose mode */
- if (lp_verbose(lp))
- console_logging_error(lp, string);
-}
-
-StripCtrlChars *console_stripctrl_new(
- Seat *seat, BinarySink *bs_out, SeatInteractionContext sic)
-{
- return stripctrl_new(bs_out, false, 0);
-}
-
-/*
- * Special functions to read and print to the console for password
- * prompts and the like. Uses /dev/tty or stdin/stderr, in that order
- * of preference; also sanitises escape sequences out of the text, on
- * the basis that it might have been sent by a hostile SSH server
- * doing malicious keyboard-interactive.
- */
-static void console_open(FILE **outfp, int *infd)
-{
- int fd;
-
- if ((fd = open("/dev/tty", O_RDWR)) >= 0) {
- *infd = fd;
- *outfp = fdopen(*infd, "w");
- } else {
- *infd = 0;
- *outfp = stderr;
- }
-}
-static void console_close(FILE *outfp, int infd)
-{
- if (outfp != stderr)
- fclose(outfp); /* will automatically close infd too */
-}
-
-static void console_write(FILE *outfp, ptrlen data)
-{
- fwrite(data.ptr, 1, data.len, outfp);
- fflush(outfp);
-}
-
-int console_get_userpass_input(prompts_t *p)
-{
- size_t curr_prompt;
- FILE *outfp = NULL;
- int infd;
-
- /*
- * Zero all the results, in case we abort half-way through.
- */
- {
- int i;
- for (i = 0; i < p->n_prompts; i++)
- prompt_set_result(p->prompts[i], "");
- }
-
- if (p->n_prompts && console_batch_mode)
- return 0;
-
- console_open(&outfp, &infd);
-
- /*
- * Preamble.
- */
- /* We only print the `name' caption if we have to... */
- if (p->name_reqd && p->name) {
- ptrlen plname = ptrlen_from_asciz(p->name);
- console_write(outfp, plname);
- if (!ptrlen_endswith(plname, PTRLEN_LITERAL("\n"), NULL))
- console_write(outfp, PTRLEN_LITERAL("\n"));
- }
- /* ...but we always print any `instruction'. */
- if (p->instruction) {
- ptrlen plinst = ptrlen_from_asciz(p->instruction);
- console_write(outfp, plinst);
- if (!ptrlen_endswith(plinst, PTRLEN_LITERAL("\n"), NULL))
- console_write(outfp, PTRLEN_LITERAL("\n"));
- }
-
- for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) {
-
- struct termios oldmode, newmode;
- prompt_t *pr = p->prompts[curr_prompt];
-
- tcgetattr(infd, &oldmode);
- newmode = oldmode;
- newmode.c_lflag |= ISIG | ICANON;
- if (!pr->echo)
- newmode.c_lflag &= ~ECHO;
- else
- newmode.c_lflag |= ECHO;
- tcsetattr(infd, TCSANOW, &newmode);
-
- console_write(outfp, ptrlen_from_asciz(pr->prompt));
-
- bool failed = false;
- while (1) {
- size_t toread = 65536;
- size_t prev_result_len = pr->result->len;
- void *ptr = strbuf_append(pr->result, toread);
- int ret = read(infd, ptr, toread);
-
- if (ret <= 0) {
- failed = true;
- break;
- }
-
- strbuf_shrink_to(pr->result, prev_result_len + ret);
- if (strbuf_chomp(pr->result, '\n'))
- break;
- }
-
- tcsetattr(infd, TCSANOW, &oldmode);
-
- if (!pr->echo)
- console_write(outfp, PTRLEN_LITERAL("\n"));
-
- if (failed) {
- console_close(outfp, infd);
- return 0; /* failure due to read error */
- }
- }
-
- console_close(outfp, infd);
-
- return 1; /* success */
-}
-
-bool is_interactive(void)
-{
- return isatty(0);
-}
-
-/*
- * X11-forwarding-related things suitable for console.
- */
-
-char *platform_get_x_display(void) {
- return dupstr(getenv("DISPLAY"));
-}
diff --git a/unix/uxfdsock.c b/unix/uxfdsock.c
deleted file mode 100644
index 7697d995..00000000
--- a/unix/uxfdsock.c
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * uxfdsock.c: implementation of Socket that just talks to two
- * existing input and output file descriptors.
- */
-
-#include <stdio.h>
-#include <assert.h>
-#include <errno.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-#include "tree234.h"
-#include "putty.h"
-#include "network.h"
-
-typedef struct FdSocket {
- int outfd, infd, inerrfd;
-
- bufchain pending_output_data;
- bufchain pending_input_data;
- ProxyStderrBuf psb;
- enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
-
- int pending_error;
-
- Plug *plug;
-
- Socket sock;
-} FdSocket;
-
-static void fdsocket_select_result_input(int fd, int event);
-static void fdsocket_select_result_output(int fd, int event);
-static void fdsocket_select_result_input_error(int fd, int event);
-
-/*
- * Trees to look up the fds in.
- */
-static tree234 *fdsocket_by_outfd;
-static tree234 *fdsocket_by_infd;
-static tree234 *fdsocket_by_inerrfd;
-
-static int fdsocket_infd_cmp(void *av, void *bv)
-{
- FdSocket *a = (FdSocket *)av;
- FdSocket *b = (FdSocket *)bv;
- if (a->infd < b->infd)
- return -1;
- if (a->infd > b->infd)
- return +1;
- return 0;
-}
-static int fdsocket_infd_find(void *av, void *bv)
-{
- int a = *(int *)av;
- FdSocket *b = (FdSocket *)bv;
- if (a < b->infd)
- return -1;
- if (a > b->infd)
- return +1;
- return 0;
-}
-static int fdsocket_inerrfd_cmp(void *av, void *bv)
-{
- FdSocket *a = (FdSocket *)av;
- FdSocket *b = (FdSocket *)bv;
- if (a->inerrfd < b->inerrfd)
- return -1;
- if (a->inerrfd > b->inerrfd)
- return +1;
- return 0;
-}
-static int fdsocket_inerrfd_find(void *av, void *bv)
-{
- int a = *(int *)av;
- FdSocket *b = (FdSocket *)bv;
- if (a < b->inerrfd)
- return -1;
- if (a > b->inerrfd)
- return +1;
- return 0;
-}
-static int fdsocket_outfd_cmp(void *av, void *bv)
-{
- FdSocket *a = (FdSocket *)av;
- FdSocket *b = (FdSocket *)bv;
- if (a->outfd < b->outfd)
- return -1;
- if (a->outfd > b->outfd)
- return +1;
- return 0;
-}
-static int fdsocket_outfd_find(void *av, void *bv)
-{
- int a = *(int *)av;
- FdSocket *b = (FdSocket *)bv;
- if (a < b->outfd)
- return -1;
- if (a > b->outfd)
- return +1;
- return 0;
-}
-
-static Plug *fdsocket_plug(Socket *s, Plug *p)
-{
- FdSocket *fds = container_of(s, FdSocket, sock);
- Plug *ret = fds->plug;
- if (p)
- fds->plug = p;
- return ret;
-}
-
-static void fdsocket_close(Socket *s)
-{
- FdSocket *fds = container_of(s, FdSocket, sock);
-
- if (fds->outfd >= 0) {
- del234(fdsocket_by_outfd, fds);
- uxsel_del(fds->outfd);
- close(fds->outfd);
- }
-
- if (fds->infd >= 0) {
- del234(fdsocket_by_infd, fds);
- uxsel_del(fds->infd);
- close(fds->infd);
- }
-
- if (fds->inerrfd >= 0) {
- del234(fdsocket_by_inerrfd, fds);
- uxsel_del(fds->inerrfd);
- close(fds->inerrfd);
- }
-
- bufchain_clear(&fds->pending_input_data);
- bufchain_clear(&fds->pending_output_data);
-
- delete_callbacks_for_context(fds);
-
- sfree(fds);
-}
-
-static void fdsocket_error_callback(void *vs)
-{
- FdSocket *fds = (FdSocket *)vs;
-
- /*
- * Just in case other socket work has caused this socket to vanish
- * or become somehow non-erroneous before this callback arrived...
- */
- if (!fds->pending_error)
- return;
-
- /*
- * An error has occurred on this socket. Pass it to the plug.
- */
- plug_closing(fds->plug, strerror(fds->pending_error),
- fds->pending_error, 0);
-}
-
-static int fdsocket_try_send(FdSocket *fds)
-{
- int sent = 0;
-
- while (bufchain_size(&fds->pending_output_data) > 0) {
- ssize_t ret;
-
- ptrlen data = bufchain_prefix(&fds->pending_output_data);
- ret = write(fds->outfd, data.ptr, data.len);
- noise_ultralight(NOISE_SOURCE_IOID, ret);
- if (ret < 0 && errno != EWOULDBLOCK) {
- if (!fds->pending_error) {
- fds->pending_error = errno;
- queue_toplevel_callback(fdsocket_error_callback, fds);
- }
- return 0;
- } else if (ret <= 0) {
- break;
- } else {
- bufchain_consume(&fds->pending_output_data, ret);
- sent += ret;
- }
- }
-
- if (fds->outgoingeof == EOF_PENDING) {
- del234(fdsocket_by_outfd, fds);
- close(fds->outfd);
- uxsel_del(fds->outfd);
- fds->outfd = -1;
- fds->outgoingeof = EOF_SENT;
- }
-
- if (bufchain_size(&fds->pending_output_data) == 0)
- uxsel_del(fds->outfd);
- else
- uxsel_set(fds->outfd, SELECT_W, fdsocket_select_result_output);
-
- return sent;
-}
-
-static size_t fdsocket_write(Socket *s, const void *data, size_t len)
-{
- FdSocket *fds = container_of(s, FdSocket, sock);
-
- assert(fds->outgoingeof == EOF_NO);
-
- bufchain_add(&fds->pending_output_data, data, len);
-
- fdsocket_try_send(fds);
-
- return bufchain_size(&fds->pending_output_data);
-}
-
-static size_t fdsocket_write_oob(Socket *s, const void *data, size_t len)
-{
- /*
- * oob data is treated as inband; nasty, but nothing really
- * better we can do
- */
- return fdsocket_write(s, data, len);
-}
-
-static void fdsocket_write_eof(Socket *s)
-{
- FdSocket *fds = container_of(s, FdSocket, sock);
-
- assert(fds->outgoingeof == EOF_NO);
- fds->outgoingeof = EOF_PENDING;
-
- fdsocket_try_send(fds);
-}
-
-static void fdsocket_set_frozen(Socket *s, bool is_frozen)
-{
- FdSocket *fds = container_of(s, FdSocket, sock);
-
- if (fds->infd < 0)
- return;
-
- if (is_frozen)
- uxsel_del(fds->infd);
- else
- uxsel_set(fds->infd, SELECT_R, fdsocket_select_result_input);
-}
-
-static const char *fdsocket_socket_error(Socket *s)
-{
- return NULL;
-}
-
-static void fdsocket_select_result_input(int fd, int event)
-{
- FdSocket *fds;
- char buf[20480];
- int retd;
-
- if (!(fds = find234(fdsocket_by_infd, &fd, fdsocket_infd_find)))
- return;
-
- retd = read(fds->infd, buf, sizeof(buf));
- if (retd > 0) {
- plug_receive(fds->plug, 0, buf, retd);
- } else {
- if (retd < 0) {
- plug_closing(fds->plug, strerror(errno), errno, 0);
- } else {
- plug_closing(fds->plug, NULL, 0, 0);
- }
- del234(fdsocket_by_infd, fds);
- uxsel_del(fds->infd);
- close(fds->infd);
- fds->infd = -1;
- }
-}
-
-static void fdsocket_select_result_output(int fd, int event)
-{
- FdSocket *fds;
-
- if (!(fds = find234(fdsocket_by_outfd, &fd, fdsocket_outfd_find)))
- return;
-
- if (fdsocket_try_send(fds))
- plug_sent(fds->plug, bufchain_size(&fds->pending_output_data));
-}
-
-static void fdsocket_select_result_input_error(int fd, int event)
-{
- FdSocket *fds;
- char buf[20480];
- int retd;
-
- if (!(fds = find234(fdsocket_by_inerrfd, &fd, fdsocket_inerrfd_find)))
- return;
-
- retd = read(fd, buf, sizeof(buf));
- if (retd > 0) {
- log_proxy_stderr(fds->plug, &fds->psb, buf, retd);
- } else {
- del234(fdsocket_by_inerrfd, fds);
- uxsel_del(fds->inerrfd);
- close(fds->inerrfd);
- fds->inerrfd = -1;
- }
-}
-
-static const SocketVtable FdSocket_sockvt = {
- .plug = fdsocket_plug,
- .close = fdsocket_close,
- .write = fdsocket_write,
- .write_oob = fdsocket_write_oob,
- .write_eof = fdsocket_write_eof,
- .set_frozen = fdsocket_set_frozen,
- .socket_error = fdsocket_socket_error,
- .peer_info = NULL,
-};
-
-Socket *make_fd_socket(int infd, int outfd, int inerrfd, Plug *plug)
-{
- FdSocket *fds;
-
- fds = snew(FdSocket);
- fds->sock.vt = &FdSocket_sockvt;
- fds->plug = plug;
- fds->outgoingeof = EOF_NO;
- fds->pending_error = 0;
-
- fds->infd = infd;
- fds->outfd = outfd;
- fds->inerrfd = inerrfd;
-
- bufchain_init(&fds->pending_input_data);
- bufchain_init(&fds->pending_output_data);
- psb_init(&fds->psb);
-
- if (fds->outfd >= 0) {
- if (!fdsocket_by_outfd)
- fdsocket_by_outfd = newtree234(fdsocket_outfd_cmp);
- add234(fdsocket_by_outfd, fds);
- }
-
- if (fds->infd >= 0) {
- if (!fdsocket_by_infd)
- fdsocket_by_infd = newtree234(fdsocket_infd_cmp);
- add234(fdsocket_by_infd, fds);
- uxsel_set(fds->infd, SELECT_R, fdsocket_select_result_input);
- }
-
- if (fds->inerrfd >= 0) {
- assert(fds->inerrfd != fds->infd);
- if (!fdsocket_by_inerrfd)
- fdsocket_by_inerrfd = newtree234(fdsocket_inerrfd_cmp);
- add234(fdsocket_by_inerrfd, fds);
- uxsel_set(fds->inerrfd, SELECT_R, fdsocket_select_result_input_error);
- }
-
- return &fds->sock;
-}
diff --git a/unix/uxgen.c b/unix/uxgen.c
deleted file mode 100644
index da5e8f05..00000000
--- a/unix/uxgen.c
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * uxgen.c: Unix implementation of get_heavy_noise() from cmdgen.c.
- */
-
-#include <stdio.h>
-#include <errno.h>
-
-#include <fcntl.h>
-#include <unistd.h>
-
-#include "putty.h"
-
-char *get_random_data(int len, const char *device)
-{
- char *buf = snewn(len, char);
- int fd;
- int ngot, ret;
-
- if (!device) {
- static const char *const default_devices[] = {
- "/dev/urandom", "/dev/random"
- };
- size_t i;
-
- for (i = 0; i < lenof(default_devices); i++) {
- if (access(default_devices[i], R_OK) == 0) {
- device = default_devices[i];
- break;
- }
- }
-
- if (!device) {
- sfree(buf);
- fprintf(stderr, "puttygen: cannot find a readable "
- "random number source; use --random-device\n");
- return NULL;
- }
- }
-
- fd = open(device, O_RDONLY);
- if (fd < 0) {
- sfree(buf);
- fprintf(stderr, "puttygen: %s: open: %s\n",
- device, strerror(errno));
- return NULL;
- }
-
- ngot = 0;
- while (ngot < len) {
- ret = read(fd, buf+ngot, len-ngot);
- if (ret < 0) {
- close(fd);
- sfree(buf);
- fprintf(stderr, "puttygen: %s: read: %s\n",
- device, strerror(errno));
- return NULL;
- }
- ngot += ret;
- }
-
- close(fd);
-
- return buf;
-}
diff --git a/unix/uxgss.c b/unix/uxgss.c
deleted file mode 100644
index 2d71c543..00000000
--- a/unix/uxgss.c
+++ /dev/null
@@ -1,175 +0,0 @@
-#include "putty.h"
-#ifndef NO_GSSAPI
-#include "pgssapi.h"
-#include "sshgss.h"
-#include "sshgssc.h"
-
-/* Unix code to set up the GSSAPI library list. */
-
-#if !defined NO_LIBDL && !defined NO_GSSAPI
-
-const int ngsslibs = 4;
-const char *const gsslibnames[4] = {
- "libgssapi (Heimdal)",
- "libgssapi_krb5 (MIT Kerberos)",
- "libgss (Sun)",
- "User-specified GSSAPI library",
-};
-const struct keyvalwhere gsslibkeywords[] = {
- { "libgssapi", 0, -1, -1 },
- { "libgssapi_krb5", 1, -1, -1 },
- { "libgss", 2, -1, -1 },
- { "custom", 3, -1, -1 },
-};
-
-/*
- * Run-time binding against a choice of GSSAPI implementations. We
- * try loading several libraries, and produce an entry in
- * ssh_gss_libraries[] for each one.
- */
-
-static void gss_init(struct ssh_gss_library *lib, void *dlhandle,
- int id, const char *msg)
-{
- lib->id = id;
- lib->gsslogmsg = msg;
- lib->handle = dlhandle;
-
-#define BIND_GSS_FN(name) \
- lib->u.gssapi.name = (t_gss_##name) dlsym(dlhandle, "gss_" #name)
-
- BIND_GSS_FN(delete_sec_context);
- BIND_GSS_FN(display_status);
- BIND_GSS_FN(get_mic);
- BIND_GSS_FN(verify_mic);
- BIND_GSS_FN(import_name);
- BIND_GSS_FN(init_sec_context);
- BIND_GSS_FN(release_buffer);
- BIND_GSS_FN(release_cred);
- BIND_GSS_FN(release_name);
- BIND_GSS_FN(acquire_cred);
- BIND_GSS_FN(inquire_cred_by_mech);
-
-#undef BIND_GSS_FN
-
- ssh_gssapi_bind_fns(lib);
-}
-
-/* Dynamically load gssapi libs. */
-struct ssh_gss_liblist *ssh_gss_setup(Conf *conf)
-{
- void *gsslib;
- char *gsspath;
- struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist);
-
- list->libraries = snewn(4, struct ssh_gss_library);
- list->nlibraries = 0;
-
- /* Heimdal's GSSAPI Library */
- if ((gsslib = dlopen("libgssapi.so.2", RTLD_LAZY)) != NULL)
- gss_init(&list->libraries[list->nlibraries++], gsslib,
- 0, "Using GSSAPI from libgssapi.so.2");
-
- /* MIT Kerberos's GSSAPI Library */
- if ((gsslib = dlopen("libgssapi_krb5.so.2", RTLD_LAZY)) != NULL)
- gss_init(&list->libraries[list->nlibraries++], gsslib,
- 1, "Using GSSAPI from libgssapi_krb5.so.2");
-
- /* Sun's GSSAPI Library */
- if ((gsslib = dlopen("libgss.so.1", RTLD_LAZY)) != NULL)
- gss_init(&list->libraries[list->nlibraries++], gsslib,
- 2, "Using GSSAPI from libgss.so.1");
-
- /* User-specified GSSAPI library */
- gsspath = conf_get_filename(conf, CONF_ssh_gss_custom)->path;
- if (*gsspath && (gsslib = dlopen(gsspath, RTLD_LAZY)) != NULL)
- gss_init(&list->libraries[list->nlibraries++], gsslib,
- 3, dupprintf("Using GSSAPI from user-specified"
- " library '%s'", gsspath));
-
- return list;
-}
-
-void ssh_gss_cleanup(struct ssh_gss_liblist *list)
-{
- int i;
-
- /*
- * dlopen and dlclose are defined to employ reference counting
- * in the case where the same library is repeatedly dlopened, so
- * even in a multiple-sessions-per-process context it's safe to
- * naively dlclose everything here without worrying about
- * destroying it under the feet of another SSH instance still
- * using it.
- */
- for (i = 0; i < list->nlibraries; i++) {
- dlclose(list->libraries[i].handle);
- if (list->libraries[i].id == 3) {
- /* The 'custom' id involves a dynamically allocated message.
- * Note that we must cast away the 'const' to free it. */
- sfree((char *)list->libraries[i].gsslogmsg);
- }
- }
- sfree(list->libraries);
- sfree(list);
-}
-
-#elif !defined NO_GSSAPI
-
-const int ngsslibs = 1;
-const char *const gsslibnames[1] = {
- "static",
-};
-const struct keyvalwhere gsslibkeywords[] = {
- { "static", 0, -1, -1 },
-};
-
-/*
- * Link-time binding against GSSAPI. Here we just construct a single
- * library structure containing pointers to the functions we linked
- * against.
- */
-
-#include <gssapi/gssapi.h>
-
-/* Dynamically load gssapi libs. */
-struct ssh_gss_liblist *ssh_gss_setup(Conf *conf)
-{
- struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist);
-
- list->libraries = snew(struct ssh_gss_library);
- list->nlibraries = 1;
-
- list->libraries[0].gsslogmsg = "Using statically linked GSSAPI";
-
-#define BIND_GSS_FN(name) \
- list->libraries[0].u.gssapi.name = (t_gss_##name) gss_##name
-
- BIND_GSS_FN(delete_sec_context);
- BIND_GSS_FN(display_status);
- BIND_GSS_FN(get_mic);
- BIND_GSS_FN(verify_mic);
- BIND_GSS_FN(import_name);
- BIND_GSS_FN(init_sec_context);
- BIND_GSS_FN(release_buffer);
- BIND_GSS_FN(release_cred);
- BIND_GSS_FN(release_name);
- BIND_GSS_FN(acquire_cred);
- BIND_GSS_FN(inquire_cred_by_mech);
-
-#undef BIND_GSS_FN
-
- ssh_gssapi_bind_fns(&list->libraries[0]);
-
- return list;
-}
-
-void ssh_gss_cleanup(struct ssh_gss_liblist *list)
-{
- sfree(list->libraries);
- sfree(list);
-}
-
-#endif /* NO_LIBDL */
-
-#endif /* NO_GSSAPI */
diff --git a/unix/uxmisc.c b/unix/uxmisc.c
deleted file mode 100644
index 57df7b7a..00000000
--- a/unix/uxmisc.c
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
- * PuTTY miscellaneous Unix stuff
- */
-
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <errno.h>
-#include <unistd.h>
-#include <time.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <pwd.h>
-
-#include "putty.h"
-
-unsigned long getticks(void)
-{
- /*
- * We want to use milliseconds rather than the microseconds or
- * nanoseconds given by the underlying clock functions, because we
- * need a decent number of them to fit into a 32-bit word so it
- * can be used for keepalives.
- */
-#if defined HAVE_CLOCK_GETTIME && defined HAVE_DECL_CLOCK_MONOTONIC
- {
- /* Use CLOCK_MONOTONIC if available, so as to be unconfused if
- * the system clock changes. */
- struct timespec ts;
- if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
- return ts.tv_sec * TICKSPERSEC +
- ts.tv_nsec / (1000000000 / TICKSPERSEC);
- }
-#endif
- {
- struct timeval tv;
- gettimeofday(&tv, NULL);
- return tv.tv_sec * TICKSPERSEC + tv.tv_usec / (1000000 / TICKSPERSEC);
- }
-}
-
-Filename *filename_from_str(const char *str)
-{
- Filename *ret = snew(Filename);
- ret->path = dupstr(str);
- return ret;
-}
-
-Filename *filename_copy(const Filename *fn)
-{
- return filename_from_str(fn->path);
-}
-
-const char *filename_to_str(const Filename *fn)
-{
- return fn->path;
-}
-
-bool filename_equal(const Filename *f1, const Filename *f2)
-{
- return !strcmp(f1->path, f2->path);
-}
-
-bool filename_is_null(const Filename *fn)
-{
- return !fn->path[0];
-}
-
-void filename_free(Filename *fn)
-{
- sfree(fn->path);
- sfree(fn);
-}
-
-void filename_serialise(BinarySink *bs, const Filename *f)
-{
- put_asciz(bs, f->path);
-}
-Filename *filename_deserialise(BinarySource *src)
-{
- return filename_from_str(get_asciz(src));
-}
-
-char filename_char_sanitise(char c)
-{
- if (c == '/')
- return '.';
- return c;
-}
-
-#ifdef DEBUG
-static FILE *debug_fp = NULL;
-
-void dputs(const char *buf)
-{
- if (!debug_fp) {
- debug_fp = fopen("debug.log", "w");
- }
-
- if (write(1, buf, strlen(buf)) < 0) {} /* 'error check' to placate gcc */
-
- fputs(buf, debug_fp);
- fflush(debug_fp);
-}
-#endif
-
-char *get_username(void)
-{
- struct passwd *p;
- uid_t uid = getuid();
- char *user, *ret = NULL;
-
- /*
- * First, find who we think we are using getlogin. If this
- * agrees with our uid, we'll go along with it. This should
- * allow sharing of uids between several login names whilst
- * coping correctly with people who have su'ed.
- */
- user = getlogin();
-#if HAVE_SETPWENT
- setpwent();
-#endif
- if (user)
- p = getpwnam(user);
- else
- p = NULL;
- if (p && p->pw_uid == uid) {
- /*
- * The result of getlogin() really does correspond to
- * our uid. Fine.
- */
- ret = user;
- } else {
- /*
- * If that didn't work, for whatever reason, we'll do
- * the simpler version: look up our uid in the password
- * file and map it straight to a name.
- */
- p = getpwuid(uid);
- if (!p)
- return NULL;
- ret = p->pw_name;
- }
-#if HAVE_ENDPWENT
- endpwent();
-#endif
-
- return dupstr(ret);
-}
-
-/*
- * Display the fingerprints of the PGP Master Keys to the user.
- * (This is here rather than in uxcons because it's appropriate even for
- * Unix GUI apps.)
- */
-void pgp_fingerprints(void)
-{
- fputs("These are the fingerprints of the PuTTY PGP Master Keys. They can\n"
- "be used to establish a trust path from this executable to another\n"
- "one. See the manual for more information.\n"
- "(Note: these fingerprints have nothing to do with SSH!)\n"
- "\n"
- "PuTTY Master Key as of " 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);
-}
-
-/*
- * Set and clear fcntl options on a file descriptor. We don't
- * realistically expect any of these operations to fail (the most
- * plausible error condition is EBADF, but we always believe ourselves
- * to be passing a valid fd so even that's an assertion-fail sort of
- * response), so we don't make any effort to return sensible error
- * codes to the caller - we just log to standard error and die
- * unceremoniously. However, nonblock and no_nonblock do return the
- * previous state of O_NONBLOCK.
- */
-void cloexec(int fd) {
- int fdflags;
-
- fdflags = fcntl(fd, F_GETFD);
- if (fdflags < 0) {
- fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno));
- exit(1);
- }
- if (fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC) < 0) {
- fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno));
- exit(1);
- }
-}
-void noncloexec(int fd) {
- int fdflags;
-
- fdflags = fcntl(fd, F_GETFD);
- if (fdflags < 0) {
- fprintf(stderr, "%d: fcntl(F_GETFD): %s\n", fd, strerror(errno));
- exit(1);
- }
- if (fcntl(fd, F_SETFD, fdflags & ~FD_CLOEXEC) < 0) {
- fprintf(stderr, "%d: fcntl(F_SETFD): %s\n", fd, strerror(errno));
- exit(1);
- }
-}
-bool nonblock(int fd) {
- int fdflags;
-
- fdflags = fcntl(fd, F_GETFL);
- if (fdflags < 0) {
- fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno));
- exit(1);
- }
- if (fcntl(fd, F_SETFL, fdflags | O_NONBLOCK) < 0) {
- fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno));
- exit(1);
- }
-
- return fdflags & O_NONBLOCK;
-}
-bool no_nonblock(int fd) {
- int fdflags;
-
- fdflags = fcntl(fd, F_GETFL);
- if (fdflags < 0) {
- fprintf(stderr, "%d: fcntl(F_GETFL): %s\n", fd, strerror(errno));
- exit(1);
- }
- if (fcntl(fd, F_SETFL, fdflags & ~O_NONBLOCK) < 0) {
- fprintf(stderr, "%d: fcntl(F_SETFL): %s\n", fd, strerror(errno));
- exit(1);
- }
-
- return fdflags & O_NONBLOCK;
-}
-
-FILE *f_open(const Filename *filename, char const *mode, bool is_private)
-{
- if (!is_private) {
- return fopen(filename->path, mode);
- } else {
- int fd;
- assert(mode[0] == 'w'); /* is_private is meaningless for read,
- and tricky for append */
- fd = open(filename->path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
- if (fd < 0)
- return NULL;
- return fdopen(fd, mode);
- }
-}
-
-FontSpec *fontspec_new(const char *name)
-{
- FontSpec *f = snew(FontSpec);
- f->name = dupstr(name);
- return f;
-}
-FontSpec *fontspec_copy(const FontSpec *f)
-{
- return fontspec_new(f->name);
-}
-void fontspec_free(FontSpec *f)
-{
- sfree(f->name);
- sfree(f);
-}
-void fontspec_serialise(BinarySink *bs, FontSpec *f)
-{
- put_asciz(bs, f->name);
-}
-FontSpec *fontspec_deserialise(BinarySource *src)
-{
- return fontspec_new(get_asciz(src));
-}
-
-char *make_dir_and_check_ours(const char *dirname)
-{
- struct stat st;
-
- /*
- * Create the directory. We might have created it before, so
- * EEXIST is an OK error; but anything else is doom.
- */
- if (mkdir(dirname, 0700) < 0 && errno != EEXIST)
- return dupprintf("%s: mkdir: %s", dirname, strerror(errno));
-
- /*
- * Now check that that directory is _owned by us_ and not writable
- * by anybody else. This protects us against somebody else
- * previously having created the directory in a way that's
- * writable to us, and thus manipulating us into creating the
- * actual socket in a directory they can see so that they can
- * connect to it and use our authenticated SSH sessions.
- */
- if (stat(dirname, &st) < 0)
- return dupprintf("%s: stat: %s", dirname, strerror(errno));
- if (st.st_uid != getuid())
- return dupprintf("%s: directory owned by uid %d, not by us",
- dirname, st.st_uid);
- if ((st.st_mode & 077) != 0)
- return dupprintf("%s: directory has overgenerous permissions %03o"
- " (expected 700)", dirname, st.st_mode & 0777);
-
- return NULL;
-}
-
-char *make_dir_path(const char *path, mode_t mode)
-{
- int pos = 0;
- char *prefix;
-
- while (1) {
- pos += strcspn(path + pos, "/");
-
- if (pos > 0) {
- prefix = dupprintf("%.*s", pos, path);
-
- if (mkdir(prefix, mode) < 0 && errno != EEXIST) {
- char *ret = dupprintf("%s: mkdir: %s",
- prefix, strerror(errno));
- sfree(prefix);
- return ret;
- }
-
- sfree(prefix);
- }
-
- if (!path[pos])
- return NULL;
- pos += strspn(path + pos, "/");
- }
-}
-
-bool open_for_write_would_lose_data(const Filename *fn)
-{
- struct stat st;
-
- if (stat(fn->path, &st) < 0) {
- /*
- * If the file doesn't even exist, we obviously want to return
- * false. If we failed to stat it for any other reason,
- * ignoring the precise error code and returning false still
- * doesn't seem too unreasonable, because then we'll try to
- * open the file for writing and report _that_ error, which is
- * likely to be more to the point.
- */
- return false;
- }
-
- /*
- * OK, something exists at this pathname and we've found out
- * something about it. But an open-for-write will only
- * destructively truncate it if it's a regular file with nonzero
- * size. If it's empty, or some other kind of special thing like a
- * character device (e.g. /dev/tty) or a named pipe, then opening
- * it for write is already non-destructive and it's pointless and
- * annoying to warn about it just because the same file can be
- * opened for reading. (Indeed, if it's a named pipe, opening it
- * for reading actually _causes inconvenience_ in its own right,
- * even before the question of whether it gives misleading
- * information.)
- */
- if (S_ISREG(st.st_mode) && st.st_size > 0) {
- return true;
- }
-
- return false;
-}
diff --git a/unix/uxnet.c b/unix/uxnet.c
deleted file mode 100644
index bd6ebb1d..00000000
--- a/unix/uxnet.c
+++ /dev/null
@@ -1,1761 +0,0 @@
-/*
- * Unix networking abstraction.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/ioctl.h>
-#include <arpa/inet.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <netdb.h>
-#include <sys/un.h>
-#include <pwd.h>
-#include <grp.h>
-
-#include "putty.h"
-#include "network.h"
-#include "tree234.h"
-
-/* Solaris needs <sys/sockio.h> for SIOCATMARK. */
-#ifndef SIOCATMARK
-#include <sys/sockio.h>
-#endif
-
-#ifndef X11_UNIX_PATH
-# define X11_UNIX_PATH "/tmp/.X11-unix/X"
-#endif
-
-/*
- * Access to sockaddr types without breaking C strict aliasing rules.
- */
-union sockaddr_union {
- struct sockaddr_storage storage;
- struct sockaddr sa;
- struct sockaddr_in sin;
-#ifndef NO_IPV6
- struct sockaddr_in6 sin6;
-#endif
- struct sockaddr_un su;
-};
-
-/*
- * Mutable state that goes with a SockAddr: stores information
- * about where in the list of candidate IP(v*) addresses we've
- * currently got to.
- */
-typedef struct SockAddrStep_tag SockAddrStep;
-struct SockAddrStep_tag {
-#ifndef NO_IPV6
- struct addrinfo *ai; /* steps along addr->ais */
-#endif
- int curraddr;
-};
-
-typedef struct NetSocket NetSocket;
-struct NetSocket {
- const char *error;
- int s;
- Plug *plug;
- bufchain output_data;
- bool connected; /* irrelevant for listening sockets */
- bool writable;
- bool frozen; /* this causes readability notifications to be ignored */
- bool localhost_only; /* for listening sockets */
- char oobdata[1];
- size_t sending_oob;
- bool oobpending; /* is there OOB data available to read? */
- bool oobinline;
- enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
- bool incomingeof;
- int pending_error; /* in case send() returns error */
- bool listener;
- bool nodelay, keepalive; /* for connect()-type sockets */
- bool privport;
- int port; /* and again */
- SockAddr *addr;
- SockAddrStep step;
- /*
- * We sometimes need pairs of Socket structures to be linked:
- * if we are listening on the same IPv6 and v4 port, for
- * example. So here we define `parent' and `child' pointers to
- * track this link.
- */
- NetSocket *parent, *child;
-
- Socket sock;
-};
-
-struct SockAddr {
- int refcount;
- const char *error;
- enum { UNRESOLVED, UNIX, IP } superfamily;
-#ifndef NO_IPV6
- struct addrinfo *ais; /* Addresses IPv6 style. */
-#else
- unsigned long *addresses; /* Addresses IPv4 style. */
- int naddresses;
-#endif
- char hostname[512]; /* Store an unresolved host name. */
-};
-
-/*
- * Which address family this address belongs to. AF_INET for IPv4;
- * AF_INET6 for IPv6; AF_UNSPEC indicates that name resolution has
- * not been done and a simple host name is held in this SockAddr
- * structure.
- */
-#ifndef NO_IPV6
-#define SOCKADDR_FAMILY(addr, step) \
- ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \
- (addr)->superfamily == UNIX ? AF_UNIX : \
- (step).ai ? (step).ai->ai_family : AF_INET)
-#else
-/* Here we gratuitously reference 'step' to avoid gcc warnings about
- * 'set but not used' when compiling -DNO_IPV6 */
-#define SOCKADDR_FAMILY(addr, step) \
- ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \
- (addr)->superfamily == UNIX ? AF_UNIX : \
- (step).curraddr ? AF_INET : AF_INET)
-#endif
-
-/*
- * Start a SockAddrStep structure to step through multiple
- * addresses.
- */
-#ifndef NO_IPV6
-#define START_STEP(addr, step) \
- ((step).ai = (addr)->ais, (step).curraddr = 0)
-#else
-#define START_STEP(addr, step) \
- ((step).curraddr = 0)
-#endif
-
-static tree234 *sktree;
-
-static void uxsel_tell(NetSocket *s);
-
-static int cmpfortree(void *av, void *bv)
-{
- NetSocket *a = (NetSocket *) av, *b = (NetSocket *) bv;
- int as = a->s, bs = b->s;
- if (as < bs)
- return -1;
- if (as > bs)
- return +1;
- if (a < b)
- return -1;
- if (a > b)
- return +1;
- return 0;
-}
-
-static int cmpforsearch(void *av, void *bv)
-{
- NetSocket *b = (NetSocket *) bv;
- int as = *(int *)av, bs = b->s;
- if (as < bs)
- return -1;
- if (as > bs)
- return +1;
- return 0;
-}
-
-void sk_init(void)
-{
- sktree = newtree234(cmpfortree);
-}
-
-void sk_cleanup(void)
-{
- NetSocket *s;
- int i;
-
- if (sktree) {
- for (i = 0; (s = index234(sktree, i)) != NULL; i++) {
- close(s->s);
- }
- }
-}
-
-SockAddr *sk_namelookup(const char *host, char **canonicalname, int address_family)
-{
- if (host[0] == '/') {
- *canonicalname = dupstr(host);
- return unix_sock_addr(host);
- }
-
- SockAddr *ret = snew(SockAddr);
-#ifndef NO_IPV6
- struct addrinfo hints;
- int err;
-#else
- unsigned long a;
- struct hostent *h = NULL;
- int n;
-#endif
- strbuf *realhost = strbuf_new();
-
- /* Clear the structure and default to IPv4. */
- memset(ret, 0, sizeof(SockAddr));
- ret->superfamily = UNRESOLVED;
- ret->error = NULL;
- ret->refcount = 1;
-
-#ifndef NO_IPV6
- hints.ai_flags = AI_CANONNAME;
- hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET :
- address_family == ADDRTYPE_IPV6 ? AF_INET6 :
- AF_UNSPEC);
- hints.ai_socktype = SOCK_STREAM;
- hints.ai_protocol = 0;
- hints.ai_addrlen = 0;
- hints.ai_addr = NULL;
- hints.ai_canonname = NULL;
- hints.ai_next = NULL;
- {
- char *trimmed_host = host_strduptrim(host); /* strip [] on literals */
- err = getaddrinfo(trimmed_host, NULL, &hints, &ret->ais);
- sfree(trimmed_host);
- }
- if (err != 0) {
- ret->error = gai_strerror(err);
- strbuf_free(realhost);
- return ret;
- }
- ret->superfamily = IP;
-
- if (ret->ais->ai_canonname != NULL)
- strbuf_catf(realhost, "%s", ret->ais->ai_canonname);
- else
- strbuf_catf(realhost, "%s", host);
-#else
- if ((a = inet_addr(host)) == (unsigned long)(in_addr_t)(-1)) {
- /*
- * Otherwise use the IPv4-only gethostbyname... (NOTE:
- * we don't use gethostbyname as a fallback!)
- */
- if (ret->superfamily == UNRESOLVED) {
- /*debug("Resolving \"%s\" with gethostbyname() (IPv4 only)...\n", host); */
- if ( (h = gethostbyname(host)) )
- ret->superfamily = IP;
- }
- if (ret->superfamily == UNRESOLVED) {
- ret->error = (h_errno == HOST_NOT_FOUND ||
- h_errno == NO_DATA ||
- h_errno == NO_ADDRESS ? "Host does not exist" :
- h_errno == TRY_AGAIN ?
- "Temporary name service failure" :
- "gethostbyname: unknown error");
- strbuf_free(realhost);
- return ret;
- }
- /* This way we are always sure the h->h_name is valid :) */
- strbuf_clear(realhost);
- strbuf_catf(realhost, "%s", h->h_name);
- for (n = 0; h->h_addr_list[n]; n++);
- ret->addresses = snewn(n, unsigned long);
- ret->naddresses = n;
- for (n = 0; n < ret->naddresses; n++) {
- memcpy(&a, h->h_addr_list[n], sizeof(a));
- ret->addresses[n] = ntohl(a);
- }
- } else {
- /*
- * This must be a numeric IPv4 address because it caused a
- * success return from inet_addr.
- */
- ret->superfamily = IP;
- strbuf_clear(realhost);
- strbuf_catf(realhost, "%s", host);
- ret->addresses = snew(unsigned long);
- ret->naddresses = 1;
- ret->addresses[0] = ntohl(a);
- }
-#endif
- *canonicalname = strbuf_to_str(realhost);
- return ret;
-}
-
-SockAddr *sk_nonamelookup(const char *host)
-{
- SockAddr *ret = snew(SockAddr);
- ret->error = NULL;
- ret->superfamily = UNRESOLVED;
- strncpy(ret->hostname, host, lenof(ret->hostname));
- ret->hostname[lenof(ret->hostname)-1] = '\0';
-#ifndef NO_IPV6
- ret->ais = NULL;
-#else
- ret->addresses = NULL;
-#endif
- ret->refcount = 1;
- return ret;
-}
-
-static bool sk_nextaddr(SockAddr *addr, SockAddrStep *step)
-{
-#ifndef NO_IPV6
- if (step->ai && step->ai->ai_next) {
- step->ai = step->ai->ai_next;
- return true;
- } else
- return false;
-#else
- if (step->curraddr+1 < addr->naddresses) {
- step->curraddr++;
- return true;
- } else {
- return false;
- }
-#endif
-}
-
-void sk_getaddr(SockAddr *addr, char *buf, int buflen)
-{
- if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) {
- strncpy(buf, addr->hostname, buflen);
- buf[buflen-1] = '\0';
- } else {
-#ifndef NO_IPV6
- if (getnameinfo(addr->ais->ai_addr, addr->ais->ai_addrlen, buf, buflen,
- NULL, 0, NI_NUMERICHOST) != 0) {
- buf[0] = '\0';
- strncat(buf, "<unknown>", buflen - 1);
- }
-#else
- struct in_addr a;
- SockAddrStep step;
- START_STEP(addr, step);
- assert(SOCKADDR_FAMILY(addr, step) == AF_INET);
- a.s_addr = htonl(addr->addresses[0]);
- strncpy(buf, inet_ntoa(a), buflen);
- buf[buflen-1] = '\0';
-#endif
- }
-}
-
-/*
- * This constructs a SockAddr that points at one specific sub-address
- * of a parent SockAddr. The returned SockAddr does not own all its
- * own memory: it points into the old one's data structures, so it
- * MUST NOT be used after the old one is freed, and it MUST NOT be
- * passed to sk_addr_free. (The latter is why it's returned by value
- * rather than dynamically allocated - that should clue in anyone
- * writing a call to it that something is weird about it.)
- */
-static SockAddr sk_extractaddr_tmp(
- SockAddr *addr, const SockAddrStep *step)
-{
- SockAddr toret;
- toret = *addr; /* structure copy */
- toret.refcount = 1;
-
- if (addr->superfamily == IP) {
-#ifndef NO_IPV6
- toret.ais = step->ai;
-#else
- assert(SOCKADDR_FAMILY(addr, *step) == AF_INET);
- toret.addresses += step->curraddr;
-#endif
- }
-
- return toret;
-}
-
-bool sk_addr_needs_port(SockAddr *addr)
-{
- if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) {
- return false;
- } else {
- return true;
- }
-}
-
-bool sk_hostname_is_local(const char *name)
-{
- return !strcmp(name, "localhost") ||
- !strcmp(name, "::1") ||
- !strncmp(name, "127.", 4);
-}
-
-#define ipv4_is_loopback(addr) \
- (((addr).s_addr & htonl(0xff000000)) == htonl(0x7f000000))
-
-static bool sockaddr_is_loopback(struct sockaddr *sa)
-{
- union sockaddr_union *u = (union sockaddr_union *)sa;
- switch (u->sa.sa_family) {
- case AF_INET:
- return ipv4_is_loopback(u->sin.sin_addr);
-#ifndef NO_IPV6
- case AF_INET6:
- return IN6_IS_ADDR_LOOPBACK(&u->sin6.sin6_addr);
-#endif
- case AF_UNIX:
- return true;
- default:
- return false;
- }
-}
-
-bool sk_address_is_local(SockAddr *addr)
-{
- if (addr->superfamily == UNRESOLVED)
- return false; /* we don't know; assume not */
- else if (addr->superfamily == UNIX)
- return true;
- else {
-#ifndef NO_IPV6
- return sockaddr_is_loopback(addr->ais->ai_addr);
-#else
- struct in_addr a;
- SockAddrStep step;
- START_STEP(addr, step);
- assert(SOCKADDR_FAMILY(addr, step) == AF_INET);
- a.s_addr = htonl(addr->addresses[0]);
- return ipv4_is_loopback(a);
-#endif
- }
-}
-
-bool sk_address_is_special_local(SockAddr *addr)
-{
- return addr->superfamily == UNIX;
-}
-
-int sk_addrtype(SockAddr *addr)
-{
- SockAddrStep step;
- int family;
- START_STEP(addr, step);
- family = SOCKADDR_FAMILY(addr, step);
-
- return (family == AF_INET ? ADDRTYPE_IPV4 :
-#ifndef NO_IPV6
- family == AF_INET6 ? ADDRTYPE_IPV6 :
-#endif
- ADDRTYPE_NAME);
-}
-
-void sk_addrcopy(SockAddr *addr, char *buf)
-{
- SockAddrStep step;
- int family;
- START_STEP(addr, step);
- family = SOCKADDR_FAMILY(addr, step);
-
-#ifndef NO_IPV6
- if (family == AF_INET)
- memcpy(buf, &((struct sockaddr_in *)step.ai->ai_addr)->sin_addr,
- sizeof(struct in_addr));
- else if (family == AF_INET6)
- memcpy(buf, &((struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr,
- sizeof(struct in6_addr));
- else
- unreachable("bad address family in sk_addrcopy");
-#else
- struct in_addr a;
-
- assert(family == AF_INET);
- a.s_addr = htonl(addr->addresses[step.curraddr]);
- memcpy(buf, (char*) &a.s_addr, 4);
-#endif
-}
-
-void sk_addr_free(SockAddr *addr)
-{
- if (--addr->refcount > 0)
- return;
-#ifndef NO_IPV6
- if (addr->ais != NULL)
- freeaddrinfo(addr->ais);
-#else
- sfree(addr->addresses);
-#endif
- sfree(addr);
-}
-
-SockAddr *sk_addr_dup(SockAddr *addr)
-{
- addr->refcount++;
- return addr;
-}
-
-static Plug *sk_net_plug(Socket *sock, Plug *p)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
- Plug *ret = s->plug;
- if (p)
- s->plug = p;
- return ret;
-}
-
-static void sk_net_close(Socket *s);
-static size_t sk_net_write(Socket *s, const void *data, size_t len);
-static size_t sk_net_write_oob(Socket *s, const void *data, size_t len);
-static void sk_net_write_eof(Socket *s);
-static void sk_net_set_frozen(Socket *s, bool is_frozen);
-static SocketPeerInfo *sk_net_peer_info(Socket *s);
-static const char *sk_net_socket_error(Socket *s);
-
-static const SocketVtable NetSocket_sockvt = {
- .plug = sk_net_plug,
- .close = sk_net_close,
- .write = sk_net_write,
- .write_oob = sk_net_write_oob,
- .write_eof = sk_net_write_eof,
- .set_frozen = sk_net_set_frozen,
- .socket_error = sk_net_socket_error,
- .peer_info = sk_net_peer_info,
-};
-
-static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug)
-{
- int sockfd = ctx.i;
- NetSocket *ret;
-
- /*
- * Create NetSocket structure.
- */
- ret = snew(NetSocket);
- ret->sock.vt = &NetSocket_sockvt;
- ret->error = NULL;
- ret->plug = plug;
- bufchain_init(&ret->output_data);
- ret->writable = true; /* to start with */
- ret->sending_oob = 0;
- ret->frozen = true;
- ret->localhost_only = false; /* unused, but best init anyway */
- ret->pending_error = 0;
- ret->oobpending = false;
- ret->outgoingeof = EOF_NO;
- ret->incomingeof = false;
- ret->listener = false;
- ret->parent = ret->child = NULL;
- ret->addr = NULL;
- ret->connected = true;
-
- ret->s = sockfd;
-
- if (ret->s < 0) {
- ret->error = strerror(errno);
- return &ret->sock;
- }
-
- ret->oobinline = false;
-
- uxsel_tell(ret);
- add234(sktree, ret);
-
- return &ret->sock;
-}
-
-static int try_connect(NetSocket *sock)
-{
- int s;
- union sockaddr_union u;
- const union sockaddr_union *sa;
- int err = 0;
- short localport;
- int salen, family;
-
- /*
- * Remove the socket from the tree before we overwrite its
- * internal socket id, because that forms part of the tree's
- * sorting criterion. We'll add it back before exiting this
- * function, whether we changed anything or not.
- */
- del234(sktree, sock);
-
- if (sock->s >= 0)
- close(sock->s);
-
- {
- SockAddr thisaddr = sk_extractaddr_tmp(
- sock->addr, &sock->step);
- plug_log(sock->plug, PLUGLOG_CONNECT_TRYING,
- &thisaddr, sock->port, NULL, 0);
- }
-
- /*
- * Open socket.
- */
- family = SOCKADDR_FAMILY(sock->addr, sock->step);
- assert(family != AF_UNSPEC);
- s = socket(family, SOCK_STREAM, 0);
- sock->s = s;
-
- if (s < 0) {
- err = errno;
- goto ret;
- }
-
- cloexec(s);
-
- if (sock->oobinline) {
- int b = 1;
- if (setsockopt(s, SOL_SOCKET, SO_OOBINLINE,
- (void *) &b, sizeof(b)) < 0) {
- err = errno;
- close(s);
- goto ret;
- }
- }
-
- if (sock->nodelay && family != AF_UNIX) {
- int b = 1;
- if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY,
- (void *) &b, sizeof(b)) < 0) {
- err = errno;
- close(s);
- goto ret;
- }
- }
-
- if (sock->keepalive) {
- int b = 1;
- if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
- (void *) &b, sizeof(b)) < 0) {
- err = errno;
- close(s);
- goto ret;
- }
- }
-
- /*
- * Bind to local address.
- */
- if (sock->privport)
- localport = 1023; /* count from 1023 downwards */
- else
- localport = 0; /* just use port 0 (ie kernel picks) */
-
- /* BSD IP stacks need sockaddr_in zeroed before filling in */
- memset(&u,'\0',sizeof(u));
-
- /* We don't try to bind to a local address for UNIX domain sockets. (Why
- * do we bother doing the bind when localport == 0 anyway?) */
- if (family != AF_UNIX) {
- /* Loop round trying to bind */
- while (1) {
- int retcode;
-
-#ifndef NO_IPV6
- if (family == AF_INET6) {
- /* XXX use getaddrinfo to get a local address? */
- u.sin6.sin6_family = AF_INET6;
- u.sin6.sin6_addr = in6addr_any;
- u.sin6.sin6_port = htons(localport);
- retcode = bind(s, &u.sa, sizeof(u.sin6));
- } else
-#endif
- {
- assert(family == AF_INET);
- u.sin.sin_family = AF_INET;
- u.sin.sin_addr.s_addr = htonl(INADDR_ANY);
- u.sin.sin_port = htons(localport);
- retcode = bind(s, &u.sa, sizeof(u.sin));
- }
- if (retcode >= 0) {
- err = 0;
- break; /* done */
- } else {
- err = errno;
- if (err != EADDRINUSE) /* failed, for a bad reason */
- break;
- }
-
- if (localport == 0)
- break; /* we're only looping once */
- localport--;
- if (localport == 0)
- break; /* we might have got to the end */
- }
-
- if (err)
- goto ret;
- }
-
- /*
- * Connect to remote address.
- */
- switch(family) {
-#ifndef NO_IPV6
- case AF_INET:
- /* XXX would be better to have got getaddrinfo() to fill in the port. */
- ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port =
- htons(sock->port);
- sa = (const union sockaddr_union *)sock->step.ai->ai_addr;
- salen = sock->step.ai->ai_addrlen;
- break;
- case AF_INET6:
- ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port =
- htons(sock->port);
- sa = (const union sockaddr_union *)sock->step.ai->ai_addr;
- salen = sock->step.ai->ai_addrlen;
- break;
-#else
- case AF_INET:
- u.sin.sin_family = AF_INET;
- u.sin.sin_addr.s_addr = htonl(sock->addr->addresses[sock->step.curraddr]);
- u.sin.sin_port = htons((short) sock->port);
- sa = &u;
- salen = sizeof u.sin;
- break;
-#endif
- case AF_UNIX:
- assert(strlen(sock->addr->hostname) < sizeof u.su.sun_path);
- u.su.sun_family = AF_UNIX;
- strcpy(u.su.sun_path, sock->addr->hostname);
- sa = &u;
- salen = sizeof u.su;
- break;
-
- default:
- unreachable("unknown address family");
- exit(1); /* XXX: GCC doesn't understand assert() on some systems. */
- }
-
- nonblock(s);
-
- if ((connect(s, &(sa->sa), salen)) < 0) {
- if ( errno != EINPROGRESS ) {
- err = errno;
- goto ret;
- }
- } else {
- /*
- * If we _don't_ get EWOULDBLOCK, the connect has completed
- * and we should set the socket as connected and writable.
- */
- sock->connected = true;
- sock->writable = true;
-
- SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step);
- plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS,
- &thisaddr, sock->port, NULL, 0);
- }
-
- uxsel_tell(sock);
-
- ret:
-
- /*
- * No matter what happened, put the socket back in the tree.
- */
- add234(sktree, sock);
-
- if (err) {
- SockAddr thisaddr = sk_extractaddr_tmp(
- sock->addr, &sock->step);
- plug_log(sock->plug, PLUGLOG_CONNECT_FAILED,
- &thisaddr, sock->port, strerror(err), err);
- }
- return err;
-}
-
-Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline,
- bool nodelay, bool keepalive, Plug *plug)
-{
- NetSocket *ret;
- int 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 */
- ret->writable = false; /* to start with */
- ret->sending_oob = 0;
- ret->frozen = false;
- ret->localhost_only = false; /* unused, but best init anyway */
- ret->pending_error = 0;
- ret->parent = ret->child = NULL;
- ret->oobpending = false;
- ret->outgoingeof = EOF_NO;
- ret->incomingeof = false;
- ret->listener = false;
- ret->addr = addr;
- START_STEP(ret->addr, ret->step);
- ret->s = -1;
- ret->oobinline = oobinline;
- ret->nodelay = nodelay;
- ret->keepalive = keepalive;
- ret->privport = privport;
- ret->port = port;
-
- do {
- err = try_connect(ret);
- } while (err && sk_nextaddr(ret->addr, &ret->step));
-
- if (err)
- ret->error = strerror(err);
-
- return &ret->sock;
-}
-
-Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
- bool local_host_only, int orig_address_family)
-{
- int s;
-#ifndef NO_IPV6
- struct addrinfo hints, *ai = NULL;
- char portstr[6];
-#endif
- union sockaddr_union u;
- union sockaddr_union *addr;
- int addrlen;
- NetSocket *ret;
- int retcode;
- int address_family;
- int on = 1;
-
- /*
- * Create NetSocket structure.
- */
- ret = snew(NetSocket);
- ret->sock.vt = &NetSocket_sockvt;
- ret->error = NULL;
- ret->plug = plug;
- bufchain_init(&ret->output_data);
- ret->writable = false; /* to start with */
- ret->sending_oob = 0;
- ret->frozen = false;
- ret->localhost_only = local_host_only;
- ret->pending_error = 0;
- ret->parent = ret->child = NULL;
- ret->oobpending = false;
- ret->outgoingeof = EOF_NO;
- ret->incomingeof = false;
- ret->listener = true;
- ret->addr = NULL;
- ret->s = -1;
-
- /*
- * Translate address_family from platform-independent constants
- * into local reality.
- */
- address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET :
-#ifndef NO_IPV6
- orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 :
-#endif
- AF_UNSPEC);
-
-#ifndef NO_IPV6
- /* Let's default to IPv6.
- * If the stack doesn't support IPv6, we will fall back to IPv4. */
- if (address_family == AF_UNSPEC) address_family = AF_INET6;
-#else
- /* No other choice, default to IPv4 */
- if (address_family == AF_UNSPEC) address_family = AF_INET;
-#endif
-
- /*
- * Open socket.
- */
- s = socket(address_family, SOCK_STREAM, 0);
-
-#ifndef NO_IPV6
- /* If the host doesn't support IPv6 try fallback to IPv4. */
- if (s < 0 && address_family == AF_INET6) {
- address_family = AF_INET;
- s = socket(address_family, SOCK_STREAM, 0);
- }
-#endif
-
- if (s < 0) {
- ret->error = strerror(errno);
- return &ret->sock;
- }
-
- cloexec(s);
-
- ret->oobinline = false;
-
- if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
- (const char *)&on, sizeof(on)) < 0) {
- ret->error = strerror(errno);
- close(s);
- return &ret->sock;
- }
-
- retcode = -1;
- addr = NULL; addrlen = -1; /* placate optimiser */
-
- if (srcaddr != NULL) {
-#ifndef NO_IPV6
- hints.ai_flags = AI_NUMERICHOST;
- hints.ai_family = address_family;
- hints.ai_socktype = SOCK_STREAM;
- hints.ai_protocol = 0;
- hints.ai_addrlen = 0;
- hints.ai_addr = NULL;
- hints.ai_canonname = NULL;
- hints.ai_next = NULL;
- assert(port >= 0 && port <= 99999);
- sprintf(portstr, "%d", port);
- {
- char *trimmed_addr = host_strduptrim(srcaddr);
- retcode = getaddrinfo(trimmed_addr, portstr, &hints, &ai);
- sfree(trimmed_addr);
- }
- if (retcode == 0) {
- addr = (union sockaddr_union *)ai->ai_addr;
- addrlen = ai->ai_addrlen;
- }
-#else
- memset(&u,'\0',sizeof u);
- u.sin.sin_family = AF_INET;
- u.sin.sin_port = htons(port);
- u.sin.sin_addr.s_addr = inet_addr(srcaddr);
- if (u.sin.sin_addr.s_addr != (in_addr_t)(-1)) {
- /* Override localhost_only with specified listen addr. */
- ret->localhost_only = ipv4_is_loopback(u.sin.sin_addr);
- }
- addr = &u;
- addrlen = sizeof(u.sin);
- retcode = 0;
-#endif
- }
-
- if (retcode != 0) {
- memset(&u,'\0',sizeof u);
-#ifndef NO_IPV6
- if (address_family == AF_INET6) {
- u.sin6.sin6_family = AF_INET6;
- u.sin6.sin6_port = htons(port);
- if (local_host_only)
- u.sin6.sin6_addr = in6addr_loopback;
- else
- u.sin6.sin6_addr = in6addr_any;
- addr = &u;
- addrlen = sizeof(u.sin6);
- } else
-#endif
- {
- u.sin.sin_family = AF_INET;
- u.sin.sin_port = htons(port);
- if (local_host_only)
- u.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- else
- u.sin.sin_addr.s_addr = htonl(INADDR_ANY);
- addr = &u;
- addrlen = sizeof(u.sin);
- }
- }
-
- retcode = bind(s, &addr->sa, addrlen);
-
-#ifndef NO_IPV6
- if (ai)
- freeaddrinfo(ai);
-#endif
-
- if (retcode < 0) {
- close(s);
- ret->error = strerror(errno);
- return &ret->sock;
- }
-
- if (listen(s, SOMAXCONN) < 0) {
- close(s);
- ret->error = strerror(errno);
- return &ret->sock;
- }
-
-#ifndef NO_IPV6
- /*
- * If we were given ADDRTYPE_UNSPEC, we must also create an
- * IPv4 listening socket and link it to this one.
- */
- if (address_family == AF_INET6 && orig_address_family == ADDRTYPE_UNSPEC) {
- NetSocket *other;
-
- other = container_of(
- sk_newlistener(srcaddr, port, plug,
- local_host_only, ADDRTYPE_IPV4),
- NetSocket, sock);
-
- if (other) {
- if (!other->error) {
- other->parent = ret;
- ret->child = other;
- } else {
- /* If we couldn't create a listening socket on IPv4 as well
- * as IPv6, we must return an error overall. */
- close(s);
- sfree(ret);
- return &other->sock;
- }
- }
- }
-#endif
-
- ret->s = s;
-
- uxsel_tell(ret);
- add234(sktree, ret);
-
- return &ret->sock;
-}
-
-static void sk_net_close(Socket *sock)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
-
- if (s->child)
- sk_net_close(&s->child->sock);
-
- bufchain_clear(&s->output_data);
-
- del234(sktree, s);
- if (s->s >= 0) {
- uxsel_del(s->s);
- close(s->s);
- }
- if (s->addr)
- sk_addr_free(s->addr);
- delete_callbacks_for_context(s);
- sfree(s);
-}
-
-void *sk_getxdmdata(Socket *sock, int *lenp)
-{
- NetSocket *s;
- union sockaddr_union u;
- socklen_t addrlen;
- char *buf;
- static unsigned int unix_addr = 0xFFFFFFFF;
-
- /*
- * We must check that this socket really _is_ a NetSocket before
- * downcasting it.
- */
- if (sock->vt != &NetSocket_sockvt)
- return NULL; /* failure */
- s = container_of(sock, NetSocket, sock);
-
- addrlen = sizeof(u);
- if (getsockname(s->s, &u.sa, &addrlen) < 0)
- return NULL;
- switch(u.sa.sa_family) {
- case AF_INET:
- *lenp = 6;
- buf = snewn(*lenp, char);
- PUT_32BIT_MSB_FIRST(buf, ntohl(u.sin.sin_addr.s_addr));
- PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin.sin_port));
- break;
-#ifndef NO_IPV6
- case AF_INET6:
- *lenp = 6;
- buf = snewn(*lenp, char);
- if (IN6_IS_ADDR_V4MAPPED(&u.sin6.sin6_addr)) {
- memcpy(buf, u.sin6.sin6_addr.s6_addr + 12, 4);
- PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin6.sin6_port));
- } else
- /* This is stupid, but it's what XLib does. */
- memset(buf, 0, 6);
- break;
-#endif
- case AF_UNIX:
- *lenp = 6;
- buf = snewn(*lenp, char);
- PUT_32BIT_MSB_FIRST(buf, unix_addr--);
- PUT_16BIT_MSB_FIRST(buf+4, getpid());
- break;
-
- /* XXX IPV6 */
-
- default:
- return NULL;
- }
-
- return buf;
-}
-
-/*
- * 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, strerror(s->pending_error), s->pending_error, 0);
-}
-
-/*
- * The function which tries to send on a socket once it's deemed
- * writable.
- */
-void try_send(NetSocket *s)
-{
- while (s->sending_oob || bufchain_size(&s->output_data) > 0) {
- int nsent;
- int err;
- const void *data;
- size_t len;
- int urgentflag;
-
- if (s->sending_oob) {
- urgentflag = MSG_OOB;
- len = s->sending_oob;
- data = &s->oobdata;
- } else {
- urgentflag = 0;
- ptrlen bufdata = bufchain_prefix(&s->output_data);
- data = bufdata.ptr;
- len = bufdata.len;
- }
- nsent = send(s->s, data, len, urgentflag);
- noise_ultralight(NOISE_SOURCE_IOLEN, nsent);
- if (nsent <= 0) {
- err = (nsent < 0 ? errno : 0);
- if (err == EWOULDBLOCK) {
- /*
- * Perfectly normal: we've sent all we can for the moment.
- */
- s->writable = false;
- return;
- } else {
- /*
- * We unfortunately can't just call plug_closing(),
- * because it's quite likely that we're currently
- * _in_ a call from the code we'd be calling back
- * to, so we'd have to make half the SSH code
- * reentrant. Instead we flag a pending error on
- * the socket, to be dealt with (by calling
- * plug_closing()) at some suitable future moment.
- */
- s->pending_error = err;
- /*
- * Immediately cease selecting on this socket, so that
- * we don't tight-loop repeatedly trying to do
- * whatever it was that went wrong.
- */
- uxsel_tell(s);
- /*
- * Arrange to be called back from the top level to
- * deal with the error condition on this socket.
- */
- queue_toplevel_callback(socket_error_callback, s);
- return;
- }
- } else {
- if (s->sending_oob) {
- if (nsent < len) {
- memmove(s->oobdata, s->oobdata+nsent, len-nsent);
- s->sending_oob = len - nsent;
- } else {
- s->sending_oob = 0;
- }
- } else {
- bufchain_consume(&s->output_data, nsent);
- }
- }
- }
-
- /*
- * If we reach here, we've finished sending everything we might
- * have needed to send. Send EOF, if we need to.
- */
- if (s->outgoingeof == EOF_PENDING) {
- shutdown(s->s, SHUT_WR);
- s->outgoingeof = EOF_SENT;
- }
-
- /*
- * Also update the select status, because we don't need to select
- * for writing any more.
- */
- uxsel_tell(s);
-}
-
-static size_t sk_net_write(Socket *sock, const void *buf, size_t len)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
-
- assert(s->outgoingeof == EOF_NO);
-
- /*
- * Add the data to the buffer list on the socket.
- */
- bufchain_add(&s->output_data, buf, len);
-
- /*
- * Now try sending from the start of the buffer list.
- */
- if (s->writable)
- try_send(s);
-
- /*
- * Update the select() status to correctly reflect whether or
- * not we should be selecting for write.
- */
- uxsel_tell(s);
-
- return bufchain_size(&s->output_data);
-}
-
-static size_t sk_net_write_oob(Socket *sock, const void *buf, size_t len)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
-
- assert(s->outgoingeof == EOF_NO);
-
- /*
- * Replace the buffer list on the socket with the data.
- */
- bufchain_clear(&s->output_data);
- assert(len <= sizeof(s->oobdata));
- memcpy(s->oobdata, buf, len);
- s->sending_oob = len;
-
- /*
- * Now try sending from the start of the buffer list.
- */
- if (s->writable)
- try_send(s);
-
- /*
- * Update the select() status to correctly reflect whether or
- * not we should be selecting for write.
- */
- uxsel_tell(s);
-
- return s->sending_oob;
-}
-
-static void sk_net_write_eof(Socket *sock)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
-
- assert(s->outgoingeof == EOF_NO);
-
- /*
- * Mark the socket as pending outgoing EOF.
- */
- s->outgoingeof = EOF_PENDING;
-
- /*
- * Now try sending from the start of the buffer list.
- */
- if (s->writable)
- try_send(s);
-
- /*
- * Update the select() status to correctly reflect whether or
- * not we should be selecting for write.
- */
- uxsel_tell(s);
-}
-
-static void net_select_result(int fd, int event)
-{
- int ret;
- char buf[20480]; /* nice big buffer for plenty of speed */
- NetSocket *s;
- bool atmark = true;
-
- /* Find the Socket structure */
- s = find234(sktree, &fd, cmpforsearch);
- if (!s)
- return; /* boggle */
-
- noise_ultralight(NOISE_SOURCE_IOID, fd);
-
- switch (event) {
- case SELECT_X: /* exceptional */
- if (!s->oobinline) {
- /*
- * On a non-oobinline socket, this indicates that we
- * can immediately perform an OOB read and get back OOB
- * data, which we will send to the back end with
- * type==2 (urgent data).
- */
- ret = recv(s->s, buf, sizeof(buf), MSG_OOB);
- noise_ultralight(NOISE_SOURCE_IOLEN, ret);
- if (ret <= 0) {
- plug_closing(s->plug,
- ret == 0 ? "Internal networking trouble" :
- strerror(errno), errno, 0);
- } else {
- /*
- * Receiving actual data on a socket means we can
- * stop falling back through the candidate
- * addresses to connect to.
- */
- if (s->addr) {
- sk_addr_free(s->addr);
- s->addr = NULL;
- }
- plug_receive(s->plug, 2, buf, ret);
- }
- break;
- }
-
- /*
- * If we reach here, this is an oobinline socket, which
- * means we should set s->oobpending and then deal with it
- * when we get called for the readability event (which
- * should also occur).
- */
- s->oobpending = true;
- break;
- case SELECT_R: /* readable; also acceptance */
- if (s->listener) {
- /*
- * On a listening socket, the readability event means a
- * connection is ready to be accepted.
- */
- union sockaddr_union su;
- socklen_t addrlen = sizeof(su);
- accept_ctx_t actx;
- int t; /* socket of connection */
-
- memset(&su, 0, addrlen);
- t = accept(s->s, &su.sa, &addrlen);
- if (t < 0) {
- break;
- }
-
- nonblock(t);
- actx.i = t;
-
- if ((!s->addr || s->addr->superfamily != UNIX) &&
- s->localhost_only && !sockaddr_is_loopback(&su.sa)) {
- close(t); /* someone let nonlocal through?! */
- } else if (plug_accepting(s->plug, sk_net_accept, actx)) {
- close(t); /* denied or error */
- }
- break;
- }
-
- /*
- * If we reach here, this is not a listening socket, so
- * readability really means readability.
- */
-
- /* In the case the socket is still frozen, we don't even bother */
- if (s->frozen)
- break;
-
- /*
- * We have received data on the socket. For an oobinline
- * socket, this might be data _before_ an urgent pointer,
- * in which case we send it to the back end with type==1
- * (data prior to urgent).
- */
- if (s->oobinline && s->oobpending) {
- int atmark_from_ioctl;
- if (ioctl(s->s, SIOCATMARK, &atmark_from_ioctl) == 0) {
- atmark = atmark_from_ioctl;
- if (atmark)
- s->oobpending = false; /* clear this indicator */
- }
- } else
- atmark = true;
-
- ret = recv(s->s, buf, s->oobpending ? 1 : sizeof(buf), 0);
- noise_ultralight(NOISE_SOURCE_IOLEN, ret);
- if (ret < 0) {
- if (errno == EWOULDBLOCK) {
- break;
- }
- }
- if (ret < 0) {
- plug_closing(s->plug, strerror(errno), errno, 0);
- } else if (0 == ret) {
- s->incomingeof = true; /* stop trying to read now */
- uxsel_tell(s);
- plug_closing(s->plug, NULL, 0, 0);
- } else {
- /*
- * Receiving actual data on a socket means we can
- * stop falling back through the candidate
- * addresses to connect to.
- */
- if (s->addr) {
- sk_addr_free(s->addr);
- s->addr = NULL;
- }
- plug_receive(s->plug, atmark ? 0 : 1, buf, ret);
- }
- break;
- case SELECT_W: /* writable */
- if (!s->connected) {
- /*
- * select/poll reports a socket as _writable_ when an
- * asynchronous connect() attempt either completes or
- * fails. So first we must find out which.
- */
- {
- int err;
- socklen_t errlen = sizeof(err);
- char *errmsg = NULL;
- if (getsockopt(s->s, SOL_SOCKET, SO_ERROR, &err, &errlen)<0) {
- errmsg = dupprintf("getsockopt(SO_ERROR): %s",
- strerror(errno));
- err = errno; /* got to put something in here */
- } else if (err != 0) {
- errmsg = dupstr(strerror(err));
- }
- if (errmsg) {
- /*
- * The asynchronous connection attempt failed.
- * Report the problem via plug_log, and try again
- * with the next candidate address, if we have
- * more than one.
- */
- SockAddr thisaddr;
- assert(s->addr);
-
- thisaddr = sk_extractaddr_tmp(s->addr, &s->step);
- plug_log(s->plug, PLUGLOG_CONNECT_FAILED,
- &thisaddr, s->port, errmsg, err);
-
- while (err && s->addr && sk_nextaddr(s->addr, &s->step)) {
- err = try_connect(s);
- }
- if (err) {
- plug_closing(s->plug, strerror(err), err, 0);
- return; /* socket is now presumably defunct */
- }
- if (!s->connected)
- return; /* another async attempt in progress */
- } else {
- /*
- * The connection attempt succeeded.
- */
- SockAddr thisaddr = sk_extractaddr_tmp(s->addr, &s->step);
- plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS,
- &thisaddr, s->port, NULL, 0);
- }
- }
-
- /*
- * If we get here, we've managed to make a connection.
- */
- if (s->addr) {
- sk_addr_free(s->addr);
- s->addr = NULL;
- }
- s->connected = true;
- s->writable = true;
- uxsel_tell(s);
- } else {
- size_t bufsize_before, bufsize_after;
- s->writable = true;
- bufsize_before = s->sending_oob + bufchain_size(&s->output_data);
- try_send(s);
- bufsize_after = s->sending_oob + bufchain_size(&s->output_data);
- if (bufsize_after < bufsize_before)
- plug_sent(s->plug, bufsize_after);
- }
- break;
- }
-}
-
-/*
- * Special error values are returned from sk_namelookup and sk_new
- * if there's a problem. These functions extract an error message,
- * or return NULL if there's no problem.
- */
-const char *sk_addr_error(SockAddr *addr)
-{
- return addr->error;
-}
-static const char *sk_net_socket_error(Socket *sock)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
- return s->error;
-}
-
-static void sk_net_set_frozen(Socket *sock, bool is_frozen)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
- if (s->frozen == is_frozen)
- return;
- s->frozen = is_frozen;
- uxsel_tell(s);
-}
-
-static SocketPeerInfo *sk_net_peer_info(Socket *sock)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
- union sockaddr_union addr;
- socklen_t addrlen = sizeof(addr);
-#ifndef NO_IPV6
- char buf[INET6_ADDRSTRLEN];
-#endif
- SocketPeerInfo *pi;
-
- if (getpeername(s->s, &addr.sa, &addrlen) < 0)
- return NULL;
-
- pi = snew(SocketPeerInfo);
- pi->addressfamily = ADDRTYPE_UNSPEC;
- pi->addr_text = NULL;
- pi->port = -1;
- pi->log_text = NULL;
-
- if (addr.storage.ss_family == AF_INET) {
- pi->addressfamily = ADDRTYPE_IPV4;
- memcpy(pi->addr_bin.ipv4, &addr.sin.sin_addr, 4);
- pi->port = ntohs(addr.sin.sin_port);
- pi->addr_text = dupstr(inet_ntoa(addr.sin.sin_addr));
- pi->log_text = dupprintf("%s:%d", pi->addr_text, pi->port);
-
-#ifndef NO_IPV6
- } else if (addr.storage.ss_family == AF_INET6) {
- pi->addressfamily = ADDRTYPE_IPV6;
- memcpy(pi->addr_bin.ipv6, &addr.sin6.sin6_addr, 16);
- pi->port = ntohs(addr.sin6.sin6_port);
- pi->addr_text = dupstr(
- inet_ntop(AF_INET6, &addr.sin6.sin6_addr, buf, sizeof(buf)));
- pi->log_text = dupprintf("[%s]:%d", pi->addr_text, pi->port);
-#endif
-
- } else if (addr.storage.ss_family == AF_UNIX) {
- pi->addressfamily = ADDRTYPE_LOCAL;
-
- /*
- * For Unix sockets, the source address is unlikely to be
- * helpful, so we leave addr_txt NULL (and we certainly can't
- * fill in port, obviously). Instead, we try SO_PEERCRED and
- * try to get the source pid, and put that in the log text.
- */
- int pid, uid, gid;
- if (so_peercred(s->s, &pid, &uid, &gid)) {
- char uidbuf[64], gidbuf[64];
- sprintf(uidbuf, "%d", uid);
- sprintf(gidbuf, "%d", gid);
- struct passwd *pw = getpwuid(uid);
- struct group *gr = getgrgid(gid);
- pi->log_text = dupprintf("pid %d (%s:%s)", pid,
- pw ? pw->pw_name : uidbuf,
- gr ? gr->gr_name : gidbuf);
- }
- } else {
- sfree(pi);
- return NULL;
- }
-
- return pi;
-}
-
-int sk_net_get_fd(Socket *sock)
-{
- /* This function is not fully general: it only works on NetSocket */
- if (sock->vt != &NetSocket_sockvt)
- return -1; /* failure */
- NetSocket *s = container_of(sock, NetSocket, sock);
- return s->s;
-}
-
-static void uxsel_tell(NetSocket *s)
-{
- int rwx = 0;
- if (!s->pending_error) {
- if (s->listener) {
- rwx |= SELECT_R; /* read == accept */
- } else {
- if (!s->connected)
- rwx |= SELECT_W; /* write == connect */
- if (s->connected && !s->frozen && !s->incomingeof)
- rwx |= SELECT_R | SELECT_X;
- if (bufchain_size(&s->output_data))
- rwx |= SELECT_W;
- }
- }
- uxsel_set(s->s, rwx, net_select_result);
-}
-
-int net_service_lookup(char *service)
-{
- struct servent *se;
- se = getservbyname(service, NULL);
- if (se != NULL)
- return ntohs(se->s_port);
- else
- return 0;
-}
-
-char *get_hostname(void)
-{
- size_t size = 0;
- char *hostname = NULL;
- do {
- sgrowarray(hostname, size, size);
- if ((gethostname(hostname, size) < 0) && (errno != ENAMETOOLONG)) {
- sfree(hostname);
- hostname = NULL;
- break;
- }
- } while (strlen(hostname) >= size-1);
- return hostname;
-}
-
-SockAddr *platform_get_x11_unix_address(const char *sockpath, int displaynum)
-{
- SockAddr *ret = snew(SockAddr);
- int n;
-
- memset(ret, 0, sizeof *ret);
- ret->superfamily = UNIX;
- /*
- * In special circumstances (notably Mac OS X Leopard), we'll
- * have been passed an explicit Unix socket path.
- */
- if (sockpath) {
- n = snprintf(ret->hostname, sizeof ret->hostname,
- "%s", sockpath);
- } else {
- n = snprintf(ret->hostname, sizeof ret->hostname,
- "%s%d", X11_UNIX_PATH, displaynum);
- }
-
- if (n < 0)
- ret->error = "snprintf failed";
- else if (n >= sizeof ret->hostname)
- ret->error = "X11 UNIX name too long";
-
-#ifndef NO_IPV6
- ret->ais = NULL;
-#else
- ret->addresses = NULL;
- ret->naddresses = 0;
-#endif
- ret->refcount = 1;
- return ret;
-}
-
-SockAddr *unix_sock_addr(const char *path)
-{
- SockAddr *ret = snew(SockAddr);
- int n;
-
- memset(ret, 0, sizeof *ret);
- ret->superfamily = UNIX;
- n = snprintf(ret->hostname, sizeof ret->hostname, "%s", path);
-
- if (n < 0)
- ret->error = "snprintf failed";
- else if (n >= sizeof ret->hostname ||
- n >= sizeof(((struct sockaddr_un *)0)->sun_path))
- ret->error = "socket pathname too long";
-
-#ifndef NO_IPV6
- ret->ais = NULL;
-#else
- ret->addresses = NULL;
- ret->naddresses = 0;
-#endif
- ret->refcount = 1;
- return ret;
-}
-
-Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug)
-{
- int s;
- union sockaddr_union u;
- union sockaddr_union *addr;
- int addrlen;
- NetSocket *ret;
- int retcode;
-
- /*
- * Create NetSocket structure.
- */
- ret = snew(NetSocket);
- ret->sock.vt = &NetSocket_sockvt;
- ret->error = NULL;
- ret->plug = plug;
- bufchain_init(&ret->output_data);
- ret->writable = false; /* to start with */
- ret->sending_oob = 0;
- ret->frozen = false;
- ret->localhost_only = true;
- ret->pending_error = 0;
- ret->parent = ret->child = NULL;
- ret->oobpending = false;
- ret->outgoingeof = EOF_NO;
- ret->incomingeof = false;
- ret->listener = true;
- ret->addr = listenaddr;
- ret->s = -1;
-
- assert(listenaddr->superfamily == UNIX);
-
- /*
- * Open socket.
- */
- s = socket(AF_UNIX, SOCK_STREAM, 0);
- if (s < 0) {
- ret->error = strerror(errno);
- return &ret->sock;
- }
-
- cloexec(s);
-
- ret->oobinline = false;
-
- memset(&u, '\0', sizeof(u));
- u.su.sun_family = AF_UNIX;
-#if __GNUC__ >= 8
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wstringop-truncation"
-#endif // __GNUC__ >= 8
- strncpy(u.su.sun_path, listenaddr->hostname, sizeof(u.su.sun_path)-1);
-#if __GNUC__ >= 8
-# pragma GCC diagnostic pop
-#endif // __GNUC__ >= 8
- addr = &u;
- addrlen = sizeof(u.su);
-
- if (unlink(u.su.sun_path) < 0 && errno != ENOENT) {
- close(s);
- ret->error = strerror(errno);
- return &ret->sock;
- }
-
- retcode = bind(s, &addr->sa, addrlen);
- if (retcode < 0) {
- close(s);
- ret->error = strerror(errno);
- return &ret->sock;
- }
-
- if (listen(s, SOMAXCONN) < 0) {
- close(s);
- ret->error = strerror(errno);
- return &ret->sock;
- }
-
- ret->s = s;
-
- uxsel_tell(ret);
- add234(sktree, ret);
-
- return &ret->sock;
-}
diff --git a/unix/uxnogtk.c b/unix/uxnogtk.c
deleted file mode 100644
index c9028ebf..00000000
--- a/unix/uxnogtk.c
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * uxnogtk.c: link into non-GUI Unix programs so that they can tell
- * buildinfo about a lack of GTK.
- */
-
-#include "putty.h"
-
-char *buildinfo_gtk_version(void)
-{
- return NULL;
-}
diff --git a/unix/uxnoise.c b/unix/uxnoise.c
deleted file mode 100644
index 0fbf8c4d..00000000
--- a/unix/uxnoise.c
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Noise generation for PuTTY's cryptographic random number
- * generator.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include <fcntl.h>
-#include <unistd.h>
-#include <sys/time.h>
-#include <sys/resource.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "storage.h"
-
-static bool read_dev_urandom(char *buf, int len)
-{
- int fd;
- int ngot, ret;
-
- fd = open("/dev/urandom", O_RDONLY);
- if (fd < 0)
- return false;
-
- ngot = 0;
- while (ngot < len) {
- ret = read(fd, buf+ngot, len-ngot);
- if (ret < 0) {
- close(fd);
- return false;
- }
- ngot += ret;
- }
-
- close(fd);
-
- return true;
-}
-
-/*
- * This function is called once, at PuTTY startup. It will do some
- * slightly silly things such as fetching an entire process listing
- * and scanning /tmp, load the saved random seed from disk, and
- * also read 32 bytes out of /dev/urandom.
- */
-
-void noise_get_heavy(void (*func) (void *, int))
-{
- char buf[512];
- FILE *fp;
- int ret;
- bool got_dev_urandom = false;
-
- if (read_dev_urandom(buf, 32)) {
- got_dev_urandom = true;
- func(buf, 32);
- }
-
- fp = popen("ps -axu 2>/dev/null", "r");
- if (fp) {
- while ( (ret = fread(buf, 1, sizeof(buf), fp)) > 0)
- func(buf, ret);
- pclose(fp);
- } else if (!got_dev_urandom) {
- fprintf(stderr, "popen: %s\n"
- "Unable to access fallback entropy source\n", strerror(errno));
- exit(1);
- }
-
- fp = popen("ls -al /tmp 2>/dev/null", "r");
- if (fp) {
- while ( (ret = fread(buf, 1, sizeof(buf), fp)) > 0)
- func(buf, ret);
- pclose(fp);
- } else if (!got_dev_urandom) {
- fprintf(stderr, "popen: %s\n"
- "Unable to access fallback entropy source\n", strerror(errno));
- exit(1);
- }
-
- read_random_seed(func);
-}
-
-/*
- * This function is called on a timer, and grabs as much changeable
- * system data as it can quickly get its hands on.
- */
-void noise_regular(void)
-{
- int fd;
- int ret;
- char buf[512];
- struct rusage rusage;
-
- if ((fd = open("/proc/meminfo", O_RDONLY)) >= 0) {
- while ( (ret = read(fd, buf, sizeof(buf))) > 0)
- random_add_noise(NOISE_SOURCE_MEMINFO, buf, ret);
- close(fd);
- }
- if ((fd = open("/proc/stat", O_RDONLY)) >= 0) {
- while ( (ret = read(fd, buf, sizeof(buf))) > 0)
- random_add_noise(NOISE_SOURCE_STAT, buf, ret);
- close(fd);
- }
- getrusage(RUSAGE_SELF, &rusage);
- random_add_noise(NOISE_SOURCE_RUSAGE, &rusage, sizeof(rusage));
-}
-
-/*
- * This function is called on every keypress or mouse move, and
- * will add the current time to the noise pool. It gets the scan
- * code or mouse position passed in, and adds that too.
- */
-void noise_ultralight(NoiseSourceId id, unsigned long data)
-{
- struct timeval tv;
- gettimeofday(&tv, NULL);
- random_add_noise(NOISE_SOURCE_TIME, &tv, sizeof(tv));
- random_add_noise(id, &data, sizeof(data));
-}
-
-uint64_t prng_reseed_time_ms(void)
-{
- struct timeval tv;
- gettimeofday(&tv, NULL);
- return tv.tv_sec * 1000 + tv.tv_usec / 1000;
-}
diff --git a/unix/uxpeer.c b/unix/uxpeer.c
deleted file mode 100644
index 4ad26322..00000000
--- a/unix/uxpeer.c
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Unix: wrapper for getsockopt(SO_PEERCRED), conditionalised on
- * appropriate autoconfery.
- */
-
-#ifdef HAVE_CONFIG_H
-# include "uxconfig.h" /* leading space prevents mkfiles.pl trying to follow */
-#endif
-
-#ifdef HAVE_SO_PEERCRED
-#define _GNU_SOURCE
-#include <features.h>
-#endif
-
-#include <sys/socket.h>
-
-#include "putty.h"
-
-bool so_peercred(int fd, int *pid, int *uid, int *gid)
-{
-#ifdef HAVE_SO_PEERCRED
- struct ucred cr;
- socklen_t crlen = sizeof(cr);
- if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &crlen) == 0) {
- *pid = cr.pid;
- *uid = cr.uid;
- *gid = cr.gid;
- return true;
- }
-#endif
- return false;
-}
diff --git a/unix/uxpgnt.c b/unix/uxpgnt.c
deleted file mode 100644
index 21087d63..00000000
--- a/unix/uxpgnt.c
+++ /dev/null
@@ -1,1540 +0,0 @@
-/*
- * Unix Pageant, more or less similar to ssh-agent.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <assert.h>
-#include <signal.h>
-#include <ctype.h>
-
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <termios.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "misc.h"
-#include "pageant.h"
-
-void cmdline_error(const char *fmt, ...)
-{
- va_list ap;
- va_start(ap, fmt);
- console_print_error_msg_fmt_v("pageant", fmt, ap);
- va_end(ap);
- exit(1);
-}
-
-static void setup_sigchld_handler(void);
-
-typedef enum RuntimePromptType {
- RTPROMPT_UNAVAILABLE,
- RTPROMPT_DEBUG,
- RTPROMPT_GUI,
-} RuntimePromptType;
-
-static const char *progname;
-
-struct uxpgnt_client {
- FILE *logfp;
- strbuf *prompt_buf;
- RuntimePromptType prompt_type;
- bool prompt_active;
- PageantClientDialogId *dlgid;
- int passphrase_fd;
- int termination_pid;
-
- PageantListenerClient plc;
-};
-
-static void uxpgnt_log(PageantListenerClient *plc, const char *fmt, va_list ap)
-{
- struct uxpgnt_client *upc = container_of(plc, struct uxpgnt_client, plc);
-
- if (!upc->logfp)
- return;
-
- fprintf(upc->logfp, "pageant: ");
- vfprintf(upc->logfp, fmt, ap);
- fprintf(upc->logfp, "\n");
-}
-
-static int make_pipe_to_askpass(const char *msg)
-{
- int pipefds[2];
-
- setup_sigchld_handler();
-
- if (pipe(pipefds) < 0)
- return -1;
-
- pid_t pid = fork();
- if (pid < 0) {
- close(pipefds[0]);
- close(pipefds[1]);
- return -1;
- }
-
- if (pid == 0) {
- const char *args[5] = {
- progname, "--gui-prompt", "--askpass", msg, NULL
- };
-
- dup2(pipefds[1], 1);
- cloexec(pipefds[0]);
- cloexec(pipefds[1]);
-
- /*
- * See comment in fork_and_exec_self() in gtkmain.c.
- */
- execv("/proc/self/exe", (char **)args);
- execvp(progname, (char **)args);
- perror("exec");
- _exit(127);
- }
-
- close(pipefds[1]);
- return pipefds[0];
-}
-
-static bool uxpgnt_ask_passphrase(
- PageantListenerClient *plc, PageantClientDialogId *dlgid,
- const char *comment)
-{
- struct uxpgnt_client *upc = container_of(plc, struct uxpgnt_client, plc);
-
- assert(!upc->dlgid); /* Pageant core should be serialising requests */
-
- char *msg = dupprintf(
- "A client of Pageant wants to use the following encrypted key:\n"
- "%s\n"
- "If you intended this, enter the passphrase to decrypt the key.",
- comment);
-
- switch (upc->prompt_type) {
- case RTPROMPT_UNAVAILABLE:
- sfree(msg);
- return false;
-
- case RTPROMPT_GUI:
- upc->passphrase_fd = make_pipe_to_askpass(msg);
- sfree(msg);
- if (upc->passphrase_fd < 0)
- return false; /* something went wrong */
- break;
-
- case RTPROMPT_DEBUG:
- fprintf(upc->logfp, "pageant passphrase request: %s\n", msg);
- sfree(msg);
- break;
- }
-
- upc->prompt_active = true;
- upc->dlgid = dlgid;
- return true;
-}
-
-static void passphrase_done(struct uxpgnt_client *upc, bool success)
-{
- PageantClientDialogId *dlgid = upc->dlgid;
- upc->dlgid = NULL;
- upc->prompt_active = false;
-
- if (upc->logfp)
- fprintf(upc->logfp, "pageant passphrase response: %s\n",
- success ? "success" : "failure");
-
- if (success)
- pageant_passphrase_request_success(
- dlgid, ptrlen_from_strbuf(upc->prompt_buf));
- else
- pageant_passphrase_request_refused(dlgid);
-
- strbuf_free(upc->prompt_buf);
- upc->prompt_buf = strbuf_new_nm();
-}
-
-static const PageantListenerClientVtable uxpgnt_vtable = {
- .log = uxpgnt_log,
- .ask_passphrase = uxpgnt_ask_passphrase,
-};
-
-/*
- * More stubs.
- */
-void random_save_seed(void) {}
-void random_destroy_seed(void) {}
-char *platform_default_s(const char *name) { return NULL; }
-bool platform_default_b(const char *name, bool def) { return def; }
-int platform_default_i(const char *name, int def) { return def; }
-FontSpec *platform_default_fontspec(const char *name) { return fontspec_new(""); }
-Filename *platform_default_filename(const char *name) { return filename_from_str(""); }
-char *x_get_default(const char *key) { return NULL; }
-
-/*
- * Short description of parameters.
- */
-static void usage(void)
-{
- printf("Pageant: SSH agent\n");
- printf("%s\n", ver);
- printf("Usage: pageant <lifetime> [[--encrypted] key files]\n");
- printf(" pageant [[--encrypted] key files] --exec <command> [args]\n");
- printf(" pageant -a [--encrypted] [key files]\n");
- printf(" pageant -d [key identifiers]\n");
- printf(" pageant -D\n");
- printf(" pageant -r [key identifiers]\n");
- printf(" pageant -R\n");
- printf(" pageant --public [key identifiers]\n");
- printf(" pageant ( --public-openssh | -L ) [key identifiers]\n");
- printf(" pageant -l [-E fptype]\n");
- printf("Lifetime options, for running Pageant as an agent:\n");
- printf(" -X run with the lifetime of the X server\n");
- printf(" -T run with the lifetime of the controlling tty\n");
- printf(" --permanent run permanently\n");
- printf(" --debug run in debugging mode, without forking\n");
- printf(" --exec <command> run with the lifetime of that command\n");
- printf("Client options, for talking to an existing agent:\n");
- printf(" -a add key(s) to the existing agent\n");
- printf(" -l list currently loaded key fingerprints and comments\n");
- printf(" --public print public keys in RFC 4716 format\n");
- printf(" --public-openssh, -L print public keys in OpenSSH format\n");
- printf(" -d delete key(s) from the agent\n");
- printf(" -D delete all keys from the agent\n");
- printf(" -r re-encrypt keys in the agent (forget cleartext\n");
- printf(" -R re-encrypt all possible keys in the agent\n");
- printf("Other options:\n");
- printf(" -v verbose mode (in agent mode)\n");
- printf(" -s -c force POSIX or C shell syntax (in agent mode)\n");
- printf(" --symlink path create symlink to socket (in agent mode)\n");
- printf(" --encrypted when adding keys, don't decrypt\n");
- printf(" -E alg, --fptype alg fingerprint type for -l (sha256, md5)\n");
- printf(" --tty-prompt force tty-based passphrase prompt\n");
- printf(" --gui-prompt force GUI-based passphrase prompt\n");
- printf(" --askpass <prompt> behave like a standalone askpass program\n");
- exit(1);
-}
-
-static void version(void)
-{
- char *buildinfo_text = buildinfo("\n");
- printf("pageant: %s\n%s\n", ver, buildinfo_text);
- sfree(buildinfo_text);
- exit(0);
-}
-
-void keylist_update(void)
-{
- /* Nothing needs doing in Unix Pageant */
-}
-
-#define PAGEANT_DIR_PREFIX "/tmp/pageant"
-
-const char *const appname = "Pageant";
-
-static bool time_to_die = false;
-
-/* Stub functions to permit linking against x11fwd.c. These never get
- * used, because in LIFE_X11 mode we connect to the X server using a
- * straightforward Socket and don't try to create an ersatz SSH
- * forwarding too. */
-void chan_remotely_opened_confirmation(Channel *chan) { }
-void chan_remotely_opened_failure(Channel *chan, const char *err) { }
-bool chan_default_want_close(Channel *chan, bool s, bool r) { return false; }
-bool chan_no_exit_status(Channel *ch, int s) { return false; }
-bool chan_no_exit_signal(Channel *ch, ptrlen s, bool c, ptrlen m)
-{ return false; }
-bool chan_no_exit_signal_numeric(Channel *ch, int s, bool c, ptrlen m)
-{ 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) {}
-
-/*
- * These functions are part of the plug for our connection to the X
- * display, so they do get called. They needn't actually do anything,
- * except that x11_closing has to signal back to the main loop that
- * it's time to terminate.
- */
-static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port,
- const char *error_msg, int error_code) {}
-static void x11_receive(Plug *plug, int urgent, const char *data, size_t len) {}
-static void x11_sent(Plug *plug, size_t bufsize) {}
-static void x11_closing(Plug *plug, const char *error_msg, int error_code,
- bool calling_back)
-{
- time_to_die = true;
-}
-struct X11Connection {
- Plug plug;
-};
-
-static char *socketname;
-static enum { SHELL_AUTO, SHELL_SH, SHELL_CSH } shell_type = SHELL_AUTO;
-void pageant_print_env(int pid)
-{
- if (shell_type == SHELL_AUTO) {
- /* Same policy as OpenSSH: if $SHELL ends in "csh" then assume
- * it's csh-shaped. */
- const char *shell = getenv("SHELL");
- if (shell && strlen(shell) >= 3 &&
- !strcmp(shell + strlen(shell) - 3, "csh"))
- shell_type = SHELL_CSH;
- else
- shell_type = SHELL_SH;
- }
-
- /*
- * These shell snippets could usefully pay some attention to
- * escaping of interesting characters. I don't think it causes a
- * problem at the moment, because the pathnames we use are so
- * utterly boring, but it's a lurking bug waiting to happen once
- * a bit more flexibility turns up.
- */
-
- switch (shell_type) {
- case SHELL_SH:
- printf("SSH_AUTH_SOCK=%s; export SSH_AUTH_SOCK;\n"
- "SSH_AGENT_PID=%d; export SSH_AGENT_PID;\n",
- socketname, pid);
- break;
- case SHELL_CSH:
- printf("setenv SSH_AUTH_SOCK %s;\n"
- "setenv SSH_AGENT_PID %d;\n",
- socketname, pid);
- break;
- case SHELL_AUTO:
- unreachable("SHELL_AUTO should have been eliminated by now");
- break;
- }
-}
-
-void pageant_fork_and_print_env(bool retain_tty)
-{
- pid_t pid = fork();
- if (pid == -1) {
- perror("fork");
- exit(1);
- } else if (pid != 0) {
- pageant_print_env(pid);
- exit(0);
- }
-
- /*
- * Having forked off, we now daemonise ourselves as best we can.
- * It's good practice in general to setsid() ourself out of any
- * process group we didn't want to be part of, and to chdir("/")
- * to avoid holding any directories open that we don't need in
- * case someone wants to umount them; also, we should definitely
- * close standard output (because it will very likely be pointing
- * at a pipe from which some parent process is trying to read our
- * environment variable dump, so if we hold open another copy of
- * it then that process will never finish reading). We close
- * standard input too on general principles, but not standard
- * error, since we might need to shout a panicky error message
- * down that one.
- */
- if (chdir("/") < 0) {
- /* should there be an error condition, nothing we can do about
- * it anyway */
- }
- close(0);
- close(1);
- if (retain_tty) {
- /* Get out of our previous process group, to avoid being
- * blasted by passing signals. But keep our controlling tty,
- * so we can keep checking to see if we still have one. */
- setpgrp();
- } else {
- /* Do that, but also leave our entire session and detach from
- * the controlling tty (if any). */
- setsid();
- }
-}
-
-static int signalpipe[2] = { -1, -1 };
-
-static void sigchld(int signum)
-{
- if (write(signalpipe[1], "x", 1) <= 0)
- /* not much we can do about it */;
-}
-
-static void setup_sigchld_handler(void)
-{
- if (signalpipe[0] >= 0)
- return;
-
- /*
- * Set up the pipe we'll use to tell us about SIGCHLD.
- */
- if (pipe(signalpipe) < 0) {
- perror("pipe");
- exit(1);
- }
- putty_signal(SIGCHLD, sigchld);
-}
-
-#define TTY_LIFE_POLL_INTERVAL (TICKSPERSEC * 30)
-static void *dummy_timer_ctx;
-static void tty_life_timer(void *ctx, unsigned long now)
-{
- schedule_timer(TTY_LIFE_POLL_INTERVAL, tty_life_timer, &dummy_timer_ctx);
-}
-
-typedef enum {
- KEYACT_AGENT_LOAD,
- KEYACT_AGENT_LOAD_ENCRYPTED,
- KEYACT_CLIENT_BASE,
- KEYACT_CLIENT_ADD = KEYACT_CLIENT_BASE,
- KEYACT_CLIENT_ADD_ENCRYPTED,
- KEYACT_CLIENT_DEL,
- KEYACT_CLIENT_DEL_ALL,
- KEYACT_CLIENT_LIST,
- KEYACT_CLIENT_PUBLIC_OPENSSH,
- KEYACT_CLIENT_PUBLIC,
- KEYACT_CLIENT_SIGN,
- KEYACT_CLIENT_REENCRYPT,
- KEYACT_CLIENT_REENCRYPT_ALL,
-} keyact;
-struct cmdline_key_action {
- struct cmdline_key_action *next;
- keyact action;
- const char *filename;
-};
-
-bool is_agent_action(keyact action)
-{
- return action < KEYACT_CLIENT_BASE;
-}
-
-static struct cmdline_key_action *keyact_head = NULL, *keyact_tail = NULL;
-static uint32_t sign_flags = 0;
-
-void add_keyact(keyact action, const char *filename)
-{
- struct cmdline_key_action *a = snew(struct cmdline_key_action);
- a->action = action;
- a->filename = filename;
- a->next = NULL;
- if (keyact_tail)
- keyact_tail->next = a;
- else
- keyact_head = a;
- keyact_tail = a;
-}
-
-bool have_controlling_tty(void)
-{
- int fd = open("/dev/tty", O_RDONLY);
- if (fd < 0) {
- if (errno != ENXIO) {
- perror("/dev/tty: open");
- exit(1);
- }
- return false;
- } else {
- close(fd);
- return true;
- }
-}
-
-static char **exec_args = NULL;
-static enum {
- LIFE_UNSPEC, LIFE_X11, LIFE_TTY, LIFE_DEBUG, LIFE_PERM, LIFE_EXEC
-} life = LIFE_UNSPEC;
-static const char *display = NULL;
-static enum {
- PROMPT_UNSPEC, PROMPT_TTY, PROMPT_GUI
-} prompt_type = PROMPT_UNSPEC;
-static FingerprintType key_list_fptype = SSH_FPTYPE_DEFAULT;
-
-static char *askpass_tty(const char *prompt)
-{
- int ret;
- prompts_t *p = new_prompts();
- p->to_server = false;
- p->from_server = false;
- p->name = dupstr("Pageant passphrase prompt");
- add_prompt(p, dupcat(prompt, ": "), false);
- ret = console_get_userpass_input(p);
- assert(ret >= 0);
-
- if (!ret) {
- perror("pageant: unable to read passphrase");
- free_prompts(p);
- return NULL;
- } else {
- char *passphrase = prompt_get_result(p->prompts[0]);
- free_prompts(p);
- return passphrase;
- }
-}
-
-static char *askpass_gui(const char *prompt)
-{
- char *passphrase;
- bool success;
-
- passphrase = gtk_askpass_main(
- display, "Pageant passphrase prompt", prompt, &success);
- if (!success) {
- /* return value is error message */
- fprintf(stderr, "%s\n", passphrase);
- sfree(passphrase);
- passphrase = NULL;
- }
- return passphrase;
-}
-
-static char *askpass(const char *prompt)
-{
- if (prompt_type == PROMPT_TTY) {
- if (!have_controlling_tty()) {
- fprintf(stderr, "no controlling terminal available "
- "for passphrase prompt\n");
- return NULL;
- }
- return askpass_tty(prompt);
- }
-
- if (prompt_type == PROMPT_GUI) {
- if (!display) {
- fprintf(stderr, "no graphical display available "
- "for passphrase prompt\n");
- return NULL;
- }
- return askpass_gui(prompt);
- }
-
- if (have_controlling_tty()) {
- return askpass_tty(prompt);
- } else if (display) {
- return askpass_gui(prompt);
- } else {
- fprintf(stderr, "no way to read a passphrase without tty or "
- "X display\n");
- return NULL;
- }
-}
-
-static bool unix_add_keyfile(const char *filename_str, bool add_encrypted)
-{
- Filename *filename = filename_from_str(filename_str);
- int status;
- bool ret;
- char *err;
-
- ret = true;
-
- /*
- * Try without a passphrase.
- */
- status = pageant_add_keyfile(filename, NULL, &err, add_encrypted);
- if (status == PAGEANT_ACTION_OK) {
- goto cleanup;
- } else if (status == PAGEANT_ACTION_FAILURE) {
- fprintf(stderr, "pageant: %s: %s\n", filename_str, err);
- ret = false;
- goto cleanup;
- }
-
- /*
- * And now try prompting for a passphrase.
- */
- while (1) {
- char *prompt = dupprintf(
- "Enter passphrase to load key '%s'", err);
- char *passphrase = askpass(prompt);
- sfree(err);
- sfree(prompt);
- err = NULL;
- if (!passphrase)
- break;
-
- status = pageant_add_keyfile(filename, passphrase, &err,
- add_encrypted);
-
- smemclr(passphrase, strlen(passphrase));
- sfree(passphrase);
- passphrase = NULL;
-
- if (status == PAGEANT_ACTION_OK) {
- goto cleanup;
- } else if (status == PAGEANT_ACTION_FAILURE) {
- fprintf(stderr, "pageant: %s: %s\n", filename_str, err);
- ret = false;
- goto cleanup;
- }
- }
-
- cleanup:
- sfree(err);
- filename_free(filename);
- return ret;
-}
-
-void key_list_callback(void *ctx, char **fingerprints, const char *comment,
- uint32_t ext_flags, struct pageant_pubkey *key)
-{
- const char *mode = "";
- if (ext_flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY)
- mode = " (encrypted)";
- else if (ext_flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE)
- mode = " (re-encryptable)";
-
- FingerprintType this_type =
- ssh2_pick_fingerprint(fingerprints, key_list_fptype);
- printf("%s %s%s\n", fingerprints[this_type], comment, mode);
-}
-
-struct key_find_ctx {
- const char *string;
- bool match_fp, match_comment;
- bool match_fptypes[SSH_N_FPTYPES];
- struct pageant_pubkey *found;
- int nfound;
-};
-
-static bool match_fingerprint_string(
- const char *string_orig, char **fingerprints,
- const struct key_find_ctx *ctx)
-{
- const char *hash;
-
- for (unsigned fptype = 0; fptype < SSH_N_FPTYPES; fptype++) {
- if (!ctx->match_fptypes[fptype])
- continue;
-
- const char *fingerprint = fingerprints[fptype];
- if (!fingerprint)
- continue;
-
- /* Find the hash in the fingerprint string. It'll be the word
- * at the end. */
- hash = strrchr(fingerprint, ' ');
- assert(hash);
- hash++;
-
- const char *string = string_orig;
- bool case_sensitive;
- const char *ignore_chars = "";
-
- switch (fptype) {
- case SSH_FPTYPE_MD5:
- /* MD5 fingerprints are in hex, so disregard case differences. */
- case_sensitive = false;
- /* And we don't really need to force the user to type the
- * colons in between the digits, which are always the
- * same. */
- ignore_chars = ":";
- break;
- case SSH_FPTYPE_SHA256:
- /* Skip over the "SHA256:" prefix, which we don't really
- * want to force the user to type. On the other hand,
- * tolerate it on the input string. */
- assert(strstartswith(hash, "SHA256:"));
- hash += 7;
- if (strstartswith(string, "SHA256:"))
- string += 7;
- /* SHA256 fingerprints are base64, which is intrinsically
- * case sensitive. */
- case_sensitive = true;
- break;
- }
-
- /* Now see if the search string is a prefix of the full hash,
- * neglecting colons and (where appropriate) case differences. */
- while (1) {
- string += strspn(string, ignore_chars);
- hash += strspn(hash, ignore_chars);
- if (!*string)
- return true;
- char sc = *string, hc = *hash;
- if (!case_sensitive) {
- sc = tolower((unsigned char)sc);
- hc = tolower((unsigned char)hc);
- }
- if (sc != hc)
- break;
- string++;
- hash++;
- }
- }
-
- return false;
-}
-
-void key_find_callback(void *vctx, char **fingerprints,
- const char *comment, uint32_t ext_flags,
- struct pageant_pubkey *key)
-{
- struct key_find_ctx *ctx = (struct key_find_ctx *)vctx;
-
- if ((ctx->match_comment && !strcmp(ctx->string, comment)) ||
- (ctx->match_fp && match_fingerprint_string(ctx->string, fingerprints,
- ctx)))
- {
- if (!ctx->found)
- ctx->found = pageant_pubkey_copy(key);
- ctx->nfound++;
- }
-}
-
-struct pageant_pubkey *find_key(const char *string, char **retstr)
-{
- struct key_find_ctx ctx[1];
- struct pageant_pubkey key_in, *key_ret;
- bool try_file = true, try_fp = true, try_comment = true;
- bool file_errors = false;
- bool try_all_fptypes = true;
- FingerprintType fptype = SSH_FPTYPE_DEFAULT;
-
- /*
- * Trim off disambiguating prefixes telling us how to interpret
- * the provided string.
- */
- if (!strncmp(string, "file:", 5)) {
- string += 5;
- try_fp = false;
- try_comment = false;
- file_errors = true; /* also report failure to load the file */
- } else if (!strncmp(string, "comment:", 8)) {
- string += 8;
- try_file = false;
- try_fp = false;
- } else if (!strncmp(string, "fp:", 3)) {
- string += 3;
- try_file = false;
- try_comment = false;
- } else if (!strncmp(string, "fingerprint:", 12)) {
- string += 12;
- try_file = false;
- try_comment = false;
- } else if (!strnicmp(string, "md5:", 4)) {
- string += 4;
- try_file = false;
- try_comment = false;
- try_all_fptypes = false;
- fptype = SSH_FPTYPE_MD5;
- } else if (!strncmp(string, "sha256:", 7)) {
- string += 7;
- try_file = false;
- try_comment = false;
- try_all_fptypes = false;
- fptype = SSH_FPTYPE_SHA256;
- }
-
- /*
- * Try interpreting the string as a key file name.
- */
- if (try_file) {
- Filename *fn = filename_from_str(string);
- int keytype = key_type(fn);
- if (keytype == SSH_KEYTYPE_SSH1 ||
- keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
- const char *error;
-
- key_in.blob = strbuf_new();
- if (!rsa1_loadpub_f(fn, BinarySink_UPCAST(key_in.blob),
- NULL, &error)) {
- strbuf_free(key_in.blob);
- key_in.blob = NULL;
- if (file_errors) {
- *retstr = dupprintf("unable to load file '%s': %s",
- string, error);
- filename_free(fn);
- return NULL;
- }
- } else {
- /*
- * If we've successfully loaded the file, stop here - we
- * already have a key blob and need not go to the agent to
- * list things.
- */
- key_in.ssh_version = 1;
- key_in.comment = NULL;
- key_ret = pageant_pubkey_copy(&key_in);
- strbuf_free(key_in.blob);
- key_in.blob = NULL;
- filename_free(fn);
- return key_ret;
- }
- } else if (keytype == SSH_KEYTYPE_SSH2 ||
- keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
- keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
- const char *error;
-
- key_in.blob = strbuf_new();
- if (!ppk_loadpub_f(fn, NULL, BinarySink_UPCAST(key_in.blob),
- NULL, &error)) {
- strbuf_free(key_in.blob);
- key_in.blob = NULL;
- if (file_errors) {
- *retstr = dupprintf("unable to load file '%s': %s",
- string, error);
- filename_free(fn);
- return NULL;
- }
- } else {
- /*
- * If we've successfully loaded the file, stop here - we
- * already have a key blob and need not go to the agent to
- * list things.
- */
- key_in.ssh_version = 2;
- key_in.comment = NULL;
- key_ret = pageant_pubkey_copy(&key_in);
- strbuf_free(key_in.blob);
- key_in.blob = NULL;
- filename_free(fn);
- return key_ret;
- }
- } else {
- if (file_errors) {
- *retstr = dupprintf("unable to load key file '%s': %s",
- string, key_type_to_str(keytype));
- filename_free(fn);
- return NULL;
- }
- }
- filename_free(fn);
- }
-
- /*
- * Failing that, go through the keys in the agent, and match
- * against fingerprints and comments as appropriate.
- */
- ctx->string = string;
- ctx->match_fp = try_fp;
- ctx->match_comment = try_comment;
- for (unsigned i = 0; i < SSH_N_FPTYPES; i++)
- ctx->match_fptypes[i] = (try_all_fptypes || i == fptype);
- ctx->found = NULL;
- ctx->nfound = 0;
- if (pageant_enum_keys(key_find_callback, ctx, retstr) ==
- PAGEANT_ACTION_FAILURE)
- return NULL;
-
- if (ctx->nfound == 0) {
- *retstr = dupstr("no key matched");
- assert(!ctx->found);
- return NULL;
- } else if (ctx->nfound > 1) {
- *retstr = dupstr("multiple keys matched");
- assert(ctx->found);
- pageant_pubkey_free(ctx->found);
- return NULL;
- }
-
- assert(ctx->found);
- return ctx->found;
-}
-
-void run_client(void)
-{
- const struct cmdline_key_action *act;
- struct pageant_pubkey *key;
- bool errors = false;
- char *retstr;
- LoadedFile *message = lf_new(AGENT_MAX_MSGLEN);
- bool message_loaded = false, message_ok = false;
- strbuf *signature = strbuf_new();
-
- if (!agent_exists()) {
- fprintf(stderr, "pageant: no agent running to talk to\n");
- exit(1);
- }
-
- for (act = keyact_head; act; act = act->next) {
- switch (act->action) {
- case KEYACT_CLIENT_ADD:
- case KEYACT_CLIENT_ADD_ENCRYPTED:
- if (!unix_add_keyfile(act->filename,
- act->action == KEYACT_CLIENT_ADD_ENCRYPTED))
- errors = true;
- break;
- case KEYACT_CLIENT_LIST:
- if (pageant_enum_keys(key_list_callback, NULL, &retstr) ==
- PAGEANT_ACTION_FAILURE) {
- fprintf(stderr, "pageant: listing keys: %s\n", retstr);
- sfree(retstr);
- errors = true;
- }
- break;
- case KEYACT_CLIENT_DEL:
- key = NULL;
- if (!(key = find_key(act->filename, &retstr)) ||
- pageant_delete_key(key, &retstr) == PAGEANT_ACTION_FAILURE) {
- fprintf(stderr, "pageant: deleting key '%s': %s\n",
- act->filename, retstr);
- sfree(retstr);
- errors = true;
- }
- if (key)
- pageant_pubkey_free(key);
- break;
- case KEYACT_CLIENT_REENCRYPT:
- key = NULL;
- if (!(key = find_key(act->filename, &retstr)) ||
- pageant_reencrypt_key(key, &retstr) == PAGEANT_ACTION_FAILURE) {
- fprintf(stderr, "pageant: re-encrypting key '%s': %s\n",
- act->filename, retstr);
- sfree(retstr);
- errors = true;
- }
- if (key)
- pageant_pubkey_free(key);
- break;
- case KEYACT_CLIENT_PUBLIC_OPENSSH:
- case KEYACT_CLIENT_PUBLIC:
- key = NULL;
- if (!(key = find_key(act->filename, &retstr))) {
- fprintf(stderr, "pageant: finding key '%s': %s\n",
- act->filename, retstr);
- sfree(retstr);
- errors = true;
- } else {
- FILE *fp = stdout; /* FIXME: add a -o option? */
-
- if (key->ssh_version == 1) {
- BinarySource src[1];
- RSAKey rkey;
-
- BinarySource_BARE_INIT(src, key->blob->u, key->blob->len);
- memset(&rkey, 0, sizeof(rkey));
- rkey.comment = dupstr(key->comment);
- get_rsa_ssh1_pub(src, &rkey, RSA_SSH1_EXPONENT_FIRST);
- ssh1_write_pubkey(fp, &rkey);
- freersakey(&rkey);
- } else {
- ssh2_write_pubkey(fp, key->comment,
- key->blob->u,
- key->blob->len,
- (act->action == KEYACT_CLIENT_PUBLIC ?
- SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 :
- SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH));
- }
- pageant_pubkey_free(key);
- }
- break;
- case KEYACT_CLIENT_DEL_ALL:
- if (pageant_delete_all_keys(&retstr) == PAGEANT_ACTION_FAILURE) {
- fprintf(stderr, "pageant: deleting all keys: %s\n", retstr);
- sfree(retstr);
- errors = true;
- }
- break;
- case KEYACT_CLIENT_REENCRYPT_ALL: {
- int status = pageant_reencrypt_all_keys(&retstr);
- if (status == PAGEANT_ACTION_FAILURE) {
- fprintf(stderr, "pageant: re-encrypting all keys: "
- "%s\n", retstr);
- sfree(retstr);
- errors = true;
- } else if (status == PAGEANT_ACTION_WARNING) {
- fprintf(stderr, "pageant: re-encrypting all keys: "
- "warning: %s\n", retstr);
- sfree(retstr);
- }
- break;
- }
- case KEYACT_CLIENT_SIGN:
- key = NULL;
- if (!message_loaded) {
- message_loaded = true;
- switch(lf_load_fp(message, stdin)) {
- case LF_TOO_BIG:
- fprintf(stderr, "pageant: message to sign is too big\n");
- errors = true;
- break;
- case LF_ERROR:
- fprintf(stderr, "pageant: reading message to sign: %s\n",
- strerror(errno));
- errors = true;
- break;
- case LF_OK:
- message_ok = true;
- break;
- }
- }
- if (!message_ok)
- break;
- strbuf_clear(signature);
- if (!(key = find_key(act->filename, &retstr)) ||
- pageant_sign(key, ptrlen_from_lf(message), signature,
- sign_flags, &retstr) == PAGEANT_ACTION_FAILURE) {
- fprintf(stderr, "pageant: signing with key '%s': %s\n",
- act->filename, retstr);
- sfree(retstr);
- errors = true;
- } else {
- fwrite(signature->s, 1, signature->len, stdout);
- }
- if (key)
- pageant_pubkey_free(key);
- break;
- default:
- unreachable("Invalid client action found");
- }
- }
-
- lf_free(message);
- strbuf_free(signature);
-
- if (errors)
- exit(1);
-}
-
-static const PlugVtable X11Connection_plugvt = {
- .log = x11_log,
- .closing = x11_closing,
- .receive = x11_receive,
- .sent = x11_sent,
-};
-
-
-static bool agent_loop_pw_setup(void *vctx, pollwrapper *pw)
-{
- struct uxpgnt_client *upc = (struct uxpgnt_client *)vctx;
-
- if (signalpipe[0] >= 0) {
- pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R);
- }
-
- if (upc->prompt_active)
- pollwrap_add_fd_rwx(pw, upc->passphrase_fd, SELECT_R);
-
- return true;
-}
-
-static void agent_loop_pw_check(void *vctx, pollwrapper *pw)
-{
- struct uxpgnt_client *upc = (struct uxpgnt_client *)vctx;
-
- if (life == LIFE_TTY) {
- /*
- * Every time we wake up (whether it was due to tty_timer
- * elapsing or for any other reason), poll to see if we still
- * have a controlling terminal. If we don't, then our
- * containing tty session has ended, so it's time to clean up
- * and leave.
- */
- if (!have_controlling_tty()) {
- time_to_die = true;
- return;
- }
- }
-
- if (signalpipe[0] >= 0 &&
- pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) {
- char c[1];
- if (read(signalpipe[0], c, 1) <= 0)
- /* ignore error */;
- /* ignore its value; it'll be `x' */
- while (1) {
- int status;
- pid_t pid;
- pid = waitpid(-1, &status, WNOHANG);
- if (pid <= 0)
- break;
- if (pid == upc->termination_pid)
- time_to_die = true;
- }
- }
-
- if (upc->prompt_active &&
- pollwrap_check_fd_rwx(pw, upc->passphrase_fd, SELECT_R)) {
- char c;
- int retd = read(upc->passphrase_fd, &c, 1);
-
- switch (upc->prompt_type) {
- case RTPROMPT_GUI:
- if (retd <= 0) {
- close(upc->passphrase_fd);
- upc->passphrase_fd = -1;
- bool ok = (retd == 0);
- if (!strbuf_chomp(upc->prompt_buf, '\n'))
- ok = false;
- passphrase_done(upc, ok);
- } else {
- put_byte(upc->prompt_buf, c);
- }
- break;
- case RTPROMPT_DEBUG:
- if (retd <= 0) {
- passphrase_done(upc, false);
- /* Now never try to read from stdin again */
- upc->prompt_type = RTPROMPT_UNAVAILABLE;
- break;
- }
-
- switch (c) {
- case '\n':
- case '\r':
- passphrase_done(upc, true);
- break;
- case '\004':
- passphrase_done(upc, false);
- break;
- case '\b':
- case '\177':
- strbuf_shrink_by(upc->prompt_buf, 1);
- break;
- case '\025':
- strbuf_clear(upc->prompt_buf);
- break;
- default:
- put_byte(upc->prompt_buf, c);
- break;
- }
- break;
- case RTPROMPT_UNAVAILABLE:
- unreachable("Should never have started a prompt at all");
- }
- }
-}
-
-static bool agent_loop_continue(void *vctx, bool fd, bool cb)
-{
- return !time_to_die;
-}
-
-void run_agent(FILE *logfp, const char *symlink_path)
-{
- const char *err;
- char *errw;
- struct pageant_listen_state *pl;
- Plug *pl_plug;
- Socket *sock;
- bool errors = false;
- Conf *conf;
- const struct cmdline_key_action *act;
-
- pageant_init();
-
- /*
- * Start by loading any keys provided on the command line.
- */
- for (act = keyact_head; act; act = act->next) {
- assert(act->action == KEYACT_AGENT_LOAD ||
- act->action == KEYACT_AGENT_LOAD_ENCRYPTED);
- if (!unix_add_keyfile(act->filename,
- act->action == KEYACT_AGENT_LOAD_ENCRYPTED))
- errors = true;
- }
- if (errors)
- exit(1);
-
- /*
- * Set up a listening socket and run Pageant on it.
- */
- struct uxpgnt_client upc[1];
- memset(upc, 0, sizeof(upc));
- upc->plc.vt = &uxpgnt_vtable;
- upc->logfp = logfp;
- upc->passphrase_fd = -1;
- upc->termination_pid = -1;
- upc->prompt_buf = strbuf_new_nm();
- upc->prompt_type = display ? RTPROMPT_GUI : RTPROMPT_UNAVAILABLE;
- pl = pageant_listener_new(&pl_plug, &upc->plc);
- sock = platform_make_agent_socket(pl_plug, PAGEANT_DIR_PREFIX,
- &errw, &socketname);
- if (!sock) {
- fprintf(stderr, "pageant: %s\n", errw);
- sfree(errw);
- exit(1);
- }
- pageant_listener_got_socket(pl, sock);
-
- if (symlink_path) {
- /*
- * Try to make a symlink to the Unix socket, in a location of
- * the user's choosing.
- *
- * If the link already exists, we want to replace it. There
- * are two ways we could do this: either make it under another
- * name and then rename it over the top, or remove the old
- * link first. The former is what 'ln -sf' does, on the
- * grounds that it's more atomic. But I think in this case,
- * where the expected use case is that the previous agent has
- * long since shut down, atomicity isn't a critical concern
- * compared to not accidentally overwriting some non-symlink
- * that might have important data in it!
- */
- struct stat st;
- if (lstat(symlink_path, &st) == 0 && S_ISLNK(st.st_mode))
- unlink(symlink_path);
- if (symlink(socketname, symlink_path) < 0)
- fprintf(stderr, "pageant: making symlink %s: %s\n",
- symlink_path, strerror(errno));
- }
-
- conf = conf_new();
- conf_set_int(conf, CONF_proxy_type, PROXY_NONE);
-
- /*
- * Lifetime preparations.
- */
- if (life == LIFE_X11) {
- struct X11Display *disp;
- void *greeting;
- int greetinglen;
- Socket *s;
- struct X11Connection *conn;
- char *x11_setup_err;
-
- if (!display) {
- fprintf(stderr, "pageant: no DISPLAY for -X mode\n");
- exit(1);
- }
- disp = x11_setup_display(display, conf, &x11_setup_err);
- if (!disp) {
- fprintf(stderr, "pageant: unable to connect to X server: %s\n",
- x11_setup_err);
- sfree(x11_setup_err);
- exit(1);
- }
-
- conn = snew(struct X11Connection);
- conn->plug.vt = &X11Connection_plugvt;
- s = new_connection(sk_addr_dup(disp->addr),
- disp->realhost, disp->port,
- false, true, false, false, &conn->plug, conf);
- if ((err = sk_socket_error(s)) != NULL) {
- fprintf(stderr, "pageant: unable to connect to X server: %s", err);
- exit(1);
- }
- greeting = x11_make_greeting('B', 11, 0, disp->localauthproto,
- disp->localauthdata,
- disp->localauthdatalen,
- NULL, 0, &greetinglen);
- sk_write(s, greeting, greetinglen);
- smemclr(greeting, greetinglen);
- sfree(greeting);
-
- pageant_fork_and_print_env(false);
- } else if (life == LIFE_TTY) {
- schedule_timer(TTY_LIFE_POLL_INTERVAL,
- tty_life_timer, &dummy_timer_ctx);
- pageant_fork_and_print_env(true);
- } else if (life == LIFE_PERM) {
- pageant_fork_and_print_env(false);
- } else if (life == LIFE_DEBUG) {
- pageant_print_env(getpid());
- upc->logfp = stdout;
-
- struct termios orig_termios;
- upc->passphrase_fd = fileno(stdin);
- if (tcgetattr(upc->passphrase_fd, &orig_termios) == 0) {
- struct termios new_termios = orig_termios;
- new_termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON);
-
- /*
- * Try to set up a watchdog process that will restore
- * termios if we crash or are killed. If successful, turn
- * off echo, for runtime passphrase prompts.
- */
- int pipefd[2];
- if (pipe(pipefd) == 0) {
- pid_t pid = fork();
- if (pid == 0) {
- tcsetattr(upc->passphrase_fd, TCSADRAIN, &new_termios);
- close(pipefd[1]);
- char buf[4096];
- while (read(pipefd[0], buf, sizeof(buf)) > 0);
- tcsetattr(upc->passphrase_fd, TCSADRAIN, &new_termios);
- _exit(0);
- } else if (pid > 0) {
- upc->prompt_type = RTPROMPT_DEBUG;
- }
-
- close(pipefd[0]);
- if (pid < 0)
- close(pipefd[1]);
- }
- }
- } else if (life == LIFE_EXEC) {
- pid_t agentpid, pid;
-
- agentpid = getpid();
- setup_sigchld_handler();
-
- pid = fork();
- if (pid < 0) {
- perror("fork");
- exit(1);
- } else if (pid == 0) {
- setenv("SSH_AUTH_SOCK", socketname, true);
- setenv("SSH_AGENT_PID", dupprintf("%d", (int)agentpid), true);
- execvp(exec_args[0], exec_args);
- perror("exec");
- _exit(127);
- } else {
- upc->termination_pid = pid;
- }
- }
-
- if (!upc->logfp)
- upc->plc.suppress_logging = true;
-
- cli_main_loop(agent_loop_pw_setup, agent_loop_pw_check,
- agent_loop_continue, upc);
-
- /*
- * Before terminating, clean up our Unix socket file if possible.
- */
- if (unlink(socketname) < 0) {
- fprintf(stderr, "pageant: %s: %s\n", socketname, strerror(errno));
- exit(1);
- }
-
- strbuf_free(upc->prompt_buf);
- conf_free(conf);
-}
-
-int main(int argc, char **argv)
-{
- bool doing_opts = true;
- keyact curr_keyact = KEYACT_AGENT_LOAD;
- const char *standalone_askpass_prompt = NULL;
- const char *symlink_path = NULL;
- FILE *logfp = NULL;
-
- progname = argv[0];
-
- /*
- * Process the command line.
- */
- while (--argc > 0) {
- char *p = *++argv;
- if (*p == '-' && doing_opts) {
- if (!strcmp(p, "-V") || !strcmp(p, "--version")) {
- version();
- } else if (!strcmp(p, "--help")) {
- usage();
- exit(0);
- } else if (!strcmp(p, "-v")) {
- logfp = stderr;
- } else if (!strcmp(p, "-a")) {
- curr_keyact = KEYACT_CLIENT_ADD;
- } else if (!strcmp(p, "-d")) {
- curr_keyact = KEYACT_CLIENT_DEL;
- } else if (!strcmp(p, "-r")) {
- curr_keyact = KEYACT_CLIENT_REENCRYPT;
- } else if (!strcmp(p, "-s")) {
- shell_type = SHELL_SH;
- } else if (!strcmp(p, "-c")) {
- shell_type = SHELL_CSH;
- } else if (!strcmp(p, "-D")) {
- add_keyact(KEYACT_CLIENT_DEL_ALL, NULL);
- } else if (!strcmp(p, "-R")) {
- add_keyact(KEYACT_CLIENT_REENCRYPT_ALL, NULL);
- } else if (!strcmp(p, "-l")) {
- add_keyact(KEYACT_CLIENT_LIST, NULL);
- } else if (!strcmp(p, "--public")) {
- curr_keyact = KEYACT_CLIENT_PUBLIC;
- } else if (!strcmp(p, "--public-openssh") || !strcmp(p, "-L")) {
- curr_keyact = KEYACT_CLIENT_PUBLIC_OPENSSH;
- } else if (!strcmp(p, "-X")) {
- life = LIFE_X11;
- } else if (!strcmp(p, "-T")) {
- life = LIFE_TTY;
- } else if (!strcmp(p, "--no-decrypt") ||
- !strcmp(p, "-no-decrypt") ||
- !strcmp(p, "--no_decrypt") ||
- !strcmp(p, "-no_decrypt") ||
- !strcmp(p, "--nodecrypt") ||
- !strcmp(p, "-nodecrypt") ||
- !strcmp(p, "--encrypted") ||
- !strcmp(p, "-encrypted")) {
- if (curr_keyact == KEYACT_AGENT_LOAD)
- curr_keyact = KEYACT_AGENT_LOAD_ENCRYPTED;
- else if (curr_keyact == KEYACT_CLIENT_ADD)
- curr_keyact = KEYACT_CLIENT_ADD_ENCRYPTED;
- else {
- fprintf(stderr, "pageant: unexpected -E while not adding "
- "keys\n");
- exit(1);
- }
- } else if (!strcmp(p, "--debug")) {
- life = LIFE_DEBUG;
- } else if (!strcmp(p, "--test-sign")) {
- curr_keyact = KEYACT_CLIENT_SIGN;
- sign_flags = 0;
- } else if (strstartswith(p, "--test-sign-with-flags=")) {
- curr_keyact = KEYACT_CLIENT_SIGN;
- sign_flags = atoi(p + strlen("--test-sign-with-flags="));
- } else if (!strcmp(p, "--permanent")) {
- life = LIFE_PERM;
- } else if (!strcmp(p, "--exec")) {
- life = LIFE_EXEC;
- /* Now all subsequent arguments go to the exec command. */
- if (--argc > 0) {
- exec_args = ++argv;
- argc = 0; /* force end of option processing */
- } else {
- fprintf(stderr, "pageant: expected a command "
- "after --exec\n");
- exit(1);
- }
- } else if (!strcmp(p, "--tty-prompt")) {
- prompt_type = PROMPT_TTY;
- } else if (!strcmp(p, "--gui-prompt")) {
- prompt_type = PROMPT_GUI;
- } else if (!strcmp(p, "--askpass")) {
- if (--argc > 0) {
- standalone_askpass_prompt = *++argv;
- } else {
- fprintf(stderr, "pageant: expected a prompt message "
- "after --askpass\n");
- exit(1);
- }
- } else if (!strcmp(p, "--symlink")) {
- if (--argc > 0) {
- symlink_path = *++argv;
- } else {
- fprintf(stderr, "pageant: expected a pathname "
- "after --symlink\n");
- exit(1);
- }
- } else if (!strcmp(p, "-E") || !strcmp(p, "--fptype")) {
- const char *keyword;
- if (--argc > 0) {
- keyword = *++argv;
- } else {
- fprintf(stderr, "pageant: expected a type string "
- "after %s\n", p);
- exit(1);
- }
- if (!strcmp(keyword, "md5"))
- key_list_fptype = SSH_FPTYPE_MD5;
- else if (!strcmp(keyword, "sha256"))
- key_list_fptype = SSH_FPTYPE_SHA256;
- else {
- fprintf(stderr, "pageant: unknown fingerprint type `%s'\n",
- keyword);
- exit(1);
- }
- } else if (!strcmp(p, "--")) {
- doing_opts = false;
- } else {
- fprintf(stderr, "pageant: unrecognised option '%s'\n", p);
- exit(1);
- }
- } else {
- /*
- * Non-option arguments (apart from those after --exec,
- * which are treated specially above) are interpreted as
- * the names of private key files to either add or delete
- * from an agent.
- */
- add_keyact(curr_keyact, p);
- }
- }
-
- if (life == LIFE_EXEC && !exec_args) {
- fprintf(stderr, "pageant: expected a command with --exec\n");
- exit(1);
- }
-
- if (!display) {
- display = getenv("DISPLAY");
- if (display && !*display)
- display = NULL;
- }
-
- /*
- * Deal with standalone-askpass mode.
- */
- if (standalone_askpass_prompt) {
- char *passphrase = askpass(standalone_askpass_prompt);
-
- if (!passphrase)
- return 1;
-
- puts(passphrase);
- fflush(stdout);
-
- smemclr(passphrase, strlen(passphrase));
- sfree(passphrase);
- return 0;
- }
-
- /*
- * Block SIGPIPE, so that we'll get EPIPE individually on
- * particular network connections that go wrong.
- */
- putty_signal(SIGPIPE, SIG_IGN);
-
- sk_init();
- uxsel_init();
-
- /*
- * Now distinguish our two main running modes. Either we're
- * actually starting up an agent, in which case we should have a
- * lifetime mode, and no key actions of KEYACT_CLIENT_* type; or
- * else we're contacting an existing agent to add or remove keys,
- * in which case we should have no lifetime mode, and no key
- * actions of KEYACT_AGENT_* type.
- */
- {
- bool has_agent_actions = false;
- bool has_client_actions = false;
- bool has_lifetime = false;
- const struct cmdline_key_action *act;
-
- for (act = keyact_head; act; act = act->next) {
- if (is_agent_action(act->action))
- has_agent_actions = true;
- else
- has_client_actions = true;
- }
- if (life != LIFE_UNSPEC)
- has_lifetime = true;
-
- if (has_lifetime && has_client_actions) {
- fprintf(stderr, "pageant: client key actions (-a, -d, -D, -r, -R, "
- "-l, -L) do not go with an agent lifetime option\n");
- exit(1);
- }
- if (!has_lifetime && has_agent_actions) {
- fprintf(stderr, "pageant: expected an agent lifetime option with"
- " bare key file arguments\n");
- exit(1);
- }
- if (!has_lifetime && !has_client_actions) {
- fprintf(stderr, "pageant: expected an agent lifetime option"
- " or a client key action\n");
- exit(1);
- }
-
- if (has_lifetime) {
- run_agent(logfp, symlink_path);
- } else if (has_client_actions) {
- run_client();
- }
- }
-
- return 0;
-}
diff --git a/unix/uxplink.c b/unix/uxplink.c
deleted file mode 100644
index 240783f4..00000000
--- a/unix/uxplink.c
+++ /dev/null
@@ -1,966 +0,0 @@
-/*
- * PLink - a command-line (stdin/stdout) variant of PuTTY.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <signal.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <termios.h>
-#include <pwd.h>
-#include <sys/ioctl.h>
-#include <sys/time.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "storage.h"
-#include "tree234.h"
-
-#define MAX_STDIN_BACKLOG 4096
-
-static LogContext *logctx;
-
-static struct termios orig_termios;
-
-void cmdline_error(const char *fmt, ...)
-{
- va_list ap;
- va_start(ap, fmt);
- console_print_error_msg_fmt_v("plink", fmt, ap);
- va_end(ap);
- exit(1);
-}
-
-static bool local_tty = false; /* do we have a local tty? */
-
-static Backend *backend;
-static Conf *conf;
-
-/*
- * Default settings that are specific to Unix plink.
- */
-char *platform_default_s(const char *name)
-{
- if (!strcmp(name, "TermType"))
- return dupstr(getenv("TERM"));
- if (!strcmp(name, "SerialLine"))
- return dupstr("/dev/ttyS0");
- return NULL;
-}
-
-bool platform_default_b(const char *name, bool def)
-{
- return def;
-}
-
-int platform_default_i(const char *name, int def)
-{
- return def;
-}
-
-FontSpec *platform_default_fontspec(const char *name)
-{
- return fontspec_new("");
-}
-
-Filename *platform_default_filename(const char *name)
-{
- if (!strcmp(name, "LogFileName"))
- return filename_from_str("putty.log");
- else
- return filename_from_str("");
-}
-
-char *x_get_default(const char *key)
-{
- return NULL; /* this is a stub */
-}
-static void plink_echoedit_update(Seat *seat, bool echo, bool edit)
-{
- /* Update stdin read mode to reflect changes in line discipline. */
- struct termios mode;
-
- if (!local_tty) return;
-
- mode = orig_termios;
-
- if (echo)
- mode.c_lflag |= ECHO;
- else
- mode.c_lflag &= ~ECHO;
-
- if (edit) {
- mode.c_iflag |= ICRNL;
- mode.c_lflag |= ISIG | ICANON;
- mode.c_oflag |= OPOST;
- } else {
- mode.c_iflag &= ~ICRNL;
- mode.c_lflag &= ~(ISIG | ICANON);
- mode.c_oflag &= ~OPOST;
- /* Solaris sets these to unhelpful values */
- mode.c_cc[VMIN] = 1;
- mode.c_cc[VTIME] = 0;
- /* FIXME: perhaps what we do with IXON/IXOFF should be an
- * argument to the echoedit_update() method, to allow
- * implementation of SSH-2 "xon-xoff" and Rlogin's
- * equivalent? */
- mode.c_iflag &= ~IXON;
- mode.c_iflag &= ~IXOFF;
- }
- /*
- * Mark parity errors and (more important) BREAK on input. This
- * is more complex than it need be because POSIX-2001 suggests
- * that escaping of valid 0xff in the input stream is dependent on
- * IGNPAR being clear even though marking of BREAK isn't. NetBSD
- * 2.0 goes one worse and makes it dependent on INPCK too. We
- * deal with this by forcing these flags into a useful state and
- * then faking the state in which we found them in from_tty() if
- * we get passed a parity or framing error.
- */
- mode.c_iflag = (mode.c_iflag | INPCK | PARMRK) & ~IGNPAR;
-
- tcsetattr(STDIN_FILENO, TCSANOW, &mode);
-}
-
-/* Helper function to extract a special character from a termios. */
-static char *get_ttychar(struct termios *t, int index)
-{
- cc_t c = t->c_cc[index];
-#if defined(_POSIX_VDISABLE)
- if (c == _POSIX_VDISABLE)
- return dupstr("");
-#endif
- return dupprintf("^<%d>", c);
-}
-
-static char *plink_get_ttymode(Seat *seat, const char *mode)
-{
- /*
- * Propagate appropriate terminal modes from the local terminal,
- * if any.
- */
- if (!local_tty) return NULL;
-
-#define GET_CHAR(ourname, uxname) \
- do { \
- if (strcmp(mode, ourname) == 0) \
- return get_ttychar(&orig_termios, uxname); \
- } while(0)
-#define GET_BOOL(ourname, uxname, uxmemb, transform) \
- do { \
- if (strcmp(mode, ourname) == 0) { \
- bool b = (orig_termios.uxmemb & uxname) != 0; \
- transform; \
- return dupprintf("%d", b); \
- } \
- } while (0)
-
- /*
- * Modes that want to be the same on all terminal devices involved.
- */
- /* All the special characters supported by SSH */
-#if defined(VINTR)
- GET_CHAR("INTR", VINTR);
-#endif
-#if defined(VQUIT)
- GET_CHAR("QUIT", VQUIT);
-#endif
-#if defined(VERASE)
- GET_CHAR("ERASE", VERASE);
-#endif
-#if defined(VKILL)
- GET_CHAR("KILL", VKILL);
-#endif
-#if defined(VEOF)
- GET_CHAR("EOF", VEOF);
-#endif
-#if defined(VEOL)
- GET_CHAR("EOL", VEOL);
-#endif
-#if defined(VEOL2)
- GET_CHAR("EOL2", VEOL2);
-#endif
-#if defined(VSTART)
- GET_CHAR("START", VSTART);
-#endif
-#if defined(VSTOP)
- GET_CHAR("STOP", VSTOP);
-#endif
-#if defined(VSUSP)
- GET_CHAR("SUSP", VSUSP);
-#endif
-#if defined(VDSUSP)
- GET_CHAR("DSUSP", VDSUSP);
-#endif
-#if defined(VREPRINT)
- GET_CHAR("REPRINT", VREPRINT);
-#endif
-#if defined(VWERASE)
- GET_CHAR("WERASE", VWERASE);
-#endif
-#if defined(VLNEXT)
- GET_CHAR("LNEXT", VLNEXT);
-#endif
-#if defined(VFLUSH)
- GET_CHAR("FLUSH", VFLUSH);
-#endif
-#if defined(VSWTCH)
- GET_CHAR("SWTCH", VSWTCH);
-#endif
-#if defined(VSTATUS)
- GET_CHAR("STATUS", VSTATUS);
-#endif
-#if defined(VDISCARD)
- GET_CHAR("DISCARD", VDISCARD);
-#endif
- /* Modes that "configure" other major modes. These should probably be
- * considered as user preferences. */
- /* Configuration of ICANON */
-#if defined(ECHOK)
- GET_BOOL("ECHOK", ECHOK, c_lflag, );
-#endif
-#if defined(ECHOKE)
- GET_BOOL("ECHOKE", ECHOKE, c_lflag, );
-#endif
-#if defined(ECHOE)
- GET_BOOL("ECHOE", ECHOE, c_lflag, );
-#endif
-#if defined(ECHONL)
- GET_BOOL("ECHONL", ECHONL, c_lflag, );
-#endif
-#if defined(XCASE)
- GET_BOOL("XCASE", XCASE, c_lflag, );
-#endif
-#if defined(IUTF8)
- GET_BOOL("IUTF8", IUTF8, c_iflag, );
-#endif
- /* Configuration of ECHO */
-#if defined(ECHOCTL)
- GET_BOOL("ECHOCTL", ECHOCTL, c_lflag, );
-#endif
- /* Configuration of IXON/IXOFF */
-#if defined(IXANY)
- GET_BOOL("IXANY", IXANY, c_iflag, );
-#endif
- /* Configuration of OPOST */
-#if defined(OLCUC)
- GET_BOOL("OLCUC", OLCUC, c_oflag, );
-#endif
-#if defined(ONLCR)
- GET_BOOL("ONLCR", ONLCR, c_oflag, );
-#endif
-#if defined(OCRNL)
- GET_BOOL("OCRNL", OCRNL, c_oflag, );
-#endif
-#if defined(ONOCR)
- GET_BOOL("ONOCR", ONOCR, c_oflag, );
-#endif
-#if defined(ONLRET)
- GET_BOOL("ONLRET", ONLRET, c_oflag, );
-#endif
-
- /*
- * Modes that want to be set in only one place, and that we have
- * squashed locally.
- */
-#if defined(ISIG)
- GET_BOOL("ISIG", ISIG, c_lflag, );
-#endif
-#if defined(ICANON)
- GET_BOOL("ICANON", ICANON, c_lflag, );
-#endif
-#if defined(ECHO)
- GET_BOOL("ECHO", ECHO, c_lflag, );
-#endif
-#if defined(IXON)
- GET_BOOL("IXON", IXON, c_iflag, );
-#endif
-#if defined(IXOFF)
- GET_BOOL("IXOFF", IXOFF, c_iflag, );
-#endif
-#if defined(OPOST)
- GET_BOOL("OPOST", OPOST, c_oflag, );
-#endif
-
- /*
- * We do not propagate the following modes:
- * - Parity/serial settings, which are a local affair and don't
- * make sense propagated over SSH's 8-bit byte-stream.
- * IGNPAR PARMRK INPCK CS7 CS8 PARENB PARODD
- * - Things that want to be enabled in one place that we don't
- * squash locally.
- * IUCLC
- * - Status bits.
- * PENDIN
- * - Things I don't know what to do with. (FIXME)
- * ISTRIP IMAXBEL NOFLSH TOSTOP IEXTEN
- * INLCR IGNCR ICRNL
- */
-
-#undef GET_CHAR
-#undef GET_BOOL
-
- /* Fall through to here for unrecognised names, or ones that are
- * unsupported on this platform */
- return NULL;
-}
-
-void cleanup_termios(void)
-{
- if (local_tty)
- tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
-}
-
-static bufchain stdout_data, stderr_data;
-static bufchain_sink stdout_bcs, stderr_bcs;
-static StripCtrlChars *stdout_scc, *stderr_scc;
-static BinarySink *stdout_bs, *stderr_bs;
-
-static enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
-
-size_t try_output(bool is_stderr)
-{
- bufchain *chain = (is_stderr ? &stderr_data : &stdout_data);
- int fd = (is_stderr ? STDERR_FILENO : STDOUT_FILENO);
- ssize_t ret;
-
- if (bufchain_size(chain) > 0) {
- bool prev_nonblock = nonblock(fd);
- ptrlen senddata;
- do {
- senddata = bufchain_prefix(chain);
- ret = write(fd, senddata.ptr, senddata.len);
- if (ret > 0)
- bufchain_consume(chain, ret);
- } while (ret == senddata.len && bufchain_size(chain) != 0);
- if (!prev_nonblock)
- no_nonblock(fd);
- if (ret < 0 && errno != EAGAIN) {
- perror(is_stderr ? "stderr: write" : "stdout: write");
- exit(1);
- }
- }
- if (outgoingeof == EOF_PENDING && bufchain_size(&stdout_data) == 0) {
- close(STDOUT_FILENO);
- outgoingeof = EOF_SENT;
- }
- return bufchain_size(&stdout_data) + bufchain_size(&stderr_data);
-}
-
-static size_t plink_output(
- Seat *seat, bool is_stderr, const void *data, size_t len)
-{
- assert(is_stderr || outgoingeof == EOF_NO);
-
- BinarySink *bs = is_stderr ? stderr_bs : stdout_bs;
- put_data(bs, data, len);
-
- return try_output(is_stderr);
-}
-
-static bool plink_eof(Seat *seat)
-{
- assert(outgoingeof == EOF_NO);
- outgoingeof = EOF_PENDING;
- try_output(false);
- return false; /* do not respond to incoming EOF with outgoing */
-}
-
-static 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 = plink_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 }};
-
-/*
- * Handle data from a local tty in PARMRK format.
- */
-static void from_tty(void *vbuf, unsigned len)
-{
- char *p, *q, *end, *buf = vbuf;
- static enum {NORMAL, FF, FF00} state = NORMAL;
-
- p = buf; end = buf + len;
- while (p < end) {
- switch (state) {
- case NORMAL:
- if (*p == '\xff') {
- p++;
- state = FF;
- } else {
- q = memchr(p, '\xff', end - p);
- if (q == NULL) q = end;
- backend_send(backend, p, q - p);
- p = q;
- }
- break;
- case FF:
- if (*p == '\xff') {
- backend_send(backend, p, 1);
- p++;
- state = NORMAL;
- } else if (*p == '\0') {
- p++;
- state = FF00;
- } else abort();
- break;
- case FF00:
- if (*p == '\0') {
- backend_special(backend, SS_BRK, 0);
- } else {
- /*
- * Pretend that PARMRK wasn't set. This involves
- * faking what INPCK and IGNPAR would have done if
- * we hadn't overridden them. Unfortunately, we
- * can't do this entirely correctly because INPCK
- * distinguishes between framing and parity
- * errors, but PARMRK format represents both in
- * the same way. We assume that parity errors are
- * more common than framing errors, and hence
- * treat all input errors as being subject to
- * INPCK.
- */
- if (orig_termios.c_iflag & INPCK) {
- /* If IGNPAR is set, we throw away the character. */
- if (!(orig_termios.c_iflag & IGNPAR)) {
- /* PE/FE get passed on as NUL. */
- *p = 0;
- backend_send(backend, p, 1);
- }
- } else {
- /* INPCK not set. Assume we got a parity error. */
- backend_send(backend, p, 1);
- }
- }
- p++;
- state = NORMAL;
- }
- }
-}
-
-static int signalpipe[2];
-
-void sigwinch(int signum)
-{
- if (write(signalpipe[1], "x", 1) <= 0)
- /* not much we can do about it */;
-}
-
-/*
- * Short description of parameters.
- */
-static void usage(void)
-{
- printf("Plink: command-line connection utility\n");
- printf("%s\n", ver);
- printf("Usage: plink [options] [user@]host [command]\n");
- printf(" (\"host\" can also be a PuTTY saved session name)\n");
- printf("Options:\n");
- printf(" -V print version information and exit\n");
- printf(" -pgpfp print PGP key fingerprints and exit\n");
- printf(" -v show verbose messages\n");
- printf(" -load sessname Load settings from saved session\n");
- printf(" -ssh -telnet -rlogin -raw -serial\n");
- printf(" force use of a particular protocol\n");
- printf(" -ssh-connection\n");
- printf(" force use of the bare ssh-connection protocol\n");
- printf(" -P port connect to specified port\n");
- printf(" -l user connect with specified username\n");
- printf(" -batch disable all interactive prompts\n");
- printf(" -proxycmd command\n");
- printf(" use 'command' as local proxy\n");
- printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n");
- printf(" Specify the serial configuration (serial only)\n");
- printf("The following options only apply to SSH connections:\n");
- printf(" -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");
- exit(1);
-}
-
-static void version(void)
-{
- char *buildinfo_text = buildinfo("\n");
- printf("plink: %s\n%s\n", ver, buildinfo_text);
- sfree(buildinfo_text);
- exit(0);
-}
-
-void frontend_net_error_pending(void) {}
-
-const bool share_can_be_downstream = true;
-const bool share_can_be_upstream = true;
-
-const bool buildinfo_gtk_relevant = false;
-
-const unsigned cmdline_tooltype =
- TOOLTYPE_HOST_ARG |
- TOOLTYPE_HOST_ARG_CAN_BE_SESSION |
- TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX |
- TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD;
-
-static bool seen_stdin_eof = false;
-
-static bool plink_pw_setup(void *vctx, pollwrapper *pw)
-{
- pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R);
-
- if (!seen_stdin_eof &&
- backend_connected(backend) &&
- backend_sendok(backend) &&
- backend_sendbuffer(backend) < MAX_STDIN_BACKLOG) {
- /* If we're OK to send, then try to read from stdin. */
- pollwrap_add_fd_rwx(pw, STDIN_FILENO, SELECT_R);
- }
-
- if (bufchain_size(&stdout_data) > 0) {
- /* If we have data for stdout, try to write to stdout. */
- pollwrap_add_fd_rwx(pw, STDOUT_FILENO, SELECT_W);
- }
-
- if (bufchain_size(&stderr_data) > 0) {
- /* If we have data for stderr, try to write to stderr. */
- pollwrap_add_fd_rwx(pw, STDERR_FILENO, SELECT_W);
- }
-
- return true;
-}
-
-static void plink_pw_check(void *vctx, pollwrapper *pw)
-{
- if (pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) {
- char c[1];
- struct winsize size;
- if (read(signalpipe[0], c, 1) <= 0)
- /* ignore error */;
- /* ignore its value; it'll be `x' */
- if (ioctl(STDIN_FILENO, TIOCGWINSZ, (void *)&size) >= 0)
- backend_size(backend, size.ws_col, size.ws_row);
- }
-
- if (pollwrap_check_fd_rwx(pw, STDIN_FILENO, SELECT_R)) {
- char buf[4096];
- int ret;
-
- if (backend_connected(backend)) {
- ret = read(STDIN_FILENO, buf, sizeof(buf));
- noise_ultralight(NOISE_SOURCE_IOLEN, ret);
- if (ret < 0) {
- perror("stdin: read");
- exit(1);
- } else if (ret == 0) {
- backend_special(backend, SS_EOF, 0);
- seen_stdin_eof = true;
- } else {
- if (local_tty)
- from_tty(buf, ret);
- else
- backend_send(backend, buf, ret);
- }
- }
- }
-
- if (pollwrap_check_fd_rwx(pw, STDOUT_FILENO, SELECT_W)) {
- backend_unthrottle(backend, try_output(false));
- }
-
- if (pollwrap_check_fd_rwx(pw, STDERR_FILENO, SELECT_W)) {
- backend_unthrottle(backend, try_output(true));
- }
-}
-
-static bool plink_continue(void *vctx, bool found_any_fd,
- bool ran_any_callback)
-{
- if (!backend_connected(backend) &&
- bufchain_size(&stdout_data) == 0 && bufchain_size(&stderr_data) == 0)
- return false; /* terminate main loop */
- return true;
-}
-
-int main(int argc, char **argv)
-{
- int exitcode;
- bool errors;
- enum TriState sanitise_stdout = AUTO, sanitise_stderr = AUTO;
- bool use_subsystem = false;
- bool just_test_share_exists = false;
- struct winsize size;
- const struct BackendVtable *backvt;
-
- /*
- * Initialise port and protocol to sensible defaults. (These
- * will be overridden by more or less anything.)
- */
- settings_set_default_protocol(PROT_SSH);
- settings_set_default_port(22);
-
- bufchain_init(&stdout_data);
- bufchain_init(&stderr_data);
- bufchain_sink_init(&stdout_bcs, &stdout_data);
- bufchain_sink_init(&stderr_bcs, &stderr_data);
- stdout_bs = BinarySink_UPCAST(&stdout_bcs);
- stderr_bs = BinarySink_UPCAST(&stderr_bcs);
- outgoingeof = EOF_NO;
-
- stderr_tty_init();
- /*
- * Process the command line.
- */
- conf = conf_new();
- do_defaults(NULL, conf);
- settings_set_default_protocol(conf_get_int(conf, CONF_protocol));
- settings_set_default_port(conf_get_int(conf, CONF_port));
- errors = false;
- {
- /*
- * Override the default protocol if PLINK_PROTOCOL is set.
- */
- char *p = getenv("PLINK_PROTOCOL");
- if (p) {
- const struct BackendVtable *vt = backend_vt_from_name(p);
- if (vt) {
- settings_set_default_protocol(vt->protocol);
- settings_set_default_port(vt->default_port);
- conf_set_int(conf, CONF_protocol, vt->protocol);
- conf_set_int(conf, CONF_port, vt->default_port);
- }
- }
- }
- while (--argc) {
- char *p = *++argv;
- int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
- 1, conf);
- if (ret == -2) {
- fprintf(stderr,
- "plink: option \"%s\" requires an argument\n", p);
- errors = true;
- } else if (ret == 2) {
- --argc, ++argv;
- } else if (ret == 1) {
- continue;
- } else if (!strcmp(p, "-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();
- exit(0);
- } else if (!strcmp(p, "-pgpfp")) {
- pgp_fingerprints();
- exit(1);
- } else if (!strcmp(p, "-o")) {
- if (argc <= 1) {
- fprintf(stderr,
- "plink: option \"-o\" requires an argument\n");
- errors = true;
- } else {
- --argc;
- /* Explicitly pass "plink" in place of appname for
- * error reporting purposes. appname will have been
- * set by be_foo.c to something more generic, probably
- * "PuTTY". */
- provide_xrm_string(*++argv, "plink");
- }
- } else if (!strcmp(p, "-shareexists")) {
- just_test_share_exists = true;
- } else if (!strcmp(p, "-fuzznet")) {
- conf_set_int(conf, CONF_proxy_type, PROXY_FUZZ);
- conf_set_str(conf, CONF_proxy_telnet_command, "%host");
- } else if (!strcmp(p, "-sanitise-stdout") ||
- !strcmp(p, "-sanitize-stdout")) {
- sanitise_stdout = FORCE_ON;
- } else if (!strcmp(p, "-no-sanitise-stdout") ||
- !strcmp(p, "-no-sanitize-stdout")) {
- sanitise_stdout = FORCE_OFF;
- } else if (!strcmp(p, "-sanitise-stderr") ||
- !strcmp(p, "-sanitize-stderr")) {
- sanitise_stderr = FORCE_ON;
- } else if (!strcmp(p, "-no-sanitise-stderr") ||
- !strcmp(p, "-no-sanitize-stderr")) {
- sanitise_stderr = FORCE_OFF;
- } else if (!strcmp(p, "-no-antispoof")) {
- console_antispoof_prompt = false;
- } else if (*p != '-') {
- strbuf *cmdbuf = strbuf_new();
-
- while (argc > 0) {
- if (cmdbuf->len > 0)
- put_byte(cmdbuf, ' '); /* add space separator */
- put_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);
-
- /*
- * If we have no better ideas for the remote username, use the local
- * one, as 'ssh' does.
- */
- if (conf_get_str(conf, CONF_username)[0] == '\0') {
- char *user = get_username();
- if (user) {
- conf_set_str(conf, CONF_username, user);
- sfree(user);
- }
- }
-
- /*
- * Apply subsystem status.
- */
- if (use_subsystem)
- conf_set_bool(conf, CONF_ssh_subsys, true);
-
- /*
- * Select protocol. This is farmed out into a table in a
- * separate file to enable an ssh-free variant.
- */
- backvt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol));
- if (!backvt) {
- fprintf(stderr,
- "Internal fault: Unsupported protocol found\n");
- return 1;
- }
-
- if (backvt->flags & BACKEND_NEEDS_TERMINAL) {
- fprintf(stderr,
- "Plink doesn't support %s, which needs terminal emulation\n",
- backvt->displayname);
- return 1;
- }
-
- /*
- * Block SIGPIPE, so that we'll get EPIPE individually on
- * particular network connections that go wrong.
- */
- putty_signal(SIGPIPE, SIG_IGN);
-
- /*
- * Set up the pipe we'll use to tell us about SIGWINCH.
- */
- if (pipe(signalpipe) < 0) {
- perror("pipe");
- exit(1);
- }
- /* We don't want the signal handler to block if the pipe's full. */
- nonblock(signalpipe[0]);
- nonblock(signalpipe[1]);
- cloexec(signalpipe[0]);
- cloexec(signalpipe[1]);
- putty_signal(SIGWINCH, sigwinch);
-
- /*
- * Now that we've got the SIGWINCH handler installed, try to find
- * out the initial terminal size.
- */
- if (ioctl(STDIN_FILENO, TIOCGWINSZ, &size) >= 0) {
- conf_set_int(conf, CONF_width, size.ws_col);
- conf_set_int(conf, CONF_height, size.ws_row);
- }
-
- /*
- * Decide whether to sanitise control sequences out of standard
- * output and standard error.
- *
- * If we weren't given a command-line override, we do this if (a)
- * the fd in question is pointing at a terminal, and (b) we aren't
- * trying to allocate a terminal as part of the session.
- *
- * (Rationale: the risk of control sequences is that they cause
- * confusion when sent to a local terminal, so if there isn't one,
- * no problem. Also, if we allocate a remote terminal, then we
- * sent a terminal type, i.e. we told it what kind of escape
- * sequences we _like_, i.e. we were expecting to receive some.)
- */
- if (sanitise_stdout == FORCE_ON ||
- (sanitise_stdout == AUTO && isatty(STDOUT_FILENO) &&
- conf_get_bool(conf, CONF_nopty))) {
- stdout_scc = stripctrl_new(stdout_bs, true, L'\0');
- stdout_bs = BinarySink_UPCAST(stdout_scc);
- }
- if (sanitise_stderr == FORCE_ON ||
- (sanitise_stderr == AUTO && isatty(STDERR_FILENO) &&
- conf_get_bool(conf, CONF_nopty))) {
- stderr_scc = stripctrl_new(stderr_bs, true, L'\0');
- stderr_bs = BinarySink_UPCAST(stderr_scc);
- }
-
- sk_init();
- uxsel_init();
-
- /*
- * Plink doesn't provide any way to add forwardings after the
- * connection is set up, so if there are none now, we can safely set
- * the "simple" flag.
- */
- if (conf_get_int(conf, CONF_protocol) == PROT_SSH &&
- !conf_get_bool(conf, CONF_x11_forward) &&
- !conf_get_bool(conf, CONF_agentfwd) &&
- !conf_get_str_nthstrkey(conf, CONF_portfwd, 0))
- conf_set_bool(conf, CONF_ssh_simple, true);
-
- if (just_test_share_exists) {
- if (!backvt->test_for_upstream) {
- fprintf(stderr, "Connection sharing not supported for this "
- "connection type (%s)'\n", backvt->displayname);
- return 1;
- }
- if (backvt->test_for_upstream(conf_get_str(conf, CONF_host),
- conf_get_int(conf, CONF_port), conf))
- return 0;
- else
- return 1;
- }
-
- /*
- * Start up the connection.
- */
- logctx = log_init(console_cli_logpolicy, conf);
- {
- char *error, *realhost;
- /* nodelay is only useful if stdin is a terminal device */
- bool nodelay = conf_get_bool(conf, CONF_tcp_nodelay) && isatty(0);
-
- /* This is a good place for a fuzzer to fork us. */
-#ifdef __AFL_HAVE_MANUAL_CONTROL
- __AFL_INIT();
-#endif
-
- error = backend_init(backvt, plink_seat, &backend, logctx, conf,
- conf_get_str(conf, CONF_host),
- conf_get_int(conf, CONF_port),
- &realhost, nodelay,
- conf_get_bool(conf, CONF_tcp_keepalives));
- if (error) {
- fprintf(stderr, "Unable to open connection:\n%s\n", error);
- sfree(error);
- return 1;
- }
- ldisc_create(conf, NULL, backend, plink_seat);
- sfree(realhost);
- }
-
- /*
- * Set up the initial console mode. We don't care if this call
- * fails, because we know we aren't necessarily running in a
- * console.
- */
- local_tty = (tcgetattr(STDIN_FILENO, &orig_termios) == 0);
- atexit(cleanup_termios);
- seat_echoedit_update(plink_seat, 1, 1);
-
- cli_main_loop(plink_pw_setup, plink_pw_check, plink_continue, NULL);
-
- exitcode = backend_exitcode(backend);
- if (exitcode < 0) {
- fprintf(stderr, "Remote process exit code unavailable\n");
- exitcode = 1; /* this is an error condition */
- }
- cleanup_exit(exitcode);
- return exitcode; /* shouldn't happen, but placates gcc */
-}
diff --git a/unix/uxpoll.c b/unix/uxpoll.c
deleted file mode 100644
index 474926bb..00000000
--- a/unix/uxpoll.c
+++ /dev/null
@@ -1,169 +0,0 @@
-/* On some systems this is needed to get poll.h to define eg.. POLLRDNORM */
-#define _XOPEN_SOURCE
-
-#include <poll.h>
-
-#include "putty.h"
-#include "tree234.h"
-
-struct pollwrapper {
- struct pollfd *fds;
- size_t nfd, fdsize;
- tree234 *fdtopos;
-};
-
-typedef struct pollwrap_fdtopos pollwrap_fdtopos;
-struct pollwrap_fdtopos {
- int fd;
- size_t pos;
-};
-
-static int pollwrap_fd_cmp(void *av, void *bv)
-{
- pollwrap_fdtopos *a = (pollwrap_fdtopos *)av;
- pollwrap_fdtopos *b = (pollwrap_fdtopos *)bv;
- return a->fd < b->fd ? -1 : a->fd > b->fd ? +1 : 0;
-}
-
-pollwrapper *pollwrap_new(void)
-{
- pollwrapper *pw = snew(pollwrapper);
- pw->fdsize = 16;
- pw->nfd = 0;
- pw->fds = snewn(pw->fdsize, struct pollfd);
- pw->fdtopos = newtree234(pollwrap_fd_cmp);
- return pw;
-}
-
-void pollwrap_free(pollwrapper *pw)
-{
- pollwrap_clear(pw);
- freetree234(pw->fdtopos);
- sfree(pw->fds);
- sfree(pw);
-}
-
-void pollwrap_clear(pollwrapper *pw)
-{
- pw->nfd = 0;
- for (pollwrap_fdtopos *f2p;
- (f2p = delpos234(pw->fdtopos, 0)) != NULL ;)
- sfree(f2p);
-}
-
-void pollwrap_add_fd_events(pollwrapper *pw, int fd, int events)
-{
- pollwrap_fdtopos *f2p, f2p_find;
-
- assert(fd >= 0);
-
- f2p_find.fd = fd;
- f2p = find234(pw->fdtopos, &f2p_find, NULL);
- if (!f2p) {
- sgrowarray(pw->fds, pw->fdsize, pw->nfd);
- size_t index = pw->nfd++;
- pw->fds[index].fd = fd;
- pw->fds[index].events = pw->fds[index].revents = 0;
-
- f2p = snew(pollwrap_fdtopos);
- f2p->fd = fd;
- f2p->pos = index;
- pollwrap_fdtopos *added = add234(pw->fdtopos, f2p);
- assert(added == f2p);
- }
-
- pw->fds[f2p->pos].events |= events;
-}
-
-/* Omit any of the POLL{RD,WR}{NORM,BAND} flag values that are still
- * not defined by poll.h, just in case */
-#ifndef POLLRDNORM
-#define POLLRDNORM 0
-#endif
-#ifndef POLLRDBAND
-#define POLLRDBAND 0
-#endif
-#ifndef POLLWRNORM
-#define POLLWRNORM 0
-#endif
-#ifndef POLLWRBAND
-#define POLLWRBAND 0
-#endif
-
-#define SELECT_R_IN (POLLIN | POLLRDNORM | POLLRDBAND)
-#define SELECT_W_IN (POLLOUT | POLLWRNORM | POLLWRBAND)
-#define SELECT_X_IN (POLLPRI)
-
-#define SELECT_R_OUT (SELECT_R_IN | POLLERR | POLLHUP)
-#define SELECT_W_OUT (SELECT_W_IN | POLLERR)
-#define SELECT_X_OUT (SELECT_X_IN)
-
-void pollwrap_add_fd_rwx(pollwrapper *pw, int fd, int rwx)
-{
- int events = 0;
- if (rwx & SELECT_R)
- events |= SELECT_R_IN;
- if (rwx & SELECT_W)
- events |= SELECT_W_IN;
- if (rwx & SELECT_X)
- events |= SELECT_X_IN;
- pollwrap_add_fd_events(pw, fd, events);
-}
-
-int pollwrap_poll_instant(pollwrapper *pw)
-{
- return poll(pw->fds, pw->nfd, 0);
-}
-
-int pollwrap_poll_endless(pollwrapper *pw)
-{
- return poll(pw->fds, pw->nfd, -1);
-}
-
-int pollwrap_poll_timeout(pollwrapper *pw, int milliseconds)
-{
- assert(milliseconds >= 0);
- return poll(pw->fds, pw->nfd, milliseconds);
-}
-
-static void pollwrap_get_fd_events_revents(pollwrapper *pw, int fd,
- int *events_p, int *revents_p)
-{
- pollwrap_fdtopos *f2p, f2p_find;
- int events = 0, revents = 0;
-
- assert(fd >= 0);
-
- f2p_find.fd = fd;
- f2p = find234(pw->fdtopos, &f2p_find, NULL);
- if (f2p) {
- events = pw->fds[f2p->pos].events;
- revents = pw->fds[f2p->pos].revents;
- }
-
- if (events_p)
- *events_p = events;
- if (revents_p)
- *revents_p = revents;
-}
-
-int pollwrap_get_fd_events(pollwrapper *pw, int fd)
-{
- int revents;
- pollwrap_get_fd_events_revents(pw, fd, NULL, &revents);
- return revents;
-}
-
-int pollwrap_get_fd_rwx(pollwrapper *pw, int fd)
-{
- int events, revents;
- pollwrap_get_fd_events_revents(pw, fd, &events, &revents);
- int rwx = 0;
- if ((events & POLLIN) && (revents & SELECT_R_OUT))
- rwx |= SELECT_R;
- if ((events & POLLOUT) && (revents & SELECT_W_OUT))
- rwx |= SELECT_W;
- if ((events & POLLPRI) && (revents & SELECT_X_OUT))
- rwx |= SELECT_X;
- return rwx;
-}
diff --git a/unix/uxprint.c b/unix/uxprint.c
deleted file mode 100644
index 3de6d21b..00000000
--- a/unix/uxprint.c
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Printing interface for PuTTY.
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include "putty.h"
-
-struct printer_job_tag {
- FILE *fp;
-};
-
-printer_job *printer_start_job(char *printer)
-{
- printer_job *ret = snew(printer_job);
- /*
- * On Unix, we treat the printer string as the name of a
- * command to pipe to - typically lpr, of course.
- */
- ret->fp = popen(printer, "w");
- if (!ret->fp) {
- sfree(ret);
- ret = NULL;
- }
- return ret;
-}
-
-void printer_job_data(printer_job *pj, const void *data, size_t len)
-{
- if (!pj)
- return;
-
- if (fwrite(data, 1, len, pj->fp) < len)
- /* ignore */;
-}
-
-void printer_finish_job(printer_job *pj)
-{
- if (!pj)
- return;
-
- pclose(pj->fp);
- sfree(pj);
-}
-
-/*
- * There's no sensible way to enumerate printers under Unix, since
- * practically any valid Unix command is a valid printer :-) So
- * these are useless stub functions, and uxcfg.c will disable the
- * drop-down list in the printer configurer.
- */
-printer_enum *printer_start_enum(int *nprinters_ptr) {
- *nprinters_ptr = 0;
- return NULL;
-}
-char *printer_get_name(printer_enum *pe, int i) { return NULL;
-}
-void printer_finish_enum(printer_enum *pe) { }
diff --git a/unix/uxproxy.c b/unix/uxproxy.c
deleted file mode 100644
index 0a637bd9..00000000
--- a/unix/uxproxy.c
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * uxproxy.c: Unix implementation of platform_new_connection(),
- * supporting an OpenSSH-like proxy command.
- */
-
-#include <stdio.h>
-#include <assert.h>
-#include <errno.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-#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;
-
- int to_cmd_pipe[2], from_cmd_pipe[2], cmd_err_pipe[2], pid, proxytype;
- int infd, outfd, inerrfd;
-
- proxytype = conf_get_int(conf, CONF_proxy_type);
- if (proxytype != PROXY_CMD && proxytype != PROXY_FUZZ)
- return NULL;
-
- if (proxytype == PROXY_CMD) {
- cmd = format_telnet_command(addr, port, conf);
-
- {
- char *logmsg = dupprintf("Starting local proxy command: %s", cmd);
- plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0);
- sfree(logmsg);
- }
-
- /*
- * Create the pipes to the proxy command, and spawn the proxy
- * command process.
- */
- if (pipe(to_cmd_pipe) < 0 ||
- pipe(from_cmd_pipe) < 0 ||
- pipe(cmd_err_pipe) < 0) {
- sfree(cmd);
- return new_error_socket_fmt(plug, "pipe: %s", strerror(errno));
- }
- cloexec(to_cmd_pipe[1]);
- cloexec(from_cmd_pipe[0]);
- cloexec(cmd_err_pipe[0]);
-
- pid = fork();
- if (pid == 0) {
- close(0);
- close(1);
- dup2(to_cmd_pipe[0], 0);
- dup2(from_cmd_pipe[1], 1);
- close(to_cmd_pipe[0]);
- close(from_cmd_pipe[1]);
- dup2(cmd_err_pipe[1], 2);
- noncloexec(0);
- noncloexec(1);
- execl("/bin/sh", "sh", "-c", cmd, (void *)NULL);
- _exit(255);
- }
-
- sfree(cmd);
-
- if (pid < 0)
- return new_error_socket_fmt(plug, "fork: %s", strerror(errno));
-
- close(to_cmd_pipe[0]);
- close(from_cmd_pipe[1]);
- close(cmd_err_pipe[1]);
-
- outfd = to_cmd_pipe[1];
- infd = from_cmd_pipe[0];
- inerrfd = cmd_err_pipe[0];
- } else {
- cmd = format_telnet_command(addr, port, conf);
- outfd = open("/dev/null", O_WRONLY);
- if (outfd == -1) {
- sfree(cmd);
- return new_error_socket_fmt(
- plug, "/dev/null: %s", strerror(errno));
- }
- infd = open(cmd, O_RDONLY);
- if (infd == -1) {
- Socket *toret = new_error_socket_fmt(
- plug, "%s: %s", cmd, strerror(errno));
- sfree(cmd);
- close(outfd);
- return toret;
- }
- sfree(cmd);
- inerrfd = -1;
- }
-
- /* We are responsible for this and don't need it any more */
- sk_addr_free(addr);
-
- return make_fd_socket(infd, outfd, inerrfd, plug);
-}
diff --git a/unix/uxpsusan.c b/unix/uxpsusan.c
deleted file mode 100644
index c60728ce..00000000
--- a/unix/uxpsusan.c
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- * 'psusan': Pseudo Ssh for Untappable, Separately Authenticated Networks
- *
- * This is a standalone application that speaks on its standard I/O
- * (or a listening Unix-domain socket) the server end of the bare
- * ssh-connection protocol used by PuTTY's connection sharing.
- *
- * The idea of this tool is that you can use it to communicate across
- * any 8-bit-clean data channel between two inconveniently separated
- * domains, provided the channel is already (as the name suggests)
- * adequately secured against eavesdropping and modification and
- * already authenticated as the right user.
- *
- * If you're sitting at one end of such a channel and want to type
- * commands into the other end, the most obvious thing to do is to run
- * a terminal session directly over it. But if you run psusan at one
- * end, and a PuTTY (or compatible) client at the other end, then you
- * not only get a single terminal session: you get all the other SSH
- * amenities, like the ability to spawn extra terminal sessions,
- * forward ports or X11 connections, even forward an SSH agent.
- *
- * There are a surprising number of channels of that kind; see the man
- * page for some examples.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <signal.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <termios.h>
-#include <pwd.h>
-#include <sys/ioctl.h>
-#include <sys/time.h>
-
-#include "putty.h"
-#include "mpint.h"
-#include "ssh.h"
-#include "sshserver.h"
-
-const char *const appname = "psusan";
-
-void modalfatalbox(const char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "FATAL ERROR: ");
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
- exit(1);
-}
-void nonfatal(const char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "ERROR: ");
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
-}
-
-char *platform_default_s(const char *name)
-{
- return NULL;
-}
-
-bool platform_default_b(const char *name, bool def)
-{
- return def;
-}
-
-int platform_default_i(const char *name, int def)
-{
- return def;
-}
-
-FontSpec *platform_default_fontspec(const char *name)
-{
- return fontspec_new("");
-}
-
-Filename *platform_default_filename(const char *name)
-{
- return filename_from_str("");
-}
-
-char *x_get_default(const char *key)
-{
- return NULL; /* this is a stub */
-}
-
-void old_keyfile_warning(void) { }
-
-void timer_change_notify(unsigned long next)
-{
-}
-
-char *platform_get_x_display(void) { return NULL; }
-
-void make_unix_sftp_filehandle_key(void *vdata, size_t size)
-{
- /* psusan runs without a random number generator, so we can't make
- * this up by random_read. Fortunately, psusan is also
- * non-adversarial, so it's safe to generate this trivially. */
- unsigned char *data = (unsigned char *)vdata;
- for (size_t i = 0; i < size; i++)
- data[i] = (unsigned)rand() / ((unsigned)RAND_MAX / 256);
-}
-
-static bool verbose;
-
-struct server_instance {
- unsigned id;
- LogPolicy logpolicy;
-};
-
-static void log_to_stderr(unsigned id, const char *msg)
-{
- if (!verbose)
- return;
- if (id != (unsigned)-1)
- fprintf(stderr, "#%u: ", id);
- fputs(msg, stderr);
- fputc('\n', stderr);
- fflush(stderr);
-}
-
-static void server_eventlog(LogPolicy *lp, const char *event)
-{
- struct server_instance *inst = container_of(
- lp, struct server_instance, logpolicy);
- if (verbose)
- log_to_stderr(inst->id, event);
-}
-
-static void server_logging_error(LogPolicy *lp, const char *event)
-{
- struct server_instance *inst = container_of(
- lp, struct server_instance, logpolicy);
- log_to_stderr(inst->id, event); /* unconditional */
-}
-
-static int server_askappend(
- LogPolicy *lp, Filename *filename,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- return 2; /* always overwrite (FIXME: could make this a cmdline option) */
-}
-
-static const LogPolicyVtable server_logpolicy_vt = {
- .eventlog = server_eventlog,
- .askappend = server_askappend,
- .logging_error = server_logging_error,
- .verbose = null_lp_verbose_no,
-};
-
-static void show_help(FILE *fp)
-{
- fputs("usage: psusan [options]\n"
- "options: --listen SOCKETPATH listen for connections on a Unix-domain socket\n"
- " --listen-once (with --listen) stop after one connection\n"
- " --verbose print log messages to standard error\n"
- " --sessiondir DIR cwd for session subprocess (default $HOME)\n"
- " --sshlog FILE write ssh-connection packet log to FILE\n"
- " --sshrawlog FILE write packets and raw data log to FILE\n"
- "also: psusan --help show this text\n"
- " psusan --version show version information\n", fp);
-}
-
-static void show_version_and_exit(void)
-{
- char *buildinfo_text = buildinfo("\n");
- printf("%s: %s\n%s\n", appname, ver, buildinfo_text);
- sfree(buildinfo_text);
- exit(0);
-}
-
-const bool buildinfo_gtk_relevant = false;
-
-static bool listening = false, listen_once = false;
-static bool finished = false;
-void server_instance_terminated(LogPolicy *lp)
-{
- struct server_instance *inst = container_of(
- lp, struct server_instance, logpolicy);
-
- if (listening && !listen_once) {
- log_to_stderr(inst->id, "connection terminated");
- } else {
- finished = true;
- }
-
- sfree(inst);
-}
-
-bool psusan_continue(void *ctx, bool fd, bool cb)
-{
- return !finished;
-}
-
-static bool longoptarg(const char *arg, const char *expected,
- const char **val, int *argcp, char ***argvp)
-{
- int len = strlen(expected);
- if (memcmp(arg, expected, len))
- return false;
- if (arg[len] == '=') {
- *val = arg + len + 1;
- return true;
- } else if (arg[len] == '\0') {
- if (--*argcp > 0) {
- *val = *++*argvp;
- return true;
- } else {
- fprintf(stderr, "%s: option %s expects an argument\n",
- appname, expected);
- exit(1);
- }
- }
- return false;
-}
-
-static bool longoptnoarg(const char *arg, const char *expected)
-{
- int len = strlen(expected);
- if (memcmp(arg, expected, len))
- return false;
- if (arg[len] == '=') {
- fprintf(stderr, "%s: option %s expects no argument\n",
- appname, expected);
- exit(1);
- } else if (arg[len] == '\0') {
- return true;
- }
- return false;
-}
-
-struct server_config {
- Conf *conf;
- const SshServerConfig *ssc;
-
- unsigned next_id;
-
- Socket *listening_socket;
- Plug listening_plug;
-};
-
-static Plug *server_conn_plug(
- struct server_config *cfg, struct server_instance **inst_out)
-{
- struct server_instance *inst = snew(struct server_instance);
-
- memset(inst, 0, sizeof(*inst));
-
- inst->id = cfg->next_id++;
- inst->logpolicy.vt = &server_logpolicy_vt;
-
- if (inst_out)
- *inst_out = inst;
-
- return ssh_server_plug(
- cfg->conf, cfg->ssc, NULL, 0, NULL, NULL,
- &inst->logpolicy, &unix_live_sftpserver_vt);
-}
-
-static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
- const char *error_msg, int error_code)
-{
- log_to_stderr(-1, error_msg);
-}
-
-static void server_closing(Plug *plug, const char *error_msg, int error_code,
- bool calling_back)
-{
- log_to_stderr(-1, error_msg);
-}
-
-static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
-{
- struct server_config *cfg = container_of(
- p, struct server_config, listening_plug);
- Socket *s;
- const char *err;
-
- struct server_instance *inst;
-
- if (listen_once) {
- if (!cfg->listening_socket) /* in case of rapid double-accept */
- return 1;
- sk_close(cfg->listening_socket);
- cfg->listening_socket = NULL;
- }
-
- Plug *plug = server_conn_plug(cfg, &inst);
- s = constructor(ctx, plug);
- if ((err = sk_socket_error(s)) != NULL)
- return 1;
-
- SocketPeerInfo *pi = sk_peer_info(s);
-
- char *msg = dupprintf("new connection from %s", pi->log_text);
- log_to_stderr(inst->id, msg);
- sfree(msg);
- sk_free_peer_info(pi);
-
- sk_set_frozen(s, false);
- ssh_server_start(plug, s);
- return 0;
-}
-
-static const PlugVtable server_plugvt = {
- .log = server_log,
- .closing = server_closing,
- .accepting = server_accepting,
-};
-
-unsigned auth_methods(AuthPolicy *ap)
-{ return 0; }
-bool auth_none(AuthPolicy *ap, ptrlen username)
-{ return false; }
-int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password,
- ptrlen *new_password_opt)
-{ return 0; }
-bool auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob)
-{ return false; }
-RSAKey *auth_publickey_ssh1(
- AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus)
-{ return NULL; }
-AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username)
-{ return NULL; }
-int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses)
-{ return -1; }
-char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username)
-{ return NULL; }
-bool auth_ssh1int_response(AuthPolicy *ap, ptrlen response)
-{ return false; }
-bool auth_successful(AuthPolicy *ap, ptrlen username, unsigned method)
-{ return false; }
-
-int main(int argc, char **argv)
-{
- const char *listen_socket = NULL;
-
- SshServerConfig ssc;
-
- Conf *conf = make_ssh_server_conf();
-
- memset(&ssc, 0, sizeof(ssc));
-
- ssc.application_name = "PSUSAN";
- ssc.session_starting_dir = getenv("HOME");
- ssc.bare_connection = true;
-
- while (--argc > 0) {
- const char *arg = *++argv;
- const char *val;
-
- if (longoptnoarg(arg, "--help")) {
- show_help(stdout);
- exit(0);
- } else if (longoptnoarg(arg, "--version")) {
- show_version_and_exit();
- } else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) {
- verbose = true;
- } else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) {
- ssc.session_starting_dir = val;
- } else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) ||
- longoptarg(arg, "-sshlog", &val, &argc, &argv)) {
- Filename *logfile = filename_from_str(val);
- conf_set_filename(conf, CONF_logfilename, logfile);
- filename_free(logfile);
- conf_set_int(conf, CONF_logtype, LGTYP_PACKETS);
- conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
- } else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) ||
- longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) {
- Filename *logfile = filename_from_str(val);
- conf_set_filename(conf, CONF_logfilename, logfile);
- filename_free(logfile);
- conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW);
- conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
- } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) {
- listen_socket = val;
- } else if (!strcmp(arg, "--listen-once")) {
- listen_once = true;
- } else {
- fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg);
- exit(1);
- }
- }
-
- sk_init();
- uxsel_init();
-
- struct server_config scfg;
- scfg.conf = conf;
- scfg.ssc = &ssc;
- scfg.next_id = 0;
-
- if (listen_socket) {
- listening = true;
- scfg.listening_plug.vt = &server_plugvt;
- SockAddr *addr = unix_sock_addr(listen_socket);
- scfg.listening_socket = new_unix_listener(addr, &scfg.listening_plug);
- char *msg = dupprintf("listening on Unix socket %s", listen_socket);
- log_to_stderr(-1, msg);
- sfree(msg);
- } else {
- struct server_instance *inst;
- Plug *plug = server_conn_plug(&scfg, &inst);
- ssh_server_start(plug, make_fd_socket(0, 1, -1, plug));
- log_to_stderr(inst->id, "running directly on stdio");
- }
-
- cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check,
- psusan_continue, NULL);
-
- return 0;
-}
diff --git a/unix/uxpterm.c b/unix/uxpterm.c
deleted file mode 100644
index b18e3442..00000000
--- a/unix/uxpterm.c
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * pterm main program.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "putty.h"
-
-const char *const appname = "pterm";
-const bool use_event_log = false; /* pterm doesn't need it */
-const bool new_session = false, saved_sessions = false; /* or these */
-const bool dup_check_launchable = false; /* no need to check host name
- * in conf */
-const bool use_pty_argv = true;
-
-const unsigned cmdline_tooltype = TOOLTYPE_NONNETWORK;
-
-/* gtkwin.c will call this, and in pterm it's not needed */
-void noise_ultralight(NoiseSourceId id, unsigned long data) { }
-
-const struct BackendVtable *select_backend(Conf *conf)
-{
- return &pty_backend;
-}
-
-void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx)
-{
- /*
- * This is a no-op in pterm, except that we'll ensure the protocol
- * is set to -1 to inhibit the useless Connection panel in the
- * config box. So we do that and then just immediately call the
- * post-dialog function with a positive result.
- */
- conf_set_int(conf, CONF_protocol, -1);
- after(afterctx, 1);
-}
-
-void cleanup_exit(int code)
-{
- exit(code);
-}
-
-void setup(bool single)
-{
- settings_set_default_protocol(-1);
-
- if (single)
- pty_pre_init();
-}
diff --git a/unix/uxpty.c b/unix/uxpty.c
deleted file mode 100644
index bfd0de57..00000000
--- a/unix/uxpty.c
+++ /dev/null
@@ -1,1612 +0,0 @@
-/*
- * Pseudo-tty backend for pterm.
- */
-
-#define _GNU_SOURCE
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <signal.h>
-#include <assert.h>
-#include <fcntl.h>
-#include <termios.h>
-#include <grp.h>
-#include <utmp.h>
-#include <pwd.h>
-#include <time.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <sys/ioctl.h>
-#include <errno.h>
-#include <termios.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "sshserver.h" /* to check the prototypes of server-needed things */
-#include "tree234.h"
-
-#ifndef OMIT_UTMP
-#include <utmpx.h>
-#endif
-
-/* updwtmpx() needs the name of the wtmp file. Try to find it. */
-#ifndef WTMPX_FILE
-#ifdef _PATH_WTMPX
-#define WTMPX_FILE _PATH_WTMPX
-#else
-#define WTMPX_FILE "/var/log/wtmpx"
-#endif
-#endif
-
-#ifndef LASTLOG_FILE
-#ifdef _PATH_LASTLOG
-#define LASTLOG_FILE _PATH_LASTLOG
-#else
-#define LASTLOG_FILE "/var/log/lastlog"
-#endif
-#endif
-
-/*
- * Set up a default for vaguely sane systems. The idea is that if
- * OMIT_UTMP is not defined, then at least one of the symbols which
- * enable particular forms of utmp processing should be, if only so
- * that a link error can warn you that you should have defined
- * OMIT_UTMP if you didn't want any. Currently HAVE_PUTUTLINE is
- * the only such symbol.
- */
-#ifndef OMIT_UTMP
-#if !defined HAVE_PUTUTLINE
-#define HAVE_PUTUTLINE
-#endif
-#endif
-
-typedef struct Pty Pty;
-
-/*
- * The pty_signal_pipe, along with the SIGCHLD handler, must be
- * process-global rather than session-specific.
- */
-static int pty_signal_pipe[2] = { -1, -1 }; /* obviously bogus initial val */
-
-typedef struct PtyFd {
- int fd;
- Pty *pty;
-} PtyFd;
-
-struct Pty {
- Conf *conf;
-
- int master_fd, slave_fd;
- int pipefds[6];
- PtyFd fds[3];
- int master_i, master_o, master_e;
-
- Seat *seat;
- char name[FILENAME_MAX];
- pid_t child_pid;
- int term_width, term_height;
- bool child_dead, finished;
- int exit_code;
- bufchain output_data;
- bool pending_eof;
- Backend backend;
-};
-
-/*
- * We store all the (active) PtyFd structures in a tree sorted by fd,
- * so that when we get an uxsel notification we know which backend
- * instance is the owner of the pty that caused it, and then we can
- * find out which fd is the relevant one too.
- */
-static int ptyfd_compare(void *av, void *bv)
-{
- PtyFd *a = (PtyFd *)av;
- PtyFd *b = (PtyFd *)bv;
-
- if (a->fd < b->fd)
- return -1;
- else if (a->fd > b->fd)
- return +1;
- return 0;
-}
-
-static int ptyfd_find(void *av, void *bv)
-{
- int a = *(int *)av;
- PtyFd *b = (PtyFd *)bv;
-
- if (a < b->fd)
- return -1;
- else if (a > b->fd)
- return +1;
- return 0;
-}
-
-static tree234 *ptyfds = NULL;
-
-/*
- * We also have a tree of Pty structures themselves, sorted by child
- * pid, so that when we wait() in response to the signal we know which
- * backend instance is the owner of the process that caused the
- * signal.
- */
-static int pty_compare_by_pid(void *av, void *bv)
-{
- Pty *a = (Pty *)av;
- Pty *b = (Pty *)bv;
-
- if (a->child_pid < b->child_pid)
- return -1;
- else if (a->child_pid > b->child_pid)
- return +1;
- return 0;
-}
-
-static int pty_find_by_pid(void *av, void *bv)
-{
- pid_t a = *(pid_t *)av;
- Pty *b = (Pty *)bv;
-
- if (a < b->child_pid)
- return -1;
- else if (a > b->child_pid)
- return +1;
- return 0;
-}
-
-static tree234 *ptys_by_pid = NULL;
-
-/*
- * If we are using pty_pre_init(), it will need to have already
- * allocated a pty structure, which we must then return from
- * pty_init() rather than allocating a new one. Here we store that
- * structure between allocation and use.
- *
- * Note that although most of this module is entirely capable of
- * handling multiple ptys in a single process, pty_pre_init() is
- * fundamentally _dependent_ on there being at most one pty per
- * process, so the normal static-data constraints don't apply.
- *
- * Likewise, since utmp is only used via pty_pre_init, it too must
- * be single-instance, so we can declare utmp-related variables
- * here.
- */
-static Pty *single_pty = NULL;
-
-#ifndef OMIT_UTMP
-static pid_t pty_utmp_helper_pid = -1;
-static int pty_utmp_helper_pipe = -1;
-static bool pty_stamped_utmp;
-static struct utmpx utmp_entry;
-#endif
-
-/*
- * pty_argv is a grievous hack to allow a proper argv to be passed
- * through from the Unix command line. Again, it doesn't really
- * make sense outside a one-pty-per-process setup.
- */
-char **pty_argv;
-
-char *pty_osx_envrestore_prefix;
-
-static void pty_close(Pty *pty);
-static void pty_try_write(Pty *pty);
-
-#ifndef OMIT_UTMP
-static void setup_utmp(char *ttyname, char *location)
-{
-#ifdef HAVE_LASTLOG
- struct lastlog lastlog_entry;
- FILE *lastlog;
-#endif
- struct passwd *pw;
- struct timeval tv;
-
- pw = getpwuid(getuid());
- if (!pw)
- return; /* can't stamp utmp if we don't have a username */
- memset(&utmp_entry, 0, sizeof(utmp_entry));
- utmp_entry.ut_type = USER_PROCESS;
- utmp_entry.ut_pid = getpid();
-#if __GNUC__ >= 8
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wstringop-truncation"
-#endif // __GNUC__ >= 8
- strncpy(utmp_entry.ut_line, ttyname+5, lenof(utmp_entry.ut_line));
- strncpy(utmp_entry.ut_id, ttyname+8, lenof(utmp_entry.ut_id));
- strncpy(utmp_entry.ut_user, pw->pw_name, lenof(utmp_entry.ut_user));
- strncpy(utmp_entry.ut_host, location, lenof(utmp_entry.ut_host));
-#if __GNUC__ >= 8
-# pragma GCC diagnostic pop
-#endif // __GNUC__ >= 8
- /*
- * Apparently there are some architectures where (struct
- * utmpx).ut_tv is not essentially struct timeval (e.g. Linux
- * amd64). Hence the temporary.
- */
- gettimeofday(&tv, NULL);
- utmp_entry.ut_tv.tv_sec = tv.tv_sec;
- utmp_entry.ut_tv.tv_usec = tv.tv_usec;
-
- setutxent();
- pututxline(&utmp_entry);
- endutxent();
-
- updwtmpx(WTMPX_FILE, &utmp_entry);
-
-#ifdef HAVE_LASTLOG
- memset(&lastlog_entry, 0, sizeof(lastlog_entry));
- strncpy(lastlog_entry.ll_line, ttyname+5, lenof(lastlog_entry.ll_line));
- strncpy(lastlog_entry.ll_host, location, lenof(lastlog_entry.ll_host));
- time(&lastlog_entry.ll_time);
- if ((lastlog = fopen(LASTLOG_FILE, "r+")) != NULL) {
- fseek(lastlog, sizeof(lastlog_entry) * getuid(), SEEK_SET);
- fwrite(&lastlog_entry, 1, sizeof(lastlog_entry), lastlog);
- fclose(lastlog);
- }
-#endif
-
- pty_stamped_utmp = true;
-
-}
-
-static void cleanup_utmp(void)
-{
- struct timeval tv;
-
- if (!pty_stamped_utmp)
- return;
-
- utmp_entry.ut_type = DEAD_PROCESS;
- memset(utmp_entry.ut_user, 0, lenof(utmp_entry.ut_user));
- gettimeofday(&tv, NULL);
- utmp_entry.ut_tv.tv_sec = tv.tv_sec;
- utmp_entry.ut_tv.tv_usec = tv.tv_usec;
-
- updwtmpx(WTMPX_FILE, &utmp_entry);
-
- memset(utmp_entry.ut_line, 0, lenof(utmp_entry.ut_line));
- utmp_entry.ut_tv.tv_sec = 0;
- utmp_entry.ut_tv.tv_usec = 0;
-
- setutxent();
- pututxline(&utmp_entry);
- endutxent();
-
- pty_stamped_utmp = false; /* ensure we never double-cleanup */
-}
-#endif
-
-static void sigchld_handler(int signum)
-{
- if (write(pty_signal_pipe[1], "x", 1) <= 0)
- /* not much we can do about it */;
-}
-
-static void pty_setup_sigchld_handler(void)
-{
- static bool setup = false;
- if (!setup) {
- putty_signal(SIGCHLD, sigchld_handler);
- setup = true;
- }
-}
-
-#ifndef OMIT_UTMP
-static void fatal_sig_handler(int signum)
-{
- putty_signal(signum, SIG_DFL);
- cleanup_utmp();
- raise(signum);
-}
-#endif
-
-static int pty_open_slave(Pty *pty)
-{
- if (pty->slave_fd < 0) {
- pty->slave_fd = open(pty->name, O_RDWR);
- cloexec(pty->slave_fd);
- }
-
- return pty->slave_fd;
-}
-
-static void pty_open_master(Pty *pty)
-{
-#ifdef BSD_PTYS
- const char chars1[] = "pqrstuvwxyz";
- const char chars2[] = "0123456789abcdef";
- const char *p1, *p2;
- char master_name[20];
- struct group *gp;
-
- for (p1 = chars1; *p1; p1++)
- for (p2 = chars2; *p2; p2++) {
- sprintf(master_name, "/dev/pty%c%c", *p1, *p2);
- pty->master_fd = open(master_name, O_RDWR);
- if (pty->master_fd >= 0) {
- if (geteuid() == 0 ||
- access(master_name, R_OK | W_OK) == 0) {
- /*
- * We must also check at this point that we are
- * able to open the slave side of the pty. We
- * wouldn't want to allocate the wrong master,
- * get all the way down to forking, and _then_
- * find we're unable to open the slave.
- */
- strcpy(pty->name, master_name);
- pty->name[5] = 't'; /* /dev/ptyXX -> /dev/ttyXX */
-
- cloexec(pty->master_fd);
-
- if (pty_open_slave(pty) >= 0 &&
- access(pty->name, R_OK | W_OK) == 0)
- goto got_one;
- if (pty->slave_fd > 0)
- close(pty->slave_fd);
- pty->slave_fd = -1;
- }
- close(pty->master_fd);
- }
- }
-
- /* If we get here, we couldn't get a tty at all. */
- fprintf(stderr, "pterm: unable to open a pseudo-terminal device\n");
- exit(1);
-
- got_one:
-
- /* We need to chown/chmod the /dev/ttyXX device. */
- gp = getgrnam("tty");
- chown(pty->name, getuid(), gp ? gp->gr_gid : -1);
- chmod(pty->name, 0600);
-#else
-
- const int flags = O_RDWR
-#ifdef O_NOCTTY
- | O_NOCTTY
-#endif
- ;
-
-#ifdef HAVE_POSIX_OPENPT
-#ifdef SET_NONBLOCK_VIA_OPENPT
- /*
- * OS X, as of 10.10 at least, doesn't permit me to set O_NONBLOCK
- * on pty master fds via the usual fcntl mechanism. Fortunately,
- * it does let me work around this by adding O_NONBLOCK to the
- * posix_openpt flags parameter, which isn't a documented use of
- * the API but seems to work. So we'll do that for now.
- */
- pty->master_fd = posix_openpt(flags | O_NONBLOCK);
-#else
- pty->master_fd = posix_openpt(flags);
-#endif
-
- if (pty->master_fd < 0) {
- perror("posix_openpt");
- exit(1);
- }
-#else
- pty->master_fd = open("/dev/ptmx", flags);
-
- if (pty->master_fd < 0) {
- perror("/dev/ptmx: open");
- exit(1);
- }
-#endif
-
- if (grantpt(pty->master_fd) < 0) {
- perror("grantpt");
- exit(1);
- }
-
- if (unlockpt(pty->master_fd) < 0) {
- perror("unlockpt");
- exit(1);
- }
-
- cloexec(pty->master_fd);
-
- pty->name[FILENAME_MAX-1] = '\0';
- strncpy(pty->name, ptsname(pty->master_fd), FILENAME_MAX-1);
-#endif
-
-#ifndef SET_NONBLOCK_VIA_OPENPT
- nonblock(pty->master_fd);
-#endif
-}
-
-static Pty *new_pty_struct(void)
-{
- Pty *pty = snew(Pty);
- pty->conf = NULL;
- pty->pending_eof = false;
- bufchain_init(&pty->output_data);
- return pty;
-}
-
-/*
- * Pre-initialisation. This is here to get around the fact that GTK
- * doesn't like being run in setuid/setgid programs (probably
- * sensibly). So before we initialise GTK - and therefore before we
- * even process the command line - we check to see if we're running
- * set[ug]id. If so, we open our pty master _now_, chown it as
- * necessary, and drop privileges. We can always close it again
- * later. If we're potentially going to be doing utmp as well, we
- * also fork off a utmp helper process and communicate with it by
- * means of a pipe; the utmp helper will keep privileges in order
- * to clean up utmp when we exit (i.e. when its end of our pipe
- * closes).
- */
-void pty_pre_init(void)
-{
-#ifndef NO_PTY_PRE_INIT
-
- Pty *pty;
-
-#ifndef OMIT_UTMP
- pid_t pid;
- int pipefd[2];
-#endif
-
- pty = single_pty = new_pty_struct();
-
- /* set the child signal handler straight away; it needs to be set
- * before we ever fork. */
- pty_setup_sigchld_handler();
- pty->master_fd = pty->slave_fd = -1;
-#ifndef OMIT_UTMP
- pty_stamped_utmp = false;
-#endif
-
- if (geteuid() != getuid() || getegid() != getgid()) {
- pty_open_master(pty);
-
-#ifndef OMIT_UTMP
- /*
- * Fork off the utmp helper.
- */
- if (pipe(pipefd) < 0) {
- perror("pterm: pipe");
- exit(1);
- }
- cloexec(pipefd[0]);
- cloexec(pipefd[1]);
- pid = fork();
- if (pid < 0) {
- perror("pterm: fork");
- exit(1);
- } else if (pid == 0) {
- char display[128], buffer[128];
- int dlen, ret;
-
- close(pipefd[1]);
- /*
- * Now sit here until we receive a display name from the
- * other end of the pipe, and then stamp utmp. Unstamp utmp
- * again, and exit, when the pipe closes.
- */
-
- dlen = 0;
- while (1) {
-
- ret = read(pipefd[0], buffer, lenof(buffer));
- if (ret <= 0) {
- cleanup_utmp();
- _exit(0);
- } else if (!pty_stamped_utmp) {
- if (dlen < lenof(display))
- memcpy(display+dlen, buffer,
- min(ret, lenof(display)-dlen));
- if (buffer[ret-1] == '\0') {
- /*
- * Now we have a display name. NUL-terminate
- * it, and stamp utmp.
- */
- display[lenof(display)-1] = '\0';
- /*
- * Trap as many fatal signals as we can in the
- * hope of having the best possible chance to
- * clean up utmp before termination. We are
- * unfortunately unprotected against SIGKILL,
- * but that's life.
- */
- putty_signal(SIGHUP, fatal_sig_handler);
- putty_signal(SIGINT, fatal_sig_handler);
- putty_signal(SIGQUIT, fatal_sig_handler);
- putty_signal(SIGILL, fatal_sig_handler);
- putty_signal(SIGABRT, fatal_sig_handler);
- putty_signal(SIGFPE, fatal_sig_handler);
- putty_signal(SIGPIPE, fatal_sig_handler);
- putty_signal(SIGALRM, fatal_sig_handler);
- putty_signal(SIGTERM, fatal_sig_handler);
- putty_signal(SIGSEGV, fatal_sig_handler);
- putty_signal(SIGUSR1, fatal_sig_handler);
- putty_signal(SIGUSR2, fatal_sig_handler);
-#ifdef SIGBUS
- putty_signal(SIGBUS, fatal_sig_handler);
-#endif
-#ifdef SIGPOLL
- putty_signal(SIGPOLL, fatal_sig_handler);
-#endif
-#ifdef SIGPROF
- putty_signal(SIGPROF, fatal_sig_handler);
-#endif
-#ifdef SIGSYS
- putty_signal(SIGSYS, fatal_sig_handler);
-#endif
-#ifdef SIGTRAP
- putty_signal(SIGTRAP, fatal_sig_handler);
-#endif
-#ifdef SIGVTALRM
- putty_signal(SIGVTALRM, fatal_sig_handler);
-#endif
-#ifdef SIGXCPU
- putty_signal(SIGXCPU, fatal_sig_handler);
-#endif
-#ifdef SIGXFSZ
- putty_signal(SIGXFSZ, fatal_sig_handler);
-#endif
-#ifdef SIGIO
- putty_signal(SIGIO, fatal_sig_handler);
-#endif
- setup_utmp(pty->name, display);
- }
- }
- }
- } else {
- close(pipefd[0]);
- pty_utmp_helper_pid = pid;
- pty_utmp_helper_pipe = pipefd[1];
- }
-#endif
- }
-
- /* Drop privs. */
- {
-#ifndef HAVE_NO_SETRESUID
- int gid = getgid(), uid = getuid();
- int setresgid(gid_t, gid_t, gid_t);
- int setresuid(uid_t, uid_t, uid_t);
- if (setresgid(gid, gid, gid) < 0) {
- perror("setresgid");
- exit(1);
- }
- if (setresuid(uid, uid, uid) < 0) {
- perror("setresuid");
- exit(1);
- }
-#else
- if (setgid(getgid()) < 0) {
- perror("setgid");
- exit(1);
- }
- if (setuid(getuid()) < 0) {
- perror("setuid");
- exit(1);
- }
-#endif
- }
-
-#endif /* NO_PTY_PRE_INIT */
-
-}
-
-static void pty_try_wait(void);
-
-static void pty_real_select_result(Pty *pty, int fd, int event, int status)
-{
- char buf[4096];
- int ret;
- bool finished = false;
-
- if (event < 0) {
- /*
- * We've been called because our child process did
- * something. `status' tells us what.
- */
- if ((WIFEXITED(status) || WIFSIGNALED(status))) {
- /*
- * The primary child process died.
- */
- pty->child_dead = true;
- del234(ptys_by_pid, pty);
- pty->exit_code = status;
-
- /*
- * If this is an ordinary pty session, this is also the
- * moment to terminate the whole backend.
- *
- * We _could_ instead keep the terminal open for remaining
- * subprocesses to output to, but conventional wisdom
- * seems to feel that that's the Wrong Thing for an
- * xterm-alike, so we bail out now (though we don't
- * necessarily _close_ the window, depending on the state
- * of Close On Exit). This would be easy enough to change
- * or make configurable if necessary.
- */
- if (pty->master_fd >= 0)
- finished = true;
- }
- } else {
- if (event == SELECT_R) {
- bool is_stdout = (fd == pty->master_o);
-
- ret = read(fd, buf, sizeof(buf));
-
- /*
- * Treat EIO on a pty master as equivalent to EOF (because
- * that's how the kernel seems to report the event where
- * the last process connected to the other end of the pty
- * went away).
- */
- if (fd == pty->master_fd && ret < 0 && errno == EIO)
- ret = 0;
-
- if (ret == 0) {
- /*
- * EOF on this input fd, so to begin with, we may as
- * well close it, and remove all references to it in
- * the pty's fd fields.
- */
- uxsel_del(fd);
- close(fd);
- if (pty->master_fd == fd)
- pty->master_fd = -1;
- if (pty->master_o == fd)
- pty->master_o = -1;
- if (pty->master_e == fd)
- pty->master_e = -1;
-
- if (is_stdout) {
- /*
- * We assume a clean exit if the pty (or stdout
- * pipe) has closed, but the actual child process
- * hasn't. The only way I can imagine this
- * happening is if it detaches itself from the pty
- * and goes daemonic - in which case the expected
- * usage model would precisely _not_ be for the
- * pterm window to hang around!
- */
- finished = true;
- pty_try_wait(); /* one last effort to collect exit code */
- if (!pty->child_dead)
- pty->exit_code = 0;
- }
- } else if (ret < 0) {
- perror("read pty master");
- exit(1);
- } else if (ret > 0) {
- seat_output(pty->seat, !is_stdout, buf, ret);
- }
- } else if (event == SELECT_W) {
- /*
- * Attempt to send data down the pty.
- */
- pty_try_write(pty);
- }
- }
-
- if (finished && !pty->finished) {
- int close_on_exit;
- int i;
-
- for (i = 0; i < 3; i++)
- if (pty->fds[i].fd >= 0)
- uxsel_del(pty->fds[i].fd);
-
- pty_close(pty);
-
- pty->finished = true;
-
- /*
- * This is a slight layering-violation sort of hack: only
- * if we're not closing on exit (COE is set to Never, or to
- * Only On Clean and it wasn't a clean exit) do we output a
- * `terminated' message.
- */
- close_on_exit = conf_get_int(pty->conf, CONF_close_on_exit);
- if (close_on_exit == FORCE_OFF ||
- (close_on_exit == AUTO && pty->exit_code != 0)) {
- char *message;
- if (WIFEXITED(pty->exit_code)) {
- message = dupprintf(
- "\r\n[pterm: process terminated with exit code %d]\r\n",
- WEXITSTATUS(pty->exit_code));
- } else if (WIFSIGNALED(pty->exit_code)) {
-#ifdef HAVE_NO_STRSIGNAL
- message = dupprintf(
- "\r\n[pterm: process terminated on signal %d]\r\n",
- WTERMSIG(pty->exit_code));
-#else
- message = dupprintf(
- "\r\n[pterm: process terminated on signal %d (%s)]\r\n",
- WTERMSIG(pty->exit_code),
- strsignal(WTERMSIG(pty->exit_code)));
-#endif
- } else {
- /* _Shouldn't_ happen, but if it does, a vague message
- * is better than no message at all */
- message = dupprintf("\r\n[pterm: process terminated]\r\n");
- }
- seat_stdout_pl(pty->seat, ptrlen_from_asciz(message));
- sfree(message);
- }
-
- seat_eof(pty->seat);
- seat_notify_remote_exit(pty->seat);
- }
-}
-
-static void pty_try_wait(void)
-{
- Pty *pty;
- pid_t pid;
- int status;
-
- do {
- pid = waitpid(-1, &status, WNOHANG);
-
- pty = find234(ptys_by_pid, &pid, pty_find_by_pid);
-
- if (pty)
- pty_real_select_result(pty, -1, -1, status);
- } while (pid > 0);
-}
-
-void pty_select_result(int fd, int event)
-{
- if (fd == pty_signal_pipe[0]) {
- char c[1];
-
- if (read(pty_signal_pipe[0], c, 1) <= 0)
- /* ignore error */;
- /* ignore its value; it'll be `x' */
-
- pty_try_wait();
- } else {
- PtyFd *ptyfd = find234(ptyfds, &fd, ptyfd_find);
-
- if (ptyfd)
- pty_real_select_result(ptyfd->pty, fd, event, 0);
- }
-}
-
-static void pty_uxsel_setup_fd(Pty *pty, int fd)
-{
- int rwx = 0;
-
- if (fd < 0)
- return;
-
- /* read from standard output and standard error pipes */
- if (pty->master_o == fd || pty->master_e == fd)
- rwx |= SELECT_R;
- /* write to standard input pipe if we have any data */
- if (pty->master_i == fd && bufchain_size(&pty->output_data))
- rwx |= SELECT_W;
-
- uxsel_set(fd, rwx, pty_select_result);
-}
-
-static void pty_uxsel_setup(Pty *pty)
-{
- /*
- * We potentially have three separate fds here, but on the other
- * hand, some of them might be the same (if they're a pty master).
- * So we can't just call uxsel_set(master_o, SELECT_R) and then
- * uxsel_set(master_i, SELECT_W), without the latter potentially
- * undoing the work of the former if master_o == master_i.
- *
- * Instead, here we call a single uxsel on each one of these fds
- * (if it exists at all), and for each one, check it against all
- * three to see which bits to set.
- */
- pty_uxsel_setup_fd(pty, pty->master_o);
- pty_uxsel_setup_fd(pty, pty->master_e);
- pty_uxsel_setup_fd(pty, pty->master_i);
-
- /*
- * In principle this only needs calling once for all pty
- * backend instances, but it's simplest just to call it every
- * time; uxsel won't mind.
- */
- uxsel_set(pty_signal_pipe[0], SELECT_R, pty_select_result);
-}
-
-static void copy_ttymodes_into_termios(
- struct termios *attrs, struct ssh_ttymodes modes)
-{
-#define TTYMODE_CHAR(name, ssh_opcode, cc_index) { \
- if (modes.have_mode[ssh_opcode]) { \
- unsigned value = modes.mode_val[ssh_opcode]; \
- /* normalise wire value of 255 to local _POSIX_VDISABLE */ \
- attrs->c_cc[cc_index] = (value == 255 ? \
- _POSIX_VDISABLE : value); \
- } \
- }
-
-#define TTYMODE_FLAG(flagval, ssh_opcode, field, flagmask) { \
- if (modes.have_mode[ssh_opcode]) { \
- attrs->c_##field##flag &= ~flagmask; \
- if (modes.mode_val[ssh_opcode]) \
- attrs->c_##field##flag |= flagval; \
- } \
- }
-
-#define TTYMODES_LOCAL_ONLY /* omit any that this platform doesn't know */
-#include "sshttymodes.h"
-
-#undef TTYMODES_LOCAL_ONLY
-#undef TTYMODE_CHAR
-#undef TTYMODE_FLAG
-
- if (modes.have_mode[TTYMODE_ISPEED])
- cfsetispeed(attrs, modes.mode_val[TTYMODE_ISPEED]);
- if (modes.have_mode[TTYMODE_OSPEED])
- cfsetospeed(attrs, modes.mode_val[TTYMODE_OSPEED]);
-}
-
-/*
- * The main setup function for the pty back end. This doesn't match
- * the signature of backend_init(), partly because it has to be able
- * to take extra arguments such as an argv array, and also because
- * once we're changing the type signature _anyway_ we can discard the
- * stuff that's not really applicable to this backend like host names
- * and port numbers.
- */
-Backend *pty_backend_create(
- Seat *seat, LogContext *logctx, Conf *conf, char **argv, const char *cmd,
- struct ssh_ttymodes ttymodes, bool pipes_instead, const char *dir,
- const char *const *env_vars_to_unset)
-{
- int slavefd;
- pid_t pid, pgrp;
-#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */
- bool got_windowid;
- long windowid;
-#endif
- Pty *pty;
- int i;
-
- /* No local authentication phase in this protocol */
- seat_set_trust_status(seat, false);
-
- if (single_pty) {
- pty = single_pty;
- assert(pty->conf == NULL);
- } else {
- pty = new_pty_struct();
- pty->master_fd = pty->slave_fd = -1;
-#ifndef OMIT_UTMP
- pty_stamped_utmp = false;
-#endif
- }
- for (i = 0; i < 6; i++)
- pty->pipefds[i] = -1;
- for (i = 0; i < 3; i++) {
- pty->fds[i].fd = -1;
- pty->fds[i].pty = pty;
- }
-
- if (pty_signal_pipe[0] < 0) {
- if (pipe(pty_signal_pipe) < 0) {
- perror("pipe");
- exit(1);
- }
- cloexec(pty_signal_pipe[0]);
- cloexec(pty_signal_pipe[1]);
- }
-
- pty->seat = seat;
- pty->backend.vt = &pty_backend;
-
- pty->conf = conf_copy(conf);
- pty->term_width = conf_get_int(conf, CONF_width);
- pty->term_height = conf_get_int(conf, CONF_height);
-
- if (!ptyfds)
- ptyfds = newtree234(ptyfd_compare);
-
- if (pipes_instead) {
- if (pty->master_fd >= 0) {
- /* If somehow we've got a pty master already and don't
- * need it, throw it away! */
- close(pty->master_fd);
-#ifndef OMIT_UTMP
- if (pty_utmp_helper_pipe >= 0) {
- close(pty_utmp_helper_pipe); /* don't need this either */
- pty_utmp_helper_pipe = -1;
- }
-#endif
- }
-
-
- for (i = 0; i < 6; i += 2) {
- if (pipe(pty->pipefds + i) < 0) {
- backend_free(&pty->backend);
- return NULL;
- }
- }
-
- pty->fds[0].fd = pty->master_i = pty->pipefds[1];
- pty->fds[1].fd = pty->master_o = pty->pipefds[2];
- pty->fds[2].fd = pty->master_e = pty->pipefds[4];
-
- add234(ptyfds, &pty->fds[0]);
- add234(ptyfds, &pty->fds[1]);
- add234(ptyfds, &pty->fds[2]);
- } else {
- if (pty->master_fd < 0)
- pty_open_master(pty);
-
-#ifndef OMIT_UTMP
- /*
- * Stamp utmp (that is, tell the utmp helper process to do so),
- * or not.
- */
- if (pty_utmp_helper_pipe >= 0) { /* if it's < 0, we can't anyway */
- if (!conf_get_bool(conf, CONF_stamp_utmp)) {
- /* We're not stamping utmp, so just let the child
- * process die that was waiting to unstamp it later. */
- close(pty_utmp_helper_pipe);
- pty_utmp_helper_pipe = -1;
- } else {
- const char *location = seat_get_x_display(pty->seat);
- int len = strlen(location)+1, pos = 0; /* +1 to include NUL */
- while (pos < len) {
- int ret = write(pty_utmp_helper_pipe,
- location + pos, len - pos);
- if (ret < 0) {
- perror("pterm: writing to utmp helper process");
- close(pty_utmp_helper_pipe); /* arrgh, just give up */
- pty_utmp_helper_pipe = -1;
- break;
- }
- pos += ret;
- }
- }
- }
-#endif
-
- pty->master_i = pty->master_fd;
- pty->master_o = pty->master_fd;
- pty->master_e = -1;
-
- pty->fds[0].fd = pty->master_fd;
- add234(ptyfds, &pty->fds[0]);
- }
-
-#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */
- got_windowid = seat_get_windowid(pty->seat, &windowid);
-#endif
-
- /*
- * Set up the signal handler to catch SIGCHLD, if pty_pre_init
- * didn't already do it.
- */
- pty_setup_sigchld_handler();
-
- /*
- * Fork and execute the command.
- */
- pid = fork();
- if (pid < 0) {
- perror("fork");
- exit(1);
- }
-
- if (pid == 0) {
- struct termios attrs;
-
- /*
- * We are the child.
- */
-
- if (pty_osx_envrestore_prefix) {
- int plen = strlen(pty_osx_envrestore_prefix);
- extern char **environ;
- char **ep;
-
- restart_osx_env_restore:
- for (ep = environ; *ep; ep++) {
- char *e = *ep;
-
- if (!strncmp(e, pty_osx_envrestore_prefix, plen)) {
- bool unset = (e[plen] == 'u');
- char *pname = dupprintf("%.*s", (int)strcspn(e, "="), e);
- char *name = pname + plen + 1;
- char *value = e + strcspn(e, "=");
- if (*value) value++;
- value = dupstr(value);
- if (unset)
- unsetenv(name);
- else
- setenv(name, value, 1);
- unsetenv(pname);
- sfree(pname);
- sfree(value);
- goto restart_osx_env_restore;
- }
- }
- }
-
- pgrp = getpid();
-
- if (pipes_instead) {
- int i;
- dup2(pty->pipefds[0], 0);
- dup2(pty->pipefds[3], 1);
- dup2(pty->pipefds[5], 2);
- for (i = 0; i < 6; i++)
- close(pty->pipefds[i]);
-
- setsid();
- } else {
- slavefd = pty_open_slave(pty);
- if (slavefd < 0) {
- perror("slave pty: open");
- _exit(1);
- }
-
- close(pty->master_fd);
- noncloexec(slavefd);
- dup2(slavefd, 0);
- dup2(slavefd, 1);
- dup2(slavefd, 2);
- close(slavefd);
- setsid();
-#ifdef TIOCSCTTY
- ioctl(0, TIOCSCTTY, 1);
-#endif
- tcsetpgrp(0, pgrp);
-
- /*
- * Set up configuration-dependent termios settings on the new
- * pty. Linux would have let us do this on the pty master
- * before we forked, but that fails on OS X, so we do it here
- * instead.
- */
- if (tcgetattr(0, &attrs) == 0) {
- /*
- * Set the backspace character to be whichever of ^H and
- * ^? is specified by bksp_is_delete.
- */
- attrs.c_cc[VERASE] = conf_get_bool(conf, CONF_bksp_is_delete)
- ? '\177' : '\010';
-
- /*
- * Set the IUTF8 bit iff the character set is UTF-8.
- */
-#ifdef IUTF8
- if (seat_is_utf8(seat))
- attrs.c_iflag |= IUTF8;
- else
- attrs.c_iflag &= ~IUTF8;
-#endif
-
- copy_ttymodes_into_termios(&attrs, ttymodes);
-
- tcsetattr(0, TCSANOW, &attrs);
- }
- }
-
- setpgid(pgrp, pgrp);
- if (!pipes_instead) {
- int ptyfd = open(pty->name, O_WRONLY, 0);
- if (ptyfd >= 0)
- close(ptyfd);
- }
- setpgid(pgrp, pgrp);
-
- if (env_vars_to_unset)
- for (const char *const *p = env_vars_to_unset; *p; p++)
- unsetenv(*p);
-
- if (!pipes_instead) {
- char *term_env_var = dupprintf("TERM=%s",
- conf_get_str(conf, CONF_termtype));
- putenv(term_env_var);
- /* We mustn't free term_env_var, as putenv links it into the
- * environment in place.
- */
- }
-#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */
- if (got_windowid) {
- char *windowid_env_var = dupprintf("WINDOWID=%ld", windowid);
- putenv(windowid_env_var);
- /* We mustn't free windowid_env_var, as putenv links it into the
- * environment in place.
- */
- }
- {
- /*
- * In case we were invoked with a --display argument that
- * doesn't match DISPLAY in our actual environment, we
- * should set DISPLAY for processes running inside the
- * terminal to match the display the terminal itself is
- * on.
- */
- const char *x_display = seat_get_x_display(pty->seat);
- if (x_display) {
- char *x_display_env_var = dupprintf("DISPLAY=%s", x_display);
- putenv(x_display_env_var);
- /* As above, we don't free this. */
- } else {
- unsetenv("DISPLAY");
- }
- }
-#endif
- {
- char *key, *val;
-
- for (val = conf_get_str_strs(conf, CONF_environmt, NULL, &key);
- val != NULL;
- val = conf_get_str_strs(conf, CONF_environmt, key, &key)) {
- char *varval = dupcat(key, "=", val);
- putenv(varval);
- /*
- * We must not free varval, since putenv links it
- * into the environment _in place_. Weird, but
- * there we go. Memory usage will be rationalised
- * as soon as we exec anyway.
- */
- }
- }
-
- if (dir) {
- if (chdir(dir) < 0) {
- /* Ignore the error - nothing we can sensibly do about it,
- * and our existing cwd is as good a fallback as any. */
- }
- }
-
- /*
- * SIGINT, SIGQUIT and SIGPIPE may have been set to ignored by
- * our parent, particularly by things like sh -c 'pterm &' and
- * some window or session managers. SIGPIPE was also
- * (potentially) blocked by us during startup. Reverse all
- * this for our child process.
- */
- putty_signal(SIGINT, SIG_DFL);
- putty_signal(SIGQUIT, SIG_DFL);
- putty_signal(SIGPIPE, SIG_DFL);
- block_signal(SIGPIPE, false);
- if (argv || cmd) {
- /*
- * If we were given a separated argument list, try to exec
- * it.
- */
- if (argv) {
- execvp(argv[0], argv);
- }
- /*
- * Otherwise, if we were given a single command string,
- * try passing that to $SHELL -c.
- *
- * In the case of pterm, this system of fallbacks arranges
- * that we can _either_ follow 'pterm -e' with a list of
- * argv elements to be fed directly to exec, _or_ with a
- * single argument containing a command to be parsed by a
- * shell (but, in cases of doubt, the former is more
- * reliable). We arrange this by setting argv to the full
- * argument list, and also setting cmd to the single
- * element of argv if it's a length-1 list.
- *
- * A quick survey of other terminal emulators' -e options
- * (as of Debian squeeze) suggests that:
- *
- * - xterm supports both modes, more or less like this
- * - gnome-terminal will only accept a one-string shell command
- * - Eterm, kterm and rxvt will only accept a list of
- * argv elements (as did older versions of pterm).
- *
- * It therefore seems important to support both usage
- * modes in order to be a drop-in replacement for either
- * xterm or gnome-terminal, and hence for anyone's
- * plausible uses of the Debian-style alias
- * 'x-terminal-emulator'.
- *
- * In other use cases, a caller can set only one of argv
- * and cmd to get a fixed handling of the input.
- */
- if (cmd) {
- char *shell = getenv("SHELL");
- if (shell)
- execl(shell, shell, "-c", cmd, (void *)NULL);
- }
- } else {
- const char *shell = getenv("SHELL");
- if (!shell)
- shell = "/bin/sh";
- char *shellname;
- if (conf_get_bool(conf, CONF_login_shell)) {
- const char *p = strrchr(shell, '/');
- shellname = snewn(2+strlen(shell), char);
- p = p ? p+1 : shell;
- sprintf(shellname, "-%s", p);
- } else
- shellname = (char *)shell;
- execl(shell, shellname, (void *)NULL);
- }
-
- /*
- * If we're here, exec has gone badly foom.
- */
- perror("exec");
- _exit(127);
- } else {
- pty->child_pid = pid;
- pty->child_dead = false;
- pty->finished = false;
- if (pty->slave_fd > 0)
- close(pty->slave_fd);
- if (!ptys_by_pid)
- ptys_by_pid = newtree234(pty_compare_by_pid);
- if (pty->pipefds[0] >= 0) {
- close(pty->pipefds[0]);
- pty->pipefds[0] = -1;
- }
- if (pty->pipefds[3] >= 0) {
- close(pty->pipefds[3]);
- pty->pipefds[3] = -1;
- }
- if (pty->pipefds[5] >= 0) {
- close(pty->pipefds[5]);
- pty->pipefds[5] = -1;
- }
- add234(ptys_by_pid, pty);
- }
-
- pty_uxsel_setup(pty);
-
- return &pty->backend;
-}
-
-/*
- * This is the pty backend's _official_ init method, for BackendVtable
- * purposes. Its job is just to be an API converter, ignoring the
- * irrelevant input parameters and making up auxiliary outputs. Also
- * it gets the argv array from the global variable pty_argv, expecting
- * that it will have been invoked by pterm.
- */
-static char *pty_init(const BackendVtable *vt, Seat *seat,
- Backend **backend_handle, LogContext *logctx,
- Conf *conf, const char *host, int port,
- char **realhost, bool nodelay, bool keepalive)
-{
- const char *cmd = NULL;
- struct ssh_ttymodes modes;
-
- memset(&modes, 0, sizeof(modes));
-
- if (pty_argv && pty_argv[0] && !pty_argv[1])
- cmd = pty_argv[0];
-
- assert(vt == &pty_backend);
- *backend_handle = pty_backend_create(
- seat, logctx, conf, pty_argv, cmd, modes, false, NULL, NULL);
- *realhost = dupstr("");
- return NULL;
-}
-
-static void pty_reconfig(Backend *be, Conf *conf)
-{
- Pty *pty = container_of(be, Pty, backend);
- /*
- * We don't have much need to reconfigure this backend, but
- * unfortunately we do need to pick up the setting of Close On
- * Exit so we know whether to give a `terminated' message.
- */
- conf_copy_into(pty->conf, conf);
-}
-
-/*
- * Stub routine (never called in pterm).
- */
-static void pty_free(Backend *be)
-{
- Pty *pty = container_of(be, Pty, backend);
- int i;
-
- pty_close(pty);
-
- /* Either of these may fail `not found'. That's fine with us. */
- del234(ptys_by_pid, pty);
- for (i = 0; i < 3; i++)
- if (pty->fds[i].fd >= 0)
- del234(ptyfds, &pty->fds[i]);
-
- bufchain_clear(&pty->output_data);
-
- conf_free(pty->conf);
- pty->conf = NULL;
-
- if (pty == single_pty) {
- /*
- * Leave this structure around in case we need to Restart
- * Session.
- */
- } else {
- sfree(pty);
- }
-}
-
-static void pty_try_write(Pty *pty)
-{
- ssize_t ret;
-
- assert(pty->master_i >= 0);
-
- while (bufchain_size(&pty->output_data) > 0) {
- ptrlen data = bufchain_prefix(&pty->output_data);
- ret = write(pty->master_i, data.ptr, data.len);
-
- if (ret < 0 && (errno == EWOULDBLOCK)) {
- /*
- * We've sent all we can for the moment.
- */
- break;
- }
- if (ret < 0) {
- perror("write pty master");
- exit(1);
- }
- bufchain_consume(&pty->output_data, ret);
- }
-
- if (pty->pending_eof && bufchain_size(&pty->output_data) == 0) {
- /* This should only happen if pty->master_i is a pipe that
- * doesn't alias either output fd */
- assert(pty->master_i != pty->master_o);
- assert(pty->master_i != pty->master_e);
- uxsel_del(pty->master_i);
- close(pty->master_i);
- pty->master_i = -1;
- pty->pending_eof = false;
- }
-
- pty_uxsel_setup(pty);
-}
-
-/*
- * Called to send data down the pty.
- */
-static size_t pty_send(Backend *be, const char *buf, size_t len)
-{
- Pty *pty = container_of(be, Pty, backend);
-
- if (pty->master_i < 0 || pty->pending_eof)
- return 0; /* ignore all writes if fd closed */
-
- bufchain_add(&pty->output_data, buf, len);
- pty_try_write(pty);
-
- return bufchain_size(&pty->output_data);
-}
-
-static void pty_close(Pty *pty)
-{
- int i;
-
- if (pty->master_o >= 0)
- uxsel_del(pty->master_o);
- if (pty->master_e >= 0)
- uxsel_del(pty->master_e);
- if (pty->master_i >= 0)
- uxsel_del(pty->master_i);
-
- if (pty->master_fd >= 0) {
- close(pty->master_fd);
- pty->master_fd = -1;
- }
- for (i = 0; i < 6; i++) {
- if (pty->pipefds[i] >= 0)
- close(pty->pipefds[i]);
- pty->pipefds[i] = -1;
- }
- pty->master_i = pty->master_o = pty->master_e = -1;
-#ifndef OMIT_UTMP
- if (pty_utmp_helper_pipe >= 0) {
- close(pty_utmp_helper_pipe); /* this causes utmp to be cleaned up */
- pty_utmp_helper_pipe = -1;
- }
-#endif
-}
-
-/*
- * Called to query the current socket sendability status.
- */
-static size_t pty_sendbuffer(Backend *be)
-{
- /* Pty *pty = container_of(be, Pty, backend); */
- return 0;
-}
-
-/*
- * Called to set the size of the window
- */
-static void pty_size(Backend *be, int width, int height)
-{
- Pty *pty = container_of(be, Pty, backend);
- struct winsize size;
- int xpixel = 0, ypixel = 0;
-
- pty->term_width = width;
- pty->term_height = height;
-
- if (pty->master_fd < 0)
- return;
-
- seat_get_window_pixel_size(pty->seat, &xpixel, &ypixel);
-
- size.ws_row = (unsigned short)pty->term_height;
- size.ws_col = (unsigned short)pty->term_width;
- size.ws_xpixel = (unsigned short)xpixel;
- size.ws_ypixel = (unsigned short)ypixel;
- ioctl(pty->master_fd, TIOCSWINSZ, (void *)&size);
- return;
-}
-
-/*
- * Send special codes.
- */
-static void pty_special(Backend *be, SessionSpecialCode code, int arg)
-{
- Pty *pty = container_of(be, Pty, backend);
-
- if (code == SS_BRK) {
- if (pty->master_fd >= 0)
- tcsendbreak(pty->master_fd, 0);
- return;
- }
-
- if (code == SS_EOF) {
- if (pty->master_i >= 0 && pty->master_i != pty->master_fd) {
- pty->pending_eof = true;
- pty_try_write(pty);
- }
- return;
- }
-
- {
- int sig = -1;
-
- #define SIGNAL_SUB(name) if (code == SS_SIG ## name) sig = SIG ## name;
- #define SIGNAL_MAIN(name, text) SIGNAL_SUB(name)
- #define SIGNALS_LOCAL_ONLY
- #include "sshsignals.h"
- #undef SIGNAL_SUB
- #undef SIGNAL_MAIN
- #undef SIGNALS_LOCAL_ONLY
-
- if (sig != -1) {
- if (!pty->child_dead)
- kill(pty->child_pid, sig);
- return;
- }
- }
-
- return;
-}
-
-/*
- * Return a list of the special codes that make sense in this
- * protocol.
- */
-static const SessionSpecial *pty_get_specials(Backend *be)
-{
- /* Pty *pty = container_of(be, Pty, backend); */
- /*
- * Hmm. When I get round to having this actually usable, it
- * might be quite nice to have the ability to deliver a few
- * well chosen signals to the child process - SIGINT, SIGTERM,
- * SIGKILL at least.
- */
- return NULL;
-}
-
-static bool pty_connected(Backend *be)
-{
- /* Pty *pty = container_of(be, Pty, backend); */
- return true;
-}
-
-static bool pty_sendok(Backend *be)
-{
- /* Pty *pty = container_of(be, Pty, backend); */
- return true;
-}
-
-static void pty_unthrottle(Backend *be, size_t backlog)
-{
- /* Pty *pty = container_of(be, Pty, backend); */
- /* do nothing */
-}
-
-static bool pty_ldisc(Backend *be, int option)
-{
- /* Pty *pty = container_of(be, Pty, backend); */
- return false; /* neither editing nor echoing */
-}
-
-static void pty_provide_ldisc(Backend *be, Ldisc *ldisc)
-{
- /* Pty *pty = container_of(be, Pty, backend); */
- /* This is a stub. */
-}
-
-static int pty_exitcode(Backend *be)
-{
- Pty *pty = container_of(be, Pty, backend);
- if (!pty->finished)
- return -1; /* not dead yet */
- else if (WIFSIGNALED(pty->exit_code))
- return 128 + WTERMSIG(pty->exit_code);
- else
- return WEXITSTATUS(pty->exit_code);
-}
-
-int pty_backend_exit_signum(Backend *be)
-{
- Pty *pty = container_of(be, Pty, backend);
-
- if (!pty->finished || !WIFSIGNALED(pty->exit_code))
- return -1;
-
- return WTERMSIG(pty->exit_code);
-}
-
-ptrlen pty_backend_exit_signame(Backend *be, char **aux_msg)
-{
- *aux_msg = NULL;
-
- int sig = pty_backend_exit_signum(be);
- if (sig < 0)
- return PTRLEN_LITERAL("");
-
- #define SIGNAL_SUB(s) { \
- if (sig == SIG ## s) \
- return PTRLEN_LITERAL(#s); \
- }
- #define SIGNAL_MAIN(s, desc) SIGNAL_SUB(s)
- #define SIGNALS_LOCAL_ONLY
- #include "sshsignals.h"
- #undef SIGNAL_MAIN
- #undef SIGNAL_SUB
- #undef SIGNALS_LOCAL_ONLY
-
- *aux_msg = dupprintf("untranslatable signal number %d: %s",
- sig, strsignal(sig));
- return PTRLEN_LITERAL("HUP"); /* need some kind of default */
-}
-
-static int pty_cfg_info(Backend *be)
-{
- /* Pty *pty = container_of(be, Pty, backend); */
- return 0;
-}
-
-const BackendVtable pty_backend = {
- .init = pty_init,
- .free = pty_free,
- .reconfig = pty_reconfig,
- .send = pty_send,
- .sendbuffer = pty_sendbuffer,
- .size = pty_size,
- .special = pty_special,
- .get_specials = pty_get_specials,
- .connected = pty_connected,
- .exitcode = pty_exitcode,
- .sendok = pty_sendok,
- .ldisc_option_state = pty_ldisc,
- .provide_ldisc = pty_provide_ldisc,
- .unthrottle = pty_unthrottle,
- .cfg_info = pty_cfg_info,
- .id = "pty",
- .displayname = "pty",
- .protocol = -1,
-};
diff --git a/unix/uxputty.c b/unix/uxputty.c
deleted file mode 100644
index 7a808087..00000000
--- a/unix/uxputty.c
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Unix PuTTY main program.
- */
-
-#include <stdio.h>
-#include <ctype.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <unistd.h>
-#include <gtk/gtk.h>
-#include <gdk/gdk.h>
-
-#define MAY_REFER_TO_GTK_IN_HEADERS
-
-#include "putty.h"
-#include "ssh.h"
-#include "storage.h"
-
-#include "gtkcompat.h"
-
-/*
- * Stubs to avoid uxpty.c needing to be linked in.
- */
-const bool use_pty_argv = false;
-char **pty_argv; /* never used */
-char *pty_osx_envrestore_prefix;
-
-/*
- * Clean up and exit.
- */
-void cleanup_exit(int code)
-{
- /*
- * Clean up.
- */
- sk_cleanup();
- random_save_seed();
- exit(code);
-}
-
-const struct BackendVtable *select_backend(Conf *conf)
-{
- const struct BackendVtable *vt =
- backend_vt_from_proto(conf_get_int(conf, CONF_protocol));
- assert(vt != NULL);
- return vt;
-}
-
-void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx)
-{
- char *title = dupcat(appname, " Configuration");
- create_config_box(title, conf, false, 0, after, afterctx);
- sfree(title);
-}
-
-const bool use_event_log = true, new_session = true, saved_sessions = true;
-const bool dup_check_launchable = true;
-
-/*
- * X11-forwarding-related things suitable for Gtk app.
- */
-
-char *platform_get_x_display(void) {
- const char *display;
- /* Try to take account of --display and what have you. */
- if (!(display = gdk_get_display()))
- /* fall back to traditional method */
- display = getenv("DISPLAY");
- return dupstr(display);
-}
-
-const bool share_can_be_downstream = true;
-const bool share_can_be_upstream = true;
-
-const unsigned cmdline_tooltype =
- TOOLTYPE_HOST_ARG |
- TOOLTYPE_PORT_ARG |
- TOOLTYPE_NO_VERBOSE_OPTION;
-
-void setup(bool single)
-{
- sk_init();
- settings_set_default_protocol(be_default_protocol);
- /* Find the appropriate default port. */
- {
- const struct BackendVtable *vt =
- backend_vt_from_proto(be_default_protocol);
- settings_set_default_port(0); /* illegal */
- if (vt)
- settings_set_default_port(vt->default_port);
- }
-}
diff --git a/unix/uxsel.c b/unix/uxsel.c
index eb3abed3..18d512ac 100644
--- a/unix/uxsel.c
+++ b/unix/uxsel.c
@@ -2,10 +2,10 @@
* uxsel.c
*
* This module is a sort of all-purpose interchange for file
- * descriptors. At one end it talks to uxnet.c and pty.c and
+ * descriptors. At one end it talks to network.c and pty.c and
* anything else which might have one or more fds that need
* select() or poll()-type things doing to them during an extended
- * program run; at the other end it talks to pterm.c or uxplink.c or
+ * program run; at the other end it talks to window.c or plink.c or
* anything else which might have its own means of actually doing
* those select()-type things.
*/
diff --git a/unix/uxser.c b/unix/uxser.c
deleted file mode 100644
index d4a1e0ba..00000000
--- a/unix/uxser.c
+++ /dev/null
@@ -1,595 +0,0 @@
-/*
- * Serial back end (Unix-specific).
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <limits.h>
-
-#include <errno.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <termios.h>
-
-#include "putty.h"
-#include "tree234.h"
-
-#define SERIAL_MAX_BACKLOG 4096
-
-typedef struct Serial Serial;
-struct Serial {
- Seat *seat;
- LogContext *logctx;
- int fd;
- bool finished;
- size_t inbufsize;
- bufchain output_data;
- Backend backend;
-};
-
-/*
- * We store our serial backends in a tree sorted by fd, so that
- * when we get an uxsel notification we know which backend instance
- * is the owner of the serial port that caused it.
- */
-static int serial_compare_by_fd(void *av, void *bv)
-{
- Serial *a = (Serial *)av;
- Serial *b = (Serial *)bv;
-
- if (a->fd < b->fd)
- return -1;
- else if (a->fd > b->fd)
- return +1;
- return 0;
-}
-
-static int serial_find_by_fd(void *av, void *bv)
-{
- int a = *(int *)av;
- Serial *b = (Serial *)bv;
-
- if (a < b->fd)
- return -1;
- else if (a > b->fd)
- return +1;
- return 0;
-}
-
-static tree234 *serial_by_fd = NULL;
-
-static void serial_select_result(int fd, int event);
-static void serial_uxsel_setup(Serial *serial);
-static void serial_try_write(Serial *serial);
-
-static char *serial_configure(Serial *serial, Conf *conf)
-{
- struct termios options;
- int bflag, bval, speed, flow, parity;
- const char *str;
-
- if (serial->fd < 0)
- return dupstr("Unable to reconfigure already-closed "
- "serial connection");
-
- tcgetattr(serial->fd, &options);
-
- /*
- * Find the appropriate baud rate flag.
- */
- speed = conf_get_int(conf, CONF_serspeed);
-#define SETBAUD(x) (bflag = B ## x, bval = x)
-#define CHECKBAUD(x) do { if (speed >= x) SETBAUD(x); } while (0)
- SETBAUD(50);
-#ifdef B75
- CHECKBAUD(75);
-#endif
-#ifdef B110
- CHECKBAUD(110);
-#endif
-#ifdef B134
- CHECKBAUD(134);
-#endif
-#ifdef B150
- CHECKBAUD(150);
-#endif
-#ifdef B200
- CHECKBAUD(200);
-#endif
-#ifdef B300
- CHECKBAUD(300);
-#endif
-#ifdef B600
- CHECKBAUD(600);
-#endif
-#ifdef B1200
- CHECKBAUD(1200);
-#endif
-#ifdef B1800
- CHECKBAUD(1800);
-#endif
-#ifdef B2400
- CHECKBAUD(2400);
-#endif
-#ifdef B4800
- CHECKBAUD(4800);
-#endif
-#ifdef B9600
- CHECKBAUD(9600);
-#endif
-#ifdef B19200
- CHECKBAUD(19200);
-#endif
-#ifdef B38400
- CHECKBAUD(38400);
-#endif
-#ifdef B57600
- CHECKBAUD(57600);
-#endif
-#ifdef B76800
- CHECKBAUD(76800);
-#endif
-#ifdef B115200
- CHECKBAUD(115200);
-#endif
-#ifdef B153600
- CHECKBAUD(153600);
-#endif
-#ifdef B230400
- CHECKBAUD(230400);
-#endif
-#ifdef B307200
- CHECKBAUD(307200);
-#endif
-#ifdef B460800
- CHECKBAUD(460800);
-#endif
-#ifdef B500000
- CHECKBAUD(500000);
-#endif
-#ifdef B576000
- CHECKBAUD(576000);
-#endif
-#ifdef B921600
- CHECKBAUD(921600);
-#endif
-#ifdef B1000000
- CHECKBAUD(1000000);
-#endif
-#ifdef B1152000
- CHECKBAUD(1152000);
-#endif
-#ifdef B1500000
- CHECKBAUD(1500000);
-#endif
-#ifdef B2000000
- CHECKBAUD(2000000);
-#endif
-#ifdef B2500000
- CHECKBAUD(2500000);
-#endif
-#ifdef B3000000
- CHECKBAUD(3000000);
-#endif
-#ifdef B3500000
- CHECKBAUD(3500000);
-#endif
-#ifdef B4000000
- CHECKBAUD(4000000);
-#endif
-#undef CHECKBAUD
-#undef SETBAUD
- cfsetispeed(&options, bflag);
- cfsetospeed(&options, bflag);
- logeventf(serial->logctx, "Configuring baud rate %d", bval);
-
- options.c_cflag &= ~CSIZE;
- switch (conf_get_int(conf, CONF_serdatabits)) {
- case 5: options.c_cflag |= CS5; break;
- case 6: options.c_cflag |= CS6; break;
- case 7: options.c_cflag |= CS7; break;
- case 8: options.c_cflag |= CS8; break;
- default: return dupstr("Invalid number of data bits "
- "(need 5, 6, 7 or 8)");
- }
- logeventf(serial->logctx, "Configuring %d data bits",
- conf_get_int(conf, CONF_serdatabits));
-
- if (conf_get_int(conf, CONF_serstopbits) >= 4) {
- options.c_cflag |= CSTOPB;
- } else {
- options.c_cflag &= ~CSTOPB;
- }
- logeventf(serial->logctx, "Configuring %s",
- (options.c_cflag & CSTOPB ? "2 stop bits" : "1 stop bit"));
-
- options.c_iflag &= ~(IXON|IXOFF);
-#ifdef CRTSCTS
- options.c_cflag &= ~CRTSCTS;
-#endif
-#ifdef CNEW_RTSCTS
- options.c_cflag &= ~CNEW_RTSCTS;
-#endif
- flow = conf_get_int(conf, CONF_serflow);
- if (flow == SER_FLOW_XONXOFF) {
- options.c_iflag |= IXON | IXOFF;
- str = "XON/XOFF";
- } else if (flow == SER_FLOW_RTSCTS) {
-#ifdef CRTSCTS
- options.c_cflag |= CRTSCTS;
-#endif
-#ifdef CNEW_RTSCTS
- options.c_cflag |= CNEW_RTSCTS;
-#endif
- str = "RTS/CTS";
- } else
- str = "no";
- logeventf(serial->logctx, "Configuring %s flow control", str);
-
- /* Parity */
- parity = conf_get_int(conf, CONF_serparity);
- if (parity == SER_PAR_ODD) {
- options.c_cflag |= PARENB;
- options.c_cflag |= PARODD;
- str = "odd";
- } else if (parity == SER_PAR_EVEN) {
- options.c_cflag |= PARENB;
- options.c_cflag &= ~PARODD;
- str = "even";
- } else {
- options.c_cflag &= ~PARENB;
- str = "no";
- }
- logeventf(serial->logctx, "Configuring %s parity", str);
-
- options.c_cflag |= CLOCAL | CREAD;
- options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
- options.c_iflag &= ~(ISTRIP | IGNCR | INLCR | ICRNL
-#ifdef IUCLC
- | IUCLC
-#endif
- );
- options.c_oflag &= ~(OPOST
-#ifdef ONLCR
- | ONLCR
-#endif
-#ifdef OCRNL
- | OCRNL
-#endif
-#ifdef ONOCR
- | ONOCR
-#endif
-#ifdef ONLRET
- | ONLRET
-#endif
- );
- options.c_cc[VMIN] = 1;
- options.c_cc[VTIME] = 0;
-
- if (tcsetattr(serial->fd, TCSANOW, &options) < 0)
- return dupprintf("Configuring serial port: %s", strerror(errno));
-
- return NULL;
-}
-
-/*
- * Called to set up the serial connection.
- *
- * Returns an error message, or NULL on success.
- *
- * Also places the canonical host name into `realhost'. It must be
- * freed by the caller.
- */
-static char *serial_init(const BackendVtable *vt, Seat *seat,
- Backend **backend_handle, LogContext *logctx,
- Conf *conf, const char *host, int port,
- char **realhost, bool nodelay, bool keepalive)
-{
- Serial *serial;
- char *err;
- char *line;
-
- /* No local authentication phase in this protocol */
- seat_set_trust_status(seat, false);
-
- serial = snew(Serial);
- serial->backend.vt = vt;
- *backend_handle = &serial->backend;
-
- serial->seat = seat;
- serial->logctx = logctx;
- serial->finished = false;
- serial->inbufsize = 0;
- bufchain_init(&serial->output_data);
-
- line = conf_get_str(conf, CONF_serline);
- logeventf(serial->logctx, "Opening serial device %s", line);
-
- serial->fd = open(line, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
- if (serial->fd < 0)
- return dupprintf("Opening serial port '%s': %s",
- line, strerror(errno));
-
- cloexec(serial->fd);
-
- err = serial_configure(serial, conf);
- if (err)
- return err;
-
- *realhost = dupstr(line);
-
- if (!serial_by_fd)
- serial_by_fd = newtree234(serial_compare_by_fd);
- add234(serial_by_fd, serial);
-
- serial_uxsel_setup(serial);
-
- /*
- * Specials are always available.
- */
- seat_update_specials_menu(serial->seat);
-
- return NULL;
-}
-
-static void serial_close(Serial *serial)
-{
- if (serial->fd >= 0) {
- uxsel_del(serial->fd);
- close(serial->fd);
- serial->fd = -1;
- }
-}
-
-static void serial_free(Backend *be)
-{
- Serial *serial = container_of(be, Serial, backend);
-
- serial_close(serial);
-
- bufchain_clear(&serial->output_data);
-
- sfree(serial);
-}
-
-static void serial_reconfig(Backend *be, Conf *conf)
-{
- Serial *serial = container_of(be, Serial, backend);
-
- char *err = serial_configure(serial, conf);
- if (err) {
- /*
- * FIXME: apart from freeing the dynamically allocated
- * message, what should we do if this returns an error?
- */
- sfree(err);
- }
-}
-
-static void serial_select_result(int fd, int event)
-{
- Serial *serial;
- char buf[4096];
- int ret;
- bool finished = false;
-
- serial = find234(serial_by_fd, &fd, serial_find_by_fd);
-
- if (!serial)
- return; /* spurious event; keep going */
-
- if (event == 1) {
- ret = read(serial->fd, buf, sizeof(buf));
-
- if (ret == 0) {
- /*
- * Shouldn't happen on a real serial port, but I'm open
- * to the idea that there might be two-way devices we
- * can treat _like_ serial ports which can return EOF.
- */
- finished = true;
- } else if (ret < 0) {
-#ifdef EAGAIN
- if (errno == EAGAIN)
- return; /* spurious */
-#endif
-#ifdef EWOULDBLOCK
- if (errno == EWOULDBLOCK)
- return; /* spurious */
-#endif
- perror("read serial port");
- exit(1);
- } else if (ret > 0) {
- serial->inbufsize = seat_stdout(serial->seat, buf, ret);
- serial_uxsel_setup(serial); /* might acquire backlog and freeze */
- }
- } else if (event == 2) {
- /*
- * Attempt to send data down the pty.
- */
- serial_try_write(serial);
- }
-
- if (finished) {
- serial_close(serial);
-
- serial->finished = true;
-
- seat_notify_remote_exit(serial->seat);
- }
-}
-
-static void serial_uxsel_setup(Serial *serial)
-{
- int rwx = 0;
-
- if (serial->inbufsize <= SERIAL_MAX_BACKLOG)
- rwx |= SELECT_R;
- if (bufchain_size(&serial->output_data))
- rwx |= SELECT_W; /* might also want to write to it */
- uxsel_set(serial->fd, rwx, serial_select_result);
-}
-
-static void serial_try_write(Serial *serial)
-{
- ssize_t ret;
-
- assert(serial->fd >= 0);
-
- while (bufchain_size(&serial->output_data) > 0) {
- ptrlen data = bufchain_prefix(&serial->output_data);
- ret = write(serial->fd, data.ptr, data.len);
-
- if (ret < 0 && (errno == EWOULDBLOCK)) {
- /*
- * We've sent all we can for the moment.
- */
- break;
- }
- if (ret < 0) {
- perror("write serial port");
- exit(1);
- }
- bufchain_consume(&serial->output_data, ret);
- }
-
- serial_uxsel_setup(serial);
-}
-
-/*
- * 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->fd < 0)
- return 0;
-
- bufchain_add(&serial->output_data, buf, len);
- serial_try_write(serial);
-
- return bufchain_size(&serial->output_data);
-}
-
-/*
- * Called to query the current sendability status.
- */
-static size_t serial_sendbuffer(Backend *be)
-{
- Serial *serial = container_of(be, Serial, backend);
- return bufchain_size(&serial->output_data);
-}
-
-/*
- * Called to set the size of the window
- */
-static void serial_size(Backend *be, int width, int height)
-{
- /* Do nothing! */
- return;
-}
-
-/*
- * Send serial special codes.
- */
-static void serial_special(Backend *be, SessionSpecialCode code, int arg)
-{
- Serial *serial = container_of(be, Serial, backend);
-
- if (serial->fd >= 0 && code == SS_BRK) {
- tcsendbreak(serial->fd, 0);
- logevent(serial->logctx, "Sending serial break at user request");
- }
-
- return;
-}
-
-/*
- * Return a list of the special codes that make sense in this
- * protocol.
- */
-static const SessionSpecial *serial_get_specials(Backend *be)
-{
- static const struct SessionSpecial specials[] = {
- {"Break", SS_BRK},
- {NULL, SS_EXITMENU}
- };
- return specials;
-}
-
-static bool serial_connected(Backend *be)
-{
- return true; /* always connected */
-}
-
-static bool serial_sendok(Backend *be)
-{
- return true;
-}
-
-static void serial_unthrottle(Backend *be, size_t backlog)
-{
- Serial *serial = container_of(be, Serial, backend);
- serial->inbufsize = backlog;
- serial_uxsel_setup(serial);
-}
-
-static bool serial_ldisc(Backend *be, int option)
-{
- /*
- * Local editing and local echo are off by default.
- */
- return false;
-}
-
-static void serial_provide_ldisc(Backend *be, Ldisc *ldisc)
-{
- /* This is a stub. */
-}
-
-static int serial_exitcode(Backend *be)
-{
- Serial *serial = container_of(be, Serial, backend);
- if (serial->fd >= 0)
- return -1; /* still connected */
- else
- /* Exit codes are a meaningless concept with serial ports */
- return INT_MAX;
-}
-
-/*
- * cfg_info for Serial does nothing at all.
- */
-static int serial_cfg_info(Backend *be)
-{
- return 0;
-}
-
-const BackendVtable serial_backend = {
- .init = serial_init,
- .free = serial_free,
- .reconfig = serial_reconfig,
- .send = serial_send,
- .sendbuffer = serial_sendbuffer,
- .size = serial_size,
- .special = serial_special,
- .get_specials = serial_get_specials,
- .connected = serial_connected,
- .exitcode = serial_exitcode,
- .sendok = serial_sendok,
- .ldisc_option_state = serial_ldisc,
- .provide_ldisc = serial_provide_ldisc,
- .unthrottle = serial_unthrottle,
- .cfg_info = serial_cfg_info,
- .id = "serial",
- .displayname = "Serial",
- .protocol = PROT_SERIAL,
- .serial_parity_mask = ((1 << SER_PAR_NONE) |
- (1 << SER_PAR_ODD) |
- (1 << SER_PAR_EVEN)),
- .serial_flow_mask = ((1 << SER_FLOW_NONE) |
- (1 << SER_FLOW_XONXOFF) |
- (1 << SER_FLOW_RTSCTS)),
-};
diff --git a/unix/uxserver.c b/unix/uxserver.c
deleted file mode 100644
index 448c6515..00000000
--- a/unix/uxserver.c
+++ /dev/null
@@ -1,850 +0,0 @@
-/*
- * SSH server for Unix: main program.
- *
- * ======================================================================
- *
- * This server is NOT SECURE!
- *
- * DO NOT DEPLOY IT IN A HOSTILE-FACING ENVIRONMENT!
- *
- * Its purpose is to speak the server end of everything PuTTY speaks
- * on the client side, so that I can test that I haven't broken PuTTY
- * when I reorganise its code, even things like RSA key exchange or
- * chained auth methods which it's hard to find a server that speaks
- * at all.
- *
- * It has no interaction with the OS's authentication system: the
- * authentications it will accept are configurable by command-line
- * option, and once you authenticate, it will run the connection
- * protocol - including all subprocesses and shells - under the same
- * Unix user id you started it under.
- *
- * It really is only suitable for testing the actual SSH protocol.
- * Don't use it for anything more serious!
- *
- * ======================================================================
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <assert.h>
-#include <stdarg.h>
-#include <signal.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <termios.h>
-#include <pwd.h>
-#include <sys/ioctl.h>
-#include <sys/time.h>
-
-#include "putty.h"
-#include "mpint.h"
-#include "ssh.h"
-#include "sshserver.h"
-
-const char *const appname = "uppity";
-
-void modalfatalbox(const char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "FATAL ERROR: ");
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
- exit(1);
-}
-void nonfatal(const char *p, ...)
-{
- va_list ap;
- fprintf(stderr, "ERROR: ");
- va_start(ap, p);
- vfprintf(stderr, p, ap);
- va_end(ap);
- fputc('\n', stderr);
-}
-
-char *platform_default_s(const char *name)
-{
- return NULL;
-}
-
-bool platform_default_b(const char *name, bool def)
-{
- return def;
-}
-
-int platform_default_i(const char *name, int def)
-{
- return def;
-}
-
-FontSpec *platform_default_fontspec(const char *name)
-{
- return fontspec_new("");
-}
-
-Filename *platform_default_filename(const char *name)
-{
- return filename_from_str("");
-}
-
-char *x_get_default(const char *key)
-{
- return NULL; /* this is a stub */
-}
-
-void old_keyfile_warning(void) { }
-
-void timer_change_notify(unsigned long next)
-{
-}
-
-char *platform_get_x_display(void) { return NULL; }
-
-void make_unix_sftp_filehandle_key(void *data, size_t size)
-{
- random_read(data, size);
-}
-
-static bool verbose;
-
-struct AuthPolicyShared {
- struct AuthPolicy_ssh1_pubkey *ssh1keys;
- struct AuthPolicy_ssh2_pubkey *ssh2keys;
-};
-
-struct AuthPolicy {
- struct AuthPolicyShared *shared;
- int kbdint_state;
-};
-
-struct server_instance {
- unsigned id;
- AuthPolicy ap;
- LogPolicy logpolicy;
-};
-
-static void log_to_stderr(unsigned id, const char *msg)
-{
- if (id != (unsigned)-1)
- fprintf(stderr, "#%u: ", id);
- fputs(msg, stderr);
- fputc('\n', stderr);
- fflush(stderr);
-}
-
-static void server_eventlog(LogPolicy *lp, const char *event)
-{
- struct server_instance *inst = container_of(
- lp, struct server_instance, logpolicy);
- if (verbose)
- log_to_stderr(inst->id, event);
-}
-
-static void server_logging_error(LogPolicy *lp, const char *event)
-{
- struct server_instance *inst = container_of(
- lp, struct server_instance, logpolicy);
- log_to_stderr(inst->id, event); /* unconditional */
-}
-
-static int server_askappend(
- LogPolicy *lp, Filename *filename,
- void (*callback)(void *ctx, int result), void *ctx)
-{
- return 2; /* always overwrite (FIXME: could make this a cmdline option) */
-}
-
-static const LogPolicyVtable server_logpolicy_vt = {
- .eventlog = server_eventlog,
- .askappend = server_askappend,
- .logging_error = server_logging_error,
- .verbose = null_lp_verbose_no,
-};
-
-struct AuthPolicy_ssh1_pubkey {
- RSAKey key;
- struct AuthPolicy_ssh1_pubkey *next;
-};
-struct AuthPolicy_ssh2_pubkey {
- ptrlen public_blob;
- struct AuthPolicy_ssh2_pubkey *next;
-};
-
-unsigned auth_methods(AuthPolicy *ap)
-{
- return (AUTHMETHOD_PUBLICKEY | AUTHMETHOD_PASSWORD | AUTHMETHOD_KBDINT |
- AUTHMETHOD_TIS | AUTHMETHOD_CRYPTOCARD);
-}
-bool auth_none(AuthPolicy *ap, ptrlen username)
-{
- return false;
-}
-int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password,
- ptrlen *new_password_opt)
-{
- const char *PHONY_GOOD_PASSWORD = "weasel";
- const char *PHONY_BAD_PASSWORD = "ferret";
-
- if (!new_password_opt) {
- /* Accept login with our preconfigured good password */
- if (ptrlen_eq_string(password, PHONY_GOOD_PASSWORD))
- return 1;
- /* Don't outright reject the bad password, but insist on a change */
- if (ptrlen_eq_string(password, PHONY_BAD_PASSWORD))
- return 2;
- /* Reject anything else */
- return 0;
- } else {
- /* In a password-change request, expect the bad password as input */
- if (!ptrlen_eq_string(password, PHONY_BAD_PASSWORD))
- return 0;
- /* Accept a request to change it to the good password */
- if (ptrlen_eq_string(*new_password_opt, PHONY_GOOD_PASSWORD))
- return 1;
- /* Outright reject a request to change it to the same password
- * as it already 'was' */
- if (ptrlen_eq_string(*new_password_opt, PHONY_BAD_PASSWORD))
- return 0;
- /* Anything else, pretend the new pw wasn't good enough, and
- * re-request a change */
- return 2;
- }
-}
-bool auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob)
-{
- struct AuthPolicy_ssh2_pubkey *iter;
- for (iter = ap->shared->ssh2keys; iter; iter = iter->next) {
- if (ptrlen_eq_ptrlen(public_blob, iter->public_blob))
- return true;
- }
- return false;
-}
-RSAKey *auth_publickey_ssh1(
- AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus)
-{
- struct AuthPolicy_ssh1_pubkey *iter;
- for (iter = ap->shared->ssh1keys; iter; iter = iter->next) {
- if (mp_cmp_eq(rsa_modulus, iter->key.modulus))
- return &iter->key;
- }
- return NULL;
-}
-AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username)
-{
- AuthKbdInt *aki;
-
- switch (ap->kbdint_state) {
- case 0:
- aki = snew(AuthKbdInt);
- aki->title = dupstr("Initial double prompt");
- aki->instruction =
- dupstr("First prompt should echo, second should not");
- aki->nprompts = 2;
- aki->prompts = snewn(aki->nprompts, AuthKbdIntPrompt);
- aki->prompts[0].prompt = dupstr("Echoey prompt: ");
- aki->prompts[0].echo = true;
- aki->prompts[1].prompt = dupstr("Silent prompt: ");
- aki->prompts[1].echo = false;
- return aki;
- case 1:
- aki = snew(AuthKbdInt);
- aki->title = dupstr("Zero-prompt step");
- aki->instruction = dupstr("Shouldn't see any prompts this time");
- aki->nprompts = 0;
- aki->prompts = NULL;
- return aki;
- default:
- ap->kbdint_state = 0;
- return NULL;
- }
-}
-int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses)
-{
- switch (ap->kbdint_state) {
- case 0:
- if (ptrlen_eq_string(responses[0], "stoat") &&
- ptrlen_eq_string(responses[1], "weasel")) {
- ap->kbdint_state++;
- return 0; /* those are the expected responses */
- } else {
- ap->kbdint_state = 0;
- return -1;
- }
- break;
- case 1:
- return +1; /* succeed after the zero-prompt step */
- default:
- ap->kbdint_state = 0;
- return -1;
- }
-}
-char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username)
-{
- /* FIXME: test returning a challenge string without \n, and ensure
- * it gets printed as a prompt in its own right, without PuTTY
- * making up a "Response: " prompt to follow it */
- return dupprintf("This is a dummy %s challenge!\n",
- (method == AUTHMETHOD_TIS ? "TIS" : "CryptoCard"));
-}
-bool auth_ssh1int_response(AuthPolicy *ap, ptrlen response)
-{
- return ptrlen_eq_string(response, "otter");
-}
-bool auth_successful(AuthPolicy *ap, ptrlen username, unsigned method)
-{
- return true;
-}
-
-static void safety_warning(FILE *fp)
-{
- fputs(" =================================================\n"
- " THIS SSH SERVER IS NOT WRITTEN TO BE SECURE!\n"
- " DO NOT DEPLOY IT IN A HOSTILE-FACING ENVIRONMENT!\n"
- " =================================================\n", fp);
-}
-
-static void show_help(FILE *fp)
-{
- safety_warning(fp);
- fputs("\n"
- "usage: uppity [options]\n"
- "options: --listen [PORT|PATH] listen to a port on localhost, or Unix socket\n"
- " --listen-once (with --listen) stop after one "
- "connection\n"
- " --hostkey KEY SSH host key (need at least one)\n"
- " --rsakexkey KEY key for SSH-2 RSA key exchange "
- "(in SSH-1 format)\n"
- " --userkey KEY public key"
- " acceptable for user authentication\n"
- " --sessiondir DIR cwd for session subprocess (default $HOME)\n"
- " --bannertext TEXT send TEXT as SSH-2 auth banner\n"
- " --bannerfile FILE send contents of FILE as SSH-2 auth "
- "banner\n"
- " --kexinit-kex STR override list of SSH-2 KEX methods\n"
- " --kexinit-hostkey STR override list of SSH-2 host key "
- "types\n"
- " --kexinit-cscipher STR override list of SSH-2 "
- "client->server ciphers\n"
- " --kexinit-sccipher STR override list of SSH-2 "
- "server->client ciphers\n"
- " --kexinit-csmac STR override list of SSH-2 "
- "client->server MACs\n"
- " --kexinit-scmac STR override list of SSH-2 "
- "server->client MACs\n"
- " --kexinit-cscomp STR override list of SSH-2 "
- "c->s compression types\n"
- " --kexinit-sccomp STR override list of SSH-2 "
- "s->c compression types\n"
- " --ssh1-ciphers STR override list of SSH-1 ciphers\n"
- " --ssh1-no-compression forbid compression in SSH-1\n"
- " --exitsignum send buggy numeric \"exit-signal\" "
- "message\n"
- " --verbose print event log messages to standard "
- "error\n"
- " --sshlog FILE write SSH packet log to FILE\n"
- " --sshrawlog FILE write SSH packets + raw data log"
- " to FILE\n"
- "also: uppity --help show this text\n"
- " uppity --version show version information\n"
- "\n", fp);
- safety_warning(fp);
-}
-
-static void show_version_and_exit(void)
-{
- char *buildinfo_text = buildinfo("\n");
- printf("%s: %s\n%s\n", appname, ver, buildinfo_text);
- sfree(buildinfo_text);
- exit(0);
-}
-
-const bool buildinfo_gtk_relevant = false;
-
-static bool listening = false, listen_once = false;
-static bool finished = false;
-void server_instance_terminated(LogPolicy *lp)
-{
- struct server_instance *inst = container_of(
- lp, struct server_instance, logpolicy);
-
- if (listening && !listen_once) {
- log_to_stderr(inst->id, "connection terminated");
- } else {
- finished = true;
- }
-
- sfree(inst);
-}
-
-static bool longoptarg(const char *arg, const char *expected,
- const char **val, int *argcp, char ***argvp)
-{
- int len = strlen(expected);
- if (memcmp(arg, expected, len))
- return false;
- if (arg[len] == '=') {
- *val = arg + len + 1;
- return true;
- } else if (arg[len] == '\0') {
- if (--*argcp > 0) {
- *val = *++*argvp;
- return true;
- } else {
- fprintf(stderr, "%s: option %s expects an argument\n",
- appname, expected);
- exit(1);
- }
- }
- return false;
-}
-
-static bool longoptnoarg(const char *arg, const char *expected)
-{
- int len = strlen(expected);
- if (memcmp(arg, expected, len))
- return false;
- if (arg[len] == '=') {
- fprintf(stderr, "%s: option %s expects no argument\n",
- appname, expected);
- exit(1);
- } else if (arg[len] == '\0') {
- return true;
- }
- return false;
-}
-
-struct server_config {
- Conf *conf;
- const SshServerConfig *ssc;
-
- ssh_key **hostkeys;
- int nhostkeys;
-
- RSAKey *hostkey1;
-
- struct AuthPolicyShared *ap_shared;
-
- unsigned next_id;
-
- Socket *listening_socket;
- Plug listening_plug;
-};
-
-static Plug *server_conn_plug(
- struct server_config *cfg, struct server_instance **inst_out)
-{
- struct server_instance *inst = snew(struct server_instance);
-
- memset(inst, 0, sizeof(*inst));
-
- inst->id = cfg->next_id++;
- inst->ap.shared = cfg->ap_shared;
- inst->logpolicy.vt = &server_logpolicy_vt;
-
- if (inst_out)
- *inst_out = inst;
-
- return ssh_server_plug(
- cfg->conf, cfg->ssc, cfg->hostkeys, cfg->nhostkeys, cfg->hostkey1,
- &inst->ap, &inst->logpolicy, &unix_live_sftpserver_vt);
-}
-
-static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port,
- const char *error_msg, int error_code)
-{
- log_to_stderr((unsigned)-1, error_msg);
-}
-
-static void server_closing(Plug *plug, const char *error_msg, int error_code,
- bool calling_back)
-{
- log_to_stderr((unsigned)-1, error_msg);
-}
-
-static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx)
-{
- struct server_config *cfg = container_of(
- p, struct server_config, listening_plug);
- Socket *s;
- const char *err;
-
- struct server_instance *inst;
-
- if (listen_once) {
- if (!cfg->listening_socket) /* in case of rapid double-accept */
- return 1;
- sk_close(cfg->listening_socket);
- cfg->listening_socket = NULL;
- }
-
- unsigned old_next_id = cfg->next_id;
-
- Plug *plug = server_conn_plug(cfg, &inst);
- s = constructor(ctx, plug);
- if ((err = sk_socket_error(s)) != NULL)
- return 1;
-
- SocketPeerInfo *pi = sk_peer_info(s);
-
- if (pi->addressfamily != ADDRTYPE_LOCAL && !sk_peer_trusted(s)) {
- fprintf(stderr, "rejected connection from %s (untrustworthy peer)\n",
- pi->log_text);
- sk_free_peer_info(pi);
- sk_close(s);
- cfg->next_id = old_next_id;
- return 1;
- }
-
- char *msg = dupprintf("new connection from %s", pi->log_text);
- log_to_stderr(inst->id, msg);
- sfree(msg);
- sk_free_peer_info(pi);
-
- sk_set_frozen(s, false);
- ssh_server_start(plug, s);
- return 0;
-}
-
-static const PlugVtable server_plugvt = {
- .log = server_log,
- .closing = server_closing,
- .accepting = server_accepting,
-};
-
-int main(int argc, char **argv)
-{
- int listen_port = -1;
- const char *listen_socket = NULL;
-
- ssh_key **hostkeys = NULL;
- size_t nhostkeys = 0, hostkeysize = 0;
- RSAKey *hostkey1 = NULL;
-
- struct AuthPolicyShared aps;
- SshServerConfig ssc;
-
- Conf *conf = make_ssh_server_conf();
-
- aps.ssh1keys = NULL;
- aps.ssh2keys = NULL;
-
- memset(&ssc, 0, sizeof(ssc));
-
- ssc.application_name = "Uppity";
- ssc.session_starting_dir = getenv("HOME");
- ssc.ssh1_cipher_mask = SSH1_SUPPORTED_CIPHER_MASK;
- ssc.ssh1_allow_compression = true;
-
- if (argc <= 1) {
- /*
- * We're going to terminate with an error message below,
- * because there are no host keys. But we'll display the help
- * as additional standard-error output, if nothing else so
- * that people see the giant safety warning.
- */
- show_help(stderr);
- fputc('\n', stderr);
- }
-
- while (--argc > 0) {
- const char *arg = *++argv;
- const char *val;
-
- if (!strcmp(arg, "--help")) {
- show_help(stdout);
- exit(0);
- } else if (longoptnoarg(arg, "--version")) {
- show_version_and_exit();
- } else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) {
- verbose = true;
- } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) {
- if (val[0] == '/') {
- listen_port = -1;
- listen_socket = val;
- } else {
- listen_port = atoi(val);
- listen_socket = NULL;
- }
- } else if (!strcmp(arg, "--listen-once")) {
- listen_once = true;
- } else if (longoptarg(arg, "--hostkey", &val, &argc, &argv)) {
- Filename *keyfile;
- int keytype;
- const char *error;
-
- keyfile = filename_from_str(val);
- keytype = key_type(keyfile);
-
- if (keytype == SSH_KEYTYPE_SSH2) {
- ssh2_userkey *uk;
- ssh_key *key;
- uk = ppk_load_f(keyfile, NULL, &error);
- filename_free(keyfile);
- if (!uk || !uk->key) {
- fprintf(stderr, "%s: unable to load host key '%s': "
- "%s\n", appname, val, error);
- exit(1);
- }
- char *invalid = ssh_key_invalid(uk->key, 0);
- if (invalid) {
- fprintf(stderr, "%s: host key '%s' is unusable: "
- "%s\n", appname, val, invalid);
- exit(1);
- }
- key = uk->key;
- sfree(uk->comment);
- sfree(uk);
-
- for (int i = 0; i < nhostkeys; i++)
- if (ssh_key_alg(hostkeys[i]) == ssh_key_alg(key)) {
- fprintf(stderr, "%s: host key '%s' duplicates key "
- "type %s\n", appname, val,
- ssh_key_alg(key)->ssh_id);
- exit(1);
- }
-
- sgrowarray(hostkeys, hostkeysize, nhostkeys);
- hostkeys[nhostkeys++] = key;
- } else if (keytype == SSH_KEYTYPE_SSH1) {
- if (hostkey1) {
- fprintf(stderr, "%s: host key '%s' is a redundant "
- "SSH-1 host key\n", appname, val);
- exit(1);
- }
- hostkey1 = snew(RSAKey);
- if (!rsa1_load_f(keyfile, hostkey1, NULL, &error)) {
- fprintf(stderr, "%s: unable to load host key '%s': "
- "%s\n", appname, val, error);
- exit(1);
- }
- } else {
- fprintf(stderr, "%s: '%s' is not loadable as a "
- "private key (%s)", appname, val,
- key_type_to_str(keytype));
- exit(1);
- }
- } else if (longoptarg(arg, "--rsakexkey", &val, &argc, &argv)) {
- Filename *keyfile;
- int keytype;
- const char *error;
-
- keyfile = filename_from_str(val);
- keytype = key_type(keyfile);
-
- if (keytype != SSH_KEYTYPE_SSH1) {
- fprintf(stderr, "%s: '%s' is not loadable as an SSH-1 format "
- "private key (%s)", appname, val,
- key_type_to_str(keytype));
- exit(1);
- }
-
- if (ssc.rsa_kex_key) {
- freersakey(ssc.rsa_kex_key);
- } else {
- ssc.rsa_kex_key = snew(RSAKey);
- }
-
- if (!rsa1_load_f(keyfile, ssc.rsa_kex_key, NULL, &error)) {
- fprintf(stderr, "%s: unable to load RSA kex key '%s': "
- "%s\n", appname, val, error);
- exit(1);
- }
-
- ssc.rsa_kex_key->sshk.vt = &ssh_rsa;
- } else if (longoptarg(arg, "--userkey", &val, &argc, &argv)) {
- Filename *keyfile;
- int keytype;
- const char *error;
-
- keyfile = filename_from_str(val);
- keytype = key_type(keyfile);
-
- if (keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
- keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
- strbuf *sb = strbuf_new();
- struct AuthPolicy_ssh2_pubkey *node;
- void *blob;
-
- if (!ppk_loadpub_f(keyfile, NULL, BinarySink_UPCAST(sb),
- NULL, &error)) {
- fprintf(stderr, "%s: unable to load user key '%s': "
- "%s\n", appname, val, error);
- exit(1);
- }
-
- node = snew_plus(struct AuthPolicy_ssh2_pubkey, sb->len);
- blob = snew_plus_get_aux(node);
- memcpy(blob, sb->u, sb->len);
- node->public_blob = make_ptrlen(blob, sb->len);
-
- node->next = aps.ssh2keys;
- aps.ssh2keys = node;
-
- strbuf_free(sb);
- } else if (keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
- strbuf *sb = strbuf_new();
- BinarySource src[1];
- struct AuthPolicy_ssh1_pubkey *node;
-
- if (!rsa1_loadpub_f(keyfile, BinarySink_UPCAST(sb),
- NULL, &error)) {
- fprintf(stderr, "%s: unable to load user key '%s': "
- "%s\n", appname, val, error);
- exit(1);
- }
-
- node = snew(struct AuthPolicy_ssh1_pubkey);
- BinarySource_BARE_INIT(src, sb->u, sb->len);
- get_rsa_ssh1_pub(src, &node->key, RSA_SSH1_EXPONENT_FIRST);
-
- node->next = aps.ssh1keys;
- aps.ssh1keys = node;
-
- strbuf_free(sb);
- } else {
- fprintf(stderr, "%s: '%s' is not loadable as a public key "
- "(%s)\n", appname, val, key_type_to_str(keytype));
- exit(1);
- }
- } else if (longoptarg(arg, "--bannerfile", &val, &argc, &argv)) {
- FILE *fp = fopen(val, "r");
- if (!fp) {
- fprintf(stderr, "%s: %s: open: %s\n", appname,
- val, strerror(errno));
- exit(1);
- }
- strbuf *sb = strbuf_new();
- if (!read_file_into(BinarySink_UPCAST(sb), fp)) {
- fprintf(stderr, "%s: %s: read: %s\n", appname,
- val, strerror(errno));
- exit(1);
- }
- fclose(fp);
- ssc.banner = ptrlen_from_strbuf(sb);
- } else if (longoptarg(arg, "--bannertext", &val, &argc, &argv)) {
- ssc.banner = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) {
- ssc.session_starting_dir = val;
- } else if (longoptarg(arg, "--kexinit-kex", &val, &argc, &argv)) {
- ssc.kex_override[KEXLIST_KEX] = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--kexinit-hostkey", &val, &argc, &argv)) {
- ssc.kex_override[KEXLIST_HOSTKEY] = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--kexinit-cscipher", &val, &argc, &argv)) {
- ssc.kex_override[KEXLIST_CSCIPHER] = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--kexinit-csmac", &val, &argc, &argv)) {
- ssc.kex_override[KEXLIST_CSMAC] = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--kexinit-cscomp", &val, &argc, &argv)) {
- ssc.kex_override[KEXLIST_CSCOMP] = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--kexinit-sccipher", &val, &argc, &argv)) {
- ssc.kex_override[KEXLIST_SCCIPHER] = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--kexinit-scmac", &val, &argc, &argv)) {
- ssc.kex_override[KEXLIST_SCMAC] = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--kexinit-sccomp", &val, &argc, &argv)) {
- ssc.kex_override[KEXLIST_SCCOMP] = ptrlen_from_asciz(val);
- } else if (longoptarg(arg, "--ssh1-ciphers", &val, &argc, &argv)) {
- ptrlen list = ptrlen_from_asciz(val);
- ptrlen word;
- unsigned long mask = 0;
- while (word = ptrlen_get_word(&list, ","), word.len != 0) {
-
-#define SSH1_CIPHER_CASE(bitpos, name) \
- if (ptrlen_eq_string(word, name)) { \
- mask |= 1U << bitpos; \
- continue; \
- }
- SSH1_SUPPORTED_CIPHER_LIST(SSH1_CIPHER_CASE);
-#undef SSH1_CIPHER_CASE
-
- fprintf(stderr, "%s: unrecognised SSH-1 cipher '%.*s'\n",
- appname, PTRLEN_PRINTF(word));
- exit(1);
- }
- ssc.ssh1_cipher_mask = mask;
- } else if (longoptnoarg(arg, "--ssh1-no-compression")) {
- ssc.ssh1_allow_compression = false;
- } else if (longoptnoarg(arg, "--exitsignum")) {
- ssc.exit_signal_numeric = true;
- } else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) ||
- longoptarg(arg, "-sshlog", &val, &argc, &argv)) {
- Filename *logfile = filename_from_str(val);
- conf_set_filename(conf, CONF_logfilename, logfile);
- filename_free(logfile);
- conf_set_int(conf, CONF_logtype, LGTYP_PACKETS);
- conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
- } else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) ||
- longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) {
- Filename *logfile = filename_from_str(val);
- conf_set_filename(conf, CONF_logfilename, logfile);
- filename_free(logfile);
- conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW);
- conf_set_int(conf, CONF_logxfovr, LGXF_OVR);
- } else if (!strcmp(arg, "--pretend-to-accept-any-pubkey")) {
- ssc.stunt_pretend_to_accept_any_pubkey = true;
- } else if (!strcmp(arg, "--open-unconditional-agent-socket")) {
- ssc.stunt_open_unconditional_agent_socket = true;
- } else {
- fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg);
- exit(1);
- }
- }
-
- if (nhostkeys == 0 && !hostkey1) {
- fprintf(stderr, "%s: specify at least one host key\n", appname);
- exit(1);
- }
-
- random_ref();
-
- /*
- * Block SIGPIPE, so that we'll get EPIPE individually on
- * particular network connections that go wrong.
- */
- putty_signal(SIGPIPE, SIG_IGN);
-
- sk_init();
- uxsel_init();
-
- struct server_config scfg;
- scfg.conf = conf;
- scfg.ssc = &ssc;
- scfg.hostkeys = hostkeys;
- scfg.nhostkeys = nhostkeys;
- scfg.hostkey1 = hostkey1;
- scfg.ap_shared = &aps;
- scfg.next_id = 0;
-
- if (listen_port >= 0 || listen_socket) {
- listening = true;
- scfg.listening_plug.vt = &server_plugvt;
- char *msg;
- if (listen_port >= 0) {
- scfg.listening_socket = sk_newlistener(
- NULL, listen_port, &scfg.listening_plug, true,
- ADDRTYPE_UNSPEC);
- msg = dupprintf("%s: listening on port %d",
- appname, listen_port);
- } else {
- SockAddr *addr = unix_sock_addr(listen_socket);
- scfg.listening_socket = new_unix_listener(
- addr, &scfg.listening_plug);
- msg = dupprintf("%s: listening on Unix socket %s",
- appname, listen_socket);
- }
-
- log_to_stderr(-1, msg);
- sfree(msg);
- } else {
- struct server_instance *inst;
- Plug *plug = server_conn_plug(&scfg, &inst);
- ssh_server_start(plug, make_fd_socket(0, 1, -1, plug));
- log_to_stderr(inst->id, "speaking SSH on stdio");
- }
-
- cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check,
- cliloop_always_continue, NULL);
-
- return 0;
-}
diff --git a/unix/uxsftp.c b/unix/uxsftp.c
deleted file mode 100644
index 89a81c92..00000000
--- a/unix/uxsftp.c
+++ /dev/null
@@ -1,578 +0,0 @@
-/*
- * uxsftp.c: the Unix-specific parts of PSFTP and PSCP.
- */
-
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <stdlib.h>
-#include <fcntl.h>
-#include <dirent.h>
-#include <unistd.h>
-#include <utime.h>
-#include <errno.h>
-#include <assert.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "psftp.h"
-
-#if HAVE_GLOB_H
-#include <glob.h>
-#endif
-
-char *x_get_default(const char *key)
-{
- return NULL; /* this is a stub */
-}
-
-void platform_get_x11_auth(struct X11Display *display, Conf *conf)
-{
- /* Do nothing, therefore no auth. */
-}
-const bool platform_uses_x11_unix_by_default = true;
-
-/*
- * Default settings that are specific to PSFTP.
- */
-char *platform_default_s(const char *name)
-{
- return NULL;
-}
-
-bool platform_default_b(const char *name, bool def)
-{
- return def;
-}
-
-int platform_default_i(const char *name, int def)
-{
- return def;
-}
-
-FontSpec *platform_default_fontspec(const char *name)
-{
- return fontspec_new("");
-}
-
-Filename *platform_default_filename(const char *name)
-{
- if (!strcmp(name, "LogFileName"))
- return filename_from_str("putty.log");
- else
- return filename_from_str("");
-}
-
-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;
-}
-
-/*
- * Set local current directory. Returns NULL on success, or else an
- * error message which must be freed after printing.
- */
-char *psftp_lcd(char *dir)
-{
- if (chdir(dir) < 0)
- return dupprintf("%s: chdir: %s", dir, strerror(errno));
- else
- return NULL;
-}
-
-/*
- * Get local current directory. Returns a string which must be
- * freed.
- */
-char *psftp_getcwd(void)
-{
- char *buffer, *ret;
- size_t size = 256;
-
- buffer = snewn(size, char);
- while (1) {
- ret = getcwd(buffer, size);
- if (ret != NULL)
- return ret;
- if (errno != ERANGE) {
- sfree(buffer);
- return dupprintf("[cwd unavailable: %s]", strerror(errno));
- }
- /*
- * Otherwise, ERANGE was returned, meaning the buffer
- * wasn't big enough.
- */
- sgrowarray(buffer, size, size);
- }
-}
-
-struct RFile {
- int fd;
-};
-
-RFile *open_existing_file(const char *name, uint64_t *size,
- unsigned long *mtime, unsigned long *atime,
- long *perms)
-{
- int fd;
- RFile *ret;
-
- fd = open(name, O_RDONLY);
- if (fd < 0)
- return NULL;
-
- ret = snew(RFile);
- ret->fd = fd;
-
- if (size || mtime || atime || perms) {
- struct stat statbuf;
- if (fstat(fd, &statbuf) < 0) {
- fprintf(stderr, "%s: stat: %s\n", name, strerror(errno));
- memset(&statbuf, 0, sizeof(statbuf));
- }
-
- if (size)
- *size = statbuf.st_size;
-
- if (mtime)
- *mtime = statbuf.st_mtime;
-
- if (atime)
- *atime = statbuf.st_atime;
-
- if (perms)
- *perms = statbuf.st_mode;
- }
-
- return ret;
-}
-
-int read_from_file(RFile *f, void *buffer, int length)
-{
- return read(f->fd, buffer, length);
-}
-
-void close_rfile(RFile *f)
-{
- close(f->fd);
- sfree(f);
-}
-
-struct WFile {
- int fd;
- char *name;
-};
-
-WFile *open_new_file(const char *name, long perms)
-{
- int fd;
- WFile *ret;
-
- fd = open(name, O_CREAT | O_TRUNC | O_WRONLY,
- (mode_t)(perms ? perms : 0666));
- if (fd < 0)
- return NULL;
-
- ret = snew(WFile);
- ret->fd = fd;
- ret->name = dupstr(name);
-
- return ret;
-}
-
-
-WFile *open_existing_wfile(const char *name, uint64_t *size)
-{
- int fd;
- WFile *ret;
-
- fd = open(name, O_APPEND | O_WRONLY);
- if (fd < 0)
- return NULL;
-
- ret = snew(WFile);
- ret->fd = fd;
- ret->name = dupstr(name);
-
- if (size) {
- struct stat statbuf;
- if (fstat(fd, &statbuf) < 0) {
- fprintf(stderr, "%s: stat: %s\n", name, strerror(errno));
- memset(&statbuf, 0, sizeof(statbuf));
- }
-
- *size = statbuf.st_size;
- }
-
- return ret;
-}
-
-int write_to_file(WFile *f, void *buffer, int length)
-{
- char *p = (char *)buffer;
- int so_far = 0;
-
- /* Keep trying until we've really written as much as we can. */
- while (length > 0) {
- int ret = write(f->fd, p, length);
-
- if (ret < 0)
- return ret;
-
- if (ret == 0)
- break;
-
- p += ret;
- length -= ret;
- so_far += ret;
- }
-
- return so_far;
-}
-
-void set_file_times(WFile *f, unsigned long mtime, unsigned long atime)
-{
- struct utimbuf ut;
-
- ut.actime = atime;
- ut.modtime = mtime;
-
- utime(f->name, &ut);
-}
-
-/* Closes and frees the WFile */
-void close_wfile(WFile *f)
-{
- close(f->fd);
- sfree(f->name);
- sfree(f);
-}
-
-/* Seek offset bytes through file, from whence, where whence is
- FROM_START, FROM_CURRENT, or FROM_END */
-int seek_file(WFile *f, uint64_t offset, int whence)
-{
- int lseek_whence;
-
- switch (whence) {
- case FROM_START:
- lseek_whence = SEEK_SET;
- break;
- case FROM_CURRENT:
- lseek_whence = SEEK_CUR;
- break;
- case FROM_END:
- lseek_whence = SEEK_END;
- break;
- default:
- return -1;
- }
-
- return lseek(f->fd, offset, lseek_whence) >= 0 ? 0 : -1;
-}
-
-uint64_t get_file_posn(WFile *f)
-{
- return lseek(f->fd, (off_t) 0, SEEK_CUR);
-}
-
-int file_type(const char *name)
-{
- struct stat statbuf;
-
- if (stat(name, &statbuf) < 0) {
- if (errno != ENOENT)
- fprintf(stderr, "%s: stat: %s\n", name, strerror(errno));
- return FILE_TYPE_NONEXISTENT;
- }
-
- if (S_ISREG(statbuf.st_mode))
- return FILE_TYPE_FILE;
-
- if (S_ISDIR(statbuf.st_mode))
- return FILE_TYPE_DIRECTORY;
-
- return FILE_TYPE_WEIRD;
-}
-
-struct DirHandle {
- DIR *dir;
-};
-
-DirHandle *open_directory(const char *name, const char **errmsg)
-{
- DIR *dir;
- DirHandle *ret;
-
- dir = opendir(name);
- if (!dir) {
- *errmsg = strerror(errno);
- return NULL;
- }
-
- ret = snew(DirHandle);
- ret->dir = dir;
- return ret;
-}
-
-char *read_filename(DirHandle *dir)
-{
- struct dirent *de;
-
- do {
- de = readdir(dir->dir);
- if (de == NULL)
- return NULL;
- } while ((de->d_name[0] == '.' &&
- (de->d_name[1] == '\0' ||
- (de->d_name[1] == '.' && de->d_name[2] == '\0'))));
-
- return dupstr(de->d_name);
-}
-
-void close_directory(DirHandle *dir)
-{
- closedir(dir->dir);
- sfree(dir);
-}
-
-int test_wildcard(const char *name, bool cmdline)
-{
- struct stat statbuf;
-
- if (stat(name, &statbuf) == 0) {
- return WCTYPE_FILENAME;
- } else if (cmdline) {
- /*
- * On Unix, we never need to parse wildcards coming from
- * the command line, because the shell will have expanded
- * them into a filename list already.
- */
- return WCTYPE_NONEXISTENT;
- } else {
-#if HAVE_GLOB_H
- glob_t globbed;
- int ret = WCTYPE_NONEXISTENT;
-
- if (glob(name, GLOB_ERR, NULL, &globbed) == 0) {
- if (globbed.gl_pathc > 0)
- ret = WCTYPE_WILDCARD;
- globfree(&globbed);
- }
-
- return ret;
-#else
- /* On a system without glob.h, we just have to return a
- * failure code */
- return WCTYPE_NONEXISTENT;
-#endif
- }
-}
-
-/*
- * Actually return matching file names for a local wildcard.
- */
-#if HAVE_GLOB_H
-struct WildcardMatcher {
- glob_t globbed;
- int i;
-};
-WildcardMatcher *begin_wildcard_matching(const char *name) {
- WildcardMatcher *ret = snew(WildcardMatcher);
-
- if (glob(name, 0, NULL, &ret->globbed) < 0) {
- sfree(ret);
- return NULL;
- }
-
- ret->i = 0;
-
- return ret;
-}
-char *wildcard_get_filename(WildcardMatcher *dir) {
- if (dir->i < dir->globbed.gl_pathc) {
- return dupstr(dir->globbed.gl_pathv[dir->i++]);
- } else
- return NULL;
-}
-void finish_wildcard_matching(WildcardMatcher *dir) {
- globfree(&dir->globbed);
- sfree(dir);
-}
-#else
-WildcardMatcher *begin_wildcard_matching(const char *name)
-{
- return NULL;
-}
-char *wildcard_get_filename(WildcardMatcher *dir)
-{
- unreachable("Can't construct a valid WildcardMatcher without <glob.h>");
-}
-void finish_wildcard_matching(WildcardMatcher *dir)
-{
- unreachable("Can't construct a valid WildcardMatcher without <glob.h>");
-}
-#endif
-
-char *stripslashes(const char *str, bool local)
-{
- char *p;
-
- /*
- * On Unix, we do the same thing regardless of the 'local'
- * parameter.
- */
- p = strrchr(str, '/');
- if (p) str = p+1;
-
- return (char *)str;
-}
-
-bool vet_filename(const char *name)
-{
- if (strchr(name, '/'))
- return false;
-
- if (name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2])))
- return false;
-
- return true;
-}
-
-bool create_directory(const char *name)
-{
- return mkdir(name, 0777) == 0;
-}
-
-char *dir_file_cat(const char *dir, const char *file)
-{
- ptrlen dir_pl = ptrlen_from_asciz(dir);
- return dupcat(
- dir, ptrlen_endswith(dir_pl, PTRLEN_LITERAL("/"), NULL) ? "" : "/",
- file);
-}
-
-/*
- * Do a select() between all currently active network fds and
- * optionally stdin, using cli_main_loop.
- */
-
-struct ssh_sftp_mainloop_ctx {
- bool include_stdin, no_fds_ok;
- int toret;
-};
-static bool ssh_sftp_pw_setup(void *vctx, pollwrapper *pw)
-{
- struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx;
- int fdstate, rwx;
-
- if (!ctx->no_fds_ok && !toplevel_callback_pending() &&
- first_fd(&fdstate, &rwx) < 0) {
- ctx->toret = -1;
- return false; /* terminate cli_main_loop */
- }
-
- if (ctx->include_stdin)
- pollwrap_add_fd_rwx(pw, 0, SELECT_R);
-
- return true;
-}
-static void ssh_sftp_pw_check(void *vctx, pollwrapper *pw)
-{
- struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx;
-
- if (ctx->include_stdin && pollwrap_check_fd_rwx(pw, 0, SELECT_R))
- ctx->toret = 1;
-}
-static bool ssh_sftp_mainloop_continue(void *vctx, bool found_any_fd,
- bool ran_any_callback)
-{
- struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx;
- if (ctx->toret != 0 || found_any_fd || ran_any_callback)
- return false; /* finish the loop */
- return true;
-}
-static int ssh_sftp_do_select(bool include_stdin, bool no_fds_ok)
-{
- struct ssh_sftp_mainloop_ctx ctx[1];
- ctx->include_stdin = include_stdin;
- ctx->no_fds_ok = no_fds_ok;
- ctx->toret = 0;
-
- cli_main_loop(ssh_sftp_pw_setup, ssh_sftp_pw_check,
- ssh_sftp_mainloop_continue, ctx);
-
- return ctx->toret;
-}
-
-/*
- * Wait for some network data and process it.
- */
-int ssh_sftp_loop_iteration(void)
-{
- return ssh_sftp_do_select(false, false);
-}
-
-/*
- * Read a PSFTP command line from stdin.
- */
-char *ssh_sftp_get_cmdline(const char *prompt, bool no_fds_ok)
-{
- char *buf;
- size_t buflen, bufsize;
- int ret;
-
- fputs(prompt, stdout);
- fflush(stdout);
-
- buf = NULL;
- buflen = bufsize = 0;
-
- while (1) {
- ret = ssh_sftp_do_select(true, no_fds_ok);
- if (ret < 0) {
- printf("connection died\n");
- sfree(buf);
- return NULL; /* woop woop */
- }
- if (ret > 0) {
- sgrowarray(buf, bufsize, buflen);
- ret = read(0, buf+buflen, 1);
- if (ret < 0) {
- perror("read");
- sfree(buf);
- return NULL;
- }
- if (ret == 0) {
- /* eof on stdin; no error, but no answer either */
- sfree(buf);
- return NULL;
- }
-
- if (buf[buflen++] == '\n') {
- /* we have a full line */
- return buf;
- }
- }
- }
-}
-
-void frontend_net_error_pending(void) {}
-
-void platform_psftp_pre_conn_setup(LogPolicy *lp) {}
-
-const bool buildinfo_gtk_relevant = false;
-
-/*
- * Main program: do platform-specific initialisation and then call
- * psftp_main().
- */
-int main(int argc, char *argv[])
-{
- uxsel_init();
- return psftp_main(argc, argv);
-}
diff --git a/unix/uxsftpserver.c b/unix/uxsftpserver.c
deleted file mode 100644
index acefe9bd..00000000
--- a/unix/uxsftpserver.c
+++ /dev/null
@@ -1,703 +0,0 @@
-/*
- * Implement the SftpServer abstraction, in the 'live' form (i.e.
- * really operating on the Unix filesystem).
- */
-
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/time.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <pwd.h>
-#include <grp.h>
-#include <dirent.h>
-#include <utime.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "sshserver.h"
-#include "sftp.h"
-#include "tree234.h"
-
-typedef struct UnixSftpServer UnixSftpServer;
-
-struct UnixSftpServer {
- unsigned *fdseqs;
- bool *fdsopen;
- size_t fdsize;
-
- tree234 *dirhandles;
- int last_dirhandle_index;
-
- char handlekey[8];
-
- SftpServer srv;
-};
-
-struct uss_dirhandle {
- int index;
- DIR *dp;
-};
-
-#define USS_DIRHANDLE_SEQ (0xFFFFFFFFU)
-
-static int uss_dirhandle_cmp(void *av, void *bv)
-{
- struct uss_dirhandle *a = (struct uss_dirhandle *)av;
- struct uss_dirhandle *b = (struct uss_dirhandle *)bv;
- if (a->index < b->index)
- return -1;
- if (a->index > b->index)
- return +1;
- return 0;
-}
-
-static SftpServer *uss_new(const SftpServerVtable *vt)
-{
- UnixSftpServer *uss = snew(UnixSftpServer);
-
- memset(uss, 0, sizeof(UnixSftpServer));
-
- uss->dirhandles = newtree234(uss_dirhandle_cmp);
- uss->srv.vt = vt;
-
- make_unix_sftp_filehandle_key(uss->handlekey, sizeof(uss->handlekey));
-
- return &uss->srv;
-}
-
-static void uss_free(SftpServer *srv)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
- struct uss_dirhandle *udh;
-
- for (size_t i = 0; i < uss->fdsize; i++)
- if (uss->fdsopen[i])
- close(i);
- sfree(uss->fdseqs);
-
- while ((udh = delpos234(uss->dirhandles, 0)) != NULL) {
- closedir(udh->dp);
- sfree(udh);
- }
-
- sfree(uss);
-}
-
-static void uss_return_handle_raw(
- UnixSftpServer *uss, SftpReplyBuilder *reply, int index, unsigned seq)
-{
- unsigned char handlebuf[8];
- PUT_32BIT_MSB_FIRST(handlebuf, index);
- PUT_32BIT_MSB_FIRST(handlebuf + 4, seq);
- des_encrypt_xdmauth(uss->handlekey, handlebuf, 8);
- fxp_reply_handle(reply, make_ptrlen(handlebuf, 8));
-}
-
-static bool uss_decode_handle(
- UnixSftpServer *uss, ptrlen handle, int *index, unsigned *seq)
-{
- unsigned char handlebuf[8];
-
- if (handle.len != 8)
- return false;
- memcpy(handlebuf, handle.ptr, 8);
- des_decrypt_xdmauth(uss->handlekey, handlebuf, 8);
- *index = toint(GET_32BIT_MSB_FIRST(handlebuf));
- *seq = GET_32BIT_MSB_FIRST(handlebuf + 4);
- return true;
-}
-
-static void uss_return_new_handle(
- UnixSftpServer *uss, SftpReplyBuilder *reply, int fd)
-{
- assert(fd >= 0);
- if (fd >= uss->fdsize) {
- size_t old_size = uss->fdsize;
- sgrowarray(uss->fdseqs, uss->fdsize, fd);
- uss->fdsopen = sresize(uss->fdsopen, uss->fdsize, bool);
- while (old_size < uss->fdsize) {
- uss->fdseqs[old_size] = 0;
- uss->fdsopen[old_size] = false;
- old_size++;
- }
- }
- assert(!uss->fdsopen[fd]);
- uss->fdsopen[fd] = true;
- if (++uss->fdseqs[fd] == USS_DIRHANDLE_SEQ)
- uss->fdseqs[fd] = 0;
- uss_return_handle_raw(uss, reply, fd, uss->fdseqs[fd]);
-}
-
-static int uss_try_lookup_fd(UnixSftpServer *uss, ptrlen handle)
-{
- int fd;
- unsigned seq;
- if (!uss_decode_handle(uss, handle, &fd, &seq) ||
- fd < 0 || fd >= uss->fdsize ||
- !uss->fdsopen[fd] || uss->fdseqs[fd] != seq)
- return -1;
-
- return fd;
-}
-
-static int uss_lookup_fd(UnixSftpServer *uss, SftpReplyBuilder *reply,
- ptrlen handle)
-{
- int fd = uss_try_lookup_fd(uss, handle);
- if (fd < 0)
- fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle");
- return fd;
-}
-
-static void uss_return_new_dirhandle(
- UnixSftpServer *uss, SftpReplyBuilder *reply, DIR *dp)
-{
- struct uss_dirhandle *udh = snew(struct uss_dirhandle);
- udh->index = uss->last_dirhandle_index++;
- udh->dp = dp;
- struct uss_dirhandle *added = add234(uss->dirhandles, udh);
- assert(added == udh);
- uss_return_handle_raw(uss, reply, udh->index, USS_DIRHANDLE_SEQ);
-}
-
-static struct uss_dirhandle *uss_try_lookup_dirhandle(
- UnixSftpServer *uss, ptrlen handle)
-{
- struct uss_dirhandle key, *udh;
- unsigned seq;
-
- if (!uss_decode_handle(uss, handle, &key.index, &seq) ||
- seq != USS_DIRHANDLE_SEQ ||
- (udh = find234(uss->dirhandles, &key, NULL)) == NULL)
- return NULL;
-
- return udh;
-}
-
-static struct uss_dirhandle *uss_lookup_dirhandle(
- UnixSftpServer *uss, SftpReplyBuilder *reply, ptrlen handle)
-{
- struct uss_dirhandle *udh = uss_try_lookup_dirhandle(uss, handle);
- if (!udh)
- fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle");
- return udh;
-}
-
-static void uss_error(UnixSftpServer *uss, SftpReplyBuilder *reply)
-{
- unsigned code = SSH_FX_FAILURE;
- switch (errno) {
- case ENOENT:
- code = SSH_FX_NO_SUCH_FILE;
- break;
- case EPERM:
- code = SSH_FX_PERMISSION_DENIED;
- break;
- }
- fxp_reply_error(reply, code, strerror(errno));
-}
-
-static void uss_realpath(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen path)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
-
- char *inpath = mkstr(path);
- char *outpath = realpath(inpath, NULL);
- free(inpath);
-
- if (!outpath) {
- uss_error(uss, reply);
- } else {
- fxp_reply_simple_name(reply, ptrlen_from_asciz(outpath));
- free(outpath);
- }
-}
-
-static void uss_open(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen path, unsigned flags, struct fxp_attrs attrs)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
-
- int openflags = 0;
- if (!((SSH_FXF_READ | SSH_FXF_WRITE) &~ flags))
- openflags |= O_RDWR;
- else if (flags & SSH_FXF_WRITE)
- openflags |= O_WRONLY;
- else if (flags & SSH_FXF_READ)
- openflags |= O_RDONLY;
- if (flags & SSH_FXF_APPEND)
- openflags |= O_APPEND;
- if (flags & SSH_FXF_CREAT)
- openflags |= O_CREAT;
- if (flags & SSH_FXF_TRUNC)
- openflags |= O_TRUNC;
- if (flags & SSH_FXF_EXCL)
- openflags |= O_EXCL;
-
- char *pathstr = mkstr(path);
- int fd = open(pathstr, openflags, GET_PERMISSIONS(attrs, 0777));
- free(pathstr);
-
- if (fd < 0) {
- uss_error(uss, reply);
- } else {
- uss_return_new_handle(uss, reply, fd);
- }
-}
-
-static void uss_opendir(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen path)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
-
- char *pathstr = mkstr(path);
- DIR *dp = opendir(pathstr);
- free(pathstr);
-
- if (!dp) {
- uss_error(uss, reply);
- } else {
- uss_return_new_dirhandle(uss, reply, dp);
- }
-}
-
-static void uss_close(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen handle)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
- int fd;
- struct uss_dirhandle *udh;
-
- if ((udh = uss_try_lookup_dirhandle(uss, handle)) != NULL) {
- closedir(udh->dp);
- del234(uss->dirhandles, udh);
- sfree(udh);
- fxp_reply_ok(reply);
- } else if ((fd = uss_lookup_fd(uss, reply, handle)) >= 0) {
- close(fd);
- assert(0 <= fd && fd <= uss->fdsize);
- uss->fdsopen[fd] = false;
- fxp_reply_ok(reply);
- }
- /* if both failed, uss_lookup_fd will have filled in an error response */
-}
-
-static void uss_mkdir(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen path, struct fxp_attrs attrs)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
-
- char *pathstr = mkstr(path);
- int status = mkdir(pathstr, GET_PERMISSIONS(attrs, 0777));
- free(pathstr);
-
- if (status < 0) {
- uss_error(uss, reply);
- } else {
- fxp_reply_ok(reply);
- }
-}
-
-static void uss_rmdir(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
-
- char *pathstr = mkstr(path);
- int status = rmdir(pathstr);
- free(pathstr);
-
- if (status < 0) {
- uss_error(uss, reply);
- } else {
- fxp_reply_ok(reply);
- }
-}
-
-static void uss_remove(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen path)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
-
- char *pathstr = mkstr(path);
- int status = unlink(pathstr);
- free(pathstr);
-
- if (status < 0) {
- uss_error(uss, reply);
- } else {
- fxp_reply_ok(reply);
- }
-}
-
-static void uss_rename(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen srcpath, ptrlen dstpath)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
-
- char *srcstr = mkstr(srcpath), *dststr = mkstr(dstpath);
- int status = rename(srcstr, dststr);
- free(srcstr);
- free(dststr);
-
- if (status < 0) {
- uss_error(uss, reply);
- } else {
- fxp_reply_ok(reply);
- }
-}
-
-static struct fxp_attrs uss_translate_struct_stat(const struct stat *st)
-{
- struct fxp_attrs attrs;
-
- attrs.flags = (SSH_FILEXFER_ATTR_SIZE |
- SSH_FILEXFER_ATTR_PERMISSIONS |
- SSH_FILEXFER_ATTR_UIDGID |
- SSH_FILEXFER_ATTR_ACMODTIME);
-
- attrs.size = st->st_size;
- attrs.permissions = st->st_mode;
- attrs.uid = st->st_uid;
- attrs.gid = st->st_gid;
- attrs.atime = st->st_atime;
- attrs.mtime = st->st_mtime;
-
- return attrs;
-}
-
-static void uss_reply_struct_stat(SftpReplyBuilder *reply,
- const struct stat *st)
-{
- fxp_reply_attrs(reply, uss_translate_struct_stat(st));
-}
-
-static void uss_stat(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen path, bool follow_symlinks)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
- struct stat st;
-
- char *pathstr = mkstr(path);
- int status = (follow_symlinks ? stat : lstat) (pathstr, &st);
- free(pathstr);
-
- if (status < 0) {
- uss_error(uss, reply);
- } else {
- uss_reply_struct_stat(reply, &st);
- }
-}
-
-static void uss_fstat(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen handle)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
- struct stat st;
- int fd;
-
- if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
- return;
- int status = fstat(fd, &st);
-
- if (status < 0) {
- uss_error(uss, reply);
- } else {
- uss_reply_struct_stat(reply, &st);
- }
-}
-
-#if !HAVE_FUTIMES
-static inline int futimes(int fd, const struct timeval tv[2])
-{
- /* If the OS doesn't support futimes(3) then we have to pretend it
- * always returns failure */
- errno = EINVAL;
- return -1;
-}
-#endif
-
-/*
- * The guts of setstat and fsetstat, macroised so that they can call
- * fchown(fd,...) or chown(path,...) depending on parameters.
- */
-#define SETSTAT_GUTS(api_prefix, api_arg, attrs, success) do \
- { \
- if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) \
- if (api_prefix(truncate)(api_arg, attrs.size) < 0) \
- success = false; \
- if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) \
- if (api_prefix(chown)(api_arg, attrs.uid, attrs.gid) < 0) \
- success = false; \
- if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) \
- if (api_prefix(chmod)(api_arg, attrs.permissions) < 0) \
- success = false; \
- if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) { \
- struct timeval tv[2]; \
- tv[0].tv_sec = attrs.atime; \
- tv[1].tv_sec = attrs.mtime; \
- tv[0].tv_usec = tv[1].tv_usec = 0; \
- if (api_prefix(utimes)(api_arg, tv) < 0) \
- success = false; \
- } \
- } while (0)
-
-#define PATH_PREFIX(func) func
-#define FD_PREFIX(func) f ## func
-
-static void uss_setstat(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen path, struct fxp_attrs attrs)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
-
- char *pathstr = mkstr(path);
- bool success = true;
- SETSTAT_GUTS(PATH_PREFIX, pathstr, attrs, success);
- free(pathstr);
-
- if (!success) {
- uss_error(uss, reply);
- } else {
- fxp_reply_ok(reply);
- }
-}
-
-static void uss_fsetstat(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen handle, struct fxp_attrs attrs)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
- int fd;
-
- if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
- return;
-
- bool success = true;
- SETSTAT_GUTS(FD_PREFIX, fd, attrs, success);
-
- if (!success) {
- uss_error(uss, reply);
- } else {
- fxp_reply_ok(reply);
- }
-}
-
-static void uss_read(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen handle, uint64_t offset, unsigned length)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
- int fd;
- char *buf;
-
- if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
- return;
-
- if ((buf = malloc(length)) == NULL) {
- /* A rare case in which I bother to check malloc failure,
- * because in this case we can localise the problem easily by
- * turning it into a failure response from this one sftp
- * request */
- fxp_reply_error(reply, SSH_FX_FAILURE,
- "Out of memory for read buffer");
- return;
- }
-
- char *p = buf;
-
- int status = lseek(fd, offset, SEEK_SET);
- if (status >= 0 || errno == ESPIPE) {
- bool seekable = (status >= 0);
- while (length > 0) {
- status = read(fd, p, length);
- if (status <= 0)
- break;
-
- unsigned bytes_read = status;
- assert(bytes_read <= length);
- length -= bytes_read;
- p += bytes_read;
-
- if (!seekable) {
- /*
- * If the seek failed because the file is fundamentally
- * not a seekable kind of thing, abandon this loop after
- * one attempt, i.e. we just read whatever we could get
- * and we don't mind returning a short buffer.
- */
- }
- }
- }
-
- if (status < 0) {
- uss_error(uss, reply);
- } else if (p == buf) {
- fxp_reply_error(reply, SSH_FX_EOF, "End of file");
- } else {
- fxp_reply_data(reply, make_ptrlen(buf, p - buf));
- }
-
- free(buf);
-}
-
-static void uss_write(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen handle, uint64_t offset, ptrlen data)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
- int fd;
-
- if ((fd = uss_lookup_fd(uss, reply, handle)) < 0)
- return;
-
- const char *p = data.ptr;
- unsigned length = data.len;
-
- int status = lseek(fd, offset, SEEK_SET);
- if (status >= 0 || errno == ESPIPE) {
-
- while (length > 0) {
- status = write(fd, p, length);
- assert(status != 0);
- if (status < 0)
- break;
-
- unsigned bytes_written = status;
- assert(bytes_written <= length);
- length -= bytes_written;
- p += bytes_written;
- }
- }
-
- if (status < 0) {
- uss_error(uss, reply);
- } else {
- fxp_reply_ok(reply);
- }
-}
-
-static void uss_readdir(SftpServer *srv, SftpReplyBuilder *reply,
- ptrlen handle, int max_entries, bool omit_longname)
-{
- UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv);
- struct dirent *de;
- struct uss_dirhandle *udh;
-
- if ((udh = uss_lookup_dirhandle(uss, reply, handle)) == NULL)
- return;
-
- errno = 0;
- de = readdir(udh->dp);
- if (!de) {
- if (errno == 0) {
- fxp_reply_error(reply, SSH_FX_EOF, "End of directory");
- } else {
- uss_error(uss, reply);
- }
- } else {
- ptrlen longname = PTRLEN_LITERAL("");
- char *longnamebuf = NULL;
- struct fxp_attrs attrs = no_attrs;
-
-#if defined HAVE_FSTATAT && defined HAVE_DIRFD
- struct stat st;
- if (!fstatat(dirfd(udh->dp), de->d_name, &st, AT_SYMLINK_NOFOLLOW)) {
- char perms[11], *uidbuf = NULL, *gidbuf = NULL;
- struct passwd *pwd;
- struct group *grp;
- const char *user, *group;
- struct tm tm;
-
- attrs = uss_translate_struct_stat(&st);
-
- if (!omit_longname) {
-
- strcpy(perms, "----------");
- switch (st.st_mode & S_IFMT) {
- case S_IFBLK: perms[0] = 'b'; break;
- case S_IFCHR: perms[0] = 'c'; break;
- case S_IFDIR: perms[0] = 'd'; break;
- case S_IFIFO: perms[0] = 'p'; break;
- case S_IFLNK: perms[0] = 'l'; break;
- case S_IFSOCK: perms[0] = 's'; break;
- }
- if (st.st_mode & S_IRUSR)
- perms[1] = 'r';
- if (st.st_mode & S_IWUSR)
- perms[2] = 'w';
- if (st.st_mode & S_IXUSR)
- perms[3] = (st.st_mode & S_ISUID ? 's' : 'x');
- else
- perms[3] = (st.st_mode & S_ISUID ? 'S' : '-');
- if (st.st_mode & S_IRGRP)
- perms[4] = 'r';
- if (st.st_mode & S_IWGRP)
- perms[5] = 'w';
- if (st.st_mode & S_IXGRP)
- perms[6] = (st.st_mode & S_ISGID ? 's' : 'x');
- else
- perms[6] = (st.st_mode & S_ISGID ? 'S' : '-');
- if (st.st_mode & S_IROTH)
- perms[7] = 'r';
- if (st.st_mode & S_IWOTH)
- perms[8] = 'w';
- if (st.st_mode & S_IXOTH)
- perms[9] = 'x';
-
- if ((pwd = getpwuid(st.st_uid)) != NULL)
- user = pwd->pw_name;
- else
- user = uidbuf = dupprintf("%u", (unsigned)st.st_uid);
- if ((grp = getgrgid(st.st_gid)) != NULL)
- group = grp->gr_name;
- else
- group = gidbuf = dupprintf("%u", (unsigned)st.st_gid);
-
- tm = *localtime(&st.st_mtime);
-
- longnamebuf = dupprintf(
- "%s %3u %-8s %-8s %8"PRIuMAX" %.3s %2d %02d:%02d %s",
- perms, (unsigned)st.st_nlink, user, group,
- (uintmax_t)st.st_size,
- (&"JanFebMarAprMayJunJulAugSepOctNovDec"[3*tm.tm_mon]),
- tm.tm_mday, tm.tm_hour, tm.tm_min, de->d_name);
- longname = ptrlen_from_asciz(longnamebuf);
-
- sfree(uidbuf);
- sfree(gidbuf);
- }
- }
-#endif
-
- /* FIXME: be able to return more than one, in which case we
- * must also check max_entries */
- fxp_reply_name_count(reply, 1);
- fxp_reply_full_name(reply, ptrlen_from_asciz(de->d_name),
- longname, attrs);
-
- sfree(longnamebuf);
- }
-}
-
-const SftpServerVtable unix_live_sftpserver_vt = {
- .new = uss_new,
- .free = uss_free,
- .realpath = uss_realpath,
- .open = uss_open,
- .opendir = uss_opendir,
- .close = uss_close,
- .mkdir = uss_mkdir,
- .rmdir = uss_rmdir,
- .remove = uss_remove,
- .rename = uss_rename,
- .stat = uss_stat,
- .fstat = uss_fstat,
- .setstat = uss_setstat,
- .fsetstat = uss_fsetstat,
- .read = uss_read,
- .write = uss_write,
- .readdir = uss_readdir,
-};
diff --git a/unix/uxshare.c b/unix/uxshare.c
deleted file mode 100644
index f1ef2019..00000000
--- a/unix/uxshare.c
+++ /dev/null
@@ -1,370 +0,0 @@
-/*
- * Unix implementation of SSH connection-sharing IPC setup.
- */
-
-#include <stdio.h>
-#include <assert.h>
-#include <errno.h>
-#include <limits.h>
-
-#include <unistd.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/file.h>
-
-#include "tree234.h"
-#include "putty.h"
-#include "network.h"
-#include "proxy.h"
-#include "ssh.h"
-
-#define CONNSHARE_SOCKETDIR_PREFIX "/tmp/putty-connshare"
-#define SALT_FILENAME "salt"
-#define SALT_SIZE 64
-#ifndef PIPE_BUF
-#define PIPE_BUF _POSIX_PIPE_BUF
-#endif
-
-static char *make_parentdir_name(void)
-{
- char *username, *parent;
-
- username = get_username();
- parent = dupprintf("%s.%s", CONNSHARE_SOCKETDIR_PREFIX, username);
- sfree(username);
- assert(*parent == '/');
-
- return parent;
-}
-
-static char *make_dirname(const char *pi_name, char **logtext)
-{
- char *name, *parentdirname, *dirname, *err;
-
- /*
- * First, create the top-level directory for all shared PuTTY
- * connections owned by this user.
- */
- parentdirname = make_parentdir_name();
- if ((err = make_dir_and_check_ours(parentdirname)) != NULL) {
- *logtext = err;
- sfree(parentdirname);
- return NULL;
- }
-
- /*
- * Transform the platform-independent version of the connection
- * identifier into the name we'll actually use for the directory
- * containing the Unix socket.
- *
- * We do this by hashing the identifier with some user-specific
- * secret information, to avoid the privacy leak of having
- * "user@host" strings show up in 'netstat -x'. (Irritatingly, the
- * full pathname of a Unix-domain socket _does_ show up in the
- * 'netstat -x' output, at least on Linux, even if that socket is
- * in a directory not readable to the user running netstat. You'd
- * think putting things inside an 0700 directory would hide their
- * names from other users, but no.)
- *
- * The secret information we use to salt the hash lives in a file
- * inside the top-level directory we just created, so we must
- * first create that file (with some fresh random data in it) if
- * it's not already been done by a previous PuTTY.
- */
- {
- unsigned char saltbuf[SALT_SIZE];
- char *saltname;
- int saltfd, i, ret;
-
- saltname = dupprintf("%s/%s", parentdirname, SALT_FILENAME);
- saltfd = open(saltname, O_RDONLY);
- if (saltfd < 0) {
- char *tmpname;
- int pid;
-
- if (errno != ENOENT) {
- *logtext = dupprintf("%s: open: %s", saltname,
- strerror(errno));
- sfree(saltname);
- sfree(parentdirname);
- return NULL;
- }
-
- /*
- * The salt file doesn't already exist, so try to create
- * it. Another process may be attempting the same thing
- * simultaneously, so we must do this carefully: we write
- * a salt file under a different name, then hard-link it
- * into place, which guarantees that we won't change the
- * contents of an existing salt file.
- */
- pid = getpid();
- for (i = 0;; i++) {
- tmpname = dupprintf("%s/%s.tmp.%d.%d",
- parentdirname, SALT_FILENAME, pid, i);
- saltfd = open(tmpname, O_WRONLY | O_EXCL | O_CREAT, 0400);
- if (saltfd >= 0)
- break;
- if (errno != EEXIST) {
- *logtext = dupprintf("%s: open: %s", tmpname,
- strerror(errno));
- sfree(tmpname);
- sfree(saltname);
- sfree(parentdirname);
- return NULL;
- }
- sfree(tmpname); /* go round and try again with i+1 */
- }
- /*
- * Invent some random data.
- */
- random_read(saltbuf, SALT_SIZE);
- ret = write(saltfd, saltbuf, SALT_SIZE);
- /* POSIX atomicity guarantee: because we wrote less than
- * PIPE_BUF bytes, the write either completed in full or
- * failed. */
- assert(SALT_SIZE < PIPE_BUF);
- assert(ret < 0 || ret == SALT_SIZE);
- if (ret < 0) {
- close(saltfd);
- *logtext = dupprintf("%s: write: %s", tmpname,
- strerror(errno));
- sfree(tmpname);
- sfree(saltname);
- sfree(parentdirname);
- return NULL;
- }
- if (close(saltfd) < 0) {
- *logtext = dupprintf("%s: close: %s", tmpname,
- strerror(errno));
- sfree(tmpname);
- sfree(saltname);
- sfree(parentdirname);
- return NULL;
- }
-
- /*
- * Now attempt to hard-link our temp file into place. We
- * tolerate EEXIST as an outcome, because that just means
- * another PuTTY got their attempt in before we did (and
- * we only care that there is a valid salt file we can
- * agree on, no matter who created it).
- */
- if (link(tmpname, saltname) < 0 && errno != EEXIST) {
- *logtext = dupprintf("%s: link: %s", saltname,
- strerror(errno));
- sfree(tmpname);
- sfree(saltname);
- sfree(parentdirname);
- return NULL;
- }
-
- /*
- * Whether that succeeded or not, get rid of our temp file.
- */
- if (unlink(tmpname) < 0) {
- *logtext = dupprintf("%s: unlink: %s", tmpname,
- strerror(errno));
- sfree(tmpname);
- sfree(saltname);
- sfree(parentdirname);
- return NULL;
- }
-
- /*
- * And now we've arranged for there to be a salt file, so
- * we can try to open it for reading again and this time
- * expect it to work.
- */
- sfree(tmpname);
-
- saltfd = open(saltname, O_RDONLY);
- if (saltfd < 0) {
- *logtext = dupprintf("%s: open: %s", saltname,
- strerror(errno));
- sfree(saltname);
- sfree(parentdirname);
- return NULL;
- }
- }
-
- for (i = 0; i < SALT_SIZE; i++) {
- ret = read(saltfd, saltbuf, SALT_SIZE);
- if (ret <= 0) {
- close(saltfd);
- *logtext = dupprintf("%s: read: %s", saltname,
- ret == 0 ? "unexpected EOF" :
- strerror(errno));
- sfree(saltname);
- sfree(parentdirname);
- return NULL;
- }
- assert(0 < ret && ret <= SALT_SIZE - i);
- i += ret;
- }
-
- close(saltfd);
- sfree(saltname);
-
- /*
- * Now we've got our salt, hash it with the connection
- * identifier to produce our actual socket name.
- */
- {
- unsigned char digest[32];
- char retbuf[65];
-
- ssh_hash *h = ssh_hash_new(&ssh_sha256);
- put_string(h, saltbuf, SALT_SIZE);
- put_stringz(h, pi_name);
- ssh_hash_final(h, digest);
-
- /*
- * And make it printable.
- */
- for (i = 0; i < 32; i++) {
- sprintf(retbuf + 2*i, "%02x", digest[i]);
- /* the last of those will also write the trailing NUL */
- }
-
- name = dupstr(retbuf);
- }
-
- smemclr(saltbuf, sizeof(saltbuf));
- }
-
- dirname = dupprintf("%s/%s", parentdirname, name);
- sfree(parentdirname);
- sfree(name);
-
- return dirname;
-}
-
-int platform_ssh_share(const char *pi_name, Conf *conf,
- Plug *downplug, Plug *upplug, Socket **sock,
- char **logtext, char **ds_err, char **us_err,
- bool can_upstream, bool can_downstream)
-{
- char *dirname, *lockname, *sockname, *err;
- int lockfd;
- Socket *retsock;
-
- /*
- * Sort out what we're going to call the directory in which we
- * keep the socket. This has the side effect of potentially
- * creating its top-level containing dir and/or the salt file
- * within that, if they don't already exist.
- */
- dirname = make_dirname(pi_name, logtext);
- if (!dirname) {
- return SHARE_NONE;
- }
-
- /*
- * Now make sure the subdirectory exists.
- */
- if ((err = make_dir_and_check_ours(dirname)) != NULL) {
- *logtext = err;
- sfree(dirname);
- return SHARE_NONE;
- }
-
- /*
- * Acquire a lock on a file in that directory.
- */
- lockname = dupcat(dirname, "/lock");
- lockfd = open(lockname, O_CREAT | O_RDWR | O_TRUNC, 0600);
- if (lockfd < 0) {
- *logtext = dupprintf("%s: open: %s", lockname, strerror(errno));
- sfree(dirname);
- sfree(lockname);
- return SHARE_NONE;
- }
- if (flock(lockfd, LOCK_EX) < 0) {
- *logtext = dupprintf("%s: flock(LOCK_EX): %s",
- lockname, strerror(errno));
- sfree(dirname);
- sfree(lockname);
- close(lockfd);
- return SHARE_NONE;
- }
-
- sockname = dupprintf("%s/socket", dirname);
-
- *logtext = NULL;
-
- if (can_downstream) {
- retsock = new_connection(unix_sock_addr(sockname),
- "", 0, false, true, false, false,
- downplug, conf);
- if (sk_socket_error(retsock) == NULL) {
- sfree(*logtext);
- *logtext = sockname;
- *sock = retsock;
- sfree(dirname);
- sfree(lockname);
- close(lockfd);
- return SHARE_DOWNSTREAM;
- }
- sfree(*ds_err);
- *ds_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
- sk_close(retsock);
- }
-
- if (can_upstream) {
- retsock = new_unix_listener(unix_sock_addr(sockname), upplug);
- if (sk_socket_error(retsock) == NULL) {
- sfree(*logtext);
- *logtext = sockname;
- *sock = retsock;
- sfree(dirname);
- sfree(lockname);
- close(lockfd);
- return SHARE_UPSTREAM;
- }
- sfree(*us_err);
- *us_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
- sk_close(retsock);
- }
-
- /* One of the above clauses ought to have happened. */
- assert(*logtext || *ds_err || *us_err);
-
- sfree(dirname);
- sfree(lockname);
- sfree(sockname);
- close(lockfd);
- return SHARE_NONE;
-}
-
-void platform_ssh_share_cleanup(const char *name)
-{
- char *dirname, *filename, *logtext;
-
- dirname = make_dirname(name, &logtext);
- if (!dirname) {
- sfree(logtext); /* we can't do much with this */
- return;
- }
-
- filename = dupcat(dirname, "/socket");
- remove(filename);
- sfree(filename);
-
- filename = dupcat(dirname, "/lock");
- remove(filename);
- sfree(filename);
-
- rmdir(dirname);
-
- /*
- * We deliberately _don't_ clean up the parent directory
- * /tmp/putty-connshare.<username>, because if we leave it around
- * then it reduces the ability for other users to be a nuisance by
- * putting their own directory in the way of it. Also, the salt
- * file in it can be reused.
- */
-
- sfree(dirname);
-}
diff --git a/unix/uxsignal.c b/unix/uxsignal.c
deleted file mode 100644
index d75cce43..00000000
--- a/unix/uxsignal.c
+++ /dev/null
@@ -1,47 +0,0 @@
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "defs.h"
-
-/*
- * Calling signal() is non-portable, as it varies in meaning
- * between platforms and depending on feature macros, and has
- * stupid semantics at least some of the time.
- *
- * This function provides the same interface as the libc function,
- * but provides consistent semantics. It assumes POSIX semantics
- * for sigaction() (so you might need to do some more work if you
- * port to something ancient like SunOS 4)
- */
-void (*putty_signal(int sig, void (*func)(int)))(int) {
- struct sigaction sa;
- struct sigaction old;
-
- sa.sa_handler = func;
- if(sigemptyset(&sa.sa_mask) < 0)
- return SIG_ERR;
- sa.sa_flags = SA_RESTART;
- if(sigaction(sig, &sa, &old) < 0)
- return SIG_ERR;
- return old.sa_handler;
-}
-
-void block_signal(int sig, bool block_it)
-{
- sigset_t ss;
-
- sigemptyset(&ss);
- sigaddset(&ss, sig);
- if(sigprocmask(block_it ? SIG_BLOCK : SIG_UNBLOCK, &ss, 0) < 0) {
- perror("sigprocmask");
- exit(1);
- }
-}
-
-/*
-Local Variables:
-c-basic-offset:4
-comment-column:40
-End:
-*/
diff --git a/unix/uxsocks.c b/unix/uxsocks.c
deleted file mode 100644
index 91613afd..00000000
--- a/unix/uxsocks.c
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Main program for Unix psocks.
- */
-
-#include <string.h>
-#include <errno.h>
-
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <signal.h>
-#include <fcntl.h>
-#include <unistd.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "psocks.h"
-
-const bool buildinfo_gtk_relevant = false;
-
-typedef struct PsocksDataSinkPopen {
- stdio_sink sink[2];
- PsocksDataSink pds;
-} PsocksDataSinkPopen;
-
-static void popen_free(PsocksDataSink *pds)
-{
- PsocksDataSinkPopen *pdsp = container_of(pds, PsocksDataSinkPopen, pds);
- for (size_t i = 0; i < 2; i++)
- pclose(pdsp->sink[i].fp);
- sfree(pdsp);
-}
-
-static PsocksDataSink *open_pipes(
- const char *cmd, const char *const *direction_args,
- const char *index_arg, char **err)
-{
- FILE *fp[2];
- char *errmsg = NULL;
-
- for (size_t i = 0; i < 2; i++) {
- /* No escaping needed: the provided command is already
- * shell-quoted, and our extra arguments are simple */
- char *command = dupprintf("%s %s %s", cmd,
- direction_args[i], index_arg);
-
- fp[i] = popen(command, "w");
- sfree(command);
-
- if (!fp[i]) {
- if (!errmsg)
- errmsg = dupprintf("%s", strerror(errno));
- }
- }
-
- if (errmsg) {
- for (size_t i = 0; i < 2; i++)
- if (fp[i])
- pclose(fp[i]);
- *err = errmsg;
- return NULL;
- }
-
- PsocksDataSinkPopen *pdsp = snew(PsocksDataSinkPopen);
-
- for (size_t i = 0; i < 2; i++) {
- setvbuf(fp[i], NULL, _IONBF, 0);
- stdio_sink_init(&pdsp->sink[i], fp[i]);
- pdsp->pds.s[i] = BinarySink_UPCAST(&pdsp->sink[i]);
- }
-
- pdsp->pds.free = popen_free;
-
- return &pdsp->pds;
-}
-
-static int signalpipe[2] = { -1, -1 };
-static void sigchld(int signum)
-{
- if (write(signalpipe[1], "x", 1) <= 0)
- /* not much we can do about it */;
-}
-
-static pid_t subcommand_pid = -1;
-
-static bool still_running = true;
-
-static void start_subcommand(strbuf *args)
-{
- pid_t pid;
-
- /*
- * Set up the pipe we'll use to tell us about SIGCHLD.
- */
- if (pipe(signalpipe) < 0) {
- perror("pipe");
- exit(1);
- }
- putty_signal(SIGCHLD, sigchld);
-
- /*
- * Make an array of argument pointers that execvp will like.
- */
- size_t nargs = 0;
- for (size_t i = 0; i < args->len; i++)
- if (args->s[i] == '\0')
- nargs++;
-
- char **exec_args = snewn(nargs + 1, char *);
- char *p = args->s;
- for (size_t a = 0; a < nargs; a++) {
- exec_args[a] = p;
- size_t len = strlen(p);
- assert(len < args->len - (p - args->s));
- p += 1 + len;
- }
- exec_args[nargs] = NULL;
-
- pid = fork();
- if (pid < 0) {
- perror("fork");
- exit(1);
- } else if (pid == 0) {
- execvp(exec_args[0], exec_args);
- perror("exec");
- _exit(127);
- } else {
- subcommand_pid = pid;
- sfree(exec_args);
- }
-}
-
-static const PsocksPlatform platform = {
- open_pipes,
- start_subcommand,
-};
-
-static bool psocks_pw_setup(void *ctx, pollwrapper *pw)
-{
- if (signalpipe[0] >= 0)
- pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R);
- return true;
-}
-
-static void psocks_pw_check(void *ctx, pollwrapper *pw)
-{
- if (signalpipe[0] >= 0 &&
- pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) {
- while (true) {
- int status;
- pid_t pid = waitpid(-1, &status, WNOHANG);
- if (pid <= 0)
- break;
- if (pid == subcommand_pid)
- still_running = false;
- }
- }
-}
-
-static bool psocks_continue(void *ctx, bool found_any_fd,
- bool ran_any_callback)
-{
- return still_running;
-}
-
-typedef bool (*cliloop_continue_t)(void *ctx, bool found_any_fd,
- bool ran_any_callback);
-
-int main(int argc, char **argv)
-{
- psocks_state *ps = psocks_new(&platform);
- psocks_cmdline(ps, argc, argv);
-
- sk_init();
- uxsel_init();
- psocks_start(ps);
-
- cli_main_loop(psocks_pw_setup, psocks_pw_check, psocks_continue, NULL);
-}
diff --git a/unix/uxstore.c b/unix/uxstore.c
deleted file mode 100644
index 9db713d1..00000000
--- a/unix/uxstore.c
+++ /dev/null
@@ -1,822 +0,0 @@
-/*
- * uxstore.c: Unix-specific implementation of the interface defined
- * in storage.h.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <errno.h>
-#include <ctype.h>
-#include <limits.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <dirent.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <pwd.h>
-#include "putty.h"
-#include "storage.h"
-#include "tree234.h"
-
-#ifdef PATH_MAX
-#define FNLEN PATH_MAX
-#else
-#define FNLEN 1024 /* XXX */
-#endif
-
-enum {
- INDEX_DIR, INDEX_HOSTKEYS, INDEX_HOSTKEYS_TMP, INDEX_RANDSEED,
- INDEX_SESSIONDIR, INDEX_SESSION,
-};
-
-static const char hex[16] = "0123456789ABCDEF";
-
-static void make_session_filename(const char *in, strbuf *out)
-{
- if (!in || !*in)
- in = "Default Settings";
-
- while (*in) {
- /*
- * There are remarkably few punctuation characters that
- * aren't shell-special in some way or likely to be used as
- * separators in some file format or another! Hence we use
- * opt-in for safe characters rather than opt-out for
- * specific unsafe ones...
- */
- if (*in!='+' && *in!='-' && *in!='.' && *in!='@' && *in!='_' &&
- !(*in >= '0' && *in <= '9') &&
- !(*in >= 'A' && *in <= 'Z') &&
- !(*in >= 'a' && *in <= 'z')) {
- put_byte(out, '%');
- put_byte(out, hex[((unsigned char) *in) >> 4]);
- put_byte(out, hex[((unsigned char) *in) & 15]);
- } else
- put_byte(out, *in);
- in++;
- }
-}
-
-static void decode_session_filename(const char *in, strbuf *out)
-{
- while (*in) {
- if (*in == '%' && in[1] && in[2]) {
- int i, j;
-
- i = in[1] - '0';
- i -= (i > 9 ? 7 : 0);
- j = in[2] - '0';
- j -= (j > 9 ? 7 : 0);
-
- put_byte(out, (i << 4) + j);
- in += 3;
- } else {
- put_byte(out, *in++);
- }
- }
-}
-
-static char *make_filename(int index, const char *subname)
-{
- char *env, *tmp, *ret;
-
- /*
- * Allow override of the PuTTY configuration location, and of
- * specific subparts of it, by means of environment variables.
- */
- if (index == INDEX_DIR) {
- struct passwd *pwd;
- char *xdg_dir, *old_dir, *old_dir2, *old_dir3, *home, *pwd_home;
-
- env = getenv("PUTTYDIR");
- if (env)
- return dupstr(env);
-
- home = getenv("HOME");
- pwd = getpwuid(getuid());
- if (pwd && pwd->pw_dir) {
- pwd_home = pwd->pw_dir;
- } else {
- pwd_home = NULL;
- }
-
- xdg_dir = NULL;
- env = getenv("XDG_CONFIG_HOME");
- if (env && *env) {
- xdg_dir = dupprintf("%s/putty", env);
- }
- if (!xdg_dir) {
- if (home) {
- tmp = home;
- } else if (pwd_home) {
- tmp = pwd_home;
- } else {
- tmp = "";
- }
- xdg_dir = dupprintf("%s/.config/putty", tmp);
- }
- if (xdg_dir && access(xdg_dir, F_OK) == 0) {
- return xdg_dir;
- }
-
- old_dir = old_dir2 = old_dir3 = NULL;
- if (home) {
- old_dir = dupprintf("%s/.putty", home);
- }
- if (pwd_home) {
- old_dir2 = dupprintf("%s/.putty", pwd_home);
- }
- old_dir3 = dupstr("/.putty");
-
- if (old_dir && access(old_dir, F_OK) == 0) {
- ret = old_dir;
- goto out;
- }
- if (old_dir2 && access(old_dir2, F_OK) == 0) {
- ret = old_dir2;
- goto out;
- }
- if (access(old_dir3, F_OK) == 0) {
- ret = old_dir3;
- goto out;
- }
-#ifdef XDG_DEFAULT
- if (xdg_dir) {
- ret = xdg_dir;
- goto out;
- }
-#endif
- ret = old_dir ? old_dir : (old_dir2 ? old_dir2 : old_dir3);
-
- out:
- if (ret != old_dir)
- sfree(old_dir);
- if (ret != old_dir2)
- sfree(old_dir2);
- if (ret != old_dir3)
- sfree(old_dir3);
- if (ret != xdg_dir)
- sfree(xdg_dir);
- return ret;
- }
- if (index == INDEX_SESSIONDIR) {
- env = getenv("PUTTYSESSIONS");
- if (env)
- return dupstr(env);
- tmp = make_filename(INDEX_DIR, NULL);
- ret = dupprintf("%s/sessions", tmp);
- sfree(tmp);
- return ret;
- }
- if (index == INDEX_SESSION) {
- strbuf *sb = strbuf_new();
- tmp = make_filename(INDEX_SESSIONDIR, NULL);
- strbuf_catf(sb, "%s/", tmp);
- sfree(tmp);
- make_session_filename(subname, sb);
- return strbuf_to_str(sb);
- }
- if (index == INDEX_HOSTKEYS) {
- env = getenv("PUTTYSSHHOSTKEYS");
- if (env)
- return dupstr(env);
- tmp = make_filename(INDEX_DIR, NULL);
- ret = dupprintf("%s/sshhostkeys", tmp);
- sfree(tmp);
- return ret;
- }
- if (index == INDEX_HOSTKEYS_TMP) {
- tmp = make_filename(INDEX_HOSTKEYS, NULL);
- ret = dupprintf("%s.tmp", tmp);
- sfree(tmp);
- return ret;
- }
- if (index == INDEX_RANDSEED) {
- env = getenv("PUTTYRANDOMSEED");
- if (env)
- return dupstr(env);
- tmp = make_filename(INDEX_DIR, NULL);
- ret = dupprintf("%s/randomseed", tmp);
- sfree(tmp);
- return ret;
- }
- tmp = make_filename(INDEX_DIR, NULL);
- ret = dupprintf("%s/ERROR", tmp);
- sfree(tmp);
- return ret;
-}
-
-struct settings_w {
- FILE *fp;
-};
-
-settings_w *open_settings_w(const char *sessionname, char **errmsg)
-{
- char *filename, *err;
- FILE *fp;
-
- *errmsg = NULL;
-
- /*
- * Start by making sure the .putty directory and its sessions
- * subdir actually exist.
- */
- filename = make_filename(INDEX_DIR, NULL);
- if ((err = make_dir_path(filename, 0700)) != NULL) {
- *errmsg = dupprintf("Unable to save session: %s", err);
- sfree(err);
- sfree(filename);
- return NULL;
- }
- sfree(filename);
-
- filename = make_filename(INDEX_SESSIONDIR, NULL);
- if ((err = make_dir_path(filename, 0700)) != NULL) {
- *errmsg = dupprintf("Unable to save session: %s", err);
- sfree(err);
- sfree(filename);
- return NULL;
- }
- sfree(filename);
-
- filename = make_filename(INDEX_SESSION, sessionname);
- fp = fopen(filename, "w");
- if (!fp) {
- *errmsg = dupprintf("Unable to save session: open(\"%s\") "
- "returned '%s'", filename, strerror(errno));
- sfree(filename);
- return NULL; /* can't open */
- }
- sfree(filename);
-
- settings_w *toret = snew(settings_w);
- toret->fp = fp;
- return toret;
-}
-
-void write_setting_s(settings_w *handle, const char *key, const char *value)
-{
- fprintf(handle->fp, "%s=%s\n", key, value);
-}
-
-void write_setting_i(settings_w *handle, const char *key, int value)
-{
- fprintf(handle->fp, "%s=%d\n", key, value);
-}
-
-void close_settings_w(settings_w *handle)
-{
- fclose(handle->fp);
- sfree(handle);
-}
-
-/* ----------------------------------------------------------------------
- * System for treating X resources as a fallback source of defaults,
- * after data read from a saved-session disk file.
- *
- * The read_setting_* functions will call get_setting(key) as a
- * fallback if the setting isn't in the file they loaded. That in turn
- * will hand on to x_get_default, which the front end application
- * provides, and which actually reads resources from the X server (if
- * appropriate). In between, there's a tree234 of X-resource shaped
- * settings living locally in this file: the front end can call
- * provide_xrm_string() to insert a setting into this tree (typically
- * in response to an -xrm command line option or similar), and those
- * will override the actual X resources.
- */
-
-struct skeyval {
- const char *key;
- const char *value;
-};
-
-static tree234 *xrmtree = NULL;
-
-static int keycmp(void *av, void *bv)
-{
- struct skeyval *a = (struct skeyval *)av;
- struct skeyval *b = (struct skeyval *)bv;
- return strcmp(a->key, b->key);
-}
-
-void provide_xrm_string(const char *string, const char *progname)
-{
- const char *p, *q;
- char *key;
- struct skeyval *xrms, *ret;
-
- p = q = strchr(string, ':');
- if (!q) {
- fprintf(stderr, "%s: expected a colon in resource string"
- " \"%s\"\n", progname, string);
- return;
- }
- q++;
- while (p > string && p[-1] != '.' && p[-1] != '*')
- p--;
- xrms = snew(struct skeyval);
- key = snewn(q-p, char);
- memcpy(key, p, q-p);
- key[q-p-1] = '\0';
- xrms->key = key;
- while (*q && isspace((unsigned char)*q))
- q++;
- xrms->value = dupstr(q);
-
- if (!xrmtree)
- xrmtree = newtree234(keycmp);
-
- ret = add234(xrmtree, xrms);
- if (ret) {
- /* Override an existing string. */
- del234(xrmtree, ret);
- add234(xrmtree, xrms);
- }
-}
-
-static const char *get_setting(const char *key)
-{
- struct skeyval tmp, *ret;
- tmp.key = key;
- if (xrmtree) {
- ret = find234(xrmtree, &tmp, NULL);
- if (ret)
- return ret->value;
- }
- return x_get_default(key);
-}
-
-/* ----------------------------------------------------------------------
- * Main code for reading settings from a disk file, calling the above
- * get_setting() as a fallback if necessary.
- */
-
-struct settings_r {
- tree234 *t;
-};
-
-settings_r *open_settings_r(const char *sessionname)
-{
- char *filename;
- FILE *fp;
- char *line;
- settings_r *toret;
-
- filename = make_filename(INDEX_SESSION, sessionname);
- fp = fopen(filename, "r");
- sfree(filename);
- if (!fp)
- return NULL; /* can't open */
-
- toret = snew(settings_r);
- toret->t = newtree234(keycmp);
-
- while ( (line = fgetline(fp)) ) {
- char *value = strchr(line, '=');
- struct skeyval *kv;
-
- if (!value) {
- sfree(line);
- continue;
- }
- *value++ = '\0';
- value[strcspn(value, "\r\n")] = '\0'; /* trim trailing NL */
-
- kv = snew(struct skeyval);
- kv->key = dupstr(line);
- kv->value = dupstr(value);
- add234(toret->t, kv);
-
- sfree(line);
- }
-
- fclose(fp);
-
- return toret;
-}
-
-char *read_setting_s(settings_r *handle, const char *key)
-{
- const char *val;
- struct skeyval tmp, *kv;
-
- tmp.key = key;
- if (handle != NULL &&
- (kv = find234(handle->t, &tmp, NULL)) != NULL) {
- val = kv->value;
- assert(val != NULL);
- } else
- val = get_setting(key);
-
- if (!val)
- return NULL;
- else
- return dupstr(val);
-}
-
-int read_setting_i(settings_r *handle, const char *key, int defvalue)
-{
- const char *val;
- struct skeyval tmp, *kv;
-
- tmp.key = key;
- if (handle != NULL &&
- (kv = find234(handle->t, &tmp, NULL)) != NULL) {
- val = kv->value;
- assert(val != NULL);
- } else
- val = get_setting(key);
-
- if (!val)
- return defvalue;
- else
- return atoi(val);
-}
-
-FontSpec *read_setting_fontspec(settings_r *handle, const char *name)
-{
- /*
- * In GTK1-only PuTTY, we used to store font names simply as a
- * valid X font description string (logical or alias), under a
- * bare key such as "Font".
- *
- * In GTK2 PuTTY, we have a prefix system where "client:"
- * indicates a Pango font and "server:" an X one; existing
- * configuration needs to be reinterpreted as having the
- * "server:" prefix, so we change the storage key from the
- * provided name string (e.g. "Font") to a suffixed one
- * ("FontName").
- */
- char *suffname = dupcat(name, "Name");
- char *tmp;
-
- if ((tmp = read_setting_s(handle, suffname)) != NULL) {
- FontSpec *fs = fontspec_new(tmp);
- sfree(suffname);
- sfree(tmp);
- return fs; /* got new-style name */
- }
- sfree(suffname);
-
- /* Fall back to old-style name. */
- tmp = read_setting_s(handle, name);
- if (tmp && *tmp) {
- char *tmp2 = dupcat("server:", tmp);
- FontSpec *fs = fontspec_new(tmp2);
- sfree(tmp2);
- sfree(tmp);
- return fs;
- } else {
- sfree(tmp);
- return NULL;
- }
-}
-Filename *read_setting_filename(settings_r *handle, const char *name)
-{
- char *tmp = read_setting_s(handle, name);
- if (tmp) {
- Filename *ret = filename_from_str(tmp);
- sfree(tmp);
- return ret;
- } else
- return NULL;
-}
-
-void write_setting_fontspec(settings_w *handle, const char *name, FontSpec *fs)
-{
- /*
- * read_setting_fontspec had to handle two cases, but when
- * writing our settings back out we simply always generate the
- * new-style name.
- */
- char *suffname = dupcat(name, "Name");
- write_setting_s(handle, suffname, fs->name);
- sfree(suffname);
-}
-void write_setting_filename(settings_w *handle,
- const char *name, Filename *result)
-{
- write_setting_s(handle, name, result->path);
-}
-
-void close_settings_r(settings_r *handle)
-{
- struct skeyval *kv;
-
- if (!handle)
- return;
-
- while ( (kv = index234(handle->t, 0)) != NULL) {
- del234(handle->t, kv);
- sfree((char *)kv->key);
- sfree((char *)kv->value);
- sfree(kv);
- }
-
- freetree234(handle->t);
- sfree(handle);
-}
-
-void del_settings(const char *sessionname)
-{
- char *filename;
- filename = make_filename(INDEX_SESSION, sessionname);
- unlink(filename);
- sfree(filename);
-}
-
-struct settings_e {
- DIR *dp;
-};
-
-settings_e *enum_settings_start(void)
-{
- DIR *dp;
- char *filename;
-
- filename = make_filename(INDEX_SESSIONDIR, NULL);
- dp = opendir(filename);
- sfree(filename);
-
- settings_e *toret = snew(settings_e);
- toret->dp = dp;
- return toret;
-}
-
-bool enum_settings_next(settings_e *handle, strbuf *out)
-{
- struct dirent *de;
- struct stat st;
- strbuf *fullpath;
-
- if (!handle->dp)
- return NULL;
-
- fullpath = strbuf_new();
-
- char *sessiondir = make_filename(INDEX_SESSIONDIR, NULL);
- put_datapl(fullpath, ptrlen_from_asciz(sessiondir));
- sfree(sessiondir);
- put_byte(fullpath, '/');
-
- size_t baselen = fullpath->len;
-
- while ( (de = readdir(handle->dp)) != NULL ) {
- strbuf_shrink_to(fullpath, baselen);
- put_datapl(fullpath, ptrlen_from_asciz(de->d_name));
-
- if (stat(fullpath->s, &st) < 0 || !S_ISREG(st.st_mode))
- continue; /* try another one */
-
- decode_session_filename(de->d_name, out);
- strbuf_free(fullpath);
- return true;
- }
-
- strbuf_free(fullpath);
- return false;
-}
-
-void enum_settings_finish(settings_e *handle)
-{
- if (handle->dp)
- closedir(handle->dp);
- sfree(handle);
-}
-
-/*
- * Lines in the host keys file are of the form
- *
- * type@port:hostname keydata
- *
- * e.g.
- *
- * rsa@22:foovax.example.org 0x23,0x293487364395345345....2343
- */
-int verify_host_key(const char *hostname, int port,
- const char *keytype, const char *key)
-{
- FILE *fp;
- char *filename;
- char *line;
- int ret;
-
- filename = make_filename(INDEX_HOSTKEYS, NULL);
- fp = fopen(filename, "r");
- sfree(filename);
- if (!fp)
- return 1; /* key does not exist */
-
- ret = 1;
- while ( (line = fgetline(fp)) ) {
- int i;
- char *p = line;
- char porttext[20];
-
- line[strcspn(line, "\n")] = '\0'; /* strip trailing newline */
-
- i = strlen(keytype);
- if (strncmp(p, keytype, i))
- goto done;
- p += i;
-
- if (*p != '@')
- goto done;
- p++;
-
- sprintf(porttext, "%d", port);
- i = strlen(porttext);
- if (strncmp(p, porttext, i))
- goto done;
- p += i;
-
- if (*p != ':')
- goto done;
- p++;
-
- i = strlen(hostname);
- if (strncmp(p, hostname, i))
- goto done;
- p += i;
-
- if (*p != ' ')
- goto done;
- p++;
-
- /*
- * Found the key. Now just work out whether it's the right
- * one or not.
- */
- if (!strcmp(p, key))
- ret = 0; /* key matched OK */
- else
- ret = 2; /* key mismatch */
-
- done:
- sfree(line);
- if (ret != 1)
- break;
- }
-
- fclose(fp);
- return ret;
-}
-
-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)
-{
- FILE *rfp, *wfp;
- char *newtext, *line;
- int headerlen;
- char *filename, *tmpfilename;
-
- /*
- * Open both the old file and a new file.
- */
- tmpfilename = make_filename(INDEX_HOSTKEYS_TMP, NULL);
- wfp = fopen(tmpfilename, "w");
- if (!wfp && errno == ENOENT) {
- char *dir, *errmsg;
-
- dir = make_filename(INDEX_DIR, NULL);
- if ((errmsg = make_dir_path(dir, 0700)) != NULL) {
- nonfatal("Unable to store host key: %s", errmsg);
- sfree(errmsg);
- sfree(dir);
- sfree(tmpfilename);
- return;
- }
- sfree(dir);
-
- wfp = fopen(tmpfilename, "w");
- }
- if (!wfp) {
- nonfatal("Unable to store host key: open(\"%s\") "
- "returned '%s'", tmpfilename, strerror(errno));
- sfree(tmpfilename);
- return;
- }
- filename = make_filename(INDEX_HOSTKEYS, NULL);
- rfp = fopen(filename, "r");
-
- newtext = dupprintf("%s@%d:%s %s\n", keytype, port, hostname, key);
- headerlen = 1 + strcspn(newtext, " "); /* count the space too */
-
- /*
- * Copy all lines from the old file to the new one that _don't_
- * involve the same host key identifier as the one we're adding.
- */
- if (rfp) {
- while ( (line = fgetline(rfp)) ) {
- if (strncmp(line, newtext, headerlen))
- fputs(line, wfp);
- sfree(line);
- }
- fclose(rfp);
- }
-
- /*
- * Now add the new line at the end.
- */
- fputs(newtext, wfp);
-
- fclose(wfp);
-
- if (rename(tmpfilename, filename) < 0) {
- nonfatal("Unable to store host key: rename(\"%s\",\"%s\")"
- " returned '%s'", tmpfilename, filename,
- strerror(errno));
- }
-
- sfree(tmpfilename);
- sfree(filename);
- sfree(newtext);
-}
-
-void read_random_seed(noise_consumer_t consumer)
-{
- int fd;
- char *fname;
-
- fname = make_filename(INDEX_RANDSEED, NULL);
- fd = open(fname, O_RDONLY);
- sfree(fname);
- if (fd >= 0) {
- char buf[512];
- int ret;
- while ( (ret = read(fd, buf, sizeof(buf))) > 0)
- consumer(buf, ret);
- close(fd);
- }
-}
-
-void write_random_seed(void *data, int len)
-{
- int fd;
- char *fname;
-
- fname = make_filename(INDEX_RANDSEED, NULL);
- /*
- * Don't truncate the random seed file if it already exists; if
- * something goes wrong half way through writing it, it would
- * be better to leave the old data there than to leave it empty.
- */
- fd = open(fname, O_CREAT | O_WRONLY, 0600);
- if (fd < 0) {
- if (errno != ENOENT) {
- nonfatal("Unable to write random seed: open(\"%s\") "
- "returned '%s'", fname, strerror(errno));
- sfree(fname);
- return;
- }
- char *dir, *errmsg;
-
- dir = make_filename(INDEX_DIR, NULL);
- if ((errmsg = make_dir_path(dir, 0700)) != NULL) {
- nonfatal("Unable to write random seed: %s", errmsg);
- sfree(errmsg);
- sfree(fname);
- sfree(dir);
- return;
- }
- sfree(dir);
-
- fd = open(fname, O_CREAT | O_WRONLY, 0600);
- if (fd < 0) {
- nonfatal("Unable to write random seed: open(\"%s\") "
- "returned '%s'", fname, strerror(errno));
- sfree(fname);
- return;
- }
- }
-
- while (len > 0) {
- int ret = write(fd, data, len);
- if (ret < 0) {
- nonfatal("Unable to write random seed: write "
- "returned '%s'", strerror(errno));
- break;
- }
- len -= ret;
- data = (char *)data + len;
- }
-
- close(fd);
- sfree(fname);
-}
-
-void cleanup_all(void)
-{
-}
diff --git a/unix/uxucs.c b/unix/uxucs.c
deleted file mode 100644
index c1d76a42..00000000
--- a/unix/uxucs.c
+++ /dev/null
@@ -1,268 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <locale.h>
-#include <limits.h>
-#include <wchar.h>
-
-#include <time.h>
-
-#include "putty.h"
-#include "charset.h"
-#include "terminal.h"
-#include "misc.h"
-
-/*
- * Unix Unicode-handling routines.
- */
-
-bool is_dbcs_leadbyte(int codepage, char byte)
-{
- return false; /* we don't do DBCS */
-}
-
-int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen,
- wchar_t *wcstr, int wclen)
-{
- if (codepage == DEFAULT_CODEPAGE) {
- int n = 0;
- mbstate_t state;
-
- memset(&state, 0, sizeof state);
-
- while (mblen > 0) {
- size_t i = mbrtowc(wcstr+n, mbstr, (size_t)mblen, &state);
- if (i == (size_t)-1 || i == (size_t)-2)
- break;
- n++;
- mbstr += i;
- mblen -= i;
- }
-
- return n;
- } else if (codepage == CS_NONE) {
- int n = 0;
-
- while (mblen > 0) {
- wcstr[n] = 0xD800 | (mbstr[0] & 0xFF);
- n++;
- mbstr++;
- mblen--;
- }
-
- return n;
- } else
- return charset_to_unicode(&mbstr, &mblen, wcstr, wclen, codepage,
- NULL, NULL, 0);
-}
-
-int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen,
- char *mbstr, int mblen, const char *defchr,
- struct unicode_data *ucsdata)
-{
- if (codepage == DEFAULT_CODEPAGE) {
- char output[MB_LEN_MAX];
- mbstate_t state;
- int n = 0;
-
- memset(&state, 0, sizeof state);
-
- while (wclen > 0) {
- size_t i = wcrtomb(output, wcstr[0], &state);
- if (i == (size_t)-1 || i > n - mblen)
- break;
- memcpy(mbstr+n, output, i);
- n += i;
- wcstr++;
- wclen--;
- }
-
- return n;
- } else if (codepage == CS_NONE) {
- int n = 0;
- while (wclen > 0 && n < mblen) {
- if (*wcstr >= 0xD800 && *wcstr < 0xD900)
- mbstr[n++] = (*wcstr & 0xFF);
- else if (defchr)
- mbstr[n++] = *defchr;
- wcstr++;
- wclen--;
- }
- return n;
- } else {
- return charset_from_unicode(&wcstr, &wclen, mbstr, mblen, codepage,
- NULL, defchr?defchr:NULL, defchr?1:0);
- }
-}
-
-/*
- * Return value is true if pterm is to run in direct-to-font mode.
- */
-bool init_ucs(struct unicode_data *ucsdata, char *linecharset,
- bool utf8_override, int font_charset, int vtmode)
-{
- int i;
- bool ret = false;
-
- /*
- * In the platform-independent parts of the code, font_codepage
- * is used only for system DBCS support - which we don't
- * support at all. So we set this to something which will never
- * be used.
- */
- ucsdata->font_codepage = -1;
-
- /*
- * If utf8_override is set and the POSIX locale settings
- * dictate a UTF-8 character set, then just go straight for
- * UTF-8.
- */
- ucsdata->line_codepage = CS_NONE;
- if (utf8_override) {
- const char *s;
- if (((s = getenv("LC_ALL")) && *s) ||
- ((s = getenv("LC_CTYPE")) && *s) ||
- ((s = getenv("LANG")) && *s)) {
- if (strstr(s, "UTF-8"))
- ucsdata->line_codepage = CS_UTF8;
- }
- }
-
- /*
- * Failing that, line_codepage should be decoded from the
- * specification in conf.
- */
- if (ucsdata->line_codepage == CS_NONE)
- ucsdata->line_codepage = decode_codepage(linecharset);
-
- /*
- * If line_codepage is _still_ CS_NONE, we assume we're using
- * the font's own encoding. This has been passed in to us, so
- * we use that. If it's still CS_NONE after _that_ - i.e. the
- * font we were given had an incomprehensible charset - then we
- * fall back to using the D800 page.
- */
- if (ucsdata->line_codepage == CS_NONE)
- ucsdata->line_codepage = font_charset;
-
- if (ucsdata->line_codepage == CS_NONE)
- ret = true;
-
- /*
- * Set up unitab_line, by translating each individual character
- * in the line codepage into Unicode.
- */
- for (i = 0; i < 256; i++) {
- char c[1];
- const char *p;
- wchar_t wc[1];
- int len;
- c[0] = i;
- p = c;
- len = 1;
- if (ucsdata->line_codepage == CS_NONE)
- ucsdata->unitab_line[i] = 0xD800 | i;
- else if (1 == charset_to_unicode(&p, &len, wc, 1,
- ucsdata->line_codepage,
- NULL, L"", 0))
- ucsdata->unitab_line[i] = wc[0];
- else
- ucsdata->unitab_line[i] = 0xFFFD;
- }
-
- /*
- * Set up unitab_xterm. This is the same as unitab_line except
- * in the line-drawing regions, where it follows the Unicode
- * encoding.
- *
- * (Note that the strange X encoding of line-drawing characters
- * in the bottom 32 glyphs of ISO8859-1 fonts is taken care of
- * by the font encoding, which will spot such a font and act as
- * if it were in a variant encoding of ISO8859-1.)
- */
- for (i = 0; i < 256; i++) {
- static const wchar_t unitab_xterm_std[32] = {
- 0x2666, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1,
- 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba,
- 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c,
- 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x0020
- };
- static const wchar_t unitab_xterm_poorman[32] =
- L"*#****o~**+++++-----++++|****L. ";
-
- const wchar_t *ptr;
-
- if (vtmode == VT_POORMAN)
- ptr = unitab_xterm_poorman;
- else
- ptr = unitab_xterm_std;
-
- if (i >= 0x5F && i < 0x7F)
- ucsdata->unitab_xterm[i] = ptr[i & 0x1F];
- else
- ucsdata->unitab_xterm[i] = ucsdata->unitab_line[i];
- }
-
- /*
- * Set up unitab_scoacs. The SCO Alternate Character Set is
- * simply CP437.
- */
- for (i = 0; i < 256; i++) {
- char c[1];
- const char *p;
- wchar_t wc[1];
- int len;
- c[0] = i;
- p = c;
- len = 1;
- if (1 == charset_to_unicode(&p, &len, wc, 1, CS_CP437, NULL, L"", 0))
- ucsdata->unitab_scoacs[i] = wc[0];
- else
- ucsdata->unitab_scoacs[i] = 0xFFFD;
- }
-
- /*
- * Find the control characters in the line codepage. For
- * direct-to-font mode using the D800 hack, we assume 00-1F and
- * 7F are controls, but allow 80-9F through. (It's as good a
- * guess as anything; and my bet is that half the weird fonts
- * used in this way will be IBM or MS code pages anyway.)
- */
- for (i = 0; i < 256; i++) {
- int lineval = ucsdata->unitab_line[i];
- if (lineval < ' ' || (lineval >= 0x7F && lineval < 0xA0) ||
- (lineval >= 0xD800 && lineval < 0xD820) || (lineval == 0xD87F))
- ucsdata->unitab_ctrl[i] = i;
- else
- ucsdata->unitab_ctrl[i] = 0xFF;
- }
-
- return ret;
-}
-
-const char *cp_name(int codepage)
-{
- if (codepage == CS_NONE)
- return "Use font encoding";
- return charset_to_localenc(codepage);
-}
-
-const char *cp_enumerate(int index)
-{
- int charset;
- charset = charset_localenc_nth(index);
- if (charset == CS_NONE) {
- /* "Use font encoding" comes after all the named charsets */
- if (charset_localenc_nth(index-1) != CS_NONE)
- return "Use font encoding";
- return NULL;
- }
- return charset_to_localenc(charset);
-}
-
-int decode_codepage(char *cp_name)
-{
- if (!cp_name || !*cp_name)
- return CS_UTF8;
- return charset_from_localenc(cp_name);
-}
diff --git a/unix/uxutils.c b/unix/uxutils.c
deleted file mode 100644
index 3a04c1be..00000000
--- a/unix/uxutils.c
+++ /dev/null
@@ -1,65 +0,0 @@
-#include "putty.h"
-#include "ssh.h"
-
-#include "uxutils.h"
-
-#if defined __arm__ || defined __aarch64__
-
-bool platform_aes_hw_available(void)
-{
-#if defined HWCAP_AES
- return getauxval(AT_HWCAP) & HWCAP_AES;
-#elif defined HWCAP2_AES
- return getauxval(AT_HWCAP2) & HWCAP2_AES;
-#elif defined __APPLE__
- /* M1 macOS defines no optional sysctl flag indicating presence of
- * the AES extension, which I assume to be because it's always
- * present */
- return true;
-#else
- return false;
-#endif
-}
-
-bool platform_sha256_hw_available(void)
-{
-#if defined HWCAP_SHA2
- return getauxval(AT_HWCAP) & HWCAP_SHA2;
-#elif defined HWCAP2_SHA2
- return getauxval(AT_HWCAP2) & HWCAP2_SHA2;
-#elif defined __APPLE__
- /* Assume always present on M1 macOS, similarly to AES */
- return true;
-#else
- return false;
-#endif
-}
-
-bool platform_sha1_hw_available(void)
-{
-#if defined HWCAP_SHA1
- return getauxval(AT_HWCAP) & HWCAP_SHA1;
-#elif defined HWCAP2_SHA1
- return getauxval(AT_HWCAP2) & HWCAP2_SHA1;
-#elif defined __APPLE__
- /* Assume always present on M1 macOS, similarly to AES */
- return true;
-#else
- return false;
-#endif
-}
-
-bool platform_sha512_hw_available(void)
-{
-#if defined HWCAP_SHA512
- return getauxval(AT_HWCAP) & HWCAP_SHA512;
-#elif defined HWCAP2_SHA512
- return getauxval(AT_HWCAP2) & HWCAP2_SHA512;
-#elif defined __APPLE__
- return test_sysctl_flag("hw.optional.armv8_2_sha512");
-#else
- return false;
-#endif
-}
-
-#endif /* defined __arm__ || defined __aarch64__ */
diff --git a/unix/uxutils.h b/unix/uxutils.h
deleted file mode 100644
index c9acff53..00000000
--- a/unix/uxutils.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * uxutils.h: header included only by uxutils.c.
- *
- * The only reason this is a header file instead of a source file is
- * so that I can define 'static inline' functions which may or may not
- * be used, without provoking a compiler warning when I turn out not
- * to use them in the subsequent source file.
- */
-
-#ifndef PUTTY_UXUTILS_H
-#define PUTTY_UXUTILS_H
-
-#if defined __APPLE__
-#ifdef HAVE_SYS_SYSCTL_H
-#include <sys/sysctl.h>
-#endif
-#endif /* defined __APPLE__ */
-
-#if defined __arm__ || defined __aarch64__
-
-#ifdef HAVE_SYS_TYPES_H
-#include <sys/types.h>
-#endif
-
-#ifdef HAVE_SYS_AUXV_H
-#include <sys/auxv.h>
-#endif
-
-#ifdef HAVE_ASM_HWCAP_H
-#include <asm/hwcap.h>
-#endif
-
-#if defined HAVE_GETAUXVAL
-/* No code needed: getauxval has just the API we want already */
-#elif defined HAVE_ELF_AUX_INFO
-/* Implement the simple getauxval API in terms of FreeBSD elf_aux_info */
-static inline u_long getauxval(int which)
-{
- u_long toret;
- if (elf_aux_info(which, &toret, sizeof(toret)) != 0)
- return 0; /* elf_aux_info didn't work */
- return toret;
-}
-#else
-/* Implement a stub getauxval which returns no capabilities */
-static inline u_long getauxval(int which) { return 0; }
-#endif
-
-#endif /* defined __arm__ || defined __aarch64__ */
-
-#if defined __APPLE__
-static inline bool test_sysctl_flag(const char *flagname)
-{
-#ifdef HAVE_SYSCTLBYNAME
- int value;
- size_t size = sizeof(value);
- return (sysctlbyname(flagname, &value, &size, NULL, 0) == 0 &&
- size == sizeof(value) && value != 0);
-#else /* HAVE_SYSCTLBYNAME */
- return false;
-#endif /* HAVE_SYSCTLBYNAME */
-}
-#endif /* defined __APPLE__ */
-
-#endif /* PUTTY_UXUTILS_H */
diff --git a/unix/window.c b/unix/window.c
new file mode 100644
index 00000000..18cce475
--- /dev/null
+++ b/unix/window.c
@@ -0,0 +1,5639 @@
+/*
+ * window.c: the main code that runs a PuTTY terminal emulator and
+ * backend in a GTK window.
+ */
+
+#define _GNU_SOURCE
+
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <stdio.h>
+#include <time.h>
+#include <errno.h>
+#include <locale.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <gtk/gtk.h>
+#if !GTK_CHECK_VERSION(3,0,0)
+#include <gdk/gdkkeysyms.h>
+#endif
+
+#if GTK_CHECK_VERSION(2,0,0)
+#include <gtk/gtkimmodule.h>
+#endif
+
+#define MAY_REFER_TO_GTK_IN_HEADERS
+
+#include "putty.h"
+#include "terminal.h"
+#include "gtkcompat.h"
+#include "unifont.h"
+#include "gtkmisc.h"
+
+#ifndef NOT_X_WINDOWS
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#endif
+
+#include "x11misc.h"
+
+GdkAtom compound_text_atom, utf8_string_atom;
+static GdkAtom clipboard_atom
+#if GTK_CHECK_VERSION(2,0,0) /* GTK1 will have to fill this in at startup */
+ = GDK_SELECTION_CLIPBOARD
+#endif
+ ;
+
+#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
+/*
+ * Because calling gtk_clipboard_set_with_data triggers a call to the
+ * clipboard_clear function from the last time, we need to arrange a
+ * way to distinguish a real call to clipboard_clear for the _new_
+ * instance of the clipboard data from the leftover call for the
+ * outgoing one. We do this by setting the user data field in our
+ * gtk_clipboard_set_with_data() call, instead of the obvious pointer
+ * to 'inst', to one of these.
+ */
+struct clipboard_data_instance {
+ char *pasteout_data_utf8;
+ int pasteout_data_utf8_len;
+ struct clipboard_state *state;
+ struct clipboard_data_instance *next, *prev;
+};
+#endif
+
+struct clipboard_state {
+ GtkFrontend *inst;
+ int clipboard;
+ GdkAtom atom;
+#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
+ GtkClipboard *gtkclipboard;
+ struct clipboard_data_instance *current_cdi;
+#else
+ char *pasteout_data, *pasteout_data_ctext, *pasteout_data_utf8;
+ int pasteout_data_len, pasteout_data_ctext_len, pasteout_data_utf8_len;
+#endif
+};
+
+typedef struct XpmHolder XpmHolder; /* only used for GTK 1 */
+
+struct GtkFrontend {
+ GtkWidget *window, *area, *sbar;
+ gboolean sbar_visible;
+ gboolean drawing_area_got_size, drawing_area_realised;
+ gboolean drawing_area_setup_needed;
+ bool drawing_area_setup_called;
+ GtkBox *hbox;
+ GtkAdjustment *sbar_adjust;
+ GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2,
+ *restartitem;
+ GtkWidget *sessionsmenu;
+#ifndef NOT_X_WINDOWS
+ Display *disp;
+#endif
+#ifndef NO_BACKING_PIXMAPS
+ /*
+ * Server-side pixmap which we use to cache the terminal window's
+ * contents. When we draw text in the terminal, we draw it to this
+ * pixmap first, and then blit from there to the actual window;
+ * this way, X expose events can be handled with an absolute
+ * minimum of network traffic, by just sending a command to
+ * re-blit an appropriate rectangle from this pixmap.
+ */
+ GdkPixmap *pixmap;
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ /*
+ * If we're drawing using Cairo, we cache the same image on the
+ * client side in a Cairo surface.
+ *
+ * In GTK2+Cairo, this happens _as well_ as having the server-side
+ * pixmap cache above; in GTK3+Cairo, server-side pixmaps are
+ * deprecated, so we _just_ have this client-side cache. In the
+ * latter case that means we have to transmit a big wodge of
+ * bitmap data over the X connection on every expose event; but
+ * GTK3 apparently deliberately provides no way to avoid that
+ * inefficiency, and at least this way we don't _also_ have to
+ * redo any font rendering just because the window was temporarily
+ * covered.
+ */
+ cairo_surface_t *surface;
+#endif
+ int backing_w, backing_h;
+#if GTK_CHECK_VERSION(2,0,0)
+ GtkIMContext *imc;
+#endif
+ unifont *fonts[4]; /* normal, bold, wide, widebold */
+ int xpos, ypos, gravity;
+ bool gotpos;
+ GdkCursor *rawcursor, *textcursor, *blankcursor, *waitcursor, *currcursor;
+ GdkColor cols[OSC4_NCOLOURS]; /* indexed by xterm colour indices */
+#if !GTK_CHECK_VERSION(3,0,0)
+ GdkColormap *colmap;
+#endif
+ bool direct_to_font;
+ struct clipboard_state clipstates[N_CLIPBOARDS];
+#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
+ /* Remember all clipboard_data_instance structures currently
+ * associated with this GtkFrontend, in case they're still around
+ * when it gets destroyed */
+ struct clipboard_data_instance cdi_headtail;
+#endif
+ int clipboard_ctrlshiftins, clipboard_ctrlshiftcv;
+ int font_width, font_height;
+ int width, height, scale;
+ bool ignore_sbar;
+ bool mouseptr_visible;
+ BusyStatus busy_status;
+ int alt_keycode;
+ int alt_digits;
+ char *wintitle;
+ char *icontitle;
+ int master_fd, master_func_id;
+ Ldisc *ldisc;
+ Backend *backend;
+ Terminal *term;
+ cmdline_get_passwd_input_state cmdline_get_passwd_state;
+ LogContext *logctx;
+ bool exited;
+ struct unicode_data ucsdata;
+ Conf *conf;
+ eventlog_stuff *eventlogstuff;
+ guint32 input_event_time; /* Timestamp of the most recent input event. */
+ GtkWidget *dialogs[DIALOG_SLOT_LIMIT];
+#if GTK_CHECK_VERSION(3,4,0)
+ gdouble cumulative_scroll;
+#endif
+ /* Cached things out of conf that we refer to a lot */
+ int bold_style;
+ int window_border;
+ int cursor_type;
+ int drawtype;
+ int meta_mod_mask;
+#ifdef OSX_META_KEY_CONFIG
+ int system_mod_mask;
+#endif
+ bool send_raw_mouse;
+ bool pointer_indicates_raw_mouse;
+ unifont_drawctx uctx;
+#if GTK_CHECK_VERSION(2,0,0)
+ GdkPixbuf *trust_sigil_pb;
+#else
+ GdkPixmap *trust_sigil_pm;
+#endif
+ int trust_sigil_w, trust_sigil_h;
+
+ /*
+ * Not every GDK backend can be relied on 100% to reply to a
+ * resize request in a timely manner. (In X11 it's all
+ * asynchronous and goes via the window manager, and if your
+ * window manager is seriously unwell, you'd rather not have
+ * terminal windows start becoming unusable as a knock-on effect,
+ * since those are just the thing you might need to use for
+ * emergency WM maintenance!)
+ *
+ * So when we ask GTK to resize our terminal window, we also set a
+ * 5-second timer, after which we'll regretfully conclude that a
+ * resize (or ConfigureNotify telling us no resize took place) is
+ * probably not going to happen after all.
+ */
+ bool win_resize_pending, term_resize_notification_required;
+ long win_resize_timeout;
+ #define WIN_RESIZE_TIMEOUT (TICKSPERSEC*5)
+
+ Seat seat;
+ TermWin termwin;
+ LogPolicy logpolicy;
+};
+
+static void cache_conf_values(GtkFrontend *inst)
+{
+ inst->bold_style = conf_get_int(inst->conf, CONF_bold_style);
+ inst->window_border = conf_get_int(inst->conf, CONF_window_border);
+ inst->cursor_type = conf_get_int(inst->conf, CONF_cursor_type);
+#ifdef OSX_META_KEY_CONFIG
+ inst->meta_mod_mask = 0;
+ if (conf_get_bool(inst->conf, CONF_osx_option_meta))
+ inst->meta_mod_mask |= GDK_MOD1_MASK;
+ if (conf_get_bool(inst->conf, CONF_osx_command_meta))
+ inst->meta_mod_mask |= GDK_MOD2_MASK;
+ inst->system_mod_mask = GDK_MOD2_MASK & ~inst->meta_mod_mask;
+#else
+ inst->meta_mod_mask = GDK_MOD1_MASK;
+#endif
+}
+
+static void start_backend(GtkFrontend *inst);
+static void exit_callback(void *vinst);
+static void destroy_inst_connection(GtkFrontend *inst);
+static void delete_inst(GtkFrontend *inst);
+
+static void post_fatal_message_box_toplevel(void *vctx)
+{
+ GtkFrontend *inst = (GtkFrontend *)vctx;
+ gtk_widget_destroy(inst->window);
+}
+
+static void post_fatal_message_box(void *vctx, int result)
+{
+ GtkFrontend *inst = (GtkFrontend *)vctx;
+ unregister_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL);
+ queue_toplevel_callback(post_fatal_message_box_toplevel, inst);
+}
+
+static void common_connfatal_message_box(
+ GtkFrontend *inst, const char *msg, post_dialog_fn_t postfn)
+{
+ char *title = dupcat(appname, " Fatal Error");
+ GtkWidget *dialog = create_message_box(
+ inst->window, title, msg,
+ string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"),
+ false, &buttons_ok, postfn, inst);
+ register_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL, dialog);
+ sfree(title);
+}
+
+void fatal_message_box(GtkFrontend *inst, const char *msg)
+{
+ common_connfatal_message_box(inst, msg, post_fatal_message_box);
+}
+
+static void connection_fatal_callback(void *vctx)
+{
+ GtkFrontend *inst = (GtkFrontend *)vctx;
+ destroy_inst_connection(inst);
+}
+
+static void post_nonfatal_message_box(void *vctx, int result)
+{
+ GtkFrontend *inst = (GtkFrontend *)vctx;
+ unregister_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL);
+}
+
+static void gtk_seat_connection_fatal(Seat *seat, const char *msg)
+{
+ GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
+ if (conf_get_int(inst->conf, CONF_close_on_exit) == FORCE_ON) {
+ fatal_message_box(inst, msg);
+ } else {
+ common_connfatal_message_box(inst, msg, post_nonfatal_message_box);
+ }
+
+ inst->exited = true; /* suppress normal exit handling */
+ queue_toplevel_callback(connection_fatal_callback, inst);
+}
+
+/*
+ * Default settings that are specific to pterm.
+ */
+FontSpec *platform_default_fontspec(const char *name)
+{
+ if (!strcmp(name, "Font"))
+ return fontspec_new(DEFAULT_GTK_FONT);
+ else
+ return fontspec_new("");
+}
+
+Filename *platform_default_filename(const char *name)
+{
+ if (!strcmp(name, "LogFileName"))
+ return filename_from_str("putty.log");
+ else
+ return filename_from_str("");
+}
+
+char *platform_default_s(const char *name)
+{
+ if (!strcmp(name, "SerialLine"))
+ return dupstr("/dev/ttyS0");
+ return NULL;
+}
+
+bool platform_default_b(const char *name, bool def)
+{
+ if (!strcmp(name, "WinNameAlways")) {
+ /* X natively supports icon titles, so use 'em by default */
+ return false;
+ }
+ return def;
+}
+
+int platform_default_i(const char *name, int def)
+{
+ if (!strcmp(name, "CloseOnExit"))
+ return 2; /* maps to FORCE_ON after painful rearrangement :-( */
+ return def;
+}
+
+static char *gtk_seat_get_ttymode(Seat *seat, const char *mode)
+{
+ GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
+ return term_get_ttymode(inst->term, mode);
+}
+
+static size_t gtk_seat_output(Seat *seat, SeatOutputType type,
+ const void *data, size_t len)
+{
+ GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
+ return term_data(inst->term, data, len);
+}
+
+static void gtkwin_unthrottle(TermWin *win, size_t bufsize)
+{
+ GtkFrontend *inst = container_of(win, GtkFrontend, termwin);
+ if (inst->backend)
+ backend_unthrottle(inst->backend, bufsize);
+}
+
+static bool gtk_seat_eof(Seat *seat)
+{
+ /* GtkFrontend *inst = container_of(seat, GtkFrontend, seat); */
+ return true; /* do respond to incoming EOF with outgoing */
+}
+
+static SeatPromptResult gtk_seat_get_userpass_input(Seat *seat, prompts_t *p)
+{
+ GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
+ SeatPromptResult spr;
+ spr = cmdline_get_passwd_input(p, &inst->cmdline_get_passwd_state, true);
+ if (spr.kind == SPRK_INCOMPLETE)
+ spr = term_get_userpass_input(inst->term, p);
+ return spr;
+}
+
+static bool gtk_seat_is_utf8(Seat *seat)
+{
+ GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
+ return inst->ucsdata.line_codepage == CS_UTF8;
+}
+
+static void get_window_pixel_size(GtkFrontend *inst, int *w, int *h)
+{
+ /*
+ * I assume that when the GTK version of this call is available
+ * we should use it. Not sure how it differs from the GDK one,
+ * though.
+ */
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_window_get_size(GTK_WINDOW(inst->window), w, h);
+#else
+ gdk_window_get_size(gtk_widget_get_window(inst->window), w, h);
+#endif
+}
+
+static bool gtk_seat_get_window_pixel_size(Seat *seat, int *w, int *h)
+{
+ GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
+ get_window_pixel_size(inst, w, h);
+ return true;
+}
+
+StripCtrlChars *gtk_seat_stripctrl_new(
+ Seat *seat, BinarySink *bs_out, SeatInteractionContext sic)
+{
+ GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
+ return stripctrl_new_term(bs_out, false, 0, inst->term);
+}
+
+static void gtk_seat_notify_remote_exit(Seat *seat);
+static void gtk_seat_update_specials_menu(Seat *seat);
+static void gtk_seat_set_busy_status(Seat *seat, BusyStatus status);
+static const char *gtk_seat_get_x_display(Seat *seat);
+#ifndef NOT_X_WINDOWS
+static bool gtk_seat_get_windowid(Seat *seat, long *id);
+#endif
+static void gtk_seat_set_trust_status(Seat *seat, bool trusted);
+static bool gtk_seat_can_set_trust_status(Seat *seat);
+static bool gtk_seat_get_cursor_position(Seat *seat, int *x, int *y);
+
+static const SeatVtable gtk_seat_vt = {
+ .output = gtk_seat_output,
+ .eof = gtk_seat_eof,
+ .sent = nullseat_sent,
+ .banner = nullseat_banner_to_stderr,
+ .get_userpass_input = gtk_seat_get_userpass_input,
+ .notify_session_started = nullseat_notify_session_started,
+ .notify_remote_exit = gtk_seat_notify_remote_exit,
+ .notify_remote_disconnect = nullseat_notify_remote_disconnect,
+ .connection_fatal = gtk_seat_connection_fatal,
+ .update_specials_menu = gtk_seat_update_specials_menu,
+ .get_ttymode = gtk_seat_get_ttymode,
+ .set_busy_status = gtk_seat_set_busy_status,
+ .confirm_ssh_host_key = gtk_seat_confirm_ssh_host_key,
+ .confirm_weak_crypto_primitive = gtk_seat_confirm_weak_crypto_primitive,
+ .confirm_weak_cached_hostkey = gtk_seat_confirm_weak_cached_hostkey,
+ .prompt_descriptions = gtk_seat_prompt_descriptions,
+ .is_utf8 = gtk_seat_is_utf8,
+ .echoedit_update = nullseat_echoedit_update,
+ .get_x_display = gtk_seat_get_x_display,
+#ifdef NOT_X_WINDOWS
+ .get_windowid = nullseat_get_windowid,
+#else
+ .get_windowid = gtk_seat_get_windowid,
+#endif
+ .get_window_pixel_size = gtk_seat_get_window_pixel_size,
+ .stripctrl_new = gtk_seat_stripctrl_new,
+ .set_trust_status = gtk_seat_set_trust_status,
+ .can_set_trust_status = gtk_seat_can_set_trust_status,
+ .has_mixed_input_stream = nullseat_has_mixed_input_stream_yes,
+ .verbose = nullseat_verbose_yes,
+ .interactive = nullseat_interactive_yes,
+ .get_cursor_position = gtk_seat_get_cursor_position,
+};
+
+static void gtk_eventlog(LogPolicy *lp, const char *string)
+{
+ GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy);
+ logevent_dlg(inst->eventlogstuff, string);
+}
+
+static int gtk_askappend(LogPolicy *lp, Filename *filename,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy);
+ return gtkdlg_askappend(&inst->seat, filename, callback, ctx);
+}
+
+static void gtk_logging_error(LogPolicy *lp, const char *event)
+{
+ GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy);
+
+ /* Send 'can't open log file' errors to the terminal window.
+ * (Marked as stderr, although terminal.c won't care.) */
+ seat_stderr_pl(&inst->seat, ptrlen_from_asciz(event));
+ seat_stderr_pl(&inst->seat, PTRLEN_LITERAL("\r\n"));
+}
+
+static const LogPolicyVtable gtk_logpolicy_vt = {
+ .eventlog = gtk_eventlog,
+ .askappend = gtk_askappend,
+ .logging_error = gtk_logging_error,
+ .verbose = null_lp_verbose_yes,
+};
+
+/*
+ * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
+ * into a cooked one (SELECT, EXTEND, PASTE).
+ *
+ * In Unix, this is not configurable; the X button arrangement is
+ * rock-solid across all applications, everyone has a three-button
+ * mouse or a means of faking it, and there is no need to switch
+ * buttons around at all.
+ */
+static Mouse_Button translate_button(Mouse_Button button)
+{
+ if (button == MBT_LEFT)
+ return MBT_SELECT;
+ if (button == MBT_MIDDLE)
+ return MBT_PASTE;
+ if (button == MBT_RIGHT)
+ return MBT_EXTEND;
+ return 0; /* shouldn't happen */
+}
+
+/*
+ * Return the top-level GtkWindow associated with a particular
+ * front end instance.
+ */
+GtkWidget *gtk_seat_get_window(Seat *seat)
+{
+ GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
+ return inst->window;
+}
+
+/*
+ * Set and clear a pointer to a dialog box created as a result of the
+ * network code wanting to ask an asynchronous user question (e.g.
+ * 'what about this dodgy host key, then?').
+ */
+void register_dialog(Seat *seat, enum DialogSlot slot, GtkWidget *dialog)
+{
+ GtkFrontend *inst;
+ assert(seat->vt == &gtk_seat_vt);
+ inst = container_of(seat, GtkFrontend, seat);
+ assert(slot < DIALOG_SLOT_LIMIT);
+ assert(!inst->dialogs[slot]);
+ inst->dialogs[slot] = dialog;
+}
+void unregister_dialog(Seat *seat, enum DialogSlot slot)
+{
+ GtkFrontend *inst;
+ assert(seat->vt == &gtk_seat_vt);
+ inst = container_of(seat, GtkFrontend, seat);
+ assert(slot < DIALOG_SLOT_LIMIT);
+ assert(inst->dialogs[slot]);
+ inst->dialogs[slot] = NULL;
+}
+
+/*
+ * Minimise or restore the window in response to a server-side
+ * request.
+ */
+static void gtkwin_set_minimised(TermWin *tw, bool minimised)
+{
+ /*
+ * GTK 1.2 doesn't know how to do this.
+ */
+#if GTK_CHECK_VERSION(2,0,0)
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ if (minimised)
+ gtk_window_iconify(GTK_WINDOW(inst->window));
+ else
+ gtk_window_deiconify(GTK_WINDOW(inst->window));
+#endif
+}
+
+/*
+ * Move the window in response to a server-side request.
+ */
+static void gtkwin_move(TermWin *tw, int x, int y)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ /*
+ * I assume that when the GTK version of this call is available
+ * we should use it. Not sure how it differs from the GDK one,
+ * though.
+ */
+#if GTK_CHECK_VERSION(2,0,0)
+ /* in case we reset this at startup due to a geometry string */
+ gtk_window_set_gravity(GTK_WINDOW(inst->window), GDK_GRAVITY_NORTH_EAST);
+ gtk_window_move(GTK_WINDOW(inst->window), x, y);
+#else
+ gdk_window_move(gtk_widget_get_window(inst->window), x, y);
+#endif
+}
+
+/*
+ * Move the window to the top or bottom of the z-order in response
+ * to a server-side request.
+ */
+static void gtkwin_set_zorder(TermWin *tw, bool top)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ if (top)
+ gdk_window_raise(gtk_widget_get_window(inst->window));
+ else
+ gdk_window_lower(gtk_widget_get_window(inst->window));
+}
+
+/*
+ * Refresh the window in response to a server-side request.
+ */
+static void gtkwin_refresh(TermWin *tw)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ term_invalidate(inst->term);
+}
+
+/*
+ * Maximise or restore the window in response to a server-side
+ * request.
+ */
+static void gtkwin_set_maximised(TermWin *tw, bool maximised)
+{
+ /*
+ * GTK 1.2 doesn't know how to do this.
+ */
+#if GTK_CHECK_VERSION(2,0,0)
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ if (maximised)
+ gtk_window_maximize(GTK_WINDOW(inst->window));
+ else
+ gtk_window_unmaximize(GTK_WINDOW(inst->window));
+#endif
+}
+
+/*
+ * Find out whether a dialog box already exists for this window in a
+ * particular DialogSlot. If it does, uniconify it (if we can) and
+ * raise it, so that the user realises they've already been asked this
+ * question.
+ */
+static bool find_and_raise_dialog(GtkFrontend *inst, enum DialogSlot slot)
+{
+ GtkWidget *dialog = inst->dialogs[slot];
+ if (!dialog)
+ return false;
+
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_window_deiconify(GTK_WINDOW(dialog));
+#endif
+ gdk_window_raise(gtk_widget_get_window(dialog));
+ return true;
+}
+
+static void warn_on_close_callback(void *vctx, int result)
+{
+ GtkFrontend *inst = (GtkFrontend *)vctx;
+ unregister_dialog(&inst->seat, DIALOG_SLOT_WARN_ON_CLOSE);
+ if (result)
+ gtk_widget_destroy(inst->window);
+}
+
+/*
+ * Handle the 'delete window' event (e.g. user clicking the WM close
+ * button). The return value false means the window should close, and
+ * true means it shouldn't.
+ *
+ * (That's counterintuitive, but really, in GTK terms, true means 'I
+ * have done everything necessary to handle this event, so the default
+ * handler need not do anything', i.e. 'suppress default handler',
+ * i.e. 'do not close the window'.)
+ */
+gint delete_window(GtkWidget *widget, GdkEvent *event, GtkFrontend *inst)
+{
+ if (!inst->exited && conf_get_bool(inst->conf, CONF_warn_on_close)) {
+ /*
+ * We're not going to exit right now. We must put up a
+ * warn-on-close dialog, unless one already exists, in which
+ * case we'll just re-emphasise that one.
+ */
+ if (!find_and_raise_dialog(inst, DIALOG_SLOT_WARN_ON_CLOSE)) {
+ char *title = dupcat(appname, " Exit Confirmation");
+ char *msg, *additional = NULL;
+ if (inst->backend && inst->backend->vt->close_warn_text) {
+ additional = inst->backend->vt->close_warn_text(inst->backend);
+ }
+ msg = dupprintf("Are you sure you want to close this session?%s%s",
+ additional ? "\n" : "",
+ additional ? additional : "");
+ GtkWidget *dialog = create_message_box(
+ inst->window, title, msg,
+ string_width("Most of the width of the above text"),
+ false, &buttons_yn, warn_on_close_callback, inst);
+ register_dialog(&inst->seat, DIALOG_SLOT_WARN_ON_CLOSE, dialog);
+ sfree(title);
+ sfree(msg);
+ sfree(additional);
+ }
+ return true;
+ }
+ return false;
+}
+
+#if GTK_CHECK_VERSION(2,0,0)
+static gboolean window_state_event(
+ GtkWidget *widget, GdkEventWindowState *event, gpointer user_data)
+{
+ GtkFrontend *inst = (GtkFrontend *)user_data;
+ term_notify_minimised(
+ inst->term, event->new_window_state & GDK_WINDOW_STATE_ICONIFIED);
+ return false;
+}
+#endif
+
+static void update_mouseptr(GtkFrontend *inst)
+{
+ switch (inst->busy_status) {
+ case BUSY_NOT:
+ if (!inst->mouseptr_visible) {
+ gdk_window_set_cursor(gtk_widget_get_window(inst->area),
+ inst->blankcursor);
+ } else if (inst->pointer_indicates_raw_mouse) {
+ gdk_window_set_cursor(gtk_widget_get_window(inst->area),
+ inst->rawcursor);
+ } else {
+ gdk_window_set_cursor(gtk_widget_get_window(inst->area),
+ inst->textcursor);
+ }
+ break;
+ case BUSY_WAITING: /* XXX can we do better? */
+ case BUSY_CPU:
+ /* We always display these cursors. */
+ gdk_window_set_cursor(gtk_widget_get_window(inst->area),
+ inst->waitcursor);
+ break;
+ default:
+ unreachable("Bad busy_status");
+ }
+}
+
+static void show_mouseptr(GtkFrontend *inst, bool show)
+{
+ if (!conf_get_bool(inst->conf, CONF_hide_mouseptr))
+ show = true;
+ inst->mouseptr_visible = show;
+ update_mouseptr(inst);
+}
+
+static void draw_backing_rect(GtkFrontend *inst);
+
+static void drawing_area_setup(GtkFrontend *inst, int width, int height)
+{
+ int w, h, new_scale;
+
+ /*
+ * See if the terminal size has changed.
+ */
+ w = (width - 2*inst->window_border) / inst->font_width;
+ h = (height - 2*inst->window_border) / inst->font_height;
+ if (w != inst->width || h != inst->height) {
+ /*
+ * Update conf.
+ */
+ inst->width = w;
+ inst->height = h;
+ conf_set_int(inst->conf, CONF_width, inst->width);
+ conf_set_int(inst->conf, CONF_height, inst->height);
+ /*
+ * We must refresh the window's backing image.
+ */
+ inst->drawing_area_setup_needed = true;
+ }
+
+#if GTK_CHECK_VERSION(3,10,0)
+ new_scale = gtk_widget_get_scale_factor(inst->area);
+ if (new_scale != inst->scale)
+ inst->drawing_area_setup_needed = true;
+#else
+ new_scale = 1;
+#endif
+
+ int new_backing_w = width * new_scale;
+ int new_backing_h = height * new_scale;
+
+ if (inst->backing_w != new_backing_w || inst->backing_h != new_backing_h)
+ inst->drawing_area_setup_needed = true;
+
+ /*
+ * GTK will sometimes send us configure events when nothing about
+ * the window size has actually changed. In some situations this
+ * can happen quite often, so it's a worthwhile optimisation to
+ * detect that situation and avoid the expensive reinitialisation
+ * of the backing surface / image, and so on.
+ *
+ * However, we must still communicate to the terminal that we
+ * received a resize event, because sometimes a trivial resize
+ * event (to the same size we already were) is a signal from the
+ * window system that a _nontrivial_ resize we recently asked for
+ * has failed to happen.
+ */
+
+ inst->drawing_area_setup_called = true;
+ if (inst->term)
+ term_size(inst->term, h, w, conf_get_int(inst->conf, CONF_savelines));
+ if (inst->term_resize_notification_required)
+ term_resize_request_completed(inst->term);
+ if (inst->win_resize_pending)
+ inst->win_resize_pending = false;
+
+ if (!inst->drawing_area_setup_needed)
+ return;
+
+ inst->drawing_area_setup_needed = false;
+ inst->scale = new_scale;
+ inst->backing_w = new_backing_w;
+ inst->backing_h = new_backing_h;
+
+#ifndef NO_BACKING_PIXMAPS
+ if (inst->pixmap) {
+ gdk_pixmap_unref(inst->pixmap);
+ inst->pixmap = NULL;
+ }
+
+ inst->pixmap = gdk_pixmap_new(gtk_widget_get_window(inst->area),
+ inst->backing_w, inst->backing_h, -1);
+#endif
+
+#ifdef DRAW_TEXT_CAIRO
+ if (inst->surface) {
+ cairo_surface_destroy(inst->surface);
+ inst->surface = NULL;
+ }
+
+ inst->surface = cairo_image_surface_create(
+ CAIRO_FORMAT_ARGB32, inst->backing_w, inst->backing_h);
+#endif
+
+ draw_backing_rect(inst);
+
+ if (inst->term)
+ term_invalidate(inst->term);
+
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_im_context_set_client_window(
+ inst->imc, gtk_widget_get_window(inst->area));
+#endif
+}
+
+static void drawing_area_setup_simple(GtkFrontend *inst)
+{
+ /*
+ * Wrapper on drawing_area_setup which fetches the width and
+ * height of the drawing area. We go directly to the inner version
+ * in the case where a new size allocation comes in (just in case
+ * GTK hasn't installed it in the normal place yet).
+ */
+#if GTK_CHECK_VERSION(2,0,0)
+ GdkRectangle alloc;
+ gtk_widget_get_allocation(inst->area, &alloc);
+#else
+ GtkAllocation alloc = inst->area->allocation;
+#endif
+ drawing_area_setup(inst, alloc.width, alloc.height);
+}
+
+static void drawing_area_setup_cb(void *vctx)
+{
+ GtkFrontend *inst = (GtkFrontend *)vctx;
+
+ if (!inst->drawing_area_setup_called)
+ drawing_area_setup_simple(inst);
+}
+
+static void area_realised(GtkWidget *widget, GtkFrontend *inst)
+{
+ inst->drawing_area_realised = true;
+ if (inst->drawing_area_realised && inst->drawing_area_got_size &&
+ inst->drawing_area_setup_needed)
+ drawing_area_setup_simple(inst);
+}
+
+static void area_size_allocate(
+ GtkWidget *widget, GdkRectangle *alloc, GtkFrontend *inst)
+{
+ inst->drawing_area_got_size = true;
+ if (inst->drawing_area_realised && inst->drawing_area_got_size)
+ drawing_area_setup(inst, alloc->width, alloc->height);
+}
+
+#if GTK_CHECK_VERSION(3,10,0)
+static void area_check_scale(GtkFrontend *inst)
+{
+ if (!inst->drawing_area_setup_needed &&
+ inst->scale != gtk_widget_get_scale_factor(inst->area)) {
+ drawing_area_setup_simple(inst);
+ if (inst->term) {
+ term_invalidate(inst->term);
+ term_update(inst->term);
+ }
+ }
+}
+#endif
+
+static gboolean window_configured(
+ GtkWidget *widget, GdkEventConfigure *event, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ if (inst->term) {
+ term_notify_window_pos(inst->term, event->x, event->y);
+ term_notify_window_size_pixels(
+ inst->term, event->width, event->height);
+ if (inst->drawing_area_realised && inst->drawing_area_got_size) {
+ inst->drawing_area_setup_called = false;
+ queue_toplevel_callback(drawing_area_setup_cb, inst);
+ }
+ }
+ return false;
+}
+
+#if GTK_CHECK_VERSION(3,10,0)
+static gboolean area_configured(
+ GtkWidget *widget, GdkEventConfigure *event, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ area_check_scale(inst);
+ return false;
+}
+#endif
+
+#ifdef DRAW_TEXT_CAIRO
+static void cairo_setup_draw_ctx(GtkFrontend *inst)
+{
+ cairo_get_matrix(inst->uctx.u.cairo.cr,
+ &inst->uctx.u.cairo.origmatrix);
+ cairo_set_line_width(inst->uctx.u.cairo.cr, 1.0);
+ cairo_set_line_cap(inst->uctx.u.cairo.cr, CAIRO_LINE_CAP_SQUARE);
+ cairo_set_line_join(inst->uctx.u.cairo.cr, CAIRO_LINE_JOIN_MITER);
+ /* This antialiasing setting appears to be ignored for Pango
+ * font rendering but honoured for stroking and filling paths;
+ * I don't quite understand the logic of that, but I won't
+ * complain since it's exactly what I happen to want */
+ cairo_set_antialias(inst->uctx.u.cairo.cr, CAIRO_ANTIALIAS_NONE);
+}
+#endif
+
+#if GTK_CHECK_VERSION(3,0,0)
+static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+
+#if GTK_CHECK_VERSION(3,10,0)
+ /*
+ * This may be the first we hear of the window scale having
+ * changed, in which case we must hastily reconstruct our backing
+ * surface before we copy the wrong one into the newly resized
+ * real window.
+ */
+ area_check_scale(inst);
+#endif
+
+ /*
+ * GTK3 window redraw: we always expect Cairo to be enabled, so
+ * that inst->surface exists, and pixmaps to be disabled, so that
+ * inst->pixmap does not exist. Hence, we just blit from
+ * inst->surface to the window.
+ */
+ if (inst->surface) {
+ GdkRectangle dirtyrect;
+ cairo_surface_t *target_surface;
+ double orig_sx, orig_sy;
+ cairo_matrix_t m;
+
+ /*
+ * Furtle around in the Cairo setup to force the device scale
+ * back to 1, so that when we blit a collection of pixels from
+ * our backing surface into the window, they really are
+ * _pixels_ and not some confusing antialiased slightly-offset
+ * 2x2 rectangle of pixeloids.
+ *
+ * I have no idea whether GTK expects me not to mess with the
+ * device scale in the cairo_surface_t backing its window, so
+ * I carefully put it back when I've finished.
+ *
+ * In some GTK setups, the Cairo context we're given may not
+ * have a zero translation offset in its matrix, in which case
+ * we have to adjust that to compensate for the change of
+ * scale, or else the old translation offset (designed for the
+ * old scale) will be multiplied by the new scale instead and
+ * put everything in the wrong place.
+ */
+ target_surface = cairo_get_target(cr);
+ cairo_get_matrix(cr, &m);
+ cairo_surface_get_device_scale(target_surface, &orig_sx, &orig_sy);
+ cairo_surface_set_device_scale(target_surface, 1.0, 1.0);
+ cairo_translate(cr, m.x0 * (orig_sx - 1.0), m.y0 * (orig_sy - 1.0));
+
+ gdk_cairo_get_clip_rectangle(cr, &dirtyrect);
+
+ cairo_set_source_surface(cr, inst->surface, 0, 0);
+ cairo_rectangle(cr, dirtyrect.x, dirtyrect.y,
+ dirtyrect.width, dirtyrect.height);
+ cairo_fill(cr);
+
+ cairo_surface_set_device_scale(target_surface, orig_sx, orig_sy);
+ }
+
+ return true;
+}
+#else
+gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+
+#ifndef NO_BACKING_PIXMAPS
+ /*
+ * Draw to the exposed part of the window from the server-side
+ * backing pixmap.
+ */
+ if (inst->pixmap) {
+ gdk_draw_pixmap(gtk_widget_get_window(widget),
+ (gtk_widget_get_style(widget)->fg_gc
+ [gtk_widget_get_state(widget)]),
+ inst->pixmap,
+ event->area.x, event->area.y,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height);
+ }
+#else
+ /*
+ * Failing that, draw from the client-side Cairo surface. (We
+ * should never be compiled in a context where we have _neither_
+ * inst->surface nor inst->pixmap.)
+ */
+ if (inst->surface) {
+ cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(widget));
+ cairo_set_source_surface(cr, inst->surface, 0, 0);
+ cairo_rectangle(cr, event->area.x, event->area.y,
+ event->area.width, event->area.height);
+ cairo_fill(cr);
+ cairo_destroy(cr);
+ }
+#endif
+
+ return true;
+}
+#endif
+
+#define KEY_PRESSED(k) \
+ (inst->keystate[(k) / 32] & (1 << ((k) % 32)))
+
+#ifdef KEY_EVENT_DIAGNOSTICS
+char *dup_keyval_name(guint keyval)
+{
+ const char *name = gdk_keyval_name(keyval);
+ if (name)
+ return dupstr(name);
+ else
+ return dupprintf("UNKNOWN[%u]", (unsigned)keyval);
+}
+#endif
+
+static void change_font_size(GtkFrontend *inst, int increment);
+static void key_pressed(GtkFrontend *inst);
+
+/* Subroutine used in key_event */
+static int return_key(GtkFrontend *inst, char *output, bool *special)
+{
+ int end;
+
+ /* Ugly label so we can come here as a fallback from
+ * numeric keypad Enter handling */
+ if (inst->term->cr_lf_return) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Return in cr_lf_return mode, translating as 0d 0a\n");
+#endif
+ output[1] = '\015';
+ output[2] = '\012';
+ end = 3;
+ } else {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Return special case, translating as 0d + special\n");
+#endif
+ output[1] = '\015';
+ end = 2;
+ *special = true;
+ }
+
+ return end;
+}
+
+gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ char output[256];
+ wchar_t ucsoutput[2];
+ int ucsval, start, end, output_charset;
+ bool special, use_ucsoutput;
+ bool force_format_numeric_keypad = false;
+ bool generated_something = false;
+ char num_keypad_key = '\0';
+ const char *event_string = event->string ? event->string : "";
+
+ noise_ultralight(NOISE_SOURCE_KEY, event->keyval);
+
+#ifdef OSX_META_KEY_CONFIG
+ if (event->state & inst->system_mod_mask)
+ return false; /* let GTK process OS X Command key */
+#endif
+
+ /* Remember the timestamp. */
+ inst->input_event_time = event->time;
+
+ /* By default, nothing is generated. */
+ end = start = 0;
+ special = use_ucsoutput = false;
+ output_charset = CS_ISO8859_1;
+
+#ifdef KEY_EVENT_DIAGNOSTICS
+ {
+ char *type_string, *state_string, *keyval_string, *string_string;
+
+ type_string = (event->type == GDK_KEY_PRESS ? dupstr("PRESS") :
+ event->type == GDK_KEY_RELEASE ? dupstr("RELEASE") :
+ dupprintf("UNKNOWN[%d]", (int)event->type));
+
+ {
+ static const struct {
+ int mod_bit;
+ const char *name;
+ } mod_bits[] = {
+ {GDK_SHIFT_MASK, "SHIFT"},
+ {GDK_LOCK_MASK, "LOCK"},
+ {GDK_CONTROL_MASK, "CONTROL"},
+ {GDK_MOD1_MASK, "MOD1"},
+ {GDK_MOD2_MASK, "MOD2"},
+ {GDK_MOD3_MASK, "MOD3"},
+ {GDK_MOD4_MASK, "MOD4"},
+ {GDK_MOD5_MASK, "MOD5"},
+ {GDK_SUPER_MASK, "SUPER"},
+ {GDK_HYPER_MASK, "HYPER"},
+ {GDK_META_MASK, "META"},
+ };
+ int i;
+ int val = event->state;
+
+ state_string = dupstr("");
+
+ for (i = 0; i < lenof(mod_bits); i++) {
+ if (val & mod_bits[i].mod_bit) {
+ char *old = state_string;
+ state_string = dupcat(state_string,
+ state_string[0] ? "|" : "",
+ mod_bits[i].name);
+ sfree(old);
+
+ val &= ~mod_bits[i].mod_bit;
+ }
+ }
+
+ if (val || !state_string[0]) {
+ char *old = state_string;
+ state_string = dupprintf("%s%s%d", state_string,
+ state_string[0] ? "|" : "", val);
+ sfree(old);
+ }
+ }
+
+ keyval_string = dup_keyval_name(event->keyval);
+
+ string_string = dupstr("");
+ {
+ int i;
+ for (i = 0; event_string[i]; i++) {
+ char *old = string_string;
+ string_string = dupprintf("%s%s%02x", string_string,
+ string_string[0] ? " " : "",
+ (unsigned)event_string[i] & 0xFF);
+ sfree(old);
+ }
+ }
+
+ debug("key_event: type=%s keyval=%s state=%s "
+ "hardware_keycode=%d is_modifier=%s string=[%s]\n",
+ type_string, keyval_string, state_string,
+ (int)event->hardware_keycode,
+ event->is_modifier ? "true" : "false",
+ string_string);
+
+ sfree(type_string);
+ sfree(state_string);
+ sfree(keyval_string);
+ sfree(string_string);
+ }
+#endif /* KEY_EVENT_DIAGNOSTICS */
+
+ /*
+ * If Alt is being released after typing an Alt+numberpad
+ * sequence, we should generate the code that was typed.
+ *
+ * Note that we only do this if more than one key was actually
+ * pressed - I don't think Alt+NumPad4 should be ^D or that
+ * Alt+NumPad3 should be ^C, for example. There's no serious
+ * inconvenience in having to type a zero before a single-digit
+ * character code.
+ */
+ if (event->type == GDK_KEY_RELEASE) {
+ if ((event->keyval == GDK_KEY_Meta_L ||
+ event->keyval == GDK_KEY_Meta_R ||
+ event->keyval == GDK_KEY_Alt_L ||
+ event->keyval == GDK_KEY_Alt_R) &&
+ inst->alt_keycode >= 0 && inst->alt_digits > 1) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - modifier release terminates Alt+numberpad input, "
+ "keycode = %d\n", inst->alt_keycode);
+#endif
+ /*
+ * FIXME: we might usefully try to do something clever here
+ * about interpreting the generated key code in a way that's
+ * appropriate to the line code page.
+ */
+ output[0] = inst->alt_keycode;
+ end = 1;
+ goto done;
+ }
+#if GTK_CHECK_VERSION(2,0,0)
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - key release, passing to IM\n");
+#endif
+ if (gtk_im_context_filter_keypress(inst->imc, event)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - key release accepted by IM\n");
+#endif
+ return true;
+ } else {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - key release not accepted by IM\n");
+#endif
+ }
+#endif
+ }
+
+ if (event->type == GDK_KEY_PRESS) {
+ /*
+ * If Alt has just been pressed, we start potentially
+ * accumulating an Alt+numberpad code. We do this by
+ * setting alt_keycode to -1 (nothing yet but plausible).
+ */
+ if ((event->keyval == GDK_KEY_Meta_L ||
+ event->keyval == GDK_KEY_Meta_R ||
+ event->keyval == GDK_KEY_Alt_L ||
+ event->keyval == GDK_KEY_Alt_R)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - modifier press potentially begins Alt+numberpad "
+ "input\n");
+#endif
+ inst->alt_keycode = -1;
+ inst->alt_digits = 0;
+ goto done; /* this generates nothing else */
+ }
+
+ /*
+ * If we're seeing a numberpad key press with Meta down,
+ * consider adding it to alt_keycode if that's sensible.
+ * Anything _else_ with Meta down cancels any possibility
+ * of an ALT keycode: we set alt_keycode to -2.
+ */
+ if ((event->state & inst->meta_mod_mask) && inst->alt_keycode != -2) {
+ int digit = -1;
+ switch (event->keyval) {
+ case GDK_KEY_KP_0: case GDK_KEY_KP_Insert: digit = 0; break;
+ case GDK_KEY_KP_1: case GDK_KEY_KP_End: digit = 1; break;
+ case GDK_KEY_KP_2: case GDK_KEY_KP_Down: digit = 2; break;
+ case GDK_KEY_KP_3: case GDK_KEY_KP_Page_Down: digit = 3; break;
+ case GDK_KEY_KP_4: case GDK_KEY_KP_Left: digit = 4; break;
+ case GDK_KEY_KP_5: case GDK_KEY_KP_Begin: digit = 5; break;
+ case GDK_KEY_KP_6: case GDK_KEY_KP_Right: digit = 6; break;
+ case GDK_KEY_KP_7: case GDK_KEY_KP_Home: digit = 7; break;
+ case GDK_KEY_KP_8: case GDK_KEY_KP_Up: digit = 8; break;
+ case GDK_KEY_KP_9: case GDK_KEY_KP_Page_Up: digit = 9; break;
+ }
+ if (digit < 0)
+ inst->alt_keycode = -2; /* it's invalid */
+ else {
+#if defined(DEBUG) && defined(KEY_EVENT_DIAGNOSTICS)
+ int old_keycode = inst->alt_keycode;
+#endif
+ if (inst->alt_keycode == -1)
+ inst->alt_keycode = digit; /* one-digit code */
+ else
+ inst->alt_keycode = inst->alt_keycode * 10 + digit;
+ inst->alt_digits++;
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Alt+numberpad digit %d added to keycode %d"
+ " gives %d\n", digit, old_keycode, inst->alt_keycode);
+#endif
+ /* Having used this digit, we now do nothing more with it. */
+ goto done;
+ }
+ }
+
+ if (event->keyval == GDK_KEY_greater &&
+ (event->state & GDK_CONTROL_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl->: increase font size\n");
+#endif
+ if (!inst->win_resize_pending)
+ change_font_size(inst, +1);
+ return true;
+ }
+ if (event->keyval == GDK_KEY_less &&
+ (event->state & GDK_CONTROL_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-<: increase font size\n");
+#endif
+ if (!inst->win_resize_pending)
+ change_font_size(inst, -1);
+ return true;
+ }
+
+ /*
+ * Shift-PgUp and Shift-PgDn don't even generate keystrokes
+ * at all.
+ */
+ if (event->keyval == GDK_KEY_Page_Up &&
+ ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) ==
+ (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-Shift-PgUp scroll\n");
+#endif
+ term_scroll(inst->term, 1, 0);
+ return true;
+ }
+ if (event->keyval == GDK_KEY_Page_Up &&
+ (event->state & GDK_SHIFT_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Shift-PgUp scroll\n");
+#endif
+ term_scroll(inst->term, 0, -inst->height/2);
+ return true;
+ }
+ if (event->keyval == GDK_KEY_Page_Up &&
+ (event->state & GDK_CONTROL_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-PgUp scroll\n");
+#endif
+ term_scroll(inst->term, 0, -1);
+ return true;
+ }
+ if (event->keyval == GDK_KEY_Page_Down &&
+ ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) ==
+ (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-shift-PgDn scroll\n");
+#endif
+ term_scroll(inst->term, -1, 0);
+ return true;
+ }
+ if (event->keyval == GDK_KEY_Page_Down &&
+ (event->state & GDK_SHIFT_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Shift-PgDn scroll\n");
+#endif
+ term_scroll(inst->term, 0, +inst->height/2);
+ return true;
+ }
+ if (event->keyval == GDK_KEY_Page_Down &&
+ (event->state & GDK_CONTROL_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-PgDn scroll\n");
+#endif
+ term_scroll(inst->term, 0, +1);
+ return true;
+ }
+
+ /*
+ * Neither do Shift-Ins or Ctrl-Ins (if enabled).
+ */
+ if (event->keyval == GDK_KEY_Insert &&
+ (event->state & GDK_SHIFT_MASK)) {
+ int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftins);
+
+ switch (cfgval) {
+ case CLIPUI_IMPLICIT:
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Shift-Insert: paste from PRIMARY\n");
+#endif
+ term_request_paste(inst->term, CLIP_PRIMARY);
+ return true;
+ case CLIPUI_EXPLICIT:
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Shift-Insert: paste from CLIPBOARD\n");
+#endif
+ term_request_paste(inst->term, CLIP_CLIPBOARD);
+ return true;
+ case CLIPUI_CUSTOM:
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Shift-Insert: paste from custom clipboard\n");
+#endif
+ term_request_paste(inst->term, inst->clipboard_ctrlshiftins);
+ return true;
+ default:
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Shift-Insert: no paste action\n");
+#endif
+ break;
+ }
+ }
+ if (event->keyval == GDK_KEY_Insert &&
+ (event->state & GDK_CONTROL_MASK)) {
+ static const int clips_clipboard[] = { CLIP_CLIPBOARD };
+ int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftins);
+
+ switch (cfgval) {
+ case CLIPUI_IMPLICIT:
+ /* do nothing; re-copy to PRIMARY is not needed */
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-Insert: non-copy to PRIMARY\n");
+#endif
+ return true;
+ case CLIPUI_EXPLICIT:
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-Insert: copy to CLIPBOARD\n");
+#endif
+ term_request_copy(inst->term,
+ clips_clipboard, lenof(clips_clipboard));
+ return true;
+ case CLIPUI_CUSTOM:
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-Insert: copy to custom clipboard\n");
+#endif
+ term_request_copy(inst->term,
+ &inst->clipboard_ctrlshiftins, 1);
+ return true;
+ default:
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-Insert: no copy action\n");
+#endif
+ break;
+ }
+ }
+
+ /*
+ * Another pair of copy-paste keys.
+ */
+ if ((event->state & GDK_SHIFT_MASK) &&
+ (event->state & GDK_CONTROL_MASK) &&
+ (event->keyval == GDK_KEY_C || event->keyval == GDK_KEY_c ||
+ event->keyval == GDK_KEY_V || event->keyval == GDK_KEY_v)) {
+ int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftcv);
+ bool paste = (event->keyval == GDK_KEY_V ||
+ event->keyval == GDK_KEY_v);
+
+ switch (cfgval) {
+ case CLIPUI_IMPLICIT:
+ if (paste) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-Shift-V: paste from PRIMARY\n");
+#endif
+ term_request_paste(inst->term, CLIP_PRIMARY);
+ } else {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-Shift-C: non-copy to PRIMARY\n");
+#endif
+ }
+ return true;
+ case CLIPUI_EXPLICIT:
+ if (paste) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-Shift-V: paste from CLIPBOARD\n");
+#endif
+ term_request_paste(inst->term, CLIP_CLIPBOARD);
+ } else {
+ static const int clips[] = { CLIP_CLIPBOARD };
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-Shift-C: copy to CLIPBOARD\n");
+#endif
+ term_request_copy(inst->term, clips, lenof(clips));
+ }
+ return true;
+ case CLIPUI_CUSTOM:
+ if (paste) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-Shift-V: paste from custom clipboard\n");
+#endif
+ term_request_paste(inst->term,
+ inst->clipboard_ctrlshiftcv);
+ } else {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-Shift-C: copy to custom clipboard\n");
+#endif
+ term_request_copy(inst->term,
+ &inst->clipboard_ctrlshiftcv, 1);
+ }
+ return true;
+ }
+ }
+
+ special = false;
+ use_ucsoutput = false;
+
+ /* ALT+things gives leading Escape. */
+ output[0] = '\033';
+#if !GTK_CHECK_VERSION(2,0,0)
+ /*
+ * In vanilla X, and hence also GDK 1.2, the string received
+ * as part of a keyboard event is assumed to be in
+ * ISO-8859-1. (Seems woefully shortsighted in i18n terms,
+ * but it's true: see the man page for XLookupString(3) for
+ * confirmation.)
+ */
+ output_charset = CS_ISO8859_1;
+ strncpy(output+1, event_string, lenof(output)-1);
+#else /* !GTK_CHECK_VERSION(2,0,0) */
+ /*
+ * Most things can now be passed to
+ * gtk_im_context_filter_keypress without breaking anything
+ * below this point. An exception is the numeric keypad if
+ * we're in Nethack or application mode: the IM will eat
+ * numeric keypad presses if Num Lock is on, but we don't want
+ * it to.
+ */
+ bool numeric = false;
+ bool nethack_mode = conf_get_bool(inst->conf, CONF_nethack_keypad);
+ bool app_keypad_mode = (inst->term->app_keypad_keys &&
+ !conf_get_bool(inst->conf, CONF_no_applic_k));
+
+ switch (event->keyval) {
+ case GDK_KEY_Num_Lock: num_keypad_key = 'G'; break;
+ case GDK_KEY_KP_Divide: num_keypad_key = '/'; break;
+ case GDK_KEY_KP_Multiply: num_keypad_key = '*'; break;
+ case GDK_KEY_KP_Subtract: num_keypad_key = '-'; break;
+ case GDK_KEY_KP_Add: num_keypad_key = '+'; break;
+ case GDK_KEY_KP_Enter: num_keypad_key = '\r'; break;
+ case GDK_KEY_KP_0: num_keypad_key = '0'; numeric = true; break;
+ case GDK_KEY_KP_Insert: num_keypad_key = '0'; break;
+ case GDK_KEY_KP_1: num_keypad_key = '1'; numeric = true; break;
+ case GDK_KEY_KP_End: num_keypad_key = '1'; break;
+ case GDK_KEY_KP_2: num_keypad_key = '2'; numeric = true; break;
+ case GDK_KEY_KP_Down: num_keypad_key = '2'; break;
+ case GDK_KEY_KP_3: num_keypad_key = '3'; numeric = true; break;
+ case GDK_KEY_KP_Page_Down: num_keypad_key = '3'; break;
+ case GDK_KEY_KP_4: num_keypad_key = '4'; numeric = true; break;
+ case GDK_KEY_KP_Left: num_keypad_key = '4'; break;
+ case GDK_KEY_KP_5: num_keypad_key = '5'; numeric = true; break;
+ case GDK_KEY_KP_Begin: num_keypad_key = '5'; break;
+ case GDK_KEY_KP_6: num_keypad_key = '6'; numeric = true; break;
+ case GDK_KEY_KP_Right: num_keypad_key = '6'; break;
+ case GDK_KEY_KP_7: num_keypad_key = '7'; numeric = true; break;
+ case GDK_KEY_KP_Home: num_keypad_key = '7'; break;
+ case GDK_KEY_KP_8: num_keypad_key = '8'; numeric = true; break;
+ case GDK_KEY_KP_Up: num_keypad_key = '8'; break;
+ case GDK_KEY_KP_9: num_keypad_key = '9'; numeric = true; break;
+ case GDK_KEY_KP_Page_Up: num_keypad_key = '9'; break;
+ case GDK_KEY_KP_Decimal: num_keypad_key = '.'; numeric = true; break;
+ case GDK_KEY_KP_Delete: num_keypad_key = '.'; break;
+ }
+ if ((app_keypad_mode && num_keypad_key &&
+ (numeric || inst->term->funky_type != FUNKY_XTERM)) ||
+ (nethack_mode && num_keypad_key >= '1' && num_keypad_key <= '9')) {
+ /* In these modes, we override the keypad handling:
+ * regardless of Num Lock, the keys are handled by
+ * format_numeric_keypad_key below. */
+ force_format_numeric_keypad = true;
+ } else {
+ bool try_filter = true;
+
+#ifdef META_MANUAL_MASK
+ if (event->state & META_MANUAL_MASK & inst->meta_mod_mask) {
+ /*
+ * If this key event had a Meta modifier bit set which
+ * is also in META_MANUAL_MASK, that means passing
+ * such an event to the GtkIMContext will be unhelpful
+ * (it will eat the keystroke and turn it into
+ * something not what we wanted).
+ */
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Meta modifier requiring manual intervention, "
+ "suppressing IM filtering\n");
+#endif
+ try_filter = false;
+ }
+#endif
+
+ if (try_filter) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - general key press, passing to IM\n");
+#endif
+ if (gtk_im_context_filter_keypress(inst->imc, event)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - key press accepted by IM\n");
+#endif
+ return true;
+ } else {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - key press not accepted by IM\n");
+#endif
+ }
+ }
+ }
+
+ /*
+ * GDK 2.0 arranges to have done some translation for us: in
+ * GDK 2.0, event->string is encoded in the current locale.
+ *
+ * So we use the standard C library function mbstowcs() to
+ * convert from the current locale into Unicode; from there
+ * we can convert to whatever PuTTY is currently working in.
+ * (In fact I convert straight back to UTF-8 from
+ * wide-character Unicode, for the sake of simplicity: that
+ * way we can still use exactly the same code to manipulate
+ * the string, such as prefixing ESC.)
+ */
+ output_charset = CS_UTF8;
+ {
+ wchar_t widedata[32];
+ const wchar_t *wp;
+ int wlen;
+ int ulen;
+
+ wlen = mb_to_wc(DEFAULT_CODEPAGE, 0,
+ event_string, strlen(event_string),
+ widedata, lenof(widedata)-1);
+
+#ifdef KEY_EVENT_DIAGNOSTICS
+ {
+ char *string_string = dupstr("");
+ int i;
+
+ for (i = 0; i < wlen; i++) {
+ char *old = string_string;
+ string_string = dupprintf("%s%s%04x", string_string,
+ string_string[0] ? " " : "",
+ (unsigned)widedata[i]);
+ sfree(old);
+ }
+ debug(" - string translated into Unicode = [%s]\n",
+ string_string);
+ sfree(string_string);
+ }
+#endif
+
+ wp = widedata;
+ ulen = charset_from_unicode(&wp, &wlen, output+1, lenof(output)-2,
+ CS_UTF8, NULL, NULL, 0);
+
+#ifdef KEY_EVENT_DIAGNOSTICS
+ {
+ char *string_string = dupstr("");
+ int i;
+
+ for (i = 0; i < ulen; i++) {
+ char *old = string_string;
+ string_string = dupprintf("%s%s%02x", string_string,
+ string_string[0] ? " " : "",
+ (unsigned)output[i+1] & 0xFF);
+ sfree(old);
+ }
+ debug(" - string translated into UTF-8 = [%s]\n",
+ string_string);
+ sfree(string_string);
+ }
+#endif
+
+ output[1+ulen] = '\0';
+ }
+#endif /* !GTK_CHECK_VERSION(2,0,0) */
+
+ if (!output[1] &&
+ (ucsval = keysym_to_unicode(event->keyval)) >= 0) {
+ ucsoutput[0] = '\033';
+ ucsoutput[1] = ucsval;
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - keysym_to_unicode gave %04x\n",
+ (unsigned)ucsoutput[1]);
+#endif
+ use_ucsoutput = true;
+ end = 2;
+ } else {
+ output[lenof(output)-1] = '\0';
+ end = strlen(output);
+ }
+ if (event->state & inst->meta_mod_mask) {
+ start = 0;
+ if (end == 1) end = 0;
+
+#ifdef META_MANUAL_MASK
+ if (event->state & META_MANUAL_MASK) {
+ /*
+ * Key events which have a META_MANUAL_MASK meta bit
+ * set may have a keyval reflecting that, e.g. on OS X
+ * the Option key acts as an AltGr-like modifier and
+ * causes different Unicode characters to be output.
+ *
+ * To work around this, we clear the dangerous
+ * modifier bit and retranslate from the hardware
+ * keycode as if the key had been pressed without that
+ * modifier. Then we prefix Esc to *that*.
+ */
+ guint new_keyval;
+ GdkModifierType consumed;
+ if (gdk_keymap_translate_keyboard_state(
+ gdk_keymap_get_for_display(gdk_display_get_default()),
+ event->hardware_keycode,
+ event->state & ~META_MANUAL_MASK,
+ 0, &new_keyval, NULL, NULL, &consumed)) {
+ ucsoutput[0] = '\033';
+ ucsoutput[1] = gdk_keyval_to_unicode(new_keyval);
+#ifdef KEY_EVENT_DIAGNOSTICS
+ {
+ char *keyval_name = dup_keyval_name(new_keyval);
+ debug(" - retranslation for manual Meta: "
+ "new keyval = %s, Unicode = %04x\n",
+ keyval_name, (unsigned)ucsoutput[1]);
+ sfree(keyval_name);
+ }
+#endif
+ use_ucsoutput = true;
+ end = 2;
+ }
+ }
+#endif
+ } else
+ start = 1;
+
+ /* Control-` is the same as Control-\ (unless gtk has a better idea) */
+ if (!output[1] && event->keyval == '`' &&
+ (event->state & GDK_CONTROL_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-` special case, translating as 1c\n");
+#endif
+ output[1] = '\x1C';
+ use_ucsoutput = false;
+ end = 2;
+ }
+
+ /* Some GTK backends (e.g. Quartz) do not change event->string
+ * in response to the Control modifier. So we do it ourselves
+ * here, if it's not already happened.
+ *
+ * The translations below are in line with X11 policy as far
+ * as I know. */
+ if ((event->state & GDK_CONTROL_MASK) && end == 2) {
+ int orig = use_ucsoutput ? ucsoutput[1] : output[1];
+ int new = orig;
+
+ if (new >= '3' && new <= '7') {
+ /* ^3,...,^7 map to 0x1B,...,0x1F */
+ new += '\x1B' - '3';
+ } else if (new == '2' || new == ' ') {
+ /* ^2 and ^Space are both ^@, i.e. \0 */
+ new = '\0';
+ } else if (new == '8') {
+ /* ^8 is DEL */
+ new = '\x7F';
+ } else if (new == '/') {
+ /* ^/ is the same as ^_ */
+ new = '\x1F';
+ } else if (new >= 0x40 && new < 0x7F) {
+ /* Everything anywhere near the alphabetics just gets
+ * masked. */
+ new &= 0x1F;
+ }
+ /* Anything else, e.g. '0', is unchanged. */
+
+ if (orig == new) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - manual Ctrl key handling did nothing\n");
+#endif
+ } else {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - manual Ctrl key handling: %02x -> %02x\n",
+ (unsigned)orig, (unsigned)new);
+#endif
+ output[1] = new;
+ use_ucsoutput = false;
+ }
+ }
+
+ /* Control-Break sends a Break special to the backend */
+ if (event->keyval == GDK_KEY_Break &&
+ (event->state & GDK_CONTROL_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-Break special case, sending SS_BRK\n");
+#endif
+ if (inst->backend)
+ backend_special(inst->backend, SS_BRK, 0);
+ return true;
+ }
+
+ /* We handle Return ourselves, because it needs to be flagged as
+ * special to ldisc. */
+ if (event->keyval == GDK_KEY_Return) {
+ end = return_key(inst, output, &special);
+ use_ucsoutput = false;
+ }
+
+ /* Control-2, Control-Space and Control-@ are NUL */
+ if (!output[1] &&
+ (event->keyval == ' ' || event->keyval == '2' ||
+ event->keyval == '@') &&
+ (event->state & (GDK_SHIFT_MASK |
+ GDK_CONTROL_MASK)) == GDK_CONTROL_MASK) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-{space,2,@} special case, translating as 00\n");
+#endif
+ output[1] = '\0';
+ use_ucsoutput = false;
+ end = 2;
+ }
+
+ /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */
+ if (!output[1] && event->keyval == ' ' &&
+ (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) ==
+ (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Ctrl-Shift-space special case, translating as 00a0\n");
+#endif
+ output[1] = '\240';
+ output_charset = CS_ISO8859_1;
+ use_ucsoutput = false;
+ end = 2;
+ }
+
+ /* We don't let GTK tell us what Backspace is! We know better. */
+ if (event->keyval == GDK_KEY_BackSpace &&
+ !(event->state & GDK_SHIFT_MASK)) {
+ output[1] = conf_get_bool(inst->conf, CONF_bksp_is_delete) ?
+ '\x7F' : '\x08';
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Backspace, translating as %02x\n",
+ (unsigned)output[1]);
+#endif
+ use_ucsoutput = false;
+ end = 2;
+ special = true;
+ }
+ /* For Shift Backspace, do opposite of what is configured. */
+ if (event->keyval == GDK_KEY_BackSpace &&
+ (event->state & GDK_SHIFT_MASK)) {
+ output[1] = conf_get_bool(inst->conf, CONF_bksp_is_delete) ?
+ '\x08' : '\x7F';
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Shift-Backspace, translating as %02x\n",
+ (unsigned)output[1]);
+#endif
+ use_ucsoutput = false;
+ end = 2;
+ special = true;
+ }
+
+ /* Shift-Tab is ESC [ Z */
+ if (event->keyval == GDK_KEY_ISO_Left_Tab ||
+ (event->keyval == GDK_KEY_Tab &&
+ (event->state & GDK_SHIFT_MASK))) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Shift-Tab, translating as ESC [ Z\n");
+#endif
+ end = 1 + sprintf(output+1, "\033[Z");
+ use_ucsoutput = false;
+ }
+ /* And normal Tab is Tab, if the keymap hasn't already told us.
+ * (Curiously, at least one version of the MacOS 10.5 X server
+ * doesn't translate Tab for us. */
+ if (event->keyval == GDK_KEY_Tab && end <= 1) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - Tab, translating as 09\n");
+#endif
+ output[1] = '\t';
+ end = 2;
+ }
+
+ if (num_keypad_key && force_format_numeric_keypad) {
+ end = 1 + format_numeric_keypad_key(
+ output+1, inst->term, num_keypad_key,
+ event->state & GDK_SHIFT_MASK,
+ event->state & GDK_CONTROL_MASK);
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - numeric keypad key");
+#endif
+ use_ucsoutput = false;
+ goto done;
+ }
+
+ switch (event->keyval) {
+ int fkey_number;
+ bool consumed_meta_key;
+
+ case GDK_KEY_F1: fkey_number = 1; goto numbered_function_key;
+ case GDK_KEY_F2: fkey_number = 2; goto numbered_function_key;
+ case GDK_KEY_F3: fkey_number = 3; goto numbered_function_key;
+ case GDK_KEY_F4: fkey_number = 4; goto numbered_function_key;
+ case GDK_KEY_F5: fkey_number = 5; goto numbered_function_key;
+ case GDK_KEY_F6: fkey_number = 6; goto numbered_function_key;
+ case GDK_KEY_F7: fkey_number = 7; goto numbered_function_key;
+ case GDK_KEY_F8: fkey_number = 8; goto numbered_function_key;
+ case GDK_KEY_F9: fkey_number = 9; goto numbered_function_key;
+ case GDK_KEY_F10: fkey_number = 10; goto numbered_function_key;
+ case GDK_KEY_F11: fkey_number = 11; goto numbered_function_key;
+ case GDK_KEY_F12: fkey_number = 12; goto numbered_function_key;
+ case GDK_KEY_F13: fkey_number = 13; goto numbered_function_key;
+ case GDK_KEY_F14: fkey_number = 14; goto numbered_function_key;
+ case GDK_KEY_F15: fkey_number = 15; goto numbered_function_key;
+ case GDK_KEY_F16: fkey_number = 16; goto numbered_function_key;
+ case GDK_KEY_F17: fkey_number = 17; goto numbered_function_key;
+ case GDK_KEY_F18: fkey_number = 18; goto numbered_function_key;
+ case GDK_KEY_F19: fkey_number = 19; goto numbered_function_key;
+ case GDK_KEY_F20: fkey_number = 20; goto numbered_function_key;
+ numbered_function_key:
+ consumed_meta_key = false;
+ end = 1 + format_function_key(output+1, inst->term, fkey_number,
+ event->state & GDK_SHIFT_MASK,
+ event->state & GDK_CONTROL_MASK,
+ event->state & inst->meta_mod_mask,
+ &consumed_meta_key);
+ if (consumed_meta_key)
+ start = 1; /* supersedes the usual prefixing of Esc */
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - function key F%d", fkey_number);
+#endif
+ use_ucsoutput = false;
+ goto done;
+
+ SmallKeypadKey sk_key;
+ case GDK_KEY_Home: case GDK_KEY_KP_Home:
+ sk_key = SKK_HOME; goto small_keypad_key;
+ case GDK_KEY_Insert: case GDK_KEY_KP_Insert:
+ sk_key = SKK_INSERT; goto small_keypad_key;
+ case GDK_KEY_Delete: case GDK_KEY_KP_Delete:
+ sk_key = SKK_DELETE; goto small_keypad_key;
+ case GDK_KEY_End: case GDK_KEY_KP_End:
+ sk_key = SKK_END; goto small_keypad_key;
+ case GDK_KEY_Page_Up: case GDK_KEY_KP_Page_Up:
+ sk_key = SKK_PGUP; goto small_keypad_key;
+ case GDK_KEY_Page_Down: case GDK_KEY_KP_Page_Down:
+ sk_key = SKK_PGDN; goto small_keypad_key;
+ small_keypad_key:
+ /* These keys don't generate terminal input with Ctrl */
+ if (event->state & GDK_CONTROL_MASK)
+ break;
+
+ end = 1 + format_small_keypad_key(
+ output+1, inst->term, sk_key, event->state & GDK_SHIFT_MASK,
+ event->state & GDK_CONTROL_MASK,
+ event->state & inst->meta_mod_mask, &consumed_meta_key);
+ if (consumed_meta_key)
+ start = 1; /* supersedes the usual prefixing of Esc */
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - small keypad key");
+#endif
+ use_ucsoutput = false;
+ goto done;
+
+ int xkey;
+ case GDK_KEY_Up: case GDK_KEY_KP_Up:
+ xkey = 'A'; goto arrow_key;
+ case GDK_KEY_Down: case GDK_KEY_KP_Down:
+ xkey = 'B'; goto arrow_key;
+ case GDK_KEY_Right: case GDK_KEY_KP_Right:
+ xkey = 'C'; goto arrow_key;
+ case GDK_KEY_Left: case GDK_KEY_KP_Left:
+ xkey = 'D'; goto arrow_key;
+ case GDK_KEY_Begin: case GDK_KEY_KP_Begin:
+ xkey = 'G'; goto arrow_key;
+ arrow_key:
+ consumed_meta_key = false;
+ end = 1 + format_arrow_key(
+ output+1, inst->term, xkey, event->state & GDK_SHIFT_MASK,
+ event->state & GDK_CONTROL_MASK,
+ event->state & inst->meta_mod_mask, &consumed_meta_key);
+ if (consumed_meta_key)
+ start = 1; /* supersedes the usual prefixing of Esc */
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - arrow key");
+#endif
+ use_ucsoutput = false;
+ goto done;
+ }
+
+ if (num_keypad_key) {
+ end = 1 + format_numeric_keypad_key(
+ output+1, inst->term, num_keypad_key,
+ event->state & GDK_SHIFT_MASK,
+ event->state & GDK_CONTROL_MASK);
+#ifdef KEY_EVENT_DIAGNOSTICS
+ debug(" - numeric keypad key");
+#endif
+
+ if (end == 1 && num_keypad_key == '\r') {
+ /* Keypad Enter, lacking any other translation,
+ * becomes the same special Return code as normal
+ * Return. */
+ end = return_key(inst, output, &special);
+ use_ucsoutput = false;
+ }
+
+ use_ucsoutput = false;
+ goto done;
+ }
+
+ goto done;
+ }
+
+ done:
+
+ if (end-start > 0) {
+ if (special) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ char *string_string = dupstr("");
+ int i;
+
+ for (i = start; i < end; i++) {
+ char *old = string_string;
+ string_string = dupprintf("%s%s%02x", string_string,
+ string_string[0] ? " " : "",
+ (unsigned)output[i] & 0xFF);
+ sfree(old);
+ }
+ debug(" - final output, special, generic encoding = [%s]\n",
+ string_string);
+ sfree(string_string);
+#endif
+ /*
+ * For special control characters, the character set
+ * should never matter.
+ */
+ output[end] = '\0'; /* NUL-terminate */
+ generated_something = true;
+ term_keyinput(inst->term, -1, output+start, -2);
+ } else if (!inst->direct_to_font) {
+ if (!use_ucsoutput) {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ char *string_string = dupstr("");
+ int i;
+
+ for (i = start; i < end; i++) {
+ char *old = string_string;
+ string_string = dupprintf("%s%s%02x", string_string,
+ string_string[0] ? " " : "",
+ (unsigned)output[i] & 0xFF);
+ sfree(old);
+ }
+ debug(" - final output in %s = [%s]\n",
+ charset_to_localenc(output_charset), string_string);
+ sfree(string_string);
+#endif
+ generated_something = true;
+ term_keyinput(inst->term, output_charset,
+ output+start, end-start);
+ } else {
+#ifdef KEY_EVENT_DIAGNOSTICS
+ char *string_string = dupstr("");
+ int i;
+
+ for (i = start; i < end; i++) {
+ char *old = string_string;
+ string_string = dupprintf("%s%s%04x", string_string,
+ string_string[0] ? " " : "",
+ (unsigned)ucsoutput[i]);
+ sfree(old);
+ }
+ debug(" - final output in Unicode = [%s]\n",
+ string_string);
+ sfree(string_string);
+#endif
+
+ /*
+ * We generated our own Unicode key data from the
+ * keysym, so use that instead.
+ */
+ generated_something = true;
+ term_keyinputw(inst->term, ucsoutput+start, end-start);
+ }
+ } else {
+ /*
+ * In direct-to-font mode, we just send the string
+ * exactly as we received it.
+ */
+#ifdef KEY_EVENT_DIAGNOSTICS
+ char *string_string = dupstr("");
+ int i;
+
+ for (i = start; i < end; i++) {
+ char *old = string_string;
+ string_string = dupprintf("%s%s%02x", string_string,
+ string_string[0] ? " " : "",
+ (unsigned)output[i] & 0xFF);
+ sfree(old);
+ }
+ debug(" - final output in direct-to-font encoding = [%s]\n",
+ string_string);
+ sfree(string_string);
+#endif
+ generated_something = true;
+ term_keyinput(inst->term, -1, output+start, end-start);
+ }
+
+ show_mouseptr(inst, false);
+ }
+
+ if (generated_something)
+ key_pressed(inst);
+ return true;
+}
+
+#if GTK_CHECK_VERSION(2,0,0)
+void input_method_commit_event(GtkIMContext *imc, gchar *str, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+
+#ifdef KEY_EVENT_DIAGNOSTICS
+ char *string_string = dupstr("");
+ int i;
+
+ for (i = 0; str[i]; i++) {
+ char *old = string_string;
+ string_string = dupprintf("%s%s%02x", string_string,
+ string_string[0] ? " " : "",
+ (unsigned)str[i] & 0xFF);
+ sfree(old);
+ }
+ debug(" - IM commit event in UTF-8 = [%s]\n", string_string);
+ sfree(string_string);
+#endif
+
+ term_keyinput(inst->term, CS_UTF8, str, strlen(str));
+ show_mouseptr(inst, false);
+ key_pressed(inst);
+}
+#endif
+
+#define SCROLL_INCREMENT_LINES 5
+
+#if GTK_CHECK_VERSION(3,4,0)
+gboolean scroll_internal(GtkFrontend *inst, gdouble delta, guint state,
+ gdouble ex, gdouble ey)
+{
+ int x, y;
+ bool shift, ctrl, alt, raw_mouse_mode;
+
+ show_mouseptr(inst, true);
+
+ shift = state & GDK_SHIFT_MASK;
+ ctrl = state & GDK_CONTROL_MASK;
+ alt = state & inst->meta_mod_mask;
+
+ x = (ex - inst->window_border) / inst->font_width;
+ y = (ey - inst->window_border) / inst->font_height;
+
+ raw_mouse_mode = (inst->send_raw_mouse &&
+ !(shift && conf_get_bool(inst->conf,
+ CONF_mouse_override)));
+
+ inst->cumulative_scroll += delta * SCROLL_INCREMENT_LINES;
+
+ if (!raw_mouse_mode) {
+ int scroll_lines = (int)inst->cumulative_scroll; /* rounds toward 0 */
+ if (scroll_lines) {
+ term_scroll(inst->term, 0, scroll_lines);
+ inst->cumulative_scroll -= scroll_lines;
+ }
+ return true;
+ } else {
+ int scroll_events = (int)(inst->cumulative_scroll /
+ SCROLL_INCREMENT_LINES);
+ if (scroll_events) {
+ int button;
+
+ inst->cumulative_scroll -= scroll_events * SCROLL_INCREMENT_LINES;
+
+ if (scroll_events > 0) {
+ button = MBT_WHEEL_DOWN;
+ } else {
+ button = MBT_WHEEL_UP;
+ scroll_events = -scroll_events;
+ }
+
+ while (scroll_events-- > 0) {
+ term_mouse(inst->term, button, translate_button(button),
+ MA_CLICK, x, y, shift, ctrl, alt);
+ }
+ }
+ return true;
+ }
+}
+#endif
+
+static gboolean button_internal(GtkFrontend *inst, GdkEventButton *event)
+{
+ bool shift, ctrl, alt, raw_mouse_mode;
+ int x, y, button, act;
+
+ /* Remember the timestamp. */
+ inst->input_event_time = event->time;
+
+ noise_ultralight(NOISE_SOURCE_MOUSEBUTTON, event->button);
+
+ show_mouseptr(inst, true);
+
+ shift = event->state & GDK_SHIFT_MASK;
+ ctrl = event->state & GDK_CONTROL_MASK;
+ alt = event->state & inst->meta_mod_mask;
+
+ raw_mouse_mode = (inst->send_raw_mouse &&
+ !(shift && conf_get_bool(inst->conf,
+ CONF_mouse_override)));
+
+ if (!raw_mouse_mode) {
+ if (event->button == 4 && event->type == GDK_BUTTON_PRESS) {
+ term_scroll(inst->term, 0, -SCROLL_INCREMENT_LINES);
+ return true;
+ }
+ if (event->button == 5 && event->type == GDK_BUTTON_PRESS) {
+ term_scroll(inst->term, 0, +SCROLL_INCREMENT_LINES);
+ return true;
+ }
+ }
+
+ if (event->button == 3 && ctrl) {
+ /* Just in case this happened in mid-select */
+ term_cancel_selection_drag(inst->term);
+#if GTK_CHECK_VERSION(3,22,0)
+ gtk_menu_popup_at_pointer(GTK_MENU(inst->menu), (GdkEvent *)event);
+#else
+ gtk_menu_popup(GTK_MENU(inst->menu), NULL, NULL, NULL, NULL,
+ event->button, event->time);
+#endif
+ return true;
+ }
+
+ if (event->button == 1)
+ button = MBT_LEFT;
+ else if (event->button == 2)
+ button = MBT_MIDDLE;
+ else if (event->button == 3)
+ button = MBT_RIGHT;
+ else if (event->button == 4)
+ button = MBT_WHEEL_UP;
+ else if (event->button == 5)
+ button = MBT_WHEEL_DOWN;
+ else
+ return false; /* don't even know what button! */
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS: act = MA_CLICK; break;
+ case GDK_BUTTON_RELEASE: act = MA_RELEASE; break;
+ case GDK_2BUTTON_PRESS: act = MA_2CLK; break;
+ case GDK_3BUTTON_PRESS: act = MA_3CLK; break;
+ default: return false; /* don't know this event type */
+ }
+
+ if (raw_mouse_mode && act != MA_CLICK && act != MA_RELEASE)
+ return true; /* we ignore these in raw mouse mode */
+
+ x = (event->x - inst->window_border) / inst->font_width;
+ y = (event->y - inst->window_border) / inst->font_height;
+
+ term_mouse(inst->term, button, translate_button(button), act,
+ x, y, shift, ctrl, alt);
+
+ return true;
+}
+
+gboolean button_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ return button_internal(inst, event);
+}
+
+#if GTK_CHECK_VERSION(2,0,0)
+/*
+ * In GTK 2, mouse wheel events have become a new type of event.
+ * This handler translates them back into button-4 and button-5
+ * presses so that I don't have to change my old code too much :-)
+ */
+gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ GdkScrollDirection dir;
+
+#if GTK_CHECK_VERSION(3,4,0)
+ gdouble dx, dy;
+ if (gdk_event_get_scroll_deltas((GdkEvent *)event, &dx, &dy)) {
+ return scroll_internal(inst, dy, event->state, event->x, event->y);
+ } else if (!gdk_event_get_scroll_direction((GdkEvent *)event, &dir)) {
+ return false;
+ }
+#else
+ dir = event->direction;
+#endif
+
+ guint button;
+ GdkEventButton *event_button;
+ gboolean ret;
+
+ if (dir == GDK_SCROLL_UP)
+ button = 4;
+ else if (dir == GDK_SCROLL_DOWN)
+ button = 5;
+ else
+ return false;
+
+ event_button = (GdkEventButton *)gdk_event_new(GDK_BUTTON_PRESS);
+ event_button->window = g_object_ref(event->window);
+ event_button->send_event = event->send_event;
+ event_button->time = event->time;
+ event_button->x = event->x;
+ event_button->y = event->y;
+ event_button->axes = NULL;
+ event_button->state = event->state;
+ event_button->button = button;
+ event_button->device = g_object_ref(event->device);
+ event_button->x_root = event->x_root;
+ event_button->y_root = event->y_root;
+ ret = button_internal(inst, event_button);
+ gdk_event_free((GdkEvent *)event_button);
+ return ret;
+}
+#endif
+
+gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ bool shift, ctrl, alt;
+ int x, y, button;
+
+ /* Remember the timestamp. */
+ inst->input_event_time = event->time;
+
+ noise_ultralight(NOISE_SOURCE_MOUSEPOS,
+ ((uint32_t)event->x << 16) | (uint32_t)event->y);
+
+ show_mouseptr(inst, true);
+
+ shift = event->state & GDK_SHIFT_MASK;
+ ctrl = event->state & GDK_CONTROL_MASK;
+ alt = event->state & inst->meta_mod_mask;
+ if (event->state & GDK_BUTTON1_MASK)
+ button = MBT_LEFT;
+ else if (event->state & GDK_BUTTON2_MASK)
+ button = MBT_MIDDLE;
+ else if (event->state & GDK_BUTTON3_MASK)
+ button = MBT_RIGHT;
+ else
+ return false; /* don't even know what button! */
+
+ x = (event->x - inst->window_border) / inst->font_width;
+ y = (event->y - inst->window_border) / inst->font_height;
+
+ term_mouse(inst->term, button, translate_button(button), MA_DRAG,
+ x, y, shift, ctrl, alt);
+
+ return true;
+}
+
+static void key_pressed(GtkFrontend *inst)
+{
+ /*
+ * If our child process has exited but not closed, terminate on
+ * any keypress.
+ *
+ * This is a UI feature specific to GTK PuTTY, because GTK PuTTY
+ * will (at least sometimes) be running under X, and under X the
+ * window manager is sometimes absent (very occasionally on
+ * purpose, more usually temporarily because it's crashed). So
+ * it's useful to have a way to close an application window
+ * without depending on protocols like WM_DELETE_WINDOW that are
+ * typically generated by the WM (e.g. in response to a close
+ * button in the window frame).
+ */
+ if (inst->exited)
+ gtk_widget_destroy(inst->window);
+}
+
+static void exit_callback(void *vctx)
+{
+ GtkFrontend *inst = (GtkFrontend *)vctx;
+ int exitcode, close_on_exit;
+
+ if (!inst->exited &&
+ (exitcode = backend_exitcode(inst->backend)) >= 0) {
+ destroy_inst_connection(inst);
+
+ close_on_exit = conf_get_int(inst->conf, CONF_close_on_exit);
+ if (close_on_exit == FORCE_ON ||
+ (close_on_exit == AUTO && exitcode == 0)) {
+ gtk_widget_destroy(inst->window);
+ }
+ }
+}
+
+static void gtk_seat_notify_remote_exit(Seat *seat)
+{
+ GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
+ queue_toplevel_callback(exit_callback, inst);
+}
+
+static void destroy_inst_connection(GtkFrontend *inst)
+{
+ inst->exited = true;
+ if (inst->ldisc) {
+ ldisc_free(inst->ldisc);
+ inst->ldisc = NULL;
+ }
+ if (inst->backend) {
+ backend_free(inst->backend);
+ inst->backend = NULL;
+ }
+ if (inst->term)
+ term_provide_backend(inst->term, NULL);
+ if (inst->menu) {
+ seat_update_specials_menu(&inst->seat);
+ gtk_widget_set_sensitive(inst->restartitem, true);
+ }
+}
+
+static void delete_inst(GtkFrontend *inst)
+{
+ int dialog_slot;
+ for (dialog_slot = 0; dialog_slot < DIALOG_SLOT_LIMIT; dialog_slot++) {
+ if (inst->dialogs[dialog_slot]) {
+ gtk_widget_destroy(inst->dialogs[dialog_slot]);
+ inst->dialogs[dialog_slot] = NULL;
+ }
+ }
+ if (inst->window) {
+ gtk_widget_destroy(inst->window);
+ inst->window = NULL;
+ }
+ if (inst->menu) {
+ gtk_widget_destroy(inst->menu);
+ inst->menu = NULL;
+ }
+ destroy_inst_connection(inst);
+ if (inst->term) {
+ term_free(inst->term);
+ inst->term = NULL;
+ }
+ if (inst->conf) {
+ conf_free(inst->conf);
+ inst->conf = NULL;
+ }
+ if (inst->logctx) {
+ log_free(inst->logctx);
+ inst->logctx = NULL;
+ }
+#if GTK_CHECK_VERSION(2,0,0)
+ if (inst->trust_sigil_pb) {
+ g_object_unref(G_OBJECT(inst->trust_sigil_pb));
+ inst->trust_sigil_pb = NULL;
+ }
+#else
+ if (inst->trust_sigil_pm) {
+ gdk_pixmap_unref(inst->trust_sigil_pm);
+ inst->trust_sigil_pm = NULL;
+ }
+#endif
+
+#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
+ /*
+ * Clear up any in-flight clipboard_data_instances. We can't
+ * actually _free_ them, but we detach them from the inst that's
+ * about to be destroyed.
+ */
+ while (inst->cdi_headtail.next != &inst->cdi_headtail) {
+ struct clipboard_data_instance *cdi = inst->cdi_headtail.next;
+ cdi->state = NULL;
+ cdi->next->prev = cdi->prev;
+ cdi->prev->next = cdi->next;
+ cdi->next = cdi->prev = cdi;
+ }
+#endif
+
+ /*
+ * Delete any top-level callbacks associated with inst, which
+ * would otherwise become stale-pointer dereferences waiting to
+ * happen. We do this last, because some of the above cleanups
+ * (notably shutting down the backend) might themelves queue such
+ * callbacks, so we need to make sure they don't do that _after_
+ * we're supposed to have cleaned everything up.
+ */
+ delete_callbacks_for_context(inst);
+
+ eventlogstuff_free(inst->eventlogstuff);
+
+ sfree(inst);
+}
+
+void destroy(GtkWidget *widget, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ inst->window = NULL;
+ delete_inst(inst);
+ session_window_closed();
+}
+
+gint focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ term_set_focus(inst->term, event->in);
+ term_update(inst->term);
+ show_mouseptr(inst, true);
+ return false;
+}
+
+static void gtk_seat_set_busy_status(Seat *seat, BusyStatus status)
+{
+ GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
+ inst->busy_status = status;
+ update_mouseptr(inst);
+}
+
+static void gtkwin_set_raw_mouse_mode(TermWin *tw, bool activate)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ inst->send_raw_mouse = activate;
+}
+
+static void gtkwin_set_raw_mouse_mode_pointer(TermWin *tw, bool activate)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ inst->pointer_indicates_raw_mouse = activate;
+ update_mouseptr(inst);
+}
+
+#if GTK_CHECK_VERSION(2,0,0)
+static void compute_whole_window_size(GtkFrontend *inst,
+ int wchars, int hchars,
+ int *wpix, int *hpix);
+#endif
+
+static void gtkwin_deny_term_resize(void *vctx)
+{
+ GtkFrontend *inst = (GtkFrontend *)vctx;
+ drawing_area_setup_simple(inst);
+}
+
+static void gtkwin_timer(void *vctx, unsigned long now)
+{
+ GtkFrontend *inst = (GtkFrontend *)vctx;
+
+ if (inst->win_resize_pending && now == inst->win_resize_timeout) {
+ if (inst->term_resize_notification_required)
+ term_resize_request_completed(inst->term);
+ inst->win_resize_pending = false;
+ }
+}
+
+static void request_resize_internal(GtkFrontend *inst, bool from_terminal,
+ int w, int h)
+{
+#if GTK_CHECK_VERSION(2,0,0)
+ /*
+ * Initial check: don't even try to resize a window if it's in one
+ * of the states that make that impossible. This includes being
+ * maximised; being full-screen (if we ever implement that); or
+ * being in various tiled states.
+ *
+ * On X11, the effect of trying to resize the window when it can't
+ * be resized should be that the window manager sends us a
+ * synthetic ConfigureNotify event restating our existing size
+ * (ICCCM section 4.1.5 "Configuring the Window"). That's awkward
+ * to deal with, but not impossible; so for X11 alone, we might
+ * not bother with this check.
+ *
+ * (In any case, X11 has other reasons why a window resize might
+ * be rejected, which this enumeration can't be aware of in any
+ * case. For example, if your window manager is a tiling one, then
+ * all your windows are _de facto_ tiled, but not _de jure_ in a
+ * way that GDK will know about. So we have to handle those
+ * synthetic ConfigureNotifies in any case.)
+ *
+ * On Wayland, as of GTK 3.24.20, the effects are much worse: it
+ * looks to me as if GTK stops ever sending us "draw" events, or
+ * even a size_allocate, so the display locks up completely until
+ * you toggle the maximised state of the window by some other
+ * means. So it's worth checking!
+ */
+ GdkWindow *gdkwin = gtk_widget_get_window(inst->window);
+ if (gdkwin) {
+ GdkWindowState state = gdk_window_get_state(gdkwin);
+ if (state & (GDK_WINDOW_STATE_MAXIMIZED |
+ GDK_WINDOW_STATE_FULLSCREEN |
+#if GTK_CHECK_VERSION(3,10,0)
+ GDK_WINDOW_STATE_TILED |
+#endif
+#if GTK_CHECK_VERSION(3,22,23)
+ GDK_WINDOW_STATE_TOP_TILED |
+ GDK_WINDOW_STATE_RIGHT_TILED |
+ GDK_WINDOW_STATE_BOTTOM_TILED |
+ GDK_WINDOW_STATE_LEFT_TILED |
+#endif
+ 0)) {
+ queue_toplevel_callback(gtkwin_deny_term_resize, inst);
+ term_resize_request_completed(inst->term);
+ return;
+ }
+ }
+#endif
+
+#if !GTK_CHECK_VERSION(3,0,0)
+
+ int large_x, large_y;
+ int offset_x, offset_y;
+ int area_x, area_y;
+ GtkRequisition inner, outer;
+
+ /*
+ * This is a heinous hack dreamed up by the gnome-terminal
+ * people to get around a limitation in gtk. The problem is
+ * that in order to set the size correctly we really need to be
+ * calling gtk_window_resize - but that needs to know the size
+ * of the _whole window_, not the drawing area. So what we do
+ * is to set an artificially huge size request on the drawing
+ * area, recompute the resulting size request on the window,
+ * and look at the difference between the two. That gives us
+ * the x and y offsets we need to translate drawing area size
+ * into window size for real, and then we call
+ * gtk_window_resize.
+ */
+
+ /*
+ * We start by retrieving the current size of the whole window.
+ * Adding a bit to _that_ will give us a value we can use as a
+ * bogus size request which guarantees to be bigger than the
+ * current size of the drawing area.
+ */
+ get_window_pixel_size(inst, &large_x, &large_y);
+ large_x += 32;
+ large_y += 32;
+
+ gtk_widget_set_size_request(inst->area, large_x, large_y);
+ gtk_widget_size_request(inst->area, &inner);
+ gtk_widget_size_request(inst->window, &outer);
+
+ offset_x = outer.width - inner.width;
+ offset_y = outer.height - inner.height;
+
+ area_x = inst->font_width * w + 2*inst->window_border;
+ area_y = inst->font_height * h + 2*inst->window_border;
+
+ /*
+ * Now we must set the size request on the drawing area back to
+ * something sensible before we commit the real resize. Best
+ * way to do this, I think, is to set it to what the size is
+ * really going to end up being.
+ */
+ gtk_widget_set_size_request(inst->area, area_x, area_y);
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_window_resize(GTK_WINDOW(inst->window),
+ area_x + offset_x, area_y + offset_y);
+#else
+ gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), area_x, area_y);
+ /*
+ * I can no longer remember what this call to
+ * gtk_container_dequeue_resize_handler is for. It was
+ * introduced in r3092 with no comment, and the commit log
+ * message was uninformative. I'm _guessing_ its purpose is to
+ * prevent gratuitous resize processing on the window given
+ * that we're about to resize it anyway, but I have no idea
+ * why that's so incredibly vital.
+ *
+ * I've tried removing the call, and nothing seems to go
+ * wrong. I've backtracked to r3092 and tried removing the
+ * call there, and still nothing goes wrong. So I'm going to
+ * adopt the working hypothesis that it's superfluous; I won't
+ * actually remove it from the GTK 1.2 code, but I won't
+ * attempt to replicate its functionality in the GTK 2 code
+ * above.
+ */
+ gtk_container_dequeue_resize_handler(GTK_CONTAINER(inst->window));
+ gdk_window_resize(gtk_widget_get_window(inst->window),
+ area_x + offset_x, area_y + offset_y);
+#endif
+
+#else /* GTK_CHECK_VERSION(3,0,0) */
+
+ int wp, hp;
+ compute_whole_window_size(inst, w, h, &wp, &hp);
+ gtk_window_resize(GTK_WINDOW(inst->window), wp, hp);
+
+#endif
+
+ inst->win_resize_pending = true;
+ inst->term_resize_notification_required = from_terminal;
+ inst->win_resize_timeout = schedule_timer(
+ WIN_RESIZE_TIMEOUT, gtkwin_timer, inst);
+}
+
+static void gtkwin_request_resize(TermWin *tw, int w, int h)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ request_resize_internal(inst, true, w, h);
+}
+
+#if GTK_CHECK_VERSION(3,0,0)
+char *colour_to_css(const GdkColor *col)
+{
+ GdkRGBA rgba;
+ rgba.red = col->red / 65535.0;
+ rgba.green = col->green / 65535.0;
+ rgba.blue = col->blue / 65535.0;
+ rgba.alpha = 1.0;
+ return gdk_rgba_to_string(&rgba);
+}
+#endif
+
+void set_gtk_widget_background(GtkWidget *widget, const GdkColor *col)
+{
+#if GTK_CHECK_VERSION(3,0,0)
+ GtkCssProvider *provider = gtk_css_provider_new();
+ char *col_css = colour_to_css(col);
+ char *data = dupprintf(
+ "#drawing-area, #top-level { background-color: %s; }\n", col_css);
+ gtk_css_provider_load_from_data(provider, data, -1, NULL);
+ GtkStyleContext *context = gtk_widget_get_style_context(widget);
+ gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ free(data);
+ free(col_css);
+#else
+ if (gtk_widget_get_window(widget)) {
+ /* For GTK1, which doesn't have a 'const' on
+ * gdk_window_set_background's second parameter type. */
+ GdkColor col_mutable = *col;
+ gdk_window_set_background(gtk_widget_get_window(widget), &col_mutable);
+ }
+#endif
+}
+
+void set_window_background(GtkFrontend *inst)
+{
+ if (inst->area)
+ set_gtk_widget_background(GTK_WIDGET(inst->area), &inst->cols[258]);
+ if (inst->window)
+ set_gtk_widget_background(GTK_WIDGET(inst->window), &inst->cols[258]);
+}
+
+static void gtkwin_palette_set(TermWin *tw, unsigned start, unsigned ncolours,
+ const rgb *colours)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+
+ assert(start <= OSC4_NCOLOURS);
+ assert(ncolours <= OSC4_NCOLOURS - start);
+
+#if !GTK_CHECK_VERSION(3,0,0)
+ if (!inst->colmap) {
+ inst->colmap = gdk_colormap_get_system();
+ } else {
+ gdk_colormap_free_colors(inst->colmap, inst->cols, OSC4_NCOLOURS);
+ }
+#endif
+
+ for (unsigned i = 0; i < ncolours; i++) {
+ const rgb *in = &colours[i];
+ GdkColor *out = &inst->cols[start + i];
+
+ out->red = in->r * 0x0101;
+ out->green = in->g * 0x0101;
+ out->blue = in->b * 0x0101;
+ }
+
+#if !GTK_CHECK_VERSION(3,0,0)
+ {
+ gboolean success[OSC4_NCOLOURS];
+ gdk_colormap_alloc_colors(inst->colmap, inst->cols + start,
+ ncolours, false, true, success);
+ for (unsigned i = 0; i < ncolours; i++) {
+ if (!success[i])
+ g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n",
+ appname, start + i,
+ conf_get_int_int(inst->conf, CONF_colours, i*3+0),
+ conf_get_int_int(inst->conf, CONF_colours, i*3+1),
+ conf_get_int_int(inst->conf, CONF_colours, i*3+2));
+ }
+ }
+#endif
+
+ if (start <= OSC4_COLOUR_bg && OSC4_COLOUR_bg < start + ncolours) {
+ /* Default Background has changed, so ensure that space between text
+ * area and window border is refreshed. */
+ set_window_background(inst);
+ if (inst->area && gtk_widget_get_window(inst->area)) {
+ draw_backing_rect(inst);
+ gtk_widget_queue_draw(inst->area);
+ }
+ }
+}
+
+static void gtkwin_palette_get_overrides(TermWin *tw, Terminal *term)
+{
+ /* GTK has no analogue of Windows's 'standard system colours', so GTK PuTTY
+ * has no config option to override the normally configured colours from
+ * it */
+}
+
+static struct clipboard_state *clipboard_from_atom(
+ GtkFrontend *inst, GdkAtom atom)
+{
+ int i;
+
+ for (i = 0; i < N_CLIPBOARDS; i++) {
+ struct clipboard_state *state = &inst->clipstates[i];
+ if (state->inst == inst && state->atom == atom)
+ return state;
+ }
+
+ return NULL;
+}
+
+#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
+
+/* ----------------------------------------------------------------------
+ * Clipboard handling, using the high-level GtkClipboard interface in
+ * as hands-off a way as possible. We write and read the clipboard as
+ * UTF-8 text, and let GTK deal with converting to any other text
+ * formats it feels like.
+ */
+
+void set_clipboard_atom(GtkFrontend *inst, int clipboard, GdkAtom atom)
+{
+ struct clipboard_state *state = &inst->clipstates[clipboard];
+
+ state->inst = inst;
+ state->clipboard = clipboard;
+ state->atom = atom;
+
+ if (state->atom != GDK_NONE) {
+ state->gtkclipboard = gtk_clipboard_get_for_display(
+ gdk_display_get_default(), state->atom);
+ g_object_set_data(G_OBJECT(state->gtkclipboard), "user-data", state);
+ } else {
+ state->gtkclipboard = NULL;
+ }
+}
+
+int init_clipboard(GtkFrontend *inst)
+{
+ set_clipboard_atom(inst, CLIP_PRIMARY, GDK_SELECTION_PRIMARY);
+ set_clipboard_atom(inst, CLIP_CLIPBOARD, clipboard_atom);
+ return true;
+}
+
+static void clipboard_provide_data(GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ guint info, gpointer data)
+{
+ struct clipboard_data_instance *cdi =
+ (struct clipboard_data_instance *)data;
+
+ if (cdi->state && cdi->state->current_cdi == cdi) {
+ gtk_selection_data_set_text(selection_data, cdi->pasteout_data_utf8,
+ cdi->pasteout_data_utf8_len);
+ }
+}
+
+static void clipboard_clear(GtkClipboard *clipboard, gpointer data)
+{
+ struct clipboard_data_instance *cdi =
+ (struct clipboard_data_instance *)data;
+
+ if (cdi->state && cdi->state->current_cdi == cdi) {
+ if (cdi->state->inst && cdi->state->inst->term) {
+ term_lost_clipboard_ownership(cdi->state->inst->term,
+ cdi->state->clipboard);
+ }
+ cdi->state->current_cdi = NULL;
+ }
+ sfree(cdi->pasteout_data_utf8);
+ cdi->next->prev = cdi->prev;
+ cdi->prev->next = cdi->next;
+ sfree(cdi);
+}
+
+static void gtkwin_clip_write(
+ TermWin *tw, int clipboard, wchar_t *data, int *attr,
+ truecolour *truecolour, int len, bool must_deselect)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ struct clipboard_state *state = &inst->clipstates[clipboard];
+ struct clipboard_data_instance *cdi;
+
+ if (inst->direct_to_font) {
+ /* In this clipboard mode, we just can't paste if we're in
+ * direct-to-font mode. Fortunately, that shouldn't be
+ * important, because we'll only use this clipboard handling
+ * code on systems where that kind of font doesn't exist
+ * anyway. */
+ return;
+ }
+
+ if (!state->gtkclipboard)
+ return;
+
+ cdi = snew(struct clipboard_data_instance);
+ cdi->state = state;
+ state->current_cdi = cdi;
+ cdi->pasteout_data_utf8 = snewn(len*6, char);
+ cdi->prev = inst->cdi_headtail.prev;
+ cdi->next = &inst->cdi_headtail;
+ cdi->next->prev = cdi;
+ cdi->prev->next = cdi;
+ {
+ const wchar_t *tmp = data;
+ int tmplen = len;
+ cdi->pasteout_data_utf8_len =
+ charset_from_unicode(&tmp, &tmplen, cdi->pasteout_data_utf8,
+ len*6, CS_UTF8, NULL, NULL, 0);
+ }
+
+ /*
+ * It would be nice to just call gtk_clipboard_set_text() in place
+ * of all of the faffing below. Unfortunately, that won't give me
+ * access to the clipboard-clear event, which we use to visually
+ * deselect text in the terminal.
+ */
+ {
+ GtkTargetList *targetlist;
+ GtkTargetEntry *targettable;
+ gint n_targets;
+
+ targetlist = gtk_target_list_new(NULL, 0);
+ gtk_target_list_add_text_targets(targetlist, 0);
+ targettable = gtk_target_table_new_from_list(targetlist, &n_targets);
+ gtk_clipboard_set_with_data(state->gtkclipboard, targettable,
+ n_targets, clipboard_provide_data,
+ clipboard_clear, cdi);
+ gtk_target_table_free(targettable, n_targets);
+ gtk_target_list_unref(targetlist);
+ }
+}
+
+static void clipboard_text_received(GtkClipboard *clipboard,
+ const gchar *text, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ wchar_t *paste;
+ int paste_len;
+ int length;
+
+ if (!text)
+ return;
+
+ length = strlen(text);
+
+ paste = snewn(length, wchar_t);
+ paste_len = mb_to_wc(CS_UTF8, 0, text, length, paste, length);
+
+ term_do_paste(inst->term, paste, paste_len);
+
+ sfree(paste);
+}
+
+static void gtkwin_clip_request_paste(TermWin *tw, int clipboard)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ struct clipboard_state *state = &inst->clipstates[clipboard];
+
+ if (!state->gtkclipboard)
+ return;
+
+ gtk_clipboard_request_text(state->gtkclipboard,
+ clipboard_text_received, inst);
+}
+
+#else /* JUST_USE_GTK_CLIPBOARD_UTF8 */
+
+/* ----------------------------------------------------------------------
+ * Clipboard handling for X, using the low-level gtk_selection_*
+ * interface, handling conversions to fiddly things like compound text
+ * ourselves, and storing in X cut buffers too.
+ *
+ * This version of the clipboard code has to be kept around for GTK1,
+ * which doesn't have the higher-level GtkClipboard interface at all.
+ * And since it works on GTK2 and GTK3 too and has had a good few
+ * years of shakedown and bug fixing, we might as well keep using it
+ * where it's applicable.
+ *
+ * It's _possible_ that we might be able to replicate all the
+ * important wrinkles of this code in GtkClipboard. (In particular,
+ * cut buffers or local analogue look as if they might be accessible
+ * via gtk_clipboard_set_can_store(), and delivering text in
+ * non-Unicode formats only in the direct-to-font case ought to be
+ * possible if we can figure out the right set of things to put in the
+ * GtkTargetList.) But that work can wait until there's a need for it!
+ */
+
+#ifndef NOT_X_WINDOWS
+
+/* Store the data in a cut-buffer. */
+static void store_cutbuffer(GtkFrontend *inst, char *ptr, int len)
+{
+ if (inst->disp) {
+ /* ICCCM says we must rotate the buffers before storing to buffer 0. */
+ XRotateBuffers(inst->disp, 1);
+ XStoreBytes(inst->disp, ptr, len);
+ }
+}
+
+/* Retrieve data from a cut-buffer.
+ * Returned data needs to be freed with XFree().
+ */
+static char *retrieve_cutbuffer(GtkFrontend *inst, int *nbytes)
+{
+ char *ptr;
+ if (!inst->disp) {
+ *nbytes = 0;
+ return NULL;
+ }
+ ptr = XFetchBytes(inst->disp, nbytes);
+ if (*nbytes <= 0 && ptr != 0) {
+ XFree(ptr);
+ ptr = 0;
+ }
+ return ptr;
+}
+
+#endif /* NOT_X_WINDOWS */
+
+static void gtkwin_clip_write(
+ TermWin *tw, int clipboard, wchar_t *data, int *attr,
+ truecolour *truecolour, int len, bool must_deselect)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ struct clipboard_state *state = &inst->clipstates[clipboard];
+
+ if (state->pasteout_data)
+ sfree(state->pasteout_data);
+ if (state->pasteout_data_ctext)
+ sfree(state->pasteout_data_ctext);
+ if (state->pasteout_data_utf8)
+ sfree(state->pasteout_data_utf8);
+
+ /*
+ * Set up UTF-8 and compound text paste data. This only happens
+ * if we aren't in direct-to-font mode using the D800 hack.
+ */
+ if (!inst->direct_to_font) {
+ const wchar_t *tmp = data;
+ int tmplen = len;
+#ifndef NOT_X_WINDOWS
+ XTextProperty tp;
+ char *list[1];
+#endif
+
+ state->pasteout_data_utf8 = snewn(len*6, char);
+ state->pasteout_data_utf8_len = len*6;
+ state->pasteout_data_utf8_len =
+ charset_from_unicode(&tmp, &tmplen, state->pasteout_data_utf8,
+ state->pasteout_data_utf8_len,
+ CS_UTF8, NULL, NULL, 0);
+ if (state->pasteout_data_utf8_len == 0) {
+ sfree(state->pasteout_data_utf8);
+ state->pasteout_data_utf8 = NULL;
+ } else {
+ state->pasteout_data_utf8 =
+ sresize(state->pasteout_data_utf8,
+ state->pasteout_data_utf8_len + 1, char);
+ state->pasteout_data_utf8[state->pasteout_data_utf8_len] = '\0';
+ }
+
+ /*
+ * Now let Xlib convert our UTF-8 data into compound text.
+ */
+#ifndef NOT_X_WINDOWS
+ list[0] = state->pasteout_data_utf8;
+ if (inst->disp && Xutf8TextListToTextProperty(
+ inst->disp, list, 1, XCompoundTextStyle, &tp) == 0) {
+ state->pasteout_data_ctext = snewn(tp.nitems+1, char);
+ memcpy(state->pasteout_data_ctext, tp.value, tp.nitems);
+ state->pasteout_data_ctext_len = tp.nitems;
+ XFree(tp.value);
+ } else
+#endif
+ {
+ state->pasteout_data_ctext = NULL;
+ state->pasteout_data_ctext_len = 0;
+ }
+ } else {
+ state->pasteout_data_utf8 = NULL;
+ state->pasteout_data_utf8_len = 0;
+ state->pasteout_data_ctext = NULL;
+ state->pasteout_data_ctext_len = 0;
+ }
+
+ state->pasteout_data = snewn(len*6, char);
+ state->pasteout_data_len = len*6;
+ state->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0,
+ data, len, state->pasteout_data,
+ state->pasteout_data_len, NULL);
+ if (state->pasteout_data_len == 0) {
+ sfree(state->pasteout_data);
+ state->pasteout_data = NULL;
+ } else {
+ state->pasteout_data =
+ sresize(state->pasteout_data, state->pasteout_data_len, char);
+ }
+
+#ifndef NOT_X_WINDOWS
+ /* The legacy X cut buffers go with PRIMARY, not any other clipboard */
+ if (state->atom == GDK_SELECTION_PRIMARY)
+ store_cutbuffer(inst, state->pasteout_data, state->pasteout_data_len);
+#endif
+
+ if (gtk_selection_owner_set(inst->area, state->atom,
+ inst->input_event_time)) {
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_selection_clear_targets(inst->area, state->atom);
+#endif
+ gtk_selection_add_target(inst->area, state->atom,
+ GDK_SELECTION_TYPE_STRING, 1);
+ if (state->pasteout_data_ctext)
+ gtk_selection_add_target(inst->area, state->atom,
+ compound_text_atom, 1);
+ if (state->pasteout_data_utf8)
+ gtk_selection_add_target(inst->area, state->atom,
+ utf8_string_atom, 1);
+ }
+
+ if (must_deselect)
+ term_lost_clipboard_ownership(inst->term, clipboard);
+}
+
+static void selection_get(GtkWidget *widget, GtkSelectionData *seldata,
+ guint info, guint time_stamp, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ GdkAtom target = gtk_selection_data_get_target(seldata);
+ struct clipboard_state *state = clipboard_from_atom(
+ inst, gtk_selection_data_get_selection(seldata));
+
+ if (!state)
+ return;
+
+ if (target == utf8_string_atom)
+ gtk_selection_data_set(seldata, target, 8,
+ (unsigned char *)state->pasteout_data_utf8,
+ state->pasteout_data_utf8_len);
+ else if (target == compound_text_atom)
+ gtk_selection_data_set(seldata, target, 8,
+ (unsigned char *)state->pasteout_data_ctext,
+ state->pasteout_data_ctext_len);
+ else
+ gtk_selection_data_set(seldata, target, 8,
+ (unsigned char *)state->pasteout_data,
+ state->pasteout_data_len);
+}
+
+static gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
+ gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ struct clipboard_state *state = clipboard_from_atom(
+ inst, seldata->selection);
+
+ if (!state)
+ return true;
+
+ term_lost_clipboard_ownership(inst->term, state->clipboard);
+ if (state->pasteout_data)
+ sfree(state->pasteout_data);
+ if (state->pasteout_data_ctext)
+ sfree(state->pasteout_data_ctext);
+ if (state->pasteout_data_utf8)
+ sfree(state->pasteout_data_utf8);
+ state->pasteout_data = NULL;
+ state->pasteout_data_len = 0;
+ state->pasteout_data_ctext = NULL;
+ state->pasteout_data_ctext_len = 0;
+ state->pasteout_data_utf8 = NULL;
+ state->pasteout_data_utf8_len = 0;
+ return true;
+}
+
+static void gtkwin_clip_request_paste(TermWin *tw, int clipboard)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ struct clipboard_state *state = &inst->clipstates[clipboard];
+
+ /*
+ * In Unix, pasting is asynchronous: all we can do at the
+ * moment is to call gtk_selection_convert(), and when the data
+ * comes back _then_ we can call term_do_paste().
+ */
+
+ if (!inst->direct_to_font) {
+ /*
+ * First we attempt to retrieve the selection as a UTF-8
+ * string (which we will convert to the correct code page
+ * before sending to the session, of course). If that
+ * fails, selection_received() will be informed and will
+ * fall back to an ordinary string.
+ */
+ gtk_selection_convert(inst->area, state->atom, utf8_string_atom,
+ inst->input_event_time);
+ } else {
+ /*
+ * If we're in direct-to-font mode, we disable UTF-8
+ * pasting, and go straight to ordinary string data.
+ */
+ gtk_selection_convert(inst->area, state->atom,
+ GDK_SELECTION_TYPE_STRING,
+ inst->input_event_time);
+ }
+}
+
+static void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
+ guint time, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ char *text;
+ int length;
+#ifndef NOT_X_WINDOWS
+ char **list;
+ bool free_list_required = false;
+ bool free_required = false;
+#endif
+ int charset;
+ GdkAtom seldata_target = gtk_selection_data_get_target(seldata);
+ GdkAtom seldata_type = gtk_selection_data_get_data_type(seldata);
+ const guchar *seldata_data = gtk_selection_data_get_data(seldata);
+ gint seldata_length = gtk_selection_data_get_length(seldata);
+ wchar_t *paste;
+ int paste_len;
+ struct clipboard_state *state = clipboard_from_atom(
+ inst, gtk_selection_data_get_selection(seldata));
+
+ if (!state)
+ return;
+
+ if (seldata_target == utf8_string_atom && seldata_length <= 0) {
+ /*
+ * Failed to get a UTF-8 selection string. Try compound
+ * text next.
+ */
+ gtk_selection_convert(inst->area, state->atom,
+ compound_text_atom,
+ inst->input_event_time);
+ return;
+ }
+
+ if (seldata_target == compound_text_atom && seldata_length <= 0) {
+ /*
+ * Failed to get UTF-8 or compound text. Try an ordinary
+ * string.
+ */
+ gtk_selection_convert(inst->area, state->atom,
+ GDK_SELECTION_TYPE_STRING,
+ inst->input_event_time);
+ return;
+ }
+
+ /*
+ * If we have data, but it's not of a type we can deal with,
+ * we have to ignore the data.
+ */
+ if (seldata_length > 0 &&
+ seldata_type != GDK_SELECTION_TYPE_STRING &&
+ seldata_type != compound_text_atom &&
+ seldata_type != utf8_string_atom)
+ return;
+
+ /*
+ * If we have no data, try looking in a cut buffer.
+ */
+ if (seldata_length <= 0) {
+#ifndef NOT_X_WINDOWS
+ text = retrieve_cutbuffer(inst, &length);
+ if (length == 0)
+ return;
+ /* Xterm is rumoured to expect Latin-1, though I haven't checked the
+ * source, so use that as a de-facto standard. */
+ charset = CS_ISO8859_1;
+ free_required = true;
+#else
+ return;
+#endif
+ } else {
+ /*
+ * Convert COMPOUND_TEXT into UTF-8.
+ */
+ if (seldata_type == compound_text_atom) {
+#ifndef NOT_X_WINDOWS
+ XTextProperty tp;
+ int ret, count;
+
+ tp.value = (unsigned char *)seldata_data;
+ tp.encoding = (Atom) seldata_type;
+ tp.format = gtk_selection_data_get_format(seldata);
+ tp.nitems = seldata_length;
+ ret = inst->disp == NULL ? -1 :
+ Xutf8TextPropertyToTextList(inst->disp, &tp, &list, &count);
+ if (ret == 0 && count == 1) {
+ text = list[0];
+ length = strlen(list[0]);
+ charset = CS_UTF8;
+ free_list_required = true;
+ } else
+#endif
+ {
+ /*
+ * Compound text failed; fall back to STRING.
+ */
+ gtk_selection_convert(inst->area, state->atom,
+ GDK_SELECTION_TYPE_STRING,
+ inst->input_event_time);
+ return;
+ }
+ } else {
+ text = (char *)seldata_data;
+ length = seldata_length;
+ charset = (seldata_type == utf8_string_atom ?
+ CS_UTF8 : inst->ucsdata.line_codepage);
+ }
+ }
+
+ paste = snewn(length, wchar_t);
+ paste_len = mb_to_wc(charset, 0, text, length, paste, length);
+
+ term_do_paste(inst->term, paste, paste_len);
+
+ sfree(paste);
+
+#ifndef NOT_X_WINDOWS
+ if (free_list_required)
+ XFreeStringList(list);
+ if (free_required)
+ XFree(text);
+#endif
+}
+
+static void init_one_clipboard(GtkFrontend *inst, int clipboard)
+{
+ struct clipboard_state *state = &inst->clipstates[clipboard];
+
+ state->inst = inst;
+ state->clipboard = clipboard;
+}
+
+void set_clipboard_atom(GtkFrontend *inst, int clipboard, GdkAtom atom)
+{
+ struct clipboard_state *state = &inst->clipstates[clipboard];
+
+ state->inst = inst;
+ state->clipboard = clipboard;
+
+ state->atom = atom;
+}
+
+void init_clipboard(GtkFrontend *inst)
+{
+#ifndef NOT_X_WINDOWS
+ /*
+ * Ensure that all the cut buffers exist - according to the ICCCM,
+ * we must do this before we start using cut buffers.
+ */
+ if (inst->disp) {
+ unsigned char empty[] = "";
+ x11_ignore_error(inst->disp, BadMatch);
+ XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER0,
+ XA_STRING, 8, PropModeAppend, empty, 0);
+ x11_ignore_error(inst->disp, BadMatch);
+ XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER1,
+ XA_STRING, 8, PropModeAppend, empty, 0);
+ x11_ignore_error(inst->disp, BadMatch);
+ XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER2,
+ XA_STRING, 8, PropModeAppend, empty, 0);
+ x11_ignore_error(inst->disp, BadMatch);
+ XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER3,
+ XA_STRING, 8, PropModeAppend, empty, 0);
+ x11_ignore_error(inst->disp, BadMatch);
+ XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER4,
+ XA_STRING, 8, PropModeAppend, empty, 0);
+ x11_ignore_error(inst->disp, BadMatch);
+ XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER5,
+ XA_STRING, 8, PropModeAppend, empty, 0);
+ x11_ignore_error(inst->disp, BadMatch);
+ XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER6,
+ XA_STRING, 8, PropModeAppend, empty, 0);
+ x11_ignore_error(inst->disp, BadMatch);
+ XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER7,
+ XA_STRING, 8, PropModeAppend, empty, 0);
+ }
+#endif
+
+ inst->clipstates[CLIP_PRIMARY].atom = GDK_SELECTION_PRIMARY;
+ inst->clipstates[CLIP_CLIPBOARD].atom = clipboard_atom;
+ init_one_clipboard(inst, CLIP_PRIMARY);
+ init_one_clipboard(inst, CLIP_CLIPBOARD);
+
+ g_signal_connect(G_OBJECT(inst->area), "selection_received",
+ G_CALLBACK(selection_received), inst);
+ g_signal_connect(G_OBJECT(inst->area), "selection_get",
+ G_CALLBACK(selection_get), inst);
+ g_signal_connect(G_OBJECT(inst->area), "selection_clear_event",
+ G_CALLBACK(selection_clear), inst);
+}
+
+/*
+ * End of selection/clipboard handling.
+ * ----------------------------------------------------------------------
+ */
+
+#endif /* JUST_USE_GTK_CLIPBOARD_UTF8 */
+
+static void set_window_titles(GtkFrontend *inst)
+{
+ /*
+ * We must always call set_icon_name after calling set_title,
+ * since set_title will write both names. Irritating, but such
+ * is life.
+ */
+ gtk_window_set_title(GTK_WINDOW(inst->window), inst->wintitle);
+ if (!conf_get_bool(inst->conf, CONF_win_name_always))
+ gdk_window_set_icon_name(gtk_widget_get_window(inst->window),
+ inst->icontitle);
+}
+
+static void gtkwin_set_title(TermWin *tw, const char *title, int codepage)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ sfree(inst->wintitle);
+ if (codepage != CP_UTF8) {
+ wchar_t *title_w = dup_mb_to_wc(codepage, 0, title);
+ inst->wintitle = encode_wide_string_as_utf8(title_w);
+ sfree(title_w);
+ } else {
+ inst->wintitle = dupstr(title);
+ }
+ set_window_titles(inst);
+}
+
+static void gtkwin_set_icon_title(TermWin *tw, const char *title, int codepage)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ sfree(inst->icontitle);
+ if (codepage != CP_UTF8) {
+ wchar_t *title_w = dup_mb_to_wc(codepage, 0, title);
+ inst->icontitle = encode_wide_string_as_utf8(title_w);
+ sfree(title_w);
+ } else {
+ inst->icontitle = dupstr(title);
+ }
+ set_window_titles(inst);
+}
+
+static void gtkwin_set_scrollbar(TermWin *tw, int total, int start, int page)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ if (!conf_get_bool(inst->conf, CONF_scrollbar))
+ return;
+ inst->ignore_sbar = true;
+ gtk_adjustment_set_lower(inst->sbar_adjust, 0);
+ gtk_adjustment_set_upper(inst->sbar_adjust, total);
+ gtk_adjustment_set_value(inst->sbar_adjust, start);
+ gtk_adjustment_set_page_size(inst->sbar_adjust, page);
+ gtk_adjustment_set_step_increment(inst->sbar_adjust, 1);
+ gtk_adjustment_set_page_increment(inst->sbar_adjust, page/2);
+#if !GTK_CHECK_VERSION(3,18,0)
+ gtk_adjustment_changed(inst->sbar_adjust);
+#endif
+ inst->ignore_sbar = false;
+}
+
+void scrollbar_moved(GtkAdjustment *adj, GtkFrontend *inst)
+{
+ if (!conf_get_bool(inst->conf, CONF_scrollbar))
+ return;
+ if (!inst->ignore_sbar)
+ term_scroll(inst->term, 1, (int)gtk_adjustment_get_value(adj));
+}
+
+static void show_scrollbar(GtkFrontend *inst, gboolean visible)
+{
+ inst->sbar_visible = visible;
+ if (visible)
+ gtk_widget_show(inst->sbar);
+ else
+ gtk_widget_hide(inst->sbar);
+}
+
+static void gtkwin_set_cursor_pos(TermWin *tw, int x, int y)
+{
+ /*
+ * This is meaningless under X.
+ */
+}
+
+/*
+ * This is still called when mode==BELL_VISUAL, even though the
+ * visual bell is handled entirely within terminal.c, because we
+ * may want to perform additional actions on any kind of bell (for
+ * example, taskbar flashing in Windows).
+ */
+static void gtkwin_bell(TermWin *tw, int mode)
+{
+ /* GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); */
+ if (mode == BELL_DEFAULT)
+ gdk_display_beep(gdk_display_get_default());
+}
+
+static int gtkwin_char_width(TermWin *tw, int uc)
+{
+ /*
+ * In this front end, double-width characters are handled using a
+ * separate font, so this can safely just return 1 always.
+ */
+ return 1;
+}
+
+static bool gtkwin_setup_draw_ctx(TermWin *tw)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+
+ if (!gtk_widget_get_window(inst->area))
+ return false;
+
+ inst->uctx.type = inst->drawtype;
+#ifdef DRAW_TEXT_GDK
+ if (inst->uctx.type == DRAWTYPE_GDK) {
+ /* If we're doing GDK-based drawing, then we also expect
+ * inst->pixmap to exist. */
+ inst->uctx.u.gdk.target = inst->pixmap;
+ inst->uctx.u.gdk.gc = gdk_gc_new(gtk_widget_get_window(inst->area));
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (inst->uctx.type == DRAWTYPE_CAIRO) {
+ inst->uctx.u.cairo.widget = GTK_WIDGET(inst->area);
+ /* If we're doing Cairo drawing, we expect inst->surface to
+ * exist, and we draw to that first, regardless of whether we
+ * subsequently copy the results to inst->pixmap. */
+ inst->uctx.u.cairo.cr = cairo_create(inst->surface);
+ cairo_scale(inst->uctx.u.cairo.cr, inst->scale, inst->scale);
+ cairo_setup_draw_ctx(inst);
+ }
+#endif
+ return true;
+}
+
+static void gtkwin_free_draw_ctx(TermWin *tw)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+#ifdef DRAW_TEXT_GDK
+ if (inst->uctx.type == DRAWTYPE_GDK) {
+ gdk_gc_unref(inst->uctx.u.gdk.gc);
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (inst->uctx.type == DRAWTYPE_CAIRO) {
+ cairo_destroy(inst->uctx.u.cairo.cr);
+ }
+#endif
+}
+
+
+static void draw_update(GtkFrontend *inst, int x, int y, int w, int h)
+{
+#if defined DRAW_TEXT_CAIRO && !defined NO_BACKING_PIXMAPS
+ if (inst->uctx.type == DRAWTYPE_CAIRO) {
+ /*
+ * If inst->surface and inst->pixmap both exist, then we've
+ * just drawn new content to the former which we must copy to
+ * the latter.
+ */
+ cairo_t *cr = gdk_cairo_create(inst->pixmap);
+ cairo_set_source_surface(cr, inst->surface, 0, 0);
+ cairo_rectangle(cr, x, y, w, h);
+ cairo_fill(cr);
+ cairo_destroy(cr);
+ }
+#endif
+
+ /*
+ * Now we just queue a window redraw, which will cause
+ * inst->surface or inst->pixmap (whichever is appropriate for our
+ * compile mode) to be copied to the real window when we receive
+ * the resulting "expose" or "draw" event.
+ *
+ * Amazingly, this one API call is actually valid in all versions
+ * of GTK :-)
+ */
+ gtk_widget_queue_draw_area(inst->area, x, y, w, h);
+}
+
+#ifdef DRAW_TEXT_CAIRO
+static void cairo_set_source_rgb_dim(cairo_t *cr, double r, double g, double b,
+ bool dim)
+{
+ if (dim)
+ cairo_set_source_rgb(cr, r * 2 / 3, g * 2 / 3, b * 2 / 3);
+ else
+ cairo_set_source_rgb(cr, r, g, b);
+}
+#endif
+
+static void draw_set_colour(GtkFrontend *inst, int col, bool dim)
+{
+#ifdef DRAW_TEXT_GDK
+ if (inst->uctx.type == DRAWTYPE_GDK) {
+ if (dim) {
+#if GTK_CHECK_VERSION(2,0,0)
+ GdkColor color;
+ color.red = inst->cols[col].red * 2 / 3;
+ color.green = inst->cols[col].green * 2 / 3;
+ color.blue = inst->cols[col].blue * 2 / 3;
+ gdk_gc_set_rgb_fg_color(inst->uctx.u.gdk.gc, &color);
+#else
+ /* Poor GTK1 fallback */
+ gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[col]);
+#endif
+ } else {
+ gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[col]);
+ }
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (inst->uctx.type == DRAWTYPE_CAIRO) {
+ cairo_set_source_rgb_dim(inst->uctx.u.cairo.cr,
+ inst->cols[col].red / 65535.0,
+ inst->cols[col].green / 65535.0,
+ inst->cols[col].blue / 65535.0, dim);
+ }
+#endif
+}
+
+static void draw_set_colour_rgb(GtkFrontend *inst, optionalrgb orgb, bool dim)
+{
+#ifdef DRAW_TEXT_GDK
+ if (inst->uctx.type == DRAWTYPE_GDK) {
+#if GTK_CHECK_VERSION(2,0,0)
+ GdkColor color;
+ color.red = orgb.r * 256;
+ color.green = orgb.g * 256;
+ color.blue = orgb.b * 256;
+ if (dim) {
+ color.red = color.red * 2 / 3;
+ color.green = color.green * 2 / 3;
+ color.blue = color.blue * 2 / 3;
+ }
+ gdk_gc_set_rgb_fg_color(inst->uctx.u.gdk.gc, &color);
+#else
+ /* Poor GTK1 fallback */
+ gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[256]);
+#endif
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (inst->uctx.type == DRAWTYPE_CAIRO) {
+ cairo_set_source_rgb_dim(inst->uctx.u.cairo.cr, orgb.r / 255.0,
+ orgb.g / 255.0, orgb.b / 255.0, dim);
+ }
+#endif
+}
+
+static void draw_rectangle(GtkFrontend *inst, bool filled,
+ int x, int y, int w, int h)
+{
+#ifdef DRAW_TEXT_GDK
+ if (inst->uctx.type == DRAWTYPE_GDK) {
+ gdk_draw_rectangle(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc,
+ filled, x, y, w, h);
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (inst->uctx.type == DRAWTYPE_CAIRO) {
+ cairo_new_path(inst->uctx.u.cairo.cr);
+ if (filled) {
+ cairo_rectangle(inst->uctx.u.cairo.cr, x, y, w, h);
+ cairo_fill(inst->uctx.u.cairo.cr);
+ } else {
+ cairo_rectangle(inst->uctx.u.cairo.cr,
+ x + 0.5, y + 0.5, w, h);
+ cairo_close_path(inst->uctx.u.cairo.cr);
+ cairo_stroke(inst->uctx.u.cairo.cr);
+ }
+ }
+#endif
+}
+
+static void draw_clip(GtkFrontend *inst, int x, int y, int w, int h)
+{
+#ifdef DRAW_TEXT_GDK
+ if (inst->uctx.type == DRAWTYPE_GDK) {
+ GdkRectangle r;
+
+ r.x = x;
+ r.y = y;
+ r.width = w;
+ r.height = h;
+
+ gdk_gc_set_clip_rectangle(inst->uctx.u.gdk.gc, &r);
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (inst->uctx.type == DRAWTYPE_CAIRO) {
+ cairo_reset_clip(inst->uctx.u.cairo.cr);
+ cairo_new_path(inst->uctx.u.cairo.cr);
+ cairo_rectangle(inst->uctx.u.cairo.cr, x, y, w, h);
+ cairo_clip(inst->uctx.u.cairo.cr);
+ }
+#endif
+}
+
+static void draw_point(GtkFrontend *inst, int x, int y)
+{
+#ifdef DRAW_TEXT_GDK
+ if (inst->uctx.type == DRAWTYPE_GDK) {
+ gdk_draw_point(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, x, y);
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (inst->uctx.type == DRAWTYPE_CAIRO) {
+ cairo_new_path(inst->uctx.u.cairo.cr);
+ cairo_rectangle(inst->uctx.u.cairo.cr, x, y, 1, 1);
+ cairo_fill(inst->uctx.u.cairo.cr);
+ }
+#endif
+}
+
+static void draw_line(GtkFrontend *inst, int x0, int y0, int x1, int y1)
+{
+#ifdef DRAW_TEXT_GDK
+ if (inst->uctx.type == DRAWTYPE_GDK) {
+ gdk_draw_line(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc,
+ x0, y0, x1, y1);
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (inst->uctx.type == DRAWTYPE_CAIRO) {
+ cairo_new_path(inst->uctx.u.cairo.cr);
+ cairo_move_to(inst->uctx.u.cairo.cr, x0 + 0.5, y0 + 0.5);
+ cairo_line_to(inst->uctx.u.cairo.cr, x1 + 0.5, y1 + 0.5);
+ cairo_stroke(inst->uctx.u.cairo.cr);
+ }
+#endif
+}
+
+static void draw_stretch_before(GtkFrontend *inst, int x, int y,
+ int w, bool wdouble,
+ int h, bool hdouble, bool hbothalf)
+{
+#ifdef DRAW_TEXT_CAIRO
+ if (inst->uctx.type == DRAWTYPE_CAIRO) {
+ cairo_matrix_t matrix;
+
+ matrix.xy = 0;
+ matrix.yx = 0;
+
+ if (wdouble) {
+ matrix.xx = 2;
+ matrix.x0 = -x;
+ } else {
+ matrix.xx = 1;
+ matrix.x0 = 0;
+ }
+
+ if (hdouble) {
+ matrix.yy = 2;
+ if (hbothalf) {
+ matrix.y0 = -(y+h);
+ } else {
+ matrix.y0 = -y;
+ }
+ } else {
+ matrix.yy = 1;
+ matrix.y0 = 0;
+ }
+ cairo_transform(inst->uctx.u.cairo.cr, &matrix);
+ }
+#endif
+}
+
+static void draw_stretch_after(GtkFrontend *inst, int x, int y,
+ int w, bool wdouble,
+ int h, bool hdouble, bool hbothalf)
+{
+#ifdef DRAW_TEXT_GDK
+#ifndef NO_BACKING_PIXMAPS
+ if (inst->uctx.type == DRAWTYPE_GDK) {
+ /*
+ * I can't find any plausible StretchBlt equivalent in the X
+ * server, so I'm going to do this the slow and painful way.
+ * This will involve repeated calls to gdk_draw_pixmap() to
+ * stretch the text horizontally. It's O(N^2) in time and O(N)
+ * in network bandwidth, but you try thinking of a better way.
+ * :-(
+ */
+ int i;
+ if (wdouble) {
+ for (i = 0; i < w; i++) {
+ gdk_draw_pixmap(inst->uctx.u.gdk.target,
+ inst->uctx.u.gdk.gc,
+ inst->uctx.u.gdk.target,
+ x + 2*i, y,
+ x + 2*i+1, y,
+ w - i, h);
+ }
+ w *= 2;
+ }
+
+ if (hdouble) {
+ int dt, db;
+ /* Now stretch vertically, in the same way. */
+ if (hbothalf)
+ dt = 0, db = 1;
+ else
+ dt = 1, db = 0;
+ for (i = 0; i < h; i += 2) {
+ gdk_draw_pixmap(inst->uctx.u.gdk.target,
+ inst->uctx.u.gdk.gc,
+ inst->uctx.u.gdk.target,
+ x, y + dt*i + db,
+ x, y + dt*(i+1),
+ w, h-i-1);
+ }
+ }
+ }
+#else
+#error No way to implement stretching in GDK without a reliable backing pixmap
+#endif
+#endif /* DRAW_TEXT_GDK */
+#ifdef DRAW_TEXT_CAIRO
+ if (inst->uctx.type == DRAWTYPE_CAIRO) {
+ cairo_set_matrix(inst->uctx.u.cairo.cr,
+ &inst->uctx.u.cairo.origmatrix);
+ }
+#endif
+}
+
+static void draw_backing_rect(GtkFrontend *inst)
+{
+ if (!win_setup_draw_ctx(&inst->termwin))
+ return;
+
+ draw_set_colour(inst, 258, false);
+ draw_rectangle(inst, true, 0, 0, inst->backing_w, inst->backing_h);
+ draw_update(inst, 0, 0, inst->backing_w, inst->backing_h);
+ win_free_draw_ctx(&inst->termwin);
+}
+
+/*
+ * Draw a line of text in the window, at given character
+ * coordinates, in given attributes.
+ *
+ * We are allowed to fiddle with the contents of `text'.
+ */
+static void do_text_internal(
+ GtkFrontend *inst, int x, int y, wchar_t *text, int len,
+ unsigned long attr, int lattr, truecolour truecolour)
+{
+ int ncombining;
+ int nfg, nbg, t, fontid, rlen, widefactor;
+ bool bold;
+ bool monochrome =
+ gdk_visual_get_depth(gtk_widget_get_visual(inst->area)) == 1;
+
+ if (attr & TATTR_COMBINING) {
+ ncombining = len;
+ len = 1;
+ } else
+ ncombining = 1;
+
+ if (monochrome)
+ truecolour.fg = truecolour.bg = optionalrgb_none;
+
+ nfg = ((monochrome ? ATTR_DEFFG : (attr & ATTR_FGMASK)) >> ATTR_FGSHIFT);
+ nbg = ((monochrome ? ATTR_DEFBG : (attr & ATTR_BGMASK)) >> ATTR_BGSHIFT);
+ if (!!(attr & ATTR_REVERSE) ^ (monochrome && (attr & TATTR_ACTCURS))) {
+ struct optionalrgb trgb;
+
+ t = nfg;
+ nfg = nbg;
+ nbg = t;
+
+ trgb = truecolour.fg;
+ truecolour.fg = truecolour.bg;
+ truecolour.bg = trgb;
+ }
+ if ((inst->bold_style & 2) && (attr & ATTR_BOLD)) {
+ if (nfg < 16) nfg |= 8;
+ else if (nfg >= 256) nfg |= 1;
+ }
+ if ((inst->bold_style & 2) && (attr & ATTR_BLINK)) {
+ if (nbg < 16) nbg |= 8;
+ else if (nbg >= 256) nbg |= 1;
+ }
+ if ((attr & TATTR_ACTCURS) && !monochrome) {
+ truecolour.fg = truecolour.bg = optionalrgb_none;
+ nfg = 260;
+ nbg = 261;
+ attr &= ~ATTR_DIM; /* don't dim the cursor */
+ }
+
+ fontid = 0;
+
+ if (attr & ATTR_WIDE) {
+ widefactor = 2;
+ fontid |= 2;
+ } else {
+ widefactor = 1;
+ }
+
+ if ((attr & ATTR_BOLD) && (inst->bold_style & 1)) {
+ bold = true;
+ fontid |= 1;
+ } else {
+ bold = false;
+ }
+
+ if (!inst->fonts[fontid]) {
+ int i;
+ /*
+ * Fall back through font ids with subsets of this one's
+ * set bits, in order.
+ */
+ for (i = fontid; i-- > 0 ;) {
+ if (i & ~fontid)
+ continue; /* some other bit is set */
+ if (inst->fonts[i]) {
+ fontid = i;
+ break;
+ }
+ }
+ assert(inst->fonts[fontid]); /* we should at least have hit zero */
+ }
+
+ if ((lattr & LATTR_MODE) != LATTR_NORM) {
+ x *= 2;
+ if (x >= inst->term->cols)
+ return;
+ if (x + len*2*widefactor > inst->term->cols) {
+ len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */
+ if (len == 0)
+ return; /* rounded down half a double-width char to zero */
+ }
+ rlen = len * 2;
+ } else
+ rlen = len;
+
+ draw_clip(inst,
+ x*inst->font_width+inst->window_border,
+ y*inst->font_height+inst->window_border,
+ rlen*widefactor*inst->font_width,
+ inst->font_height);
+
+ if ((lattr & LATTR_MODE) != LATTR_NORM) {
+ draw_stretch_before(inst,
+ x*inst->font_width+inst->window_border,
+ y*inst->font_height+inst->window_border,
+ rlen*widefactor*inst->font_width, true,
+ inst->font_height,
+ ((lattr & LATTR_MODE) != LATTR_WIDE),
+ ((lattr & LATTR_MODE) == LATTR_BOT));
+ }
+
+ if (truecolour.bg.enabled)
+ draw_set_colour_rgb(inst, truecolour.bg, attr & ATTR_DIM);
+ else
+ draw_set_colour(inst, nbg, attr & ATTR_DIM);
+ draw_rectangle(inst, true,
+ x*inst->font_width+inst->window_border,
+ y*inst->font_height+inst->window_border,
+ rlen*widefactor*inst->font_width, inst->font_height);
+
+ if (truecolour.fg.enabled)
+ draw_set_colour_rgb(inst, truecolour.fg, attr & ATTR_DIM);
+ else
+ draw_set_colour(inst, nfg, attr & ATTR_DIM);
+ if (ncombining > 1) {
+ assert(len == 1);
+ unifont_draw_combining(&inst->uctx, inst->fonts[fontid],
+ x*inst->font_width+inst->window_border,
+ (y*inst->font_height+inst->window_border+
+ inst->fonts[0]->ascent),
+ text, ncombining, widefactor > 1,
+ bold, inst->font_width);
+ } else {
+ unifont_draw_text(&inst->uctx, inst->fonts[fontid],
+ x*inst->font_width+inst->window_border,
+ (y*inst->font_height+inst->window_border+
+ inst->fonts[0]->ascent),
+ text, len, widefactor > 1,
+ bold, inst->font_width);
+ }
+
+ if (attr & ATTR_UNDER) {
+ int uheight = inst->fonts[0]->ascent + 1;
+ if (uheight >= inst->font_height)
+ uheight = inst->font_height - 1;
+ draw_line(inst, x*inst->font_width+inst->window_border,
+ y*inst->font_height + uheight + inst->window_border,
+ (x+len)*widefactor*inst->font_width-1+inst->window_border,
+ y*inst->font_height + uheight + inst->window_border);
+ }
+
+ if (attr & ATTR_STRIKE) {
+ int sheight = inst->fonts[fontid]->strikethrough_y;
+ draw_line(inst, x*inst->font_width+inst->window_border,
+ y*inst->font_height + sheight + inst->window_border,
+ (x+len)*widefactor*inst->font_width-1+inst->window_border,
+ y*inst->font_height + sheight + inst->window_border);
+ }
+
+ if ((lattr & LATTR_MODE) != LATTR_NORM) {
+ draw_stretch_after(inst,
+ x*inst->font_width+inst->window_border,
+ y*inst->font_height+inst->window_border,
+ rlen*widefactor*inst->font_width, true,
+ inst->font_height,
+ ((lattr & LATTR_MODE) != LATTR_WIDE),
+ ((lattr & LATTR_MODE) == LATTR_BOT));
+ }
+}
+
+static void gtkwin_draw_text(
+ TermWin *tw, int x, int y, wchar_t *text, int len,
+ unsigned long attr, int lattr, truecolour truecolour)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ int widefactor;
+
+ do_text_internal(inst, x, y, text, len, attr, lattr, truecolour);
+
+ if (attr & ATTR_WIDE) {
+ widefactor = 2;
+ } else {
+ widefactor = 1;
+ }
+
+ if ((lattr & LATTR_MODE) != LATTR_NORM) {
+ x *= 2;
+ if (x >= inst->term->cols)
+ return;
+ if (x + len*2*widefactor > inst->term->cols)
+ len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */
+ len *= 2;
+ }
+
+ draw_update(inst,
+ x*inst->font_width+inst->window_border,
+ y*inst->font_height+inst->window_border,
+ len*widefactor*inst->font_width, inst->font_height);
+}
+
+static void gtkwin_draw_cursor(
+ TermWin *tw, int x, int y, wchar_t *text, int len,
+ unsigned long attr, int lattr, truecolour truecolour)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+ bool active, passive;
+ int widefactor;
+
+ if (attr & TATTR_PASCURS) {
+ attr &= ~TATTR_PASCURS;
+ passive = true;
+ } else
+ passive = false;
+ if ((attr & TATTR_ACTCURS) && inst->cursor_type != 0) {
+ attr &= ~TATTR_ACTCURS;
+ active = true;
+ } else
+ active = false;
+ do_text_internal(inst, x, y, text, len, attr, lattr, truecolour);
+
+ if (attr & TATTR_COMBINING)
+ len = 1;
+
+ if (attr & ATTR_WIDE) {
+ widefactor = 2;
+ } else {
+ widefactor = 1;
+ }
+
+ if ((lattr & LATTR_MODE) != LATTR_NORM) {
+ x *= 2;
+ if (x >= inst->term->cols)
+ return;
+ if (x + len*2*widefactor > inst->term->cols)
+ len = (inst->term->cols-x)/2/widefactor;/* trim to LH half */
+ len *= 2;
+ }
+
+ if (inst->cursor_type == 0) {
+ /*
+ * An active block cursor will already have been done by
+ * the above do_text call, so we only need to do anything
+ * if it's passive.
+ */
+ if (passive) {
+ draw_set_colour(inst, 261, false);
+ draw_rectangle(inst, false,
+ x*inst->font_width+inst->window_border,
+ y*inst->font_height+inst->window_border,
+ len*widefactor*inst->font_width-1,
+ inst->font_height-1);
+ }
+ } else {
+ int uheight;
+ int startx, starty, dx, dy, length, i;
+
+ int char_width;
+
+ if ((attr & ATTR_WIDE) || (lattr & LATTR_MODE) != LATTR_NORM)
+ char_width = 2*inst->font_width;
+ else
+ char_width = inst->font_width;
+
+ if (inst->cursor_type == 1) {
+ uheight = inst->fonts[0]->ascent + 1;
+ if (uheight >= inst->font_height)
+ uheight = inst->font_height - 1;
+
+ startx = x * inst->font_width + inst->window_border;
+ starty = y * inst->font_height + inst->window_border + uheight;
+ dx = 1;
+ dy = 0;
+ length = len * widefactor * char_width;
+ } else {
+ int xadjust = 0;
+ if (attr & TATTR_RIGHTCURS)
+ xadjust = char_width - 1;
+ startx = x * inst->font_width + inst->window_border + xadjust;
+ starty = y * inst->font_height + inst->window_border;
+ dx = 0;
+ dy = 1;
+ length = inst->font_height;
+ }
+
+ draw_set_colour(inst, 261, false);
+ if (passive) {
+ for (i = 0; i < length; i++) {
+ if (i % 2 == 0) {
+ draw_point(inst, startx, starty);
+ }
+ startx += dx;
+ starty += dy;
+ }
+ } else if (active) {
+ draw_line(inst, startx, starty,
+ startx + (length-1) * dx, starty + (length-1) * dy);
+ } /* else no cursor (e.g., blinked off) */
+ }
+
+ draw_update(inst,
+ x*inst->font_width+inst->window_border,
+ y*inst->font_height+inst->window_border,
+ len*widefactor*inst->font_width, inst->font_height);
+
+#if GTK_CHECK_VERSION(2,0,0)
+ {
+ GdkRectangle cursorrect;
+ cursorrect.x = x*inst->font_width+inst->window_border;
+ cursorrect.y = y*inst->font_height+inst->window_border;
+ cursorrect.width = len*widefactor*inst->font_width;
+ cursorrect.height = inst->font_height;
+ gtk_im_context_set_cursor_location(inst->imc, &cursorrect);
+ }
+#endif
+}
+
+#if !GTK_CHECK_VERSION(2,0,0)
+/*
+ * For GTK 1, manual code to scale an in-memory XPM, producing a new
+ * one as output. It will be ugly, but good enough to use as a trust
+ * sigil.
+ */
+struct XpmHolder {
+ char **strings;
+ size_t nstrings;
+};
+
+static void xpmholder_free(XpmHolder *xh)
+{
+ for (size_t i = 0; i < xh->nstrings; i++)
+ sfree(xh->strings[i]);
+ sfree(xh->strings);
+ sfree(xh);
+}
+
+static XpmHolder *xpm_scale(const char *const *xpm, int wo, int ho)
+{
+ /* Get image dimensions, # colours, and chars-per-pixel */
+ int wi = 0, hi = 0, nc = 0, cpp = 0;
+ int retd = sscanf(xpm[0], "%d %d %d %d", &wi, &hi, &nc, &cpp);
+ assert(retd == 4);
+
+ /* Make output XpmHolder */
+ XpmHolder *xh = snew(XpmHolder);
+ xh->nstrings = 1 + nc + ho;
+ xh->strings = snewn(xh->nstrings, char *);
+
+ /* Set up header */
+ xh->strings[0] = dupprintf("%d %d %d %d", wo, ho, nc, cpp);
+ for (int i = 0; i < nc; i++)
+ xh->strings[1 + i] = dupstr(xpm[1 + i]);
+
+ /* Scale image */
+ for (int yo = 0; yo < ho; yo++) {
+ int yi = yo * hi / ho;
+ char *ro = snewn(cpp * wo + 1, char);
+ ro[cpp * wo] = '\0';
+ xh->strings[1 + nc + yo] = ro;
+ const char *ri = xpm[1 + nc + yi];
+
+ for (int xo = 0; xo < wo; xo++) {
+ int xi = xo * wi / wo;
+ memcpy(ro + cpp * xo, ri + cpp * xi, cpp);
+ }
+ }
+
+ return xh;
+}
+#endif /* !GTK_CHECK_VERSION(2,0,0) */
+
+static void gtkwin_draw_trust_sigil(TermWin *tw, int cx, int cy)
+{
+ GtkFrontend *inst = container_of(tw, GtkFrontend, termwin);
+
+ int x = cx * inst->font_width + inst->window_border;
+ int y = cy * inst->font_height + inst->window_border;
+ int w = 2*inst->font_width, h = inst->font_height;
+
+ if (inst->trust_sigil_w != w || inst->trust_sigil_h != h ||
+#if GTK_CHECK_VERSION(2,0,0)
+ !inst->trust_sigil_pb
+#else
+ !inst->trust_sigil_pm
+#endif
+ ) {
+
+#if GTK_CHECK_VERSION(2,0,0)
+ if (inst->trust_sigil_pb)
+ g_object_unref(G_OBJECT(inst->trust_sigil_pb));
+#else
+ if (inst->trust_sigil_pm)
+ gdk_pixmap_unref(inst->trust_sigil_pm);
+#endif
+
+ int best_icon_index = 0;
+ unsigned score = UINT_MAX;
+ for (int i = 0; i < n_main_icon; i++) {
+ int iw, ih;
+ if (sscanf(main_icon[i][0], "%d %d", &iw, &ih) == 2) {
+ int this_excess = (iw + ih) - (w + h);
+ unsigned this_score = (abs(this_excess) |
+ (this_excess > 0 ? 0 : 0x80000000U));
+ if (this_score < score) {
+ best_icon_index = i;
+ score = this_score;
+ }
+ }
+ }
+
+#if GTK_CHECK_VERSION(2,0,0)
+ GdkPixbuf *icon_unscaled = gdk_pixbuf_new_from_xpm_data(
+ (const gchar **)main_icon[best_icon_index]);
+ inst->trust_sigil_pb = gdk_pixbuf_scale_simple(
+ icon_unscaled, w, h, GDK_INTERP_BILINEAR);
+ g_object_unref(G_OBJECT(icon_unscaled));
+#else
+ XpmHolder *xh = xpm_scale(main_icon[best_icon_index], w, h);
+ inst->trust_sigil_pm = gdk_pixmap_create_from_xpm_d(
+ gtk_widget_get_window(inst->window), NULL,
+ &inst->cols[258], xh->strings);
+ xpmholder_free(xh);
+#endif
+
+ inst->trust_sigil_w = w;
+ inst->trust_sigil_h = h;
+ }
+
+#ifdef DRAW_TEXT_GDK
+ if (inst->uctx.type == DRAWTYPE_GDK) {
+#if GTK_CHECK_VERSION(2,0,0)
+ gdk_draw_pixbuf(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc,
+ inst->trust_sigil_pb, 0, 0, x, y, w, h,
+ GDK_RGB_DITHER_NORMAL, 0, 0);
+#else
+ gdk_draw_pixmap(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc,
+ inst->trust_sigil_pm, 0, 0, x, y, w, h);
+#endif
+ }
+#endif
+#ifdef DRAW_TEXT_CAIRO
+ if (inst->uctx.type == DRAWTYPE_CAIRO) {
+ inst->uctx.u.cairo.widget = GTK_WIDGET(inst->area);
+ cairo_save(inst->uctx.u.cairo.cr);
+ cairo_translate(inst->uctx.u.cairo.cr, x, y);
+ gdk_cairo_set_source_pixbuf(inst->uctx.u.cairo.cr,
+ inst->trust_sigil_pb, 0, 0);
+ cairo_rectangle(inst->uctx.u.cairo.cr, 0, 0, w, h);
+ cairo_fill(inst->uctx.u.cairo.cr);
+ cairo_restore(inst->uctx.u.cairo.cr);
+ }
+#endif
+
+ draw_update(inst, x, y, w, h);
+}
+
+GdkCursor *make_mouse_ptr(GtkFrontend *inst, int cursor_val)
+{
+ if (cursor_val == -1) {
+#if GTK_CHECK_VERSION(2,16,0)
+ cursor_val = GDK_BLANK_CURSOR;
+#else
+ /*
+ * Work around absence of GDK_BLANK_CURSOR by inventing a
+ * blank pixmap.
+ */
+ GdkCursor *ret;
+ GdkColor bg = { 0, 0, 0, 0 };
+ GdkPixmap *pm = gdk_pixmap_new(NULL, 1, 1, 1);
+ GdkGC *gc = gdk_gc_new(pm);
+ gdk_gc_set_foreground(gc, &bg);
+ gdk_draw_rectangle(pm, gc, 1, 0, 0, 1, 1);
+ gdk_gc_unref(gc);
+ ret = gdk_cursor_new_from_pixmap(pm, pm, &bg, &bg, 1, 1);
+ gdk_pixmap_unref(pm);
+ return ret;
+#endif
+ }
+
+ return gdk_cursor_new(cursor_val);
+}
+
+void modalfatalbox(const char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "FATAL ERROR: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ exit(1);
+}
+
+static const char *gtk_seat_get_x_display(Seat *seat)
+{
+ return gdk_get_display();
+}
+
+#ifndef NOT_X_WINDOWS
+static bool gtk_seat_get_windowid(Seat *seat, long *id)
+{
+ GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
+ GdkWindow *window = gtk_widget_get_window(inst->area);
+ if (!GDK_IS_X11_WINDOW(window))
+ return false;
+ *id = GDK_WINDOW_XID(window);
+ return true;
+}
+#endif
+
+char *setup_fonts_ucs(GtkFrontend *inst)
+{
+ bool shadowbold = conf_get_bool(inst->conf, CONF_shadowbold);
+ int shadowboldoffset = conf_get_int(inst->conf, CONF_shadowboldoffset);
+ FontSpec *fs;
+ unifont *fonts[4];
+ int i;
+
+ fs = conf_get_fontspec(inst->conf, CONF_font);
+ fonts[0] = multifont_create(inst->area, fs->name, false, false,
+ shadowboldoffset, shadowbold);
+ if (!fonts[0]) {
+ return dupprintf("unable to load font \"%s\"", fs->name);
+ }
+
+ fs = conf_get_fontspec(inst->conf, CONF_boldfont);
+ if (shadowbold || !fs->name[0]) {
+ fonts[1] = NULL;
+ } else {
+ fonts[1] = multifont_create(inst->area, fs->name, false, true,
+ shadowboldoffset, shadowbold);
+ if (!fonts[1]) {
+ if (fonts[0])
+ unifont_destroy(fonts[0]);
+ return dupprintf("unable to load bold font \"%s\"", fs->name);
+ }
+ }
+
+ fs = conf_get_fontspec(inst->conf, CONF_widefont);
+ if (fs->name[0]) {
+ fonts[2] = multifont_create(inst->area, fs->name, true, false,
+ shadowboldoffset, shadowbold);
+ if (!fonts[2]) {
+ for (i = 0; i < 2; i++)
+ if (fonts[i])
+ unifont_destroy(fonts[i]);
+ return dupprintf("unable to load wide font \"%s\"", fs->name);
+ }
+ } else {
+ fonts[2] = NULL;
+ }
+
+ fs = conf_get_fontspec(inst->conf, CONF_wideboldfont);
+ if (shadowbold || !fs->name[0]) {
+ fonts[3] = NULL;
+ } else {
+ fonts[3] = multifont_create(inst->area, fs->name, true, true,
+ shadowboldoffset, shadowbold);
+ if (!fonts[3]) {
+ for (i = 0; i < 3; i++)
+ if (fonts[i])
+ unifont_destroy(fonts[i]);
+ return dupprintf("unable to load wide bold font \"%s\"", fs->name);
+ }
+ }
+
+ /*
+ * Now we've got past all the possible error conditions, we can
+ * actually update our state.
+ */
+
+ for (i = 0; i < 4; i++) {
+ if (inst->fonts[i])
+ unifont_destroy(inst->fonts[i]);
+ inst->fonts[i] = fonts[i];
+ }
+
+ if (inst->font_width != inst->fonts[0]->width ||
+ inst->font_height != inst->fonts[0]->height) {
+
+ inst->font_width = inst->fonts[0]->width;
+ inst->font_height = inst->fonts[0]->height;
+
+ /*
+ * The font size has changed, so force the next call to
+ * drawing_area_setup to regenerate the backing surface.
+ */
+ inst->drawing_area_setup_needed = true;
+ }
+
+ inst->direct_to_font = init_ucs(&inst->ucsdata,
+ conf_get_str(inst->conf, CONF_line_codepage),
+ conf_get_bool(inst->conf, CONF_utf8_override),
+ inst->fonts[0]->public_charset,
+ conf_get_int(inst->conf, CONF_vtmode));
+
+ inst->drawtype = inst->fonts[0]->preferred_drawtype;
+
+ return NULL;
+}
+
+#if GTK_CHECK_VERSION(3,0,0)
+struct find_app_menu_bar_ctx {
+ GtkWidget *area, *menubar;
+};
+static void find_app_menu_bar(GtkWidget *widget, gpointer data)
+{
+ struct find_app_menu_bar_ctx *ctx = (struct find_app_menu_bar_ctx *)data;
+ if (widget != ctx->area && GTK_IS_MENU_BAR(widget))
+ ctx->menubar = widget;
+}
+#endif
+
+static void compute_geom_hints(GtkFrontend *inst, GdkGeometry *geom)
+{
+ /*
+ * Unused fields in geom.
+ */
+ geom->max_width = geom->max_height = -1;
+ geom->min_aspect = geom->max_aspect = 0;
+
+ /*
+ * Set up the geometry fields we care about, with reference to
+ * just the drawing area. We'll correct for other widgets in a
+ * moment.
+ */
+ geom->min_width = inst->font_width + 2*inst->window_border;
+ geom->min_height = inst->font_height + 2*inst->window_border;
+ geom->base_width = 2*inst->window_border;
+ geom->base_height = 2*inst->window_border;
+ geom->width_inc = inst->font_width;
+ geom->height_inc = inst->font_height;
+
+ /*
+ * If we've got a scrollbar visible, then we must include its
+ * width as part of the base and min width, and also ensure that
+ * our window's minimum height is at least the height required by
+ * the scrollbar.
+ *
+ * In the latter case, we must also take care to arrange that
+ * (geom->min_height - geom->base_height) is an integer multiple of
+ * geom->height_inc, because if it's not, then some window managers
+ * (we know of xfwm4) get confused, with the effect that they
+ * resize our window to a height based on min_height instead of
+ * base_height, which we then round down and the window ends up
+ * too short.
+ */
+ if (inst->sbar_visible) {
+ GtkRequisition req;
+ int min_sb_height;
+
+#if GTK_CHECK_VERSION(3,0,0)
+ gtk_widget_get_preferred_size(inst->sbar, &req, NULL);
+#else
+ gtk_widget_size_request(inst->sbar, &req);
+#endif
+
+ /* Compute rounded-up scrollbar height. */
+ min_sb_height = req.height;
+ min_sb_height += geom->height_inc - 1;
+ min_sb_height -= ((min_sb_height - geom->base_height%geom->height_inc)
+ % geom->height_inc);
+
+ geom->min_width += req.width;
+ geom->base_width += req.width;
+ if (geom->min_height < min_sb_height)
+ geom->min_height = min_sb_height;
+ }
+
+#if GTK_CHECK_VERSION(3,0,0)
+ /*
+ * And if we're running a main-gtk-application.c based program and
+ * GtkApplicationWindow has given us a menu bar inside the window,
+ * then we must take that into account as well.
+ *
+ * In its unbounded wisdom, GtkApplicationWindow doesn't actually
+ * give us a direct function call to _find_ the menu bar widget.
+ * Fortunately, we can find it by enumerating the children of the
+ * top-level window and looking for one we didn't put there
+ * ourselves.
+ */
+ {
+ struct find_app_menu_bar_ctx ctx[1];
+ ctx->area = inst->area;
+ ctx->menubar = NULL;
+ gtk_container_foreach(GTK_CONTAINER(inst->window),
+ find_app_menu_bar, ctx);
+
+ if (ctx->menubar) {
+ GtkRequisition req;
+ int min_menu_width;
+ gtk_widget_get_preferred_size(ctx->menubar, NULL, &req);
+
+ /*
+ * This time, the height adjustment is easy (the menu bar
+ * sits above everything), but we have to take care with
+ * the _width_ to ensure we keep min_width and base_width
+ * congruent modulo width_inc.
+ */
+ geom->min_height += req.height;
+ geom->base_height += req.height;
+
+ min_menu_width = req.width;
+ min_menu_width += geom->width_inc - 1;
+ min_menu_width -=
+ ((min_menu_width - geom->base_width%geom->width_inc)
+ % geom->width_inc);
+ if (geom->min_width < min_menu_width)
+ geom->min_width = min_menu_width;
+ }
+ }
+#endif
+}
+
+void set_geom_hints(GtkFrontend *inst)
+{
+ /*
+ * 2021-12-20: I've found that on Ubuntu 20.04 Wayland (using GTK
+ * 3.24.20), setting geometry hints causes the window size to come
+ * out wrong. As far as I can tell, that's because the GDK Wayland
+ * backend internally considers windows to be a lot larger than
+ * their obvious display size (*even* considering visible window
+ * furniture like title bars), with an extra margin on every side
+ * to account for surrounding effects like shadows. And the
+ * geometry hints like base size and resize increment are applied
+ * to that larger size rather than the more obvious 'client area'
+ * size. So when we ask for a window of exactly the size we want,
+ * it gets modified by GDK based on the geometry hints, but
+ * applying this extra margin, which causes the size to be a
+ * little bit too small.
+ *
+ * I don't know how you can sensibly find out the size of that
+ * margin. If I did, I could account for it in the geometry hints.
+ * But I also see that gtk_window_set_geometry_hints is removed in
+ * GTK 4, which suggests that probably doing a lot of hard work to
+ * fix this is not the way forward.
+ *
+ * So instead, I simply avoid setting geometry hints at all on any
+ * GDK backend other than X11, and hopefully that's a workaround.
+ */
+#if GTK_CHECK_VERSION(3,0,0) && !defined NOT_X_WINDOWS
+ if (!GDK_IS_X11_DISPLAY(gdk_display_get_default()))
+ return;
+#endif
+
+ const struct BackendVtable *vt;
+ GdkGeometry geom;
+ gint flags = GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE | GDK_HINT_RESIZE_INC;
+ compute_geom_hints(inst, &geom);
+#if GTK_CHECK_VERSION(2,0,0)
+ if (inst->gotpos)
+ flags |= GDK_HINT_USER_POS;
+#endif
+ vt = backend_vt_from_proto(conf_get_int(inst->conf, CONF_protocol));
+ if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) {
+ /* Window resizing forbidden. Set both minimum and maximum
+ * dimensions to be the initial size. */
+ geom.min_width = geom.base_width + geom.width_inc * inst->width;
+ geom.min_height = geom.base_height + geom.height_inc * inst->height;
+ geom.max_width = geom.min_width;
+ geom.max_height = geom.min_height;
+ flags |= GDK_HINT_MAX_SIZE;
+ }
+ gtk_window_set_geometry_hints(GTK_WINDOW(inst->window),
+ NULL, &geom, flags);
+}
+
+#if GTK_CHECK_VERSION(2,0,0)
+static void compute_whole_window_size(GtkFrontend *inst,
+ int wchars, int hchars,
+ int *wpix, int *hpix)
+{
+ GdkGeometry geom;
+ compute_geom_hints(inst, &geom);
+ if (wpix) *wpix = geom.base_width + wchars * geom.width_inc;
+ if (hpix) *hpix = geom.base_height + hchars * geom.height_inc;
+}
+#endif
+
+void clear_scrollback_menuitem(GtkMenuItem *item, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ term_clrsb(inst->term);
+}
+
+void reset_terminal_menuitem(GtkMenuItem *item, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ term_pwron(inst->term, true);
+ if (inst->ldisc)
+ ldisc_echoedit_update(inst->ldisc);
+}
+
+void copy_clipboard_menuitem(GtkMenuItem *item, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ static const int clips[] = { MENU_CLIPBOARD };
+ term_request_copy(inst->term, clips, lenof(clips));
+}
+
+void paste_clipboard_menuitem(GtkMenuItem *item, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ term_request_paste(inst->term, MENU_CLIPBOARD);
+}
+
+void copy_all_menuitem(GtkMenuItem *item, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ static const int clips[] = { COPYALL_CLIPBOARDS };
+ term_copyall(inst->term, clips, lenof(clips));
+}
+
+void special_menuitem(GtkMenuItem *item, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ SessionSpecial *sc = g_object_get_data(G_OBJECT(item), "user-data");
+
+ if (inst->backend)
+ backend_special(inst->backend, sc->code, sc->arg);
+}
+
+void about_menuitem(GtkMenuItem *item, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ about_box(inst->window);
+}
+
+void event_log_menuitem(GtkMenuItem *item, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ showeventlog(inst->eventlogstuff, inst->window);
+}
+
+void setup_clipboards(GtkFrontend *inst, Terminal *term, Conf *conf)
+{
+ assert(term->mouse_select_clipboards[0] == CLIP_LOCAL);
+
+ term->n_mouse_select_clipboards = 1;
+ term->mouse_select_clipboards[
+ term->n_mouse_select_clipboards++] = MOUSE_SELECT_CLIPBOARD;
+
+ if (conf_get_bool(conf, CONF_mouseautocopy)) {
+ term->mouse_select_clipboards[
+ term->n_mouse_select_clipboards++] = CLIP_CLIPBOARD;
+ }
+
+ set_clipboard_atom(inst, CLIP_CUSTOM_1, GDK_NONE);
+ set_clipboard_atom(inst, CLIP_CUSTOM_2, GDK_NONE);
+ set_clipboard_atom(inst, CLIP_CUSTOM_3, GDK_NONE);
+
+ switch (conf_get_int(conf, CONF_mousepaste)) {
+ case CLIPUI_IMPLICIT:
+ term->mouse_paste_clipboard = MOUSE_PASTE_CLIPBOARD;
+ break;
+ case CLIPUI_EXPLICIT:
+ term->mouse_paste_clipboard = CLIP_CLIPBOARD;
+ break;
+ case CLIPUI_CUSTOM:
+ term->mouse_paste_clipboard = CLIP_CUSTOM_1;
+ set_clipboard_atom(inst, CLIP_CUSTOM_1,
+ gdk_atom_intern(
+ conf_get_str(conf, CONF_mousepaste_custom),
+ false));
+ break;
+ default:
+ term->mouse_paste_clipboard = CLIP_NULL;
+ break;
+ }
+
+ if (conf_get_int(conf, CONF_ctrlshiftins) == CLIPUI_CUSTOM) {
+ GdkAtom atom = gdk_atom_intern(
+ conf_get_str(conf, CONF_ctrlshiftins_custom), false);
+ struct clipboard_state *state = clipboard_from_atom(inst, atom);
+ if (state) {
+ inst->clipboard_ctrlshiftins = state->clipboard;
+ } else {
+ inst->clipboard_ctrlshiftins = CLIP_CUSTOM_2;
+ set_clipboard_atom(inst, CLIP_CUSTOM_2, atom);
+ }
+ }
+
+ if (conf_get_int(conf, CONF_ctrlshiftcv) == CLIPUI_CUSTOM) {
+ GdkAtom atom = gdk_atom_intern(
+ conf_get_str(conf, CONF_ctrlshiftcv_custom), false);
+ struct clipboard_state *state = clipboard_from_atom(inst, atom);
+ if (state) {
+ inst->clipboard_ctrlshiftins = state->clipboard;
+ } else {
+ inst->clipboard_ctrlshiftcv = CLIP_CUSTOM_3;
+ set_clipboard_atom(inst, CLIP_CUSTOM_3, atom);
+ }
+ }
+}
+
+struct after_change_settings_dialog_ctx {
+ GtkFrontend *inst;
+ Conf *newconf;
+};
+
+static void after_change_settings_dialog(void *vctx, int retval);
+
+void change_settings_menuitem(GtkMenuItem *item, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ struct after_change_settings_dialog_ctx *ctx;
+ GtkWidget *dialog;
+ char *title;
+
+ if (find_and_raise_dialog(inst, DIALOG_SLOT_RECONFIGURE))
+ return;
+
+ title = dupcat(appname, " Reconfiguration");
+
+ ctx = snew(struct after_change_settings_dialog_ctx);
+ ctx->inst = inst;
+ ctx->newconf = conf_copy(inst->conf);
+
+ term_pre_reconfig(inst->term, ctx->newconf);
+
+ dialog = create_config_box(
+ title, ctx->newconf, true,
+ inst->backend ? backend_cfg_info(inst->backend) : 0,
+ after_change_settings_dialog, ctx);
+ register_dialog(&inst->seat, DIALOG_SLOT_RECONFIGURE, dialog);
+
+ sfree(title);
+}
+
+static void after_change_settings_dialog(void *vctx, int retval)
+{
+ struct after_change_settings_dialog_ctx ctx =
+ *(struct after_change_settings_dialog_ctx *)vctx;
+ GtkFrontend *inst = ctx.inst;
+ Conf *oldconf = inst->conf, *newconf = ctx.newconf;
+ bool need_size;
+
+ sfree(vctx); /* we've copied this already */
+
+ unregister_dialog(&inst->seat, DIALOG_SLOT_RECONFIGURE);
+
+ if (retval > 0) {
+ inst->conf = newconf;
+
+ /* Pass new config data to the logging module */
+ log_reconfig(inst->logctx, inst->conf);
+ /*
+ * Flush the line discipline's edit buffer in the case
+ * where local editing has just been disabled.
+ */
+ if (inst->ldisc) {
+ ldisc_configure(inst->ldisc, inst->conf);
+ ldisc_echoedit_update(inst->ldisc);
+ }
+ /* Pass new config data to the terminal */
+ term_reconfig(inst->term, inst->conf);
+ setup_clipboards(inst, inst->term, inst->conf);
+ /* Pass new config data to the back end */
+ if (inst->backend)
+ backend_reconfig(inst->backend, inst->conf);
+
+ cache_conf_values(inst);
+
+ need_size = false;
+
+ /*
+ * If the scrollbar needs to be shown, hidden, or moved
+ * from one end to the other of the window, do so now.
+ */
+ if (conf_get_bool(oldconf, CONF_scrollbar) !=
+ conf_get_bool(newconf, CONF_scrollbar)) {
+ show_scrollbar(inst, conf_get_bool(newconf, CONF_scrollbar));
+ need_size = true;
+ }
+ if (conf_get_bool(oldconf, CONF_scrollbar_on_left) !=
+ conf_get_bool(newconf, CONF_scrollbar_on_left)) {
+ gtk_box_reorder_child(inst->hbox, inst->sbar,
+ conf_get_bool(newconf, CONF_scrollbar_on_left)
+ ? 0 : 1);
+ }
+
+ /*
+ * Redo the whole tangled fonts and Unicode mess if
+ * necessary.
+ */
+ if (strcmp(conf_get_fontspec(oldconf, CONF_font)->name,
+ conf_get_fontspec(newconf, CONF_font)->name) ||
+ strcmp(conf_get_fontspec(oldconf, CONF_boldfont)->name,
+ conf_get_fontspec(newconf, CONF_boldfont)->name) ||
+ strcmp(conf_get_fontspec(oldconf, CONF_widefont)->name,
+ conf_get_fontspec(newconf, CONF_widefont)->name) ||
+ strcmp(conf_get_fontspec(oldconf, CONF_wideboldfont)->name,
+ conf_get_fontspec(newconf, CONF_wideboldfont)->name) ||
+ strcmp(conf_get_str(oldconf, CONF_line_codepage),
+ conf_get_str(newconf, CONF_line_codepage)) ||
+ conf_get_bool(oldconf, CONF_utf8_override) !=
+ conf_get_bool(newconf, CONF_utf8_override) ||
+ conf_get_int(oldconf, CONF_vtmode) !=
+ conf_get_int(newconf, CONF_vtmode) ||
+ conf_get_bool(oldconf, CONF_shadowbold) !=
+ conf_get_bool(newconf, CONF_shadowbold) ||
+ conf_get_int(oldconf, CONF_shadowboldoffset) !=
+ conf_get_int(newconf, CONF_shadowboldoffset)) {
+ char *errmsg = setup_fonts_ucs(inst);
+ if (errmsg) {
+ char *msgboxtext =
+ dupprintf("Could not change fonts in terminal window: %s\n",
+ errmsg);
+ create_message_box(
+ inst->window, "Font setup error", msgboxtext,
+ string_width("Could not change fonts in terminal window:"),
+ false, &buttons_ok, trivial_post_dialog_fn, NULL);
+ sfree(msgboxtext);
+ sfree(errmsg);
+ } else {
+ need_size = true;
+ }
+ }
+
+ /*
+ * Resize the window.
+ */
+ if (conf_get_int(oldconf, CONF_width) !=
+ conf_get_int(newconf, CONF_width) ||
+ conf_get_int(oldconf, CONF_height) !=
+ conf_get_int(newconf, CONF_height) ||
+ conf_get_int(oldconf, CONF_window_border) !=
+ conf_get_int(newconf, CONF_window_border) ||
+ need_size) {
+ set_geom_hints(inst);
+ request_resize_internal(inst, false,
+ conf_get_int(newconf, CONF_width),
+ conf_get_int(newconf, CONF_height));
+ } else {
+ /*
+ * The above will have caused a call to term_size() for
+ * us if it happened. If the user has fiddled with only
+ * the scrollback size, the above will not have
+ * happened and we will need an explicit term_size()
+ * here.
+ */
+ if (conf_get_int(oldconf, CONF_savelines) !=
+ conf_get_int(newconf, CONF_savelines))
+ term_size(inst->term, inst->term->rows, inst->term->cols,
+ conf_get_int(newconf, CONF_savelines));
+ }
+
+ term_invalidate(inst->term);
+
+ /*
+ * We do an explicit full redraw here to ensure the window
+ * border has been redrawn as well as the text area.
+ */
+ gtk_widget_queue_draw(inst->area);
+
+ conf_free(oldconf);
+ } else {
+ conf_free(newconf);
+ }
+}
+
+static void change_font_size(GtkFrontend *inst, int increment)
+{
+ static const int conf_keys[lenof(inst->fonts)] = {
+ CONF_font, CONF_boldfont, CONF_widefont, CONF_wideboldfont,
+ };
+ FontSpec *oldfonts[lenof(inst->fonts)];
+ FontSpec *newfonts[lenof(inst->fonts)];
+ char *errmsg = NULL;
+ int i;
+
+ for (i = 0; i < lenof(newfonts); i++)
+ oldfonts[i] = newfonts[i] = NULL;
+
+ for (i = 0; i < lenof(inst->fonts); i++) {
+ if (inst->fonts[i]) {
+ char *newname = unifont_size_increment(inst->fonts[i], increment);
+ if (!newname)
+ goto cleanup;
+ newfonts[i] = fontspec_new(newname);
+ sfree(newname);
+ }
+ }
+
+ for (i = 0; i < lenof(newfonts); i++) {
+ if (newfonts[i]) {
+ oldfonts[i] = fontspec_copy(
+ conf_get_fontspec(inst->conf, conf_keys[i]));
+ conf_set_fontspec(inst->conf, conf_keys[i], newfonts[i]);
+ }
+ }
+
+ errmsg = setup_fonts_ucs(inst);
+ if (errmsg)
+ goto cleanup;
+
+ /* Success, so suppress putting everything back */
+ for (i = 0; i < lenof(newfonts); i++) {
+ if (oldfonts[i]) {
+ fontspec_free(oldfonts[i]);
+ oldfonts[i] = NULL;
+ }
+ }
+
+ set_geom_hints(inst);
+ request_resize_internal(inst, false, conf_get_int(inst->conf, CONF_width),
+ conf_get_int(inst->conf, CONF_height));
+ term_invalidate(inst->term);
+ gtk_widget_queue_draw(inst->area);
+
+ cleanup:
+ for (i = 0; i < lenof(oldfonts); i++) {
+ if (oldfonts[i]) {
+ conf_set_fontspec(inst->conf, conf_keys[i], oldfonts[i]);
+ fontspec_free(oldfonts[i]);
+ }
+ if (newfonts[i])
+ fontspec_free(newfonts[i]);
+ }
+ sfree(errmsg);
+}
+
+void dup_session_menuitem(GtkMenuItem *item, gpointer gdata)
+{
+ GtkFrontend *inst = (GtkFrontend *)gdata;
+
+ launch_duplicate_session(inst->conf);
+}
+
+void new_session_menuitem(GtkMenuItem *item, gpointer data)
+{
+ launch_new_session();
+}
+
+void restart_session_menuitem(GtkMenuItem *item, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+
+ if (!inst->backend) {
+ logevent(inst->logctx, "----- Session restarted -----");
+ term_pwron(inst->term, false);
+ start_backend(inst);
+ inst->exited = false;
+ }
+}
+
+void saved_session_menuitem(GtkMenuItem *item, gpointer data)
+{
+ char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data");
+
+ launch_saved_session(str);
+}
+
+void saved_session_freedata(GtkMenuItem *item, gpointer data)
+{
+ char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data");
+
+ sfree(str);
+}
+
+void app_menu_action(GtkFrontend *frontend, enum MenuAction action)
+{
+ GtkFrontend *inst = (GtkFrontend *)frontend;
+ switch (action) {
+ case MA_COPY:
+ copy_clipboard_menuitem(NULL, inst);
+ break;
+ case MA_PASTE:
+ paste_clipboard_menuitem(NULL, inst);
+ break;
+ case MA_COPY_ALL:
+ copy_all_menuitem(NULL, inst);
+ break;
+ case MA_DUPLICATE_SESSION:
+ dup_session_menuitem(NULL, inst);
+ break;
+ case MA_RESTART_SESSION:
+ restart_session_menuitem(NULL, inst);
+ break;
+ case MA_CHANGE_SETTINGS:
+ change_settings_menuitem(NULL, inst);
+ break;
+ case MA_CLEAR_SCROLLBACK:
+ clear_scrollback_menuitem(NULL, inst);
+ break;
+ case MA_RESET_TERMINAL:
+ reset_terminal_menuitem(NULL, inst);
+ break;
+ case MA_EVENT_LOG:
+ event_log_menuitem(NULL, inst);
+ break;
+ }
+}
+
+static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data)
+{
+ GtkFrontend *inst = (GtkFrontend *)data;
+ struct sesslist sesslist;
+ int i;
+
+ gtk_container_foreach(GTK_CONTAINER(inst->sessionsmenu),
+ (GtkCallback)gtk_widget_destroy, NULL);
+
+ get_sesslist(&sesslist, true);
+ /* skip sesslist.sessions[0] == Default Settings */
+ for (i = 1; i < sesslist.nsessions; i++) {
+ GtkWidget *menuitem =
+ gtk_menu_item_new_with_label(sesslist.sessions[i]);
+ gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem);
+ gtk_widget_show(menuitem);
+ g_object_set_data(G_OBJECT(menuitem), "user-data",
+ dupstr(sesslist.sessions[i]));
+ g_signal_connect(G_OBJECT(menuitem), "activate",
+ G_CALLBACK(saved_session_menuitem),
+ inst);
+ g_signal_connect(G_OBJECT(menuitem), "destroy",
+ G_CALLBACK(saved_session_freedata),
+ inst);
+ }
+ if (sesslist.nsessions <= 1) {
+ GtkWidget *menuitem =
+ gtk_menu_item_new_with_label("(No sessions)");
+ gtk_widget_set_sensitive(menuitem, false);
+ gtk_container_add(GTK_CONTAINER(inst->sessionsmenu), menuitem);
+ gtk_widget_show(menuitem);
+ }
+ get_sesslist(&sesslist, false); /* free up */
+}
+
+void set_window_icon(GtkWidget *window, const char *const *const *icon,
+ int n_icon)
+{
+#if GTK_CHECK_VERSION(2,0,0)
+ GList *iconlist;
+ int n;
+#else
+ GdkPixmap *iconpm;
+ GdkBitmap *iconmask;
+#endif
+
+ if (!n_icon)
+ return;
+
+ gtk_widget_realize(window);
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_window_set_icon(GTK_WINDOW(window),
+ gdk_pixbuf_new_from_xpm_data((const gchar **)icon[0]));
+#else
+ iconpm = gdk_pixmap_create_from_xpm_d(gtk_widget_get_window(window),
+ &iconmask, NULL, (gchar **)icon[0]);
+ gdk_window_set_icon(gtk_widget_get_window(window), NULL, iconpm, iconmask);
+#endif
+
+#if GTK_CHECK_VERSION(2,0,0)
+ iconlist = NULL;
+ for (n = 0; n < n_icon; n++) {
+ iconlist =
+ g_list_append(iconlist,
+ gdk_pixbuf_new_from_xpm_data((const gchar **)
+ icon[n]));
+ }
+ gtk_window_set_icon_list(GTK_WINDOW(window), iconlist);
+#endif
+}
+
+static void free_special_cmd(gpointer data) { sfree(data); }
+
+static void gtk_seat_update_specials_menu(Seat *seat)
+{
+ GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
+ const SessionSpecial *specials;
+
+ if (inst->backend)
+ specials = backend_get_specials(inst->backend);
+ else
+ specials = NULL;
+
+ /* I believe this disposes of submenus too. */
+ gtk_container_foreach(GTK_CONTAINER(inst->specialsmenu),
+ (GtkCallback)gtk_widget_destroy, NULL);
+ if (specials) {
+ int i;
+ GtkWidget *menu = inst->specialsmenu;
+ /* A lame "stack" for submenus that will do for now. */
+ GtkWidget *saved_menu = NULL;
+ int nesting = 1;
+ for (i = 0; nesting > 0; i++) {
+ GtkWidget *menuitem = NULL;
+ switch (specials[i].code) {
+ case SS_SUBMENU:
+ assert (nesting < 2);
+ saved_menu = menu; /* XXX lame stacking */
+ menu = gtk_menu_new();
+ menuitem = gtk_menu_item_new_with_label(specials[i].name);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+ gtk_container_add(GTK_CONTAINER(saved_menu), menuitem);
+ gtk_widget_show(menuitem);
+ menuitem = NULL;
+ nesting++;
+ break;
+ case SS_EXITMENU:
+ nesting--;
+ if (nesting) {
+ menu = saved_menu; /* XXX lame stacking */
+ saved_menu = NULL;
+ }
+ break;
+ case SS_SEP:
+ menuitem = gtk_menu_item_new();
+ break;
+ default: {
+ menuitem = gtk_menu_item_new_with_label(specials[i].name);
+ SessionSpecial *sc = snew(SessionSpecial);
+ *sc = specials[i]; /* structure copy */
+ g_object_set_data_full(G_OBJECT(menuitem), "user-data",
+ sc, free_special_cmd);
+ g_signal_connect(G_OBJECT(menuitem), "activate",
+ G_CALLBACK(special_menuitem), inst);
+ break;
+ }
+ }
+ if (menuitem) {
+ gtk_container_add(GTK_CONTAINER(menu), menuitem);
+ gtk_widget_show(menuitem);
+ }
+ }
+ gtk_widget_show(inst->specialsitem1);
+ gtk_widget_show(inst->specialsitem2);
+ } else {
+ gtk_widget_hide(inst->specialsitem1);
+ gtk_widget_hide(inst->specialsitem2);
+ }
+}
+
+static void start_backend(GtkFrontend *inst)
+{
+ const struct BackendVtable *vt;
+ char *error, *realhost;
+
+ inst->cmdline_get_passwd_state = cmdline_get_passwd_input_state_new;
+
+ vt = select_backend(inst->conf);
+
+ seat_set_trust_status(&inst->seat, true);
+ error = backend_init(vt, &inst->seat, &inst->backend,
+ inst->logctx, inst->conf,
+ conf_get_str(inst->conf, CONF_host),
+ conf_get_int(inst->conf, CONF_port),
+ &realhost,
+ conf_get_bool(inst->conf, CONF_tcp_nodelay),
+ conf_get_bool(inst->conf, CONF_tcp_keepalives));
+
+ if (error) {
+ if (cmdline_tooltype & TOOLTYPE_NONNETWORK) {
+ /* Special case for pterm. */
+ seat_connection_fatal(&inst->seat,
+ "Unable to open terminal:\n%s",
+ error);
+ } else {
+ seat_connection_fatal(&inst->seat,
+ "Unable to open connection to %s:\n%s",
+ conf_dest(inst->conf), error);
+ }
+ sfree(error);
+ inst->exited = true;
+ return;
+ }
+
+ term_setup_window_titles(inst->term, realhost);
+ sfree(realhost);
+
+ term_provide_backend(inst->term, inst->backend);
+
+ inst->ldisc = ldisc_create(inst->conf, inst->term, inst->backend,
+ &inst->seat);
+
+ gtk_widget_set_sensitive(inst->restartitem, false);
+}
+
+#if GTK_CHECK_VERSION(2,0,0)
+static void get_monitor_geometry(GtkWidget *widget, GdkRectangle *geometry)
+{
+#if GTK_CHECK_VERSION(3,4,0)
+ GdkDisplay *display = gtk_widget_get_display(widget);
+ GdkWindow *gdkwindow = gtk_widget_get_window(widget);
+# if GTK_CHECK_VERSION(3,22,0)
+ GdkMonitor *monitor;
+ if (gdkwindow)
+ monitor = gdk_display_get_monitor_at_window(display, gdkwindow);
+ else
+ monitor = gdk_display_get_monitor(display, 0);
+ gdk_monitor_get_geometry(monitor, geometry);
+# else
+ GdkScreen *screen = gdk_display_get_default_screen(display);
+ gint monitor_num = gdk_screen_get_monitor_at_window(screen, gdkwindow);
+ gdk_screen_get_monitor_geometry(screen, monitor_num, geometry);
+# endif
+#else
+ geometry->x = geometry->y = 0;
+ geometry->width = gdk_screen_width();
+ geometry->height = gdk_screen_height();
+#endif
+}
+#endif
+
+static const TermWinVtable gtk_termwin_vt = {
+ .setup_draw_ctx = gtkwin_setup_draw_ctx,
+ .draw_text = gtkwin_draw_text,
+ .draw_cursor = gtkwin_draw_cursor,
+ .draw_trust_sigil = gtkwin_draw_trust_sigil,
+ .char_width = gtkwin_char_width,
+ .free_draw_ctx = gtkwin_free_draw_ctx,
+ .set_cursor_pos = gtkwin_set_cursor_pos,
+ .set_raw_mouse_mode = gtkwin_set_raw_mouse_mode,
+ .set_raw_mouse_mode_pointer = gtkwin_set_raw_mouse_mode_pointer,
+ .set_scrollbar = gtkwin_set_scrollbar,
+ .bell = gtkwin_bell,
+ .clip_write = gtkwin_clip_write,
+ .clip_request_paste = gtkwin_clip_request_paste,
+ .refresh = gtkwin_refresh,
+ .request_resize = gtkwin_request_resize,
+ .set_title = gtkwin_set_title,
+ .set_icon_title = gtkwin_set_icon_title,
+ .set_minimised = gtkwin_set_minimised,
+ .set_maximised = gtkwin_set_maximised,
+ .move = gtkwin_move,
+ .set_zorder = gtkwin_set_zorder,
+ .palette_set = gtkwin_palette_set,
+ .palette_get_overrides = gtkwin_palette_get_overrides,
+ .unthrottle = gtkwin_unthrottle,
+};
+
+void new_session_window(Conf *conf, const char *geometry_string)
+{
+ GtkFrontend *inst;
+
+ prepare_session(conf);
+
+ /*
+ * Create an instance structure and initialise to zeroes
+ */
+ inst = snew(GtkFrontend);
+ memset(inst, 0, sizeof(*inst));
+#ifdef JUST_USE_GTK_CLIPBOARD_UTF8
+ inst->cdi_headtail.next = inst->cdi_headtail.prev = &inst->cdi_headtail;
+#endif
+ inst->alt_keycode = -1; /* this one needs _not_ to be zero */
+ inst->busy_status = BUSY_NOT;
+ inst->conf = conf;
+ inst->wintitle = inst->icontitle = NULL;
+ inst->drawtype = DRAWTYPE_DEFAULT;
+#if GTK_CHECK_VERSION(3,4,0)
+ inst->cumulative_scroll = 0.0;
+#endif
+ inst->drawing_area_setup_needed = true;
+
+ inst->termwin.vt = &gtk_termwin_vt;
+ inst->seat.vt = &gtk_seat_vt;
+ inst->logpolicy.vt = &gtk_logpolicy_vt;
+
+#ifndef NOT_X_WINDOWS
+ inst->disp = get_x11_display();
+ if (geometry_string) {
+ int flags, x, y;
+ unsigned int w, h;
+ flags = XParseGeometry(geometry_string, &x, &y, &w, &h);
+ if (flags & WidthValue)
+ conf_set_int(conf, CONF_width, w);
+ if (flags & HeightValue)
+ conf_set_int(conf, CONF_height, h);
+
+ if (flags & (XValue | YValue)) {
+ inst->xpos = x;
+ inst->ypos = y;
+ inst->gotpos = true;
+ inst->gravity = ((flags & XNegative ? 1 : 0) |
+ (flags & YNegative ? 2 : 0));
+ }
+ }
+#endif
+
+ if (!compound_text_atom)
+ compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", false);
+ if (!utf8_string_atom)
+ utf8_string_atom = gdk_atom_intern("UTF8_STRING", false);
+ if (!clipboard_atom)
+ clipboard_atom = gdk_atom_intern("CLIPBOARD", false);
+
+ inst->area = gtk_drawing_area_new();
+ gtk_widget_set_name(GTK_WIDGET(inst->area), "drawing-area");
+
+ {
+ char *errmsg = setup_fonts_ucs(inst);
+ if (errmsg) {
+ window_setup_error(errmsg);
+ sfree(errmsg);
+ gtk_widget_destroy(inst->area);
+ sfree(inst);
+ return;
+ }
+ }
+
+#if GTK_CHECK_VERSION(2,0,0)
+ inst->imc = gtk_im_multicontext_new();
+#endif
+
+ inst->window = make_gtk_toplevel_window(inst);
+ gtk_widget_set_name(GTK_WIDGET(inst->window), "top-level");
+ {
+ const char *winclass = conf_get_str(inst->conf, CONF_winclass);
+ if (*winclass) {
+#if GTK_CHECK_VERSION(3,22,0)
+#ifndef NOT_X_WINDOWS
+ GdkWindow *gdkwin;
+ gtk_widget_realize(GTK_WIDGET(inst->window));
+ gdkwin = gtk_widget_get_window(GTK_WIDGET(inst->window));
+ if (inst->disp && gdk_window_ensure_native(gdkwin)) {
+ XClassHint *xch = XAllocClassHint();
+ xch->res_name = (char *)winclass;
+ xch->res_class = (char *)winclass;
+ XSetClassHint(inst->disp, GDK_WINDOW_XID(gdkwin), xch);
+ XFree(xch);
+ }
+#endif
+ /*
+ * If we do have NOT_X_WINDOWS set, then we don't have any
+ * function in GTK 3.22 equivalent to the above. But then,
+ * surely in that situation the deprecated
+ * gtk_window_set_wmclass wouldn't have done anything
+ * meaningful in previous GTKs either.
+ */
+#else
+ gtk_window_set_wmclass(GTK_WINDOW(inst->window),
+ winclass, winclass);
+#endif
+ }
+ }
+
+#if GTK_CHECK_VERSION(2,0,0)
+ {
+ const BackendVtable *vt = select_backend(inst->conf);
+ if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN)
+ gtk_window_set_resizable(GTK_WINDOW(inst->window), false);
+ }
+#endif
+
+ inst->width = conf_get_int(inst->conf, CONF_width);
+ inst->height = conf_get_int(inst->conf, CONF_height);
+ cache_conf_values(inst);
+
+ init_clipboard(inst);
+
+ inst->sbar_adjust = GTK_ADJUSTMENT(gtk_adjustment_new(0,0,0,0,0,0));
+ inst->sbar = gtk_vscrollbar_new(inst->sbar_adjust);
+ inst->hbox = GTK_BOX(gtk_hbox_new(false, 0));
+ /*
+ * We always create the scrollbar; it remains invisible if
+ * unwanted, so we can pop it up quickly if it suddenly becomes
+ * desirable.
+ */
+ if (conf_get_bool(inst->conf, CONF_scrollbar_on_left))
+ gtk_box_pack_start(inst->hbox, inst->sbar, false, false, 0);
+ gtk_box_pack_start(inst->hbox, inst->area, true, true, 0);
+ if (!conf_get_bool(inst->conf, CONF_scrollbar_on_left))
+ gtk_box_pack_start(inst->hbox, inst->sbar, false, false, 0);
+
+ gtk_container_add(GTK_CONTAINER(inst->window), GTK_WIDGET(inst->hbox));
+
+ gtk_widget_show(inst->area);
+ show_scrollbar(inst, conf_get_bool(inst->conf, CONF_scrollbar));
+ gtk_widget_show(GTK_WIDGET(inst->hbox));
+
+ /*
+ * We must call gtk_widget_realize before setting up the geometry
+ * hints, so that GtkApplicationWindow will have actually created
+ * its menu bar (if it's going to) and hence compute_geom_hints
+ * can find it to take its size into account.
+ */
+ gtk_widget_realize(inst->window);
+ set_geom_hints(inst);
+
+#if GTK_CHECK_VERSION(3,0,0)
+ {
+ int wp, hp;
+ compute_whole_window_size(inst, inst->width, inst->height, &wp, &hp);
+ gtk_window_set_default_size(GTK_WINDOW(inst->window), wp, hp);
+ }
+#else
+ {
+ int w = inst->font_width * inst->width + 2*inst->window_border;
+ int h = inst->font_height * inst->height + 2*inst->window_border;
+#if GTK_CHECK_VERSION(2,0,0)
+ gtk_widget_set_size_request(inst->area, w, h);
+#else
+ gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), w, h);
+#endif
+ }
+#endif
+
+#if GTK_CHECK_VERSION(2,0,0)
+ if (inst->gotpos) {
+ static const GdkGravity gravities[] = {
+ GDK_GRAVITY_NORTH_WEST,
+ GDK_GRAVITY_NORTH_EAST,
+ GDK_GRAVITY_SOUTH_WEST,
+ GDK_GRAVITY_SOUTH_EAST,
+ };
+ int x = inst->xpos, y = inst->ypos;
+ int wp, hp;
+ GdkRectangle monitor_geometry;
+ compute_whole_window_size(inst, inst->width, inst->height, &wp, &hp);
+ get_monitor_geometry(GTK_WIDGET(inst->window), &monitor_geometry);
+ if (inst->gravity & 1) x += (monitor_geometry.width - wp);
+ if (inst->gravity & 2) y += (monitor_geometry.height - hp);
+ gtk_window_set_gravity(GTK_WINDOW(inst->window),
+ gravities[inst->gravity & 3]);
+ gtk_window_move(GTK_WINDOW(inst->window), x, y);
+ }
+#else
+ if (inst->gotpos) {
+ int x = inst->xpos, y = inst->ypos;
+ GtkRequisition req;
+ gtk_widget_size_request(GTK_WIDGET(inst->window), &req);
+ if (inst->gravity & 1) x += gdk_screen_width() - req.width;
+ if (inst->gravity & 2) y += gdk_screen_height() - req.height;
+ gtk_window_set_position(GTK_WINDOW(inst->window), GTK_WIN_POS_NONE);
+ gtk_widget_set_uposition(GTK_WIDGET(inst->window), x, y);
+ }
+#endif
+
+ g_signal_connect(G_OBJECT(inst->window), "destroy",
+ G_CALLBACK(destroy), inst);
+ g_signal_connect(G_OBJECT(inst->window), "delete_event",
+ G_CALLBACK(delete_window), inst);
+ g_signal_connect(G_OBJECT(inst->window), "key_press_event",
+ G_CALLBACK(key_event), inst);
+ g_signal_connect(G_OBJECT(inst->window), "key_release_event",
+ G_CALLBACK(key_event), inst);
+ g_signal_connect(G_OBJECT(inst->window), "focus_in_event",
+ G_CALLBACK(focus_event), inst);
+ g_signal_connect(G_OBJECT(inst->window), "focus_out_event",
+ G_CALLBACK(focus_event), inst);
+ g_signal_connect(G_OBJECT(inst->area), "realize",
+ G_CALLBACK(area_realised), inst);
+ g_signal_connect(G_OBJECT(inst->area), "size_allocate",
+ G_CALLBACK(area_size_allocate), inst);
+ g_signal_connect(G_OBJECT(inst->window), "configure_event",
+ G_CALLBACK(window_configured), inst);
+#if GTK_CHECK_VERSION(3,10,0)
+ g_signal_connect(G_OBJECT(inst->area), "configure_event",
+ G_CALLBACK(area_configured), inst);
+#endif
+#if GTK_CHECK_VERSION(3,0,0)
+ g_signal_connect(G_OBJECT(inst->area), "draw",
+ G_CALLBACK(draw_area), inst);
+#else
+ g_signal_connect(G_OBJECT(inst->area), "expose_event",
+ G_CALLBACK(expose_area), inst);
+#endif
+ g_signal_connect(G_OBJECT(inst->area), "button_press_event",
+ G_CALLBACK(button_event), inst);
+ g_signal_connect(G_OBJECT(inst->area), "button_release_event",
+ G_CALLBACK(button_event), inst);
+#if GTK_CHECK_VERSION(2,0,0)
+ g_signal_connect(G_OBJECT(inst->area), "scroll_event",
+ G_CALLBACK(scroll_event), inst);
+#endif
+ g_signal_connect(G_OBJECT(inst->area), "motion_notify_event",
+ G_CALLBACK(motion_event), inst);
+#if GTK_CHECK_VERSION(2,0,0)
+ g_signal_connect(G_OBJECT(inst->imc), "commit",
+ G_CALLBACK(input_method_commit_event), inst);
+#endif
+ if (conf_get_bool(inst->conf, CONF_scrollbar))
+ g_signal_connect(G_OBJECT(inst->sbar_adjust), "value_changed",
+ G_CALLBACK(scrollbar_moved), inst);
+ gtk_widget_add_events(GTK_WIDGET(inst->area),
+ GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK
+#if GTK_CHECK_VERSION(3,4,0)
+ | GDK_SMOOTH_SCROLL_MASK
+#endif
+ );
+
+ set_window_icon(inst->window, main_icon, n_main_icon);
+
+ gtk_widget_show(inst->window);
+
+ set_window_background(inst);
+
+ /*
+ * Set up the Ctrl+rightclick context menu.
+ */
+ {
+ GtkWidget *menuitem;
+ char *s;
+
+ inst->menu = gtk_menu_new();
+
+#define MKMENUITEM(title, func) do \
+ { \
+ menuitem = gtk_menu_item_new_with_label(title); \
+ gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \
+ gtk_widget_show(menuitem); \
+ g_signal_connect(G_OBJECT(menuitem), "activate", \
+ G_CALLBACK(func), inst); \
+ } while (0)
+
+#define MKSUBMENU(title) do \
+ { \
+ menuitem = gtk_menu_item_new_with_label(title); \
+ gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \
+ gtk_widget_show(menuitem); \
+ } while (0)
+
+#define MKSEP() do \
+ { \
+ menuitem = gtk_menu_item_new(); \
+ gtk_container_add(GTK_CONTAINER(inst->menu), menuitem); \
+ gtk_widget_show(menuitem); \
+ } while (0)
+
+ if (new_session)
+ MKMENUITEM("New Session...", new_session_menuitem);
+ MKMENUITEM("Restart Session", restart_session_menuitem);
+ inst->restartitem = menuitem;
+ gtk_widget_set_sensitive(inst->restartitem, false);
+ MKMENUITEM("Duplicate Session", dup_session_menuitem);
+ if (saved_sessions) {
+ inst->sessionsmenu = gtk_menu_new();
+ /* sessionsmenu will be updated when it's invoked */
+ /* XXX is this the right way to do dynamic menus in Gtk? */
+ MKMENUITEM("Saved Sessions", update_savedsess_menu);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem),
+ inst->sessionsmenu);
+ }
+ MKSEP();
+ MKMENUITEM("Change Settings...", change_settings_menuitem);
+ MKSEP();
+ if (use_event_log)
+ MKMENUITEM("Event Log", event_log_menuitem);
+ MKSUBMENU("Special Commands");
+ inst->specialsmenu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), inst->specialsmenu);
+ inst->specialsitem1 = menuitem;
+ MKSEP();
+ inst->specialsitem2 = menuitem;
+ gtk_widget_hide(inst->specialsitem1);
+ gtk_widget_hide(inst->specialsitem2);
+ MKMENUITEM("Clear Scrollback", clear_scrollback_menuitem);
+ MKMENUITEM("Reset Terminal", reset_terminal_menuitem);
+ MKSEP();
+ MKMENUITEM("Copy to " CLIPNAME_EXPLICIT_OBJECT,
+ copy_clipboard_menuitem);
+ MKMENUITEM("Paste from " CLIPNAME_EXPLICIT_OBJECT,
+ paste_clipboard_menuitem);
+ MKMENUITEM("Copy All", copy_all_menuitem);
+ MKSEP();
+ s = dupcat("About ", appname);
+ MKMENUITEM(s, about_menuitem);
+ sfree(s);
+#undef MKMENUITEM
+#undef MKSUBMENU
+#undef MKSEP
+ }
+
+ inst->textcursor = make_mouse_ptr(inst, GDK_XTERM);
+ inst->rawcursor = make_mouse_ptr(inst, GDK_LEFT_PTR);
+ inst->waitcursor = make_mouse_ptr(inst, GDK_WATCH);
+ inst->blankcursor = make_mouse_ptr(inst, -1);
+ inst->currcursor = inst->textcursor;
+ show_mouseptr(inst, true);
+
+ inst->eventlogstuff = eventlogstuff_new();
+
+ inst->term = term_init(inst->conf, &inst->ucsdata, &inst->termwin);
+ setup_clipboards(inst, inst->term, inst->conf);
+ inst->logctx = log_init(&inst->logpolicy, inst->conf);
+ term_provide_logctx(inst->term, inst->logctx);
+
+ term_size(inst->term, inst->height, inst->width,
+ conf_get_int(inst->conf, CONF_savelines));
+
+#if GTK_CHECK_VERSION(2,0,0)
+ /* Delay this signal connection until after inst->term exists */
+ g_signal_connect(G_OBJECT(inst->window), "window_state_event",
+ G_CALLBACK(window_state_event), inst);
+#endif
+
+ inst->exited = false;
+
+ start_backend(inst);
+
+ if (inst->ldisc) /* early backend failure might make this NULL already */
+ ldisc_echoedit_update(inst->ldisc); /* cause ldisc to notice changes */
+}
+
+static void gtk_seat_set_trust_status(Seat *seat, bool trusted)
+{
+ GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
+ term_set_trust_status(inst->term, trusted);
+}
+
+static bool gtk_seat_can_set_trust_status(Seat *seat)
+{
+ return true;
+}
+
+static bool gtk_seat_get_cursor_position(Seat *seat, int *x, int *y)
+{
+ GtkFrontend *inst = container_of(seat, GtkFrontend, seat);
+ if (inst->term) {
+ term_get_cursor_position(inst->term, x, y);
+ return true;
+ }
+ return false;
+}
diff --git a/unix/x11.c b/unix/x11.c
new file mode 100644
index 00000000..710ff849
--- /dev/null
+++ b/unix/x11.c
@@ -0,0 +1,221 @@
+/*
+ * x11.c: fetch local auth data for X forwarding.
+ */
+
+#include <ctype.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "network.h"
+
+void platform_get_x11_auth(struct X11Display *disp, Conf *conf)
+{
+ char *xauthfile;
+ bool needs_free;
+
+ /*
+ * Find the .Xauthority file.
+ */
+ needs_free = false;
+ xauthfile = getenv("XAUTHORITY");
+ if (!xauthfile) {
+ xauthfile = getenv("HOME");
+ if (xauthfile) {
+ xauthfile = dupcat(xauthfile, "/.Xauthority");
+ needs_free = true;
+ }
+ }
+
+ if (xauthfile) {
+ x11_get_auth_from_authfile(disp, xauthfile);
+ if (needs_free)
+ sfree(xauthfile);
+ }
+}
+
+const bool platform_uses_x11_unix_by_default = true;
+
+int platform_make_x11_server(Plug *plug, const char *progname, int mindisp,
+ const char *screen_number_suffix,
+ ptrlen authproto, ptrlen authdata,
+ Socket **sockets, Conf *conf)
+{
+ char *tmpdir;
+ char *authfilename = NULL;
+ strbuf *authfiledata = NULL;
+ char *unix_path = NULL;
+
+ SockAddr *a_tcp = NULL, *a_unix = NULL;
+
+ int authfd;
+ FILE *authfp;
+
+ int displayno;
+
+ authfiledata = strbuf_new_nm();
+
+ int nsockets = 0;
+
+ /*
+ * Look for a free TCP port to run our server on.
+ */
+ for (displayno = mindisp;; displayno++) {
+ const char *err;
+ int tcp_port = displayno + 6000;
+ int addrtype = ADDRTYPE_IPV4;
+
+ sockets[nsockets] = new_listener(
+ NULL, tcp_port, plug, false, conf, addrtype);
+
+ err = sk_socket_error(sockets[nsockets]);
+ if (!err) {
+ char *hostname = get_hostname();
+ if (hostname) {
+ char *canonicalname = NULL;
+ a_tcp = sk_namelookup(hostname, &canonicalname, addrtype);
+ sfree(canonicalname);
+ }
+ sfree(hostname);
+ nsockets++;
+ break; /* success! */
+ } else {
+ sk_close(sockets[nsockets]);
+ }
+
+ /*
+ * If we weren't able to bind to this port because it's in use
+ * by another program, go round this loop and try again. But
+ * for any other reason, give up completely and return failure
+ * to our caller.
+ *
+ * sk_socket_error currently has no machine-readable component
+ * (it would need a cross-platform abstraction of the socket
+ * error types we care about, plus translation from each OS
+ * error enumeration into that). So we use the disgusting
+ * approach of a string compare between the error string and
+ * the one EADDRINUSE would have given :-(
+ */
+ if (strcmp(err, strerror(EADDRINUSE)))
+ goto out;
+ }
+
+ if (a_tcp) {
+ x11_format_auth_for_authfile(
+ BinarySink_UPCAST(authfiledata),
+ a_tcp, displayno, authproto, authdata);
+ }
+
+ /*
+ * Try to establish the Unix-domain analogue. That may or may not
+ * work - file permissions in /tmp may prevent it, for example -
+ * but it's worth a try, and we don't consider it a fatal error if
+ * it doesn't work.
+ */
+ unix_path = dupprintf("/tmp/.X11-unix/X%d", displayno);
+ a_unix = unix_sock_addr(unix_path);
+
+ sockets[nsockets] = new_unix_listener(a_unix, plug);
+ if (!sk_socket_error(sockets[nsockets])) {
+ x11_format_auth_for_authfile(
+ BinarySink_UPCAST(authfiledata),
+ a_unix, displayno, authproto, authdata);
+ nsockets++;
+ } else {
+ sk_close(sockets[nsockets]);
+ sfree(unix_path);
+ unix_path = NULL;
+ }
+
+ /*
+ * Decide where the authority data will be written.
+ */
+
+ tmpdir = getenv("TMPDIR");
+ if (!tmpdir || !*tmpdir)
+ tmpdir = "/tmp";
+
+ authfilename = dupcat(tmpdir, "/", progname, "-Xauthority-XXXXXX");
+
+ {
+ int oldumask = umask(077);
+ authfd = mkstemp(authfilename);
+ umask(oldumask);
+ }
+ if (authfd < 0) {
+ while (nsockets-- > 0)
+ sk_close(sockets[nsockets]);
+ goto out;
+ }
+
+ /*
+ * Spawn a subprocess which will try to reliably delete our
+ * auth file when we terminate, in case we die unexpectedly.
+ */
+ {
+ int cleanup_pipe[2];
+ pid_t pid;
+
+ /* Don't worry if pipe or fork fails; it's not _that_ critical. */
+ if (!pipe(cleanup_pipe)) {
+ if ((pid = fork()) == 0) {
+ int buf[1024];
+ /*
+ * Our parent process holds the writing end of
+ * this pipe, and writes nothing to it. Hence,
+ * we expect read() to return EOF as soon as
+ * that process terminates.
+ */
+
+ close(0);
+ close(1);
+ close(2);
+
+ setpgid(0, 0);
+ close(cleanup_pipe[1]);
+ close(authfd);
+ while (read(cleanup_pipe[0], buf, sizeof(buf)) > 0);
+ unlink(authfilename);
+ if (unix_path)
+ unlink(unix_path);
+ _exit(0);
+ } else if (pid < 0) {
+ close(cleanup_pipe[0]);
+ close(cleanup_pipe[1]);
+ } else {
+ close(cleanup_pipe[0]);
+ cloexec(cleanup_pipe[1]);
+ }
+ }
+ }
+
+ authfp = fdopen(authfd, "wb");
+ fwrite(authfiledata->u, 1, authfiledata->len, authfp);
+ fclose(authfp);
+
+ {
+ char *display = dupprintf(":%d%s", displayno, screen_number_suffix);
+ conf_set_str_str(conf, CONF_environmt, "DISPLAY", display);
+ sfree(display);
+ }
+ conf_set_str_str(conf, CONF_environmt, "XAUTHORITY", authfilename);
+
+ /*
+ * FIXME: return at least the DISPLAY and XAUTHORITY env settings,
+ * and perhaps also the display number
+ */
+
+ out:
+ if (a_tcp)
+ sk_addr_free(a_tcp);
+ /* a_unix doesn't need freeing, because new_unix_listener took it over */
+ sfree(authfilename);
+ strbuf_free(authfiledata);
+ sfree(unix_path);
+ return nsockets;
+}
diff --git a/unix/x11misc.c b/unix/x11misc.c
deleted file mode 100644
index e1fd1906..00000000
--- a/unix/x11misc.c
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * x11misc.c: miscellaneous stuff for dealing directly with X servers.
- */
-
-#include <ctype.h>
-#include <unistd.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include "putty.h"
-
-#ifndef NOT_X_WINDOWS
-
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-
-#include "x11misc.h"
-
-/* ----------------------------------------------------------------------
- * Error handling mechanism which permits us to ignore specific X11
- * errors from particular requests. We maintain a list of upcoming
- * potential error events that we want to not treat as fatal errors.
- */
-
-static int (*orig_x11_error_handler)(Display *thisdisp, XErrorEvent *err);
-
-struct x11_err_to_ignore {
- Display *display;
- unsigned char error_code;
- unsigned long serial;
-};
-
-static struct x11_err_to_ignore *errs;
-static size_t nerrs, errsize;
-
-static int x11_error_handler(Display *thisdisp, XErrorEvent *err)
-{
- for (size_t i = 0; i < nerrs; i++) {
- if (thisdisp == errs[i].display &&
- err->serial == errs[i].serial &&
- err->error_code == errs[i].error_code) {
- /* Ok, this is an error we're happy to ignore */
- return 0;
- }
- }
-
- return (*orig_x11_error_handler)(thisdisp, err);
-}
-
-void x11_ignore_error(Display *disp, unsigned char errcode)
-{
- /*
- * Install our error handler, if we haven't already.
- */
- if (!orig_x11_error_handler)
- orig_x11_error_handler = XSetErrorHandler(x11_error_handler);
-
- /*
- * This is as good a moment as any to winnow the ignore list based
- * on requests we know to have been processed.
- */
- {
- unsigned long last = LastKnownRequestProcessed(disp);
- size_t i, j;
- for (i = j = 0; i < nerrs; i++) {
- if (errs[i].display == disp && errs[i].serial <= last)
- continue;
- errs[j++] = errs[i];
- }
- nerrs = j;
- }
-
- sgrowarray(errs, errsize, nerrs);
- errs[nerrs].display = disp;
- errs[nerrs].error_code = errcode;
- errs[nerrs].serial = NextRequest(disp);
- nerrs++;
-}
-
-#endif
-
diff --git a/unix/x11misc.h b/unix/x11misc.h
index 159d4226..5f5a2d26 100644
--- a/unix/x11misc.h
+++ b/unix/x11misc.h
@@ -7,14 +7,8 @@
#ifndef NOT_X_WINDOWS
-/*
- * x11misc.c.
- */
+/* Defined in unix/utils */
void x11_ignore_error(Display *disp, unsigned char errcode);
-
-/*
- * gtkmisc.c
- */
Display *get_x11_display(void);
#endif
diff --git a/unix/xkeysym.c b/unix/xkeysym.c
deleted file mode 100644
index aa9f9539..00000000
--- a/unix/xkeysym.c
+++ /dev/null
@@ -1,1011 +0,0 @@
-/*
- * xkeysym.c: mapping from X keysyms to Unicode values
- *
- * The basic idea of this is shamelessly cribbed from xterm. The
- * actual character data is generated from Markus Kuhn's proposed
- * redraft of the X11 keysym mapping table, using the following
- * piece of Perl/sh code:
-
-wget -q -O - http://www.cl.cam.ac.uk/~mgk25/ucs/X11.keysyms | \
-perl -ne '/^(\d+)\s+(\d+)\s+[\d\/]+\s+U\+([\dA-Fa-f]+)/ and' \
- -e ' do { $a{$1 * 256+ $2} = hex $3; };' \
- -e 'END { foreach $i (sort {$a <=> $b} keys %a) {' \
- -e ' printf " {0x%x, 0x%x},\n", $i, $a{$i} } }' \
- -e 'BEGIN { $a{0x13a4} = 0x20ac }'
-
- * (The BEGIN clause inserts a mapping for the Euro sign which for
- * some reason isn't in the list but xterm supports. *shrug*.)
- */
-
-#include "misc.h"
-
-struct keysym {
- /*
- * Currently nothing in here is above 0xFFFF, so I'll use
- * `unsigned short' to save space.
- */
- unsigned short keysym;
- unsigned short unicode;
-};
-
-static struct keysym keysyms[] = {
- {0x20, 0x20},
- {0x21, 0x21},
- {0x22, 0x22},
- {0x23, 0x23},
- {0x24, 0x24},
- {0x25, 0x25},
- {0x26, 0x26},
- {0x27, 0x27},
- {0x28, 0x28},
- {0x29, 0x29},
- {0x2a, 0x2a},
- {0x2b, 0x2b},
- {0x2c, 0x2c},
- {0x2d, 0x2d},
- {0x2e, 0x2e},
- {0x2f, 0x2f},
- {0x30, 0x30},
- {0x31, 0x31},
- {0x32, 0x32},
- {0x33, 0x33},
- {0x34, 0x34},
- {0x35, 0x35},
- {0x36, 0x36},
- {0x37, 0x37},
- {0x38, 0x38},
- {0x39, 0x39},
- {0x3a, 0x3a},
- {0x3b, 0x3b},
- {0x3c, 0x3c},
- {0x3d, 0x3d},
- {0x3e, 0x3e},
- {0x3f, 0x3f},
- {0x40, 0x40},
- {0x41, 0x41},
- {0x42, 0x42},
- {0x43, 0x43},
- {0x44, 0x44},
- {0x45, 0x45},
- {0x46, 0x46},
- {0x47, 0x47},
- {0x48, 0x48},
- {0x49, 0x49},
- {0x4a, 0x4a},
- {0x4b, 0x4b},
- {0x4c, 0x4c},
- {0x4d, 0x4d},
- {0x4e, 0x4e},
- {0x4f, 0x4f},
- {0x50, 0x50},
- {0x51, 0x51},
- {0x52, 0x52},
- {0x53, 0x53},
- {0x54, 0x54},
- {0x55, 0x55},
- {0x56, 0x56},
- {0x57, 0x57},
- {0x58, 0x58},
- {0x59, 0x59},
- {0x5a, 0x5a},
- {0x5b, 0x5b},
- {0x5c, 0x5c},
- {0x5d, 0x5d},
- {0x5e, 0x5e},
- {0x5f, 0x5f},
- {0x60, 0x60},
- {0x61, 0x61},
- {0x62, 0x62},
- {0x63, 0x63},
- {0x64, 0x64},
- {0x65, 0x65},
- {0x66, 0x66},
- {0x67, 0x67},
- {0x68, 0x68},
- {0x69, 0x69},
- {0x6a, 0x6a},
- {0x6b, 0x6b},
- {0x6c, 0x6c},
- {0x6d, 0x6d},
- {0x6e, 0x6e},
- {0x6f, 0x6f},
- {0x70, 0x70},
- {0x71, 0x71},
- {0x72, 0x72},
- {0x73, 0x73},
- {0x74, 0x74},
- {0x75, 0x75},
- {0x76, 0x76},
- {0x77, 0x77},
- {0x78, 0x78},
- {0x79, 0x79},
- {0x7a, 0x7a},
- {0x7b, 0x7b},
- {0x7c, 0x7c},
- {0x7d, 0x7d},
- {0x7e, 0x7e},
- {0xa0, 0xa0},
- {0xa1, 0xa1},
- {0xa2, 0xa2},
- {0xa3, 0xa3},
- {0xa4, 0xa4},
- {0xa5, 0xa5},
- {0xa6, 0xa6},
- {0xa7, 0xa7},
- {0xa8, 0xa8},
- {0xa9, 0xa9},
- {0xaa, 0xaa},
- {0xab, 0xab},
- {0xac, 0xac},
- {0xad, 0xad},
- {0xae, 0xae},
- {0xaf, 0xaf},
- {0xb0, 0xb0},
- {0xb1, 0xb1},
- {0xb2, 0xb2},
- {0xb3, 0xb3},
- {0xb4, 0xb4},
- {0xb5, 0xb5},
- {0xb6, 0xb6},
- {0xb7, 0xb7},
- {0xb8, 0xb8},
- {0xb9, 0xb9},
- {0xba, 0xba},
- {0xbb, 0xbb},
- {0xbc, 0xbc},
- {0xbd, 0xbd},
- {0xbe, 0xbe},
- {0xbf, 0xbf},
- {0xc0, 0xc0},
- {0xc1, 0xc1},
- {0xc2, 0xc2},
- {0xc3, 0xc3},
- {0xc4, 0xc4},
- {0xc5, 0xc5},
- {0xc6, 0xc6},
- {0xc7, 0xc7},
- {0xc8, 0xc8},
- {0xc9, 0xc9},
- {0xca, 0xca},
- {0xcb, 0xcb},
- {0xcc, 0xcc},
- {0xcd, 0xcd},
- {0xce, 0xce},
- {0xcf, 0xcf},
- {0xd0, 0xd0},
- {0xd1, 0xd1},
- {0xd2, 0xd2},
- {0xd3, 0xd3},
- {0xd4, 0xd4},
- {0xd5, 0xd5},
- {0xd6, 0xd6},
- {0xd7, 0xd7},
- {0xd8, 0xd8},
- {0xd9, 0xd9},
- {0xda, 0xda},
- {0xdb, 0xdb},
- {0xdc, 0xdc},
- {0xdd, 0xdd},
- {0xde, 0xde},
- {0xdf, 0xdf},
- {0xe0, 0xe0},
- {0xe1, 0xe1},
- {0xe2, 0xe2},
- {0xe3, 0xe3},
- {0xe4, 0xe4},
- {0xe5, 0xe5},
- {0xe6, 0xe6},
- {0xe7, 0xe7},
- {0xe8, 0xe8},
- {0xe9, 0xe9},
- {0xea, 0xea},
- {0xeb, 0xeb},
- {0xec, 0xec},
- {0xed, 0xed},
- {0xee, 0xee},
- {0xef, 0xef},
- {0xf0, 0xf0},
- {0xf1, 0xf1},
- {0xf2, 0xf2},
- {0xf3, 0xf3},
- {0xf4, 0xf4},
- {0xf5, 0xf5},
- {0xf6, 0xf6},
- {0xf7, 0xf7},
- {0xf8, 0xf8},
- {0xf9, 0xf9},
- {0xfa, 0xfa},
- {0xfb, 0xfb},
- {0xfc, 0xfc},
- {0xfd, 0xfd},
- {0xfe, 0xfe},
- {0xff, 0xff},
- {0x1a1, 0x104},
- {0x1a2, 0x2d8},
- {0x1a3, 0x141},
- {0x1a5, 0x13d},
- {0x1a6, 0x15a},
- {0x1a9, 0x160},
- {0x1aa, 0x15e},
- {0x1ab, 0x164},
- {0x1ac, 0x179},
- {0x1ae, 0x17d},
- {0x1af, 0x17b},
- {0x1b1, 0x105},
- {0x1b2, 0x2db},
- {0x1b3, 0x142},
- {0x1b5, 0x13e},
- {0x1b6, 0x15b},
- {0x1b7, 0x2c7},
- {0x1b9, 0x161},
- {0x1ba, 0x15f},
- {0x1bb, 0x165},
- {0x1bc, 0x17a},
- {0x1bd, 0x2dd},
- {0x1be, 0x17e},
- {0x1bf, 0x17c},
- {0x1c0, 0x154},
- {0x1c3, 0x102},
- {0x1c5, 0x139},
- {0x1c6, 0x106},
- {0x1c8, 0x10c},
- {0x1ca, 0x118},
- {0x1cc, 0x11a},
- {0x1cf, 0x10e},
- {0x1d0, 0x110},
- {0x1d1, 0x143},
- {0x1d2, 0x147},
- {0x1d5, 0x150},
- {0x1d8, 0x158},
- {0x1d9, 0x16e},
- {0x1db, 0x170},
- {0x1de, 0x162},
- {0x1e0, 0x155},
- {0x1e3, 0x103},
- {0x1e5, 0x13a},
- {0x1e6, 0x107},
- {0x1e8, 0x10d},
- {0x1ea, 0x119},
- {0x1ec, 0x11b},
- {0x1ef, 0x10f},
- {0x1f0, 0x111},
- {0x1f1, 0x144},
- {0x1f2, 0x148},
- {0x1f5, 0x151},
- {0x1f8, 0x159},
- {0x1f9, 0x16f},
- {0x1fb, 0x171},
- {0x1fe, 0x163},
- {0x1ff, 0x2d9},
- {0x2a1, 0x126},
- {0x2a6, 0x124},
- {0x2a9, 0x130},
- {0x2ab, 0x11e},
- {0x2ac, 0x134},
- {0x2b1, 0x127},
- {0x2b6, 0x125},
- {0x2b9, 0x131},
- {0x2bb, 0x11f},
- {0x2bc, 0x135},
- {0x2c5, 0x10a},
- {0x2c6, 0x108},
- {0x2d5, 0x120},
- {0x2d8, 0x11c},
- {0x2dd, 0x16c},
- {0x2de, 0x15c},
- {0x2e5, 0x10b},
- {0x2e6, 0x109},
- {0x2f5, 0x121},
- {0x2f8, 0x11d},
- {0x2fd, 0x16d},
- {0x2fe, 0x15d},
- {0x3a2, 0x138},
- {0x3a3, 0x156},
- {0x3a5, 0x128},
- {0x3a6, 0x13b},
- {0x3aa, 0x112},
- {0x3ab, 0x122},
- {0x3ac, 0x166},
- {0x3b3, 0x157},
- {0x3b5, 0x129},
- {0x3b6, 0x13c},
- {0x3ba, 0x113},
- {0x3bb, 0x123},
- {0x3bc, 0x167},
- {0x3bd, 0x14a},
- {0x3bf, 0x14b},
- {0x3c0, 0x100},
- {0x3c7, 0x12e},
- {0x3cc, 0x116},
- {0x3cf, 0x12a},
- {0x3d1, 0x145},
- {0x3d2, 0x14c},
- {0x3d3, 0x136},
- {0x3d9, 0x172},
- {0x3dd, 0x168},
- {0x3de, 0x16a},
- {0x3e0, 0x101},
- {0x3e7, 0x12f},
- {0x3ec, 0x117},
- {0x3ef, 0x12b},
- {0x3f1, 0x146},
- {0x3f2, 0x14d},
- {0x3f3, 0x137},
- {0x3f9, 0x173},
- {0x3fd, 0x169},
- {0x3fe, 0x16b},
- {0x47e, 0x203e},
- {0x4a1, 0x3002},
- {0x4a2, 0x300c},
- {0x4a3, 0x300d},
- {0x4a4, 0x3001},
- {0x4a5, 0x30fb},
- {0x4a6, 0x30f2},
- {0x4a7, 0x30a1},
- {0x4a8, 0x30a3},
- {0x4a9, 0x30a5},
- {0x4aa, 0x30a7},
- {0x4ab, 0x30a9},
- {0x4ac, 0x30e3},
- {0x4ad, 0x30e5},
- {0x4ae, 0x30e7},
- {0x4af, 0x30c3},
- {0x4b0, 0x30fc},
- {0x4b1, 0x30a2},
- {0x4b2, 0x30a4},
- {0x4b3, 0x30a6},
- {0x4b4, 0x30a8},
- {0x4b5, 0x30aa},
- {0x4b6, 0x30ab},
- {0x4b7, 0x30ad},
- {0x4b8, 0x30af},
- {0x4b9, 0x30b1},
- {0x4ba, 0x30b3},
- {0x4bb, 0x30b5},
- {0x4bc, 0x30b7},
- {0x4bd, 0x30b9},
- {0x4be, 0x30bb},
- {0x4bf, 0x30bd},
- {0x4c0, 0x30bf},
- {0x4c1, 0x30c1},
- {0x4c2, 0x30c4},
- {0x4c3, 0x30c6},
- {0x4c4, 0x30c8},
- {0x4c5, 0x30ca},
- {0x4c6, 0x30cb},
- {0x4c7, 0x30cc},
- {0x4c8, 0x30cd},
- {0x4c9, 0x30ce},
- {0x4ca, 0x30cf},
- {0x4cb, 0x30d2},
- {0x4cc, 0x30d5},
- {0x4cd, 0x30d8},
- {0x4ce, 0x30db},
- {0x4cf, 0x30de},
- {0x4d0, 0x30df},
- {0x4d1, 0x30e0},
- {0x4d2, 0x30e1},
- {0x4d3, 0x30e2},
- {0x4d4, 0x30e4},
- {0x4d5, 0x30e6},
- {0x4d6, 0x30e8},
- {0x4d7, 0x30e9},
- {0x4d8, 0x30ea},
- {0x4d9, 0x30eb},
- {0x4da, 0x30ec},
- {0x4db, 0x30ed},
- {0x4dc, 0x30ef},
- {0x4dd, 0x30f3},
- {0x4de, 0x309b},
- {0x4df, 0x309c},
- {0x5ac, 0x60c},
- {0x5bb, 0x61b},
- {0x5bf, 0x61f},
- {0x5c1, 0x621},
- {0x5c2, 0x622},
- {0x5c3, 0x623},
- {0x5c4, 0x624},
- {0x5c5, 0x625},
- {0x5c6, 0x626},
- {0x5c7, 0x627},
- {0x5c8, 0x628},
- {0x5c9, 0x629},
- {0x5ca, 0x62a},
- {0x5cb, 0x62b},
- {0x5cc, 0x62c},
- {0x5cd, 0x62d},
- {0x5ce, 0x62e},
- {0x5cf, 0x62f},
- {0x5d0, 0x630},
- {0x5d1, 0x631},
- {0x5d2, 0x632},
- {0x5d3, 0x633},
- {0x5d4, 0x634},
- {0x5d5, 0x635},
- {0x5d6, 0x636},
- {0x5d7, 0x637},
- {0x5d8, 0x638},
- {0x5d9, 0x639},
- {0x5da, 0x63a},
- {0x5e0, 0x640},
- {0x5e1, 0x641},
- {0x5e2, 0x642},
- {0x5e3, 0x643},
- {0x5e4, 0x644},
- {0x5e5, 0x645},
- {0x5e6, 0x646},
- {0x5e7, 0x647},
- {0x5e8, 0x648},
- {0x5e9, 0x649},
- {0x5ea, 0x64a},
- {0x5eb, 0x64b},
- {0x5ec, 0x64c},
- {0x5ed, 0x64d},
- {0x5ee, 0x64e},
- {0x5ef, 0x64f},
- {0x5f0, 0x650},
- {0x5f1, 0x651},
- {0x5f2, 0x652},
- {0x6a1, 0x452},
- {0x6a2, 0x453},
- {0x6a3, 0x451},
- {0x6a4, 0x454},
- {0x6a5, 0x455},
- {0x6a6, 0x456},
- {0x6a7, 0x457},
- {0x6a8, 0x458},
- {0x6a9, 0x459},
- {0x6aa, 0x45a},
- {0x6ab, 0x45b},
- {0x6ac, 0x45c},
- {0x6ae, 0x45e},
- {0x6af, 0x45f},
- {0x6b0, 0x2116},
- {0x6b1, 0x402},
- {0x6b2, 0x403},
- {0x6b3, 0x401},
- {0x6b4, 0x404},
- {0x6b5, 0x405},
- {0x6b6, 0x406},
- {0x6b7, 0x407},
- {0x6b8, 0x408},
- {0x6b9, 0x409},
- {0x6ba, 0x40a},
- {0x6bb, 0x40b},
- {0x6bc, 0x40c},
- {0x6be, 0x40e},
- {0x6bf, 0x40f},
- {0x6c0, 0x44e},
- {0x6c1, 0x430},
- {0x6c2, 0x431},
- {0x6c3, 0x446},
- {0x6c4, 0x434},
- {0x6c5, 0x435},
- {0x6c6, 0x444},
- {0x6c7, 0x433},
- {0x6c8, 0x445},
- {0x6c9, 0x438},
- {0x6ca, 0x439},
- {0x6cb, 0x43a},
- {0x6cc, 0x43b},
- {0x6cd, 0x43c},
- {0x6ce, 0x43d},
- {0x6cf, 0x43e},
- {0x6d0, 0x43f},
- {0x6d1, 0x44f},
- {0x6d2, 0x440},
- {0x6d3, 0x441},
- {0x6d4, 0x442},
- {0x6d5, 0x443},
- {0x6d6, 0x436},
- {0x6d7, 0x432},
- {0x6d8, 0x44c},
- {0x6d9, 0x44b},
- {0x6da, 0x437},
- {0x6db, 0x448},
- {0x6dc, 0x44d},
- {0x6dd, 0x449},
- {0x6de, 0x447},
- {0x6df, 0x44a},
- {0x6e0, 0x42e},
- {0x6e1, 0x410},
- {0x6e2, 0x411},
- {0x6e3, 0x426},
- {0x6e4, 0x414},
- {0x6e5, 0x415},
- {0x6e6, 0x424},
- {0x6e7, 0x413},
- {0x6e8, 0x425},
- {0x6e9, 0x418},
- {0x6ea, 0x419},
- {0x6eb, 0x41a},
- {0x6ec, 0x41b},
- {0x6ed, 0x41c},
- {0x6ee, 0x41d},
- {0x6ef, 0x41e},
- {0x6f0, 0x41f},
- {0x6f1, 0x42f},
- {0x6f2, 0x420},
- {0x6f3, 0x421},
- {0x6f4, 0x422},
- {0x6f5, 0x423},
- {0x6f6, 0x416},
- {0x6f7, 0x412},
- {0x6f8, 0x42c},
- {0x6f9, 0x42b},
- {0x6fa, 0x417},
- {0x6fb, 0x428},
- {0x6fc, 0x42d},
- {0x6fd, 0x429},
- {0x6fe, 0x427},
- {0x6ff, 0x42a},
- {0x7a1, 0x386},
- {0x7a2, 0x388},
- {0x7a3, 0x389},
- {0x7a4, 0x38a},
- {0x7a5, 0x3aa},
- {0x7a7, 0x38c},
- {0x7a8, 0x38e},
- {0x7a9, 0x3ab},
- {0x7ab, 0x38f},
- {0x7ae, 0x385},
- {0x7af, 0x2015},
- {0x7b1, 0x3ac},
- {0x7b2, 0x3ad},
- {0x7b3, 0x3ae},
- {0x7b4, 0x3af},
- {0x7b5, 0x3ca},
- {0x7b6, 0x390},
- {0x7b7, 0x3cc},
- {0x7b8, 0x3cd},
- {0x7b9, 0x3cb},
- {0x7ba, 0x3b0},
- {0x7bb, 0x3ce},
- {0x7c1, 0x391},
- {0x7c2, 0x392},
- {0x7c3, 0x393},
- {0x7c4, 0x394},
- {0x7c5, 0x395},
- {0x7c6, 0x396},
- {0x7c7, 0x397},
- {0x7c8, 0x398},
- {0x7c9, 0x399},
- {0x7ca, 0x39a},
- {0x7cb, 0x39b},
- {0x7cc, 0x39c},
- {0x7cd, 0x39d},
- {0x7ce, 0x39e},
- {0x7cf, 0x39f},
- {0x7d0, 0x3a0},
- {0x7d1, 0x3a1},
- {0x7d2, 0x3a3},
- {0x7d4, 0x3a4},
- {0x7d5, 0x3a5},
- {0x7d6, 0x3a6},
- {0x7d7, 0x3a7},
- {0x7d8, 0x3a8},
- {0x7d9, 0x3a9},
- {0x7e1, 0x3b1},
- {0x7e2, 0x3b2},
- {0x7e3, 0x3b3},
- {0x7e4, 0x3b4},
- {0x7e5, 0x3b5},
- {0x7e6, 0x3b6},
- {0x7e7, 0x3b7},
- {0x7e8, 0x3b8},
- {0x7e9, 0x3b9},
- {0x7ea, 0x3ba},
- {0x7eb, 0x3bb},
- {0x7ec, 0x3bc},
- {0x7ed, 0x3bd},
- {0x7ee, 0x3be},
- {0x7ef, 0x3bf},
- {0x7f0, 0x3c0},
- {0x7f1, 0x3c1},
- {0x7f2, 0x3c3},
- {0x7f3, 0x3c2},
- {0x7f4, 0x3c4},
- {0x7f5, 0x3c5},
- {0x7f6, 0x3c6},
- {0x7f7, 0x3c7},
- {0x7f8, 0x3c8},
- {0x7f9, 0x3c9},
- {0x8a1, 0x23b7},
- {0x8a2, 0x250c},
- {0x8a3, 0x2500},
- {0x8a4, 0x2320},
- {0x8a5, 0x2321},
- {0x8a6, 0x2502},
- {0x8a7, 0x23a1},
- {0x8a8, 0x23a3},
- {0x8a9, 0x23a4},
- {0x8aa, 0x23a6},
- {0x8ab, 0x239b},
- {0x8ac, 0x239d},
- {0x8ad, 0x239e},
- {0x8ae, 0x23a0},
- {0x8af, 0x23a8},
- {0x8b0, 0x23ac},
- {0x8bc, 0x2264},
- {0x8bd, 0x2260},
- {0x8be, 0x2265},
- {0x8bf, 0x222b},
- {0x8c0, 0x2234},
- {0x8c1, 0x221d},
- {0x8c2, 0x221e},
- {0x8c5, 0x2207},
- {0x8c8, 0x223c},
- {0x8c9, 0x2243},
- {0x8cd, 0x21d4},
- {0x8ce, 0x21d2},
- {0x8cf, 0x2261},
- {0x8d6, 0x221a},
- {0x8da, 0x2282},
- {0x8db, 0x2283},
- {0x8dc, 0x2229},
- {0x8dd, 0x222a},
- {0x8de, 0x2227},
- {0x8df, 0x2228},
- {0x8ef, 0x2202},
- {0x8f6, 0x192},
- {0x8fb, 0x2190},
- {0x8fc, 0x2191},
- {0x8fd, 0x2192},
- {0x8fe, 0x2193},
- {0x9e0, 0x25c6},
- {0x9e1, 0x2592},
- {0x9e2, 0x2409},
- {0x9e3, 0x240c},
- {0x9e4, 0x240d},
- {0x9e5, 0x240a},
- {0x9e8, 0x2424},
- {0x9e9, 0x240b},
- {0x9ea, 0x2518},
- {0x9eb, 0x2510},
- {0x9ec, 0x250c},
- {0x9ed, 0x2514},
- {0x9ee, 0x253c},
- {0x9ef, 0x23ba},
- {0x9f0, 0x23bb},
- {0x9f1, 0x2500},
- {0x9f2, 0x23bc},
- {0x9f3, 0x23bd},
- {0x9f4, 0x251c},
- {0x9f5, 0x2524},
- {0x9f6, 0x2534},
- {0x9f7, 0x252c},
- {0x9f8, 0x2502},
- {0xaa1, 0x2003},
- {0xaa2, 0x2002},
- {0xaa3, 0x2004},
- {0xaa4, 0x2005},
- {0xaa5, 0x2007},
- {0xaa6, 0x2008},
- {0xaa7, 0x2009},
- {0xaa8, 0x200a},
- {0xaa9, 0x2014},
- {0xaaa, 0x2013},
- {0xaae, 0x2026},
- {0xaaf, 0x2025},
- {0xab0, 0x2153},
- {0xab1, 0x2154},
- {0xab2, 0x2155},
- {0xab3, 0x2156},
- {0xab4, 0x2157},
- {0xab5, 0x2158},
- {0xab6, 0x2159},
- {0xab7, 0x215a},
- {0xab8, 0x2105},
- {0xabb, 0x2012},
- {0xabc, 0x2329},
- {0xabe, 0x232a},
- {0xac3, 0x215b},
- {0xac4, 0x215c},
- {0xac5, 0x215d},
- {0xac6, 0x215e},
- {0xac9, 0x2122},
- {0xaca, 0x2613},
- {0xacc, 0x25c1},
- {0xacd, 0x25b7},
- {0xace, 0x25cb},
- {0xacf, 0x25af},
- {0xad0, 0x2018},
- {0xad1, 0x2019},
- {0xad2, 0x201c},
- {0xad3, 0x201d},
- {0xad4, 0x211e},
- {0xad6, 0x2032},
- {0xad7, 0x2033},
- {0xad9, 0x271d},
- {0xadb, 0x25ac},
- {0xadc, 0x25c0},
- {0xadd, 0x25b6},
- {0xade, 0x25cf},
- {0xadf, 0x25ae},
- {0xae0, 0x25e6},
- {0xae1, 0x25ab},
- {0xae2, 0x25ad},
- {0xae3, 0x25b3},
- {0xae4, 0x25bd},
- {0xae5, 0x2606},
- {0xae6, 0x2022},
- {0xae7, 0x25aa},
- {0xae8, 0x25b2},
- {0xae9, 0x25bc},
- {0xaea, 0x261c},
- {0xaeb, 0x261e},
- {0xaec, 0x2663},
- {0xaed, 0x2666},
- {0xaee, 0x2665},
- {0xaf0, 0x2720},
- {0xaf1, 0x2020},
- {0xaf2, 0x2021},
- {0xaf3, 0x2713},
- {0xaf4, 0x2717},
- {0xaf5, 0x266f},
- {0xaf6, 0x266d},
- {0xaf7, 0x2642},
- {0xaf8, 0x2640},
- {0xaf9, 0x260e},
- {0xafa, 0x2315},
- {0xafb, 0x2117},
- {0xafc, 0x2038},
- {0xafd, 0x201a},
- {0xafe, 0x201e},
- {0xba3, 0x3c},
- {0xba6, 0x3e},
- {0xba8, 0x2228},
- {0xba9, 0x2227},
- {0xbc0, 0xaf},
- {0xbc2, 0x22a5},
- {0xbc3, 0x2229},
- {0xbc4, 0x230a},
- {0xbc6, 0x5f},
- {0xbca, 0x2218},
- {0xbcc, 0x2395},
- {0xbce, 0x22a4},
- {0xbcf, 0x25cb},
- {0xbd3, 0x2308},
- {0xbd6, 0x222a},
- {0xbd8, 0x2283},
- {0xbda, 0x2282},
- {0xbdc, 0x22a2},
- {0xbfc, 0x22a3},
- {0xcdf, 0x2017},
- {0xce0, 0x5d0},
- {0xce1, 0x5d1},
- {0xce2, 0x5d2},
- {0xce3, 0x5d3},
- {0xce4, 0x5d4},
- {0xce5, 0x5d5},
- {0xce6, 0x5d6},
- {0xce7, 0x5d7},
- {0xce8, 0x5d8},
- {0xce9, 0x5d9},
- {0xcea, 0x5da},
- {0xceb, 0x5db},
- {0xcec, 0x5dc},
- {0xced, 0x5dd},
- {0xcee, 0x5de},
- {0xcef, 0x5df},
- {0xcf0, 0x5e0},
- {0xcf1, 0x5e1},
- {0xcf2, 0x5e2},
- {0xcf3, 0x5e3},
- {0xcf4, 0x5e4},
- {0xcf5, 0x5e5},
- {0xcf6, 0x5e6},
- {0xcf7, 0x5e7},
- {0xcf8, 0x5e8},
- {0xcf9, 0x5e9},
- {0xcfa, 0x5ea},
- {0xda1, 0xe01},
- {0xda2, 0xe02},
- {0xda3, 0xe03},
- {0xda4, 0xe04},
- {0xda5, 0xe05},
- {0xda6, 0xe06},
- {0xda7, 0xe07},
- {0xda8, 0xe08},
- {0xda9, 0xe09},
- {0xdaa, 0xe0a},
- {0xdab, 0xe0b},
- {0xdac, 0xe0c},
- {0xdad, 0xe0d},
- {0xdae, 0xe0e},
- {0xdaf, 0xe0f},
- {0xdb0, 0xe10},
- {0xdb1, 0xe11},
- {0xdb2, 0xe12},
- {0xdb3, 0xe13},
- {0xdb4, 0xe14},
- {0xdb5, 0xe15},
- {0xdb6, 0xe16},
- {0xdb7, 0xe17},
- {0xdb8, 0xe18},
- {0xdb9, 0xe19},
- {0xdba, 0xe1a},
- {0xdbb, 0xe1b},
- {0xdbc, 0xe1c},
- {0xdbd, 0xe1d},
- {0xdbe, 0xe1e},
- {0xdbf, 0xe1f},
- {0xdc0, 0xe20},
- {0xdc1, 0xe21},
- {0xdc2, 0xe22},
- {0xdc3, 0xe23},
- {0xdc4, 0xe24},
- {0xdc5, 0xe25},
- {0xdc6, 0xe26},
- {0xdc7, 0xe27},
- {0xdc8, 0xe28},
- {0xdc9, 0xe29},
- {0xdca, 0xe2a},
- {0xdcb, 0xe2b},
- {0xdcc, 0xe2c},
- {0xdcd, 0xe2d},
- {0xdce, 0xe2e},
- {0xdcf, 0xe2f},
- {0xdd0, 0xe30},
- {0xdd1, 0xe31},
- {0xdd2, 0xe32},
- {0xdd3, 0xe33},
- {0xdd4, 0xe34},
- {0xdd5, 0xe35},
- {0xdd6, 0xe36},
- {0xdd7, 0xe37},
- {0xdd8, 0xe38},
- {0xdd9, 0xe39},
- {0xdda, 0xe3a},
- {0xddf, 0xe3f},
- {0xde0, 0xe40},
- {0xde1, 0xe41},
- {0xde2, 0xe42},
- {0xde3, 0xe43},
- {0xde4, 0xe44},
- {0xde5, 0xe45},
- {0xde6, 0xe46},
- {0xde7, 0xe47},
- {0xde8, 0xe48},
- {0xde9, 0xe49},
- {0xdea, 0xe4a},
- {0xdeb, 0xe4b},
- {0xdec, 0xe4c},
- {0xded, 0xe4d},
- {0xdf0, 0xe50},
- {0xdf1, 0xe51},
- {0xdf2, 0xe52},
- {0xdf3, 0xe53},
- {0xdf4, 0xe54},
- {0xdf5, 0xe55},
- {0xdf6, 0xe56},
- {0xdf7, 0xe57},
- {0xdf8, 0xe58},
- {0xdf9, 0xe59},
- {0xea1, 0x3131},
- {0xea2, 0x3132},
- {0xea3, 0x3133},
- {0xea4, 0x3134},
- {0xea5, 0x3135},
- {0xea6, 0x3136},
- {0xea7, 0x3137},
- {0xea8, 0x3138},
- {0xea9, 0x3139},
- {0xeaa, 0x313a},
- {0xeab, 0x313b},
- {0xeac, 0x313c},
- {0xead, 0x313d},
- {0xeae, 0x313e},
- {0xeaf, 0x313f},
- {0xeb0, 0x3140},
- {0xeb1, 0x3141},
- {0xeb2, 0x3142},
- {0xeb3, 0x3143},
- {0xeb4, 0x3144},
- {0xeb5, 0x3145},
- {0xeb6, 0x3146},
- {0xeb7, 0x3147},
- {0xeb8, 0x3148},
- {0xeb9, 0x3149},
- {0xeba, 0x314a},
- {0xebb, 0x314b},
- {0xebc, 0x314c},
- {0xebd, 0x314d},
- {0xebe, 0x314e},
- {0xebf, 0x314f},
- {0xec0, 0x3150},
- {0xec1, 0x3151},
- {0xec2, 0x3152},
- {0xec3, 0x3153},
- {0xec4, 0x3154},
- {0xec5, 0x3155},
- {0xec6, 0x3156},
- {0xec7, 0x3157},
- {0xec8, 0x3158},
- {0xec9, 0x3159},
- {0xeca, 0x315a},
- {0xecb, 0x315b},
- {0xecc, 0x315c},
- {0xecd, 0x315d},
- {0xece, 0x315e},
- {0xecf, 0x315f},
- {0xed0, 0x3160},
- {0xed1, 0x3161},
- {0xed2, 0x3162},
- {0xed3, 0x3163},
- {0xed4, 0x11a8},
- {0xed5, 0x11a9},
- {0xed6, 0x11aa},
- {0xed7, 0x11ab},
- {0xed8, 0x11ac},
- {0xed9, 0x11ad},
- {0xeda, 0x11ae},
- {0xedb, 0x11af},
- {0xedc, 0x11b0},
- {0xedd, 0x11b1},
- {0xede, 0x11b2},
- {0xedf, 0x11b3},
- {0xee0, 0x11b4},
- {0xee1, 0x11b5},
- {0xee2, 0x11b6},
- {0xee3, 0x11b7},
- {0xee4, 0x11b8},
- {0xee5, 0x11b9},
- {0xee6, 0x11ba},
- {0xee7, 0x11bb},
- {0xee8, 0x11bc},
- {0xee9, 0x11bd},
- {0xeea, 0x11be},
- {0xeeb, 0x11bf},
- {0xeec, 0x11c0},
- {0xeed, 0x11c1},
- {0xeee, 0x11c2},
- {0xeef, 0x316d},
- {0xef0, 0x3171},
- {0xef1, 0x3178},
- {0xef2, 0x317f},
- {0xef3, 0x3181},
- {0xef4, 0x3184},
- {0xef5, 0x3186},
- {0xef6, 0x318d},
- {0xef7, 0x318e},
- {0xef8, 0x11eb},
- {0xef9, 0x11f0},
- {0xefa, 0x11f9},
- {0xeff, 0x20a9},
- {0x13a4, 0x20ac},
- {0x13bc, 0x152},
- {0x13bd, 0x153},
- {0x13be, 0x178},
- {0x20a0, 0x20a0},
- {0x20a1, 0x20a1},
- {0x20a2, 0x20a2},
- {0x20a3, 0x20a3},
- {0x20a4, 0x20a4},
- {0x20a5, 0x20a5},
- {0x20a6, 0x20a6},
- {0x20a7, 0x20a7},
- {0x20a8, 0x20a8},
- {0x20aa, 0x20aa},
- {0x20ab, 0x20ab},
- {0x20ac, 0x20ac},
-};
-
-int keysym_to_unicode(int keysym)
-{
- int i, j, k;
-
- i = -1;
- j = lenof(keysyms);
-
- while (j - i >= 2) {
- k = (j + i) / 2;
- if (keysyms[k].keysym == keysym)
- return keysyms[k].unicode;
- else if (keysyms[k].keysym < keysym)
- i = k;
- else
- j = k;
- }
- return -1;
-}
diff --git a/unix/xpmptcfg.c b/unix/xpmptcfg.c
deleted file mode 100644
index 92835c15..00000000
--- a/unix/xpmptcfg.c
+++ /dev/null
@@ -1,150 +0,0 @@
-/* XPM */
-static const char *const cfg_icon_0[] = {
-/* columns rows colors chars-per-pixel */
-"16 16 9 1",
-" c black",
-". c navy",
-"X c blue",
-"o c #808000",
-"O c yellow",
-"+ c #808080",
-"@ c #C0C0C0",
-"# c gray100",
-"$ c None",
-/* pixels */
-"$$$ $$$$$$$$$$$",
-"$$ OO $$$$",
-"$ +oO+###@+ $$$",
-" o #.oO.XX@+ $$$",
-" oO+.OO.XX@+ $$$",
-"$ oOOOO.XX@+ $$$",
-"$$ oooOO.X@+ $$$",
-"$$ +..oOO.@+ $$$",
-"$$ @@@+oOO++ $$",
-"$ +++++ oOO #+ $",
-" #######+oOO++ $",
-" #@@@@@++ oOO $",
-" @++++++++ oOO $",
-"$ oOO ",
-"$$$$$$$$$$$$ oO ",
-"$$$$$$$$$$$$$ $"
-};
-
-/* XPM */
-static const char *const cfg_icon_1[] = {
-/* columns rows colors chars-per-pixel */
-"32 32 9 1",
-" c black",
-". c navy",
-"X c blue",
-"o c #808000",
-"O c yellow",
-"+ c #808080",
-"@ c #C0C0C0",
-"# c gray100",
-"$ c None",
-/* pixels */
-"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
-"$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$",
-"$$$$$ OO $$$$$$$$$$$$$$$$$$$$$$",
-"$$$$$ ooOO $$$$$$$$$$$$$$$$$$$$$",
-"$$$$$$ ooOO $$$$$$",
-"$$ $$$ oOO @@@@@@@@@@@@@+ $$$$$",
-"$ oO $$ oOOO @@@@@@@@@@@++ $$$$$",
-"$ oOO oOOOO #########@+++ $$$$$",
-"$$ oOOOOOOO ..........@+++ $$$$$",
-"$$ ooOOOOOOO XXXXXXXXX@+++ $$$$$",
-"$$$ ooooooOOO XXXXXXXX@+++ $$$$$",
-"$$$$ oo ooOOO XXXXXXX@+++ $$$$$",
-"$$$$$$ . ooOOO XXXXXX@+++ $$$$$",
-"$$$$$$ #.X ooOOO XXXXX@+++ $$$$$",
-"$$$$$$ #.XX ooOOO XXXX@+++ $$$$$",
-"$$$$$$ #.XXX ooOOO XXX@+++ $$$$$",
-"$$$$$$ #.XXXX ooOOO XX@+++ $$$$$",
-"$$$$$$ ####### ooOOO #@+++ $$$",
-"$$$$$ #@@@@@@@ ooOOO +++ @#+ $$",
-"$$$$ @ @++++++++ ooOOO + @#++ $$",
-"$$$ @@ ooOOO @#+++ $$",
-"$$ ############### ooOOO @+++ $$",
-"$$ #@@@@@@@@@@@@@@@ ooOOO +++ $$",
-"$$ #@@@@@@@@@@@@@@@@ ooOOO + $$$",
-"$$ #@@@@@@@@@@@@+ ooOOO $$$$",
-"$$ @++++++++++++++++++ ooOOO $$$",
-"$$$ ooOOO $$",
-"$$$$$$$$$$$$$$$$$$$$$$$$ ooO $$$",
-"$$$$$$$$$$$$$$$$$$$$$$$$$ o $$$$",
-"$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$",
-"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
-"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"
-};
-
-/* XPM */
-static const char *const cfg_icon_2[] = {
-/* columns rows colors chars-per-pixel */
-"48 48 9 1",
-" c black",
-". c navy",
-"X c blue",
-"o c #808000",
-"O c yellow",
-"+ c #808080",
-"@ c #C0C0C0",
-"# c gray100",
-"$ c None",
-/* pixels */
-"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
-"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
-"$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
-"$$$$$$$$ OO $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
-"$$$$$$$$ oOOOO $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
-"$$$$$$$$$ ooOOO $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
-"$$$$$$$$$$ ooOOO $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
-"$$$$$$$$$$$ oOOO $$$$$$$$$$",
-"$$$ $$$$$$ oOOO @@@@@@@@@@@@@@@@@@@@+ $$$$$$$$$",
-"$$ oO $$$$$ oOOOO @@@@@@@@@@@@@@@@@@++ $$$$$$$$$",
-"$$ ooO $$$ oOOOO @@@@@@@@@@@@@@@@@+++ $$$$$$$$$",
-"$$$ oOO OOOOO ################@++++ $$$$$$$$$",
-"$$$ ooOOOOOOOOOOO ++++++++++++++@+++++ $$$$$$$$$",
-"$$$ ooOOOOOOOOOOOO .............#+++++ $$$$$$$$$",
-"$$$$ oooOOOOoOOOOOO XXXXXXXXXXXX#+++++ $$$$$$$$$",
-"$$$$$ oooooooOOOOOOO XXXXXXXXXXX#+++++ $$$$$$$$$",
-"$$$$$$ oo ooOOOOOOO XXXXXXXXXX#+++++ $$$$$$$$$",
-"$$$$$$$$$ + ooOOOOOOO XXXXXXXXX#+++++ $$$$$$$$$",
-"$$$$$$$$$ #+. ooOOOOOOO XXXXXXXX#+++++ $$$$$$$$$",
-"$$$$$$$$$ #+.X ooOOOOOOO XXXXXXX#+++++ $$$$$$$$$",
-"$$$$$$$$$ #+.XX ooOOOOOOO XXXXXX#+++++ $$$$$$$$$",
-"$$$$$$$$$ #+.XXX ooOOOOOOO XXXXX#+++++ $$$$$$$$$",
-"$$$$$$$$$ #+.XXXX ooOOOOOOO XXXX#+++++ $$$$$$$$$",
-"$$$$$$$$$ #+.XXXXX ooOOOOOOO XXX#+++++ $$$$$$$$$",
-"$$$$$$$$$ #+.XXXXXX ooOOOOOOO XX#+++++ $$$$$$$$$",
-"$$$$$$$$$ #+.XXXXXXX ooOOOOOOO X#+++++ $$$$$$$$$",
-"$$$$$$$$$ #+.XXXXXXXX ooOOOOOOO #+++++ $$$$$$$$$",
-"$$$$$$$$ #@########## ooOOOOOOO +++++ $$$$$",
-"$$$$$$$ @ #@@@@@@@@@@@@ ooOOOOOOO +++ @@##+ $$$$",
-"$$$$$$ @@ #@@@@@@@@@@@@@ ooOOOOOOO + @@##++ $$$$",
-"$$$$$ @@@ @++++++++++++++ ooOOOOOOO @@##+++ $$$$",
-"$$$$ @@@@ ooOOOOOOO ##++++ $$$$",
-"$$$ ####################### ooOOOOOOO @++++ $$$$",
-"$$$ ######################## ooOOOOOOO ++++ $$$$",
-"$$$ ##@@@@@@@@@@@@@@@@@@@@@@@ ooOOOOOOO +++ $$$$",
-"$$$ ##@@@@@@@@@@@@@@@@@@@@@@@@ ooOOOOOOO ++ $$$$",
-"$$$ ##@@@@@@@@@@@@@@@@@@@@@@@@@ ooOOOOOOO $$$$$",
-"$$$ ##@@@@@@@@@@@@@@@@@@ ooOOOOOOO $$$$$",
-"$$$ @@+++++++++++++++++++++++++++ ooOOOOOOO $$$$",
-"$$$ @@++++++++++++++++++++++++++++ ooOOOOOOO $$$",
-"$$$$ ooOOOOO $$$$",
-"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ooOOO $$$$$",
-"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ooO $$$$$$",
-"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ o $$$$$$$",
-"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$",
-"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
-"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$",
-"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"
-};
-
-const char *const *const cfg_icon[] = {
- cfg_icon_0,
- cfg_icon_1,
- cfg_icon_2,
-};
-const int n_cfg_icon = 3;
diff --git a/unix/xpmpterm.c b/unix/xpmpterm.c
deleted file mode 100644
index aea5e4e2..00000000
--- a/unix/xpmpterm.c
+++ /dev/null
@@ -1,143 +0,0 @@
-/* XPM */
-static const char *const main_icon_0[] = {
-/* columns rows colors chars-per-pixel */
-"16 16 6 1",
-" c black",
-". c blue",
-"X c #808080",
-"o c #C0C0C0",
-"O c gray100",
-"+ c None",
-/* pixels */
-"++++++++++++++++",
-"+++ ++++",
-"++ OOOOOOOoX +++",
-"++ O......oX +++",
-"++ O......oX +++",
-"++ O......oX +++",
-"++ O......oX +++",
-"++ O......oX +++",
-"++ ooooooooX ++",
-"+ XXXXXXXXXXOX +",
-" OOOOOOOOOOOoX +",
-" OoooooXXXXoXX +",
-" oXXXXXXXXXXX ++",
-"+ +++",
-"++++++++++++++++",
-"++++++++++++++++"
-};
-
-/* XPM */
-static const char *const main_icon_1[] = {
-/* columns rows colors chars-per-pixel */
-"32 32 7 1",
-" c black",
-". c navy",
-"X c blue",
-"o c #808080",
-"O c #C0C0C0",
-"+ c gray100",
-"@ c None",
-/* pixels */
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@ @@@@@@",
-"@@@@@@@@ OOOOOOOOOOOOOOOOo @@@@@",
-"@@@@@@@ OOOOOOOOOOOOOOOOoo @@@@@",
-"@@@@@@ +++++++++++++++Oooo @@@@@",
-"@@@@@@ +..............Oooo @@@@@",
-"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@",
-"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@",
-"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@",
-"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@",
-"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@",
-"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@",
-"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@",
-"@@@@@@ +.XXXXXXXXXXXXXOooo @@@@@",
-"@@@@@@ +++++++++++++++Oooo @@@",
-"@@@@@ +OOOOOOOOOOOOOOooo O+o @@",
-"@@@@ O Ooooooooooooooooo O+oo @@",
-"@@@ OO O+ooo @@",
-"@@ ++++++++++++++++++++++Oooo @@",
-"@@ +OOOOOOOOOOOOOOOOOOOOOoooo @@",
-"@@ +OOOOOOOOOOOOOOOOOOOOOooo @@@",
-"@@ +OOOOOOOOOOOOo oOoo @@@@",
-"@@ Ooooooooooooooooooooooo @@@@@",
-"@@@ @@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
-};
-
-/* XPM */
-static const char *const main_icon_2[] = {
-/* columns rows colors chars-per-pixel */
-"48 48 7 1",
-" c black",
-". c navy",
-"X c blue",
-"o c #808080",
-"O c #C0C0C0",
-"+ c gray100",
-"@ c None",
-/* pixels */
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@ @@@@@@@@@@",
-"@@@@@@@@@@@@ OOOOOOOOOOOOOOOOOOOOOOOOo @@@@@@@@@",
-"@@@@@@@@@@@ OOOOOOOOOOOOOOOOOOOOOOOOoo @@@@@@@@@",
-"@@@@@@@@@@ OOOOOOOOOOOOOOOOOOOOOOOOooo @@@@@@@@@",
-"@@@@@@@@@ +++++++++++++++++++++++Ooooo @@@@@@@@@",
-"@@@@@@@@@ +oooooooooooooooooooooOooooo @@@@@@@@@",
-"@@@@@@@@@ +o....................+ooooo @@@@@@@@@",
-"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
-"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
-"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
-"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
-"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
-"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
-"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
-"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
-"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
-"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
-"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
-"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
-"@@@@@@@@@ +o.XXXXXXXXXXXXXXXXXXX+ooooo @@@@@@@@@",
-"@@@@@@@@ +O+++++++++++++++++++++ooooo @@@@@",
-"@@@@@@@ O +OOOOOOOOOOOOOOOOOOOOOOoooo OO++o @@@@",
-"@@@@@@ OO +OOOOOOOOOOOOOOOOOOOOOOooo OO++oo @@@@",
-"@@@@@ OOO Ooooooooooooooooooooooooo OO++ooo @@@@",
-"@@@@ OOOO OO++oooo @@@@",
-"@@@ ++++++++++++++++++++++++++++++++++Ooooo @@@@",
-"@@@ +++++++++++++++++++++++++++++++++Oooooo @@@@",
-"@@@ ++OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOoooooo @@@@",
-"@@@ ++OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOoooooo @@@@",
-"@@@ ++OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOooooo @@@@@",
-"@@@ ++OOOOOOOOOOOOOOOOOO oOOoooo @@@@@@",
-"@@@ OOoooooooooooooooooooooooooooooooooo @@@@@@@",
-"@@@ OOooooooooooooooooooooooooooooooooo @@@@@@@@",
-"@@@@ @@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
-"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
-};
-
-const char *const *const main_icon[] = {
- main_icon_0,
- main_icon_1,
- main_icon_2,
-};
-const int n_main_icon = 3;
diff --git a/unix/xpmpucfg.c b/unix/xpmpucfg.c
deleted file mode 100644
index 34c5d491..00000000
--- a/unix/xpmpucfg.c
+++ /dev/null
@@ -1,150 +0,0 @@
-/* XPM */
-static const char *const cfg_icon_0[] = {
-/* columns rows colors chars-per-pixel */
-"16 16 9 1",
-" c black",
-". c navy",
-"X c blue",
-"o c #808000",
-"O c yellow",
-"+ c #808080",
-"@ c #C0C0C0",
-"# c gray100",
-"$ c None",
-/* pixels */
-"$$$ $$ $$",
-"$$ OO #####@+ $",
-"$ $ oO #XX..@+ $",
-" o $ oO+X.O.@+ $",
-" oO OO .O.X@+ $",
-"$ oOOOOoO++@@+ $",
-"$$ oooOOoOO +++ ",
-"$ # oooOO +++++ ",
-"$ #X..ooOO +++ $",
-"$ #X.O. oOO $$",
-"$ #.O.X@ oOO $$$",
-"$ @++@@@+ oOO $$",
-"$ ++++++++ oOO $",
-" #####++++ oOO ",
-" @+++++++ $$ oO ",
-"$ $$$$ $"
-};
-
-/* XPM */
-static const char *const cfg_icon_1[] = {
-/* columns rows colors chars-per-pixel */
-"32 32 9 1",
-" c black",
-". c navy",
-"X c blue",
-"o c #808000",
-"O c yellow",
-"+ c #808080",
-"@ c #C0C0C0",
-"# c gray100",
-"$ c None",
-/* pixels */
-"$$$$$$$$$$$$$$$$ $$$$",
-"$$$$$$ $$$$$$$ @@@@@@@@@@@+ $$$",
-"$$$$$ OO $$$$ ##########@++ $$$",
-"$$$$$ ooOO $$$ #.........@++ $$$",
-"$$$$$$ ooOO $$ #.XXXXXXXX@++ $$$",
-"$$ $$$ oOO $$ #.XXXX XX@++ $$$",
-"$ oO $$ oOOO $ #.XXX O XX@++ $$$",
-"$ oOO oOOOO $ #.X O XXX@++ $$$",
-"$$ oOOOOOOO $$ #. OO XXXX@++ $$$",
-"$$ ooOOOOOOO $ # OO XXXXX@++ $$$",
-"$$$ ooooooOOO OO ######@++ $",
-"$$$$ oo ooOOO OO +++++++++ @#+ ",
-"$$$$$$ $ ooOOO @#++ ",
-"$$$$$$$$$$ ooOOO OOOO ######@++ ",
-"$$$$$ O ooOOO O @@@@@@@+++ ",
-"$$$$ @@@@@ ooOOO @@+ +@++ $",
-"$$$ ######### ooOOO +++++++++ $$",
-"$$$ #....... O ooOOO $$$",
-"$$$ #.XXXXX OO ooOOO $$$$$$$$$$",
-"$$$ #.XXXX OO @+ ooOOO $$$$$$$$$",
-"$$$ #.XXX O X@++ ooOOO $$$$$$$$",
-"$$$ #.XX O XXX@++ ooOOO $$$$$$$",
-"$$$ #.XX XXXX@++ $ ooOOO $$$$$$",
-"$$$ #.XXXXXXXX@++ $$ ooOOO $$$$$",
-"$$$ ##########@++ $ ooOOO $$$$",
-"$$ @+++++++++++ @#+ $ ooOOO $$$",
-"$ @ @#++ $$ ooOOO $$",
-" ################@++ $$$ ooO $$$",
-" #@@@@@@@@@@@@@@@+++ $$$$ o $$$$",
-" #@@@@@@@@+ +@++ $$$$$$ $$$$$",
-" @++++++++++++++++ $$$$$$$$$$$$$",
-"$ $$$$$$$$$$$$$$"
-};
-
-/* XPM */
-static const char *const cfg_icon_2[] = {
-/* columns rows colors chars-per-pixel */
-"48 48 9 1",
-" c black",
-". c navy",
-"X c blue",
-"o c #808000",
-"O c yellow",
-"+ c #808080",
-"@ c #C0C0C0",
-"# c gray100",
-"$ c None",
-/* pixels */
-"$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$",
-"$$$$$$$$$$$$$$$$$$$$$$$$ @@@@@@@@@@@@@@@@@+ $$$$",
-"$$$$$$$$$ $$$$$$$$$$$$ @@@@@@@@@@@@@@@@@++ $$$$",
-"$$$$$$$$ OO $$$$$$$$ ################@+++ $$$$",
-"$$$$$$$$ oOOOO $$$$$$$ #++++++++++++++@++++ $$$$",
-"$$$$$$$$$ ooOOO $$$$$$ #+.............#++++ $$$$",
-"$$$$$$$$$$ ooOOO $$$$$ #+.XXXXXXXXXXXX#++++ $$$$",
-"$$$$$$$$$$$ oOOO $$$$$ #+.XXXXXXXXXXXX#++++ $$$$",
-"$$$ $$$$$$ oOOO $$$$$ #+.XXXXXXX XXX#++++ $$$$",
-"$$ oO $$$$$ oOOOO $$$$ #+.XXXXXX O XXX#++++ $$$$",
-"$$ ooO $$$$ oOOOO $$$$ #+.XXXXX O XXXX#++++ $$$$",
-"$$$ oOO OOOOO $$$$$ #+.XXX O XXXXX#++++ $$$$",
-"$$$ ooOOOOOOOOOOO $$$$ #+.XX OO XXXXXX#++++ $$$$",
-"$$$ ooOOOOOOOOOOOO $$$ #+.X OO XXXXXXX#++++ $$$$",
-"$$$$ oooOOOOoOOOOOO $$ #@ OO #########++++ $",
-"$$$$$ oooooooOOOOOOO # OOO @@@@@@@@@@+++ @##+ ",
-"$$$$$$ oo ooOOOOOOO OO +++++++++++++ @##++ ",
-"$$$$$$$$$ $ ooOOOOOOO OO @##+++ ",
-"$$$$$$$$$$$$$ ooOOOOOOO ############@+++ ",
-"$$$$$$$$$$$$$$ ooOOOOOOO OOOOOO ##########@++++ ",
-"$$$$$$$$$$$$$$$ ooOOOOOOO OOO @@+ @++++ $",
-"$$$$$$$$$$$$$$$$ ooOOOOOOO O ++++++++++++++++ $$",
-"$$$$$$$$$$$$$$$ O ooOOOOOOO ++++++++++++++++ $$$",
-"$$$$$$$$$$$$$$$$ ooOOOOOOO $$$$",
-"$$$$$$$ ooOOOOOOO $$$$$$$$$$$$$$$$$$",
-"$$$$$$ @@@@@@@@@@@@ ooOOOOOOO $$$$$$$$$$$$$$$$$",
-"$$$$$ @@@@@@@@@@@@ OO ooOOOOOOO $$$$$$$$$$$$$$$$",
-"$$$$ ############ OO ooOOOOOOO $$$$$$$$$$$$$$$",
-"$$$$ #++++++++++ OO @++ ooOOOOOOO $$$$$$$$$$$$$$",
-"$$$$ #+........ OO .#+++ ooOOOOOOO $$$$$$$$$$$$$",
-"$$$$ #+.XXXXXX O XX#++++ ooOOOOOOO $$$$$$$$$$$$",
-"$$$$ #+.XXXXX O XXXX#++++ ooOOOOOOO $$$$$$$$$$$",
-"$$$$ #+.XXXX O XXXXX#++++ $ ooOOOOOOO $$$$$$$$$$",
-"$$$$ #+.XXXX XXXXXX#++++ $$ ooOOOOOOO $$$$$$$$$",
-"$$$$ #+.XXXXXXXXXXXX#++++ $$$ ooOOOOOOO $$$$$$$$",
-"$$$$ #+.XXXXXXXXXXXX#++++ $$$$ ooOOOOOOO $$$$$$$",
-"$$$$ #+.XXXXXXXXXXXX#++++ $$$$$ ooOOOOOOO $$$$$$",
-"$$$$ #+.XXXXXXXXXXXX#++++ $$$$$$ ooOOOOOOO $$$$$",
-"$$$$ #@##############++++ $$$$ ooOOOOOOO $$$$",
-"$$$ #@@@@@@@@@@@@@@@+++ @##+ $$$$ ooOOOOOOO $$$",
-"$$ @ @+++++++++++++++++ @##++ $$$$$ ooOOOOO $$$$",
-"$ @@ @##+++ $$$$$$ ooOOO $$$$$",
-" ########################@+++ $$$$$$$ ooO $$$$$$",
-" #######################@++++ $$$$$$$$ o $$$$$$$",
-" ##@@@@@@@@@@@@+ @++++ $$$$$$$$$$ $$$$$$$$",
-" @@++++++++++++++++++++++++ $$$$$$$$$$$$$$$$$$$$",
-" @@+++++++++++++++++++++++ $$$$$$$$$$$$$$$$$$$$$",
-"$ $$$$$$$$$$$$$$$$$$$$$$"
-};
-
-const char *const *const cfg_icon[] = {
- cfg_icon_0,
- cfg_icon_1,
- cfg_icon_2,
-};
-const int n_cfg_icon = 3;
diff --git a/unix/xpmputty.c b/unix/xpmputty.c
deleted file mode 100644
index 56d16bee..00000000
--- a/unix/xpmputty.c
+++ /dev/null
@@ -1,147 +0,0 @@
-/* XPM */
-static const char *const main_icon_0[] = {
-/* columns rows colors chars-per-pixel */
-"16 16 8 1",
-" c black",
-". c navy",
-"X c blue",
-"o c yellow",
-"O c #808080",
-"+ c #C0C0C0",
-"@ c gray100",
-"# c None",
-/* pixels */
-"####### ##",
-"###### @@@@@+O #",
-"###### @XX..+O #",
-"###### @X.o.+O #",
-"###### O.o.X+O #",
-"###### ooOO++O #",
-"## ooooo OOO ",
-"# @Oooooo OOOOO ",
-"# @X..oo OOOO #",
-"# @X.o.OO ##",
-"# @.o.X+O ######",
-"# +OO+++O ######",
-"# OOOOOOOO #####",
-" @@@@@OOOO #####",
-" +OOOOOOO ######",
-"# #######"
-};
-
-/* XPM */
-static const char *const main_icon_1[] = {
-/* columns rows colors chars-per-pixel */
-"32 32 8 1",
-" c black",
-". c navy",
-"X c blue",
-"o c yellow",
-"O c #808080",
-"+ c #C0C0C0",
-"@ c gray100",
-"# c None",
-/* pixels */
-"################ ####",
-"############### +++++++++++O ###",
-"############## @@@@@@@@@@+OO ###",
-"############## @.........+OO ###",
-"############## @.XXXXXXXX+OO ###",
-"############## @.XXXX XX+OO ###",
-"############## @.XXX o XX+OO ###",
-"############## @.X o XXX+OO ###",
-"############## @. oo XXXX+OO ###",
-"############## @ oo XXXXX+OO ###",
-"############## oo @@@@@@+OO #",
-"############# ooo OOOOOOOOO +@O ",
-"############ ooo +@OO ",
-"########## ooooooooo @@@@@@+OO ",
-"##### ooooooooo +++++++OOO ",
-"#### +++++ ooo ++O O+OO #",
-"### @@@@@@@@@ ooo OOOOOOOOOOO ##",
-"### @....... oo ###",
-"### @.XXXXX oo OO ##############",
-"### @.XXXX oo +OO ##############",
-"### @.XXX o X+OO ##############",
-"### @.XX o XXX+OO ##############",
-"### @.XX XXXX+OO ##############",
-"### @.XXXXXXXX+OO ##############",
-"### @@@@@@@@@@+OO ############",
-"## +OOOOOOOOOOO +@O ###########",
-"# + +@OO ###########",
-" @@@@@@@@@@@@@@@@+OO ###########",
-" @+++++++++++++++OOO ###########",
-" @++++++++O O+OO ############",
-" +OOOOOOOOOOOOOOOO #############",
-"# ##############"
-};
-
-/* XPM */
-static const char *const main_icon_2[] = {
-/* columns rows colors chars-per-pixel */
-"48 48 8 1",
-" c black",
-". c navy",
-"X c blue",
-"o c yellow",
-"O c #808080",
-"+ c #C0C0C0",
-"@ c gray100",
-"# c None",
-/* pixels */
-"######################### #####",
-"######################## +++++++++++++++++O ####",
-"####################### +++++++++++++++++OO ####",
-"###################### @@@@@@@@@@@@@@@@+OOO ####",
-"###################### @OOOOOOOOOOOOOO+OOOO ####",
-"###################### @O.............@OOOO ####",
-"###################### @O.XXXXXXXXXXXX@OOOO ####",
-"###################### @O.XXXXXXXXXXXX@OOOO ####",
-"###################### @O.XXXXXXX XXX@OOOO ####",
-"###################### @O.XXXXXX o XXX@OOOO ####",
-"###################### @O.XXXXX o XXXX@OOOO ####",
-"###################### @O.XXX o XXXXX@OOOO ####",
-"###################### @O.XX oo XXXXXX@OOOO ####",
-"###################### @O.X oo XXXXXXX@OOOO ####",
-"###################### @+ oo @@@@@@@@@OOOO #",
-"##################### @ ooo ++++++++++OOO +@@O ",
-"#################### + oo OOOOOOOOOOOOO +@@OO ",
-"################### + oo +@@OOO ",
-"################## @ ooo @@@@@@@@@@@@+OOO ",
-"################## ooooooooooo @@@@@@@@@@+OOOO ",
-"################## oooooooooo ++O +OOOO #",
-"################ oooooooooo OOOOOOOOOOOOOOOO ##",
-"############### ooooooooooo OOOOOOOOOOOOOOOO ###",
-"################ ooo ####",
-"####### oo ######################",
-"###### ++++++++++++ oo O ######################",
-"##### ++++++++++++ ooo OO ######################",
-"#### @@@@@@@@@@@@ oo OOO ######################",
-"#### @OOOOOOOOOO oo +OOOO ######################",
-"#### @O........ oo .@OOOO ######################",
-"#### @O.XXXXXX o XX@OOOO ######################",
-"#### @O.XXXXX o XXXX@OOOO ######################",
-"#### @O.XXXX o XXXXX@OOOO ######################",
-"#### @O.XXXX XXXXXX@OOOO ######################",
-"#### @O.XXXXXXXXXXXX@OOOO ######################",
-"#### @O.XXXXXXXXXXXX@OOOO ######################",
-"#### @O.XXXXXXXXXXXX@OOOO ######################",
-"#### @O.XXXXXXXXXXXX@OOOO ######################",
-"#### @+@@@@@@@@@@@@@@OOOO ###################",
-"### @+++++++++++++++OOO +@@O ##################",
-"## + +OOOOOOOOOOOOOOOOO +@@OO ##################",
-"# ++ +@@OOO ##################",
-" @@@@@@@@@@@@@@@@@@@@@@@@+OOO ##################",
-" @@@@@@@@@@@@@@@@@@@@@@@+OOOO ##################",
-" @@++++++++++++O +OOOO ###################",
-" ++OOOOOOOOOOOOOOOOOOOOOOOO ####################",
-" ++OOOOOOOOOOOOOOOOOOOOOOO #####################",
-"# ######################"
-};
-
-const char *const *const main_icon[] = {
- main_icon_0,
- main_icon_1,
- main_icon_2,
-};
-const int n_main_icon = 3;
diff --git a/utils.c b/utils.c
deleted file mode 100644
index 30b326ee..00000000
--- a/utils.c
+++ /dev/null
@@ -1,1122 +0,0 @@
-/*
- * 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 <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <limits.h>
-#include <ctype.h>
-#include <assert.h>
-
-#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
- * <num> 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 <float.h>, 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/utils/CMakeLists.txt b/utils/CMakeLists.txt
new file mode 100644
index 00000000..6ef33c8d
--- /dev/null
+++ b/utils/CMakeLists.txt
@@ -0,0 +1,78 @@
+add_sources_from_current_dir(utils
+ antispoof.c
+ backend_socket_log.c
+ base64_decode_atom.c
+ base64_decode.c
+ base64_encode_atom.c
+ base64_encode.c
+ base64_valid.c
+ bufchain.c
+ buildinfo.c
+ burnstr.c
+ cert-expr.c
+ chomp.c
+ cmdline_get_passwd_input_state_new.c
+ conf.c
+ conf_dest.c
+ conf_launchable.c
+ ctrlparse.c
+ ctrlset_normalise.c
+ debug.c
+ decode_utf8.c
+ decode_utf8_to_wchar.c
+ default_description.c
+ dupcat.c
+ dupprintf.c
+ dupstr.c
+ dup_mb_to_wc.c
+ dup_wc_to_mb.c
+ encode_utf8.c
+ encode_wide_string_as_utf8.c
+ fgetline.c
+ host_ca_new_free.c
+ host_strchr.c
+ host_strchr_internal.c
+ host_strcspn.c
+ host_strduptrim.c
+ host_strrchr.c
+ key_components.c
+ log_proxy_stderr.c
+ make_spr_sw_abort_static.c
+ marshal.c
+ memory.c
+ memxor.c
+ nullstrcmp.c
+ out_of_memory.c
+ parse_blocksize.c
+ percent_decode.c
+ percent_encode.c
+ prompts.c
+ ptrlen.c
+ read_file_into.c
+ seat_connection_fatal.c
+ seat_dialog_text.c
+ sessprep.c
+ sk_free_peer_info.c
+ smemclr.c
+ smemeq.c
+ spr_get_error_message.c
+ ssh_key_clone.c
+ ssh2_pick_fingerprint.c
+ sshutils.c
+ strbuf.c
+ string_length_for_printf.c
+ stripctrl.c
+ tempseat.c
+ tree234.c
+ validate_manual_hostkey.c
+ version.c
+ wcwidth.c
+ wildcard.c
+ wordwrap.c
+ write_c_string_literal.c
+ x11authfile.c
+ x11authnames.c
+ x11_dehexify.c
+ x11_identify_auth_proto.c
+ x11_make_greeting.c
+ x11_parse_ip.c)
diff --git a/utils/antispoof.c b/utils/antispoof.c
new file mode 100644
index 00000000..6435944f
--- /dev/null
+++ b/utils/antispoof.c
@@ -0,0 +1,28 @@
+#include "putty.h"
+#include "misc.h"
+
+void seat_antispoof_msg(InteractionReadySeat iseat, const char *msg)
+{
+ strbuf *sb = strbuf_new();
+ seat_set_trust_status(iseat.seat, true);
+ if (seat_can_set_trust_status(iseat.seat)) {
+ /*
+ * If the seat can directly indicate that this message is
+ * generated by the client, then we can just use the message
+ * unmodified as an unspoofable header.
+ */
+ put_dataz(sb, msg);
+ } else if (*msg) {
+ /*
+ * Otherwise, add enough padding around it that the server
+ * wouldn't be able to mimic it within our line-length
+ * constraint.
+ */
+ put_fmt(sb, "-- %s ", msg);
+ while (sb->len < 78)
+ put_byte(sb, '-');
+ }
+ put_datapl(sb, PTRLEN_LITERAL("\r\n"));
+ seat_banner_pl(iseat, ptrlen_from_strbuf(sb));
+ strbuf_free(sb);
+}
diff --git a/utils/backend_socket_log.c b/utils/backend_socket_log.c
new file mode 100644
index 00000000..783cca31
--- /dev/null
+++ b/utils/backend_socket_log.c
@@ -0,0 +1,62 @@
+#include <assert.h>
+#include <string.h>
+
+#include "putty.h"
+#include "network.h"
+
+void backend_socket_log(Seat *seat, LogContext *logctx,
+ PlugLogType type, SockAddr *addr, int port,
+ const char *error_msg, int error_code, Conf *conf,
+ bool session_started)
+{
+ char addrbuf[256], *msg;
+
+ switch (type) {
+ case PLUGLOG_CONNECT_TRYING:
+ sk_getaddr(addr, addrbuf, lenof(addrbuf));
+ if (sk_addr_needs_port(addr)) {
+ msg = dupprintf("Connecting to %s port %d", addrbuf, port);
+ } else {
+ msg = dupprintf("Connecting to %s", addrbuf);
+ }
+ break;
+ case PLUGLOG_CONNECT_FAILED:
+ sk_getaddr(addr, addrbuf, lenof(addrbuf));
+ msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);
+ break;
+ case PLUGLOG_CONNECT_SUCCESS:
+ if (addr)
+ sk_getaddr(addr, addrbuf, lenof(addrbuf));
+ else /* fallback if address unavailable */
+ sprintf(addrbuf, "remote host");
+ msg = dupprintf("Connected to %s", addrbuf);
+ break;
+ case PLUGLOG_PROXY_MSG: {
+ /* Proxy-related log messages have their own identifying
+ * prefix already, put on by our caller. */
+ int len, log_to_term;
+
+ /* Suffix \r\n temporarily, so we can log to the terminal. */
+ msg = dupprintf("%s\r\n", error_msg);
+ len = strlen(msg);
+ assert(len >= 2);
+
+ log_to_term = conf_get_int(conf, CONF_proxy_log_to_term);
+ if (log_to_term == AUTO)
+ log_to_term = session_started ? FORCE_OFF : FORCE_ON;
+ if (log_to_term == FORCE_ON)
+ seat_stderr(seat, msg, len);
+
+ msg[len-2] = '\0'; /* remove the \r\n again */
+ break;
+ }
+ default:
+ msg = NULL; /* shouldn't happen, but placate optimiser */
+ break;
+ }
+
+ if (msg) {
+ logevent(logctx, msg);
+ sfree(msg);
+ }
+}
diff --git a/utils/base64_decode.c b/utils/base64_decode.c
new file mode 100644
index 00000000..4f00f196
--- /dev/null
+++ b/utils/base64_decode.c
@@ -0,0 +1,37 @@
+#include "misc.h"
+
+void base64_decode_bs(BinarySink *bs, ptrlen input)
+{
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, input);
+
+ while (get_avail(src)) {
+ char b64atom[4];
+ unsigned char binatom[3];
+
+ for (size_t i = 0; i < 4 ;) {
+ char c = get_byte(src);
+ if (get_err(src))
+ c = '=';
+ if (c == '\n' || c == '\r')
+ continue;
+ b64atom[i++] = c;
+ }
+
+ put_data(bs, binatom, base64_decode_atom(b64atom, binatom));
+ }
+}
+
+void base64_decode_fp(FILE *fp, ptrlen input)
+{
+ stdio_sink ss;
+ stdio_sink_init(&ss, fp);
+ base64_decode_bs(BinarySink_UPCAST(&ss), input);
+}
+
+strbuf *base64_decode_sb(ptrlen input)
+{
+ strbuf *sb = strbuf_new_nm();
+ base64_decode_bs(BinarySink_UPCAST(sb), input);
+ return sb;
+}
diff --git a/utils/base64_decode_atom.c b/utils/base64_decode_atom.c
new file mode 100644
index 00000000..2a98150e
--- /dev/null
+++ b/utils/base64_decode_atom.c
@@ -0,0 +1,54 @@
+/*
+ * Core routine to decode a single atomic base64 chunk.
+ */
+
+#include "defs.h"
+#include "misc.h"
+
+int base64_decode_atom(const char *atom, unsigned char *out)
+{
+ int vals[4];
+ int i, v, len;
+ unsigned word;
+ char c;
+
+ for (i = 0; i < 4; i++) {
+ c = atom[i];
+ if (c >= 'A' && c <= 'Z')
+ v = c - 'A';
+ else if (c >= 'a' && c <= 'z')
+ v = c - 'a' + 26;
+ else if (c >= '0' && c <= '9')
+ v = c - '0' + 52;
+ else if (c == '+')
+ v = 62;
+ else if (c == '/')
+ v = 63;
+ else if (c == '=')
+ v = -1;
+ else
+ return 0; /* invalid atom */
+ vals[i] = v;
+ }
+
+ if (vals[0] == -1 || vals[1] == -1)
+ return 0;
+ if (vals[2] == -1 && vals[3] != -1)
+ return 0;
+
+ if (vals[3] != -1)
+ len = 3;
+ else if (vals[2] != -1)
+ len = 2;
+ else
+ len = 1;
+
+ word = ((vals[0] << 18) |
+ (vals[1] << 12) | ((vals[2] & 0x3F) << 6) | (vals[3] & 0x3F));
+ out[0] = (word >> 16) & 0xFF;
+ if (len > 1)
+ out[1] = (word >> 8) & 0xFF;
+ if (len > 2)
+ out[2] = word & 0xFF;
+ return len;
+}
diff --git a/utils/base64_encode.c b/utils/base64_encode.c
new file mode 100644
index 00000000..3db85571
--- /dev/null
+++ b/utils/base64_encode.c
@@ -0,0 +1,40 @@
+#include "misc.h"
+
+void base64_encode_bs(BinarySink *bs, ptrlen input, int cpl)
+{
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, input);
+ int linelen = 0;
+
+ while (get_avail(src)) {
+ size_t n = get_avail(src) < 3 ? get_avail(src) : 3;
+ ptrlen binatom = get_data(src, n);
+
+ char b64atom[4];
+ base64_encode_atom(binatom.ptr, binatom.len, b64atom);
+ for (size_t i = 0; i < 4; i++) {
+ if (cpl > 0 && linelen >= cpl) {
+ linelen = 0;
+ put_byte(bs, '\n');
+ }
+ put_byte(bs, b64atom[i]);
+ linelen++;
+ }
+ }
+ if (cpl > 0)
+ put_byte(bs, '\n');
+}
+
+void base64_encode_fp(FILE *fp, ptrlen input, int cpl)
+{
+ stdio_sink ss;
+ stdio_sink_init(&ss, fp);
+ base64_encode_bs(BinarySink_UPCAST(&ss), input, cpl);
+}
+
+strbuf *base64_encode_sb(ptrlen input, int cpl)
+{
+ strbuf *sb = strbuf_new_nm();
+ base64_encode_bs(BinarySink_UPCAST(sb), input, cpl);
+ return sb;
+}
diff --git a/utils/base64_encode_atom.c b/utils/base64_encode_atom.c
new file mode 100644
index 00000000..c33476f0
--- /dev/null
+++ b/utils/base64_encode_atom.c
@@ -0,0 +1,30 @@
+/*
+ * Core routine to encode a single atomic base64 chunk.
+ */
+
+#include "defs.h"
+#include "misc.h"
+
+void base64_encode_atom(const unsigned char *data, int n, char *out)
+{
+ static const char base64_chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ unsigned word;
+
+ word = data[0] << 16;
+ if (n > 1)
+ word |= data[1] << 8;
+ if (n > 2)
+ word |= data[2];
+ out[0] = base64_chars[(word >> 18) & 0x3F];
+ out[1] = base64_chars[(word >> 12) & 0x3F];
+ if (n > 1)
+ out[2] = base64_chars[(word >> 6) & 0x3F];
+ else
+ out[2] = '=';
+ if (n > 2)
+ out[3] = base64_chars[word & 0x3F];
+ else
+ out[3] = '=';
+}
diff --git a/utils/base64_valid.c b/utils/base64_valid.c
new file mode 100644
index 00000000..8eb1f3a0
--- /dev/null
+++ b/utils/base64_valid.c
@@ -0,0 +1,54 @@
+/*
+ * Determine whether a string looks like valid base64-encoded data.
+ */
+
+#include "misc.h"
+
+static inline bool valid_char_main(char c)
+{
+ return ((c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9') ||
+ c == '+' || c == '/');
+}
+
+bool base64_valid(ptrlen data)
+{
+ size_t blocklen = 0, nequals = 0;
+
+ for (size_t i = 0; i < data.len; i++) {
+ char c = ((const char *)data.ptr)[i];
+
+ if (c == '\n' || c == '\r')
+ continue;
+
+ if (valid_char_main(c)) {
+ if (nequals) /* can't go back to data after = */
+ return false;
+ blocklen++;
+ if (blocklen == 4)
+ blocklen = 0;
+ continue;
+ }
+
+ if (c == '=') {
+ if (blocklen == 0 && nequals) /* started a fresh block */
+ return false;
+
+ nequals++;
+ blocklen++;
+ if (blocklen == 4) {
+ if (nequals > 2)
+ return false; /* nonsensical final block */
+ blocklen = 0;
+ }
+ continue;
+ }
+
+ return false; /* bad character */
+ }
+
+ if (blocklen == 0 || blocklen == 2 || blocklen == 3)
+ return true; /* permit eliding the trailing = */
+ return false;
+}
diff --git a/utils/bufchain.c b/utils/bufchain.c
new file mode 100644
index 00000000..9b02c65f
--- /dev/null
+++ b/utils/bufchain.c
@@ -0,0 +1,193 @@
+/*
+ * Generic routines to deal with send buffers: a linked list of
+ * smallish blocks, with the operations
+ *
+ * - add an arbitrary amount of data to the end of the list
+ * - remove the first N bytes from the list
+ * - return a (pointer,length) pair giving some initial data in
+ * the list, suitable for passing to a send or write system
+ * call
+ * - retrieve a larger amount of initial data from the list
+ * - return the current size of the buffer chain in bytes
+ */
+
+#include "defs.h"
+#include "misc.h"
+
+#define BUFFER_MIN_GRANULE 512
+
+struct bufchain_granule {
+ struct bufchain_granule *next;
+ char *bufpos, *bufend, *bufmax;
+};
+
+static void uninitialised_queue_idempotent_callback(IdempotentCallback *ic)
+{
+ unreachable("bufchain callback used while uninitialised");
+}
+
+void bufchain_init(bufchain *ch)
+{
+ ch->head = ch->tail = NULL;
+ ch->buffersize = 0;
+ ch->ic = NULL;
+ ch->queue_idempotent_callback = uninitialised_queue_idempotent_callback;
+}
+
+void bufchain_clear(bufchain *ch)
+{
+ struct bufchain_granule *b;
+ while (ch->head) {
+ b = ch->head;
+ ch->head = ch->head->next;
+ smemclr(b, sizeof(*b));
+ sfree(b);
+ }
+ ch->tail = NULL;
+ ch->buffersize = 0;
+}
+
+size_t bufchain_size(bufchain *ch)
+{
+ return ch->buffersize;
+}
+
+void bufchain_set_callback_inner(
+ bufchain *ch, IdempotentCallback *ic,
+ void (*queue_idempotent_callback)(IdempotentCallback *ic))
+{
+ ch->queue_idempotent_callback = queue_idempotent_callback;
+ ch->ic = ic;
+}
+
+void bufchain_add(bufchain *ch, const void *data, size_t len)
+{
+ const char *buf = (const char *)data;
+
+ if (len == 0) return;
+
+ ch->buffersize += len;
+
+ while (len > 0) {
+ if (ch->tail && ch->tail->bufend < ch->tail->bufmax) {
+ size_t copylen = min(len, ch->tail->bufmax - ch->tail->bufend);
+ memcpy(ch->tail->bufend, buf, copylen);
+ buf += copylen;
+ len -= copylen;
+ ch->tail->bufend += copylen;
+ }
+ if (len > 0) {
+ size_t grainlen =
+ max(sizeof(struct bufchain_granule) + len, BUFFER_MIN_GRANULE);
+ struct bufchain_granule *newbuf;
+ newbuf = smalloc(grainlen);
+ newbuf->bufpos = newbuf->bufend =
+ (char *)newbuf + sizeof(struct bufchain_granule);
+ newbuf->bufmax = (char *)newbuf + grainlen;
+ newbuf->next = NULL;
+ if (ch->tail)
+ ch->tail->next = newbuf;
+ else
+ ch->head = newbuf;
+ ch->tail = newbuf;
+ }
+ }
+
+ if (ch->ic)
+ ch->queue_idempotent_callback(ch->ic);
+}
+
+void bufchain_consume(bufchain *ch, size_t len)
+{
+ struct bufchain_granule *tmp;
+
+ assert(ch->buffersize >= len);
+ while (len > 0) {
+ int remlen = len;
+ assert(ch->head != NULL);
+ if (remlen >= ch->head->bufend - ch->head->bufpos) {
+ remlen = ch->head->bufend - ch->head->bufpos;
+ tmp = ch->head;
+ ch->head = tmp->next;
+ if (!ch->head)
+ ch->tail = NULL;
+ smemclr(tmp, sizeof(*tmp));
+ sfree(tmp);
+ } else
+ ch->head->bufpos += remlen;
+ ch->buffersize -= remlen;
+ len -= remlen;
+ }
+}
+
+ptrlen bufchain_prefix(bufchain *ch)
+{
+ return make_ptrlen(ch->head->bufpos, ch->head->bufend - ch->head->bufpos);
+}
+
+void bufchain_fetch(bufchain *ch, void *data, size_t len)
+{
+ struct bufchain_granule *tmp;
+ char *data_c = (char *)data;
+
+ tmp = ch->head;
+
+ assert(ch->buffersize >= len);
+ while (len > 0) {
+ int remlen = len;
+
+ assert(tmp != NULL);
+ if (remlen >= tmp->bufend - tmp->bufpos)
+ remlen = tmp->bufend - tmp->bufpos;
+ memcpy(data_c, tmp->bufpos, remlen);
+
+ tmp = tmp->next;
+ len -= remlen;
+ data_c += remlen;
+ }
+}
+
+void bufchain_fetch_consume(bufchain *ch, void *data, size_t len)
+{
+ bufchain_fetch(ch, data, len);
+ bufchain_consume(ch, len);
+}
+
+bool bufchain_try_fetch(bufchain *ch, void *data, size_t len)
+{
+ if (ch->buffersize >= len) {
+ bufchain_fetch(ch, data, len);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool bufchain_try_consume(bufchain *ch, size_t len)
+{
+ if (ch->buffersize >= len) {
+ bufchain_consume(ch, len);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len)
+{
+ if (ch->buffersize >= len) {
+ bufchain_fetch_consume(ch, data, len);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len)
+{
+ if (len > ch->buffersize)
+ len = ch->buffersize;
+ if (len)
+ bufchain_fetch_consume(ch, data, len);
+ return len;
+}
diff --git a/utils/buildinfo.c b/utils/buildinfo.c
new file mode 100644
index 00000000..5a837e01
--- /dev/null
+++ b/utils/buildinfo.c
@@ -0,0 +1,164 @@
+/*
+ * Return a string describing everything we know about how this
+ * particular binary was built: from what source, for what target
+ * platform, using what tools, with what settings, etc.
+ */
+
+#include "putty.h"
+
+char *buildinfo(const char *newline)
+{
+ strbuf *buf = strbuf_new();
+
+ put_fmt(buf, "Build platform: %d-bit %s",
+ (int)(CHAR_BIT * sizeof(void *)), BUILDINFO_PLATFORM);
+
+#ifdef __clang_version__
+#define FOUND_COMPILER
+ put_fmt(buf, "%sCompiler: clang %s", newline, __clang_version__);
+#elif defined __GNUC__ && defined __VERSION__
+#define FOUND_COMPILER
+ put_fmt(buf, "%sCompiler: gcc %s", newline, __VERSION__);
+#endif
+
+#if defined _MSC_VER
+#ifndef FOUND_COMPILER
+#define FOUND_COMPILER
+ put_fmt(buf, "%sCompiler: ", newline);
+#else
+ put_fmt(buf, ", emulating ");
+#endif
+ put_fmt(buf, "Visual Studio");
+
+#if 0
+ /*
+ * List of _MSC_VER values and their translations taken from
+ * https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros
+ *
+ * The pointless #if 0 branch containing this comment is there so
+ * that every real clause can start with #elif and there's no
+ * anomalous first clause. That way the patch looks nicer when you
+ * add extra ones.
+ *
+ * Mostly you can tell the version just from _MSC_VER, but in some
+ * cases, two different compiler versions have the same _MSC_VER
+ * value, and have to be distinguished by _MSC_FULL_VER.
+ */
+#elif _MSC_VER == 1933
+ put_fmt(buf, " 2022 (17.3)");
+#elif _MSC_VER == 1932
+ put_fmt(buf, " 2022 (17.2)");
+#elif _MSC_VER == 1931
+ put_fmt(buf, " 2022 (17.1)");
+#elif _MSC_VER == 1930
+ put_fmt(buf, " 2022 (17.0)");
+#elif _MSC_VER == 1929 && _MSC_FULL_VER >= 192930100
+ put_fmt(buf, " 2019 (16.11)");
+#elif _MSC_VER == 1929
+ put_fmt(buf, " 2019 (16.10)");
+#elif _MSC_VER == 1928 && _MSC_FULL_VER >= 192829500
+ put_fmt(buf, " 2019 (16.9)");
+#elif _MSC_VER == 1928
+ put_fmt(buf, " 2019 (16.8)");
+#elif _MSC_VER == 1927
+ put_fmt(buf, " 2019 (16.7)");
+#elif _MSC_VER == 1926
+ put_fmt(buf, " 2019 (16.6)");
+#elif _MSC_VER == 1925
+ put_fmt(buf, " 2019 (16.5)");
+#elif _MSC_VER == 1924
+ put_fmt(buf, " 2019 (16.4)");
+#elif _MSC_VER == 1923
+ put_fmt(buf, " 2019 (16.3)");
+#elif _MSC_VER == 1922
+ put_fmt(buf, " 2019 (16.2)");
+#elif _MSC_VER == 1921
+ put_fmt(buf, " 2019 (16.1)");
+#elif _MSC_VER == 1920
+ put_fmt(buf, " 2019 (16.0)");
+#elif _MSC_VER == 1916
+ put_fmt(buf, " 2017 version 15.9");
+#elif _MSC_VER == 1915
+ put_fmt(buf, " 2017 version 15.8");
+#elif _MSC_VER == 1914
+ put_fmt(buf, " 2017 version 15.7");
+#elif _MSC_VER == 1913
+ put_fmt(buf, " 2017 version 15.6");
+#elif _MSC_VER == 1912
+ put_fmt(buf, " 2017 version 15.5");
+#elif _MSC_VER == 1911
+ put_fmt(buf, " 2017 version 15.3");
+#elif _MSC_VER == 1910
+ put_fmt(buf, " 2017 RTW (15.0)");
+#elif _MSC_VER == 1900
+ put_fmt(buf, " 2015 (14.0)");
+#elif _MSC_VER == 1800
+ put_fmt(buf, " 2013 (12.0)");
+#elif _MSC_VER == 1700
+ put_fmt(buf, " 2012 (11.0)");
+#elif _MSC_VER == 1600
+ put_fmt(buf, " 2010 (10.0)");
+#elif _MSC_VER == 1500
+ put_fmt(buf, " 2008 (9.0)");
+#elif _MSC_VER == 1400
+ put_fmt(buf, " 2005 (8.0)");
+#elif _MSC_VER == 1310
+ put_fmt(buf, " .NET 2003 (7.1)");
+#elif _MSC_VER == 1300
+ put_fmt(buf, " .NET 2002 (7.0)");
+#elif _MSC_VER == 1200
+ put_fmt(buf, " 6.0");
+#else
+ put_fmt(buf, ", unrecognised version");
+#endif
+ put_fmt(buf, ", _MSC_VER=%d", (int)_MSC_VER);
+#ifdef _MSC_FULL_VER
+ put_fmt(buf, ", _MSC_FULL_VER=%d", (int)_MSC_FULL_VER);
+#endif
+#endif
+
+#ifdef BUILDINFO_GTK
+ {
+ char *gtk_buildinfo = buildinfo_gtk_version();
+ if (gtk_buildinfo) {
+ put_fmt(buf, "%sCompiled against GTK version %s",
+ newline, gtk_buildinfo);
+ sfree(gtk_buildinfo);
+ }
+ }
+#endif
+#if defined _WINDOWS
+ {
+ int echm = has_embedded_chm();
+ if (echm >= 0)
+ put_fmt(buf, "%sEmbedded HTML Help file: %s", newline,
+ echm ? "yes" : "no");
+ }
+#endif
+
+#if defined _WINDOWS && defined MINEFIELD
+ put_fmt(buf, "%sBuild option: MINEFIELD", newline);
+#endif
+#ifdef NO_IPV6
+ put_fmt(buf, "%sBuild option: NO_IPV6", newline);
+#endif
+#ifdef NO_GSSAPI
+ put_fmt(buf, "%sBuild option: NO_GSSAPI", newline);
+#endif
+#ifdef STATIC_GSSAPI
+ put_fmt(buf, "%sBuild option: STATIC_GSSAPI", newline);
+#endif
+#ifdef UNPROTECT
+ put_fmt(buf, "%sBuild option: UNPROTECT", newline);
+#endif
+#ifdef FUZZING
+ put_fmt(buf, "%sBuild option: FUZZING", newline);
+#endif
+#ifdef DEBUG
+ put_fmt(buf, "%sBuild option: DEBUG", newline);
+#endif
+
+ put_fmt(buf, "%sSource commit: %s", newline, commitid);
+
+ return strbuf_to_str(buf);
+}
diff --git a/utils/burnstr.c b/utils/burnstr.c
new file mode 100644
index 00000000..33214d89
--- /dev/null
+++ b/utils/burnstr.c
@@ -0,0 +1,15 @@
+/*
+ * 'Burn' a dynamically allocated string, in the sense of destroying
+ * it beyond recovery: overwrite it with zeroes and then free it.
+ */
+
+#include "defs.h"
+#include "misc.h"
+
+void burnstr(char *string)
+{
+ if (string) {
+ smemclr(string, strlen(string));
+ sfree(string);
+ }
+}
diff --git a/utils/cert-expr.c b/utils/cert-expr.c
new file mode 100644
index 00000000..b22b380c
--- /dev/null
+++ b/utils/cert-expr.c
@@ -0,0 +1,968 @@
+/*
+ * Parser for the boolean expression language used to configure what
+ * host names an OpenSSH certificate will be trusted to sign for.
+ */
+
+/*
+
+Language specification
+======================
+
+Outer lexical layer: the input expression is broken up into tokens,
+with any whitespace between them discarded and ignored. The following
+tokens are special:
+
+ ( ) && || !
+
+and the remaining token type is an 'atom', which is any non-empty
+sequence of characters from the following set:
+
+ ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ abcdefghijklmnopqrstuvwxyz
+ 0123456789
+ .-_*?[]/:
+
+Inner lexical layer: once the boundaries of an 'atom' token have been
+determined by the outer lex layer, each atom is further classified
+into one of the following subtypes:
+
+ - If it contains no ':' or '/', it's taken to be a wildcard matching
+ hostnames, e.g. "*.example.com".
+
+ - If it begins with 'port:' followed by digits, it's taken to be a
+ single port number specification, e.g. "port:22".
+
+ - If it begins with 'port:' followed by two digit sequences separated
+ by '-', it's taken to be a port number range, e.g. "port:0-1023".
+
+ - Any other atom is reserved for future expansion. (See Rationale.)
+
+Syntax layer: all of those types of atom are interpreted as predicates
+applied to the (hostname, port) data configured for the SSH connection
+for which the certificate is being validated.
+
+Wildcards are handled using the syntax in wildcard.c. The dot-
+separated structure of hostnames is thus not special; the '*' in
+"*.example.com" will match any number of subdomains under example.com.
+
+More complex boolean expressions can be made by combining those
+predicates using the boolean operators and parentheses, in the obvious
+way: && and || are infix operators representing logical AND and OR, !
+is a prefix operator representing logical NOT, and parentheses
+indicate grouping.
+
+Each of && and || can associate freely with itself (that is, you can
+write "a && b && c" without having to parenthesise one or the other
+subexpression). But they are forbidden to associate with _each other_.
+That is, if you write "a && b || c" or "a || b && c", it's a syntax
+error, and you must add parentheses to indicate which operator was
+intended to have the higher priority.
+
+Rationale
+=========
+
+Atoms: restrictions
+-------------------
+
+The characters permitted in the 'atom' token don't include \, even
+though it's a special character defined by wildcard.c. That's because
+in this restricted context wildcards will never need it: no hostname
+contains a literal \, and neither does any hostname contain a literal
+instance of any of the wildcard characters that wildcard.c allows you
+to use \ to escape.
+
+Atoms: future extension
+-----------------------
+
+The specification of the 'atom' token is intended to leave space for
+more than one kind of future extension.
+
+Most obviously, additional special predicates similar to "port:", with
+different disambiguating prefixes. I don't know what things of that
+kind we might need, but space is left for them just in case.
+
+Also, the unused '/' in the permitted-characters spec is intended to
+leave open the possibility of allowing certificate acceptance to be
+based on IP address, because the usual CIDR syntax for specifying IP
+ranges (e.g. "192.168.1.0/24" or "2345:6789:abcd:ef01::/128") would be
+lexed as a single atom under these rules.
+
+For the moment, certificate acceptance rules based on IP address are
+not supported, because it's not clear what the semantics ought to be.
+There are two problems with using IP addresses for this purpose:
+
+ 1. Sometimes they come from the DNS, which means you can't trust
+ them. The whole idea of SSH is to end-to-end authenticate the host
+ key against only the input given _by the user_ to the client. Any
+ additional data provided by the network, such as the result of a
+ DNS lookup, is suspect.
+
+ On the other hand, sometimes the IP address *is* part of the user
+ input, because the user can provide an IP address rather than a
+ hostname as the intended connection destination. So there are two
+ kinds of IP address, and they should very likely be treated
+ differently.
+
+ 2. Sometimes the server's IP address is not even *known* by the
+ client, if you're connecting via a proxy and leaving DNS lookups
+ to the proxy.
+
+So, what should a boolean expression do if it's asked to accept or
+reject based on an IP address, and the IP address is unknown or
+untrustworthy? I'm not sure, and therefore, in the initial version of
+this expression system, I haven't implemented them at all.
+
+But the syntax is still available for a future extension to use, if we
+come up with good answers to these questions.
+
+(One possibility would be to evaluate the whole expression in Kleene
+three-valued logic, so that every subexpression has the possible
+answers TRUE, FALSE and UNKNOWN. If a definite IP address is not
+available, IP address predicates evaluate to UNKNOWN. Then, once the
+expression as a whole is evaluated, fail closed, by interpreting
+UNKNOWN as 'reject'. The effect would be that a positive _or_ negative
+constraint on the IP address would cause rejection if the IP address
+is not reliably known, because once the predicate itself has returned
+UNKNOWN, negating it still gives UNKNOWN. The only way you could still
+accept a certificate in that situation would be if the overall
+structure of the expression meant that the test of the IP address
+couldn't affect the result anyway, e.g. if it was ANDed with another
+subexpression that definitely evaluated to FALSE, or ORed with one
+that evaluated to TRUE. This system seems conceptually elegant to me,
+but the argument against it is that it's complicated and
+counterintuitive, which is not a property you want in something a user
+is writing for security purposes!)
+
+Operator precedence
+-------------------
+
+Why did I choose to make && and || refuse to associate with each
+other, instead of applying the usual C precedence rule that && beats
+||? Because I think the C precedence rule is essentially arbitrary, in
+the sense that when people are writing boolean expressions in practice
+based on predicates from the rest of their program, it's about equally
+common to want to nest an && within an || and vice versa. So the
+default precedence rule only gives the user what they actually wanted
+about 50% of the time, and leads to absent-minded errors about as
+often as it conveniently allows you to omit a pair of parens.
+
+With my mathematician hat on, it's not so arbitrary. I agree that if
+you're *going* to give || and && a relative priority then it makes
+more sense to make && the higher-priority one, because if you're
+thinking algebraically, && is more multiplicative and || is more
+additive. But the pure-maths contexts in which that's convenient have
+nothing to do with general boolean expressions in if statements.
+
+This boolean syntax is still close enough to that of C and its
+derivatives to allow easy enough expression interchange (not counting
+the fact that atoms would need rewriting). Any boolean expression
+structure accepted by this syntax is also legal C and means the same
+thing; any expression structure accepted by C is either legal and
+equivalent in this syntax, or will fail with an error. In no case is
+anything accepted but mapped to a different meaning.
+
+ */
+
+#include "putty.h"
+
+typedef enum Token {
+ TOK_LPAR, TOK_RPAR,
+ TOK_AND, TOK_OR, TOK_NOT,
+ TOK_ATOM,
+ TOK_END, TOK_ERROR
+} Token;
+
+static inline bool is_space(char c)
+{
+ return (c == ' ' || c == '\n' || c == '\r' || c == '\t' ||
+ c == '\f' || c == '\v');
+}
+
+static inline bool is_operator_char(char c)
+{
+ return (c == '(' || c == ')' || c == '&' || c == '|' || c == '!');
+}
+
+static inline bool is_atom_char(char c)
+{
+ return (('A' <= c && c <= 'Z') ||
+ ('a' <= c && c <= 'z') ||
+ ('0' <= c && c <= '9') ||
+ c == '.' || c == '-' || c == '_' || c == '*' || c == '?' ||
+ c == '[' || c == ']' || c == '/' || c == ':');
+}
+
+static Token lex(ptrlen *text, ptrlen *token, char **err)
+{
+ const char *p = text->ptr, *e = p + text->len;
+ Token type = TOK_ERROR;
+
+ /* Skip whitespace */
+ while (p < e && is_space(*p))
+ p++;
+
+ const char *start = p;
+
+ if (!(p < e)) {
+ type = TOK_END;
+ goto out;
+ }
+
+ if (is_operator_char(*p)) {
+ /* Match boolean-expression tokens */
+ static const struct operator {
+ ptrlen text;
+ Token type;
+ } operators[] = {
+ {PTRLEN_DECL_LITERAL("("), TOK_LPAR},
+ {PTRLEN_DECL_LITERAL(")"), TOK_RPAR},
+ {PTRLEN_DECL_LITERAL("&&"), TOK_AND},
+ {PTRLEN_DECL_LITERAL("||"), TOK_OR},
+ {PTRLEN_DECL_LITERAL("!"), TOK_NOT},
+ };
+
+ for (size_t i = 0; i < lenof(operators); i++) {
+ const struct operator *op = &operators[i];
+ if (e - p >= op->text.len &&
+ ptrlen_eq_ptrlen(op->text, make_ptrlen(p, op->text.len))) {
+ p += op->text.len;
+ type = op->type;
+ goto out;
+ }
+ }
+
+ /*
+ * Report an error if one of the operator characters is used
+ * in a way that doesn't match something in that table (e.g. a
+ * single &).
+ */
+ p++;
+ type = TOK_ERROR;
+ *err = dupstr("unrecognised boolean operator");
+ goto out;
+ } else if (is_atom_char(*p)) {
+ /*
+ * Match an 'atom' token, which is any non-empty sequence of
+ * characters from the combined set that allows hostname
+ * wildcards, IP address ranges and special predicates like
+ * port numbers.
+ */
+ do {
+ p++;
+ } while (p < e && is_atom_char(*p));
+
+ type = TOK_ATOM;
+ goto out;
+ } else {
+ /*
+ * Otherwise, report an error.
+ */
+ p++;
+ type = TOK_ERROR;
+ *err = dupstr("unexpected character in expression");
+ goto out;
+ }
+
+ out:
+ *token = make_ptrlen(start, p - start);
+ text->ptr = p;
+ text->len = e - p;
+ return type;
+}
+
+typedef enum Operator {
+ OP_AND, OP_OR, OP_NOT,
+ OP_HOSTNAME_WC, OP_PORT_RANGE
+} Operator;
+
+typedef struct ExprNode ExprNode;
+struct ExprNode {
+ Operator op;
+ ptrlen text;
+ union {
+ struct {
+ /* OP_AND, OP_OR */
+ ExprNode **subexprs;
+ size_t nsubexprs;
+ };
+ struct {
+ /* OP_NOT */
+ ExprNode *subexpr;
+ };
+ struct {
+ /* OP_HOSTNAME_WC */
+ char *wc;
+ };
+ struct {
+ /* OP_PORT_RANGE */
+ unsigned lo, hi; /* both inclusive */
+ };
+ };
+};
+
+static ExprNode *exprnode_new(Operator op, ptrlen text)
+{
+ ExprNode *en = snew(ExprNode);
+ memset(en, 0, sizeof(*en));
+ en->op = op;
+ en->text = text;
+ return en;
+}
+
+static void exprnode_free(ExprNode *en)
+{
+ switch (en->op) {
+ case OP_AND:
+ case OP_OR:
+ for (size_t i = 0; i < en->nsubexprs; i++)
+ exprnode_free(en->subexprs[i]);
+ sfree(en->subexprs);
+ break;
+ case OP_NOT:
+ exprnode_free(en->subexpr);
+ break;
+ case OP_HOSTNAME_WC:
+ sfree(en->wc);
+ break;
+ case OP_PORT_RANGE:
+ break;
+ default:
+ unreachable("unhandled node type in exprnode_free");
+ }
+
+ sfree(en);
+}
+
+static unsigned ptrlen_to_port_number(ptrlen input)
+{
+ unsigned val = 0;
+ for (const char *p = input.ptr, *end = p + input.len; p < end; p++) {
+ assert('0' <= *p && *p <= '9'); /* expect parser to have checked */
+ val = 10 * val + (*p - '0');
+ if (val >= 65536)
+ val = 65536; /* normalise 'too large' to avoid integer overflow */
+ }
+ return val;
+}
+
+typedef struct ParserState ParserState;
+struct ParserState {
+ ptrlen currtext;
+ Token tok;
+ ptrlen toktext;
+ char *err;
+ ptrlen errloc;
+};
+
+static void error(ParserState *ps, char *errtext, ptrlen errloc)
+{
+ if (!ps->err) {
+ ps->err = errtext;
+ ps->errloc = errloc;
+ } else {
+ sfree(errtext);
+ }
+}
+
+static void advance(ParserState *ps)
+{
+ char *err = NULL;
+ ps->tok = lex(&ps->currtext, &ps->toktext, &err);
+ if (ps->tok == TOK_ERROR)
+ error(ps, err, ps->toktext);
+}
+
+static ExprNode *parse_atom(ParserState *ps);
+static ExprNode *parse_expr(ParserState *ps);
+
+static bool atom_is_hostname_wc(ptrlen toktext)
+{
+ return !ptrlen_contains(toktext, ":/");
+}
+
+static ExprNode *parse_atom(ParserState *ps)
+{
+ if (ps->tok == TOK_LPAR) {
+ ptrlen openpar = ps->toktext;
+ advance(ps); /* eat the ( */
+
+ ExprNode *subexpr = parse_expr(ps);
+ if (!subexpr)
+ return NULL;
+
+ if (ps->tok != TOK_RPAR) {
+ error(ps, dupstr("expected ')' after parenthesised subexpression"),
+ subexpr->text);
+ exprnode_free(subexpr);
+ return NULL;
+ }
+
+ ptrlen closepar = ps->toktext;
+ advance(ps); /* eat the ) */
+
+ /* We can reuse the existing AST node, but we need to extend
+ * its bounds within the input expression to include the
+ * parentheses */
+ subexpr->text = make_ptrlen_startend(
+ openpar.ptr, ptrlen_end(closepar));
+ return subexpr;
+ }
+
+ if (ps->tok == TOK_NOT) {
+ ptrlen notloc = ps->toktext;
+ advance(ps); /* eat the ! */
+
+ ExprNode *subexpr = parse_atom(ps);
+ if (!subexpr)
+ return NULL;
+
+ ExprNode *en = exprnode_new(
+ OP_NOT, make_ptrlen_startend(
+ notloc.ptr, ptrlen_end(subexpr->text)));
+ en->subexpr = subexpr;
+ return en;
+ }
+
+ if (ps->tok == TOK_ATOM) {
+ if (atom_is_hostname_wc(ps->toktext)) {
+ /* Hostname wildcard. */
+ ExprNode *en = exprnode_new(OP_HOSTNAME_WC, ps->toktext);
+ en->wc = mkstr(ps->toktext);
+ advance(ps);
+ return en;
+ }
+
+ ptrlen tail;
+ if (ptrlen_startswith(ps->toktext, PTRLEN_LITERAL("port:"), &tail)) {
+ /* Port number (single or range). */
+ unsigned lo, hi;
+ char *minus;
+ static const char DIGITS[] = "0123456789\0";
+ bool parse_ok = false;
+
+ if (tail.len > 0 && ptrlen_contains_only(tail, DIGITS)) {
+ lo = ptrlen_to_port_number(tail);
+ if (lo >= 65536) {
+ error(ps, dupstr("port number too large"), tail);
+ return NULL;
+ }
+ hi = lo;
+ parse_ok = true;
+ } else if ((minus = memchr(tail.ptr, '-', tail.len)) != NULL) {
+ ptrlen pl_lo = make_ptrlen_startend(tail.ptr, minus);
+ ptrlen pl_hi = make_ptrlen_startend(minus+1, ptrlen_end(tail));
+ if (pl_lo.len > 0 && ptrlen_contains_only(pl_lo, DIGITS) &&
+ pl_hi.len > 0 && ptrlen_contains_only(pl_hi, DIGITS)) {
+
+ lo = ptrlen_to_port_number(pl_lo);
+ if (lo >= 65536) {
+ error(ps, dupstr("port number too large"), pl_lo);
+ return NULL;
+ }
+
+ hi = ptrlen_to_port_number(pl_hi);
+ if (hi >= 65536) {
+ error(ps, dupstr("port number too large"), pl_hi);
+ return NULL;
+ }
+
+ if (hi < lo) {
+ error(ps, dupstr("port number range is backwards"),
+ make_ptrlen_startend(pl_lo.ptr,
+ ptrlen_end(pl_hi)));
+ return NULL;
+ }
+
+ parse_ok = true;
+ }
+ }
+
+ if (!parse_ok) {
+ error(ps, dupstr("unable to parse port number specification"),
+ ps->toktext);
+ return NULL;
+ }
+
+
+ ExprNode *en = exprnode_new(OP_PORT_RANGE, ps->toktext);
+ en->lo = lo;
+ en->hi = hi;
+ advance(ps);
+ return en;
+ }
+ }
+
+ error(ps, dupstr("expected a predicate or a parenthesised subexpression"),
+ ps->toktext);
+ return NULL;
+}
+
+static ExprNode *parse_expr(ParserState *ps)
+{
+ ExprNode *subexpr = parse_atom(ps);
+ if (!subexpr)
+ return NULL;
+
+ if (ps->tok != TOK_AND && ps->tok != TOK_OR)
+ return subexpr;
+
+ Token operator = ps->tok;
+ ExprNode *en = exprnode_new(ps->tok == TOK_AND ? OP_AND : OP_OR,
+ subexpr->text);
+ size_t subexprs_size = 0;
+
+ sgrowarray(en->subexprs, subexprs_size, en->nsubexprs);
+ en->subexprs[en->nsubexprs++] = subexpr;
+
+ while (true) {
+ advance(ps); /* eat the operator */
+
+ subexpr = parse_atom(ps);
+ if (!subexpr) {
+ exprnode_free(en);
+ return NULL;
+ }
+ sgrowarray(en->subexprs, subexprs_size, en->nsubexprs);
+ en->subexprs[en->nsubexprs++] = subexpr;
+ en->text = make_ptrlen_startend(
+ en->text.ptr, ptrlen_end(subexpr->text));
+
+ if (ps->tok != TOK_AND && ps->tok != TOK_OR)
+ return en;
+
+ if (ps->tok != operator) {
+ error(ps, dupstr("expected parentheses to disambiguate && and || "
+ "on either side of expression"), subexpr->text);
+ exprnode_free(en);
+ return NULL;
+ }
+ }
+}
+
+static ExprNode *parse(ptrlen expr, char **error_msg, ptrlen *error_loc)
+{
+ ParserState ps[1];
+ ps->currtext = expr;
+ ps->err = NULL;
+ advance(ps);
+
+ ExprNode *en = parse_expr(ps);
+ if (en && ps->tok != TOK_END) {
+ error(ps, dupstr("unexpected text at end of expression"),
+ make_ptrlen_startend(ps->toktext.ptr, ptrlen_end(expr)));
+ exprnode_free(en);
+ en = NULL;
+ }
+
+ if (!en) {
+ if (error_msg)
+ *error_msg = ps->err;
+ else
+ sfree(ps->err);
+ if (error_loc)
+ *error_loc = ps->errloc;
+ return NULL;
+ }
+
+ return en;
+}
+
+static bool eval(ExprNode *en, const char *hostname, unsigned port)
+{
+ switch (en->op) {
+ case OP_AND:
+ for (size_t i = 0; i < en->nsubexprs; i++)
+ if (!eval(en->subexprs[i], hostname, port))
+ return false;
+ return true;
+
+ case OP_OR:
+ for (size_t i = 0; i < en->nsubexprs; i++)
+ if (eval(en->subexprs[i], hostname, port))
+ return true;
+ return false;
+
+ case OP_NOT:
+ return !eval(en->subexpr, hostname, port);
+
+ case OP_HOSTNAME_WC:
+ return wc_match(en->wc, hostname);
+
+ case OP_PORT_RANGE:
+ return en->lo <= port && port <= en->hi;
+
+ default:
+ unreachable("unhandled node type in eval");
+ }
+}
+
+bool cert_expr_match_str(const char *expression,
+ const char *hostname, unsigned port)
+{
+ ExprNode *en = parse(ptrlen_from_asciz(expression), NULL, NULL);
+ if (!en)
+ return false;
+
+ bool matched = eval(en, hostname, port);
+ exprnode_free(en);
+ return matched;
+}
+
+bool cert_expr_valid(const char *expression,
+ char **error_msg, ptrlen *error_loc)
+{
+ ExprNode *en = parse(ptrlen_from_asciz(expression), error_msg, error_loc);
+ if (en) {
+ exprnode_free(en);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+struct CertExprBuilder {
+ char **wcs;
+ size_t nwcs, wcsize;
+};
+
+CertExprBuilder *cert_expr_builder_new(void)
+{
+ CertExprBuilder *eb = snew(CertExprBuilder);
+ eb->wcs = NULL;
+ eb->nwcs = eb->wcsize = 0;
+ return eb;
+}
+
+void cert_expr_builder_free(CertExprBuilder *eb)
+{
+ for (size_t i = 0; i < eb->nwcs; i++)
+ sfree(eb->wcs[i]);
+ sfree(eb->wcs);
+ sfree(eb);
+}
+
+void cert_expr_builder_add(CertExprBuilder *eb, const char *wildcard)
+{
+ /* Check this wildcard is lexically valid as an atom */
+ ptrlen orig = ptrlen_from_asciz(wildcard), pl = orig;
+ ptrlen toktext;
+ char *err;
+ Token tok = lex(&pl, &toktext, &err);
+ if (!(tok == TOK_ATOM &&
+ toktext.ptr == orig.ptr &&
+ toktext.len == orig.len &&
+ atom_is_hostname_wc(toktext))) {
+ if (tok == TOK_ERROR)
+ sfree(err);
+ return;
+ }
+
+ sgrowarray(eb->wcs, eb->wcsize, eb->nwcs);
+ eb->wcs[eb->nwcs++] = mkstr(orig);
+}
+
+char *cert_expr_expression(CertExprBuilder *eb)
+{
+ strbuf *sb = strbuf_new();
+ for (size_t i = 0; i < eb->nwcs; i++) {
+ if (i)
+ put_dataz(sb, " || ");
+ put_dataz(sb, eb->wcs[i]);
+ }
+ return strbuf_to_str(sb);
+}
+
+#ifdef TEST
+
+void out_of_memory(void) { fprintf(stderr, "out of memory\n"); abort(); }
+
+static void exprnode_dump(BinarySink *bs, ExprNode *en, const char *origtext)
+{
+ put_fmt(bs, "(%zu:%zu ",
+ (size_t)((const char *)en->text.ptr - origtext),
+ (size_t)((const char *)ptrlen_end(en->text) - origtext));
+ switch (en->op) {
+ case OP_AND:
+ case OP_OR:
+ put_dataz(bs, en->op == OP_AND ? "and" : "or");
+ for (size_t i = 0; i < en->nsubexprs; i++) {
+ put_byte(bs, ' ');
+ exprnode_dump(bs, en->subexprs[i], origtext);
+ }
+ break;
+ case OP_NOT:
+ put_dataz(bs, "not ");
+ exprnode_dump(bs, en->subexpr, origtext);
+ break;
+ case OP_HOSTNAME_WC:
+ put_dataz(bs, "host-wc '");
+ put_dataz(bs, en->wc);
+ put_byte(bs, '\'');
+ break;
+ case OP_PORT_RANGE:
+ put_fmt(bs, "port-range %u %u", en->lo, en->hi);
+ break;
+ default:
+ unreachable("unhandled node type in exprnode_dump");
+ }
+ put_byte(bs, ')');
+}
+
+static const struct ParseTest {
+ const char *file;
+ int line;
+ const char *expr, *output;
+} parsetests[] = {
+#define T(expr_, output_) { \
+ .file=__FILE__, .line=__LINE__, .expr=expr_, .output=output_}
+
+ T("*.example.com", "(0:13 host-wc '*.example.com')"),
+ T("port:0", "(0:6 port-range 0 0)"),
+ T("port:22", "(0:7 port-range 22 22)"),
+ T("port:22-22", "(0:10 port-range 22 22)"),
+ T("port:65535", "(0:10 port-range 65535 65535)"),
+ T("port:0-1023", "(0:11 port-range 0 1023)"),
+
+ T("&", "ERR:0:1:unrecognised boolean operator"),
+ T("|", "ERR:0:1:unrecognised boolean operator"),
+ T(";", "ERR:0:1:unexpected character in expression"),
+ T("port:", "ERR:0:5:unable to parse port number specification"),
+ T("port:abc", "ERR:0:8:unable to parse port number specification"),
+ T("port:65536", "ERR:5:10:port number too large"),
+ T("port:65536-65537", "ERR:5:10:port number too large"),
+ T("port:0-65536", "ERR:7:12:port number too large"),
+ T("port:23-22", "ERR:5:10:port number range is backwards"),
+
+ T("a", "(0:1 host-wc 'a')"),
+ T("(a)", "(0:3 host-wc 'a')"),
+ T("((a))", "(0:5 host-wc 'a')"),
+ T(" (\n(\ra\t)\f)\v", "(1:10 host-wc 'a')"),
+ T("a&&b", "(0:4 and (0:1 host-wc 'a') (3:4 host-wc 'b'))"),
+ T("a||b", "(0:4 or (0:1 host-wc 'a') (3:4 host-wc 'b'))"),
+ T("a&&b&&c", "(0:7 and (0:1 host-wc 'a') (3:4 host-wc 'b') (6:7 host-wc 'c'))"),
+ T("a||b||c", "(0:7 or (0:1 host-wc 'a') (3:4 host-wc 'b') (6:7 host-wc 'c'))"),
+ T("a&&(b||c)", "(0:9 and (0:1 host-wc 'a') (3:9 or (4:5 host-wc 'b') (7:8 host-wc 'c')))"),
+ T("a||(b&&c)", "(0:9 or (0:1 host-wc 'a') (3:9 and (4:5 host-wc 'b') (7:8 host-wc 'c')))"),
+ T("(a&&b)||c", "(0:9 or (0:6 and (1:2 host-wc 'a') (4:5 host-wc 'b')) (8:9 host-wc 'c'))"),
+ T("(a||b)&&c", "(0:9 and (0:6 or (1:2 host-wc 'a') (4:5 host-wc 'b')) (8:9 host-wc 'c'))"),
+ T("!a&&b", "(0:5 and (0:2 not (1:2 host-wc 'a')) (4:5 host-wc 'b'))"),
+ T("a&&!b&&c", "(0:8 and (0:1 host-wc 'a') (3:5 not (4:5 host-wc 'b')) (7:8 host-wc 'c'))"),
+ T("!a||b", "(0:5 or (0:2 not (1:2 host-wc 'a')) (4:5 host-wc 'b'))"),
+ T("a||!b||c", "(0:8 or (0:1 host-wc 'a') (3:5 not (4:5 host-wc 'b')) (7:8 host-wc 'c'))"),
+
+ T("", "ERR:0:0:expected a predicate or a parenthesised subexpression"),
+ T("a &&", "ERR:4:4:expected a predicate or a parenthesised subexpression"),
+ T("a ||", "ERR:4:4:expected a predicate or a parenthesised subexpression"),
+ T("a b c d", "ERR:2:7:unexpected text at end of expression"),
+ T("(", "ERR:1:1:expected a predicate or a parenthesised subexpression"),
+ T("(a", "ERR:1:2:expected ')' after parenthesised subexpression"),
+ T("(a b", "ERR:1:2:expected ')' after parenthesised subexpression"),
+ T("a&&b&&c||d||e", "ERR:6:7:expected parentheses to disambiguate && and || on either side of expression"),
+ T("a||b||c&&d&&e", "ERR:6:7:expected parentheses to disambiguate && and || on either side of expression"),
+ T("!", "ERR:1:1:expected a predicate or a parenthesised subexpression"),
+
+ T("!a", "(0:2 not (1:2 host-wc 'a'))"),
+
+#undef T
+};
+
+static const struct EvalTest {
+ const char *file;
+ int line;
+ const char *expr;
+ const char *host;
+ unsigned port;
+ bool output;
+} evaltests[] = {
+#define T(expr_, host_, port_, output_) { \
+ .file=__FILE__, .line=__LINE__, \
+ .expr=expr_, .host=host_, .port=port_, .output=output_}
+
+ T("*.example.com", "hostname.example.com", 22, true),
+ T("*.example.com", "hostname.example.org", 22, false),
+ T("*.example.com", "hostname.dept.example.com", 22, true),
+ T("*.example.com && port:22", "hostname.example.com", 21, false),
+ T("*.example.com && port:22", "hostname.example.com", 22, true),
+ T("*.example.com && port:22", "hostname.example.com", 23, false),
+ T("*.example.com && port:22-24", "hostname.example.com", 21, false),
+ T("*.example.com && port:22-24", "hostname.example.com", 22, true),
+ T("*.example.com && port:22-24", "hostname.example.com", 23, true),
+ T("*.example.com && port:22-24", "hostname.example.com", 24, true),
+ T("*.example.com && port:22-24", "hostname.example.com", 25, false),
+
+ T("*a* && *b* && *c*", "", 22, false),
+ T("*a* && *b* && *c*", "a", 22, false),
+ T("*a* && *b* && *c*", "b", 22, false),
+ T("*a* && *b* && *c*", "c", 22, false),
+ T("*a* && *b* && *c*", "ab", 22, false),
+ T("*a* && *b* && *c*", "ac", 22, false),
+ T("*a* && *b* && *c*", "bc", 22, false),
+ T("*a* && *b* && *c*", "abc", 22, true),
+
+ T("*a* || *b* || *c*", "", 22, false),
+ T("*a* || *b* || *c*", "a", 22, true),
+ T("*a* || *b* || *c*", "b", 22, true),
+ T("*a* || *b* || *c*", "c", 22, true),
+ T("*a* || *b* || *c*", "ab", 22, true),
+ T("*a* || *b* || *c*", "ac", 22, true),
+ T("*a* || *b* || *c*", "bc", 22, true),
+ T("*a* || *b* || *c*", "abc", 22, true),
+
+ T("*a* && !*b* && *c*", "", 22, false),
+ T("*a* && !*b* && *c*", "a", 22, false),
+ T("*a* && !*b* && *c*", "b", 22, false),
+ T("*a* && !*b* && *c*", "c", 22, false),
+ T("*a* && !*b* && *c*", "ab", 22, false),
+ T("*a* && !*b* && *c*", "ac", 22, true),
+ T("*a* && !*b* && *c*", "bc", 22, false),
+ T("*a* && !*b* && *c*", "abc", 22, false),
+
+ T("*a* || !*b* || *c*", "", 22, true),
+ T("*a* || !*b* || *c*", "a", 22, true),
+ T("*a* || !*b* || *c*", "b", 22, false),
+ T("*a* || !*b* || *c*", "c", 22, true),
+ T("*a* || !*b* || *c*", "ab", 22, true),
+ T("*a* || !*b* || *c*", "ac", 22, true),
+ T("*a* || !*b* || *c*", "bc", 22, true),
+ T("*a* || !*b* || *c*", "abc", 22, true),
+
+#undef T
+};
+
+int main(int argc, char **argv)
+{
+ if (argc > 1) {
+ /*
+ * Parse an expression from the command line.
+ */
+
+ ptrlen expr = ptrlen_from_asciz(argv[1]);
+ char *error_msg;
+ ptrlen error_loc;
+ ExprNode *en = parse(expr, &error_msg, &error_loc);
+ if (!en) {
+ fprintf(stderr, "ERR:%zu:%zu:%s\n",
+ (size_t)((const char *)error_loc.ptr - argv[1]),
+ (size_t)((const char *)ptrlen_end(error_loc) - argv[1]),
+ error_msg);
+ fprintf(stderr, "%.*s\n", PTRLEN_PRINTF(expr));
+ for (const char *p = expr.ptr, *e = error_loc.ptr; p<e; p++)
+ fputc(' ', stderr);
+ for (size_t i = 0; i < error_loc.len || i < 1; i++)
+ fputc('^', stderr);
+ fputc('\n', stderr);
+ sfree(error_msg);
+ return 1;
+ }
+
+ if (argc > 2) {
+ /*
+ * Test-evaluate against a host/port pair given on the
+ * command line.
+ */
+ const char *host = argv[2];
+ unsigned port = (argc > 3 ? strtoul(argv[3], NULL, 0) : 22);
+ bool result = eval(en, host, port);
+ printf("%s\n", result ? "accept" : "reject");
+ } else {
+ /*
+ * Just dump the result of parsing the expression.
+ */
+ stdio_sink ss[1];
+ stdio_sink_init(ss, stdout);
+ exprnode_dump(BinarySink_UPCAST(ss), en, expr.ptr);
+ put_byte(ss, '\n');
+ }
+
+ exprnode_free(en);
+
+ return 0;
+ } else {
+ /*
+ * Run our automated tests.
+ */
+ size_t pass = 0, fail = 0;
+
+ for (size_t i = 0; i < lenof(parsetests); i++) {
+ const struct ParseTest *test = &parsetests[i];
+
+ ptrlen expr = ptrlen_from_asciz(test->expr);
+ char *error_msg;
+ ptrlen error_loc;
+ ExprNode *en = parse(expr, &error_msg, &error_loc);
+
+ strbuf *output = strbuf_new();
+ if (!en) {
+ put_fmt(output, "ERR:%zu:%zu:%s",
+ (size_t)((const char *)error_loc.ptr - test->expr),
+ (size_t)((const char *)ptrlen_end(error_loc) -
+ test->expr),
+ error_msg);
+ sfree(error_msg);
+ } else {
+ exprnode_dump(BinarySink_UPCAST(output), en, expr.ptr);
+ exprnode_free(en);
+ }
+
+ if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(output),
+ ptrlen_from_asciz(test->output))) {
+ pass++;
+ } else {
+ fprintf(stderr, "FAIL: parsetests[%zu] @ %s:%d:\n"
+ " expression: %s\n"
+ " expected: %s\n"
+ " actual: %s\n",
+ i, test->file, test->line, test->expr,
+ test->output, output->s);
+ fail++;
+ }
+
+ strbuf_free(output);
+ }
+
+ for (size_t i = 0; i < lenof(evaltests); i++) {
+ const struct EvalTest *test = &evaltests[i];
+
+ ptrlen expr = ptrlen_from_asciz(test->expr);
+ char *error_msg;
+ ptrlen error_loc;
+ ExprNode *en = parse(expr, &error_msg, &error_loc);
+
+ if (!en) {
+ fprintf(stderr, "FAIL: evaltests[%zu] @ %s:%d:\n"
+ " expression: %s\n"
+ " parse error: %zu:%zu:%s\n",
+ i, test->file, test->line, test->expr,
+ (size_t)((const char *)error_loc.ptr - test->expr),
+ (size_t)((const char *)ptrlen_end(error_loc) -
+ test->expr),
+ error_msg);
+ sfree(error_msg);
+ } else {
+ bool output = eval(en, test->host, test->port);
+ if (output == test->output) {
+ pass++;
+ } else {
+ fprintf(stderr, "FAIL: evaltests[%zu] @ %s:%d:\n"
+ " expression: %s\n"
+ " host: %s\n"
+ " port: %u\n"
+ " expected: %s\n"
+ " actual: %s\n",
+ i, test->file, test->line, test->expr,
+ test->host, test->port,
+ test->output ? "accept" : "reject",
+ output ? "accept" : "reject");
+ fail++;
+ }
+ exprnode_free(en);
+ }
+ }
+
+ fprintf(stderr, "pass %zu fail %zu total %zu\n",
+ pass, fail, pass+fail);
+ return fail != 0;
+ }
+}
+
+#endif // TEST
diff --git a/utils/chomp.c b/utils/chomp.c
new file mode 100644
index 00000000..866fc652
--- /dev/null
+++ b/utils/chomp.c
@@ -0,0 +1,26 @@
+/*
+ * Perl-style 'chomp', for a line we just read with fgetline.
+ *
+ * Unlike Perl chomp, however, we're deliberately forgiving of strange
+ * line-ending conventions.
+ *
+ * Also we forgive NULL on input, so you can just write 'line =
+ * chomp(fgetline(fp));' and not bother checking for NULL until
+ * afterwards.
+ */
+
+#include <string.h>
+
+#include "defs.h"
+#include "misc.h"
+
+char *chomp(char *str)
+{
+ if (str) {
+ int len = strlen(str);
+ while (len > 0 && (str[len-1] == '\r' || str[len-1] == '\n'))
+ len--;
+ str[len] = '\0';
+ }
+ return str;
+}
diff --git a/utils/cmdline_get_passwd_input_state_new.c b/utils/cmdline_get_passwd_input_state_new.c
new file mode 100644
index 00000000..cd39bfa1
--- /dev/null
+++ b/utils/cmdline_get_passwd_input_state_new.c
@@ -0,0 +1,9 @@
+/*
+ * A preinitialised cmdline_get_passwd_input_state which makes it easy
+ * to assign by structure copy.
+ */
+
+#include "putty.h"
+
+const cmdline_get_passwd_input_state cmdline_get_passwd_input_state_new =
+ CMDLINE_GET_PASSWD_INPUT_STATE_INIT;
diff --git a/utils/conf.c b/utils/conf.c
new file mode 100644
index 00000000..53195180
--- /dev/null
+++ b/utils/conf.c
@@ -0,0 +1,593 @@
+/*
+ * conf.c: implementation of the internal storage format used for
+ * the configuration of a PuTTY session.
+ */
+
+#include <stdio.h>
+#include <stddef.h>
+#include <assert.h>
+
+#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;
+
+ 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;
+
+ 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);
+
+ 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);
+
+ 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/utils/conf_dest.c b/utils/conf_dest.c
new file mode 100644
index 00000000..13e4ce65
--- /dev/null
+++ b/utils/conf_dest.c
@@ -0,0 +1,15 @@
+/*
+ * Decide whether the 'host name' or 'serial line' field of a Conf is
+ * important, based on which protocol it has selected.
+ */
+
+#include "putty.h"
+
+char const *conf_dest(Conf *conf)
+{
+ if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL)
+ return conf_get_str(conf, CONF_serline);
+ else
+ return conf_get_str(conf, CONF_host);
+}
+
diff --git a/utils/conf_launchable.c b/utils/conf_launchable.c
new file mode 100644
index 00000000..904ade61
--- /dev/null
+++ b/utils/conf_launchable.c
@@ -0,0 +1,14 @@
+/*
+ * Determine whether or not a Conf represents a session which can
+ * sensibly be launched right now.
+ */
+
+#include "putty.h"
+
+bool conf_launchable(Conf *conf)
+{
+ if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL)
+ return conf_get_str(conf, CONF_serline)[0] != 0;
+ else
+ return conf_get_str(conf, CONF_host)[0] != 0;
+}
diff --git a/utils/ctrlparse.c b/utils/ctrlparse.c
new file mode 100644
index 00000000..86f87902
--- /dev/null
+++ b/utils/ctrlparse.c
@@ -0,0 +1,49 @@
+/*
+ * 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
+ * <num> specified by number (decimal, 0octal, 0xHEX)
+ * ~ ^ escape
+ */
+
+#include <stdlib.h>
+
+#include "defs.h"
+#include "misc.h"
+
+char ctrlparse(char *s, char **next)
+{
+ char c = 0;
+ if (*s != '^') {
+ *next = NULL;
+ } else {
+ s++;
+ if (*s == '\0') {
+ *next = NULL;
+ } else if (*s == '<') {
+ s++;
+ c = (char)strtol(s, next, 0);
+ if ((*next == s) || (**next != '>')) {
+ c = 0;
+ *next = NULL;
+ } else
+ (*next)++;
+ } else if (*s >= 'a' && *s <= 'z') {
+ c = (*s - ('a' - 1));
+ *next = s+1;
+ } else if ((*s >= '@' && *s <= '_') || *s == '?' || (*s & 0x80)) {
+ c = ('@' ^ *s);
+ *next = s+1;
+ } else if (*s == '~') {
+ c = '^';
+ *next = s+1;
+ }
+ }
+ return c;
+}
diff --git a/utils/ctrlset_normalise.c b/utils/ctrlset_normalise.c
new file mode 100644
index 00000000..3d922ebb
--- /dev/null
+++ b/utils/ctrlset_normalise.c
@@ -0,0 +1,60 @@
+/*
+ * Helper function from the dialog.h mechanism.
+ */
+
+#include "putty.h"
+#include "misc.h"
+#include "dialog.h"
+
+void ctrlset_normalise_aligns(struct controlset *s)
+{
+ /*
+ * The algorithm in here is quadratic time. Never on very much data, but
+ * even so, let's avoid bothering to use it where possible. In most
+ * controlsets, there's no use of align_next_to in any case, so we have
+ * nothing to do.
+ */
+ for (size_t j = 0; j < s->ncontrols; j++)
+ if (s->ctrls[j]->align_next_to)
+ goto must_do_something;
+ /* If we fell out of this loop, there's nothing to do here */
+ return;
+ must_do_something:;
+
+ size_t *idx = snewn(s->ncontrols, size_t);
+
+ /*
+ * Follow align_next_to links to identify, for each control, the least
+ * index within this controlset of things it's linked to. That way,
+ * controls with the same idx[j] will be in the same alignment class.
+ */
+ for (size_t j = 0; j < s->ncontrols; j++) {
+ dlgcontrol *c = s->ctrls[j];
+ idx[j] = j;
+ if (c->align_next_to) {
+ for (size_t k = 0; k < j; k++) {
+ if (s->ctrls[k] == c->align_next_to) {
+ idx[j] = idx[k];
+ break;
+ }
+ }
+ }
+ }
+
+ /*
+ * Having done that, re-link each control to the most recent one in its
+ * class, so that the links form a backward linked list.
+ */
+ for (size_t j = 0; j < s->ncontrols; j++) {
+ dlgcontrol *c = s->ctrls[j];
+ c->align_next_to = NULL;
+ for (size_t k = j; k-- > 0 ;) {
+ if (idx[k] == idx[j]) {
+ c->align_next_to = s->ctrls[k];
+ break;
+ }
+ }
+ }
+
+ sfree(idx);
+}
diff --git a/utils/debug.c b/utils/debug.c
new file mode 100644
index 00000000..79e437a2
--- /dev/null
+++ b/utils/debug.c
@@ -0,0 +1,56 @@
+/*
+ * Debugging routines used by the debug() macros, at least if you
+ * compiled with -DDEBUG (aka the PUTTY_DEBUG cmake option) so that
+ * those macros don't optimise down to nothing.
+ */
+
+#include "defs.h"
+#include "misc.h"
+#include "utils/utils.h"
+
+void debug_printf(const char *fmt, ...)
+{
+ char *buf;
+ va_list ap;
+
+ va_start(ap, fmt);
+ buf = dupvprintf(fmt, ap);
+ dputs(buf);
+ sfree(buf);
+ va_end(ap);
+}
+
+void debug_memdump(const void *buf, int len, bool L)
+{
+ int i;
+ const unsigned char *p = buf;
+ char foo[17];
+ if (L) {
+ int delta;
+ debug_printf("\t%d (0x%x) bytes:\n", len, len);
+ delta = 15 & (uintptr_t)p;
+ p -= delta;
+ len += delta;
+ }
+ for (; 0 < len; p += 16, len -= 16) {
+ dputs(" ");
+ if (L)
+ debug_printf("%p: ", p);
+ strcpy(foo, "................"); /* sixteen dots */
+ for (i = 0; i < 16 && i < len; ++i) {
+ if (&p[i] < (unsigned char *) buf) {
+ dputs(" "); /* 3 spaces */
+ foo[i] = ' ';
+ } else {
+ debug_printf("%c%2.2x",
+ &p[i] != (unsigned char *) buf
+ && i % 4 ? '.' : ' ', p[i]
+ );
+ if (p[i] >= ' ' && p[i] <= '~')
+ foo[i] = (char) p[i];
+ }
+ }
+ foo[i] = '\0';
+ debug_printf("%*s%s\n", (16 - i) * 3 + 2, "", foo);
+ }
+}
diff --git a/utils/decode_utf8.c b/utils/decode_utf8.c
new file mode 100644
index 00000000..c8dbec79
--- /dev/null
+++ b/utils/decode_utf8.c
@@ -0,0 +1,178 @@
+/*
+ * Decode a single UTF-8 character.
+ */
+
+#include "putty.h"
+#include "misc.h"
+
+unsigned long decode_utf8(const char **utf8)
+{
+ unsigned char c = (unsigned char)*(*utf8)++;
+
+ /* One-byte cases. */
+ if (c < 0x80) {
+ return c;
+ } else if (c < 0xC0) {
+ return 0xFFFD; /* spurious continuation byte */
+ }
+
+ unsigned long wc, min;
+ size_t ncont;
+ if (c < 0xE0) {
+ wc = c & 0x1F; ncont = 1; min = 0x80;
+ } else if (c < 0xF0) {
+ wc = c & 0x0F; ncont = 2; min = 0x800;
+ } else if (c < 0xF8) {
+ wc = c & 0x07; ncont = 3; min = 0x10000;
+ } else if (c < 0xFC) {
+ wc = c & 0x03; ncont = 4; min = 0x200000;
+ } else if (c < 0xFE) {
+ wc = c & 0x01; ncont = 5; min = 0x4000000;
+ } else {
+ return 0xFFFD; /* FE or FF illegal bytes */
+ }
+
+ while (ncont-- > 0) {
+ unsigned char cont = (unsigned char)**utf8;
+ if (!(0x80 <= cont && cont < 0xC0))
+ return 0xFFFD; /* short sequence */
+ (*utf8)++;
+
+ wc = (wc << 6) | (cont & 0x3F);
+ }
+
+ if (wc < min)
+ return 0xFFFD; /* overlong encoding */
+ if (0xD800 <= wc && wc < 0xE000)
+ return 0xFFFD; /* UTF-8 encoding of surrogate */
+ if (wc > 0x10FFFF)
+ return 0xFFFD; /* outside Unicode range */
+ return wc;
+}
+
+#ifdef TEST
+
+#include <stdio.h>
+
+bool dotest(const char *file, int line, const char *input,
+ const unsigned long *chars, size_t nchars)
+{
+ const char *start = input;
+ const char *end = input + strlen(input) + 1;
+ size_t noutput = 0;
+
+ printf("%s:%d: test start\n", file, line);
+
+ while (input < end) {
+ const char *before = input;
+ unsigned long wc = decode_utf8(&input);
+
+ printf("%s:%d in+%"SIZEu" out+%"SIZEu":",
+ file, line, (size_t)(before-start), noutput);
+ while (before < input)
+ printf(" %02x", (unsigned)(unsigned char)(*before++));
+ printf(" -> U-%08lx\n", wc);
+
+ if (noutput >= nchars) {
+ printf("%s:%d: FAIL: expected no further output\n", file, line);
+ return false;
+ }
+
+ if (chars[noutput] != wc) {
+ printf("%s:%d: FAIL: expected U-%08lx\n",
+ file, line, chars[noutput]);
+ return false;
+ }
+
+ noutput++;
+ }
+
+ if (noutput < nchars) {
+ printf("%s:%d: FAIL: expected further output\n", file, line);
+ return false;
+ }
+
+ printf("%s:%d: pass\n", file, line);
+ return true;
+}
+
+#define DOTEST(input, ...) do { \
+ static const unsigned long chars[] = { __VA_ARGS__, 0 }; \
+ ntest++; \
+ if (dotest(__FILE__, __LINE__, input, chars, lenof(chars))) \
+ npass++; \
+ } while (0)
+
+int main(void)
+{
+ int ntest = 0, npass = 0;
+
+ DOTEST("\xCE\xBA\xE1\xBD\xB9\xCF\x83\xCE\xBC\xCE\xB5",
+ 0x03BA, 0x1F79, 0x03C3, 0x03BC, 0x03B5);
+
+ /* First sequence of each length (not counting NUL, which is
+ * tested anyway by the string-termination handling in every test) */
+ DOTEST("\xC2\x80", 0x0080);
+ DOTEST("\xE0\xA0\x80", 0x0800);
+ DOTEST("\xF0\x90\x80\x80", 0x00010000);
+ DOTEST("\xF8\x88\x80\x80\x80", 0xFFFD); /* would be 0x00200000 */
+ DOTEST("\xFC\x84\x80\x80\x80\x80", 0xFFFD); /* would be 0x04000000 */
+
+ /* Last sequence of each length */
+ DOTEST("\x7F", 0x007F);
+ DOTEST("\xDF\xBF", 0x07FF);
+ DOTEST("\xEF\xBF\xBF", 0xFFFF);
+ DOTEST("\xF7\xBF\xBF\xBF", 0xFFFD); /* would be 0x001FFFFF */
+ DOTEST("\xFB\xBF\xBF\xBF\xBF", 0xFFFD); /* would be 0x03FFFFFF */
+ DOTEST("\xFD\xBF\xBF\xBF\xBF\xBF", 0xFFFD); /* would be 0x7FFFFFFF */
+
+ /* Endpoints of the surrogate range */
+ DOTEST("\xED\x9F\xBF", 0xD7FF);
+ DOTEST("\xED\xA0\x00", 0xFFFD); /* would be 0xD800 */
+ DOTEST("\xED\xBF\xBF", 0xFFFD); /* would be 0xDFFF */
+ DOTEST("\xEE\x80\x80", 0xE000);
+
+ /* REPLACEMENT CHARACTER itself */
+ DOTEST("\xEF\xBF\xBD", 0xFFFD);
+
+ /* Endpoints of the legal Unicode range */
+ DOTEST("\xF4\x8F\xBF\xBF", 0x0010FFFF);
+ DOTEST("\xF4\x90\x80\x80", 0xFFFD); /* would be 0x00110000 */
+
+ /* Spurious continuation bytes, each shown as a separate failure */
+ DOTEST("\x80 \x81\x82 \xBD\xBE\xBF",
+ 0xFFFD, 0x0020, 0xFFFD, 0xFFFD, 0x0020, 0xFFFD, 0xFFFD, 0xFFFD);
+
+ /* Truncated sequences, each shown as just one failure */
+ DOTEST("\xC2\xE0\xA0\xF0\x90\x80\xF8\x88\x80\x80\xFC\x84\x80\x80\x80",
+ 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD);
+ DOTEST("\xC2 \xE0\xA0 \xF0\x90\x80 \xF8\x88\x80\x80 \xFC\x84\x80\x80\x80",
+ 0xFFFD, 0x0020, 0xFFFD, 0x0020, 0xFFFD, 0x0020, 0xFFFD, 0x0020,
+ 0xFFFD);
+
+ /* Illegal bytes */
+ DOTEST("\xFE\xFF", 0xFFFD, 0xFFFD);
+
+ /* Overlong sequences */
+ DOTEST("\xC1\xBF", 0xFFFD);
+ DOTEST("\xE0\x9F\xBF", 0xFFFD);
+ DOTEST("\xF0\x8F\xBF\xBF", 0xFFFD);
+ DOTEST("\xF8\x87\xBF\xBF\xBF", 0xFFFD);
+ DOTEST("\xFC\x83\xBF\xBF\xBF\xBF", 0xFFFD);
+
+ DOTEST("\xC0\x80", 0xFFFD);
+ DOTEST("\xE0\x80\x80", 0xFFFD);
+ DOTEST("\xF0\x80\x80\x80", 0xFFFD);
+ DOTEST("\xF8\x80\x80\x80\x80", 0xFFFD);
+ DOTEST("\xFC\x80\x80\x80\x80\x80", 0xFFFD);
+
+ printf("%d tests %d passed", ntest, npass);
+ if (npass < ntest) {
+ printf(" %d FAILED\n", ntest-npass);
+ return 1;
+ } else {
+ printf("\n");
+ return 0;
+ }
+}
+#endif
diff --git a/utils/decode_utf8_to_wchar.c b/utils/decode_utf8_to_wchar.c
new file mode 100644
index 00000000..97a83218
--- /dev/null
+++ b/utils/decode_utf8_to_wchar.c
@@ -0,0 +1,20 @@
+/*
+ * Decode a single UTF-8 character to the platform's local wchar_t.
+ */
+
+#include "putty.h"
+#include "misc.h"
+
+size_t decode_utf8_to_wchar(const char **utf8, wchar_t *out)
+{
+ size_t outlen = 0;
+ unsigned wc = decode_utf8(utf8);
+ if (sizeof(wchar_t) > 2 || wc < 0x10000) {
+ out[outlen++] = wc;
+ } else {
+ unsigned wcoff = wc - 0x10000;
+ out[outlen++] = 0xD800 | (0x3FF & (wcoff >> 10));
+ out[outlen++] = 0xDC00 | (0x3FF & wcoff);
+ }
+ return outlen;
+}
diff --git a/utils/default_description.c b/utils/default_description.c
new file mode 100644
index 00000000..e0695ee6
--- /dev/null
+++ b/utils/default_description.c
@@ -0,0 +1,22 @@
+/*
+ * Construct a description string for a backend to use as
+ * backend_description(), or a plug as plug_description().
+ *
+ * For some backends this will be overridden: e.g. SSH prefers to
+ * think in terms of _logical_ host names (i.e. the one associated
+ * with the host key) rather than the physical details of where you're
+ * connecting to. But this default is good for simpler backends.
+ */
+
+#include "putty.h"
+
+char *default_description(const BackendVtable *backvt,
+ const char *host, int port)
+{
+ const char *be_name = backvt->displayname_lc;
+
+ if (backvt->default_port && port == backvt->default_port)
+ return dupprintf("%s connection to %s", be_name, host);
+ else
+ return dupprintf("%s connection to %s port %d", be_name, host, port);
+}
diff --git a/utils/dup_mb_to_wc.c b/utils/dup_mb_to_wc.c
new file mode 100644
index 00000000..c3f17aba
--- /dev/null
+++ b/utils/dup_mb_to_wc.c
@@ -0,0 +1,29 @@
+/*
+ * dup_mb_to_wc: memory-allocating wrapper on mb_to_wc.
+ *
+ * Also dup_mb_to_wc_c: same but you already know the length of the
+ * string.
+ */
+
+#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/utils/dup_wc_to_mb.c b/utils/dup_wc_to_mb.c
new file mode 100644
index 00000000..36088196
--- /dev/null
+++ b/utils/dup_wc_to_mb.c
@@ -0,0 +1,38 @@
+/*
+ * dup_wc_to_mb: memory-allocating wrapper on wc_to_mb.
+ *
+ * Also dup_wc_to_mb_c: same but you already know the length of the
+ * string.
+ */
+
+#include <wchar.h>
+
+#include "putty.h"
+#include "misc.h"
+
+char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len,
+ const char *defchr)
+{
+ size_t outsize = len+1;
+ char *out = snewn(outsize, char);
+
+ while (true) {
+ size_t outlen = wc_to_mb(codepage, flags, string, len, out, outsize,
+ defchr);
+ /* We can only be sure we've consumed the whole input if the
+ * output is not within a multibyte-character-length of the
+ * end of the buffer! */
+ if (outlen < outsize && outsize - outlen > MB_LEN_MAX) {
+ out[outlen] = '\0';
+ return out;
+ }
+
+ sgrowarray(out, outsize, outsize);
+ }
+}
+
+char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string,
+ const char *defchr)
+{
+ return dup_wc_to_mb_c(codepage, flags, string, wcslen(string), defchr);
+}
diff --git a/utils/dupcat.c b/utils/dupcat.c
new file mode 100644
index 00000000..ddd6599e
--- /dev/null
+++ b/utils/dupcat.c
@@ -0,0 +1,48 @@
+/*
+ * Implementation function behind dupcat() in misc.h.
+ *
+ * This function is called with an arbitrary number of 'const char *'
+ * parameters, of which the last one is a null pointer. The wrapper
+ * macro puts on the null pointer itself, so normally callers don't
+ * have to.
+ */
+
+#include <string.h>
+#include <stdarg.h>
+
+#include "defs.h"
+#include "misc.h"
+
+char *dupcat_fn(const char *s1, ...)
+{
+ int len;
+ char *p, *q, *sn;
+ va_list ap;
+
+ len = strlen(s1);
+ va_start(ap, s1);
+ while (1) {
+ sn = va_arg(ap, char *);
+ if (!sn)
+ break;
+ len += strlen(sn);
+ }
+ va_end(ap);
+
+ p = snewn(len + 1, char);
+ strcpy(p, s1);
+ q = p + strlen(p);
+
+ va_start(ap, s1);
+ while (1) {
+ sn = va_arg(ap, char *);
+ if (!sn)
+ break;
+ strcpy(q, sn);
+ q += strlen(q);
+ }
+ va_end(ap);
+
+ return p;
+}
+
diff --git a/utils/dupprintf.c b/utils/dupprintf.c
new file mode 100644
index 00000000..aa9f330b
--- /dev/null
+++ b/utils/dupprintf.c
@@ -0,0 +1,100 @@
+/*
+ * 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 <float.h>, or even to predict it dynamically by
+ * looking at the exponent of the specific float provided, but
+ * it won't be fun.
+ *
+ * - Don't forget to _check_, after calling sprintf, that it's
+ * used at most the amount of space we had available.
+ *
+ * - Fault any formatting directive we don't fully understand. The
+ * aim here is to _guarantee_ that we never overflow the buffer,
+ * because this is a security-critical function. If we see a
+ * directive we don't know about, we should panic and die rather
+ * than run any risk.
+ */
+
+#include <stdio.h>
+
+#include "defs.h"
+#include "misc.h"
+#include "utils/utils.h"
+
+/* Work around lack of va_copy in old MSC */
+#if defined _MSC_VER && !defined va_copy
+#define va_copy(a, b) TYPECHECK( \
+ (va_list *)0 == &(a) && (va_list *)0 == &(b), \
+ memcpy(&a, &b, sizeof(va_list)))
+#endif
+
+/* Also lack of vsnprintf before VS2015 */
+#if defined _WINDOWS && \
+ !defined __MINGW32__ && \
+ !defined __WINE__ && \
+ _MSC_VER < 1900
+#define vsnprintf _vsnprintf
+#endif
+
+char *dupvprintf_inner(char *buf, size_t oldlen, size_t *sizeptr,
+ const char *fmt, va_list ap)
+{
+ size_t size = *sizeptr;
+ sgrowarrayn_nm(buf, size, oldlen, 512);
+
+ while (1) {
+ va_list aq;
+ va_copy(aq, ap);
+ int len = vsnprintf(buf + oldlen, size - oldlen, fmt, aq);
+ va_end(aq);
+
+ if (len >= 0 && len < size) {
+ /* This is the C99-specified criterion for snprintf to have
+ * been completely successful. */
+ *sizeptr = size;
+ return buf;
+ } else if (len > 0) {
+ /* This is the C99 error condition: the returned length is
+ * the required buffer size not counting the NUL. */
+ sgrowarrayn_nm(buf, size, oldlen + 1, len);
+ } else {
+ /* This is the pre-C99 glibc error condition: <0 means the
+ * buffer wasn't big enough, so we enlarge it a bit and hope. */
+ sgrowarray_nm(buf, size, size);
+ }
+ }
+}
+
+char *dupvprintf(const char *fmt, va_list ap)
+{
+ size_t size = 0;
+ return dupvprintf_inner(NULL, 0, &size, fmt, ap);
+}
+char *dupprintf(const char *fmt, ...)
+{
+ char *ret;
+ va_list ap;
+ va_start(ap, fmt);
+ ret = dupvprintf(fmt, ap);
+ va_end(ap);
+ return ret;
+}
diff --git a/utils/dupstr.c b/utils/dupstr.c
new file mode 100644
index 00000000..fd79583f
--- /dev/null
+++ b/utils/dupstr.c
@@ -0,0 +1,19 @@
+/*
+ * Allocate a duplicate of an ordinary C NUL-terminated string.
+ */
+
+#include <string.h>
+
+#include "defs.h"
+#include "misc.h"
+
+char *dupstr(const char *s)
+{
+ char *p = NULL;
+ if (s) {
+ int len = strlen(s);
+ p = snewn(len + 1, char);
+ strcpy(p, s);
+ }
+ return p;
+}
diff --git a/utils/encode_utf8.c b/utils/encode_utf8.c
new file mode 100644
index 00000000..731ab925
--- /dev/null
+++ b/utils/encode_utf8.c
@@ -0,0 +1,29 @@
+/*
+ * Encode a single code point as UTF-8.
+ */
+
+#include "defs.h"
+#include "misc.h"
+
+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 {
+ assert(ch <= 0x10FFFF);
+ *p++ = 0xF0 | (ch >> 18);
+ *p++ = 0x80 | ((ch >> 12) & 0x3F);
+ *p++ = 0x80 | ((ch >> 6) & 0x3F);
+ *p++ = 0x80 | (ch & 0x3F);
+ }
+ return p - start;
+}
diff --git a/utils/encode_wide_string_as_utf8.c b/utils/encode_wide_string_as_utf8.c
new file mode 100644
index 00000000..870903d5
--- /dev/null
+++ b/utils/encode_wide_string_as_utf8.c
@@ -0,0 +1,25 @@
+/*
+ * Encode a string of wchar_t as UTF-8.
+ */
+
+#include "putty.h"
+#include "misc.h"
+
+char *encode_wide_string_as_utf8(const wchar_t *ws)
+{
+ strbuf *sb = strbuf_new();
+ while (*ws) {
+ unsigned long ch = *ws++;
+ if (sizeof(wchar_t) == 2 && IS_HIGH_SURROGATE(ch) &&
+ IS_LOW_SURROGATE(*ws)) {
+ ch = FROM_SURROGATES(ch, *ws);
+ ws++;
+ } else if (IS_SURROGATE(ch)) {
+ ch = 0xfffd; /* illegal UTF-16 -> REPLACEMENT CHARACTER */
+ }
+ char utf8[6];
+ size_t size = encode_utf8(utf8, ch);
+ put_data(sb, utf8, size);
+ }
+ return strbuf_to_str(sb);
+}
diff --git a/utils/fgetline.c b/utils/fgetline.c
new file mode 100644
index 00000000..2bb580f1
--- /dev/null
+++ b/utils/fgetline.c
@@ -0,0 +1,25 @@
+/*
+ * Read an entire line of text from a file. Return a buffer
+ * malloced to be as big as necessary (caller must free).
+ */
+
+#include "defs.h"
+#include "misc.h"
+
+char *fgetline(FILE *fp)
+{
+ char *ret = snewn(512, char);
+ size_t size = 512, len = 0;
+ while (fgets(ret + len, size - len, fp)) {
+ len += strlen(ret + len);
+ if (len > 0 && ret[len-1] == '\n')
+ break; /* got a newline, we're done */
+ sgrowarrayn_nm(ret, size, len, 512);
+ }
+ if (len == 0) { /* first fgets returned NULL */
+ sfree(ret);
+ return NULL;
+ }
+ ret[len] = '\0';
+ return ret;
+}
diff --git a/utils/host_ca_new_free.c b/utils/host_ca_new_free.c
new file mode 100644
index 00000000..4c91c320
--- /dev/null
+++ b/utils/host_ca_new_free.c
@@ -0,0 +1,22 @@
+#include "defs.h"
+#include "misc.h"
+#include "storage.h"
+
+host_ca *host_ca_new(void)
+{
+ host_ca *hca = snew(host_ca);
+ memset(hca, 0, sizeof(*hca));
+ hca->opts.permit_rsa_sha1 = false;
+ hca->opts.permit_rsa_sha256 = true;
+ hca->opts.permit_rsa_sha512 = true;
+ return hca;
+}
+
+void host_ca_free(host_ca *hca)
+{
+ sfree(hca->name);
+ sfree(hca->validity_expression);
+ if (hca->ca_public_key)
+ strbuf_free(hca->ca_public_key);
+ sfree(hca);
+}
diff --git a/utils/host_strchr.c b/utils/host_strchr.c
new file mode 100644
index 00000000..363a915e
--- /dev/null
+++ b/utils/host_strchr.c
@@ -0,0 +1,18 @@
+/*
+ * strchr-like wrapper around host_strchr_internal.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+
+#include "defs.h"
+#include "misc.h"
+#include "utils/utils.h"
+
+char *host_strchr(const char *s, int c)
+{
+ char set[2];
+ set[0] = c;
+ set[1] = '\0';
+ return (char *) host_strchr_internal(s, set, true);
+}
diff --git a/utils/host_strchr_internal.c b/utils/host_strchr_internal.c
new file mode 100644
index 00000000..f995b53d
--- /dev/null
+++ b/utils/host_strchr_internal.c
@@ -0,0 +1,80 @@
+/*
+ * Find a character in a string, unless it's a colon contained within
+ * square brackets. Used for untangling strings of the form
+ * 'host:port', where host can be an IPv6 literal.
+ *
+ * This internal function provides an API that's a bit like strchr (in
+ * that it returns a pointer to the character it found, or NULL), and
+ * a bit like strcspn (in that you can give it a set of characters to
+ * look for, not just one). Also it has an option to return the first
+ * or last character it finds. Other functions in the utils directory
+ * provide wrappers on it with APIs more like familiar <string.h>
+ * functions.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+
+#include "defs.h"
+#include "misc.h"
+#include "utils/utils.h"
+
+const char *host_strchr_internal(const char *s, const char *set, bool first)
+{
+ int brackets = 0;
+ const char *ret = NULL;
+
+ while (1) {
+ if (!*s)
+ return ret;
+
+ if (*s == '[')
+ brackets++;
+ else if (*s == ']' && brackets > 0)
+ brackets--;
+ else if (brackets && *s == ':')
+ /* never match */ ;
+ else if (strchr(set, *s)) {
+ ret = s;
+ if (first)
+ return ret;
+ }
+
+ s++;
+ }
+}
+
+#ifdef TEST
+
+int main(void)
+{
+ int passes = 0, fails = 0;
+
+#define TEST1(func, string, arg2, suffix, result) do \
+ { \
+ const char *str = string; \
+ unsigned ret = func(str, arg2) suffix; \
+ if (ret == result) { \
+ passes++; \
+ } else { \
+ printf("fail: %s(%s,%s)%s = %u, expected %u\n", \
+ #func, #string, #arg2, #suffix, ret, \
+ (unsigned)result); \
+ fails++; \
+ } \
+} while (0)
+
+ TEST1(host_strchr, "[1:2:3]:4:5", ':', -str, 7);
+ TEST1(host_strrchr, "[1:2:3]:4:5", ':', -str, 9);
+ TEST1(host_strcspn, "[1:2:3]:4:5", "/:",, 7);
+ TEST1(host_strchr, "[1:2:3]", ':', == NULL, 1);
+ TEST1(host_strrchr, "[1:2:3]", ':', == NULL, 1);
+ TEST1(host_strcspn, "[1:2:3]", "/:",, 7);
+ TEST1(host_strcspn, "[1:2/3]", "/:",, 4);
+ TEST1(host_strcspn, "[1:2:3]/", "/:",, 7);
+
+ printf("passed %d failed %d total %d\n", passes, fails, passes+fails);
+ return fails != 0 ? 1 : 0;
+}
+
+#endif /* TEST */
diff --git a/utils/host_strcspn.c b/utils/host_strcspn.c
new file mode 100644
index 00000000..958f47f8
--- /dev/null
+++ b/utils/host_strcspn.c
@@ -0,0 +1,19 @@
+/*
+ * strcspn-like wrapper around host_strchr_internal.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+
+#include "defs.h"
+#include "misc.h"
+#include "utils/utils.h"
+
+size_t host_strcspn(const char *s, const char *set)
+{
+ const char *answer = host_strchr_internal(s, set, true);
+ if (answer)
+ return answer - s;
+ else
+ return strlen(s);
+}
diff --git a/utils/host_strduptrim.c b/utils/host_strduptrim.c
new file mode 100644
index 00000000..94492e64
--- /dev/null
+++ b/utils/host_strduptrim.c
@@ -0,0 +1,51 @@
+/*
+ * Trim square brackets off the outside of an IPv6 address literal.
+ * Leave all other strings unchanged. Returns a fresh dynamically
+ * allocated string.
+ */
+
+#include <ctype.h>
+#include <string.h>
+
+#include "defs.h"
+#include "misc.h"
+
+char *host_strduptrim(const char *s)
+{
+ if (s[0] == '[') {
+ const char *p = s+1;
+ int colons = 0;
+ while (*p && *p != ']') {
+ if (isxdigit((unsigned char)*p))
+ /* OK */;
+ else if (*p == ':')
+ colons++;
+ else
+ break;
+ p++;
+ }
+ if (*p == '%') {
+ /*
+ * This delimiter character introduces an RFC 4007 scope
+ * id suffix (e.g. suffixing the address literal with
+ * %eth1 or %2 or some such). There's no syntax
+ * specification for the scope id, so just accept anything
+ * except the closing ].
+ */
+ p += strcspn(p, "]");
+ }
+ if (*p == ']' && !p[1] && colons > 1) {
+ /*
+ * This looks like an IPv6 address literal (hex digits and
+ * at least two colons, plus optional scope id, contained
+ * in square brackets). Trim off the brackets.
+ */
+ return dupprintf("%.*s", (int)(p - (s+1)), s+1);
+ }
+ }
+
+ /*
+ * Any other shape of string is simply duplicated.
+ */
+ return dupstr(s);
+}
diff --git a/utils/host_strrchr.c b/utils/host_strrchr.c
new file mode 100644
index 00000000..a1dd4c94
--- /dev/null
+++ b/utils/host_strrchr.c
@@ -0,0 +1,18 @@
+/*
+ * strchr-like wrapper around host_strchr_internal.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+
+#include "defs.h"
+#include "misc.h"
+#include "utils/utils.h"
+
+char *host_strrchr(const char *s, int c)
+{
+ char set[2];
+ set[0] = c;
+ set[1] = '\0';
+ return (char *) host_strchr_internal(s, set, false);
+}
diff --git a/utils/key_components.c b/utils/key_components.c
new file mode 100644
index 00000000..b072ff51
--- /dev/null
+++ b/utils/key_components.c
@@ -0,0 +1,93 @@
+#include "ssh.h"
+#include "mpint.h"
+
+key_components *key_components_new(void)
+{
+ key_components *kc = snew(key_components);
+ kc->ncomponents = 0;
+ kc->componentsize = 0;
+ kc->components = NULL;
+ return kc;
+}
+
+static void key_components_add_str(key_components *kc, const char *name,
+ KeyComponentType type, ptrlen data)
+{
+ sgrowarray(kc->components, kc->componentsize, kc->ncomponents);
+ size_t n = kc->ncomponents++;
+ kc->components[n].name = dupstr(name);
+ kc->components[n].type = type;
+ kc->components[n].str = strbuf_dup_nm(data);
+}
+
+void key_components_add_text(key_components *kc,
+ const char *name, const char *value)
+{
+ key_components_add_str(kc, name, KCT_TEXT, ptrlen_from_asciz(value));
+}
+
+void key_components_add_text_pl(key_components *kc,
+ const char *name, ptrlen value)
+{
+ key_components_add_str(kc, name, KCT_TEXT, value);
+}
+
+void key_components_add_binary(key_components *kc,
+ const char *name, ptrlen value)
+{
+ key_components_add_str(kc, name, KCT_BINARY, value);
+}
+
+void key_components_add_mp(key_components *kc,
+ const char *name, mp_int *value)
+{
+ sgrowarray(kc->components, kc->componentsize, kc->ncomponents);
+ size_t n = kc->ncomponents++;
+ kc->components[n].name = dupstr(name);
+ kc->components[n].type = KCT_MPINT;
+ kc->components[n].mp = mp_copy(value);
+}
+
+void key_components_add_uint(key_components *kc,
+ const char *name, uintmax_t value)
+{
+ mp_int *mpvalue = mp_from_integer(value);
+ key_components_add_mp(kc, name, mpvalue);
+ mp_free(mpvalue);
+}
+
+void key_components_add_copy(key_components *kc,
+ const char *name, const key_component *value)
+{
+ switch (value->type) {
+ case KCT_TEXT:
+ case KCT_BINARY:
+ key_components_add_str(kc, name, value->type,
+ ptrlen_from_strbuf(value->str));
+ break;
+ case KCT_MPINT:
+ key_components_add_mp(kc, name, value->mp);
+ break;
+ }
+}
+
+void key_components_free(key_components *kc)
+{
+ for (size_t i = 0; i < kc->ncomponents; i++) {
+ key_component *comp = &kc->components[i];
+ sfree(comp->name);
+ switch (comp->type) {
+ case KCT_MPINT:
+ mp_free(comp->mp);
+ break;
+ case KCT_TEXT:
+ case KCT_BINARY:
+ strbuf_free(comp->str);
+ break;
+ default:
+ unreachable("bad key component type");
+ }
+ }
+ sfree(kc->components);
+ sfree(kc);
+}
diff --git a/utils/log_proxy_stderr.c b/utils/log_proxy_stderr.c
new file mode 100644
index 00000000..2a84173a
--- /dev/null
+++ b/utils/log_proxy_stderr.c
@@ -0,0 +1,103 @@
+#include <assert.h>
+#include <string.h>
+
+#include "putty.h"
+#include "network.h"
+
+void psb_init(ProxyStderrBuf *psb)
+{
+ psb->size = 0;
+ psb->prefix = "proxy";
+}
+
+void psb_set_prefix(ProxyStderrBuf *psb, const char *prefix)
+{
+ psb->prefix = prefix;
+}
+
+void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb,
+ const void *vdata, size_t len)
+{
+ const char *data = (const char *)vdata;
+
+ /*
+ * This helper function allows us to collect the data written to a
+ * local proxy command's standard error in whatever size chunks we
+ * happen to get from its pipe, and whenever we have a complete
+ * line, we pass it to plug_log.
+ *
+ * (We also do this when the buffer in psb fills up, to avoid just
+ * allocating more and more memory forever, and also to keep Event
+ * Log lines reasonably bounded in size.)
+ *
+ * Prerequisites: a plug to log to, and a ProxyStderrBuf stored
+ * somewhere to collect any not-yet-output partial line.
+ */
+
+ while (len > 0) {
+ /*
+ * Copy as much data into psb->buf as will fit.
+ */
+ assert(psb->size < lenof(psb->buf));
+ size_t to_consume = lenof(psb->buf) - psb->size;
+ if (to_consume > len)
+ to_consume = len;
+ memcpy(psb->buf + psb->size, data, to_consume);
+ data += to_consume;
+ len -= to_consume;
+ psb->size += to_consume;
+
+ /*
+ * Output any full lines in psb->buf.
+ */
+ size_t pos = 0;
+ while (pos < psb->size) {
+ char *nlpos = memchr(psb->buf + pos, '\n', psb->size - pos);
+ if (!nlpos)
+ break;
+
+ /*
+ * Found a newline in the buffer, so we can output a line.
+ */
+ size_t endpos = nlpos - psb->buf;
+ while (endpos > pos && (psb->buf[endpos-1] == '\n' ||
+ psb->buf[endpos-1] == '\r'))
+ endpos--;
+ char *msg = dupprintf(
+ "%s: %.*s", psb->prefix, (int)(endpos - pos), psb->buf + pos);
+ plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0);
+ sfree(msg);
+
+ pos = nlpos - psb->buf + 1;
+ assert(pos <= psb->size);
+ }
+
+ /*
+ * If the buffer is completely full and we didn't output
+ * anything, then output the whole thing, flagging it as a
+ * truncated line.
+ */
+ if (pos == 0 && psb->size == lenof(psb->buf)) {
+ char *msg = dupprintf(
+ "%s (partial line): %.*s", psb->prefix, (int)psb->size,
+ psb->buf);
+ plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0);
+ sfree(msg);
+
+ pos = psb->size = 0;
+ }
+
+ /*
+ * Now move any remaining data up to the front of the buffer.
+ */
+ size_t newsize = psb->size - pos;
+ if (newsize)
+ memmove(psb->buf, psb->buf + pos, newsize);
+ psb->size = newsize;
+
+ /*
+ * And loop round again if there's more data to be read from
+ * our input.
+ */
+ }
+}
diff --git a/TIME.C b/utils/ltime.c
index 75b89535..75b89535 100644
--- a/TIME.C
+++ b/utils/ltime.c
diff --git a/utils/make_spr_sw_abort_static.c b/utils/make_spr_sw_abort_static.c
new file mode 100644
index 00000000..f9eac59f
--- /dev/null
+++ b/utils/make_spr_sw_abort_static.c
@@ -0,0 +1,21 @@
+/*
+ * Constructor function for a SeatPromptResult of the 'software abort'
+ * category, whose error message is in the simplest possible form of a
+ * static string constant.
+ */
+
+#include "putty.h"
+
+static void spr_static_errfn(SeatPromptResult spr, BinarySink *bs)
+{
+ put_dataz(bs, spr.errdata_lit);
+}
+
+SeatPromptResult make_spr_sw_abort_static(const char *str)
+{
+ SeatPromptResult spr;
+ spr.kind = SPRK_SW_ABORT;
+ spr.errfn = spr_static_errfn;
+ spr.errdata_lit = str;
+ return spr;
+}
diff --git a/utils/marshal.c b/utils/marshal.c
new file mode 100644
index 00000000..534ecf50
--- /dev/null
+++ b/utils/marshal.c
@@ -0,0 +1,338 @@
+#include <assert.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "marshal.h"
+#include "misc.h"
+
+void BinarySink_put_data(BinarySink *bs, const void *data, size_t len)
+{
+ bs->write(bs, data, len);
+}
+
+void BinarySink_put_datapl(BinarySink *bs, ptrlen pl)
+{
+ BinarySink_put_data(bs, pl.ptr, pl.len);
+}
+
+void BinarySink_put_padding(BinarySink *bs, size_t len, unsigned char padbyte)
+{
+ char buf[16];
+ memset(buf, padbyte, sizeof(buf));
+ while (len > 0) {
+ size_t thislen = len < sizeof(buf) ? len : sizeof(buf);
+ bs->write(bs, buf, thislen);
+ len -= thislen;
+ }
+}
+
+void BinarySink_put_byte(BinarySink *bs, unsigned char val)
+{
+ bs->write(bs, &val, 1);
+}
+
+void BinarySink_put_bool(BinarySink *bs, bool val)
+{
+ unsigned char cval = val ? 1 : 0;
+ bs->write(bs, &cval, 1);
+}
+
+void BinarySink_put_uint16(BinarySink *bs, unsigned long val)
+{
+ unsigned char data[2];
+ PUT_16BIT_MSB_FIRST(data, val);
+ bs->write(bs, data, sizeof(data));
+}
+
+void BinarySink_put_uint32(BinarySink *bs, unsigned long val)
+{
+ unsigned char data[4];
+ PUT_32BIT_MSB_FIRST(data, val);
+ bs->write(bs, data, sizeof(data));
+}
+
+void BinarySink_put_uint64(BinarySink *bs, uint64_t val)
+{
+ unsigned char data[8];
+ PUT_64BIT_MSB_FIRST(data, val);
+ bs->write(bs, data, sizeof(data));
+}
+
+void BinarySink_put_string(BinarySink *bs, const void *data, size_t len)
+{
+ /* Check that the string length fits in a uint32, without doing a
+ * potentially implementation-defined shift of more than 31 bits */
+ assert((len >> 31) < 2);
+
+ BinarySink_put_uint32(bs, len);
+ bs->write(bs, data, len);
+}
+
+void BinarySink_put_stringpl(BinarySink *bs, ptrlen pl)
+{
+ BinarySink_put_string(bs, pl.ptr, pl.len);
+}
+
+void BinarySink_put_stringz(BinarySink *bs, const char *str)
+{
+ BinarySink_put_string(bs, str, strlen(str));
+}
+
+void BinarySink_put_stringsb(BinarySink *bs, strbuf *buf)
+{
+ BinarySink_put_string(bs, buf->s, buf->len);
+ strbuf_free(buf);
+}
+
+void BinarySink_put_asciz(BinarySink *bs, const char *str)
+{
+ bs->write(bs, str, strlen(str) + 1);
+}
+
+bool BinarySink_put_pstring(BinarySink *bs, const char *str)
+{
+ size_t len = strlen(str);
+ if (len > 255)
+ return false; /* can't write a Pascal-style string this long */
+ BinarySink_put_byte(bs, len);
+ bs->write(bs, str, len);
+ return true;
+}
+
+void BinarySink_put_fmtv(BinarySink *bs, const char *fmt, va_list ap)
+{
+ if (bs->writefmtv) {
+ bs->writefmtv(bs, fmt, ap);
+ } else {
+ char *str = dupvprintf(fmt, ap);
+ bs->write(bs, str, strlen(str));
+ burnstr(str);
+ }
+}
+
+void BinarySink_put_fmt(BinarySink *bs, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ BinarySink_put_fmtv(bs, fmt, ap);
+ va_end(ap);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static bool BinarySource_data_avail(BinarySource *src, size_t wanted)
+{
+ if (src->err)
+ return false;
+
+ if (wanted <= src->len - src->pos)
+ return true;
+
+ src->err = BSE_OUT_OF_DATA;
+ return false;
+}
+
+#define avail(wanted) BinarySource_data_avail(src, wanted)
+#define advance(dist) (src->pos += dist)
+#define here ((const void *)((const unsigned char *)src->data + src->pos))
+#define consume(dist) \
+ ((const void *)((const unsigned char *)src->data + \
+ ((src->pos += dist) - dist)))
+
+ptrlen BinarySource_get_data(BinarySource *src, size_t wanted)
+{
+ if (!avail(wanted))
+ return make_ptrlen("", 0);
+
+ return make_ptrlen(consume(wanted), wanted);
+}
+
+unsigned char BinarySource_get_byte(BinarySource *src)
+{
+ const unsigned char *ucp;
+
+ if (!avail(1))
+ return 0;
+
+ ucp = consume(1);
+ return *ucp;
+}
+
+bool BinarySource_get_bool(BinarySource *src)
+{
+ const unsigned char *ucp;
+
+ if (!avail(1))
+ return false;
+
+ ucp = consume(1);
+ return *ucp != 0;
+}
+
+unsigned BinarySource_get_uint16(BinarySource *src)
+{
+ const unsigned char *ucp;
+
+ if (!avail(2))
+ return 0;
+
+ ucp = consume(2);
+ return GET_16BIT_MSB_FIRST(ucp);
+}
+
+unsigned long BinarySource_get_uint32(BinarySource *src)
+{
+ const unsigned char *ucp;
+
+ if (!avail(4))
+ return 0;
+
+ ucp = consume(4);
+ return GET_32BIT_MSB_FIRST(ucp);
+}
+
+uint64_t BinarySource_get_uint64(BinarySource *src)
+{
+ const unsigned char *ucp;
+
+ if (!avail(8))
+ return 0;
+
+ ucp = consume(8);
+ return GET_64BIT_MSB_FIRST(ucp);
+}
+
+ptrlen BinarySource_get_string(BinarySource *src)
+{
+ const unsigned char *ucp;
+ size_t len;
+
+ if (!avail(4))
+ return make_ptrlen("", 0);
+
+ ucp = consume(4);
+ len = GET_32BIT_MSB_FIRST(ucp);
+
+ if (!avail(len))
+ return make_ptrlen("", 0);
+
+ return make_ptrlen(consume(len), len);
+}
+
+const char *BinarySource_get_asciz(BinarySource *src)
+{
+ const char *start, *end;
+
+ if (src->err)
+ return "";
+
+ start = here;
+ end = memchr(start, '\0', src->len - src->pos);
+ if (!end) {
+ src->err = BSE_OUT_OF_DATA;
+ return "";
+ }
+
+ advance(end + 1 - start);
+ return start;
+}
+
+static ptrlen BinarySource_get_chars_internal(
+ BinarySource *src, const char *set, bool include)
+{
+ const char *start = here;
+ while (avail(1)) {
+ bool present = NULL != strchr(set, *(const char *)consume(0));
+ if (present != include)
+ break;
+ (void) consume(1);
+ }
+ const char *end = here;
+ return make_ptrlen(start, end - start);
+}
+
+ptrlen BinarySource_get_chars(BinarySource *src, const char *include_set)
+{
+ return BinarySource_get_chars_internal(src, include_set, true);
+}
+
+ptrlen BinarySource_get_nonchars(BinarySource *src, const char *exclude_set)
+{
+ return BinarySource_get_chars_internal(src, exclude_set, false);
+}
+
+ptrlen BinarySource_get_chomped_line(BinarySource *src)
+{
+ const char *start, *end;
+
+ if (src->err)
+ return make_ptrlen(here, 0);
+
+ start = here;
+ end = memchr(start, '\n', src->len - src->pos);
+ if (end)
+ advance(end + 1 - start);
+ else
+ advance(src->len - src->pos);
+ end = here;
+
+ if (end > start && end[-1] == '\n')
+ end--;
+ if (end > start && end[-1] == '\r')
+ end--;
+
+ return make_ptrlen(start, end - start);
+}
+
+ptrlen BinarySource_get_pstring(BinarySource *src)
+{
+ const unsigned char *ucp;
+ size_t len;
+
+ if (!avail(1))
+ return make_ptrlen("", 0);
+
+ ucp = consume(1);
+ len = *ucp;
+
+ if (!avail(len))
+ return make_ptrlen("", 0);
+
+ return make_ptrlen(consume(len), len);
+}
+
+void BinarySource_REWIND_TO__(BinarySource *src, size_t pos)
+{
+ if (pos <= src->len) {
+ src->pos = pos;
+ src->err = BSE_NO_ERROR; /* clear any existing error */
+ } else {
+ src->pos = src->len;
+ src->err = BSE_OUT_OF_DATA; /* new error if we rewind out of range */
+ }
+}
+
+static void stdio_sink_write(BinarySink *bs, const void *data, size_t len)
+{
+ stdio_sink *sink = BinarySink_DOWNCAST(bs, stdio_sink);
+ fwrite(data, 1, len, sink->fp);
+}
+
+void stdio_sink_init(stdio_sink *sink, FILE *fp)
+{
+ sink->fp = fp;
+ BinarySink_INIT(sink, stdio_sink_write);
+}
+
+static void bufchain_sink_write(BinarySink *bs, const void *data, size_t len)
+{
+ bufchain_sink *sink = BinarySink_DOWNCAST(bs, bufchain_sink);
+ bufchain_add(sink->ch, data, len);
+}
+
+void bufchain_sink_init(bufchain_sink *sink, bufchain *ch)
+{
+ sink->ch = ch;
+ BinarySink_INIT(sink, bufchain_sink_write);
+}
diff --git a/memory.c b/utils/memory.c
index 97ae9401..97ae9401 100644
--- a/memory.c
+++ b/utils/memory.c
diff --git a/utils/memxor.c b/utils/memxor.c
new file mode 100644
index 00000000..4fd41f41
--- /dev/null
+++ b/utils/memxor.c
@@ -0,0 +1,34 @@
+/*
+ * XOR two pieces of memory, copying the result into a third, which
+ * may precisely alias one of the input pair (but no guarantees if it
+ * partially overlaps).
+ */
+
+#include "defs.h"
+#include "misc.h"
+
+void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size)
+{
+ switch (size & 15) {
+ case 0:
+ while (size >= 16) {
+ size -= 16;
+ *out++ = *in1++ ^ *in2++;
+ case 15: *out++ = *in1++ ^ *in2++;
+ case 14: *out++ = *in1++ ^ *in2++;
+ case 13: *out++ = *in1++ ^ *in2++;
+ case 12: *out++ = *in1++ ^ *in2++;
+ case 11: *out++ = *in1++ ^ *in2++;
+ case 10: *out++ = *in1++ ^ *in2++;
+ case 9: *out++ = *in1++ ^ *in2++;
+ case 8: *out++ = *in1++ ^ *in2++;
+ case 7: *out++ = *in1++ ^ *in2++;
+ case 6: *out++ = *in1++ ^ *in2++;
+ case 5: *out++ = *in1++ ^ *in2++;
+ case 4: *out++ = *in1++ ^ *in2++;
+ case 3: *out++ = *in1++ ^ *in2++;
+ case 2: *out++ = *in1++ ^ *in2++;
+ case 1: *out++ = *in1++ ^ *in2++;
+ }
+ }
+}
diff --git a/utils/nullstrcmp.c b/utils/nullstrcmp.c
new file mode 100644
index 00000000..a9e50f40
--- /dev/null
+++ b/utils/nullstrcmp.c
@@ -0,0 +1,21 @@
+/*
+ * Compare two strings, just like strcmp, except that we tolerate null
+ * pointers as a legal input, and define them to compare before any
+ * non-null string (even the empty string).
+ */
+
+#include <string.h>
+
+#include "defs.h"
+#include "misc.h"
+
+int nullstrcmp(const char *a, const char *b)
+{
+ if (a == NULL && b == NULL)
+ return 0;
+ if (a == NULL)
+ return -1;
+ if (b == NULL)
+ return +1;
+ return strcmp(a, b);
+}
diff --git a/utils/out_of_memory.c b/utils/out_of_memory.c
new file mode 100644
index 00000000..1c9cb259
--- /dev/null
+++ b/utils/out_of_memory.c
@@ -0,0 +1,11 @@
+/*
+ * Standard implementation of the out_of_memory function called by our
+ * malloc wrappers.
+ */
+
+#include "putty.h"
+
+void out_of_memory(void)
+{
+ modalfatalbox("Out of memory");
+}
diff --git a/utils/parse_blocksize.c b/utils/parse_blocksize.c
new file mode 100644
index 00000000..3d559276
--- /dev/null
+++ b/utils/parse_blocksize.c
@@ -0,0 +1,40 @@
+/*
+ * Parse a string block size specification. This is approximately a
+ * subset of the block size specs supported by GNU fileutils:
+ * "nk" = n kilobytes
+ * "nM" = n megabytes
+ * "nG" = n gigabytes
+ * All numbers are decimal, and suffixes refer to powers of two.
+ * Case-insensitive.
+ */
+
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "defs.h"
+#include "misc.h"
+
+unsigned long parse_blocksize(const char *bs)
+{
+ char *suf;
+ unsigned long r = strtoul(bs, &suf, 10);
+ if (*suf != '\0') {
+ while (*suf && isspace((unsigned char)*suf)) suf++;
+ switch (*suf) {
+ case 'k': case 'K':
+ r *= 1024ul;
+ break;
+ case 'm': case 'M':
+ r *= 1024ul * 1024ul;
+ break;
+ case 'g': case 'G':
+ r *= 1024ul * 1024ul * 1024ul;
+ break;
+ case '\0':
+ default:
+ break;
+ }
+ }
+ return r;
+}
diff --git a/utils/percent_decode.c b/utils/percent_decode.c
new file mode 100644
index 00000000..dff2c233
--- /dev/null
+++ b/utils/percent_decode.c
@@ -0,0 +1,41 @@
+/*
+ * Decode %-encoding in URL style.
+ */
+
+#include <ctype.h>
+
+#include "misc.h"
+
+void percent_decode_bs(BinarySink *bs, ptrlen data)
+{
+ for (const char *p = data.ptr, *e = ptrlen_end(data); p < e; p++) {
+ char c = *p;
+ if (c == '%' && e-p >= 3 &&
+ isxdigit((unsigned char)p[1]) &&
+ isxdigit((unsigned char)p[2])) {
+ char hex[3];
+ hex[0] = p[1];
+ hex[1] = p[2];
+ hex[2] = '\0';
+ put_byte(bs, strtoul(hex, NULL, 16));
+ p += 2;
+ } else {
+ put_byte(bs, c);
+ }
+ }
+
+}
+
+void percent_decode_fp(FILE *fp, ptrlen data)
+{
+ stdio_sink ss;
+ stdio_sink_init(&ss, fp);
+ percent_decode_bs(BinarySink_UPCAST(&ss), data);
+}
+
+strbuf *percent_decode_sb(ptrlen data)
+{
+ strbuf *sb = strbuf_new();
+ percent_decode_bs(BinarySink_UPCAST(sb), data);
+ return sb;
+}
diff --git a/utils/percent_encode.c b/utils/percent_encode.c
new file mode 100644
index 00000000..ea04b0ac
--- /dev/null
+++ b/utils/percent_encode.c
@@ -0,0 +1,34 @@
+/*
+ * %-encoding in URL style.
+ *
+ * Defaults to escaping % itself (necessary for decoding to even
+ * work), and any C0 escape character. Further bad characters can be
+ * provided in 'badchars'.
+ */
+
+#include "misc.h"
+
+void percent_encode_bs(BinarySink *bs, ptrlen data, const char *badchars)
+{
+ for (const char *p = data.ptr, *e = ptrlen_end(data); p < e; p++) {
+ char c = *p;
+ if (c == '%' || c < ' ' || (badchars && strchr(badchars, c)))
+ put_fmt(bs, "%%%02X", (unsigned char)c);
+ else
+ put_byte(bs, c);
+ }
+}
+
+void percent_encode_fp(FILE *fp, ptrlen data, const char *badchars)
+{
+ stdio_sink ss;
+ stdio_sink_init(&ss, fp);
+ percent_encode_bs(BinarySink_UPCAST(&ss), data, badchars);
+}
+
+strbuf *percent_encode_sb(ptrlen data, const char *badchars)
+{
+ strbuf *sb = strbuf_new();
+ percent_encode_bs(BinarySink_UPCAST(sb), data, badchars);
+ return sb;
+}
diff --git a/utils/prompts.c b/utils/prompts.c
new file mode 100644
index 00000000..d0823334
--- /dev/null
+++ b/utils/prompts.c
@@ -0,0 +1,69 @@
+/*
+ * Functions for making, destroying, and manipulating prompts_t
+ * structures.
+ */
+
+#include "putty.h"
+
+prompts_t *new_prompts(void)
+{
+ prompts_t *p = snew(prompts_t);
+ p->prompts = NULL;
+ p->n_prompts = p->prompts_size = 0;
+ p->data = NULL;
+ p->spr = SPR_INCOMPLETE;
+ p->to_server = true; /* to be on the safe side */
+ p->name = p->instruction = NULL;
+ p->name_reqd = p->instr_reqd = false;
+ p->callback = NULL;
+ p->callback_ctx = NULL;
+ p->ldisc_ptr_to_us = NULL;
+ return p;
+}
+
+void add_prompt(prompts_t *p, char *promptstr, bool echo)
+{
+ prompt_t *pr = snew(prompt_t);
+ pr->prompt = promptstr;
+ pr->echo = echo;
+ pr->result = strbuf_new_nm();
+ sgrowarray(p->prompts, p->prompts_size, p->n_prompts);
+ p->prompts[p->n_prompts++] = pr;
+}
+
+void prompt_set_result(prompt_t *pr, const char *newstr)
+{
+ strbuf_clear(pr->result);
+ put_dataz(pr->result, newstr);
+}
+
+const char *prompt_get_result_ref(prompt_t *pr)
+{
+ return pr->result->s;
+}
+
+char *prompt_get_result(prompt_t *pr)
+{
+ return dupstr(pr->result->s);
+}
+
+void free_prompts(prompts_t *p)
+{
+ size_t i;
+
+ /* If an Ldisc currently knows about us, tell it to forget us, so
+ * it won't dereference a stale pointer later. */
+ if (p->ldisc_ptr_to_us)
+ *p->ldisc_ptr_to_us = NULL;
+
+ for (i=0; i < p->n_prompts; i++) {
+ prompt_t *pr = p->prompts[i];
+ strbuf_free(pr->result);
+ sfree(pr->prompt);
+ sfree(pr);
+ }
+ sfree(p->prompts);
+ sfree(p->name);
+ sfree(p->instruction);
+ sfree(p);
+}
diff --git a/utils/ptrlen.c b/utils/ptrlen.c
new file mode 100644
index 00000000..37972e49
--- /dev/null
+++ b/utils/ptrlen.c
@@ -0,0 +1,110 @@
+/*
+ * Functions to deal with ptrlens.
+ */
+
+#include "defs.h"
+#include "misc.h"
+
+bool ptrlen_eq_string(ptrlen pl, const char *str)
+{
+ size_t len = strlen(str);
+ return (pl.len == len && !memcmp(pl.ptr, str, len));
+}
+
+bool ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2)
+{
+ return (pl1.len == pl2.len && !memcmp(pl1.ptr, pl2.ptr, pl1.len));
+}
+
+int ptrlen_strcmp(ptrlen pl1, ptrlen pl2)
+{
+ size_t minlen = pl1.len < pl2.len ? pl1.len : pl2.len;
+ if (minlen) { /* tolerate plX.ptr==NULL as long as plX.len==0 */
+ int cmp = memcmp(pl1.ptr, pl2.ptr, minlen);
+ if (cmp)
+ return cmp;
+ }
+ return pl1.len < pl2.len ? -1 : pl1.len > pl2.len ? +1 : 0;
+}
+
+bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail)
+{
+ if (whole.len >= prefix.len &&
+ !memcmp(whole.ptr, prefix.ptr, prefix.len)) {
+ if (tail) {
+ tail->ptr = (const char *)whole.ptr + prefix.len;
+ tail->len = whole.len - prefix.len;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail)
+{
+ if (whole.len >= suffix.len &&
+ !memcmp((char *)whole.ptr + (whole.len - suffix.len),
+ suffix.ptr, suffix.len)) {
+ if (tail) {
+ tail->ptr = whole.ptr;
+ tail->len = whole.len - suffix.len;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool ptrlen_contains(ptrlen input, const char *characters)
+{
+ for (const char *p = input.ptr, *end = p + input.len; p < end; p++)
+ if (strchr(characters, *p))
+ return true;
+ return false;
+}
+
+bool ptrlen_contains_only(ptrlen input, const char *characters)
+{
+ for (const char *p = input.ptr, *end = p + input.len; p < end; p++)
+ if (!strchr(characters, *p))
+ return false;
+ return true;
+}
+
+ptrlen ptrlen_get_word(ptrlen *input, const char *separators)
+{
+ const char *p = input->ptr, *end = p + input->len;
+ ptrlen toret;
+
+ while (p < end && strchr(separators, *p))
+ p++;
+ toret.ptr = p;
+ while (p < end && !strchr(separators, *p))
+ p++;
+ toret.len = p - (const char *)toret.ptr;
+
+ size_t to_consume = p - (const char *)input->ptr;
+ assert(to_consume <= input->len);
+ input->ptr = (const char *)input->ptr + to_consume;
+ input->len -= to_consume;
+
+ return toret;
+}
+
+char *mkstr(ptrlen pl)
+{
+ char *p = snewn(pl.len + 1, char);
+ memcpy(p, pl.ptr, pl.len);
+ p[pl.len] = '\0';
+ return p;
+}
+
+bool strstartswith(const char *s, const char *t)
+{
+ return !strncmp(s, t, strlen(t));
+}
+
+bool strendswith(const char *s, const char *t)
+{
+ size_t slen = strlen(s), tlen = strlen(t);
+ return slen >= tlen && !strcmp(s + (slen - tlen), t);
+}
diff --git a/utils/read_file_into.c b/utils/read_file_into.c
new file mode 100644
index 00000000..59569484
--- /dev/null
+++ b/utils/read_file_into.c
@@ -0,0 +1,19 @@
+/*
+ * Read an entire file into a BinarySink.
+ */
+
+#include <stdio.h>
+
+#include "defs.h"
+#include "misc.h"
+
+bool read_file_into(BinarySink *bs, FILE *fp)
+{
+ char buf[4096];
+ while (1) {
+ size_t retd = fread(buf, 1, sizeof(buf), fp);
+ if (retd == 0)
+ return !ferror(fp);
+ put_data(bs, buf, retd);
+ }
+}
diff --git a/utils/seat_connection_fatal.c b/utils/seat_connection_fatal.c
new file mode 100644
index 00000000..9b0186b1
--- /dev/null
+++ b/utils/seat_connection_fatal.c
@@ -0,0 +1,20 @@
+/*
+ * Wrapper function for the connection_fatal() method of a Seat,
+ * providing printf-style formatting.
+ */
+
+#include "putty.h"
+
+void seat_connection_fatal(Seat *seat, const char *fmt, ...)
+{
+ va_list ap;
+ char *msg;
+
+ va_start(ap, fmt);
+ msg = dupvprintf(fmt, ap);
+ va_end(ap);
+
+ seat->vt->connection_fatal(seat, msg);
+ sfree(msg); /* if we return */
+}
+
diff --git a/utils/seat_dialog_text.c b/utils/seat_dialog_text.c
new file mode 100644
index 00000000..03890b93
--- /dev/null
+++ b/utils/seat_dialog_text.c
@@ -0,0 +1,41 @@
+/*
+ * Helper routines for dealing with SeatDialogText structures.
+ */
+
+#include <stdarg.h>
+
+#include "putty.h"
+
+SeatDialogText *seat_dialog_text_new(void)
+{
+ SeatDialogText *sdt = snew(SeatDialogText);
+ sdt->nitems = sdt->itemsize = 0;
+ sdt->items = NULL;
+ return sdt;
+}
+
+void seat_dialog_text_free(SeatDialogText *sdt)
+{
+ for (size_t i = 0; i < sdt->nitems; i++)
+ sfree(sdt->items[i].text);
+ sfree(sdt->items);
+ sfree(sdt);
+}
+
+static void seat_dialog_text_append_v(
+ SeatDialogText *sdt, SeatDialogTextType type, const char *fmt, va_list ap)
+{
+ sgrowarray(sdt->items, sdt->itemsize, sdt->nitems);
+ SeatDialogTextItem *item = &sdt->items[sdt->nitems++];
+ item->type = type;
+ item->text = dupvprintf(fmt, ap);
+}
+
+void seat_dialog_text_append(SeatDialogText *sdt, SeatDialogTextType type,
+ const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ seat_dialog_text_append_v(sdt, type, fmt, ap);
+ va_end(ap);
+}
diff --git a/sessprep.c b/utils/sessprep.c
index 95e7b658..95e7b658 100644
--- a/sessprep.c
+++ b/utils/sessprep.c
diff --git a/utils/sk_free_peer_info.c b/utils/sk_free_peer_info.c
new file mode 100644
index 00000000..a66e09d7
--- /dev/null
+++ b/utils/sk_free_peer_info.c
@@ -0,0 +1,14 @@
+/*
+ * Free a SocketPeerInfo, and everything that dangles off it.
+ */
+
+#include "putty.h"
+
+void sk_free_peer_info(SocketPeerInfo *pi)
+{
+ if (pi) {
+ sfree((char *)pi->addr_text);
+ sfree((char *)pi->log_text);
+ sfree(pi);
+ }
+}
diff --git a/utils/smemclr.c b/utils/smemclr.c
new file mode 100644
index 00000000..5503bdc4
--- /dev/null
+++ b/utils/smemclr.c
@@ -0,0 +1,52 @@
+/*
+ * Securely wipe memory.
+ *
+ * The actual wiping is no different from what memset would do: the
+ * point of 'securely' is to try to be sure over-clever compilers
+ * won't optimise away memsets on variables that are about to be freed
+ * or go out of scope. See
+ * https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/771-BSI.html
+ */
+
+#include "defs.h"
+#include "misc.h"
+
+/*
+ * Trivial function that is given a pointer to some memory and ignores
+ * it.
+ */
+static void no_op(void *ptr, size_t size) {}
+
+/*
+ * Function pointer that is given a pointer to some memory, and from
+ * the compiler's point of view, _might_ read it, or otherwise depend
+ * on its contents.
+ *
+ * In fact, this function pointer always points to no_op() above. But
+ * because the pointer itself is volatile-qualified, the compiler
+ * isn't allowed to optimise based on the assumption that that will
+ * always be the case. So it has to call through the function pointer
+ * anyway, on the basis that it _might_ have magically changed at run
+ * time into a pointer to some completely arbitrary function. And
+ * therefore it must also avoid optimising away any observable effect
+ * beforehand that a completely arbitrary function might depend on -
+ * such as the zeroing of our memory region.
+ */
+static void (*const volatile maybe_read)(void *ptr, size_t size) = no_op;
+
+void smemclr(void *b, size_t n)
+{
+ if (b && n > 0) {
+ /*
+ * Zero out the memory.
+ */
+ memset(b, 0, n);
+
+ /*
+ * Call the above function pointer, which (for all the
+ * compiler knows) might check that we've really zeroed the
+ * memory.
+ */
+ maybe_read(b, n);
+ }
+}
diff --git a/utils/smemeq.c b/utils/smemeq.c
new file mode 100644
index 00000000..ce3761ec
--- /dev/null
+++ b/utils/smemeq.c
@@ -0,0 +1,25 @@
+/*
+ * Compare two fixed-size regions of memory, in a crypto-safe way,
+ * i.e. without timing or cache side channels indicating anything
+ * about what the answer was or where the first difference (if any)
+ * might have been.
+ */
+
+#include "defs.h"
+#include "misc.h"
+
+unsigned smemeq(const void *av, const void *bv, size_t len)
+{
+ const unsigned char *a = (const unsigned char *)av;
+ const unsigned char *b = (const unsigned char *)bv;
+ unsigned val = 0;
+
+ while (len-- > 0) {
+ val |= *a++ ^ *b++;
+ }
+ /* Now val is 0 iff we want to return 1, and in the range
+ * 0x01..0xFF iff we want to return 0. So subtracting from 0x100
+ * will clear bit 8 iff we want to return 0, and leave it set iff
+ * we want to return 1, so then we can just shift down. */
+ return (0x100 - val) >> 8;
+}
diff --git a/utils/spr_get_error_message.c b/utils/spr_get_error_message.c
new file mode 100644
index 00000000..b9f81a47
--- /dev/null
+++ b/utils/spr_get_error_message.c
@@ -0,0 +1,13 @@
+/*
+ * Construct the error message from a SeatPromptResult, and return it
+ * in a dynamically allocated string.
+ */
+
+#include "putty.h"
+
+char *spr_get_error_message(SeatPromptResult spr)
+{
+ strbuf *sb = strbuf_new();
+ spr.errfn(spr, BinarySink_UPCAST(sb));
+ return strbuf_to_str(sb);
+}
diff --git a/utils/ssh2_pick_fingerprint.c b/utils/ssh2_pick_fingerprint.c
new file mode 100644
index 00000000..f81b2f1d
--- /dev/null
+++ b/utils/ssh2_pick_fingerprint.c
@@ -0,0 +1,27 @@
+/*
+ * Choose an SSH-2 fingerprint type, out of an array of possible ones.
+ */
+
+#include "defs.h"
+#include "misc.h"
+#include "ssh.h"
+
+FingerprintType ssh2_pick_fingerprint(
+ char **fingerprints, FingerprintType preferred_type)
+{
+ /*
+ * Keys are either SSH-2, in which case we have all fingerprint
+ * types, or SSH-1, in which case we have only MD5. So we return
+ * the default type if we can, or MD5 if that's all we have; no
+ * need for a fully general preference-list system.
+ */
+ FingerprintType fptype = fingerprints[preferred_type] ?
+ preferred_type : SSH_FPTYPE_MD5;
+ assert(fingerprints[fptype]);
+ return fptype;
+}
+
+FingerprintType ssh2_pick_default_fingerprint(char **fingerprints)
+{
+ return ssh2_pick_fingerprint(fingerprints, SSH_FPTYPE_DEFAULT);
+}
diff --git a/utils/ssh_key_clone.c b/utils/ssh_key_clone.c
new file mode 100644
index 00000000..5824bdbf
--- /dev/null
+++ b/utils/ssh_key_clone.c
@@ -0,0 +1,32 @@
+/*
+ * Make a copy of an existing ssh_key object, e.g. to survive after
+ * the original is freed.
+ */
+
+#include "misc.h"
+#include "ssh.h"
+
+ssh_key *ssh_key_clone(ssh_key *key)
+{
+ /*
+ * To avoid having to add a special method in the vtable API, we
+ * clone by round-tripping through public and private blobs.
+ */
+ strbuf *pub = strbuf_new_nm();
+ ssh_key_public_blob(key, BinarySink_UPCAST(pub));
+
+ ssh_key *copy;
+
+ if (ssh_key_has_private(key)) {
+ strbuf *priv = strbuf_new_nm();
+ ssh_key_private_blob(key, BinarySink_UPCAST(priv));
+ copy = ssh_key_new_priv(ssh_key_alg(key), ptrlen_from_strbuf(pub),
+ ptrlen_from_strbuf(priv));
+ strbuf_free(priv);
+ } else {
+ copy = ssh_key_new_pub(ssh_key_alg(key), ptrlen_from_strbuf(pub));
+ }
+
+ strbuf_free(pub);
+ return copy;
+}
diff --git a/utils/sshutils.c b/utils/sshutils.c
new file mode 100644
index 00000000..49e82219
--- /dev/null
+++ b/utils/sshutils.c
@@ -0,0 +1,128 @@
+/*
+ * Supporting routines used in common by all the various components of
+ * the SSH system.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "ssh/channel.h"
+
+/* ----------------------------------------------------------------------
+ * Centralised standard methods for other channel implementations to
+ * borrow.
+ */
+
+void chan_remotely_opened_confirmation(Channel *chan)
+{
+ unreachable("this channel type should never receive OPEN_CONFIRMATION");
+}
+
+void chan_remotely_opened_failure(Channel *chan, const char *errtext)
+{
+ unreachable("this channel type should never receive OPEN_FAILURE");
+}
+
+bool chan_default_want_close(
+ Channel *chan, bool sent_local_eof, bool rcvd_remote_eof)
+{
+ /*
+ * Default close policy: we start initiating the CHANNEL_CLOSE
+ * procedure as soon as both sides of the channel have seen EOF.
+ */
+ return sent_local_eof && rcvd_remote_eof;
+}
+
+bool chan_no_exit_status(Channel *chan, int status)
+{
+ return false;
+}
+
+bool chan_no_exit_signal(
+ Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg)
+{
+ return false;
+}
+
+bool chan_no_exit_signal_numeric(
+ Channel *chan, int signum, bool core_dumped, ptrlen msg)
+{
+ return false;
+}
+
+bool chan_no_run_shell(Channel *chan)
+{
+ return false;
+}
+
+bool chan_no_run_command(Channel *chan, ptrlen command)
+{
+ return false;
+}
+
+bool chan_no_run_subsystem(Channel *chan, ptrlen subsys)
+{
+ return false;
+}
+
+bool chan_no_enable_x11_forwarding(
+ Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata,
+ unsigned screen_number)
+{
+ return false;
+}
+
+bool chan_no_enable_agent_forwarding(Channel *chan)
+{
+ return false;
+}
+
+bool chan_no_allocate_pty(
+ Channel *chan, ptrlen termtype, unsigned width, unsigned height,
+ unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes)
+{
+ return false;
+}
+
+bool chan_no_set_env(Channel *chan, ptrlen var, ptrlen value)
+{
+ return false;
+}
+
+bool chan_no_send_break(Channel *chan, unsigned length)
+{
+ return false;
+}
+
+bool chan_no_send_signal(Channel *chan, ptrlen signame)
+{
+ return false;
+}
+
+bool chan_no_change_window_size(
+ Channel *chan, unsigned width, unsigned height,
+ unsigned pixwidth, unsigned pixheight)
+{
+ return false;
+}
+
+void chan_no_request_response(Channel *chan, bool success)
+{
+ unreachable("this channel type should never send a want-reply request");
+}
+
+/* ----------------------------------------------------------------------
+ * Other miscellaneous utility functions.
+ */
+
+void free_rportfwd(struct ssh_rportfwd *rpf)
+{
+ if (rpf) {
+ sfree(rpf->log_description);
+ sfree(rpf->shost);
+ sfree(rpf->dhost);
+ sfree(rpf);
+ }
+}
diff --git a/utils/strbuf.c b/utils/strbuf.c
new file mode 100644
index 00000000..c636de47
--- /dev/null
+++ b/utils/strbuf.c
@@ -0,0 +1,128 @@
+/*
+ * Functions to work with strbufs.
+ */
+
+#include "defs.h"
+#include "misc.h"
+#include "utils/utils.h"
+
+struct strbuf_impl {
+ size_t size;
+ struct strbuf visible;
+ bool nm; /* true if we insist on non-moving buffer resizes */
+};
+
+#define STRBUF_SET_UPTR(buf) \
+ ((buf)->visible.u = (unsigned char *)(buf)->visible.s)
+#define STRBUF_SET_PTR(buf, ptr) \
+ ((buf)->visible.s = (ptr), STRBUF_SET_UPTR(buf))
+
+void *strbuf_append(strbuf *buf_o, size_t len)
+{
+ struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible);
+ char *toret;
+ sgrowarray_general(
+ buf->visible.s, buf->size, buf->visible.len + 1, len, buf->nm);
+ STRBUF_SET_UPTR(buf);
+ toret = buf->visible.s + buf->visible.len;
+ buf->visible.len += len;
+ buf->visible.s[buf->visible.len] = '\0';
+ return toret;
+}
+
+void strbuf_shrink_to(strbuf *buf, size_t new_len)
+{
+ assert(new_len <= buf->len);
+ buf->len = new_len;
+ buf->s[buf->len] = '\0';
+}
+
+void strbuf_shrink_by(strbuf *buf, size_t amount_to_remove)
+{
+ assert(amount_to_remove <= buf->len);
+ buf->len -= amount_to_remove;
+ buf->s[buf->len] = '\0';
+}
+
+bool strbuf_chomp(strbuf *buf, char char_to_remove)
+{
+ if (buf->len > 0 && buf->s[buf->len-1] == char_to_remove) {
+ strbuf_shrink_by(buf, 1);
+ return true;
+ }
+ return false;
+}
+
+static void strbuf_BinarySink_write(
+ BinarySink *bs, const void *data, size_t len)
+{
+ strbuf *buf_o = BinarySink_DOWNCAST(bs, strbuf);
+ memcpy(strbuf_append(buf_o, len), data, len);
+}
+
+static void strbuf_BinarySink_writefmtv(
+ BinarySink *bs, const char *fmt, va_list ap)
+{
+ strbuf *buf_o = BinarySink_DOWNCAST(bs, strbuf);
+ struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible);
+ STRBUF_SET_PTR(buf, dupvprintf_inner(buf->visible.s, buf->visible.len,
+ &buf->size, fmt, ap));
+ buf->visible.len += strlen(buf->visible.s + buf->visible.len);
+}
+
+static strbuf *strbuf_new_general(bool nm)
+{
+ struct strbuf_impl *buf = snew(struct strbuf_impl);
+ BinarySink_INIT(&buf->visible, strbuf_BinarySink_write);
+ buf->visible.binarysink_->writefmtv = strbuf_BinarySink_writefmtv;
+ buf->visible.len = 0;
+ buf->size = 512;
+ buf->nm = nm;
+ STRBUF_SET_PTR(buf, snewn(buf->size, char));
+ *buf->visible.s = '\0';
+ return &buf->visible;
+}
+strbuf *strbuf_new(void) { return strbuf_new_general(false); }
+strbuf *strbuf_new_nm(void) { return strbuf_new_general(true); }
+void strbuf_free(strbuf *buf_o)
+{
+ struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible);
+ if (buf->visible.s) {
+ smemclr(buf->visible.s, buf->size);
+ sfree(buf->visible.s);
+ }
+ sfree(buf);
+}
+char *strbuf_to_str(strbuf *buf_o)
+{
+ struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible);
+ char *ret = buf->visible.s;
+ sfree(buf);
+ return ret;
+}
+strbuf *strbuf_new_for_agent_query(void)
+{
+ strbuf *buf = strbuf_new();
+ strbuf_append(buf, 4);
+ return buf;
+}
+void strbuf_finalise_agent_query(strbuf *buf_o)
+{
+ struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible);
+ assert(buf->visible.len >= 5);
+ PUT_32BIT_MSB_FIRST(buf->visible.u, buf->visible.len - 4);
+}
+
+strbuf *strbuf_dup(ptrlen string)
+{
+ strbuf *buf = strbuf_new();
+ put_datapl(buf, string);
+ return buf;
+}
+
+strbuf *strbuf_dup_nm(ptrlen string)
+{
+ strbuf *buf = strbuf_new_nm();
+ put_datapl(buf, string);
+ return buf;
+}
diff --git a/utils/string_length_for_printf.c b/utils/string_length_for_printf.c
new file mode 100644
index 00000000..8b455316
--- /dev/null
+++ b/utils/string_length_for_printf.c
@@ -0,0 +1,21 @@
+/*
+ * Convert a size_t value to int, by saturating it at INT_MAX. Useful
+ * if you want to use the printf idiom "%.*s", where the '*' precision
+ * specifier expects an int in the variadic argument list, but what
+ * you have is not an int but a size_t. This method of converting to
+ * int will at least do something _safe_ with overlong values, even if
+ * (due to the limitation of printf itself) the whole string still
+ * won't be printed.
+ */
+
+#include <limits.h>
+
+#include "defs.h"
+#include "misc.h"
+
+int string_length_for_printf(size_t s)
+{
+ if (s > INT_MAX)
+ return INT_MAX;
+ return s;
+}
diff --git a/utils/stripctrl.c b/utils/stripctrl.c
new file mode 100644
index 00000000..d723a079
--- /dev/null
+++ b/utils/stripctrl.c
@@ -0,0 +1,477 @@
+/*
+ * 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 <assert.h>
+#include <locale.h>
+#include <string.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include "putty.h"
+#include "terminal.h"
+#include "misc.h"
+#include "marshal.h"
+
+#define SCC_BUFSIZE 64
+#define LINE_LIMIT 77
+
+typedef struct StripCtrlCharsImpl StripCtrlCharsImpl;
+struct StripCtrlCharsImpl {
+ mbstate_t mbs_in, mbs_out;
+
+ bool permit_cr;
+ wchar_t substitution;
+
+ char buf[SCC_BUFSIZE];
+ size_t buflen;
+
+ Terminal *term;
+ bool last_term_utf;
+ struct term_utf8_decode utf8;
+ unsigned long (*translate)(Terminal *, term_utf8_decode *, unsigned char);
+
+ bool line_limit;
+ bool line_start;
+ size_t line_chars_remaining;
+
+ BinarySink *bs_out;
+
+ StripCtrlChars public;
+};
+
+static void stripctrl_locale_BinarySink_write(
+ BinarySink *bs, const void *vp, size_t len);
+static void stripctrl_term_BinarySink_write(
+ BinarySink *bs, const void *vp, size_t len);
+
+static StripCtrlCharsImpl *stripctrl_new_common(
+ BinarySink *bs_out, bool permit_cr, wchar_t substitution)
+{
+ StripCtrlCharsImpl *scc = snew(StripCtrlCharsImpl);
+ memset(scc, 0, sizeof(StripCtrlCharsImpl)); /* zeroes mbstates */
+ scc->bs_out = bs_out;
+ scc->permit_cr = permit_cr;
+ scc->substitution = substitution;
+ return scc;
+}
+
+StripCtrlChars *stripctrl_new(
+ BinarySink *bs_out, bool permit_cr, wchar_t substitution)
+{
+ StripCtrlCharsImpl *scc = stripctrl_new_common(
+ bs_out, permit_cr, substitution);
+ BinarySink_INIT(&scc->public, stripctrl_locale_BinarySink_write);
+ return &scc->public;
+}
+
+StripCtrlChars *stripctrl_new_term_fn(
+ BinarySink *bs_out, bool permit_cr, wchar_t substitution,
+ Terminal *term, unsigned long (*translate)(
+ Terminal *, term_utf8_decode *, unsigned char))
+{
+ StripCtrlCharsImpl *scc = stripctrl_new_common(
+ bs_out, permit_cr, substitution);
+ scc->term = term;
+ scc->translate = translate;
+ BinarySink_INIT(&scc->public, stripctrl_term_BinarySink_write);
+ return &scc->public;
+}
+
+void stripctrl_retarget(StripCtrlChars *sccpub, BinarySink *new_bs_out)
+{
+ StripCtrlCharsImpl *scc =
+ container_of(sccpub, StripCtrlCharsImpl, public);
+ scc->bs_out = new_bs_out;
+ stripctrl_reset(sccpub);
+}
+
+void stripctrl_reset(StripCtrlChars *sccpub)
+{
+ StripCtrlCharsImpl *scc =
+ container_of(sccpub, StripCtrlCharsImpl, public);
+
+ /*
+ * Clear all the fields that might have been in the middle of a
+ * multibyte character or non-default shift state, so that we can
+ * start converting a fresh piece of data to send to a channel
+ * that hasn't seen the previous output.
+ */
+ memset(&scc->utf8, 0, sizeof(scc->utf8));
+ memset(&scc->mbs_in, 0, sizeof(scc->mbs_in));
+ memset(&scc->mbs_out, 0, sizeof(scc->mbs_out));
+
+ /*
+ * Also, reset the line-limiting system to its starting state.
+ */
+ scc->line_start = true;
+}
+
+void stripctrl_free(StripCtrlChars *sccpub)
+{
+ StripCtrlCharsImpl *scc =
+ container_of(sccpub, StripCtrlCharsImpl, public);
+ smemclr(scc, sizeof(StripCtrlCharsImpl));
+ sfree(scc);
+}
+
+void stripctrl_enable_line_limiting(StripCtrlChars *sccpub)
+{
+ StripCtrlCharsImpl *scc =
+ container_of(sccpub, StripCtrlCharsImpl, public);
+ scc->line_limit = true;
+ scc->line_start = true;
+}
+
+static inline bool stripctrl_ctrlchar_ok(StripCtrlCharsImpl *scc, wchar_t wc)
+{
+ return wc == L'\n' || (wc == L'\r' && scc->permit_cr);
+}
+
+static inline void stripctrl_check_line_limit(
+ StripCtrlCharsImpl *scc, wchar_t wc, size_t width)
+{
+ if (!scc->line_limit)
+ return; /* nothing to do */
+
+ if (scc->line_start) {
+ put_datapl(scc->bs_out, PTRLEN_LITERAL("| "));
+ scc->line_start = false;
+ scc->line_chars_remaining = LINE_LIMIT;
+ }
+
+ if (wc == '\n') {
+ scc->line_start = true;
+ return;
+ }
+
+ if (scc->line_chars_remaining < width) {
+ put_datapl(scc->bs_out, PTRLEN_LITERAL("\r\n> "));
+ scc->line_chars_remaining = LINE_LIMIT;
+ }
+
+ assert(width <= scc->line_chars_remaining);
+ scc->line_chars_remaining -= width;
+}
+
+static inline void stripctrl_locale_put_wc(StripCtrlCharsImpl *scc, wchar_t wc)
+{
+ int width = mk_wcwidth(wc);
+ if ((iswprint(wc) && width >= 0) || stripctrl_ctrlchar_ok(scc, wc)) {
+ /* Printable character, or one we're going to let through anyway. */
+ if (width < 0)
+ width = 0; /* sanitise for stripctrl_check_line_limit */
+ } else if (scc->substitution) {
+ wc = scc->substitution;
+ width = mk_wcwidth(wc);
+ assert(width >= 0);
+ } else {
+ /* No defined substitution, so don't write any output wchar_t. */
+ return;
+ }
+
+ stripctrl_check_line_limit(scc, wc, width);
+
+ char outbuf[MB_LEN_MAX];
+ size_t produced = wcrtomb(outbuf, wc, &scc->mbs_out);
+ if (produced > 0)
+ put_data(scc->bs_out, outbuf, produced);
+}
+
+static inline void stripctrl_term_put_wc(
+ StripCtrlCharsImpl *scc, unsigned long wc)
+{
+ ptrlen prefix = PTRLEN_LITERAL("");
+ int width = term_char_width(scc->term, wc);
+
+ if (!(wc & ~0x9F) || width < 0) {
+ /* This is something the terminal interprets as a control
+ * character. */
+ if (!stripctrl_ctrlchar_ok(scc, wc)) {
+ if (!scc->substitution) {
+ return;
+ } else {
+ wc = scc->substitution;
+ width = term_char_width(scc->term, wc);
+ assert(width >= 0);
+ }
+ } else {
+ if (width < 0)
+ width = 0; /* sanitise for stripctrl_check_line_limit */
+ }
+
+ if (wc == '\012') {
+ /* Precede \n with \r, because our terminal will not
+ * generally be in the ONLCR mode where it assumes that
+ * internally, and any \r on input has been stripped
+ * out. */
+ prefix = PTRLEN_LITERAL("\r");
+ }
+ }
+
+ stripctrl_check_line_limit(scc, wc, width);
+
+ if (prefix.len)
+ put_datapl(scc->bs_out, prefix);
+
+ 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;
+
+ char *previous_locale = dupstr(setlocale(LC_CTYPE, NULL));
+ setlocale(LC_CTYPE, "");
+
+ /*
+ * Deal with any partial multibyte character buffered from last
+ * time.
+ */
+ while (scc->buflen > 0) {
+ size_t to_copy = SCC_BUFSIZE - scc->buflen;
+ if (to_copy > len)
+ to_copy = len;
+
+ memcpy(scc->buf + scc->buflen, p, to_copy);
+ size_t consumed = stripctrl_locale_try_consume(
+ scc, scc->buf, scc->buflen + to_copy);
+
+ if (consumed >= scc->buflen) {
+ /*
+ * We've consumed a multibyte character that includes all
+ * the data buffered from last time. So we can clear our
+ * buffer and move on to processing the main input string
+ * in situ, having first discarded whatever initial
+ * segment of it completed our previous character.
+ */
+ size_t consumed_from_main_string = consumed - scc->buflen;
+ assert(consumed_from_main_string <= len);
+ p += consumed_from_main_string;
+ len -= consumed_from_main_string;
+ scc->buflen = 0;
+ break;
+ }
+
+ if (consumed == 0) {
+ /*
+ * If we didn't manage to consume anything, i.e. the whole
+ * buffer contains an incomplete sequence, it had better
+ * be because our entire input string _this_ time plus
+ * whatever leftover data we had from _last_ time still
+ * comes to less than SCC_BUFSIZE. In other words, we've
+ * already copied all the new data on to the end of our
+ * buffer, and it still hasn't helped. So increment buflen
+ * to reflect the new data, and return.
+ */
+ assert(to_copy == len);
+ scc->buflen += to_copy;
+ goto out;
+ }
+
+ /*
+ * Otherwise, we've somehow consumed _less_ data than we had
+ * buffered, and yet we weren't able to consume that data in
+ * the last call to this function. That sounds impossible, but
+ * I can think of one situation in which it could happen: if
+ * we had an incomplete MB sequence last time, and now more
+ * data has arrived, it turns out to be an _illegal_ one, so
+ * we consume one byte in the hope of resynchronising.
+ *
+ * Anyway, in this case we move the buffer up and go back
+ * round this initial loop.
+ */
+ scc->buflen -= consumed;
+ memmove(scc->buf, scc->buf + consumed, scc->buflen);
+ }
+
+ /*
+ * Now charge along the main string.
+ */
+ while (len > 0) {
+ size_t consumed = stripctrl_locale_try_consume(scc, p, len);
+ if (consumed == 0)
+ break;
+ assert(consumed <= len);
+ p += consumed;
+ len -= consumed;
+ }
+
+ /*
+ * Any data remaining should be copied into our buffer, to keep
+ * for next time.
+ */
+ assert(len <= SCC_BUFSIZE);
+ memcpy(scc->buf, p, len);
+ scc->buflen = len;
+
+ out:
+ setlocale(LC_CTYPE, previous_locale);
+ sfree(previous_locale);
+}
+
+static void stripctrl_term_BinarySink_write(
+ BinarySink *bs, const void *vp, size_t len)
+{
+ StripCtrlChars *sccpub = BinarySink_DOWNCAST(bs, StripCtrlChars);
+ StripCtrlCharsImpl *scc =
+ container_of(sccpub, StripCtrlCharsImpl, public);
+
+ bool utf = in_utf(scc->term);
+ if (utf != scc->last_term_utf) {
+ scc->last_term_utf = utf;
+ scc->utf8.state = 0;
+ }
+
+ for (const unsigned char *p = (const unsigned char *)vp;
+ len > 0; len--, p++) {
+ unsigned long t = scc->translate(scc->term, &scc->utf8, *p);
+ if (t == UCSTRUNCATED) {
+ stripctrl_term_put_wc(scc, 0xFFFD);
+ /* go round again */
+ t = scc->translate(scc->term, &scc->utf8, *p);
+ }
+ if (t == UCSINCOMPLETE)
+ continue;
+ if (t == UCSINVALID)
+ t = 0xFFFD;
+
+ stripctrl_term_put_wc(scc, t);
+ }
+}
+
+char *stripctrl_string_ptrlen(StripCtrlChars *sccpub, ptrlen str)
+{
+ strbuf *out = strbuf_new();
+ stripctrl_retarget(sccpub, BinarySink_UPCAST(out));
+ put_datapl(sccpub, str);
+ stripctrl_retarget(sccpub, NULL);
+ return strbuf_to_str(out);
+}
+
+#ifdef STRIPCTRL_TEST
+
+/*
+gcc -std=c99 -DSTRIPCTRL_TEST -o scctest stripctrl.c marshal.c utils.c memory.c wcwidth.c -I . -I unix -I charset
+*/
+
+void out_of_memory(void) { fprintf(stderr, "out of memory\n"); abort(); }
+
+void stripctrl_write(BinarySink *bs, const void *vdata, size_t len)
+{
+ const uint8_t *p = vdata;
+ printf("[");
+ for (size_t i = 0; i < len; i++)
+ printf("%*s%02x", i?1:0, "", (unsigned)p[i]);
+ printf("]");
+}
+
+void stripctrl_test(StripCtrlChars *scc, ptrlen pl)
+{
+ stripctrl_write(NULL, pl.ptr, pl.len);
+ printf(" -> ");
+ put_datapl(scc, pl);
+ printf("\n");
+}
+
+int main(void)
+{
+ struct foo { BinarySink_IMPLEMENTATION; } foo;
+ BinarySink_INIT(&foo, stripctrl_write);
+ StripCtrlChars *scc = stripctrl_new(BinarySink_UPCAST(&foo), false, '?');
+ stripctrl_test(scc, PTRLEN_LITERAL("a\033[1mb"));
+ stripctrl_test(scc, PTRLEN_LITERAL("a\xC2\x9B[1mb"));
+ stripctrl_test(scc, PTRLEN_LITERAL("a\xC2\xC2[1mb"));
+ stripctrl_test(scc, PTRLEN_LITERAL("\xC3"));
+ stripctrl_test(scc, PTRLEN_LITERAL("\xA9"));
+ stripctrl_test(scc, PTRLEN_LITERAL("\xE2\x80\x8F"));
+ stripctrl_test(scc, PTRLEN_LITERAL("a\0b"));
+ stripctrl_free(scc);
+ return 0;
+}
+
+#endif /* STRIPCTRL_TEST */
diff --git a/utils/tempseat.c b/utils/tempseat.c
new file mode 100644
index 00000000..ad37b0e3
--- /dev/null
+++ b/utils/tempseat.c
@@ -0,0 +1,430 @@
+/*
+ * Implementation of the Seat trait that buffers output and other
+ * events until it can give them back to a real Seat.
+ *
+ * This is used by the SSH proxying code, which temporarily takes over
+ * the real user-facing Seat so that it can issue host key warnings,
+ * password prompts etc for the proxy SSH connection. While it's got
+ * the real Seat, it gives the primary connection's backend one of
+ * these temporary Seats in the interim, so that if the backend wants
+ * to send some kind of initial output, or start by reconfiguring the
+ * trust status, or what have you, then it can do that without having
+ * to keep careful track of the fact that its Seat is out on loan.
+ */
+
+#include "putty.h"
+
+struct output_chunk {
+ struct output_chunk *next;
+ SeatOutputType type;
+ size_t size;
+};
+
+typedef struct TempSeat TempSeat;
+struct TempSeat {
+ Seat *realseat;
+
+ /*
+ * Single bufchain to hold all the buffered output, regardless of
+ * its type.
+ */
+ bufchain output;
+
+ /*
+ * List of pieces of that bufchain that are intended for one or
+ * another output destination
+ */
+ struct output_chunk *outchunk_head, *outchunk_tail;
+
+ bool seen_session_started;
+ bool seen_remote_exit;
+ bool seen_remote_disconnect;
+ bool seen_update_specials_menu;
+ bool seen_echoedit_update, echoing, editing;
+ bool seen_trust_status, trusted;
+
+ Seat seat;
+};
+
+/* ----------------------------------------------------------------------
+ * Methods we can usefully buffer, and pass their results on to the
+ * real Seat in tempseat_flush().
+ */
+
+static size_t tempseat_output(Seat *seat, SeatOutputType type,
+ const void *data, size_t len)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+
+ bufchain_add(&ts->output, data, len);
+
+ if (!(ts->outchunk_tail && ts->outchunk_tail->type == type)) {
+ struct output_chunk *new_chunk = snew(struct output_chunk);
+
+ new_chunk->type = type;
+ new_chunk->size = 0;
+
+ new_chunk->next = NULL;
+ if (ts->outchunk_tail)
+ ts->outchunk_tail->next = new_chunk;
+ else
+ ts->outchunk_head = new_chunk;
+ ts->outchunk_tail = new_chunk;
+ }
+ ts->outchunk_tail->size += len;
+
+ return bufchain_size(&ts->output);
+}
+
+static void tempseat_notify_session_started(Seat *seat)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ ts->seen_session_started = true;
+}
+
+static void tempseat_notify_remote_exit(Seat *seat)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ ts->seen_remote_exit = true;
+}
+
+static void tempseat_notify_remote_disconnect(Seat *seat)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ ts->seen_remote_disconnect = true;
+}
+
+static void tempseat_update_specials_menu(Seat *seat)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ ts->seen_update_specials_menu = true;
+}
+
+static void tempseat_echoedit_update(Seat *seat, bool echoing, bool editing)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ ts->seen_echoedit_update = true;
+ ts->echoing = echoing;
+ ts->editing = editing;
+}
+
+static void tempseat_set_trust_status(Seat *seat, bool trusted)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ ts->seen_trust_status = true;
+ ts->trusted = trusted;
+}
+
+/* ----------------------------------------------------------------------
+ * Methods we can safely pass straight on to the real Seat, usually
+ * (but not in every case) because they're read-only queries.
+ */
+
+static char *tempseat_get_ttymode(Seat *seat, const char *mode)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ return seat_get_ttymode(ts->realseat, mode);
+}
+
+static void tempseat_set_busy_status(Seat *seat, BusyStatus status)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ /*
+ * set_busy_status is generally called when something is about to
+ * do some single-threaded, event-loop blocking computation. This
+ * _shouldn't_ happen in a backend while it's waiting for a
+ * network connection to be made, but if for some reason it were
+ * to, there's no reason we can't just pass this straight to the
+ * real seat, because we expect that it will mark itself busy,
+ * compute, and mark itself unbusy, all between yields to the
+ * event loop that might give whatever else is using the real Seat
+ * an opportunity to do anything.
+ */
+ seat_set_busy_status(ts->realseat, status);
+}
+
+static bool tempseat_is_utf8(Seat *seat)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ return seat_is_utf8(ts->realseat);
+}
+
+static const char *tempseat_get_x_display(Seat *seat)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ return seat_get_x_display(ts->realseat);
+}
+
+static bool tempseat_get_windowid(Seat *seat, long *id_out)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ return seat_get_windowid(ts->realseat, id_out);
+}
+
+static bool tempseat_get_window_pixel_size(Seat *seat, int *width, int *height)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ return seat_get_window_pixel_size(ts->realseat, width, height);
+}
+
+static StripCtrlChars *tempseat_stripctrl_new(
+ Seat *seat, BinarySink *bs_out, SeatInteractionContext sic)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ return seat_stripctrl_new(ts->realseat, bs_out, sic);
+}
+
+static bool tempseat_verbose(Seat *seat)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ return seat_verbose(ts->realseat);
+}
+
+static bool tempseat_interactive(Seat *seat)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ return seat_interactive(ts->realseat);
+}
+
+static bool tempseat_get_cursor_position(Seat *seat, int *x, int *y)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ return seat_get_cursor_position(ts->realseat, x, y);
+}
+
+static bool tempseat_can_set_trust_status(Seat *seat)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ return seat_can_set_trust_status(ts->realseat);
+}
+
+static bool tempseat_has_mixed_input_stream(Seat *seat)
+{
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ return seat_has_mixed_input_stream(ts->realseat);
+}
+
+static const SeatDialogPromptDescriptions *tempseat_prompt_descriptions(
+ Seat *seat)
+{
+ /* It might be OK to put this in the 'unreachable' category, but I
+ * think it's equally good to put it here, which allows for
+ * someone _preparing_ a prompt right now that they intend to
+ * present once the TempSeat has given way to the real one. */
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ return seat_prompt_descriptions(ts->realseat);
+}
+
+/* ----------------------------------------------------------------------
+ * Methods that should never be called on a TempSeat, so we can put an
+ * unreachable() in them.
+ *
+ * A backend in possession of a TempSeat ought to be sitting and
+ * patiently waiting for a network connection attempt to either
+ * succeed or fail. And it should be aware of the possibility that the
+ * proxy setup code to which it has lent the real Seat might need to
+ * present interactive prompts - that's the whole point of lending out
+ * the Seat in the first place - so it absolutely shouldn't get any
+ * ideas about issuing some kind of prompt of its own while it waits
+ * for the network connection.
+ */
+
+static SeatPromptResult tempseat_get_userpass_input(Seat *seat, prompts_t *p)
+{
+ /*
+ * Interactive prompts of this nature are a thing that a backend
+ * MUST NOT do while not in possession of the real Seat, because
+ * the whole point of temporarily lending the real Seat to
+ * something else is that so it can have a clear field to do
+ * interactive stuff of its own while making a network connection.
+ */
+ unreachable("get_userpass_input should never be called on TempSeat");
+}
+
+static size_t tempseat_banner(Seat *seat, const void *data, size_t len)
+{
+ unreachable("banner should never be called on TempSeat");
+}
+
+static SeatPromptResult tempseat_confirm_ssh_host_key(
+ Seat *seat, const char *host, int port, const char *keytype,
+ char *keystr, SeatDialogText *text, HelpCtx helpctx,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{
+ unreachable("confirm_ssh_host_key should never be called on TempSeat");
+}
+
+static SeatPromptResult tempseat_confirm_weak_crypto_primitive(
+ Seat *seat, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{
+ unreachable("confirm_weak_crypto_primitive "
+ "should never be called on TempSeat");
+}
+
+static SeatPromptResult tempseat_confirm_weak_cached_hostkey(
+ Seat *seat, const char *algname, const char *betteralgs,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{
+ unreachable("confirm_weak_cached_hostkey "
+ "should never be called on TempSeat");
+}
+
+static void tempseat_connection_fatal(Seat *seat, const char *message)
+{
+ /*
+ * Fatal errors are another thing a backend should not have any
+ * reason to encounter while waiting to hear back about its
+ * network connection setup.
+ *
+ * Also, if a backend _did_ call this, it would be hellish to
+ * unpick all the error handling. Just passing on the fatal error
+ * to the real Seat wouldn't be good enough: what about freeing
+ * all the various things that are confusingly holding pointers to
+ * each other? Better to leave this as an assertion-failure level
+ * issue, so that if it does ever happen by accident, we'll know
+ * it's a bug.
+ */
+ unreachable("connection_fatal should never be called on TempSeat");
+}
+
+static bool tempseat_eof(Seat *seat)
+{
+ /*
+ * EOF is _very nearly_ something that we could buffer, and pass
+ * on to the real Seat at flush time. The only difficulty is that
+ * sometimes the front end wants to respond to an incoming EOF by
+ * instructing the back end to send an outgoing one, which it does
+ * by returning a bool from its eof method.
+ *
+ * So we'd have to arrange that tempseat_flush caught that return
+ * value and passed it on to the calling backend. And then every
+ * backend would have to deal with tempseat_flush maybe returning
+ * it an 'actually, please start closing down now' indication,
+ * which could only happen _in theory_, if it had for some reason
+ * called seat_eof on the TempSeat.
+ *
+ * But in fact, we don't expect back ends to call seat_eof on the
+ * TempSeat in the first place, so all of that effort would be a
+ * total waste. Hence, we'll put EOF in the category of things we
+ * expect backends never to do while the real Seat is out on loan.
+ */
+ unreachable("eof should never be called on TempSeat");
+}
+
+/* ----------------------------------------------------------------------
+ * Done with the TempSeat methods. Here's the vtable definition and
+ * the main setup/teardown code.
+ */
+
+static const struct SeatVtable tempseat_vt = {
+ .output = tempseat_output,
+ .eof = tempseat_eof,
+ .sent = nullseat_sent,
+ .banner = tempseat_banner,
+ .get_userpass_input = tempseat_get_userpass_input,
+ .notify_session_started = tempseat_notify_session_started,
+ .notify_remote_exit = tempseat_notify_remote_exit,
+ .notify_remote_disconnect = tempseat_notify_remote_disconnect,
+ .connection_fatal = tempseat_connection_fatal,
+ .update_specials_menu = tempseat_update_specials_menu,
+ .get_ttymode = tempseat_get_ttymode,
+ .set_busy_status = tempseat_set_busy_status,
+ .confirm_ssh_host_key = tempseat_confirm_ssh_host_key,
+ .confirm_weak_crypto_primitive = tempseat_confirm_weak_crypto_primitive,
+ .confirm_weak_cached_hostkey = tempseat_confirm_weak_cached_hostkey,
+ .prompt_descriptions = tempseat_prompt_descriptions,
+ .is_utf8 = tempseat_is_utf8,
+ .echoedit_update = tempseat_echoedit_update,
+ .get_x_display = tempseat_get_x_display,
+ .get_windowid = tempseat_get_windowid,
+ .get_window_pixel_size = tempseat_get_window_pixel_size,
+ .stripctrl_new = tempseat_stripctrl_new,
+ .set_trust_status = tempseat_set_trust_status,
+ .can_set_trust_status = tempseat_can_set_trust_status,
+ .has_mixed_input_stream = tempseat_has_mixed_input_stream,
+ .verbose = tempseat_verbose,
+ .interactive = tempseat_interactive,
+ .get_cursor_position = tempseat_get_cursor_position,
+};
+
+Seat *tempseat_new(Seat *realseat)
+{
+ TempSeat *ts = snew(TempSeat);
+ memset(ts, 0, sizeof(*ts));
+ ts->seat.vt = &tempseat_vt;
+
+ ts->realseat = realseat;
+ bufchain_init(&ts->output);
+ ts->outchunk_head = ts->outchunk_tail = NULL;
+
+ return &ts->seat;
+}
+
+bool is_tempseat(Seat *seat)
+{
+ return seat->vt == &tempseat_vt;
+}
+
+Seat *tempseat_get_real(Seat *seat)
+{
+ assert(seat->vt == &tempseat_vt);
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ return ts->realseat;
+}
+
+void tempseat_free(Seat *seat)
+{
+ assert(seat->vt == &tempseat_vt);
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+ bufchain_clear(&ts->output);
+ while (ts->outchunk_head) {
+ struct output_chunk *chunk = ts->outchunk_head;
+ ts->outchunk_head = chunk->next;
+ sfree(chunk);
+ }
+ sfree(ts);
+}
+
+void tempseat_flush(Seat *seat)
+{
+ assert(seat->vt == &tempseat_vt);
+ TempSeat *ts = container_of(seat, TempSeat, seat);
+
+ /* Empty the output bufchains into the real seat, taking care to
+ * preserve both separation and interleaving */
+ while (bufchain_size(&ts->output)) {
+ ptrlen pl = bufchain_prefix(&ts->output);
+
+ assert(ts->outchunk_head);
+ struct output_chunk *chunk = ts->outchunk_head;
+
+ if (pl.len > chunk->size)
+ pl.len = chunk->size;
+
+ seat_output(ts->realseat, chunk->type, pl.ptr, pl.len);
+ bufchain_consume(&ts->output, pl.len);
+ chunk->size -= pl.len;
+ if (chunk->size == 0) {
+ ts->outchunk_head = chunk->next;
+ sfree(chunk);
+ }
+ }
+
+ /* That should have exactly emptied the output chunk list too */
+ assert(!ts->outchunk_head);
+
+ /* Pass on any other kinds of event we've buffered */
+ if (ts->seen_session_started)
+ seat_notify_session_started(ts->realseat);
+ if (ts->seen_remote_exit)
+ seat_notify_remote_exit(ts->realseat);
+ if (ts->seen_remote_disconnect)
+ seat_notify_remote_disconnect(ts->realseat);
+ if (ts->seen_update_specials_menu)
+ seat_update_specials_menu(ts->realseat);
+ if (ts->seen_echoedit_update)
+ seat_echoedit_update(ts->realseat, ts->echoing, ts->editing);
+ if (ts->seen_trust_status)
+ seat_set_trust_status(ts->realseat, ts->trusted);
+}
diff --git a/utils/tree234.c b/utils/tree234.c
new file mode 100644
index 00000000..6440f871
--- /dev/null
+++ b/utils/tree234.c
@@ -0,0 +1,1638 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "defs.h"
+#include "tree234.h"
+#include "puttymem.h"
+
+#ifdef TEST
+static int verbose = 0;
+#define LOG(x) do \
+ { \
+ if (verbose > 2) \
+ printf x; \
+ } while (0)
+#else
+#define LOG(x)
+#endif
+
+typedef struct node234_Tag node234;
+
+struct tree234_Tag {
+ node234 *root;
+ cmpfn234 cmp;
+};
+
+struct node234_Tag {
+ node234 *parent;
+ node234 *kids[4];
+ int counts[4];
+ void *elems[3];
+};
+
+/*
+ * Create a 2-3-4 tree.
+ */
+tree234 *newtree234(cmpfn234 cmp)
+{
+ tree234 *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. */
+ assert(!(equal_permitted && !e));
+
+ if (cmp == NULL)
+ cmp = t->cmp;
+
+ search234_start(&ss, t);
+ while (ss.element) {
+ int cmpret;
+
+ if (e) {
+ cmpret = cmp(e, ss.element);
+ } else {
+ cmpret = -reldir; /* invent a fixed compare result */
+ }
+
+ if (cmpret == 0) {
+ /*
+ * We've found an element that compares exactly equal to
+ * the query element.
+ */
+ if (equal_permitted) {
+ /* If our search relation permits equality, we've
+ * finished already. */
+ if (index)
+ *index = ss.index;
+ return ss.element;
+ } else {
+ /* Otherwise, pretend this element was slightly too
+ * big/small, according to the direction of search. */
+ cmpret = reldir;
+ }
+ }
+
+ search234_step(&ss, cmpret);
+ }
+
+ /*
+ * No element compares equal to the one we were after, but
+ * ss.index indicates the index that element would have if it were
+ * inserted.
+ *
+ * So if our search relation is EQ, we must simply return failure.
+ */
+ if (relation == REL234_EQ)
+ return NULL;
+
+ /*
+ * Otherwise, we must do an index lookup for the previous index
+ * (if we're going left - LE or LT) or this index (if we're going
+ * right - GE or GT).
+ */
+ if (relation == REL234_LT || relation == REL234_LE) {
+ ss.index--;
+ }
+
+ /*
+ * We know the index of the element we want; just call index234
+ * to do the rest. This will return NULL if the index is out of
+ * bounds, which is exactly what we want.
+ */
+ toret = index234(t, ss.index);
+ if (toret && index)
+ *index = ss.index;
+ return toret;
+}
+void *find234(tree234 *t, void *e, cmpfn234 cmp)
+{
+ return findrelpos234(t, e, cmp, REL234_EQ, NULL);
+}
+void *findrel234(tree234 *t, void *e, cmpfn234 cmp, int relation)
+{
+ return findrelpos234(t, e, cmp, relation, NULL);
+}
+void *findpos234(tree234 *t, void *e, cmpfn234 cmp, int *index)
+{
+ return findrelpos234(t, e, cmp, REL234_EQ, index);
+}
+
+void search234_start(search234_state *state, tree234 *t)
+{
+ state->_node = t->root;
+ state->_base = 0; /* index of first element in this node's subtree */
+ state->_last = -1; /* indicate that this node is not previously visited */
+ search234_step(state, 0);
+}
+void search234_step(search234_state *state, int direction)
+{
+ node234 *node = state->_node;
+ int i;
+
+ if (!node) {
+ state->element = NULL;
+ state->index = 0;
+ return;
+ }
+
+ if (state->_last != -1) {
+ /*
+ * We're already pointing at some element of a node, so we
+ * should restrict to the elements left or right of it,
+ * depending on the requested search direction.
+ */
+ assert(direction);
+ assert(node);
+
+ if (direction > 0)
+ state->_lo = state->_last + 1;
+ else
+ state->_hi = state->_last - 1;
+
+ if (state->_lo > state->_hi) {
+ /*
+ * We've run out of elements in this node, i.e. we've
+ * narrowed to nothing but a child pointer. Descend to
+ * that child, and update _base to the leftmost index of
+ * its subtree.
+ */
+ for (i = 0; i < state->_lo; i++)
+ state->_base += 1 + node->counts[i];
+ state->_node = node = node->kids[state->_lo];
+ state->_last = -1;
+ }
+ }
+
+ if (state->_last == -1) {
+ /*
+ * We've just entered a new node - either because of the above
+ * code, or because we were called from search234_start - and
+ * anything in that node is a viable answer.
+ */
+ state->_lo = 0;
+ state->_hi = node ? elements234(node)-1 : 0;
+ }
+
+ /*
+ * Now we've got something we can return.
+ */
+ if (!node) {
+ state->element = NULL;
+ state->index = state->_base;
+ } else {
+ state->_last = (state->_lo + state->_hi) / 2;
+ state->element = node->elems[state->_last];
+ state->index = state->_base + state->_last;
+ for (i = 0; i <= state->_last; i++)
+ state->index += node->counts[i];
+ }
+}
+
+/*
+ * Delete an element e in a 2-3-4 tree. Does not free the element,
+ * merely removes all links to it from the tree nodes.
+ */
+static void *delpos234_internal(tree234 *t, int index)
+{
+ node234 *n;
+ void *retval;
+ int ei = -1;
+
+ retval = 0;
+
+ n = t->root;
+ LOG(("deleting item %d from tree %p\n", index, t));
+ while (1) {
+ while (n) {
+ int ki;
+ node234 *sub;
+
+ LOG(
+ (" node %p: %p/%d [%p] %p/%d [%p] %p/%d [%p] %p/%d index=%d\n",
+ n, n->kids[0], n->counts[0], n->elems[0], n->kids[1],
+ n->counts[1], n->elems[1], n->kids[2], n->counts[2],
+ n->elems[2], n->kids[3], n->counts[3], index));
+ if (index < n->counts[0]) {
+ ki = 0;
+ } else if (index -= n->counts[0] + 1, index < 0) {
+ ei = 0;
+ break;
+ } else if (index < n->counts[1]) {
+ ki = 1;
+ } else if (index -= n->counts[1] + 1, index < 0) {
+ ei = 1;
+ break;
+ } else if (index < n->counts[2]) {
+ ki = 2;
+ } else if (index -= n->counts[2] + 1, index < 0) {
+ ei = 2;
+ break;
+ } else {
+ ki = 3;
+ }
+ /*
+ * Recurse down to subtree ki. If it has only one element,
+ * we have to do some transformation to start with.
+ */
+ LOG((" moving to subtree %d\n", ki));
+ sub = n->kids[ki];
+ if (!sub->elems[1]) {
+ LOG((" subtree has only one element!\n"));
+ if (ki > 0 && n->kids[ki - 1]->elems[1]) {
+ /*
+ * Case 3a, left-handed variant. Child ki has
+ * only one element, but child ki-1 has two or
+ * more. So we need to move a subtree from ki-1
+ * to ki.
+ *
+ * . C . . B .
+ * / \ -> / \
+ * [more] a A b B c d D e [more] a A b c C d D e
+ */
+ node234 *sib = n->kids[ki - 1];
+ int lastelem = (sib->elems[2] ? 2 :
+ sib->elems[1] ? 1 : 0);
+ sub->kids[2] = sub->kids[1];
+ sub->counts[2] = sub->counts[1];
+ sub->elems[1] = sub->elems[0];
+ sub->kids[1] = sub->kids[0];
+ sub->counts[1] = sub->counts[0];
+ sub->elems[0] = n->elems[ki - 1];
+ sub->kids[0] = sib->kids[lastelem + 1];
+ sub->counts[0] = sib->counts[lastelem + 1];
+ if (sub->kids[0])
+ sub->kids[0]->parent = sub;
+ n->elems[ki - 1] = sib->elems[lastelem];
+ sib->kids[lastelem + 1] = NULL;
+ sib->counts[lastelem + 1] = 0;
+ sib->elems[lastelem] = NULL;
+ n->counts[ki] = countnode234(sub);
+ LOG((" case 3a left\n"));
+ LOG(
+ (" index and left subtree count before adjustment: %d, %d\n",
+ index, n->counts[ki - 1]));
+ index += n->counts[ki - 1];
+ n->counts[ki - 1] = countnode234(sib);
+ index -= n->counts[ki - 1];
+ LOG(
+ (" index and left subtree count after adjustment: %d, %d\n",
+ index, n->counts[ki - 1]));
+ } else if (ki < 3 && n->kids[ki + 1]
+ && n->kids[ki + 1]->elems[1]) {
+ /*
+ * Case 3a, right-handed variant. ki has only
+ * one element but ki+1 has two or more. Move a
+ * subtree from ki+1 to ki.
+ *
+ * . B . . C .
+ * / \ -> / \
+ * a A b c C d D e [more] a A b B c d D e [more]
+ */
+ node234 *sib = n->kids[ki + 1];
+ int j;
+ sub->elems[1] = n->elems[ki];
+ sub->kids[2] = sib->kids[0];
+ sub->counts[2] = sib->counts[0];
+ if (sub->kids[2])
+ sub->kids[2]->parent = sub;
+ n->elems[ki] = sib->elems[0];
+ sib->kids[0] = sib->kids[1];
+ sib->counts[0] = sib->counts[1];
+ for (j = 0; j < 2 && sib->elems[j + 1]; j++) {
+ sib->kids[j + 1] = sib->kids[j + 2];
+ sib->counts[j + 1] = sib->counts[j + 2];
+ sib->elems[j] = sib->elems[j + 1];
+ }
+ sib->kids[j + 1] = NULL;
+ sib->counts[j + 1] = 0;
+ sib->elems[j] = NULL;
+ n->counts[ki] = countnode234(sub);
+ n->counts[ki + 1] = countnode234(sib);
+ LOG((" case 3a right\n"));
+ } else {
+ /*
+ * Case 3b. ki has only one element, and has no
+ * neighbour with more than one. So pick a
+ * neighbour and merge it with ki, taking an
+ * element down from n to go in the middle.
+ *
+ * . B . .
+ * / \ -> |
+ * a A b c C d a A b B c C d
+ *
+ * (Since at all points we have avoided
+ * descending to a node with only one element,
+ * we can be sure that n is not reduced to
+ * nothingness by this move, _unless_ it was
+ * the very first node, ie the root of the
+ * tree. In that case we remove the now-empty
+ * root and replace it with its single large
+ * child as shown.)
+ */
+ node234 *sib;
+ int j;
+
+ if (ki > 0) {
+ ki--;
+ index += n->counts[ki] + 1;
+ }
+ sib = n->kids[ki];
+ sub = n->kids[ki + 1];
+
+ sub->kids[3] = sub->kids[1];
+ sub->counts[3] = sub->counts[1];
+ sub->elems[2] = sub->elems[0];
+ sub->kids[2] = sub->kids[0];
+ sub->counts[2] = sub->counts[0];
+ sub->elems[1] = n->elems[ki];
+ sub->kids[1] = sib->kids[1];
+ sub->counts[1] = sib->counts[1];
+ if (sub->kids[1])
+ sub->kids[1]->parent = sub;
+ sub->elems[0] = sib->elems[0];
+ sub->kids[0] = sib->kids[0];
+ sub->counts[0] = sib->counts[0];
+ if (sub->kids[0])
+ sub->kids[0]->parent = sub;
+
+ n->counts[ki + 1] = countnode234(sub);
+
+ sfree(sib);
+
+ /*
+ * That's built the big node in sub. Now we
+ * need to remove the reference to sib in n.
+ */
+ for (j = ki; j < 3 && n->kids[j + 1]; j++) {
+ n->kids[j] = n->kids[j + 1];
+ n->counts[j] = n->counts[j + 1];
+ n->elems[j] = j < 2 ? n->elems[j + 1] : NULL;
+ }
+ n->kids[j] = NULL;
+ n->counts[j] = 0;
+ if (j < 3)
+ n->elems[j] = NULL;
+ LOG((" case 3b ki=%d\n", ki));
+
+ if (!n->elems[0]) {
+ /*
+ * The root is empty and needs to be
+ * removed.
+ */
+ LOG((" shifting root!\n"));
+ t->root = sub;
+ sub->parent = NULL;
+ sfree(n);
+ }
+ }
+ }
+ n = sub;
+ }
+ if (!retval)
+ retval = n->elems[ei];
+
+ if (ei == -1)
+ return NULL; /* although this shouldn't happen */
+
+ /*
+ * Treat special case: this is the one remaining item in
+ * the tree. n is the tree root (no parent), has one
+ * element (no elems[1]), and has no kids (no kids[0]).
+ */
+ if (!n->parent && !n->elems[1] && !n->kids[0]) {
+ LOG((" removed last element in tree\n"));
+ sfree(n);
+ t->root = NULL;
+ return retval;
+ }
+
+ /*
+ * Now we have the element we want, as n->elems[ei], and we
+ * have also arranged for that element not to be the only
+ * one in its node. So...
+ */
+
+ if (!n->kids[0] && n->elems[1]) {
+ /*
+ * Case 1. n is a leaf node with more than one element,
+ * so it's _really easy_. Just delete the thing and
+ * we're done.
+ */
+ int i;
+ LOG((" case 1\n"));
+ for (i = ei; i < 2 && n->elems[i + 1]; i++)
+ n->elems[i] = n->elems[i + 1];
+ n->elems[i] = NULL;
+ /*
+ * Having done that to the leaf node, we now go back up
+ * the tree fixing the counts.
+ */
+ while (n->parent) {
+ int childnum;
+ childnum = (n->parent->kids[0] == n ? 0 :
+ n->parent->kids[1] == n ? 1 :
+ n->parent->kids[2] == n ? 2 : 3);
+ n->parent->counts[childnum]--;
+ n = n->parent;
+ }
+ return retval; /* finished! */
+ } else if (n->kids[ei]->elems[1]) {
+ /*
+ * Case 2a. n is an internal node, and the root of the
+ * subtree to the left of e has more than one element.
+ * So find the predecessor p to e (ie the largest node
+ * in that subtree), place it where e currently is, and
+ * then start the deletion process over again on the
+ * subtree with p as target.
+ */
+ node234 *m = n->kids[ei];
+ void *target;
+ LOG((" case 2a\n"));
+ while (m->kids[0]) {
+ m = (m->kids[3] ? m->kids[3] :
+ m->kids[2] ? m->kids[2] :
+ m->kids[1] ? m->kids[1] : m->kids[0]);
+ }
+ target = (m->elems[2] ? m->elems[2] :
+ m->elems[1] ? m->elems[1] : m->elems[0]);
+ n->elems[ei] = target;
+ index = n->counts[ei] - 1;
+ n = n->kids[ei];
+ } else if (n->kids[ei + 1]->elems[1]) {
+ /*
+ * Case 2b, symmetric to 2a but s/left/right/ and
+ * s/predecessor/successor/. (And s/largest/smallest/).
+ */
+ node234 *m = n->kids[ei + 1];
+ void *target;
+ LOG((" case 2b\n"));
+ while (m->kids[0]) {
+ m = m->kids[0];
+ }
+ target = m->elems[0];
+ n->elems[ei] = target;
+ n = n->kids[ei + 1];
+ index = 0;
+ } else {
+ /*
+ * Case 2c. n is an internal node, and the subtrees to
+ * the left and right of e both have only one element.
+ * So combine the two subnodes into a single big node
+ * with their own elements on the left and right and e
+ * in the middle, then restart the deletion process on
+ * that subtree, with e still as target.
+ */
+ node234 *a = n->kids[ei], *b = n->kids[ei + 1];
+ int j;
+
+ LOG((" case 2c\n"));
+ a->elems[1] = n->elems[ei];
+ a->kids[2] = b->kids[0];
+ a->counts[2] = b->counts[0];
+ if (a->kids[2])
+ a->kids[2]->parent = a;
+ a->elems[2] = b->elems[0];
+ a->kids[3] = b->kids[1];
+ a->counts[3] = b->counts[1];
+ if (a->kids[3])
+ a->kids[3]->parent = a;
+ sfree(b);
+ n->counts[ei] = countnode234(a);
+ /*
+ * That's built the big node in a, and destroyed b. Now
+ * remove the reference to b (and e) in n.
+ */
+ for (j = ei; j < 2 && n->elems[j + 1]; j++) {
+ n->elems[j] = n->elems[j + 1];
+ n->kids[j + 1] = n->kids[j + 2];
+ n->counts[j + 1] = n->counts[j + 2];
+ }
+ n->elems[j] = NULL;
+ n->kids[j + 1] = NULL;
+ n->counts[j + 1] = 0;
+ /*
+ * It's possible, in this case, that we've just removed
+ * the only element in the root of the tree. If so,
+ * shift the root.
+ */
+ if (n->elems[0] == NULL) {
+ LOG((" shifting root!\n"));
+ t->root = a;
+ a->parent = NULL;
+ sfree(n);
+ }
+ /*
+ * Now go round the deletion process again, with n
+ * pointing at the new big node and e still the same.
+ */
+ n = a;
+ index = a->counts[0] + a->counts[1] + 1;
+ }
+ }
+}
+void *delpos234(tree234 *t, int index)
+{
+ if (index < 0 || index >= countnode234(t->root))
+ return NULL;
+ return delpos234_internal(t, index);
+}
+void *del234(tree234 *t, void *e)
+{
+ int index;
+ if (!findrelpos234(t, e, NULL, REL234_EQ, &index))
+ return NULL; /* it wasn't in there anyway */
+ return delpos234_internal(t, index); /* it's there; delete it. */
+}
+
+#ifdef TEST
+
+/*
+ * Test code for the 2-3-4 tree. This code maintains an alternative
+ * representation of the data in the tree, in an array (using the
+ * obvious and slow insert and delete functions). After each tree
+ * operation, the verify() function is called, which ensures all
+ * the tree properties are preserved:
+ * - node->child->parent always equals node
+ * - tree->root->parent always equals NULL
+ * - number of kids == 0 or number of elements + 1;
+ * - tree has the same depth everywhere
+ * - every node has at least one element
+ * - subtree element counts are accurate
+ * - any NULL kid pointer is accompanied by a zero count
+ * - in a sorted tree: ordering property between elements of a
+ * node and elements of its children is preserved
+ * and also ensures the list represented by the tree is the same
+ * list it should be. (This last check also doubly verifies the
+ * ordering properties, because the `same list it should be' is by
+ * definition correctly ordered. It also ensures all nodes are
+ * distinct, because the enum functions would get caught in a loop
+ * if not.)
+ */
+
+#include <stdarg.h>
+#include <string.h>
+
+int n_errors = 0;
+
+/*
+ * Error reporting function.
+ */
+PRINTF_LIKE(1, 2) void error(char *fmt, ...)
+{
+ va_list ap;
+ printf("ERROR: ");
+ va_start(ap, fmt);
+ vfprintf(stdout, fmt, ap);
+ va_end(ap);
+ printf("\n");
+ n_errors++;
+}
+
+/* The array representation of the data. */
+void **array;
+int arraylen, arraysize;
+cmpfn234 cmp;
+
+/* The tree representation of the same data. */
+tree234 *tree;
+
+typedef struct {
+ int treedepth;
+ int elemcount;
+} chkctx;
+
+int chknode(chkctx *ctx, int level, node234 *node,
+ void *lowbound, void *highbound)
+{
+ int nkids, nelems;
+ int i;
+ int count;
+
+ /* Count the non-NULL kids. */
+ for (nkids = 0; nkids < 4 && node->kids[nkids]; nkids++);
+ /* Ensure no kids beyond the first NULL are non-NULL. */
+ for (i = nkids; i < 4; i++)
+ if (node->kids[i]) {
+ error("node %p: nkids=%d but kids[%d] non-NULL",
+ node, nkids, i);
+ } else if (node->counts[i]) {
+ error("node %p: kids[%d] NULL but count[%d]=%d nonzero",
+ node, i, i, node->counts[i]);
+ }
+
+ /* Count the non-NULL elements. */
+ for (nelems = 0; nelems < 3 && node->elems[nelems]; nelems++);
+ /* Ensure no elements beyond the first NULL are non-NULL. */
+ for (i = nelems; i < 3; i++)
+ if (node->elems[i]) {
+ error("node %p: nelems=%d but elems[%d] non-NULL",
+ node, nelems, i);
+ }
+
+ if (nkids == 0) {
+ /*
+ * If nkids==0, this is a leaf node; verify that the tree
+ * depth is the same everywhere.
+ */
+ if (ctx->treedepth < 0)
+ ctx->treedepth = level; /* we didn't know the depth yet */
+ else if (ctx->treedepth != level)
+ error("node %p: leaf at depth %d, previously seen depth %d",
+ node, level, ctx->treedepth);
+ } else {
+ /*
+ * If nkids != 0, then it should be nelems+1, unless nelems
+ * is 0 in which case nkids should also be 0 (and so we
+ * shouldn't be in this condition at all).
+ */
+ int shouldkids = (nelems ? nelems + 1 : 0);
+ if (nkids != shouldkids) {
+ error("node %p: %d elems should mean %d kids but has %d",
+ node, nelems, shouldkids, nkids);
+ }
+ }
+
+ /*
+ * nelems should be at least 1.
+ */
+ if (nelems == 0) {
+ error("node %p: no elems", node);
+ }
+
+ /*
+ * Add nelems to the running element count of the whole tree.
+ */
+ ctx->elemcount += nelems;
+
+ /*
+ * Check ordering property: all elements should be strictly >
+ * lowbound, strictly < highbound, and strictly < each other in
+ * sequence. (lowbound and highbound are NULL at edges of tree
+ * - both NULL at root node - and NULL is considered to be <
+ * everything and > everything. IYSWIM.)
+ */
+ if (cmp) {
+ for (i = -1; i < nelems; i++) {
+ void *lower = (i == -1 ? lowbound : node->elems[i]);
+ void *higher =
+ (i + 1 == nelems ? highbound : node->elems[i + 1]);
+ if (lower && higher && cmp(lower, higher) >= 0) {
+ error("node %p: kid comparison [%d=%s,%d=%s] failed",
+ node, i, (char *)lower, i + 1, (char *)higher);
+ }
+ }
+ }
+
+ /*
+ * Check parent pointers: all non-NULL kids should have a
+ * parent pointer coming back to this node.
+ */
+ for (i = 0; i < nkids; i++)
+ if (node->kids[i]->parent != node) {
+ error("node %p kid %d: parent ptr is %p not %p",
+ node, i, node->kids[i]->parent, node);
+ }
+
+
+ /*
+ * Now (finally!) recurse into subtrees.
+ */
+ count = nelems;
+
+ for (i = 0; i < nkids; i++) {
+ void *lower = (i == 0 ? lowbound : node->elems[i - 1]);
+ void *higher = (i >= nelems ? highbound : node->elems[i]);
+ int subcount =
+ chknode(ctx, level + 1, node->kids[i], lower, higher);
+ if (node->counts[i] != subcount) {
+ error("node %p kid %d: count says %d, subtree really has %d",
+ node, i, node->counts[i], subcount);
+ }
+ count += subcount;
+ }
+
+ return count;
+}
+
+void verify(void)
+{
+ chkctx ctx[1];
+ int i;
+ void *p;
+
+ ctx->treedepth = -1; /* depth unknown yet */
+ ctx->elemcount = 0; /* no elements seen yet */
+ /*
+ * Verify validity of tree properties.
+ */
+ if (tree->root) {
+ if (tree->root->parent != NULL)
+ error("root->parent is %p should be null", tree->root->parent);
+ chknode(ctx, 0, tree->root, NULL, NULL);
+ }
+ if (verbose)
+ printf("tree depth: %d\n", ctx->treedepth);
+ /*
+ * Enumerate the tree and ensure it matches up to the array.
+ */
+ for (i = 0; NULL != (p = index234(tree, i)); i++) {
+ if (i >= arraylen)
+ error("tree contains more than %d elements", arraylen);
+ if (array[i] != p)
+ error("enum at position %d: array says %s, tree says %s",
+ i, (char *)array[i], (char *)p);
+ }
+ if (ctx->elemcount != i) {
+ error("tree really contains %d elements, enum gave %d",
+ ctx->elemcount, i);
+ }
+ if (i < arraylen) {
+ error("enum gave only %d elements, array has %d", i, arraylen);
+ }
+ i = count234(tree);
+ if (ctx->elemcount != i) {
+ error("tree really contains %d elements, count234 gave %d",
+ ctx->elemcount, i);
+ }
+}
+
+void internal_addtest(void *elem, int index, void *realret)
+{
+ int i, j;
+ void *retval;
+
+ if (arraysize < arraylen + 1) {
+ arraysize = arraylen + 1 + 256;
+ array = sresize(array, arraysize, void *);
+ }
+
+ i = index;
+ /* now i points to the first element >= elem */
+ retval = elem; /* expect elem returned (success) */
+ for (j = arraylen; j > i; j--)
+ array[j] = array[j - 1];
+ array[i] = elem; /* add elem to array */
+ arraylen++;
+
+ if (realret != retval) {
+ error("add: retval was %p expected %p", realret, retval);
+ }
+
+ verify();
+}
+
+void addtest(void *elem)
+{
+ int i;
+ void *realret;
+
+ realret = add234(tree, elem);
+
+ i = 0;
+ while (i < arraylen && cmp(elem, array[i]) > 0)
+ i++;
+ if (i < arraylen && !cmp(elem, array[i])) {
+ void *retval = array[i]; /* expect that returned not elem */
+ if (realret != retval) {
+ error("add: retval was %p expected %p", realret, retval);
+ }
+ } else
+ internal_addtest(elem, i, realret);
+}
+
+void addpostest(void *elem, int i)
+{
+ void *realret;
+
+ realret = addpos234(tree, elem, i);
+
+ internal_addtest(elem, i, realret);
+}
+
+void delpostest(int i)
+{
+ int index = i;
+ void *elem = array[i], *ret;
+
+ /* i points to the right element */
+ while (i < arraylen - 1) {
+ array[i] = array[i + 1];
+ i++;
+ }
+ arraylen--; /* delete elem from array */
+
+ if (tree->cmp)
+ ret = del234(tree, elem);
+ else
+ ret = delpos234(tree, index);
+
+ if (ret != elem) {
+ error("del returned %p, expected %p", ret, elem);
+ }
+
+ verify();
+}
+
+void deltest(void *elem)
+{
+ int i;
+
+ i = 0;
+ while (i < arraylen && cmp(elem, array[i]) > 0)
+ i++;
+ if (i >= arraylen || cmp(elem, array[i]) != 0)
+ return; /* don't do it! */
+ delpostest(i);
+}
+
+/* A sample data set and test utility. Designed for pseudo-randomness,
+ * and yet repeatability. */
+
+/*
+ * This random number generator uses the `portable implementation'
+ * given in ANSI C99 draft N869. It assumes `unsigned' is 32 bits;
+ * change it if not.
+ */
+int randomnumber(unsigned *seed)
+{
+ *seed *= 1103515245;
+ *seed += 12345;
+ return ((*seed) / 65536) % 32768;
+}
+
+int mycmp(void *av, void *bv)
+{
+ char const *a = (char const *) av;
+ char const *b = (char const *) bv;
+ return strcmp(a, b);
+}
+
+#define lenof(x) ( sizeof((x)) / sizeof(*(x)) )
+
+char *strings[] = {
+ "a", "ab", "absque", "coram", "de",
+ "palam", "clam", "cum", "ex", "e",
+ "sine", "tenus", "pro", "prae",
+ "banana", "carrot", "cabbage", "broccoli", "onion", "zebra",
+ "penguin", "blancmange", "pangolin", "whale", "hedgehog",
+ "giraffe", "peanut", "bungee", "foo", "bar", "baz", "quux",
+ "murfl", "spoo", "breen", "flarn", "octothorpe",
+ "snail", "tiger", "elephant", "octopus", "warthog", "armadillo",
+ "aardvark", "wyvern", "dragon", "elf", "dwarf", "orc", "goblin",
+ "pixie", "basilisk", "warg", "ape", "lizard", "newt", "shopkeeper",
+ "wand", "ring", "amulet"
+};
+
+#define NSTR lenof(strings)
+
+void findtest(void)
+{
+ const static int rels[] = {
+ REL234_EQ, REL234_GE, REL234_LE, REL234_LT, REL234_GT
+ };
+ const static char *const relnames[] = {
+ "EQ", "GE", "LE", "LT", "GT"
+ };
+ int i, j, rel, index;
+ char *p, *ret, *realret, *realret2;
+ int lo, hi, mid, c;
+
+ for (i = 0; i < NSTR; i++) {
+ p = strings[i];
+ for (j = 0; j < sizeof(rels) / sizeof(*rels); j++) {
+ rel = rels[j];
+
+ lo = 0;
+ hi = arraylen - 1;
+ while (lo <= hi) {
+ mid = (lo + hi) / 2;
+ c = strcmp(p, array[mid]);
+ if (c < 0)
+ hi = mid - 1;
+ else if (c > 0)
+ lo = mid + 1;
+ else
+ break;
+ }
+
+ if (c == 0) {
+ if (rel == REL234_LT)
+ ret = (mid > 0 ? array[--mid] : NULL);
+ else if (rel == REL234_GT)
+ ret = (mid < arraylen - 1 ? array[++mid] : NULL);
+ else
+ ret = array[mid];
+ } else {
+ assert(lo == hi + 1);
+ if (rel == REL234_LT || rel == REL234_LE) {
+ mid = hi;
+ ret = (hi >= 0 ? array[hi] : NULL);
+ } else if (rel == REL234_GT || rel == REL234_GE) {
+ mid = lo;
+ ret = (lo < arraylen ? array[lo] : NULL);
+ } else
+ ret = NULL;
+ }
+
+ realret = findrelpos234(tree, p, NULL, rel, &index);
+ if (realret != ret) {
+ error("find(\"%s\",%s) gave %s should be %s",
+ p, relnames[j], realret, ret);
+ }
+ if (realret && index != mid) {
+ error("find(\"%s\",%s) gave %d should be %d",
+ p, relnames[j], index, mid);
+ }
+ if (realret && rel == REL234_EQ) {
+ realret2 = index234(tree, index);
+ if (realret2 != realret) {
+ error("find(\"%s\",%s) gave %s(%d) but %d -> %s",
+ p, relnames[j], realret, index, index, realret2);
+ }
+ }
+ if (verbose)
+ printf("find(\"%s\",%s) gave %s(%d)\n", p, relnames[j],
+ realret, index);
+ }
+ }
+
+ realret = findrelpos234(tree, NULL, NULL, REL234_GT, &index);
+ if (arraylen && (realret != array[0] || index != 0)) {
+ error("find(NULL,GT) gave %s(%d) should be %s(0)",
+ realret, index, (char *)array[0]);
+ } else if (!arraylen && (realret != NULL)) {
+ error("find(NULL,GT) gave %s(%d) should be NULL", realret, index);
+ }
+
+ realret = findrelpos234(tree, NULL, NULL, REL234_LT, &index);
+ if (arraylen
+ && (realret != array[arraylen - 1] || index != arraylen - 1)) {
+ error("find(NULL,LT) gave %s(%d) should be %s(0)", realret, index,
+ (char *)array[arraylen - 1]);
+ } else if (!arraylen && (realret != NULL)) {
+ error("find(NULL,LT) gave %s(%d) should be NULL", realret, index);
+ }
+}
+
+void searchtest_recurse(search234_state ss, int lo, int hi,
+ char **expected, char *directionbuf,
+ char *directionptr)
+{
+ *directionptr = '\0';
+
+ if (!ss.element) {
+ if (lo != hi) {
+ error("search234(%s) gave NULL for non-empty interval [%d,%d)",
+ directionbuf, lo, hi);
+ } else if (ss.index != lo) {
+ error("search234(%s) gave index %d should be %d",
+ directionbuf, ss.index, lo);
+ } else {
+ if (verbose)
+ printf("%*ssearch234(%s) gave NULL,%d\n",
+ (int)(directionptr-directionbuf) * 2, "", directionbuf,
+ ss.index);
+ }
+ } else if (lo == hi) {
+ error("search234(%s) gave %s for empty interval [%d,%d)",
+ directionbuf, (char *)ss.element, lo, hi);
+ } else if (ss.element != expected[ss.index]) {
+ error("search234(%s) gave element %s should be %s",
+ directionbuf, (char *)ss.element, expected[ss.index]);
+ } else if (ss.index < lo || ss.index >= hi) {
+ error("search234(%s) gave index %d should be in [%d,%d)",
+ directionbuf, ss.index, lo, hi);
+ return;
+ } else {
+ search234_state next;
+
+ if (verbose)
+ printf("%*ssearch234(%s) gave %s,%d\n",
+ (int)(directionptr-directionbuf) * 2, "", directionbuf,
+ (char *)ss.element, ss.index);
+
+ next = ss;
+ search234_step(&next, -1);
+ *directionptr = '-';
+ searchtest_recurse(next, lo, ss.index,
+ expected, directionbuf, directionptr+1);
+
+ next = ss;
+ search234_step(&next, +1);
+ *directionptr = '+';
+ searchtest_recurse(next, ss.index+1, hi,
+ expected, directionbuf, directionptr+1);
+ }
+}
+
+void searchtest(void)
+{
+ char *expected[NSTR], *p;
+ char directionbuf[NSTR * 10];
+ int n;
+ search234_state ss;
+
+ if (verbose)
+ printf("beginning searchtest:");
+ for (n = 0; (p = index234(tree, n)) != NULL; n++) {
+ expected[n] = p;
+ if (verbose)
+ printf(" %d=%s", n, p);
+ }
+ if (verbose)
+ printf(" count=%d\n", n);
+
+ search234_start(&ss, tree);
+ searchtest_recurse(ss, 0, n, expected, directionbuf, directionbuf);
+}
+
+void out_of_memory(void)
+{
+ fprintf(stderr, "out of memory!\n");
+ exit(2);
+}
+
+int main(int argc, char **argv)
+{
+ int in[NSTR];
+ int i, j, k;
+ unsigned seed = 0;
+
+ for (i = 1; i < argc; i++) {
+ char *arg = argv[i];
+ if (!strcmp(arg, "-v")) {
+ verbose++;
+ } else {
+ fprintf(stderr, "unrecognised option '%s'\n", arg);
+ return 1;
+ }
+ }
+
+ for (i = 0; i < NSTR; i++)
+ in[i] = 0;
+ array = NULL;
+ arraylen = arraysize = 0;
+ tree = newtree234(mycmp);
+ cmp = mycmp;
+
+ verify();
+ searchtest();
+ for (i = 0; i < 10000; i++) {
+ j = randomnumber(&seed);
+ j %= NSTR;
+ if (verbose)
+ printf("trial: %d\n", i);
+ if (in[j]) {
+ if (verbose)
+ printf("deleting %s (%d)\n", strings[j], j);
+ deltest(strings[j]);
+ in[j] = 0;
+ } else {
+ if (verbose)
+ printf("adding %s (%d)\n", strings[j], j);
+ addtest(strings[j]);
+ in[j] = 1;
+ }
+ findtest();
+ searchtest();
+ }
+
+ while (arraylen > 0) {
+ j = randomnumber(&seed);
+ j %= arraylen;
+ deltest(array[j]);
+ }
+
+ freetree234(tree);
+
+ /*
+ * Now try an unsorted tree. We don't really need to test
+ * delpos234 because we know del234 is based on it, so it's
+ * already been tested in the above sorted-tree code; but for
+ * completeness we'll use it to tear down our unsorted tree
+ * once we've built it.
+ */
+ tree = newtree234(NULL);
+ cmp = NULL;
+ verify();
+ for (i = 0; i < 1000; i++) {
+ if (verbose)
+ printf("trial: %d\n", i);
+ j = randomnumber(&seed);
+ j %= NSTR;
+ k = randomnumber(&seed);
+ k %= count234(tree) + 1;
+ if (verbose)
+ printf("adding string %s at index %d\n", strings[j], k);
+ addpostest(strings[j], k);
+ }
+ while (count234(tree) > 0) {
+ if (verbose)
+ printf("cleanup: tree size %d\n", count234(tree));
+ j = randomnumber(&seed);
+ j %= count234(tree);
+ if (verbose)
+ printf("deleting string %s from index %d\n",
+ (const char *)array[j], j);
+ delpostest(j);
+ }
+
+ printf("%d errors found\n", n_errors);
+ return (n_errors != 0);
+}
+
+#endif /* TEST */
diff --git a/utils/utils.h b/utils/utils.h
new file mode 100644
index 00000000..ea4cda0c
--- /dev/null
+++ b/utils/utils.h
@@ -0,0 +1,12 @@
+/*
+ * Internal header to the utils subdirectory, for definitions shared
+ * between the library implementations but not intended to be exposed
+ * further than that.
+ */
+
+void dputs(const char *);
+
+char *dupvprintf_inner(char *buf, size_t oldlen, size_t *sizeptr,
+ const char *fmt, va_list ap);
+
+const char *host_strchr_internal(const char *s, const char *set, bool first);
diff --git a/utils/validate_manual_hostkey.c b/utils/validate_manual_hostkey.c
new file mode 100644
index 00000000..7c5d1b88
--- /dev/null
+++ b/utils/validate_manual_hostkey.c
@@ -0,0 +1,121 @@
+/*
+ * Validate a manual host key specification (either entered in the
+ * GUI, or via -hostkey). If valid, we return true, and update 'key'
+ * to contain a canonicalised version of the key string in 'key'
+ * (which is guaranteed to take up at most as much space as the
+ * original version), suitable for putting into the Conf. If not
+ * valid, we return false.
+ */
+
+#include <string.h>
+#include <ctype.h>
+
+#include "putty.h"
+#include "misc.h"
+
+#define BASE64_CHARS_NOEQ \
+ "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"
+#define BASE64_CHARS_ALL BASE64_CHARS_NOEQ "="
+
+bool validate_manual_hostkey(char *key)
+{
+ char *p, *q, *r, *s;
+
+ /*
+ * Step through the string word by word, looking for a word that's
+ * in one of the formats we like.
+ */
+ p = key;
+ while ((p += strspn(p, " \t"))[0]) {
+ q = p;
+ p += strcspn(p, " \t");
+ if (*p) *p++ = '\0';
+
+ /*
+ * Now q is our word.
+ */
+
+ if (strstartswith(q, "SHA256:")) {
+ /* Test for a valid SHA256 key fingerprint. */
+ r = q + 7;
+ if (strspn(r, BASE64_CHARS_NOEQ) == 43) {
+ memmove(key, q, 50); /* 7-char prefix + 43-char base64 */
+ key[50] = '\0';
+ return true;
+ }
+ }
+
+ r = q;
+ if (strstartswith(r, "MD5:"))
+ r += 4;
+ if (strlen(r) == 16*3 - 1 &&
+ r[strspn(r, "0123456789abcdefABCDEF:")] == 0) {
+ /*
+ * Test for a valid MD5 key fingerprint. Check the colons
+ * are in the right places, and if so, return the same
+ * fingerprint canonicalised into lowercase.
+ */
+ int i;
+ for (i = 0; i < 16; i++)
+ if (r[3*i] == ':' || r[3*i+1] == ':')
+ goto not_fingerprint; /* sorry */
+ for (i = 0; i < 15; i++)
+ if (r[3*i+2] != ':')
+ goto not_fingerprint; /* sorry */
+ for (i = 0; i < 16*3 - 1; i++)
+ key[i] = tolower(r[i]);
+ key[16*3 - 1] = '\0';
+ return true;
+ }
+ not_fingerprint:;
+
+ /*
+ * Before we check for a public-key blob, trim newlines out of
+ * the middle of the word, in case someone's managed to paste
+ * in a public-key blob _with_ them.
+ */
+ for (r = s = q; *r; r++)
+ if (*r != '\n' && *r != '\r')
+ *s++ = *r;
+ *s = '\0';
+
+ if (strlen(q) % 4 == 0 && strlen(q) > 2*4 &&
+ q[strspn(q, BASE64_CHARS_ALL)] == 0) {
+ /*
+ * Might be a base64-encoded SSH-2 public key blob. Check
+ * that it starts with a sensible algorithm string. No
+ * canonicalisation is necessary for this string type.
+ *
+ * The algorithm string must be at most 64 characters long
+ * (RFC 4251 section 6).
+ */
+ unsigned char decoded[6];
+ unsigned alglen;
+ int minlen;
+ int len = 0;
+
+ len += base64_decode_atom(q, decoded+len);
+ if (len < 3)
+ goto not_ssh2_blob; /* sorry */
+ len += base64_decode_atom(q+4, decoded+len);
+ if (len < 4)
+ goto not_ssh2_blob; /* sorry */
+
+ alglen = GET_32BIT_MSB_FIRST(decoded);
+ if (alglen > 64)
+ goto not_ssh2_blob; /* sorry */
+
+ minlen = ((alglen + 4) + 2) / 3;
+ if (strlen(q) < minlen)
+ goto not_ssh2_blob; /* sorry */
+
+ size_t base64_len = strspn(q, BASE64_CHARS_ALL);
+ memmove(key, q, base64_len);
+ key[base64_len] = '\0';
+ return true;
+ }
+ not_ssh2_blob:;
+ }
+
+ return false;
+}
diff --git a/utils/version.c b/utils/version.c
new file mode 100644
index 00000000..edc4da99
--- /dev/null
+++ b/utils/version.c
@@ -0,0 +1,23 @@
+/*
+ * PuTTY version numbering
+ */
+
+/*
+ * The difficult part of deciding what goes in these version strings
+ * is done in Buildscr, and then written into version.h. All we have
+ * to do here is to drop it into variables of the right names.
+ */
+
+#include "putty.h"
+#include "ssh.h"
+
+#include "version.h"
+
+const char ver[] = TEXTVER;
+const char sshver[] = SSHVER;
+
+/*
+ * SSH local version string MUST be under 40 characters. Here's a
+ * compile time assertion to verify this.
+ */
+enum { vorpal_sword = 1 / (sizeof(sshver) <= 40) };
diff --git a/utils/wcwidth.c b/utils/wcwidth.c
new file mode 100644
index 00000000..7705d984
--- /dev/null
+++ b/utils/wcwidth.c
@@ -0,0 +1,887 @@
+/*
+ * 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 <wchar.h>
+
+#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 the following Perl
+ * from the Unicode 14.0.0 data files available at:
+ * https://www.unicode.org/Public/14.0.0/ucd/
+
+open DATA, "<", "UnicodeData.txt" || die "$!";
+while (<DATA>) {
+ @fields = split /;/;
+ $chr = hex $fields[0];
+ $cat = $fields[2];
+ $include = ($cat eq "Me" || $cat eq "Mn" || $cat eq "Cf");
+ $include = 0 if ($chr == 0x00AD);
+ $include = 1 if (0x1160 <= $chr && $chr <= 0x11FF);
+ $include = 1 if ($chr == 0x200B);
+ $chrs{$chr} = $include;
+}
+close DATA;
+for ($chr = 0; $chr < 0x110000; $chr++) {
+ if ($chrs{$chr}) {
+ $start = $chr;
+ $chr++ while $chrs{$chr};
+ printf " { 0x%04X, 0x%04X },\n", $start, $chr-1;
+ }
+}
+
+ */
+ static const struct interval combining[] = {
+ { 0x0300, 0x036F },
+ { 0x0483, 0x0489 },
+ { 0x0591, 0x05BD },
+ { 0x05BF, 0x05BF },
+ { 0x05C1, 0x05C2 },
+ { 0x05C4, 0x05C5 },
+ { 0x05C7, 0x05C7 },
+ { 0x0600, 0x0605 },
+ { 0x0610, 0x061A },
+ { 0x061C, 0x061C },
+ { 0x064B, 0x065F },
+ { 0x0670, 0x0670 },
+ { 0x06D6, 0x06DD },
+ { 0x06DF, 0x06E4 },
+ { 0x06E7, 0x06E8 },
+ { 0x06EA, 0x06ED },
+ { 0x070F, 0x070F },
+ { 0x0711, 0x0711 },
+ { 0x0730, 0x074A },
+ { 0x07A6, 0x07B0 },
+ { 0x07EB, 0x07F3 },
+ { 0x07FD, 0x07FD },
+ { 0x0816, 0x0819 },
+ { 0x081B, 0x0823 },
+ { 0x0825, 0x0827 },
+ { 0x0829, 0x082D },
+ { 0x0859, 0x085B },
+ { 0x0890, 0x0891 },
+ { 0x0898, 0x089F },
+ { 0x08CA, 0x0902 },
+ { 0x093A, 0x093A },
+ { 0x093C, 0x093C },
+ { 0x0941, 0x0948 },
+ { 0x094D, 0x094D },
+ { 0x0951, 0x0957 },
+ { 0x0962, 0x0963 },
+ { 0x0981, 0x0981 },
+ { 0x09BC, 0x09BC },
+ { 0x09C1, 0x09C4 },
+ { 0x09CD, 0x09CD },
+ { 0x09E2, 0x09E3 },
+ { 0x09FE, 0x09FE },
+ { 0x0A01, 0x0A02 },
+ { 0x0A3C, 0x0A3C },
+ { 0x0A41, 0x0A42 },
+ { 0x0A47, 0x0A48 },
+ { 0x0A4B, 0x0A4D },
+ { 0x0A51, 0x0A51 },
+ { 0x0A70, 0x0A71 },
+ { 0x0A75, 0x0A75 },
+ { 0x0A81, 0x0A82 },
+ { 0x0ABC, 0x0ABC },
+ { 0x0AC1, 0x0AC5 },
+ { 0x0AC7, 0x0AC8 },
+ { 0x0ACD, 0x0ACD },
+ { 0x0AE2, 0x0AE3 },
+ { 0x0AFA, 0x0AFF },
+ { 0x0B01, 0x0B01 },
+ { 0x0B3C, 0x0B3C },
+ { 0x0B3F, 0x0B3F },
+ { 0x0B41, 0x0B44 },
+ { 0x0B4D, 0x0B4D },
+ { 0x0B55, 0x0B56 },
+ { 0x0B62, 0x0B63 },
+ { 0x0B82, 0x0B82 },
+ { 0x0BC0, 0x0BC0 },
+ { 0x0BCD, 0x0BCD },
+ { 0x0C00, 0x0C00 },
+ { 0x0C04, 0x0C04 },
+ { 0x0C3C, 0x0C3C },
+ { 0x0C3E, 0x0C40 },
+ { 0x0C46, 0x0C48 },
+ { 0x0C4A, 0x0C4D },
+ { 0x0C55, 0x0C56 },
+ { 0x0C62, 0x0C63 },
+ { 0x0C81, 0x0C81 },
+ { 0x0CBC, 0x0CBC },
+ { 0x0CBF, 0x0CBF },
+ { 0x0CC6, 0x0CC6 },
+ { 0x0CCC, 0x0CCD },
+ { 0x0CE2, 0x0CE3 },
+ { 0x0D00, 0x0D01 },
+ { 0x0D3B, 0x0D3C },
+ { 0x0D41, 0x0D44 },
+ { 0x0D4D, 0x0D4D },
+ { 0x0D62, 0x0D63 },
+ { 0x0D81, 0x0D81 },
+ { 0x0DCA, 0x0DCA },
+ { 0x0DD2, 0x0DD4 },
+ { 0x0DD6, 0x0DD6 },
+ { 0x0E31, 0x0E31 },
+ { 0x0E34, 0x0E3A },
+ { 0x0E47, 0x0E4E },
+ { 0x0EB1, 0x0EB1 },
+ { 0x0EB4, 0x0EBC },
+ { 0x0EC8, 0x0ECD },
+ { 0x0F18, 0x0F19 },
+ { 0x0F35, 0x0F35 },
+ { 0x0F37, 0x0F37 },
+ { 0x0F39, 0x0F39 },
+ { 0x0F71, 0x0F7E },
+ { 0x0F80, 0x0F84 },
+ { 0x0F86, 0x0F87 },
+ { 0x0F8D, 0x0F97 },
+ { 0x0F99, 0x0FBC },
+ { 0x0FC6, 0x0FC6 },
+ { 0x102D, 0x1030 },
+ { 0x1032, 0x1037 },
+ { 0x1039, 0x103A },
+ { 0x103D, 0x103E },
+ { 0x1058, 0x1059 },
+ { 0x105E, 0x1060 },
+ { 0x1071, 0x1074 },
+ { 0x1082, 0x1082 },
+ { 0x1085, 0x1086 },
+ { 0x108D, 0x108D },
+ { 0x109D, 0x109D },
+ { 0x1160, 0x11FF },
+ { 0x135D, 0x135F },
+ { 0x1712, 0x1714 },
+ { 0x1732, 0x1733 },
+ { 0x1752, 0x1753 },
+ { 0x1772, 0x1773 },
+ { 0x17B4, 0x17B5 },
+ { 0x17B7, 0x17BD },
+ { 0x17C6, 0x17C6 },
+ { 0x17C9, 0x17D3 },
+ { 0x17DD, 0x17DD },
+ { 0x180B, 0x180F },
+ { 0x1885, 0x1886 },
+ { 0x18A9, 0x18A9 },
+ { 0x1920, 0x1922 },
+ { 0x1927, 0x1928 },
+ { 0x1932, 0x1932 },
+ { 0x1939, 0x193B },
+ { 0x1A17, 0x1A18 },
+ { 0x1A1B, 0x1A1B },
+ { 0x1A56, 0x1A56 },
+ { 0x1A58, 0x1A5E },
+ { 0x1A60, 0x1A60 },
+ { 0x1A62, 0x1A62 },
+ { 0x1A65, 0x1A6C },
+ { 0x1A73, 0x1A7C },
+ { 0x1A7F, 0x1A7F },
+ { 0x1AB0, 0x1ACE },
+ { 0x1B00, 0x1B03 },
+ { 0x1B34, 0x1B34 },
+ { 0x1B36, 0x1B3A },
+ { 0x1B3C, 0x1B3C },
+ { 0x1B42, 0x1B42 },
+ { 0x1B6B, 0x1B73 },
+ { 0x1B80, 0x1B81 },
+ { 0x1BA2, 0x1BA5 },
+ { 0x1BA8, 0x1BA9 },
+ { 0x1BAB, 0x1BAD },
+ { 0x1BE6, 0x1BE6 },
+ { 0x1BE8, 0x1BE9 },
+ { 0x1BED, 0x1BED },
+ { 0x1BEF, 0x1BF1 },
+ { 0x1C2C, 0x1C33 },
+ { 0x1C36, 0x1C37 },
+ { 0x1CD0, 0x1CD2 },
+ { 0x1CD4, 0x1CE0 },
+ { 0x1CE2, 0x1CE8 },
+ { 0x1CED, 0x1CED },
+ { 0x1CF4, 0x1CF4 },
+ { 0x1CF8, 0x1CF9 },
+ { 0x1DC0, 0x1DFF },
+ { 0x200B, 0x200F },
+ { 0x202A, 0x202E },
+ { 0x2060, 0x2064 },
+ { 0x2066, 0x206F },
+ { 0x20D0, 0x20F0 },
+ { 0x2CEF, 0x2CF1 },
+ { 0x2D7F, 0x2D7F },
+ { 0x2DE0, 0x2DFF },
+ { 0x302A, 0x302D },
+ { 0x3099, 0x309A },
+ { 0xA66F, 0xA672 },
+ { 0xA674, 0xA67D },
+ { 0xA69E, 0xA69F },
+ { 0xA6F0, 0xA6F1 },
+ { 0xA802, 0xA802 },
+ { 0xA806, 0xA806 },
+ { 0xA80B, 0xA80B },
+ { 0xA825, 0xA826 },
+ { 0xA82C, 0xA82C },
+ { 0xA8C4, 0xA8C5 },
+ { 0xA8E0, 0xA8F1 },
+ { 0xA8FF, 0xA8FF },
+ { 0xA926, 0xA92D },
+ { 0xA947, 0xA951 },
+ { 0xA980, 0xA982 },
+ { 0xA9B3, 0xA9B3 },
+ { 0xA9B6, 0xA9B9 },
+ { 0xA9BC, 0xA9BD },
+ { 0xA9E5, 0xA9E5 },
+ { 0xAA29, 0xAA2E },
+ { 0xAA31, 0xAA32 },
+ { 0xAA35, 0xAA36 },
+ { 0xAA43, 0xAA43 },
+ { 0xAA4C, 0xAA4C },
+ { 0xAA7C, 0xAA7C },
+ { 0xAAB0, 0xAAB0 },
+ { 0xAAB2, 0xAAB4 },
+ { 0xAAB7, 0xAAB8 },
+ { 0xAABE, 0xAABF },
+ { 0xAAC1, 0xAAC1 },
+ { 0xAAEC, 0xAAED },
+ { 0xAAF6, 0xAAF6 },
+ { 0xABE5, 0xABE5 },
+ { 0xABE8, 0xABE8 },
+ { 0xABED, 0xABED },
+ { 0xFB1E, 0xFB1E },
+ { 0xFE00, 0xFE0F },
+ { 0xFE20, 0xFE2F },
+ { 0xFEFF, 0xFEFF },
+ { 0xFFF9, 0xFFFB },
+ { 0x101FD, 0x101FD },
+ { 0x102E0, 0x102E0 },
+ { 0x10376, 0x1037A },
+ { 0x10A01, 0x10A03 },
+ { 0x10A05, 0x10A06 },
+ { 0x10A0C, 0x10A0F },
+ { 0x10A38, 0x10A3A },
+ { 0x10A3F, 0x10A3F },
+ { 0x10AE5, 0x10AE6 },
+ { 0x10D24, 0x10D27 },
+ { 0x10EAB, 0x10EAC },
+ { 0x10F46, 0x10F50 },
+ { 0x10F82, 0x10F85 },
+ { 0x11001, 0x11001 },
+ { 0x11038, 0x11046 },
+ { 0x11070, 0x11070 },
+ { 0x11073, 0x11074 },
+ { 0x1107F, 0x11081 },
+ { 0x110B3, 0x110B6 },
+ { 0x110B9, 0x110BA },
+ { 0x110BD, 0x110BD },
+ { 0x110C2, 0x110C2 },
+ { 0x110CD, 0x110CD },
+ { 0x11100, 0x11102 },
+ { 0x11127, 0x1112B },
+ { 0x1112D, 0x11134 },
+ { 0x11173, 0x11173 },
+ { 0x11180, 0x11181 },
+ { 0x111B6, 0x111BE },
+ { 0x111C9, 0x111CC },
+ { 0x111CF, 0x111CF },
+ { 0x1122F, 0x11231 },
+ { 0x11234, 0x11234 },
+ { 0x11236, 0x11237 },
+ { 0x1123E, 0x1123E },
+ { 0x112DF, 0x112DF },
+ { 0x112E3, 0x112EA },
+ { 0x11300, 0x11301 },
+ { 0x1133B, 0x1133C },
+ { 0x11340, 0x11340 },
+ { 0x11366, 0x1136C },
+ { 0x11370, 0x11374 },
+ { 0x11438, 0x1143F },
+ { 0x11442, 0x11444 },
+ { 0x11446, 0x11446 },
+ { 0x1145E, 0x1145E },
+ { 0x114B3, 0x114B8 },
+ { 0x114BA, 0x114BA },
+ { 0x114BF, 0x114C0 },
+ { 0x114C2, 0x114C3 },
+ { 0x115B2, 0x115B5 },
+ { 0x115BC, 0x115BD },
+ { 0x115BF, 0x115C0 },
+ { 0x115DC, 0x115DD },
+ { 0x11633, 0x1163A },
+ { 0x1163D, 0x1163D },
+ { 0x1163F, 0x11640 },
+ { 0x116AB, 0x116AB },
+ { 0x116AD, 0x116AD },
+ { 0x116B0, 0x116B5 },
+ { 0x116B7, 0x116B7 },
+ { 0x1171D, 0x1171F },
+ { 0x11722, 0x11725 },
+ { 0x11727, 0x1172B },
+ { 0x1182F, 0x11837 },
+ { 0x11839, 0x1183A },
+ { 0x1193B, 0x1193C },
+ { 0x1193E, 0x1193E },
+ { 0x11943, 0x11943 },
+ { 0x119D4, 0x119D7 },
+ { 0x119DA, 0x119DB },
+ { 0x119E0, 0x119E0 },
+ { 0x11A01, 0x11A0A },
+ { 0x11A33, 0x11A38 },
+ { 0x11A3B, 0x11A3E },
+ { 0x11A47, 0x11A47 },
+ { 0x11A51, 0x11A56 },
+ { 0x11A59, 0x11A5B },
+ { 0x11A8A, 0x11A96 },
+ { 0x11A98, 0x11A99 },
+ { 0x11C30, 0x11C36 },
+ { 0x11C38, 0x11C3D },
+ { 0x11C3F, 0x11C3F },
+ { 0x11C92, 0x11CA7 },
+ { 0x11CAA, 0x11CB0 },
+ { 0x11CB2, 0x11CB3 },
+ { 0x11CB5, 0x11CB6 },
+ { 0x11D31, 0x11D36 },
+ { 0x11D3A, 0x11D3A },
+ { 0x11D3C, 0x11D3D },
+ { 0x11D3F, 0x11D45 },
+ { 0x11D47, 0x11D47 },
+ { 0x11D90, 0x11D91 },
+ { 0x11D95, 0x11D95 },
+ { 0x11D97, 0x11D97 },
+ { 0x11EF3, 0x11EF4 },
+ { 0x13430, 0x13438 },
+ { 0x16AF0, 0x16AF4 },
+ { 0x16B30, 0x16B36 },
+ { 0x16F4F, 0x16F4F },
+ { 0x16F8F, 0x16F92 },
+ { 0x16FE4, 0x16FE4 },
+ { 0x1BC9D, 0x1BC9E },
+ { 0x1BCA0, 0x1BCA3 },
+ { 0x1CF00, 0x1CF2D },
+ { 0x1CF30, 0x1CF46 },
+ { 0x1D167, 0x1D169 },
+ { 0x1D173, 0x1D182 },
+ { 0x1D185, 0x1D18B },
+ { 0x1D1AA, 0x1D1AD },
+ { 0x1D242, 0x1D244 },
+ { 0x1DA00, 0x1DA36 },
+ { 0x1DA3B, 0x1DA6C },
+ { 0x1DA75, 0x1DA75 },
+ { 0x1DA84, 0x1DA84 },
+ { 0x1DA9B, 0x1DA9F },
+ { 0x1DAA1, 0x1DAAF },
+ { 0x1E000, 0x1E006 },
+ { 0x1E008, 0x1E018 },
+ { 0x1E01B, 0x1E021 },
+ { 0x1E023, 0x1E024 },
+ { 0x1E026, 0x1E02A },
+ { 0x1E130, 0x1E136 },
+ { 0x1E2AE, 0x1E2AE },
+ { 0x1E2EC, 0x1E2EF },
+ { 0x1E8D0, 0x1E8D6 },
+ { 0x1E944, 0x1E94A },
+ { 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 14.0.0 data files available at:
+ * https://www.unicode.org/Public/14.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},
+ {0x1AFF0, 0x1AFF3},
+ {0x1AFF5, 0x1AFFB},
+ {0x1AFFD, 0x1AFFE},
+ {0x1B000, 0x1B122},
+ {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},
+ {0x1F6DD, 0x1F6DF},
+ {0x1F6EB, 0x1F6EC},
+ {0x1F6F4, 0x1F6FC},
+ {0x1F7E0, 0x1F7EB},
+ {0x1F7F0, 0x1F7F0},
+ {0x1F90C, 0x1F93A},
+ {0x1F93C, 0x1F945},
+ {0x1F947, 0x1F9FF},
+ {0x1FA70, 0x1FA74},
+ {0x1FA78, 0x1FA7C},
+ {0x1FA80, 0x1FA86},
+ {0x1FA90, 0x1FAAC},
+ {0x1FAB0, 0x1FABA},
+ {0x1FAC0, 0x1FAC5},
+ {0x1FAD0, 0x1FAD9},
+ {0x1FAE0, 0x1FAE7},
+ {0x1FAF0, 0x1FAF6},
+ {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/utils/wildcard.c b/utils/wildcard.c
new file mode 100644
index 00000000..255cc53f
--- /dev/null
+++ b/utils/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 <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "putty.h"
+
+/*
+ * Definition of wildcard syntax:
+ *
+ * - * matches any sequence of characters, including zero.
+ * - ? matches exactly one character which can be anything.
+ * - [abc] matches exactly one character which is a, b or c.
+ * - [a-f] matches anything from a through f.
+ * - [^a-f] matches anything _except_ a through f.
+ * - [-_] matches - or _; [^-_] matches anything else. (The - is
+ * non-special if it occurs immediately after the opening
+ * bracket or ^.)
+ * - [a^] matches an a or a ^. (The ^ is non-special if it does
+ * _not_ occur immediately after the opening bracket.)
+ * - \*, \?, \[, \], \\ match the single characters *, ?, [, ], \.
+ * - All other characters are non-special and match themselves.
+ */
+
+/*
+ * Some notes on differences from POSIX globs (IEEE Std 1003.1, 2003 ed.):
+ * - backslashes act as escapes even within [] bracket expressions
+ * - does not support [!...] for non-matching list (POSIX are weird);
+ * NB POSIX allows [^...] as well via "A bracket expression starting
+ * with an unquoted circumflex character produces unspecified
+ * results". If we wanted to allow [!...] we might want to define
+ * [^!] as having its literal meaning (match '^' or '!').
+ * - none of the scary [[:class:]] stuff, etc
+ */
+
+/*
+ * The wildcard matching technique we use is very simple and
+ * potentially O(N^2) in running time, but I don't anticipate it
+ * being that bad in reality (particularly since N will be the size
+ * of a filename, which isn't all that much). Perhaps one day, once
+ * PuTTY has grown a regexp matcher for some other reason, I might
+ * come back and reimplement wildcards by translating them into
+ * regexps or directly into NFAs; but for the moment, in the
+ * absence of any other need for the NFA->DFA translation engine,
+ * anything more than the simplest possible wildcard matcher is
+ * vast code-size overkill.
+ *
+ * Essentially, these wildcards are much simpler than regexps in
+ * that they consist of a sequence of rigid fragments (? and [...]
+ * can never match more or less than one character) separated by
+ * asterisks. It is therefore extremely simple to look at a rigid
+ * fragment and determine whether or not it begins at a particular
+ * point in the test string; so we can search along the string
+ * until we find each fragment, then search for the next. As long
+ * as we find each fragment in the _first_ place it occurs, there
+ * will never be a danger of having to backpedal and try to find it
+ * again somewhere else.
+ */
+
+enum {
+ WC_TRAILINGBACKSLASH = 1,
+ WC_UNCLOSEDCLASS,
+ WC_INVALIDRANGE
+};
+
+/*
+ * Error reporting is done by returning various negative values
+ * from the wildcard routines. Passing any such value to wc_error
+ * will give a human-readable message.
+ */
+const char *wc_error(int value)
+{
+ value = abs(value);
+ switch (value) {
+ case WC_TRAILINGBACKSLASH:
+ return "'\' occurred at end of string (expected another character)";
+ case WC_UNCLOSEDCLASS:
+ return "expected ']' to close character class";
+ case WC_INVALIDRANGE:
+ return "character range was not terminated (']' just after '-')";
+ }
+ return "INTERNAL ERROR: unrecognised wildcard error number";
+}
+
+/*
+ * This is the routine that tests a target string to see if an
+ * initial substring of it matches a fragment. If successful, it
+ * returns 1, and advances both `fragment' and `target' past the
+ * fragment and matching substring respectively. If unsuccessful it
+ * returns zero. If the wildcard fragment suffers a syntax error,
+ * it returns <0 and the precise value indexes into wc_error.
+ */
+static int wc_match_fragment(const char **fragment, const char **target,
+ const char *target_end)
+{
+ const char *f, *t;
+
+ f = *fragment;
+ t = *target;
+ /*
+ * The fragment terminates at either the end of the string, or
+ * the first (unescaped) *.
+ */
+ while (*f && *f != '*' && t < target_end) {
+ /*
+ * Extract one character from t, and one character's worth
+ * of pattern from f, and step along both. Return 0 if they
+ * fail to match.
+ */
+ if (*f == '\\') {
+ /*
+ * Backslash, which means f[1] is to be treated as a
+ * literal character no matter what it is. It may not
+ * be the end of the string.
+ */
+ if (!f[1])
+ return -WC_TRAILINGBACKSLASH; /* error */
+ if (f[1] != *t)
+ return 0; /* failed to match */
+ f += 2;
+ } else if (*f == '?') {
+ /*
+ * Question mark matches anything.
+ */
+ f++;
+ } else if (*f == '[') {
+ bool invert = false;
+ bool matched = false;
+ /*
+ * Open bracket introduces a character class.
+ */
+ f++;
+ if (*f == '^') {
+ invert = true;
+ f++;
+ }
+ while (*f != ']') {
+ if (*f == '\\')
+ f++; /* backslashes still work */
+ if (!*f)
+ return -WC_UNCLOSEDCLASS; /* error again */
+ if (f[1] == '-') {
+ int lower, upper, ourchr;
+ lower = (unsigned char) *f++;
+ f++; /* eat the minus */
+ if (*f == ']')
+ return -WC_INVALIDRANGE; /* different error! */
+ if (*f == '\\')
+ f++; /* backslashes _still_ work */
+ if (!*f)
+ return -WC_UNCLOSEDCLASS; /* error again */
+ upper = (unsigned char) *f++;
+ ourchr = (unsigned char) *t;
+ if (lower > upper) {
+ int t = lower; lower = upper; upper = t;
+ }
+ if (ourchr >= lower && ourchr <= upper)
+ matched = true;
+ } else {
+ matched |= (*t == *f++);
+ }
+ }
+ if (invert == matched)
+ return 0; /* failed to match character class */
+ f++; /* eat the ] */
+ } else {
+ /*
+ * Non-special character matches itself.
+ */
+ if (*f != *t)
+ return 0;
+ f++;
+ }
+ /*
+ * Now we've done that, increment t past the character we
+ * matched.
+ */
+ t++;
+ }
+ if (!*f || *f == '*') {
+ /*
+ * We have reached the end of f without finding a mismatch;
+ * so we're done. Update the caller pointers and return 1.
+ */
+ *fragment = f;
+ *target = t;
+ return 1;
+ }
+ /*
+ * Otherwise, we must have reached the end of t before we
+ * reached the end of f; so we've failed. Return 0.
+ */
+ return 0;
+}
+
+/*
+ * This is the real wildcard matching routine. It returns 1 for a
+ * successful match, 0 for an unsuccessful match, and <0 for a
+ * syntax error in the wildcard.
+ */
+static int wc_match_inner(
+ const char *wildcard, const char *target, size_t target_len)
+{
+ const char *target_end = target + target_len;
+ int ret;
+
+ /*
+ * Every time we see a '*' _followed_ by a fragment, we just
+ * search along the string for a location at which the fragment
+ * matches. The only special case is when we see a fragment
+ * right at the start, in which case we just call the matching
+ * routine once and give up if it fails.
+ */
+ if (*wildcard != '*') {
+ ret = wc_match_fragment(&wildcard, &target, target_end);
+ if (ret <= 0)
+ return ret; /* pass back failure or error alike */
+ }
+
+ while (*wildcard) {
+ assert(*wildcard == '*');
+ while (*wildcard == '*')
+ wildcard++;
+
+ /*
+ * It's possible we've just hit the end of the wildcard
+ * after seeing a *, in which case there's no need to
+ * bother searching any more because we've won.
+ */
+ if (!*wildcard)
+ return 1;
+
+ /*
+ * Now `wildcard' points at the next fragment. So we
+ * attempt to match it against `target', and if that fails
+ * we increment `target' and try again, and so on. When we
+ * find we're about to try matching against the empty
+ * string, we give up and return 0.
+ */
+ ret = 0;
+ while (*target) {
+ const char *save_w = wildcard, *save_t = target;
+
+ ret = wc_match_fragment(&wildcard, &target, target_end);
+
+ if (ret < 0)
+ return ret; /* syntax error */
+
+ if (ret > 0 && !*wildcard && target != target_end) {
+ /*
+ * Final special case - literally.
+ *
+ * This situation arises when we are matching a
+ * _terminal_ fragment of the wildcard (that is,
+ * there is nothing after it, e.g. "*a"), and it
+ * has matched _too early_. For example, matching
+ * "*a" against "parka" will match the "a" fragment
+ * against the _first_ a, and then (if it weren't
+ * for this special case) matching would fail
+ * because we're at the end of the wildcard but not
+ * at the end of the target string.
+ *
+ * In this case what we must do is measure the
+ * length of the fragment in the target (which is
+ * why we saved `target'), jump straight to that
+ * distance from the end of the string using
+ * strlen, and match the same fragment again there
+ * (which is why we saved `wildcard'). Then we
+ * return whatever that operation returns.
+ */
+ target = target_end - (target - save_t);
+ wildcard = save_w;
+ return wc_match_fragment(&wildcard, &target, target_end);
+ }
+
+ if (ret > 0)
+ break;
+ target++;
+ }
+ if (ret > 0)
+ continue;
+ return 0;
+ }
+
+ /*
+ * If we reach here, it must be because we successfully matched
+ * a fragment and then found ourselves right at the end of the
+ * wildcard. Hence, we return 1 if and only if we are also
+ * right at the end of the target.
+ */
+ return target == target_end;
+}
+
+int wc_match(const char *wildcard, const char *target)
+{
+ return wc_match_inner(wildcard, target, strlen(target));
+}
+
+int wc_match_pl(const char *wildcard, ptrlen target)
+{
+ return wc_match_inner(wildcard, target.ptr, target.len);
+}
+
+/*
+ * Another utility routine that translates a non-wildcard string
+ * into its raw equivalent by removing any escaping backslashes.
+ * Expects a target string buffer of anything up to the length of
+ * the original wildcard. You can also pass NULL as the output
+ * buffer if you're only interested in the return value.
+ *
+ * Returns true on success, or false if a wildcard character was
+ * encountered. In the latter case the output string MAY not be
+ * zero-terminated and you should not use it for anything!
+ */
+bool wc_unescape(char *output, const char *wildcard)
+{
+ while (*wildcard) {
+ if (*wildcard == '\\') {
+ wildcard++;
+ /* We are lenient about trailing backslashes in non-wildcards. */
+ if (*wildcard) {
+ if (output)
+ *output++ = *wildcard;
+ wildcard++;
+ }
+ } else if (*wildcard == '*' || *wildcard == '?' ||
+ *wildcard == '[' || *wildcard == ']') {
+ return false; /* it's a wildcard! */
+ } else {
+ if (output)
+ *output++ = *wildcard;
+ wildcard++;
+ }
+ }
+ if (output)
+ *output = '\0';
+ return true; /* it's clean */
+}
+
+#ifdef TEST
+
+struct test {
+ const char *wildcard;
+ const char *target;
+ int expected_result;
+};
+
+const struct test fragment_tests[] = {
+ /*
+ * We exhaustively unit-test the fragment matching routine
+ * itself, which should save us the need to test all its
+ * intricacies during the full wildcard tests.
+ */
+ {"abc", "abc", 1},
+ {"abc", "abd", 0},
+ {"abc", "abcd", 1},
+ {"abcd", "abc", 0},
+ {"ab[cd]", "abc", 1},
+ {"ab[cd]", "abd", 1},
+ {"ab[cd]", "abe", 0},
+ {"ab[^cd]", "abc", 0},
+ {"ab[^cd]", "abd", 0},
+ {"ab[^cd]", "abe", 1},
+ {"ab\\", "abc", -WC_TRAILINGBACKSLASH},
+ {"ab\\*", "ab*", 1},
+ {"ab\\?", "ab*", 0},
+ {"ab?", "abc", 1},
+ {"ab?", "ab", 0},
+ {"ab[", "abc", -WC_UNCLOSEDCLASS},
+ {"ab[c-", "abb", -WC_UNCLOSEDCLASS},
+ {"ab[c-]", "abb", -WC_INVALIDRANGE},
+ {"ab[c-e]", "abb", 0},
+ {"ab[c-e]", "abc", 1},
+ {"ab[c-e]", "abd", 1},
+ {"ab[c-e]", "abe", 1},
+ {"ab[c-e]", "abf", 0},
+ {"ab[e-c]", "abb", 0},
+ {"ab[e-c]", "abc", 1},
+ {"ab[e-c]", "abd", 1},
+ {"ab[e-c]", "abe", 1},
+ {"ab[e-c]", "abf", 0},
+ {"ab[^c-e]", "abb", 1},
+ {"ab[^c-e]", "abc", 0},
+ {"ab[^c-e]", "abd", 0},
+ {"ab[^c-e]", "abe", 0},
+ {"ab[^c-e]", "abf", 1},
+ {"ab[^e-c]", "abb", 1},
+ {"ab[^e-c]", "abc", 0},
+ {"ab[^e-c]", "abd", 0},
+ {"ab[^e-c]", "abe", 0},
+ {"ab[^e-c]", "abf", 1},
+ {"ab[a^]", "aba", 1},
+ {"ab[a^]", "ab^", 1},
+ {"ab[a^]", "abb", 0},
+ {"ab[^a^]", "aba", 0},
+ {"ab[^a^]", "ab^", 0},
+ {"ab[^a^]", "abb", 1},
+ {"ab[-c]", "ab-", 1},
+ {"ab[-c]", "abc", 1},
+ {"ab[-c]", "abd", 0},
+ {"ab[^-c]", "ab-", 0},
+ {"ab[^-c]", "abc", 0},
+ {"ab[^-c]", "abd", 1},
+ {"ab[\\[-\\]]", "abZ", 0},
+ {"ab[\\[-\\]]", "ab[", 1},
+ {"ab[\\[-\\]]", "ab\\", 1},
+ {"ab[\\[-\\]]", "ab]", 1},
+ {"ab[\\[-\\]]", "ab^", 0},
+ {"ab[^\\[-\\]]", "abZ", 1},
+ {"ab[^\\[-\\]]", "ab[", 0},
+ {"ab[^\\[-\\]]", "ab\\", 0},
+ {"ab[^\\[-\\]]", "ab]", 0},
+ {"ab[^\\[-\\]]", "ab^", 1},
+ {"ab[a-fA-F]", "aba", 1},
+ {"ab[a-fA-F]", "abF", 1},
+ {"ab[a-fA-F]", "abZ", 0},
+};
+
+const struct test full_tests[] = {
+ {"a", "argh", 0},
+ {"a", "ba", 0},
+ {"a", "a", 1},
+ {"a*", "aardvark", 1},
+ {"a*", "badger", 0},
+ {"*a", "park", 0},
+ {"*a", "pArka", 1},
+ {"*a", "parka", 1},
+ {"*a*", "park", 1},
+ {"*a*", "perk", 0},
+ {"?b*r?", "abracadabra", 1},
+ {"?b*r?", "abracadabr", 0},
+ {"?b*r?", "abracadabzr", 0},
+};
+
+int main(void)
+{
+ int i;
+ int fails, passes;
+
+ fails = passes = 0;
+
+ for (i = 0; i < sizeof(fragment_tests)/sizeof(*fragment_tests); i++) {
+ const char *f, *t;
+ int eret, aret;
+ f = fragment_tests[i].wildcard;
+ t = fragment_tests[i].target;
+ eret = fragment_tests[i].expected_result;
+ aret = wc_match_fragment(&f, &t, t + strlen(t));
+ if (aret != eret) {
+ printf("failed test: /%s/ against /%s/ returned %d not %d\n",
+ fragment_tests[i].wildcard, fragment_tests[i].target,
+ aret, eret);
+ fails++;
+ } else
+ passes++;
+ }
+
+ for (i = 0; i < sizeof(full_tests)/sizeof(*full_tests); i++) {
+ const char *f, *t;
+ int eret, aret;
+ f = full_tests[i].wildcard;
+ t = full_tests[i].target;
+ eret = full_tests[i].expected_result;
+ aret = wc_match(f, t);
+ if (aret != eret) {
+ printf("failed test: /%s/ against /%s/ returned %d not %d\n",
+ full_tests[i].wildcard, full_tests[i].target,
+ aret, eret);
+ fails++;
+ } else
+ passes++;
+ }
+
+ printf("passed %d, failed %d\n", passes, fails);
+
+ return 0;
+}
+
+#endif /* TEST */
diff --git a/utils/wordwrap.c b/utils/wordwrap.c
new file mode 100644
index 00000000..330b6a03
--- /dev/null
+++ b/utils/wordwrap.c
@@ -0,0 +1,36 @@
+/*
+ * Function to wrap text to a fixed number of columns.
+ *
+ * Currently, assumes the text is in a single-byte character set,
+ * because it's only used to display host key prompt messages.
+ * Extending to Unicode and using wcwidth() could be an extension.
+ */
+
+#include "misc.h"
+
+void wordwrap(BinarySink *bs, ptrlen input, size_t maxwid)
+{
+ size_t col = 0;
+ while (true) {
+ ptrlen word = ptrlen_get_word(&input, " ");
+ if (!word.len)
+ break;
+
+ /* At the start of a line, any word is legal, even if it's
+ * overlong, because we have to display it _somehow_ and
+ * wrapping to the next line won't make it any better. */
+ if (col > 0) {
+ size_t newcol = col + 1 + word.len;
+ if (newcol <= maxwid) {
+ put_byte(bs, ' ');
+ col++;
+ } else {
+ put_byte(bs, '\n');
+ col = 0;
+ }
+ }
+
+ put_datapl(bs, word);
+ col += word.len;
+ }
+}
diff --git a/utils/write_c_string_literal.c b/utils/write_c_string_literal.c
new file mode 100644
index 00000000..e8b2f53b
--- /dev/null
+++ b/utils/write_c_string_literal.c
@@ -0,0 +1,39 @@
+/*
+ * Write data to a file or BinarySink in the form of a C string
+ * literal, with any non-printable-ASCII character escaped
+ * appropriately.
+ */
+
+#include "defs.h"
+#include "misc.h"
+
+void BinarySink_put_c_string_literal(BinarySink *bs, ptrlen str)
+{
+ for (const char *p = str.ptr; p < (const char *)str.ptr + str.len; p++) {
+ char c = *p;
+
+ if (c == '\n')
+ put_datalit(bs, "\\n");
+ else if (c == '\r')
+ put_datalit(bs, "\\r");
+ else if (c == '\t')
+ put_datalit(bs, "\\t");
+ else if (c == '\b')
+ put_datalit(bs, "\\b");
+ else if (c == '\\')
+ put_datalit(bs, "\\\\");
+ else if (c == '"')
+ put_datalit(bs, "\\\"");
+ else if (c >= 32 && c <= 126)
+ put_byte(bs, c);
+ else
+ put_fmt(bs, "\\%03o", (unsigned)c & 0xFFU);
+ }
+}
+
+void write_c_string_literal(FILE *fp, ptrlen str)
+{
+ stdio_sink s;
+ stdio_sink_init(&s, fp);
+ put_c_string_literal(&s, str);
+}
diff --git a/utils/x11_dehexify.c b/utils/x11_dehexify.c
new file mode 100644
index 00000000..080f8c91
--- /dev/null
+++ b/utils/x11_dehexify.c
@@ -0,0 +1,29 @@
+/*
+ * Utility function to convert a textual representation of an X11
+ * auth hex cookie into binary auth data.
+ */
+
+#include "putty.h"
+#include "ssh.h"
+
+void *x11_dehexify(ptrlen hexpl, int *outlen)
+{
+ int len, i;
+ unsigned char *ret;
+
+ len = hexpl.len / 2;
+ ret = snewn(len, unsigned char);
+
+ for (i = 0; i < len; i++) {
+ char bytestr[3];
+ unsigned val = 0;
+ bytestr[0] = ((const char *)hexpl.ptr)[2*i];
+ bytestr[1] = ((const char *)hexpl.ptr)[2*i+1];
+ bytestr[2] = '\0';
+ sscanf(bytestr, "%x", &val);
+ ret[i] = val;
+ }
+
+ *outlen = len;
+ return ret;
+}
diff --git a/utils/x11_identify_auth_proto.c b/utils/x11_identify_auth_proto.c
new file mode 100644
index 00000000..46e26dc0
--- /dev/null
+++ b/utils/x11_identify_auth_proto.c
@@ -0,0 +1,17 @@
+/*
+ * Utility function to convert a textual representation of an X11
+ * auth protocol name into our integer protocol ids.
+ */
+
+#include "putty.h"
+#include "ssh.h"
+
+int x11_identify_auth_proto(ptrlen protoname)
+{
+ int protocol;
+
+ for (protocol = 1; protocol < lenof(x11_authnames); protocol++)
+ if (ptrlen_eq_string(protoname, x11_authnames[protocol]))
+ return protocol;
+ return -1;
+}
diff --git a/utils/x11_make_greeting.c b/utils/x11_make_greeting.c
new file mode 100644
index 00000000..4efd8e83
--- /dev/null
+++ b/utils/x11_make_greeting.c
@@ -0,0 +1,67 @@
+/*
+ * Construct an X11 greeting packet, including making up the right
+ * authorisation data.
+ */
+
+#include "putty.h"
+#include "ssh.h"
+
+void *x11_make_greeting(int endian, int protomajor, int protominor,
+ int auth_proto, const void *auth_data, int auth_len,
+ const char *peer_addr, int peer_port,
+ int *outlen)
+{
+ unsigned char *greeting;
+ unsigned char realauthdata[64];
+ const char *authname;
+ const unsigned char *authdata;
+ int authnamelen, authnamelen_pad;
+ int authdatalen, authdatalen_pad;
+ int greeting_len;
+
+ authname = x11_authnames[auth_proto];
+ authnamelen = strlen(authname);
+ authnamelen_pad = (authnamelen + 3) & ~3;
+
+ if (auth_proto == X11_MIT) {
+ authdata = auth_data;
+ authdatalen = auth_len;
+ } else if (auth_proto == X11_XDM && auth_len == 16) {
+ time_t t;
+ unsigned long peer_ip = 0;
+
+ x11_parse_ip(peer_addr, &peer_ip);
+
+ authdata = realauthdata;
+ authdatalen = 24;
+ memset(realauthdata, 0, authdatalen);
+ memcpy(realauthdata, auth_data, 8);
+ PUT_32BIT_MSB_FIRST(realauthdata+8, peer_ip);
+ PUT_16BIT_MSB_FIRST(realauthdata+12, peer_port);
+ t = time(NULL);
+ PUT_32BIT_MSB_FIRST(realauthdata+14, t);
+
+ des_encrypt_xdmauth((char *)auth_data + 9, realauthdata, authdatalen);
+ } else {
+ authdata = realauthdata;
+ authdatalen = 0;
+ }
+
+ authdatalen_pad = (authdatalen + 3) & ~3;
+ greeting_len = 12 + authnamelen_pad + authdatalen_pad;
+
+ greeting = snewn(greeting_len, unsigned char);
+ memset(greeting, 0, greeting_len);
+ greeting[0] = endian;
+ PUT_16BIT_X11(endian, greeting+2, protomajor);
+ PUT_16BIT_X11(endian, greeting+4, protominor);
+ PUT_16BIT_X11(endian, greeting+6, authnamelen);
+ PUT_16BIT_X11(endian, greeting+8, authdatalen);
+ memcpy(greeting+12, authname, authnamelen);
+ memcpy(greeting+12+authnamelen_pad, authdata, authdatalen);
+
+ smemclr(realauthdata, sizeof(realauthdata));
+
+ *outlen = greeting_len;
+ return greeting;
+}
diff --git a/utils/x11_parse_ip.c b/utils/x11_parse_ip.c
new file mode 100644
index 00000000..8f6a7bfb
--- /dev/null
+++ b/utils/x11_parse_ip.c
@@ -0,0 +1,21 @@
+/*
+ * Try to make sense of a string as an IPv4 address, for
+ * XDM-AUTHORIZATION-1 purposes.
+ */
+
+#include <stdio.h>
+
+#include "putty.h"
+#include "ssh.h"
+
+bool x11_parse_ip(const char *addr_string, unsigned long *ip)
+{
+ int i[4];
+ if (addr_string &&
+ 4 == sscanf(addr_string, "%d.%d.%d.%d", i+0, i+1, i+2, i+3)) {
+ *ip = (i[0] << 24) | (i[1] << 16) | (i[2] << 8) | i[3];
+ return true;
+ } else {
+ return false;
+ }
+}
diff --git a/utils/x11authfile.c b/utils/x11authfile.c
new file mode 100644
index 00000000..e2358a9d
--- /dev/null
+++ b/utils/x11authfile.c
@@ -0,0 +1,244 @@
+/*
+ * Functions to handle .Xauthority files.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+
+static ptrlen BinarySource_get_string_xauth(BinarySource *src)
+{
+ size_t len = get_uint16(src);
+ return get_data(src, len);
+}
+#define get_string_xauth(src) \
+ BinarySource_get_string_xauth(BinarySource_UPCAST(src))
+
+static void BinarySink_put_stringpl_xauth(BinarySink *bs, ptrlen pl)
+{
+ assert((pl.len >> 16) == 0);
+ put_uint16(bs, pl.len);
+ put_datapl(bs, pl);
+}
+#define put_stringpl_xauth(bs, ptrlen) \
+ BinarySink_put_stringpl_xauth(BinarySink_UPCAST(bs),ptrlen)
+
+void x11_get_auth_from_authfile(struct X11Display *disp,
+ 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);
+}
diff --git a/utils/x11authnames.c b/utils/x11authnames.c
new file mode 100644
index 00000000..ef3c2673
--- /dev/null
+++ b/utils/x11authnames.c
@@ -0,0 +1,9 @@
+/*
+ * Definition of the array of X11 authorisation method names.
+ */
+
+#include "putty.h"
+
+const char *const x11_authnames[X11_NAUTHS] = {
+ "", "MIT-MAGIC-COOKIE-1", "XDM-AUTHORIZATION-1"
+};
diff --git a/version.c b/version.c
deleted file mode 100644
index 620879c3..00000000
--- a/version.c
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * PuTTY version numbering
- */
-
-/*
- * The difficult part of deciding what goes in these version strings
- * is done in Buildscr, and then written into version.h. All we have
- * to do here is to drop it into variables of the right names.
- */
-
-#include "putty.h"
-#include "ssh.h"
-
-#ifdef SOURCE_COMMIT
-#include "empty.h"
-#endif
-
-#include "version.h"
-
-const char ver[] = TEXTVER;
-const char sshver[] = SSHVER;
-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/version.h b/version.h
index 74be18f4..a45eef1d 100644
--- a/version.h
+++ b/version.h
@@ -11,25 +11,3 @@
#define TEXTVER "Unidentified build"
#define SSHVER "-Unidentified-Local-Build"
#define BINARY_VERSION 0,0,0,0
-
-#ifndef SOURCE_COMMIT
-/*
- * git commit id from which this build was made. This is defined by
- * Buildscr for official builds - both source archives and prebuilt
- * binaries - in the course of overwriting this file as described
- * above. But we put it here under ifdef, so that it can also be
- * passed in on the command line for Unix local development builds,
- * which I treat specially because Unix developers - e.g. me - are
- * quite likely to run 'make install' straight out of their dev
- * directory so as to use the bleeding-edge code for day-to-day
- * running.
- *
- * Windows doesn't really need the same treatment, because the easiest
- * way to install a build properly on Windows is to run the installer,
- * and the easiest way to do that is to run Buildscr, which will
- * populate this field its own way. It's only the Unix automake build
- * where you might go straight from local 'make' to 'make install'
- * without going through Buildscr.
- */
-#define SOURCE_COMMIT "unavailable"
-#endif
diff --git a/wcwidth.c b/wcwidth.c
deleted file mode 100644
index 6468fedd..00000000
--- a/wcwidth.c
+++ /dev/null
@@ -1,558 +0,0 @@
-/*
- * This is an implementation of wcwidth() and wcswidth() (defined in
- * IEEE Std 1002.1-2001) for Unicode.
- *
- * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
- * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
- *
- * In fixed-width output devices, Latin characters all occupy a single
- * "cell" position of equal width, whereas ideographic CJK characters
- * occupy two such cells. Interoperability between terminal-line
- * applications and (teletype-style) character terminals using the
- * UTF-8 encoding requires agreement on which character should advance
- * the cursor by how many cell positions. No established formal
- * standards exist at present on which Unicode character shall occupy
- * how many cell positions on character terminals. These routines are
- * a first attempt of defining such behavior based on simple rules
- * applied to data provided by the Unicode Consortium.
- *
- * For some graphical characters, the Unicode standard explicitly
- * defines a character-cell width via the definition of the East Asian
- * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
- * In all these cases, there is no ambiguity about which width a
- * terminal shall use. For characters in the East Asian Ambiguous (A)
- * class, the width choice depends purely on a preference of backward
- * compatibility with either historic CJK or Western practice.
- * Choosing single-width for these characters is easy to justify as
- * the appropriate long-term solution, as the CJK practice of
- * displaying these characters as double-width comes from historic
- * implementation simplicity (8-bit encoded characters were displayed
- * single-width and 16-bit ones double-width, even for Greek,
- * Cyrillic, etc.) and not any typographic considerations.
- *
- * Much less clear is the choice of width for the Not East Asian
- * (Neutral) class. Existing practice does not dictate a width for any
- * of these characters. It would nevertheless make sense
- * typographically to allocate two character cells to characters such
- * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
- * represented adequately with a single-width glyph. The following
- * routines at present merely assign a single-cell width to all
- * neutral characters, in the interest of simplicity. This is not
- * entirely satisfactory and should be reconsidered before
- * establishing a formal standard in this area. At the moment, the
- * decision which Not East Asian (Neutral) characters should be
- * represented by double-width glyphs cannot yet be answered by
- * applying a simple rule from the Unicode database content. Setting
- * up a proper standard for the behavior of UTF-8 character terminals
- * will require a careful analysis not only of each Unicode character,
- * but also of each presentation form, something the author of these
- * routines has avoided to do so far.
- *
- * http://www.unicode.org/unicode/reports/tr11/
- *
- * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
- *
- * Permission to use, copy, modify, and distribute this software
- * for any purpose and without fee is hereby granted. The author
- * disclaims all warranties with regard to this software.
- *
- * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
- */
-
-#include <wchar.h>
-
-#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/wildcard.c b/wildcard.c
deleted file mode 100644
index 697feb9a..00000000
--- a/wildcard.c
+++ /dev/null
@@ -1,486 +0,0 @@
-/*
- * Wildcard matching engine for use with SFTP-based file transfer
- * programs (PSFTP, new-look PSCP): since SFTP has no notion of
- * getting the remote side to do globbing (and rightly so) we have
- * to do it locally, by retrieving all the filenames in a directory
- * and checking each against the wildcard pattern.
- */
-
-#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-
-#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/windows/CMakeLists.txt b/windows/CMakeLists.txt
new file mode 100644
index 00000000..d34b4106
--- /dev/null
+++ b/windows/CMakeLists.txt
@@ -0,0 +1,186 @@
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
+
+add_sources_from_current_dir(utils
+ utils/agent_mutex_name.c
+ utils/agent_named_pipe_name.c
+ utils/arm_arch_queries.c
+ utils/aux_match_opt.c
+ utils/centre_window.c
+ utils/cryptoapi.c
+ utils/defaults.c
+ utils/dll_hijacking_protection.c
+ utils/dputs.c
+ utils/escape_registry_key.c
+ utils/filename.c
+ utils/fontspec.c
+ utils/getdlgitemtext_alloc.c
+ utils/get_system_dir.c
+ utils/get_username.c
+ utils/interprocess_mutex.c
+ utils/is_console_handle.c
+ utils/load_system32_dll.c
+ utils/ltime.c
+ utils/makedlgitemborderless.c
+ utils/make_spr_sw_abort_winerror.c
+ utils/message_box.c
+ utils/minefield.c
+ utils/open_for_write_would_lose_data.c
+ utils/pgp_fingerprints_msgbox.c
+ utils/platform_get_x_display.c
+ utils/registry.c
+ utils/request_file.c
+ utils/screenshot.c
+ utils/security.c
+ utils/shinydialogbox.c
+ utils/split_into_argv.c
+ utils/version.c
+ utils/win_strerror.c
+ unicode.c)
+if(NOT HAVE_STRTOUMAX)
+ add_sources_from_current_dir(utils utils/strtoumax.c)
+endif()
+add_sources_from_current_dir(eventloop
+ cliloop.c handle-wait.c)
+add_sources_from_current_dir(console
+ select-cli.c nohelp.c console.c)
+add_sources_from_current_dir(settings
+ storage.c)
+add_sources_from_current_dir(network
+ network.c handle-socket.c named-pipe-client.c named-pipe-server.c local-proxy.c x11.c)
+add_sources_from_current_dir(sshcommon
+ noise.c)
+add_sources_from_current_dir(sshclient
+ agent-client.c gss.c sharing.c)
+add_sources_from_current_dir(sftpclient
+ sftp.c)
+add_sources_from_current_dir(otherbackends
+ serial.c)
+add_sources_from_current_dir(agent
+ agent-client.c)
+add_sources_from_current_dir(guiterminal
+ dialog.c controls.c config.c printing.c jump-list.c sizetip.c)
+add_dependencies(guiterminal generated_licence_h) # dialog.c uses licence.h
+
+# This object awkwardly needs to live in the network library as well
+# as the eventloop library, in case it didn't get pulled in from the
+# latter before handle-socket.c needed it.
+add_library(handle-io OBJECT
+ handle-io.c)
+target_sources(eventloop PRIVATE $<TARGET_OBJECTS:handle-io>)
+target_sources(network PRIVATE $<TARGET_OBJECTS:handle-io>)
+
+add_library(guimisc STATIC
+ select-gui.c)
+
+add_executable(pageant
+ pageant.c
+ help.c
+ pageant.rc)
+add_dependencies(pageant generated_licence_h)
+target_link_libraries(pageant
+ guimisc eventloop agent network crypto utils
+ ${platform_libraries})
+set_target_properties(pageant PROPERTIES
+ WIN32_EXECUTABLE ON
+ LINK_FLAGS "${LFLAG_MANIFEST_NO}")
+installed_program(pageant)
+
+add_sources_from_current_dir(plink no-jump-list.c nohelp.c plink.rc)
+add_dependencies(plink generated_licence_h)
+
+add_sources_from_current_dir(pscp no-jump-list.c nohelp.c pscp.rc)
+add_dependencies(pscp generated_licence_h)
+
+add_sources_from_current_dir(psftp no-jump-list.c nohelp.c psftp.rc)
+add_dependencies(psftp generated_licence_h)
+
+add_sources_from_current_dir(psocks nohelp.c)
+
+add_executable(putty
+ window.c
+ putty.c
+ help.c
+ putty.rc)
+be_list(putty PuTTY SSH SERIAL OTHERBACKENDS)
+add_dependencies(putty generated_licence_h)
+target_link_libraries(putty
+ guiterminal guimisc eventloop sshclient otherbackends settings network crypto
+ utils
+ ${platform_libraries})
+set_target_properties(putty PROPERTIES
+ WIN32_EXECUTABLE ON
+ LINK_FLAGS "${LFLAG_MANIFEST_NO}")
+installed_program(putty)
+
+add_executable(puttytel
+ window.c
+ putty.c
+ help.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-gss.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-rand.c
+ ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c
+ ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c
+ puttytel.rc)
+be_list(puttytel PuTTYtel SERIAL OTHERBACKENDS)
+add_dependencies(puttytel generated_licence_h)
+target_link_libraries(puttytel
+ guiterminal guimisc eventloop otherbackends settings network utils
+ ${platform_libraries})
+set_target_properties(puttytel PROPERTIES
+ WIN32_EXECUTABLE ON
+ LINK_FLAGS "${LFLAG_MANIFEST_NO}")
+installed_program(puttytel)
+
+add_executable(puttygen
+ puttygen.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-timing.c
+ noise.c
+ no-jump-list.c
+ storage.c
+ help.c
+ ${CMAKE_SOURCE_DIR}/sshpubk.c
+ ${CMAKE_SOURCE_DIR}/sshrand.c
+ controls.c
+ puttygen.rc)
+add_dependencies(puttygen generated_licence_h)
+target_link_libraries(puttygen
+ keygen guimisc crypto utils
+ ${platform_libraries})
+set_target_properties(puttygen PROPERTIES
+ WIN32_EXECUTABLE ON
+ LINK_FLAGS "${LFLAG_MANIFEST_NO}")
+installed_program(puttygen)
+
+if(HAVE_CONPTY)
+ add_executable(pterm
+ window.c
+ pterm.c
+ help.c
+ conpty.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-gss.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c
+ ${CMAKE_SOURCE_DIR}/stubs/no-rand.c
+ ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c
+ pterm.rc)
+ be_list(pterm pterm)
+ add_dependencies(pterm generated_licence_h)
+ target_link_libraries(pterm
+ guiterminal guimisc eventloop settings network utils
+ ${platform_libraries})
+ set_target_properties(pterm PROPERTIES
+ WIN32_EXECUTABLE ON
+ LINK_FLAGS "${LFLAG_MANIFEST_NO}")
+ installed_program(pterm)
+else()
+ message("ConPTY not available; cannot build Windows pterm")
+endif()
+
+add_executable(test_split_into_argv
+ utils/split_into_argv.c)
+target_compile_definitions(test_split_into_argv PRIVATE TEST)
+target_link_libraries(test_split_into_argv utils ${platform_libraries})
+
+add_executable(test_screenshot
+ test_screenshot.c)
+target_link_libraries(test_screenshot utils ${platform_libraries})
diff --git a/windows/agent-client.c b/windows/agent-client.c
new file mode 100644
index 00000000..168465e5
--- /dev/null
+++ b/windows/agent-client.c
@@ -0,0 +1,293 @@
+/*
+ * Pageant client code.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "putty.h"
+#include "pageant.h" /* for AGENT_MAX_MSGLEN */
+
+#include "security-api.h"
+#include "cryptoapi.h"
+
+static bool wm_copydata_agent_exists(void)
+{
+ HWND hwnd;
+ hwnd = FindWindow("Pageant", "Pageant");
+ if (!hwnd)
+ return false;
+ else
+ return true;
+}
+
+static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen)
+{
+ HWND hwnd;
+ char *mapname;
+ HANDLE filemap;
+ unsigned char *p, *ret;
+ int id, retlen;
+ COPYDATASTRUCT cds;
+ SECURITY_ATTRIBUTES sa, *psa;
+ PSECURITY_DESCRIPTOR psd = NULL;
+ PSID usersid = NULL;
+
+ *out = NULL;
+ *outlen = 0;
+
+ if (query->len > AGENT_MAX_MSGLEN)
+ return; /* query too large */
+
+ hwnd = FindWindow("Pageant", "Pageant");
+ if (!hwnd)
+ return; /* *out == NULL, so failure */
+ mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId());
+
+ psa = NULL;
+ if (got_advapi()) {
+ /*
+ * Make the file mapping we create for communication with
+ * Pageant owned by the user SID rather than the default. This
+ * should make communication between processes with slightly
+ * different contexts more reliable: in particular, command
+ * prompts launched as administrator should still be able to
+ * run PSFTPs which refer back to the owning user's
+ * unprivileged Pageant.
+ */
+ usersid = get_user_sid();
+
+ if (usersid) {
+ psd = (PSECURITY_DESCRIPTOR)
+ LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
+ if (psd) {
+ if (p_InitializeSecurityDescriptor(
+ psd, SECURITY_DESCRIPTOR_REVISION) &&
+ p_SetSecurityDescriptorOwner(psd, usersid, false)) {
+ sa.nLength = sizeof(sa);
+ sa.bInheritHandle = true;
+ sa.lpSecurityDescriptor = psd;
+ psa = &sa;
+ } else {
+ LocalFree(psd);
+ psd = NULL;
+ }
+ }
+ }
+ }
+
+ filemap = CreateFileMapping(INVALID_HANDLE_VALUE, psa, PAGE_READWRITE,
+ 0, AGENT_MAX_MSGLEN, mapname);
+ if (filemap == NULL || filemap == INVALID_HANDLE_VALUE) {
+ sfree(mapname);
+ return; /* *out == NULL, so failure */
+ }
+ p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
+ strbuf_finalise_agent_query(query);
+ memcpy(p, query->s, query->len);
+ cds.dwData = AGENT_COPYDATA_ID;
+ cds.cbData = 1 + strlen(mapname);
+ cds.lpData = mapname;
+
+ /*
+ * The user either passed a null callback (indicating that the
+ * query is required to be synchronous) or CreateThread failed.
+ * Either way, we need a synchronous request.
+ */
+ id = SendMessage(hwnd, WM_COPYDATA, (WPARAM) NULL, (LPARAM) &cds);
+ if (id > 0) {
+ uint32_t length_field = GET_32BIT_MSB_FIRST(p);
+ if (length_field > 0 && length_field <= AGENT_MAX_MSGLEN - 4) {
+ retlen = length_field + 4;
+ ret = snewn(retlen, unsigned char);
+ memcpy(ret, p, retlen);
+ *out = ret;
+ *outlen = retlen;
+ } else {
+ /*
+ * If we get here, we received an out-of-range length
+ * field, either without space for a message type code or
+ * overflowing the FileMapping.
+ *
+ * Treat this as if Pageant didn't answer at all - which
+ * actually means we do nothing, and just don't fill in
+ * out and outlen.
+ */
+ }
+ }
+ UnmapViewOfFile(p);
+ CloseHandle(filemap);
+ sfree(mapname);
+ if (psd)
+ LocalFree(psd);
+}
+
+Socket *agent_connect(Plug *plug)
+{
+ char *pipename = agent_named_pipe_name();
+ Socket *s = new_named_pipe_client(pipename, plug);
+ sfree(pipename);
+ return s;
+}
+
+static bool named_pipe_agent_exists(void)
+{
+ char *pipename = agent_named_pipe_name();
+ WIN32_FIND_DATA data;
+ HANDLE ffh = FindFirstFile(pipename, &data);
+ sfree(pipename);
+ if (ffh == INVALID_HANDLE_VALUE)
+ return false;
+ FindClose(ffh);
+ return true;
+}
+
+bool agent_exists(void)
+{
+ return named_pipe_agent_exists() || wm_copydata_agent_exists();
+}
+
+struct agent_pending_query {
+ struct handle *handle;
+ HANDLE os_handle;
+ strbuf *response;
+ void (*callback)(void *, void *, int);
+ void *callback_ctx;
+};
+
+static int named_pipe_agent_accumulate_response(
+ strbuf *sb, const void *data, size_t len)
+{
+ put_data(sb, data, len);
+ if (sb->len >= 4) {
+ uint32_t length_field = GET_32BIT_MSB_FIRST(sb->u);
+ if (length_field > AGENT_MAX_MSGLEN)
+ return -1; /* badly formatted message */
+
+ int overall_length = length_field + 4;
+ if (sb->len >= overall_length)
+ return overall_length;
+ }
+
+ return 0; /* not done yet */
+}
+
+static size_t named_pipe_agent_gotdata(
+ struct handle *h, const void *data, size_t len, int err)
+{
+ agent_pending_query *pq = handle_get_privdata(h);
+
+ if (err || len == 0) {
+ pq->callback(pq->callback_ctx, NULL, 0);
+ agent_cancel_query(pq);
+ return 0;
+ }
+
+ int status = named_pipe_agent_accumulate_response(pq->response, data, len);
+ if (status == -1) {
+ pq->callback(pq->callback_ctx, NULL, 0);
+ agent_cancel_query(pq);
+ } else if (status > 0) {
+ void *response_buf = strbuf_to_str(pq->response);
+ pq->response = NULL;
+ pq->callback(pq->callback_ctx, response_buf, status);
+ agent_cancel_query(pq);
+ }
+ return 0;
+}
+
+static agent_pending_query *named_pipe_agent_query(
+ strbuf *query, void **out, int *outlen,
+ void (*callback)(void *, void *, int), void *callback_ctx)
+{
+ agent_pending_query *pq = NULL;
+ char *err = NULL, *pipename = NULL;
+ strbuf *sb = NULL;
+ HANDLE pipehandle;
+
+ pipename = agent_named_pipe_name();
+ pipehandle = connect_to_named_pipe(pipename, &err);
+ if (pipehandle == INVALID_HANDLE_VALUE)
+ goto failure;
+
+ strbuf_finalise_agent_query(query);
+
+ for (DWORD done = 0; done < query->len ;) {
+ DWORD nwritten;
+ bool ret = WriteFile(pipehandle, query->s + done, query->len - done,
+ &nwritten, NULL);
+ if (!ret)
+ goto failure;
+
+ done += nwritten;
+ }
+
+ if (!callback) {
+ int status;
+
+ sb = strbuf_new_nm();
+ do {
+ char buf[1024];
+ DWORD nread;
+ bool ret = ReadFile(pipehandle, buf, sizeof(buf), &nread, NULL);
+ if (!ret)
+ goto failure;
+ status = named_pipe_agent_accumulate_response(sb, buf, nread);
+ } while (status == 0);
+
+ if (status == -1)
+ goto failure;
+
+ *out = strbuf_to_str(sb);
+ *outlen = status;
+ sb = NULL;
+ pq = NULL;
+ goto out;
+ }
+
+ pq = snew(agent_pending_query);
+ pq->handle = handle_input_new(pipehandle, named_pipe_agent_gotdata, pq, 0);
+ pq->os_handle = pipehandle;
+ pipehandle = INVALID_HANDLE_VALUE; /* prevent it being closed below */
+ pq->response = strbuf_new_nm();
+ pq->callback = callback;
+ pq->callback_ctx = callback_ctx;
+ goto out;
+
+ failure:
+ *out = NULL;
+ *outlen = 0;
+ pq = NULL;
+
+ out:
+ sfree(err);
+ sfree(pipename);
+ if (pipehandle != INVALID_HANDLE_VALUE)
+ CloseHandle(pipehandle);
+ if (sb)
+ strbuf_free(sb);
+ return pq;
+}
+
+void agent_cancel_query(agent_pending_query *pq)
+{
+ handle_free(pq->handle);
+ CloseHandle(pq->os_handle);
+ if (pq->response)
+ strbuf_free(pq->response);
+ sfree(pq);
+}
+
+agent_pending_query *agent_query(
+ strbuf *query, void **out, int *outlen,
+ void (*callback)(void *, void *, int), void *callback_ctx)
+{
+ agent_pending_query *pq = named_pipe_agent_query(
+ query, out, outlen, callback, callback_ctx);
+ if (pq || *out)
+ return pq;
+
+ wm_copydata_agent_query(query, out, outlen);
+ return NULL;
+}
diff --git a/windows/cliloop.c b/windows/cliloop.c
new file mode 100644
index 00000000..eced54ca
--- /dev/null
+++ b/windows/cliloop.c
@@ -0,0 +1,134 @@
+#include "putty.h"
+
+void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx)
+{
+ SOCKET *sklist = NULL;
+ size_t skcount = 0, sksize = 0;
+ unsigned long now, next, then;
+ now = GETTICKCOUNT();
+
+ while (true) {
+ DWORD n;
+ DWORD ticks;
+
+ const HANDLE *extra_handles = NULL;
+ size_t n_extra_handles = 0;
+ if (!pre(ctx, &extra_handles, &n_extra_handles))
+ break;
+
+ if (toplevel_callback_pending()) {
+ ticks = 0;
+ next = now;
+ } else if (run_timers(now, &next)) {
+ then = now;
+ now = GETTICKCOUNT();
+ if (now - then > next - then)
+ ticks = 0;
+ else
+ ticks = next - now;
+ } else {
+ ticks = INFINITE;
+ /* no need to initialise next here because we can never
+ * get WAIT_TIMEOUT */
+ }
+
+ HandleWaitList *hwl = get_handle_wait_list();
+ size_t winselcli_index = -(size_t)1;
+ size_t extra_base = hwl->nhandles;
+ if (winselcli_event != INVALID_HANDLE_VALUE) {
+ assert(extra_base < MAXIMUM_WAIT_OBJECTS);
+ winselcli_index = extra_base++;
+ hwl->handles[winselcli_index] = winselcli_event;
+ }
+ size_t total_handles = extra_base + n_extra_handles;
+ assert(total_handles < MAXIMUM_WAIT_OBJECTS);
+ for (size_t i = 0; i < n_extra_handles; i++)
+ hwl->handles[extra_base + i] = extra_handles[i];
+
+ n = WaitForMultipleObjects(total_handles, hwl->handles, false, ticks);
+
+ size_t extra_handle_index = n_extra_handles;
+
+ if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)hwl->nhandles) {
+ handle_wait_activate(hwl, n - WAIT_OBJECT_0);
+ } else if (winselcli_event != INVALID_HANDLE_VALUE &&
+ n == WAIT_OBJECT_0 + winselcli_index) {
+ WSANETWORKEVENTS things;
+ SOCKET socket;
+ int i, socketstate;
+
+ /*
+ * We must not call select_result() for any socket
+ * until we have finished enumerating within the tree.
+ * This is because select_result() may close the socket
+ * and modify the tree.
+ */
+ /* Count the active sockets. */
+ i = 0;
+ for (socket = first_socket(&socketstate);
+ socket != INVALID_SOCKET;
+ socket = next_socket(&socketstate)) i++;
+
+ /* Expand the buffer if necessary. */
+ sgrowarray(sklist, sksize, i);
+
+ /* Retrieve the sockets into sklist. */
+ skcount = 0;
+ for (socket = first_socket(&socketstate);
+ socket != INVALID_SOCKET;
+ socket = next_socket(&socketstate)) {
+ sklist[skcount++] = socket;
+ }
+
+ /* Now we're done enumerating; go through the list. */
+ for (i = 0; i < skcount; i++) {
+ WPARAM wp;
+ socket = sklist[i];
+ wp = (WPARAM) socket;
+ if (!p_WSAEnumNetworkEvents(socket, NULL, &things)) {
+ static const struct { int bit, mask; } eventtypes[] = {
+ {FD_CONNECT_BIT, FD_CONNECT},
+ {FD_READ_BIT, FD_READ},
+ {FD_CLOSE_BIT, FD_CLOSE},
+ {FD_OOB_BIT, FD_OOB},
+ {FD_WRITE_BIT, FD_WRITE},
+ {FD_ACCEPT_BIT, FD_ACCEPT},
+ };
+ int e;
+
+ noise_ultralight(NOISE_SOURCE_IOID, socket);
+
+ for (e = 0; e < lenof(eventtypes); e++)
+ if (things.lNetworkEvents & eventtypes[e].mask) {
+ LPARAM lp;
+ int err = things.iErrorCode[eventtypes[e].bit];
+ lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err);
+ select_result(wp, lp);
+ }
+ }
+ }
+ } else if (n >= WAIT_OBJECT_0 + extra_base &&
+ n < WAIT_OBJECT_0 + extra_base + n_extra_handles) {
+ extra_handle_index = n - (WAIT_OBJECT_0 + extra_base);
+ }
+
+ run_toplevel_callbacks();
+
+ if (n == WAIT_TIMEOUT) {
+ now = next;
+ } else {
+ now = GETTICKCOUNT();
+ }
+
+ handle_wait_list_free(hwl);
+
+ if (!post(ctx, extra_handle_index))
+ break;
+ }
+
+ sfree(sklist);
+}
+
+bool cliloop_null_pre(void *vctx, const HANDLE **eh, size_t *neh)
+{ return true; }
+bool cliloop_null_post(void *vctx, size_t ehi) { return true; }
diff --git a/windows/config.c b/windows/config.c
new file mode 100644
index 00000000..fc9070bf
--- /dev/null
+++ b/windows/config.c
@@ -0,0 +1,384 @@
+/*
+ * config.c - the Windows-specific parts of the PuTTY configuration
+ * box.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "dialog.h"
+#include "storage.h"
+
+static void about_handler(dlgcontrol *ctrl, dlgparam *dlg,
+ void *data, int event)
+{
+ HWND *hwndp = (HWND *)ctrl->context.p;
+
+ if (event == EVENT_ACTION) {
+ modal_about_box(*hwndp);
+ }
+}
+
+static void help_handler(dlgcontrol *ctrl, dlgparam *dlg,
+ void *data, int event)
+{
+ HWND *hwndp = (HWND *)ctrl->context.p;
+
+ if (event == EVENT_ACTION) {
+ show_help(*hwndp);
+ }
+}
+
+static void variable_pitch_handler(dlgcontrol *ctrl, dlgparam *dlg,
+ void *data, int event)
+{
+ if (event == EVENT_REFRESH) {
+ dlg_checkbox_set(ctrl, dlg, !dlg_get_fixed_pitch_flag(dlg));
+ } else if (event == EVENT_VALCHANGE) {
+ dlg_set_fixed_pitch_flag(dlg, !dlg_checkbox_get(ctrl, dlg));
+ }
+}
+
+void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help,
+ bool midsession, int protocol)
+{
+ const struct BackendVtable *backvt;
+ bool resize_forbidden = false;
+ struct controlset *s;
+ dlgcontrol *c;
+ char *str;
+
+ if (!midsession) {
+ /*
+ * Add the About and Help buttons to the standard panel.
+ */
+ s = ctrl_getset(b, "", "", "");
+ c = ctrl_pushbutton(s, "About", 'a', HELPCTX(no_help),
+ about_handler, P(hwndp));
+ c->column = 0;
+ if (has_help) {
+ c = ctrl_pushbutton(s, "Help", 'h', HELPCTX(no_help),
+ help_handler, P(hwndp));
+ c->column = 1;
+ }
+ }
+
+ /*
+ * Full-screen mode is a Windows peculiarity; hence
+ * scrollbar_in_fullscreen is as well.
+ */
+ s = ctrl_getset(b, "Window", "scrollback",
+ "Control the scrollback in the window");
+ ctrl_checkbox(s, "Display scrollbar in full screen mode", 'i',
+ HELPCTX(window_scrollback),
+ conf_checkbox_handler,
+ I(CONF_scrollbar_in_fullscreen));
+ /*
+ * Really this wants to go just after `Display scrollbar'. See
+ * if we can find that control, and do some shuffling.
+ */
+ {
+ int i;
+ for (i = 0; i < s->ncontrols; i++) {
+ c = s->ctrls[i];
+ if (c->type == CTRL_CHECKBOX &&
+ c->context.i == CONF_scrollbar) {
+ /*
+ * Control i is the scrollbar checkbox.
+ * Control s->ncontrols-1 is the scrollbar-in-FS one.
+ */
+ if (i < s->ncontrols-2) {
+ c = s->ctrls[s->ncontrols-1];
+ memmove(s->ctrls+i+2, s->ctrls+i+1,
+ (s->ncontrols-i-2)*sizeof(dlgcontrol *));
+ s->ctrls[i+1] = c;
+ }
+ break;
+ }
+ }
+ }
+
+ /*
+ * Windows has the AltGr key, which has various Windows-
+ * specific options.
+ */
+ s = ctrl_getset(b, "Terminal/Keyboard", "features",
+ "Enable extra keyboard features:");
+ ctrl_checkbox(s, "AltGr acts as Compose key", 't',
+ HELPCTX(keyboard_compose),
+ conf_checkbox_handler, I(CONF_compose_key));
+ ctrl_checkbox(s, "Control-Alt is different from AltGr", 'd',
+ HELPCTX(keyboard_ctrlalt),
+ conf_checkbox_handler, I(CONF_ctrlaltkeys));
+
+ /*
+ * Windows allows an arbitrary .WAV to be played as a bell, and
+ * also the use of the PC speaker. For this we must search the
+ * existing controlset for the radio-button set controlling the
+ * `beep' option, and add extra buttons to it.
+ *
+ * Note that although this _looks_ like a hideous hack, it's
+ * actually all above board. The well-defined interface to the
+ * per-platform dialog box code is the _data structures_ `union
+ * control', `struct controlset' and so on; so code like this
+ * that reaches into those data structures and changes bits of
+ * them is perfectly legitimate and crosses no boundaries. All
+ * the ctrl_* routines that create most of the controls are
+ * convenient shortcuts provided on the cross-platform side of
+ * the interface, and template creation code is under no actual
+ * obligation to use them.
+ */
+ s = ctrl_getset(b, "Terminal/Bell", "style", "Set the style of bell");
+ {
+ int i;
+ for (i = 0; i < s->ncontrols; i++) {
+ c = s->ctrls[i];
+ if (c->type == CTRL_RADIO &&
+ c->context.i == CONF_beep) {
+ assert(c->handler == conf_radiobutton_handler);
+ c->radio.nbuttons += 2;
+ c->radio.buttons =
+ sresize(c->radio.buttons, c->radio.nbuttons, char *);
+ c->radio.buttons[c->radio.nbuttons-1] =
+ dupstr("Play a custom sound file");
+ c->radio.buttons[c->radio.nbuttons-2] =
+ dupstr("Beep using the PC speaker");
+ c->radio.buttondata =
+ sresize(c->radio.buttondata, c->radio.nbuttons, intorptr);
+ c->radio.buttondata[c->radio.nbuttons-1] = I(BELL_WAVEFILE);
+ c->radio.buttondata[c->radio.nbuttons-2] = I(BELL_PCSPEAKER);
+ if (c->radio.shortcuts) {
+ c->radio.shortcuts =
+ sresize(c->radio.shortcuts, c->radio.nbuttons, char);
+ c->radio.shortcuts[c->radio.nbuttons-1] = NO_SHORTCUT;
+ c->radio.shortcuts[c->radio.nbuttons-2] = NO_SHORTCUT;
+ }
+ break;
+ }
+ }
+ }
+ ctrl_filesel(s, "Custom sound file to play as a bell:", NO_SHORTCUT,
+ FILTER_WAVE_FILES, false, "Select bell sound file",
+ HELPCTX(bell_style),
+ conf_filesel_handler, I(CONF_bell_wavefile));
+
+ /*
+ * While we've got this box open, taskbar flashing on a bell is
+ * also Windows-specific.
+ */
+ ctrl_radiobuttons(s, "Taskbar/caption indication on bell:", 'i', 3,
+ HELPCTX(bell_taskbar),
+ conf_radiobutton_handler,
+ I(CONF_beep_ind),
+ "Disabled", I(B_IND_DISABLED),
+ "Flashing", I(B_IND_FLASH),
+ "Steady", I(B_IND_STEADY));
+
+ /*
+ * The sunken-edge border is a Windows GUI feature.
+ */
+ s = ctrl_getset(b, "Window/Appearance", "border",
+ "Adjust the window border");
+ ctrl_checkbox(s, "Sunken-edge border (slightly thicker)", 's',
+ HELPCTX(appearance_border),
+ conf_checkbox_handler, I(CONF_sunken_edge));
+
+ /*
+ * Configurable font quality settings for Windows.
+ */
+ s = ctrl_getset(b, "Window/Appearance", "font",
+ "Font settings");
+ ctrl_checkbox(s, "Allow selection of variable-pitch fonts", NO_SHORTCUT,
+ HELPCTX(appearance_font), variable_pitch_handler, I(0));
+ ctrl_radiobuttons(s, "Font quality:", 'q', 2,
+ HELPCTX(appearance_font),
+ conf_radiobutton_handler,
+ I(CONF_font_quality),
+ "Antialiased", I(FQ_ANTIALIASED),
+ "Non-Antialiased", I(FQ_NONANTIALIASED),
+ "ClearType", I(FQ_CLEARTYPE),
+ "Default", I(FQ_DEFAULT));
+
+ /*
+ * Cyrillic Lock is a horrid misfeature even on Windows, and
+ * the least we can do is ensure it never makes it to any other
+ * platform (at least unless someone fixes it!).
+ */
+ s = ctrl_getset(b, "Window/Translation", "tweaks", NULL);
+ ctrl_checkbox(s, "Caps Lock acts as Cyrillic switch", 's',
+ HELPCTX(translation_cyrillic),
+ conf_checkbox_handler,
+ I(CONF_xlat_capslockcyr));
+
+ /*
+ * On Windows we can use but not enumerate translation tables
+ * from the operating system. Briefly document this.
+ */
+ s = ctrl_getset(b, "Window/Translation", "trans",
+ "Character set translation on received data");
+ ctrl_text(s, "(Codepages supported by Windows but not listed here, "
+ "such as CP866 on many systems, can be entered manually)",
+ HELPCTX(translation_codepage));
+
+ /*
+ * Windows has the weird OEM font mode, which gives us some
+ * additional options when working with line-drawing
+ * characters.
+ */
+ str = dupprintf("Adjust how %s displays line drawing characters", appname);
+ s = ctrl_getset(b, "Window/Translation", "linedraw", str);
+ sfree(str);
+ {
+ int i;
+ for (i = 0; i < s->ncontrols; i++) {
+ c = s->ctrls[i];
+ if (c->type == CTRL_RADIO &&
+ c->context.i == CONF_vtmode) {
+ assert(c->handler == conf_radiobutton_handler);
+ c->radio.nbuttons += 3;
+ c->radio.buttons =
+ sresize(c->radio.buttons, c->radio.nbuttons, char *);
+ c->radio.buttons[c->radio.nbuttons-3] =
+ dupstr("Font has XWindows encoding");
+ c->radio.buttons[c->radio.nbuttons-2] =
+ dupstr("Use font in both ANSI and OEM modes");
+ c->radio.buttons[c->radio.nbuttons-1] =
+ dupstr("Use font in OEM mode only");
+ c->radio.buttondata =
+ sresize(c->radio.buttondata, c->radio.nbuttons, intorptr);
+ c->radio.buttondata[c->radio.nbuttons-3] = I(VT_XWINDOWS);
+ c->radio.buttondata[c->radio.nbuttons-2] = I(VT_OEMANSI);
+ c->radio.buttondata[c->radio.nbuttons-1] = I(VT_OEMONLY);
+ if (!c->radio.shortcuts) {
+ int j;
+ c->radio.shortcuts = snewn(c->radio.nbuttons, char);
+ for (j = 0; j < c->radio.nbuttons; j++)
+ c->radio.shortcuts[j] = NO_SHORTCUT;
+ } else {
+ c->radio.shortcuts = sresize(c->radio.shortcuts,
+ c->radio.nbuttons, char);
+ }
+ c->radio.shortcuts[c->radio.nbuttons-3] = 'x';
+ c->radio.shortcuts[c->radio.nbuttons-2] = 'b';
+ c->radio.shortcuts[c->radio.nbuttons-1] = 'e';
+ break;
+ }
+ }
+ }
+
+ /*
+ * RTF paste is Windows-specific.
+ */
+ s = ctrl_getset(b, "Window/Selection/Copy", "format",
+ "Formatting of copied characters");
+ ctrl_checkbox(s, "Copy to clipboard in RTF as well as plain text", 'f',
+ HELPCTX(copy_rtf),
+ conf_checkbox_handler, I(CONF_rtf_paste));
+
+ /*
+ * Windows often has no middle button, so we supply a selection
+ * mode in which the more critical Paste action is available on
+ * the right button instead.
+ */
+ s = ctrl_getset(b, "Window/Selection", "mouse",
+ "Control use of mouse");
+ ctrl_radiobuttons(s, "Action of mouse buttons:", 'm', 1,
+ HELPCTX(selection_buttons),
+ conf_radiobutton_handler,
+ I(CONF_mouse_is_xterm),
+ "Windows (Middle extends, Right brings up menu)", I(2),
+ "Compromise (Middle extends, Right pastes)", I(0),
+ "xterm (Right extends, Middle pastes)", I(1));
+ /*
+ * This really ought to go at the _top_ of its box, not the
+ * bottom, so we'll just do some shuffling now we've set it
+ * up...
+ */
+ c = s->ctrls[s->ncontrols-1]; /* this should be the new control */
+ memmove(s->ctrls+1, s->ctrls, (s->ncontrols-1)*sizeof(dlgcontrol *));
+ s->ctrls[0] = c;
+
+ /*
+ * Logical palettes don't even make sense anywhere except Windows.
+ */
+ s = ctrl_getset(b, "Window/Colours", "general",
+ "General options for colour usage");
+ ctrl_checkbox(s, "Attempt to use logical palettes", 'l',
+ HELPCTX(colours_logpal),
+ conf_checkbox_handler, I(CONF_try_palette));
+ ctrl_checkbox(s, "Use system colours", 's',
+ HELPCTX(colours_system),
+ conf_checkbox_handler, I(CONF_system_colour));
+
+
+ /*
+ * Resize-by-changing-font is a Windows insanity.
+ */
+
+ backvt = backend_vt_from_proto(protocol);
+ if (backvt)
+ resize_forbidden = (backvt->flags & BACKEND_RESIZE_FORBIDDEN);
+ if (!midsession || !resize_forbidden) {
+ s = ctrl_getset(b, "Window", "size", "Set the size of the window");
+ ctrl_radiobuttons(s, "When window is resized:", 'z', 1,
+ HELPCTX(window_resize),
+ conf_radiobutton_handler,
+ I(CONF_resize_action),
+ "Change the number of rows and columns", I(RESIZE_TERM),
+ "Change the size of the font", I(RESIZE_FONT),
+ "Change font size only when maximised", I(RESIZE_EITHER),
+ "Forbid resizing completely", I(RESIZE_DISABLED));
+ }
+
+ /*
+ * Most of the Window/Behaviour stuff is there to mimic Windows
+ * conventions which PuTTY can optionally disregard. Hence,
+ * most of these options are Windows-specific.
+ */
+ s = ctrl_getset(b, "Window/Behaviour", "main", NULL);
+ ctrl_checkbox(s, "Window closes on ALT-F4", '4',
+ HELPCTX(behaviour_altf4),
+ conf_checkbox_handler, I(CONF_alt_f4));
+ ctrl_checkbox(s, "System menu appears on ALT-Space", 'y',
+ HELPCTX(behaviour_altspace),
+ conf_checkbox_handler, I(CONF_alt_space));
+ ctrl_checkbox(s, "System menu appears on ALT alone", 'l',
+ HELPCTX(behaviour_altonly),
+ conf_checkbox_handler, I(CONF_alt_only));
+ ctrl_checkbox(s, "Ensure window is always on top", 'e',
+ HELPCTX(behaviour_alwaysontop),
+ conf_checkbox_handler, I(CONF_alwaysontop));
+ ctrl_checkbox(s, "Full screen on Alt-Enter", 'f',
+ HELPCTX(behaviour_altenter),
+ conf_checkbox_handler,
+ I(CONF_fullscreenonaltenter));
+
+ /*
+ * Windows supports a local-command proxy.
+ */
+ if (!midsession) {
+ int i;
+ s = ctrl_getset(b, "Connection/Proxy", "basics", NULL);
+ for (i = 0; i < s->ncontrols; i++) {
+ c = s->ctrls[i];
+ if (c->type == CTRL_LISTBOX &&
+ c->handler == proxy_type_handler) {
+ c->context.i |= PROXY_UI_FLAG_LOCAL;
+ break;
+ }
+ }
+ }
+
+ /*
+ * $XAUTHORITY is not reliable on Windows, so we provide a
+ * means to override it.
+ */
+ if (!midsession && backend_vt_from_proto(PROT_SSH)) {
+ s = ctrl_getset(b, "Connection/SSH/X11", "x11", "X11 forwarding");
+ ctrl_filesel(s, "X authority file for local display", 't',
+ NULL, false, "Select X authority file",
+ HELPCTX(ssh_tunnels_xauthority),
+ conf_filesel_handler, I(CONF_xauthfile));
+ }
+}
diff --git a/windows/conpty.c b/windows/conpty.c
new file mode 100644
index 00000000..c23c81e1
--- /dev/null
+++ b/windows/conpty.c
@@ -0,0 +1,433 @@
+/*
+ * Backend to run a Windows console session using ConPTY.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "putty.h"
+
+#include <windows.h>
+#include <consoleapi.h>
+
+typedef struct ConPTY ConPTY;
+struct ConPTY {
+ HPCON pseudoconsole;
+ HANDLE outpipe, inpipe, hprocess;
+ struct handle *out, *in;
+ HandleWait *subprocess;
+ bool exited;
+ DWORD exitstatus;
+ Seat *seat;
+ LogContext *logctx;
+ int bufsize;
+ Backend backend;
+};
+
+DECL_WINDOWS_FUNCTION(static, HRESULT, CreatePseudoConsole,
+ (COORD, HANDLE, HANDLE, DWORD, HPCON *));
+DECL_WINDOWS_FUNCTION(static, void, ClosePseudoConsole, (HPCON));
+DECL_WINDOWS_FUNCTION(static, HRESULT, ResizePseudoConsole, (HPCON, COORD));
+
+static bool init_conpty_api(void)
+{
+ static bool tried = false;
+ if (!tried) {
+ tried = true;
+ HMODULE kernel32_module = load_system32_dll("kernel32.dll");
+ GET_WINDOWS_FUNCTION(kernel32_module, CreatePseudoConsole);
+ GET_WINDOWS_FUNCTION(kernel32_module, ClosePseudoConsole);
+ GET_WINDOWS_FUNCTION(kernel32_module, ResizePseudoConsole);
+ }
+
+ return (p_CreatePseudoConsole != NULL &&
+ p_ClosePseudoConsole != NULL &&
+ p_ResizePseudoConsole != NULL);
+}
+
+static void conpty_terminate(ConPTY *conpty)
+{
+ if (conpty->out) {
+ handle_free(conpty->out);
+ conpty->out = NULL;
+ }
+ if (conpty->outpipe != INVALID_HANDLE_VALUE) {
+ CloseHandle(conpty->outpipe);
+ conpty->outpipe = INVALID_HANDLE_VALUE;
+ }
+ if (conpty->in) {
+ handle_free(conpty->in);
+ conpty->in = NULL;
+ }
+ if (conpty->inpipe != INVALID_HANDLE_VALUE) {
+ CloseHandle(conpty->inpipe);
+ conpty->inpipe = INVALID_HANDLE_VALUE;
+ }
+ if (conpty->subprocess) {
+ delete_handle_wait(conpty->subprocess);
+ conpty->subprocess = NULL;
+ conpty->hprocess = INVALID_HANDLE_VALUE;
+ }
+ if (conpty->pseudoconsole != INVALID_HANDLE_VALUE) {
+ p_ClosePseudoConsole(conpty->pseudoconsole);
+ conpty->pseudoconsole = INVALID_HANDLE_VALUE;
+ }
+}
+
+static void conpty_process_wait_callback(void *vctx)
+{
+ ConPTY *conpty = (ConPTY *)vctx;
+
+ if (!GetExitCodeProcess(conpty->hprocess, &conpty->exitstatus))
+ return;
+ conpty->exited = true;
+
+ /*
+ * We can stop waiting for the process now.
+ */
+ if (conpty->subprocess) {
+ delete_handle_wait(conpty->subprocess);
+ conpty->subprocess = NULL;
+ conpty->hprocess = INVALID_HANDLE_VALUE;
+ }
+
+ /*
+ * Once the contained process exits, close the pseudo-console as
+ * well. But don't close the pipes yet, since apparently
+ * ClosePseudoConsole can trigger a final bout of terminal output
+ * as things clean themselves up.
+ */
+ if (conpty->pseudoconsole != INVALID_HANDLE_VALUE) {
+ p_ClosePseudoConsole(conpty->pseudoconsole);
+ conpty->pseudoconsole = INVALID_HANDLE_VALUE;
+ }
+}
+
+static size_t conpty_gotdata(
+ struct handle *h, const void *data, size_t len, int err)
+{
+ ConPTY *conpty = (ConPTY *)handle_get_privdata(h);
+ if (err || len == 0) {
+ char *error_msg;
+
+ conpty_terminate(conpty);
+
+ seat_notify_remote_exit(conpty->seat);
+
+ if (!err && conpty->exited) {
+ /*
+ * The clean-exit case: our subprocess terminated, we
+ * deleted the PseudoConsole ourself, and now we got the
+ * expected EOF on the pipe.
+ */
+ return 0;
+ }
+
+ if (err)
+ error_msg = dupprintf("Error reading from console pty: %s",
+ win_strerror(err));
+ else
+ error_msg = dupprintf(
+ "Unexpected end of file reading from console pty");
+
+ logevent(conpty->logctx, error_msg);
+ seat_connection_fatal(conpty->seat, "%s", error_msg);
+ sfree(error_msg);
+
+ return 0;
+ } else {
+ return seat_stdout(conpty->seat, data, len);
+ }
+}
+
+static void conpty_sentdata(struct handle *h, size_t new_backlog, int err,
+ bool close)
+{
+ ConPTY *conpty = (ConPTY *)handle_get_privdata(h);
+ if (err) {
+ const char *error_msg = "Error writing to conpty device";
+
+ conpty_terminate(conpty);
+
+ seat_notify_remote_exit(conpty->seat);
+
+ logevent(conpty->logctx, error_msg);
+
+ seat_connection_fatal(conpty->seat, "%s", error_msg);
+ } else {
+ conpty->bufsize = new_backlog;
+ }
+}
+
+static char *conpty_init(const BackendVtable *vt, Seat *seat,
+ Backend **backend_handle, LogContext *logctx,
+ Conf *conf, const char *host, int port,
+ char **realhost, bool nodelay, bool keepalive)
+{
+ ConPTY *conpty;
+ char *err = NULL;
+
+ HANDLE in_r = INVALID_HANDLE_VALUE;
+ HANDLE in_w = INVALID_HANDLE_VALUE;
+ HANDLE out_r = INVALID_HANDLE_VALUE;
+ HANDLE out_w = INVALID_HANDLE_VALUE;
+
+ HPCON pcon;
+ bool pcon_needs_cleanup = false;
+
+ STARTUPINFOEX si;
+ memset(&si, 0, sizeof(si));
+
+ if (!init_conpty_api()) {
+ err = dupprintf("Pseudo-console API is not available on this "
+ "Windows system");
+ goto out;
+ }
+
+ if (!CreatePipe(&in_r, &in_w, NULL, 0)) {
+ err = dupprintf("CreatePipe: %s", win_strerror(GetLastError()));
+ goto out;
+ }
+ if (!CreatePipe(&out_r, &out_w, NULL, 0)) {
+ err = dupprintf("CreatePipe: %s", win_strerror(GetLastError()));
+ goto out;
+ }
+
+ COORD size;
+ size.X = conf_get_int(conf, CONF_width);
+ size.Y = conf_get_int(conf, CONF_height);
+
+ HRESULT result = p_CreatePseudoConsole(size, in_r, out_w, 0, &pcon);
+ if (FAILED(result)) {
+ if (HRESULT_FACILITY(result) == FACILITY_WIN32)
+ err = dupprintf("CreatePseudoConsole: %s",
+ win_strerror(HRESULT_CODE(result)));
+ else
+ err = dupprintf("CreatePseudoConsole failed: HRESULT=0x%08x",
+ (unsigned)result);
+ goto out;
+ }
+ pcon_needs_cleanup = true;
+
+ CloseHandle(in_r);
+ in_r = INVALID_HANDLE_VALUE;
+ CloseHandle(out_w);
+ out_w = INVALID_HANDLE_VALUE;
+
+ si.StartupInfo.cb = sizeof(si);
+
+ SIZE_T attrsize = 0;
+ InitializeProcThreadAttributeList(NULL, 1, 0, &attrsize);
+ si.lpAttributeList = smalloc(attrsize);
+ if (!InitializeProcThreadAttributeList(
+ si.lpAttributeList, 1, 0, &attrsize)) {
+ err = dupprintf("InitializeProcThreadAttributeList: %s",
+ win_strerror(GetLastError()));
+ goto out;
+ }
+ if (!UpdateProcThreadAttribute(
+ si.lpAttributeList,
+ 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
+ pcon, sizeof(pcon), NULL, NULL)) {
+ err = dupprintf("UpdateProcThreadAttribute: %s",
+ win_strerror(GetLastError()));
+ goto out;
+ }
+
+ PROCESS_INFORMATION pi;
+ memset(&pi, 0, sizeof(pi));
+
+ char *command;
+ const char *conf_cmd = conf_get_str(conf, CONF_remote_cmd);
+ if (*conf_cmd) {
+ command = dupstr(conf_cmd);
+ } else {
+ command = dupcat(get_system_dir(), "\\cmd.exe");
+ }
+ bool created_ok = CreateProcess(NULL, command, NULL, NULL,
+ false, EXTENDED_STARTUPINFO_PRESENT,
+ NULL, NULL, &si.StartupInfo, &pi);
+ sfree(command);
+ if (!created_ok) {
+ err = dupprintf("CreateProcess: %s",
+ win_strerror(GetLastError()));
+ goto out;
+ }
+
+ /* No local authentication phase in this protocol */
+ seat_set_trust_status(seat, false);
+
+ conpty = snew(ConPTY);
+ memset(conpty, 0, sizeof(ConPTY));
+ conpty->pseudoconsole = pcon;
+ pcon_needs_cleanup = false;
+ conpty->outpipe = in_w;
+ conpty->out = handle_output_new(in_w, conpty_sentdata, conpty, 0);
+ in_w = INVALID_HANDLE_VALUE;
+ conpty->inpipe = out_r;
+ conpty->in = handle_input_new(out_r, conpty_gotdata, conpty, 0);
+ out_r = INVALID_HANDLE_VALUE;
+ conpty->subprocess = add_handle_wait(
+ pi.hProcess, conpty_process_wait_callback, conpty);
+ conpty->hprocess = pi.hProcess;
+ CloseHandle(pi.hThread);
+ conpty->exited = false;
+ conpty->exitstatus = 0;
+ conpty->bufsize = 0;
+ conpty->backend.vt = vt;
+ *backend_handle = &conpty->backend;
+
+ conpty->seat = seat;
+ conpty->logctx = logctx;
+
+ *realhost = dupstr("");
+
+ /*
+ * Specials are always available.
+ */
+ seat_update_specials_menu(conpty->seat);
+
+ out:
+ if (in_r != INVALID_HANDLE_VALUE)
+ CloseHandle(in_r);
+ if (in_w != INVALID_HANDLE_VALUE)
+ CloseHandle(in_w);
+ if (out_r != INVALID_HANDLE_VALUE)
+ CloseHandle(out_r);
+ if (out_w != INVALID_HANDLE_VALUE)
+ CloseHandle(out_w);
+ if (pcon_needs_cleanup)
+ p_ClosePseudoConsole(pcon);
+ sfree(si.lpAttributeList);
+ return err;
+}
+
+static void conpty_free(Backend *be)
+{
+ ConPTY *conpty = container_of(be, ConPTY, backend);
+
+ conpty_terminate(conpty);
+ expire_timer_context(conpty);
+ sfree(conpty);
+}
+
+static void conpty_reconfig(Backend *be, Conf *conf)
+{
+}
+
+static void conpty_send(Backend *be, const char *buf, size_t len)
+{
+ ConPTY *conpty = container_of(be, ConPTY, backend);
+
+ if (conpty->out == NULL)
+ return;
+
+ conpty->bufsize = handle_write(conpty->out, buf, len);
+}
+
+static size_t conpty_sendbuffer(Backend *be)
+{
+ ConPTY *conpty = container_of(be, ConPTY, backend);
+ return conpty->bufsize;
+}
+
+static void conpty_size(Backend *be, int width, int height)
+{
+ ConPTY *conpty = container_of(be, ConPTY, backend);
+ COORD size;
+ size.X = width;
+ size.Y = height;
+ p_ResizePseudoConsole(conpty->pseudoconsole, size);
+}
+
+static void conpty_special(Backend *be, SessionSpecialCode code, int arg)
+{
+}
+
+static const SessionSpecial *conpty_get_specials(Backend *be)
+{
+ static const SessionSpecial specials[] = {
+ {NULL, SS_EXITMENU}
+ };
+ return specials;
+}
+
+static bool conpty_connected(Backend *be)
+{
+ return true; /* always connected */
+}
+
+static bool conpty_sendok(Backend *be)
+{
+ return true;
+}
+
+static void conpty_unthrottle(Backend *be, size_t backlog)
+{
+ ConPTY *conpty = container_of(be, ConPTY, backend);
+ if (conpty->in)
+ handle_unthrottle(conpty->in, backlog);
+}
+
+static bool conpty_ldisc(Backend *be, int option)
+{
+ return false;
+}
+
+static void conpty_provide_ldisc(Backend *be, Ldisc *ldisc)
+{
+}
+
+static int conpty_exitcode(Backend *be)
+{
+ ConPTY *conpty = container_of(be, ConPTY, backend);
+
+ if (conpty->exited) {
+ /*
+ * PuTTY's representation of exit statuses expects them to be
+ * non-negative 'int' values. But Windows exit statuses can
+ * include all those exception codes like 0xC000001D which
+ * convert to negative 32-bit ints.
+ *
+ * I don't think there's a great deal of use for returning
+ * those in full detail, right now. (Though if we ever
+ * connected this system up to a Windows version of psusan or
+ * Uppity, perhaps there might be?)
+ *
+ * So we clip them at INT_MAX-1, since INT_MAX is reserved for
+ * 'exit so unclean as to inhibit Close On Clean Exit'.
+ */
+ return (0 <= conpty->exitstatus && conpty->exitstatus < INT_MAX) ?
+ conpty->exitstatus : INT_MAX-1;
+ } else {
+ return -1;
+ }
+}
+
+static int conpty_cfg_info(Backend *be)
+{
+ return 0;
+}
+
+const BackendVtable conpty_backend = {
+ .init = conpty_init,
+ .free = conpty_free,
+ .reconfig = conpty_reconfig,
+ .send = conpty_send,
+ .sendbuffer = conpty_sendbuffer,
+ .size = conpty_size,
+ .special = conpty_special,
+ .get_specials = conpty_get_specials,
+ .connected = conpty_connected,
+ .exitcode = conpty_exitcode,
+ .sendok = conpty_sendok,
+ .ldisc_option_state = conpty_ldisc,
+ .provide_ldisc = conpty_provide_ldisc,
+ .unthrottle = conpty_unthrottle,
+ .cfg_info = conpty_cfg_info,
+ .id = "conpty",
+ .displayname_tc = "ConPTY",
+ .displayname_lc = "ConPTY", /* proper name, so capitalise it anyway */
+ .protocol = -1,
+};
diff --git a/windows/console.c b/windows/console.c
new file mode 100644
index 00000000..2fd572b4
--- /dev/null
+++ b/windows/console.c
@@ -0,0 +1,496 @@
+/*
+ * console.c - various interactive-prompt routines shared between
+ * the Windows console PuTTY tools
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "storage.h"
+#include "ssh.h"
+#include "console.h"
+
+void cleanup_exit(int code)
+{
+ /*
+ * Clean up.
+ */
+ sk_cleanup();
+
+ random_save_seed();
+
+ exit(code);
+}
+
+void console_print_error_msg(const char *prefix, const char *msg)
+{
+ fputs(prefix, stderr);
+ fputs(": ", stderr);
+ fputs(msg, stderr);
+ fputc('\n', stderr);
+ fflush(stderr);
+}
+
+SeatPromptResult console_confirm_ssh_host_key(
+ Seat *seat, const char *host, int port, const char *keytype,
+ char *keystr, SeatDialogText *text, HelpCtx helpctx,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+{
+ HANDLE hin;
+ DWORD savemode, i;
+ const char *prompt = NULL;
+
+ stdio_sink errsink[1];
+ stdio_sink_init(errsink, stderr);
+
+ char line[32];
+
+ for (SeatDialogTextItem *item = text->items,
+ *end = item+text->nitems; item < end; item++) {
+ switch (item->type) {
+ case SDT_PARA:
+ wordwrap(BinarySink_UPCAST(errsink),
+ ptrlen_from_asciz(item->text), 60);
+ fputc('\n', stderr);
+ break;
+ case SDT_DISPLAY:
+ fprintf(stderr, " %s\n", item->text);
+ break;
+ case SDT_SCARY_HEADING:
+ /* Can't change font size or weight in this context */
+ fprintf(stderr, "%s\n", item->text);
+ break;
+ case SDT_BATCH_ABORT:
+ if (console_batch_mode) {
+ fprintf(stderr, "%s\n", item->text);
+ fflush(stderr);
+ return SPR_SW_ABORT("Cannot confirm a host key in batch mode");
+ }
+ break;
+ case SDT_PROMPT:
+ prompt = item->text;
+ break;
+ default:
+ break;
+ }
+ }
+ assert(prompt); /* something in the SeatDialogText should have set this */
+
+ while (true) {
+ fprintf(stderr,
+ "%s (y/n, Return cancels connection, i for more info) ",
+ prompt);
+ fflush(stderr);
+
+ line[0] = '\0'; /* fail safe if ReadFile returns no data */
+
+ 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] == 'i' || line[0] == 'I') {
+ for (SeatDialogTextItem *item = text->items,
+ *end = item+text->nitems; item < end; item++) {
+ switch (item->type) {
+ case SDT_MORE_INFO_KEY:
+ fprintf(stderr, "%s", item->text);
+ break;
+ case SDT_MORE_INFO_VALUE_SHORT:
+ fprintf(stderr, ": %s\n", item->text);
+ break;
+ case SDT_MORE_INFO_VALUE_BLOB:
+ fprintf(stderr, ":\n%s\n", item->text);
+ break;
+ default:
+ break;
+ }
+ }
+ } else {
+ break;
+ }
+ }
+
+ /* In case of misplaced reflexes from another program, also recognise 'q'
+ * as 'abandon connection rather than trust this key' */
+ if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n' &&
+ line[0] != 'q' && line[0] != 'Q') {
+ if (line[0] == 'y' || line[0] == 'Y')
+ store_host_key(host, port, keytype, keystr);
+ return SPR_OK;
+ } else {
+ fputs(console_abandoned_msg, stderr);
+ return SPR_USER_ABORT;
+ }
+}
+
+SeatPromptResult console_confirm_weak_crypto_primitive(
+ Seat *seat, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, SeatPromptResult 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 SPR_SW_ABORT("Cannot confirm a weak crypto primitive "
+ "in batch mode");
+ }
+
+ 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 SPR_OK;
+ } else {
+ fputs(console_abandoned_msg, stderr);
+ return SPR_USER_ABORT;
+ }
+}
+
+SeatPromptResult console_confirm_weak_cached_hostkey(
+ Seat *seat, const char *algname, const char *betteralgs,
+ void (*callback)(void *ctx, SeatPromptResult 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 SPR_SW_ABORT("Cannot confirm a weak cached host key "
+ "in batch mode");
+ }
+
+ 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 SPR_OK;
+ } else {
+ fputs(console_abandoned_msg, stderr);
+ return SPR_USER_ABORT;
+ }
+}
+
+bool is_interactive(void)
+{
+ return is_console_handle(GetStdHandle(STD_INPUT_HANDLE));
+}
+
+bool console_antispoof_prompt = true;
+
+void console_set_trust_status(Seat *seat, bool trusted)
+{
+ /* Do nothing in response to a change of trust status, because
+ * there's nothing we can do in a console environment. However,
+ * the query function below will make a fiddly decision about
+ * whether to tell the backend to enable fallback handling. */
+}
+
+bool console_can_set_trust_status(Seat *seat)
+{
+ if (console_batch_mode) {
+ /*
+ * In batch mode, we don't need to worry about the server
+ * mimicking our interactive authentication, because the user
+ * already knows not to expect any.
+ */
+ return true;
+ }
+
+ return false;
+}
+
+bool console_has_mixed_input_stream(Seat *seat)
+{
+ if (!is_interactive() || !console_antispoof_prompt) {
+ /*
+ * If standard input isn't connected to a terminal, then even
+ * if the server did send a spoof authentication prompt, the
+ * user couldn't respond to it via the terminal anyway.
+ *
+ * We also pretend this is true if the user has purposely
+ * disabled the antispoof prompt.
+ */
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Ask whether to wipe a session log file before writing to it.
+ * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
+ */
+int console_askappend(LogPolicy *lp, Filename *filename,
+ void (*callback)(void *ctx, int result), void *ctx)
+{
+ 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);
+}
+
+SeatPromptResult 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 SPR_SW_ABORT("Cannot answer interactive prompts "
+ "in batch mode");
+ 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;
+ SeatPromptResult spr;
+ 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)) {
+ /* An OS error when reading from the console is treated as an
+ * unexpected error and reported to the user. */
+ failed = true;
+ spr = make_spr_sw_abort_winerror(
+ "Error reading from console", GetLastError());
+ break;
+ } else if (ret == 0) {
+ /* Regard EOF on the terminal as a deliberate user-abort */
+ failed = true;
+ spr = SPR_USER_ABORT;
+ 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 spr;
+ }
+
+ return SPR_OK;
+}
diff --git a/windows/controls.c b/windows/controls.c
new file mode 100644
index 00000000..ce3638e4
--- /dev/null
+++ b/windows/controls.c
@@ -0,0 +1,2658 @@
+/*
+ * controls.c: routines to self-manage the controls in a dialog
+ * box.
+ */
+
+/*
+ * Possible TODO in new cross-platform config box stuff:
+ *
+ * - When lining up two controls alongside each other, I wonder if
+ * we could conveniently arrange to centre them vertically?
+ * Particularly ugly in the current setup is the `Add new
+ * forwarded port:' static next to the rather taller `Remove'
+ * button.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+
+#include "putty.h"
+#include "misc.h"
+#include "dialog.h"
+
+#include <commctrl.h>
+
+#define GAPBETWEEN 3
+#define GAPWITHIN 1
+#define GAPXBOX 7
+#define GAPYBOX 4
+#define DLGWIDTH 168
+#define STATICHEIGHT 8
+#define TITLEHEIGHT 12
+#define CHECKBOXHEIGHT 8
+#define RADIOHEIGHT 8
+#define EDITHEIGHT 12
+#define LISTHEIGHT 11
+#define LISTINCREMENT 8
+#define COMBOHEIGHT 12
+#define PUSHBTNHEIGHT 14
+#define PROGBARHEIGHT 14
+
+DECL_WINDOWS_FUNCTION(static, void, InitCommonControls, (void));
+DECL_WINDOWS_FUNCTION(static, BOOL, MakeDragList, (HWND));
+DECL_WINDOWS_FUNCTION(static, int, LBItemFromPt, (HWND, POINT, BOOL));
+DECL_WINDOWS_FUNCTION(static, void, DrawInsert, (HWND, HWND, int));
+
+void init_common_controls(void)
+{
+ HMODULE comctl32_module = load_system32_dll("comctl32.dll");
+ GET_WINDOWS_FUNCTION(comctl32_module, InitCommonControls);
+ GET_WINDOWS_FUNCTION(comctl32_module, MakeDragList);
+ GET_WINDOWS_FUNCTION(comctl32_module, LBItemFromPt);
+ GET_WINDOWS_FUNCTION(comctl32_module, DrawInsert);
+ p_InitCommonControls();
+}
+
+void ctlposinit(struct ctlpos *cp, HWND hwnd,
+ int leftborder, int rightborder, int topborder)
+{
+ RECT r, r2;
+ cp->hwnd = hwnd;
+ cp->font = SendMessage(hwnd, WM_GETFONT, 0, 0);
+ cp->ypos = topborder;
+ GetClientRect(hwnd, &r);
+ r2.left = r2.top = 0;
+ r2.right = 4;
+ r2.bottom = 8;
+ MapDialogRect(hwnd, &r2);
+ cp->dlu4inpix = r2.right;
+ cp->width = (r.right * 4) / (r2.right) - 2 * GAPBETWEEN;
+ cp->xoff = leftborder;
+ cp->width -= leftborder + rightborder;
+}
+
+HWND doctl(struct ctlpos *cp, RECT r, const char *wclass, int wstyle,
+ int exstyle, const char *wtext, int wid)
+{
+ HWND ctl;
+ /*
+ * Note nonstandard use of RECT. This is deliberate: by
+ * transforming the width and height directly we arrange to
+ * have all supposedly same-sized controls really same-sized.
+ */
+
+ r.left += cp->xoff;
+ MapDialogRect(cp->hwnd, &r);
+
+ /*
+ * We can pass in cp->hwnd == NULL, to indicate a dry run
+ * without creating any actual controls.
+ */
+ if (cp->hwnd) {
+ ctl = CreateWindowEx(exstyle, wclass, wtext, wstyle,
+ r.left, r.top, r.right, r.bottom,
+ cp->hwnd, (HMENU)(ULONG_PTR)wid, hinst, NULL);
+ SendMessage(ctl, WM_SETFONT, cp->font, MAKELPARAM(true, 0));
+
+ if (!strcmp(wclass, "LISTBOX")) {
+ /*
+ * Bizarre Windows bug: the list box calculates its
+ * number of lines based on the font it has at creation
+ * time, but sending it WM_SETFONT doesn't cause it to
+ * recalculate. So now, _after_ we've sent it
+ * WM_SETFONT, we explicitly resize it (to the same
+ * size it was already!) to force it to reconsider.
+ */
+ SetWindowPos(ctl, NULL, 0, 0, r.right, r.bottom,
+ SWP_NOACTIVATE | SWP_NOCOPYBITS |
+ SWP_NOMOVE | SWP_NOZORDER);
+ }
+ } else
+ ctl = NULL;
+ return ctl;
+}
+
+/*
+ * A title bar across the top of a sub-dialog.
+ */
+void bartitle(struct ctlpos *cp, const char *name, int id)
+{
+ RECT r;
+
+ r.left = GAPBETWEEN;
+ r.right = cp->width;
+ r.top = cp->ypos;
+ r.bottom = STATICHEIGHT;
+ cp->ypos += r.bottom + GAPBETWEEN;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, name, id);
+}
+
+/*
+ * Begin a grouping box, with or without a group title.
+ */
+void beginbox(struct ctlpos *cp, const char *name, int idbox)
+{
+ cp->boxystart = cp->ypos;
+ if (!name)
+ cp->boxystart -= STATICHEIGHT / 2;
+ if (name)
+ cp->ypos += STATICHEIGHT;
+ cp->ypos += GAPYBOX;
+ cp->width -= 2 * GAPXBOX;
+ cp->xoff += GAPXBOX;
+ cp->boxid = idbox;
+ cp->boxtext = name;
+}
+
+/*
+ * End a grouping box.
+ */
+void endbox(struct ctlpos *cp)
+{
+ RECT r;
+ cp->xoff -= GAPXBOX;
+ cp->width += 2 * GAPXBOX;
+ cp->ypos += GAPYBOX - GAPBETWEEN;
+ r.left = GAPBETWEEN;
+ r.right = cp->width;
+ r.top = cp->boxystart;
+ r.bottom = cp->ypos - cp->boxystart;
+ doctl(cp, r, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 0,
+ cp->boxtext ? cp->boxtext : "", cp->boxid);
+ cp->ypos += GAPYBOX;
+}
+
+/*
+ * A static line, followed by a full-width edit box.
+ */
+void editboxfw(struct ctlpos *cp, bool password, bool readonly,
+ const char *text, int staticid, int editid)
+{
+ RECT r;
+
+ r.left = GAPBETWEEN;
+ r.right = cp->width;
+
+ if (text) {
+ r.top = cp->ypos;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid);
+ cp->ypos += STATICHEIGHT + GAPWITHIN;
+ }
+ r.top = cp->ypos;
+ r.bottom = EDITHEIGHT;
+ doctl(cp, r, "EDIT",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL |
+ (password ? ES_PASSWORD : 0) |
+ (readonly ? ES_READONLY : 0),
+ WS_EX_CLIENTEDGE, "", editid);
+ cp->ypos += EDITHEIGHT + GAPBETWEEN;
+}
+
+/*
+ * A static line, followed by a full-width combo box.
+ */
+void combobox(struct ctlpos *cp, const char *text, int staticid, int listid)
+{
+ RECT r;
+
+ r.left = GAPBETWEEN;
+ r.right = cp->width;
+
+ if (text) {
+ r.top = cp->ypos;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid);
+ cp->ypos += STATICHEIGHT + GAPWITHIN;
+ }
+ r.top = cp->ypos;
+ r.bottom = COMBOHEIGHT * 10;
+ doctl(cp, r, "COMBOBOX",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
+ CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", listid);
+ cp->ypos += COMBOHEIGHT + GAPBETWEEN;
+}
+
+struct radio { const char *text; int id; };
+
+static void radioline_common(struct ctlpos *cp, const char *text, int id,
+ int nacross, struct radio *buttons, int nbuttons)
+{
+ RECT r;
+ int group;
+ int i;
+ int j;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ if (text) {
+ r.right = cp->width;
+ r.bottom = STATICHEIGHT;
+ cp->ypos += r.bottom + GAPWITHIN;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
+ } else {
+ r.right = r.bottom = 0;
+ }
+
+ group = WS_GROUP;
+ i = 0;
+ for (j = 0; j < nbuttons; j++) {
+ const char *btext = buttons[j].text;
+ int bid = buttons[j].id;
+
+ if (i == nacross) {
+ cp->ypos += r.bottom + (nacross > 1 ? GAPBETWEEN : GAPWITHIN);
+ i = 0;
+ }
+ r.left = GAPBETWEEN + i * (cp->width + GAPBETWEEN) / nacross;
+ if (j < nbuttons-1)
+ r.right =
+ (i + 1) * (cp->width + GAPBETWEEN) / nacross - r.left;
+ else
+ r.right = cp->width - r.left;
+ r.top = cp->ypos;
+ r.bottom = RADIOHEIGHT;
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | BS_AUTORADIOBUTTON | WS_CHILD |
+ WS_VISIBLE | WS_TABSTOP | group, 0, btext, bid);
+ group = 0;
+ i++;
+ }
+ cp->ypos += r.bottom + GAPBETWEEN;
+}
+
+/*
+ * A set of radio buttons on the same line, with a static above
+ * them. `nacross' dictates how many parts the line is divided into
+ * (you might want this not to equal the number of buttons if you
+ * needed to line up some 2s and some 3s to look good in the same
+ * panel).
+ *
+ * There's a bit of a hack in here to ensure that if nacross
+ * exceeds the actual number of buttons, the rightmost button
+ * really does get all the space right to the edge of the line, so
+ * you can do things like
+ *
+ * (*) Button1 (*) Button2 (*) ButtonWithReallyLongTitle
+ */
+void radioline(struct ctlpos *cp, const char *text, int id, int nacross, ...)
+{
+ va_list ap;
+ struct radio *buttons;
+ int i, nbuttons;
+
+ va_start(ap, nacross);
+ nbuttons = 0;
+ while (1) {
+ const char *btext = va_arg(ap, const char *);
+ if (!btext)
+ break;
+ (void) va_arg(ap, int); /* id */
+ nbuttons++;
+ }
+ va_end(ap);
+ buttons = snewn(nbuttons, struct radio);
+ va_start(ap, nacross);
+ for (i = 0; i < nbuttons; i++) {
+ buttons[i].text = va_arg(ap, const char *);
+ buttons[i].id = va_arg(ap, int);
+ }
+ va_end(ap);
+ radioline_common(cp, text, id, nacross, buttons, nbuttons);
+ sfree(buttons);
+}
+
+/*
+ * A set of radio buttons on the same line, without a static above
+ * them. Otherwise just like radioline.
+ */
+void bareradioline(struct ctlpos *cp, int nacross, ...)
+{
+ va_list ap;
+ struct radio *buttons;
+ int i, nbuttons;
+
+ va_start(ap, nacross);
+ nbuttons = 0;
+ while (1) {
+ const char *btext = va_arg(ap, const char *);
+ if (!btext)
+ break;
+ (void) va_arg(ap, int); /* id */
+ nbuttons++;
+ }
+ va_end(ap);
+ buttons = snewn(nbuttons, struct radio);
+ va_start(ap, nacross);
+ for (i = 0; i < nbuttons; i++) {
+ buttons[i].text = va_arg(ap, const char *);
+ buttons[i].id = va_arg(ap, int);
+ }
+ va_end(ap);
+ radioline_common(cp, NULL, 0, nacross, buttons, nbuttons);
+ sfree(buttons);
+}
+
+/*
+ * A set of radio buttons on multiple lines, with a static above
+ * them.
+ */
+void radiobig(struct ctlpos *cp, const char *text, int id, ...)
+{
+ va_list ap;
+ struct radio *buttons;
+ int i, nbuttons;
+
+ va_start(ap, id);
+ nbuttons = 0;
+ while (1) {
+ const char *btext = va_arg(ap, const char *);
+ if (!btext)
+ break;
+ (void) va_arg(ap, int); /* id */
+ nbuttons++;
+ }
+ va_end(ap);
+ buttons = snewn(nbuttons, struct radio);
+ va_start(ap, id);
+ for (i = 0; i < nbuttons; i++) {
+ buttons[i].text = va_arg(ap, const char *);
+ buttons[i].id = va_arg(ap, int);
+ }
+ va_end(ap);
+ radioline_common(cp, text, id, 1, buttons, nbuttons);
+ sfree(buttons);
+}
+
+/*
+ * A single standalone checkbox.
+ */
+void checkbox(struct ctlpos *cp, const char *text, int id)
+{
+ RECT r;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = CHECKBOXHEIGHT;
+ cp->ypos += r.bottom + GAPBETWEEN;
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0,
+ text, id);
+}
+
+/*
+ * Wrap a piece of text for a static text control. Returns the
+ * wrapped text (a malloc'ed string containing \ns), and also
+ * returns the number of lines required.
+ */
+char *staticwrap(struct ctlpos *cp, HWND hwnd, const char *text, int *lines)
+{
+ HDC hdc = GetDC(hwnd);
+ int width, nlines, j;
+ INT *pwidths, nfit;
+ SIZE size;
+ const char *p;
+ char *ret, *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, const char *text, int lines, int id)
+{
+ RECT r;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = STATICHEIGHT * lines;
+ cp->ypos += r.bottom + GAPBETWEEN;
+ doctl(cp, r, "STATIC",
+ WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
+ 0, text, id);
+}
+
+/*
+ * An owner-drawn static text control for a panel title.
+ */
+void paneltitle(struct ctlpos *cp, int id)
+{
+ RECT r;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = TITLEHEIGHT;
+ cp->ypos += r.bottom + GAPBETWEEN;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_OWNERDRAW,
+ 0, NULL, id);
+}
+
+/*
+ * A button on the right hand side, with a static to its left.
+ */
+void staticbtn(struct ctlpos *cp, const char *stext, int sid,
+ const char *btext, int bid)
+{
+ const int height = (PUSHBTNHEIGHT > STATICHEIGHT ?
+ PUSHBTNHEIGHT : STATICHEIGHT);
+ RECT r;
+ int lwid, rwid, rpos;
+
+ rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
+ lwid = rpos - 2 * GAPBETWEEN;
+ rwid = cp->width + GAPBETWEEN - rpos;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos + (height - STATICHEIGHT) / 2;
+ r.right = lwid;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+
+ r.left = rpos;
+ r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
+ r.right = rwid;
+ r.bottom = PUSHBTNHEIGHT;
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+ 0, btext, bid);
+
+ cp->ypos += height + GAPBETWEEN;
+}
+
+/*
+ * A simple push button.
+ */
+void button(struct ctlpos *cp, const char *btext, int bid, bool defbtn)
+{
+ RECT r;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = PUSHBTNHEIGHT;
+
+ /* Q67655: the _dialog box_ must know which button is default
+ * as well as the button itself knowing */
+ if (defbtn && cp->hwnd)
+ SendMessage(cp->hwnd, DM_SETDEFID, bid, 0);
+
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP |
+ (defbtn ? BS_DEFPUSHBUTTON : 0) | BS_PUSHBUTTON,
+ 0, btext, bid);
+
+ cp->ypos += PUSHBTNHEIGHT + GAPBETWEEN;
+}
+
+/*
+ * Like staticbtn, but two buttons.
+ */
+void static2btn(struct ctlpos *cp, const char *stext, int sid,
+ const char *btext1, int bid1, const char *btext2, int bid2)
+{
+ const int height = (PUSHBTNHEIGHT > STATICHEIGHT ?
+ PUSHBTNHEIGHT : STATICHEIGHT);
+ RECT r;
+ int lwid, rwid1, rwid2, rpos1, rpos2;
+
+ rpos1 = GAPBETWEEN + (cp->width + GAPBETWEEN) / 2;
+ rpos2 = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
+ lwid = rpos1 - 2 * GAPBETWEEN;
+ rwid1 = rpos2 - rpos1 - GAPBETWEEN;
+ rwid2 = cp->width + GAPBETWEEN - rpos2;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos + (height - STATICHEIGHT) / 2;
+ r.right = lwid;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+
+ r.left = rpos1;
+ r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
+ r.right = rwid1;
+ r.bottom = PUSHBTNHEIGHT;
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+ 0, btext1, bid1);
+
+ r.left = rpos2;
+ r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
+ r.right = rwid2;
+ r.bottom = PUSHBTNHEIGHT;
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+ 0, btext2, bid2);
+
+ cp->ypos += height + GAPBETWEEN;
+}
+
+/*
+ * An edit control on the right hand side, with a static to its left.
+ */
+static void staticedit_internal(struct ctlpos *cp, const char *stext,
+ int sid, int eid, int percentedit,
+ int style)
+{
+ const int height = (EDITHEIGHT > STATICHEIGHT ?
+ EDITHEIGHT : STATICHEIGHT);
+ RECT r;
+ int lwid, rwid, rpos;
+
+ rpos =
+ GAPBETWEEN + (100 - percentedit) * (cp->width + GAPBETWEEN) / 100;
+ lwid = rpos - 2 * GAPBETWEEN;
+ rwid = cp->width + GAPBETWEEN - rpos;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos + (height - STATICHEIGHT) / 2;
+ r.right = lwid;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+
+ r.left = rpos;
+ r.top = cp->ypos + (height - EDITHEIGHT) / 2;
+ r.right = rwid;
+ r.bottom = EDITHEIGHT;
+ doctl(cp, r, "EDIT",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | style,
+ WS_EX_CLIENTEDGE, "", eid);
+
+ cp->ypos += height + GAPBETWEEN;
+}
+
+void staticedit(struct ctlpos *cp, const char *stext,
+ int sid, int eid, int percentedit)
+{
+ staticedit_internal(cp, stext, sid, eid, percentedit, 0);
+}
+
+void staticpassedit(struct ctlpos *cp, const char *stext,
+ int sid, int eid, int percentedit)
+{
+ staticedit_internal(cp, stext, sid, eid, percentedit, ES_PASSWORD);
+}
+
+/*
+ * A drop-down list box on the right hand side, with a static to
+ * its left.
+ */
+void staticddl(struct ctlpos *cp, const char *stext,
+ int sid, int lid, int percentlist)
+{
+ const int height = (COMBOHEIGHT > STATICHEIGHT ?
+ COMBOHEIGHT : STATICHEIGHT);
+ RECT r;
+ int lwid, rwid, rpos;
+
+ rpos =
+ GAPBETWEEN + (100 - percentlist) * (cp->width + GAPBETWEEN) / 100;
+ lwid = rpos - 2 * GAPBETWEEN;
+ rwid = cp->width + GAPBETWEEN - rpos;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos + (height - STATICHEIGHT) / 2;
+ r.right = lwid;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+
+ r.left = rpos;
+ r.top = cp->ypos + (height - EDITHEIGHT) / 2;
+ r.right = rwid;
+ r.bottom = COMBOHEIGHT*4;
+ doctl(cp, r, "COMBOBOX",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
+ CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
+
+ cp->ypos += height + GAPBETWEEN;
+}
+
+/*
+ * A combo box on the right hand side, with a static to its left.
+ */
+void staticcombo(struct ctlpos *cp, const char *stext,
+ int sid, int lid, int percentlist)
+{
+ const int height = (COMBOHEIGHT > STATICHEIGHT ?
+ COMBOHEIGHT : STATICHEIGHT);
+ RECT r;
+ int lwid, rwid, rpos;
+
+ rpos =
+ GAPBETWEEN + (100 - percentlist) * (cp->width + GAPBETWEEN) / 100;
+ lwid = rpos - 2 * GAPBETWEEN;
+ rwid = cp->width + GAPBETWEEN - rpos;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos + (height - STATICHEIGHT) / 2;
+ r.right = lwid;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+
+ r.left = rpos;
+ r.top = cp->ypos + (height - EDITHEIGHT) / 2;
+ r.right = rwid;
+ r.bottom = COMBOHEIGHT*10;
+ doctl(cp, r, "COMBOBOX",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
+ CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
+
+ cp->ypos += height + GAPBETWEEN;
+}
+
+/*
+ * A static, with a full-width drop-down list box below it.
+ */
+void staticddlbig(struct ctlpos *cp, const char *stext,
+ int sid, int lid)
+{
+ RECT r;
+
+ if (stext) {
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+ cp->ypos += STATICHEIGHT;
+ }
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = COMBOHEIGHT*4;
+ doctl(cp, r, "COMBOBOX",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
+ CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
+ cp->ypos += COMBOHEIGHT + GAPBETWEEN;
+}
+
+/*
+ * A big multiline edit control with a static labelling it.
+ */
+void bigeditctrl(struct ctlpos *cp, const char *stext,
+ int sid, int eid, int lines)
+{
+ RECT r;
+
+ if (stext) {
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = STATICHEIGHT;
+ cp->ypos += r.bottom + GAPWITHIN;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+ }
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = EDITHEIGHT + (lines - 1) * STATICHEIGHT;
+ cp->ypos += r.bottom + GAPBETWEEN;
+ doctl(cp, r, "EDIT",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | ES_MULTILINE,
+ WS_EX_CLIENTEDGE, "", eid);
+}
+
+/*
+ * A list box with a static labelling it.
+ */
+void listbox(struct ctlpos *cp, const char *stext,
+ int sid, int lid, int lines, bool multi)
+{
+ RECT r;
+
+ if (stext != NULL) {
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = STATICHEIGHT;
+ cp->ypos += r.bottom + GAPWITHIN;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+ }
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = LISTHEIGHT + (lines - 1) * LISTINCREMENT;
+ cp->ypos += r.bottom + GAPBETWEEN;
+ doctl(cp, r, "LISTBOX",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
+ LBS_NOTIFY | LBS_HASSTRINGS | LBS_USETABSTOPS |
+ (multi ? LBS_MULTIPLESEL : 0),
+ WS_EX_CLIENTEDGE, "", lid);
+}
+
+/*
+ * A tab-control substitute when a real tab control is unavailable.
+ */
+void ersatztab(struct ctlpos *cp, const char *stext, int sid, int lid,
+ int s2id)
+{
+ const int height = (COMBOHEIGHT > STATICHEIGHT ?
+ COMBOHEIGHT : STATICHEIGHT);
+ RECT r;
+ int bigwid, lwid, rwid, rpos;
+ static const int BIGGAP = 15;
+ static const int MEDGAP = 3;
+
+ bigwid = cp->width + 2 * GAPBETWEEN - 2 * BIGGAP;
+ cp->ypos += MEDGAP;
+ rpos = BIGGAP + (bigwid + BIGGAP) / 2;
+ lwid = rpos - 2 * BIGGAP;
+ rwid = bigwid + BIGGAP - rpos;
+
+ r.left = BIGGAP;
+ r.top = cp->ypos + (height - STATICHEIGHT) / 2;
+ r.right = lwid;
+ r.bottom = STATICHEIGHT;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+
+ r.left = rpos;
+ r.top = cp->ypos + (height - COMBOHEIGHT) / 2;
+ r.right = rwid;
+ r.bottom = COMBOHEIGHT * 10;
+ doctl(cp, r, "COMBOBOX",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP |
+ CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
+
+ cp->ypos += height + MEDGAP + GAPBETWEEN;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = 2;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
+ 0, "", s2id);
+}
+
+/*
+ * A static line, followed by an edit control on the left hand side
+ * and a button on the right.
+ */
+void editbutton(struct ctlpos *cp, const char *stext, int sid,
+ int eid, const char *btext, int bid)
+{
+ const int height = (EDITHEIGHT > PUSHBTNHEIGHT ?
+ EDITHEIGHT : PUSHBTNHEIGHT);
+ RECT r;
+ int lwid, rwid, rpos;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = STATICHEIGHT;
+ cp->ypos += r.bottom + GAPWITHIN;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+
+ rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
+ lwid = rpos - 2 * GAPBETWEEN;
+ rwid = cp->width + GAPBETWEEN - rpos;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos + (height - EDITHEIGHT) / 2;
+ r.right = lwid;
+ r.bottom = EDITHEIGHT;
+ doctl(cp, r, "EDIT",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
+ WS_EX_CLIENTEDGE, "", eid);
+
+ r.left = rpos;
+ r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
+ r.right = rwid;
+ r.bottom = PUSHBTNHEIGHT;
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+ 0, btext, bid);
+
+ cp->ypos += height + GAPBETWEEN;
+}
+
+/*
+ * A special control for manipulating an ordered preference list
+ * (eg. for cipher selection).
+ * XXX: this is a rough hack and could be improved.
+ */
+void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines,
+ const char *stext, int sid, int listid, int upbid, int dnbid)
+{
+ const static int percents[] = { 5, 75, 20 };
+ RECT r;
+ int xpos, percent = 0, i;
+ int listheight = LISTHEIGHT + (lines - 1) * LISTINCREMENT;
+ const int BTNSHEIGHT = 2*PUSHBTNHEIGHT + GAPBETWEEN;
+ int totalheight, buttonpos;
+
+ /* Squirrel away IDs. */
+ hdl->listid = listid;
+ hdl->upbid = upbid;
+ hdl->dnbid = dnbid;
+
+ /* The static label. */
+ if (stext != NULL) {
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = STATICHEIGHT;
+ cp->ypos += r.bottom + GAPWITHIN;
+ doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+ }
+
+ if (listheight > BTNSHEIGHT) {
+ totalheight = listheight;
+ buttonpos = (listheight - BTNSHEIGHT) / 2;
+ } else {
+ totalheight = BTNSHEIGHT;
+ buttonpos = 0;
+ }
+
+ for (i=0; i<3; i++) {
+ int left, wid;
+ xpos = (cp->width + GAPBETWEEN) * percent / 100;
+ left = xpos + GAPBETWEEN;
+ percent += percents[i];
+ xpos = (cp->width + GAPBETWEEN) * percent / 100;
+ wid = xpos - left;
+
+ switch (i) {
+ case 1: {
+ /* The drag list box. */
+ r.left = left; r.right = wid;
+ r.top = cp->ypos; r.bottom = listheight;
+ HWND ctl = doctl(cp, r, "LISTBOX",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP |
+ WS_VSCROLL | LBS_HASSTRINGS | LBS_USETABSTOPS,
+ WS_EX_CLIENTEDGE,
+ "", listid);
+ p_MakeDragList(ctl);
+ break;
+ }
+
+ case 2:
+ /* The "Up" and "Down" buttons. */
+ /* XXX worry about accelerators if we have more than one
+ * prefslist on a panel */
+ r.left = left; r.right = wid;
+ r.top = cp->ypos + buttonpos; r.bottom = PUSHBTNHEIGHT;
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | WS_CHILD | WS_VISIBLE |
+ WS_TABSTOP | BS_PUSHBUTTON,
+ 0, "&Up", upbid);
+
+ r.left = left; r.right = wid;
+ r.top = cp->ypos + buttonpos + PUSHBTNHEIGHT + GAPBETWEEN;
+ r.bottom = PUSHBTNHEIGHT;
+ doctl(cp, r, "BUTTON",
+ BS_NOTIFY | WS_CHILD | WS_VISIBLE |
+ WS_TABSTOP | BS_PUSHBUTTON,
+ 0, "&Down", dnbid);
+
+ break;
+
+ }
+ }
+
+ cp->ypos += totalheight + GAPBETWEEN;
+
+}
+
+/*
+ * Helper function for prefslist: move item in list box.
+ */
+static void pl_moveitem(HWND hwnd, int listid, int src, int dst)
+{
+ int tlen, val;
+ char *txt;
+ /* Get the item's data. */
+ tlen = SendDlgItemMessage (hwnd, listid, LB_GETTEXTLEN, src, 0);
+ txt = snewn(tlen+1, char);
+ SendDlgItemMessage (hwnd, listid, LB_GETTEXT, src, (LPARAM) txt);
+ val = SendDlgItemMessage (hwnd, listid, LB_GETITEMDATA, src, 0);
+ /* Deselect old location. */
+ SendDlgItemMessage (hwnd, listid, LB_SETSEL, false, src);
+ /* Delete it at the old location. */
+ SendDlgItemMessage (hwnd, listid, LB_DELETESTRING, src, 0);
+ /* Insert it at new location. */
+ SendDlgItemMessage (hwnd, listid, LB_INSERTSTRING, dst,
+ (LPARAM) txt);
+ SendDlgItemMessage (hwnd, listid, LB_SETITEMDATA, dst,
+ (LPARAM) val);
+ /* Set selection. */
+ SendDlgItemMessage (hwnd, listid, LB_SETCURSEL, dst, 0);
+ sfree (txt);
+}
+
+int pl_itemfrompt(HWND hwnd, POINT cursor, bool scroll)
+{
+ int ret;
+ POINT uppoint, downpoint;
+ int updist, downdist, upitem, downitem, i;
+
+ /*
+ * Ghastly hackery to try to figure out not which
+ * _item_, but which _gap between items_, the user
+ * is pointing at. We do this by first working out
+ * which list item is under the cursor, and then
+ * working out how far the cursor would have to
+ * move up or down before the answer was different.
+ * Then we put the insertion point _above_ the
+ * current item if the upper edge is closer than
+ * the lower edge, or _below_ it if vice versa.
+ */
+ ret = p_LBItemFromPt(hwnd, cursor, scroll);
+ if (ret == -1)
+ return ret;
+ ret = p_LBItemFromPt(hwnd, cursor, false);
+ updist = downdist = 0;
+ for (i = 1; i < 4096 && (!updist || !downdist); i++) {
+ uppoint = downpoint = cursor;
+ uppoint.y -= i;
+ downpoint.y += i;
+ upitem = p_LBItemFromPt(hwnd, uppoint, false);
+ downitem = p_LBItemFromPt(hwnd, downpoint, false);
+ if (!updist && upitem != ret)
+ updist = i;
+ if (!downdist && downitem != ret)
+ downdist = i;
+ }
+ if (downdist < updist)
+ ret++;
+ return ret;
+}
+
+/*
+ * Handler for prefslist above.
+ *
+ * Return value has bit 0 set if the dialog box procedure needs to
+ * return true from handling this message; it has bit 1 set if a
+ * change may have been made in the contents of the list.
+ */
+int handle_prefslist(struct prefslist *hdl,
+ int *array, int maxmemb,
+ bool is_dlmsg, HWND hwnd,
+ WPARAM wParam, LPARAM lParam)
+{
+ int i;
+ int ret = 0;
+
+ if (is_dlmsg) {
+
+ if ((int)wParam == hdl->listid) {
+ DRAGLISTINFO *dlm = (DRAGLISTINFO *)lParam;
+ int dest = 0; /* initialise to placate gcc */
+ switch (dlm->uNotification) {
+ case DL_BEGINDRAG:
+ /* Add a dummy item to make pl_itemfrompt() work
+ * better.
+ * FIXME: this causes scrollbar glitches if the count of
+ * listbox contains >= its height. */
+ hdl->dummyitem =
+ SendDlgItemMessage(hwnd, hdl->listid,
+ LB_ADDSTRING, 0, (LPARAM) "");
+
+ hdl->srcitem = p_LBItemFromPt(dlm->hWnd, dlm->ptCursor, true);
+ hdl->dragging = false;
+ /* XXX hack Q183115 */
+ SetWindowLongPtr(hwnd, DWLP_MSGRESULT, true);
+ ret |= 1; break;
+ case DL_CANCELDRAG:
+ p_DrawInsert(hwnd, dlm->hWnd, -1); /* Clear arrow */
+ SendDlgItemMessage(hwnd, hdl->listid,
+ LB_DELETESTRING, hdl->dummyitem, 0);
+ hdl->dragging = false;
+ ret |= 1; break;
+ case DL_DRAGGING:
+ hdl->dragging = true;
+ dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, true);
+ if (dest > hdl->dummyitem) dest = hdl->dummyitem;
+ p_DrawInsert (hwnd, dlm->hWnd, dest);
+ if (dest >= 0)
+ SetWindowLongPtr(hwnd, DWLP_MSGRESULT, DL_MOVECURSOR);
+ else
+ SetWindowLongPtr(hwnd, DWLP_MSGRESULT, DL_STOPCURSOR);
+ ret |= 1; break;
+ case DL_DROPPED:
+ if (hdl->dragging) {
+ dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, true);
+ if (dest > hdl->dummyitem) dest = hdl->dummyitem;
+ p_DrawInsert (hwnd, dlm->hWnd, -1);
+ }
+ SendDlgItemMessage(hwnd, hdl->listid,
+ LB_DELETESTRING, hdl->dummyitem, 0);
+ if (hdl->dragging) {
+ hdl->dragging = false;
+ if (dest >= 0) {
+ /* Correct for "missing" item. */
+ if (dest > hdl->srcitem) dest--;
+ pl_moveitem(hwnd, hdl->listid, hdl->srcitem, dest);
+ }
+ ret |= 2;
+ }
+ ret |= 1; break;
+ }
+ }
+
+ } else {
+
+ if (((LOWORD(wParam) == hdl->upbid) ||
+ (LOWORD(wParam) == hdl->dnbid)) &&
+ ((HIWORD(wParam) == BN_CLICKED) ||
+ (HIWORD(wParam) == BN_DOUBLECLICKED))) {
+ /* Move an item up or down the list. */
+ /* Get the current selection, if any. */
+ int selection = SendDlgItemMessage (hwnd, hdl->listid, LB_GETCURSEL, 0, 0);
+ if (selection == LB_ERR) {
+ MessageBeep(0);
+ } else {
+ int nitems;
+ /* Get the total number of items. */
+ nitems = SendDlgItemMessage (hwnd, hdl->listid, LB_GETCOUNT, 0, 0);
+ /* Should we do anything? */
+ if (LOWORD(wParam) == hdl->upbid && (selection > 0))
+ pl_moveitem(hwnd, hdl->listid, selection, selection - 1);
+ else if (LOWORD(wParam) == hdl->dnbid && (selection < nitems - 1))
+ pl_moveitem(hwnd, hdl->listid, selection, selection + 1);
+ ret |= 2;
+ }
+
+ }
+
+ }
+
+ if (array) {
+ /* Update array to match the list box. */
+ for (i=0; i < maxmemb; i++)
+ array[i] = SendDlgItemMessage (hwnd, hdl->listid, LB_GETITEMDATA,
+ i, 0);
+ }
+
+ return ret;
+}
+
+/*
+ * A progress bar (from Common Controls). We like our progress bars
+ * to be smooth and unbroken, without those ugly divisions; some
+ * older compilers may not support that, but that's life.
+ */
+void progressbar(struct ctlpos *cp, int id)
+{
+ RECT r;
+
+ r.left = GAPBETWEEN;
+ r.top = cp->ypos;
+ r.right = cp->width;
+ r.bottom = PROGBARHEIGHT;
+ cp->ypos += r.bottom + GAPBETWEEN;
+
+ doctl(cp, r, PROGRESS_CLASS, WS_CHILD | WS_VISIBLE
+#ifdef PBS_SMOOTH
+ | PBS_SMOOTH
+#endif
+ , WS_EX_CLIENTEDGE, "", id);
+}
+
+/* ----------------------------------------------------------------------
+ * Platform-specific side of portable dialog-box mechanism.
+ */
+
+/*
+ * This function takes a string, escapes all the ampersands, and
+ * places a single (unescaped) ampersand in front of the first
+ * occurrence of the given shortcut character (which may be
+ * NO_SHORTCUT).
+ *
+ * Return value is a malloc'ed copy of the processed version of the
+ * string.
+ */
+static char *shortcut_escape(const char *text, char shortcut)
+{
+ 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)
+{
+ dlgcontrol *a = (dlgcontrol *)av;
+ struct winctrl *b = (struct winctrl *)bv;
+ if (a < b->ctrl)
+ return -1;
+ else if (a > b->ctrl)
+ return +1;
+ else
+ return 0;
+}
+static int winctrl_cmp_byid_find(void *av, void *bv)
+{
+ int *a = (int *)av;
+ struct winctrl *b = (struct winctrl *)bv;
+ if (*a < b->base_id)
+ return -1;
+ else if (*a >= b->base_id + b->num_ids)
+ return +1;
+ else
+ return 0;
+}
+
+void winctrl_init(struct winctrls *wc)
+{
+ wc->byctrl = newtree234(winctrl_cmp_byctrl);
+ wc->byid = newtree234(winctrl_cmp_byid);
+}
+void winctrl_cleanup(struct winctrls *wc)
+{
+ struct winctrl *c;
+
+ while ((c = index234(wc->byid, 0)) != NULL) {
+ winctrl_remove(wc, c);
+ sfree(c->data);
+ sfree(c);
+ }
+
+ freetree234(wc->byctrl);
+ freetree234(wc->byid);
+ wc->byctrl = wc->byid = NULL;
+}
+
+void winctrl_add(struct winctrls *wc, struct winctrl *c)
+{
+ struct winctrl *ret;
+ if (c->ctrl) {
+ ret = add234(wc->byctrl, c);
+ assert(ret == c);
+ }
+ ret = add234(wc->byid, c);
+ assert(ret == c);
+}
+
+void winctrl_remove(struct winctrls *wc, struct winctrl *c)
+{
+ struct winctrl *ret;
+ ret = del234(wc->byctrl, c);
+ ret = del234(wc->byid, c);
+ assert(ret == c);
+}
+
+struct winctrl *winctrl_findbyctrl(struct winctrls *wc, dlgcontrol *ctrl)
+{
+ return find234(wc->byctrl, ctrl, winctrl_cmp_byctrl_find);
+}
+
+struct winctrl *winctrl_findbyid(struct winctrls *wc, int id)
+{
+ return find234(wc->byid, &id, winctrl_cmp_byid_find);
+}
+
+struct winctrl *winctrl_findbyindex(struct winctrls *wc, int index)
+{
+ return index234(wc->byid, index);
+}
+
+static void move_windows(HWND hwnd, int base_id, int num_ids, LONG dy)
+{
+ if (!dy)
+ return;
+ for (int i = 0; i < num_ids; i++) {
+ HWND win = GetDlgItem(hwnd, base_id + i);
+
+ RECT rect;
+ if (!GetWindowRect(win, &rect))
+ continue;
+
+ POINT p;
+ p.x = rect.left;
+ p.y = rect.top + dy;
+ if (!ScreenToClient(hwnd, &p))
+ continue;
+
+ SetWindowPos(win, NULL, p.x, p.y, 0, 0,
+ SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
+ }
+}
+
+void winctrl_layout(struct dlgparam *dp, struct winctrls *wc,
+ struct ctlpos *cp, struct controlset *s, int *id)
+{
+ struct ctlpos columns[16];
+ int ncols, colstart, colspan;
+
+ struct ctlpos tabdelays[16];
+ dlgcontrol *tabdelayed[16];
+ int ntabdelays;
+
+ struct ctlpos pos;
+
+ char shortcuts[MAX_SHORTCUTS_PER_CTRL];
+ int nshortcuts;
+ char *escaped;
+ int i, actual_base_id, base_id, num_ids, align_id_relative;
+ void *data;
+
+ base_id = *id;
+
+ ctrlset_normalise_aligns(s);
+
+ /* Start a containing box, if we have a boxname. */
+ if (s->boxname && *s->boxname) {
+ struct winctrl *c = snew(struct winctrl);
+ c->ctrl = NULL;
+ c->base_id = c->align_id = base_id;
+ c->num_ids = 1;
+ c->data = NULL;
+ memset(c->shortcuts, NO_SHORTCUT, lenof(c->shortcuts));
+ winctrl_add(wc, c);
+ beginbox(cp, s->boxtitle, base_id);
+ base_id++;
+ }
+
+ /* Draw a title, if we have one. */
+ if (!s->boxname && s->boxtitle) {
+ struct winctrl *c = snew(struct winctrl);
+ c->ctrl = NULL;
+ c->base_id = c->align_id = base_id;
+ c->num_ids = 1;
+ c->data = dupstr(s->boxtitle);
+ memset(c->shortcuts, NO_SHORTCUT, lenof(c->shortcuts));
+ winctrl_add(wc, c);
+ paneltitle(cp, base_id);
+ base_id++;
+ }
+
+ /* Initially we have just one column. */
+ ncols = 1;
+ columns[0] = *cp; /* structure copy */
+
+ /* And initially, there are no pending tab-delayed controls. */
+ ntabdelays = 0;
+
+ /* Loop over each control in the controlset. */
+ for (i = 0; i < s->ncontrols; i++) {
+ dlgcontrol *ctrl = s->ctrls[i];
+
+ /*
+ * Generic processing that pertains to all control types.
+ * At the end of this if statement, we'll have produced
+ * `ctrl' (a pointer to the control we have to create, or
+ * think about creating, in this iteration of the loop),
+ * `pos' (a suitable ctlpos with which to position it), and
+ * `c' (a winctrl structure to receive details of the
+ * dialog IDs). Or we'll have done a `continue', if it was
+ * CTRL_COLUMNS and doesn't require any control creation at
+ * all.
+ */
+ if (ctrl->type == CTRL_COLUMNS) {
+ assert((ctrl->columns.ncols == 1) ^ (ncols == 1));
+
+ if (ncols == 1) {
+ /*
+ * We're splitting into multiple columns.
+ */
+ int lpercent, rpercent, lx, rx, i;
+
+ ncols = ctrl->columns.ncols;
+ assert(ncols <= lenof(columns));
+ for (i = 1; i < ncols; i++)
+ columns[i] = columns[0]; /* structure copy */
+
+ lpercent = 0;
+ for (i = 0; i < ncols; i++) {
+ rpercent = lpercent + ctrl->columns.percentages[i];
+ lx = columns[i].xoff + lpercent *
+ (columns[i].width + GAPBETWEEN) / 100;
+ rx = columns[i].xoff + rpercent *
+ (columns[i].width + GAPBETWEEN) / 100;
+ columns[i].xoff = lx;
+ columns[i].width = rx - lx - GAPBETWEEN;
+ lpercent = rpercent;
+ }
+ } else {
+ /*
+ * We're recombining the various columns into one.
+ */
+ int maxy = columns[0].ypos;
+ int i;
+ for (i = 1; i < ncols; i++)
+ if (maxy < columns[i].ypos)
+ maxy = columns[i].ypos;
+ ncols = 1;
+ columns[0] = *cp; /* structure copy */
+ columns[0].ypos = maxy;
+ }
+
+ continue;
+ } else if (ctrl->type == CTRL_TABDELAY) {
+ int i;
+
+ assert(!ctrl->delay_taborder);
+ ctrl = ctrl->tabdelay.ctrl;
+
+ for (i = 0; i < ntabdelays; i++)
+ if (tabdelayed[i] == ctrl)
+ break;
+ assert(i < ntabdelays); /* we have to have found it */
+
+ pos = tabdelays[i]; /* structure copy */
+
+ colstart = colspan = -1; /* indicate this was tab-delayed */
+
+ } else {
+ /*
+ * If it wasn't one of those, it's a genuine control;
+ * so we'll have to compute a position for it now, by
+ * checking its column span.
+ */
+ int col;
+
+ colstart = COLUMN_START(ctrl->column);
+ colspan = COLUMN_SPAN(ctrl->column);
+
+ pos = columns[colstart]; /* structure copy */
+ pos.width = columns[colstart+colspan-1].width +
+ (columns[colstart+colspan-1].xoff - columns[colstart].xoff);
+
+ for (col = colstart; col < colstart+colspan; col++)
+ if (pos.ypos < columns[col].ypos)
+ pos.ypos = columns[col].ypos;
+
+ /*
+ * If this control is to be tabdelayed, add it to the
+ * tabdelay list, and unset pos.hwnd to inhibit actual
+ * control creation.
+ */
+ if (ctrl->delay_taborder) {
+ assert(ntabdelays < lenof(tabdelays));
+ tabdelays[ntabdelays] = pos; /* structure copy */
+ tabdelayed[ntabdelays] = ctrl;
+ ntabdelays++;
+ pos.hwnd = NULL;
+ }
+ }
+
+ /* Most controls don't need anything in c->data. */
+ data = NULL;
+
+ /* And they all start off with no shortcuts registered. */
+ memset(shortcuts, NO_SHORTCUT, lenof(shortcuts));
+ nshortcuts = 0;
+
+ /* Almost all controls start at base_id. */
+ actual_base_id = base_id;
+
+ /* For vertical alignment purposes, the most relevant control
+ * in a group is usually the last one. But that can be
+ * overridden occasionally. */
+ align_id_relative = -1;
+
+ /*
+ * Now we're ready to actually create the control, by
+ * switching on its type.
+ */
+ switch (ctrl->type) {
+ case CTRL_TEXT:
+ if (ctrl->text.wrap) {
+ char *wrapped, *escaped;
+ int lines;
+ num_ids = 1;
+ wrapped = staticwrap(&pos, cp->hwnd,
+ ctrl->label, &lines);
+ escaped = shortcut_escape(wrapped, NO_SHORTCUT);
+ statictext(&pos, escaped, lines, base_id);
+ sfree(escaped);
+ sfree(wrapped);
+ } else {
+ num_ids = 1;
+ editboxfw(&pos, false, true, NULL, 0, base_id);
+ SetDlgItemText(pos.hwnd, base_id, ctrl->label);
+ MakeDlgItemBorderless(pos.hwnd, base_id);
+ }
+ break;
+ case CTRL_EDITBOX:
+ num_ids = 2; /* static, edit */
+ escaped = shortcut_escape(ctrl->label,
+ ctrl->editbox.shortcut);
+ shortcuts[nshortcuts++] = ctrl->editbox.shortcut;
+ if (ctrl->editbox.percentwidth == 100) {
+ if (ctrl->editbox.has_list)
+ combobox(&pos, escaped,
+ base_id, base_id+1);
+ else
+ editboxfw(&pos, ctrl->editbox.password, false, escaped,
+ base_id, base_id+1);
+ } else {
+ if (ctrl->editbox.has_list) {
+ staticcombo(&pos, escaped, base_id, base_id+1,
+ ctrl->editbox.percentwidth);
+ } else {
+ (ctrl->editbox.password ? staticpassedit : staticedit)
+ (&pos, escaped, base_id, base_id+1,
+ ctrl->editbox.percentwidth);
+ }
+ }
+ sfree(escaped);
+ break;
+ case CTRL_RADIO: {
+ num_ids = ctrl->radio.nbuttons + 1; /* label as well */
+ struct radio *buttons;
+ int i;
+
+ escaped = shortcut_escape(ctrl->label,
+ ctrl->radio.shortcut);
+ shortcuts[nshortcuts++] = ctrl->radio.shortcut;
+
+ buttons = snewn(ctrl->radio.nbuttons, struct radio);
+
+ for (i = 0; i < ctrl->radio.nbuttons; i++) {
+ buttons[i].text =
+ shortcut_escape(ctrl->radio.buttons[i],
+ (char)(ctrl->radio.shortcuts ?
+ ctrl->radio.shortcuts[i] :
+ NO_SHORTCUT));
+ buttons[i].id = base_id + 1 + i;
+ if (ctrl->radio.shortcuts) {
+ assert(nshortcuts < MAX_SHORTCUTS_PER_CTRL);
+ shortcuts[nshortcuts++] = ctrl->radio.shortcuts[i];
+ }
+ }
+
+ radioline_common(&pos, escaped, base_id,
+ ctrl->radio.ncolumns,
+ buttons, ctrl->radio.nbuttons);
+
+ for (i = 0; i < ctrl->radio.nbuttons; i++) {
+ sfree((char *)buttons[i].text);
+ }
+ sfree(buttons);
+ sfree(escaped);
+ break;
+ }
+ case CTRL_CHECKBOX:
+ num_ids = 1;
+ escaped = shortcut_escape(ctrl->label,
+ ctrl->checkbox.shortcut);
+ shortcuts[nshortcuts++] = ctrl->checkbox.shortcut;
+ checkbox(&pos, escaped, base_id);
+ sfree(escaped);
+ break;
+ case CTRL_BUTTON:
+ escaped = shortcut_escape(ctrl->label,
+ ctrl->button.shortcut);
+ shortcuts[nshortcuts++] = ctrl->button.shortcut;
+ if (ctrl->button.iscancel)
+ actual_base_id = IDCANCEL;
+ num_ids = 1;
+ button(&pos, escaped, actual_base_id, ctrl->button.isdefault);
+ sfree(escaped);
+ break;
+ case CTRL_LISTBOX:
+ num_ids = 2;
+ escaped = shortcut_escape(ctrl->label,
+ ctrl->listbox.shortcut);
+ shortcuts[nshortcuts++] = ctrl->listbox.shortcut;
+ if (ctrl->listbox.draglist) {
+ data = snew(struct prefslist);
+ num_ids = 4;
+ prefslist(data, &pos, ctrl->listbox.height, escaped,
+ base_id, base_id+1, base_id+2, base_id+3);
+ shortcuts[nshortcuts++] = 'u'; /* Up */
+ shortcuts[nshortcuts++] = 'd'; /* Down */
+ } else if (ctrl->listbox.height == 0) {
+ /* Drop-down list. */
+ if (ctrl->listbox.percentwidth == 100) {
+ staticddlbig(&pos, escaped,
+ base_id, base_id+1);
+ } else {
+ staticddl(&pos, escaped, base_id,
+ base_id+1, ctrl->listbox.percentwidth);
+ }
+ } else {
+ /* Ordinary list. */
+ listbox(&pos, escaped, base_id, base_id+1,
+ ctrl->listbox.height, ctrl->listbox.multisel);
+ }
+ if (ctrl->listbox.ncols) {
+ /*
+ * This method of getting the box width is a bit of
+ * a hack; we'd do better to try to retrieve the
+ * actual width in dialog units from doctl() just
+ * before MapDialogRect. But that's going to be no
+ * fun, and this should be good enough accuracy.
+ */
+ int width = cp->width * ctrl->listbox.percentwidth;
+ int *tabarray;
+ int i, percent;
+
+ tabarray = snewn(ctrl->listbox.ncols-1, int);
+ percent = 0;
+ for (i = 0; i < ctrl->listbox.ncols-1; i++) {
+ percent += ctrl->listbox.percentages[i];
+ tabarray[i] = width * percent / 10000;
+ }
+ SendDlgItemMessage(cp->hwnd, base_id+1, LB_SETTABSTOPS,
+ ctrl->listbox.ncols-1, (LPARAM)tabarray);
+ sfree(tabarray);
+ }
+ sfree(escaped);
+ break;
+ case CTRL_FILESELECT:
+ escaped = shortcut_escape(ctrl->label, ctrl->fileselect.shortcut);
+ shortcuts[nshortcuts++] = ctrl->fileselect.shortcut;
+ num_ids = 3;
+ if (!ctrl->fileselect.just_button) {
+ editbutton(&pos, escaped, base_id, base_id+1,
+ "Browse...", base_id+2);
+ } else {
+ button(&pos, escaped, base_id+2, false);
+ }
+ sfree(escaped);
+ break;
+ case CTRL_FONTSELECT:
+ num_ids = 3;
+ escaped = shortcut_escape(ctrl->label,
+ ctrl->fontselect.shortcut);
+ shortcuts[nshortcuts++] = ctrl->fontselect.shortcut;
+ statictext(&pos, escaped, 1, base_id);
+ staticbtn(&pos, "", base_id+1, "Change...", base_id+2);
+ data = fontspec_new("", false, 0, 0);
+ sfree(escaped);
+ break;
+ default:
+ unreachable("bad control type in winctrl_layout");
+ }
+
+ /* Translate the original align_id_relative of -1 into n-1 */
+ if (align_id_relative < 0)
+ align_id_relative += num_ids;
+
+ /*
+ * Create a `struct winctrl' for this control, and advance
+ * the dialog ID counter, if it's actually been created
+ * (and isn't tabdelayed).
+ */
+ if (pos.hwnd) {
+ struct winctrl *c = snew(struct winctrl);
+
+ c->ctrl = ctrl;
+ c->base_id = actual_base_id;
+ c->align_id = c->base_id + align_id_relative;
+ c->num_ids = num_ids;
+ c->data = data;
+ memcpy(c->shortcuts, shortcuts, sizeof(shortcuts));
+ winctrl_add(wc, c);
+ winctrl_add_shortcuts(dp, c);
+ if (actual_base_id == base_id)
+ base_id += num_ids;
+
+ if (ctrl->align_next_to) {
+ /*
+ * Implement align_next_to by looking at the y extents
+ * of the two controls now that both are created, and
+ * moving one or the other downwards so that they're
+ * centred on a common horizontal line.
+ */
+ LONG mid2 = 0;
+ for (dlgcontrol *thisctrl = ctrl; thisctrl;
+ thisctrl = thisctrl->align_next_to) {
+ struct winctrl *thisc = winctrl_findbyctrl(wc, thisctrl);
+ assert(thisc);
+
+ HWND win = GetDlgItem(pos.hwnd, thisc->align_id);
+ assert(win);
+
+ RECT rect;
+ if (!GetWindowRect(win, &rect))
+ continue;
+ if (mid2 < rect.top + rect.bottom)
+ mid2 = rect.top + rect.bottom;
+ }
+
+ for (dlgcontrol *thisctrl = ctrl; thisctrl;
+ thisctrl = thisctrl->align_next_to) {
+ struct winctrl *thisc = winctrl_findbyctrl(wc, thisctrl);
+ assert(thisc);
+
+ HWND win = GetDlgItem(pos.hwnd, thisc->align_id);
+ assert(win);
+
+ RECT rect;
+ if (!GetWindowRect(win, &rect))
+ continue;
+
+ LONG dy = (mid2 - (rect.top + rect.bottom)) / 2;
+ move_windows(pos.hwnd, thisc->base_id, thisc->num_ids, dy);
+ }
+ }
+ } else {
+ sfree(data);
+ }
+
+ if (colstart >= 0) {
+ /*
+ * Update the ypos in all columns crossed by this
+ * control.
+ */
+ int i;
+ for (i = colstart; i < colstart+colspan; i++)
+ columns[i].ypos = pos.ypos;
+ }
+ }
+
+ /*
+ * We've now finished laying out the controls; so now update
+ * the ctlpos and control ID that were passed in, terminate
+ * any containing box, and return.
+ */
+ for (i = 0; i < ncols; i++)
+ if (cp->ypos < columns[i].ypos)
+ cp->ypos = columns[i].ypos;
+ *id = base_id;
+
+ if (s->boxname && *s->boxname)
+ endbox(cp);
+}
+
+static void winctrl_set_focus(dlgcontrol *ctrl, struct dlgparam *dp,
+ bool has_focus)
+{
+ if (has_focus) {
+ if (dp->focused)
+ dp->lastfocused = dp->focused;
+ dp->focused = ctrl;
+ } else if (!has_focus && dp->focused == ctrl) {
+ dp->lastfocused = dp->focused;
+ dp->focused = NULL;
+ }
+}
+
+dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp)
+{
+ return dp->focused == ctrl ? dp->lastfocused : dp->focused;
+}
+
+/*
+ * The dialog-box procedure calls this function to handle Windows
+ * messages on a control we manage.
+ */
+bool winctrl_handle_command(struct dlgparam *dp, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ struct winctrl *c;
+ dlgcontrol *ctrl;
+ int i, id;
+ bool ret;
+ static UINT draglistmsg = WM_NULL;
+
+ /*
+ * Filter out pointless window messages. Our interest is in
+ * WM_COMMAND and the drag list message, and nothing else.
+ */
+ if (draglistmsg == WM_NULL)
+ draglistmsg = RegisterWindowMessage (DRAGLISTMSGSTRING);
+
+ if (msg != draglistmsg && msg != WM_COMMAND && msg != WM_DRAWITEM)
+ return false;
+
+ /*
+ * Look up the control ID in our data.
+ */
+ c = NULL;
+ for (i = 0; i < dp->nctrltrees; i++) {
+ c = winctrl_findbyid(dp->controltrees[i], LOWORD(wParam));
+ if (c)
+ break;
+ }
+ if (!c)
+ return false; /* we have nothing to do */
+
+ if (msg == WM_DRAWITEM) {
+ /*
+ * Owner-draw request for a panel title.
+ */
+ LPDRAWITEMSTRUCT di = (LPDRAWITEMSTRUCT) lParam;
+ HDC hdc = di->hDC;
+ RECT r = di->rcItem;
+ SIZE s;
+
+ SetMapMode(hdc, MM_TEXT); /* ensure logical units == pixels */
+
+ GetTextExtentPoint32(hdc, (char *)c->data,
+ strlen((char *)c->data), &s);
+ DrawEdge(hdc, &r, EDGE_ETCHED, BF_ADJUST | BF_RECT);
+ TextOut(hdc,
+ r.left + (r.right-r.left-s.cx)/2,
+ r.top + (r.bottom-r.top-s.cy)/2,
+ (char *)c->data, strlen((char *)c->data));
+
+ return true;
+ }
+
+ ctrl = c->ctrl;
+ id = LOWORD(wParam) - c->base_id;
+
+ if (!ctrl || !ctrl->handler)
+ return false; /* nothing we can do here */
+
+ /*
+ * From here on we do not issue `return' statements until the
+ * very end of the dialog box: any event handler is entitled to
+ * ask for a colour selector, so we _must_ always allow control
+ * to reach the end of this switch statement so that the
+ * subsequent code can test dp->coloursel_wanted().
+ */
+ ret = false;
+ dp->coloursel_wanted = false;
+
+ /*
+ * Now switch on the control type and the message.
+ */
+ switch (ctrl->type) {
+ case CTRL_EDITBOX:
+ if (msg == WM_COMMAND && !ctrl->editbox.has_list &&
+ (HIWORD(wParam) == EN_SETFOCUS || HIWORD(wParam) == EN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == EN_SETFOCUS);
+ if (msg == WM_COMMAND && ctrl->editbox.has_list &&
+ (HIWORD(wParam)==CBN_SETFOCUS || HIWORD(wParam)==CBN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == CBN_SETFOCUS);
+
+ if (msg == WM_COMMAND && !ctrl->editbox.has_list &&
+ HIWORD(wParam) == EN_CHANGE)
+ ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ if (msg == WM_COMMAND &&
+ ctrl->editbox.has_list) {
+ if (HIWORD(wParam) == CBN_SELCHANGE) {
+ int index, len;
+ char *text;
+
+ index = SendDlgItemMessage(dp->hwnd, c->base_id+1,
+ CB_GETCURSEL, 0, 0);
+ len = SendDlgItemMessage(dp->hwnd, c->base_id+1,
+ CB_GETLBTEXTLEN, index, 0);
+ text = snewn(len+1, char);
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, CB_GETLBTEXT,
+ index, (LPARAM)text);
+ SetDlgItemText(dp->hwnd, c->base_id+1, text);
+ sfree(text);
+ ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ } else if (HIWORD(wParam) == CBN_EDITCHANGE) {
+ ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ } else if (HIWORD(wParam) == CBN_KILLFOCUS) {
+ ctrl->handler(ctrl, dp, dp->data, EVENT_REFRESH);
+ }
+
+ }
+ break;
+ case CTRL_RADIO:
+ if (msg == WM_COMMAND &&
+ (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+ /*
+ * We sometimes get spurious BN_CLICKED messages for the
+ * radio button that is just about to _lose_ selection, if
+ * we're switching using the arrow keys. Therefore we
+ * double-check that the button in wParam is actually
+ * checked before generating an event.
+ */
+ if (msg == WM_COMMAND &&
+ (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED) &&
+ IsDlgButtonChecked(dp->hwnd, LOWORD(wParam))) {
+ ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ }
+ break;
+ case CTRL_CHECKBOX:
+ if (msg == WM_COMMAND &&
+ (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+ if (msg == WM_COMMAND &&
+ (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED)) {
+ ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ }
+ break;
+ case CTRL_BUTTON:
+ if (msg == WM_COMMAND &&
+ (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+ if (msg == WM_COMMAND &&
+ (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED)) {
+ ctrl->handler(ctrl, dp, dp->data, EVENT_ACTION);
+ }
+ break;
+ case CTRL_LISTBOX:
+ if (msg == WM_COMMAND && ctrl->listbox.height != 0 &&
+ (HIWORD(wParam)==LBN_SETFOCUS || HIWORD(wParam)==LBN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == LBN_SETFOCUS);
+ if (msg == WM_COMMAND && ctrl->listbox.height == 0 &&
+ (HIWORD(wParam)==CBN_SETFOCUS || HIWORD(wParam)==CBN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == CBN_SETFOCUS);
+ if (msg == WM_COMMAND && id >= 2 &&
+ (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+ if (ctrl->listbox.draglist) {
+ int pret;
+ pret = handle_prefslist(c->data, NULL, 0, (msg != WM_COMMAND),
+ dp->hwnd, wParam, lParam);
+ if (pret & 2)
+ ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ ret = pret & 1;
+ } else {
+ if (msg == WM_COMMAND && HIWORD(wParam) == LBN_DBLCLK) {
+ SetCapture(dp->hwnd);
+ ctrl->handler(ctrl, dp, dp->data, EVENT_ACTION);
+ } else if (msg == WM_COMMAND && HIWORD(wParam) == LBN_SELCHANGE) {
+ ctrl->handler(ctrl, dp, dp->data, EVENT_SELCHANGE);
+ }
+ }
+ break;
+ case CTRL_FILESELECT:
+ if (msg == WM_COMMAND && id == 1 &&
+ (HIWORD(wParam) == EN_SETFOCUS || HIWORD(wParam) == EN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == EN_SETFOCUS);
+ if (msg == WM_COMMAND && id == 2 &&
+ (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+ if (msg == WM_COMMAND && id == 1 && HIWORD(wParam) == EN_CHANGE)
+ ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ if (id == 2 &&
+ (msg == WM_COMMAND &&
+ (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED))) {
+ 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;
+ if (!ctrl->fileselect.just_button) {
+ GetDlgItemText(dp->hwnd, c->base_id+1,
+ filename, lenof(filename));
+ filename[lenof(filename)-1] = '\0';
+ } else {
+ *filename = '\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)) {
+ if (!ctrl->fileselect.just_button) {
+ SetDlgItemText(dp->hwnd, c->base_id + 1, filename);
+ ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ } else {
+ assert(!c->data);
+ c->data = filename;
+ ctrl->handler(ctrl, dp, dp->data, EVENT_ACTION);
+ c->data = NULL;
+ }
+ }
+ }
+ break;
+ case CTRL_FONTSELECT:
+ if (msg == WM_COMMAND && id == 2 &&
+ (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+ winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+ if (id == 2 &&
+ (msg == WM_COMMAND &&
+ (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED))) {
+ CHOOSEFONT cf;
+ LOGFONT lf;
+ HDC hdc;
+ FontSpec *fs = (FontSpec *)c->data;
+
+ hdc = GetDC(0);
+ lf.lfHeight = -MulDiv(fs->height,
+ GetDeviceCaps(hdc, LOGPIXELSY), 72);
+ ReleaseDC(0, hdc);
+ lf.lfWidth = lf.lfEscapement = lf.lfOrientation = 0;
+ lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = 0;
+ lf.lfWeight = (fs->isbold ? FW_BOLD : 0);
+ lf.lfCharSet = fs->charset;
+ lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
+ lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
+ lf.lfQuality = DEFAULT_QUALITY;
+ lf.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
+ strncpy(lf.lfFaceName, fs->name,
+ sizeof(lf.lfFaceName) - 1);
+ lf.lfFaceName[sizeof(lf.lfFaceName) - 1] = '\0';
+
+ cf.lStructSize = sizeof(cf);
+ cf.hwndOwner = dp->hwnd;
+ cf.lpLogFont = &lf;
+ cf.Flags = (dp->fixed_pitch_fonts ? CF_FIXEDPITCHONLY : 0) |
+ CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
+
+ if (ChooseFont(&cf)) {
+ fs = fontspec_new(lf.lfFaceName, (lf.lfWeight == FW_BOLD),
+ cf.iPointSize / 10, lf.lfCharSet);
+ dlg_fontsel_set(ctrl, dp, fs);
+ fontspec_free(fs);
+
+ ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+ }
+ }
+ break;
+ }
+
+ /*
+ * If the above event handler has asked for a colour selector,
+ * now is the time to generate one.
+ */
+ if (dp->coloursel_wanted) {
+ static CHOOSECOLOR cc;
+ static DWORD custom[16] = { 0 }; /* zero initialisers */
+ cc.lStructSize = sizeof(cc);
+ cc.hwndOwner = dp->hwnd;
+ cc.hInstance = (HWND) hinst;
+ cc.lpCustColors = custom;
+ cc.rgbResult = RGB(dp->coloursel_result.r,
+ dp->coloursel_result.g,
+ dp->coloursel_result.b);
+ cc.Flags = CC_FULLOPEN | CC_RGBINIT;
+ if (ChooseColor(&cc)) {
+ dp->coloursel_result.r =
+ (unsigned char) (cc.rgbResult & 0xFF);
+ dp->coloursel_result.g =
+ (unsigned char) (cc.rgbResult >> 8) & 0xFF;
+ dp->coloursel_result.b =
+ (unsigned char) (cc.rgbResult >> 16) & 0xFF;
+ dp->coloursel_result.ok = true;
+ } else
+ dp->coloursel_result.ok = false;
+ ctrl->handler(ctrl, dp, dp->data, EVENT_CALLBACK);
+ }
+
+ return ret;
+}
+
+/*
+ * This function can be called to produce context help on a
+ * control. Returns true if it has actually launched some help.
+ */
+bool winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id)
+{
+ int i;
+ struct winctrl *c;
+
+ /*
+ * Look up the control ID in our data.
+ */
+ c = NULL;
+ for (i = 0; i < dp->nctrltrees; i++) {
+ c = winctrl_findbyid(dp->controltrees[i], id);
+ if (c)
+ break;
+ }
+ if (!c)
+ return false; /* we have nothing to do */
+
+ /*
+ * This is the Windows front end, so we're allowed to assume
+ * `helpctx' is a context string.
+ */
+ if (!c->ctrl || !c->ctrl->helpctx)
+ return false; /* no help available for this ctrl */
+
+ launch_help(hwnd, c->ctrl->helpctx);
+ return true;
+}
+
+/*
+ * Now the various functions that the platform-independent
+ * mechanism can call to access the dialog box entries.
+ */
+
+static struct winctrl *dlg_findbyctrl(struct dlgparam *dp, dlgcontrol *ctrl)
+{
+ int i;
+
+ for (i = 0; i < dp->nctrltrees; i++) {
+ struct winctrl *c = winctrl_findbyctrl(dp->controltrees[i], ctrl);
+ if (c)
+ return c;
+ }
+ return NULL;
+}
+
+bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp)
+{
+ /*
+ * In this implementation of the dialog box, we physically
+ * uncreate controls that aren't in a visible panel of the config
+ * box. So we can tell if a control is visible just by checking if
+ * it _exists_.
+ */
+ return dlg_findbyctrl(dp, ctrl) != NULL;
+}
+
+void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int whichbutton)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ assert(c && c->ctrl->type == CTRL_RADIO);
+ CheckRadioButton(dp->hwnd,
+ c->base_id + 1,
+ c->base_id + c->ctrl->radio.nbuttons,
+ c->base_id + 1 + whichbutton);
+}
+
+int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int i;
+ assert(c && c->ctrl->type == CTRL_RADIO);
+ for (i = 0; i < c->ctrl->radio.nbuttons; i++)
+ if (IsDlgButtonChecked(dp->hwnd, c->base_id + 1 + i))
+ return i;
+ unreachable("no radio button was checked");
+}
+
+void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ assert(c && c->ctrl->type == CTRL_CHECKBOX);
+ CheckDlgButton(dp->hwnd, c->base_id, checked);
+}
+
+bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ assert(c && c->ctrl->type == CTRL_CHECKBOX);
+ return 0 != IsDlgButtonChecked(dp->hwnd, c->base_id);
+}
+
+void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ assert(c && c->ctrl->type == CTRL_EDITBOX);
+ SetDlgItemText(dp->hwnd, c->base_id+1, text);
+}
+
+char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ assert(c && c->ctrl->type == CTRL_EDITBOX);
+ return GetDlgItemText_alloc(dp->hwnd, c->base_id+1);
+}
+
+void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp,
+ size_t start, size_t len)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ assert(c && c->ctrl->type == CTRL_EDITBOX);
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, EM_SETSEL, start, start+len);
+}
+
+/* The `listbox' functions can also apply to combo boxes. */
+void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int msg;
+ assert(c &&
+ (c->ctrl->type == CTRL_LISTBOX ||
+ (c->ctrl->type == CTRL_EDITBOX &&
+ c->ctrl->editbox.has_list)));
+ msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
+ LB_RESETCONTENT : CB_RESETCONTENT);
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0);
+}
+
+void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int msg;
+ assert(c &&
+ (c->ctrl->type == CTRL_LISTBOX ||
+ (c->ctrl->type == CTRL_EDITBOX &&
+ c->ctrl->editbox.has_list)));
+ msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
+ LB_DELETESTRING : CB_DELETESTRING);
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0);
+}
+
+void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int msg;
+ assert(c &&
+ (c->ctrl->type == CTRL_LISTBOX ||
+ (c->ctrl->type == CTRL_EDITBOX &&
+ c->ctrl->editbox.has_list)));
+ msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
+ LB_ADDSTRING : CB_ADDSTRING);
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)text);
+}
+
+/*
+ * Each listbox entry may have a numeric id associated with it.
+ * Note that some front ends only permit a string to be stored at
+ * each position, which means that _if_ you put two identical
+ * strings in any listbox then you MUST not assign them different
+ * IDs and expect to get meaningful results back.
+ */
+void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp,
+ char const *text, int id)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int msg, msg2, index;
+ assert(c &&
+ (c->ctrl->type == CTRL_LISTBOX ||
+ (c->ctrl->type == CTRL_EDITBOX &&
+ c->ctrl->editbox.has_list)));
+ msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
+ LB_ADDSTRING : CB_ADDSTRING);
+ msg2 = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
+ LB_SETITEMDATA : CB_SETITEMDATA);
+ index = SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)text);
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, msg2, index, (LPARAM)id);
+}
+
+int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int msg;
+ assert(c && c->ctrl->type == CTRL_LISTBOX);
+ msg = (c->ctrl->listbox.height != 0 ? LB_GETITEMDATA : CB_GETITEMDATA);
+ return
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0);
+}
+
+/* dlg_listbox_index returns <0 if no single element is selected. */
+int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int msg, ret;
+ assert(c && c->ctrl->type == CTRL_LISTBOX);
+ if (c->ctrl->listbox.multisel) {
+ assert(c->ctrl->listbox.height != 0); /* not combo box */
+ ret = SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETSELCOUNT, 0, 0);
+ if (ret == LB_ERR || ret > 1)
+ return -1;
+ }
+ msg = (c->ctrl->listbox.height != 0 ? LB_GETCURSEL : CB_GETCURSEL);
+ ret = SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0);
+ if (ret == LB_ERR)
+ return -1;
+ else
+ return ret;
+}
+
+bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ assert(c && c->ctrl->type == CTRL_LISTBOX &&
+ c->ctrl->listbox.multisel &&
+ c->ctrl->listbox.height != 0);
+ return
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETSEL, index, 0);
+}
+
+void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int msg;
+ assert(c && c->ctrl->type == CTRL_LISTBOX &&
+ !c->ctrl->listbox.multisel);
+ msg = (c->ctrl->listbox.height != 0 ? LB_SETCURSEL : CB_SETCURSEL);
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0);
+}
+
+void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ assert(c && c->ctrl->type == CTRL_TEXT);
+ SetDlgItemText(dp->hwnd, c->base_id, text);
+}
+
+void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ char *escaped = NULL;
+ int id = -1;
+
+ assert(c);
+ switch (c->ctrl->type) {
+ case CTRL_EDITBOX:
+ escaped = shortcut_escape(text, c->ctrl->editbox.shortcut);
+ id = c->base_id;
+ break;
+ case CTRL_RADIO:
+ escaped = shortcut_escape(text, c->ctrl->radio.shortcut);
+ id = c->base_id;
+ break;
+ case CTRL_CHECKBOX:
+ escaped = shortcut_escape(text, ctrl->checkbox.shortcut);
+ id = c->base_id;
+ break;
+ case CTRL_BUTTON:
+ escaped = shortcut_escape(text, ctrl->button.shortcut);
+ id = c->base_id;
+ break;
+ case CTRL_LISTBOX:
+ escaped = shortcut_escape(text, ctrl->listbox.shortcut);
+ id = c->base_id;
+ break;
+ case CTRL_FILESELECT:
+ escaped = shortcut_escape(text, ctrl->fileselect.shortcut);
+ if (ctrl->fileselect.just_button)
+ id = c->base_id + 2; /* the button */
+ else
+ id = c->base_id; /* the label */
+ break;
+ case CTRL_FONTSELECT:
+ escaped = shortcut_escape(text, ctrl->fontselect.shortcut);
+ id = c->base_id;
+ break;
+ default:
+ unreachable("bad control type in label_change");
+ }
+ if (escaped) {
+ SetDlgItemText(dp->hwnd, id, escaped);
+ sfree(escaped);
+ }
+}
+
+void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ assert(c);
+ assert(c->ctrl->type == CTRL_FILESELECT);
+ assert(!c->ctrl->fileselect.just_button);
+ SetDlgItemText(dp->hwnd, c->base_id+1, fn->path);
+}
+
+Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ char *tmp;
+ Filename *ret;
+ assert(c);
+ assert(c->ctrl->type == CTRL_FILESELECT);
+ if (!c->ctrl->fileselect.just_button) {
+ tmp = GetDlgItemText_alloc(dp->hwnd, c->base_id+1);
+ ret = filename_from_str(tmp);
+ sfree(tmp);
+ return ret;
+ } else {
+ return filename_from_str(c->data);
+ }
+}
+
+void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fs)
+{
+ char *buf, *boldstr;
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ assert(c && c->ctrl->type == CTRL_FONTSELECT);
+
+ fontspec_free((FontSpec *)c->data);
+ c->data = fontspec_copy(fs);
+
+ boldstr = (fs->isbold ? "bold, " : "");
+ if (fs->height == 0)
+ buf = dupprintf("Font: %s, %sdefault height", fs->name, boldstr);
+ else
+ buf = dupprintf("Font: %s, %s%d-%s", fs->name, boldstr,
+ (fs->height < 0 ? -fs->height : fs->height),
+ (fs->height < 0 ? "pixel" : "point"));
+ SetDlgItemText(dp->hwnd, c->base_id+1, buf);
+ sfree(buf);
+
+ dlg_auto_set_fixed_pitch_flag(dp);
+}
+
+FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ assert(c && c->ctrl->type == CTRL_FONTSELECT);
+ return fontspec_copy((FontSpec *)c->data);
+}
+
+/*
+ * Bracketing a large set of updates in these two functions will
+ * cause the front end (if possible) to delay updating the screen
+ * until it's all complete, thus avoiding flicker.
+ */
+void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ if (c && c->ctrl->type == CTRL_LISTBOX) {
+ SendDlgItemMessage(dp->hwnd, c->base_id+1, WM_SETREDRAW, false, 0);
+ }
+}
+
+void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ if (c && c->ctrl->type == CTRL_LISTBOX) {
+ HWND hw = GetDlgItem(dp->hwnd, c->base_id+1);
+ SendMessage(hw, WM_SETREDRAW, true, 0);
+ InvalidateRect(hw, NULL, true);
+ }
+}
+
+void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp)
+{
+ struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+ int id;
+ HWND ctl;
+ if (!c)
+ return;
+ switch (ctrl->type) {
+ case CTRL_EDITBOX: id = c->base_id + 1; break;
+ case CTRL_RADIO:
+ for (id = c->base_id + ctrl->radio.nbuttons; id > 1; id--)
+ if (IsDlgButtonChecked(dp->hwnd, id))
+ break;
+ /*
+ * In the theoretically-unlikely case that no button was
+ * selected, id should come out of this as 1, which is a
+ * reasonable enough choice.
+ */
+ break;
+ case CTRL_CHECKBOX: id = c->base_id; break;
+ case CTRL_BUTTON: id = c->base_id; break;
+ case CTRL_LISTBOX: id = c->base_id + 1; break;
+ case CTRL_FILESELECT: id = c->base_id + 1; break;
+ case CTRL_FONTSELECT: id = c->base_id + 2; break;
+ default: id = c->base_id; break;
+ }
+ ctl = GetDlgItem(dp->hwnd, id);
+ SetFocus(ctl);
+}
+
+/*
+ * During event processing, you might well want to give an error
+ * indication to the user. dlg_beep() is a quick and easy generic
+ * error; dlg_error() puts up a message-box or equivalent.
+ */
+void dlg_beep(dlgparam *dp)
+{
+ MessageBeep(0);
+}
+
+void dlg_error_msg(dlgparam *dp, const char *msg)
+{
+ MessageBox(dp->hwnd, msg,
+ dp->errtitle ? dp->errtitle : NULL,
+ MB_OK | MB_ICONERROR);
+}
+
+/*
+ * This function signals to the front end that the dialog's
+ * processing is completed, and passes an integer value (typically
+ * a success status).
+ */
+void dlg_end(dlgparam *dp, int value)
+{
+ dp->ended = true;
+ dp->endresult = value;
+}
+
+void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp)
+{
+ int i, j;
+ struct winctrl *c;
+
+ if (!ctrl) {
+ /*
+ * Send EVENT_REFRESH to absolutely everything.
+ */
+ for (j = 0; j < dp->nctrltrees; j++) {
+ for (i = 0;
+ (c = winctrl_findbyindex(dp->controltrees[j], i)) != NULL;
+ i++) {
+ if (c->ctrl && c->ctrl->handler != NULL)
+ c->ctrl->handler(c->ctrl, dp,
+ dp->data, EVENT_REFRESH);
+ }
+ }
+ } else {
+ /*
+ * Send EVENT_REFRESH to a specific control.
+ */
+ if (ctrl->handler != NULL)
+ ctrl->handler(ctrl, dp, dp->data, EVENT_REFRESH);
+ }
+}
+
+void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, int r, int g, int b)
+{
+ dp->coloursel_wanted = true;
+ dp->coloursel_result.r = r;
+ dp->coloursel_result.g = g;
+ dp->coloursel_result.b = b;
+}
+
+bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp,
+ int *r, int *g, int *b)
+{
+ if (dp->coloursel_result.ok) {
+ *r = dp->coloursel_result.r;
+ *g = dp->coloursel_result.g;
+ *b = dp->coloursel_result.b;
+ return true;
+ } else
+ return false;
+}
+
+void dlg_auto_set_fixed_pitch_flag(dlgparam *dp)
+{
+ Conf *conf = (Conf *)dp->data;
+ FontSpec *fs;
+ int quality;
+ HFONT hfont;
+ HDC hdc;
+ TEXTMETRIC tm;
+ bool is_var;
+
+ /*
+ * Attempt to load the current font, and see if it's
+ * variable-pitch. If so, start off the fixed-pitch flag for the
+ * dialog box as false.
+ *
+ * We assume here that any client of the dlg_* mechanism which is
+ * using font selectors at all is also using a normal 'Conf *'
+ * as dp->data.
+ */
+
+ quality = conf_get_int(conf, CONF_font_quality);
+ fs = conf_get_fontspec(conf, CONF_font);
+
+ hfont = CreateFont(0, 0, 0, 0, FW_DONTCARE, false, false, false,
+ DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
+ CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality),
+ FIXED_PITCH | FF_DONTCARE, fs->name);
+ hdc = GetDC(NULL);
+ if (hdc && SelectObject(hdc, hfont) && GetTextMetrics(hdc, &tm)) {
+ /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */
+ is_var = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH);
+ } else {
+ is_var = false; /* assume it's basically normal */
+ }
+ if (hdc)
+ ReleaseDC(NULL, hdc);
+ if (hfont)
+ DeleteObject(hfont);
+
+ if (is_var)
+ dp->fixed_pitch_fonts = false;
+}
+
+bool dlg_get_fixed_pitch_flag(dlgparam *dp)
+{
+ return dp->fixed_pitch_fonts;
+}
+
+void dlg_set_fixed_pitch_flag(dlgparam *dp, bool flag)
+{
+ dp->fixed_pitch_fonts = flag;
+}
+
+void dp_init(struct dlgparam *dp)
+{
+ dp->nctrltrees = 0;
+ dp->data = NULL;
+ dp->ended = false;
+ dp->focused = dp->lastfocused = NULL;
+ memset(dp->shortcuts, 0, sizeof(dp->shortcuts));
+ dp->hwnd = NULL;
+ dp->wintitle = dp->errtitle = NULL;
+ dp->fixed_pitch_fonts = true;
+}
+
+void dp_add_tree(struct dlgparam *dp, struct winctrls *wc)
+{
+ assert(dp->nctrltrees < lenof(dp->controltrees));
+ dp->controltrees[dp->nctrltrees++] = wc;
+}
+
+void dp_cleanup(struct dlgparam *dp)
+{
+ sfree(dp->wintitle);
+ sfree(dp->errtitle);
+}
diff --git a/windows/cryptoapi.h b/windows/cryptoapi.h
new file mode 100644
index 00000000..4ea7fe49
--- /dev/null
+++ b/windows/cryptoapi.h
@@ -0,0 +1,27 @@
+/*
+ * cryptoapi.h: Windows Crypto API functions defined in PuTTY that
+ * use the crypt32 library. Also centralises the machinery for
+ * dynamically loading that library, and our own functions using that
+ * in turn.
+ */
+
+DECL_WINDOWS_FUNCTION(extern, BOOL, CryptProtectMemory, (LPVOID,DWORD,DWORD));
+
+bool got_crypt(void);
+
+/*
+ * Function to obfuscate an input string into something usable as a
+ * pathname for a Windows named pipe. Uses CryptProtectMemory to make
+ * the obfuscation depend on a key Windows stores for the owning user,
+ * and then hashes the string as well to make it have a manageable
+ * length and be composed of filename-legal characters.
+ *
+ * Rationale: Windows's named pipes all live in the same namespace, so
+ * one user can see what pipes another user has open. This is an
+ * undesirable privacy leak: in particular, if we used unobfuscated
+ * names for the connection-sharing pipe names, it would permit one
+ * user to know what username@host another user is SSHing to.
+ *
+ * The returned string is dynamically allocated.
+ */
+char *capi_obfuscate_string(const char *realname);
diff --git a/windows/dialog.c b/windows/dialog.c
new file mode 100644
index 00000000..5e49cca9
--- /dev/null
+++ b/windows/dialog.c
@@ -0,0 +1,1342 @@
+/*
+ * dialog.c - dialogs for PuTTY(tel), including the configuration dialog.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <assert.h>
+#include <ctype.h>
+#include <time.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "putty-rc.h"
+#include "win-gui-seat.h"
+#include "storage.h"
+#include "dialog.h"
+#include "licence.h"
+
+#include <commctrl.h>
+#include <commdlg.h>
+#include <shellapi.h>
+
+#ifdef MSVC4
+#define TVINSERTSTRUCT TV_INSERTSTRUCT
+#define TVITEM TV_ITEM
+#define ICON_BIG 1
+#endif
+
+typedef struct PortableDialogStuff {
+ /*
+ * These are the various bits of data required to handle a dialog
+ * box that's been built up from the cross-platform dialog.c
+ * system.
+ */
+
+ /* The 'controlbox' that was returned from the portable setup function */
+ struct controlbox *ctrlbox;
+
+ /* The dlgparam that's passed to all the runtime dlg_* functions.
+ * Declared as an array of 1 so it's convenient to pass it as a pointer. */
+ struct dlgparam dp[1];
+
+ /*
+ * Collections of instantiated controls. There can be more than
+ * one of these, because sometimes you want to destroy and
+ * recreate a subset of them - e.g. when switching panes in the
+ * main PuTTY config box, you delete and recreate _most_ of the
+ * controls, but not the OK and Cancel buttons at the bottom.
+ */
+ size_t nctrltrees;
+ struct winctrls *ctrltrees;
+
+ /*
+ * Flag indicating whether the dialog box has been initialised.
+ * Used to suppresss spurious firing of message handlers during
+ * setup.
+ */
+ bool initialised;
+} PortableDialogStuff;
+
+/*
+ * Initialise a PortableDialogStuff, before launching the dialog box.
+ */
+static PortableDialogStuff *pds_new(size_t nctrltrees)
+{
+ PortableDialogStuff *pds = snew(PortableDialogStuff);
+ memset(pds, 0, sizeof(*pds));
+
+ pds->ctrlbox = ctrl_new_box();
+
+ dp_init(pds->dp);
+
+ pds->nctrltrees = nctrltrees;
+ pds->ctrltrees = snewn(pds->nctrltrees, struct winctrls);
+ for (size_t i = 0; i < pds->nctrltrees; i++) {
+ winctrl_init(&pds->ctrltrees[i]);
+ dp_add_tree(pds->dp, &pds->ctrltrees[i]);
+ }
+
+ pds->dp->errtitle = dupprintf("%s Error", appname);
+
+ pds->initialised = false;
+
+ return pds;
+}
+
+static void pds_free(PortableDialogStuff *pds)
+{
+ ctrl_free_box(pds->ctrlbox);
+
+ dp_cleanup(pds->dp);
+
+ for (size_t i = 0; i < pds->nctrltrees; i++)
+ winctrl_cleanup(&pds->ctrltrees[i]);
+ sfree(pds->ctrltrees);
+
+ sfree(pds);
+}
+
+static INT_PTR pds_default_dlgproc(PortableDialogStuff *pds, HWND hwnd,
+ UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_LBUTTONUP:
+ /*
+ * Button release should trigger WM_OK if there was a
+ * previous double click on the host CA list.
+ */
+ ReleaseCapture();
+ if (pds->dp->ended)
+ ShinyEndDialog(hwnd, pds->dp->endresult ? 1 : 0);
+ break;
+ case WM_COMMAND:
+ case WM_DRAWITEM:
+ default: { /* also handle drag list msg here */
+ /*
+ * Only process WM_COMMAND once the dialog is fully formed.
+ */
+ int ret;
+ if (pds->initialised) {
+ ret = winctrl_handle_command(pds->dp, msg, wParam, lParam);
+ if (pds->dp->ended && GetCapture() != hwnd)
+ ShinyEndDialog(hwnd, pds->dp->endresult ? 1 : 0);
+ } else
+ ret = 0;
+ return ret;
+ }
+ case WM_HELP:
+ if (!winctrl_context_help(pds->dp,
+ hwnd, ((LPHELPINFO)lParam)->iCtrlId))
+ MessageBeep(0);
+ break;
+ case WM_CLOSE:
+ quit_help(hwnd);
+ ShinyEndDialog(hwnd, 0);
+ return 0;
+
+ /* Grrr Explorer will maximize Dialogs! */
+ case WM_SIZE:
+ if (wParam == SIZE_MAXIMIZED)
+ force_normal(hwnd);
+ return 0;
+
+ }
+ return 0;
+}
+
+static void pds_initdialog_start(PortableDialogStuff *pds, HWND hwnd)
+{
+ pds->dp->hwnd = hwnd;
+
+ if (pds->dp->wintitle) /* apply override title, if provided */
+ SetWindowText(hwnd, pds->dp->wintitle);
+
+ /* The portable dialog system generally includes the ability to
+ * handle context help for particular controls. Enable the
+ * relevant window styles if we have a help file available. */
+ if (has_help()) {
+ LONG_PTR style = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
+ SetWindowLongPtr(hwnd, GWL_EXSTYLE, style | WS_EX_CONTEXTHELP);
+ } else {
+ /* If not, and if the dialog template provided a top-level
+ * Help button, delete it */
+ HWND item = GetDlgItem(hwnd, IDC_HELPBTN);
+ if (item)
+ DestroyWindow(item);
+ }
+}
+
+/*
+ * Create the panelfuls of controls in the configuration box.
+ */
+static void pds_create_controls(
+ PortableDialogStuff *pds, size_t which_tree, int base_id,
+ int left, int right, int top, char *path)
+{
+ struct ctlpos cp;
+
+ ctlposinit(&cp, pds->dp->hwnd, left, right, top);
+
+ for (int index = -1; (index = ctrl_find_path(
+ pds->ctrlbox, path, index)) >= 0 ;) {
+ struct controlset *s = pds->ctrlbox->ctrlsets[index];
+ winctrl_layout(pds->dp, &pds->ctrltrees[which_tree], &cp, s, &base_id);
+ }
+}
+
+static void pds_initdialog_finish(PortableDialogStuff *pds)
+{
+ /*
+ * Set focus into the first available control in ctrltree #0,
+ * which the caller was expected to set up to be the one
+ * containing the dialog controls likely to be used first.
+ */
+ struct winctrl *c;
+ for (int i = 0; (c = winctrl_findbyindex(&pds->ctrltrees[0], i)) != NULL;
+ i++) {
+ if (c->ctrl) {
+ dlg_set_focus(c->ctrl, pds->dp);
+ break;
+ }
+ }
+
+ /*
+ * Now we've finished creating our initial set of controls,
+ * it's safe to actually show the window without risking setup
+ * flicker.
+ */
+ ShowWindow(pds->dp->hwnd, SW_SHOWNORMAL);
+
+ pds->initialised = true;
+}
+
+#define LOGEVENT_INITIAL_MAX 128
+#define LOGEVENT_CIRCULAR_MAX 128
+
+static char *events_initial[LOGEVENT_INITIAL_MAX];
+static char *events_circular[LOGEVENT_CIRCULAR_MAX];
+static int ninitial = 0, ncircular = 0, circular_first = 0;
+
+#define PRINTER_DISABLED_STRING "None (printing disabled)"
+
+void force_normal(HWND hwnd)
+{
+ static bool recurse = false;
+
+ WINDOWPLACEMENT wp;
+
+ if (recurse)
+ return;
+ recurse = true;
+
+ wp.length = sizeof(wp);
+ if (GetWindowPlacement(hwnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) {
+ wp.showCmd = SW_SHOWNORMAL;
+ SetWindowPlacement(hwnd, &wp);
+ }
+ recurse = false;
+}
+
+static char *getevent(int i)
+{
+ if (i < ninitial)
+ return events_initial[i];
+ if ((i -= ninitial) < ncircular)
+ return events_circular[(circular_first + i) % LOGEVENT_CIRCULAR_MAX];
+ return NULL;
+}
+
+static HWND logbox;
+HWND event_log_window(void) { return logbox; }
+
+static INT_PTR CALLBACK LogProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ int i;
+
+ switch (msg) {
+ case WM_INITDIALOG: {
+ char *str = dupprintf("%s Event Log", appname);
+ SetWindowText(hwnd, str);
+ sfree(str);
+
+ static int tabs[4] = { 78, 108 };
+ SendDlgItemMessage(hwnd, IDN_LIST, LB_SETTABSTOPS, 2,
+ (LPARAM) tabs);
+
+ for (i = 0; i < ninitial; i++)
+ SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING,
+ 0, (LPARAM) events_initial[i]);
+ for (i = 0; i < ncircular; i++)
+ SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING,
+ 0, (LPARAM) events_circular[(circular_first + i) % LOGEVENT_CIRCULAR_MAX]);
+ return 1;
+ }
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ logbox = NULL;
+ SetActiveWindow(GetParent(hwnd));
+ DestroyWindow(hwnd);
+ return 0;
+ case IDN_COPY:
+ if (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED) {
+ int selcount;
+ int *selitems;
+ selcount = SendDlgItemMessage(hwnd, IDN_LIST,
+ LB_GETSELCOUNT, 0, 0);
+ if (selcount == 0) { /* don't even try to copy zero items */
+ MessageBeep(0);
+ break;
+ }
+
+ selitems = snewn(selcount, int);
+ if (selitems) {
+ int count = SendDlgItemMessage(hwnd, IDN_LIST,
+ LB_GETSELITEMS,
+ selcount,
+ (LPARAM) selitems);
+ 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;
+}
+
+static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ char *str;
+
+ switch (msg) {
+ case WM_INITDIALOG: {
+ str = dupprintf("About %s", appname);
+ SetWindowText(hwnd, str);
+ sfree(str);
+ char *buildinfo_text = buildinfo("\r\n");
+ char *text = dupprintf(
+ "%s\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s",
+ appname, ver, buildinfo_text,
+ "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved.");
+ sfree(buildinfo_text);
+ SetDlgItemText(hwnd, IDA_TEXT, text);
+ MakeDlgItemBorderless(hwnd, IDA_TEXT);
+ sfree(text);
+ return 1;
+ }
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hwnd, true);
+ return 0;
+ case IDA_LICENCE:
+ EnableWindow(hwnd, 0);
+ DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCEBOX),
+ hwnd, LicenceProc);
+ EnableWindow(hwnd, 1);
+ SetActiveWindow(hwnd);
+ return 0;
+
+ case IDA_WEB:
+ /* Load web browser */
+ ShellExecute(hwnd, "open",
+ "https://www.chiark.greenend.org.uk/~sgtatham/putty/",
+ 0, 0, SW_SHOWDEFAULT);
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ EndDialog(hwnd, true);
+ return 0;
+ }
+ return 0;
+}
+
+/*
+ * Null dialog procedure.
+ */
+static INT_PTR CALLBACK NullDlgProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ return 0;
+}
+
+enum {
+ IDCX_ABOUT = IDC_ABOUT,
+ IDCX_TVSTATIC,
+ IDCX_TREEVIEW,
+ IDCX_STDBASE,
+ IDCX_PANELBASE = IDCX_STDBASE + 32
+};
+
+struct treeview_faff {
+ HWND treeview;
+ HTREEITEM lastat[4];
+};
+
+static HTREEITEM treeview_insert(struct treeview_faff *faff,
+ int level, char *text, char *path)
+{
+ TVINSERTSTRUCT ins;
+ int i;
+ HTREEITEM newitem;
+ ins.hParent = (level > 0 ? faff->lastat[level - 1] : TVI_ROOT);
+ ins.hInsertAfter = faff->lastat[level];
+#if _WIN32_IE >= 0x0400 && defined NONAMELESSUNION
+#define INSITEM DUMMYUNIONNAME.item
+#else
+#define INSITEM item
+#endif
+ ins.INSITEM.mask = TVIF_TEXT | TVIF_PARAM;
+ ins.INSITEM.pszText = text;
+ ins.INSITEM.cchTextMax = strlen(text)+1;
+ ins.INSITEM.lParam = (LPARAM)path;
+ newitem = TreeView_InsertItem(faff->treeview, &ins);
+ if (level > 0)
+ TreeView_Expand(faff->treeview, faff->lastat[level - 1],
+ (level > 1 ? TVE_COLLAPSE : TVE_EXPAND));
+ faff->lastat[level] = newitem;
+ for (i = level + 1; i < 4; i++)
+ faff->lastat[i] = NULL;
+ return newitem;
+}
+
+const char *dialog_box_demo_screenshot_filename = NULL;
+
+/* ctrltrees indices for the main dialog box */
+enum {
+ TREE_PANEL, /* things we swap out every time treeview selects a new pane */
+ TREE_BASE, /* fixed things at the bottom like OK and Cancel buttons */
+};
+
+/*
+ * This function is the configuration box.
+ * (Being a dialog procedure, in general it returns 0 if the default
+ * dialog processing should be performed, and 1 if it should not.)
+ */
+static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
+ LPARAM lParam, void *ctx)
+{
+ PortableDialogStuff *pds = (PortableDialogStuff *)ctx;
+ const int DEMO_SCREENSHOT_TIMER_ID = 1230;
+ HWND treeview;
+ struct treeview_faff tvfaff;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ pds_initdialog_start(pds, hwnd);
+
+ pds_create_controls(pds, TREE_BASE, IDCX_STDBASE, 3, 3, 235, "");
+
+ SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
+ (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON)));
+
+ centre_window(hwnd);
+
+ /*
+ * Create the tree view.
+ */
+ {
+ RECT r;
+ WPARAM font;
+ HWND tvstatic;
+
+ r.left = 3;
+ r.right = r.left + 95;
+ r.top = 3;
+ r.bottom = r.top + 10;
+ MapDialogRect(hwnd, &r);
+ tvstatic = CreateWindowEx(0, "STATIC", "Cate&gory:",
+ WS_CHILD | WS_VISIBLE,
+ r.left, r.top,
+ r.right - r.left, r.bottom - r.top,
+ hwnd, (HMENU) IDCX_TVSTATIC, hinst,
+ NULL);
+ font = SendMessage(hwnd, WM_GETFONT, 0, 0);
+ SendMessage(tvstatic, WM_SETFONT, font, MAKELPARAM(true, 0));
+
+ r.left = 3;
+ r.right = r.left + 95;
+ r.top = 13;
+ r.bottom = r.top + 219;
+ MapDialogRect(hwnd, &r);
+ treeview = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, "",
+ WS_CHILD | WS_VISIBLE |
+ WS_TABSTOP | TVS_HASLINES |
+ TVS_DISABLEDRAGDROP | TVS_HASBUTTONS
+ | TVS_LINESATROOT |
+ TVS_SHOWSELALWAYS, r.left, r.top,
+ r.right - r.left, r.bottom - r.top,
+ hwnd, (HMENU) IDCX_TREEVIEW, hinst,
+ NULL);
+ font = SendMessage(hwnd, WM_GETFONT, 0, 0);
+ SendMessage(treeview, WM_SETFONT, font, MAKELPARAM(true, 0));
+ tvfaff.treeview = treeview;
+ memset(tvfaff.lastat, 0, sizeof(tvfaff.lastat));
+ }
+
+ /*
+ * Set up the tree view contents.
+ */
+ {
+ HTREEITEM hfirst = NULL;
+ int i;
+ char *path = NULL;
+ char *firstpath = NULL;
+
+ for (i = 0; i < pds->ctrlbox->nctrlsets; i++) {
+ struct controlset *s = pds->ctrlbox->ctrlsets[i];
+ HTREEITEM item;
+ int j;
+ char *c;
+
+ if (!s->pathname[0])
+ continue;
+ j = path ? ctrl_path_compare(s->pathname, path) : 0;
+ if (j == INT_MAX)
+ continue; /* same path, nothing to add to tree */
+
+ /*
+ * We expect never to find an implicit path
+ * component. For example, we expect never to see
+ * A/B/C followed by A/D/E, because that would
+ * _implicitly_ create A/D. All our path prefixes
+ * are expected to contain actual controls and be
+ * selectable in the treeview; so we would expect
+ * to see A/D _explicitly_ before encountering
+ * A/D/E.
+ */
+ assert(j == ctrl_path_elements(s->pathname) - 1);
+
+ c = strrchr(s->pathname, '/');
+ if (!c)
+ c = s->pathname;
+ else
+ c++;
+
+ item = treeview_insert(&tvfaff, j, c, s->pathname);
+ if (!hfirst) {
+ hfirst = item;
+ firstpath = s->pathname;
+ }
+
+ path = s->pathname;
+ }
+
+ /*
+ * Put the treeview selection on to the first panel in the
+ * ctrlbox.
+ */
+ TreeView_SelectItem(treeview, hfirst);
+
+ /*
+ * And create the actual control set for that panel, to
+ * match the initial treeview selection.
+ */
+ assert(firstpath); /* config.c must have given us _something_ */
+ pds_create_controls(pds, TREE_PANEL, IDCX_PANELBASE,
+ 100, 3, 13, firstpath);
+ dlg_refresh(NULL, pds->dp); /* and set up control values */
+ }
+
+ if (dialog_box_demo_screenshot_filename)
+ SetTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID, TICKSPERSEC, NULL);
+
+ pds_initdialog_finish(pds);
+ return 0;
+
+ case WM_TIMER:
+ if (dialog_box_demo_screenshot_filename &&
+ (UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) {
+ KillTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID);
+ char *err = save_screenshot(
+ hwnd, dialog_box_demo_screenshot_filename);
+ if (err) {
+ MessageBox(hwnd, err, "Demo screenshot failure",
+ MB_OK | MB_ICONERROR);
+ sfree(err);
+ }
+ ShinyEndDialog(hwnd, 0);
+ }
+ return 0;
+
+ case WM_NOTIFY:
+ if (LOWORD(wParam) == IDCX_TREEVIEW &&
+ ((LPNMHDR) lParam)->code == TVN_SELCHANGED) {
+ /*
+ * Selection-change events on the treeview cause us to do
+ * a flurry of control deletion and creation - but only
+ * after WM_INITDIALOG has finished. The initial
+ * selection-change event(s) during treeview setup are
+ * ignored.
+ */
+ HTREEITEM i;
+ TVITEM item;
+ char buffer[64];
+
+ if (!pds->initialised)
+ return 0;
+
+ i = TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom);
+
+ SendMessage (hwnd, WM_SETREDRAW, false, 0);
+
+ item.hItem = i;
+ item.pszText = buffer;
+ item.cchTextMax = sizeof(buffer);
+ item.mask = TVIF_TEXT | TVIF_PARAM;
+ TreeView_GetItem(((LPNMHDR) lParam)->hwndFrom, &item);
+ {
+ /* Destroy all controls in the currently visible panel. */
+ int k;
+ HWND item;
+ struct winctrl *c;
+
+ while ((c = winctrl_findbyindex(
+ &pds->ctrltrees[TREE_PANEL], 0)) != NULL) {
+ for (k = 0; k < c->num_ids; k++) {
+ item = GetDlgItem(hwnd, c->base_id + k);
+ if (item)
+ DestroyWindow(item);
+ }
+ winctrl_rem_shortcuts(pds->dp, c);
+ winctrl_remove(&pds->ctrltrees[TREE_PANEL], c);
+ sfree(c->data);
+ sfree(c);
+ }
+ }
+ pds_create_controls(pds, TREE_PANEL, IDCX_PANELBASE,
+ 100, 3, 13, (char *)item.lParam);
+
+ dlg_refresh(NULL, pds->dp); /* set up control values */
+
+ SendMessage (hwnd, WM_SETREDRAW, true, 0);
+ InvalidateRect (hwnd, NULL, true);
+
+ SetFocus(((LPNMHDR) lParam)->hwndFrom); /* ensure focus stays */
+ }
+ return 0;
+
+ default:
+ return pds_default_dlgproc(pds, hwnd, msg, wParam, lParam);
+ }
+}
+
+void modal_about_box(HWND hwnd)
+{
+ EnableWindow(hwnd, 0);
+ DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
+ EnableWindow(hwnd, 1);
+ SetActiveWindow(hwnd);
+}
+
+void show_help(HWND hwnd)
+{
+ launch_help(hwnd, NULL);
+}
+
+void defuse_showwindow(void)
+{
+ /*
+ * Work around the fact that the app's first call to ShowWindow
+ * will ignore the default in favour of the shell-provided
+ * setting.
+ */
+ {
+ HWND hwnd;
+ hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX),
+ NULL, NullDlgProc);
+ ShowWindow(hwnd, SW_HIDE);
+ SetActiveWindow(hwnd);
+ DestroyWindow(hwnd);
+ }
+}
+
+bool do_config(Conf *conf)
+{
+ bool ret;
+ PortableDialogStuff *pds = pds_new(2);
+
+ setup_config_box(pds->ctrlbox, false, 0, 0);
+ win_setup_config_box(pds->ctrlbox, &pds->dp->hwnd, has_help(), false, 0);
+
+ pds->dp->wintitle = dupprintf("%s Configuration", appname);
+ pds->dp->data = conf;
+
+ dlg_auto_set_fixed_pitch_flag(pds->dp);
+
+ pds->dp->shortcuts['g'] = true; /* the treeview: `Cate&gory' */
+
+ ret = ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), "PuTTYConfigBox",
+ NULL, GenericMainDlgProc, pds);
+
+ pds_free(pds);
+
+ return ret;
+}
+
+bool do_reconfig(HWND hwnd, Conf *conf, int protcfginfo)
+{
+ Conf *backup_conf;
+ bool ret;
+ int protocol;
+ PortableDialogStuff *pds = pds_new(2);
+
+ backup_conf = conf_copy(conf);
+
+ protocol = conf_get_int(conf, CONF_protocol);
+ setup_config_box(pds->ctrlbox, true, protocol, protcfginfo);
+ win_setup_config_box(pds->ctrlbox, &pds->dp->hwnd, has_help(),
+ true, protocol);
+
+ pds->dp->wintitle = dupprintf("%s Reconfiguration", appname);
+ pds->dp->data = conf;
+
+ dlg_auto_set_fixed_pitch_flag(pds->dp);
+
+ pds->dp->shortcuts['g'] = true; /* the treeview: `Cate&gory' */
+
+ ret = ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), "PuTTYConfigBox",
+ NULL, GenericMainDlgProc, pds);
+
+ pds_free(pds);
+
+ if (!ret)
+ conf_copy_into(conf, backup_conf);
+
+ conf_free(backup_conf);
+
+ return ret;
+}
+
+static void win_gui_eventlog(LogPolicy *lp, const char *string)
+{
+ char timebuf[40];
+ char **location;
+ struct tm tm;
+
+ tm=ltime();
+ strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
+
+ if (ninitial < LOGEVENT_INITIAL_MAX)
+ location = &events_initial[ninitial];
+ else
+ location = &events_circular[(circular_first + ncircular) % LOGEVENT_CIRCULAR_MAX];
+
+ if (*location)
+ sfree(*location);
+ *location = dupcat(timebuf, string);
+ if (logbox) {
+ int count;
+ SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING,
+ 0, (LPARAM) *location);
+ count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0);
+ SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0);
+ }
+ if (ninitial < LOGEVENT_INITIAL_MAX) {
+ ninitial++;
+ } else if (ncircular < LOGEVENT_CIRCULAR_MAX) {
+ ncircular++;
+ } else if (ncircular == LOGEVENT_CIRCULAR_MAX) {
+ circular_first = (circular_first + 1) % LOGEVENT_CIRCULAR_MAX;
+ sfree(events_circular[circular_first]);
+ events_circular[circular_first] = dupstr("..");
+ }
+}
+
+static void win_gui_logging_error(LogPolicy *lp, const char *event)
+{
+ WinGuiSeat *wgs = container_of(lp, WinGuiSeat, logpolicy);
+
+ /* Send 'can't open log file' errors to the terminal window.
+ * (Marked as stderr, although terminal.c won't care.) */
+ seat_stderr_pl(&wgs->seat, ptrlen_from_asciz(event));
+ seat_stderr_pl(&wgs->seat, PTRLEN_LITERAL("\r\n"));
+}
+
+void showeventlog(HWND hwnd)
+{
+ if (!logbox) {
+ logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX),
+ hwnd, LogProc);
+ ShowWindow(logbox, SW_SHOWNORMAL);
+ }
+ SetActiveWindow(logbox);
+}
+
+void showabout(HWND hwnd)
+{
+ DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
+}
+
+struct hostkey_dialog_ctx {
+ SeatDialogText *text;
+ bool has_title;
+ const char *helpctx;
+};
+
+static INT_PTR HostKeyMoreInfoProc(HWND hwnd, UINT msg, WPARAM wParam,
+ LPARAM lParam, void *vctx)
+{
+ struct hostkey_dialog_ctx *ctx = (struct hostkey_dialog_ctx *)vctx;
+
+ switch (msg) {
+ case WM_INITDIALOG: {
+ int index = 100, y = 12;
+
+ WPARAM font = SendMessage(hwnd, WM_GETFONT, 0, 0);
+
+ const char *key = NULL;
+ for (SeatDialogTextItem *item = ctx->text->items,
+ *end = item + ctx->text->nitems; item < end; item++) {
+ switch (item->type) {
+ case SDT_MORE_INFO_KEY:
+ key = item->text;
+ break;
+ case SDT_MORE_INFO_VALUE_SHORT:
+ case SDT_MORE_INFO_VALUE_BLOB: {
+ RECT rk, rv;
+ DWORD editstyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP |
+ ES_AUTOHSCROLL | ES_READONLY;
+ if (item->type == SDT_MORE_INFO_VALUE_BLOB) {
+ rk.left = 12;
+ rk.right = 376;
+ rk.top = y;
+ rk.bottom = 8;
+ y += 10;
+
+ editstyle |= ES_MULTILINE;
+ rv.left = 12;
+ rv.right = 376;
+ rv.top = y;
+ rv.bottom = 64;
+ y += 68;
+ } else {
+ rk.left = 12;
+ rk.right = 80;
+ rk.top = y+2;
+ rk.bottom = 8;
+
+ rv.left = 100;
+ rv.right = 288;
+ rv.top = y;
+ rv.bottom = 12;
+
+ y += 16;
+ }
+
+ MapDialogRect(hwnd, &rk);
+ HWND ctl = CreateWindowEx(
+ 0, "STATIC", key, WS_CHILD | WS_VISIBLE,
+ rk.left, rk.top, rk.right, rk.bottom,
+ hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL);
+ SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0));
+
+ MapDialogRect(hwnd, &rv);
+ ctl = CreateWindowEx(
+ WS_EX_CLIENTEDGE, "EDIT", item->text, editstyle,
+ rv.left, rv.top, rv.right, rv.bottom,
+ hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL);
+ SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0));
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ /*
+ * Now resize the overall window, and move the Close button at
+ * the bottom.
+ */
+ RECT r;
+ r.left = 176;
+ r.top = y + 10;
+ r.right = r.bottom = 0;
+ MapDialogRect(hwnd, &r);
+ HWND ctl = GetDlgItem(hwnd, IDOK);
+ SetWindowPos(ctl, NULL, r.left, r.top, 0, 0,
+ SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER);
+
+ r.left = r.top = r.right = 0;
+ r.bottom = 300;
+ MapDialogRect(hwnd, &r);
+ int oldheight = r.bottom;
+
+ r.left = r.top = r.right = 0;
+ r.bottom = y + 30;
+ MapDialogRect(hwnd, &r);
+ int newheight = r.bottom;
+
+ GetWindowRect(hwnd, &r);
+
+ SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left,
+ r.bottom - r.top + newheight - oldheight,
+ SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
+
+ ShowWindow(hwnd, SW_SHOWNORMAL);
+ return 1;
+ }
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ ShinyEndDialog(hwnd, 0);
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ ShinyEndDialog(hwnd, 0);
+ return 0;
+ }
+ return 0;
+}
+
+static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam, void *vctx)
+{
+ struct hostkey_dialog_ctx *ctx = (struct hostkey_dialog_ctx *)vctx;
+
+ switch (msg) {
+ case WM_INITDIALOG: {
+ strbuf *dlg_text = strbuf_new();
+ const char *dlg_title = "";
+ ctx->has_title = false;
+ LPCTSTR iconid = IDI_QUESTION;
+
+ for (SeatDialogTextItem *item = ctx->text->items,
+ *end = item + ctx->text->nitems; item < end; item++) {
+ switch (item->type) {
+ case SDT_PARA:
+ put_fmt(dlg_text, "%s\r\n\r\n", item->text);
+ break;
+ case SDT_DISPLAY:
+ put_fmt(dlg_text, "%s\r\n\r\n", item->text);
+ break;
+ case SDT_SCARY_HEADING:
+ SetDlgItemText(hwnd, IDC_HK_TITLE, item->text);
+ iconid = IDI_WARNING;
+ ctx->has_title = true;
+ break;
+ case SDT_TITLE:
+ dlg_title = item->text;
+ break;
+ default:
+ break;
+ }
+ }
+ while (strbuf_chomp(dlg_text, '\r') || strbuf_chomp(dlg_text, '\n'));
+
+ SetDlgItemText(hwnd, IDC_HK_TEXT, dlg_text->s);
+ MakeDlgItemBorderless(hwnd, IDC_HK_TEXT);
+ strbuf_free(dlg_text);
+
+ SetWindowText(hwnd, dlg_title);
+
+ if (!ctx->has_title) {
+ HWND item = GetDlgItem(hwnd, IDC_HK_TITLE);
+ if (item)
+ DestroyWindow(item);
+ }
+
+ /*
+ * Find out how tall the text in the edit control really ended
+ * up (after line wrapping), and adjust the height of the
+ * whole box to match it.
+ */
+ int height = SendDlgItemMessage(hwnd, IDC_HK_TEXT,
+ EM_GETLINECOUNT, 0, 0);
+ height *= 8; /* height of a text line, by definition of dialog units */
+
+ int edittop = ctx->has_title ? 40 : 20;
+
+ RECT r;
+ r.left = 40;
+ r.top = edittop;
+ r.right = 290;
+ r.bottom = height;
+ MapDialogRect(hwnd, &r);
+ SetWindowPos(GetDlgItem(hwnd, IDC_HK_TEXT), NULL,
+ r.left, r.top, r.right, r.bottom,
+ SWP_NOREDRAW | SWP_NOZORDER);
+
+ static const struct {
+ int id, x;
+ } buttons[] = {
+ { IDCANCEL, 288 },
+ { IDC_HK_ACCEPT, 168 },
+ { IDC_HK_ONCE, 216 },
+ { IDC_HK_MOREINFO, 60 },
+ { IDHELP, 12 },
+ };
+ for (size_t i = 0; i < lenof(buttons); i++) {
+ HWND ctl = GetDlgItem(hwnd, buttons[i].id);
+ r.left = buttons[i].x;
+ r.top = edittop + height + 20;
+ r.right = r.bottom = 0;
+ MapDialogRect(hwnd, &r);
+ SetWindowPos(ctl, NULL, r.left, r.top, 0, 0,
+ SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER);
+ }
+
+ r.left = r.top = r.right = 0;
+ r.bottom = 240;
+ MapDialogRect(hwnd, &r);
+ int oldheight = r.bottom;
+
+ r.left = r.top = r.right = 0;
+ r.bottom = edittop + height + 40;
+ MapDialogRect(hwnd, &r);
+ int newheight = r.bottom;
+
+ GetWindowRect(hwnd, &r);
+
+ SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left,
+ r.bottom - r.top + newheight - oldheight,
+ SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
+
+ HANDLE icon = LoadImage(
+ NULL, iconid, IMAGE_ICON,
+ GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON),
+ LR_SHARED);
+ SendDlgItemMessage(hwnd, IDC_HK_ICON, STM_SETICON, (WPARAM)icon, 0);
+
+ if (!has_help()) {
+ HWND item = GetDlgItem(hwnd, IDHELP);
+ if (item)
+ DestroyWindow(item);
+ }
+
+ ShowWindow(hwnd, SW_SHOWNORMAL);
+
+ return 1;
+ }
+ case WM_CTLCOLORSTATIC: {
+ HDC hdc = (HDC)wParam;
+ HWND control = (HWND)lParam;
+
+ if (GetWindowLongPtr(control, GWLP_ID) == IDC_HK_TITLE &&
+ ctx->has_title) {
+ SetBkMode(hdc, TRANSPARENT);
+ HFONT prev_font = (HFONT)SelectObject(
+ hdc, (HFONT)GetStockObject(SYSTEM_FONT));
+ LOGFONT lf;
+ if (GetObject(prev_font, sizeof(lf), &lf)) {
+ lf.lfWeight = FW_BOLD;
+ lf.lfHeight = lf.lfHeight * 3 / 2;
+ HFONT bold_font = CreateFontIndirect(&lf);
+ if (bold_font)
+ SelectObject(hdc, bold_font);
+ }
+ return (INT_PTR)GetSysColorBrush(COLOR_BTNFACE);
+ }
+ return 0;
+ }
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_HK_ACCEPT:
+ case IDC_HK_ONCE:
+ case IDCANCEL:
+ ShinyEndDialog(hwnd, LOWORD(wParam));
+ return 0;
+ case IDHELP: {
+ launch_help(hwnd, ctx->helpctx);
+ return 0;
+ }
+ case IDC_HK_MOREINFO: {
+ ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_HK_MOREINFO),
+ "PuTTYHostKeyMoreInfo", hwnd,
+ HostKeyMoreInfoProc, ctx);
+ }
+ }
+ return 0;
+ case WM_CLOSE:
+ ShinyEndDialog(hwnd, IDCANCEL);
+ return 0;
+ }
+ return 0;
+}
+
+const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat)
+{
+ static const SeatDialogPromptDescriptions descs = {
+ .hk_accept_action = "press \"Accept\"",
+ .hk_connect_once_action = "press \"Connect Once\"",
+ .hk_cancel_action = "press \"Cancel\"",
+ .hk_cancel_action_Participle = "Pressing \"Cancel\"",
+ };
+ return &descs;
+}
+
+SeatPromptResult win_seat_confirm_ssh_host_key(
+ Seat *seat, const char *host, int port, const char *keytype,
+ char *keystr, SeatDialogText *text, HelpCtx helpctx,
+ void (*callback)(void *ctx, SeatPromptResult result), void *cbctx)
+{
+ WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat);
+
+ struct hostkey_dialog_ctx ctx[1];
+ ctx->text = text;
+ ctx->helpctx = helpctx;
+
+ int mbret = ShinyDialogBox(
+ hinst, MAKEINTRESOURCE(IDD_HOSTKEY), "PuTTYHostKeyDialog",
+ wgs->term_hwnd, HostKeyDialogProc, ctx);
+ assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL);
+ if (mbret == IDC_HK_ACCEPT) {
+ store_host_key(host, port, keytype, keystr);
+ return SPR_OK;
+ } else if (mbret == IDC_HK_ONCE) {
+ return SPR_OK;
+ }
+
+ return SPR_USER_ABORT;
+}
+
+/*
+ * Ask whether the selected algorithm is acceptable (since it was
+ * below the configured 'warn' threshold).
+ */
+SeatPromptResult win_seat_confirm_weak_crypto_primitive(
+ Seat *seat, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, SeatPromptResult 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 SPR_OK;
+ else
+ return SPR_USER_ABORT;
+}
+
+SeatPromptResult win_seat_confirm_weak_cached_hostkey(
+ Seat *seat, const char *algname, const char *betteralgs,
+ void (*callback)(void *ctx, SeatPromptResult 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 SPR_OK;
+ else
+ return SPR_USER_ABORT;
+}
+
+/*
+ * Ask whether to wipe a session log file before writing to it.
+ * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
+ */
+static int win_gui_askappend(LogPolicy *lp, Filename *filename,
+ void (*callback)(void *ctx, int result),
+ void *ctx)
+{
+ static const char msgtemplate[] =
+ "The session log file \"%.*s\" already exists.\n"
+ "You can overwrite it with a new session log,\n"
+ "append your session log to the end of it,\n"
+ "or disable session logging for this session.\n"
+ "Hit Yes to wipe the file, No to append to it,\n"
+ "or Cancel to disable logging.";
+ char *message;
+ char *mbtitle;
+ int mbret;
+
+ message = dupprintf(msgtemplate, FILENAME_MAX, filename->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);
+}
+
+static INT_PTR CAConfigProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam,
+ void *ctx)
+{
+ PortableDialogStuff *pds = (PortableDialogStuff *)ctx;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ pds_initdialog_start(pds, hwnd);
+
+ SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
+ (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON)));
+
+ centre_window(hwnd);
+
+ pds_create_controls(pds, 0, IDCX_PANELBASE, 3, 3, 3, "Main");
+ pds_create_controls(pds, 0, IDCX_STDBASE, 3, 3, 243, "");
+ dlg_refresh(NULL, pds->dp); /* and set up control values */
+
+ pds_initdialog_finish(pds);
+ return 0;
+
+ default:
+ return pds_default_dlgproc(pds, hwnd, msg, wParam, lParam);
+ }
+}
+
+void show_ca_config_box(dlgparam *dp)
+{
+ PortableDialogStuff *pds = pds_new(1);
+
+ setup_ca_config_box(pds->ctrlbox);
+
+ ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_CA_CONFIG), "PuTTYConfigBox",
+ dp ? dp->hwnd : NULL, CAConfigProc, pds);
+
+ pds_free(pds);
+}
diff --git a/windows/gss.c b/windows/gss.c
new file mode 100644
index 00000000..724eeec1
--- /dev/null
+++ b/windows/gss.c
@@ -0,0 +1,702 @@
+#ifndef NO_GSSAPI
+
+#include <limits.h>
+#include "putty.h"
+
+#define SECURITY_WIN32
+#include <security.h>
+
+#include "ssh/pgssapi.h"
+#include "ssh/gss.h"
+#include "ssh/gssc.h"
+
+#include "misc.h"
+
+#define UNIX_EPOCH 11644473600ULL /* Seconds from Windows epoch */
+#define CNS_PERSEC 10000000ULL /* # 100ns per second */
+
+/*
+ * Note, as a special case, 0 relative to the Windows epoch (unspecified) maps
+ * to 0 relative to the POSIX epoch (unspecified)!
+ */
+#define TIME_WIN_TO_POSIX(ft, t) do { \
+ ULARGE_INTEGER uli; \
+ uli.LowPart = (ft).dwLowDateTime; \
+ uli.HighPart = (ft).dwHighDateTime; \
+ if (uli.QuadPart != 0) \
+ uli.QuadPart = uli.QuadPart / CNS_PERSEC - UNIX_EPOCH; \
+ (t) = (time_t) uli.QuadPart; \
+} while(0)
+
+/* Windows code to set up the GSSAPI library list. */
+
+#ifdef _WIN64
+#define MIT_KERB_SUFFIX "64"
+#else
+#define MIT_KERB_SUFFIX "32"
+#endif
+
+const int ngsslibs = 3;
+const char *const gsslibnames[3] = {
+ "MIT Kerberos GSSAPI"MIT_KERB_SUFFIX".DLL",
+ "Microsoft SSPI SECUR32.DLL",
+ "User-specified GSSAPI DLL",
+};
+const struct keyvalwhere gsslibkeywords[] = {
+ { "gssapi32", 0, -1, -1 },
+ { "sspi", 1, -1, -1 },
+ { "custom", 2, -1, -1 },
+};
+
+DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS,
+ AcquireCredentialsHandleA,
+ (SEC_CHAR *, SEC_CHAR *, ULONG, PVOID,
+ PVOID, SEC_GET_KEY_FN, PVOID, PCredHandle, PTimeStamp));
+DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS,
+ InitializeSecurityContextA,
+ (PCredHandle, PCtxtHandle, SEC_CHAR *, ULONG, ULONG,
+ ULONG, PSecBufferDesc, ULONG, PCtxtHandle,
+ PSecBufferDesc, PULONG, PTimeStamp));
+DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS,
+ FreeContextBuffer,
+ (PVOID));
+DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS,
+ FreeCredentialsHandle,
+ (PCredHandle));
+DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS,
+ DeleteSecurityContext,
+ (PCtxtHandle));
+DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS,
+ QueryContextAttributesA,
+ (PCtxtHandle, ULONG, PVOID));
+DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS,
+ MakeSignature,
+ (PCtxtHandle, ULONG, PSecBufferDesc, ULONG));
+DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS,
+ VerifySignature,
+ (PCtxtHandle, PSecBufferDesc, ULONG, PULONG));
+DECL_WINDOWS_FUNCTION(static, DLL_DIRECTORY_COOKIE,
+ AddDllDirectory,
+ (PCWSTR));
+
+typedef struct winSsh_gss_ctx {
+ unsigned long maj_stat;
+ unsigned long min_stat;
+ CredHandle cred_handle;
+ CtxtHandle context;
+ PCtxtHandle context_handle;
+ TimeStamp expiry;
+} winSsh_gss_ctx;
+
+
+const Ssh_gss_buf gss_mech_krb5={9,"\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"};
+
+static void ssh_sspi_bind_fns(struct ssh_gss_library *lib);
+
+static tree234 *libraries_to_never_unload;
+static int library_to_never_unload_cmp(void *av, void *bv)
+{
+ uintptr_t a = (uintptr_t)av, b = (uintptr_t)bv;
+ return a < b ? -1 : a > b ? +1 : 0;
+}
+static void ensure_library_tree_exists(void)
+{
+ if (!libraries_to_never_unload)
+ libraries_to_never_unload = newtree234(library_to_never_unload_cmp);
+}
+static bool library_is_in_never_unload_tree(HMODULE module)
+{
+ ensure_library_tree_exists();
+ return find234(libraries_to_never_unload, module, NULL);
+}
+static void add_library_to_never_unload_tree(HMODULE module)
+{
+ ensure_library_tree_exists();
+ add234(libraries_to_never_unload, module);
+}
+
+struct ssh_gss_liblist *ssh_gss_setup(Conf *conf)
+{
+ HMODULE module;
+ 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 !HAVE_ADDDLLDIRECTORY
+ /* Omit the type-check because older MSVCs don't have this function */
+ GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, AddDllDirectory);
+#else
+ GET_WINDOWS_FUNCTION(kernel32_module, AddDllDirectory);
+#endif
+
+ list->libraries = snewn(3, struct ssh_gss_library);
+ list->nlibraries = 0;
+
+ /* MIT Kerberos GSSAPI implementation */
+ module = NULL;
+ if (RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\MIT\\Kerberos", &regkey)
+ == 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;
+
+ /* No typecheck because Winelib thinks one PVOID is a PLUID */
+ GET_WINDOWS_FUNCTION_NO_TYPECHECK(module, AcquireCredentialsHandleA);
+ GET_WINDOWS_FUNCTION(module, InitializeSecurityContextA);
+ GET_WINDOWS_FUNCTION(module, FreeContextBuffer);
+ GET_WINDOWS_FUNCTION(module, FreeCredentialsHandle);
+ GET_WINDOWS_FUNCTION(module, DeleteSecurityContext);
+ GET_WINDOWS_FUNCTION(module, QueryContextAttributesA);
+ GET_WINDOWS_FUNCTION(module, MakeSignature);
+ GET_WINDOWS_FUNCTION(module, VerifySignature);
+
+ ssh_sspi_bind_fns(lib);
+ }
+
+ /*
+ * Custom GSSAPI DLL.
+ */
+ module = NULL;
+ 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;
+ ULONG ret_flags=0;
+ TimeStamp localexp;
+
+ /* check if we have to delegate ... */
+ if (to_deleg) flags |= ISC_REQ_DELEGATE;
+ winctx->maj_stat = p_InitializeSecurityContextA(&winctx->cred_handle,
+ winctx->context_handle,
+ (char*) srv_name,
+ flags,
+ 0, /* reserved */
+ SECURITY_NATIVE_DREP,
+ &input_desc,
+ 0, /* reserved */
+ &winctx->context,
+ &output_desc,
+ &ret_flags,
+ &localexp);
+
+ localexp_to_exp_lifetime(&localexp, expiry, lifetime);
+
+ /* prepare for the next round */
+ winctx->context_handle = &winctx->context;
+ send_tok->value = wsend_tok.pvBuffer;
+ send_tok->length = wsend_tok.cbBuffer;
+
+ /* check & return our status */
+ if (winctx->maj_stat==SEC_E_OK) return SSH_GSS_S_COMPLETE;
+ if (winctx->maj_stat==SEC_I_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED;
+
+ return SSH_GSS_FAILURE;
+}
+
+static Ssh_gss_stat ssh_sspi_free_tok(struct ssh_gss_library *lib,
+ Ssh_gss_buf *send_tok)
+{
+ /* check input */
+ if (send_tok == NULL) return SSH_GSS_FAILURE;
+
+ /* free Windows buffer */
+ p_FreeContextBuffer(send_tok->value);
+ SSH_GSS_CLEAR_BUF(send_tok);
+
+ return SSH_GSS_OK;
+}
+
+static Ssh_gss_stat ssh_sspi_release_cred(struct ssh_gss_library *lib,
+ Ssh_gss_ctx *ctx)
+{
+ winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) *ctx;
+
+ /* check input */
+ if (winctx == NULL) return SSH_GSS_FAILURE;
+
+ /* free Windows data */
+ p_FreeCredentialsHandle(&winctx->cred_handle);
+ p_DeleteSecurityContext(&winctx->context);
+
+ /* delete our "wrapper" structure */
+ sfree(winctx);
+ *ctx = (Ssh_gss_ctx) NULL;
+
+ return SSH_GSS_OK;
+}
+
+
+static Ssh_gss_stat ssh_sspi_release_name(struct ssh_gss_library *lib,
+ Ssh_gss_name *srv_name)
+{
+ char *pStr= (char *) *srv_name;
+
+ if (pStr == NULL) return SSH_GSS_FAILURE;
+ sfree(pStr);
+ *srv_name = (Ssh_gss_name) NULL;
+
+ return SSH_GSS_OK;
+}
+
+static Ssh_gss_stat ssh_sspi_display_status(struct ssh_gss_library *lib,
+ Ssh_gss_ctx ctx, Ssh_gss_buf *buf)
+{
+ winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) ctx;
+ const char *msg;
+
+ if (winctx == NULL) return SSH_GSS_FAILURE;
+
+ /* decode the error code */
+ switch (winctx->maj_stat) {
+ case SEC_E_OK: msg="SSPI status OK"; break;
+ case SEC_E_INVALID_HANDLE: msg="The handle passed to the function"
+ " is invalid.";
+ break;
+ case SEC_E_TARGET_UNKNOWN: msg="The target was not recognized."; break;
+ case SEC_E_LOGON_DENIED: msg="The logon failed."; break;
+ case SEC_E_INTERNAL_ERROR: msg="The Local Security Authority cannot"
+ " be contacted.";
+ break;
+ case SEC_E_NO_CREDENTIALS: msg="No credentials are available in the"
+ " security package.";
+ break;
+ case SEC_E_NO_AUTHENTICATING_AUTHORITY:
+ msg="No authority could be contacted for authentication."
+ "The domain name of the authenticating party could be wrong,"
+ " the domain could be unreachable, or there might have been"
+ " a trust relationship failure.";
+ break;
+ case SEC_E_INSUFFICIENT_MEMORY:
+ msg="One or more of the SecBufferDesc structures passed as"
+ " an OUT parameter has a buffer that is too small.";
+ break;
+ case SEC_E_INVALID_TOKEN:
+ msg="The error is due to a malformed input token, such as a"
+ " token corrupted in transit, a token"
+ " of incorrect size, or a token passed into the wrong"
+ " security package. Passing a token to"
+ " the wrong package can happen if client and server did not"
+ " negotiate the proper security package.";
+ break;
+ default:
+ msg = "Internal SSPI error";
+ break;
+ }
+
+ buf->value = dupstr(msg);
+ buf->length = strlen(buf->value);
+
+ return SSH_GSS_OK;
+}
+
+static Ssh_gss_stat ssh_sspi_get_mic(struct ssh_gss_library *lib,
+ Ssh_gss_ctx ctx, Ssh_gss_buf *buf,
+ Ssh_gss_buf *hash)
+{
+ winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx;
+ SecPkgContext_Sizes ContextSizes;
+ SecBufferDesc InputBufferDescriptor;
+ SecBuffer InputSecurityToken[2];
+
+ if (winctx == NULL) return SSH_GSS_FAILURE;
+
+ winctx->maj_stat = 0;
+
+ memset(&ContextSizes, 0, sizeof(ContextSizes));
+
+ winctx->maj_stat = p_QueryContextAttributesA(&winctx->context,
+ SECPKG_ATTR_SIZES,
+ &ContextSizes);
+
+ if (winctx->maj_stat != SEC_E_OK ||
+ ContextSizes.cbMaxSignature == 0)
+ return winctx->maj_stat;
+
+ InputBufferDescriptor.cBuffers = 2;
+ InputBufferDescriptor.pBuffers = InputSecurityToken;
+ InputBufferDescriptor.ulVersion = SECBUFFER_VERSION;
+ InputSecurityToken[0].BufferType = SECBUFFER_DATA;
+ InputSecurityToken[0].cbBuffer = buf->length;
+ InputSecurityToken[0].pvBuffer = buf->value;
+ InputSecurityToken[1].BufferType = SECBUFFER_TOKEN;
+ InputSecurityToken[1].cbBuffer = ContextSizes.cbMaxSignature;
+ InputSecurityToken[1].pvBuffer = snewn(ContextSizes.cbMaxSignature, char);
+
+ winctx->maj_stat = p_MakeSignature(&winctx->context,
+ 0,
+ &InputBufferDescriptor,
+ 0);
+
+ if (winctx->maj_stat == SEC_E_OK) {
+ hash->length = InputSecurityToken[1].cbBuffer;
+ hash->value = InputSecurityToken[1].pvBuffer;
+ }
+
+ return winctx->maj_stat;
+}
+
+static Ssh_gss_stat ssh_sspi_verify_mic(struct ssh_gss_library *lib,
+ Ssh_gss_ctx ctx,
+ Ssh_gss_buf *buf,
+ Ssh_gss_buf *mic)
+{
+ winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx;
+ SecBufferDesc InputBufferDescriptor;
+ SecBuffer InputSecurityToken[2];
+ ULONG qop;
+
+ if (winctx == NULL) return SSH_GSS_FAILURE;
+
+ winctx->maj_stat = 0;
+
+ InputBufferDescriptor.cBuffers = 2;
+ InputBufferDescriptor.pBuffers = InputSecurityToken;
+ InputBufferDescriptor.ulVersion = SECBUFFER_VERSION;
+ InputSecurityToken[0].BufferType = SECBUFFER_DATA;
+ InputSecurityToken[0].cbBuffer = buf->length;
+ InputSecurityToken[0].pvBuffer = buf->value;
+ InputSecurityToken[1].BufferType = SECBUFFER_TOKEN;
+ InputSecurityToken[1].cbBuffer = mic->length;
+ InputSecurityToken[1].pvBuffer = mic->value;
+
+ winctx->maj_stat = p_VerifySignature(&winctx->context,
+ &InputBufferDescriptor,
+ 0, &qop);
+ return winctx->maj_stat;
+}
+
+static Ssh_gss_stat ssh_sspi_free_mic(struct ssh_gss_library *lib,
+ Ssh_gss_buf *hash)
+{
+ sfree(hash->value);
+ return SSH_GSS_OK;
+}
+
+static void ssh_sspi_bind_fns(struct ssh_gss_library *lib)
+{
+ lib->indicate_mech = ssh_sspi_indicate_mech;
+ lib->import_name = ssh_sspi_import_name;
+ lib->release_name = ssh_sspi_release_name;
+ lib->init_sec_context = ssh_sspi_init_sec_context;
+ lib->free_tok = ssh_sspi_free_tok;
+ lib->acquire_cred = ssh_sspi_acquire_cred;
+ lib->release_cred = ssh_sspi_release_cred;
+ lib->get_mic = ssh_sspi_get_mic;
+ lib->verify_mic = ssh_sspi_verify_mic;
+ lib->free_mic = ssh_sspi_free_mic;
+ lib->display_status = ssh_sspi_display_status;
+}
+
+#else
+
+/* Dummy function so this source file defines something if NO_GSSAPI
+ is defined. */
+
+void ssh_gss_init(void)
+{
+}
+
+#endif
diff --git a/windows/handle-io.c b/windows/handle-io.c
new file mode 100644
index 00000000..0f5b7e94
--- /dev/null
+++ b/windows/handle-io.c
@@ -0,0 +1,687 @@
+/*
+ * handle-io.c: Module to give Windows front ends the general
+ * ability to deal with consoles, pipes, serial ports, or any other
+ * type of data stream accessed through a Windows API HANDLE rather
+ * than a WinSock SOCKET.
+ *
+ * We do this by spawning a subthread to continuously try to read
+ * from the handle. Every time a read successfully returns some
+ * data, the subthread sets an event object which is picked up by
+ * the main thread, and the main thread then sets an event in
+ * return to instruct the subthread to resume reading.
+ *
+ * Output works precisely the other way round, in a second
+ * subthread. The output subthread should not be attempting to
+ * write all the time, because it hasn't always got data _to_
+ * write; so the output thread waits for an event object notifying
+ * it to _attempt_ a write, and then it sets an event in return
+ * when one completes.
+ *
+ * (It's terribly annoying having to spawn a subthread for each
+ * direction of each handle. Technically it isn't necessary for
+ * serial ports, since we could use overlapped I/O within the main
+ * thread and wait directly on the event objects in the OVERLAPPED
+ * structures. However, we can't use this trick for some types of
+ * file handle at all - for some reason Windows restricts use of
+ * OVERLAPPED to files which were opened with the overlapped flag -
+ * and so we must use threads for those. This being the case, it's
+ * simplest just to use threads for everything rather than trying
+ * to keep track of multiple completely separate mechanisms.)
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+
+/* ----------------------------------------------------------------------
+ * Generic definitions.
+ */
+
+typedef struct handle_list_node handle_list_node;
+struct handle_list_node {
+ handle_list_node *next, *prev;
+};
+static void add_to_ready_list(handle_list_node *node);
+
+/*
+ * Maximum amount of backlog we will allow to build up on an input
+ * handle before we stop reading from it.
+ */
+#define MAX_BACKLOG 32768
+
+struct handle_generic {
+ /*
+ * Initial fields common to both handle_input and handle_output
+ * structures.
+ *
+ * The three HANDLEs are set up at initialisation time and are
+ * thereafter read-only to both main thread and subthread.
+ * `moribund' is only used by the main thread; `done' is
+ * written by the main thread before signalling to the
+ * subthread. `defunct' and `busy' are used only by the main
+ * thread.
+ */
+ HANDLE h; /* the handle itself */
+ handle_list_node ready_node; /* for linking on to the ready list */
+ HANDLE ev_from_main; /* event used to signal back to us */
+ bool moribund; /* are we going to kill this soon? */
+ bool done; /* request subthread to terminate */
+ bool defunct; /* has the subthread already gone? */
+ bool busy; /* operation currently in progress? */
+ void *privdata; /* for client to remember who they are */
+};
+
+typedef enum { HT_INPUT, HT_OUTPUT } HandleType;
+
+/* ----------------------------------------------------------------------
+ * Input threads.
+ */
+
+/*
+ * Data required by an input thread.
+ */
+struct handle_input {
+ /*
+ * Copy of the handle_generic structure.
+ */
+ HANDLE h; /* the handle itself */
+ handle_list_node ready_node; /* for linking on to the ready list */
+ HANDLE ev_from_main; /* event used to signal back to us */
+ bool moribund; /* are we going to kill this soon? */
+ bool done; /* request subthread to terminate */
+ bool defunct; /* has the subthread already gone? */
+ bool busy; /* operation currently in progress? */
+ void *privdata; /* for client to remember who they are */
+
+ /*
+ * Data set at initialisation and then read-only.
+ */
+ int flags;
+
+ /*
+ * Data set by the input thread before marking the handle ready,
+ * and read by the main thread after receiving that signal.
+ */
+ char buffer[4096]; /* the data read from the handle */
+ DWORD len; /* how much data that was */
+ int readerr; /* lets us know about read errors */
+
+ /*
+ * Callback function called by this module when data arrives on
+ * an input handle.
+ */
+ handle_inputfn_t gotdata;
+};
+
+/*
+ * The actual thread procedure for an input thread.
+ */
+static DWORD WINAPI handle_input_threadfunc(void *param)
+{
+ struct handle_input *ctx = (struct handle_input *) param;
+ OVERLAPPED ovl, *povl;
+ HANDLE oev;
+ bool readret, finished;
+ int readlen;
+
+ if (ctx->flags & HANDLE_FLAG_OVERLAPPED) {
+ povl = &ovl;
+ oev = CreateEvent(NULL, true, false, NULL);
+ } else {
+ povl = NULL;
+ }
+
+ if (ctx->flags & HANDLE_FLAG_UNITBUFFER)
+ readlen = 1;
+ else
+ readlen = sizeof(ctx->buffer);
+
+ while (1) {
+ if (povl) {
+ memset(povl, 0, sizeof(OVERLAPPED));
+ povl->hEvent = oev;
+ }
+ readret = ReadFile(ctx->h, ctx->buffer,readlen, &ctx->len, povl);
+ if (!readret)
+ ctx->readerr = GetLastError();
+ else
+ ctx->readerr = 0;
+ if (povl && !readret && ctx->readerr == ERROR_IO_PENDING) {
+ WaitForSingleObject(povl->hEvent, INFINITE);
+ readret = GetOverlappedResult(ctx->h, povl, &ctx->len, false);
+ if (!readret)
+ ctx->readerr = GetLastError();
+ else
+ ctx->readerr = 0;
+ }
+
+ if (!readret) {
+ /*
+ * Windows apparently sends ERROR_BROKEN_PIPE when a
+ * pipe we're reading from is closed normally from the
+ * writing end. This is ludicrous; if that situation
+ * isn't a natural EOF, _nothing_ is. So if we get that
+ * particular error, we pretend it's EOF.
+ */
+ if (ctx->readerr == ERROR_BROKEN_PIPE)
+ ctx->readerr = 0;
+ ctx->len = 0;
+ }
+
+ if (readret && ctx->len == 0 &&
+ (ctx->flags & HANDLE_FLAG_IGNOREEOF))
+ continue;
+
+ /*
+ * If we just set ctx->len to 0, that means the read operation
+ * has returned end-of-file. Telling that to the main thread
+ * will cause it to set its 'defunct' flag and dispose of the
+ * handle structure at the next opportunity, in which case we
+ * mustn't touch ctx at all after the SetEvent. (Hence we do
+ * even _this_ check before the SetEvent.)
+ */
+ finished = (ctx->len == 0);
+
+ add_to_ready_list(&ctx->ready_node);
+
+ if (finished)
+ break;
+
+ WaitForSingleObject(ctx->ev_from_main, INFINITE);
+ if (ctx->done) {
+ /*
+ * The main thread has asked us to shut down. Send back an
+ * event indicating that we've done so. Hereafter we must
+ * not touch ctx at all, because the main thread might
+ * have freed it.
+ */
+ add_to_ready_list(&ctx->ready_node);
+ break;
+ }
+ }
+
+ if (povl)
+ CloseHandle(oev);
+
+ return 0;
+}
+
+/*
+ * This is called after a successful read, or from the
+ * `unthrottle' function. It decides whether or not to begin a new
+ * read operation.
+ */
+static void handle_throttle(struct handle_input *ctx, int backlog)
+{
+ if (ctx->defunct)
+ return;
+
+ /*
+ * If there's a read operation already in progress, do nothing:
+ * when that completes, we'll come back here and be in a
+ * position to make a better decision.
+ */
+ if (ctx->busy)
+ return;
+
+ /*
+ * Otherwise, we must decide whether to start a new read based
+ * on the size of the backlog.
+ */
+ if (backlog < MAX_BACKLOG) {
+ SetEvent(ctx->ev_from_main);
+ ctx->busy = true;
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * Output threads.
+ */
+
+/*
+ * Data required by an output thread.
+ */
+struct handle_output {
+ /*
+ * Copy of the handle_generic structure.
+ */
+ HANDLE h; /* the handle itself */
+ handle_list_node ready_node; /* for linking on to the ready list */
+ HANDLE ev_from_main; /* event used to signal back to us */
+ bool moribund; /* are we going to kill this soon? */
+ bool done; /* request subthread to terminate */
+ bool defunct; /* has the subthread already gone? */
+ bool busy; /* operation currently in progress? */
+ void *privdata; /* for client to remember who they are */
+
+ /*
+ * Data set at initialisation and then read-only.
+ */
+ int flags;
+
+ /*
+ * Data set by the main thread before signalling ev_from_main,
+ * and read by the input thread after receiving that signal.
+ */
+ const char *buffer; /* the data to write */
+ DWORD len; /* how much data there is */
+
+ /*
+ * Data set by the input thread before marking this handle as
+ * ready, and read by the main thread after receiving that signal.
+ */
+ DWORD lenwritten; /* how much data we actually wrote */
+ int writeerr; /* return value from WriteFile */
+
+ /*
+ * Data only ever read or written by the main thread.
+ */
+ bufchain queued_data; /* data still waiting to be written */
+ enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
+
+ /*
+ * Callback function called when the backlog in the bufchain
+ * drops.
+ */
+ handle_outputfn_t sentdata;
+ struct handle *sentdata_param;
+};
+
+static DWORD WINAPI handle_output_threadfunc(void *param)
+{
+ struct handle_output *ctx = (struct handle_output *) param;
+ OVERLAPPED ovl, *povl;
+ HANDLE oev;
+ bool writeret;
+
+ if (ctx->flags & HANDLE_FLAG_OVERLAPPED) {
+ povl = &ovl;
+ oev = CreateEvent(NULL, true, false, NULL);
+ } else {
+ povl = NULL;
+ }
+
+ while (1) {
+ WaitForSingleObject(ctx->ev_from_main, INFINITE);
+ if (ctx->done) {
+ /*
+ * The main thread has asked us to shut down. Send back an
+ * event indicating that we've done so. Hereafter we must
+ * not touch ctx at all, because the main thread might
+ * have freed it.
+ */
+ add_to_ready_list(&ctx->ready_node);
+ break;
+ }
+ if (povl) {
+ memset(povl, 0, sizeof(OVERLAPPED));
+ povl->hEvent = oev;
+ }
+
+ writeret = WriteFile(ctx->h, ctx->buffer, ctx->len,
+ &ctx->lenwritten, povl);
+ if (!writeret)
+ ctx->writeerr = GetLastError();
+ else
+ ctx->writeerr = 0;
+ if (povl && !writeret && GetLastError() == ERROR_IO_PENDING) {
+ writeret = GetOverlappedResult(ctx->h, povl,
+ &ctx->lenwritten, true);
+ if (!writeret)
+ ctx->writeerr = GetLastError();
+ else
+ ctx->writeerr = 0;
+ }
+
+ add_to_ready_list(&ctx->ready_node);
+ if (!writeret) {
+ /*
+ * The write operation has suffered an error. Telling that
+ * to the main thread will cause it to set its 'defunct'
+ * flag and dispose of the handle structure at the next
+ * opportunity, so we must not touch ctx at all after
+ * this.
+ */
+ break;
+ }
+ }
+
+ if (povl)
+ CloseHandle(oev);
+
+ return 0;
+}
+
+static void handle_try_output(struct handle_output *ctx)
+{
+ if (!ctx->busy && bufchain_size(&ctx->queued_data)) {
+ ptrlen data = bufchain_prefix(&ctx->queued_data);
+ ctx->buffer = data.ptr;
+ ctx->len = min(data.len, ~(DWORD)0);
+ SetEvent(ctx->ev_from_main);
+ ctx->busy = true;
+ } else if (!ctx->busy && bufchain_size(&ctx->queued_data) == 0 &&
+ ctx->outgoingeof == EOF_PENDING) {
+ ctx->sentdata(ctx->sentdata_param, 0, 0, true);
+ ctx->h = INVALID_HANDLE_VALUE;
+ ctx->outgoingeof = EOF_SENT;
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * Unified code handling both input and output threads.
+ */
+
+struct handle {
+ HandleType type;
+ union {
+ struct handle_generic g;
+ struct handle_input i;
+ struct handle_output o;
+ } u;
+};
+
+/*
+ * Linked list storing the current list of handles ready to have
+ * something done to them by the main thread.
+ */
+static handle_list_node ready_head[1];
+static CRITICAL_SECTION ready_critsec[1];
+
+/*
+ * Event object used by all subthreads to signal that they've just put
+ * something on the ready list, i.e. that the ready list is non-empty.
+ */
+static HANDLE ready_event = INVALID_HANDLE_VALUE;
+
+static void add_to_ready_list(handle_list_node *node)
+{
+ /*
+ * Called from subthreads, when their handle has done something
+ * that they need the main thread to respond to. We append the
+ * given list node to the end of the ready list, and set
+ * ready_event to signal to the main thread that the ready list is
+ * now non-empty.
+ */
+ EnterCriticalSection(ready_critsec);
+ node->next = ready_head;
+ node->prev = ready_head->prev;
+ node->next->prev = node->prev->next = node;
+ SetEvent(ready_event);
+ LeaveCriticalSection(ready_critsec);
+}
+
+static void remove_from_ready_list(handle_list_node *node)
+{
+ /*
+ * Called from the main thread, just before destroying a 'struct
+ * handle' completely: as a precaution, we make absolutely sure
+ * it's not linked on the ready list, just in case somehow it
+ * still was.
+ */
+ EnterCriticalSection(ready_critsec);
+ node->next->prev = node->prev;
+ node->prev->next = node->next;
+ node->next = node->prev = node;
+ LeaveCriticalSection(ready_critsec);
+}
+
+static void handle_ready(struct handle *h); /* process one handle (below) */
+
+static void handle_ready_callback(void *vctx)
+{
+ /*
+ * Called when the main thread detects ready_event, indicating
+ * that at least one handle is on the ready list. We empty the
+ * whole list and process the handles one by one.
+ *
+ * It's possible that other handles may be destroyed, and hence
+ * taken _off_ the ready list, during this processing. That
+ * shouldn't cause a deadlock, because according to the API docs,
+ * it's safe to call EnterCriticalSection twice in the same thread
+ * - the second call will return immediately because that thread
+ * already owns the critsec. (And then it takes two calls to
+ * LeaveCriticalSection to release it again, which is just what we
+ * want here.)
+ */
+ EnterCriticalSection(ready_critsec);
+ while (ready_head->next != ready_head) {
+ handle_list_node *node = ready_head->next;
+ node->prev->next = node->next;
+ node->next->prev = node->prev;
+ node->next = node->prev = node;
+ handle_ready(container_of(node, struct handle, u.g.ready_node));
+ }
+ LeaveCriticalSection(ready_critsec);
+}
+
+static inline void ensure_ready_event_setup(void)
+{
+ if (ready_event == INVALID_HANDLE_VALUE) {
+ ready_head->prev = ready_head->next = ready_head;
+ InitializeCriticalSection(ready_critsec);
+ ready_event = CreateEvent(NULL, false, false, NULL);
+ add_handle_wait(ready_event, handle_ready_callback, NULL);
+ }
+}
+
+struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata,
+ void *privdata, int flags)
+{
+ struct handle *h = snew(struct handle);
+ DWORD in_threadid; /* required for Win9x */
+
+ h->type = HT_INPUT;
+ h->u.i.h = handle;
+ h->u.i.ev_from_main = CreateEvent(NULL, false, false, NULL);
+ h->u.i.gotdata = gotdata;
+ h->u.i.defunct = false;
+ h->u.i.moribund = false;
+ h->u.i.done = false;
+ h->u.i.privdata = privdata;
+ h->u.i.flags = flags;
+
+ ensure_ready_event_setup();
+ HANDLE hThread = CreateThread(NULL, 0, handle_input_threadfunc,
+ &h->u.i, 0, &in_threadid);
+ if (hThread)
+ CloseHandle(hThread); /* we don't need the thread handle */
+ h->u.i.busy = true;
+
+ return h;
+}
+
+struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata,
+ void *privdata, int flags)
+{
+ struct handle *h = snew(struct handle);
+ DWORD out_threadid; /* required for Win9x */
+
+ h->type = HT_OUTPUT;
+ h->u.o.h = handle;
+ h->u.o.ev_from_main = CreateEvent(NULL, false, false, NULL);
+ h->u.o.busy = false;
+ h->u.o.defunct = false;
+ h->u.o.moribund = false;
+ h->u.o.done = false;
+ h->u.o.privdata = privdata;
+ bufchain_init(&h->u.o.queued_data);
+ h->u.o.outgoingeof = EOF_NO;
+ h->u.o.sentdata = sentdata;
+ h->u.o.sentdata_param = h;
+ h->u.o.flags = flags;
+
+ ensure_ready_event_setup();
+ HANDLE hThread = CreateThread(NULL, 0, handle_output_threadfunc,
+ &h->u.o, 0, &out_threadid);
+ if (hThread)
+ CloseHandle(hThread); /* we don't need the thread handle */
+
+ return h;
+}
+
+size_t handle_write(struct handle *h, const void *data, size_t len)
+{
+ assert(h->type == HT_OUTPUT);
+ assert(h->u.o.outgoingeof == EOF_NO);
+ bufchain_add(&h->u.o.queued_data, data, len);
+ handle_try_output(&h->u.o);
+ return bufchain_size(&h->u.o.queued_data);
+}
+
+void handle_write_eof(struct handle *h)
+{
+ /*
+ * This function is called when we want to proactively send an
+ * end-of-file notification on the handle. We can only do this by
+ * actually closing the handle - so never call this on a
+ * bidirectional handle if we're still interested in its incoming
+ * direction!
+ */
+ assert(h->type == HT_OUTPUT);
+ if (h->u.o.outgoingeof == EOF_NO) {
+ h->u.o.outgoingeof = EOF_PENDING;
+ handle_try_output(&h->u.o);
+ }
+}
+
+static void handle_destroy(struct handle *h)
+{
+ if (h->type == HT_OUTPUT)
+ bufchain_clear(&h->u.o.queued_data);
+ CloseHandle(h->u.g.ev_from_main);
+ remove_from_ready_list(&h->u.g.ready_node);
+ sfree(h);
+}
+
+void handle_free(struct handle *h)
+{
+ assert(h && !h->u.g.moribund);
+ if (h->u.g.busy) {
+ /*
+ * If the handle is currently busy, we cannot immediately free
+ * it, because its subthread is in the middle of something.
+ * (Exception: foreign handles don't have a subthread.)
+ *
+ * Instead we must wait until it's finished its current
+ * operation, because otherwise the subthread will write to
+ * invalid memory after we free its context from under it. So
+ * we set the moribund flag, which will be noticed next time
+ * an operation completes.
+ */
+ h->u.g.moribund = true;
+ } else if (h->u.g.defunct) {
+ /*
+ * There isn't even a subthread; we can go straight to
+ * handle_destroy.
+ */
+ handle_destroy(h);
+ } else {
+ /*
+ * The subthread is alive but not busy, so we now signal it
+ * to die. Set the moribund flag to indicate that it will
+ * want destroying after that.
+ */
+ h->u.g.moribund = true;
+ h->u.g.done = true;
+ h->u.g.busy = true;
+ SetEvent(h->u.g.ev_from_main);
+ }
+}
+
+static void handle_ready(struct handle *h)
+{
+ if (h->u.g.moribund) {
+ /*
+ * A moribund handle is one which we have either already
+ * signalled to die, or are waiting until its current I/O op
+ * completes to do so. Either way, it's treated as already
+ * dead from the external user's point of view, so we ignore
+ * the actual I/O result. We just signal the thread to die if
+ * we haven't yet done so, or destroy the handle if not.
+ */
+ if (h->u.g.done) {
+ handle_destroy(h);
+ } else {
+ h->u.g.done = true;
+ h->u.g.busy = true;
+ SetEvent(h->u.g.ev_from_main);
+ }
+ return;
+ }
+
+ switch (h->type) {
+ int backlog;
+
+ case HT_INPUT:
+ h->u.i.busy = false;
+
+ /*
+ * A signal on an input handle means data has arrived.
+ */
+ if (h->u.i.len == 0) {
+ /*
+ * EOF, or (nearly equivalently) read error.
+ */
+ h->u.i.defunct = true;
+ h->u.i.gotdata(h, NULL, 0, h->u.i.readerr);
+ } else {
+ backlog = h->u.i.gotdata(h, h->u.i.buffer, h->u.i.len, 0);
+ handle_throttle(&h->u.i, backlog);
+ }
+ break;
+
+ case HT_OUTPUT:
+ h->u.o.busy = false;
+
+ /*
+ * A signal on an output handle means we have completed a
+ * write. Call the callback to indicate that the output
+ * buffer size has decreased, or to indicate an error.
+ */
+ if (h->u.o.writeerr) {
+ /*
+ * Write error. Send a negative value to the callback,
+ * and mark the thread as defunct (because the output
+ * thread is terminating by now).
+ */
+ h->u.o.defunct = true;
+ h->u.o.sentdata(h, 0, h->u.o.writeerr, false);
+ } else {
+ bufchain_consume(&h->u.o.queued_data, h->u.o.lenwritten);
+ noise_ultralight(NOISE_SOURCE_IOLEN, h->u.o.lenwritten);
+ h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data), 0, false);
+ handle_try_output(&h->u.o);
+ }
+ break;
+ }
+}
+
+void handle_unthrottle(struct handle *h, size_t backlog)
+{
+ assert(h->type == HT_INPUT);
+ handle_throttle(&h->u.i, backlog);
+}
+
+size_t handle_backlog(struct handle *h)
+{
+ assert(h->type == HT_OUTPUT);
+ return bufchain_size(&h->u.o.queued_data);
+}
+
+void *handle_get_privdata(struct handle *h)
+{
+ return h->u.g.privdata;
+}
+
+static void handle_sink_write(BinarySink *bs, const void *data, size_t len)
+{
+ handle_sink *sink = BinarySink_DOWNCAST(bs, handle_sink);
+ handle_write(sink->h, data, len);
+}
+
+void handle_sink_init(handle_sink *sink, struct handle *h)
+{
+ sink->h = h;
+ BinarySink_INIT(sink, handle_sink_write);
+}
diff --git a/windows/handle-socket.c b/windows/handle-socket.c
new file mode 100644
index 00000000..2820975c
--- /dev/null
+++ b/windows/handle-socket.c
@@ -0,0 +1,512 @@
+/*
+ * General mechanism for wrapping up reading/writing of Windows
+ * HANDLEs into a PuTTY Socket abstraction.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <limits.h>
+
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+
+/*
+ * Freezing one of these sockets is a slightly fiddly business,
+ * because the reads from the handle are happening in a separate
+ * thread as blocking system calls and so once one is in progress it
+ * can't sensibly be interrupted. Hence, after the user tries to
+ * freeze one of these sockets, it's unavoidable that we may receive
+ * one more load of data before we manage to get handle-io.c to stop
+ * reading.
+ */
+typedef enum HandleSocketFreezeState {
+ UNFROZEN, /* reading as normal */
+ FREEZING, /* have been set to frozen but winhandl is still reading */
+ FROZEN, /* really frozen - winhandl has been throttled */
+ THAWING /* we're gradually releasing our remaining data */
+} HandleSocketFreezeState;
+
+typedef struct HandleSocket {
+ union {
+ struct {
+ HANDLE send_H, recv_H, stderr_H;
+ struct handle *send_h, *recv_h, *stderr_h;
+
+ HandleSocketFreezeState frozen;
+ /* We buffer data here if we receive it from winhandl
+ * while frozen. */
+ bufchain inputdata;
+
+ /* Handle logging proxy error messages from stderr_H, if
+ * we have one */
+ ProxyStderrBuf psb;
+
+ bool defer_close, deferred_close; /* in case of re-entrance */
+ };
+ struct {
+ DeferredSocketOpener *opener;
+
+ /* We buffer data here if we receive it via sk_write
+ * before the socket is opened. */
+ bufchain outputdata;
+
+ bool output_eof_pending;
+
+ bool start_frozen;
+ };
+ };
+
+ char *error;
+
+ SockAddr *addr;
+ int port;
+ Plug *plug;
+
+ Socket sock;
+} HandleSocket;
+
+static size_t handle_gotdata(
+ struct handle *h, const void *data, size_t len, int err)
+{
+ HandleSocket *hs = (HandleSocket *)handle_get_privdata(h);
+
+ if (err) {
+ plug_closing_error(hs->plug, "Read error from handle");
+ return 0;
+ } else if (len == 0) {
+ plug_closing_normal(hs->plug);
+ return 0;
+ } else {
+ assert(hs->frozen != FROZEN && hs->frozen != THAWING);
+ if (hs->frozen == FREEZING) {
+ /*
+ * If we've received data while this socket is supposed to
+ * be frozen (because the read handle-io.c started before
+ * sk_set_frozen was called has now returned) then buffer
+ * the data for when we unfreeze.
+ */
+ bufchain_add(&hs->inputdata, data, len);
+ hs->frozen = FROZEN;
+
+ /*
+ * And return a very large backlog, to prevent further
+ * data arriving from winhandl until we unfreeze.
+ */
+ return INT_MAX;
+ } else {
+ plug_receive(hs->plug, 0, data, len);
+ return 0;
+ }
+ }
+}
+
+static size_t handle_stderr(
+ struct handle *h, const void *data, size_t len, int err)
+{
+ HandleSocket *hs = (HandleSocket *)handle_get_privdata(h);
+
+ if (!err && len > 0)
+ log_proxy_stderr(hs->plug, &hs->psb, data, len);
+
+ return 0;
+}
+
+static void handle_sentdata(struct handle *h, size_t new_backlog, int err,
+ bool close)
+{
+ HandleSocket *hs = (HandleSocket *)handle_get_privdata(h);
+
+ if (close) {
+ if (hs->send_H != INVALID_HANDLE_VALUE)
+ CloseHandle(hs->send_H);
+ if (hs->recv_H != INVALID_HANDLE_VALUE && hs->recv_H != hs->send_H)
+ CloseHandle(hs->recv_H);
+ hs->send_H = hs->recv_H = INVALID_HANDLE_VALUE;
+ }
+
+ if (err) {
+ plug_closing_system_error(hs->plug, err);
+ return;
+ }
+
+ plug_sent(hs->plug, new_backlog);
+}
+
+static Plug *sk_handle_plug(Socket *s, Plug *p)
+{
+ HandleSocket *hs = container_of(s, HandleSocket, sock);
+ Plug *ret = hs->plug;
+ if (p)
+ hs->plug = p;
+ return ret;
+}
+
+static void sk_handle_close(Socket *s)
+{
+ HandleSocket *hs = container_of(s, HandleSocket, sock);
+
+ if (hs->defer_close) {
+ hs->deferred_close = true;
+ return;
+ }
+
+ handle_free(hs->send_h);
+ handle_free(hs->recv_h);
+ if (hs->send_H != INVALID_HANDLE_VALUE)
+ CloseHandle(hs->send_H);
+ if (hs->recv_H != INVALID_HANDLE_VALUE && hs->recv_H != hs->send_H)
+ CloseHandle(hs->recv_H);
+ bufchain_clear(&hs->inputdata);
+
+ if (hs->addr)
+ sk_addr_free(hs->addr);
+
+ delete_callbacks_for_context(hs);
+
+ sfree(hs);
+}
+
+static size_t sk_handle_write(Socket *s, const void *data, size_t len)
+{
+ HandleSocket *hs = container_of(s, HandleSocket, sock);
+
+ return handle_write(hs->send_h, data, len);
+}
+
+static size_t sk_handle_write_oob(Socket *s, const void *data, size_t len)
+{
+ /*
+ * oob data is treated as inband; nasty, but nothing really
+ * better we can do
+ */
+ return sk_handle_write(s, data, len);
+}
+
+static void sk_handle_write_eof(Socket *s)
+{
+ HandleSocket *hs = container_of(s, HandleSocket, sock);
+
+ handle_write_eof(hs->send_h);
+}
+
+static void handle_socket_unfreeze(void *hsv)
+{
+ HandleSocket *hs = (HandleSocket *)hsv;
+
+ /*
+ * If we've been put into a state other than THAWING since the
+ * last callback, then we're done.
+ */
+ if (hs->frozen != THAWING)
+ return;
+
+ /*
+ * Get some of the data we've buffered.
+ */
+ ptrlen data = bufchain_prefix(&hs->inputdata);
+ assert(data.len > 0);
+
+ /*
+ * Hand it off to the plug. Be careful of re-entrance - that might
+ * have the effect of trying to close this socket.
+ */
+ hs->defer_close = true;
+ plug_receive(hs->plug, 0, data.ptr, data.len);
+ bufchain_consume(&hs->inputdata, data.len);
+ hs->defer_close = false;
+ if (hs->deferred_close) {
+ sk_handle_close(&hs->sock);
+ return;
+ }
+
+ if (bufchain_size(&hs->inputdata) > 0) {
+ /*
+ * If there's still data in our buffer, stay in THAWING state,
+ * and reschedule ourself.
+ */
+ queue_toplevel_callback(handle_socket_unfreeze, hs);
+ } else {
+ /*
+ * Otherwise, we've successfully thawed!
+ */
+ hs->frozen = UNFROZEN;
+ handle_unthrottle(hs->recv_h, 0);
+ }
+}
+
+static void sk_handle_set_frozen(Socket *s, bool is_frozen)
+{
+ HandleSocket *hs = container_of(s, HandleSocket, sock);
+
+ if (is_frozen) {
+ switch (hs->frozen) {
+ case FREEZING:
+ case FROZEN:
+ return; /* nothing to do */
+
+ case THAWING:
+ /*
+ * We were in the middle of emptying our bufchain, and got
+ * frozen again. In that case, handle-io.c is already
+ * throttled, so just return to FROZEN state. The toplevel
+ * callback will notice and disable itself.
+ */
+ hs->frozen = FROZEN;
+ break;
+
+ case UNFROZEN:
+ /*
+ * The normal case. Go to FREEZING, and expect one more
+ * load of data from winhandl if we're unlucky.
+ */
+ hs->frozen = FREEZING;
+ break;
+ }
+ } else {
+ switch (hs->frozen) {
+ case UNFROZEN:
+ case THAWING:
+ return; /* nothing to do */
+
+ case FREEZING:
+ /*
+ * If winhandl didn't send us any data throughout the time
+ * we were frozen, then we'll still be in this state and
+ * can just unfreeze in the trivial way.
+ */
+ assert(bufchain_size(&hs->inputdata) == 0);
+ hs->frozen = UNFROZEN;
+ break;
+
+ case FROZEN:
+ /*
+ * If we have buffered data, go to THAWING and start
+ * releasing it in top-level callbacks.
+ */
+ hs->frozen = THAWING;
+ queue_toplevel_callback(handle_socket_unfreeze, hs);
+ }
+ }
+}
+
+static const char *sk_handle_socket_error(Socket *s)
+{
+ HandleSocket *hs = container_of(s, HandleSocket, sock);
+ return hs->error;
+}
+
+static SocketPeerInfo *sk_handle_peer_info(Socket *s)
+{
+ HandleSocket *hs = container_of(s, HandleSocket, sock);
+ ULONG pid;
+ static HMODULE kernel32_module;
+ DECL_WINDOWS_FUNCTION(static, BOOL, GetNamedPipeClientProcessId,
+ (HANDLE, PULONG));
+
+ if (!kernel32_module) {
+ kernel32_module = load_system32_dll("kernel32.dll");
+#if !HAVE_GETNAMEDPIPECLIENTPROCESSID
+ /* For older Visual Studio, and MinGW too (at least as of
+ * Ubuntu 16.04), this function isn't available in the header
+ * files to type-check. Ditto the toolchain I use for
+ * Coveritying the Windows code. */
+ GET_WINDOWS_FUNCTION_NO_TYPECHECK(
+ kernel32_module, GetNamedPipeClientProcessId);
+#else
+ GET_WINDOWS_FUNCTION(
+ kernel32_module, GetNamedPipeClientProcessId);
+#endif
+ }
+
+ /*
+ * Of course, not all handles managed by this module will be
+ * server ends of named pipes, but if they are, then it's useful
+ * to log what we can find out about the client end.
+ */
+ if (p_GetNamedPipeClientProcessId &&
+ p_GetNamedPipeClientProcessId(hs->send_H, &pid)) {
+ SocketPeerInfo *pi = snew(SocketPeerInfo);
+ pi->addressfamily = ADDRTYPE_LOCAL;
+ pi->addr_text = NULL;
+ pi->port = -1;
+ pi->log_text = dupprintf("process id %lu", (unsigned long)pid);
+ return pi;
+ }
+
+ return NULL;
+}
+
+static const SocketVtable HandleSocket_sockvt = {
+ .plug = sk_handle_plug,
+ .close = sk_handle_close,
+ .write = sk_handle_write,
+ .write_oob = sk_handle_write_oob,
+ .write_eof = sk_handle_write_eof,
+ .set_frozen = sk_handle_set_frozen,
+ .socket_error = sk_handle_socket_error,
+ .peer_info = sk_handle_peer_info,
+};
+
+static void sk_handle_connect_success_callback(void *ctx)
+{
+ HandleSocket *hs = (HandleSocket *)ctx;
+ plug_log(hs->plug, PLUGLOG_CONNECT_SUCCESS, hs->addr, hs->port, NULL, 0);
+}
+
+Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H,
+ SockAddr *addr, int port, Plug *plug,
+ bool overlapped)
+{
+ HandleSocket *hs;
+ int flags = (overlapped ? HANDLE_FLAG_OVERLAPPED : 0);
+
+ hs = snew(HandleSocket);
+ hs->sock.vt = &HandleSocket_sockvt;
+ hs->addr = addr;
+ hs->port = port;
+ hs->plug = plug;
+ hs->error = NULL;
+
+ hs->frozen = UNFROZEN;
+ bufchain_init(&hs->inputdata);
+ psb_init(&hs->psb);
+
+ hs->recv_H = recv_H;
+ hs->recv_h = handle_input_new(hs->recv_H, handle_gotdata, hs, flags);
+ hs->send_H = send_H;
+ hs->send_h = handle_output_new(hs->send_H, handle_sentdata, hs, flags);
+ hs->stderr_H = stderr_H;
+ if (hs->stderr_H)
+ hs->stderr_h = handle_input_new(hs->stderr_H, handle_stderr,
+ hs, flags);
+
+ hs->defer_close = hs->deferred_close = false;
+
+ queue_toplevel_callback(sk_handle_connect_success_callback, hs);
+
+ return &hs->sock;
+}
+
+void handle_socket_set_psb_prefix(Socket *s, const char *prefix)
+{
+ HandleSocket *hs = container_of(s, HandleSocket, sock);
+ assert(hs->sock.vt == &HandleSocket_sockvt);
+ psb_set_prefix(&hs->psb, prefix);
+}
+
+static void sk_handle_deferred_close(Socket *s)
+{
+ HandleSocket *hs = container_of(s, HandleSocket, sock);
+
+ deferred_socket_opener_free(hs->opener);
+ bufchain_clear(&hs->outputdata);
+
+ if (hs->addr)
+ sk_addr_free(hs->addr);
+
+ delete_callbacks_for_context(hs);
+
+ sfree(hs);
+}
+
+static size_t sk_handle_deferred_write(Socket *s, const void *data, size_t len)
+{
+ HandleSocket *hs = container_of(s, HandleSocket, sock);
+ assert(!hs->output_eof_pending);
+ bufchain_add(&hs->outputdata, data, len);
+ return bufchain_size(&hs->outputdata);
+}
+
+static void sk_handle_deferred_write_eof(Socket *s)
+{
+ HandleSocket *hs = container_of(s, HandleSocket, sock);
+ assert(!hs->output_eof_pending);
+ hs->output_eof_pending = true;
+}
+
+static void sk_handle_deferred_set_frozen(Socket *s, bool is_frozen)
+{
+ HandleSocket *hs = container_of(s, HandleSocket, sock);
+ hs->frozen = is_frozen;
+}
+
+static SocketPeerInfo *sk_handle_deferred_peer_info(Socket *s)
+{
+ return NULL;
+}
+
+static const SocketVtable HandleSocket_deferred_sockvt = {
+ .plug = sk_handle_plug,
+ .close = sk_handle_deferred_close,
+ .write = sk_handle_deferred_write,
+ .write_oob = sk_handle_deferred_write,
+ .write_eof = sk_handle_deferred_write_eof,
+ .set_frozen = sk_handle_deferred_set_frozen,
+ .socket_error = sk_handle_socket_error,
+ .peer_info = sk_handle_deferred_peer_info,
+};
+
+Socket *make_deferred_handle_socket(DeferredSocketOpener *opener,
+ SockAddr *addr, int port, Plug *plug)
+{
+ HandleSocket *hs = snew(HandleSocket);
+ hs->sock.vt = &HandleSocket_deferred_sockvt;
+ hs->addr = addr;
+ hs->port = port;
+ hs->plug = plug;
+ hs->error = NULL;
+
+ hs->opener = opener;
+ bufchain_init(&hs->outputdata);
+ hs->output_eof_pending = false;
+ hs->start_frozen = false;
+
+ return &hs->sock;
+}
+
+void setup_handle_socket(Socket *s, HANDLE send_H, HANDLE recv_H,
+ HANDLE stderr_H, bool overlapped)
+{
+ HandleSocket *hs = container_of(s, HandleSocket, sock);
+ assert(hs->sock.vt == &HandleSocket_deferred_sockvt);
+
+ int flags = (overlapped ? HANDLE_FLAG_OVERLAPPED : 0);
+
+ struct handle *recv_h = handle_input_new(
+ recv_H, handle_gotdata, hs, flags);
+ struct handle *send_h = handle_output_new(
+ send_H, handle_sentdata, hs, flags);
+ struct handle *stderr_h = !stderr_H ? NULL : handle_input_new(
+ stderr_H, handle_stderr, hs, flags);
+
+ while (bufchain_size(&hs->outputdata)) {
+ ptrlen data = bufchain_prefix(&hs->outputdata);
+ handle_write(send_h, data.ptr, data.len);
+ bufchain_consume(&hs->outputdata, data.len);
+ }
+
+ if (hs->output_eof_pending)
+ handle_write_eof(send_h);
+
+ bool start_frozen = hs->start_frozen;
+
+ deferred_socket_opener_free(hs->opener);
+ bufchain_clear(&hs->outputdata);
+
+ hs->sock.vt = &HandleSocket_sockvt;
+ hs->frozen = start_frozen ? FREEZING : UNFROZEN;
+ bufchain_init(&hs->inputdata);
+ psb_init(&hs->psb);
+
+ hs->recv_H = recv_H;
+ hs->recv_h = recv_h;
+ hs->send_H = send_H;
+ hs->send_h = send_h;
+ hs->stderr_H = stderr_H;
+ hs->stderr_h = stderr_h;
+
+ hs->defer_close = hs->deferred_close = false;
+
+ queue_toplevel_callback(sk_handle_connect_success_callback, hs);
+}
diff --git a/windows/handle-wait.c b/windows/handle-wait.c
new file mode 100644
index 00000000..9e4e522d
--- /dev/null
+++ b/windows/handle-wait.c
@@ -0,0 +1,143 @@
+/*
+ * handle-wait.c: Manage a collection of HANDLEs to wait for (in a
+ * WaitFor{Single,Multiple}Objects sense), each with a callback to be
+ * called when it's activated. Tracks the list, and provides an API to
+ * event loops that let them get a list of things to wait for and a
+ * way to call back to here when one of them does something.
+ */
+
+/*
+ * TODO: currently this system can't cope with more than
+ * MAXIMUM_WAIT_OBJECTS (= 64) handles at a time. It enforces that by
+ * assertion, so we'll at least find out if that assumption is ever
+ * violated.
+ *
+ * It should be OK for the moment. As of 2021-05-24, the only uses of
+ * this system are by the ConPTY backend (just once, to watch for its
+ * subprocess terminating); by Pageant (for the event that the
+ * WM_COPYDATA subthread uses to signal the main thread); and by
+ * named-pipe-server.c (once per named-pipe server, of which there is
+ * one in Pageant and one in connection-sharing upstreams). So the
+ * total number of handles has a pretty small upper bound.
+ *
+ * But sooner or later, I'm sure we'll find a reason why we really
+ * need to watch a squillion handles at once. When that happens, I
+ * can't see any alternative to setting up some kind of tree of
+ * subthreads in this module, each one condensing 64 of our handles
+ * into one, by doing its own WaitForMultipleObjects and setting an
+ * event object to indicate that one of them did something. It'll be
+ * horribly ugly.
+ */
+
+#include "putty.h"
+
+struct HandleWait {
+ HANDLE handle;
+ handle_wait_callback_fn_t callback;
+ void *callback_ctx;
+
+ int index; /* sort key for tree234 */
+};
+
+struct HandleWaitListInner {
+ HandleWait *hws[MAXIMUM_WAIT_OBJECTS];
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS];
+
+ struct HandleWaitList hwl;
+};
+
+static int handlewait_cmp(void *av, void *bv)
+{
+ HandleWait *a = (HandleWait *)av, *b = (HandleWait *)bv;
+ if (a->index < b->index)
+ return -1;
+ if (a->index > b->index)
+ return +1;
+ return 0;
+}
+
+static tree234 *handlewaits_tree_real;
+
+static inline tree234 *ensure_handlewaits_tree_exists(void)
+{
+ if (!handlewaits_tree_real)
+ handlewaits_tree_real = newtree234(handlewait_cmp);
+ return handlewaits_tree_real;
+}
+
+static int allocate_index(void)
+{
+ tree234 *t = ensure_handlewaits_tree_exists();
+ search234_state st[1];
+
+ search234_start(st, t);
+ while (st->element) {
+ HandleWait *hw = (HandleWait *)st->element;
+ if (st->index < hw->index) {
+ /* There are unused index slots to the left of this element */
+ search234_step(st, -1);
+ } else {
+ assert(st->index == hw->index);
+ search234_step(st, +1);
+ }
+ }
+
+ return st->index;
+}
+
+HandleWait *add_handle_wait(HANDLE h, handle_wait_callback_fn_t callback,
+ void *callback_ctx)
+{
+ HandleWait *hw = snew(HandleWait);
+ hw->handle = h;
+ hw->callback = callback;
+ hw->callback_ctx = callback_ctx;
+
+ tree234 *t = ensure_handlewaits_tree_exists();
+ hw->index = allocate_index();
+ HandleWait *added = add234(t, hw);
+ assert(added == hw);
+
+ return hw;
+}
+
+void delete_handle_wait(HandleWait *hw)
+{
+ tree234 *t = ensure_handlewaits_tree_exists();
+ HandleWait *deleted = del234(t, hw);
+ assert(deleted == hw);
+ sfree(hw);
+}
+
+HandleWaitList *get_handle_wait_list(void)
+{
+ tree234 *t = ensure_handlewaits_tree_exists();
+ struct HandleWaitListInner *hwli = snew(struct HandleWaitListInner);
+ size_t n = 0;
+ HandleWait *hw;
+ for (int i = 0; (hw = index234(t, i)) != NULL; i++) {
+ assert(n < MAXIMUM_WAIT_OBJECTS);
+ hwli->hws[n] = hw;
+ hwli->hwl.handles[n] = hw->handle;
+ n++;
+ }
+ hwli->hwl.nhandles = n;
+ return &hwli->hwl;
+}
+
+void handle_wait_activate(HandleWaitList *hwl, int index)
+{
+ struct HandleWaitListInner *hwli =
+ container_of(hwl, struct HandleWaitListInner, hwl);
+ assert(0 <= index);
+ assert(index < hwli->hwl.nhandles);
+ HandleWait *hw = hwli->hws[index];
+ hw->callback(hw->callback_ctx);
+}
+
+void handle_wait_list_free(HandleWaitList *hwl)
+{
+ struct HandleWaitListInner *hwli =
+ container_of(hwl, struct HandleWaitListInner, hwl);
+ sfree(hwli);
+}
diff --git a/windows/help.c b/windows/help.c
new file mode 100644
index 00000000..d087c722
--- /dev/null
+++ b/windows/help.c
@@ -0,0 +1,250 @@
+/*
+ * help.c: centralised functions to launch Windows HTML Help files.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "putty.h"
+#include "putty-rc.h"
+
+#ifdef NO_HTMLHELP
+
+/* If htmlhelp.h is not available, we can't do any of this at all */
+bool has_help(void) { return false; }
+void init_help(void) { }
+void shutdown_help(void) { }
+void launch_help(HWND hwnd, const char *topic) { }
+void quit_help(HWND hwnd) { }
+
+#else
+
+#include <htmlhelp.h>
+
+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 = get_reg_sz_simple(
+ HKEY_LOCAL_MACHINE, reg_paths[i], NULL);
+
+ if (filename) {
+ chm_path = filename;
+ chm_created_by_us = false;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void init_help(void)
+{
+ /* Just in case of multiple calls */
+ static bool already_called = false;
+ if (already_called)
+ return;
+ already_called = true;
+
+ /*
+ * Don't even try looking for the CHM file if we can't even find
+ * the HtmlHelp() API function.
+ */
+ HINSTANCE dllHH = load_system32_dll("hhctrl.ocx");
+ GET_WINDOWS_FUNCTION(dllHH, HtmlHelpA);
+ if (!p_HtmlHelpA) {
+ FreeLibrary(dllHH);
+ return;
+ }
+
+ /*
+ * If there's a CHM file embedded in this executable, we should
+ * use that as the first choice.
+ */
+ if (find_chm_resource())
+ return;
+
+ /*
+ * Otherwise, try looking for the CHM in the location that the
+ * installer marked in the registry.
+ */
+ if (find_chm_from_installation())
+ return;
+}
+
+void shutdown_help(void)
+{
+ if (chm_path && chm_created_by_us) {
+ p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0);
+ DeleteFile(chm_path);
+ }
+ sfree(chm_path);
+ chm_path = NULL;
+ chm_created_by_us = false;
+}
+
+bool has_help(void)
+{
+ return chm_path != NULL || chm_resource != NULL;
+}
+
+void launch_help(HWND hwnd, const char *topic)
+{
+ if (!chm_path && chm_resource) {
+ /*
+ * If we've been called without already having a file name for
+ * the CHM file, that might be because we've located it in our
+ * resource section but not written it to a temp file yet. Do
+ * so now, on first use.
+ */
+ load_chm_resource();
+ }
+
+ /* If we _still_ don't have a CHM pathname, we just can't display help. */
+ if (!chm_path)
+ return;
+
+ if (topic) {
+ char *fname = dupprintf(
+ "%s::/%s.html>main", chm_path, topic);
+ p_HtmlHelpA(hwnd, fname, HH_DISPLAY_TOPIC, 0);
+ sfree(fname);
+ } else {
+ p_HtmlHelpA(hwnd, chm_path, HH_DISPLAY_TOPIC, 0);
+ }
+ requested_help = true;
+}
+
+void quit_help(HWND hwnd)
+{
+ if (requested_help)
+ p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0);
+ if (chm_path && chm_created_by_us)
+ DeleteFile(chm_path);
+}
+
+#endif /* NO_HTMLHELP */
diff --git a/windows/help.h b/windows/help.h
new file mode 100644
index 00000000..de6ec0be
--- /dev/null
+++ b/windows/help.h
@@ -0,0 +1,219 @@
+/*
+ * help.h - define Windows Help context names.
+ * Each definition is simply a string which matches up with the
+ * section names in the Halibut source, and is used for HTML Help.
+ */
+
+/* Maximum length for WINHELP_CTX_foo strings */
+#define WINHELP_CTX_MAXLEN 80
+
+/* These are used in the cross-platform configuration dialog code. */
+
+typedef const char *HelpCtx;
+#define NULL_HELPCTX NULL
+#define HELPCTX(x) WINHELP_CTX_ ## x
+
+#define WINHELP_CTX_no_help NULL
+
+#define WINHELP_CTX_session_hostname "config-hostname"
+#define WINHELP_CTX_session_saved "config-saving"
+#define WINHELP_CTX_session_coe "config-closeonexit"
+#define WINHELP_CTX_logging_main "config-logging"
+#define WINHELP_CTX_logging_filename "config-logfilename"
+#define WINHELP_CTX_logging_exists "config-logfileexists"
+#define WINHELP_CTX_logging_flush "config-logflush"
+#define WINHELP_CTX_logging_header "config-logheader"
+#define WINHELP_CTX_logging_ssh_omit_password "config-logssh"
+#define WINHELP_CTX_logging_ssh_omit_data "config-logssh"
+#define WINHELP_CTX_keyboard_backspace "config-backspace"
+#define WINHELP_CTX_keyboard_homeend "config-homeend"
+#define WINHELP_CTX_keyboard_funkeys "config-funkeys"
+#define WINHELP_CTX_keyboard_sharrow "config-sharrow"
+#define WINHELP_CTX_keyboard_appkeypad "config-appkeypad"
+#define WINHELP_CTX_keyboard_appcursor "config-appcursor"
+#define WINHELP_CTX_keyboard_nethack "config-nethack"
+#define WINHELP_CTX_keyboard_compose "config-compose"
+#define WINHELP_CTX_keyboard_ctrlalt "config-ctrlalt"
+#define WINHELP_CTX_features_application "config-features-application"
+#define WINHELP_CTX_features_mouse "config-features-mouse"
+#define WINHELP_CTX_features_resize "config-features-resize"
+#define WINHELP_CTX_features_altscreen "config-features-altscreen"
+#define WINHELP_CTX_features_retitle "config-features-retitle"
+#define WINHELP_CTX_features_qtitle "config-features-qtitle"
+#define WINHELP_CTX_features_dbackspace "config-features-dbackspace"
+#define WINHELP_CTX_features_charset "config-features-charset"
+#define WINHELP_CTX_features_clearscroll "config-features-clearscroll"
+#define WINHELP_CTX_features_arabicshaping "config-features-shaping"
+#define WINHELP_CTX_features_bidi "config-features-bidi"
+#define WINHELP_CTX_terminal_autowrap "config-autowrap"
+#define WINHELP_CTX_terminal_decom "config-decom"
+#define WINHELP_CTX_terminal_lfhascr "config-crlf"
+#define WINHELP_CTX_terminal_crhaslf "config-lfcr"
+#define WINHELP_CTX_terminal_bce "config-erase"
+#define WINHELP_CTX_terminal_blink "config-blink"
+#define WINHELP_CTX_terminal_answerback "config-answerback"
+#define WINHELP_CTX_terminal_localecho "config-localecho"
+#define WINHELP_CTX_terminal_localedit "config-localedit"
+#define WINHELP_CTX_terminal_printing "config-printing"
+#define WINHELP_CTX_supdup_location "supdup-location"
+#define WINHELP_CTX_supdup_ascii "supdup-ascii"
+#define WINHELP_CTX_supdup_more "supdup-more"
+#define WINHELP_CTX_supdup_scroll "supdup-scroll"
+#define WINHELP_CTX_bell_style "config-bellstyle"
+#define WINHELP_CTX_bell_taskbar "config-belltaskbar"
+#define WINHELP_CTX_bell_overload "config-bellovl"
+#define WINHELP_CTX_window_size "config-winsize"
+#define WINHELP_CTX_window_resize "config-winsizelock"
+#define WINHELP_CTX_window_scrollback "config-scrollback"
+#define WINHELP_CTX_window_erased "config-erasetoscrollback"
+#define WINHELP_CTX_behaviour_closewarn "config-warnonclose"
+#define WINHELP_CTX_behaviour_altf4 "config-altf4"
+#define WINHELP_CTX_behaviour_altspace "config-altspace"
+#define WINHELP_CTX_behaviour_altonly "config-altonly"
+#define WINHELP_CTX_behaviour_alwaysontop "config-alwaysontop"
+#define WINHELP_CTX_behaviour_altenter "config-fullscreen"
+#define WINHELP_CTX_appearance_cursor "config-cursor"
+#define WINHELP_CTX_appearance_font "config-font"
+#define WINHELP_CTX_appearance_title "config-title"
+#define WINHELP_CTX_appearance_hidemouse "config-mouseptr"
+#define WINHELP_CTX_appearance_border "config-winborder"
+#define WINHELP_CTX_connection_termtype "config-termtype"
+#define WINHELP_CTX_connection_termspeed "config-termspeed"
+#define WINHELP_CTX_connection_username "config-username"
+#define WINHELP_CTX_connection_username_from_env "config-username-from-env"
+#define WINHELP_CTX_connection_keepalive "config-keepalive"
+#define WINHELP_CTX_connection_nodelay "config-nodelay"
+#define WINHELP_CTX_connection_ipversion "config-address-family"
+#define WINHELP_CTX_connection_tcpkeepalive "config-tcp-keepalives"
+#define WINHELP_CTX_connection_loghost "config-loghost"
+#define WINHELP_CTX_proxy_type "config-proxy-type"
+#define WINHELP_CTX_proxy_main "config-proxy"
+#define WINHELP_CTX_proxy_exclude "config-proxy-exclude"
+#define WINHELP_CTX_proxy_dns "config-proxy-dns"
+#define WINHELP_CTX_proxy_auth "config-proxy-auth"
+#define WINHELP_CTX_proxy_command "config-proxy-command"
+#define WINHELP_CTX_proxy_logging "config-proxy-logging"
+#define WINHELP_CTX_telnet_environ "config-environ"
+#define WINHELP_CTX_telnet_oldenviron "config-oldenviron"
+#define WINHELP_CTX_telnet_passive "config-ptelnet"
+#define WINHELP_CTX_telnet_specialkeys "config-telnetkey"
+#define WINHELP_CTX_telnet_newline "config-telnetnl"
+#define WINHELP_CTX_rlogin_localuser "config-rlogin-localuser"
+#define WINHELP_CTX_ssh_nopty "config-ssh-pty"
+#define WINHELP_CTX_ssh_ttymodes "config-ttymodes"
+#define WINHELP_CTX_ssh_noshell "config-ssh-noshell"
+#define WINHELP_CTX_ssh_ciphers "config-ssh-encryption"
+#define WINHELP_CTX_ssh_protocol "config-ssh-prot"
+#define WINHELP_CTX_ssh_command "config-command"
+#define WINHELP_CTX_ssh_compress "config-ssh-comp"
+#define WINHELP_CTX_ssh_share "config-ssh-sharing"
+#define WINHELP_CTX_ssh_kexlist "config-ssh-kex-order"
+#define WINHELP_CTX_ssh_hklist "config-ssh-hostkey-order"
+#define WINHELP_CTX_ssh_hk_known "config-ssh-prefer-known-hostkeys"
+#define WINHELP_CTX_ssh_gssapi_kex_delegation "config-ssh-kex-gssapi-delegation"
+#define WINHELP_CTX_ssh_kex_repeat "config-ssh-kex-rekey"
+#define WINHELP_CTX_ssh_kex_manual_hostkeys "config-ssh-kex-manual-hostkeys"
+#define WINHELP_CTX_ssh_kex_cert "config-ssh-kex-cert"
+#define WINHELP_CTX_ssh_cert_valid_expr "config-ssh-cert-valid-expr"
+#define WINHELP_CTX_ssh_cert_rsa_hash "config-ssh-cert-rsa-hash"
+#define WINHELP_CTX_ssh_auth_bypass "config-ssh-noauth"
+#define WINHELP_CTX_ssh_no_trivial_userauth "config-ssh-notrivialauth"
+#define WINHELP_CTX_ssh_auth_banner "config-ssh-banner"
+#define WINHELP_CTX_ssh_auth_privkey "config-ssh-privkey"
+#define WINHELP_CTX_ssh_auth_plugin "config-ssh-authplugin"
+#define WINHELP_CTX_ssh_auth_cert "config-ssh-cert"
+#define WINHELP_CTX_ssh_auth_agentfwd "config-ssh-agentfwd"
+#define WINHELP_CTX_ssh_auth_changeuser "config-ssh-changeuser"
+#define WINHELP_CTX_ssh_auth_pageant "config-ssh-tryagent"
+#define WINHELP_CTX_ssh_auth_tis "config-ssh-tis"
+#define WINHELP_CTX_ssh_auth_ki "config-ssh-ki"
+#define WINHELP_CTX_ssh_gssapi "config-ssh-auth-gssapi"
+#define WINHELP_CTX_ssh_gssapi_delegation "config-ssh-auth-gssapi-delegation"
+#define WINHELP_CTX_ssh_gssapi_libraries "config-ssh-auth-gssapi-libraries"
+#define WINHELP_CTX_selection_buttons "config-mouse"
+#define WINHELP_CTX_selection_shiftdrag "config-mouseshift"
+#define WINHELP_CTX_selection_rect "config-rectselect"
+#define WINHELP_CTX_selection_linedraw "config-linedrawpaste"
+#define WINHELP_CTX_selection_autocopy "config-selection-autocopy"
+#define WINHELP_CTX_selection_clipactions "config-selection-clipactions"
+#define WINHELP_CTX_selection_pastectrl "config-paste-ctrl-char"
+#define WINHELP_CTX_copy_charclasses "config-charclasses"
+#define WINHELP_CTX_copy_rtf "config-rtfcopy"
+#define WINHELP_CTX_colours_ansi "config-ansicolour"
+#define WINHELP_CTX_colours_xterm256 "config-xtermcolour"
+#define WINHELP_CTX_colours_truecolour "config-truecolour"
+#define WINHELP_CTX_colours_bold "config-boldcolour"
+#define WINHELP_CTX_colours_system "config-syscolour"
+#define WINHELP_CTX_colours_logpal "config-logpalette"
+#define WINHELP_CTX_colours_config "config-colourcfg"
+#define WINHELP_CTX_translation_codepage "config-charset"
+#define WINHELP_CTX_translation_cjk_ambig_wide "config-cjk-ambig-wide"
+#define WINHELP_CTX_translation_cyrillic "config-cyr"
+#define WINHELP_CTX_translation_linedraw "config-linedraw"
+#define WINHELP_CTX_translation_utf8linedraw "config-utf8linedraw"
+#define WINHELP_CTX_ssh_tunnels_x11 "config-ssh-x11"
+#define WINHELP_CTX_ssh_tunnels_x11auth "config-ssh-x11auth"
+#define WINHELP_CTX_ssh_tunnels_xauthority "config-ssh-xauthority"
+#define WINHELP_CTX_ssh_tunnels_portfwd "config-ssh-portfwd"
+#define WINHELP_CTX_ssh_tunnels_portfwd_localhost "config-ssh-portfwd-localhost"
+#define WINHELP_CTX_ssh_tunnels_portfwd_ipversion "config-ssh-portfwd-address-family"
+#define WINHELP_CTX_ssh_bugs_ignore1 "config-ssh-bug-ignore1"
+#define WINHELP_CTX_ssh_bugs_plainpw1 "config-ssh-bug-plainpw1"
+#define WINHELP_CTX_ssh_bugs_rsa1 "config-ssh-bug-rsa1"
+#define WINHELP_CTX_ssh_bugs_ignore2 "config-ssh-bug-ignore2"
+#define WINHELP_CTX_ssh_bugs_hmac2 "config-ssh-bug-hmac2"
+#define WINHELP_CTX_ssh_bugs_derivekey2 "config-ssh-bug-derivekey2"
+#define WINHELP_CTX_ssh_bugs_rsapad2 "config-ssh-bug-sig"
+#define WINHELP_CTX_ssh_bugs_pksessid2 "config-ssh-bug-pksessid2"
+#define WINHELP_CTX_ssh_bugs_rekey2 "config-ssh-bug-rekey"
+#define WINHELP_CTX_ssh_bugs_maxpkt2 "config-ssh-bug-maxpkt2"
+#define WINHELP_CTX_ssh_bugs_winadj "config-ssh-bug-winadj"
+#define WINHELP_CTX_ssh_bugs_chanreq "config-ssh-bug-chanreq"
+#define WINHELP_CTX_ssh_bugs_oldgex2 "config-ssh-bug-oldgex2"
+#define WINHELP_CTX_ssh_bugs_dropstart "config-ssh-bug-dropstart"
+#define WINHELP_CTX_ssh_bugs_filter_kexinit "config-ssh-bug-filter-kexinit"
+#define WINHELP_CTX_serial_line "config-serial-line"
+#define WINHELP_CTX_serial_speed "config-serial-speed"
+#define WINHELP_CTX_serial_databits "config-serial-databits"
+#define WINHELP_CTX_serial_stopbits "config-serial-stopbits"
+#define WINHELP_CTX_serial_parity "config-serial-parity"
+#define WINHELP_CTX_serial_flow "config-serial-flow"
+
+#define WINHELP_CTX_pageant_general "pageant"
+#define WINHELP_CTX_pageant_keylist "pageant-mainwin-keylist"
+#define WINHELP_CTX_pageant_addkey "pageant-mainwin-addkey"
+#define WINHELP_CTX_pageant_remkey "pageant-mainwin-remkey"
+#define WINHELP_CTX_pageant_deferred "pageant-deferred-decryption"
+#define WINHELP_CTX_pgpfingerprints "pgpkeys"
+#define WINHELP_CTX_puttygen_general "pubkey-puttygen"
+#define WINHELP_CTX_puttygen_keytype "puttygen-keytype"
+#define WINHELP_CTX_puttygen_bits "puttygen-strength"
+#define WINHELP_CTX_puttygen_generate "puttygen-generate"
+#define WINHELP_CTX_puttygen_fingerprint "puttygen-fingerprint"
+#define WINHELP_CTX_puttygen_comment "puttygen-comment"
+#define WINHELP_CTX_puttygen_passphrase "puttygen-passphrase"
+#define WINHELP_CTX_puttygen_savepriv "puttygen-savepriv"
+#define WINHELP_CTX_puttygen_savepub "puttygen-savepub"
+#define WINHELP_CTX_puttygen_pastekey "puttygen-pastekey"
+#define WINHELP_CTX_puttygen_load "puttygen-load"
+#define WINHELP_CTX_puttygen_conversions "puttygen-conversions"
+#define WINHELP_CTX_puttygen_ppkver "puttygen-save-ppk-version"
+#define WINHELP_CTX_puttygen_kdfparam "puttygen-save-passphrase-hashing"
+#define WINHELP_CTX_errors_cert_mismatch "errors-cert-mismatch"
+
+/* These are used in Windows-specific bits of the frontend.
+ * We (ab)use "help context identifiers" (dwContextId) to identify them. */
+
+#define HELPCTXID(x) WINHELP_CTXID_ ## x
+
+#define WINHELP_CTXID_no_help 0
+#define WINHELP_CTX_errors_hostkey_absent "errors-hostkey-absent"
+#define WINHELP_CTXID_errors_hostkey_absent 1
+#define WINHELP_CTX_errors_hostkey_changed "errors-hostkey-wrong"
+#define WINHELP_CTXID_errors_hostkey_changed 2
+#define WINHELP_CTX_errors_cantloadkey "errors-cant-load-key"
+#define WINHELP_CTXID_errors_cantloadkey 3
+#define WINHELP_CTX_option_cleanup "using-cleanup"
+#define WINHELP_CTXID_option_cleanup 4
+#define WINHELP_CTX_pgp_fingerprints "pgpkeys"
+#define WINHELP_CTXID_pgp_fingerprints 5
diff --git a/windows/help.rc2 b/windows/help.rc2
new file mode 100644
index 00000000..16bb41f0
--- /dev/null
+++ b/windows/help.rc2
@@ -0,0 +1,8 @@
+#include "putty-rc.h"
+
+#ifdef EMBEDDED_CHM_FILE
+ID_CUSTOM_CHMFILE TYPE_CUSTOM_CHMFILE EMBEDDED_CHM_FILE
+#define HELPVER " (with embedded help)"
+#else
+#define HELPVER " (without embedded help)"
+#endif
diff --git a/windows/installer.wxs b/windows/installer.wxs
index b221320e..65a7f379 100644
--- a/windows/installer.wxs
+++ b/windows/installer.wxs
@@ -91,6 +91,10 @@
<?define Desktop_Shortcut_Component_GUID = "8999BBE1-F99E-4301-B7A6-480C19DE13B9" ?>
<?endif ?>
+<?ifndef HelpFilePath ?>
+ <?define HelpFilePath = "../doc/putty.chm" ?>
+<?endif ?>
+
<?define ProgramName = "PuTTY$(var.Bitness)" ?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
@@ -117,12 +121,6 @@
Language="1033" Codepage="1252" Version="$(var.Winver)">
<!--
- We force the install scope to perMachine, largely because I
- don't really understand how to make it usefully switchable
- between the two. If anyone is a WiX expert and does want to
- install PuTTY locally in a user account, I hope they'll send a
- well explained patch!
-
$(var.Puttytextver) is again defined on the candle command
line, and describes the version of PuTTY in human-readable
form, e.g. "PuTTY 0.67" or "PuTTY development snapshot [foo]".
@@ -131,8 +129,7 @@
Description="$(var.Puttytextver) installer"
Manufacturer="Simon Tatham"
InstallerVersion="$(var.InstallerVersion)" Languages="1033"
- Compressed="yes" SummaryCodepage="1252"
- InstallScope="perMachine" />
+ Compressed="yes" SummaryCodepage="1252" />
<!--
Permit installing an arbitrary one of these PuTTY installers
@@ -238,7 +235,7 @@ https://msdn.microsoft.com/en-us/library/windows/desktop/dd391569(v=vs.85).aspx
<Component Id="HelpFile_Component"
Guid="$(var.HelpFile_Component_GUID)">
<File Id="HelpFile_File"
- Source="../doc/putty.chm" KeyPath="yes">
+ Source="$(var.HelpFilePath)" KeyPath="yes">
<Shortcut Id="startmenuManual" Directory="ProgramMenuDir"
Name="PuTTY Manual"
Advertise="no" />
diff --git a/windows/jump-list.c b/windows/jump-list.c
new file mode 100644
index 00000000..47c41a04
--- /dev/null
+++ b/windows/jump-list.c
@@ -0,0 +1,746 @@
+/*
+ * jump-list.c: support for Windows 7 jump lists.
+ *
+ * The Windows 7 jumplist is a customizable list defined by the
+ * application. It is persistent across application restarts: the OS
+ * maintains the list when the app is not running. The list is shown
+ * when the user right-clicks on the taskbar button of a running app
+ * or a pinned non-running application. We use the jumplist to
+ * maintain a list of recently started saved sessions, started either
+ * by doubleclicking on a saved session, or with the command line
+ * "-load" parameter.
+ *
+ * Since the jumplist is write-only: it can only be replaced and the
+ * current list cannot be read, we must maintain the contents of the
+ * list persistently in the registry. The file winstore.h contains
+ * functions to directly manipulate these registry entries. This file
+ * contains higher level functions to manipulate the jumplist.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "storage.h"
+
+#define MAX_JUMPLIST_ITEMS 30 /* PuTTY will never show more items in
+ * the jumplist than this, regardless of
+ * user preferences. */
+
+/*
+ * COM structures and functions.
+ */
+#ifndef PROPERTYKEY_DEFINED
+#define PROPERTYKEY_DEFINED
+typedef struct _tagpropertykey {
+ GUID fmtid;
+ DWORD pid;
+} PROPERTYKEY;
+#endif
+#ifndef _REFPROPVARIANT_DEFINED
+#define _REFPROPVARIANT_DEFINED
+typedef PROPVARIANT *REFPROPVARIANT;
+#endif
+/* MinGW doesn't define this yet: */
+#if !defined _PROPVARIANTINIT_DEFINED_ && !defined _PROPVARIANT_INIT_DEFINED_
+#define _PROPVARIANTINIT_DEFINED_
+#define PropVariantInit(pvar) memset((pvar),0,sizeof(PROPVARIANT))
+#endif
+
+#define IID_IShellLink IID_IShellLinkA
+
+typedef struct ICustomDestinationListVtbl {
+ HRESULT ( __stdcall *QueryInterface ) (
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppvObject);
+
+ ULONG ( __stdcall *AddRef )(
+ /* [in] ICustomDestinationList*/ void *This);
+
+ ULONG ( __stdcall *Release )(
+ /* [in] ICustomDestinationList*/ void *This);
+
+ HRESULT ( __stdcall *SetAppID )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [string][in] */ LPCWSTR pszAppID);
+
+ HRESULT ( __stdcall *BeginList )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [out] */ UINT *pcMinSlots,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppv);
+
+ HRESULT ( __stdcall *AppendCategory )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [string][in] */ LPCWSTR pszCategory,
+ /* [in] IObjectArray*/ void *poa);
+
+ HRESULT ( __stdcall *AppendKnownCategory )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [in] KNOWNDESTCATEGORY*/ int category);
+
+ HRESULT ( __stdcall *AddUserTasks )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [in] IObjectArray*/ void *poa);
+
+ HRESULT ( __stdcall *CommitList )(
+ /* [in] ICustomDestinationList*/ void *This);
+
+ HRESULT ( __stdcall *GetRemovedDestinations )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [in] */ const IID * const riid,
+ /* [out] */ void **ppv);
+
+ HRESULT ( __stdcall *DeleteList )(
+ /* [in] ICustomDestinationList*/ void *This,
+ /* [string][unique][in] */ LPCWSTR pszAppID);
+
+ HRESULT ( __stdcall *AbortList )(
+ /* [in] ICustomDestinationList*/ void *This);
+
+} ICustomDestinationListVtbl;
+
+typedef struct ICustomDestinationList
+{
+ ICustomDestinationListVtbl *lpVtbl;
+} ICustomDestinationList;
+
+typedef struct IObjectArrayVtbl
+{
+ HRESULT ( __stdcall *QueryInterface )(
+ /* [in] IObjectArray*/ void *This,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppvObject);
+
+ ULONG ( __stdcall *AddRef )(
+ /* [in] IObjectArray*/ void *This);
+
+ ULONG ( __stdcall *Release )(
+ /* [in] IObjectArray*/ void *This);
+
+ HRESULT ( __stdcall *GetCount )(
+ /* [in] IObjectArray*/ void *This,
+ /* [out] */ UINT *pcObjects);
+
+ HRESULT ( __stdcall *GetAt )(
+ /* [in] IObjectArray*/ void *This,
+ /* [in] */ UINT uiIndex,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppv);
+
+} IObjectArrayVtbl;
+
+typedef struct IObjectArray
+{
+ IObjectArrayVtbl *lpVtbl;
+} IObjectArray;
+
+typedef struct IShellLinkVtbl
+{
+ HRESULT ( __stdcall *QueryInterface )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppvObject);
+
+ ULONG ( __stdcall *AddRef )(
+ /* [in] IShellLink*/ void *This);
+
+ ULONG ( __stdcall *Release )(
+ /* [in] IShellLink*/ void *This);
+
+ HRESULT ( __stdcall *GetPath )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][out] */ LPSTR pszFile,
+ /* [in] */ int cch,
+ /* [unique][out][in] */ WIN32_FIND_DATAA *pfd,
+ /* [in] */ DWORD fFlags);
+
+ HRESULT ( __stdcall *GetIDList )(
+ /* [in] IShellLink*/ void *This,
+ /* [out] LPITEMIDLIST*/ void **ppidl);
+
+ HRESULT ( __stdcall *SetIDList )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] LPITEMIDLIST*/ void *pidl);
+
+ HRESULT ( __stdcall *GetDescription )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][out] */ LPSTR pszName,
+ /* [in] */ int cch);
+
+ HRESULT ( __stdcall *SetDescription )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszName);
+
+ HRESULT ( __stdcall *GetWorkingDirectory )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][out] */ LPSTR pszDir,
+ /* [in] */ int cch);
+
+ HRESULT ( __stdcall *SetWorkingDirectory )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszDir);
+
+ HRESULT ( __stdcall *GetArguments )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][out] */ LPSTR pszArgs,
+ /* [in] */ int cch);
+
+ HRESULT ( __stdcall *SetArguments )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszArgs);
+
+ HRESULT ( __stdcall *GetHotkey )(
+ /* [in] IShellLink*/ void *This,
+ /* [out] */ WORD *pwHotkey);
+
+ HRESULT ( __stdcall *SetHotkey )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ WORD wHotkey);
+
+ HRESULT ( __stdcall *GetShowCmd )(
+ /* [in] IShellLink*/ void *This,
+ /* [out] */ int *piShowCmd);
+
+ HRESULT ( __stdcall *SetShowCmd )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ int iShowCmd);
+
+ HRESULT ( __stdcall *GetIconLocation )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][out] */ LPSTR pszIconPath,
+ /* [in] */ int cch,
+ /* [out] */ int *piIcon);
+
+ HRESULT ( __stdcall *SetIconLocation )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszIconPath,
+ /* [in] */ int iIcon);
+
+ HRESULT ( __stdcall *SetRelativePath )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszPathRel,
+ /* [in] */ DWORD dwReserved);
+
+ HRESULT ( __stdcall *Resolve )(
+ /* [in] IShellLink*/ void *This,
+ /* [unique][in] */ HWND hwnd,
+ /* [in] */ DWORD fFlags);
+
+ HRESULT ( __stdcall *SetPath )(
+ /* [in] IShellLink*/ void *This,
+ /* [string][in] */ LPCSTR pszFile);
+
+} IShellLinkVtbl;
+
+typedef struct IShellLink
+{
+ IShellLinkVtbl *lpVtbl;
+} IShellLink;
+
+typedef struct IObjectCollectionVtbl
+{
+ HRESULT ( __stdcall *QueryInterface )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ const GUID * const riid,
+ /* [out] */ void **ppvObject);
+
+ ULONG ( __stdcall *AddRef )(
+ /* [in] IShellLink*/ void *This);
+
+ ULONG ( __stdcall *Release )(
+ /* [in] IShellLink*/ void *This);
+
+ HRESULT ( __stdcall *GetCount )(
+ /* [in] IShellLink*/ void *This,
+ /* [out] */ UINT *pcObjects);
+
+ HRESULT ( __stdcall *GetAt )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ UINT uiIndex,
+ /* [in] */ const GUID * const riid,
+ /* [iid_is][out] */ void **ppv);
+
+ HRESULT ( __stdcall *AddObject )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ void *punk);
+
+ HRESULT ( __stdcall *AddFromArray )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ IObjectArray *poaSource);
+
+ HRESULT ( __stdcall *RemoveObjectAt )(
+ /* [in] IShellLink*/ void *This,
+ /* [in] */ UINT uiIndex);
+
+ HRESULT ( __stdcall *Clear )(
+ /* [in] IShellLink*/ void *This);
+
+} IObjectCollectionVtbl;
+
+typedef struct IObjectCollection
+{
+ IObjectCollectionVtbl *lpVtbl;
+} IObjectCollection;
+
+typedef struct IPropertyStoreVtbl
+{
+ HRESULT ( __stdcall *QueryInterface )(
+ /* [in] IPropertyStore*/ void *This,
+ /* [in] */ const GUID * const riid,
+ /* [iid_is][out] */ void **ppvObject);
+
+ ULONG ( __stdcall *AddRef )(
+ /* [in] IPropertyStore*/ void *This);
+
+ ULONG ( __stdcall *Release )(
+ /* [in] IPropertyStore*/ void *This);
+
+ HRESULT ( __stdcall *GetCount )(
+ /* [in] IPropertyStore*/ void *This,
+ /* [out] */ DWORD *cProps);
+
+ HRESULT ( __stdcall *GetAt )(
+ /* [in] IPropertyStore*/ void *This,
+ /* [in] */ DWORD iProp,
+ /* [out] */ PROPERTYKEY *pkey);
+
+ HRESULT ( __stdcall *GetValue )(
+ /* [in] IPropertyStore*/ void *This,
+ /* [in] */ const PROPERTYKEY * const key,
+ /* [out] */ PROPVARIANT *pv);
+
+ HRESULT ( __stdcall *SetValue )(
+ /* [in] IPropertyStore*/ void *This,
+ /* [in] */ const PROPERTYKEY * const key,
+ /* [in] */ REFPROPVARIANT propvar);
+
+ HRESULT ( __stdcall *Commit )(
+ /* [in] IPropertyStore*/ void *This);
+} IPropertyStoreVtbl;
+
+typedef struct IPropertyStore
+{
+ IPropertyStoreVtbl *lpVtbl;
+} IPropertyStore;
+
+static const CLSID CLSID_DestinationList = {
+ 0x77f10cf0, 0x3db5, 0x4966, {0xb5,0x20,0xb7,0xc5,0x4f,0xd3,0x5e,0xd6}
+};
+static const CLSID CLSID_ShellLink = {
+ 0x00021401, 0x0000, 0x0000, {0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}
+};
+static const CLSID CLSID_EnumerableObjectCollection = {
+ 0x2d3468c1, 0x36a7, 0x43b6, {0xac,0x24,0xd3,0xf0,0x2f,0xd9,0x60,0x7a}
+};
+static const IID IID_IObjectCollection = {
+ 0x5632b1a4, 0xe38a, 0x400a, {0x92,0x8a,0xd4,0xcd,0x63,0x23,0x02,0x95}
+};
+static const IID IID_IShellLink = {
+ 0x000214ee, 0x0000, 0x0000, {0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}
+};
+static const IID IID_ICustomDestinationList = {
+ 0x6332debf, 0x87b5, 0x4670, {0x90,0xc0,0x5e,0x57,0xb4,0x08,0xa4,0x9e}
+};
+static const IID IID_IObjectArray = {
+ 0x92ca9dcd, 0x5622, 0x4bba, {0xa8,0x05,0x5e,0x9f,0x54,0x1b,0xd8,0xc9}
+};
+static const IID IID_IPropertyStore = {
+ 0x886d8eeb, 0x8cf2, 0x4446, {0x8d,0x02,0xcd,0xba,0x1d,0xbd,0xcf,0x99}
+};
+static const PROPERTYKEY PKEY_Title = {
+ {0xf29f85e0, 0x4ff9, 0x1068, {0xab,0x91,0x08,0x00,0x2b,0x27,0xb3,0xd9}},
+ 0x00000002
+};
+
+/* Type-checking macro to provide arguments for CoCreateInstance()
+ * etc, ensuring that 'obj' really is a 'type **'. */
+#define typecheck(checkexpr, result) \
+ (sizeof(checkexpr) ? (result) : (result))
+#define COMPTR(type, obj) &IID_##type, \
+ typecheck((obj)-(type **)(obj), (void **)(void *)(obj))
+
+static char putty_path[2048];
+
+/*
+ * Function to make an IShellLink describing a particular PuTTY
+ * command. If 'appname' is null, the command run will be the one
+ * returned by GetModuleFileName, i.e. our own executable; if it's
+ * non-null then it will be assumed to be a filename in the same
+ * directory as our own executable, and the return value will be NULL
+ * if that file doesn't exist.
+ *
+ * If 'sessionname' is null then no command line will be passed to the
+ * program. If it's non-null, the command line will be that text
+ * prefixed with an @ (to load a PuTTY saved session).
+ *
+ * Hence, you can launch a saved session using make_shell_link(NULL,
+ * sessionname), and launch another app using e.g.
+ * make_shell_link("puttygen.exe", NULL).
+ */
+static IShellLink *make_shell_link(const char *appname,
+ const char *sessionname)
+{
+ IShellLink *ret;
+ char *app_path, *param_string, *desc_string;
+ IPropertyStore *pPS;
+ PROPVARIANT pv;
+
+ /* Retrieve path to executable. */
+ if (!putty_path[0])
+ GetModuleFileName(NULL, putty_path, sizeof(putty_path) - 1);
+ if (appname) {
+ char *p, *q = putty_path;
+ FILE *fp;
+
+ if ((p = strrchr(q, '\\')) != NULL) q = p+1;
+ if ((p = strrchr(q, ':')) != NULL) q = p+1;
+ app_path = dupprintf("%.*s%s", (int)(q - putty_path), putty_path,
+ appname);
+ if ((fp = fopen(app_path, "r")) == NULL) {
+ sfree(app_path);
+ return NULL;
+ }
+ fclose(fp);
+ } else {
+ app_path = dupstr(putty_path);
+ }
+
+ /* Check if this is a valid session, otherwise don't add. */
+ if (sessionname) {
+ settings_r *psettings_tmp = open_settings_r(sessionname);
+ if (!psettings_tmp) {
+ sfree(app_path);
+ return NULL;
+ }
+ close_settings_r(psettings_tmp);
+ }
+
+ /* Create the new item. */
+ if (!SUCCEEDED(CoCreateInstance(&CLSID_ShellLink, NULL,
+ CLSCTX_INPROC_SERVER,
+ COMPTR(IShellLink, &ret)))) {
+ sfree(app_path);
+ return NULL;
+ }
+
+ /* Set path, parameters, icon and description. */
+ ret->lpVtbl->SetPath(ret, app_path);
+
+ if (sessionname) {
+ /* The leading space is reported to work around a Windows 10
+ * behaviour change in which an argument string starting with
+ * '@' causes the SetArguments method to silently do the wrong
+ * thing. */
+ param_string = dupcat(" @", sessionname);
+ } else {
+ param_string = dupstr("");
+ }
+ ret->lpVtbl->SetArguments(ret, param_string);
+ sfree(param_string);
+
+ if (sessionname) {
+ desc_string = dupcat("Connect to PuTTY session '", sessionname, "'");
+ } else {
+ assert(appname);
+ desc_string = dupprintf("Run %.*s",
+ (int)strcspn(appname, "."), appname);
+ }
+ ret->lpVtbl->SetDescription(ret, desc_string);
+ sfree(desc_string);
+
+ ret->lpVtbl->SetIconLocation(ret, app_path, 0);
+
+ /* To set the link title, we require the property store of the link. */
+ if (SUCCEEDED(ret->lpVtbl->QueryInterface(ret,
+ COMPTR(IPropertyStore, &pPS)))) {
+ PropVariantInit(&pv);
+ pv.vt = VT_LPSTR;
+ if (sessionname) {
+ pv.pszVal = dupstr(sessionname);
+ } else {
+ assert(appname);
+ pv.pszVal = dupprintf("Run %.*s",
+ (int)strcspn(appname, "."), appname);
+ }
+ pPS->lpVtbl->SetValue(pPS, &PKEY_Title, &pv);
+ sfree(pv.pszVal);
+ pPS->lpVtbl->Commit(pPS);
+ pPS->lpVtbl->Release(pPS);
+ }
+
+ sfree(app_path);
+
+ return ret;
+}
+
+/* Updates jumplist from registry. */
+static void update_jumplist_from_registry(void)
+{
+ const char *piterator;
+ UINT num_items;
+ int jumplist_counter;
+ UINT nremoved;
+
+ /* Variables used by the cleanup code must be initialised to NULL,
+ * so that we don't try to free or release them if they were never
+ * set up. */
+ ICustomDestinationList *pCDL = NULL;
+ char *pjumplist_reg_entries = NULL;
+ IObjectCollection *collection = NULL;
+ IObjectArray *array = NULL;
+ IShellLink *link = NULL;
+ IObjectArray *pRemoved = NULL;
+ bool need_abort = false;
+
+ /*
+ * Create an ICustomDestinationList: the top-level object which
+ * deals with jump list management.
+ */
+ if (!SUCCEEDED(CoCreateInstance(&CLSID_DestinationList, NULL,
+ CLSCTX_INPROC_SERVER,
+ COMPTR(ICustomDestinationList, &pCDL))))
+ goto cleanup;
+
+ /*
+ * Call its BeginList method to start compiling a list. This gives
+ * us back 'num_items' (a hint derived from systemwide
+ * configuration about how many things to put on the list) and
+ * 'pRemoved' (user configuration about things to leave off the
+ * list).
+ */
+ if (!SUCCEEDED(pCDL->lpVtbl->BeginList(pCDL, &num_items,
+ COMPTR(IObjectArray, &pRemoved))))
+ goto cleanup;
+ need_abort = true;
+ if (!SUCCEEDED(pRemoved->lpVtbl->GetCount(pRemoved, &nremoved)))
+ nremoved = 0;
+
+ /*
+ * Create an object collection to form the 'Recent Sessions'
+ * category on the jump list.
+ */
+ if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection,
+ NULL, CLSCTX_INPROC_SERVER,
+ COMPTR(IObjectCollection, &collection))))
+ goto cleanup;
+
+ /*
+ * Go through the jump list entries from the registry and add each
+ * one to the collection.
+ */
+ pjumplist_reg_entries = get_jumplist_registry_entries();
+ piterator = pjumplist_reg_entries;
+ jumplist_counter = 0;
+ while (*piterator != '\0' &&
+ (jumplist_counter < min(MAX_JUMPLIST_ITEMS, (int) num_items))) {
+ link = make_shell_link(NULL, piterator);
+ if (link) {
+ UINT i;
+ bool found;
+
+ /*
+ * Check that the link isn't in the user-removed list.
+ */
+ for (i = 0, found = false; i < nremoved && !found; i++) {
+ IShellLink *rlink;
+ if (SUCCEEDED(pRemoved->lpVtbl->GetAt(
+ pRemoved, i, COMPTR(IShellLink, &rlink)))) {
+ char desc1[2048], desc2[2048];
+ if (SUCCEEDED(link->lpVtbl->GetDescription(
+ link, desc1, sizeof(desc1)-1)) &&
+ SUCCEEDED(rlink->lpVtbl->GetDescription(
+ rlink, desc2, sizeof(desc2)-1)) &&
+ !strcmp(desc1, desc2)) {
+ found = true;
+ }
+ rlink->lpVtbl->Release(rlink);
+ }
+ }
+
+ if (!found) {
+ collection->lpVtbl->AddObject(collection, link);
+ jumplist_counter++;
+ }
+
+ link->lpVtbl->Release(link);
+ link = NULL;
+ }
+ piterator += strlen(piterator) + 1;
+ }
+ sfree(pjumplist_reg_entries);
+ pjumplist_reg_entries = NULL;
+
+ /*
+ * Get the array form of the collection we've just constructed,
+ * and put it in the jump list.
+ */
+ if (!SUCCEEDED(collection->lpVtbl->QueryInterface(
+ collection, COMPTR(IObjectArray, &array))))
+ goto cleanup;
+
+ pCDL->lpVtbl->AppendCategory(pCDL, L"Recent Sessions", array);
+
+ /*
+ * Create an object collection to form the 'Tasks' category on the
+ * jump list.
+ */
+ if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection,
+ NULL, CLSCTX_INPROC_SERVER,
+ COMPTR(IObjectCollection, &collection))))
+ goto cleanup;
+
+ /*
+ * Add task entries for PuTTYgen and Pageant.
+ */
+ piterator = "Pageant.exe\0PuTTYgen.exe\0\0";
+ while (*piterator != '\0') {
+ link = make_shell_link(piterator, NULL);
+ if (link) {
+ collection->lpVtbl->AddObject(collection, link);
+ link->lpVtbl->Release(link);
+ link = NULL;
+ }
+ piterator += strlen(piterator) + 1;
+ }
+
+ /*
+ * Get the array form of the collection we've just constructed,
+ * and put it in the jump list.
+ */
+ if (!SUCCEEDED(collection->lpVtbl->QueryInterface(
+ collection, COMPTR(IObjectArray, &array))))
+ goto cleanup;
+
+ pCDL->lpVtbl->AddUserTasks(pCDL, array);
+
+ /*
+ * Now we can clean up the array and collection variables, so as
+ * to be able to reuse them.
+ */
+ array->lpVtbl->Release(array);
+ array = NULL;
+ collection->lpVtbl->Release(collection);
+ collection = NULL;
+
+ /*
+ * Create another object collection to form the user tasks
+ * category.
+ */
+ if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection,
+ NULL, CLSCTX_INPROC_SERVER,
+ COMPTR(IObjectCollection, &collection))))
+ goto cleanup;
+
+ /*
+ * Get the array form of the collection we've just constructed,
+ * and put it in the jump list.
+ */
+ if (!SUCCEEDED(collection->lpVtbl->QueryInterface(
+ collection, COMPTR(IObjectArray, &array))))
+ goto cleanup;
+
+ pCDL->lpVtbl->AddUserTasks(pCDL, array);
+
+ /*
+ * Now we can clean up the array and collection variables, so as
+ * to be able to reuse them.
+ */
+ array->lpVtbl->Release(array);
+ array = NULL;
+ collection->lpVtbl->Release(collection);
+ collection = NULL;
+
+ /*
+ * Commit the jump list.
+ */
+ pCDL->lpVtbl->CommitList(pCDL);
+ need_abort = false;
+
+ /*
+ * Clean up.
+ */
+ cleanup:
+ if (pRemoved) pRemoved->lpVtbl->Release(pRemoved);
+ if (pCDL && need_abort) pCDL->lpVtbl->AbortList(pCDL);
+ if (pCDL) pCDL->lpVtbl->Release(pCDL);
+ if (collection) collection->lpVtbl->Release(collection);
+ if (array) array->lpVtbl->Release(array);
+ if (link) link->lpVtbl->Release(link);
+ sfree(pjumplist_reg_entries);
+}
+
+/* Clears the entire jumplist. */
+void clear_jumplist(void)
+{
+ ICustomDestinationList *pCDL;
+
+ if (CoCreateInstance(&CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER,
+ COMPTR(ICustomDestinationList, &pCDL)) == S_OK) {
+ pCDL->lpVtbl->DeleteList(pCDL, NULL);
+ pCDL->lpVtbl->Release(pCDL);
+ }
+
+}
+
+/* Adds a saved session to the Windows 7 jumplist. */
+void add_session_to_jumplist(const char * const sessionname)
+{
+ if ((osMajorVersion < 6) || (osMajorVersion == 6 && osMinorVersion < 1))
+ return; /* do nothing on pre-Win7 systems */
+
+ if (add_to_jumplist_registry(sessionname) == JUMPLISTREG_OK) {
+ update_jumplist_from_registry();
+ } else {
+ /* Make sure we don't leave the jumplist dangling. */
+ clear_jumplist();
+ }
+}
+
+/* Removes a saved session from the Windows jumplist. */
+void remove_session_from_jumplist(const char * const sessionname)
+{
+ if ((osMajorVersion < 6) || (osMajorVersion == 6 && osMinorVersion < 1))
+ return; /* do nothing on pre-Win7 systems */
+
+ if (remove_from_jumplist_registry(sessionname) == JUMPLISTREG_OK) {
+ update_jumplist_from_registry();
+ } else {
+ /* Make sure we don't leave the jumplist dangling. */
+ clear_jumplist();
+ }
+}
+
+/* Set Explicit App User Model Id to fix removable media error with
+ jump lists */
+
+bool set_explicit_app_user_model_id(void)
+{
+ DECL_WINDOWS_FUNCTION(
+ static, HRESULT, SetCurrentProcessExplicitAppUserModelID, (PCWSTR));
+
+ static HMODULE shell32_module = 0;
+
+ if (!shell32_module) {
+ shell32_module = load_system32_dll("Shell32.dll");
+ /*
+ * We can't typecheck this function here, because it's defined
+ * in <shobjidl.h>, which we're not including due to clashes
+ * with all the manual-COM machinery above.
+ */
+ GET_WINDOWS_FUNCTION_NO_TYPECHECK(
+ shell32_module, SetCurrentProcessExplicitAppUserModelID);
+ }
+
+ if (p_SetCurrentProcessExplicitAppUserModelID) {
+ const wchar_t *id = get_app_user_model_id();
+ if (p_SetCurrentProcessExplicitAppUserModelID(id) == S_OK) {
+ return true;
+ }
+ return false;
+ }
+ /* Function doesn't exist, which is ok for Pre-7 systems */
+
+ return true;
+
+}
diff --git a/windows/local-proxy.c b/windows/local-proxy.c
new file mode 100644
index 00000000..55e9cbf3
--- /dev/null
+++ b/windows/local-proxy.c
@@ -0,0 +1,118 @@
+/*
+ * local-proxy.c: Windows implementation of platform_new_connection(),
+ * supporting an OpenSSH-like proxy command via the handle-io.c
+ * mechanism.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+#include "proxy/proxy.h"
+
+char *platform_setup_local_proxy(Socket *socket, const char *cmd)
+{
+ HANDLE us_to_cmd, cmd_from_us;
+ HANDLE us_from_cmd, cmd_to_us;
+ HANDLE us_from_cmd_err, cmd_err_to_us;
+ SECURITY_ATTRIBUTES sa;
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+
+ /*
+ * Create the pipes to the proxy command, and spawn the proxy
+ * command process.
+ */
+ sa.nLength = sizeof(sa);
+ sa.lpSecurityDescriptor = NULL; /* default */
+ sa.bInheritHandle = true;
+ if (!CreatePipe(&us_from_cmd, &cmd_to_us, &sa, 0)) {
+ return dupprintf("Unable to create pipes for proxy command: %s",
+ win_strerror(GetLastError()));
+ }
+
+ if (!CreatePipe(&cmd_from_us, &us_to_cmd, &sa, 0)) {
+ CloseHandle(us_from_cmd);
+ CloseHandle(cmd_to_us);
+ return dupprintf("Unable to create pipes for proxy command: %s",
+ win_strerror(GetLastError()));
+ }
+
+ if (!CreatePipe(&us_from_cmd_err, &cmd_err_to_us, &sa, 0)) {
+ CloseHandle(us_from_cmd);
+ CloseHandle(cmd_to_us);
+ CloseHandle(us_to_cmd);
+ CloseHandle(cmd_from_us);
+ return dupprintf("Unable to create pipes for proxy command: %s",
+ win_strerror(GetLastError()));
+ }
+
+ SetHandleInformation(us_to_cmd, HANDLE_FLAG_INHERIT, 0);
+ SetHandleInformation(us_from_cmd, HANDLE_FLAG_INHERIT, 0);
+ if (us_from_cmd_err != NULL)
+ SetHandleInformation(us_from_cmd_err, HANDLE_FLAG_INHERIT, 0);
+
+ si.cb = sizeof(si);
+ si.lpReserved = NULL;
+ si.lpDesktop = NULL;
+ si.lpTitle = NULL;
+ si.dwFlags = STARTF_USESTDHANDLES;
+ si.cbReserved2 = 0;
+ si.lpReserved2 = NULL;
+ si.hStdInput = cmd_from_us;
+ si.hStdOutput = cmd_to_us;
+ si.hStdError = cmd_err_to_us;
+ char *cmd_mutable = dupstr(cmd); /* CreateProcess needs non-const char * */
+ CreateProcess(NULL, cmd_mutable, NULL, NULL, true,
+ CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS,
+ NULL, NULL, &si, &pi);
+ sfree(cmd_mutable);
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+
+ CloseHandle(cmd_from_us);
+ CloseHandle(cmd_to_us);
+
+ if (cmd_err_to_us != NULL)
+ CloseHandle(cmd_err_to_us);
+
+ setup_handle_socket(socket, us_to_cmd, us_from_cmd, us_from_cmd_err,
+ false);
+
+ return NULL;
+}
+
+Socket *platform_new_connection(SockAddr *addr, const char *hostname,
+ int port, bool privport,
+ bool oobinline, bool nodelay, bool keepalive,
+ Plug *plug, Conf *conf, Interactor *itr)
+{
+ if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD)
+ return NULL;
+
+ DeferredSocketOpener *opener = local_proxy_opener(
+ addr, port, plug, conf, itr);
+ Socket *socket = make_deferred_handle_socket(opener, addr, port, plug);
+ local_proxy_opener_set_socket(opener, socket);
+ return socket;
+}
+
+Socket *platform_start_subprocess(const char *cmd, Plug *plug,
+ const char *prefix)
+{
+ Socket *socket = make_deferred_handle_socket(
+ null_deferred_socket_opener(),
+ sk_nonamelookup("<local command>"), 0, plug);
+ char *err = platform_setup_local_proxy(socket, cmd);
+ handle_socket_set_psb_prefix(socket, prefix);
+
+ if (err) {
+ sk_close(socket);
+ socket = new_error_socket_fmt(plug, "%s", err);
+ sfree(err);
+ }
+
+ return socket;
+}
diff --git a/windows/named-pipe-client.c b/windows/named-pipe-client.c
new file mode 100644
index 00000000..2ab6a309
--- /dev/null
+++ b/windows/named-pipe-client.c
@@ -0,0 +1,95 @@
+/*
+ * Windows support module which deals with being a named-pipe client.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+#include "proxy/proxy.h"
+#include "ssh.h"
+
+#include "security-api.h"
+
+HANDLE connect_to_named_pipe(const char *pipename, char **err)
+{
+ HANDLE pipehandle;
+ PSID usersid, pipeowner;
+ PSECURITY_DESCRIPTOR psd;
+
+ assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0);
+ assert(strchr(pipename + 9, '\\') == NULL);
+
+ while (1) {
+ pipehandle = CreateFile(pipename, GENERIC_READ | GENERIC_WRITE,
+ 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED, NULL);
+
+ if (pipehandle != INVALID_HANDLE_VALUE)
+ break;
+
+ if (GetLastError() != ERROR_PIPE_BUSY) {
+ *err = dupprintf(
+ "Unable to open named pipe '%s': %s",
+ pipename, win_strerror(GetLastError()));
+ return INVALID_HANDLE_VALUE;
+ }
+
+ /*
+ * If we got ERROR_PIPE_BUSY, wait for the server to create a
+ * new pipe instance. (Since the server is expected to be
+ * named-pipe-server.c, which will do that immediately after a
+ * previous connection is accepted, that shouldn't take
+ * excessively long.)
+ */
+ if (!WaitNamedPipe(pipename, NMPWAIT_USE_DEFAULT_WAIT)) {
+ *err = dupprintf(
+ "Error waiting for named pipe '%s': %s",
+ pipename, win_strerror(GetLastError()));
+ return INVALID_HANDLE_VALUE;
+ }
+ }
+
+ if ((usersid = get_user_sid()) == NULL) {
+ CloseHandle(pipehandle);
+ *err = dupprintf(
+ "Unable to get user SID: %s", win_strerror(GetLastError()));
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (p_GetSecurityInfo(pipehandle, SE_KERNEL_OBJECT,
+ OWNER_SECURITY_INFORMATION,
+ &pipeowner, NULL, NULL, NULL,
+ &psd) != ERROR_SUCCESS) {
+ CloseHandle(pipehandle);
+ *err = dupprintf(
+ "Unable to get named pipe security information: %s",
+ win_strerror(GetLastError()));
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (!EqualSid(pipeowner, usersid)) {
+ CloseHandle(pipehandle);
+ LocalFree(psd);
+ *err = dupprintf(
+ "Owner of named pipe '%s' is not us", pipename);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ LocalFree(psd);
+
+ return pipehandle;
+}
+
+Socket *new_named_pipe_client(const char *pipename, Plug *plug)
+{
+ char *err = NULL;
+ HANDLE pipehandle = connect_to_named_pipe(pipename, &err);
+ if (pipehandle == INVALID_HANDLE_VALUE)
+ return new_error_socket_consume_string(plug, err);
+ else
+ return make_handle_socket(pipehandle, pipehandle, NULL, NULL, 0,
+ plug, true);
+}
diff --git a/windows/named-pipe-server.c b/windows/named-pipe-server.c
new file mode 100644
index 00000000..87adb940
--- /dev/null
+++ b/windows/named-pipe-server.c
@@ -0,0 +1,235 @@
+/*
+ * Windows support module which deals with being a named-pipe server.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+#include "proxy/proxy.h"
+#include "ssh.h"
+
+#include "security-api.h"
+
+typedef struct NamedPipeServerSocket {
+ /* Parameters for (repeated) creation of named pipe objects */
+ PSECURITY_DESCRIPTOR psd;
+ PACL acl;
+ char *pipename;
+
+ /* The current named pipe object + attempt to connect to it */
+ HANDLE pipehandle;
+ OVERLAPPED connect_ovl;
+ HandleWait *callback_handle; /* handle-wait.c's reference */
+
+ /* PuTTY Socket machinery */
+ Plug *plug;
+ char *error;
+
+ Socket sock;
+} NamedPipeServerSocket;
+
+static Plug *sk_namedpipeserver_plug(Socket *s, Plug *p)
+{
+ NamedPipeServerSocket *ps = container_of(s, NamedPipeServerSocket, sock);
+ Plug *ret = ps->plug;
+ if (p)
+ ps->plug = p;
+ return ret;
+}
+
+static void sk_namedpipeserver_close(Socket *s)
+{
+ NamedPipeServerSocket *ps = container_of(s, NamedPipeServerSocket, sock);
+
+ if (ps->callback_handle)
+ delete_handle_wait(ps->callback_handle);
+ CloseHandle(ps->pipehandle);
+ CloseHandle(ps->connect_ovl.hEvent);
+ sfree(ps->error);
+ sfree(ps->pipename);
+ if (ps->acl)
+ LocalFree(ps->acl);
+ if (ps->psd)
+ LocalFree(ps->psd);
+ sfree(ps);
+}
+
+static const char *sk_namedpipeserver_socket_error(Socket *s)
+{
+ NamedPipeServerSocket *ps = container_of(s, NamedPipeServerSocket, sock);
+ return ps->error;
+}
+
+static SocketPeerInfo *sk_namedpipeserver_peer_info(Socket *s)
+{
+ return NULL;
+}
+
+static bool create_named_pipe(NamedPipeServerSocket *ps, bool first_instance)
+{
+ SECURITY_ATTRIBUTES sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.nLength = sizeof(sa);
+ sa.lpSecurityDescriptor = ps->psd;
+ sa.bInheritHandle = false;
+
+ ps->pipehandle = CreateNamedPipe(
+ /* lpName */
+ ps->pipename,
+
+ /* dwOpenMode */
+ PIPE_ACCESS_DUPLEX |
+ FILE_FLAG_OVERLAPPED |
+ (first_instance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0),
+
+ /* dwPipeMode */
+ PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT
+#ifdef PIPE_REJECT_REMOTE_CLIENTS
+ | PIPE_REJECT_REMOTE_CLIENTS
+#endif
+ ,
+
+ /* nMaxInstances */
+ PIPE_UNLIMITED_INSTANCES,
+
+ /* nOutBufferSize, nInBufferSize */
+ 4096, 4096, /* FIXME: think harder about buffer sizes? */
+
+ /* nDefaultTimeOut */
+ 0 /* default timeout */,
+
+ /* lpSecurityAttributes */
+ &sa);
+
+ return ps->pipehandle != INVALID_HANDLE_VALUE;
+}
+
+static Socket *named_pipe_accept(accept_ctx_t ctx, Plug *plug)
+{
+ HANDLE conn = (HANDLE)ctx.p;
+
+ return make_handle_socket(conn, conn, NULL, NULL, 0, plug, true);
+}
+
+static void named_pipe_accept_loop(NamedPipeServerSocket *ps,
+ bool got_one_already)
+{
+ while (1) {
+ int error;
+ char *errmsg;
+
+ if (got_one_already) {
+ /* If we were called with a connection already waiting,
+ * skip this step. */
+ got_one_already = false;
+ error = 0;
+ } else {
+ /*
+ * Call ConnectNamedPipe, which might succeed or might
+ * tell us that an overlapped operation is in progress and
+ * we should wait for our event object.
+ */
+ if (ConnectNamedPipe(ps->pipehandle, &ps->connect_ovl))
+ error = 0;
+ else
+ error = GetLastError();
+
+ if (error == ERROR_IO_PENDING)
+ return;
+ }
+
+ if (error == 0 || error == ERROR_PIPE_CONNECTED) {
+ /*
+ * We've successfully retrieved an incoming connection, so
+ * ps->pipehandle now refers to that connection. So
+ * convert that handle into a separate connection-type
+ * Socket, and create a fresh one to be the new listening
+ * pipe.
+ */
+ HANDLE conn = ps->pipehandle;
+ accept_ctx_t actx;
+
+ actx.p = (void *)conn;
+ if (plug_accepting(ps->plug, named_pipe_accept, actx)) {
+ /*
+ * If the plug didn't want the connection, might as
+ * well close this handle.
+ */
+ CloseHandle(conn);
+ }
+
+ if (!create_named_pipe(ps, false)) {
+ error = GetLastError();
+ } else {
+ /*
+ * Go round again to see if more connections can be
+ * got, or to begin waiting on the event object.
+ */
+ continue;
+ }
+ }
+
+ errmsg = dupprintf("Error while listening to named pipe: %s",
+ win_strerror(error));
+ plug_log(ps->plug, 1, sk_namedpipe_addr(ps->pipename), 0,
+ errmsg, error);
+ sfree(errmsg);
+ break;
+ }
+}
+
+static void named_pipe_connect_callback(void *vps)
+{
+ NamedPipeServerSocket *ps = (NamedPipeServerSocket *)vps;
+ named_pipe_accept_loop(ps, true);
+}
+
+/*
+ * This socket type is only used for listening, so it should never
+ * be asked to write or set_frozen.
+ */
+static const SocketVtable NamedPipeServerSocket_sockvt = {
+ .plug = sk_namedpipeserver_plug,
+ .close = sk_namedpipeserver_close,
+ .socket_error = sk_namedpipeserver_socket_error,
+ .peer_info = sk_namedpipeserver_peer_info,
+};
+
+Socket *new_named_pipe_listener(const char *pipename, Plug *plug)
+{
+ NamedPipeServerSocket *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 = add_handle_wait(
+ ret->connect_ovl.hEvent, named_pipe_connect_callback, ret);
+ named_pipe_accept_loop(ret, false);
+
+ cleanup:
+ return &ret->sock;
+}
diff --git a/windows/network.c b/windows/network.c
new file mode 100644
index 00000000..2c087b69
--- /dev/null
+++ b/windows/network.c
@@ -0,0 +1,1879 @@
+/*
+ * Windows networking abstraction.
+ *
+ * For the IPv6 code in here I am indebted to Jeroen Massar and
+ * unfix.org.
+ */
+
+#include <winsock2.h> /* need to put this first, for winelib builds */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#define NEED_DECLARATION_OF_SELECT /* in order to initialise it */
+
+#include "putty.h"
+#include "network.h"
+#include "tree234.h"
+#include "ssh.h"
+
+#include <ws2tcpip.h>
+
+#if HAVE_AFUNIX_H
+#include <afunix.h>
+#endif
+
+#ifndef NO_IPV6
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wmissing-braces"
+#endif
+const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
+const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+#endif
+
+#define ipv4_is_loopback(addr) \
+ ((p_ntohl(addr.s_addr) & 0xFF000000L) == 0x7F000000L)
+
+/*
+ * Mutable state that goes with a SockAddr: stores information
+ * about where in the list of candidate IP(v*) addresses we've
+ * currently got to.
+ */
+typedef struct SockAddrStep_tag SockAddrStep;
+struct SockAddrStep_tag {
+#ifndef NO_IPV6
+ struct addrinfo *ai; /* steps along addr->ais */
+#endif
+ int curraddr;
+};
+
+typedef struct NetSocket NetSocket;
+struct NetSocket {
+ const char *error;
+ SOCKET s;
+ Plug *plug;
+ bufchain output_data;
+ bool connected;
+ bool writable;
+ bool frozen; /* this causes readability notifications to be ignored */
+ bool frozen_readable; /* this means we missed at least one readability
+ * notification while we were frozen */
+ bool localhost_only; /* for listening sockets */
+ char oobdata[1];
+ size_t sending_oob;
+ bool oobinline, nodelay, keepalive, privport;
+ enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
+ SockAddr *addr;
+ SockAddrStep step;
+ int port;
+ int pending_error; /* in case send() returns error */
+ /*
+ * We sometimes need pairs of Socket structures to be linked:
+ * if we are listening on the same IPv6 and v4 port, for
+ * example. So here we define `parent' and `child' pointers to
+ * track this link.
+ */
+ NetSocket *parent, *child;
+
+ Socket sock;
+};
+
+/*
+ * Top-level discriminator for SockAddr.
+ *
+ * UNRESOLVED means a host name not yet put through DNS; IP means a
+ * resolved IP address (or list of them); UNIX indicates the AF_UNIX
+ * network family (which Windows also has); NAMEDPIPE indicates that
+ * this SockAddr is phony, holding a Windows named pipe pathname
+ * instead of any address WinSock can understand.
+ */
+typedef enum SuperFamily {
+ UNRESOLVED,
+ IP,
+#if HAVE_AFUNIX_H
+ UNIX,
+#endif
+ NAMEDPIPE
+} SuperFamily;
+
+struct SockAddr {
+ int refcount;
+ const char *error;
+ SuperFamily superfamily;
+#ifndef NO_IPV6
+ struct addrinfo *ais; /* Addresses IPv6 style. */
+#endif
+ unsigned long *addresses; /* Addresses IPv4 style. */
+ int naddresses;
+ char hostname[512]; /* Store an unresolved host name. */
+};
+
+/*
+ * Which address family this address belongs to. AF_INET for IPv4;
+ * AF_INET6 for IPv6; AF_UNIX for Unix-domain sockets; AF_UNSPEC
+ * indicates that name resolution has not been done and a simple host
+ * name is held in this SockAddr structure.
+ */
+static inline int sockaddr_family(SockAddr *addr, SockAddrStep step)
+{
+ switch (addr->superfamily) {
+ case IP:
+#ifndef NO_IPV6
+ if (step.ai)
+ return step.ai->ai_family;
+#endif
+ return AF_INET;
+#if HAVE_AFUNIX_H
+ case UNIX:
+ return AF_UNIX;
+#endif
+ default:
+ return AF_UNSPEC;
+ }
+}
+
+/*
+ * Start a SockAddrStep structure to step through multiple
+ * addresses.
+ */
+#ifndef NO_IPV6
+#define START_STEP(addr, step) \
+ ((step).ai = (addr)->ais, (step).curraddr = 0)
+#else
+#define START_STEP(addr, step) \
+ ((step).curraddr = 0)
+#endif
+
+static tree234 *sktree;
+
+static int cmpfortree(void *av, void *bv)
+{
+ NetSocket *a = (NetSocket *)av, *b = (NetSocket *)bv;
+ uintptr_t as = (uintptr_t) a->s, bs = (uintptr_t) b->s;
+ if (as < bs)
+ return -1;
+ if (as > bs)
+ return +1;
+ if (a < b)
+ return -1;
+ if (a > b)
+ return +1;
+ return 0;
+}
+
+static int cmpforsearch(void *av, void *bv)
+{
+ NetSocket *b = (NetSocket *)bv;
+ uintptr_t as = (uintptr_t) av, bs = (uintptr_t) b->s;
+ if (as < bs)
+ return -1;
+ if (as > bs)
+ return +1;
+ return 0;
+}
+
+DECL_WINDOWS_FUNCTION(static, int, WSAStartup, (WORD, LPWSADATA));
+DECL_WINDOWS_FUNCTION(static, int, WSACleanup, (void));
+DECL_WINDOWS_FUNCTION(static, int, closesocket, (SOCKET));
+DECL_WINDOWS_FUNCTION(static, ULONG, ntohl, (ULONG));
+DECL_WINDOWS_FUNCTION(static, ULONG, htonl, (ULONG));
+DECL_WINDOWS_FUNCTION(static, USHORT, htons, (USHORT));
+DECL_WINDOWS_FUNCTION(static, USHORT, ntohs, (USHORT));
+DECL_WINDOWS_FUNCTION(static, int, gethostname, (char *, int));
+DECL_WINDOWS_FUNCTION(static, struct hostent FAR *, gethostbyname,
+ (const char FAR *));
+DECL_WINDOWS_FUNCTION(static, struct servent FAR *, getservbyname,
+ (const char FAR *, const char FAR *));
+DECL_WINDOWS_FUNCTION(static, ULONG, inet_addr, (const char FAR *));
+DECL_WINDOWS_FUNCTION(static, char FAR *, inet_ntoa, (struct in_addr));
+DECL_WINDOWS_FUNCTION(static, const char FAR *, inet_ntop,
+ (int, void FAR *, char *, size_t));
+DECL_WINDOWS_FUNCTION(static, int, connect,
+ (SOCKET, const struct sockaddr FAR *, int));
+DECL_WINDOWS_FUNCTION(static, int, bind,
+ (SOCKET, const struct sockaddr FAR *, int));
+DECL_WINDOWS_FUNCTION(static, int, setsockopt,
+ (SOCKET, int, int, const char FAR *, int));
+DECL_WINDOWS_FUNCTION(static, SOCKET, socket, (int, int, int));
+DECL_WINDOWS_FUNCTION(static, int, listen, (SOCKET, int));
+DECL_WINDOWS_FUNCTION(static, int, send, (SOCKET, const char FAR *, int, int));
+DECL_WINDOWS_FUNCTION(static, int, shutdown, (SOCKET, int));
+DECL_WINDOWS_FUNCTION(static, int, ioctlsocket,
+ (SOCKET, LONG, ULONG FAR *));
+DECL_WINDOWS_FUNCTION(static, SOCKET, accept,
+ (SOCKET, struct sockaddr FAR *, int FAR *));
+DECL_WINDOWS_FUNCTION(static, int, getpeername,
+ (SOCKET, struct sockaddr FAR *, int FAR *));
+DECL_WINDOWS_FUNCTION(static, int, recv, (SOCKET, char FAR *, int, int));
+DECL_WINDOWS_FUNCTION(static, int, WSAIoctl,
+ (SOCKET, DWORD, LPVOID, DWORD, LPVOID, DWORD,
+ LPDWORD, LPWSAOVERLAPPED,
+ LPWSAOVERLAPPED_COMPLETION_ROUTINE));
+#ifndef NO_IPV6
+DECL_WINDOWS_FUNCTION(static, int, getaddrinfo,
+ (const char *nodename, const char *servname,
+ const struct addrinfo *hints, struct addrinfo **res));
+DECL_WINDOWS_FUNCTION(static, void, freeaddrinfo, (struct addrinfo *res));
+DECL_WINDOWS_FUNCTION(static, int, getnameinfo,
+ (const struct sockaddr FAR *sa, socklen_t salen,
+ char FAR *host, DWORD hostlen, char FAR *serv,
+ DWORD servlen, int flags));
+DECL_WINDOWS_FUNCTION(static, int, WSAAddressToStringA,
+ (LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFO,
+ LPSTR, LPDWORD));
+#endif
+
+static HMODULE winsock_module = NULL;
+static WSADATA wsadata;
+#ifndef NO_IPV6
+static HMODULE winsock2_module = NULL;
+static HMODULE wship6_module = NULL;
+#endif
+
+static bool sk_startup(int hi, int lo)
+{
+ WORD winsock_ver;
+
+ winsock_ver = MAKEWORD(hi, lo);
+
+ if (p_WSAStartup(winsock_ver, &wsadata)) {
+ return false;
+ }
+
+ if (LOBYTE(wsadata.wVersion) != LOBYTE(winsock_ver)) {
+ return false;
+ }
+
+ return true;
+}
+
+DEF_WINDOWS_FUNCTION(WSAAsyncSelect);
+DEF_WINDOWS_FUNCTION(WSAEventSelect);
+DEF_WINDOWS_FUNCTION(WSAGetLastError);
+DEF_WINDOWS_FUNCTION(WSAEnumNetworkEvents);
+DEF_WINDOWS_FUNCTION(select);
+
+void sk_init(void)
+{
+#ifndef NO_IPV6
+ winsock2_module =
+#endif
+ winsock_module = load_system32_dll("ws2_32.dll");
+ if (!winsock_module) {
+ winsock_module = load_system32_dll("wsock32.dll");
+ }
+ if (!winsock_module)
+ modalfatalbox("Unable to load any WinSock library");
+
+#ifndef NO_IPV6
+ /* Check if we have getaddrinfo in Winsock */
+ if (GetProcAddress(winsock_module, "getaddrinfo") != NULL) {
+ GET_WINDOWS_FUNCTION(winsock_module, getaddrinfo);
+ GET_WINDOWS_FUNCTION(winsock_module, freeaddrinfo);
+ GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, getnameinfo);
+ /* This function would fail its type-check if we did one,
+ * because the VS header file provides an inline definition
+ * which is __cdecl instead of WINAPI. */
+ } else {
+ /* Fall back to wship6.dll for Windows 2000 */
+ wship6_module = load_system32_dll("wship6.dll");
+ if (wship6_module) {
+ GET_WINDOWS_FUNCTION(wship6_module, getaddrinfo);
+ GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo);
+ /* See comment above about type check */
+ GET_WINDOWS_FUNCTION_NO_TYPECHECK(wship6_module, getnameinfo);
+ } else {
+ }
+ }
+ GET_WINDOWS_FUNCTION(winsock2_module, WSAAddressToStringA);
+#endif
+
+ GET_WINDOWS_FUNCTION(winsock_module, WSAAsyncSelect);
+ GET_WINDOWS_FUNCTION(winsock_module, WSAEventSelect);
+ /* We don't type-check select because at least some MinGW versions
+ * of the Windows API headers seem to disagree with the
+ * documentation on whether the 'struct timeval *' pointer is
+ * const or not. */
+ GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, select);
+ GET_WINDOWS_FUNCTION(winsock_module, WSAGetLastError);
+ GET_WINDOWS_FUNCTION(winsock_module, WSAEnumNetworkEvents);
+ GET_WINDOWS_FUNCTION(winsock_module, WSAStartup);
+ GET_WINDOWS_FUNCTION(winsock_module, WSACleanup);
+ GET_WINDOWS_FUNCTION(winsock_module, closesocket);
+ /* Winelib maps ntohl and friends to things like
+ * __wine_ulong_swap, which fail these type checks hopelessly */
+ GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, ntohl);
+ GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, htonl);
+ GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, htons);
+ GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, ntohs);
+ GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gethostname);
+ GET_WINDOWS_FUNCTION(winsock_module, gethostbyname);
+ GET_WINDOWS_FUNCTION(winsock_module, getservbyname);
+ GET_WINDOWS_FUNCTION(winsock_module, inet_addr);
+ GET_WINDOWS_FUNCTION(winsock_module, inet_ntoa);
+ /* Older Visual Studio, and MinGW as of Ubuntu 16.04, don't know
+ * about this function at all, so can't type-check it. Also there
+ * seems to be some disagreement in the VS headers about whether
+ * the second argument is void * or const void *, so I omit the
+ * type check. */
+ GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, inet_ntop);
+ GET_WINDOWS_FUNCTION(winsock_module, connect);
+ GET_WINDOWS_FUNCTION(winsock_module, bind);
+ GET_WINDOWS_FUNCTION(winsock_module, setsockopt);
+ GET_WINDOWS_FUNCTION(winsock_module, socket);
+ GET_WINDOWS_FUNCTION(winsock_module, listen);
+ GET_WINDOWS_FUNCTION(winsock_module, send);
+ GET_WINDOWS_FUNCTION(winsock_module, shutdown);
+ GET_WINDOWS_FUNCTION(winsock_module, ioctlsocket);
+ GET_WINDOWS_FUNCTION(winsock_module, accept);
+ GET_WINDOWS_FUNCTION(winsock_module, getpeername);
+ GET_WINDOWS_FUNCTION(winsock_module, recv);
+ GET_WINDOWS_FUNCTION(winsock_module, WSAIoctl);
+
+ /* Try to get the best WinSock version we can get */
+ if (!sk_startup(2,2) &&
+ !sk_startup(2,0) &&
+ !sk_startup(1,1)) {
+ modalfatalbox("Unable to initialise WinSock");
+ }
+
+ sktree = newtree234(cmpfortree);
+}
+
+void sk_cleanup(void)
+{
+ NetSocket *s;
+ int i;
+
+ if (sktree) {
+ for (i = 0; (s = index234(sktree, i)) != NULL; i++) {
+ p_closesocket(s->s);
+ }
+ freetree234(sktree);
+ sktree = NULL;
+ }
+
+ if (p_WSACleanup)
+ p_WSACleanup();
+ if (winsock_module)
+ FreeLibrary(winsock_module);
+#ifndef NO_IPV6
+ if (wship6_module)
+ FreeLibrary(wship6_module);
+#endif
+}
+
+const char *winsock_error_string(int error)
+{
+ /*
+ * Error codes we know about and have historically had reasonably
+ * sensible error messages for.
+ */
+ switch (error) {
+ case WSAEACCES:
+ return "Network error: Permission denied";
+ case WSAEADDRINUSE:
+ return "Network error: Address already in use";
+ case WSAEADDRNOTAVAIL:
+ return "Network error: Cannot assign requested address";
+ case WSAEAFNOSUPPORT:
+ return
+ "Network error: Address family not supported by protocol family";
+ case WSAEALREADY:
+ return "Network error: Operation already in progress";
+ case WSAECONNABORTED:
+ return "Network error: Software caused connection abort";
+ case WSAECONNREFUSED:
+ return "Network error: Connection refused";
+ case WSAECONNRESET:
+ return "Network error: Connection reset by peer";
+ case WSAEDESTADDRREQ:
+ return "Network error: Destination address required";
+ case WSAEFAULT:
+ return "Network error: Bad address";
+ case WSAEHOSTDOWN:
+ return "Network error: Host is down";
+ case WSAEHOSTUNREACH:
+ return "Network error: No route to host";
+ case WSAEINPROGRESS:
+ return "Network error: Operation now in progress";
+ case WSAEINTR:
+ return "Network error: Interrupted function call";
+ case WSAEINVAL:
+ return "Network error: Invalid argument";
+ case WSAEISCONN:
+ return "Network error: Socket is already connected";
+ case WSAEMFILE:
+ return "Network error: Too many open files";
+ case WSAEMSGSIZE:
+ return "Network error: Message too long";
+ case WSAENETDOWN:
+ return "Network error: Network is down";
+ case WSAENETRESET:
+ return "Network error: Network dropped connection on reset";
+ case WSAENETUNREACH:
+ return "Network error: Network is unreachable";
+ case WSAENOBUFS:
+ return "Network error: No buffer space available";
+ case WSAENOPROTOOPT:
+ return "Network error: Bad protocol option";
+ case WSAENOTCONN:
+ return "Network error: Socket is not connected";
+ case WSAENOTSOCK:
+ return "Network error: Socket operation on non-socket";
+ case WSAEOPNOTSUPP:
+ return "Network error: Operation not supported";
+ case WSAEPFNOSUPPORT:
+ return "Network error: Protocol family not supported";
+ case WSAEPROCLIM:
+ return "Network error: Too many processes";
+ case WSAEPROTONOSUPPORT:
+ return "Network error: Protocol not supported";
+ case WSAEPROTOTYPE:
+ return "Network error: Protocol wrong type for socket";
+ case WSAESHUTDOWN:
+ return "Network error: Cannot send after socket shutdown";
+ case WSAESOCKTNOSUPPORT:
+ return "Network error: Socket type not supported";
+ case WSAETIMEDOUT:
+ return "Network error: Connection timed out";
+ case WSAEWOULDBLOCK:
+ return "Network error: Resource temporarily unavailable";
+ case WSAEDISCON:
+ return "Network error: Graceful shutdown in progress";
+ }
+
+ /*
+ * Handle any other error code by delegating to win_strerror.
+ */
+ return win_strerror(error);
+}
+
+static inline const char *namelookup_strerror(DWORD err)
+{
+ /* PuTTY has traditionally translated a few of the likely error
+ * messages into more concise strings than the standard Windows ones */
+ return (err == WSAENETDOWN ? "Network is down" :
+ err == WSAHOST_NOT_FOUND ? "Host does not exist" :
+ err == WSATRY_AGAIN ? "Host not found" :
+ win_strerror(err));
+}
+
+SockAddr *sk_namelookup(const char *host, char **canonicalname,
+ int address_family)
+{
+ *canonicalname = NULL;
+
+ SockAddr *addr = snew(SockAddr);
+ memset(addr, 0, sizeof(SockAddr));
+ addr->superfamily = UNRESOLVED;
+ addr->refcount = 1;
+
+#ifndef NO_IPV6
+ /*
+ * Use getaddrinfo, as long as it's available. This should handle
+ * both IPv4 and IPv6 address literals, and hostnames, in one
+ * unified API.
+ */
+ if (p_getaddrinfo) {
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET :
+ address_family == ADDRTYPE_IPV6 ? AF_INET6 :
+ AF_UNSPEC);
+ hints.ai_flags = AI_CANONNAME;
+ hints.ai_socktype = SOCK_STREAM;
+
+ /* strip [] on IPv6 address literals */
+ char *trimmed_host = host_strduptrim(host);
+ int err = p_getaddrinfo(trimmed_host, NULL, &hints, &addr->ais);
+ sfree(trimmed_host);
+
+ if (addr->ais) {
+ addr->superfamily = IP;
+ if (addr->ais->ai_canonname)
+ *canonicalname = dupstr(addr->ais->ai_canonname);
+ else
+ *canonicalname = dupstr(host);
+ } else {
+ addr->error = namelookup_strerror(err);
+ }
+ return addr;
+ }
+#endif
+
+ /*
+ * Failing that (if IPv6 support was not compiled in, or if
+ * getaddrinfo turned out to be unavailable at run time), try the
+ * old-fashioned approach, which is to start by manually checking
+ * for an IPv4 literal and then use gethostbyname.
+ */
+ unsigned long a = p_inet_addr(host);
+ if (a != (unsigned long) INADDR_NONE) {
+ addr->addresses = snew(unsigned long);
+ addr->naddresses = 1;
+ addr->addresses[0] = p_ntohl(a);
+ addr->superfamily = IP;
+ *canonicalname = dupstr(host);
+ return addr;
+ }
+
+ struct hostent *h = p_gethostbyname(host);
+ if (h) {
+ addr->superfamily = IP;
+
+ size_t n;
+ for (n = 0; h->h_addr_list[n]; n++);
+ addr->addresses = snewn(n, unsigned long);
+ addr->naddresses = n;
+ for (n = 0; n < addr->naddresses; n++) {
+ uint32_t a;
+ memcpy(&a, h->h_addr_list[n], sizeof(a));
+ addr->addresses[n] = p_ntohl(a);
+ }
+
+ *canonicalname = dupstr(h->h_name);
+ } else {
+ DWORD err = p_WSAGetLastError();
+ addr->error = namelookup_strerror(err);
+ }
+ return addr;
+}
+
+static SockAddr *sk_special_addr(SuperFamily superfamily, const char *name)
+{
+ SockAddr *ret = snew(SockAddr);
+ ret->error = NULL;
+ ret->superfamily = superfamily;
+#ifndef NO_IPV6
+ ret->ais = NULL;
+#endif
+ ret->addresses = NULL;
+ ret->naddresses = 0;
+ ret->refcount = 1;
+ strncpy(ret->hostname, name, lenof(ret->hostname));
+ ret->hostname[lenof(ret->hostname)-1] = '\0';
+ return ret;
+}
+
+SockAddr *sk_nonamelookup(const char *host)
+{
+ return sk_special_addr(UNRESOLVED, host);
+}
+
+SockAddr *sk_namedpipe_addr(const char *pipename)
+{
+ return sk_special_addr(NAMEDPIPE, pipename);
+}
+
+#if HAVE_AFUNIX_H
+SockAddr *sk_unix_addr(const char *sockpath)
+{
+ return sk_special_addr(UNIX, sockpath);
+}
+#endif
+
+static bool sk_nextaddr(SockAddr *addr, SockAddrStep *step)
+{
+#ifndef NO_IPV6
+ if (step->ai) {
+ if (step->ai->ai_next) {
+ step->ai = step->ai->ai_next;
+ return true;
+ } else
+ return false;
+ }
+#endif
+ if (step->curraddr+1 < addr->naddresses) {
+ step->curraddr++;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void sk_getaddr(SockAddr *addr, char *buf, int buflen)
+{
+ SockAddrStep step;
+ START_STEP(addr, step);
+
+#ifndef NO_IPV6
+ if (step.ai) {
+ int err = 0;
+ if (p_WSAAddressToStringA) {
+ DWORD dwbuflen = buflen;
+ err = p_WSAAddressToStringA(step.ai->ai_addr, step.ai->ai_addrlen,
+ NULL, buf, &dwbuflen);
+ } else
+ err = -1;
+ if (err) {
+ strncpy(buf, addr->hostname, buflen);
+ if (!buf[0])
+ strncpy(buf, "<unknown>", buflen);
+ buf[buflen-1] = '\0';
+ }
+ } else
+#endif
+ if (sockaddr_family(addr, step) == AF_INET) {
+ struct in_addr a;
+ assert(addr->addresses && step.curraddr < addr->naddresses);
+ a.s_addr = p_htonl(addr->addresses[step.curraddr]);
+ strncpy(buf, p_inet_ntoa(a), buflen);
+ buf[buflen-1] = '\0';
+ } else {
+ strncpy(buf, addr->hostname, buflen);
+ buf[buflen-1] = '\0';
+ }
+}
+
+/*
+ * This constructs a SockAddr that points at one specific sub-address
+ * of a parent SockAddr. The returned SockAddr does not own all its
+ * own memory: it points into the old one's data structures, so it
+ * MUST NOT be used after the old one is freed, and it MUST NOT be
+ * passed to sk_addr_free. (The latter is why it's returned by value
+ * rather than dynamically allocated - that should clue in anyone
+ * writing a call to it that something is weird about it.)
+ */
+static SockAddr sk_extractaddr_tmp(
+ SockAddr *addr, const SockAddrStep *step)
+{
+ SockAddr toret;
+ toret = *addr; /* structure copy */
+ toret.refcount = 1;
+
+#ifndef NO_IPV6
+ toret.ais = step->ai;
+#endif
+ if (sockaddr_family(addr, *step) == AF_INET
+#ifndef NO_IPV6
+ && !toret.ais
+#endif
+ )
+ toret.addresses += step->curraddr;
+
+ return toret;
+}
+
+bool sk_addr_needs_port(SockAddr *addr)
+{
+ return addr->superfamily != NAMEDPIPE
+#if HAVE_AFUNIX_H
+ && addr->superfamily != UNIX
+#endif
+ ;
+}
+
+bool sk_hostname_is_local(const char *name)
+{
+ return !strcmp(name, "localhost") ||
+ !strcmp(name, "::1") ||
+ !strncmp(name, "127.", 4);
+}
+
+static INTERFACE_INFO local_interfaces[16];
+static int n_local_interfaces; /* 0=not yet, -1=failed, >0=number */
+
+static bool ipv4_is_local_addr(struct in_addr addr)
+{
+ if (ipv4_is_loopback(addr))
+ return true; /* loopback addresses are local */
+ if (!n_local_interfaces) {
+ SOCKET s = p_socket(AF_INET, SOCK_DGRAM, 0);
+ DWORD retbytes;
+
+ SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0);
+
+ if (p_WSAIoctl &&
+ p_WSAIoctl(s, SIO_GET_INTERFACE_LIST, NULL, 0,
+ local_interfaces, sizeof(local_interfaces),
+ &retbytes, NULL, NULL) == 0)
+ n_local_interfaces = retbytes / sizeof(INTERFACE_INFO);
+ else
+ n_local_interfaces = -1;
+ }
+ if (n_local_interfaces > 0) {
+ int i;
+ for (i = 0; i < n_local_interfaces; i++) {
+ SOCKADDR_IN *address =
+ (SOCKADDR_IN *)&local_interfaces[i].iiAddress;
+ if (address->sin_addr.s_addr == addr.s_addr)
+ return true; /* this address is local */
+ }
+ }
+ return false; /* this address is not local */
+}
+
+bool sk_address_is_local(SockAddr *addr)
+{
+ SockAddrStep step;
+ int family;
+ START_STEP(addr, step);
+ family = sockaddr_family(addr, step);
+
+#ifndef NO_IPV6
+ if (family == AF_INET6) {
+ return IN6_IS_ADDR_LOOPBACK(&((const struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr);
+ } else
+#endif
+ if (family == AF_INET) {
+#ifndef NO_IPV6
+ if (step.ai) {
+ return ipv4_is_local_addr(((struct sockaddr_in *)step.ai->ai_addr)
+ ->sin_addr);
+ } else
+#endif
+ {
+ struct in_addr a;
+ assert(addr->addresses && step.curraddr < addr->naddresses);
+ a.s_addr = p_htonl(addr->addresses[step.curraddr]);
+ return ipv4_is_local_addr(a);
+ }
+ } else {
+ assert(family == AF_UNSPEC);
+ return false; /* we don't know; assume not */
+ }
+}
+
+bool sk_address_is_special_local(SockAddr *addr)
+{
+ return false; /* no Unix-domain socket analogue here */
+}
+
+int sk_addrtype(SockAddr *addr)
+{
+ SockAddrStep step;
+ int family;
+ START_STEP(addr, step);
+ family = sockaddr_family(addr, step);
+
+ return (family == AF_INET ? ADDRTYPE_IPV4 :
+#ifndef NO_IPV6
+ family == AF_INET6 ? ADDRTYPE_IPV6 :
+#endif
+ ADDRTYPE_NAME);
+}
+
+void sk_addrcopy(SockAddr *addr, char *buf)
+{
+ SockAddrStep step;
+ int family;
+ START_STEP(addr, step);
+ family = sockaddr_family(addr, step);
+
+ assert(family != AF_UNSPEC);
+#ifndef NO_IPV6
+ if (step.ai) {
+ if (family == AF_INET)
+ memcpy(buf, &((struct sockaddr_in *)step.ai->ai_addr)->sin_addr,
+ sizeof(struct in_addr));
+ else if (family == AF_INET6)
+ memcpy(buf, &((struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr,
+ sizeof(struct in6_addr));
+ else
+ unreachable("bad address family in sk_addrcopy");
+ } else
+#endif
+ if (family == AF_INET) {
+ struct in_addr a;
+ assert(addr->addresses && step.curraddr < addr->naddresses);
+ a.s_addr = p_htonl(addr->addresses[step.curraddr]);
+ memcpy(buf, (char*) &a.s_addr, 4);
+ }
+}
+
+void sk_addr_free(SockAddr *addr)
+{
+ if (--addr->refcount > 0)
+ return;
+#ifndef NO_IPV6
+ if (addr->ais && p_freeaddrinfo)
+ p_freeaddrinfo(addr->ais);
+#endif
+ if (addr->addresses)
+ sfree(addr->addresses);
+ sfree(addr);
+}
+
+SockAddr *sk_addr_dup(SockAddr *addr)
+{
+ addr->refcount++;
+ return addr;
+}
+
+static Plug *sk_net_plug(Socket *sock, Plug *p)
+{
+ NetSocket *s = container_of(sock, NetSocket, sock);
+ Plug *ret = s->plug;
+ if (p)
+ s->plug = p;
+ return ret;
+}
+
+static void sk_net_close(Socket *s);
+static size_t sk_net_write(Socket *s, const void *data, size_t len);
+static size_t sk_net_write_oob(Socket *s, const void *data, size_t len);
+static void sk_net_write_eof(Socket *s);
+static void sk_net_set_frozen(Socket *s, bool is_frozen);
+static const char *sk_net_socket_error(Socket *s);
+static SocketPeerInfo *sk_net_peer_info(Socket *s);
+
+static const SocketVtable NetSocket_sockvt = {
+ .plug = sk_net_plug,
+ .close = sk_net_close,
+ .write = sk_net_write,
+ .write_oob = sk_net_write_oob,
+ .write_eof = sk_net_write_eof,
+ .set_frozen = sk_net_set_frozen,
+ .socket_error = sk_net_socket_error,
+ .peer_info = sk_net_peer_info,
+};
+
+static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug)
+{
+ DWORD err;
+ const char *errstr;
+ NetSocket *ret;
+
+ /*
+ * Create NetSocket structure.
+ */
+ ret = snew(NetSocket);
+ ret->sock.vt = &NetSocket_sockvt;
+ ret->error = NULL;
+ ret->plug = plug;
+ bufchain_init(&ret->output_data);
+ 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);
+ }
+
+ {
+ SockAddr thisaddr = sk_extractaddr_tmp(
+ sock->addr, &sock->step);
+ plug_log(sock->plug, PLUGLOG_CONNECT_TRYING,
+ &thisaddr, sock->port, NULL, 0);
+ }
+
+ /*
+ * Open socket.
+ */
+ family = sockaddr_family(sock->addr, sock->step);
+
+ /*
+ * Remove the socket from the tree before we overwrite its
+ * internal socket id, because that forms part of the tree's
+ * sorting criterion. We'll add it back before exiting this
+ * function, whether we changed anything or not.
+ */
+ del234(sktree, sock);
+
+ s = p_socket(family, SOCK_STREAM, 0);
+ sock->s = s;
+
+ if (s == INVALID_SOCKET) {
+ err = p_WSAGetLastError();
+ sock->error = winsock_error_string(err);
+ goto ret;
+ }
+
+ SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0);
+
+ if (sock->oobinline) {
+ BOOL b = true;
+ p_setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (void *) &b, sizeof(b));
+ }
+
+ if (sock->nodelay) {
+ BOOL b = true;
+ p_setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b));
+ }
+
+ if (sock->keepalive) {
+ BOOL b = true;
+ p_setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &b, sizeof(b));
+ }
+
+ /*
+ * Bind to local address.
+ */
+ if (sock->privport)
+ localport = 1023; /* count from 1023 downwards */
+ else
+ localport = 0; /* just use port 0 (ie winsock picks) */
+
+ /* Loop round trying to bind */
+ while (1) {
+ int sockcode;
+
+#ifndef NO_IPV6
+ if (family == AF_INET6) {
+ memset(&a6, 0, sizeof(a6));
+ a6.sin6_family = AF_INET6;
+ /*a6.sin6_addr = in6addr_any; */ /* == 0 done by memset() */
+ a6.sin6_port = p_htons(localport);
+ } else
+#endif
+ {
+ a.sin_family = AF_INET;
+ a.sin_addr.s_addr = p_htonl(INADDR_ANY);
+ a.sin_port = p_htons(localport);
+ }
+#ifndef NO_IPV6
+ sockcode = p_bind(s, (family == AF_INET6 ?
+ (struct sockaddr *) &a6 :
+ (struct sockaddr *) &a),
+ (family == AF_INET6 ? sizeof(a6) : sizeof(a)));
+#else
+ sockcode = p_bind(s, (struct sockaddr *) &a, sizeof(a));
+#endif
+ if (sockcode != SOCKET_ERROR) {
+ err = 0;
+ break; /* done */
+ } else {
+ err = p_WSAGetLastError();
+ if (err != WSAEADDRINUSE) /* failed, for a bad reason */
+ break;
+ }
+
+ if (localport == 0)
+ break; /* we're only looping once */
+ localport--;
+ if (localport == 0)
+ break; /* we might have got to the end */
+ }
+
+ if (err) {
+ sock->error = winsock_error_string(err);
+ goto ret;
+ }
+
+ /*
+ * Connect to remote address.
+ */
+#ifndef NO_IPV6
+ if (sock->step.ai) {
+ if (family == AF_INET6) {
+ a6.sin6_family = AF_INET6;
+ a6.sin6_port = p_htons((short) sock->port);
+ a6.sin6_addr =
+ ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_addr;
+ a6.sin6_flowinfo = ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_flowinfo;
+ a6.sin6_scope_id = ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_scope_id;
+ } else {
+ a.sin_family = AF_INET;
+ a.sin_addr =
+ ((struct sockaddr_in *) sock->step.ai->ai_addr)->sin_addr;
+ a.sin_port = p_htons((short) sock->port);
+ }
+ } else
+#endif
+ {
+ assert(sock->addr->addresses && sock->step.curraddr < sock->addr->naddresses);
+ a.sin_family = AF_INET;
+ a.sin_addr.s_addr = p_htonl(sock->addr->addresses[sock->step.curraddr]);
+ a.sin_port = p_htons((short) sock->port);
+ }
+
+ /* Set up a select mechanism. This could be an AsyncSelect on a
+ * window, or an EventSelect on an event object. */
+ errstr = do_select(s, true);
+ if (errstr) {
+ sock->error = errstr;
+ err = 1;
+ goto ret;
+ }
+
+ if ((
+#ifndef NO_IPV6
+ p_connect(s,
+ ((family == AF_INET6) ? (struct sockaddr *) &a6 :
+ (struct sockaddr *) &a),
+ (family == AF_INET6) ? sizeof(a6) : sizeof(a))
+#else
+ p_connect(s, (struct sockaddr *) &a, sizeof(a))
+#endif
+ ) == SOCKET_ERROR) {
+ err = p_WSAGetLastError();
+ /*
+ * We expect a potential EWOULDBLOCK here, because the
+ * chances are the front end has done a select for
+ * FD_CONNECT, so that connect() will complete
+ * asynchronously.
+ */
+ if ( err != WSAEWOULDBLOCK ) {
+ sock->error = winsock_error_string(err);
+ goto ret;
+ }
+ } else {
+ /*
+ * If we _don't_ get EWOULDBLOCK, the connect has completed
+ * and we should set the socket as writable.
+ */
+ sock->writable = true;
+ SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step);
+ plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS,
+ &thisaddr, sock->port, NULL, 0);
+ }
+
+ err = 0;
+
+ ret:
+
+ /*
+ * No matter what happened, put the socket back in the tree.
+ */
+ add234(sktree, sock);
+
+ if (err) {
+ SockAddr thisaddr = sk_extractaddr_tmp(
+ sock->addr, &sock->step);
+ plug_log(sock->plug, PLUGLOG_CONNECT_FAILED,
+ &thisaddr, sock->port, sock->error, err);
+ }
+ return err;
+}
+
+Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline,
+ bool nodelay, bool keepalive, Plug *plug)
+{
+ NetSocket *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 */
+ 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;
+}
+
+static Socket *sk_newlistener_internal(
+ const char *srcaddr, int port, Plug *plug,
+ bool local_host_only, int orig_address_family)
+{
+ SOCKET s;
+ SOCKADDR_IN a;
+#ifndef NO_IPV6
+ SOCKADDR_IN6 a6;
+#endif
+#if HAVE_AFUNIX_H
+ SOCKADDR_UN au;
+#endif
+ struct sockaddr *bindaddr;
+ unsigned bindsize;
+
+ DWORD err;
+ const char *errstr;
+ NetSocket *ret;
+ int retcode;
+
+ int address_family = orig_address_family;
+
+ /*
+ * Create NetSocket structure.
+ */
+ ret = snew(NetSocket);
+ ret->sock.vt = &NetSocket_sockvt;
+ ret->error = NULL;
+ ret->plug = plug;
+ bufchain_init(&ret->output_data);
+ 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;
+
+ /*
+ * 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;
+
+#if HAVE_AFUNIX_H
+ if (address_family != AF_UNIX)
+#endif
+ {
+ BOOL on = true;
+ p_setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
+ (const char *)&on, sizeof(on));
+ }
+
+ switch (address_family) {
+#ifndef NO_IPV6
+ case AF_INET6: {
+ memset(&a6, 0, sizeof(a6));
+ a6.sin6_family = AF_INET6;
+ if (local_host_only)
+ a6.sin6_addr = in6addr_loopback;
+ else
+ a6.sin6_addr = in6addr_any;
+ if (srcaddr != NULL && p_getaddrinfo) {
+ struct addrinfo hints;
+ struct addrinfo *ai;
+ int err;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_flags = 0;
+ {
+ /* strip [] on IPv6 address literals */
+ char *trimmed_addr = host_strduptrim(srcaddr);
+ err = p_getaddrinfo(trimmed_addr, NULL, &hints, &ai);
+ sfree(trimmed_addr);
+ }
+ if (err == 0 && ai->ai_family == AF_INET6) {
+ a6.sin6_addr =
+ ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;
+ }
+ }
+ a6.sin6_port = p_htons(port);
+ bindaddr = (struct sockaddr *)&a6;
+ bindsize = sizeof(a6);
+ break;
+ }
+#endif
+ case AF_INET: {
+ bool got_addr = false;
+ a.sin_family = AF_INET;
+
+ /*
+ * Bind to source address. First try an explicitly
+ * specified one...
+ */
+ if (srcaddr) {
+ a.sin_addr.s_addr = p_inet_addr(srcaddr);
+ if (a.sin_addr.s_addr != INADDR_NONE) {
+ /* Override localhost_only with specified listen addr. */
+ 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);
+ bindaddr = (struct sockaddr *)&a;
+ bindsize = sizeof(a);
+ break;
+ }
+#if HAVE_AFUNIX_H
+ case AF_UNIX: {
+ au.sun_family = AF_UNIX;
+ strncpy(au.sun_path, srcaddr, sizeof(au.sun_path));
+ bindaddr = (struct sockaddr *)&au;
+ bindsize = sizeof(au);
+ break;
+ }
+#endif
+ default:
+ unreachable("bad address family in sk_newlistener_internal");
+ }
+
+ retcode = p_bind(s, bindaddr, bindsize);
+ 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 == AF_UNSPEC) {
+ Socket *other = sk_newlistener_internal(srcaddr, port, plug,
+ local_host_only, AF_INET6);
+
+ if (other) {
+ NetSocket *ns = container_of(other, NetSocket, sock);
+ if (!ns->error) {
+ ns->parent = ret;
+ ret->child = ns;
+ } else {
+ sfree(ns);
+ }
+ }
+ }
+#endif
+
+ return &ret->sock;
+}
+
+Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
+ bool local_host_only, int orig_address_family)
+{
+ /*
+ * Translate address_family from platform-independent constants
+ * into local reality.
+ */
+ int address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET :
+#ifndef NO_IPV6
+ orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 :
+#endif
+ AF_UNSPEC);
+
+ return sk_newlistener_internal(srcaddr, port, plug, local_host_only,
+ address_family);
+}
+
+Socket *sk_newlistener_unix(const char *path, Plug *plug)
+{
+#if HAVE_AFUNIX_H
+ return sk_newlistener_internal(path, 0, plug, false, AF_UNIX);
+#else
+ return new_error_socket_fmt(
+ plug, "AF_UNIX support not compiled into this program");
+#endif
+}
+
+static void sk_net_close(Socket *sock)
+{
+ NetSocket *s = container_of(sock, NetSocket, sock);
+
+ if (s->child)
+ sk_net_close(&s->child->sock);
+
+ bufchain_clear(&s->output_data);
+
+ del234(sktree, s);
+ do_select(s->s, false);
+ p_closesocket(s->s);
+ if (s->addr)
+ sk_addr_free(s->addr);
+ delete_callbacks_for_context(s);
+ sfree(s);
+}
+
+void plug_closing_system_error(Plug *plug, DWORD error)
+{
+ PlugCloseType type = PLUGCLOSE_ERROR;
+ if (error == ERROR_BROKEN_PIPE)
+ type = PLUGCLOSE_BROKEN_PIPE;
+ plug_closing(plug, type, win_strerror(error));
+}
+
+void plug_closing_winsock_error(Plug *plug, DWORD error)
+{
+ plug_closing(plug, PLUGCLOSE_ERROR, winsock_error_string(error));
+}
+
+/*
+ * Deal with socket errors detected in try_send().
+ */
+static void socket_error_callback(void *vs)
+{
+ NetSocket *s = (NetSocket *)vs;
+
+ /*
+ * Just in case other socket work has caused this socket to vanish
+ * or become somehow non-erroneous before this callback arrived...
+ */
+ if (!find234(sktree, s, NULL) || !s->pending_error)
+ return;
+
+ /*
+ * An error has occurred on this socket. Pass it to the plug.
+ */
+ plug_closing_winsock_error(s->plug, s->pending_error);
+}
+
+/*
+ * The function which tries to send on a socket once it's deemed
+ * writable.
+ */
+static void try_send(NetSocket *s)
+{
+ while (s->sending_oob || bufchain_size(&s->output_data) > 0) {
+ int nsent;
+ DWORD err;
+ const void *data;
+ size_t len;
+ int urgentflag;
+
+ if (s->sending_oob) {
+ urgentflag = MSG_OOB;
+ len = s->sending_oob;
+ data = &s->oobdata;
+ } else {
+ urgentflag = 0;
+ ptrlen bufdata = bufchain_prefix(&s->output_data);
+ data = bufdata.ptr;
+ len = bufdata.len;
+ }
+ len = min(len, INT_MAX); /* WinSock send() takes an int */
+ nsent = p_send(s->s, data, len, urgentflag);
+ noise_ultralight(NOISE_SOURCE_IOLEN, nsent);
+ if (nsent <= 0) {
+ err = (nsent < 0 ? p_WSAGetLastError() : 0);
+ if ((err < WSABASEERR && nsent < 0) || err == WSAEWOULDBLOCK) {
+ /*
+ * Perfectly normal: we've sent all we can for the moment.
+ *
+ * (Some WinSock send() implementations can return
+ * <0 but leave no sensible error indication -
+ * WSAGetLastError() is called but returns zero or
+ * a small number - so we check that case and treat
+ * it just like WSAEWOULDBLOCK.)
+ */
+ s->writable = false;
+ return;
+ } else {
+ /*
+ * If send() returns a socket error, we unfortunately
+ * can't just call plug_closing(), because it's quite
+ * likely that we're currently _in_ a call from the
+ * code we'd be calling back to, so we'd have to make
+ * half the SSH code reentrant. Instead we flag a
+ * pending error on the socket, to be dealt with (by
+ * calling plug_closing()) at some suitable future
+ * moment.
+ */
+ s->pending_error = err;
+ queue_toplevel_callback(socket_error_callback, s);
+ return;
+ }
+ } else {
+ if (s->sending_oob) {
+ if (nsent < len) {
+ memmove(s->oobdata, s->oobdata+nsent, len-nsent);
+ s->sending_oob = len - nsent;
+ } else {
+ s->sending_oob = 0;
+ }
+ } else {
+ bufchain_consume(&s->output_data, nsent);
+ }
+ }
+ }
+
+ /*
+ * If we reach here, we've finished sending everything we might
+ * have needed to send. Send EOF, if we need to.
+ */
+ if (s->outgoingeof == EOF_PENDING) {
+ p_shutdown(s->s, SD_SEND);
+ s->outgoingeof = EOF_SENT;
+ }
+}
+
+static size_t sk_net_write(Socket *sock, const void *buf, size_t len)
+{
+ NetSocket *s = container_of(sock, NetSocket, sock);
+
+ assert(s->outgoingeof == EOF_NO);
+
+ /*
+ * Add the data to the buffer list on the socket.
+ */
+ bufchain_add(&s->output_data, buf, len);
+
+ /*
+ * Now try sending from the start of the buffer list.
+ */
+ if (s->writable)
+ try_send(s);
+
+ return bufchain_size(&s->output_data);
+}
+
+static size_t sk_net_write_oob(Socket *sock, const void *buf, size_t len)
+{
+ NetSocket *s = container_of(sock, NetSocket, sock);
+
+ assert(s->outgoingeof == EOF_NO);
+
+ /*
+ * Replace the buffer list on the socket with the data.
+ */
+ bufchain_clear(&s->output_data);
+ assert(len <= sizeof(s->oobdata));
+ memcpy(s->oobdata, buf, len);
+ s->sending_oob = len;
+
+ /*
+ * Now try sending from the start of the buffer list.
+ */
+ if (s->writable)
+ try_send(s);
+
+ return s->sending_oob;
+}
+
+static void sk_net_write_eof(Socket *sock)
+{
+ NetSocket *s = container_of(sock, NetSocket, sock);
+
+ assert(s->outgoingeof == EOF_NO);
+
+ /*
+ * Mark the socket as pending outgoing EOF.
+ */
+ s->outgoingeof = EOF_PENDING;
+
+ /*
+ * Now try sending from the start of the buffer list.
+ */
+ if (s->writable)
+ try_send(s);
+}
+
+void select_result(WPARAM wParam, LPARAM lParam)
+{
+ int ret;
+ DWORD err;
+ char buf[20480]; /* nice big buffer for plenty of speed */
+ NetSocket *s;
+ bool atmark;
+
+ /* wParam is the socket itself */
+
+ if (wParam == 0)
+ return; /* boggle */
+
+ s = find234(sktree, (void *) wParam, cmpforsearch);
+ if (!s)
+ return; /* boggle */
+
+ if ((err = WSAGETSELECTERROR(lParam)) != 0) {
+ /*
+ * An error has occurred on this socket. Pass it to the
+ * plug.
+ */
+ if (s->addr) {
+ SockAddr thisaddr = sk_extractaddr_tmp(
+ s->addr, &s->step);
+ plug_log(s->plug, PLUGLOG_CONNECT_FAILED, &thisaddr, s->port,
+ winsock_error_string(err), err);
+ while (err && s->addr && sk_nextaddr(s->addr, &s->step)) {
+ err = try_connect(s);
+ }
+ }
+ if (err != 0)
+ plug_closing_winsock_error(s->plug, err);
+ return;
+ }
+
+ noise_ultralight(NOISE_SOURCE_IOID, wParam);
+
+ switch (WSAGETSELECTEVENT(lParam)) {
+ case FD_CONNECT:
+ s->connected = true;
+ s->writable = true;
+
+ /*
+ * Once a socket is connected, we can stop falling back
+ * through the candidate addresses to connect to. But first,
+ * let the plug know we were successful.
+ */
+ if (s->addr) {
+ SockAddr thisaddr = sk_extractaddr_tmp(
+ s->addr, &s->step);
+ plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS,
+ &thisaddr, s->port, NULL, 0);
+
+ sk_addr_free(s->addr);
+ s->addr = NULL;
+ }
+ break;
+ case FD_READ:
+ /* In the case the socket is still frozen, we don't even bother */
+ if (s->frozen) {
+ s->frozen_readable = true;
+ break;
+ }
+
+ /*
+ * We have received data on the socket. For an oobinline
+ * socket, this might be data _before_ an urgent pointer,
+ * in which case we send it to the back end with type==1
+ * (data prior to urgent).
+ */
+ if (s->oobinline) {
+ u_long atmark_from_ioctl = 1;
+ p_ioctlsocket(s->s, SIOCATMARK, &atmark_from_ioctl);
+ /*
+ * Avoid checking the return value from ioctlsocket(),
+ * on the grounds that some WinSock wrappers don't
+ * support it. If it does nothing, we get atmark==1,
+ * which is equivalent to `no OOB pending', so the
+ * effect will be to non-OOB-ify any OOB data.
+ */
+ atmark = atmark_from_ioctl;
+ } else
+ atmark = true;
+
+ ret = p_recv(s->s, buf, sizeof(buf), 0);
+ noise_ultralight(NOISE_SOURCE_IOLEN, ret);
+ if (ret < 0) {
+ err = p_WSAGetLastError();
+ if (err == WSAEWOULDBLOCK) {
+ break;
+ }
+ }
+ if (ret < 0) {
+ plug_closing_winsock_error(s->plug, err);
+ } else if (0 == ret) {
+ plug_closing_normal(s->plug);
+ } else {
+ plug_receive(s->plug, atmark ? 0 : 1, buf, ret);
+ }
+ break;
+ case FD_OOB:
+ /*
+ * This will only happen on a non-oobinline socket. It
+ * indicates that we can immediately perform an OOB read
+ * and get back OOB data, which we will send to the back
+ * end with type==2 (urgent data).
+ */
+ ret = p_recv(s->s, buf, sizeof(buf), MSG_OOB);
+ noise_ultralight(NOISE_SOURCE_IOLEN, ret);
+ if (ret <= 0) {
+ int err = p_WSAGetLastError();
+ plug_closing_winsock_error(s->plug, err);
+ } else {
+ plug_receive(s->plug, 2, buf, ret);
+ }
+ break;
+ case FD_WRITE: {
+ int bufsize_before, bufsize_after;
+ s->writable = true;
+ bufsize_before = s->sending_oob + bufchain_size(&s->output_data);
+ try_send(s);
+ bufsize_after = s->sending_oob + bufchain_size(&s->output_data);
+ if (bufsize_after < bufsize_before)
+ plug_sent(s->plug, bufsize_after);
+ break;
+ }
+ case FD_CLOSE:
+ /* Signal a close on the socket. First read any outstanding data. */
+ do {
+ ret = p_recv(s->s, buf, sizeof(buf), 0);
+ if (ret < 0) {
+ err = p_WSAGetLastError();
+ if (err == WSAEWOULDBLOCK)
+ break;
+ plug_closing_winsock_error(s->plug, err);
+ } else {
+ if (ret)
+ plug_receive(s->plug, 0, buf, ret);
+ else
+ plug_closing_normal(s->plug);
+ }
+ } while (ret > 0);
+ return;
+ case FD_ACCEPT: {
+#ifdef NO_IPV6
+ struct sockaddr_in isa;
+#else
+ struct sockaddr_storage isa; // FIXME: also if Unix and no IPv6
+#endif
+ int addrlen = sizeof(isa);
+ SOCKET t; /* socket of connection */
+ accept_ctx_t actx;
+
+ memset(&isa, 0, sizeof(isa));
+ err = 0;
+ t = p_accept(s->s,(struct sockaddr *)&isa,&addrlen);
+ if (t == INVALID_SOCKET)
+ {
+ err = p_WSAGetLastError();
+ if (err == WSATRY_AGAIN)
+ break;
+ }
+
+ actx.p = (void *)t;
+
+#ifndef NO_IPV6
+ if (isa.ss_family == AF_INET &&
+ s->localhost_only &&
+ !ipv4_is_local_addr(((struct sockaddr_in *)&isa)->sin_addr))
+#else
+ if (s->localhost_only && !ipv4_is_local_addr(isa.sin_addr))
+#endif
+ {
+ p_closesocket(t); /* dodgy WinSock let nonlocal through */
+ } else if (plug_accepting(s->plug, sk_net_accept, actx)) {
+ p_closesocket(t); /* denied or error */
+ }
+ break;
+ }
+ }
+}
+
+/*
+ * Special error values are returned from sk_namelookup and sk_new
+ * if there's a problem. These functions extract an error message,
+ * or return NULL if there's no problem.
+ */
+const char *sk_addr_error(SockAddr *addr)
+{
+ return addr->error;
+}
+static const char *sk_net_socket_error(Socket *sock)
+{
+ NetSocket *s = container_of(sock, NetSocket, sock);
+ return s->error;
+}
+
+static SocketPeerInfo *sk_net_peer_info(Socket *sock)
+{
+ NetSocket *s = container_of(sock, NetSocket, sock);
+#ifdef NO_IPV6
+ struct sockaddr_in addr;
+#else
+ struct sockaddr_storage addr; // FIXME: also if Unix and no IPv6
+ char buf[INET6_ADDRSTRLEN];
+#endif
+ int addrlen = sizeof(addr);
+ SocketPeerInfo *pi;
+
+ if (p_getpeername(s->s, (struct sockaddr *)&addr, &addrlen) < 0)
+ return NULL;
+
+ pi = snew(SocketPeerInfo);
+ pi->addressfamily = ADDRTYPE_UNSPEC;
+ pi->addr_text = NULL;
+ pi->port = -1;
+ pi->log_text = NULL;
+
+ if (((struct sockaddr *)&addr)->sa_family == AF_INET) {
+ pi->addressfamily = ADDRTYPE_IPV4;
+ memcpy(pi->addr_bin.ipv4, &((struct sockaddr_in *)&addr)->sin_addr, 4);
+ pi->port = p_ntohs(((struct sockaddr_in *)&addr)->sin_port);
+ pi->addr_text = dupstr(
+ p_inet_ntoa(((struct sockaddr_in *)&addr)->sin_addr));
+ pi->log_text = dupprintf("%s:%d", pi->addr_text, pi->port);
+
+#ifndef NO_IPV6
+ } else if (((struct sockaddr *)&addr)->sa_family == AF_INET6) {
+ pi->addressfamily = ADDRTYPE_IPV6;
+ memcpy(pi->addr_bin.ipv6,
+ &((struct sockaddr_in6 *)&addr)->sin6_addr, 16);
+ pi->port = p_ntohs(((struct sockaddr_in6 *)&addr)->sin6_port);
+ pi->addr_text = dupstr(
+ p_inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&addr)->sin6_addr,
+ buf, sizeof(buf)));
+ pi->log_text = dupprintf("[%s]:%d", pi->addr_text, pi->port);
+
+#endif
+ } else {
+ sfree(pi);
+ return NULL;
+ }
+
+ return pi;
+}
+
+static void sk_net_set_frozen(Socket *sock, bool is_frozen)
+{
+ NetSocket *s = container_of(sock, NetSocket, sock);
+ if (s->frozen == is_frozen)
+ return;
+ s->frozen = is_frozen;
+ if (!is_frozen) {
+ do_select(s->s, true);
+ if (s->frozen_readable) {
+ char c;
+ p_recv(s->s, &c, 1, MSG_PEEK);
+ }
+ }
+ s->frozen_readable = false;
+}
+
+void socket_reselect_all(void)
+{
+ NetSocket *s;
+ int i;
+
+ for (i = 0; (s = index234(sktree, i)) != NULL; i++) {
+ if (!s->frozen)
+ do_select(s->s, true);
+ }
+}
+
+/*
+ * For Plink: enumerate all sockets currently active.
+ */
+SOCKET first_socket(int *state)
+{
+ NetSocket *s;
+ *state = 0;
+ s = index234(sktree, (*state)++);
+ return s ? s->s : INVALID_SOCKET;
+}
+
+SOCKET next_socket(int *state)
+{
+ NetSocket *s = index234(sktree, (*state)++);
+ return s ? s->s : INVALID_SOCKET;
+}
+
+bool socket_writable(SOCKET skt)
+{
+ NetSocket *s = find234(sktree, (void *)skt, cmpforsearch);
+
+ if (s)
+ return bufchain_size(&s->output_data) > 0;
+ else
+ return false;
+}
+
+int net_service_lookup(const char *service)
+{
+ struct servent *se;
+ se = p_getservbyname(service, NULL);
+ if (se != NULL)
+ return p_ntohs(se->s_port);
+ else
+ return 0;
+}
+
+char *get_hostname(void)
+{
+ char hostbuf[256]; /* MSDN docs for gethostname() promise this is enough */
+ if (p_gethostname(hostbuf, sizeof(hostbuf)) < 0)
+ return NULL;
+ return dupstr(hostbuf);
+}
+
+SockAddr *platform_get_x11_unix_address(const char *display, int displaynum)
+{
+ SockAddr *ret = snew(SockAddr);
+ memset(ret, 0, sizeof(SockAddr));
+ ret->error = "unix sockets for X11 not supported on this platform";
+ ret->refcount = 1;
+ return ret;
+}
diff --git a/windows/no-jump-list.c b/windows/no-jump-list.c
new file mode 100644
index 00000000..beabd107
--- /dev/null
+++ b/windows/no-jump-list.c
@@ -0,0 +1,10 @@
+/*
+ * no-jump-list.c: stub jump list functions for Windows executables
+ * that don't update the jump list.
+ */
+
+#include "putty.h"
+
+void add_session_to_jumplist(const char * const sessionname) {}
+void remove_session_from_jumplist(const char * const sessionname) {}
+void clear_jumplist(void) {}
diff --git a/windows/nohelp.c b/windows/nohelp.c
new file mode 100644
index 00000000..1b748776
--- /dev/null
+++ b/windows/nohelp.c
@@ -0,0 +1,15 @@
+/*
+ * nohelp.c: implement the has_embedded_chm() function for
+ * applications that have no help file at all, so that buildinfo.c's
+ * buildinfo string knows not to talk meaninglessly about whether the
+ * nonexistent help file is present.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "putty.h"
+
+int has_embedded_chm(void) { return -1; }
diff --git a/WINDOWS/WINNOISE.C b/windows/noise.c
index 65c4c92d..65c4c92d 100644
--- a/WINDOWS/WINNOISE.C
+++ b/windows/noise.c
diff --git a/windows/pageant.c b/windows/pageant.c
new file mode 100644
index 00000000..d1368903
--- /dev/null
+++ b/windows/pageant.c
@@ -0,0 +1,1958 @@
+/*
+ * Pageant: the PuTTY Authentication Agent.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <assert.h>
+#include <tchar.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "misc.h"
+#include "tree234.h"
+#include "security-api.h"
+#include "cryptoapi.h"
+#include "pageant.h"
+#include "licence.h"
+#include "pageant-rc.h"
+
+#include <shellapi.h>
+
+#include <aclapi.h>
+#ifdef DEBUG_IPC
+#define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */
+#include <sddl.h>
+#endif
+
+#define WM_SYSTRAY (WM_APP + 6)
+#define WM_SYSTRAY2 (WM_APP + 7)
+
+#define APPNAME "Pageant"
+
+/* Titles and class names for invisible windows. IPCWINTITLE and
+ * IPCCLASSNAME are critical to backwards compatibility: WM_COPYDATA
+ * based Pageant clients will call FindWindow with those parameters
+ * and expect to find the Pageant IPC receiver. */
+#define TRAYWINTITLE "Pageant"
+#define TRAYCLASSNAME "PageantSysTray"
+#define IPCWINTITLE "Pageant"
+#define IPCCLASSNAME "Pageant"
+
+static HWND traywindow;
+static HWND keylist;
+static HWND aboutbox;
+static HMENU systray_menu, session_menu;
+static bool already_running;
+static FingerprintType fptype = SSH_FPTYPE_DEFAULT;
+
+static char *putty_path;
+static bool restrict_putty_acl = false;
+
+/* CWD for "add key" file requester. */
+static filereq *keypath = NULL;
+
+/* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
+ * wParam are used by Windows, and should be masked off, so we shouldn't
+ * attempt to store information in them. Hence all these identifiers have
+ * the low 4 bits clear. Also, identifiers should < 0xF000. */
+
+#define IDM_CLOSE 0x0010
+#define IDM_VIEWKEYS 0x0020
+#define IDM_ADDKEY 0x0030
+#define IDM_ADDKEY_ENCRYPTED 0x0040
+#define IDM_REMOVE_ALL 0x0050
+#define IDM_REENCRYPT_ALL 0x0060
+#define IDM_HELP 0x0070
+#define IDM_ABOUT 0x0080
+#define IDM_PUTTY 0x0090
+#define IDM_SESSIONS_BASE 0x1000
+#define IDM_SESSIONS_MAX 0x2000
+#define PUTTY_REGKEY "Software\\SimonTatham\\PuTTY\\Sessions"
+#define PUTTY_DEFAULT "Default%20Settings"
+static int initial_menuitems_count;
+
+/*
+ * Print a modal (Really Bad) message box and perform a fatal exit.
+ */
+void modalfatalbox(const char *fmt, ...)
+{
+ va_list ap;
+ char *buf;
+
+ va_start(ap, fmt);
+ buf = dupvprintf(fmt, ap);
+ va_end(ap);
+ MessageBox(traywindow, buf, "Pageant Fatal Error",
+ MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
+ sfree(buf);
+ exit(1);
+}
+
+struct PassphraseProcStruct {
+ bool modal;
+ const char *help_topic;
+ PageantClientDialogId *dlgid;
+ char *passphrase;
+ const char *comment;
+};
+
+/*
+ * Dialog-box function for the Licence box.
+ */
+static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ SetDlgItemText(hwnd, IDC_LICENCE_TEXTBOX, LICENCE_TEXT("\r\n\r\n"));
+ return 1;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hwnd, 1);
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ EndDialog(hwnd, 1);
+ return 0;
+ }
+ return 0;
+}
+
+/*
+ * Dialog-box function for the About box.
+ */
+static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG: {
+ char *buildinfo_text = buildinfo("\r\n");
+ char *text = dupprintf(
+ "Pageant\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s",
+ ver, buildinfo_text,
+ "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved.");
+ sfree(buildinfo_text);
+ SetDlgItemText(hwnd, IDC_ABOUT_TEXTBOX, text);
+ MakeDlgItemBorderless(hwnd, IDC_ABOUT_TEXTBOX);
+ sfree(text);
+ return 1;
+ }
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ aboutbox = NULL;
+ DestroyWindow(hwnd);
+ return 0;
+ case IDC_ABOUT_LICENCE:
+ EnableWindow(hwnd, 0);
+ DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCE), hwnd, LicenceProc);
+ EnableWindow(hwnd, 1);
+ SetActiveWindow(hwnd);
+ return 0;
+ case IDC_ABOUT_WEBSITE:
+ /* Load web browser */
+ ShellExecute(hwnd, "open",
+ "https://www.chiark.greenend.org.uk/~sgtatham/putty/",
+ 0, 0, SW_SHOWDEFAULT);
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ aboutbox = NULL;
+ DestroyWindow(hwnd);
+ return 0;
+ }
+ return 0;
+}
+
+static HWND modal_passphrase_hwnd = NULL;
+static HWND nonmodal_passphrase_hwnd = NULL;
+
+static void end_passphrase_dialog(HWND hwnd, INT_PTR result)
+{
+ struct PassphraseProcStruct *p = (struct PassphraseProcStruct *)
+ GetWindowLongPtr(hwnd, GWLP_USERDATA);
+
+ if (p->modal) {
+ EndDialog(hwnd, result);
+ } else {
+ /*
+ * Destroy this passphrase dialog box before passing the
+ * results back to the main pageant.c, to avoid re-entrancy
+ * issues.
+ *
+ * If we successfully got a passphrase from the user, but it
+ * was _wrong_, then pageant_passphrase_request_success will
+ * respond by calling back - synchronously - to our
+ * ask_passphrase() implementation, which will expect the
+ * previous value of nonmodal_passphrase_hwnd to have already
+ * been cleaned up.
+ */
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) NULL);
+ DestroyWindow(hwnd);
+ nonmodal_passphrase_hwnd = NULL;
+
+ if (result)
+ pageant_passphrase_request_success(
+ p->dlgid, ptrlen_from_asciz(p->passphrase));
+ else
+ pageant_passphrase_request_refused(p->dlgid);
+
+ burnstr(p->passphrase);
+ sfree(p);
+ }
+}
+
+/*
+ * Dialog-box function for the passphrase box.
+ */
+static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ struct PassphraseProcStruct *p;
+
+ if (msg == WM_INITDIALOG) {
+ p = (struct PassphraseProcStruct *) lParam;
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) p);
+ } else {
+ p = (struct PassphraseProcStruct *)
+ GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ }
+
+ switch (msg) {
+ case WM_INITDIALOG: {
+ if (p->modal)
+ modal_passphrase_hwnd = hwnd;
+
+ /*
+ * Centre the window.
+ */
+ RECT rs, rd;
+ HWND hw;
+
+ hw = GetDesktopWindow();
+ if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
+ MoveWindow(hwnd,
+ (rs.right + rs.left + rd.left - rd.right) / 2,
+ (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
+ rd.right - rd.left, rd.bottom - rd.top, true);
+
+ SetForegroundWindow(hwnd);
+ SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
+ if (!p->modal)
+ SetActiveWindow(hwnd); /* this won't have happened automatically */
+ if (p->comment)
+ SetDlgItemText(hwnd, IDC_PASSPHRASE_FINGERPRINT, p->comment);
+ burnstr(p->passphrase);
+ p->passphrase = dupstr("");
+ SetDlgItemText(hwnd, IDC_PASSPHRASE_EDITBOX, p->passphrase);
+ if (!p->help_topic || !has_help()) {
+ HWND item = GetDlgItem(hwnd, IDHELP);
+ if (item)
+ DestroyWindow(item);
+ }
+ return 0;
+ }
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ if (p->passphrase)
+ end_passphrase_dialog(hwnd, 1);
+ else
+ MessageBeep(0);
+ return 0;
+ case IDCANCEL:
+ end_passphrase_dialog(hwnd, 0);
+ return 0;
+ case IDHELP:
+ if (p->help_topic)
+ launch_help(hwnd, p->help_topic);
+ return 0;
+ case IDC_PASSPHRASE_EDITBOX:
+ if ((HIWORD(wParam) == EN_CHANGE) && p->passphrase) {
+ burnstr(p->passphrase);
+ p->passphrase = GetDlgItemText_alloc(
+ hwnd, IDC_PASSPHRASE_EDITBOX);
+ }
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ end_passphrase_dialog(hwnd, 0);
+ return 0;
+ }
+ return 0;
+}
+
+/*
+ * Warn about the obsolescent key file format.
+ */
+void old_keyfile_warning(void)
+{
+ static const char mbtitle[] = "PuTTY Key File Warning";
+ static const char message[] =
+ "You are loading an SSH-2 private key which has an\n"
+ "old version of the file format. This means your key\n"
+ "file is not fully tamperproof. Future versions of\n"
+ "PuTTY may stop supporting this private key format,\n"
+ "so we recommend you convert your key to the new\n"
+ "format.\n"
+ "\n"
+ "You can perform this conversion by loading the key\n"
+ "into PuTTYgen and then saving it again.";
+
+ MessageBox(NULL, message, mbtitle, MB_OK);
+}
+
+struct keylist_update_ctx {
+ HDC hdc;
+ int algbitswidth, algwidth, bitswidth, hashwidth;
+ bool enable_remove_controls;
+ bool enable_reencrypt_controls;
+};
+
+struct keylist_display_data {
+ strbuf *alg, *bits, *hash, *comment, *info;
+};
+
+static void keylist_update_callback(
+ void *vctx, char **fingerprints, const char *comment, uint32_t ext_flags,
+ struct pageant_pubkey *key)
+{
+ struct keylist_update_ctx *ctx = (struct keylist_update_ctx *)vctx;
+ FingerprintType this_type = ssh2_pick_fingerprint(fingerprints, fptype);
+ ptrlen fingerprint = ptrlen_from_asciz(fingerprints[this_type]);
+
+ struct keylist_display_data *disp = snew(struct keylist_display_data);
+ disp->alg = strbuf_new();
+ disp->bits = strbuf_new();
+ disp->hash = strbuf_new();
+ disp->comment = strbuf_new();
+ disp->info = strbuf_new();
+
+ /* There is at least one key, so the controls for removing keys
+ * should be enabled */
+ ctx->enable_remove_controls = true;
+
+ switch (key->ssh_version) {
+ case 1: {
+ /*
+ * Expect the fingerprint to contain two words: bit count and
+ * hash.
+ */
+ put_dataz(disp->alg, "SSH-1");
+ put_datapl(disp->bits, ptrlen_get_word(&fingerprint, " "));
+ put_datapl(disp->hash, ptrlen_get_word(&fingerprint, " "));
+ break;
+ }
+
+ case 2: {
+ /*
+ * Expect the fingerprint to contain three words: algorithm
+ * name, bit count, hash.
+ */
+ const ssh_keyalg *alg = pubkey_blob_to_alg(
+ ptrlen_from_strbuf(key->blob));
+
+ ptrlen keytype_word = ptrlen_get_word(&fingerprint, " ");
+ if (alg) {
+ /* Use our own human-legible algorithm names if available,
+ * because they fit better in the space. (Certificate key
+ * algorithm names in particular are terribly long.) */
+ char *alg_desc = ssh_keyalg_desc(alg);
+ put_dataz(disp->alg, alg_desc);
+ sfree(alg_desc);
+ } else {
+ put_datapl(disp->alg, keytype_word);
+ }
+
+ ptrlen bits_word = ptrlen_get_word(&fingerprint, " ");
+ if (alg && ssh_keyalg_variable_size(alg))
+ put_datapl(disp->bits, bits_word);
+
+ put_datapl(disp->hash, ptrlen_get_word(&fingerprint, " "));
+ }
+ }
+
+ put_dataz(disp->comment, comment);
+
+ SIZE sz;
+ if (disp->bits->len) {
+ GetTextExtentPoint32(ctx->hdc, disp->alg->s, disp->alg->len, &sz);
+ if (ctx->algwidth < sz.cx) ctx->algwidth = sz.cx;
+ GetTextExtentPoint32(ctx->hdc, disp->bits->s, disp->bits->len, &sz);
+ if (ctx->bitswidth < sz.cx) ctx->bitswidth = sz.cx;
+ } else {
+ GetTextExtentPoint32(ctx->hdc, disp->alg->s, disp->alg->len, &sz);
+ if (ctx->algbitswidth < sz.cx) ctx->algbitswidth = sz.cx;
+ }
+ GetTextExtentPoint32(ctx->hdc, disp->hash->s, disp->hash->len, &sz);
+ if (ctx->hashwidth < sz.cx) ctx->hashwidth = sz.cx;
+
+ if (ext_flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY) {
+ put_fmt(disp->info, "(encrypted)");
+ } else if (ext_flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE) {
+ put_fmt(disp->info, "(re-encryptable)");
+
+ /* At least one key can be re-encrypted */
+ ctx->enable_reencrypt_controls = true;
+ }
+
+ /* This list box is owner-drawn but doesn't have LBS_HASSTRINGS,
+ * so we can use LB_ADDSTRING to hand the list box our display
+ * info pointer */
+ SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX,
+ LB_ADDSTRING, 0, (LPARAM)disp);
+}
+
+/* Column start positions for the list box, in pixels (not dialog units). */
+static int colpos_bits, colpos_hash, colpos_comment;
+
+/*
+ * Update the visible key list.
+ */
+void keylist_update(void)
+{
+ if (keylist) {
+ /*
+ * Clear the previous list box content and free their display
+ * structures.
+ */
+ {
+ int nitems = SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX,
+ LB_GETCOUNT, 0, 0);
+ for (int i = 0; i < nitems; i++) {
+ struct keylist_display_data *disp =
+ (struct keylist_display_data *)SendDlgItemMessage(
+ keylist, IDC_KEYLIST_LISTBOX, LB_GETITEMDATA, i, 0);
+ strbuf_free(disp->alg);
+ strbuf_free(disp->bits);
+ strbuf_free(disp->hash);
+ strbuf_free(disp->comment);
+ strbuf_free(disp->info);
+ sfree(disp);
+ }
+ }
+ SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX,
+ LB_RESETCONTENT, 0, 0);
+
+ char *errmsg;
+ struct keylist_update_ctx ctx[1];
+ ctx->enable_remove_controls = false;
+ ctx->enable_reencrypt_controls = false;
+ ctx->algbitswidth = ctx->algwidth = 0;
+ ctx->bitswidth = ctx->hashwidth = 0;
+ ctx->hdc = GetDC(keylist);
+ SelectObject(ctx->hdc, (HFONT)SendMessage(keylist, WM_GETFONT, 0, 0));
+ int status = pageant_enum_keys(keylist_update_callback, ctx, &errmsg);
+
+ SIZE sz;
+ GetTextExtentPoint32(ctx->hdc, "MM", 2, &sz);
+ int gutter = sz.cx;
+
+ DeleteDC(ctx->hdc);
+ colpos_hash = ctx->algwidth + ctx->bitswidth + 2*gutter;
+ if (colpos_hash < ctx->algbitswidth + gutter)
+ colpos_hash = ctx->algbitswidth + gutter;
+ colpos_bits = colpos_hash - ctx->bitswidth - gutter;
+ colpos_comment = colpos_hash + ctx->hashwidth + gutter;
+ assert(status == PAGEANT_ACTION_OK);
+ assert(!errmsg);
+
+ SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX,
+ LB_SETCURSEL, (WPARAM) - 1, 0);
+
+ EnableWindow(GetDlgItem(keylist, IDC_KEYLIST_REMOVE),
+ ctx->enable_remove_controls);
+ EnableWindow(GetDlgItem(keylist, IDC_KEYLIST_REENCRYPT),
+ ctx->enable_reencrypt_controls);
+ }
+}
+
+static void win_add_keyfile(Filename *filename, bool encrypted)
+{
+ char *err;
+ int ret;
+
+ /*
+ * Try loading the key without a passphrase. (Or rather, without a
+ * _new_ passphrase; pageant_add_keyfile will take care of trying
+ * all the passphrases we've already stored.)
+ */
+ ret = pageant_add_keyfile(filename, NULL, &err, encrypted);
+ if (ret == PAGEANT_ACTION_OK) {
+ goto done;
+ } else if (ret == PAGEANT_ACTION_FAILURE) {
+ goto error;
+ }
+
+ /*
+ * OK, a passphrase is needed, and we've been given the key
+ * comment to use in the passphrase prompt.
+ */
+ while (1) {
+ INT_PTR dlgret;
+ struct PassphraseProcStruct pps;
+ pps.modal = true;
+ pps.help_topic = NULL; /* this dialog has no help button */
+ pps.dlgid = NULL;
+ pps.passphrase = NULL;
+ pps.comment = err;
+ dlgret = DialogBoxParam(
+ hinst, MAKEINTRESOURCE(IDD_LOAD_PASSPHRASE),
+ NULL, PassphraseProc, (LPARAM) &pps);
+ modal_passphrase_hwnd = NULL;
+
+ if (!dlgret) {
+ burnstr(pps.passphrase);
+ goto done; /* operation cancelled */
+ }
+
+ sfree(err);
+
+ assert(pps.passphrase != NULL);
+
+ ret = pageant_add_keyfile(filename, pps.passphrase, &err, false);
+ burnstr(pps.passphrase);
+
+ if (ret == PAGEANT_ACTION_OK) {
+ goto done;
+ } else if (ret == PAGEANT_ACTION_FAILURE) {
+ goto error;
+ }
+ }
+
+ error:
+ message_box(traywindow, err, APPNAME, MB_OK | MB_ICONERROR,
+ 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},
+ {"SHA256 including certificate", SSH_FPTYPE_SHA256_CERT},
+ {"MD5 including certificate", SSH_FPTYPE_MD5_CERT},
+ };
+
+ switch (msg) {
+ case WM_INITDIALOG: {
+ /*
+ * Centre the window.
+ */
+ RECT rs, rd;
+ HWND hw;
+
+ hw = GetDesktopWindow();
+ if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
+ MoveWindow(hwnd,
+ (rs.right + rs.left + rd.left - rd.right) / 2,
+ (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
+ rd.right - rd.left, rd.bottom - rd.top, true);
+
+ if (has_help())
+ SetWindowLongPtr(hwnd, GWL_EXSTYLE,
+ GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
+ WS_EX_CONTEXTHELP);
+ else {
+ HWND item = GetDlgItem(hwnd, IDC_KEYLIST_HELP);
+ if (item)
+ DestroyWindow(item);
+ }
+
+ keylist = hwnd;
+
+ int selection = 0;
+ for (size_t i = 0; i < lenof(fptypes); i++) {
+ SendDlgItemMessage(hwnd, IDC_KEYLIST_FPTYPE, CB_ADDSTRING,
+ 0, (LPARAM)fptypes[i].name);
+ if (fptype == fptypes[i].value)
+ selection = (int)i;
+ }
+ SendDlgItemMessage(hwnd, IDC_KEYLIST_FPTYPE,
+ CB_SETCURSEL, 0, selection);
+
+ keylist_update();
+ return 0;
+ }
+ case WM_MEASUREITEM: {
+ assert(wParam == IDC_KEYLIST_LISTBOX);
+
+ MEASUREITEMSTRUCT *mi = (MEASUREITEMSTRUCT *)lParam;
+
+ /*
+ * Our list box is owner-drawn, but we put normal text in it.
+ * So the line height is the same as it would normally be,
+ * which is 8 dialog units.
+ */
+ RECT r;
+ r.left = r.right = r.top = 0;
+ r.bottom = 8;
+ MapDialogRect(hwnd, &r);
+ mi->itemHeight = r.bottom;
+
+ return 0;
+ }
+ case WM_DRAWITEM: {
+ assert(wParam == IDC_KEYLIST_LISTBOX);
+
+ DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam;
+
+ if (di->itemAction == ODA_FOCUS) {
+ /* Just toggle the focus rectangle either on or off. This
+ * is an XOR-type function, so it's the same call in
+ * either case. */
+ DrawFocusRect(di->hDC, &di->rcItem);
+ } else {
+ /* Draw the full text. */
+ bool selected = (di->itemState & ODS_SELECTED);
+ COLORREF newfg = GetSysColor(
+ selected ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT);
+ COLORREF newbg = GetSysColor(
+ selected ? COLOR_HIGHLIGHT : COLOR_WINDOW);
+ COLORREF oldfg = SetTextColor(di->hDC, newfg);
+ COLORREF oldbg = SetBkColor(di->hDC, newbg);
+
+ HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0);
+ HFONT oldfont = SelectObject(di->hDC, font);
+
+ /* ExtTextOut("") is an easy way to just draw the
+ * background rectangle */
+ ExtTextOut(di->hDC, di->rcItem.left, di->rcItem.top,
+ ETO_OPAQUE | ETO_CLIPPED, &di->rcItem, "", 0, NULL);
+
+ struct keylist_display_data *disp =
+ (struct keylist_display_data *)di->itemData;
+
+ RECT r;
+
+ /* Apparently real list boxes start drawing at x=1, not x=0 */
+ r.left = r.top = r.bottom = 0;
+ r.right = 1;
+ MapDialogRect(hwnd, &r);
+ ExtTextOut(di->hDC, di->rcItem.left + r.right, di->rcItem.top,
+ ETO_CLIPPED, &di->rcItem, disp->alg->s,
+ disp->alg->len, NULL);
+
+ if (disp->bits->len) {
+ ExtTextOut(di->hDC, di->rcItem.left + r.right + colpos_bits,
+ di->rcItem.top, ETO_CLIPPED, &di->rcItem,
+ disp->bits->s, disp->bits->len, NULL);
+ }
+
+ ExtTextOut(di->hDC, di->rcItem.left + r.right + colpos_hash,
+ di->rcItem.top, ETO_CLIPPED, &di->rcItem,
+ disp->hash->s, disp->hash->len, NULL);
+
+ strbuf *sb = strbuf_new();
+ put_datapl(sb, ptrlen_from_strbuf(disp->comment));
+ if (disp->info->len) {
+ put_byte(sb, '\t');
+ put_datapl(sb, ptrlen_from_strbuf(disp->info));
+ }
+
+ TabbedTextOut(di->hDC, di->rcItem.left + r.right + colpos_comment,
+ di->rcItem.top, sb->s, sb->len, 0, NULL, 0);
+
+ strbuf_free(sb);
+
+ SetTextColor(di->hDC, oldfg);
+ SetBkColor(di->hDC, oldbg);
+ SelectObject(di->hDC, oldfont);
+
+ if (di->itemState & ODS_FOCUS)
+ DrawFocusRect(di->hDC, &di->rcItem);
+ }
+ return 0;
+ }
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ keylist = NULL;
+ DestroyWindow(hwnd);
+ return 0;
+ case IDC_KEYLIST_ADDKEY:
+ case IDC_KEYLIST_ADDKEY_ENC:
+ if (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED) {
+ if (modal_passphrase_hwnd) {
+ MessageBeep(MB_ICONERROR);
+ SetForegroundWindow(modal_passphrase_hwnd);
+ break;
+ }
+ prompt_add_keyfile(LOWORD(wParam) == IDC_KEYLIST_ADDKEY_ENC);
+ }
+ return 0;
+ case IDC_KEYLIST_REMOVE:
+ case IDC_KEYLIST_REENCRYPT:
+ if (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED) {
+ int i;
+ int rCount, sCount;
+ int *selectedArray;
+
+ /* our counter within the array of selected items */
+ int itemNum;
+
+ /* get the number of items selected in the list */
+ int numSelected = SendDlgItemMessage(
+ hwnd, IDC_KEYLIST_LISTBOX, LB_GETSELCOUNT, 0, 0);
+
+ /* none selected? that was silly */
+ if (numSelected == 0) {
+ MessageBeep(0);
+ break;
+ }
+
+ /* get item indices in an array */
+ selectedArray = snewn(numSelected, int);
+ SendDlgItemMessage(hwnd, IDC_KEYLIST_LISTBOX, LB_GETSELITEMS,
+ numSelected, (WPARAM)selectedArray);
+
+ itemNum = numSelected - 1;
+ rCount = pageant_count_ssh1_keys();
+ sCount = pageant_count_ssh2_keys();
+
+ /* go through the non-rsakeys until we've covered them all,
+ * and/or we're out of selected items to check. note that
+ * we go *backwards*, to avoid complications from deleting
+ * things hence altering the offset of subsequent items
+ */
+ for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) {
+ if (selectedArray[itemNum] == rCount + i) {
+ switch (LOWORD(wParam)) {
+ case IDC_KEYLIST_REMOVE:
+ pageant_delete_nth_ssh2_key(i);
+ break;
+ case IDC_KEYLIST_REENCRYPT:
+ pageant_reencrypt_nth_ssh2_key(i);
+ break;
+ }
+ itemNum--;
+ }
+ }
+
+ /* do the same for the rsa keys */
+ for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) {
+ if(selectedArray[itemNum] == i) {
+ switch (LOWORD(wParam)) {
+ case IDC_KEYLIST_REMOVE:
+ pageant_delete_nth_ssh1_key(i);
+ break;
+ case IDC_KEYLIST_REENCRYPT:
+ /* SSH-1 keys can't be re-encrypted */
+ break;
+ }
+ itemNum--;
+ }
+ }
+
+ sfree(selectedArray);
+ keylist_update();
+ }
+ return 0;
+ case IDC_KEYLIST_HELP:
+ if (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED) {
+ launch_help(hwnd, WINHELP_CTX_pageant_general);
+ }
+ return 0;
+ case IDC_KEYLIST_FPTYPE:
+ if (HIWORD(wParam) == CBN_SELCHANGE) {
+ int selection = SendDlgItemMessage(
+ hwnd, IDC_KEYLIST_FPTYPE, CB_GETCURSEL, 0, 0);
+ if (selection >= 0 && (size_t)selection < lenof(fptypes)) {
+ fptype = fptypes[selection].value;
+ keylist_update();
+ }
+ }
+ return 0;
+ }
+ return 0;
+ case WM_HELP: {
+ int id = ((LPHELPINFO)lParam)->iCtrlId;
+ const char *topic = NULL;
+ switch (id) {
+ case IDC_KEYLIST_LISTBOX:
+ case IDC_KEYLIST_FPTYPE:
+ case IDC_KEYLIST_FPTYPE_STATIC:
+ topic = WINHELP_CTX_pageant_keylist; break;
+ case IDC_KEYLIST_ADDKEY: topic = WINHELP_CTX_pageant_addkey; break;
+ case IDC_KEYLIST_REMOVE: topic = WINHELP_CTX_pageant_remkey; break;
+ case IDC_KEYLIST_ADDKEY_ENC:
+ case IDC_KEYLIST_REENCRYPT:
+ topic = WINHELP_CTX_pageant_deferred; break;
+ }
+ if (topic) {
+ launch_help(hwnd, topic);
+ } else {
+ MessageBeep(0);
+ }
+ break;
+ }
+ case WM_CLOSE:
+ keylist = NULL;
+ DestroyWindow(hwnd);
+ return 0;
+ }
+ return 0;
+}
+
+/* Set up a system tray icon */
+static BOOL AddTrayIcon(HWND hwnd)
+{
+ BOOL res;
+ NOTIFYICONDATA tnid;
+ HICON hicon;
+
+#ifdef NIM_SETVERSION
+ tnid.uVersion = 0;
+ res = Shell_NotifyIcon(NIM_SETVERSION, &tnid);
+#endif
+
+ tnid.cbSize = sizeof(NOTIFYICONDATA);
+ tnid.hWnd = hwnd;
+ tnid.uID = 1; /* unique within this systray use */
+ tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
+ tnid.uCallbackMessage = WM_SYSTRAY;
+ tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
+ strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
+
+ res = Shell_NotifyIcon(NIM_ADD, &tnid);
+
+ if (hicon) DestroyIcon(hicon);
+
+ return res;
+}
+
+/* Update the saved-sessions menu. */
+static void update_sessions(void)
+{
+ int num_entries;
+ HKEY hkey;
+ TCHAR buf[MAX_PATH + 1];
+ MENUITEMINFO mii;
+ strbuf *sb;
+
+ int index_key, index_menu;
+
+ if (!putty_path)
+ return;
+
+ if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
+ return;
+
+ for(num_entries = GetMenuItemCount(session_menu);
+ num_entries > initial_menuitems_count;
+ num_entries--)
+ RemoveMenu(session_menu, 0, MF_BYPOSITION);
+
+ index_key = 0;
+ index_menu = 0;
+
+ sb = strbuf_new();
+ while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
+ if(strcmp(buf, PUTTY_DEFAULT) != 0) {
+ strbuf_clear(sb);
+ unescape_registry_key(buf, sb);
+
+ memset(&mii, 0, sizeof(mii));
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
+ mii.fType = MFT_STRING;
+ mii.fState = MFS_ENABLED;
+ mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
+ mii.dwTypeData = sb->s;
+ InsertMenuItem(session_menu, index_menu, true, &mii);
+ index_menu++;
+ }
+ index_key++;
+ }
+ strbuf_free(sb);
+
+ RegCloseKey(hkey);
+
+ if(index_menu == 0) {
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_TYPE | MIIM_STATE;
+ mii.fType = MFT_STRING;
+ mii.fState = MFS_GRAYED;
+ mii.dwTypeData = _T("(No sessions)");
+ InsertMenuItem(session_menu, index_menu, true, &mii);
+ }
+}
+
+/*
+ * Versions of Pageant prior to 0.61 expected this SID on incoming
+ * communications. For backwards compatibility, and more particularly
+ * for compatibility with derived works of PuTTY still using the old
+ * Pageant client code, we accept it as an alternative to the one
+ * returned from get_user_sid().
+ */
+PSID get_default_sid(void)
+{
+ HANDLE proc = NULL;
+ DWORD sidlen;
+ PSECURITY_DESCRIPTOR psd = NULL;
+ PSID sid = NULL, copy = NULL, ret = NULL;
+
+ if ((proc = OpenProcess(MAXIMUM_ALLOWED, false,
+ GetCurrentProcessId())) == NULL)
+ goto cleanup;
+
+ if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION,
+ &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)
+ goto cleanup;
+
+ sidlen = GetLengthSid(sid);
+
+ copy = (PSID)smalloc(sidlen);
+
+ if (!CopySid(sidlen, copy, sid))
+ goto cleanup;
+
+ /* Success. Move sid into the return value slot, and null it out
+ * to stop the cleanup code freeing it. */
+ ret = copy;
+ copy = NULL;
+
+ cleanup:
+ if (proc != NULL)
+ CloseHandle(proc);
+ if (psd != NULL)
+ LocalFree(psd);
+ if (copy != NULL)
+ sfree(copy);
+
+ return ret;
+}
+
+struct WmCopydataTransaction {
+ char *length, *body;
+ size_t bodysize, bodylen;
+ HANDLE ev_msg_ready, ev_reply_ready;
+} wmct;
+
+static struct PageantClient wmcpc;
+
+static void wm_copydata_got_msg(void *vctx)
+{
+ pageant_handle_msg(&wmcpc, NULL, make_ptrlen(wmct.body, wmct.bodylen));
+}
+
+static void wm_copydata_got_response(
+ PageantClient *pc, PageantClientRequestId *reqid, ptrlen response)
+{
+ if (response.len > wmct.bodysize) {
+ /* Output would overflow message buffer. Replace with a
+ * failure message. */
+ static const unsigned char failure[] = { SSH_AGENT_FAILURE };
+ response = make_ptrlen(failure, lenof(failure));
+ assert(response.len <= wmct.bodysize);
+ }
+
+ PUT_32BIT_MSB_FIRST(wmct.length, response.len);
+ memcpy(wmct.body, response.ptr, response.len);
+
+ SetEvent(wmct.ev_reply_ready);
+}
+
+static bool ask_passphrase_common(PageantClientDialogId *dlgid,
+ const char *comment)
+{
+ /* Pageant core should be serialising requests, so we never expect
+ * a passphrase prompt to exist already at this point */
+ assert(!nonmodal_passphrase_hwnd);
+
+ struct PassphraseProcStruct *pps = snew(struct PassphraseProcStruct);
+ pps->modal = false;
+ pps->help_topic = WINHELP_CTX_pageant_deferred;
+ pps->dlgid = dlgid;
+ pps->passphrase = NULL;
+ pps->comment = comment;
+
+ nonmodal_passphrase_hwnd = CreateDialogParam(
+ hinst, MAKEINTRESOURCE(IDD_ONDEMAND_PASSPHRASE),
+ NULL, PassphraseProc, (LPARAM)pps);
+
+ /*
+ * Try to put this passphrase prompt into the foreground.
+ *
+ * This will probably not succeed in giving it the actual keyboard
+ * focus, because Windows is quite opposed to applications being
+ * able to suddenly steal the focus on their own initiative.
+ *
+ * That makes sense in a lot of situations, as a defensive
+ * measure. If you were about to type a password or other secret
+ * data into the window you already had focused, and some
+ * malicious app stole the focus, it might manage to trick you
+ * into typing your secrets into _it_ instead.
+ *
+ * In this case it's possible to regard the same defensive measure
+ * as counterproductive, because the effect if we _do_ steal focus
+ * is that you type something into our passphrase prompt that
+ * isn't the passphrase, and we fail to decrypt the key, and no
+ * harm is done. Whereas the effect of the user wrongly _assuming_
+ * the new passphrase prompt has the focus is much worse: now you
+ * type your highly secret passphrase into some other window you
+ * didn't mean to trust with that information - such as the
+ * agent-forwarded PuTTY in which you just ran an ssh command,
+ * which the _whole point_ was to avoid telling your passphrase to!
+ *
+ * On the other hand, I'm sure _every_ application author can come
+ * up with an argument for why they think _they_ should be allowed
+ * to steal the focus. Probably most of them include the claim
+ * that no harm is done if their application receives data
+ * intended for something else, and of course that's not always
+ * true!
+ *
+ * In any case, I don't know of anything I can do about it, or
+ * anything I _should_ do about it if I could. If anyone thinks
+ * they can improve on all this, patches are welcome.
+ */
+ SetForegroundWindow(nonmodal_passphrase_hwnd);
+
+ return true;
+}
+
+static bool wm_copydata_ask_passphrase(
+ PageantClient *pc, PageantClientDialogId *dlgid, const char *comment)
+{
+ return ask_passphrase_common(dlgid, comment);
+}
+
+static const PageantClientVtable wmcpc_vtable = {
+ .log = NULL, /* no logging in this client */
+ .got_response = wm_copydata_got_response,
+ .ask_passphrase = wm_copydata_ask_passphrase,
+};
+
+static char *answer_filemapping_message(const char *mapname)
+{
+ HANDLE maphandle = INVALID_HANDLE_VALUE;
+ void *mapaddr = NULL;
+ char *err = NULL;
+ size_t mapsize;
+ unsigned msglen;
+
+ PSID mapsid = NULL;
+ PSID expectedsid = NULL;
+ PSID expectedsid_bc = NULL;
+ PSECURITY_DESCRIPTOR psd = NULL;
+
+ wmct.length = wmct.body = NULL;
+
+#ifdef DEBUG_IPC
+ debug("mapname = \"%s\"\n", mapname);
+#endif
+
+ maphandle = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, mapname);
+ if (maphandle == NULL || maphandle == INVALID_HANDLE_VALUE) {
+ err = dupprintf("OpenFileMapping(\"%s\"): %s",
+ mapname, win_strerror(GetLastError()));
+ goto cleanup;
+ }
+
+#ifdef DEBUG_IPC
+ debug("maphandle = %p\n", maphandle);
+#endif
+
+ if (should_have_security()) {
+ DWORD retd;
+
+ if ((expectedsid = get_user_sid()) == NULL) {
+ err = dupstr("unable to get user SID");
+ goto cleanup;
+ }
+
+ if ((expectedsid_bc = get_default_sid()) == NULL) {
+ err = dupstr("unable to get default SID");
+ goto cleanup;
+ }
+
+ if ((retd = p_GetSecurityInfo(
+ maphandle, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION,
+ &mapsid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)) {
+ err = dupprintf("unable to get owner of file mapping: "
+ "GetSecurityInfo returned: %s",
+ win_strerror(retd));
+ goto cleanup;
+ }
+
+#ifdef DEBUG_IPC
+ {
+ LPTSTR ours, ours2, theirs;
+ ConvertSidToStringSid(mapsid, &theirs);
+ ConvertSidToStringSid(expectedsid, &ours);
+ ConvertSidToStringSid(expectedsid_bc, &ours2);
+ debug("got sids:\n oursnew=%s\n oursold=%s\n"
+ " theirs=%s\n", ours, ours2, theirs);
+ LocalFree(ours);
+ LocalFree(ours2);
+ LocalFree(theirs);
+ }
+#endif
+
+ if (!EqualSid(mapsid, expectedsid) &&
+ !EqualSid(mapsid, expectedsid_bc)) {
+ err = dupstr("wrong owning SID of file mapping");
+ goto cleanup;
+ }
+ } else {
+#ifdef DEBUG_IPC
+ debug("security APIs not present\n");
+#endif
+ }
+
+ mapaddr = MapViewOfFile(maphandle, FILE_MAP_WRITE, 0, 0, 0);
+ if (!mapaddr) {
+ err = dupprintf("unable to obtain view of file mapping: %s",
+ win_strerror(GetLastError()));
+ goto cleanup;
+ }
+
+#ifdef DEBUG_IPC
+ debug("mapped address = %p\n", mapaddr);
+#endif
+
+ {
+ MEMORY_BASIC_INFORMATION mbi;
+ size_t mbiSize = VirtualQuery(mapaddr, &mbi, sizeof(mbi));
+ if (mbiSize == 0) {
+ err = dupprintf("unable to query view of file mapping: %s",
+ win_strerror(GetLastError()));
+ goto cleanup;
+ }
+ if (mbiSize < (offsetof(MEMORY_BASIC_INFORMATION, RegionSize) +
+ sizeof(mbi.RegionSize))) {
+ err = dupstr("VirtualQuery returned too little data to get "
+ "region size");
+ goto cleanup;
+ }
+
+ mapsize = mbi.RegionSize;
+ }
+#ifdef DEBUG_IPC
+ debug("region size = %"SIZEu"\n", mapsize);
+#endif
+ if (mapsize < 5) {
+ err = dupstr("mapping smaller than smallest possible request");
+ goto cleanup;
+ }
+
+ wmct.length = (char *)mapaddr;
+ msglen = GET_32BIT_MSB_FIRST(wmct.length);
+
+#ifdef DEBUG_IPC
+ debug("msg length=%08x, msg type=%02x\n",
+ msglen, (unsigned)((unsigned char *) mapaddr)[4]);
+#endif
+
+ wmct.body = wmct.length + 4;
+ wmct.bodysize = mapsize - 4;
+
+ if (msglen > wmct.bodysize) {
+ /* Incoming length field is too large. Emit a failure response
+ * without even trying to handle the request.
+ *
+ * (We know this must fit, because we checked mapsize >= 5
+ * above.) */
+ PUT_32BIT_MSB_FIRST(wmct.length, 1);
+ *wmct.body = SSH_AGENT_FAILURE;
+ } else {
+ wmct.bodylen = msglen;
+ SetEvent(wmct.ev_msg_ready);
+ WaitForSingleObject(wmct.ev_reply_ready, INFINITE);
+ }
+
+ cleanup:
+ /* expectedsid has the lifetime of the program, so we don't free it */
+ sfree(expectedsid_bc);
+ if (psd)
+ LocalFree(psd);
+ if (mapaddr)
+ UnmapViewOfFile(mapaddr);
+ if (maphandle != NULL && maphandle != INVALID_HANDLE_VALUE)
+ CloseHandle(maphandle);
+ return err;
+}
+
+static void create_keylist_window(void)
+{
+ if (keylist)
+ return;
+
+ keylist = CreateDialog(hinst, MAKEINTRESOURCE(IDD_KEYLIST),
+ NULL, KeyListProc);
+ ShowWindow(keylist, SW_SHOWNORMAL);
+}
+
+static LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT message,
+ WPARAM wParam, LPARAM lParam)
+{
+ static bool menuinprogress;
+ static UINT msgTaskbarCreated = 0;
+
+ switch (message) {
+ case WM_CREATE:
+ msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
+ break;
+ default:
+ if (message==msgTaskbarCreated) {
+ /*
+ * Explorer has been restarted, so the tray icon will
+ * have been lost.
+ */
+ AddTrayIcon(hwnd);
+ }
+ break;
+
+ case WM_SYSTRAY:
+ if (lParam == WM_RBUTTONUP) {
+ POINT cursorpos;
+ GetCursorPos(&cursorpos);
+ PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y);
+ } else if (lParam == WM_LBUTTONDBLCLK) {
+ /* Run the default menu item. */
+ UINT menuitem = GetMenuDefaultItem(systray_menu, false, 0);
+ if (menuitem != -1)
+ PostMessage(hwnd, WM_COMMAND, menuitem, 0);
+ }
+ break;
+ case WM_SYSTRAY2:
+ if (!menuinprogress) {
+ menuinprogress = true;
+ update_sessions();
+ SetForegroundWindow(hwnd);
+ TrackPopupMenu(systray_menu,
+ TPM_RIGHTALIGN | TPM_BOTTOMALIGN |
+ TPM_RIGHTBUTTON,
+ wParam, lParam, 0, hwnd, NULL);
+ menuinprogress = false;
+ }
+ break;
+ case WM_COMMAND:
+ case WM_SYSCOMMAND: {
+ unsigned command = wParam & ~0xF; /* low 4 bits reserved to Windows */
+ switch (command) {
+ case IDM_PUTTY: {
+ TCHAR cmdline[10];
+ cmdline[0] = '\0';
+ if (restrict_putty_acl)
+ strcat(cmdline, "&R");
+
+ if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, cmdline,
+ _T(""), SW_SHOW) <= 32) {
+ MessageBox(NULL, "Unable to execute PuTTY!",
+ "Error", MB_OK | MB_ICONERROR);
+ }
+ break;
+ }
+ case IDM_CLOSE:
+ if (modal_passphrase_hwnd)
+ SendMessage(modal_passphrase_hwnd, WM_CLOSE, 0, 0);
+ SendMessage(hwnd, WM_CLOSE, 0, 0);
+ break;
+ case IDM_VIEWKEYS:
+ create_keylist_window();
+ /*
+ * Sometimes the window comes up minimised / hidden for
+ * no obvious reason. Prevent this. This also brings it
+ * to the front if it's already present (the user
+ * selected View Keys because they wanted to _see_ the
+ * thing).
+ */
+ SetForegroundWindow(keylist);
+ SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
+ break;
+ case IDM_ADDKEY:
+ case IDM_ADDKEY_ENCRYPTED:
+ if (modal_passphrase_hwnd) {
+ MessageBeep(MB_ICONERROR);
+ SetForegroundWindow(modal_passphrase_hwnd);
+ break;
+ }
+ prompt_add_keyfile(command == IDM_ADDKEY_ENCRYPTED);
+ break;
+ case IDM_REMOVE_ALL:
+ pageant_delete_all();
+ keylist_update();
+ break;
+ case IDM_REENCRYPT_ALL:
+ pageant_reencrypt_all();
+ keylist_update();
+ break;
+ case IDM_ABOUT:
+ if (!aboutbox) {
+ aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUT),
+ NULL, AboutProc);
+ ShowWindow(aboutbox, SW_SHOWNORMAL);
+ /*
+ * Sometimes the window comes up minimised / hidden
+ * for no obvious reason. Prevent this.
+ */
+ SetForegroundWindow(aboutbox);
+ SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
+ }
+ break;
+ case IDM_HELP:
+ launch_help(hwnd, WINHELP_CTX_pageant_general);
+ break;
+ default: {
+ if(wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) {
+ MENUITEMINFO mii;
+ TCHAR buf[MAX_PATH + 1];
+ TCHAR param[MAX_PATH + 1];
+ memset(&mii, 0, sizeof(mii));
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_TYPE;
+ mii.cch = MAX_PATH;
+ mii.dwTypeData = buf;
+ GetMenuItemInfo(session_menu, wParam, false, &mii);
+ param[0] = '\0';
+ if (restrict_putty_acl)
+ strcat(param, "&R");
+ strcat(param, "@");
+ strcat(param, mii.dwTypeData);
+ if((INT_PTR)ShellExecute(hwnd, NULL, putty_path, param,
+ _T(""), SW_SHOW) <= 32) {
+ MessageBox(NULL, "Unable to execute PuTTY!", "Error",
+ MB_OK | MB_ICONERROR);
+ }
+ }
+ break;
+ }
+ }
+ break;
+ }
+ case WM_NETEVENT:
+ winselgui_response(wParam, lParam);
+ return 0;
+ case WM_DESTROY:
+ quit_help(hwnd);
+ PostQuitMessage(0);
+ return 0;
+ }
+
+ return DefWindowProc(hwnd, message, wParam, lParam);
+}
+
+static LRESULT CALLBACK wm_copydata_WndProc(HWND hwnd, UINT message,
+ WPARAM wParam, LPARAM lParam)
+{
+ switch (message) {
+ case WM_COPYDATA: {
+ COPYDATASTRUCT *cds;
+ char *mapname, *err;
+
+ cds = (COPYDATASTRUCT *) lParam;
+ if (cds->dwData != AGENT_COPYDATA_ID)
+ return 0; /* not our message, mate */
+ mapname = (char *) cds->lpData;
+ if (mapname[cds->cbData - 1] != '\0')
+ return 0; /* failure to be ASCIZ! */
+ err = answer_filemapping_message(mapname);
+ if (err) {
+#ifdef DEBUG_IPC
+ debug("IPC failed: %s\n", err);
+#endif
+ sfree(err);
+ return 0;
+ }
+ return 1;
+ }
+ }
+
+ return DefWindowProc(hwnd, message, wParam, lParam);
+}
+
+static DWORD WINAPI wm_copydata_threadfunc(void *param)
+{
+ HINSTANCE inst = *(HINSTANCE *)param;
+
+ HWND ipchwnd = CreateWindow(IPCCLASSNAME, IPCWINTITLE,
+ WS_OVERLAPPEDWINDOW | WS_VSCROLL,
+ CW_USEDEFAULT, CW_USEDEFAULT,
+ 100, 100, NULL, NULL, inst, NULL);
+ ShowWindow(ipchwnd, SW_HIDE);
+
+ MSG msg;
+ while (GetMessage(&msg, NULL, 0, 0) == 1) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ return 0;
+}
+
+/*
+ * Fork and Exec the command in cmdline. [DBW]
+ */
+void spawn_cmd(const char *cmdline, const char *args, int show)
+{
+ if (ShellExecute(NULL, _T("open"), cmdline,
+ args, NULL, show) <= (HINSTANCE) 32) {
+ char *msg;
+ msg = dupprintf("Failed to run \"%s\": %s", cmdline,
+ win_strerror(GetLastError()));
+ MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION);
+ sfree(msg);
+ }
+}
+
+void noise_ultralight(NoiseSourceId id, unsigned long data)
+{
+ /* Pageant doesn't use random numbers, so we ignore this */
+}
+
+void cleanup_exit(int code)
+{
+ shutdown_help();
+ exit(code);
+}
+
+static bool winpgnt_listener_ask_passphrase(
+ PageantListenerClient *plc, PageantClientDialogId *dlgid,
+ const char *comment)
+{
+ return ask_passphrase_common(dlgid, comment);
+}
+
+struct winpgnt_client {
+ PageantListenerClient plc;
+};
+static const PageantListenerClientVtable winpgnt_vtable = {
+ .log = NULL, /* no logging */
+ .ask_passphrase = winpgnt_listener_ask_passphrase,
+};
+
+static struct winpgnt_client wpc[1];
+
+HINSTANCE hinst;
+
+static NORETURN void opt_error(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ char *msg = dupvprintf(fmt, ap);
+ va_end(ap);
+
+ MessageBox(NULL, msg, "Pageant command line error", MB_ICONERROR | MB_OK);
+
+ exit(1);
+}
+
+#ifdef LEGACY_WINDOWS
+BOOL sw_PeekMessage(LPMSG msg, HWND hwnd, UINT min, UINT max, UINT remove)
+{
+ static bool unicode_unavailable = false;
+ if (!unicode_unavailable) {
+ BOOL ret = PeekMessageW(msg, hwnd, min, max, remove);
+ if (!ret && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED)
+ unicode_unavailable = true; /* don't try again */
+ else
+ return ret;
+ }
+ return PeekMessageA(msg, hwnd, min, max, remove);
+}
+#else
+#define sw_PeekMessage PeekMessageW
+#endif
+
+int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
+{
+ MSG msg;
+ const char *command = NULL;
+ const char *unixsocket = NULL;
+ bool show_keylist_on_startup = false;
+ int argc;
+ char **argv, **argstart;
+ const char *openssh_config_file = NULL;
+
+ typedef struct CommandLineKey {
+ Filename *fn;
+ bool add_encrypted;
+ } CommandLineKey;
+
+ CommandLineKey *clkeys = NULL;
+ size_t nclkeys = 0, clkeysize = 0;
+
+ dll_hijacking_protection();
+
+ hinst = inst;
+
+ if (should_have_security()) {
+ /*
+ * Attempt to get the security API we need.
+ */
+ if (!got_advapi()) {
+ MessageBox(NULL,
+ "Unable to access security APIs. Pageant will\n"
+ "not run, in case it causes a security breach.",
+ "Pageant Fatal Error", MB_ICONERROR | MB_OK);
+ return 1;
+ }
+ }
+
+ /*
+ * See if we can find our Help file.
+ */
+ init_help();
+
+ /*
+ * Look for the PuTTY binary (we will enable the saved session
+ * submenu if we find it).
+ */
+ {
+ char b[2048], *p, *q, *r;
+ FILE *fp;
+ GetModuleFileName(NULL, b, sizeof(b) - 16);
+ r = b;
+ p = strrchr(b, '\\');
+ if (p && p >= r) r = p+1;
+ q = strrchr(b, ':');
+ if (q && q >= r) r = q+1;
+ strcpy(r, "putty.exe");
+ if ( (fp = fopen(b, "r")) != NULL) {
+ putty_path = dupstr(b);
+ fclose(fp);
+ } else
+ putty_path = NULL;
+ }
+
+ /*
+ * Process the command line, handling anything that can be done
+ * immediately, but deferring adding keys until after we've
+ * started up the main agent. Details of keys to be added are
+ * stored in the 'clkeys' array.
+ */
+ split_into_argv(cmdline, &argc, &argv, &argstart);
+ bool add_keys_encrypted = false;
+ AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error);
+ while (!aux_match_done(&amo)) {
+ char *val;
+ #define match_opt(...) aux_match_opt( \
+ &amo, NULL, __VA_ARGS__, (const char *)NULL)
+ #define match_optval(...) aux_match_opt( \
+ &amo, &val, __VA_ARGS__, (const char *)NULL)
+
+ if (aux_match_arg(&amo, &val)) {
+ /*
+ * Non-option arguments are expected to be key files, and
+ * added to clkeys.
+ */
+ sgrowarray(clkeys, clkeysize, nclkeys);
+ CommandLineKey *clkey = &clkeys[nclkeys++];
+ clkey->fn = filename_from_str(val);
+ clkey->add_encrypted = add_keys_encrypted;
+ } else if (match_opt("-pgpfp")) {
+ pgp_fingerprints_msgbox(NULL);
+ return 1;
+ } else if (match_opt("-restrict-acl", "-restrict_acl",
+ "-restrictacl")) {
+ restrict_process_acl();
+ } else if (match_opt("-restrict-putty-acl", "-restrict_putty_acl")) {
+ restrict_putty_acl = true;
+ } else if (match_opt("-no-decrypt", "-no_decrypt",
+ "-nodecrypt", "-encrypted")) {
+ add_keys_encrypted = true;
+ } else if (match_opt("-keylist")) {
+ show_keylist_on_startup = true;
+ } else if (match_optval("-openssh-config", "-openssh_config")) {
+ openssh_config_file = val;
+ } else if (match_optval("-unix")) {
+ unixsocket = val;
+ } else if (match_opt("-c")) {
+ /*
+ * If we see `-c', then the rest of the command line
+ * should be treated as a command to be spawned.
+ */
+ if (amo.index < amo.argc)
+ command = argstart[amo.index];
+ else
+ command = "";
+ break;
+ } else {
+ opt_error("unrecognised option '%s'\n", amo.argv[amo.index]);
+ }
+ }
+
+ /*
+ * Create and lock an interprocess mutex while we figure out
+ * whether we're going to be the Pageant server or a client. That
+ * way, two Pageant processes started up simultaneously will be
+ * able to agree on which one becomes the server without a race
+ * condition.
+ */
+ HANDLE mutex;
+ {
+ char *err;
+ char *mutexname = agent_mutex_name();
+ mutex = lock_interprocess_mutex(mutexname, &err);
+ sfree(mutexname);
+ if (!mutex) {
+ MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK);
+ return 1;
+ }
+ }
+
+ /*
+ * Find out if Pageant is already running.
+ */
+ already_running = agent_exists();
+
+ /*
+ * If it isn't, we're going to be the primary Pageant that stays
+ * running, so set up all the machinery to answer requests.
+ */
+ if (!already_running) {
+ /*
+ * Set up the window class for the hidden window that receives
+ * all the messages to do with our presence in the system tray.
+ */
+
+ if (!prev) {
+ WNDCLASS wndclass;
+
+ memset(&wndclass, 0, sizeof(wndclass));
+ wndclass.lpfnWndProc = TrayWndProc;
+ wndclass.hInstance = inst;
+ wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
+ wndclass.lpszClassName = TRAYCLASSNAME;
+
+ RegisterClass(&wndclass);
+ }
+
+ keylist = NULL;
+
+ traywindow = CreateWindow(TRAYCLASSNAME, TRAYWINTITLE,
+ WS_OVERLAPPEDWINDOW | WS_VSCROLL,
+ CW_USEDEFAULT, CW_USEDEFAULT,
+ 100, 100, NULL, NULL, inst, NULL);
+ winselgui_set_hwnd(traywindow);
+
+ /*
+ * Initialise the cross-platform Pageant code.
+ */
+ pageant_init();
+
+ /*
+ * Set up a named-pipe listener.
+ */
+ wpc->plc.vt = &winpgnt_vtable;
+ wpc->plc.suppress_logging = true;
+ if (should_have_security()) {
+ Plug *pl_plug;
+ struct pageant_listen_state *pl =
+ pageant_listener_new(&pl_plug, &wpc->plc);
+ char *pipename = agent_named_pipe_name();
+ Socket *sock = new_named_pipe_listener(pipename, pl_plug);
+ if (sk_socket_error(sock)) {
+ char *err = dupprintf("Unable to open named pipe at %s "
+ "for SSH agent:\n%s", pipename,
+ sk_socket_error(sock));
+ MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK);
+ return 1;
+ }
+ pageant_listener_got_socket(pl, sock);
+
+ /*
+ * If we've been asked to write out an OpenSSH config file
+ * pointing at the named pipe, do so.
+ */
+ if (openssh_config_file) {
+ FILE *fp = fopen(openssh_config_file, "w");
+ if (!fp) {
+ char *err = dupprintf("Unable to write OpenSSH config "
+ "file to %s", openssh_config_file);
+ MessageBox(NULL, err, "Pageant Error",
+ MB_ICONERROR | MB_OK);
+ return 1;
+ }
+ fprintf(fp, "IdentityAgent %s\n", pipename);
+ fclose(fp);
+ }
+
+ sfree(pipename);
+ }
+
+ /*
+ * Set up an AF_UNIX listener too, if we were asked to.
+ */
+ if (unixsocket) {
+ sk_init();
+
+ /* FIXME: diagnose any error except file-not-found. Also,
+ * check the file type if possible? */
+ remove(unixsocket);
+
+ Plug *pl_plug;
+ struct pageant_listen_state *pl =
+ pageant_listener_new(&pl_plug, &wpc->plc);
+ Socket *sock = sk_newlistener_unix(unixsocket, pl_plug);
+ if (sk_socket_error(sock)) {
+ char *err = dupprintf("Unable to open AF_UNIX socket at %s "
+ "for SSH agent:\n%s", unixsocket,
+ sk_socket_error(sock));
+ MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK);
+ return 1;
+ }
+ pageant_listener_got_socket(pl, sock);
+ }
+
+ /*
+ * Set up the window class for the hidden window that receives
+ * the WM_COPYDATA message used by the old-style Pageant IPC
+ * system.
+ */
+ if (!prev) {
+ WNDCLASS wndclass;
+
+ memset(&wndclass, 0, sizeof(wndclass));
+ wndclass.lpfnWndProc = wm_copydata_WndProc;
+ wndclass.hInstance = inst;
+ wndclass.lpszClassName = IPCCLASSNAME;
+
+ RegisterClass(&wndclass);
+ }
+
+ /*
+ * And launch the subthread which will open that hidden window and
+ * handle WM_COPYDATA messages on it.
+ */
+ wmcpc.vt = &wmcpc_vtable;
+ wmcpc.suppress_logging = true;
+ pageant_register_client(&wmcpc);
+ DWORD wm_copydata_threadid;
+ wmct.ev_msg_ready = CreateEvent(NULL, false, false, NULL);
+ wmct.ev_reply_ready = CreateEvent(NULL, false, false, NULL);
+ HANDLE hThread = CreateThread(NULL, 0, wm_copydata_threadfunc,
+ &inst, 0, &wm_copydata_threadid);
+ if (hThread)
+ CloseHandle(hThread); /* we don't need the thread handle */
+ add_handle_wait(wmct.ev_msg_ready, wm_copydata_got_msg, NULL);
+ }
+
+ /*
+ * Now we're either a fully set up Pageant server, or we know one
+ * is running somewhere else. Either way, now it's safe to unlock
+ * the mutex.
+ */
+ unlock_interprocess_mutex(mutex);
+
+ /*
+ * Add any keys provided on the command line.
+ */
+ for (size_t i = 0; i < nclkeys; i++) {
+ CommandLineKey *clkey = &clkeys[i];
+ win_add_keyfile(clkey->fn, clkey->add_encrypted);
+ filename_free(clkey->fn);
+ }
+ sfree(clkeys);
+ /* And forget any passphrases we stashed during that loop. */
+ pageant_forget_passphrases();
+
+ /*
+ * Now our keys are present, spawn a command, if we were asked to.
+ */
+ if (command) {
+ char *args;
+ if (command[0] == '"')
+ args = strchr(++command, '"');
+ else
+ args = strchr(command, ' ');
+ if (args) {
+ *args++ = 0;
+ while(*args && isspace(*args)) args++;
+ }
+ spawn_cmd(command, args, show);
+ }
+
+ /*
+ * If Pageant was already running, we leave now. If we haven't
+ * even taken any auxiliary action (spawned a command or added
+ * keys), complain.
+ */
+ if (already_running) {
+ if (!command && !nclkeys) {
+ MessageBox(NULL, "Pageant is already running", "Pageant Error",
+ MB_ICONERROR | MB_OK);
+ }
+ return 0;
+ }
+
+ /* Set up a system tray icon */
+ AddTrayIcon(traywindow);
+
+ /* Accelerators used: nsvkxa */
+ systray_menu = CreatePopupMenu();
+ if (putty_path) {
+ session_menu = CreateMenu();
+ AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session");
+ AppendMenu(systray_menu, MF_POPUP | MF_ENABLED,
+ (UINT_PTR) session_menu, "&Saved Sessions");
+ AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
+ }
+ AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS,
+ "&View Keys");
+ AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key");
+ AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY_ENCRYPTED,
+ "Add key (encrypted)");
+ AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
+ AppendMenu(systray_menu, MF_ENABLED, IDM_REMOVE_ALL,
+ "Remove All Keys");
+ AppendMenu(systray_menu, MF_ENABLED, IDM_REENCRYPT_ALL,
+ "Re-encrypt All Keys");
+ AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
+ if (has_help())
+ AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help");
+ AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About");
+ AppendMenu(systray_menu, MF_SEPARATOR, 0, 0);
+ AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit");
+ initial_menuitems_count = GetMenuItemCount(session_menu);
+
+ /* Set the default menu item. */
+ SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, false);
+
+ ShowWindow(traywindow, SW_HIDE);
+
+ /* Open the visible key list window, if we've been asked to. */
+ if (show_keylist_on_startup)
+ create_keylist_window();
+
+ /*
+ * Main message loop.
+ */
+ while (true) {
+ int n;
+
+ HandleWaitList *hwl = get_handle_wait_list();
+
+ DWORD timeout = toplevel_callback_pending() ? 0 : INFINITE;
+ n = MsgWaitForMultipleObjects(hwl->nhandles, hwl->handles, false,
+ timeout, QS_ALLINPUT);
+
+ if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)hwl->nhandles)
+ handle_wait_activate(hwl, n - WAIT_OBJECT_0);
+ handle_wait_list_free(hwl);
+
+ while (sw_PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+ if (msg.message == WM_QUIT)
+ goto finished; /* two-level break */
+
+ if (IsWindow(keylist) && IsDialogMessage(keylist, &msg))
+ continue;
+ if (IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg))
+ continue;
+ if (IsWindow(nonmodal_passphrase_hwnd) &&
+ IsDialogMessage(nonmodal_passphrase_hwnd, &msg))
+ continue;
+
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ run_toplevel_callbacks();
+ }
+ finished:
+
+ /* Clean up the system tray icon */
+ {
+ NOTIFYICONDATA tnid;
+
+ tnid.cbSize = sizeof(NOTIFYICONDATA);
+ tnid.hWnd = traywindow;
+ tnid.uID = 1;
+
+ Shell_NotifyIcon(NIM_DELETE, &tnid);
+
+ DestroyMenu(systray_menu);
+ }
+
+ if (keypath) filereq_free(keypath);
+
+ if (openssh_config_file) {
+ /*
+ * Leave this file around, but empty it, so that it doesn't
+ * refer to a pipe we aren't listening on any more.
+ */
+ FILE *fp = fopen(openssh_config_file, "w");
+ if (fp)
+ fclose(fp);
+ }
+
+ cleanup_exit(msg.wParam);
+ return msg.wParam; /* just in case optimiser complains */
+}
diff --git a/windows/pageant.rc b/windows/pageant.rc
index a4a15195..1bea78b4 100644
--- a/windows/pageant.rc
+++ b/windows/pageant.rc
@@ -9,7 +9,7 @@
#include "pageant-rc.h"
-#include "winhelp.rc2"
+#include "help.rc2"
IDI_MAINICON ICON "pageant.ico"
IDI_TRAYICON ICON "pageants.ico"
@@ -51,8 +51,8 @@ STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Pageant Key List"
FONT 8, "MS Shell Dlg"
BEGIN
- LISTBOX 100, 10, 10, 420, 155,
- LBS_EXTENDEDSEL | LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL | WS_TABSTOP
+ LISTBOX IDC_KEYLIST_LISTBOX, 10, 10, 420, 155,
+ LBS_EXTENDEDSEL | LBS_OWNERDRAWFIXED | WS_VSCROLL | WS_TABSTOP
PUSHBUTTON "&Add Key", IDC_KEYLIST_ADDKEY, 10, 187, 60, 14
PUSHBUTTON "Add Key (&encrypted)", IDC_KEYLIST_ADDKEY_ENC, 75, 187, 80, 14
PUSHBUTTON "Re-e&ncrypt", IDC_KEYLIST_REENCRYPT, 315, 187, 60, 14
@@ -60,7 +60,7 @@ BEGIN
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
+ COMBOBOX IDC_KEYLIST_FPTYPE, 70, 170, 100, 12, CBS_DROPDOWNLIST
END
/* Accelerators used: cl */
diff --git a/windows/platform.h b/windows/platform.h
new file mode 100644
index 00000000..959a207c
--- /dev/null
+++ b/windows/platform.h
@@ -0,0 +1,796 @@
+/*
+ * windows/platform.h: Windows-specific inter-module stuff.
+ */
+
+#ifndef PUTTY_WINDOWS_PLATFORM_H
+#define PUTTY_WINDOWS_PLATFORM_H
+
+#include <winsock2.h>
+#include <windows.h>
+#include <stdio.h> /* for FILENAME_MAX */
+
+/* We use uintptr_t for Win32/Win64 portability, so we should in
+ * principle include stdint.h, which defines it according to the C
+ * standard. But older versions of Visual Studio don't provide
+ * stdint.h at all, but do (non-standardly) define uintptr_t in
+ * stddef.h. So here we try to make sure _some_ standard header is
+ * included which defines uintptr_t. */
+#include <stddef.h>
+#if !HAVE_NO_STDINT_H
+#include <stdint.h>
+#endif
+
+#include "defs.h"
+#include "marshal.h"
+
+#include "tree234.h"
+
+#include "help.h"
+
+#if defined _M_IX86 || defined _M_AMD64
+#define BUILDINFO_PLATFORM "x86 Windows"
+#elif defined _M_ARM || defined _M_ARM64
+#define BUILDINFO_PLATFORM "Arm Windows"
+#else
+#define BUILDINFO_PLATFORM "Windows"
+#endif
+
+#if defined __GNUC__ || defined __clang__
+#define THREADLOCAL __thread
+#elif defined _MSC_VER
+#define THREADLOCAL __declspec(thread)
+#else
+#error Do not know how to declare thread-local storage with this toolchain
+#endif
+
+/* Randomly-chosen dwData value identifying a WM_COPYDATA message as
+ * being a Pageant transaction */
+#define AGENT_COPYDATA_ID 0x804e50ba
+
+struct Filename {
+ 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
+
+#if !HAVE_STRTOUMAX
+/* Work around lack of strtoumax in older MSVC libraries */
+static inline uintmax_t strtoumax(const char *nptr, char **endptr, int base)
+{ return _strtoui64(nptr, endptr, base); }
+#endif
+
+typedef INT_PTR (*ShinyDlgProc)(HWND hwnd, UINT msg, WPARAM wParam,
+ LPARAM lParam, void *ctx);
+int ShinyDialogBox(HINSTANCE hinst, LPCTSTR tmpl, const char *winclass,
+ HWND hwndparent, ShinyDlgProc proc, void *ctx);
+void ShinyEndDialog(HWND hwnd, int ret);
+
+void centre_window(HWND hwnd);
+
+#ifndef __WINE__
+/* Up-to-date Windows headers warn that the unprefixed versions of
+ * these names are deprecated. */
+#define stricmp _stricmp
+#define strnicmp _strnicmp
+#else
+/* Compiling with winegcc, _neither_ version of these functions
+ * exists. Use the POSIX names. */
+#define stricmp strcasecmp
+#define strnicmp strncasecmp
+#endif
+
+/*
+ * Dynamically linked functions. These come in two flavours:
+ *
+ * - GET_WINDOWS_FUNCTION does not expose "name" to the preprocessor,
+ * so will always dynamically link against exactly what is specified
+ * in "name". If you're not sure, use this one.
+ *
+ * - GET_WINDOWS_FUNCTION_PP allows "name" to be redirected via
+ * preprocessor definitions like "#define foo bar"; this is principally
+ * intended for the ANSI/Unicode DoSomething/DoSomethingA/DoSomethingW.
+ * If your function has an argument of type "LPTSTR" or similar, this
+ * is the variant to use.
+ * (However, it can't always be used, as it trips over more complicated
+ * macro trickery such as the WspiapiGetAddrInfo wrapper for getaddrinfo.)
+ *
+ * (DECL_WINDOWS_FUNCTION works with both these variants.)
+ */
+#define DECL_WINDOWS_FUNCTION(linkage, rettype, name, params) \
+ typedef rettype (WINAPI *t_##name) params; \
+ linkage t_##name p_##name
+/* If you DECL_WINDOWS_FUNCTION as extern in a header file, use this to
+ * define the function pointer in a source file */
+#define DEF_WINDOWS_FUNCTION(name) t_##name p_##name
+#define GET_WINDOWS_FUNCTION_PP(module, name) \
+ TYPECHECK((t_##name)NULL == name, \
+ (p_##name = module ? \
+ (t_##name) GetProcAddress(module, STR(name)) : NULL))
+#define GET_WINDOWS_FUNCTION(module, name) \
+ TYPECHECK((t_##name)NULL == name, \
+ (p_##name = module ? \
+ (t_##name) GetProcAddress(module, #name) : NULL))
+#define GET_WINDOWS_FUNCTION_NO_TYPECHECK(module, name) \
+ (p_##name = module ? \
+ (t_##name) GetProcAddress(module, #name) : NULL)
+
+#define PUTTY_REG_POS "Software\\SimonTatham\\PuTTY"
+#define PUTTY_REG_PARENT "Software\\SimonTatham"
+#define PUTTY_REG_PARENT_CHILD "PuTTY"
+#define PUTTY_REG_GPARENT "Software"
+#define PUTTY_REG_GPARENT_CHILD "SimonTatham"
+
+/* Result values for the jumplist registry functions. */
+#define JUMPLISTREG_OK 0
+#define JUMPLISTREG_ERROR_INVALID_PARAMETER 1
+#define JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE 2
+#define JUMPLISTREG_ERROR_VALUEREAD_FAILURE 3
+#define JUMPLISTREG_ERROR_VALUEWRITE_FAILURE 4
+#define JUMPLISTREG_ERROR_INVALID_VALUE 5
+
+#define PUTTY_CHM_FILE "putty.chm"
+
+#define GETTICKCOUNT GetTickCount
+#define CURSORBLINK GetCaretBlinkTime()
+#define TICKSPERSEC 1000 /* GetTickCount returns milliseconds */
+
+#define DEFAULT_CODEPAGE CP_ACP
+#define USES_VTLINE_HACK
+
+#ifndef NO_GSSAPI
+/*
+ * GSS-API stuff
+ */
+#define GSS_CC CALLBACK
+/*
+typedef struct Ssh_gss_buf {
+ size_t length;
+ char *value;
+} Ssh_gss_buf;
+
+#define SSH_GSS_EMPTY_BUF (Ssh_gss_buf) {0,NULL}
+typedef void *Ssh_gss_name;
+*/
+#endif
+
+/*
+ * The all-important instance handle, saved from WinMain in every GUI
+ * program and exported for other GUI code to pass back to the Windows
+ * API.
+ */
+extern HINSTANCE hinst;
+
+/*
+ * Help file stuff in help.c.
+ */
+void init_help(void);
+void shutdown_help(void);
+bool has_help(void);
+void launch_help(HWND hwnd, const char *topic);
+void quit_help(HWND hwnd);
+int has_embedded_chm(void); /* 1 = yes, 0 = no, -1 = N/A */
+
+/*
+ * GUI seat methods in dialog.c, so that the vtable definition in
+ * window.c can refer to them.
+ */
+SeatPromptResult win_seat_confirm_ssh_host_key(
+ Seat *seat, const char *host, int port, const char *keytype,
+ char *keystr, SeatDialogText *text, HelpCtx helpctx,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+SeatPromptResult win_seat_confirm_weak_crypto_primitive(
+ Seat *seat, const char *algtype, const char *algname,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+SeatPromptResult win_seat_confirm_weak_cached_hostkey(
+ Seat *seat, const char *algname, const char *betteralgs,
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat);
+
+/*
+ * Windows-specific clipboard helper function shared with dialog.c,
+ * which takes the data string in the system code page instead of
+ * Unicode.
+ */
+void write_aclip(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/platform.h.
+ */
+#define MULTICLICK_ONLY_EVENT 1
+
+/*
+ * On Windows, data written to the clipboard must be NUL-terminated.
+ */
+#define SELECTION_NUL_TERMINATED 1
+
+/*
+ * On Windows, copying to the clipboard terminates lines with CRLF.
+ */
+#define SEL_NL { 13, 10 }
+
+/*
+ * sk_getxdmdata() does not exist under Windows (not that I
+ * couldn't write it if I wanted to, but I haven't bothered), so
+ * it's a macro which always returns NULL. With any luck this will
+ * cause the compiler to notice it can optimise away the
+ * implementation of XDM-AUTHORIZATION-1 in ssh/x11fwd.c :-)
+ */
+#define sk_getxdmdata(socket, lenp) (NULL)
+
+/*
+ * File-selector filter strings used in the config box. On Windows,
+ * these strings are of exactly the type needed to go in
+ * `lpstrFilter' in an OPENFILENAME structure.
+ */
+#define FILTER_KEY_FILES ("PuTTY Private Key Files (*.ppk)\0*.ppk\0" \
+ "All Files (*.*)\0*\0\0\0")
+#define FILTER_WAVE_FILES ("Wave Files (*.wav)\0*.WAV\0" \
+ "All Files (*.*)\0*\0\0\0")
+#define FILTER_DYNLIB_FILES ("Dynamic Library Files (*.dll)\0*.dll\0" \
+ "All Files (*.*)\0*\0\0\0")
+
+/*
+ * Exports from network.c.
+ */
+/* Report an event notification from WSA*Select */
+void select_result(WPARAM, LPARAM);
+/* Enumerate all currently live OS-level SOCKETs */
+SOCKET first_socket(int *);
+SOCKET next_socket(int *);
+/* Ask network.c whether we currently want to try to write to a SOCKET */
+bool socket_writable(SOCKET skt);
+/* Force a refresh of the SOCKET list by re-calling do_select for each one */
+void socket_reselect_all(void);
+/* Make a SockAddr which just holds a named pipe address. */
+SockAddr *sk_namedpipe_addr(const char *pipename);
+/* Turn a WinSock error code into a string. */
+const char *winsock_error_string(int error);
+Socket *sk_newlistener_unix(const char *socketpath, Plug *plug);
+
+/*
+ * network.c dynamically loads WinSock 2 or WinSock 1 depending on
+ * what it can get, which means any WinSock routines used outside
+ * that module must be exported from it as function pointers. So
+ * here they are.
+ */
+DECL_WINDOWS_FUNCTION(extern, int, WSAAsyncSelect,
+ (SOCKET, HWND, u_int, LONG));
+DECL_WINDOWS_FUNCTION(extern, int, WSAEventSelect,
+ (SOCKET, WSAEVENT, LONG));
+DECL_WINDOWS_FUNCTION(extern, int, WSAGetLastError, (void));
+DECL_WINDOWS_FUNCTION(extern, int, WSAEnumNetworkEvents,
+ (SOCKET, WSAEVENT, LPWSANETWORKEVENTS));
+#ifdef NEED_DECLARATION_OF_SELECT
+/* This declaration is protected by an ifdef for the sake of building
+ * against winelib, in which you have to include winsock2.h before
+ * stdlib.h so that the right fd_set type gets defined. It would be a
+ * pain to do that throughout this codebase, so instead I arrange that
+ * only a modules actually needing to use (or define, or initialise)
+ * this function pointer will see its declaration, and _those_ modules
+ * - which will be Windows-specific anyway - can take more care. */
+DECL_WINDOWS_FUNCTION(extern, int, select,
+ (int, fd_set FAR *, fd_set FAR *,
+ fd_set FAR *, const struct timeval FAR *));
+#endif
+
+/*
+ * Implemented differently depending on the client of network.c, and
+ * called by network.c to turn on or off WSA*Select for a given socket.
+ */
+const char *do_select(SOCKET skt, bool enable);
+
+/*
+ * Exports from select-{gui,cli}.c, each of which provides an
+ * implementation of do_select.
+ */
+void winselgui_set_hwnd(HWND hwnd);
+void winselgui_clear_hwnd(void);
+void winselgui_response(WPARAM wParam, LPARAM lParam);
+
+void winselcli_setup(void);
+SOCKET winselcli_unique_socket(void);
+extern HANDLE winselcli_event;
+
+/*
+ * Network-subsystem-related functions provided in other Windows modules.
+ */
+Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H,
+ SockAddr *addr, int port, Plug *plug,
+ bool overlapped); /* winhsock */
+Socket *make_deferred_handle_socket(DeferredSocketOpener *opener,
+ SockAddr *addr, int port, Plug *plug);
+void setup_handle_socket(Socket *s, HANDLE send_H, HANDLE recv_H,
+ HANDLE stderr_H, bool overlapped);
+void handle_socket_set_psb_prefix(Socket *s, const char *prefix);
+Socket *new_named_pipe_client(const char *pipename, Plug *plug); /* winnpc */
+Socket *new_named_pipe_listener(const char *pipename, Plug *plug); /* winnps */
+
+/* A lower-level function in named-pipe-client.c, which does most of
+ * the work of new_named_pipe_client (including checking the ownership
+ * of what it's connected to), but returns a plain HANDLE instead of
+ * wrapping it into a Socket. */
+HANDLE connect_to_named_pipe(const char *pipename, char **err);
+
+/*
+ * Exports from controls.c.
+ */
+
+struct ctlpos {
+ HWND hwnd;
+ WPARAM font;
+ int dlu4inpix;
+ int ypos, width;
+ int xoff;
+ int boxystart, boxid;
+ const char *boxtext;
+};
+void init_common_controls(void); /* also does some DLL-loading */
+
+/*
+ * Exports from utils.
+ */
+typedef struct filereq_tag filereq; /* cwd for file requester */
+bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save);
+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 */
+ dlgcontrol *focused, *lastfocused; /* which ctrl has focus now/before */
+ bool shortcuts[128]; /* track which shortcuts in use */
+ bool coloursel_wanted; /* has an event handler asked for
+ * a colour selector? */
+ struct {
+ unsigned char r, g, b; /* 0-255 */
+ bool ok;
+ } coloursel_result;
+ tree234 *privdata; /* stores per-control private data */
+ bool ended; /* has the dialog been ended? */
+ int endresult; /* and if so, what was the result? */
+ bool fixed_pitch_fonts; /* are we constrained to fixed fonts? */
+};
+
+/*
+ * Exports from controls.c.
+ */
+void ctlposinit(struct ctlpos *cp, HWND hwnd,
+ int leftborder, int rightborder, int topborder);
+HWND doctl(struct ctlpos *cp, RECT r, const char *wclass, int wstyle,
+ int exstyle, const char *wtext, int wid);
+void bartitle(struct ctlpos *cp, const char *name, int id);
+void beginbox(struct ctlpos *cp, const char *name, int idbox);
+void endbox(struct ctlpos *cp);
+void editboxfw(struct ctlpos *cp, bool password, bool readonly,
+ const char *text, int staticid, int editid);
+void radioline(struct ctlpos *cp, const char *text, int id, int nacross, ...);
+void bareradioline(struct ctlpos *cp, int nacross, ...);
+void radiobig(struct ctlpos *cp, const char *text, int id, ...);
+void checkbox(struct ctlpos *cp, const char *text, int id);
+void button(struct ctlpos *cp, const char *btext, int bid, bool defbtn);
+void statictext(struct ctlpos *cp, const char *text, int lines, int id);
+void staticbtn(struct ctlpos *cp, const char *stext, int sid,
+ const char *btext, int bid);
+void static2btn(struct ctlpos *cp, const char *stext, int sid,
+ const char *btext1, int bid1, const char *btext2, int bid2);
+void staticedit(struct ctlpos *cp, const char *stext,
+ int sid, int eid, int percentedit);
+void staticddl(struct ctlpos *cp, const char *stext,
+ int sid, int lid, int percentlist);
+void combobox(struct ctlpos *cp, const char *text, int staticid, int listid);
+void staticpassedit(struct ctlpos *cp, const char *stext,
+ int sid, int eid, int percentedit);
+void bigeditctrl(struct ctlpos *cp, const char *stext,
+ int sid, int eid, int lines);
+void ersatztab(struct ctlpos *cp, const char *stext, int sid, int lid,
+ int s2id);
+void editbutton(struct ctlpos *cp, const char *stext, int sid,
+ int eid, const char *btext, int bid);
+void sesssaver(struct ctlpos *cp, const char *text,
+ int staticid, int editid, int listid, ...);
+void envsetter(struct ctlpos *cp, const char *stext, int sid,
+ const char *e1stext, int e1sid, int e1id,
+ const char *e2stext, int e2sid, int e2id,
+ int listid, const char *b1text, int b1id,
+ const char *b2text, int b2id);
+void charclass(struct ctlpos *cp, const char *stext, int sid, int listid,
+ const char *btext, int bid, int eid, const char *s2text,
+ int s2id);
+void colouredit(struct ctlpos *cp, const char *stext, int sid, int listid,
+ const char *btext, int bid, ...);
+void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines,
+ const char *stext, int sid, int listid, int upbid, int dnbid);
+int handle_prefslist(struct prefslist *hdl,
+ int *array, int maxmemb,
+ bool is_dlmsg, HWND hwnd,
+ WPARAM wParam, LPARAM lParam);
+void progressbar(struct ctlpos *cp, int id);
+void fwdsetter(struct ctlpos *cp, int listid, const char *stext, int sid,
+ const char *e1stext, int e1sid, int e1id,
+ const char *e2stext, int e2sid, int e2id,
+ const char *btext, int bid,
+ const char *r1text, int r1id, const char *r2text, int r2id);
+
+void dlg_auto_set_fixed_pitch_flag(dlgparam *dlg);
+bool dlg_get_fixed_pitch_flag(dlgparam *dlg);
+void dlg_set_fixed_pitch_flag(dlgparam *dlg, bool flag);
+
+#define MAX_SHORTCUTS_PER_CTRL 16
+
+/*
+ * This structure is what's stored for each `dlgcontrol' in the
+ * portable-dialog interface.
+ */
+struct winctrl {
+ dlgcontrol *ctrl;
+ /*
+ * The control may have several components at the Windows
+ * level, with different dialog IDs. To avoid needing N
+ * separate platformsidectrl structures (which could be stored
+ * separately in a tree234 so that lookup by ID worked), we
+ * impose the constraint that those IDs must be in a contiguous
+ * block.
+ */
+ int base_id;
+ int num_ids;
+ /*
+ * For vertical alignment, the id of a particular representative
+ * control that has the y-extent of the sensible part of the
+ * control.
+ */
+ int align_id;
+ /*
+ * Remember what keyboard shortcuts were used by this control,
+ * so that when we remove it again we can take them out of the
+ * list in the dlgparam.
+ */
+ char shortcuts[MAX_SHORTCUTS_PER_CTRL];
+ /*
+ * Some controls need a piece of allocated memory in which to
+ * store temporary data about the control.
+ */
+ void *data;
+};
+/*
+ * And this structure holds a set of the above, in two separate
+ * tree234s so that it can find an item by `dlgcontrol' or by
+ * dialog ID.
+ */
+struct winctrls {
+ tree234 *byctrl, *byid;
+};
+struct controlset;
+struct controlbox;
+
+void winctrl_init(struct winctrls *);
+void winctrl_cleanup(struct winctrls *);
+void winctrl_add(struct winctrls *, struct winctrl *);
+void winctrl_remove(struct winctrls *, struct winctrl *);
+struct winctrl *winctrl_findbyctrl(struct winctrls *, dlgcontrol *);
+struct winctrl *winctrl_findbyid(struct winctrls *, int);
+struct winctrl *winctrl_findbyindex(struct winctrls *, int);
+void winctrl_layout(struct dlgparam *dp, struct winctrls *wc,
+ struct ctlpos *cp, struct controlset *s, int *id);
+bool winctrl_handle_command(struct dlgparam *dp, UINT msg,
+ WPARAM wParam, LPARAM lParam);
+void winctrl_rem_shortcuts(struct dlgparam *dp, struct winctrl *c);
+bool winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id);
+
+void dp_init(struct dlgparam *dp);
+void dp_add_tree(struct dlgparam *dp, struct winctrls *tree);
+void dp_cleanup(struct dlgparam *dp);
+
+/*
+ * Exports from config.c.
+ */
+void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help,
+ bool midsession, int protocol);
+
+/*
+ * Exports from dialog.c.
+ */
+void defuse_showwindow(void);
+bool do_config(Conf *);
+bool do_reconfig(HWND, Conf *, int);
+void showeventlog(HWND);
+void showabout(HWND);
+void force_normal(HWND hwnd);
+void modal_about_box(HWND hwnd);
+void show_help(HWND hwnd);
+HWND event_log_window(void);
+
+/*
+ * Exports from utils.
+ */
+extern DWORD osMajorVersion, osMinorVersion, osPlatformId;
+void init_winver(void);
+void dll_hijacking_protection(void);
+const char *get_system_dir(void);
+HMODULE load_system32_dll(const char *libname);
+const char *win_strerror(int error);
+bool should_have_security(void);
+void restrict_process_acl(void);
+bool restricted_acl(void);
+void escape_registry_key(const char *in, strbuf *out);
+void unescape_registry_key(const char *in, strbuf *out);
+
+bool is_console_handle(HANDLE);
+
+/* A few pieces of up-to-date Windows API definition needed for older
+ * compilers. */
+#ifndef LOAD_LIBRARY_SEARCH_SYSTEM32
+#define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800
+#endif
+#ifndef LOAD_LIBRARY_SEARCH_USER_DIRS
+#define LOAD_LIBRARY_SEARCH_USER_DIRS 0x00000400
+#endif
+#ifndef LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
+#define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR 0x00000100
+#endif
+#ifndef DLL_DIRECTORY_COOKIE
+typedef PVOID DLL_DIRECTORY_COOKIE;
+DECLSPEC_IMPORT DLL_DIRECTORY_COOKIE WINAPI AddDllDirectory (PCWSTR NewDirectory);
+#endif
+
+/*
+ * Exports from sizetip.c.
+ */
+void UpdateSizeTip(HWND src, int cx, int cy);
+void EnableSizeTip(bool bEnable);
+
+/*
+ * Exports from unicode.c.
+ */
+void init_ucs(Conf *, struct unicode_data *);
+
+/*
+ * Exports from handle-io.c.
+ */
+#define HANDLE_FLAG_OVERLAPPED 1
+#define HANDLE_FLAG_IGNOREEOF 2
+#define HANDLE_FLAG_UNITBUFFER 4
+struct handle;
+typedef size_t (*handle_inputfn_t)(
+ struct handle *h, const void *data, size_t len, int err);
+typedef void (*handle_outputfn_t)(
+ struct handle *h, size_t new_backlog, int err, bool close);
+struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata,
+ void *privdata, int flags);
+struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata,
+ void *privdata, int flags);
+size_t handle_write(struct handle *h, const void *data, size_t len);
+void handle_write_eof(struct handle *h);
+void handle_free(struct handle *h);
+void handle_unthrottle(struct handle *h, size_t backlog);
+size_t handle_backlog(struct handle *h);
+void *handle_get_privdata(struct handle *h);
+/* Analogue of stdio_sink in marshal.h, for a Windows handle */
+struct handle_sink {
+ struct handle *h;
+ BinarySink_IMPLEMENTATION;
+};
+void handle_sink_init(handle_sink *sink, struct handle *h);
+
+/*
+ * Exports from handle-wait.c.
+ */
+typedef struct HandleWait HandleWait;
+typedef void (*handle_wait_callback_fn_t)(void *);
+HandleWait *add_handle_wait(HANDLE h, handle_wait_callback_fn_t callback,
+ void *callback_ctx);
+void delete_handle_wait(HandleWait *hw);
+
+typedef struct HandleWaitList {
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS];
+ int nhandles;
+} HandleWaitList;
+HandleWaitList *get_handle_wait_list(void);
+void handle_wait_activate(HandleWaitList *hwl, int index);
+void handle_wait_list_free(HandleWaitList *hwl);
+
+/*
+ * Pageant-related pathnames.
+ */
+char *agent_mutex_name(void);
+char *agent_named_pipe_name(void);
+
+/*
+ * Exports from serial.c.
+ */
+extern const struct BackendVtable serial_backend;
+
+/*
+ * Exports from jump-list.c.
+ */
+#define JUMPLIST_SUPPORTED /* suppress #defines in putty.h */
+void add_session_to_jumplist(const char * const sessionname);
+void remove_session_from_jumplist(const char * const sessionname);
+void clear_jumplist(void);
+bool set_explicit_app_user_model_id(void);
+
+/*
+ * Exports from noise.c.
+ */
+bool win_read_random(void *buf, unsigned wanted); /* returns true on success */
+
+/*
+ * Extra functions in storage.c over and above the interface in
+ * storage.h.
+ *
+ * These functions manipulate the Registry section which mirrors the
+ * current Windows 7 jump list. (Because the real jump list storage is
+ * write-only, we need to keep another copy of whatever we put in it,
+ * so that we can put in a slightly modified version the next time.)
+ */
+
+/* Adds a saved session to the registry jump list mirror. 'item' is a
+ * string naming a saved session. */
+int add_to_jumplist_registry(const char *item);
+
+/* Removes an item from the registry jump list mirror. */
+int remove_from_jumplist_registry(const char *item);
+
+/* Returns the current jump list entries from the registry. Caller
+ * must free the returned pointer, which points to a contiguous
+ * sequence of NUL-terminated strings in memory, terminated with an
+ * empty one. */
+char *get_jumplist_registry_entries(void);
+
+/*
+ * Windows clipboard-UI wording.
+ */
+#define CLIPNAME_IMPLICIT "Last selected text"
+#define CLIPNAME_EXPLICIT "System clipboard"
+#define CLIPNAME_EXPLICIT_OBJECT "system clipboard"
+/* These defaults are the ones PuTTY has historically had */
+#define CLIPUI_DEFAULT_AUTOCOPY true
+#define CLIPUI_DEFAULT_MOUSE CLIPUI_EXPLICIT
+#define CLIPUI_DEFAULT_INS CLIPUI_EXPLICIT
+
+/* In utils */
+HKEY open_regkey_fn(bool create, HKEY base, const char *path, ...);
+#define open_regkey(create, base, ...) \
+ open_regkey_fn(create, base, __VA_ARGS__, (const char *)NULL)
+void close_regkey(HKEY key);
+void del_regkey(HKEY key, const char *name);
+char *enum_regkey(HKEY key, int index);
+bool get_reg_dword(HKEY key, const char *name, DWORD *out);
+bool put_reg_dword(HKEY key, const char *name, DWORD value);
+char *get_reg_sz(HKEY key, const char *name);
+bool put_reg_sz(HKEY key, const char *name, const char *str);
+strbuf *get_reg_multi_sz(HKEY key, const char *name);
+bool put_reg_multi_sz(HKEY key, const char *name, strbuf *str);
+
+char *get_reg_sz_simple(HKEY key, const char *name, const char *leaf);
+
+/* In cliloop.c */
+typedef bool (*cliloop_pre_t)(void *vctx, const HANDLE **extra_handles,
+ size_t *n_extra_handles);
+typedef bool (*cliloop_post_t)(void *vctx, size_t extra_handle_index);
+void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx);
+bool cliloop_null_pre(void *vctx, const HANDLE **, size_t *);
+bool cliloop_null_post(void *vctx, size_t);
+
+extern const struct BackendVtable conpty_backend;
+
+/* Functions that parametrise window.c between PuTTY and pterm */
+void gui_term_process_cmdline(Conf *conf, char *cmdline);
+const struct BackendVtable *backend_vt_from_conf(Conf *conf);
+const wchar_t *get_app_user_model_id(void);
+/* And functions in window.c that those files call back to */
+char *handle_restrict_acl_cmdline_prefix(char *cmdline);
+bool handle_special_sessionname_cmdline(char *cmdline, Conf *conf);
+bool handle_special_filemapping_cmdline(char *cmdline, Conf *conf);
+
+/* network.c: network error reporting helpers taking OS error code */
+void plug_closing_system_error(Plug *plug, DWORD error);
+void plug_closing_winsock_error(Plug *plug, DWORD error);
+
+SeatPromptResult make_spr_sw_abort_winerror(const char *prefix, DWORD error);
+
+HANDLE lock_interprocess_mutex(const char *mutexname, char **error);
+void unlock_interprocess_mutex(HANDLE mutex);
+
+typedef void (*aux_opt_error_fn_t)(const char *, ...);
+typedef struct AuxMatchOpt {
+ int index, argc;
+ char **argv;
+ bool doing_opts;
+ aux_opt_error_fn_t error;
+} AuxMatchOpt;
+AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index,
+ aux_opt_error_fn_t opt_error);
+bool aux_match_arg(AuxMatchOpt *amo, char **val);
+bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...);
+bool aux_match_done(AuxMatchOpt *amo);
+
+char *save_screenshot(HWND hwnd, const char *outfile);
+void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend);
+
+#endif /* PUTTY_WINDOWS_PLATFORM_H */
diff --git a/windows/plink.c b/windows/plink.c
new file mode 100644
index 00000000..05c25073
--- /dev/null
+++ b/windows/plink.c
@@ -0,0 +1,556 @@
+/*
+ * PLink - a Windows command-line (stdin/stdout) variant of PuTTY.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <stdarg.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "storage.h"
+#include "tree234.h"
+#include "security-api.h"
+
+void cmdline_error(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ console_print_error_msg_fmt_v("plink", fmt, ap);
+ va_end(ap);
+ exit(1);
+}
+
+static HANDLE inhandle, outhandle, errhandle;
+static struct handle *stdin_handle, *stdout_handle, *stderr_handle;
+static handle_sink stdout_hs, stderr_hs;
+static StripCtrlChars *stdout_scc, *stderr_scc;
+static BinarySink *stdout_bs, *stderr_bs;
+static DWORD orig_console_mode;
+
+static Backend *backend;
+static LogContext *logctx;
+static Conf *conf;
+
+static void plink_echoedit_update(Seat *seat, bool echo, bool edit)
+{
+ /* Update stdin read mode to reflect changes in line discipline. */
+ DWORD mode;
+
+ mode = ENABLE_PROCESSED_INPUT;
+ if (echo)
+ mode = mode | ENABLE_ECHO_INPUT;
+ else
+ mode = mode & ~ENABLE_ECHO_INPUT;
+ if (edit)
+ mode = mode | ENABLE_LINE_INPUT;
+ else
+ mode = mode & ~ENABLE_LINE_INPUT;
+ SetConsoleMode(inhandle, mode);
+}
+
+static size_t plink_output(
+ Seat *seat, SeatOutputType type, const void *data, size_t len)
+{
+ bool is_stderr = type != SEAT_OUTPUT_STDOUT;
+ BinarySink *bs = is_stderr ? stderr_bs : stdout_bs;
+ put_data(bs, data, len);
+
+ return handle_backlog(stdout_handle) + handle_backlog(stderr_handle);
+}
+
+static bool plink_eof(Seat *seat)
+{
+ handle_write_eof(stdout_handle);
+ return false; /* do not respond to incoming EOF with outgoing */
+}
+
+static SeatPromptResult plink_get_userpass_input(Seat *seat, prompts_t *p)
+{
+ /* Plink doesn't support Restart Session, so we can just have a
+ * single static cmdline_get_passwd_input_state that's never reset */
+ static cmdline_get_passwd_input_state cmdline_state =
+ CMDLINE_GET_PASSWD_INPUT_STATE_INIT;
+
+ SeatPromptResult spr;
+ spr = cmdline_get_passwd_input(p, &cmdline_state, false);
+ if (spr.kind == SPRK_INCOMPLETE)
+ spr = console_get_userpass_input(p);
+ return spr;
+}
+
+static bool plink_seat_interactive(Seat *seat)
+{
+ return (!*conf_get_str(conf, CONF_remote_cmd) &&
+ !*conf_get_str(conf, CONF_remote_cmd2) &&
+ !*conf_get_str(conf, CONF_ssh_nc_host));
+}
+
+static const SeatVtable plink_seat_vt = {
+ .output = plink_output,
+ .eof = plink_eof,
+ .sent = nullseat_sent,
+ .banner = nullseat_banner_to_stderr,
+ .get_userpass_input = plink_get_userpass_input,
+ .notify_session_started = nullseat_notify_session_started,
+ .notify_remote_exit = nullseat_notify_remote_exit,
+ .notify_remote_disconnect = nullseat_notify_remote_disconnect,
+ .connection_fatal = console_connection_fatal,
+ .update_specials_menu = nullseat_update_specials_menu,
+ .get_ttymode = nullseat_get_ttymode,
+ .set_busy_status = nullseat_set_busy_status,
+ .confirm_ssh_host_key = console_confirm_ssh_host_key,
+ .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive,
+ .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey,
+ .prompt_descriptions = console_prompt_descriptions,
+ .is_utf8 = nullseat_is_never_utf8,
+ .echoedit_update = plink_echoedit_update,
+ .get_x_display = nullseat_get_x_display,
+ .get_windowid = nullseat_get_windowid,
+ .get_window_pixel_size = nullseat_get_window_pixel_size,
+ .stripctrl_new = console_stripctrl_new,
+ .set_trust_status = console_set_trust_status,
+ .can_set_trust_status = console_can_set_trust_status,
+ .has_mixed_input_stream = console_has_mixed_input_stream,
+ .verbose = cmdline_seat_verbose,
+ .interactive = plink_seat_interactive,
+ .get_cursor_position = nullseat_get_cursor_position,
+};
+static Seat plink_seat[1] = {{ &plink_seat_vt }};
+
+static DWORD main_thread_id;
+
+/*
+ * Short description of parameters.
+ */
+static void usage(void)
+{
+ printf("Plink: command-line connection utility\n");
+ printf("%s\n", ver);
+ printf("Usage: plink [options] [user@]host [command]\n");
+ printf(" (\"host\" can also be a PuTTY saved session name)\n");
+ printf("Options:\n");
+ printf(" -V print version information and exit\n");
+ printf(" -pgpfp print PGP key fingerprints and exit\n");
+ printf(" -v show verbose messages\n");
+ printf(" -load sessname Load settings from saved session\n");
+ printf(" -ssh -telnet -rlogin -raw -serial\n");
+ printf(" force use of a particular protocol\n");
+ printf(" -ssh-connection\n");
+ printf(" force use of the bare ssh-connection protocol\n");
+ printf(" -P port connect to specified port\n");
+ printf(" -l user connect with specified username\n");
+ printf(" -batch disable all interactive prompts\n");
+ printf(" -proxycmd command\n");
+ printf(" use 'command' as local proxy\n");
+ printf(" -sercfg configuration-string (e.g. 19200,8,n,1,X)\n");
+ printf(" Specify the serial configuration (serial only)\n");
+ printf("The following options only apply to SSH connections:\n");
+ printf(" -pwfile file login with password read from specified file\n");
+ printf(" -D [listen-IP:]listen-port\n");
+ printf(" Dynamic SOCKS-based port forwarding\n");
+ printf(" -L [listen-IP:]listen-port:host:port\n");
+ printf(" Forward local port to remote address\n");
+ printf(" -R [listen-IP:]listen-port:host:port\n");
+ printf(" Forward remote port to local address\n");
+ printf(" -X -x enable / disable X11 forwarding\n");
+ printf(" -A -a enable / disable agent forwarding\n");
+ printf(" -t -T enable / disable pty allocation\n");
+ printf(" -1 -2 force use of particular SSH protocol version\n");
+ printf(" -4 -6 force use of IPv4 or IPv6\n");
+ printf(" -C enable compression\n");
+ printf(" -i key private key file for user authentication\n");
+ printf(" -noagent disable use of Pageant\n");
+ printf(" -agent enable use of Pageant\n");
+ printf(" -no-trivial-auth\n");
+ printf(" disconnect if SSH authentication succeeds trivially\n");
+ printf(" -noshare disable use of connection sharing\n");
+ printf(" -share enable use of connection sharing\n");
+ printf(" -hostkey keyid\n");
+ printf(" manually specify a host key (may be repeated)\n");
+ printf(" -sanitise-stderr, -sanitise-stdout, "
+ "-no-sanitise-stderr, -no-sanitise-stdout\n");
+ printf(" do/don't strip control chars from standard "
+ "output/error\n");
+ printf(" -no-antispoof omit anti-spoofing prompt after "
+ "authentication\n");
+ printf(" -m file read remote command(s) from file\n");
+ printf(" -s remote command is an SSH subsystem (SSH-2 only)\n");
+ printf(" -N don't start a shell/command (SSH-2 only)\n");
+ printf(" -nc host:port\n");
+ printf(" open tunnel in place of session (SSH-2 only)\n");
+ printf(" -sshlog file\n");
+ printf(" -sshrawlog file\n");
+ printf(" log protocol details to a file\n");
+ printf(" -logoverwrite\n");
+ printf(" -logappend\n");
+ printf(" control what happens when a log file already exists\n");
+ printf(" -shareexists\n");
+ printf(" test whether a connection-sharing upstream exists\n");
+ exit(1);
+}
+
+static void version(void)
+{
+ char *buildinfo_text = buildinfo("\n");
+ printf("plink: %s\n%s\n", ver, buildinfo_text);
+ sfree(buildinfo_text);
+ exit(0);
+}
+
+size_t stdin_gotdata(struct handle *h, const void *data, size_t len, int err)
+{
+ if (err) {
+ char buf[4096];
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0,
+ buf, lenof(buf), NULL);
+ buf[lenof(buf)-1] = '\0';
+ if (buf[strlen(buf)-1] == '\n')
+ buf[strlen(buf)-1] = '\0';
+ fprintf(stderr, "Unable to read from standard input: %s\n", buf);
+ cleanup_exit(0);
+ }
+
+ noise_ultralight(NOISE_SOURCE_IOLEN, len);
+ if (backend_connected(backend)) {
+ if (len > 0) {
+ backend_send(backend, data, len);
+ return backend_sendbuffer(backend);
+ } else {
+ backend_special(backend, SS_EOF, 0);
+ return 0;
+ }
+ } else
+ return 0;
+}
+
+void stdouterr_sent(struct handle *h, size_t new_backlog, int err, bool close)
+{
+ if (close) {
+ CloseHandle(outhandle);
+ CloseHandle(errhandle);
+ outhandle = errhandle = INVALID_HANDLE_VALUE;
+ }
+
+ if (err) {
+ char buf[4096];
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0,
+ buf, lenof(buf), NULL);
+ buf[lenof(buf)-1] = '\0';
+ if (buf[strlen(buf)-1] == '\n')
+ buf[strlen(buf)-1] = '\0';
+ fprintf(stderr, "Unable to write to standard %s: %s\n",
+ (h == stdout_handle ? "output" : "error"), buf);
+ cleanup_exit(0);
+ }
+
+ if (backend_connected(backend)) {
+ backend_unthrottle(backend, (handle_backlog(stdout_handle) +
+ handle_backlog(stderr_handle)));
+ }
+}
+
+const bool share_can_be_downstream = true;
+const bool share_can_be_upstream = true;
+
+const unsigned cmdline_tooltype =
+ TOOLTYPE_HOST_ARG |
+ TOOLTYPE_HOST_ARG_CAN_BE_SESSION |
+ TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX |
+ TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD;
+
+static bool sending;
+
+static bool plink_mainloop_pre(void *vctx, const HANDLE **extra_handles,
+ size_t *n_extra_handles)
+{
+ if (!sending && backend_sendok(backend)) {
+ stdin_handle = handle_input_new(inhandle, stdin_gotdata, NULL,
+ 0);
+ sending = true;
+ }
+
+ return true;
+}
+
+static bool plink_mainloop_post(void *vctx, size_t extra_handle_index)
+{
+ if (sending)
+ handle_unthrottle(stdin_handle, backend_sendbuffer(backend));
+
+ if (!backend_connected(backend) &&
+ handle_backlog(stdout_handle) + handle_backlog(stderr_handle) == 0)
+ return false; /* we closed the connection */
+
+ return true;
+}
+
+int main(int argc, char **argv)
+{
+ int exitcode;
+ bool errors;
+ bool use_subsystem = false;
+ bool just_test_share_exists = false;
+ enum TriState sanitise_stdout = AUTO, sanitise_stderr = AUTO;
+ const struct BackendVtable *vt;
+
+ dll_hijacking_protection();
+
+ /*
+ * Initialise port and protocol to sensible defaults. (These
+ * will be overridden by more or less anything.)
+ */
+ settings_set_default_protocol(PROT_SSH);
+ settings_set_default_port(22);
+
+ /*
+ * Process the command line.
+ */
+ conf = conf_new();
+ do_defaults(NULL, conf);
+ settings_set_default_protocol(conf_get_int(conf, CONF_protocol));
+ settings_set_default_port(conf_get_int(conf, CONF_port));
+ errors = false;
+ {
+ /*
+ * Override the default protocol if PLINK_PROTOCOL is set.
+ */
+ char *p = getenv("PLINK_PROTOCOL");
+ if (p) {
+ const struct BackendVtable *vt = backend_vt_from_name(p);
+ if (vt) {
+ settings_set_default_protocol(vt->protocol);
+ settings_set_default_port(vt->default_port);
+ conf_set_int(conf, CONF_protocol, vt->protocol);
+ conf_set_int(conf, CONF_port, vt->default_port);
+ }
+ }
+ }
+ while (--argc) {
+ char *p = *++argv;
+ int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
+ 1, conf);
+ if (ret == -2) {
+ fprintf(stderr,
+ "plink: option \"%s\" requires an argument\n", p);
+ errors = true;
+ } else if (ret == 2) {
+ --argc, ++argv;
+ } else if (ret == 1) {
+ continue;
+ } else if (!strcmp(p, "-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_dataz(cmdbuf, p);
+ if (--argc > 0)
+ p = *++argv;
+ }
+
+ conf_set_str(conf, CONF_remote_cmd, cmdbuf->s);
+ conf_set_str(conf, CONF_remote_cmd2, "");
+ conf_set_bool(conf, CONF_nopty, true); /* command => no tty */
+
+ strbuf_free(cmdbuf);
+ break; /* done with cmdline */
+ } else {
+ fprintf(stderr, "plink: unknown option \"%s\"\n", p);
+ errors = true;
+ }
+ }
+
+ if (errors)
+ return 1;
+
+ if (!cmdline_host_ok(conf)) {
+ usage();
+ }
+
+ prepare_session(conf);
+
+ /*
+ * Perform command-line overrides on session configuration.
+ */
+ cmdline_run_saved(conf);
+
+ /*
+ * Apply subsystem status.
+ */
+ if (use_subsystem)
+ conf_set_bool(conf, CONF_ssh_subsys, true);
+
+ /*
+ * Select protocol. This is farmed out into a table in a
+ * separate file to enable an ssh-free variant.
+ */
+ vt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol));
+ if (vt == NULL) {
+ fprintf(stderr,
+ "Internal fault: Unsupported protocol found\n");
+ return 1;
+ }
+
+ if (vt->flags & BACKEND_NEEDS_TERMINAL) {
+ fprintf(stderr,
+ "Plink doesn't support %s, which needs terminal emulation\n",
+ vt->displayname_lc);
+ return 1;
+ }
+
+ sk_init();
+ if (p_WSAEventSelect == NULL) {
+ fprintf(stderr, "Plink requires WinSock 2\n");
+ return 1;
+ }
+
+ /*
+ * Plink doesn't provide any way to add forwardings after the
+ * connection is set up, so if there are none now, we can safely set
+ * the "simple" flag.
+ */
+ if (conf_get_int(conf, CONF_protocol) == PROT_SSH &&
+ !conf_get_bool(conf, CONF_x11_forward) &&
+ !conf_get_bool(conf, CONF_agentfwd) &&
+ !conf_get_str_nthstrkey(conf, CONF_portfwd, 0))
+ conf_set_bool(conf, CONF_ssh_simple, true);
+
+ logctx = log_init(console_cli_logpolicy, conf);
+
+ if (just_test_share_exists) {
+ if (!vt->test_for_upstream) {
+ fprintf(stderr, "Connection sharing not supported for this "
+ "connection type (%s)'\n", vt->displayname_lc);
+ return 1;
+ }
+ if (vt->test_for_upstream(conf_get_str(conf, CONF_host),
+ conf_get_int(conf, CONF_port), conf))
+ return 0;
+ else
+ return 1;
+ }
+
+ if (restricted_acl()) {
+ lp_eventlog(console_cli_logpolicy,
+ "Running with restricted process ACL");
+ }
+
+ inhandle = GetStdHandle(STD_INPUT_HANDLE);
+ outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
+ errhandle = GetStdHandle(STD_ERROR_HANDLE);
+
+ /*
+ * Turn off ECHO and LINE input modes. We don't care if this
+ * call fails, because we know we aren't necessarily running in
+ * a console.
+ */
+ GetConsoleMode(inhandle, &orig_console_mode);
+ SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
+
+ /*
+ * Pass the output handles to the handle-handling subsystem.
+ * (The input one we leave until we're through the
+ * authentication process.)
+ */
+ stdout_handle = handle_output_new(outhandle, stdouterr_sent, NULL, 0);
+ stderr_handle = handle_output_new(errhandle, stdouterr_sent, NULL, 0);
+ handle_sink_init(&stdout_hs, stdout_handle);
+ handle_sink_init(&stderr_hs, stderr_handle);
+ stdout_bs = BinarySink_UPCAST(&stdout_hs);
+ stderr_bs = BinarySink_UPCAST(&stderr_hs);
+
+ /*
+ * Decide whether to sanitise control sequences out of standard
+ * output and standard error.
+ *
+ * If we weren't given a command-line override, we do this if (a)
+ * the fd in question is pointing at a console, and (b) we aren't
+ * trying to allocate a terminal as part of the session.
+ *
+ * (Rationale: the risk of control sequences is that they cause
+ * confusion when sent to a local console, so if there isn't one,
+ * no problem. Also, if we allocate a remote terminal, then we
+ * sent a terminal type, i.e. we told it what kind of escape
+ * sequences we _like_, i.e. we were expecting to receive some.)
+ */
+ if (sanitise_stdout == FORCE_ON ||
+ (sanitise_stdout == AUTO && is_console_handle(outhandle) &&
+ conf_get_bool(conf, CONF_nopty))) {
+ stdout_scc = stripctrl_new(stdout_bs, true, L'\0');
+ stdout_bs = BinarySink_UPCAST(stdout_scc);
+ }
+ if (sanitise_stderr == FORCE_ON ||
+ (sanitise_stderr == AUTO && is_console_handle(errhandle) &&
+ conf_get_bool(conf, CONF_nopty))) {
+ stderr_scc = stripctrl_new(stderr_bs, true, L'\0');
+ stderr_bs = BinarySink_UPCAST(stderr_scc);
+ }
+
+ /*
+ * Start up the connection.
+ */
+ winselcli_setup(); /* ensure event object exists */
+ {
+ char *error, *realhost;
+ /* nodelay is only useful if stdin is a character device (console) */
+ bool nodelay = conf_get_bool(conf, CONF_tcp_nodelay) &&
+ (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);
+
+ error = backend_init(vt, plink_seat, &backend, logctx, conf,
+ conf_get_str(conf, CONF_host),
+ conf_get_int(conf, CONF_port),
+ &realhost, nodelay,
+ conf_get_bool(conf, CONF_tcp_keepalives));
+ if (error) {
+ fprintf(stderr, "Unable to open connection:\n%s", error);
+ sfree(error);
+ return 1;
+ }
+ ldisc_create(conf, NULL, backend, plink_seat);
+ sfree(realhost);
+ }
+
+ main_thread_id = GetCurrentThreadId();
+
+ sending = false;
+
+ cli_main_loop(plink_mainloop_pre, plink_mainloop_post, NULL);
+
+ exitcode = backend_exitcode(backend);
+ if (exitcode < 0) {
+ fprintf(stderr, "Remote process exit code unavailable\n");
+ exitcode = 1; /* this is an error condition */
+ }
+ cleanup_exit(exitcode);
+ return 0; /* placate compiler warning */
+}
diff --git a/windows/printing.c b/windows/printing.c
new file mode 100644
index 00000000..2286c236
--- /dev/null
+++ b/windows/printing.c
@@ -0,0 +1,228 @@
+/*
+ * Printing interface for PuTTY.
+ */
+
+#include "putty.h"
+#include <winspool.h>
+
+struct printer_enum_tag {
+ int nprinters;
+ DWORD enum_level;
+ union {
+ LPPRINTER_INFO_4 i4;
+ LPPRINTER_INFO_5 i5;
+ } info;
+};
+
+struct printer_job_tag {
+ HANDLE hprinter;
+};
+
+DECL_WINDOWS_FUNCTION(static, BOOL, EnumPrinters,
+ (DWORD, LPTSTR, DWORD, LPBYTE, DWORD, LPDWORD, LPDWORD));
+DECL_WINDOWS_FUNCTION(static, BOOL, OpenPrinter,
+ (LPTSTR, LPHANDLE, LPPRINTER_DEFAULTS));
+DECL_WINDOWS_FUNCTION(static, BOOL, ClosePrinter, (HANDLE));
+DECL_WINDOWS_FUNCTION(static, DWORD, StartDocPrinter, (HANDLE, DWORD, LPBYTE));
+DECL_WINDOWS_FUNCTION(static, BOOL, EndDocPrinter, (HANDLE));
+DECL_WINDOWS_FUNCTION(static, BOOL, StartPagePrinter, (HANDLE));
+DECL_WINDOWS_FUNCTION(static, BOOL, EndPagePrinter, (HANDLE));
+DECL_WINDOWS_FUNCTION(static, BOOL, WritePrinter,
+ (HANDLE, LPVOID, DWORD, LPDWORD));
+
+static void init_winfuncs(void)
+{
+ static bool initialised = false;
+ if (initialised)
+ return;
+ {
+ HMODULE winspool_module = load_system32_dll("winspool.drv");
+ /* Some MSDN documentation claims that some of the below functions
+ * should be loaded from spoolss.dll, but this doesn't seem to
+ * be reliable in practice.
+ * Nevertheless, we load spoolss.dll ourselves using our safe
+ * loading method, against the possibility that winspool.drv
+ * later loads it unsafely. */
+ (void) load_system32_dll("spoolss.dll");
+ GET_WINDOWS_FUNCTION_PP(winspool_module, EnumPrinters);
+ GET_WINDOWS_FUNCTION_PP(winspool_module, OpenPrinter);
+ GET_WINDOWS_FUNCTION_PP(winspool_module, ClosePrinter);
+ GET_WINDOWS_FUNCTION_PP(winspool_module, StartDocPrinter);
+ GET_WINDOWS_FUNCTION_PP(winspool_module, EndDocPrinter);
+ GET_WINDOWS_FUNCTION_PP(winspool_module, StartPagePrinter);
+ GET_WINDOWS_FUNCTION_PP(winspool_module, EndPagePrinter);
+ GET_WINDOWS_FUNCTION_PP(winspool_module, WritePrinter);
+ }
+ initialised = true;
+}
+
+static bool printer_add_enum(int param, DWORD level, char **buffer,
+ int offset, int *nprinters_ptr)
+{
+ DWORD needed = 0, nprinters = 0;
+
+ init_winfuncs();
+
+ *buffer = sresize(*buffer, offset+512, char);
+
+ /*
+ * Exploratory call to EnumPrinters to determine how much space
+ * we'll need for the output.
+ *
+ * If we get ERROR_INSUFFICIENT_BUFFER, that's fine, we're
+ * prepared to deal with it. Any other error, we return failure.
+ */
+ if (p_EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset), 512,
+ &needed, &nprinters) == 0 &&
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ return false;
+
+ if (needed < 512)
+ needed = 512;
+
+ *buffer = sresize(*buffer, offset+needed, char);
+
+ if (p_EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset),
+ needed, &needed, &nprinters) == 0)
+ return false;
+
+ *nprinters_ptr += nprinters;
+
+ return true;
+}
+
+printer_enum *printer_start_enum(int *nprinters_ptr)
+{
+ printer_enum *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/WINDOWS/winsocks.c b/windows/psocks.c
index 83ba364c..83ba364c 100644
--- a/WINDOWS/winsocks.c
+++ b/windows/psocks.c
diff --git a/windows/pterm.c b/windows/pterm.c
new file mode 100644
index 00000000..bb68245d
--- /dev/null
+++ b/windows/pterm.c
@@ -0,0 +1,65 @@
+#include "putty.h"
+#include "storage.h"
+
+const unsigned cmdline_tooltype =
+ TOOLTYPE_NONNETWORK |
+ TOOLTYPE_NO_VERBOSE_OPTION;
+
+void gui_term_process_cmdline(Conf *conf, char *cmdline)
+{
+ do_defaults(NULL, conf);
+ conf_set_str(conf, CONF_remote_cmd, "");
+
+ cmdline = handle_restrict_acl_cmdline_prefix(cmdline);
+ if (handle_special_sessionname_cmdline(cmdline, conf) ||
+ handle_special_filemapping_cmdline(cmdline, conf))
+ return;
+
+ int argc;
+ char **argv, **argstart;
+ split_into_argv(cmdline, &argc, &argv, &argstart);
+
+ for (int i = 0; i < argc; i++) {
+ char *arg = argv[i];
+ int retd = cmdline_process_param(
+ arg, i+1<argc?argv[i+1]:NULL, 1, conf);
+ if (retd == -2) {
+ cmdline_error("option \"%s\" requires an argument", arg);
+ } else if (retd == 2) {
+ i++; /* skip next argument */
+ } else if (retd == 1) {
+ continue; /* nothing further needs doing */
+ } else if (!strcmp(arg, "-e")) {
+ if (i+1 < argc) {
+ /* The command to execute is taken to be the unparsed
+ * version of the whole remainder of the command line. */
+ conf_set_str(conf, CONF_remote_cmd, argstart[i+1]);
+ return;
+ } else {
+ cmdline_error("option \"%s\" requires an argument", arg);
+ }
+ } else if (arg[0] == '-') {
+ cmdline_error("unrecognised option \"%s\"", arg);
+ } else {
+ cmdline_error("unexpected non-option argument \"%s\"", arg);
+ }
+ }
+
+ cmdline_run_saved(conf);
+
+ conf_set_int(conf, CONF_sharrow_type, SHARROW_BITMAP);
+}
+
+const struct BackendVtable *backend_vt_from_conf(Conf *conf)
+{
+ return &conpty_backend;
+}
+
+const wchar_t *get_app_user_model_id(void)
+{
+ return L"SimonTatham.Pterm";
+}
+
+void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend)
+{
+}
diff --git a/windows/pterm.ico b/windows/pterm.ico
new file mode 100644
index 00000000..6909a8d2
--- /dev/null
+++ b/windows/pterm.ico
Binary files differ
diff --git a/windows/pterm.rc b/windows/pterm.rc
new file mode 100644
index 00000000..8bd3a043
--- /dev/null
+++ b/windows/pterm.rc
@@ -0,0 +1,15 @@
+#include "rcstuff.h"
+#include "putty-rc.h"
+
+#define APPNAME "pterm"
+#define APPDESC "PuTTY-style wrapper for Windows command prompts"
+
+IDI_MAINICON ICON "pterm.ico"
+IDI_CFGICON ICON "ptermcfg.ico"
+
+#include "help.rc2"
+#include "putty-common.rc2"
+
+#ifndef NO_MANIFESTS
+1 RT_MANIFEST "putty.mft"
+#endif /* NO_MANIFESTS */
diff --git a/windows/ptermcfg.ico b/windows/ptermcfg.ico
new file mode 100644
index 00000000..53bde87e
--- /dev/null
+++ b/windows/ptermcfg.ico
Binary files differ
diff --git a/windows/putty-common.rc2 b/windows/putty-common.rc2
new file mode 100644
index 00000000..a43e3ac1
--- /dev/null
+++ b/windows/putty-common.rc2
@@ -0,0 +1,98 @@
+/*
+ * Windows resources shared between PuTTY and PuTTYtel, to be #include'd
+ * after defining appropriate macros.
+ *
+ * Note that many of these strings mention PuTTY. Due to restrictions in
+ * VC's handling of string concatenation, this can't easily be fixed.
+ * It's fixed up at runtime.
+ *
+ * This file has the more or less arbitrary extension '.rc2' to avoid
+ * IDEs taking it to be a top-level resource script in its own right
+ * (which has been known to happen if the extension was '.rc'), and
+ * also to avoid the resource compiler ignoring everything included
+ * from it (which happens if the extension is '.h').
+ */
+
+/* Accelerators used: clw */
+IDD_ABOUTBOX DIALOG DISCARDABLE 140, 40, 270, 136
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "About PuTTY"
+FONT 8, "MS Shell Dlg"
+BEGIN
+ DEFPUSHBUTTON "&Close", IDOK, 216, 118, 48, 14
+ PUSHBUTTON "View &Licence", IDA_LICENCE, 6, 118, 70, 14
+ PUSHBUTTON "Visit &Web Site", IDA_WEB, 140, 118, 70, 14
+ EDITTEXT IDA_TEXT, 10, 6, 250, 110, ES_READONLY | ES_MULTILINE | ES_CENTER, WS_EX_STATICEDGE
+END
+
+/* Accelerators used: aco */
+IDD_MAINBOX DIALOG DISCARDABLE 0, 0, 300, 252
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "PuTTY Configuration"
+FONT 8, "MS Shell Dlg"
+CLASS "PuTTYConfigBox"
+BEGIN
+END
+
+/* Accelerators used: co */
+IDD_LOGBOX DIALOG DISCARDABLE 100, 20, 300, 119
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "PuTTY Event Log"
+FONT 8, "MS Shell Dlg"
+BEGIN
+ DEFPUSHBUTTON "&Close", IDOK, 135, 102, 44, 14
+ PUSHBUTTON "C&opy", IDN_COPY, 81, 102, 44, 14
+ LISTBOX IDN_LIST, 3, 3, 294, 95, LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL | LBS_EXTENDEDSEL
+END
+
+/* No accelerators used */
+IDD_LICENCEBOX DIALOG DISCARDABLE 50, 50, 326, 239
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "PuTTY Licence"
+FONT 8, "MS Shell Dlg"
+BEGIN
+ DEFPUSHBUTTON "OK", IDOK, 148, 219, 44, 14
+
+ EDITTEXT IDA_TEXT, 10, 10, 306, 200, ES_READONLY | ES_MULTILINE | ES_LEFT, WS_EX_STATICEDGE
+END
+
+/* Accelerators used: achio */
+IDD_HOSTKEY DIALOG DISCARDABLE 50, 50, 340, 240
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "PuTTY Security Alert"
+FONT 8, "MS Shell Dlg"
+CLASS "PuTTYHostKeyDialog"
+BEGIN
+ ICON "", IDC_HK_ICON, 10, 18, 0, 0
+
+ PUSHBUTTON "&Cancel", IDCANCEL, 288, 220, 40, 14
+ PUSHBUTTON "&Accept", IDC_HK_ACCEPT, 168, 220, 40, 14
+ PUSHBUTTON "Connect &Once", IDC_HK_ONCE, 216, 220, 64, 14
+ PUSHBUTTON "More &info...", IDC_HK_MOREINFO, 60, 220, 64, 14
+ PUSHBUTTON "&Help", IDHELP, 12, 220, 40, 14
+
+ LTEXT "", IDC_HK_TITLE, 40, 20, 300, 12
+
+ EDITTEXT IDC_HK_TEXT, 40, 20, 290, 200, ES_READONLY | ES_MULTILINE | ES_LEFT, WS_EX_STATICEDGE
+END
+
+/* Accelerators used: c */
+IDD_HK_MOREINFO DIALOG DISCARDABLE 140, 40, 400, 300
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "PuTTY: information about the server's host key"
+FONT 8, "MS Shell Dlg"
+CLASS "PuTTYHostKeyMoreInfo"
+BEGIN
+ DEFPUSHBUTTON "&Close", IDOK, 176, 130, 48, 14
+END
+
+/* Accelerators used: aco */
+IDD_CA_CONFIG DIALOG DISCARDABLE 0, 0, 350, 260
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "PuTTY trusted host certification authorities"
+FONT 8, "MS Shell Dlg"
+CLASS "PuTTYConfigBox"
+BEGIN
+END
+
+#include "version.rc2"
diff --git a/windows/putty-rc.h b/windows/putty-rc.h
new file mode 100644
index 00000000..35d9bcda
--- /dev/null
+++ b/windows/putty-rc.h
@@ -0,0 +1,51 @@
+/*
+ * putty-rc.h - constants shared between putty-common.rc2 and the C code.
+ */
+
+#ifndef PUTTY_WIN_RES_H
+#define PUTTY_WIN_RES_H
+
+#define IDI_MAINICON 200
+#define IDI_CFGICON 201
+
+#define IDD_MAINBOX 102
+#define IDD_LOGBOX 110
+#define IDD_ABOUTBOX 111
+#define IDD_RECONF 112
+#define IDD_LICENCEBOX 113
+#define IDD_HOSTKEY 114
+#define IDD_HK_MOREINFO 116
+#define IDD_CA_CONFIG 117
+
+#define IDN_LIST 1001
+#define IDN_COPY 1002
+
+#define IDA_ICON 1001
+#define IDA_TEXT 1002
+#define IDA_LICENCE 1003
+#define IDA_WEB 1004
+
+#define IDC_TAB 1001
+#define IDC_TABSTATIC1 1002
+#define IDC_TABSTATIC2 1003
+#define IDC_TABLIST 1004
+#define IDC_HELPBTN 1005
+#define IDC_ABOUT 1006
+
+#define IDC_HK_ICON 98
+#define IDC_HK_TITLE 99
+#define IDC_HK_TEXT 100
+#define IDC_HK_ACCEPT 1001
+#define IDC_HK_ONCE 1000
+#define IDC_HK_HOST 1002
+#define IDC_HK_FINGERPRINT 1003
+#define IDC_HK_MOREINFO 1004
+
+#define IDC_HKI_SHA256 1000
+#define IDC_HKI_MD5 1001
+#define IDC_HKI_PUBKEY 1002
+
+#define ID_CUSTOM_CHMFILE 2000
+#define TYPE_CUSTOM_CHMFILE 2000
+
+#endif
diff --git a/windows/putty.c b/windows/putty.c
new file mode 100644
index 00000000..3c41b557
--- /dev/null
+++ b/windows/putty.c
@@ -0,0 +1,203 @@
+#include "putty.h"
+#include "storage.h"
+
+extern bool sesslist_demo_mode;
+extern const char *dialog_box_demo_screenshot_filename;
+static strbuf *demo_terminal_data = NULL;
+static const char *terminal_demo_screenshot_filename;
+
+const unsigned cmdline_tooltype =
+ TOOLTYPE_HOST_ARG |
+ TOOLTYPE_PORT_ARG |
+ TOOLTYPE_NO_VERBOSE_OPTION;
+
+void gui_term_process_cmdline(Conf *conf, char *cmdline)
+{
+ char *p;
+ bool special_launchable_argument = false;
+ bool demo_config_box = false;
+
+ settings_set_default_protocol(be_default_protocol);
+ /* Find the appropriate default port. */
+ {
+ const struct BackendVtable *vt =
+ backend_vt_from_proto(be_default_protocol);
+ settings_set_default_port(0); /* illegal */
+ if (vt)
+ settings_set_default_port(vt->default_port);
+ }
+ conf_set_int(conf, CONF_logtype, LGTYP_NONE);
+
+ do_defaults(NULL, conf);
+
+ p = handle_restrict_acl_cmdline_prefix(cmdline);
+
+ if (handle_special_sessionname_cmdline(p, conf)) {
+ if (!conf_launchable(conf) && !do_config(conf)) {
+ cleanup_exit(0);
+ }
+ special_launchable_argument = true;
+ } else if (handle_special_filemapping_cmdline(p, conf)) {
+ special_launchable_argument = true;
+ } else if (!*p) {
+ /* Do-nothing case for an empty command line - or rather,
+ * for a command line that's empty _after_ we strip off
+ * the &R prefix. */
+ } else {
+ /*
+ * Otherwise, break up the command line and deal with
+ * it sensibly.
+ */
+ int argc, i;
+ char **argv;
+
+ split_into_argv(cmdline, &argc, &argv, NULL);
+
+ for (i = 0; i < argc; i++) {
+ char *p = argv[i];
+ int ret;
+
+ ret = cmdline_process_param(p, i+1<argc?argv[i+1]:NULL,
+ 1, conf);
+ if (ret == -2) {
+ cmdline_error("option \"%s\" requires an argument", p);
+ } else if (ret == 2) {
+ i++; /* skip next argument */
+ } else if (ret == 1) {
+ continue; /* nothing further needs doing */
+ } 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 (has_ca_config_box &&
+ (!strcmp(p, "-host-ca") || !strcmp(p, "--host-ca") ||
+ !strcmp(p, "-host_ca") || !strcmp(p, "--host_ca"))) {
+ show_ca_config_box(NULL);
+ exit(0);
+ } else if (!strcmp(p, "-demo-config-box")) {
+ if (i+1 >= argc) {
+ cmdline_error("%s expects an output filename", p);
+ } else {
+ demo_config_box = true;
+ dialog_box_demo_screenshot_filename = argv[++i];
+ }
+ } else if (!strcmp(p, "-demo-terminal")) {
+ if (i+2 >= argc) {
+ cmdline_error("%s expects input and output filenames", p);
+ } else {
+ const char *infile = argv[++i];
+ terminal_demo_screenshot_filename = argv[++i];
+ FILE *fp = fopen(infile, "rb");
+ if (!fp)
+ cmdline_error("can't open input file '%s'", infile);
+ demo_terminal_data = strbuf_new();
+ char buf[4096];
+ int retd;
+ while ((retd = fread(buf, 1, sizeof(buf), fp)) > 0)
+ put_data(demo_terminal_data, buf, retd);
+ fclose(fp);
+ }
+ } else if (*p != '-') {
+ cmdline_error("unexpected argument \"%s\"", p);
+ } else {
+ cmdline_error("unknown option \"%s\"", p);
+ }
+ }
+ }
+
+ cmdline_run_saved(conf);
+
+ if (demo_config_box) {
+ sesslist_demo_mode = true;
+ load_open_settings(NULL, conf);
+ conf_set_str(conf, CONF_host, "demo-server.example.com");
+ do_config(conf);
+ cleanup_exit(0);
+ } else if (demo_terminal_data) {
+ /* Ensure conf will cause an immediate session launch */
+ load_open_settings(NULL, conf);
+ conf_set_str(conf, CONF_host, "demo-server.example.com");
+ conf_set_int(conf, CONF_close_on_exit, FORCE_OFF);
+ } else {
+ /*
+ * Bring up the config dialog if the command line hasn't
+ * (explicitly) specified a launchable configuration.
+ */
+ if (!(special_launchable_argument || cmdline_host_ok(conf))) {
+ if (!do_config(conf))
+ cleanup_exit(0);
+ }
+ }
+
+ prepare_session(conf);
+}
+
+const struct BackendVtable *backend_vt_from_conf(Conf *conf)
+{
+ if (demo_terminal_data) {
+ return &null_backend;
+ }
+
+ /*
+ * Select protocol. This is farmed out into a table in a
+ * separate file to enable an ssh-free variant.
+ */
+ const struct BackendVtable *vt = backend_vt_from_proto(
+ conf_get_int(conf, CONF_protocol));
+ if (!vt) {
+ char *str = dupprintf("%s Internal Error", appname);
+ MessageBox(NULL, "Unsupported protocol number found",
+ str, MB_OK | MB_ICONEXCLAMATION);
+ sfree(str);
+ cleanup_exit(1);
+ }
+ return vt;
+}
+
+const wchar_t *get_app_user_model_id(void)
+{
+ return L"SimonTatham.PuTTY";
+}
+
+static void demo_terminal_screenshot(void *ctx, unsigned long now)
+{
+ HWND hwnd = (HWND)ctx;
+ char *err = save_screenshot(hwnd, terminal_demo_screenshot_filename);
+ if (err) {
+ MessageBox(hwnd, err, "Demo screenshot failure", MB_OK | MB_ICONERROR);
+ sfree(err);
+ }
+ cleanup_exit(0);
+}
+
+void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend)
+{
+ if (demo_terminal_data) {
+ ptrlen data = ptrlen_from_strbuf(demo_terminal_data);
+ seat_stdout(seat, data.ptr, data.len);
+ schedule_timer(TICKSPERSEC, demo_terminal_screenshot, (void *)hwnd);
+ }
+}
diff --git a/windows/putty.rc b/windows/putty.rc
index 14f62f48..b8df49f2 100644
--- a/windows/putty.rc
+++ b/windows/putty.rc
@@ -1,10 +1,14 @@
#include "rcstuff.h"
+#include "putty-rc.h"
#define APPNAME "PuTTYNG"
#define APPDESC "SSH, Telnet, Rlogin, and SUPDUP client"
-#include "winhelp.rc2"
-#include "win_res.rc2"
+IDI_MAINICON ICON "putty.ico"
+IDI_CFGICON ICON "puttycfg.ico"
+
+#include "help.rc2"
+#include "putty-common.rc2"
#ifndef NO_MANIFESTS
1 RT_MANIFEST "putty.mft"
diff --git a/windows/puttygen.c b/windows/puttygen.c
new file mode 100644
index 00000000..457dbfa1
--- /dev/null
+++ b/windows/puttygen.c
@@ -0,0 +1,2612 @@
+/*
+ * PuTTY key generation front end (Windows).
+ */
+
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sshkeygen.h"
+#include "licence.h"
+#include "security-api.h"
+#include "puttygen-rc.h"
+
+#include <commctrl.h>
+
+#ifdef MSVC4
+#define ICON_BIG 1
+#endif
+
+#define WM_DONEKEY (WM_APP + 1)
+
+#define DEFAULT_KEY_BITS 2048
+#define DEFAULT_ECCURVE_INDEX 0
+#define DEFAULT_EDCURVE_INDEX 0
+
+static char *cmdline_keyfile = NULL;
+static ptrlen cmdline_demo_keystr;
+static const char *demo_screenshot_filename = NULL;
+
+/*
+ * Print a modal (Really Bad) message box and perform a fatal exit.
+ */
+void modalfatalbox(const char *fmt, ...)
+{
+ va_list ap;
+ char *stuff;
+
+ va_start(ap, fmt);
+ stuff = dupvprintf(fmt, ap);
+ va_end(ap);
+ MessageBox(NULL, stuff, "PuTTYgen Fatal Error",
+ MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
+ sfree(stuff);
+ exit(1);
+}
+
+/*
+ * Print a non-fatal message box and do not exit.
+ */
+void nonfatal(const char *fmt, ...)
+{
+ va_list ap;
+ char *stuff;
+
+ va_start(ap, fmt);
+ stuff = dupvprintf(fmt, ap);
+ va_end(ap);
+ MessageBox(NULL, stuff, "PuTTYgen Error",
+ MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
+ sfree(stuff);
+}
+
+/* ----------------------------------------------------------------------
+ * ProgressReceiver implementation.
+ */
+
+#define PROGRESSRANGE 65535
+
+struct progressphase {
+ double startpoint, total;
+ /* For exponential phases */
+ double exp_probability, exp_current_value;
+};
+
+struct progress {
+ size_t nphases, phasessize;
+ struct progressphase *phases, *currphase;
+
+ double scale;
+ HWND progbar;
+
+ ProgressReceiver rec;
+};
+
+static ProgressPhase win_progress_add_linear(
+ ProgressReceiver *prog, double overall_cost) {
+ struct progress *p = container_of(prog, struct progress, rec);
+
+ sgrowarray(p->phases, p->phasessize, p->nphases);
+ int phase = p->nphases++;
+
+ p->phases[phase].total = overall_cost;
+
+ ProgressPhase ph = { .n = phase };
+ return ph;
+}
+
+static ProgressPhase win_progress_add_probabilistic(
+ ProgressReceiver *prog, double cost_per_attempt, double probability) {
+ struct progress *p = container_of(prog, struct progress, rec);
+
+ sgrowarray(p->phases, p->phasessize, p->nphases);
+ int phase = p->nphases++;
+
+ p->phases[phase].exp_probability = 1.0 - probability;
+ p->phases[phase].exp_current_value = 1.0;
+ /* Expected number of attempts = 1 / probability of attempt succeeding */
+ p->phases[phase].total = cost_per_attempt / probability;
+
+ ProgressPhase ph = { .n = phase };
+ return ph;
+}
+
+static void win_progress_ready(ProgressReceiver *prog)
+{
+ struct progress *p = container_of(prog, struct progress, rec);
+
+ double total = 0;
+ for (int i = 0; i < p->nphases; i++) {
+ p->phases[i].startpoint = total;
+ total += p->phases[i].total;
+ }
+ p->scale = PROGRESSRANGE / total;
+
+ SendMessage(p->progbar, PBM_SETRANGE, 0, MAKELPARAM(0, PROGRESSRANGE));
+}
+
+static void win_progress_start_phase(ProgressReceiver *prog,
+ ProgressPhase phase)
+{
+ struct progress *p = container_of(prog, struct progress, rec);
+
+ assert(phase.n < p->nphases);
+ p->currphase = &p->phases[phase.n];
+}
+
+static void win_progress_update(struct progress *p, double phasepos)
+{
+ double position = (p->currphase->startpoint +
+ p->currphase->total * phasepos);
+ position *= p->scale;
+ if (position < 0)
+ position = 0;
+ if (position > PROGRESSRANGE)
+ position = PROGRESSRANGE;
+
+ SendMessage(p->progbar, PBM_SETPOS, (WPARAM)position, 0);
+}
+
+static void win_progress_report(ProgressReceiver *prog, double progress)
+{
+ struct progress *p = container_of(prog, struct progress, rec);
+
+ win_progress_update(p, progress);
+}
+
+static void win_progress_report_attempt(ProgressReceiver *prog)
+{
+ struct progress *p = container_of(prog, struct progress, rec);
+
+ p->currphase->exp_current_value *= p->currphase->exp_probability;
+ win_progress_update(p, 1.0 - p->currphase->exp_current_value);
+}
+
+static void win_progress_report_phase_complete(ProgressReceiver *prog)
+{
+ struct progress *p = container_of(prog, struct progress, rec);
+
+ win_progress_update(p, 1.0);
+}
+
+static const ProgressReceiverVtable win_progress_vt = {
+ .add_linear = win_progress_add_linear,
+ .add_probabilistic = win_progress_add_probabilistic,
+ .ready = win_progress_ready,
+ .start_phase = win_progress_start_phase,
+ .report = win_progress_report,
+ .report_attempt = win_progress_report_attempt,
+ .report_phase_complete = win_progress_report_phase_complete,
+};
+
+static void win_progress_initialise(struct progress *p)
+{
+ p->nphases = p->phasessize = 0;
+ p->phases = p->currphase = NULL;
+ p->rec.vt = &win_progress_vt;
+}
+
+static void win_progress_cleanup(struct progress *p)
+{
+ sfree(p->phases);
+}
+
+struct PassphraseProcStruct {
+ char **passphrase;
+ char *comment;
+};
+
+/*
+ * Dialog-box function for the passphrase box.
+ */
+static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ static char **passphrase = NULL;
+ struct PassphraseProcStruct *p;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ SetForegroundWindow(hwnd);
+ SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
+
+ /*
+ * Centre the window.
+ */
+ { /* centre the window */
+ RECT rs, rd;
+ HWND hw;
+
+ hw = GetDesktopWindow();
+ if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
+ MoveWindow(hwnd,
+ (rs.right + rs.left + rd.left - rd.right) / 2,
+ (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
+ rd.right - rd.left, rd.bottom - rd.top, true);
+ }
+
+ p = (struct PassphraseProcStruct *) lParam;
+ passphrase = p->passphrase;
+ if (p->comment)
+ SetDlgItemText(hwnd, 101, p->comment);
+ burnstr(*passphrase);
+ *passphrase = dupstr("");
+ SetDlgItemText(hwnd, 102, *passphrase);
+ return 0;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ if (*passphrase)
+ EndDialog(hwnd, 1);
+ else
+ MessageBeep(0);
+ return 0;
+ case IDCANCEL:
+ EndDialog(hwnd, 0);
+ return 0;
+ case 102: /* edit box */
+ if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
+ burnstr(*passphrase);
+ *passphrase = GetDlgItemText_alloc(hwnd, 102);
+ }
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ EndDialog(hwnd, 0);
+ return 0;
+ }
+ return 0;
+}
+
+static void try_get_dlg_item_uint32(HWND hwnd, int id, uint32_t *out)
+{
+ char buf[128];
+ if (!GetDlgItemText(hwnd, id, buf, sizeof(buf)))
+ return;
+
+ if (!*buf)
+ return;
+
+ char *end;
+ unsigned long val = strtoul(buf, &end, 10);
+ if (*end)
+ return;
+
+ if ((val >> 16) >> 16)
+ return;
+
+ *out = val;
+}
+
+static ppk_save_parameters save_params;
+
+struct PPKParams {
+ ppk_save_parameters params;
+ uint32_t time_passes, time_ms;
+};
+
+/*
+ * Dialog-box function for the passphrase box.
+ */
+static INT_PTR CALLBACK PPKParamsProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ struct PPKParams *pp;
+ char *buf;
+
+ if (msg == WM_INITDIALOG) {
+ pp = (struct PPKParams *)lParam;
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pp);
+ } else {
+ pp = (struct PPKParams *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ }
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ SetForegroundWindow(hwnd);
+ SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
+
+ if (has_help())
+ SetWindowLongPtr(hwnd, GWL_EXSTYLE,
+ GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
+ WS_EX_CONTEXTHELP);
+
+ /*
+ * Centre the window.
+ */
+ { /* centre the window */
+ RECT rs, rd;
+ HWND hw;
+
+ hw = GetDesktopWindow();
+ if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
+ MoveWindow(hwnd,
+ (rs.right + rs.left + rd.left - rd.right) / 2,
+ (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
+ rd.right - rd.left, rd.bottom - rd.top, true);
+ }
+
+ CheckRadioButton(hwnd, IDC_PPKVER_2, IDC_PPKVER_3,
+ IDC_PPKVER_2 + (pp->params.fmt_version - 2));
+
+ CheckRadioButton(
+ hwnd, IDC_KDF_ARGON2ID, IDC_KDF_ARGON2D,
+ (pp->params.argon2_flavour == Argon2id ? IDC_KDF_ARGON2ID :
+ pp->params.argon2_flavour == Argon2i ? IDC_KDF_ARGON2I :
+ /* pp->params.argon2_flavour == Argon2d ? */ IDC_KDF_ARGON2D));
+
+ buf = dupprintf("%"PRIu32, pp->params.argon2_mem);
+ SetDlgItemText(hwnd, IDC_ARGON2_MEM, buf);
+ sfree(buf);
+
+ if (pp->params.argon2_passes_auto) {
+ CheckRadioButton(hwnd, IDC_PPK_AUTO_YES, IDC_PPK_AUTO_NO,
+ IDC_PPK_AUTO_YES);
+ buf = dupprintf("%"PRIu32, pp->time_ms);
+ SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf);
+ sfree(buf);
+ } else {
+ CheckRadioButton(hwnd, IDC_PPK_AUTO_YES, IDC_PPK_AUTO_NO,
+ IDC_PPK_AUTO_NO);
+ buf = dupprintf("%"PRIu32, pp->time_passes);
+ SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf);
+ sfree(buf);
+ }
+
+ buf = dupprintf("%"PRIu32, pp->params.argon2_parallelism);
+ SetDlgItemText(hwnd, IDC_ARGON2_PARALLEL, buf);
+ sfree(buf);
+
+ return 0;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ EndDialog(hwnd, 1);
+ return 0;
+ case IDCANCEL:
+ EndDialog(hwnd, 0);
+ return 0;
+ case IDC_PPKVER_2:
+ pp->params.fmt_version = 2;
+ return 0;
+ case IDC_PPKVER_3:
+ pp->params.fmt_version = 3;
+ return 0;
+ case IDC_KDF_ARGON2ID:
+ pp->params.argon2_flavour = Argon2id;
+ return 0;
+ case IDC_KDF_ARGON2I:
+ pp->params.argon2_flavour = Argon2i;
+ return 0;
+ case IDC_KDF_ARGON2D:
+ pp->params.argon2_flavour = Argon2d;
+ return 0;
+ case IDC_ARGON2_MEM:
+ try_get_dlg_item_uint32(hwnd, IDC_ARGON2_MEM,
+ &pp->params.argon2_mem);
+ return 0;
+ case IDC_PPK_AUTO_YES:
+ pp->params.argon2_passes_auto = true;
+ buf = dupprintf("%"PRIu32, pp->time_ms);
+ SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf);
+ sfree(buf);
+ return 0;
+ case IDC_PPK_AUTO_NO:
+ pp->params.argon2_passes_auto = false;
+ buf = dupprintf("%"PRIu32, pp->time_passes);
+ SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf);
+ sfree(buf);
+ return 0;
+ case IDC_ARGON2_TIME:
+ try_get_dlg_item_uint32(hwnd, IDC_ARGON2_TIME,
+ pp->params.argon2_passes_auto ?
+ &pp->time_ms : &pp->time_passes);
+ return 0;
+ case IDC_ARGON2_PARALLEL:
+ try_get_dlg_item_uint32(hwnd, IDC_ARGON2_PARALLEL,
+ &pp->params.argon2_parallelism);
+ return 0;
+ }
+ return 0;
+ case WM_HELP: {
+ int id = ((LPHELPINFO)lParam)->iCtrlId;
+ const char *topic = NULL;
+ switch (id) {
+ case IDC_PPKVER_STATIC:
+ case IDC_PPKVER_2:
+ case IDC_PPKVER_3:
+ topic = WINHELP_CTX_puttygen_ppkver; break;
+ case IDC_KDF_STATIC:
+ case IDC_KDF_ARGON2ID:
+ case IDC_KDF_ARGON2I:
+ case IDC_KDF_ARGON2D:
+ case IDC_ARGON2_MEM_STATIC:
+ case IDC_ARGON2_MEM:
+ case IDC_ARGON2_MEM_STATIC2:
+ case IDC_ARGON2_TIME_STATIC:
+ case IDC_ARGON2_TIME:
+ case IDC_PPK_AUTO_YES:
+ case IDC_PPK_AUTO_NO:
+ case IDC_ARGON2_PARALLEL_STATIC:
+ case IDC_ARGON2_PARALLEL:
+ topic = WINHELP_CTX_puttygen_kdfparam; break;
+ }
+ if (topic) {
+ launch_help(hwnd, topic);
+ } else {
+ MessageBeep(0);
+ }
+ break;
+ }
+ case WM_CLOSE:
+ EndDialog(hwnd, 0);
+ return 0;
+ }
+ return 0;
+}
+
+/*
+ * Prompt for a key file. Assumes the filename buffer is of size
+ * FILENAME_MAX.
+ */
+static bool prompt_keyfile(HWND hwnd, char *dlgtitle,
+ char *filename, bool save, bool ppk)
+{
+ OPENFILENAME of;
+ memset(&of, 0, sizeof(of));
+ of.hwndOwner = hwnd;
+ if (ppk) {
+ of.lpstrFilter = "PuTTY Private Key Files (*.ppk)\0*.ppk\0"
+ "All Files (*.*)\0*\0\0\0";
+ of.lpstrDefExt = ".ppk";
+ } else {
+ of.lpstrFilter = "All Files (*.*)\0*\0\0\0";
+ }
+ of.lpstrCustomFilter = NULL;
+ of.nFilterIndex = 1;
+ of.lpstrFile = filename;
+ *filename = '\0';
+ of.nMaxFile = FILENAME_MAX;
+ of.lpstrFileTitle = NULL;
+ of.lpstrTitle = dlgtitle;
+ of.Flags = 0;
+ return request_file(NULL, &of, false, save);
+}
+
+/*
+ * Dialog-box function for the Licence box.
+ */
+static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG: {
+ /*
+ * Centre the window.
+ */
+ RECT rs, rd;
+ HWND hw;
+
+ hw = GetDesktopWindow();
+ if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
+ MoveWindow(hwnd,
+ (rs.right + rs.left + rd.left - rd.right) / 2,
+ (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
+ rd.right - rd.left, rd.bottom - rd.top, true);
+
+ SetDlgItemText(hwnd, 1000, LICENCE_TEXT("\r\n\r\n"));
+ return 1;
+ }
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hwnd, 1);
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ EndDialog(hwnd, 1);
+ return 0;
+ }
+ return 0;
+}
+
+/*
+ * Dialog-box function for the About box.
+ */
+static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ /*
+ * Centre the window.
+ */
+ { /* centre the window */
+ RECT rs, rd;
+ HWND hw;
+
+ hw = GetDesktopWindow();
+ if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
+ MoveWindow(hwnd,
+ (rs.right + rs.left + rd.left - rd.right) / 2,
+ (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
+ rd.right - rd.left, rd.bottom - rd.top, true);
+ }
+
+ {
+ char *buildinfo_text = buildinfo("\r\n");
+ char *text = dupprintf(
+ "PuTTYgen\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s",
+ ver, buildinfo_text,
+ "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved.");
+ sfree(buildinfo_text);
+ SetDlgItemText(hwnd, 1000, text);
+ MakeDlgItemBorderless(hwnd, 1000);
+ sfree(text);
+ }
+ return 1;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ case IDCANCEL:
+ EndDialog(hwnd, 1);
+ return 0;
+ case 101:
+ EnableWindow(hwnd, 0);
+ DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
+ EnableWindow(hwnd, 1);
+ SetActiveWindow(hwnd);
+ return 0;
+ case 102:
+ /* Load web browser */
+ ShellExecute(hwnd, "open",
+ "https://www.chiark.greenend.org.uk/~sgtatham/putty/",
+ 0, 0, SW_SHOWDEFAULT);
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ EndDialog(hwnd, 1);
+ return 0;
+ }
+ return 0;
+}
+
+typedef enum {RSA, DSA, ECDSA, EDDSA} keytype;
+
+/*
+ * Thread to generate a key.
+ */
+struct rsa_key_thread_params {
+ HWND progressbar; /* notify this with progress */
+ HWND dialog; /* notify this on completion */
+ int key_bits; /* bits in key modulus (RSA, DSA) */
+ int curve_bits; /* bits in elliptic curve (ECDSA) */
+ keytype keytype;
+ const PrimeGenerationPolicy *primepolicy;
+ bool rsa_strong;
+ union {
+ RSAKey *key;
+ struct dsa_key *dsakey;
+ struct ecdsa_key *eckey;
+ struct eddsa_key *edkey;
+ };
+};
+static DWORD WINAPI generate_key_thread(void *param)
+{
+ struct rsa_key_thread_params *params =
+ (struct rsa_key_thread_params *) param;
+ struct progress prog;
+ prog.progbar = params->progressbar;
+
+ win_progress_initialise(&prog);
+
+ PrimeGenerationContext *pgc = primegen_new_context(params->primepolicy);
+
+ if (params->keytype == DSA)
+ dsa_generate(params->dsakey, params->key_bits, pgc, &prog.rec);
+ else if (params->keytype == ECDSA)
+ ecdsa_generate(params->eckey, params->curve_bits);
+ else if (params->keytype == EDDSA)
+ eddsa_generate(params->edkey, params->curve_bits);
+ else
+ rsa_generate(params->key, params->key_bits, params->rsa_strong,
+ pgc, &prog.rec);
+
+ primegen_free_context(pgc);
+
+ PostMessage(params->dialog, WM_DONEKEY, 0, 0);
+
+ win_progress_cleanup(&prog);
+
+ sfree(params);
+ return 0;
+}
+
+struct InitialParams {
+ int keybutton;
+ int primepolicybutton;
+ bool rsa_strong;
+ FingerprintType fptype;
+ int keybits;
+ int eccurve_index, edcurve_index;
+};
+
+struct MainDlgState {
+ bool generation_thread_exists;
+ bool key_exists;
+ int entropy_got, entropy_required;
+ strbuf *entropy;
+ ULONG entropy_prev_msgtime;
+ int key_bits, curve_bits;
+ bool ssh2;
+ keytype keytype;
+ const PrimeGenerationPolicy *primepolicy;
+ bool rsa_strong;
+ FingerprintType fptype;
+ char **commentptr; /* points to key.comment or ssh2key.comment */
+ ssh2_userkey ssh2key;
+ union {
+ RSAKey key;
+ struct dsa_key dsakey;
+ struct ecdsa_key eckey;
+ struct eddsa_key edkey;
+ };
+ HMENU filemenu, keymenu, cvtmenu;
+};
+
+/*
+ * Rate limit for incrementing the entropy_got counter.
+ *
+ * Some pointing devices (e.g. gaming mice) can be set to send
+ * mouse-movement events at an extremely high sample rate like 1kHz.
+ * In that situation, there's likely to be a strong correlation
+ * between the contents of successive movement events, so you have to
+ * regard the mouse movements as containing less entropy each.
+ *
+ * A reasonably simple approach to this is to continue to buffer all
+ * mouse data, but limit the rate at which we increment the counter
+ * for how much entropy we think we've collected. That way, the user
+ * still has to spend time wiggling the mouse back and forth in a way
+ * that varies with muscle motions and introduces randomness.
+ */
+#define ENTROPY_RATE_LIMIT 10 /* in units of GetMessageTime(), i.e. ms */
+
+static void hidemany(HWND hwnd, const int *ids, bool hideit)
+{
+ while (*ids) {
+ ShowWindow(GetDlgItem(hwnd, *ids++), (hideit ? SW_HIDE : SW_SHOW));
+ }
+}
+
+enum {
+ controlidstart = 100,
+ IDC_QUIT,
+ IDC_TITLE,
+ IDC_BOX_KEY,
+ IDC_NOKEY,
+ IDC_GENERATING,
+ IDC_PROGRESS,
+ IDC_PKSTATIC, IDC_KEYDISPLAY,
+ IDC_CERTSTATIC, IDC_CERTMOREINFO,
+ IDC_FPSTATIC, IDC_FINGERPRINT,
+ IDC_COMMENTSTATIC, IDC_COMMENTEDIT,
+ IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT,
+ IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT,
+ IDC_BOX_ACTIONS,
+ IDC_GENSTATIC, IDC_GENERATE,
+ IDC_LOADSTATIC, IDC_LOAD,
+ IDC_SAVESTATIC, IDC_SAVE, IDC_SAVEPUB,
+ IDC_BOX_PARAMS,
+ IDC_TYPESTATIC, IDC_KEYSSH1, IDC_KEYSSH2RSA, IDC_KEYSSH2DSA,
+ IDC_KEYSSH2ECDSA, IDC_KEYSSH2EDDSA,
+ IDC_PRIMEGEN_PROB, IDC_PRIMEGEN_MAURER_SIMPLE, IDC_PRIMEGEN_MAURER_COMPLEX,
+ IDC_RSA_STRONG,
+ IDC_FPTYPE_SHA256, IDC_FPTYPE_MD5,
+ IDC_PPK_PARAMS,
+ IDC_BITSSTATIC, IDC_BITS,
+ IDC_ECCURVESTATIC, IDC_ECCURVE,
+ IDC_EDCURVESTATIC, IDC_EDCURVE,
+ IDC_NOTHINGSTATIC,
+ IDC_ABOUT,
+ IDC_GIVEHELP,
+ IDC_IMPORT,
+ IDC_EXPORT_OPENSSH_AUTO, IDC_EXPORT_OPENSSH_NEW,
+ IDC_EXPORT_SSHCOM,
+ IDC_ADDCERT, IDC_REMCERT,
+};
+
+static void setupbigedit1(HWND hwnd, RSAKey *key)
+{
+ ShowWindow(GetDlgItem(hwnd, IDC_CERTSTATIC), SW_HIDE);
+ ShowWindow(GetDlgItem(hwnd, IDC_CERTMOREINFO), SW_HIDE);
+ ShowWindow(GetDlgItem(hwnd, IDC_PKSTATIC), SW_SHOW);
+ ShowWindow(GetDlgItem(hwnd, IDC_KEYDISPLAY), SW_SHOW);
+
+ SetDlgItemText(hwnd, IDC_PKSTATIC,
+ "&Public key for pasting into authorized_keys file:");
+
+ char *buffer = ssh1_pubkey_str(key);
+ SetDlgItemText(hwnd, IDC_KEYDISPLAY, buffer);
+ sfree(buffer);
+}
+
+static void setupbigedit2(HWND hwnd, ssh2_userkey *key)
+{
+ if (ssh_key_alg(key->key)->is_certificate) {
+ ShowWindow(GetDlgItem(hwnd, IDC_CERTSTATIC), SW_SHOW);
+ ShowWindow(GetDlgItem(hwnd, IDC_CERTMOREINFO), SW_SHOW);
+ ShowWindow(GetDlgItem(hwnd, IDC_PKSTATIC), SW_HIDE);
+ ShowWindow(GetDlgItem(hwnd, IDC_KEYDISPLAY), SW_HIDE);
+
+ SetDlgItemText(hwnd, IDC_CERTSTATIC,
+ "This public key contains an OpenSSH certificate.");
+ } else {
+ ShowWindow(GetDlgItem(hwnd, IDC_CERTSTATIC), SW_HIDE);
+ ShowWindow(GetDlgItem(hwnd, IDC_CERTMOREINFO), SW_HIDE);
+ ShowWindow(GetDlgItem(hwnd, IDC_PKSTATIC), SW_SHOW);
+ ShowWindow(GetDlgItem(hwnd, IDC_KEYDISPLAY), SW_SHOW);
+
+ SetDlgItemText(hwnd, IDC_PKSTATIC, "&Public key for pasting into "
+ "OpenSSH authorized_keys file:");
+
+ char *buffer = ssh2_pubkey_openssh_str(key);
+ SetDlgItemText(hwnd, IDC_KEYDISPLAY, buffer);
+ sfree(buffer);
+ }
+}
+
+/*
+ * Warn about the obsolescent key file format.
+ */
+void old_keyfile_warning(void)
+{
+ static const char mbtitle[] = "PuTTY Key File Warning";
+ static const char message[] =
+ "You are loading an SSH-2 private key which has an\n"
+ "old version of the file format. This means your key\n"
+ "file is not fully tamperproof. Future versions of\n"
+ "PuTTY may stop supporting this private key format,\n"
+ "so we recommend you convert your key to the new\n"
+ "format.\n"
+ "\n"
+ "Once the key is loaded into PuTTYgen, you can perform\n"
+ "this conversion simply by saving it again.";
+
+ MessageBox(NULL, message, mbtitle, MB_OK);
+}
+
+static const int nokey_ids[] = { IDC_NOKEY, 0 };
+static const int generating_ids[] = { IDC_GENERATING, IDC_PROGRESS, 0 };
+static const int gotkey_ids_unconditional[] = {
+ IDC_FPSTATIC, IDC_FINGERPRINT,
+ IDC_COMMENTSTATIC, IDC_COMMENTEDIT,
+ IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT,
+ IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 0
+};
+static const int gotkey_ids_conditional[] = {
+ IDC_PKSTATIC, IDC_KEYDISPLAY,
+ IDC_CERTSTATIC, IDC_CERTMOREINFO,
+};
+
+/*
+ * Small UI helper function to switch the state of the main dialog
+ * by enabling and disabling controls and menu items.
+ */
+void ui_set_state(HWND hwnd, struct MainDlgState *state, int status)
+{
+ int type;
+
+ switch (status) {
+ case 0: /* no key */
+ hidemany(hwnd, nokey_ids, false);
+ hidemany(hwnd, generating_ids, true);
+ hidemany(hwnd, gotkey_ids_unconditional, true);
+ hidemany(hwnd, gotkey_ids_conditional, true);
+ EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2ECDSA), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2EDDSA), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_BITS), 1);
+ EnableMenuItem(state->filemenu, IDC_LOAD, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->filemenu, IDC_SAVE, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->filemenu, IDC_SAVEPUB, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_GENERATE, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH1, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2RSA, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2DSA, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2ECDSA,
+ MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2EDDSA,
+ MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_AUTO,
+ MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_NEW,
+ MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->cvtmenu, IDC_EXPORT_SSHCOM,
+ MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_ADDCERT, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_REMCERT, MF_GRAYED|MF_BYCOMMAND);
+ break;
+ case 1: /* generating key */
+ hidemany(hwnd, nokey_ids, true);
+ hidemany(hwnd, generating_ids, false);
+ hidemany(hwnd, gotkey_ids_unconditional, true);
+ hidemany(hwnd, gotkey_ids_conditional, true);
+ EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2ECDSA), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2EDDSA), 0);
+ EnableWindow(GetDlgItem(hwnd, IDC_BITS), 0);
+ EnableMenuItem(state->filemenu, IDC_LOAD, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->filemenu, IDC_SAVE, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->filemenu, IDC_SAVEPUB, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_GENERATE, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH1, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2RSA, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2DSA, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2ECDSA,
+ MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2EDDSA,
+ MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_AUTO,
+ MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_NEW,
+ MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->cvtmenu, IDC_EXPORT_SSHCOM,
+ MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_ADDCERT, MF_GRAYED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_REMCERT, MF_GRAYED|MF_BYCOMMAND);
+ break;
+ case 2:
+ hidemany(hwnd, nokey_ids, true);
+ hidemany(hwnd, generating_ids, true);
+ hidemany(hwnd, gotkey_ids_unconditional, false);
+ // gotkey_ids_conditional will be unhidden by setupbigedit2
+ EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2ECDSA), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2EDDSA), 1);
+ EnableWindow(GetDlgItem(hwnd, IDC_BITS), 1);
+ EnableMenuItem(state->filemenu, IDC_LOAD, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->filemenu, IDC_SAVE, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->filemenu, IDC_SAVEPUB, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_GENERATE, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH1, MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2RSA,MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2DSA,MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2ECDSA,
+ MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->keymenu, IDC_KEYSSH2EDDSA,
+ MF_ENABLED|MF_BYCOMMAND);
+ EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_ENABLED|MF_BYCOMMAND);
+ /*
+ * Enable export menu items if and only if the key type
+ * supports this kind of export.
+ */
+ type = state->ssh2 ? SSH_KEYTYPE_SSH2 : SSH_KEYTYPE_SSH1;
+#define do_export_menuitem(x,y) \
+ EnableMenuItem(state->cvtmenu, x, MF_BYCOMMAND | \
+ (import_target_type(y)==type?MF_ENABLED:MF_GRAYED))
+ do_export_menuitem(IDC_EXPORT_OPENSSH_AUTO, SSH_KEYTYPE_OPENSSH_AUTO);
+ do_export_menuitem(IDC_EXPORT_OPENSSH_NEW, SSH_KEYTYPE_OPENSSH_NEW);
+ do_export_menuitem(IDC_EXPORT_SSHCOM, SSH_KEYTYPE_SSHCOM);
+#undef do_export_menuitem
+ /*
+ * Enable certificate menu items similarly.
+ */
+ {
+ bool add_cert_allowed = false, rem_cert_allowed = false;
+
+ if (state->ssh2 && state->ssh2key.key) {
+ const ssh_keyalg *alg = ssh_key_alg(state->ssh2key.key);
+ if (alg->is_certificate) {
+ /* If there's a certificate, we can remove it */
+ rem_cert_allowed = true;
+ /* And reset to the base algorithm for the next check */
+ alg = alg->base_alg;
+ }
+
+ /* Now, do we have any certified version of this alg? */
+ for (size_t i = 0; i < n_keyalgs; i++) {
+ if (all_keyalgs[i]->base_alg == alg) {
+ add_cert_allowed = true;
+ break;
+ }
+ }
+ }
+
+ EnableMenuItem(state->keymenu, IDC_ADDCERT, MF_BYCOMMAND |
+ (add_cert_allowed ? MF_ENABLED : MF_GRAYED));
+ EnableMenuItem(state->keymenu, IDC_REMCERT, MF_BYCOMMAND |
+ (rem_cert_allowed ? MF_ENABLED : MF_GRAYED));
+ }
+ break;
+ }
+}
+
+/*
+ * Helper functions to set the key type, taking care of keeping the
+ * menu and radio button selections in sync and also showing/hiding
+ * the appropriate size/curve control for the current key type.
+ */
+void ui_update_key_type_ctrls(HWND hwnd)
+{
+ enum { BITS, ECCURVE, EDCURVE, NOTHING } which;
+ static const int bits_ids[] = {
+ IDC_BITSSTATIC, IDC_BITS, 0
+ };
+ static const int eccurve_ids[] = {
+ IDC_ECCURVESTATIC, IDC_ECCURVE, 0
+ };
+ static const int edcurve_ids[] = {
+ IDC_EDCURVESTATIC, IDC_EDCURVE, 0
+ };
+ static const int nothing_ids[] = {
+ IDC_NOTHINGSTATIC, 0
+ };
+
+ if (IsDlgButtonChecked(hwnd, IDC_KEYSSH1) ||
+ IsDlgButtonChecked(hwnd, IDC_KEYSSH2RSA) ||
+ IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA)) {
+ which = BITS;
+ } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2ECDSA)) {
+ which = ECCURVE;
+ } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2EDDSA)) {
+ which = EDCURVE;
+ } else {
+ /* Currently not used since Ed25519 stopped being the only
+ * thing in its class, but I'll keep it here in case it comes
+ * in useful again */
+ which = NOTHING;
+ }
+
+ hidemany(hwnd, bits_ids, which != BITS);
+ hidemany(hwnd, eccurve_ids, which != ECCURVE);
+ hidemany(hwnd, edcurve_ids, which != EDCURVE);
+ hidemany(hwnd, nothing_ids, which != NOTHING);
+}
+void ui_set_key_type(HWND hwnd, struct MainDlgState *state, int button)
+{
+ CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2EDDSA, button);
+ CheckMenuRadioItem(state->keymenu, IDC_KEYSSH1, IDC_KEYSSH2EDDSA,
+ button, MF_BYCOMMAND);
+ ui_update_key_type_ctrls(hwnd);
+}
+void ui_set_primepolicy(HWND hwnd, struct MainDlgState *state, int option)
+{
+ CheckMenuRadioItem(state->keymenu, IDC_PRIMEGEN_PROB,
+ IDC_PRIMEGEN_MAURER_COMPLEX, option, MF_BYCOMMAND);
+ switch (option) {
+ case IDC_PRIMEGEN_PROB:
+ state->primepolicy = &primegen_probabilistic;
+ break;
+ case IDC_PRIMEGEN_MAURER_SIMPLE:
+ state->primepolicy = &primegen_provable_maurer_simple;
+ break;
+ case IDC_PRIMEGEN_MAURER_COMPLEX:
+ state->primepolicy = &primegen_provable_maurer_complex;
+ break;
+ }
+}
+void ui_set_rsa_strong(HWND hwnd, struct MainDlgState *state, bool enable)
+{
+ state->rsa_strong = enable;
+ CheckMenuItem(state->keymenu, IDC_RSA_STRONG,
+ (enable ? MF_CHECKED : 0) | MF_BYCOMMAND);
+}
+static FingerprintType idc_to_fptype(int option)
+{
+ switch (option) {
+ case IDC_FPTYPE_SHA256:
+ return SSH_FPTYPE_SHA256;
+ case IDC_FPTYPE_MD5:
+ return SSH_FPTYPE_MD5;
+ default:
+ unreachable("bad control id in idc_to_fptype");
+ }
+}
+static int fptype_to_idc(FingerprintType fptype)
+{
+ switch (fptype) {
+ case SSH_FPTYPE_SHA256:
+ return IDC_FPTYPE_SHA256;
+ case SSH_FPTYPE_MD5:
+ return IDC_FPTYPE_MD5;
+ default:
+ unreachable("bad fptype in fptype_to_idc");
+ }
+}
+void ui_set_fptype(HWND hwnd, struct MainDlgState *state, int option)
+{
+ CheckMenuRadioItem(state->keymenu, IDC_FPTYPE_SHA256,
+ IDC_FPTYPE_MD5, option, MF_BYCOMMAND);
+
+ state->fptype = idc_to_fptype(option);
+
+ if (state->key_exists && state->ssh2) {
+ char *fp = ssh2_fingerprint(state->ssh2key.key, state->fptype);
+ SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
+ sfree(fp);
+ }
+}
+
+static void update_ui_after_ssh2_pubkey_change(
+ HWND hwnd, struct MainDlgState *state)
+{
+ /* Smaller version of update_ui_after_load which doesn't need to
+ * be told things like the passphrase, which we aren't changing
+ * anyway */
+ char *savecomment = state->ssh2key.comment;
+ state->ssh2key.comment = NULL;
+ char *fp = ssh2_fingerprint(state->ssh2key.key, state->fptype);
+ state->ssh2key.comment = savecomment;
+
+ SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
+ sfree(fp);
+
+ setupbigedit2(hwnd, &state->ssh2key);
+}
+
+static void update_ui_after_load(HWND hwnd, struct MainDlgState *state,
+ const char *passphrase, int type,
+ RSAKey *newkey1, ssh2_userkey *newkey2)
+{
+ SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT, passphrase);
+ SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT, passphrase);
+
+ if (type == SSH_KEYTYPE_SSH1) {
+ char *fingerprint, *savecomment;
+
+ state->ssh2 = false;
+ state->commentptr = &state->key.comment;
+ state->key = *newkey1; /* structure copy */
+
+ /*
+ * Set the key fingerprint.
+ */
+ savecomment = state->key.comment;
+ state->key.comment = NULL;
+ fingerprint = rsa_ssh1_fingerprint(&state->key);
+ state->key.comment = savecomment;
+ SetDlgItemText(hwnd, IDC_FINGERPRINT, fingerprint);
+ sfree(fingerprint);
+
+ /*
+ * Construct a decimal representation of the key, for pasting
+ * into .ssh/authorized_keys on a Unix box.
+ */
+ setupbigedit1(hwnd, &state->key);
+ } else {
+ state->ssh2 = true;
+ state->commentptr = &state->ssh2key.comment;
+ state->ssh2key = *newkey2; /* structure copy */
+ sfree(newkey2);
+
+ update_ui_after_ssh2_pubkey_change(hwnd, state);
+ }
+ SetDlgItemText(hwnd, IDC_COMMENTEDIT,
+ *state->commentptr);
+
+ /*
+ * Finally, hide the progress bar and show the key data.
+ */
+ ui_set_state(hwnd, state, 2);
+ state->key_exists = true;
+}
+
+void load_key_file(HWND hwnd, struct MainDlgState *state,
+ Filename *filename, bool was_import_cmd)
+{
+ char *passphrase;
+ bool needs_pass;
+ int type, realtype;
+ int ret;
+ const char *errmsg = NULL;
+ char *comment;
+ RSAKey newkey1;
+ ssh2_userkey *newkey2 = NULL;
+
+ type = realtype = key_type(filename);
+ if (type != SSH_KEYTYPE_SSH1 &&
+ type != SSH_KEYTYPE_SSH2 &&
+ !import_possible(type)) {
+ char *msg = dupprintf("Couldn't load private key (%s)",
+ key_type_to_str(type));
+ message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
+ HELPCTXID(errors_cantloadkey));
+ sfree(msg);
+ return;
+ }
+
+ if (type != SSH_KEYTYPE_SSH1 &&
+ type != SSH_KEYTYPE_SSH2) {
+ realtype = type;
+ type = import_target_type(type);
+ }
+
+ comment = NULL;
+ passphrase = NULL;
+ if (realtype == SSH_KEYTYPE_SSH1)
+ needs_pass = rsa1_encrypted_f(filename, &comment);
+ else if (realtype == SSH_KEYTYPE_SSH2)
+ needs_pass = ppk_encrypted_f(filename, &comment);
+ else
+ needs_pass = import_encrypted(filename, realtype, &comment);
+ do {
+ burnstr(passphrase);
+ passphrase = NULL;
+
+ if (needs_pass) {
+ int dlgret;
+ struct PassphraseProcStruct pps;
+ pps.passphrase = &passphrase;
+ pps.comment = comment;
+ dlgret = DialogBoxParam(hinst,
+ MAKEINTRESOURCE(210),
+ NULL, PassphraseProc,
+ (LPARAM) &pps);
+ if (!dlgret) {
+ ret = -2;
+ break;
+ }
+ assert(passphrase != NULL);
+ } else
+ passphrase = dupstr("");
+ if (type == SSH_KEYTYPE_SSH1) {
+ if (realtype == type)
+ ret = rsa1_load_f(filename, &newkey1, passphrase, &errmsg);
+ else
+ ret = import_ssh1(filename, realtype, &newkey1,
+ passphrase, &errmsg);
+ } else {
+ if (realtype == type)
+ newkey2 = ppk_load_f(filename, passphrase, &errmsg);
+ else
+ newkey2 = import_ssh2(filename, realtype, passphrase, &errmsg);
+ if (newkey2 == SSH2_WRONG_PASSPHRASE)
+ ret = -1;
+ else if (!newkey2)
+ ret = 0;
+ else
+ ret = 1;
+ }
+ } while (ret == -1);
+ if (comment)
+ sfree(comment);
+ if (ret == 0) {
+ char *msg = dupprintf("Couldn't load private key (%s)", errmsg);
+ message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
+ HELPCTXID(errors_cantloadkey));
+ sfree(msg);
+ } else if (ret == 1) {
+ /*
+ * Now update the key controls with all the
+ * key data.
+ */
+ update_ui_after_load(hwnd, state, passphrase, type, &newkey1, newkey2);
+
+ /*
+ * If the user has imported a foreign key
+ * using the Load command, let them know.
+ * If they've used the Import command, be
+ * silent.
+ */
+ if (realtype != type && !was_import_cmd) {
+ char msg[512];
+ sprintf(msg, "Successfully imported foreign key\n"
+ "(%s).\n"
+ "To use this key with PuTTY, you need to\n"
+ "use the \"Save private key\" command to\n"
+ "save it in PuTTY's own format.",
+ key_type_to_str(realtype));
+ MessageBox(NULL, msg, "PuTTYgen Notice",
+ MB_OK | MB_ICONINFORMATION);
+ }
+ }
+ burnstr(passphrase);
+}
+
+void add_certificate(HWND hwnd, struct MainDlgState *state,
+ Filename *filename)
+{
+ int type = key_type(filename);
+ if (type != SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 &&
+ type != SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
+ char *msg = dupprintf("Couldn't load certificate (%s)",
+ key_type_to_str(type));
+ message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
+ HELPCTXID(errors_cantloadkey));
+ sfree(msg);
+ return;
+ }
+
+ char *algname = NULL;
+ char *comment = NULL;
+ const char *error = NULL;
+ strbuf *pub = strbuf_new();
+ if (!ppk_loadpub_f(filename, &algname, BinarySink_UPCAST(pub), &comment,
+ &error)) {
+ char *msg = dupprintf("Couldn't load certificate (%s)", error);
+ message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
+ HELPCTXID(errors_cantloadkey));
+ sfree(msg);
+ strbuf_free(pub);
+ return;
+ }
+
+ sfree(comment);
+
+ const ssh_keyalg *alg = find_pubkey_alg(algname);
+ if (!alg) {
+ char *msg = dupprintf("Couldn't load certificate (unsupported "
+ "algorithm name '%s')", algname);
+ message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
+ HELPCTXID(errors_cantloadkey));
+ sfree(msg);
+ sfree(algname);
+ strbuf_free(pub);
+ return;
+ }
+
+ sfree(algname);
+
+ /* Check the two public keys match apart from certificates */
+ strbuf *old_basepub = strbuf_new();
+ ssh_key_public_blob(ssh_key_base_key(state->ssh2key.key),
+ BinarySink_UPCAST(old_basepub));
+
+ ssh_key *new_pubkey = ssh_key_new_pub(alg, ptrlen_from_strbuf(pub));
+ strbuf *new_basepub = strbuf_new();
+ ssh_key_public_blob(ssh_key_base_key(new_pubkey),
+ BinarySink_UPCAST(new_basepub));
+ ssh_key_free(new_pubkey);
+
+ bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(old_basepub),
+ ptrlen_from_strbuf(new_basepub));
+ strbuf_free(old_basepub);
+ strbuf_free(new_basepub);
+
+ if (!match) {
+ char *msg = dupprintf("Certificate is for a different public key");
+ message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
+ HELPCTXID(errors_cantloadkey));
+ sfree(msg);
+ strbuf_free(pub);
+ return;
+ }
+
+ strbuf *priv = strbuf_new_nm();
+ ssh_key_private_blob(state->ssh2key.key, BinarySink_UPCAST(priv));
+ ssh_key *newkey = ssh_key_new_priv(
+ alg, ptrlen_from_strbuf(pub), ptrlen_from_strbuf(priv));
+ strbuf_free(pub);
+ strbuf_free(priv);
+
+ if (!newkey) {
+ char *msg = dupprintf("Couldn't combine certificate with key");
+ message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
+ HELPCTXID(errors_cantloadkey));
+ sfree(msg);
+ return;
+ }
+
+ ssh_key_free(state->ssh2key.key);
+ state->ssh2key.key = newkey;
+
+ update_ui_after_ssh2_pubkey_change(hwnd, state);
+ ui_set_state(hwnd, state, 2);
+}
+
+void remove_certificate(HWND hwnd, struct MainDlgState *state)
+{
+ ssh_key *newkey = ssh_key_clone(ssh_key_base_key(state->ssh2key.key));
+ ssh_key_free(state->ssh2key.key);
+ state->ssh2key.key = newkey;
+ update_ui_after_ssh2_pubkey_change(hwnd, state);
+ ui_set_state(hwnd, state, 2);
+}
+
+static void start_generating_key(HWND hwnd, struct MainDlgState *state)
+{
+ static const char generating_msg[] =
+ "Please wait while a key is generated...";
+
+ struct rsa_key_thread_params *params;
+ DWORD threadid;
+
+ SetDlgItemText(hwnd, IDC_GENERATING, generating_msg);
+ SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0,
+ MAKELPARAM(0, PROGRESSRANGE));
+ SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, 0, 0);
+
+ params = snew(struct rsa_key_thread_params);
+ params->progressbar = GetDlgItem(hwnd, IDC_PROGRESS);
+ params->dialog = hwnd;
+ params->key_bits = state->key_bits;
+ params->curve_bits = state->curve_bits;
+ params->keytype = state->keytype;
+ params->primepolicy = state->primepolicy;
+ params->rsa_strong = state->rsa_strong;
+ params->key = &state->key;
+ params->dsakey = &state->dsakey;
+
+ HANDLE hThread = CreateThread(NULL, 0, generate_key_thread,
+ params, 0, &threadid);
+ if (!hThread) {
+ MessageBox(hwnd, "Out of thread resources",
+ "Key generation error",
+ MB_OK | MB_ICONERROR);
+ sfree(params);
+ } else {
+ CloseHandle(hThread); /* we don't need the thread handle */
+ state->generation_thread_exists = true;
+ }
+}
+
+/*
+ * Dialog-box function and context structure for the 'Certificate
+ * info' button.
+ */
+struct certinfo_dialog_ctx {
+ SeatDialogText *text;
+};
+
+static INT_PTR CertInfoProc(HWND hwnd, UINT msg, WPARAM wParam,
+ LPARAM lParam, void *vctx)
+{
+ struct certinfo_dialog_ctx *ctx = (struct certinfo_dialog_ctx *)vctx;
+
+ switch (msg) {
+ case WM_INITDIALOG: {
+ int index = 100, y = 12;
+
+ WPARAM font = SendMessage(hwnd, WM_GETFONT, 0, 0);
+
+ const char *key = NULL;
+ for (SeatDialogTextItem *item = ctx->text->items,
+ *end = item + ctx->text->nitems; item < end; item++) {
+ switch (item->type) {
+ case SDT_MORE_INFO_KEY:
+ key = item->text;
+ break;
+ case SDT_MORE_INFO_VALUE_SHORT:
+ case SDT_MORE_INFO_VALUE_BLOB: {
+ RECT rk, rv;
+ DWORD editstyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP |
+ ES_AUTOHSCROLL | ES_READONLY;
+ if (item->type == SDT_MORE_INFO_VALUE_BLOB) {
+ rk.left = 12;
+ rk.right = 286;
+ rk.top = y;
+ rk.bottom = 8;
+ y += 10;
+
+ editstyle |= ES_MULTILINE;
+ rv.left = 12;
+ rv.right = 286;
+ rv.top = y;
+ rv.bottom = 64;
+ y += 68;
+ } else {
+ rk.left = 12;
+ rk.right = 130;
+ rk.top = y+2;
+ rk.bottom = 8;
+
+ rv.left = 150;
+ rv.right = 298;
+ rv.top = y;
+ rv.bottom = 12;
+
+ y += 16;
+ }
+
+ MapDialogRect(hwnd, &rk);
+ HWND ctl = CreateWindowEx(
+ 0, "STATIC", key, WS_CHILD | WS_VISIBLE,
+ rk.left, rk.top, rk.right, rk.bottom,
+ hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL);
+ SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0));
+
+ MapDialogRect(hwnd, &rv);
+ ctl = CreateWindowEx(
+ WS_EX_CLIENTEDGE, "EDIT", item->text, editstyle,
+ rv.left, rv.top, rv.right, rv.bottom,
+ hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL);
+ SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0));
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ /*
+ * Now resize the overall window, and move the Close button at
+ * the bottom.
+ */
+ RECT r;
+ r.left = 176;
+ r.top = y + 10;
+ r.right = r.bottom = 0;
+ MapDialogRect(hwnd, &r);
+ HWND ctl = GetDlgItem(hwnd, IDOK);
+ SetWindowPos(ctl, NULL, r.left, r.top, 0, 0,
+ SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER);
+
+ r.left = r.top = r.right = 0;
+ r.bottom = 300;
+ MapDialogRect(hwnd, &r);
+ int oldheight = r.bottom;
+
+ r.left = r.top = r.right = 0;
+ r.bottom = y + 30;
+ MapDialogRect(hwnd, &r);
+ int newheight = r.bottom;
+
+ GetWindowRect(hwnd, &r);
+
+ SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left,
+ r.bottom - r.top + newheight - oldheight,
+ SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
+
+ ShowWindow(hwnd, SW_SHOWNORMAL);
+ return 1;
+ }
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ ShinyEndDialog(hwnd, 0);
+ return 0;
+ }
+ return 0;
+ case WM_CLOSE:
+ ShinyEndDialog(hwnd, 0);
+ return 0;
+ }
+ return 0;
+}
+
+/*
+ * Dialog-box function for the main PuTTYgen dialog box.
+ */
+static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ const int DEMO_SCREENSHOT_TIMER_ID = 1230;
+ static const char entropy_msg[] =
+ "Please generate some randomness by moving the mouse over the blank area.";
+ struct MainDlgState *state;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ if (has_help())
+ SetWindowLongPtr(hwnd, GWL_EXSTYLE,
+ GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
+ WS_EX_CONTEXTHELP);
+ else {
+ /*
+ * If we add a Help button, this is where we destroy it
+ * if the help file isn't present.
+ */
+ }
+ SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
+ (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(200)));
+
+ state = snew(struct MainDlgState);
+ state->generation_thread_exists = false;
+ state->entropy = NULL;
+ state->key_exists = false;
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) state);
+ {
+ HMENU menu, menu1;
+
+ menu = CreateMenu();
+
+ menu1 = CreateMenu();
+ AppendMenu(menu1, MF_ENABLED, IDC_LOAD, "&Load private key");
+ AppendMenu(menu1, MF_ENABLED, IDC_SAVEPUB, "Save p&ublic key");
+ AppendMenu(menu1, MF_ENABLED, IDC_SAVE, "&Save private key");
+ AppendMenu(menu1, MF_SEPARATOR, 0, 0);
+ AppendMenu(menu1, MF_ENABLED, IDC_QUIT, "E&xit");
+ AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT_PTR) menu1, "&File");
+ state->filemenu = menu1;
+
+ menu1 = CreateMenu();
+ AppendMenu(menu1, MF_ENABLED, IDC_GENERATE, "&Generate key pair");
+ AppendMenu(menu1, MF_SEPARATOR, 0, 0);
+ AppendMenu(menu1, MF_ENABLED, IDC_ADDCERT,
+ "Add &certificate to key");
+ AppendMenu(menu1, MF_ENABLED, IDC_REMCERT,
+ "Remove certificate from key");
+ AppendMenu(menu1, MF_SEPARATOR, 0, 0);
+ AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH1, "SSH-&1 key (RSA)");
+ AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2RSA, "SSH-2 &RSA key");
+ AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2DSA, "SSH-2 &DSA key");
+ AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2ECDSA, "SSH-2 &ECDSA key");
+ AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2EDDSA, "SSH-2 EdD&SA key");
+ AppendMenu(menu1, MF_SEPARATOR, 0, 0);
+ AppendMenu(menu1, MF_ENABLED, IDC_PRIMEGEN_PROB,
+ "Use probable primes (fast)");
+ AppendMenu(menu1, MF_ENABLED, IDC_PRIMEGEN_MAURER_SIMPLE,
+ "Use proven primes (slower)");
+ AppendMenu(menu1, MF_ENABLED, IDC_PRIMEGEN_MAURER_COMPLEX,
+ "Use proven primes with even distribution (slowest)");
+ AppendMenu(menu1, MF_SEPARATOR, 0, 0);
+ AppendMenu(menu1, MF_ENABLED, IDC_RSA_STRONG,
+ "Use \"strong\" primes as RSA key factors");
+ AppendMenu(menu1, MF_SEPARATOR, 0, 0);
+ AppendMenu(menu1, MF_ENABLED, IDC_PPK_PARAMS,
+ "Parameters for saving key files...");
+ AppendMenu(menu1, MF_SEPARATOR, 0, 0);
+ AppendMenu(menu1, MF_ENABLED, IDC_FPTYPE_SHA256,
+ "Show fingerprint as SHA256");
+ AppendMenu(menu1, MF_ENABLED, IDC_FPTYPE_MD5,
+ "Show fingerprint as MD5");
+ AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT_PTR) menu1, "&Key");
+ state->keymenu = menu1;
+
+ menu1 = CreateMenu();
+ AppendMenu(menu1, MF_ENABLED, IDC_IMPORT, "&Import key");
+ AppendMenu(menu1, MF_SEPARATOR, 0, 0);
+ AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_OPENSSH_AUTO,
+ "Export &OpenSSH key");
+ AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_OPENSSH_NEW,
+ "Export &OpenSSH key (force new file format)");
+ AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_SSHCOM,
+ "Export &ssh.com key");
+ AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT_PTR) menu1,
+ "Con&versions");
+ state->cvtmenu = menu1;
+
+ menu1 = CreateMenu();
+ AppendMenu(menu1, MF_ENABLED, IDC_ABOUT, "&About");
+ if (has_help())
+ AppendMenu(menu1, MF_ENABLED, IDC_GIVEHELP, "&Help");
+ AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT_PTR) menu1, "&Help");
+
+ SetMenu(hwnd, menu);
+ }
+
+ /*
+ * Centre the window.
+ */
+ { /* centre the window */
+ RECT rs, rd;
+ HWND hw;
+
+ hw = GetDesktopWindow();
+ if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
+ MoveWindow(hwnd,
+ (rs.right + rs.left + rd.left - rd.right) / 2,
+ (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
+ rd.right - rd.left, rd.bottom - rd.top, true);
+ }
+
+ {
+ struct ctlpos cp, cp2;
+ int ymax;
+
+ /* Accelerators used: acglops1rbvde */
+
+ ctlposinit(&cp, hwnd, 4, 4, 4);
+ beginbox(&cp, "Key", IDC_BOX_KEY);
+ cp2 = cp;
+ statictext(&cp2, "No key.", 1, IDC_NOKEY);
+ cp2 = cp;
+ statictext(&cp2, "", 1, IDC_GENERATING);
+ progressbar(&cp2, IDC_PROGRESS);
+ cp2 = cp;
+ bigeditctrl(&cp2, NULL, -1, IDC_CERTSTATIC, 3);
+ {
+ HWND child = GetDlgItem(hwnd, IDC_CERTSTATIC);
+ LONG_PTR style = GetWindowLongPtr(child, GWL_STYLE);
+ style &= ~WS_VSCROLL;
+ SetWindowLongPtr(child, GWL_STYLE, style);
+ SendMessage(child, EM_SETREADONLY, true, 0);
+ }
+ MakeDlgItemBorderless(hwnd, IDC_CERTSTATIC);
+ cp2.xoff = cp2.width = cp2.width / 3;
+ button(&cp2, "Certificate info...", IDC_CERTMOREINFO, false);
+ bigeditctrl(&cp,
+ "&Public key for pasting into authorized_keys file:",
+ IDC_PKSTATIC, IDC_KEYDISPLAY, 5);
+ SendDlgItemMessage(hwnd, IDC_KEYDISPLAY, EM_SETREADONLY, 1, 0);
+ staticedit(&cp, "Key f&ingerprint:", IDC_FPSTATIC,
+ IDC_FINGERPRINT, 82);
+ SendDlgItemMessage(hwnd, IDC_FINGERPRINT, EM_SETREADONLY, 1,
+ 0);
+ staticedit(&cp, "Key &comment:", IDC_COMMENTSTATIC,
+ IDC_COMMENTEDIT, 82);
+ staticpassedit(&cp, "Key p&assphrase:", IDC_PASSPHRASE1STATIC,
+ IDC_PASSPHRASE1EDIT, 82);
+ staticpassedit(&cp, "C&onfirm passphrase:",
+ IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 82);
+ endbox(&cp);
+ beginbox(&cp, "Actions", IDC_BOX_ACTIONS);
+ staticbtn(&cp, "Generate a public/private key pair",
+ IDC_GENSTATIC, "&Generate", IDC_GENERATE);
+ staticbtn(&cp, "Load an existing private key file",
+ IDC_LOADSTATIC, "&Load", IDC_LOAD);
+ static2btn(&cp, "Save the generated key", IDC_SAVESTATIC,
+ "Save p&ublic key", IDC_SAVEPUB,
+ "&Save private key", IDC_SAVE);
+ endbox(&cp);
+ beginbox(&cp, "Parameters", IDC_BOX_PARAMS);
+ radioline(&cp, "Type of key to generate:", IDC_TYPESTATIC, 5,
+ "&RSA", IDC_KEYSSH2RSA,
+ "&DSA", IDC_KEYSSH2DSA,
+ "&ECDSA", IDC_KEYSSH2ECDSA,
+ "EdD&SA", IDC_KEYSSH2EDDSA,
+ "SSH-&1 (RSA)", IDC_KEYSSH1,
+ NULL);
+ cp2 = cp;
+ staticedit(&cp2, "Number of &bits in a generated key:",
+ IDC_BITSSTATIC, IDC_BITS, 20);
+ ymax = cp2.ypos;
+ cp2 = cp;
+ staticddl(&cp2, "Cur&ve to use for generating this key:",
+ IDC_ECCURVESTATIC, IDC_ECCURVE, 30);
+ SendDlgItemMessage(hwnd, IDC_ECCURVE, CB_RESETCONTENT, 0, 0);
+ {
+ int i, bits;
+ const struct ec_curve *curve;
+ const ssh_keyalg *alg;
+
+ for (i = 0; i < n_ec_nist_curve_lengths; i++) {
+ bits = ec_nist_curve_lengths[i];
+ ec_nist_alg_and_curve_by_bits(bits, &curve, &alg);
+ SendDlgItemMessage(hwnd, IDC_ECCURVE, CB_ADDSTRING, 0,
+ (LPARAM)curve->textname);
+ }
+ }
+ ymax = ymax > cp2.ypos ? ymax : cp2.ypos;
+ cp2 = cp;
+ staticddl(&cp2, "Cur&ve to use for generating this key:",
+ IDC_EDCURVESTATIC, IDC_EDCURVE, 30);
+ SendDlgItemMessage(hwnd, IDC_EDCURVE, CB_RESETCONTENT, 0, 0);
+ {
+ int i, bits;
+ const struct ec_curve *curve;
+ const ssh_keyalg *alg;
+
+ for (i = 0; i < n_ec_ed_curve_lengths; i++) {
+ bits = ec_ed_curve_lengths[i];
+ ec_ed_alg_and_curve_by_bits(bits, &curve, &alg);
+ char *desc = dupprintf("%s (%d bits)",
+ curve->textname, bits);
+ SendDlgItemMessage(hwnd, IDC_EDCURVE, CB_ADDSTRING, 0,
+ (LPARAM)desc);
+ sfree(desc);
+ }
+ }
+ ymax = ymax > cp2.ypos ? ymax : cp2.ypos;
+ cp2 = cp;
+ statictext(&cp2, "(nothing to configure for this key type)",
+ 1, IDC_NOTHINGSTATIC);
+ ymax = ymax > cp2.ypos ? ymax : cp2.ypos;
+ cp.ypos = ymax;
+ endbox(&cp);
+ }
+ struct InitialParams *params = (struct InitialParams *)lParam;
+ ui_set_key_type(hwnd, state, params->keybutton);
+ ui_set_primepolicy(hwnd, state, params->primepolicybutton);
+ ui_set_rsa_strong(hwnd, state, params->rsa_strong);
+ ui_set_fptype(hwnd, state, fptype_to_idc(params->fptype));
+ SetDlgItemInt(hwnd, IDC_BITS, params->keybits, false);
+ SendDlgItemMessage(hwnd, IDC_ECCURVE, CB_SETCURSEL,
+ params->eccurve_index, 0);
+ SendDlgItemMessage(hwnd, IDC_EDCURVE, CB_SETCURSEL,
+ params->edcurve_index, 0);
+
+ /*
+ * Initially, hide the progress bar and the key display,
+ * and show the no-key display. Also disable the Save
+ * buttons, because with no key we obviously can't save
+ * anything.
+ */
+ ui_set_state(hwnd, state, 0);
+
+ /*
+ * Load a key file if one was provided on the command line.
+ */
+ if (cmdline_keyfile) {
+ Filename *fn = filename_from_str(cmdline_keyfile);
+ load_key_file(hwnd, state, fn, false);
+ filename_free(fn);
+ } else if (cmdline_demo_keystr.ptr) {
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, cmdline_demo_keystr);
+ const char *errmsg;
+ ssh2_userkey *k = ppk_load_s(src, NULL, &errmsg);
+ assert(!errmsg);
+
+ update_ui_after_load(hwnd, state, "demo passphrase",
+ SSH_KEYTYPE_SSH2, NULL, k);
+
+ SetTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID, TICKSPERSEC, NULL);
+ }
+
+ return 1;
+ case WM_TIMER:
+ if ((UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) {
+ KillTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID);
+ char *err = save_screenshot(hwnd, demo_screenshot_filename);
+ if (err) {
+ MessageBox(hwnd, err, "Demo screenshot failure",
+ MB_OK | MB_ICONERROR);
+ sfree(err);
+ }
+ EndDialog(hwnd, 0);
+ }
+ return 0;
+ case WM_MOUSEMOVE:
+ state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (state->entropy && state->entropy_got < state->entropy_required) {
+ ULONG msgtime = GetMessageTime();
+ put_uint32(state->entropy, lParam);
+ put_uint32(state->entropy, msgtime);
+ if (msgtime - state->entropy_prev_msgtime > ENTROPY_RATE_LIMIT) {
+ state->entropy_got += 2;
+ state->entropy_prev_msgtime = msgtime;
+ }
+ SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS,
+ state->entropy_got, 0);
+ if (state->entropy_got >= state->entropy_required) {
+ /*
+ * Seed the entropy pool
+ */
+ random_reseed(ptrlen_from_strbuf(state->entropy));
+ strbuf_free(state->entropy);
+ state->entropy = NULL;
+
+ start_generating_key(hwnd, state);
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_KEYSSH1:
+ case IDC_KEYSSH2RSA:
+ case IDC_KEYSSH2DSA:
+ case IDC_KEYSSH2ECDSA:
+ case IDC_KEYSSH2EDDSA: {
+ state = (struct MainDlgState *)
+ GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ ui_set_key_type(hwnd, state, LOWORD(wParam));
+ break;
+ }
+ case IDC_PRIMEGEN_PROB:
+ case IDC_PRIMEGEN_MAURER_SIMPLE:
+ case IDC_PRIMEGEN_MAURER_COMPLEX: {
+ state = (struct MainDlgState *)
+ GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ ui_set_primepolicy(hwnd, state, LOWORD(wParam));
+ break;
+ }
+ case IDC_FPTYPE_SHA256:
+ case IDC_FPTYPE_MD5: {
+ state = (struct MainDlgState *)
+ GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ ui_set_fptype(hwnd, state, LOWORD(wParam));
+ break;
+ }
+ case IDC_RSA_STRONG: {
+ state = (struct MainDlgState *)
+ GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ ui_set_rsa_strong(hwnd, state, !state->rsa_strong);
+ break;
+ }
+ case IDC_PPK_PARAMS: {
+ struct PPKParams pp[1];
+ pp->params = save_params;
+ if (pp->params.argon2_passes_auto) {
+ pp->time_ms = pp->params.argon2_milliseconds;
+ pp->time_passes = 13;
+ } else {
+ pp->time_ms = 100;
+ pp->time_passes = pp->params.argon2_passes;
+ }
+ int dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(215),
+ NULL, PPKParamsProc, (LPARAM)pp);
+ if (dlgret) {
+ if (pp->params.argon2_passes_auto) {
+ pp->params.argon2_milliseconds = pp->time_ms;
+ } else {
+ pp->params.argon2_passes = pp->time_passes;
+ }
+ save_params = pp->params;
+ }
+ break;
+ }
+ case IDC_QUIT:
+ PostMessage(hwnd, WM_CLOSE, 0, 0);
+ break;
+ case IDC_COMMENTEDIT:
+ if (HIWORD(wParam) == EN_CHANGE) {
+ state = (struct MainDlgState *)
+ GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (state->key_exists) {
+ HWND editctl = GetDlgItem(hwnd, IDC_COMMENTEDIT);
+ int len = GetWindowTextLength(editctl);
+ if (*state->commentptr)
+ sfree(*state->commentptr);
+ *state->commentptr = snewn(len + 1, char);
+ GetWindowText(editctl, *state->commentptr, len + 1);
+ if (state->ssh2) {
+ setupbigedit2(hwnd, &state->ssh2key);
+ } else {
+ setupbigedit1(hwnd, &state->key);
+ }
+ }
+ }
+ break;
+ case IDC_ABOUT:
+ EnableWindow(hwnd, 0);
+ DialogBox(hinst, MAKEINTRESOURCE(213), hwnd, AboutProc);
+ EnableWindow(hwnd, 1);
+ SetActiveWindow(hwnd);
+ return 0;
+ case IDC_GIVEHELP:
+ if (HIWORD(wParam) == BN_CLICKED ||
+ HIWORD(wParam) == BN_DOUBLECLICKED) {
+ launch_help(hwnd, WINHELP_CTX_puttygen_general);
+ }
+ return 0;
+ case IDC_GENERATE:
+ if (HIWORD(wParam) != BN_CLICKED &&
+ HIWORD(wParam) != BN_DOUBLECLICKED)
+ break;
+ state =
+ (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (!state->generation_thread_exists) {
+ unsigned raw_entropy_required;
+ unsigned char *raw_entropy_buf;
+ BOOL ok;
+
+ state->key_bits = GetDlgItemInt(hwnd, IDC_BITS, &ok, false);
+ if (!ok)
+ state->key_bits = DEFAULT_KEY_BITS;
+ state->ssh2 = true;
+
+ if (IsDlgButtonChecked(hwnd, IDC_KEYSSH1)) {
+ state->ssh2 = false;
+ state->keytype = RSA;
+ } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2RSA)) {
+ state->keytype = RSA;
+ } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA)) {
+ state->keytype = DSA;
+ } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2ECDSA)) {
+ state->keytype = ECDSA;
+ int curveindex = SendDlgItemMessage(hwnd, IDC_ECCURVE,
+ CB_GETCURSEL, 0, 0);
+ assert(curveindex >= 0);
+ assert(curveindex < n_ec_nist_curve_lengths);
+ state->curve_bits = ec_nist_curve_lengths[curveindex];
+ } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2EDDSA)) {
+ state->keytype = EDDSA;
+ int curveindex = SendDlgItemMessage(hwnd, IDC_EDCURVE,
+ CB_GETCURSEL, 0, 0);
+ assert(curveindex >= 0);
+ assert(curveindex < n_ec_ed_curve_lengths);
+ state->curve_bits = ec_ed_curve_lengths[curveindex];
+ } else {
+ /* Somehow, no button was checked */
+ break;
+ }
+
+ if ((state->keytype == RSA || state->keytype == DSA) &&
+ state->key_bits < 256) {
+ char *message = dupprintf(
+ "PuTTYgen will not generate a key smaller than 256"
+ " bits.\nKey length reset to default %d. Continue?",
+ DEFAULT_KEY_BITS);
+ int ret = MessageBox(hwnd, message, "PuTTYgen Warning",
+ MB_ICONWARNING | MB_OKCANCEL);
+ sfree(message);
+ if (ret != IDOK)
+ break;
+ state->key_bits = DEFAULT_KEY_BITS;
+ SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, false);
+ } else if ((state->keytype == RSA || state->keytype == DSA) &&
+ state->key_bits < DEFAULT_KEY_BITS) {
+ char *message = dupprintf(
+ "Keys shorter than %d bits are not recommended. "
+ "Really generate this key?", DEFAULT_KEY_BITS);
+ int ret = MessageBox(hwnd, message, "PuTTYgen Warning",
+ MB_ICONWARNING | MB_OKCANCEL);
+ sfree(message);
+ if (ret != IDOK)
+ break;
+ }
+
+ if (state->keytype == RSA || state->keytype == DSA)
+ raw_entropy_required = (state->key_bits / 2) * 2;
+ else if (state->keytype == ECDSA || state->keytype == EDDSA)
+ raw_entropy_required = (state->curve_bits / 2) * 2;
+ else
+ unreachable("we must have initialised keytype by now");
+
+ /* Bound the entropy collection above by the amount of
+ * data we can actually fit into the PRNG. Any more
+ * than that and it's doing no more good. */
+ if (raw_entropy_required > random_seed_bits())
+ raw_entropy_required = random_seed_bits();
+
+ raw_entropy_buf = snewn(raw_entropy_required, unsigned char);
+ if (win_read_random(raw_entropy_buf, raw_entropy_required)) {
+ /*
+ * If we can get entropy from CryptGenRandom, use
+ * it. But CryptGenRandom isn't a kernel-level
+ * CPRNG (according to Wikipedia), and papers have
+ * been published cryptanalysing it. So we'll
+ * still do manual entropy collection; we'll just
+ * do it _as well_ as this.
+ */
+ random_reseed(
+ make_ptrlen(raw_entropy_buf, raw_entropy_required));
+ }
+
+ /*
+ * Manual entropy input, by making the user wave the
+ * mouse over the window a lot.
+ *
+ * My brief statistical tests on mouse movements
+ * suggest that there are about 2.5 bits of randomness
+ * in the x position, 2.5 in the y position, and 1.7
+ * in the message time, making 5.7 bits of
+ * unpredictability per mouse movement. However, other
+ * people have told me it's far less than that, so I'm
+ * going to be stupidly cautious and knock that down
+ * to a nice round 2. With this method, we require two
+ * words per mouse movement, so with 2 bits per mouse
+ * movement we expect 2 bits every 2 words, i.e. the
+ * number of _words_ of mouse data we want to collect
+ * is just the same as the number of _bits_ of entropy
+ * we want.
+ */
+ state->entropy_required = raw_entropy_required;
+
+ ui_set_state(hwnd, state, 1);
+ SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg);
+ state->key_exists = false;
+
+ state->entropy_got = 0;
+ state->entropy = strbuf_new_nm();
+ state->entropy_prev_msgtime = GetMessageTime();
+
+ SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0,
+ MAKELPARAM(0, state->entropy_required));
+ SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, 0, 0);
+
+ smemclr(raw_entropy_buf, raw_entropy_required);
+ sfree(raw_entropy_buf);
+ }
+ break;
+ case IDC_SAVE:
+ case IDC_EXPORT_OPENSSH_AUTO:
+ case IDC_EXPORT_OPENSSH_NEW:
+ case IDC_EXPORT_SSHCOM:
+ if (HIWORD(wParam) != BN_CLICKED)
+ break;
+ state =
+ (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (state->key_exists) {
+ char filename[FILENAME_MAX];
+ char *passphrase, *passphrase2;
+ int type, realtype;
+
+ if (state->ssh2)
+ realtype = SSH_KEYTYPE_SSH2;
+ else
+ realtype = SSH_KEYTYPE_SSH1;
+
+ if (LOWORD(wParam) == IDC_EXPORT_OPENSSH_AUTO)
+ type = SSH_KEYTYPE_OPENSSH_AUTO;
+ else if (LOWORD(wParam) == IDC_EXPORT_OPENSSH_NEW)
+ type = SSH_KEYTYPE_OPENSSH_NEW;
+ else if (LOWORD(wParam) == IDC_EXPORT_SSHCOM)
+ type = SSH_KEYTYPE_SSHCOM;
+ else
+ type = realtype;
+
+ if (type != realtype &&
+ import_target_type(type) != realtype) {
+ char msg[256];
+ sprintf(msg, "Cannot export an SSH-%d key in an SSH-%d"
+ " format", (state->ssh2 ? 2 : 1),
+ (state->ssh2 ? 1 : 2));
+ MessageBox(hwnd, msg,
+ "PuTTYgen Error", MB_OK | MB_ICONERROR);
+ break;
+ }
+
+ passphrase = GetDlgItemText_alloc(hwnd, IDC_PASSPHRASE1EDIT);
+ passphrase2 = GetDlgItemText_alloc(hwnd, IDC_PASSPHRASE2EDIT);
+ if (strcmp(passphrase, passphrase2)) {
+ MessageBox(hwnd,
+ "The two passphrases given do not match.",
+ "PuTTYgen Error", MB_OK | MB_ICONERROR);
+ burnstr(passphrase);
+ burnstr(passphrase2);
+ break;
+ }
+ burnstr(passphrase2);
+ if (!*passphrase) {
+ int ret;
+ ret = MessageBox(hwnd,
+ "Are you sure you want to save this key\n"
+ "without a passphrase to protect it?",
+ "PuTTYgen Warning",
+ MB_YESNO | MB_ICONWARNING);
+ if (ret != IDYES) {
+ burnstr(passphrase);
+ break;
+ }
+ }
+ if (prompt_keyfile(hwnd, "Save private key as:",
+ filename, true, (type == realtype))) {
+ int ret;
+ FILE *fp = fopen(filename, "r");
+ if (fp) {
+ char *buffer;
+ fclose(fp);
+ buffer = dupprintf("Overwrite existing file\n%s?",
+ filename);
+ ret = MessageBox(hwnd, buffer, "PuTTYgen Warning",
+ MB_YESNO | MB_ICONWARNING);
+ sfree(buffer);
+ if (ret != IDYES) {
+ burnstr(passphrase);
+ break;
+ }
+ }
+
+ if (state->ssh2) {
+ Filename *fn = filename_from_str(filename);
+ if (type != realtype)
+ ret = export_ssh2(fn, type, &state->ssh2key,
+ *passphrase ? passphrase : NULL);
+ else
+ ret = ppk_save_f(fn, &state->ssh2key,
+ *passphrase ? passphrase : NULL,
+ &save_params);
+ filename_free(fn);
+ } else {
+ Filename *fn = filename_from_str(filename);
+ if (type != realtype)
+ ret = export_ssh1(fn, type, &state->key,
+ *passphrase ? passphrase : NULL);
+ else
+ ret = rsa1_save_f(fn, &state->key,
+ *passphrase ? passphrase : NULL);
+ filename_free(fn);
+ }
+ if (ret <= 0) {
+ MessageBox(hwnd, "Unable to save key file",
+ "PuTTYgen Error", MB_OK | MB_ICONERROR);
+ }
+ }
+ burnstr(passphrase);
+ }
+ break;
+ case IDC_SAVEPUB:
+ if (HIWORD(wParam) != BN_CLICKED)
+ break;
+ state =
+ (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (state->key_exists) {
+ char filename[FILENAME_MAX];
+ if (prompt_keyfile(hwnd, "Save public key as:",
+ filename, true, false)) {
+ int ret;
+ FILE *fp = fopen(filename, "r");
+ if (fp) {
+ char *buffer;
+ fclose(fp);
+ buffer = dupprintf("Overwrite existing file\n%s?",
+ filename);
+ ret = MessageBox(hwnd, buffer, "PuTTYgen Warning",
+ MB_YESNO | MB_ICONWARNING);
+ sfree(buffer);
+ if (ret != IDYES)
+ break;
+ }
+ fp = fopen(filename, "w");
+ if (!fp) {
+ MessageBox(hwnd, "Unable to open key file",
+ "PuTTYgen Error", MB_OK | MB_ICONERROR);
+ } else {
+ if (state->ssh2) {
+ strbuf *blob = strbuf_new();
+ ssh_key_public_blob(
+ state->ssh2key.key, BinarySink_UPCAST(blob));
+ ssh2_write_pubkey(fp, state->ssh2key.comment,
+ blob->u, blob->len,
+ SSH_KEYTYPE_SSH2_PUBLIC_RFC4716);
+ strbuf_free(blob);
+ } else {
+ ssh1_write_pubkey(fp, &state->key);
+ }
+ if (fclose(fp) < 0) {
+ MessageBox(hwnd, "Unable to save key file",
+ "PuTTYgen Error", MB_OK | MB_ICONERROR);
+ }
+ }
+ }
+ }
+ break;
+ case IDC_LOAD:
+ case IDC_IMPORT:
+ if (HIWORD(wParam) != BN_CLICKED)
+ break;
+ state =
+ (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (!state->generation_thread_exists) {
+ char filename[FILENAME_MAX];
+ if (prompt_keyfile(hwnd, "Load private key:", filename, false,
+ LOWORD(wParam) == IDC_LOAD)) {
+ Filename *fn = filename_from_str(filename);
+ load_key_file(hwnd, state, fn, LOWORD(wParam) != IDC_LOAD);
+ filename_free(fn);
+ }
+ }
+ break;
+ case IDC_ADDCERT:
+ if (HIWORD(wParam) != BN_CLICKED)
+ break;
+ state =
+ (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (state->key_exists && !state->generation_thread_exists) {
+ char filename[FILENAME_MAX];
+ if (prompt_keyfile(hwnd, "Load certificate:", filename, false,
+ false)) {
+ Filename *fn = filename_from_str(filename);
+ add_certificate(hwnd, state, fn);
+ filename_free(fn);
+ }
+ }
+ break;
+ case IDC_REMCERT:
+ if (HIWORD(wParam) != BN_CLICKED)
+ break;
+ state =
+ (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (state->key_exists && !state->generation_thread_exists) {
+ remove_certificate(hwnd, state);
+ }
+ break;
+ case IDC_CERTMOREINFO: {
+ if (HIWORD(wParam) != BN_CLICKED)
+ break;
+ state =
+ (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (!state->key_exists || !state->ssh2 || !state->ssh2key.key)
+ break;
+ if (!ssh_key_alg(state->ssh2key.key)->is_certificate)
+ break;
+
+ struct certinfo_dialog_ctx ctx[1];
+ ctx->text = ssh_key_cert_info(state->ssh2key.key);
+ ShinyDialogBox(hinst, MAKEINTRESOURCE(216),
+ "PuTTYgenCertInfo", hwnd, CertInfoProc, ctx);
+ seat_dialog_text_free(ctx->text);
+ break;
+ }
+ }
+ return 0;
+ case WM_DONEKEY:
+ state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ state->generation_thread_exists = false;
+ state->key_exists = true;
+ SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0,
+ MAKELPARAM(0, PROGRESSRANGE));
+ SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, PROGRESSRANGE, 0);
+ if (state->ssh2) {
+ if (state->keytype == DSA) {
+ state->ssh2key.key = &state->dsakey.sshk;
+ } else if (state->keytype == ECDSA) {
+ state->ssh2key.key = &state->eckey.sshk;
+ } else if (state->keytype == EDDSA) {
+ state->ssh2key.key = &state->edkey.sshk;
+ } else {
+ state->ssh2key.key = &state->key.sshk;
+ }
+ state->commentptr = &state->ssh2key.comment;
+ } else {
+ state->commentptr = &state->key.comment;
+ }
+ /*
+ * Invent a comment for the key. We'll do this by including
+ * the date in it. This will be so horrifyingly ugly that
+ * the user will immediately want to change it, which is
+ * what we want :-)
+ */
+ *state->commentptr = snewn(30, char);
+ {
+ struct tm tm;
+ tm = ltime();
+ if (state->keytype == DSA)
+ strftime(*state->commentptr, 30, "dsa-key-%Y%m%d", &tm);
+ else if (state->keytype == ECDSA)
+ strftime(*state->commentptr, 30, "ecdsa-key-%Y%m%d", &tm);
+ else if (state->keytype == EDDSA)
+ strftime(*state->commentptr, 30, "eddsa-key-%Y%m%d", &tm);
+ else
+ strftime(*state->commentptr, 30, "rsa-key-%Y%m%d", &tm);
+ }
+
+ /*
+ * Now update the key controls with all the key data.
+ */
+ {
+ char *fp, *savecomment;
+ /*
+ * Blank passphrase, initially. This isn't dangerous,
+ * because we will warn (Are You Sure?) before allowing
+ * the user to save an unprotected private key.
+ */
+ SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT, "");
+ SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT, "");
+ /*
+ * Set the comment.
+ */
+ SetDlgItemText(hwnd, IDC_COMMENTEDIT, *state->commentptr);
+ /*
+ * Set the key fingerprint.
+ */
+ savecomment = *state->commentptr;
+ *state->commentptr = NULL;
+ if (state->ssh2)
+ fp = ssh2_fingerprint(state->ssh2key.key, state->fptype);
+ else
+ fp = rsa_ssh1_fingerprint(&state->key);
+ SetDlgItemText(hwnd, IDC_FINGERPRINT, fp);
+ sfree(fp);
+ *state->commentptr = savecomment;
+ /*
+ * Construct a decimal representation of the key, for
+ * pasting into .ssh/authorized_keys or
+ * .ssh/authorized_keys2 on a Unix box.
+ */
+ if (state->ssh2) {
+ setupbigedit2(hwnd, &state->ssh2key);
+ } else {
+ setupbigedit1(hwnd, &state->key);
+ }
+ }
+ /*
+ * Finally, hide the progress bar and show the key data.
+ */
+ ui_set_state(hwnd, state, 2);
+ break;
+ case WM_HELP: {
+ int id = ((LPHELPINFO)lParam)->iCtrlId;
+ const char *topic = NULL;
+ switch (id) {
+ case IDC_GENERATING:
+ case IDC_PROGRESS:
+ case IDC_GENSTATIC:
+ case IDC_GENERATE:
+ topic = WINHELP_CTX_puttygen_generate; break;
+ case IDC_PKSTATIC:
+ case IDC_KEYDISPLAY:
+ topic = WINHELP_CTX_puttygen_pastekey; break;
+ case IDC_FPSTATIC:
+ case IDC_FINGERPRINT:
+ topic = WINHELP_CTX_puttygen_fingerprint; break;
+ case IDC_COMMENTSTATIC:
+ case IDC_COMMENTEDIT:
+ topic = WINHELP_CTX_puttygen_comment; break;
+ case IDC_PASSPHRASE1STATIC:
+ case IDC_PASSPHRASE1EDIT:
+ case IDC_PASSPHRASE2STATIC:
+ case IDC_PASSPHRASE2EDIT:
+ topic = WINHELP_CTX_puttygen_passphrase; break;
+ case IDC_LOADSTATIC:
+ case IDC_LOAD:
+ topic = WINHELP_CTX_puttygen_load; break;
+ case IDC_SAVESTATIC:
+ case IDC_SAVE:
+ topic = WINHELP_CTX_puttygen_savepriv; break;
+ case IDC_SAVEPUB:
+ topic = WINHELP_CTX_puttygen_savepub; break;
+ case IDC_TYPESTATIC:
+ case IDC_KEYSSH1:
+ case IDC_KEYSSH2RSA:
+ case IDC_KEYSSH2DSA:
+ case IDC_KEYSSH2ECDSA:
+ case IDC_KEYSSH2EDDSA:
+ topic = WINHELP_CTX_puttygen_keytype; break;
+ case IDC_BITSSTATIC:
+ case IDC_BITS:
+ topic = WINHELP_CTX_puttygen_bits; break;
+ case IDC_IMPORT:
+ case IDC_EXPORT_OPENSSH_AUTO:
+ case IDC_EXPORT_OPENSSH_NEW:
+ case IDC_EXPORT_SSHCOM:
+ topic = WINHELP_CTX_puttygen_conversions; break;
+ }
+ if (topic) {
+ launch_help(hwnd, topic);
+ } else {
+ MessageBeep(0);
+ }
+ break;
+ }
+ case WM_CLOSE:
+ state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ sfree(state);
+ quit_help(hwnd);
+ EndDialog(hwnd, 1);
+ return 0;
+ }
+ return 0;
+}
+
+void cleanup_exit(int code)
+{
+ shutdown_help();
+ exit(code);
+}
+
+HINSTANCE hinst;
+
+static NORETURN void opt_error(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ char *msg = dupvprintf(fmt, ap);
+ va_end(ap);
+
+ MessageBox(NULL, msg, "PuTTYgen command line error", MB_ICONERROR | MB_OK);
+
+ exit(1);
+}
+
+int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
+{
+ int argc;
+ char **argv;
+ int ret;
+ struct InitialParams params[1];
+
+ dll_hijacking_protection();
+
+ init_common_controls();
+ hinst = inst;
+
+ /*
+ * See if we can find our Help file.
+ */
+ init_help();
+
+ params->keybutton = IDC_KEYSSH2RSA;
+ params->primepolicybutton = IDC_PRIMEGEN_PROB;
+ params->rsa_strong = false;
+ params->fptype = SSH_FPTYPE_DEFAULT;
+ params->keybits = DEFAULT_KEY_BITS;
+ params->eccurve_index = DEFAULT_ECCURVE_INDEX;
+ params->edcurve_index = DEFAULT_EDCURVE_INDEX;
+
+ save_params = ppk_save_default_parameters;
+
+ split_into_argv(cmdline, &argc, &argv, NULL);
+
+ int argbits = -1;
+ AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error);
+ while (!aux_match_done(&amo)) {
+ char *val;
+ #define match_opt(...) aux_match_opt( \
+ &amo, NULL, __VA_ARGS__, (const char *)NULL)
+ #define match_optval(...) aux_match_opt( \
+ &amo, &val, __VA_ARGS__, (const char *)NULL)
+
+ if (aux_match_arg(&amo, &val)) {
+ if (!cmdline_keyfile) {
+ /*
+ * Assume the first argument to be a private key file, and
+ * attempt to load it.
+ */
+ cmdline_keyfile = val;
+ continue;
+ } else {
+ opt_error("unexpected extra argument '%s'\n", val);
+ }
+ } else if (match_opt("-pgpfp")) {
+ pgp_fingerprints_msgbox(NULL);
+ return 1;
+ } else if (match_opt("-restrict-acl", "-restrict_acl",
+ "-restrictacl")) {
+ restrict_process_acl();
+ } else if (match_optval("-t")) {
+ if (!strcmp(val, "rsa") || !strcmp(val, "rsa2")) {
+ params->keybutton = IDC_KEYSSH2RSA;
+ } else if (!strcmp(val, "rsa1")) {
+ params->keybutton = IDC_KEYSSH1;
+ } else if (!strcmp(val, "dsa") || !strcmp(val, "dss")) {
+ params->keybutton = IDC_KEYSSH2DSA;
+ } else if (!strcmp(val, "ecdsa")) {
+ params->keybutton = IDC_KEYSSH2ECDSA;
+ } else if (!strcmp(val, "eddsa")) {
+ params->keybutton = IDC_KEYSSH2EDDSA;
+ } else if (!strcmp(val, "ed25519")) {
+ params->keybutton = IDC_KEYSSH2EDDSA;
+ argbits = 255;
+ } else if (!strcmp(val, "ed448")) {
+ params->keybutton = IDC_KEYSSH2EDDSA;
+ argbits = 448;
+ } else {
+ opt_error("unknown key type '%s'\n", val);
+ }
+ } else if (match_optval("-b")) {
+ argbits = atoi(val);
+ } else if (match_optval("-E")) {
+ if (!strcmp(val, "md5"))
+ params->fptype = SSH_FPTYPE_MD5;
+ else if (!strcmp(val, "sha256"))
+ params->fptype = SSH_FPTYPE_SHA256;
+ else
+ opt_error("unknown fingerprint type '%s'\n", val);
+ } else if (match_optval("-primes")) {
+ if (!strcmp(val, "probable") ||
+ !strcmp(val, "probabilistic")) {
+ params->primepolicybutton = IDC_PRIMEGEN_PROB;
+ } else if (!strcmp(val, "provable") ||
+ !strcmp(val, "proven") ||
+ !strcmp(val, "simple") ||
+ !strcmp(val, "maurer-simple")) {
+ params->primepolicybutton = IDC_PRIMEGEN_MAURER_SIMPLE;
+ } else if (!strcmp(val, "provable-even") ||
+ !strcmp(val, "proven-even") ||
+ !strcmp(val, "even") ||
+ !strcmp(val, "complex") ||
+ !strcmp(val, "maurer-complex")) {
+ params->primepolicybutton = IDC_PRIMEGEN_MAURER_COMPLEX;
+ } else {
+ opt_error("unrecognised prime-generation mode '%s'\n", val);
+ }
+ } else if (match_opt("-strong-rsa")) {
+ params->rsa_strong = true;
+ } else if (match_optval("-ppk-param", "-ppk-params")) {
+ char *nextval;
+ for (; val; val = nextval) {
+ nextval = strchr(val, ',');
+ if (nextval)
+ *nextval++ = '\0';
+
+ char *optvalue = strchr(val, '=');
+ if (!optvalue)
+ opt_error("PPK parameter '%s' expected a value\n", val);
+ *optvalue++ = '\0';
+
+ /* Non-numeric options */
+ if (!strcmp(val, "kdf")) {
+ if (!strcmp(optvalue, "Argon2id") ||
+ !strcmp(optvalue, "argon2id")) {
+ save_params.argon2_flavour = Argon2id;
+ } else if (!strcmp(optvalue, "Argon2i") ||
+ !strcmp(optvalue, "argon2i")) {
+ save_params.argon2_flavour = Argon2i;
+ } else if (!strcmp(optvalue, "Argon2d") ||
+ !strcmp(optvalue, "argon2d")) {
+ save_params.argon2_flavour = Argon2d;
+ } else {
+ opt_error("unrecognised kdf '%s'\n", optvalue);
+ }
+ continue;
+ }
+
+ char *end;
+ unsigned long n = strtoul(optvalue, &end, 0);
+ if (!*optvalue || *end)
+ opt_error("value '%s' for PPK parameter '%s': expected a "
+ "number\n", optvalue, val);
+
+ if (!strcmp(val, "version")) {
+ save_params.fmt_version = n;
+ } else if (!strcmp(val, "memory") ||
+ !strcmp(val, "mem")) {
+ save_params.argon2_mem = n;
+ } else if (!strcmp(val, "time")) {
+ save_params.argon2_passes_auto = true;
+ save_params.argon2_milliseconds = n;
+ } else if (!strcmp(val, "passes")) {
+ save_params.argon2_passes_auto = false;
+ save_params.argon2_passes = n;
+ } else if (!strcmp(val, "parallelism") ||
+ !strcmp(val, "parallel")) {
+ save_params.argon2_parallelism = n;
+ } else {
+ opt_error("unrecognised PPK parameter '%s'\n", val);
+ }
+ }
+ } else if (match_optval("-demo-screenshot")) {
+ demo_screenshot_filename = val;
+ cmdline_demo_keystr = PTRLEN_LITERAL(
+ "PuTTY-User-Key-File-3: ssh-ed25519\n"
+ "Encryption: none\n"
+ "Comment: ed25519-key-20220402\n"
+ "Public-Lines: 2\n"
+ "AAAAC3NzaC1lZDI1NTE5AAAAILzuIFwZ"
+ "8ZhgOlilcSb+9zPuCf/DmKJiloVlmWGy\n"
+ "xa/F\n"
+ "Private-Lines: 1\n"
+ "AAAAIPca6vLwtB2NJhZUpABQISR0gcQH8jjQLta19VyzA3wc\n"
+ "Private-MAC: 1159e9628259b35933b397379bbe8a14"
+ "a1f1d97fe91e446e45a9581a3408b70e\n");
+ params->keybutton = IDC_KEYSSH2EDDSA;
+ argbits = 255;
+ } else {
+ opt_error("unrecognised option '%s'\n", amo.argv[amo.index]);
+ }
+ }
+
+ /* Translate argbits into eccurve_index and edcurve_index */
+ if (argbits > 0) {
+ switch (params->keybutton) {
+ case IDC_KEYSSH2RSA:
+ case IDC_KEYSSH1:
+ case IDC_KEYSSH2DSA:
+ params->keybits = argbits;
+ break;
+ case IDC_KEYSSH2ECDSA: {
+ bool found = false;
+ for (int j = 0; j < n_ec_nist_curve_lengths; j++)
+ if (argbits == ec_nist_curve_lengths[j]) {
+ params->eccurve_index = j;
+ found = true;
+ break;
+ }
+ if (!found)
+ opt_error("unsupported ECDSA bit length %d", argbits);
+ break;
+ }
+ case IDC_KEYSSH2EDDSA: {
+ bool found = false;
+ for (int j = 0; j < n_ec_ed_curve_lengths; j++)
+ if (argbits == ec_ed_curve_lengths[j]) {
+ params->edcurve_index = j;
+ found = true;
+ break;
+ }
+ if (!found)
+ opt_error("unsupported EDDSA bit length %d", argbits);
+ break;
+ }
+ }
+ }
+
+ random_setup_special();
+ ret = DialogBoxParam(hinst, MAKEINTRESOURCE(201), NULL, MainDlgProc,
+ (LPARAM)params) != IDOK;
+
+ cleanup_exit(ret);
+ return ret; /* just in case optimiser complains */
+}
diff --git a/windows/puttygen.rc b/windows/puttygen.rc
index b910b6a3..fbe14fc3 100644
--- a/windows/puttygen.rc
+++ b/windows/puttygen.rc
@@ -7,7 +7,7 @@
#define APPNAME "PuTTYgen"
#define APPDESC "PuTTY SSH key generation utility"
-#include "winhelp.rc2"
+#include "help.rc2"
#include "puttygen-rc.h"
200 ICON "puttygen.ico"
@@ -82,6 +82,16 @@ BEGIN
PUSHBUTTON "&Cancel", IDCANCEL, 134, 80, 40, 14
END
+/* Accelerators used: clw */
+216 DIALOG DISCARDABLE 140, 40, 450, 300
+STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+CAPTION "PuTTYgen: certificate information"
+FONT 8, "MS Shell Dlg"
+CLASS "PuTTYgenCertInfo"
+BEGIN
+ DEFPUSHBUTTON "&Close", IDOK, 201, 130, 48, 14
+END
+
#include "version.rc2"
#ifndef NO_MANIFESTS
diff --git a/windows/puttytel.rc b/windows/puttytel.rc
index 259bc683..41767a10 100644
--- a/windows/puttytel.rc
+++ b/windows/puttytel.rc
@@ -1,10 +1,14 @@
#include "rcstuff.h"
+#include "putty-rc.h"
#define APPNAME "PuTTYtel"
#define APPDESC "Telnet and Rlogin client"
-#include "winhelp.rc2"
-#include "win_res.rc2"
+IDI_MAINICON ICON "putty.ico"
+IDI_CFGICON ICON "puttycfg.ico"
+
+#include "help.rc2"
+#include "putty-common.rc2"
#ifndef NO_MANIFESTS
1 RT_MANIFEST "puttytel.mft"
diff --git a/windows/rcstuff.h b/windows/rcstuff.h
index ee2c7696..dbace3f5 100644
--- a/windows/rcstuff.h
+++ b/windows/rcstuff.h
@@ -5,20 +5,15 @@
#ifndef PUTTY_RCSTUFF_H
#define PUTTY_RCSTUFF_H
-#ifdef __LCC__
-#include <win.h>
-#else
+#ifdef HAVE_CMAKE_H
+#include "cmake.h"
+#endif
-/* Some compilers don't have winresrc.h */
-#ifndef NO_WINRESRC_H
-#ifndef MSVC4
+#if HAVE_WINRESRC_H
#include <winresrc.h>
-#else
+#elif HAVE_WINRES_H
#include <winres.h>
#endif
-#endif
-
-#endif /* end #ifdef __LCC__ */
/* Some systems don't define this, so I do it myself if necessary */
#ifndef TCS_MULTILINE
diff --git a/windows/security-api.h b/windows/security-api.h
new file mode 100644
index 00000000..175486ef
--- /dev/null
+++ b/windows/security-api.h
@@ -0,0 +1,49 @@
+/*
+ * security-api.h: some miscellaneous security-related helper functions,
+ * defined in utils/security.c, that use the advapi32 library. Also
+ * centralises the machinery for dynamically loading that library.
+ */
+
+#include <aclapi.h>
+
+/*
+ * Functions loaded from advapi32.dll.
+ */
+DECL_WINDOWS_FUNCTION(extern, BOOL, OpenProcessToken,
+ (HANDLE, DWORD, PHANDLE));
+DECL_WINDOWS_FUNCTION(extern, BOOL, GetTokenInformation,
+ (HANDLE, TOKEN_INFORMATION_CLASS,
+ LPVOID, DWORD, PDWORD));
+DECL_WINDOWS_FUNCTION(extern, BOOL, InitializeSecurityDescriptor,
+ (PSECURITY_DESCRIPTOR, DWORD));
+DECL_WINDOWS_FUNCTION(extern, BOOL, SetSecurityDescriptorOwner,
+ (PSECURITY_DESCRIPTOR, PSID, BOOL));
+DECL_WINDOWS_FUNCTION(extern, DWORD, GetSecurityInfo,
+ (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
+ PSID *, PSID *, PACL *, PACL *,
+ PSECURITY_DESCRIPTOR *));
+DECL_WINDOWS_FUNCTION(extern, DWORD, SetSecurityInfo,
+ (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
+ PSID, PSID, PACL, PACL));
+DECL_WINDOWS_FUNCTION(extern, DWORD, SetEntriesInAclA,
+ (ULONG, PEXPLICIT_ACCESS, PACL, PACL *));
+bool got_advapi(void);
+
+/*
+ * Find the SID describing the current user. The return value (if not
+ * NULL for some error-related reason) is smalloced.
+ */
+PSID get_user_sid(void);
+
+/*
+ * Construct a PSECURITY_DESCRIPTOR of the type used for named pipe
+ * servers, i.e. allowing access only to the current user id and also
+ * only local (i.e. not over SMB) connections.
+ *
+ * If this function returns true, then 'psd' and 'acl' will have been
+ * filled in with memory allocated using LocalAlloc (and hence must be
+ * freed later using LocalFree). If it returns false, then instead
+ * 'error' has been filled with a dynamically allocated error message.
+ */
+bool make_private_security_descriptor(
+ DWORD permissions, PSECURITY_DESCRIPTOR *psd, PACL *acl, char **error);
diff --git a/windows/select-cli.c b/windows/select-cli.c
new file mode 100644
index 00000000..9bf8411e
--- /dev/null
+++ b/windows/select-cli.c
@@ -0,0 +1,78 @@
+/*
+ * Implementation of do_select() for network.c to use, suitable for use
+ * when there's no GUI window to have network activity reported to.
+ *
+ * It uses WSAEventSelect, where available, to convert network
+ * activity into activity on an event object, for integration into an
+ * event loop that includes WaitForMultipleObjects.
+ *
+ * It also maintains a list of currently active sockets, which can be
+ * retrieved by a front end that wants to use WinSock's synchronous
+ * select() function.
+ */
+
+#include "putty.h"
+
+static tree234 *winselcli_sockets;
+
+static int socket_cmp(void *av, void *bv)
+{
+ return memcmp(av, bv, sizeof(SOCKET));
+}
+
+HANDLE winselcli_event = INVALID_HANDLE_VALUE;
+
+void winselcli_setup(void)
+{
+ if (!winselcli_sockets)
+ winselcli_sockets = newtree234(socket_cmp);
+
+ if (p_WSAEventSelect && winselcli_event == INVALID_HANDLE_VALUE)
+ winselcli_event = CreateEvent(NULL, false, false, NULL);
+}
+
+SOCKET winselcli_unique_socket(void)
+{
+ if (!winselcli_sockets)
+ return INVALID_SOCKET;
+
+ assert(count234(winselcli_sockets) <= 1);
+
+ SOCKET *p = index234(winselcli_sockets, 0);
+ if (!p)
+ return INVALID_SOCKET;
+
+ return *p;
+}
+
+const char *do_select(SOCKET skt, bool enable)
+{
+ /* Check everything's been set up, for convenience of callers. */
+ winselcli_setup();
+
+ if (enable) {
+ SOCKET *ptr = snew(SOCKET);
+ *ptr = skt;
+ if (add234(winselcli_sockets, ptr) != ptr)
+ sfree(ptr); /* already there */
+ } else {
+ SOCKET *ptr = del234(winselcli_sockets, &skt);
+ if (ptr)
+ sfree(ptr);
+ }
+
+ if (p_WSAEventSelect) {
+ int events;
+ if (enable) {
+ events = (FD_CONNECT | FD_READ | FD_WRITE |
+ FD_OOB | FD_CLOSE | FD_ACCEPT);
+ } else {
+ events = 0;
+ }
+
+ if (p_WSAEventSelect(skt, winselcli_event, events) == SOCKET_ERROR)
+ return winsock_error_string(p_WSAGetLastError());
+ }
+
+ return NULL;
+}
diff --git a/windows/select-gui.c b/windows/select-gui.c
new file mode 100644
index 00000000..4c37c345
--- /dev/null
+++ b/windows/select-gui.c
@@ -0,0 +1,65 @@
+/*
+ * Implementation of do_select() for network.c to use, that uses
+ * WSAAsyncSelect to convert network activity into window messages,
+ * for integration into a GUI event loop.
+ */
+
+#include "putty.h"
+
+static HWND winsel_hwnd = NULL;
+
+void winselgui_set_hwnd(HWND hwnd)
+{
+ winsel_hwnd = hwnd;
+}
+
+void winselgui_clear_hwnd(void)
+{
+ winsel_hwnd = NULL;
+}
+
+const char *do_select(SOCKET skt, bool enable)
+{
+ int msg, events;
+ if (enable) {
+ msg = WM_NETEVENT;
+ events = (FD_CONNECT | FD_READ | FD_WRITE |
+ FD_OOB | FD_CLOSE | FD_ACCEPT);
+ } else {
+ msg = events = 0;
+ }
+
+ assert(winsel_hwnd);
+
+ if (p_WSAAsyncSelect(skt, winsel_hwnd, msg, events) == SOCKET_ERROR)
+ return winsock_error_string(p_WSAGetLastError());
+
+ return NULL;
+}
+
+struct wm_netevent_params {
+ /* Used to pass data to wm_netevent_callback */
+ WPARAM wParam;
+ LPARAM lParam;
+};
+
+static void wm_netevent_callback(void *vctx)
+{
+ struct wm_netevent_params *params = (struct wm_netevent_params *)vctx;
+ select_result(params->wParam, params->lParam);
+ sfree(params);
+}
+
+void winselgui_response(WPARAM wParam, LPARAM lParam)
+{
+ /*
+ * To protect against re-entrancy when Windows's recv()
+ * immediately triggers a new WSAAsyncSelect window message, we
+ * don't call select_result directly from this handler but instead
+ * wait until we're back out at the top level of the message loop.
+ */
+ struct wm_netevent_params *params = snew(struct wm_netevent_params);
+ params->wParam = wParam;
+ params->lParam = lParam;
+ queue_toplevel_callback(wm_netevent_callback, params);
+}
diff --git a/windows/serial.c b/windows/serial.c
new file mode 100644
index 00000000..14a89822
--- /dev/null
+++ b/windows/serial.c
@@ -0,0 +1,468 @@
+/*
+ * Serial back end (Windows-specific).
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#include "putty.h"
+
+#define SERIAL_MAX_BACKLOG 4096
+
+typedef struct Serial Serial;
+struct Serial {
+ HANDLE port;
+ struct handle *out, *in;
+ Seat *seat;
+ LogContext *logctx;
+ int bufsize;
+ long clearbreak_time;
+ bool break_in_progress;
+ Backend backend;
+};
+
+static void serial_terminate(Serial *serial)
+{
+ if (serial->out) {
+ handle_free(serial->out);
+ serial->out = NULL;
+ }
+ if (serial->in) {
+ handle_free(serial->in);
+ serial->in = NULL;
+ }
+ if (serial->port != INVALID_HANDLE_VALUE) {
+ if (serial->break_in_progress)
+ ClearCommBreak(serial->port);
+ CloseHandle(serial->port);
+ serial->port = INVALID_HANDLE_VALUE;
+ }
+}
+
+static size_t serial_gotdata(
+ struct handle *h, const void *data, size_t len, int err)
+{
+ Serial *serial = (Serial *)handle_get_privdata(h);
+ if (err || len == 0) {
+ const char *error_msg;
+
+ /*
+ * Currently, len==0 should never happen because we're
+ * ignoring EOFs. However, it seems not totally impossible
+ * that this same back end might be usable to talk to named
+ * pipes or some other non-serial device, in which case EOF
+ * may become meaningful here.
+ */
+ if (!err)
+ error_msg = "End of file reading from serial device";
+ else
+ error_msg = "Error reading from serial device";
+
+ serial_terminate(serial);
+
+ seat_notify_remote_exit(serial->seat);
+
+ logevent(serial->logctx, error_msg);
+
+ seat_connection_fatal(serial->seat, "%s", error_msg);
+
+ return 0;
+ } else {
+ return seat_stdout(serial->seat, data, len);
+ }
+}
+
+static void serial_sentdata(struct handle *h, size_t new_backlog, int err,
+ bool close)
+{
+ Serial *serial = (Serial *)handle_get_privdata(h);
+ if (err) {
+ const char *error_msg = "Error writing to serial device";
+
+ serial_terminate(serial);
+
+ seat_notify_remote_exit(serial->seat);
+
+ logevent(serial->logctx, error_msg);
+
+ seat_connection_fatal(serial->seat, "%s", error_msg);
+ } else {
+ serial->bufsize = new_backlog;
+ seat_sent(serial->seat, serial->bufsize);
+ }
+}
+
+static char *serial_configure(Serial *serial, HANDLE serport, Conf *conf)
+{
+ DCB dcb;
+ COMMTIMEOUTS timeouts;
+
+ /*
+ * Set up the serial port parameters. If we can't even
+ * GetCommState, we ignore the problem on the grounds that the
+ * user might have pointed us at some other type of two-way
+ * device instead of a serial port.
+ */
+ if (GetCommState(serport, &dcb)) {
+ const char *str;
+
+ /*
+ * Boilerplate.
+ */
+ dcb.fBinary = true;
+ dcb.fDtrControl = DTR_CONTROL_ENABLE;
+ dcb.fDsrSensitivity = false;
+ dcb.fTXContinueOnXoff = false;
+ dcb.fOutX = false;
+ dcb.fInX = false;
+ dcb.fErrorChar = false;
+ dcb.fNull = false;
+ dcb.fRtsControl = RTS_CONTROL_ENABLE;
+ dcb.fAbortOnError = false;
+ dcb.fOutxCtsFlow = false;
+ dcb.fOutxDsrFlow = false;
+
+ /*
+ * Configurable parameters.
+ */
+ dcb.BaudRate = conf_get_int(conf, CONF_serspeed);
+ logeventf(serial->logctx, "Configuring baud rate %lu",
+ (unsigned long)dcb.BaudRate);
+
+ dcb.ByteSize = conf_get_int(conf, CONF_serdatabits);
+ logeventf(serial->logctx, "Configuring %u data bits",
+ (unsigned)dcb.ByteSize);
+
+ switch (conf_get_int(conf, CONF_serstopbits)) {
+ case 2: dcb.StopBits = ONESTOPBIT; str = "1 stop bit"; break;
+ case 3: dcb.StopBits = ONE5STOPBITS; str = "1.5 stop bits"; break;
+ case 4: dcb.StopBits = TWOSTOPBITS; str = "2 stop bits"; break;
+ default: return dupstr("Invalid number of stop bits "
+ "(need 1, 1.5 or 2)");
+ }
+ logeventf(serial->logctx, "Configuring %s", str);
+
+ switch (conf_get_int(conf, CONF_serparity)) {
+ case SER_PAR_NONE: dcb.Parity = NOPARITY; str = "no"; break;
+ case SER_PAR_ODD: dcb.Parity = ODDPARITY; str = "odd"; break;
+ case SER_PAR_EVEN: dcb.Parity = EVENPARITY; str = "even"; break;
+ case SER_PAR_MARK: dcb.Parity = MARKPARITY; str = "mark"; break;
+ case SER_PAR_SPACE: dcb.Parity = SPACEPARITY; str = "space"; break;
+ }
+ logeventf(serial->logctx, "Configuring %s parity", str);
+
+ switch (conf_get_int(conf, CONF_serflow)) {
+ case SER_FLOW_NONE:
+ str = "no";
+ break;
+ case SER_FLOW_XONXOFF:
+ dcb.fOutX = dcb.fInX = true;
+ str = "XON/XOFF";
+ break;
+ case SER_FLOW_RTSCTS:
+ dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
+ dcb.fOutxCtsFlow = true;
+ str = "RTS/CTS";
+ break;
+ case SER_FLOW_DSRDTR:
+ dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
+ dcb.fOutxDsrFlow = true;
+ str = "DSR/DTR";
+ break;
+ }
+ logeventf(serial->logctx, "Configuring %s flow control", str);
+
+ if (!SetCommState(serport, &dcb))
+ return dupprintf("Configuring serial port: %s",
+ win_strerror(GetLastError()));
+
+ timeouts.ReadIntervalTimeout = 1;
+ timeouts.ReadTotalTimeoutMultiplier = 0;
+ timeouts.ReadTotalTimeoutConstant = 0;
+ timeouts.WriteTotalTimeoutMultiplier = 0;
+ timeouts.WriteTotalTimeoutConstant = 0;
+ if (!SetCommTimeouts(serport, &timeouts))
+ return dupprintf("Configuring serial timeouts: %s",
+ win_strerror(GetLastError()));
+ }
+
+ return NULL;
+}
+
+/*
+ * Called to set up the serial connection.
+ *
+ * Returns an error message, or NULL on success.
+ *
+ * Also places the canonical host name into `realhost'. It must be
+ * freed by the caller.
+ */
+static char *serial_init(const BackendVtable *vt, Seat *seat,
+ Backend **backend_handle, LogContext *logctx,
+ Conf *conf, const char *host, int port,
+ char **realhost, bool nodelay, bool keepalive)
+{
+ Serial *serial;
+ HANDLE serport;
+ char *err;
+ char *serline;
+
+ /* No local authentication phase in this protocol */
+ seat_set_trust_status(seat, false);
+
+ serial = snew(Serial);
+ memset(serial, 0, sizeof(Serial));
+ serial->port = INVALID_HANDLE_VALUE;
+ serial->out = serial->in = NULL;
+ serial->bufsize = 0;
+ serial->break_in_progress = false;
+ serial->backend.vt = vt;
+ *backend_handle = &serial->backend;
+
+ serial->seat = seat;
+ serial->logctx = logctx;
+
+ serline = conf_get_str(conf, CONF_serline);
+ logeventf(serial->logctx, "Opening serial device %s", serline);
+
+ /*
+ * Munge the string supplied by the user into a Windows filename.
+ *
+ * Windows supports opening a few "legacy" devices (including
+ * COM1-9) by specifying their names verbatim as a filename to
+ * open. (Thus, no files can ever have these names. See
+ * <http://msdn2.microsoft.com/en-us/library/aa365247.aspx>
+ * ("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 <http://msdn2.microsoft.com/en-us/library/aa363858.aspx>
+ * (CreateFile() docs).
+ *
+ * So, we believe that prepending "\\.\" should always be the
+ * Right Thing. However, just in case someone finds something to
+ * talk to that doesn't exist under there, if the serial line
+ * contains a backslash, we use it verbatim. (This also lets
+ * existing configurations using \\.\ continue working.)
+ */
+ char *serfilename =
+ dupprintf("%s%s", strchr(serline, '\\') ? "" : "\\\\.\\", serline);
+ serport = CreateFile(serfilename, GENERIC_READ | GENERIC_WRITE, 0, NULL,
+ OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+ if (serport == INVALID_HANDLE_VALUE) {
+ err = dupprintf("Opening '%s': %s",
+ serfilename, win_strerror(GetLastError()));
+ sfree(serfilename);
+ return err;
+ }
+
+ sfree(serfilename);
+
+ err = serial_configure(serial, serport, conf);
+ if (err)
+ return err;
+
+ serial->port = serport;
+ serial->out = handle_output_new(serport, serial_sentdata, serial,
+ HANDLE_FLAG_OVERLAPPED);
+ serial->in = handle_input_new(serport, serial_gotdata, serial,
+ HANDLE_FLAG_OVERLAPPED |
+ HANDLE_FLAG_IGNOREEOF |
+ HANDLE_FLAG_UNITBUFFER);
+
+ *realhost = dupstr(serline);
+
+ /*
+ * Specials are always available.
+ */
+ seat_update_specials_menu(serial->seat);
+
+ return NULL;
+}
+
+static void serial_free(Backend *be)
+{
+ Serial *serial = container_of(be, Serial, backend);
+
+ serial_terminate(serial);
+ expire_timer_context(serial);
+ sfree(serial);
+}
+
+static void serial_reconfig(Backend *be, Conf *conf)
+{
+ Serial *serial = container_of(be, Serial, backend);
+
+ serial_configure(serial, serial->port, conf);
+
+ /*
+ * FIXME: what should we do if that call returned a non-NULL error
+ * message?
+ */
+}
+
+/*
+ * Called to send data down the serial connection.
+ */
+static void serial_send(Backend *be, const char *buf, size_t len)
+{
+ Serial *serial = container_of(be, Serial, backend);
+
+ if (serial->out == NULL)
+ return;
+
+ serial->bufsize = handle_write(serial->out, buf, len);
+}
+
+/*
+ * Called to query the current sendability status.
+ */
+static size_t serial_sendbuffer(Backend *be)
+{
+ Serial *serial = container_of(be, Serial, backend);
+ return serial->bufsize;
+}
+
+/*
+ * Called to set the size of the window
+ */
+static void serial_size(Backend *be, int width, int height)
+{
+ /* Do nothing! */
+ return;
+}
+
+static void serbreak_timer(void *ctx, unsigned long now)
+{
+ Serial *serial = (Serial *)ctx;
+
+ if (now == serial->clearbreak_time && serial->port) {
+ ClearCommBreak(serial->port);
+ serial->break_in_progress = false;
+ logevent(serial->logctx, "Finished serial break");
+ }
+}
+
+/*
+ * Send serial special codes.
+ */
+static void serial_special(Backend *be, SessionSpecialCode code, int arg)
+{
+ Serial *serial = container_of(be, Serial, backend);
+
+ if (serial->port && code == SS_BRK) {
+ logevent(serial->logctx, "Starting serial break at user request");
+ SetCommBreak(serial->port);
+ /*
+ * To send a serial break on Windows, we call SetCommBreak
+ * to begin the break, then wait a bit, and then call
+ * ClearCommBreak to finish it. Hence, I must use timing.c
+ * to arrange a callback when it's time to do the latter.
+ *
+ * SUS says that a default break length must be between 1/4
+ * and 1/2 second. FreeBSD apparently goes with 2/5 second,
+ * and so will I.
+ */
+ serial->clearbreak_time =
+ schedule_timer(TICKSPERSEC * 2 / 5, serbreak_timer, serial);
+ serial->break_in_progress = true;
+ }
+
+ return;
+}
+
+/*
+ * Return a list of the special codes that make sense in this
+ * protocol.
+ */
+static const SessionSpecial *serial_get_specials(Backend *be)
+{
+ static const SessionSpecial specials[] = {
+ {"Break", SS_BRK},
+ {NULL, SS_EXITMENU}
+ };
+ return specials;
+}
+
+static bool serial_connected(Backend *be)
+{
+ return true; /* always connected */
+}
+
+static bool serial_sendok(Backend *be)
+{
+ return true;
+}
+
+static void serial_unthrottle(Backend *be, size_t backlog)
+{
+ Serial *serial = container_of(be, Serial, backend);
+ if (serial->in)
+ handle_unthrottle(serial->in, backlog);
+}
+
+static bool serial_ldisc(Backend *be, int option)
+{
+ /*
+ * Local editing and local echo are off by default.
+ */
+ return false;
+}
+
+static void serial_provide_ldisc(Backend *be, Ldisc *ldisc)
+{
+ /* This is a stub. */
+}
+
+static int serial_exitcode(Backend *be)
+{
+ Serial *serial = container_of(be, Serial, backend);
+ if (serial->port != INVALID_HANDLE_VALUE)
+ return -1; /* still connected */
+ else
+ /* Exit codes are a meaningless concept with serial ports */
+ return INT_MAX;
+}
+
+/*
+ * cfg_info for Serial does nothing at all.
+ */
+static int serial_cfg_info(Backend *be)
+{
+ return 0;
+}
+
+const BackendVtable serial_backend = {
+ .init = serial_init,
+ .free = serial_free,
+ .reconfig = serial_reconfig,
+ .send = serial_send,
+ .sendbuffer = serial_sendbuffer,
+ .size = serial_size,
+ .special = serial_special,
+ .get_specials = serial_get_specials,
+ .connected = serial_connected,
+ .exitcode = serial_exitcode,
+ .sendok = serial_sendok,
+ .ldisc_option_state = serial_ldisc,
+ .provide_ldisc = serial_provide_ldisc,
+ .unthrottle = serial_unthrottle,
+ .cfg_info = serial_cfg_info,
+ .id = "serial",
+ .displayname_tc = "Serial",
+ .displayname_lc = "serial",
+ .protocol = PROT_SERIAL,
+ .serial_parity_mask = ((1 << SER_PAR_NONE) |
+ (1 << SER_PAR_ODD) |
+ (1 << SER_PAR_EVEN) |
+ (1 << SER_PAR_MARK) |
+ (1 << SER_PAR_SPACE)),
+ .serial_flow_mask = ((1 << SER_FLOW_NONE) |
+ (1 << SER_FLOW_XONXOFF) |
+ (1 << SER_FLOW_RTSCTS) |
+ (1 << SER_FLOW_DSRDTR)),
+};
diff --git a/windows/sftp.c b/windows/sftp.c
new file mode 100644
index 00000000..a6546269
--- /dev/null
+++ b/windows/sftp.c
@@ -0,0 +1,656 @@
+/*
+ * sftp.c: the Windows-specific parts of PSFTP and PSCP.
+ */
+
+#include <winsock2.h> /* need to put this first, for winelib builds */
+#include <assert.h>
+
+#define NEED_DECLARATION_OF_SELECT
+
+#include "putty.h"
+#include "psftp.h"
+#include "ssh.h"
+#include "security-api.h"
+
+SeatPromptResult filexfer_get_userpass_input(Seat *seat, prompts_t *p)
+{
+ /* The file transfer tools don't support Restart Session, so we
+ * can just have a single static cmdline_get_passwd_input_state
+ * that's never reset */
+ static cmdline_get_passwd_input_state cmdline_state =
+ CMDLINE_GET_PASSWD_INPUT_STATE_INIT;
+
+ SeatPromptResult spr;
+ spr = cmdline_get_passwd_input(p, &cmdline_state, false);
+ if (spr.kind == SPRK_INCOMPLETE)
+ spr = console_get_userpass_input(p);
+ return spr;
+}
+
+void platform_get_x11_auth(struct X11Display *display, Conf *conf)
+{
+ /* Do nothing, therefore no auth. */
+}
+const bool platform_uses_x11_unix_by_default = true;
+
+/* ----------------------------------------------------------------------
+ * File access abstraction.
+ */
+
+/*
+ * Set local current directory. Returns NULL on success, or else an
+ * error message which must be freed after printing.
+ */
+char *psftp_lcd(char *dir)
+{
+ char *ret = NULL;
+
+ if (!SetCurrentDirectory(dir)) {
+ LPVOID message;
+ int i;
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR)&message, 0, NULL);
+ i = strcspn((char *)message, "\n");
+ ret = dupprintf("%.*s", i, (LPCTSTR)message);
+ LocalFree(message);
+ }
+
+ return ret;
+}
+
+/*
+ * Get local current directory. Returns a string which must be
+ * freed.
+ */
+char *psftp_getcwd(void)
+{
+ char *ret = snewn(256, char);
+ size_t len = GetCurrentDirectory(256, ret);
+ if (len > 256)
+ ret = sresize(ret, len, char);
+ GetCurrentDirectory(len, ret);
+ return ret;
+}
+
+static inline uint64_t uint64_from_words(uint32_t hi, uint32_t lo)
+{
+ return (((uint64_t)hi) << 32) | lo;
+}
+
+#define TIME_POSIX_TO_WIN(t, ft) do { \
+ ULARGE_INTEGER uli; \
+ uli.QuadPart = ((ULONGLONG)(t) + 11644473600ull) * 10000000ull; \
+ (ft).dwLowDateTime = uli.LowPart; \
+ (ft).dwHighDateTime = uli.HighPart; \
+} while(0)
+#define TIME_WIN_TO_POSIX(ft, t) do { \
+ ULARGE_INTEGER uli; \
+ uli.LowPart = (ft).dwLowDateTime; \
+ uli.HighPart = (ft).dwHighDateTime; \
+ uli.QuadPart = uli.QuadPart / 10000000ull - 11644473600ull; \
+ (t) = (unsigned long) uli.QuadPart; \
+} while(0)
+
+struct RFile {
+ HANDLE h;
+};
+
+RFile *open_existing_file(const char *name, uint64_t *size,
+ unsigned long *mtime, unsigned long *atime,
+ long *perms)
+{
+ HANDLE h;
+ RFile *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;
+
+ dll_hijacking_protection();
+
+ ret = psftp_main(argc, argv);
+
+ return ret;
+}
diff --git a/windows/sharing.c b/windows/sharing.c
new file mode 100644
index 00000000..15b1da48
--- /dev/null
+++ b/windows/sharing.c
@@ -0,0 +1,113 @@
+/*
+ * Windows implementation of SSH connection-sharing IPC setup.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+#include "proxy/proxy.h"
+#include "ssh.h"
+
+#include "cryptoapi.h"
+#include "security-api.h"
+
+#define CONNSHARE_PIPE_PREFIX "\\\\.\\pipe\\putty-connshare"
+#define CONNSHARE_MUTEX_PREFIX "Local\\putty-connshare-mutex"
+
+static char *make_name(const char *prefix, const char *name)
+{
+ char *username, *retname;
+
+ username = get_username();
+ retname = dupprintf("%s.%s.%s", prefix, username, name);
+ sfree(username);
+
+ return retname;
+}
+
+int platform_ssh_share(const char *pi_name, Conf *conf,
+ Plug *downplug, Plug *upplug, Socket **sock,
+ char **logtext, char **ds_err, char **us_err,
+ bool can_upstream, bool can_downstream)
+{
+ char *name, *mutexname, *pipename;
+ HANDLE mutex;
+ Socket *retsock;
+
+ /*
+ * Transform the platform-independent version of the connection
+ * identifier into the obfuscated version we'll use for our
+ * Windows named pipe and mutex. A side effect of doing this is
+ * that it also eliminates any characters illegal in Windows pipe
+ * names.
+ */
+ name = capi_obfuscate_string(pi_name);
+ if (!name) {
+ *logtext = dupprintf("Unable to call CryptProtectMemory: %s",
+ win_strerror(GetLastError()));
+ return SHARE_NONE;
+ }
+
+ /*
+ * Make a mutex name out of the connection identifier, and lock it
+ * while we decide whether to be upstream or downstream.
+ */
+ mutexname = make_name(CONNSHARE_MUTEX_PREFIX, name);
+ mutex = lock_interprocess_mutex(mutexname, logtext);
+ if (!mutex) {
+ sfree(mutexname);
+ sfree(name);
+ return SHARE_NONE;
+ }
+
+ pipename = make_name(CONNSHARE_PIPE_PREFIX, name);
+
+ *logtext = NULL;
+
+ if (can_downstream) {
+ retsock = new_named_pipe_client(pipename, downplug);
+ if (sk_socket_error(retsock) == NULL) {
+ sfree(*logtext);
+ *logtext = pipename;
+ *sock = retsock;
+ sfree(name);
+ unlock_interprocess_mutex(mutex);
+ return SHARE_DOWNSTREAM;
+ }
+ sfree(*ds_err);
+ *ds_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock));
+ sk_close(retsock);
+ }
+
+ if (can_upstream) {
+ retsock = new_named_pipe_listener(pipename, upplug);
+ if (sk_socket_error(retsock) == NULL) {
+ sfree(*logtext);
+ *logtext = pipename;
+ *sock = retsock;
+ sfree(name);
+ ReleaseMutex(mutex);
+ CloseHandle(mutex);
+ return SHARE_UPSTREAM;
+ }
+ sfree(*us_err);
+ *us_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock));
+ sk_close(retsock);
+ }
+
+ /* One of the above clauses ought to have happened. */
+ assert(*logtext || *ds_err || *us_err);
+
+ sfree(pipename);
+ sfree(name);
+ ReleaseMutex(mutex);
+ CloseHandle(mutex);
+ return SHARE_NONE;
+}
+
+void platform_ssh_share_cleanup(const char *name)
+{
+}
diff --git a/windows/storage.c b/windows/storage.c
new file mode 100644
index 00000000..f2c33feb
--- /dev/null
+++ b/windows/storage.c
@@ -0,0 +1,853 @@
+/*
+ * storage.c: Windows-specific implementation of the interface
+ * defined in storage.h.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <assert.h>
+#include "putty.h"
+#include "storage.h"
+
+#include <shlobj.h>
+#ifndef CSIDL_APPDATA
+#define CSIDL_APPDATA 0x001a
+#endif
+#ifndef CSIDL_LOCAL_APPDATA
+#define CSIDL_LOCAL_APPDATA 0x001c
+#endif
+
+static const char *const reg_jumplist_key = PUTTY_REG_POS "\\Jumplist";
+static const char *const reg_jumplist_value = "Recent sessions";
+static const char *const puttystr = PUTTY_REG_POS "\\Sessions";
+static const char *const host_ca_key = PUTTY_REG_POS "\\SshHostCAs";
+
+static bool tried_shgetfolderpath = false;
+static HMODULE shell32_module = NULL;
+DECL_WINDOWS_FUNCTION(static, HRESULT, SHGetFolderPathA,
+ (HWND, int, HANDLE, DWORD, LPSTR));
+
+struct settings_w {
+ HKEY sesskey;
+};
+
+settings_w *open_settings_w(const char *sessionname, char **errmsg)
+{
+ *errmsg = NULL;
+
+ if (!sessionname || !*sessionname)
+ sessionname = "Default Settings";
+
+ strbuf *sb = strbuf_new();
+ escape_registry_key(sessionname, sb);
+
+ HKEY sesskey = open_regkey(true, HKEY_CURRENT_USER, puttystr, sb->s);
+ if (!sesskey) {
+ *errmsg = dupprintf("Unable to create registry key\n"
+ "HKEY_CURRENT_USER\\%s\\%s", puttystr, sb->s);
+ strbuf_free(sb);
+ return NULL;
+ }
+ strbuf_free(sb);
+
+ settings_w *toret = snew(settings_w);
+ toret->sesskey = sesskey;
+ return toret;
+}
+
+void write_setting_s(settings_w *handle, const char *key, const char *value)
+{
+ if (handle)
+ put_reg_sz(handle->sesskey, key, value);
+}
+
+void write_setting_i(settings_w *handle, const char *key, int value)
+{
+ if (handle)
+ put_reg_dword(handle->sesskey, key, value);
+}
+
+void close_settings_w(settings_w *handle)
+{
+ close_regkey(handle->sesskey);
+ sfree(handle);
+}
+
+struct settings_r {
+ HKEY sesskey;
+};
+
+settings_r *open_settings_r(const char *sessionname)
+{
+ if (!sessionname || !*sessionname)
+ sessionname = "Default Settings";
+
+ strbuf *sb = strbuf_new();
+ escape_registry_key(sessionname, sb);
+ HKEY sesskey = open_regkey(false, HKEY_CURRENT_USER, puttystr, sb->s);
+ 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)
+{
+ if (!handle)
+ return NULL;
+ return get_reg_sz(handle->sesskey, key);
+}
+
+int read_setting_i(settings_r *handle, const char *key, int defvalue)
+{
+ DWORD val;
+ if (!handle || !get_reg_dword(handle->sesskey, key, &val))
+ return defvalue;
+ else
+ return val;
+}
+
+FontSpec *read_setting_fontspec(settings_r *handle, const char *name)
+{
+ char *settingname;
+ char *fontname;
+ FontSpec *ret;
+ int isbold, height, charset;
+
+ fontname = read_setting_s(handle, name);
+ if (!fontname)
+ return NULL;
+
+ settingname = dupcat(name, "IsBold");
+ isbold = read_setting_i(handle, settingname, -1);
+ sfree(settingname);
+ if (isbold == -1) {
+ sfree(fontname);
+ return NULL;
+ }
+
+ settingname = dupcat(name, "CharSet");
+ charset = read_setting_i(handle, settingname, -1);
+ sfree(settingname);
+ if (charset == -1) {
+ sfree(fontname);
+ return NULL;
+ }
+
+ settingname = dupcat(name, "Height");
+ height = read_setting_i(handle, settingname, INT_MIN);
+ sfree(settingname);
+ if (height == INT_MIN) {
+ sfree(fontname);
+ return NULL;
+ }
+
+ ret = fontspec_new(fontname, isbold, height, charset);
+ sfree(fontname);
+ return ret;
+}
+
+void write_setting_fontspec(settings_w *handle,
+ const char *name, FontSpec *font)
+{
+ char *settingname;
+
+ write_setting_s(handle, name, font->name);
+ settingname = dupcat(name, "IsBold");
+ write_setting_i(handle, settingname, font->isbold);
+ sfree(settingname);
+ settingname = dupcat(name, "CharSet");
+ write_setting_i(handle, settingname, font->charset);
+ sfree(settingname);
+ settingname = dupcat(name, "Height");
+ write_setting_i(handle, settingname, font->height);
+ sfree(settingname);
+}
+
+Filename *read_setting_filename(settings_r *handle, const char *name)
+{
+ char *tmp = read_setting_s(handle, name);
+ if (tmp) {
+ Filename *ret = filename_from_str(tmp);
+ sfree(tmp);
+ return ret;
+ } else
+ return NULL;
+}
+
+void write_setting_filename(settings_w *handle,
+ const char *name, Filename *result)
+{
+ write_setting_s(handle, name, result->path);
+}
+
+void close_settings_r(settings_r *handle)
+{
+ if (handle) {
+ close_regkey(handle->sesskey);
+ sfree(handle);
+ }
+}
+
+void del_settings(const char *sessionname)
+{
+ HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, puttystr);
+ if (!rkey)
+ return;
+
+ strbuf *sb = strbuf_new();
+ escape_registry_key(sessionname, sb);
+ del_regkey(rkey, sb->s);
+ strbuf_free(sb);
+
+ close_regkey(rkey);
+
+ remove_session_from_jumplist(sessionname);
+}
+
+struct settings_e {
+ HKEY key;
+ int i;
+};
+
+settings_e *enum_settings_start(void)
+{
+ HKEY key = open_regkey(false, HKEY_CURRENT_USER, puttystr);
+ if (!key)
+ return NULL;
+
+ settings_e *ret = snew(settings_e);
+ if (ret) {
+ ret->key = key;
+ ret->i = 0;
+ }
+
+ return ret;
+}
+
+bool enum_settings_next(settings_e *e, strbuf *sb)
+{
+ char *name = enum_regkey(e->key, e->i);
+ if (!name)
+ return false;
+
+ unescape_registry_key(name, sb);
+ sfree(name);
+ e->i++;
+ return true;
+}
+
+void enum_settings_finish(settings_e *e)
+{
+ close_regkey(e->key);
+ sfree(e);
+}
+
+static void hostkey_regname(strbuf *sb, const char *hostname,
+ int port, const char *keytype)
+{
+ put_fmt(sb, "%s@%d:", keytype, port);
+ escape_registry_key(hostname, sb);
+}
+
+int check_stored_host_key(const char *hostname, int port,
+ const char *keytype, const char *key)
+{
+ /*
+ * Read a saved key in from the registry and see what it says.
+ */
+ strbuf *regname = strbuf_new();
+ hostkey_regname(regname, hostname, port, keytype);
+
+ HKEY rkey = open_regkey(false, HKEY_CURRENT_USER,
+ PUTTY_REG_POS "\\SshHostKeys");
+ if (!rkey) {
+ strbuf_free(regname);
+ return 1; /* key does not exist in registry */
+ }
+
+ char *otherstr = get_reg_sz(rkey, regname->s);
+ if (!otherstr && !strcmp(keytype, "rsa")) {
+ /*
+ * Key didn't exist. If the key type is RSA, we'll try
+ * another trick, which is to look up the _old_ key format
+ * under just the hostname and translate that.
+ */
+ char *justhost = regname->s + 1 + strcspn(regname->s, ":");
+ char *oldstyle = get_reg_sz(rkey, justhost);
+
+ if (oldstyle) {
+ /*
+ * The old format is two old-style bignums separated by
+ * a slash. An old-style bignum is made of groups of
+ * four hex digits: digits are ordered in sensible
+ * (most to least significant) order within each group,
+ * but groups are ordered in silly (least to most)
+ * order within the bignum. The new format is two
+ * ordinary C-format hex numbers (0xABCDEFG...XYZ, with
+ * A nonzero except in the special case 0x0, which
+ * doesn't appear anyway in RSA keys) separated by a
+ * comma. All hex digits are lowercase in both formats.
+ */
+ strbuf *new = strbuf_new();
+ const char *q = oldstyle;
+ int i, j;
+
+ for (i = 0; i < 2; i++) {
+ int ndigits, nwords;
+ put_datapl(new, PTRLEN_LITERAL("0x"));
+ ndigits = strcspn(q, "/"); /* find / or end of string */
+ nwords = ndigits / 4;
+ /* now trim ndigits to remove leading zeros */
+ while (q[(ndigits - 1) ^ 3] == '0' && ndigits > 1)
+ ndigits--;
+ /* now move digits over to new string */
+ for (j = ndigits; j-- > 0 ;)
+ put_byte(new, q[j ^ 3]);
+ q += nwords * 4;
+ if (*q) {
+ q++; /* eat the slash */
+ put_byte(new, ','); /* add a comma */
+ }
+ }
+
+ /*
+ * Now _if_ this key matches, we'll enter it in the new
+ * format. If not, we'll assume something odd went
+ * wrong, and hyper-cautiously do nothing.
+ */
+ if (!strcmp(new->s, key)) {
+ put_reg_sz(rkey, regname->s, new->s);
+ otherstr = strbuf_to_str(new);
+ } else {
+ strbuf_free(new);
+ }
+ }
+
+ sfree(oldstyle);
+ }
+
+ close_regkey(rkey);
+
+ int compare = otherstr ? strcmp(otherstr, key) : -1;
+
+ sfree(otherstr);
+ strbuf_free(regname);
+
+ if (!otherstr)
+ return 1; /* key does not exist in registry */
+ else if (compare)
+ return 2; /* key is different in registry */
+ else
+ return 0; /* key matched OK in registry */
+}
+
+bool have_ssh_host_key(const char *hostname, int port,
+ const char *keytype)
+{
+ /*
+ * If we have a host key, check_stored_host_key will return 0 or 2.
+ * If we don't have one, it'll return 1.
+ */
+ return check_stored_host_key(hostname, port, keytype, "") != 1;
+}
+
+void store_host_key(const char *hostname, int port,
+ const char *keytype, const char *key)
+{
+ strbuf *regname = strbuf_new();
+ hostkey_regname(regname, hostname, port, keytype);
+
+ HKEY rkey = open_regkey(true, HKEY_CURRENT_USER,
+ PUTTY_REG_POS "\\SshHostKeys");
+ if (rkey) {
+ put_reg_sz(rkey, regname->s, key);
+ close_regkey(rkey);
+ } /* else key does not exist in registry */
+
+ strbuf_free(regname);
+}
+
+struct host_ca_enum {
+ HKEY key;
+ int i;
+};
+
+host_ca_enum *enum_host_ca_start(void)
+{
+ host_ca_enum *e;
+ HKEY key;
+
+ if (!(key = open_regkey(false, HKEY_CURRENT_USER, host_ca_key)))
+ return NULL;
+
+ e = snew(host_ca_enum);
+ e->key = key;
+ e->i = 0;
+
+ return e;
+}
+
+bool enum_host_ca_next(host_ca_enum *e, strbuf *sb)
+{
+ char *regbuf = enum_regkey(e->key, e->i);
+ if (!regbuf)
+ return false;
+
+ unescape_registry_key(regbuf, sb);
+ sfree(regbuf);
+ e->i++;
+ return true;
+}
+
+void enum_host_ca_finish(host_ca_enum *e)
+{
+ close_regkey(e->key);
+ sfree(e);
+}
+
+host_ca *host_ca_load(const char *name)
+{
+ strbuf *sb;
+ const char *s;
+
+ sb = strbuf_new();
+ escape_registry_key(name, sb);
+ HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, host_ca_key, sb->s);
+ strbuf_free(sb);
+
+ if (!rkey)
+ return NULL;
+
+ host_ca *hca = host_ca_new();
+ hca->name = dupstr(name);
+
+ DWORD val;
+
+ if ((s = get_reg_sz(rkey, "PublicKey")) != NULL)
+ hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(s));
+
+ if ((s = get_reg_sz(rkey, "Validity")) != NULL) {
+ hca->validity_expression = strbuf_to_str(
+ percent_decode_sb(ptrlen_from_asciz(s)));
+ } else if ((sb = get_reg_multi_sz(rkey, "MatchHosts")) != NULL) {
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(sb));
+ CertExprBuilder *eb = cert_expr_builder_new();
+
+ const char *wc;
+ while (wc = get_asciz(src), !get_err(src))
+ cert_expr_builder_add(eb, wc);
+
+ hca->validity_expression = cert_expr_expression(eb);
+ cert_expr_builder_free(eb);
+ }
+
+ if (get_reg_dword(rkey, "PermitRSASHA1", &val))
+ hca->opts.permit_rsa_sha1 = val;
+ if (get_reg_dword(rkey, "PermitRSASHA256", &val))
+ hca->opts.permit_rsa_sha256 = val;
+ if (get_reg_dword(rkey, "PermitRSASHA512", &val))
+ hca->opts.permit_rsa_sha512 = val;
+
+ close_regkey(rkey);
+ return hca;
+}
+
+char *host_ca_save(host_ca *hca)
+{
+ if (!*hca->name)
+ return dupstr("CA record must have a name");
+
+ strbuf *sb = strbuf_new();
+ escape_registry_key(hca->name, sb);
+ HKEY rkey = open_regkey(true, HKEY_CURRENT_USER, host_ca_key, sb->s);
+ if (!rkey) {
+ char *err = dupprintf("Unable to create registry key\n"
+ "HKEY_CURRENT_USER\\%s\\%s", host_ca_key, sb->s);
+ strbuf_free(sb);
+ return err;
+ }
+ strbuf_free(sb);
+
+ strbuf *base64_pubkey = base64_encode_sb(
+ ptrlen_from_strbuf(hca->ca_public_key), 0);
+ put_reg_sz(rkey, "PublicKey", base64_pubkey->s);
+ strbuf_free(base64_pubkey);
+
+ strbuf *validity = percent_encode_sb(
+ ptrlen_from_asciz(hca->validity_expression), NULL);
+ put_reg_sz(rkey, "Validity", validity->s);
+ strbuf_free(validity);
+
+ put_reg_dword(rkey, "PermitRSASHA1", hca->opts.permit_rsa_sha1);
+ put_reg_dword(rkey, "PermitRSASHA256", hca->opts.permit_rsa_sha256);
+ put_reg_dword(rkey, "PermitRSASHA512", hca->opts.permit_rsa_sha512);
+
+ close_regkey(rkey);
+ return NULL;
+}
+
+char *host_ca_delete(const char *name)
+{
+ HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, host_ca_key);
+ if (!rkey)
+ return NULL;
+
+ strbuf *sb = strbuf_new();
+ escape_registry_key(name, sb);
+ del_regkey(rkey, sb->s);
+ strbuf_free(sb);
+
+ return NULL;
+}
+
+/*
+ * Open (or delete) the random seed file.
+ */
+enum { DEL, OPEN_R, OPEN_W };
+static bool try_random_seed(char const *path, int action, HANDLE *ret)
+{
+ if (action == DEL) {
+ if (!DeleteFile(path) && GetLastError() != ERROR_FILE_NOT_FOUND) {
+ nonfatal("Unable to delete '%s': %s", path,
+ win_strerror(GetLastError()));
+ }
+ *ret = INVALID_HANDLE_VALUE;
+ return false; /* so we'll do the next ones too */
+ }
+
+ *ret = CreateFile(path,
+ action == OPEN_W ? GENERIC_WRITE : GENERIC_READ,
+ action == OPEN_W ? 0 : (FILE_SHARE_READ |
+ FILE_SHARE_WRITE),
+ NULL,
+ action == OPEN_W ? CREATE_ALWAYS : OPEN_EXISTING,
+ action == OPEN_W ? FILE_ATTRIBUTE_NORMAL : 0,
+ NULL);
+
+ return (*ret != INVALID_HANDLE_VALUE);
+}
+
+static bool try_random_seed_and_free(char *path, int action, HANDLE *hout)
+{
+ bool retd = try_random_seed(path, action, hout);
+ sfree(path);
+ return retd;
+}
+
+static HANDLE access_random_seed(int action)
+{
+ HANDLE rethandle;
+
+ /*
+ * Iterate over a selection of possible random seed paths until
+ * we find one that works.
+ *
+ * We do this iteration separately for reading and writing,
+ * meaning that we will automatically migrate random seed files
+ * if a better location becomes available (by reading from the
+ * best location in which we actually find one, and then
+ * writing to the best location in which we can _create_ one).
+ */
+
+ /*
+ * First, try the location specified by the user in the
+ * Registry, if any.
+ */
+ {
+ HKEY rkey = open_regkey(false, HKEY_CURRENT_USER, PUTTY_REG_POS);
+ if (rkey) {
+ char *regpath = get_reg_sz(rkey, "RandSeedFile");
+ close_regkey(rkey);
+ if (regpath) {
+ bool success = try_random_seed(regpath, action, &rethandle);
+ sfree(regpath);
+ if (success)
+ return rethandle;
+ }
+ }
+ }
+
+ /*
+ * Next, try the user's local Application Data directory,
+ * followed by their non-local one. This is found using the
+ * SHGetFolderPath function, which won't be present on all
+ * versions of Windows.
+ */
+ if (!tried_shgetfolderpath) {
+ /* This is likely only to bear fruit on systems with IE5+
+ * installed, or WinMe/2K+. There is some faffing with
+ * SHFOLDER.DLL we could do to try to find an equivalent
+ * on older versions of Windows if we cared enough.
+ * However, the invocation below requires IE5+ anyway,
+ * so stuff that. */
+ shell32_module = load_system32_dll("shell32.dll");
+ GET_WINDOWS_FUNCTION(shell32_module, SHGetFolderPathA);
+ tried_shgetfolderpath = true;
+ }
+ if (p_SHGetFolderPathA) {
+ char profile[MAX_PATH + 1];
+ if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA,
+ NULL, SHGFP_TYPE_CURRENT, profile)) &&
+ try_random_seed_and_free(dupcat(profile, "\\PUTTY.RND"),
+ action, &rethandle))
+ return rethandle;
+
+ if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_APPDATA,
+ NULL, SHGFP_TYPE_CURRENT, profile)) &&
+ try_random_seed_and_free(dupcat(profile, "\\PUTTY.RND"),
+ action, &rethandle))
+ return rethandle;
+ }
+
+ /*
+ * Failing that, try %HOMEDRIVE%%HOMEPATH% as a guess at the
+ * user's home directory.
+ */
+ {
+ char drv[MAX_PATH], path[MAX_PATH];
+
+ DWORD drvlen = GetEnvironmentVariable("HOMEDRIVE", drv, sizeof(drv));
+ DWORD pathlen = GetEnvironmentVariable("HOMEPATH", path, sizeof(path));
+
+ /* We permit %HOMEDRIVE% to expand to an empty string, but if
+ * %HOMEPATH% does that, we abort the attempt. Same if either
+ * variable overflows its buffer. */
+ if (drvlen == 0)
+ drv[0] = '\0';
+
+ if (drvlen < lenof(drv) && pathlen < lenof(path) && pathlen > 0 &&
+ try_random_seed_and_free(
+ dupcat(drv, path, "\\PUTTY.RND"), action, &rethandle))
+ return rethandle;
+ }
+
+ /*
+ * And finally, fall back to C:\WINDOWS.
+ */
+ {
+ char windir[MAX_PATH];
+ DWORD len = GetWindowsDirectory(windir, sizeof(windir));
+ if (len < lenof(windir) &&
+ try_random_seed_and_free(
+ dupcat(windir, "\\PUTTY.RND"), action, &rethandle))
+ return rethandle;
+ }
+
+ /*
+ * If even that failed, give up.
+ */
+ return INVALID_HANDLE_VALUE;
+}
+
+void read_random_seed(noise_consumer_t consumer)
+{
+ HANDLE seedf = access_random_seed(OPEN_R);
+
+ if (seedf != INVALID_HANDLE_VALUE) {
+ while (1) {
+ char buf[1024];
+ DWORD len;
+
+ if (ReadFile(seedf, buf, sizeof(buf), &len, NULL) && len)
+ consumer(buf, len);
+ else
+ break;
+ }
+ CloseHandle(seedf);
+ }
+}
+
+void write_random_seed(void *data, int len)
+{
+ HANDLE seedf = access_random_seed(OPEN_W);
+
+ if (seedf != INVALID_HANDLE_VALUE) {
+ DWORD lenwritten;
+
+ WriteFile(seedf, data, len, &lenwritten, NULL);
+ CloseHandle(seedf);
+ }
+}
+
+/*
+ * Internal function supporting the jump list registry code. All the
+ * functions to add, remove and read the list have substantially
+ * similar content, so this is a generalisation of all of them which
+ * transforms the list in the registry by prepending 'add' (if
+ * non-null), removing 'rem' from what's left (if non-null), and
+ * returning the resulting concatenated list of strings in 'out' (if
+ * non-null).
+ */
+static int transform_jumplist_registry(
+ const char *add, const char *rem, char **out)
+{
+ HKEY rkey = open_regkey(true, HKEY_CURRENT_USER, reg_jumplist_key);
+ if (!rkey)
+ return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE;
+
+ /* Get current list of saved sessions in the registry. */
+ strbuf *oldlist = get_reg_multi_sz(rkey, reg_jumplist_value);
+ if (!oldlist) {
+ /* Start again with the empty list. */
+ oldlist = strbuf_new();
+ put_data(oldlist, "\0\0", 2);
+ }
+
+ /*
+ * Modify the list, if we're modifying.
+ */
+ bool write_failure = false;
+ if (add || rem) {
+ BinarySource src[1];
+ BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(oldlist));
+ strbuf *newlist = strbuf_new();
+
+ /* First add the new item to the beginning of the list. */
+ if (add)
+ put_asciz(newlist, add);
+
+ /* Now add the existing list, taking care to leave out the removed
+ * item, if it was already in the existing list. */
+ while (true) {
+ const char *olditem = get_asciz(src);
+ if (get_err(src))
+ break;
+
+ if (!rem || strcmp(olditem, rem) != 0) {
+ /* Check if this is a valid session, otherwise don't add. */
+ settings_r *psettings_tmp = open_settings_r(olditem);
+ if (psettings_tmp != NULL) {
+ close_settings_r(psettings_tmp);
+ put_asciz(newlist, olditem);
+ }
+ }
+ }
+
+ /* Save the new list to the registry. */
+ write_failure = !put_reg_multi_sz(rkey, reg_jumplist_value, newlist);
+
+ strbuf_free(oldlist);
+ oldlist = newlist;
+ }
+
+ close_regkey(rkey);
+
+ if (out && !write_failure)
+ *out = strbuf_to_str(oldlist);
+ else
+ strbuf_free(oldlist);
+
+ if (write_failure)
+ return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE;
+ else
+ return JUMPLISTREG_OK;
+}
+
+/* Adds a new entry to the jumplist entries in the registry. */
+int add_to_jumplist_registry(const char *item)
+{
+ return transform_jumplist_registry(item, item, NULL);
+}
+
+/* Removes an item from the jumplist entries in the registry. */
+int remove_from_jumplist_registry(const char *item)
+{
+ return transform_jumplist_registry(NULL, item, NULL);
+}
+
+/* Returns the jumplist entries from the registry. Caller must free
+ * the returned pointer. */
+char *get_jumplist_registry_entries (void)
+{
+ char *list_value;
+
+ if (transform_jumplist_registry(NULL,NULL,&list_value) != JUMPLISTREG_OK) {
+ list_value = snewn(2, char);
+ *list_value = '\0';
+ *(list_value + 1) = '\0';
+ }
+ return list_value;
+}
+
+/*
+ * Recursively delete a registry key and everything under it.
+ */
+static void registry_recursive_remove(HKEY key)
+{
+ char *name;
+
+ DWORD i = 0;
+ while ((name = enum_regkey(key, i)) != NULL) {
+ HKEY subkey = open_regkey(false, key, name);
+ if (subkey) {
+ registry_recursive_remove(subkey);
+ close_regkey(subkey);
+ }
+ del_regkey(key, name);
+ sfree(name);
+ }
+}
+
+void cleanup_all(void)
+{
+ /* ------------------------------------------------------------
+ * Wipe out the random seed file, in all of its possible
+ * locations.
+ */
+ access_random_seed(DEL);
+
+ /* ------------------------------------------------------------
+ * Ask Windows to delete any jump list information associated
+ * with this installation of PuTTY.
+ */
+ clear_jumplist();
+
+ /* ------------------------------------------------------------
+ * Destroy all registry information associated with PuTTY.
+ */
+
+ /*
+ * Open the main PuTTY registry key and remove everything in it.
+ */
+ HKEY key = open_regkey(false, HKEY_CURRENT_USER, PUTTY_REG_POS);
+ if (key) {
+ registry_recursive_remove(key);
+ close_regkey(key);
+ }
+ /*
+ * Now open the parent key and remove the PuTTY main key. Once
+ * we've done that, see if the parent key has any other
+ * children.
+ */
+ if ((key = open_regkey(false, HKEY_CURRENT_USER,
+ PUTTY_REG_PARENT)) != NULL) {
+ del_regkey(key, PUTTY_REG_PARENT_CHILD);
+ char *name = enum_regkey(key, 0);
+ close_regkey(key);
+
+ /*
+ * If the parent key had no other children, we must delete
+ * it in its turn. That means opening the _grandparent_
+ * key.
+ */
+ if (name) {
+ sfree(name);
+ } else {
+ if ((key = open_regkey(false, HKEY_CURRENT_USER,
+ PUTTY_REG_GPARENT)) != NULL) {
+ del_regkey(key, PUTTY_REG_GPARENT_CHILD);
+ close_regkey(key);
+ }
+ }
+ }
+ /*
+ * Now we're done.
+ */
+}
diff --git a/windows/test_screenshot.c b/windows/test_screenshot.c
new file mode 100644
index 00000000..1e3a20d7
--- /dev/null
+++ b/windows/test_screenshot.c
@@ -0,0 +1,45 @@
+#include "putty.h"
+
+static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...)
+{
+ va_list ap;
+ fprintf(stderr, "screenshot: ");
+ va_start(ap, p);
+ vfprintf(stderr, p, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ exit(1);
+}
+
+void out_of_memory(void) { fatal_error("out of memory"); }
+
+int main(int argc, char **argv)
+{
+ const char *outfile = NULL;
+
+ AuxMatchOpt amo = aux_match_opt_init(argc-1, argv+1, 0, fatal_error);
+ while (!aux_match_done(&amo)) {
+ char *val;
+ #define match_opt(...) aux_match_opt( \
+ &amo, NULL, __VA_ARGS__, (const char *)NULL)
+ #define match_optval(...) aux_match_opt( \
+ &amo, &val, __VA_ARGS__, (const char *)NULL)
+
+ if (aux_match_arg(&amo, &val)) {
+ fatal_error("unexpected argument '%s'", val);
+ } else if (match_optval("-o", "--output")) {
+ outfile = val;
+ } else {
+ fatal_error("unrecognised option '%s'\n", amo.argv[amo.index]);
+ }
+ }
+
+ if (!outfile)
+ fatal_error("expected an output file name");
+
+ char *err = save_screenshot(NULL, outfile);
+ if (err)
+ fatal_error("%s", err);
+
+ return 0;
+}
diff --git a/windows/unicode.c b/windows/unicode.c
new file mode 100644
index 00000000..b3f6d802
--- /dev/null
+++ b/windows/unicode.c
@@ -0,0 +1,1402 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <time.h>
+#include <assert.h>
+
+#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 <http://www.unicode.org/Public/MAPPINGS/> -- 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&#0142;), which is not
+ * widely used and for which there is no Unicode equivalent.
+ * One reference shows 0xA8 as U+00A7 SECTION SIGN, but we're
+ * told that's incorrect. */
+ 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x0105, 0x00E7,
+ 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x0107, 0x00C4, 0x0104,
+ 0x0118, 0x0119, 0x0142, 0x00F4, 0x00F6, 0x0106, 0x00FB, 0x00F9,
+ 0x015a, 0x00D6, 0x00DC, 0xFFFD, 0x0141, 0x00A5, 0x015b, 0x0192,
+ 0x0179, 0x017b, 0x00F3, 0x00d3, 0x0144, 0x0143, 0x017a, 0x017c,
+ 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
+ 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
+ 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
+ 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+ 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
+ 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
+ 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
+ 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+
+struct cp_list_item {
+ char *name;
+ int codepage;
+ int cp_size;
+ const wchar_t *cp_table;
+};
+
+static const struct cp_list_item cp_list[] = {
+ {"UTF-8", CP_UTF8},
+
+ {"ISO-8859-1:1998 (Latin-1, West Europe)", 0, 96, iso_8859_1},
+ {"ISO-8859-2:1999 (Latin-2, East Europe)", 0, 96, iso_8859_2},
+ {"ISO-8859-3:1999 (Latin-3, South Europe)", 0, 96, iso_8859_3},
+ {"ISO-8859-4:1998 (Latin-4, North Europe)", 0, 96, iso_8859_4},
+ {"ISO-8859-5:1999 (Latin/Cyrillic)", 0, 96, iso_8859_5},
+ {"ISO-8859-6:1999 (Latin/Arabic)", 0, 96, iso_8859_6},
+ {"ISO-8859-7:1987 (Latin/Greek)", 0, 96, iso_8859_7},
+ {"ISO-8859-8:1999 (Latin/Hebrew)", 0, 96, iso_8859_8},
+ {"ISO-8859-9:1999 (Latin-5, Turkish)", 0, 96, iso_8859_9},
+ {"ISO-8859-10:1998 (Latin-6, Nordic)", 0, 96, iso_8859_10},
+ {"ISO-8859-11:2001 (Latin/Thai)", 0, 96, iso_8859_11},
+ {"ISO-8859-13:1998 (Latin-7, Baltic)", 0, 96, iso_8859_13},
+ {"ISO-8859-14:1998 (Latin-8, Celtic)", 0, 96, iso_8859_14},
+ {"ISO-8859-15:1999 (Latin-9, \"euro\")", 0, 96, iso_8859_15},
+ {"ISO-8859-16:2001 (Latin-10, Balkan)", 0, 96, iso_8859_16},
+
+ {"KOI8-U", 0, 128, koi8_u},
+ {"KOI8-R", 20866},
+ {"HP-ROMAN8", 0, 96, roman8},
+ {"VSCII", 0, 256, vscii},
+ {"DEC-MCS", 0, 96, dec_mcs},
+
+ {"Win1250 (Central European)", 1250},
+ {"Win1251 (Cyrillic)", 1251},
+ {"Win1252 (Western)", 1252},
+ {"Win1253 (Greek)", 1253},
+ {"Win1254 (Turkish)", 1254},
+ {"Win1255 (Hebrew)", 1255},
+ {"Win1256 (Arabic)", 1256},
+ {"Win1257 (Baltic)", 1257},
+ {"Win1258 (Vietnamese)", 1258},
+
+ {"CP437", 437},
+ {"CP620 (Mazovia)", 0, 128, mazovia},
+ {"CP819", 28591},
+ {"CP852", 852},
+ {"CP878", 20866},
+
+ {"Use font encoding", -1},
+
+ {0, 0}
+};
+
+static void link_font(WCHAR *line_tbl, WCHAR *font_tbl, WCHAR attr);
+
+/*
+ * We keep a collection of reverse mappings from Unicode back to code pages,
+ * in the form of array[256] of array[256] of char. These live forever in a
+ * local tree234, and we just make a new one whenever we find a need.
+ */
+typedef struct reverse_mapping {
+ int codepage;
+ char **blocks;
+} reverse_mapping;
+static tree234 *reverse_mappings = NULL;
+
+static int reverse_mapping_cmp(void *av, void *bv)
+{
+ const reverse_mapping *a = (const reverse_mapping *)av;
+ const reverse_mapping *b = (const reverse_mapping *)bv;
+ if (a->codepage < b->codepage)
+ return -1;
+ if (a->codepage > b->codepage)
+ return +1;
+ return 0;
+}
+
+static int reverse_mapping_find(void *av, void *bv)
+{
+ const reverse_mapping *a = (const reverse_mapping *)av;
+ int b_codepage = *(const int *)bv;
+ if (a->codepage < b_codepage)
+ return -1;
+ if (a->codepage > b_codepage)
+ return +1;
+ return 0;
+}
+
+static reverse_mapping *get_existing_reverse_mapping(int codepage)
+{
+ if (!reverse_mappings)
+ return NULL;
+ return find234(reverse_mappings, &codepage, reverse_mapping_find);
+}
+
+static reverse_mapping *make_reverse_mapping_inner(
+ int codepage, const wchar_t *mapping)
+{
+ if (!reverse_mappings)
+ reverse_mappings = newtree234(reverse_mapping_cmp);
+
+ reverse_mapping *rmap = snew(reverse_mapping);
+ rmap->blocks = snewn(256, char *);
+ memset(rmap->blocks, 0, 256 * sizeof(char *));
+
+ for (size_t i = 0; i < 256; i++) {
+ /* These special kinds of value correspond to no Unicode character */
+ if (DIRECT_CHAR(mapping[i]))
+ continue;
+ if (DIRECT_FONT(mapping[i]))
+ continue;
+
+ size_t chr = mapping[i];
+ size_t block = chr >> 8, index = chr & 0xFF;
+
+ if (!rmap->blocks[block]) {
+ rmap->blocks[block] = snewn(256, char);
+ memset(rmap->blocks[block], 0, 256);
+ }
+ rmap->blocks[block][index] = i;
+ }
+
+ rmap->codepage = codepage;
+ reverse_mapping *added = add234(reverse_mappings, rmap);
+ assert(added == rmap); /* we already checked it wasn't already in there */
+ return added;
+}
+
+static void make_reverse_mapping(int codepage, const wchar_t *mapping)
+{
+ if (get_existing_reverse_mapping(codepage))
+ return; /* we've already got this one */
+ make_reverse_mapping_inner(codepage, mapping);
+}
+
+static reverse_mapping *get_reverse_mapping(int codepage)
+{
+ /*
+ * Try harder to get a reverse mapping for a codepage we implement
+ * internally via a translation table, by hastily making it if it doesn't
+ * already exist.
+ */
+
+ reverse_mapping *rmap = get_existing_reverse_mapping(codepage);
+ if (rmap)
+ return rmap;
+
+ if (codepage < 65536)
+ return NULL;
+ if (codepage >= 65536 + lenof(cp_list))
+ return NULL;
+ const struct cp_list_item *cp = &cp_list[codepage - 65536];
+ if (!cp->cp_table)
+ return NULL;
+
+ wchar_t mapping[256];
+ get_unitab(codepage, mapping, 0);
+ return make_reverse_mapping_inner(codepage, mapping);
+}
+
+void init_ucs(Conf *conf, struct unicode_data *ucsdata)
+{
+ int i;
+ bool used_dtf = false;
+ int vtmode;
+
+ /* Decide on the Line and Font codepages */
+ ucsdata->line_codepage = decode_codepage(conf_get_str(conf,
+ CONF_line_codepage));
+
+ if (ucsdata->font_codepage <= 0) {
+ ucsdata->font_codepage=0;
+ ucsdata->dbcs_screenfont=false;
+ }
+
+ vtmode = conf_get_int(conf, CONF_vtmode);
+ if (vtmode == VT_OEMONLY) {
+ ucsdata->font_codepage = 437;
+ ucsdata->dbcs_screenfont = false;
+ if (ucsdata->line_codepage <= 0)
+ ucsdata->line_codepage = GetACP();
+ } else if (ucsdata->line_codepage <= 0)
+ ucsdata->line_codepage = ucsdata->font_codepage;
+
+ /* Collect screen font ucs table */
+ if (ucsdata->dbcs_screenfont || ucsdata->font_codepage == 0) {
+ get_unitab(ucsdata->font_codepage, ucsdata->unitab_font, 2);
+ for (i = 128; i < 256; i++)
+ ucsdata->unitab_font[i] = (WCHAR) (CSET_ACP + i);
+ } else {
+ get_unitab(ucsdata->font_codepage, ucsdata->unitab_font, 1);
+
+ /* CP437 fonts are often broken ... */
+ if (ucsdata->font_codepage == 437)
+ ucsdata->unitab_font[0] = ucsdata->unitab_font[255] = 0xFFFF;
+ }
+ if (vtmode == VT_XWINDOWS)
+ memcpy(ucsdata->unitab_font + 1, unitab_xterm_std,
+ sizeof(unitab_xterm_std));
+
+ /* Collect OEMCP ucs table */
+ get_unitab(CP_OEMCP, ucsdata->unitab_oemcp, 1);
+
+ /* Collect CP437 ucs table for SCO acs */
+ if (vtmode == VT_OEMANSI || vtmode == VT_XWINDOWS)
+ memcpy(ucsdata->unitab_scoacs, ucsdata->unitab_oemcp,
+ sizeof(ucsdata->unitab_scoacs));
+ else
+ get_unitab(437, ucsdata->unitab_scoacs, 1);
+
+ /* Collect line set ucs table */
+ if (ucsdata->line_codepage == ucsdata->font_codepage &&
+ (ucsdata->dbcs_screenfont ||
+ vtmode == VT_POORMAN || ucsdata->font_codepage==0)) {
+
+ /* For DBCS and POOR fonts force direct to font */
+ used_dtf = true;
+ for (i = 0; i < 32; i++)
+ ucsdata->unitab_line[i] = (WCHAR) i;
+ for (i = 32; i < 256; i++)
+ ucsdata->unitab_line[i] = (WCHAR) (CSET_ACP + i);
+ ucsdata->unitab_line[127] = (WCHAR) 127;
+ } else {
+ get_unitab(ucsdata->line_codepage, ucsdata->unitab_line, 0);
+ }
+
+#if 0
+ debug("Line cp%d, Font cp%d%s\n", ucsdata->line_codepage,
+ ucsdata->font_codepage, ucsdata->dbcs_screenfont ? " DBCS" : "");
+
+ for (i = 0; i < 256; i += 16) {
+ for (j = 0; j < 16; j++) {
+ debug("%04x%s", ucsdata->unitab_line[i + j], j == 15 ? "" : ",");
+ }
+ debug("\n");
+ }
+#endif
+
+ /* VT100 graphics - NB: Broken for non-ascii CP's */
+ memcpy(ucsdata->unitab_xterm, ucsdata->unitab_line,
+ sizeof(ucsdata->unitab_xterm));
+ memcpy(ucsdata->unitab_xterm + '`', unitab_xterm_std,
+ sizeof(unitab_xterm_std));
+ ucsdata->unitab_xterm['_'] = ' ';
+
+ if (!used_dtf) {
+ /* Make sure a reverse mapping exists for this code page. */
+ make_reverse_mapping(ucsdata->line_codepage, ucsdata->unitab_line);
+ }
+
+ /* Find the line control characters. */
+ for (i = 0; i < 256; i++)
+ if (ucsdata->unitab_line[i] < ' '
+ || (ucsdata->unitab_line[i] >= 0x7F &&
+ ucsdata->unitab_line[i] < 0xA0))
+ ucsdata->unitab_ctrl[i] = i;
+ else
+ ucsdata->unitab_ctrl[i] = 0xFF;
+
+ /* Generate line->screen direct conversion links. */
+ if (vtmode == VT_OEMANSI || vtmode == VT_XWINDOWS)
+ link_font(ucsdata->unitab_scoacs, ucsdata->unitab_oemcp, CSET_OEMCP);
+
+ link_font(ucsdata->unitab_line, ucsdata->unitab_font, CSET_ACP);
+ link_font(ucsdata->unitab_scoacs, ucsdata->unitab_font, CSET_ACP);
+ link_font(ucsdata->unitab_xterm, ucsdata->unitab_font, CSET_ACP);
+
+ if (vtmode == VT_OEMANSI || vtmode == VT_XWINDOWS) {
+ link_font(ucsdata->unitab_line, ucsdata->unitab_oemcp, CSET_OEMCP);
+ link_font(ucsdata->unitab_xterm, ucsdata->unitab_oemcp, CSET_OEMCP);
+ }
+
+ if (ucsdata->dbcs_screenfont &&
+ ucsdata->font_codepage != ucsdata->line_codepage) {
+ /* F***ing Microsoft fonts, Japanese and Korean codepage fonts
+ * have a currency symbol at 0x5C but their unicode value is
+ * still given as U+005C not the correct U+00A5. */
+ ucsdata->unitab_line['\\'] = CSET_OEMCP + '\\';
+ }
+
+ /* Last chance, if !unicode then try poorman links. */
+ if (vtmode != VT_UNICODE) {
+ static const char poorman_scoacs[] =
+ "CueaaaaceeeiiiAAE**ooouuyOUc$YPsaiounNao?++**!<>###||||++||++++++--|-+||++--|-+----++++++++##||#aBTPEsyt******EN=+><++-=... n2* ";
+ static const char poorman_latin1[] =
+ " !cL.Y|S\"Ca<--R~o+23'u|.,1o>///?AAAAAAACEEEEIIIIDNOOOOOxOUUUUYPBaaaaaaaceeeeiiiionooooo/ouuuuypy";
+ static const char poorman_vt100[] = "*#****o~**+++++-----++++|****L.";
+
+ for (i = 160; i < 256; i++)
+ if (!DIRECT_FONT(ucsdata->unitab_line[i]) &&
+ ucsdata->unitab_line[i] >= 160 &&
+ ucsdata->unitab_line[i] < 256) {
+ ucsdata->unitab_line[i] =
+ (WCHAR) (CSET_ACP +
+ poorman_latin1[ucsdata->unitab_line[i] - 160]);
+ }
+ for (i = 96; i < 127; i++)
+ if (!DIRECT_FONT(ucsdata->unitab_xterm[i]))
+ ucsdata->unitab_xterm[i] =
+ (WCHAR) (CSET_ACP + poorman_vt100[i - 96]);
+ for(i=128;i<256;i++)
+ if (!DIRECT_FONT(ucsdata->unitab_scoacs[i]))
+ ucsdata->unitab_scoacs[i] =
+ (WCHAR) (CSET_ACP + poorman_scoacs[i - 128]);
+ }
+}
+
+static void link_font(WCHAR *line_tbl, WCHAR *font_tbl, WCHAR attr)
+{
+ int font_index, line_index, i;
+ for (line_index = 0; line_index < 256; line_index++) {
+ if (DIRECT_FONT(line_tbl[line_index]))
+ continue;
+ for(i = 0; i < 256; i++) {
+ font_index = ((32 + i) & 0xFF);
+ if (line_tbl[line_index] == font_tbl[font_index]) {
+ line_tbl[line_index] = (WCHAR) (attr + font_index);
+ break;
+ }
+ }
+ }
+}
+
+wchar_t xlat_uskbd2cyrllic(int ch)
+{
+ static const wchar_t cyrtab[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 0x042d, 35, 36, 37, 38, 0x044d,
+ 40, 41, 42, 0x0406, 0x0431, 0x0454, 0x044e, 0x002e,
+ 48, 49, 50, 51, 52, 53, 54, 55,
+ 56, 57, 0x0416, 0x0436, 0x0411, 0x0456, 0x042e, 0x002c,
+ 64, 0x0424, 0x0418, 0x0421, 0x0412, 0x0423, 0x0410, 0x041f,
+ 0x0420, 0x0428, 0x041e, 0x041b, 0x0414, 0x042c, 0x0422, 0x0429,
+ 0x0417, 0x0419, 0x041a, 0x042b, 0x0415, 0x0413, 0x041c, 0x0426,
+ 0x0427, 0x041d, 0x042f, 0x0445, 0x0457, 0x044a, 94, 0x0404,
+ 96, 0x0444, 0x0438, 0x0441, 0x0432, 0x0443, 0x0430, 0x043f,
+ 0x0440, 0x0448, 0x043e, 0x043b, 0x0434, 0x044c, 0x0442, 0x0449,
+ 0x0437, 0x0439, 0x043a, 0x044b, 0x0435, 0x0433, 0x043c, 0x0446,
+ 0x0447, 0x043d, 0x044f, 0x0425, 0x0407, 0x042a, 126, 127
+ };
+ return cyrtab[ch&0x7F];
+}
+
+static int check_compose_internal(int first, int second, int recurse)
+{
+
+ static const struct {
+ char first, second;
+ wchar_t composed;
+ } composetbl[] = {
+ {0x2b, 0x2b, 0x0023},
+ {0x41, 0x41, 0x0040},
+ {0x28, 0x28, 0x005b},
+ {0x2f, 0x2f, 0x005c},
+ {0x29, 0x29, 0x005d},
+ {0x28, 0x2d, 0x007b},
+ {0x2d, 0x29, 0x007d},
+ {0x2f, 0x5e, 0x007c},
+ {0x21, 0x21, 0x00a1},
+ {0x43, 0x2f, 0x00a2},
+ {0x43, 0x7c, 0x00a2},
+ {0x4c, 0x2d, 0x00a3},
+ {0x4c, 0x3d, 0x20a4},
+ {0x58, 0x4f, 0x00a4},
+ {0x58, 0x30, 0x00a4},
+ {0x59, 0x2d, 0x00a5},
+ {0x59, 0x3d, 0x00a5},
+ {0x7c, 0x7c, 0x00a6},
+ {0x53, 0x4f, 0x00a7},
+ {0x53, 0x21, 0x00a7},
+ {0x53, 0x30, 0x00a7},
+ {0x22, 0x22, 0x00a8},
+ {0x43, 0x4f, 0x00a9},
+ {0x43, 0x30, 0x00a9},
+ {0x41, 0x5f, 0x00aa},
+ {0x3c, 0x3c, 0x00ab},
+ {0x2c, 0x2d, 0x00ac},
+ {0x2d, 0x2d, 0x00ad},
+ {0x52, 0x4f, 0x00ae},
+ {0x2d, 0x5e, 0x00af},
+ {0x30, 0x5e, 0x00b0},
+ {0x2b, 0x2d, 0x00b1},
+ {0x32, 0x5e, 0x00b2},
+ {0x33, 0x5e, 0x00b3},
+ {0x27, 0x27, 0x00b4},
+ {0x2f, 0x55, 0x00b5},
+ {0x50, 0x21, 0x00b6},
+ {0x2e, 0x5e, 0x00b7},
+ {0x2c, 0x2c, 0x00b8},
+ {0x31, 0x5e, 0x00b9},
+ {0x4f, 0x5f, 0x00ba},
+ {0x3e, 0x3e, 0x00bb},
+ {0x31, 0x34, 0x00bc},
+ {0x31, 0x32, 0x00bd},
+ {0x33, 0x34, 0x00be},
+ {0x3f, 0x3f, 0x00bf},
+ {0x60, 0x41, 0x00c0},
+ {0x27, 0x41, 0x00c1},
+ {0x5e, 0x41, 0x00c2},
+ {0x7e, 0x41, 0x00c3},
+ {0x22, 0x41, 0x00c4},
+ {0x2a, 0x41, 0x00c5},
+ {0x41, 0x45, 0x00c6},
+ {0x2c, 0x43, 0x00c7},
+ {0x60, 0x45, 0x00c8},
+ {0x27, 0x45, 0x00c9},
+ {0x5e, 0x45, 0x00ca},
+ {0x22, 0x45, 0x00cb},
+ {0x60, 0x49, 0x00cc},
+ {0x27, 0x49, 0x00cd},
+ {0x5e, 0x49, 0x00ce},
+ {0x22, 0x49, 0x00cf},
+ {0x2d, 0x44, 0x00d0},
+ {0x7e, 0x4e, 0x00d1},
+ {0x60, 0x4f, 0x00d2},
+ {0x27, 0x4f, 0x00d3},
+ {0x5e, 0x4f, 0x00d4},
+ {0x7e, 0x4f, 0x00d5},
+ {0x22, 0x4f, 0x00d6},
+ {0x58, 0x58, 0x00d7},
+ {0x2f, 0x4f, 0x00d8},
+ {0x60, 0x55, 0x00d9},
+ {0x27, 0x55, 0x00da},
+ {0x5e, 0x55, 0x00db},
+ {0x22, 0x55, 0x00dc},
+ {0x27, 0x59, 0x00dd},
+ {0x48, 0x54, 0x00de},
+ {0x73, 0x73, 0x00df},
+ {0x60, 0x61, 0x00e0},
+ {0x27, 0x61, 0x00e1},
+ {0x5e, 0x61, 0x00e2},
+ {0x7e, 0x61, 0x00e3},
+ {0x22, 0x61, 0x00e4},
+ {0x2a, 0x61, 0x00e5},
+ {0x61, 0x65, 0x00e6},
+ {0x2c, 0x63, 0x00e7},
+ {0x60, 0x65, 0x00e8},
+ {0x27, 0x65, 0x00e9},
+ {0x5e, 0x65, 0x00ea},
+ {0x22, 0x65, 0x00eb},
+ {0x60, 0x69, 0x00ec},
+ {0x27, 0x69, 0x00ed},
+ {0x5e, 0x69, 0x00ee},
+ {0x22, 0x69, 0x00ef},
+ {0x2d, 0x64, 0x00f0},
+ {0x7e, 0x6e, 0x00f1},
+ {0x60, 0x6f, 0x00f2},
+ {0x27, 0x6f, 0x00f3},
+ {0x5e, 0x6f, 0x00f4},
+ {0x7e, 0x6f, 0x00f5},
+ {0x22, 0x6f, 0x00f6},
+ {0x3a, 0x2d, 0x00f7},
+ {0x6f, 0x2f, 0x00f8},
+ {0x60, 0x75, 0x00f9},
+ {0x27, 0x75, 0x00fa},
+ {0x5e, 0x75, 0x00fb},
+ {0x22, 0x75, 0x00fc},
+ {0x27, 0x79, 0x00fd},
+ {0x68, 0x74, 0x00fe},
+ {0x22, 0x79, 0x00ff},
+ /* Unicode extras. */
+ {0x6f, 0x65, 0x0153},
+ {0x4f, 0x45, 0x0152},
+ /* Compose pairs from UCS */
+ {0x41, 0x2D, 0x0100},
+ {0x61, 0x2D, 0x0101},
+ {0x43, 0x27, 0x0106},
+ {0x63, 0x27, 0x0107},
+ {0x43, 0x5E, 0x0108},
+ {0x63, 0x5E, 0x0109},
+ {0x45, 0x2D, 0x0112},
+ {0x65, 0x2D, 0x0113},
+ {0x47, 0x5E, 0x011C},
+ {0x67, 0x5E, 0x011D},
+ {0x47, 0x2C, 0x0122},
+ {0x67, 0x2C, 0x0123},
+ {0x48, 0x5E, 0x0124},
+ {0x68, 0x5E, 0x0125},
+ {0x49, 0x7E, 0x0128},
+ {0x69, 0x7E, 0x0129},
+ {0x49, 0x2D, 0x012A},
+ {0x69, 0x2D, 0x012B},
+ {0x4A, 0x5E, 0x0134},
+ {0x6A, 0x5E, 0x0135},
+ {0x4B, 0x2C, 0x0136},
+ {0x6B, 0x2C, 0x0137},
+ {0x4C, 0x27, 0x0139},
+ {0x6C, 0x27, 0x013A},
+ {0x4C, 0x2C, 0x013B},
+ {0x6C, 0x2C, 0x013C},
+ {0x4E, 0x27, 0x0143},
+ {0x6E, 0x27, 0x0144},
+ {0x4E, 0x2C, 0x0145},
+ {0x6E, 0x2C, 0x0146},
+ {0x4F, 0x2D, 0x014C},
+ {0x6F, 0x2D, 0x014D},
+ {0x52, 0x27, 0x0154},
+ {0x72, 0x27, 0x0155},
+ {0x52, 0x2C, 0x0156},
+ {0x72, 0x2C, 0x0157},
+ {0x53, 0x27, 0x015A},
+ {0x73, 0x27, 0x015B},
+ {0x53, 0x5E, 0x015C},
+ {0x73, 0x5E, 0x015D},
+ {0x53, 0x2C, 0x015E},
+ {0x73, 0x2C, 0x015F},
+ {0x54, 0x2C, 0x0162},
+ {0x74, 0x2C, 0x0163},
+ {0x55, 0x7E, 0x0168},
+ {0x75, 0x7E, 0x0169},
+ {0x55, 0x2D, 0x016A},
+ {0x75, 0x2D, 0x016B},
+ {0x55, 0x2A, 0x016E},
+ {0x75, 0x2A, 0x016F},
+ {0x57, 0x5E, 0x0174},
+ {0x77, 0x5E, 0x0175},
+ {0x59, 0x5E, 0x0176},
+ {0x79, 0x5E, 0x0177},
+ {0x59, 0x22, 0x0178},
+ {0x5A, 0x27, 0x0179},
+ {0x7A, 0x27, 0x017A},
+ {0x47, 0x27, 0x01F4},
+ {0x67, 0x27, 0x01F5},
+ {0x4E, 0x60, 0x01F8},
+ {0x6E, 0x60, 0x01F9},
+ {0x45, 0x2C, 0x0228},
+ {0x65, 0x2C, 0x0229},
+ {0x59, 0x2D, 0x0232},
+ {0x79, 0x2D, 0x0233},
+ {0x44, 0x2C, 0x1E10},
+ {0x64, 0x2C, 0x1E11},
+ {0x47, 0x2D, 0x1E20},
+ {0x67, 0x2D, 0x1E21},
+ {0x48, 0x22, 0x1E26},
+ {0x68, 0x22, 0x1E27},
+ {0x48, 0x2C, 0x1E28},
+ {0x68, 0x2C, 0x1E29},
+ {0x4B, 0x27, 0x1E30},
+ {0x6B, 0x27, 0x1E31},
+ {0x4D, 0x27, 0x1E3E},
+ {0x6D, 0x27, 0x1E3F},
+ {0x50, 0x27, 0x1E54},
+ {0x70, 0x27, 0x1E55},
+ {0x56, 0x7E, 0x1E7C},
+ {0x76, 0x7E, 0x1E7D},
+ {0x57, 0x60, 0x1E80},
+ {0x77, 0x60, 0x1E81},
+ {0x57, 0x27, 0x1E82},
+ {0x77, 0x27, 0x1E83},
+ {0x57, 0x22, 0x1E84},
+ {0x77, 0x22, 0x1E85},
+ {0x58, 0x22, 0x1E8C},
+ {0x78, 0x22, 0x1E8D},
+ {0x5A, 0x5E, 0x1E90},
+ {0x7A, 0x5E, 0x1E91},
+ {0x74, 0x22, 0x1E97},
+ {0x77, 0x2A, 0x1E98},
+ {0x79, 0x2A, 0x1E99},
+ {0x45, 0x7E, 0x1EBC},
+ {0x65, 0x7E, 0x1EBD},
+ {0x59, 0x60, 0x1EF2},
+ {0x79, 0x60, 0x1EF3},
+ {0x59, 0x7E, 0x1EF8},
+ {0x79, 0x7E, 0x1EF9},
+ /* Compatible/possibles from UCS */
+ {0x49, 0x4A, 0x0132},
+ {0x69, 0x6A, 0x0133},
+ {0x4C, 0x4A, 0x01C7},
+ {0x4C, 0x6A, 0x01C8},
+ {0x6C, 0x6A, 0x01C9},
+ {0x4E, 0x4A, 0x01CA},
+ {0x4E, 0x6A, 0x01CB},
+ {0x6E, 0x6A, 0x01CC},
+ {0x44, 0x5A, 0x01F1},
+ {0x44, 0x7A, 0x01F2},
+ {0x64, 0x7A, 0x01F3},
+ {0x2E, 0x2E, 0x2025},
+ {0x21, 0x21, 0x203C},
+ {0x3F, 0x21, 0x2048},
+ {0x21, 0x3F, 0x2049},
+ {0x52, 0x73, 0x20A8},
+ {0x4E, 0x6F, 0x2116},
+ {0x53, 0x4D, 0x2120},
+ {0x54, 0x4D, 0x2122},
+ {0x49, 0x49, 0x2161},
+ {0x49, 0x56, 0x2163},
+ {0x56, 0x49, 0x2165},
+ {0x49, 0x58, 0x2168},
+ {0x58, 0x49, 0x216A},
+ {0x69, 0x69, 0x2171},
+ {0x69, 0x76, 0x2173},
+ {0x76, 0x69, 0x2175},
+ {0x69, 0x78, 0x2178},
+ {0x78, 0x69, 0x217A},
+ {0x31, 0x30, 0x2469},
+ {0x31, 0x31, 0x246A},
+ {0x31, 0x32, 0x246B},
+ {0x31, 0x33, 0x246C},
+ {0x31, 0x34, 0x246D},
+ {0x31, 0x35, 0x246E},
+ {0x31, 0x36, 0x246F},
+ {0x31, 0x37, 0x2470},
+ {0x31, 0x38, 0x2471},
+ {0x31, 0x39, 0x2472},
+ {0x32, 0x30, 0x2473},
+ {0x31, 0x2E, 0x2488},
+ {0x32, 0x2E, 0x2489},
+ {0x33, 0x2E, 0x248A},
+ {0x34, 0x2E, 0x248B},
+ {0x35, 0x2E, 0x248C},
+ {0x36, 0x2E, 0x248D},
+ {0x37, 0x2E, 0x248E},
+ {0x38, 0x2E, 0x248F},
+ {0x39, 0x2E, 0x2490},
+ {0x64, 0x61, 0x3372},
+ {0x41, 0x55, 0x3373},
+ {0x6F, 0x56, 0x3375},
+ {0x70, 0x63, 0x3376},
+ {0x70, 0x41, 0x3380},
+ {0x6E, 0x41, 0x3381},
+ {0x6D, 0x41, 0x3383},
+ {0x6B, 0x41, 0x3384},
+ {0x4B, 0x42, 0x3385},
+ {0x4D, 0x42, 0x3386},
+ {0x47, 0x42, 0x3387},
+ {0x70, 0x46, 0x338A},
+ {0x6E, 0x46, 0x338B},
+ {0x6D, 0x67, 0x338E},
+ {0x6B, 0x67, 0x338F},
+ {0x48, 0x7A, 0x3390},
+ {0x66, 0x6D, 0x3399},
+ {0x6E, 0x6D, 0x339A},
+ {0x6D, 0x6D, 0x339C},
+ {0x63, 0x6D, 0x339D},
+ {0x6B, 0x6D, 0x339E},
+ {0x50, 0x61, 0x33A9},
+ {0x70, 0x73, 0x33B0},
+ {0x6E, 0x73, 0x33B1},
+ {0x6D, 0x73, 0x33B3},
+ {0x70, 0x56, 0x33B4},
+ {0x6E, 0x56, 0x33B5},
+ {0x6D, 0x56, 0x33B7},
+ {0x6B, 0x56, 0x33B8},
+ {0x4D, 0x56, 0x33B9},
+ {0x70, 0x57, 0x33BA},
+ {0x6E, 0x57, 0x33BB},
+ {0x6D, 0x57, 0x33BD},
+ {0x6B, 0x57, 0x33BE},
+ {0x4D, 0x57, 0x33BF},
+ {0x42, 0x71, 0x33C3},
+ {0x63, 0x63, 0x33C4},
+ {0x63, 0x64, 0x33C5},
+ {0x64, 0x42, 0x33C8},
+ {0x47, 0x79, 0x33C9},
+ {0x68, 0x61, 0x33CA},
+ {0x48, 0x50, 0x33CB},
+ {0x69, 0x6E, 0x33CC},
+ {0x4B, 0x4B, 0x33CD},
+ {0x4B, 0x4D, 0x33CE},
+ {0x6B, 0x74, 0x33CF},
+ {0x6C, 0x6D, 0x33D0},
+ {0x6C, 0x6E, 0x33D1},
+ {0x6C, 0x78, 0x33D3},
+ {0x6D, 0x62, 0x33D4},
+ {0x50, 0x48, 0x33D7},
+ {0x50, 0x52, 0x33DA},
+ {0x73, 0x72, 0x33DB},
+ {0x53, 0x76, 0x33DC},
+ {0x57, 0x62, 0x33DD},
+ {0x66, 0x66, 0xFB00},
+ {0x66, 0x69, 0xFB01},
+ {0x66, 0x6C, 0xFB02},
+ {0x73, 0x74, 0xFB06},
+ {0, 0, 0}
+ }, *c;
+
+ int nc = -1;
+
+ for (c = composetbl; c->first; c++) {
+ if (c->first == first && c->second == second)
+ return c->composed;
+ }
+
+ if (recurse == 0) {
+ nc = check_compose_internal(second, first, 1);
+ if (nc == -1)
+ nc = check_compose_internal(toupper(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(const char *cp_name)
+{
+ const char *s, *d;
+ const struct cp_list_item *cpi;
+ int codepage = -1;
+ CPINFO cpinfo;
+
+ if (!cp_name || !*cp_name)
+ return CP_UTF8; /* default */
+
+ for (cpi = cp_list; cpi->name; cpi++) {
+ s = cp_name;
+ d = cpi->name;
+ for (;;) {
+ while (*s && !isalnum(*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)
+{
+ reverse_mapping *rmap = get_reverse_mapping(codepage);
+
+ if (rmap) {
+ /* Do this by array lookup if we can. */
+ if (wclen < 0) {
+ for (wclen = 0; wcstr[wclen++] ;); /* will include the NUL */
+ }
+ char *p;
+ int i;
+ for (p = mbstr, i = 0; i < wclen; i++) {
+ wchar_t ch = wcstr[i];
+ int by;
+ const char *p1;
+
+ #define WRITECH(chr) do \
+ { \
+ assert(p - mbstr < mblen); \
+ *p++ = (char)(chr); \
+ } while (0)
+
+ if ((p1 = rmap->blocks[(ch >> 8) & 0xFF]) != NULL &&
+ (by = p1[ch & 0xFF]) != '\0')
+ WRITECH(by);
+ else if (ch < 0x80)
+ WRITECH(ch);
+ else if (defchr)
+ for (const char *q = defchr; *q; q++)
+ WRITECH(*q);
+#if 1
+ else
+ WRITECH('.');
+#endif
+
+ #undef WRITECH
+ }
+ return p - mbstr;
+ } else {
+ int defused, ret;
+ ret = WideCharToMultiByte(codepage, flags, wcstr, wclen,
+ mbstr, mblen, defchr, &defused);
+ if (ret)
+ return ret;
+
+#ifdef LEGACY_WINDOWS
+ /*
+ * Fallback for legacy platforms too old to support UTF-8: if
+ * the codepage is UTF-8, we can do the translation ourselves.
+ */
+ if (codepage == CP_UTF8 && mblen > 0 && wclen > 0) {
+ size_t remaining = mblen;
+ char *p = mbstr;
+
+ while (wclen > 0) {
+ unsigned long wc = (wclen--, *wcstr++);
+ if (wclen > 0 && IS_SURROGATE_PAIR(wc, *wcstr)) {
+ wc = FROM_SURROGATES(wc, *wcstr);
+ wclen--, wcstr++;
+ }
+
+ char utfbuf[6];
+ size_t utflen = encode_utf8(utfbuf, wc);
+ if (utflen <= remaining) {
+ memcpy(p, utfbuf, utflen);
+ p += utflen;
+ remaining -= utflen;
+ } else {
+ return p - mbstr;
+ }
+ }
+
+ return p - mbstr;
+ }
+#endif
+
+ /* No other fallbacks are available */
+ return 0;
+ }
+}
+
+int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen,
+ wchar_t *wcstr, int wclen)
+{
+ if (codepage >= 65536) {
+ /* Character set not known to Windows, so we'll have to
+ * translate it ourself */
+ size_t index = codepage - 65536;
+ if (index >= lenof(cp_list))
+ return 0;
+ const struct cp_list_item *cp = &cp_list[index];
+ if (!cp->cp_table)
+ return 0;
+
+ size_t remaining = wclen;
+ wchar_t *p = wcstr;
+ unsigned tablebase = 256 - cp->cp_size;
+
+ while (mblen > 0) {
+ mblen--;
+ unsigned c = 0xFF & *mbstr++;
+ wchar_t wc = (c < tablebase ? c : cp->cp_table[c - tablebase]);
+ if (remaining > 0) {
+ remaining--;
+ *p++ = wc;
+ } else {
+ return p - wcstr;
+ }
+ }
+
+ return p - wcstr;
+ }
+
+ int ret = MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen);
+ if (ret)
+ return ret;
+
+#ifdef LEGACY_WINDOWS
+ /*
+ * Fallback for legacy platforms too old to support UTF-8: if the
+ * codepage is UTF-8, we can do the translation ourselves.
+ */
+ if (codepage == CP_UTF8 && mblen > 0 && wclen > 0) {
+ size_t remaining = wclen;
+ wchar_t *p = wcstr;
+
+ while (mblen > 0) {
+ char utfbuf[7];
+ int thissize = mblen < 6 ? mblen : 6;
+ memcpy(utfbuf, mbstr, thissize);
+ utfbuf[thissize] = '\0';
+
+ const char *utfptr = utfbuf;
+ wchar_t wcbuf[2];
+ size_t nwc = decode_utf8_to_wchar(&utfptr, wcbuf);
+
+ for (size_t i = 0; i < nwc; i++) {
+ if (remaining > 0) {
+ remaining--;
+ *p++ = wcbuf[i];
+ } else {
+ return p - wcstr;
+ }
+ }
+
+ mbstr += (utfptr - utfbuf);
+ mblen -= (utfptr - utfbuf);
+ }
+
+ return p - wcstr;
+ }
+#endif
+
+ /* No other fallbacks are available */
+ return 0;
+}
+
+bool is_dbcs_leadbyte(int codepage, char byte)
+{
+ return IsDBCSLeadByteEx(codepage, byte);
+}
diff --git a/windows/utils/agent_mutex_name.c b/windows/utils/agent_mutex_name.c
new file mode 100644
index 00000000..949e8cfa
--- /dev/null
+++ b/windows/utils/agent_mutex_name.c
@@ -0,0 +1,14 @@
+/*
+ * Return the full pathname of the global mutex that Pageant uses at
+ * startup to atomically decide whether to be a server or a client.
+ */
+
+#include "putty.h"
+
+char *agent_mutex_name(void)
+{
+ char *username = get_username();
+ char *mutexname = dupprintf("Local\\pageant-mutex.%s", username);
+ sfree(username);
+ return mutexname;
+}
diff --git a/windows/utils/agent_named_pipe_name.c b/windows/utils/agent_named_pipe_name.c
new file mode 100644
index 00000000..aa64b3f6
--- /dev/null
+++ b/windows/utils/agent_named_pipe_name.c
@@ -0,0 +1,17 @@
+/*
+ * Return the full pathname of the named pipe Pageant will listen on.
+ * Used by both the Pageant server code and client code.
+ */
+
+#include "putty.h"
+#include "cryptoapi.h"
+
+char *agent_named_pipe_name(void)
+{
+ char *username = get_username();
+ char *suffix = capi_obfuscate_string("Pageant");
+ char *pipename = dupprintf("\\\\.\\pipe\\pageant.%s.%s", username, suffix);
+ sfree(username);
+ sfree(suffix);
+ return pipename;
+}
diff --git a/windows/utils/arm_arch_queries.c b/windows/utils/arm_arch_queries.c
new file mode 100644
index 00000000..b0193276
--- /dev/null
+++ b/windows/utils/arm_arch_queries.c
@@ -0,0 +1,45 @@
+/*
+ * Windows implementation of the OS query functions that detect Arm
+ * architecture extensions.
+ */
+
+#include "putty.h"
+#include "ssh.h"
+
+#if !(defined _M_ARM || defined _M_ARM64)
+/*
+ * For non-Arm, stub out these functions. This module shouldn't be
+ * _called_ in that situation anyway, but it will still be compiled
+ * (because that's easier than getting CMake to identify the
+ * architecture in all cases).
+ */
+#define IsProcessorFeaturePresent(...) false
+#endif
+
+bool platform_aes_neon_available(void)
+{
+ return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE);
+}
+
+bool platform_pmull_neon_available(void)
+{
+ return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE);
+}
+
+bool platform_sha256_neon_available(void)
+{
+ return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE);
+}
+
+bool platform_sha1_neon_available(void)
+{
+ return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE);
+}
+
+bool platform_sha512_neon_available(void)
+{
+ /* As of 2020-12-24, as far as I can tell from docs.microsoft.com,
+ * Windows on Arm does not yet provide a PF_ARM_V8_* flag for the
+ * SHA-512 architecture extension. */
+ return false;
+}
diff --git a/windows/utils/aux_match_opt.c b/windows/utils/aux_match_opt.c
new file mode 100644
index 00000000..190eddac
--- /dev/null
+++ b/windows/utils/aux_match_opt.c
@@ -0,0 +1,117 @@
+/*
+ * Helper function for matching command-line options in the Windows
+ * auxiliary tools (PuTTYgen and Pageant).
+ *
+ * Supports basically the usual kinds of option, including GNUish
+ * --foo long options and simple -f short options. But historically
+ * those tools have also supported long options with a single dash, so
+ * we don't go full GNU and report those as syntax errors.
+ */
+
+#include "putty.h"
+
+/*
+ * Call this to initialise the state structure.
+ */
+AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index,
+ aux_opt_error_fn_t opt_error)
+{
+ AuxMatchOpt amo[1];
+
+ amo->argc = argc;
+ amo->argv = argv;
+ amo->index = start_index;
+ amo->doing_opts = true;
+ amo->error = opt_error;
+
+ return amo[0];
+}
+
+/*
+ * Call this with a NULL-terminated list of synonymous option names.
+ * Point 'val' at a char * to receive the option argument, if one is
+ * expected. Set 'val' to NULL if no argument is expected.
+ */
+bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...)
+{
+ assert(amo->index < amo->argc);
+
+ /* Find the end of the option name */
+ char *opt = amo->argv[amo->index];
+ ptrlen argopt;
+ argopt.ptr = opt;
+ argopt.len = strcspn(opt, "=");
+
+ /* Normalise GNU-style --foo long options to the -foo that this
+ * tool has historically used */
+ ptrlen argopt2 = make_ptrlen(NULL, 0);
+ if (ptrlen_startswith(argopt, PTRLEN_LITERAL("--"), NULL))
+ ptrlen_startswith(argopt, PTRLEN_LITERAL("-"), &argopt2);
+
+ /* See if this option matches any of our synonyms */
+ va_list ap;
+ va_start(ap, optname);
+ bool matched = false;
+ while (optname) {
+ if (ptrlen_eq_string(argopt, optname)) {
+ matched = true;
+ break;
+ }
+ if (argopt2.ptr && strlen(optname) > 2 &&
+ ptrlen_eq_string(argopt2, optname)) {
+ matched = true;
+ break;
+ }
+ optname = va_arg(ap, const char *);
+ }
+ va_end(ap);
+ if (!matched)
+ return false;
+
+ /* Check for a value */
+ if (opt[argopt.len]) {
+ if (!val)
+ amo->error("option '%s' does not expect a value", opt);
+ *val = opt + argopt.len + 1;
+ amo->index++;
+ } else if (!val) {
+ amo->index++;
+ } else {
+ if (amo->index + 1 >= amo->argc)
+ amo->error("option '%s' expects a value", opt);
+ *val = amo->argv[amo->index + 1];
+ amo->index += 2;
+ }
+
+ return true;
+}
+
+/*
+ * Call this to return a non-option argument in *val.
+ */
+bool aux_match_arg(AuxMatchOpt *amo, char **val)
+{
+ assert(amo->index < amo->argc);
+ char *opt = amo->argv[amo->index];
+
+ if (!amo->doing_opts || opt[0] != '-' || !strcmp(opt, "-")) {
+ *val = opt;
+ amo->index++;
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * And call this to test whether we're done. Also handles '--'.
+ */
+bool aux_match_done(AuxMatchOpt *amo)
+{
+ if (amo->index < amo->argc && !strcmp(amo->argv[amo->index], "--")) {
+ amo->doing_opts = false;
+ amo->index++;
+ }
+
+ return amo->index >= amo->argc;
+}
diff --git a/windows/utils/centre_window.c b/windows/utils/centre_window.c
new file mode 100644
index 00000000..651fc279
--- /dev/null
+++ b/windows/utils/centre_window.c
@@ -0,0 +1,20 @@
+/*
+ * Centre a window on the screen. Used to position the main config box.
+ */
+
+#include "putty.h"
+
+void centre_window(HWND win)
+{
+ RECT rd, rw;
+
+ if (!GetWindowRect(GetDesktopWindow(), &rd))
+ return;
+ if (!GetWindowRect(win, &rw))
+ return;
+
+ MoveWindow(win,
+ (rd.right + rd.left + rw.left - rw.right) / 2,
+ (rd.bottom + rd.top + rw.top - rw.bottom) / 2,
+ rw.right - rw.left, rw.bottom - rw.top, true);
+}
diff --git a/windows/utils/cryptoapi.c b/windows/utils/cryptoapi.c
new file mode 100644
index 00000000..c3752c59
--- /dev/null
+++ b/windows/utils/cryptoapi.c
@@ -0,0 +1,89 @@
+/*
+ * windows/utils/cryptoapi.c: implementation of cryptoapi.h.
+ */
+
+#include "putty.h"
+
+#include "putty.h"
+#include "ssh.h"
+
+#include "cryptoapi.h"
+
+DEF_WINDOWS_FUNCTION(CryptProtectMemory);
+
+bool got_crypt(void)
+{
+ static bool attempted = false;
+ static bool successful;
+ static HMODULE crypt;
+
+ if (!attempted) {
+ attempted = true;
+ crypt = load_system32_dll("crypt32.dll");
+ successful = crypt &&
+ GET_WINDOWS_FUNCTION(crypt, CryptProtectMemory);
+ }
+ return successful;
+}
+
+char *capi_obfuscate_string(const char *realname)
+{
+ char *cryptdata;
+ int cryptlen;
+ unsigned char digest[32];
+ char retbuf[65];
+ int i;
+
+ cryptlen = strlen(realname) + 1;
+ cryptlen += CRYPTPROTECTMEMORY_BLOCK_SIZE - 1;
+ cryptlen /= CRYPTPROTECTMEMORY_BLOCK_SIZE;
+ cryptlen *= CRYPTPROTECTMEMORY_BLOCK_SIZE;
+
+ cryptdata = snewn(cryptlen, char);
+ memset(cryptdata, 0, cryptlen);
+ strcpy(cryptdata, realname);
+
+ /*
+ * CRYPTPROTECTMEMORY_CROSS_PROCESS causes CryptProtectMemory to
+ * use the same key in all processes with this user id, meaning
+ * that the next PuTTY process calling this function with the same
+ * input will get the same data.
+ *
+ * (Contrast with CryptProtectData, which invents a new session
+ * key every time since its API permits returning more data than
+ * was input, so calling _that_ and hashing the output would not
+ * be stable.)
+ *
+ * We don't worry too much if this doesn't work for some reason.
+ * Omitting this step still has _some_ privacy value (in that
+ * another user can test-hash things to confirm guesses as to
+ * where you might be connecting to, but cannot invert SHA-256 in
+ * the absence of any plausible guess). So we don't abort if we
+ * can't call CryptProtectMemory at all, or if it fails.
+ */
+ if (got_crypt())
+ p_CryptProtectMemory(cryptdata, cryptlen,
+ CRYPTPROTECTMEMORY_CROSS_PROCESS);
+
+ /*
+ * We don't want to give away the length of the hostname either,
+ * so having got it back out of CryptProtectMemory we now hash it.
+ */
+ {
+ ssh_hash *h = ssh_hash_new(&ssh_sha256);
+ put_string(h, cryptdata, cryptlen);
+ ssh_hash_final(h, digest);
+ }
+
+ sfree(cryptdata);
+
+ /*
+ * Finally, make printable.
+ */
+ for (i = 0; i < 32; i++) {
+ sprintf(retbuf + 2*i, "%02x", digest[i]);
+ /* the last of those will also write the trailing NUL */
+ }
+
+ return dupstr(retbuf);
+}
diff --git a/windows/utils/defaults.c b/windows/utils/defaults.c
new file mode 100644
index 00000000..1a270009
--- /dev/null
+++ b/windows/utils/defaults.c
@@ -0,0 +1,40 @@
+/*
+ * windows/utils/defaults.c: default settings that are specific to Windows.
+ */
+
+#include "putty.h"
+
+#include <commctrl.h>
+
+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"))
+ return filename_from_str("putty.log");
+ else
+ return filename_from_str("");
+}
+
+char *platform_default_s(const char *name)
+{
+ if (!strcmp(name, "SerialLine"))
+ return dupstr("COM1");
+ return NULL;
+}
+
+bool platform_default_b(const char *name, bool def)
+{
+ return def;
+}
+
+int platform_default_i(const char *name, int def)
+{
+ return def;
+}
diff --git a/windows/utils/dll_hijacking_protection.c b/windows/utils/dll_hijacking_protection.c
new file mode 100644
index 00000000..fe9ae59c
--- /dev/null
+++ b/windows/utils/dll_hijacking_protection.c
@@ -0,0 +1,43 @@
+/*
+ * If the OS provides it, call SetDefaultDllDirectories() to prevent
+ * DLLs from being loaded from the directory containing our own
+ * binary, and instead only load from system32.
+ *
+ * This is a protection against hijacking attacks, if someone runs
+ * PuTTY directly from their web browser's download directory having
+ * previously been enticed into clicking on an unwise link that
+ * downloaded a malicious DLL to the same directory under one of
+ * various magic names that seem to be things that standard Windows
+ * DLLs delegate to.
+ *
+ * It shouldn't break deliberate loading of user-provided DLLs such as
+ * GSSAPI providers, because those are specified by their full
+ * pathname by the user-provided configuration.
+ */
+
+#include "putty.h"
+
+void dll_hijacking_protection(void)
+{
+ static HMODULE kernel32_module;
+ DECL_WINDOWS_FUNCTION(static, BOOL, SetDefaultDllDirectories, (DWORD));
+
+ if (!kernel32_module) {
+ kernel32_module = load_system32_dll("kernel32.dll");
+#if !HAVE_SETDEFAULTDLLDIRECTORIES
+ /* For older Visual Studio, this function isn't available in
+ * the header files to type-check */
+ GET_WINDOWS_FUNCTION_NO_TYPECHECK(
+ kernel32_module, SetDefaultDllDirectories);
+#else
+ GET_WINDOWS_FUNCTION(kernel32_module, SetDefaultDllDirectories);
+#endif
+ }
+
+ if (p_SetDefaultDllDirectories) {
+ /* LOAD_LIBRARY_SEARCH_SYSTEM32 and explicitly specified
+ * directories only */
+ p_SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32 |
+ LOAD_LIBRARY_SEARCH_USER_DIRS);
+ }
+}
diff --git a/windows/utils/dputs.c b/windows/utils/dputs.c
new file mode 100644
index 00000000..f582509a
--- /dev/null
+++ b/windows/utils/dputs.c
@@ -0,0 +1,39 @@
+/*
+ * Implementation of dputs() for Windows.
+ *
+ * The debug messages are written to STD_OUTPUT_HANDLE, except that
+ * first it has to make sure that handle _exists_, by calling
+ * AllocConsole first if necessary.
+ *
+ * They also go into a file called debug.log.
+ */
+
+#include "putty.h"
+#include "utils/utils.h"
+
+static HANDLE debug_fp = INVALID_HANDLE_VALUE;
+static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
+static int debug_got_console = 0;
+
+void dputs(const char *buf)
+{
+ DWORD dw;
+
+ if (!debug_got_console) {
+ if (AllocConsole()) {
+ debug_got_console = 1;
+ debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE);
+ }
+ }
+ if (debug_fp == INVALID_HANDLE_VALUE) {
+ debug_fp = CreateFile("debug.log", GENERIC_WRITE, FILE_SHARE_READ,
+ NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ }
+
+ if (debug_fp != INVALID_HANDLE_VALUE) {
+ WriteFile(debug_fp, buf, strlen(buf), &dw, NULL);
+ }
+ if (debug_hdl != INVALID_HANDLE_VALUE) {
+ WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL);
+ }
+}
diff --git a/windows/utils/escape_registry_key.c b/windows/utils/escape_registry_key.c
new file mode 100644
index 00000000..f5c9c19e
--- /dev/null
+++ b/windows/utils/escape_registry_key.c
@@ -0,0 +1,48 @@
+/*
+ * Escaping/unescaping functions to translate between a saved session
+ * name, and the key name used to represent it in the Registry area
+ * where we store saved sessions.
+ *
+ * The basic technique is to %-escape characters we can't use in
+ * Registry keys.
+ */
+
+#include "putty.h"
+
+void escape_registry_key(const char *in, strbuf *out)
+{
+ bool candot = false;
+ static const char hex[16] = "0123456789ABCDEF";
+
+ while (*in) {
+ if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' ||
+ *in == '%' || *in < ' ' || *in > '~' || (*in == '.'
+ && !candot)) {
+ put_byte(out, '%');
+ put_byte(out, hex[((unsigned char) *in) >> 4]);
+ put_byte(out, hex[((unsigned char) *in) & 15]);
+ } else
+ put_byte(out, *in);
+ in++;
+ candot = true;
+ }
+}
+
+void unescape_registry_key(const char *in, strbuf *out)
+{
+ while (*in) {
+ if (*in == '%' && in[1] && in[2]) {
+ int i, j;
+
+ i = in[1] - '0';
+ i -= (i > 9 ? 7 : 0);
+ j = in[2] - '0';
+ j -= (j > 9 ? 7 : 0);
+
+ put_byte(out, (i << 4) + j);
+ in += 3;
+ } else {
+ put_byte(out, *in++);
+ }
+ }
+}
diff --git a/windows/utils/filename.c b/windows/utils/filename.c
new file mode 100644
index 00000000..f4457ecb
--- /dev/null
+++ b/windows/utils/filename.c
@@ -0,0 +1,54 @@
+/*
+ * Implementation of Filename for Windows.
+ */
+
+#include "putty.h"
+
+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;
+}
diff --git a/windows/utils/fontspec.c b/windows/utils/fontspec.c
new file mode 100644
index 00000000..7e8d5175
--- /dev/null
+++ b/windows/utils/fontspec.c
@@ -0,0 +1,43 @@
+/*
+ * Implementation of FontSpec for Windows.
+ */
+
+#include "putty.h"
+
+FontSpec *fontspec_new(const char *name, bool bold, int height, int charset)
+{
+ FontSpec *f = snew(FontSpec);
+ f->name = dupstr(name);
+ f->isbold = bold;
+ f->height = height;
+ f->charset = charset;
+ return f;
+}
+
+FontSpec *fontspec_copy(const FontSpec *f)
+{
+ return fontspec_new(f->name, f->isbold, f->height, f->charset);
+}
+
+void fontspec_free(FontSpec *f)
+{
+ sfree(f->name);
+ sfree(f);
+}
+
+void fontspec_serialise(BinarySink *bs, FontSpec *f)
+{
+ put_asciz(bs, f->name);
+ put_uint32(bs, f->isbold);
+ put_uint32(bs, f->height);
+ put_uint32(bs, f->charset);
+}
+
+FontSpec *fontspec_deserialise(BinarySource *src)
+{
+ const char *name = get_asciz(src);
+ unsigned isbold = get_uint32(src);
+ unsigned height = get_uint32(src);
+ unsigned charset = get_uint32(src);
+ return fontspec_new(name, isbold, height, charset);
+}
diff --git a/windows/utils/get_system_dir.c b/windows/utils/get_system_dir.c
new file mode 100644
index 00000000..049cd7fc
--- /dev/null
+++ b/windows/utils/get_system_dir.c
@@ -0,0 +1,21 @@
+/*
+ * Wrapper function around GetSystemDirectory that deals with
+ * allocating the output buffer, and also caches the result for future
+ * calls.
+ */
+
+#include "putty.h"
+
+const char *get_system_dir(void)
+{
+ static char *sysdir = NULL;
+ static size_t sysdirsize = 0;
+
+ if (!sysdir) {
+ size_t len;
+ while ((len = GetSystemDirectory(sysdir, sysdirsize)) >= sysdirsize)
+ sgrowarray(sysdir, sysdirsize, len);
+ }
+
+ return sysdir;
+}
diff --git a/windows/utils/get_username.c b/windows/utils/get_username.c
new file mode 100644
index 00000000..bc5780e4
--- /dev/null
+++ b/windows/utils/get_username.c
@@ -0,0 +1,77 @@
+/*
+ * Implementation of get_username() for Windows.
+ */
+
+#include "putty.h"
+
+#ifndef SECURITY_WIN32
+#define SECURITY_WIN32
+#endif
+#include <security.h>
+
+char *get_username(void)
+{
+ DWORD namelen;
+ char *user;
+ bool got_username = false;
+ DECL_WINDOWS_FUNCTION(static, BOOLEAN, GetUserNameExA,
+ (EXTENDED_NAME_FORMAT, LPSTR, PULONG));
+
+ {
+ static bool tried_usernameex = false;
+ if (!tried_usernameex) {
+ /* Not available on Win9x, so load dynamically */
+ HMODULE secur32 = load_system32_dll("secur32.dll");
+ /* If MIT Kerberos is installed, the following call to
+ GET_WINDOWS_FUNCTION makes Windows implicitly load
+ sspicli.dll WITHOUT proper path sanitizing, so better
+ load it properly before */
+ HMODULE sspicli = load_system32_dll("sspicli.dll");
+ (void)sspicli; /* squash compiler warning about unused variable */
+ GET_WINDOWS_FUNCTION(secur32, GetUserNameExA);
+ tried_usernameex = true;
+ }
+ }
+
+ if (p_GetUserNameExA) {
+ /*
+ * If available, use the principal -- this avoids the problem
+ * that the local username is case-insensitive but Kerberos
+ * usernames are case-sensitive.
+ */
+
+ /* Get the length */
+ namelen = 0;
+ (void) p_GetUserNameExA(NameUserPrincipal, NULL, &namelen);
+
+ user = snewn(namelen, char);
+ got_username = p_GetUserNameExA(NameUserPrincipal, user, &namelen);
+ if (got_username) {
+ char *p = strchr(user, '@');
+ if (p) *p = 0;
+ } else {
+ sfree(user);
+ }
+ }
+
+ if (!got_username) {
+ /* Fall back to local user name */
+ namelen = 0;
+ if (!GetUserName(NULL, &namelen)) {
+ /*
+ * Apparently this doesn't work at least on Windows XP SP2.
+ * Thus assume a maximum of 256. It will fail again if it
+ * doesn't fit.
+ */
+ namelen = 256;
+ }
+
+ user = snewn(namelen, char);
+ got_username = GetUserName(user, &namelen);
+ if (!got_username) {
+ sfree(user);
+ }
+ }
+
+ return got_username ? user : NULL;
+}
diff --git a/windows/utils/getdlgitemtext_alloc.c b/windows/utils/getdlgitemtext_alloc.c
new file mode 100644
index 00000000..f0244d71
--- /dev/null
+++ b/windows/utils/getdlgitemtext_alloc.c
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+#include "putty.h"
+
+char *GetDlgItemText_alloc(HWND hwnd, int id)
+{
+ char *ret = NULL;
+ size_t size = 0;
+
+ do {
+ sgrowarray_nm(ret, size, size);
+ GetDlgItemText(hwnd, id, ret, size);
+ } while (!memchr(ret, '\0', size-1));
+
+ return ret;
+}
diff --git a/windows/utils/interprocess_mutex.c b/windows/utils/interprocess_mutex.c
new file mode 100644
index 00000000..8612a298
--- /dev/null
+++ b/windows/utils/interprocess_mutex.c
@@ -0,0 +1,48 @@
+/*
+ * Lock and unlock a mutex with a globally visible name. Used to
+ * synchronise between unrelated processes, such as two
+ * connection-sharing PuTTYs deciding which will be the upstream.
+ */
+
+#include "putty.h"
+#include "security-api.h"
+
+HANDLE lock_interprocess_mutex(const char *mutexname, char **error)
+{
+ PSECURITY_DESCRIPTOR psd = NULL;
+ PACL acl = NULL;
+ HANDLE mutex = NULL;
+
+ if (should_have_security() && !make_private_security_descriptor(
+ MUTEX_ALL_ACCESS, &psd, &acl, error))
+ goto out;
+
+ SECURITY_ATTRIBUTES sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.nLength = sizeof(sa);
+ sa.lpSecurityDescriptor = psd;
+ sa.bInheritHandle = false;
+
+ mutex = CreateMutex(&sa, false, mutexname);
+ if (!mutex) {
+ *error = dupprintf("CreateMutex(\"%s\") failed: %s",
+ mutexname, win_strerror(GetLastError()));
+ goto out;
+ }
+
+ WaitForSingleObject(mutex, INFINITE);
+
+ out:
+ if (psd)
+ LocalFree(psd);
+ if (acl)
+ LocalFree(acl);
+
+ return mutex;
+}
+
+void unlock_interprocess_mutex(HANDLE mutex)
+{
+ ReleaseMutex(mutex);
+ CloseHandle(mutex);
+}
diff --git a/windows/utils/is_console_handle.c b/windows/utils/is_console_handle.c
new file mode 100644
index 00000000..887069c9
--- /dev/null
+++ b/windows/utils/is_console_handle.c
@@ -0,0 +1,13 @@
+/*
+ * Determine whether a Windows HANDLE points at a console device.
+ */
+
+#include "putty.h"
+
+bool is_console_handle(HANDLE handle)
+{
+ DWORD ignored_output;
+ if (GetConsoleMode(handle, &ignored_output))
+ return true;
+ return false;
+}
diff --git a/windows/utils/load_system32_dll.c b/windows/utils/load_system32_dll.c
new file mode 100644
index 00000000..d227a264
--- /dev/null
+++ b/windows/utils/load_system32_dll.c
@@ -0,0 +1,18 @@
+/*
+ * Wrapper function to load a DLL out of c:\windows\system32 without
+ * going through the full DLL search path. (Hence no attack is
+ * possible by placing a substitute DLL earlier on that path.)
+ */
+
+#include "putty.h"
+
+HMODULE load_system32_dll(const char *libname)
+{
+ char *fullpath;
+ HMODULE ret;
+
+ fullpath = dupcat(get_system_dir(), "\\", libname);
+ ret = LoadLibrary(fullpath);
+ sfree(fullpath);
+ return ret;
+}
diff --git a/windows/utils/ltime.c b/windows/utils/ltime.c
new file mode 100644
index 00000000..d4364509
--- /dev/null
+++ b/windows/utils/ltime.c
@@ -0,0 +1,27 @@
+/*
+ * Implementation of ltime() that avoids trouble with time() returning
+ * (time_t)-1 on Windows.
+ */
+
+#include "putty.h"
+#include <time.h>
+
+struct tm ltime(void)
+{
+ SYSTEMTIME st;
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm)); /* in case there are any other fields */
+
+ GetLocalTime(&st);
+ tm.tm_sec=st.wSecond;
+ tm.tm_min=st.wMinute;
+ tm.tm_hour=st.wHour;
+ tm.tm_mday=st.wDay;
+ tm.tm_mon=st.wMonth-1;
+ tm.tm_year=(st.wYear>=1900?st.wYear-1900:0);
+ tm.tm_wday=st.wDayOfWeek;
+ tm.tm_yday=-1; /* GetLocalTime doesn't tell us */
+ tm.tm_isdst=0; /* GetLocalTime doesn't tell us */
+ return tm;
+}
diff --git a/windows/utils/make_spr_sw_abort_winerror.c b/windows/utils/make_spr_sw_abort_winerror.c
new file mode 100644
index 00000000..b05ef61f
--- /dev/null
+++ b/windows/utils/make_spr_sw_abort_winerror.c
@@ -0,0 +1,22 @@
+/*
+ * Constructor function for a SeatPromptResult of the 'software abort'
+ * category, whose error message includes the translation of an OS
+ * error code.
+ */
+
+#include "putty.h"
+
+static void spr_winerror_errfn(SeatPromptResult spr, BinarySink *bs)
+{
+ put_fmt(bs, "%s: %s", spr.errdata_lit, win_strerror(spr.errdata_u));
+}
+
+SeatPromptResult make_spr_sw_abort_winerror(const char *prefix, DWORD error)
+{
+ SeatPromptResult spr;
+ spr.kind = SPRK_SW_ABORT;
+ spr.errfn = spr_winerror_errfn;
+ spr.errdata_lit = prefix;
+ spr.errdata_u = error;
+ return spr;
+}
diff --git a/windows/utils/makedlgitemborderless.c b/windows/utils/makedlgitemborderless.c
new file mode 100644
index 00000000..53975d06
--- /dev/null
+++ b/windows/utils/makedlgitemborderless.c
@@ -0,0 +1,19 @@
+/*
+ * Helper function to remove the border around a dialog item such as
+ * a read-only edit control.
+ */
+
+#include "putty.h"
+
+void MakeDlgItemBorderless(HWND parent, int id)
+{
+ HWND child = GetDlgItem(parent, id);
+ LONG_PTR style = GetWindowLongPtr(child, GWL_STYLE);
+ LONG_PTR exstyle = GetWindowLongPtr(child, GWL_EXSTYLE);
+ style &= ~WS_BORDER;
+ exstyle &= ~(WS_EX_CLIENTEDGE | WS_EX_STATICEDGE | WS_EX_WINDOWEDGE);
+ SetWindowLongPtr(child, GWL_STYLE, style);
+ SetWindowLongPtr(child, GWL_EXSTYLE, exstyle);
+ SetWindowPos(child, NULL, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
+}
diff --git a/windows/utils/message_box.c b/windows/utils/message_box.c
new file mode 100644
index 00000000..ae78de4a
--- /dev/null
+++ b/windows/utils/message_box.c
@@ -0,0 +1,49 @@
+/*
+ * Message box with optional context help.
+ */
+
+#include "putty.h"
+
+static HWND message_box_owner;
+
+/* Callback function to launch context help. */
+static VOID CALLBACK message_box_help_callback(LPHELPINFO lpHelpInfo)
+{
+ const char *context = NULL;
+#define CHECK_CTX(name) \
+ do { \
+ if (lpHelpInfo->dwContextId == WINHELP_CTXID_ ## name) \
+ context = WINHELP_CTX_ ## name; \
+ } while (0)
+ CHECK_CTX(errors_hostkey_absent);
+ CHECK_CTX(errors_hostkey_changed);
+ CHECK_CTX(errors_cantloadkey);
+ CHECK_CTX(option_cleanup);
+ CHECK_CTX(pgp_fingerprints);
+#undef CHECK_CTX
+ if (context)
+ launch_help(message_box_owner, context);
+}
+
+int message_box(HWND owner, LPCTSTR text, LPCTSTR caption,
+ DWORD style, 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);
+}
diff --git a/windows/utils/minefield.c b/windows/utils/minefield.c
new file mode 100644
index 00000000..c5262ae6
--- /dev/null
+++ b/windows/utils/minefield.c
@@ -0,0 +1,228 @@
+/*
+ * 'Minefield' - a crude Windows memory debugger, similar in concept
+ * to the old Unix 'Electric Fence'. The main difference is that
+ * Electric Fence can be imposed on a program from outside, via
+ * LD_PRELOAD, whereas this has to be included in the program at
+ * compile time with its own cooperation.
+ *
+ * This module provides the Minefield allocator. Actually enabling it
+ * is done by a #define in force when the main utils/memory.c is
+ * compiled.
+ */
+
+#include "putty.h"
+#include "puttymem.h"
+
+#define PAGESIZE 4096
+
+/*
+ * Design:
+ *
+ * We start by reserving as much virtual address space as Windows
+ * will sensibly (or not sensibly) let us have. We flag it all as
+ * invalid memory.
+ *
+ * Any allocation attempt is satisfied by committing one or more
+ * pages, with an uncommitted page on either side. The returned
+ * memory region is jammed up against the _end_ of the pages.
+ *
+ * Freeing anything causes instantaneous decommitment of the pages
+ * involved, so stale pointers are caught as soon as possible.
+ */
+
+static int minefield_initialised = 0;
+static void *minefield_region = NULL;
+static long minefield_size = 0;
+static long minefield_npages = 0;
+static long minefield_curpos = 0;
+static unsigned short *minefield_admin = NULL;
+static void *minefield_pages = NULL;
+
+static void minefield_admin_hide(int hide)
+{
+ int access = hide ? PAGE_NOACCESS : PAGE_READWRITE;
+ VirtualProtect(minefield_admin, minefield_npages * 2, access, NULL);
+}
+
+static void minefield_init(void)
+{
+ int size;
+ int admin_size;
+ int i;
+
+ for (size = 0x40000000; size > 0; size = ((size >> 3) * 7) & ~0xFFF) {
+ minefield_region = VirtualAlloc(NULL, size,
+ MEM_RESERVE, PAGE_NOACCESS);
+ if (minefield_region)
+ break;
+ }
+ minefield_size = size;
+
+ /*
+ * Firstly, allocate a section of that to be the admin block.
+ * We'll need a two-byte field for each page.
+ */
+ minefield_admin = minefield_region;
+ minefield_npages = minefield_size / PAGESIZE;
+ admin_size = (minefield_npages * 2 + PAGESIZE - 1) & ~(PAGESIZE - 1);
+ minefield_npages = (minefield_size - admin_size) / PAGESIZE;
+ minefield_pages = (char *) minefield_region + admin_size;
+
+ /*
+ * Commit the admin region.
+ */
+ VirtualAlloc(minefield_admin, minefield_npages * 2,
+ MEM_COMMIT, PAGE_READWRITE);
+
+ /*
+ * Mark all pages as unused (0xFFFF).
+ */
+ for (i = 0; i < minefield_npages; i++)
+ minefield_admin[i] = 0xFFFF;
+
+ /*
+ * Hide the admin region.
+ */
+ minefield_admin_hide(1);
+
+ minefield_initialised = 1;
+}
+
+static void minefield_bomb(void)
+{
+ div(1, *(int *) minefield_pages);
+}
+
+static void *minefield_alloc(int size)
+{
+ int npages;
+ int pos, lim, region_end, region_start;
+ int start;
+ int i;
+
+ npages = (size + PAGESIZE - 1) / PAGESIZE;
+
+ minefield_admin_hide(0);
+
+ /*
+ * Search from current position until we find a contiguous
+ * bunch of npages+2 unused pages.
+ */
+ pos = minefield_curpos;
+ lim = minefield_npages;
+ while (1) {
+ /* Skip over used pages. */
+ while (pos < lim && minefield_admin[pos] != 0xFFFF)
+ pos++;
+ /* Count unused pages. */
+ start = pos;
+ while (pos < lim && pos - start < npages + 2 &&
+ minefield_admin[pos] == 0xFFFF)
+ pos++;
+ if (pos - start == npages + 2)
+ break;
+ /* If we've reached the limit, reset the limit or stop. */
+ if (pos >= lim) {
+ if (lim == minefield_npages) {
+ /* go round and start again at zero */
+ lim = minefield_curpos;
+ pos = 0;
+ } else {
+ minefield_admin_hide(1);
+ return NULL;
+ }
+ }
+ }
+
+ minefield_curpos = pos - 1;
+
+ /*
+ * We have npages+2 unused pages starting at start. We leave
+ * the first and last of these alone and use the rest.
+ */
+ region_end = (start + npages + 1) * PAGESIZE;
+ region_start = region_end - size;
+ /* FIXME: could align here if we wanted */
+
+ /*
+ * Update the admin region.
+ */
+ for (i = start + 2; i < start + npages + 1; i++)
+ minefield_admin[i] = 0xFFFE; /* used but no region starts here */
+ minefield_admin[start + 1] = region_start % PAGESIZE;
+
+ minefield_admin_hide(1);
+
+ VirtualAlloc((char *) minefield_pages + region_start, size,
+ MEM_COMMIT, PAGE_READWRITE);
+ return (char *) minefield_pages + region_start;
+}
+
+static void minefield_free(void *ptr)
+{
+ int region_start, i, j;
+
+ minefield_admin_hide(0);
+
+ region_start = (char *) ptr - (char *) minefield_pages;
+ i = region_start / PAGESIZE;
+ if (i < 0 || i >= minefield_npages ||
+ minefield_admin[i] != region_start % PAGESIZE)
+ minefield_bomb();
+ for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++) {
+ minefield_admin[j] = 0xFFFF;
+ }
+
+ VirtualFree(ptr, j * PAGESIZE - region_start, MEM_DECOMMIT);
+
+ minefield_admin_hide(1);
+}
+
+static int minefield_get_size(void *ptr)
+{
+ int region_start, i, j;
+
+ minefield_admin_hide(0);
+
+ region_start = (char *) ptr - (char *) minefield_pages;
+ i = region_start / PAGESIZE;
+ if (i < 0 || i >= minefield_npages ||
+ minefield_admin[i] != region_start % PAGESIZE)
+ minefield_bomb();
+ for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++);
+
+ minefield_admin_hide(1);
+
+ return j * PAGESIZE - region_start;
+}
+
+void *minefield_c_malloc(size_t size)
+{
+ if (!minefield_initialised)
+ minefield_init();
+ return minefield_alloc(size);
+}
+
+void minefield_c_free(void *p)
+{
+ if (!minefield_initialised)
+ minefield_init();
+ minefield_free(p);
+}
+
+/*
+ * realloc _always_ moves the chunk, for rapid detection of code
+ * that assumes it won't.
+ */
+void *minefield_c_realloc(void *p, size_t size)
+{
+ size_t oldsize;
+ void *q;
+ if (!minefield_initialised)
+ minefield_init();
+ q = minefield_alloc(size);
+ oldsize = minefield_get_size(p);
+ memcpy(q, p, (oldsize < size ? oldsize : size));
+ minefield_free(p);
+ return q;
+}
diff --git a/windows/utils/open_for_write_would_lose_data.c b/windows/utils/open_for_write_would_lose_data.c
new file mode 100644
index 00000000..2aef5c5a
--- /dev/null
+++ b/windows/utils/open_for_write_would_lose_data.c
@@ -0,0 +1,76 @@
+/*
+ * Implementation of open_for_write_would_lose_data for Windows.
+ */
+
+#include "putty.h"
+
+/*
+ * This is slightly fiddly because we want to be backwards-compatible
+ * with systems too old to have GetFileAttributesEx. The next best
+ * thing is FindFirstFile, which will return a different data
+ * structure, but one that also contains the fields we want. (But it
+ * will behave more unhelpfully - for this application - in the
+ * presence of wildcards, so we'd prefer to use GFAE if we can.)
+ */
+
+static inline bool open_for_write_would_lose_data_impl(
+ DWORD dwFileAttributes, DWORD nFileSizeHigh, DWORD nFileSizeLow)
+{
+ if (dwFileAttributes & (FILE_ATTRIBUTE_DEVICE|FILE_ATTRIBUTE_DIRECTORY)) {
+ /*
+ * File is something other than an ordinary disk file, so
+ * opening it for writing will not cause truncation. (It may
+ * not _succeed_ either, but that's not our problem here!)
+ */
+ return false;
+ }
+ if (nFileSizeHigh == 0 && nFileSizeLow == 0) {
+ /*
+ * File is zero-length (or may be a named pipe, which
+ * dwFileAttributes can't tell apart from a regular file), so
+ * opening it for writing won't truncate any data away because
+ * there's nothing to truncate anyway.
+ */
+ return false;
+ }
+ return true;
+}
+
+bool open_for_write_would_lose_data(const Filename *fn)
+{
+ static HMODULE kernel32_module;
+ DECL_WINDOWS_FUNCTION(static, BOOL, GetFileAttributesExA,
+ (LPCSTR, GET_FILEEX_INFO_LEVELS, LPVOID));
+
+ if (!kernel32_module) {
+ kernel32_module = load_system32_dll("kernel32.dll");
+ GET_WINDOWS_FUNCTION(kernel32_module, GetFileAttributesExA);
+ }
+
+ if (p_GetFileAttributesExA) {
+ WIN32_FILE_ATTRIBUTE_DATA attrs;
+ if (!p_GetFileAttributesExA(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;
+ }
+ return open_for_write_would_lose_data_impl(
+ attrs.dwFileAttributes, attrs.nFileSizeHigh, attrs.nFileSizeLow);
+ } else {
+ WIN32_FIND_DATA fd;
+ HANDLE h = FindFirstFile(fn->path, &fd);
+ if (h == INVALID_HANDLE_VALUE) {
+ /*
+ * As above, if we can't find the file at all, return false.
+ */
+ return false;
+ }
+ CloseHandle(h);
+ return open_for_write_would_lose_data_impl(
+ fd.dwFileAttributes, fd.nFileSizeHigh, fd.nFileSizeLow);
+ }
+}
diff --git a/windows/utils/pgp_fingerprints_msgbox.c b/windows/utils/pgp_fingerprints_msgbox.c
new file mode 100644
index 00000000..6618de82
--- /dev/null
+++ b/windows/utils/pgp_fingerprints_msgbox.c
@@ -0,0 +1,25 @@
+/*
+ * Display the fingerprints of the PGP Master Keys to the user as a
+ * GUI message box.
+ */
+
+#include "putty.h"
+
+void pgp_fingerprints_msgbox(HWND owner)
+{
+ message_box(
+ owner,
+ "These are the fingerprints of the PuTTY PGP Master Keys. They can\n"
+ "be used to establish a trust path from this executable to another\n"
+ "one. See the manual for more information.\n"
+ "(Note: these fingerprints have nothing to do with SSH!)\n"
+ "\n"
+ "PuTTY Master Key as of " PGP_MASTER_KEY_YEAR
+ " (" PGP_MASTER_KEY_DETAILS "):\n"
+ " " PGP_MASTER_KEY_FP "\n\n"
+ "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR
+ ", " PGP_PREV_MASTER_KEY_DETAILS "):\n"
+ " " PGP_PREV_MASTER_KEY_FP,
+ "PGP fingerprints", MB_ICONINFORMATION | MB_OK,
+ HELPCTXID(pgp_fingerprints));
+}
diff --git a/windows/utils/platform_get_x_display.c b/windows/utils/platform_get_x_display.c
new file mode 100644
index 00000000..e2a9ddc6
--- /dev/null
+++ b/windows/utils/platform_get_x_display.c
@@ -0,0 +1,13 @@
+/*
+ * Implementation of platform_get_x_display for Windows, common to all
+ * tools.
+ */
+
+#include "putty.h"
+#include "ssh.h"
+
+char *platform_get_x_display(void)
+{
+ /* We may as well check for DISPLAY in case it's useful. */
+ return dupstr(getenv("DISPLAY"));
+}
diff --git a/windows/utils/registry.c b/windows/utils/registry.c
new file mode 100644
index 00000000..1f50e67a
--- /dev/null
+++ b/windows/utils/registry.c
@@ -0,0 +1,184 @@
+/*
+ * Implement convenience wrappers on the awkward low-level functions
+ * for accessing the Windows registry.
+ */
+
+#include "putty.h"
+
+HKEY open_regkey_fn(bool create, HKEY hk, const char *path, ...)
+{
+ HKEY toret = NULL;
+ bool hk_needs_close = false;
+ va_list ap;
+ va_start(ap, path);
+
+ for (; path; path = va_arg(ap, const char *)) {
+ HKEY hk_sub = NULL;
+
+ LONG status;
+ if (create)
+ status = RegCreateKeyEx(
+ hk, path, 0, NULL, REG_OPTION_NON_VOLATILE,
+ KEY_READ | KEY_WRITE, NULL, &hk_sub, NULL);
+ else
+ status = RegOpenKeyEx(
+ hk, path, 0, KEY_READ | KEY_WRITE, &hk_sub);
+
+ if (status != ERROR_SUCCESS)
+ goto out;
+
+ if (hk_needs_close)
+ RegCloseKey(hk);
+ hk = hk_sub;
+ hk_needs_close = true;
+ }
+
+ toret = hk;
+ hk = NULL;
+ hk_needs_close = false;
+
+ out:
+ va_end(ap);
+ if (hk_needs_close)
+ RegCloseKey(hk);
+ return toret;
+}
+
+void close_regkey(HKEY key)
+{
+ RegCloseKey(key);
+}
+
+void del_regkey(HKEY key, const char *name)
+{
+ RegDeleteKey(key, name);
+}
+
+char *enum_regkey(HKEY key, int index)
+{
+ size_t regbuf_size = MAX_PATH + 1;
+ char *regbuf = snewn(regbuf_size, char);
+
+ while (1) {
+ LONG status = RegEnumKey(key, index, regbuf, regbuf_size);
+ if (status == ERROR_SUCCESS)
+ return regbuf;
+ if (status != ERROR_MORE_DATA) {
+ sfree(regbuf);
+ return NULL;
+ }
+ sgrowarray(regbuf, regbuf_size, regbuf_size);
+ }
+}
+
+bool get_reg_dword(HKEY key, const char *name, DWORD *out)
+{
+ DWORD type, size;
+ size = sizeof(*out);
+
+ if (RegQueryValueEx(key, name, 0, &type,
+ (BYTE *)out, &size) != ERROR_SUCCESS ||
+ size != sizeof(*out) || type != REG_DWORD)
+ return false;
+ else
+ return true;
+}
+
+bool put_reg_dword(HKEY key, const char *name, DWORD value)
+{
+ return RegSetValueEx(key, name, 0, REG_DWORD, (CONST BYTE *) &value,
+ sizeof(value)) == ERROR_SUCCESS;
+}
+
+char *get_reg_sz(HKEY key, const char *name)
+{
+ DWORD type, size;
+
+ if (RegQueryValueEx(key, name, 0, &type, NULL,
+ &size) != ERROR_SUCCESS || type != REG_SZ)
+ return NULL; /* not a string */
+
+ size_t allocsize = size+1; /* allow for an extra NUL if needed */
+ char *toret = snewn(allocsize, char);
+ if (RegQueryValueEx(key, name, 0, &type, (BYTE *)toret,
+ &size) != ERROR_SUCCESS || type != REG_SZ) {
+ sfree(toret);
+ return NULL;
+ }
+ assert(size < allocsize);
+ toret[size] = '\0'; /* add an extra NUL in case RegQueryValueEx
+ * didn't supply one */
+
+ return toret;
+}
+
+bool put_reg_sz(HKEY key, const char *name, const char *str)
+{
+ /* You have to store the trailing NUL as well */
+ return RegSetValueEx(key, name, 0, REG_SZ, (CONST BYTE *)str,
+ 1 + strlen(str)) == ERROR_SUCCESS;
+}
+
+/*
+ * REG_MULTI_SZ items are stored as a concatenation of NUL-terminated
+ * strings, terminated in turn with an empty string, i.e. a second
+ * consecutive NUL.
+ *
+ * We represent these in their storage format, as a strbuf - but
+ * *without* the second consecutive NUL.
+ *
+ * So you can build up a new MULTI_SZ value in a strbuf by calling
+ * put_asciz once per output string and then put_reg_multi_sz; and you
+ * can consume one by initialising a BinarySource to the result of
+ * get_reg_multi_sz, and then calling get_asciz on it and assuming
+ * that !get_err(src) means you have a real output string.
+ *
+ * Also, calling strbuf_to_str on one of these will give you back a
+ * bare 'char *' with the same double-NUL termination, to pass back to
+ * a caller.
+ */
+strbuf *get_reg_multi_sz(HKEY key, const char *name)
+{
+ DWORD type, size;
+
+ if (RegQueryValueEx(key, name, 0, &type, NULL,
+ &size) != ERROR_SUCCESS || type != REG_MULTI_SZ)
+ return NULL; /* not a string */
+
+ strbuf *toret = strbuf_new();
+ void *ptr = strbuf_append(toret, (size_t)size + 2);
+ if (RegQueryValueEx(key, name, 0, &type, (BYTE *)ptr,
+ &size) != ERROR_SUCCESS || type != REG_MULTI_SZ) {
+ strbuf_free(toret);
+ return NULL;
+ }
+ strbuf_shrink_to(toret, size);
+ /* Ensure we end with exactly one \0 */
+ while (strbuf_chomp(toret, '\0'));
+ put_byte(toret, '\0');
+ return toret;
+}
+
+bool put_reg_multi_sz(HKEY key, const char *name, strbuf *str)
+{
+ /*
+ * Of course, to write our string list into the registry, we _do_
+ * have to include both trailing NULs. But this is easy, because a
+ * strbuf is also designed to hold a single string and make it
+ * conveniently accessible in NUL-terminated form, so it stores a
+ * NUL in its buffer just beyond its formal length. So we just
+ * include that extra byte in the data we write.
+ */
+ return RegSetValueEx(key, name, 0, REG_MULTI_SZ, (CONST BYTE *)str->s,
+ str->len + 1) == ERROR_SUCCESS;
+}
+
+char *get_reg_sz_simple(HKEY key, const char *name, const char *leaf)
+{
+ HKEY subkey = open_regkey(false, key, name);
+ if (!subkey)
+ return NULL;
+ char *toret = get_reg_sz(subkey, leaf);
+ RegCloseKey(subkey);
+ return toret;
+}
diff --git a/windows/utils/request_file.c b/windows/utils/request_file.c
new file mode 100644
index 00000000..dd2cab18
--- /dev/null
+++ b/windows/utils/request_file.c
@@ -0,0 +1,71 @@
+/*
+ * GetOpenFileName/GetSaveFileName tend to muck around with the process'
+ * working directory on at least some versions of Windows.
+ * Here's a wrapper that gives more control over this, and hides a little
+ * bit of other grottiness.
+ */
+
+#include "putty.h"
+
+struct filereq_tag {
+ TCHAR cwd[MAX_PATH];
+};
+
+/*
+ * `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);
+}
diff --git a/windows/utils/screenshot.c b/windows/utils/screenshot.c
new file mode 100644
index 00000000..777520fd
--- /dev/null
+++ b/windows/utils/screenshot.c
@@ -0,0 +1,126 @@
+#include "putty.h"
+
+#if HAVE_DWMAPI_H
+
+#include <dwmapi.h>
+
+char *save_screenshot(HWND hwnd, const char *outfile)
+{
+ HDC dcWindow = NULL, dcSave = NULL;
+ HBITMAP bmSave = NULL;
+ uint8_t *buffer = NULL;
+ char *err = NULL;
+
+ static HMODULE dwmapi_module;
+ DECL_WINDOWS_FUNCTION(static, HRESULT, DwmGetWindowAttribute,
+ (HWND, DWORD, PVOID, DWORD));
+
+ if (!dwmapi_module) {
+ dwmapi_module = load_system32_dll("dwmapi.dll");
+ GET_WINDOWS_FUNCTION(dwmapi_module, DwmGetWindowAttribute);
+ }
+
+ dcWindow = GetDC(NULL);
+ if (!dcWindow) {
+ err = dupprintf("GetDC(window): %s", win_strerror(GetLastError()));
+ goto out;
+ }
+
+ int x, y, w, h;
+ RECT wr;
+
+ /* Use DwmGetWindowAttribute in place of GetWindowRect to exclude
+ * drop shadow, otherwise we get a load of unwanted desktop
+ * background under the shadow */
+ if (p_DwmGetWindowAttribute &&
+ 0 <= p_DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS,
+ &wr, sizeof(wr))) {
+ x = wr.left;
+ y = wr.top;
+ w = wr.right - wr.left;
+ h = wr.bottom - wr.top;
+ } else {
+ BITMAP bmhdr;
+ memset(&bmhdr, 0, sizeof(bmhdr));
+ GetObject(GetCurrentObject(dcWindow, OBJ_BITMAP),
+ sizeof(bmhdr), &bmhdr);
+ x = y = 0;
+ w = bmhdr.bmWidth;
+ h = bmhdr.bmHeight;
+ }
+
+ dcSave = CreateCompatibleDC(dcWindow);
+ if (!dcSave) {
+ err = dupprintf("CreateCompatibleDC(desktop window dc): %s",
+ win_strerror(GetLastError()));
+ goto out;
+ }
+
+ bmSave = CreateCompatibleBitmap(dcWindow, w, h);
+ if (!bmSave) {
+ err = dupprintf("CreateCompatibleBitmap: %s",
+ win_strerror(GetLastError()));
+ goto out;
+ }
+
+ if (!SelectObject(dcSave, bmSave)) {
+ err = dupprintf("SelectObject: %s", win_strerror(GetLastError()));
+ goto out;
+ }
+
+ if (!BitBlt(dcSave, 0, 0, w, h, dcWindow, x, y, SRCCOPY)) {
+ err = dupprintf("BitBlt: %s", win_strerror(GetLastError()));
+ goto out;
+ }
+
+ BITMAPINFO bmInfo;
+ memset(&bmInfo, 0, sizeof(bmInfo));
+ bmInfo.bmiHeader.biSize = sizeof(bmInfo.bmiHeader);
+ bmInfo.bmiHeader.biWidth = w;
+ bmInfo.bmiHeader.biHeight = h;
+ bmInfo.bmiHeader.biPlanes = 1;
+ bmInfo.bmiHeader.biBitCount = 32;
+ bmInfo.bmiHeader.biCompression = BI_RGB;
+
+ size_t bmPixels = (size_t)w*h, bmBytes = bmPixels * 4;
+ buffer = snewn(bmBytes, uint8_t);
+
+ if (!GetDIBits(dcWindow, bmSave, 0, h, buffer, &bmInfo, DIB_RGB_COLORS))
+ err = dupprintf("GetDIBits (get data): %s",
+ win_strerror(GetLastError()));
+
+ FILE *fp = fopen(outfile, "wb");
+ if (!fp) {
+ err = dupprintf("'%s': unable to open file", outfile);
+ goto out;
+ }
+
+ BITMAPFILEHEADER bmFileHdr;
+ bmFileHdr.bfType = 'B' | ('M' << 8);
+ bmFileHdr.bfSize = sizeof(bmFileHdr) + sizeof(bmInfo.bmiHeader) + bmBytes;
+ bmFileHdr.bfOffBits = sizeof(bmFileHdr) + sizeof(bmInfo.bmiHeader);
+ fwrite((void *)&bmFileHdr, 1, sizeof(bmFileHdr), fp);
+ fwrite((void *)&bmInfo.bmiHeader, 1, sizeof(bmInfo.bmiHeader), fp);
+ fwrite((void *)buffer, 1, bmBytes, fp);
+ fclose(fp);
+
+ out:
+ if (dcWindow)
+ ReleaseDC(NULL, dcWindow);
+ if (bmSave)
+ DeleteObject(bmSave);
+ if (dcSave)
+ DeleteObject(dcSave);
+ sfree(buffer);
+ return err;
+}
+
+#else /* HAVE_DWMAPI_H */
+
+/* Without <dwmapi.h> we can't get the right window rectangle */
+char *save_screenshot(HWND hwnd, const char *outfile)
+{
+ return dupstr("Demo screenshots not compiled in to this build");
+}
+
+#endif /* HAVE_DWMAPI_H */
diff --git a/windows/utils/security.c b/windows/utils/security.c
new file mode 100644
index 00000000..52299420
--- /dev/null
+++ b/windows/utils/security.c
@@ -0,0 +1,340 @@
+/*
+ * windows/utils/security.c: implementation of security-api.h.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+
+#include "security-api.h"
+
+/* Initialised once, then kept around to reuse forever */
+static PSID worldsid, networksid, usersid;
+
+DEF_WINDOWS_FUNCTION(OpenProcessToken);
+DEF_WINDOWS_FUNCTION(GetTokenInformation);
+DEF_WINDOWS_FUNCTION(InitializeSecurityDescriptor);
+DEF_WINDOWS_FUNCTION(SetSecurityDescriptorOwner);
+DEF_WINDOWS_FUNCTION(GetSecurityInfo);
+DEF_WINDOWS_FUNCTION(SetSecurityInfo);
+DEF_WINDOWS_FUNCTION(SetEntriesInAclA);
+
+bool should_have_security(void)
+{
+#ifdef LEGACY_WINDOWS
+ /* Legacy pre-NT platforms are not expected to have any of these APIs */
+ init_winver();
+ return (osPlatformId == VER_PLATFORM_WIN32_NT);
+#else
+ /* In the up-to-date PuTTY builds which do not support those
+ * platforms, unconditionally return true, to minimise the risk of
+ * compiling out security checks. */
+ return true;
+#endif
+}
+
+bool got_advapi(void)
+{
+ static bool attempted = false;
+ static bool successful;
+ static HMODULE advapi;
+
+ if (!attempted) {
+ attempted = true;
+ advapi = load_system32_dll("advapi32.dll");
+ successful = advapi &&
+ GET_WINDOWS_FUNCTION(advapi, GetSecurityInfo) &&
+ GET_WINDOWS_FUNCTION(advapi, SetSecurityInfo) &&
+ GET_WINDOWS_FUNCTION(advapi, OpenProcessToken) &&
+ GET_WINDOWS_FUNCTION(advapi, GetTokenInformation) &&
+ GET_WINDOWS_FUNCTION(advapi, InitializeSecurityDescriptor) &&
+ GET_WINDOWS_FUNCTION(advapi, SetSecurityDescriptorOwner) &&
+ GET_WINDOWS_FUNCTION(advapi, SetEntriesInAclA);
+ }
+ return successful;
+}
+
+PSID get_user_sid(void)
+{
+ HANDLE proc = NULL, tok = NULL;
+ TOKEN_USER *user = NULL;
+ DWORD toklen, sidlen;
+ PSID sid = NULL, ret = NULL;
+
+ if (usersid)
+ return usersid;
+
+ if (!got_advapi())
+ goto cleanup;
+
+ if ((proc = OpenProcess(MAXIMUM_ALLOWED, false,
+ GetCurrentProcessId())) == NULL)
+ goto cleanup;
+
+ if (!p_OpenProcessToken(proc, TOKEN_QUERY, &tok))
+ goto cleanup;
+
+ if (!p_GetTokenInformation(tok, TokenUser, NULL, 0, &toklen) &&
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ goto cleanup;
+
+ if ((user = (TOKEN_USER *)LocalAlloc(LPTR, toklen)) == NULL)
+ goto cleanup;
+
+ if (!p_GetTokenInformation(tok, TokenUser, user, toklen, &toklen))
+ goto cleanup;
+
+ sidlen = GetLengthSid(user->User.Sid);
+
+ sid = (PSID)smalloc(sidlen);
+
+ if (!CopySid(sidlen, sid, user->User.Sid))
+ goto cleanup;
+
+ /* Success. Move sid into the return value slot, and null it out
+ * to stop the cleanup code freeing it. */
+ ret = usersid = sid;
+ sid = NULL;
+
+ cleanup:
+ if (proc != NULL)
+ CloseHandle(proc);
+ if (tok != NULL)
+ CloseHandle(tok);
+ if (user != NULL)
+ LocalFree(user);
+ if (sid != NULL)
+ sfree(sid);
+
+ return ret;
+}
+
+static bool getsids(char **error)
+{
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wmissing-braces"
+#endif
+ SID_IDENTIFIER_AUTHORITY world_auth = SECURITY_WORLD_SID_AUTHORITY;
+ SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY;
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+ bool ret = false;
+
+ *error = NULL;
+
+ if (!usersid) {
+ if ((usersid = get_user_sid()) == NULL) {
+ *error = dupprintf("unable to construct SID for current user: %s",
+ win_strerror(GetLastError()));
+ goto cleanup;
+ }
+ }
+
+ if (!worldsid) {
+ if (!AllocateAndInitializeSid(&world_auth, 1, SECURITY_WORLD_RID,
+ 0, 0, 0, 0, 0, 0, 0, &worldsid)) {
+ *error = dupprintf("unable to construct SID for world: %s",
+ win_strerror(GetLastError()));
+ goto cleanup;
+ }
+ }
+
+ if (!networksid) {
+ if (!AllocateAndInitializeSid(&nt_auth, 1, SECURITY_NETWORK_RID,
+ 0, 0, 0, 0, 0, 0, 0, &networksid)) {
+ *error = dupprintf("unable to construct SID for "
+ "local same-user access only: %s",
+ win_strerror(GetLastError()));
+ goto cleanup;
+ }
+ }
+
+ ret = true;
+
+ cleanup:
+ return ret;
+}
+
+
+bool make_private_security_descriptor(DWORD permissions,
+ PSECURITY_DESCRIPTOR *psd,
+ PACL *acl,
+ char **error)
+{
+ EXPLICIT_ACCESS ea[3];
+ int acl_err;
+ bool ret = false;
+
+
+ *psd = NULL;
+ *acl = NULL;
+ *error = NULL;
+
+ if (!getsids(error))
+ goto cleanup;
+
+ memset(ea, 0, sizeof(ea));
+ ea[0].grfAccessPermissions = permissions;
+ ea[0].grfAccessMode = REVOKE_ACCESS;
+ ea[0].grfInheritance = NO_INHERITANCE;
+ ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ ea[0].Trustee.ptstrName = (LPTSTR)worldsid;
+ ea[1].grfAccessPermissions = permissions;
+ ea[1].grfAccessMode = GRANT_ACCESS;
+ ea[1].grfInheritance = NO_INHERITANCE;
+ ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ ea[1].Trustee.ptstrName = (LPTSTR)usersid;
+ ea[2].grfAccessPermissions = permissions;
+ ea[2].grfAccessMode = REVOKE_ACCESS;
+ ea[2].grfInheritance = NO_INHERITANCE;
+ ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ ea[2].Trustee.ptstrName = (LPTSTR)networksid;
+
+ acl_err = p_SetEntriesInAclA(3, ea, NULL, acl);
+ if (acl_err != ERROR_SUCCESS || *acl == NULL) {
+ *error = dupprintf("unable to construct ACL: %s",
+ win_strerror(acl_err));
+ goto cleanup;
+ }
+
+ *psd = (PSECURITY_DESCRIPTOR)
+ LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
+ if (!*psd) {
+ *error = dupprintf("unable to allocate security descriptor: %s",
+ win_strerror(GetLastError()));
+ goto cleanup;
+ }
+
+ if (!InitializeSecurityDescriptor(*psd, SECURITY_DESCRIPTOR_REVISION)) {
+ *error = dupprintf("unable to initialise security descriptor: %s",
+ win_strerror(GetLastError()));
+ goto cleanup;
+ }
+
+ if (!SetSecurityDescriptorOwner(*psd, usersid, false)) {
+ *error = dupprintf("unable to set owner in security descriptor: %s",
+ win_strerror(GetLastError()));
+ goto cleanup;
+ }
+
+ if (!SetSecurityDescriptorDacl(*psd, true, *acl, false)) {
+ *error = dupprintf("unable to set DACL in security descriptor: %s",
+ win_strerror(GetLastError()));
+ goto cleanup;
+ }
+
+ ret = true;
+
+ cleanup:
+ if (!ret) {
+ if (*psd) {
+ LocalFree(*psd);
+ *psd = NULL;
+ }
+ if (*acl) {
+ LocalFree(*acl);
+ *acl = NULL;
+ }
+ } else {
+ sfree(*error);
+ *error = NULL;
+ }
+ return ret;
+}
+
+static bool acl_restricted = false;
+bool restricted_acl(void) { return acl_restricted; }
+
+static bool really_restrict_process_acl(char **error)
+{
+ EXPLICIT_ACCESS ea[2];
+ int acl_err;
+ bool ret = false;
+ PACL acl = NULL;
+
+ static const DWORD nastyace=WRITE_DAC | WRITE_OWNER |
+ PROCESS_CREATE_PROCESS | PROCESS_CREATE_THREAD |
+ PROCESS_DUP_HANDLE |
+ PROCESS_SET_QUOTA | PROCESS_SET_INFORMATION |
+ PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE |
+ PROCESS_SUSPEND_RESUME;
+
+ if (!getsids(error))
+ goto cleanup;
+
+ memset(ea, 0, sizeof(ea));
+
+ /* Everyone: deny */
+ ea[0].grfAccessPermissions = nastyace;
+ ea[0].grfAccessMode = DENY_ACCESS;
+ ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
+ ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ ea[0].Trustee.ptstrName = (LPTSTR)worldsid;
+
+ /* User: user ace */
+ ea[1].grfAccessPermissions = ~nastyace & 0x1fff;
+ ea[1].grfAccessMode = GRANT_ACCESS;
+ ea[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
+ ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ ea[1].Trustee.ptstrName = (LPTSTR)usersid;
+
+ acl_err = p_SetEntriesInAclA(2, ea, NULL, &acl);
+
+ if (acl_err != ERROR_SUCCESS || acl == NULL) {
+ *error = dupprintf("unable to construct ACL: %s",
+ win_strerror(acl_err));
+ goto cleanup;
+ }
+
+ if (ERROR_SUCCESS != p_SetSecurityInfo(
+ GetCurrentProcess(), SE_KERNEL_OBJECT,
+ OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
+ usersid, NULL, acl, NULL)) {
+ *error = dupprintf("Unable to set process ACL: %s",
+ win_strerror(GetLastError()));
+ goto cleanup;
+ }
+
+ acl_restricted = true;
+ ret=true;
+
+ cleanup:
+ if (!ret) {
+ if (acl) {
+ LocalFree(acl);
+ acl = NULL;
+ }
+ }
+ return ret;
+}
+
+/*
+ * Lock down our process's ACL, to present an obstacle to malware
+ * trying to write into its memory. This can't be a full defence,
+ * because well timed malware could attack us before this code runs -
+ * even if it was unconditionally run at the very start of main(),
+ * which we wouldn't want to do anyway because it turns out in practie
+ * that interfering with other processes in this way has significant
+ * non-infringing uses on Windows (e.g. screen reader software).
+ *
+ * If we've been requested to do this and are unsuccessful, bomb out
+ * via modalfatalbox rather than continue in a less protected mode.
+ *
+ * This function is intentionally outside the #ifndef NO_SECURITY that
+ * covers the rest of this file, because when PuTTY is compiled
+ * without the ability to restrict its ACL, we don't want it to
+ * silently pretend to honour the instruction to do so.
+ */
+void restrict_process_acl(void)
+{
+ char *error = NULL;
+ bool ret;
+
+ ret = really_restrict_process_acl(&error);
+ if (!ret)
+ modalfatalbox("Could not restrict process ACL: %s", error);
+}
diff --git a/windows/utils/shinydialogbox.c b/windows/utils/shinydialogbox.c
new file mode 100644
index 00000000..ab3201c1
--- /dev/null
+++ b/windows/utils/shinydialogbox.c
@@ -0,0 +1,111 @@
+/*
+ * PuTTY's own reimplementation of DialogBox() and EndDialog() which
+ * provide extra capabilities.
+ *
+ * Originally introduced in 2003 to work around a problem with our
+ * message loops, in which keystrokes pressed in the 'Change Settings'
+ * dialog in mid-session would _also_ be delivered to the main
+ * terminal window.
+ *
+ * But once we had our own wrapper it was convenient to put further
+ * things into it. In particular, this system allows you to provide a
+ * context pointer at setup time that's easy to retrieve from the
+ * window procedure.
+ */
+
+#include "putty.h"
+
+struct ShinyDialogBoxState {
+ bool ended;
+ int result;
+ ShinyDlgProc proc;
+ void *ctx;
+};
+
+/*
+ * For use in re-entrant calls to the dialog procedure from
+ * CreateDialog itself, temporarily store the state pointer that we
+ * won't store in the usual window-memory slot until later.
+ *
+ * I don't _intend_ that this system will need to be used in multiple
+ * threads at all, let alone concurrently, but just in case, declaring
+ * sdb_tempstate as thread-local will protect against that possibility.
+ */
+static THREADLOCAL struct ShinyDialogBoxState *sdb_tempstate;
+
+static inline struct ShinyDialogBoxState *ShinyDialogGetState(HWND hwnd)
+{
+ if (sdb_tempstate)
+ return sdb_tempstate;
+ return (struct ShinyDialogBoxState *)GetWindowLongPtr(
+ hwnd, DLGWINDOWEXTRA);
+}
+
+static INT_PTR CALLBACK ShinyRealDlgProc(HWND hwnd, UINT msg,
+ WPARAM wParam, LPARAM lParam)
+{
+ struct ShinyDialogBoxState *state = ShinyDialogGetState(hwnd);
+ return state->proc(hwnd, msg, wParam, lParam, state->ctx);
+}
+
+int ShinyDialogBox(HINSTANCE hinst, LPCTSTR tmpl, const char *winclass,
+ HWND hwndparent, ShinyDlgProc proc, void *ctx)
+{
+ /*
+ * Register the window class ourselves in such a way that we
+ * allocate an extra word of window memory to store the state
+ * pointer.
+ *
+ * It would be nice in principle to load the dialog template
+ * resource and dig the class name out of it. But DLGTEMPLATEEX is
+ * a nasty variable-layout structure not declared conveniently in
+ * a header file, so I think that's too much effort. Callers of
+ * this function will just have to provide the right window class
+ * name to match their template resource via the 'winclass'
+ * parameter.
+ */
+ WNDCLASS wc;
+ wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
+ wc.lpfnWndProc = DefDlgProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = DLGWINDOWEXTRA + sizeof(LONG_PTR);
+ wc.hInstance = hinst;
+ wc.hIcon = NULL;
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
+ wc.lpszMenuName = NULL;
+ wc.lpszClassName = winclass;
+ RegisterClass(&wc);
+
+ struct ShinyDialogBoxState state[1];
+ state->ended = false;
+ state->proc = proc;
+ state->ctx = ctx;
+
+ sdb_tempstate = state;
+ HWND hwnd = CreateDialog(hinst, tmpl, hwndparent, ShinyRealDlgProc);
+ SetWindowLongPtr(hwnd, DLGWINDOWEXTRA, (LONG_PTR)state);
+ sdb_tempstate = NULL;
+
+ MSG msg;
+ int gm;
+ while ((gm = GetMessage(&msg, NULL, 0, 0)) > 0) {
+ if (!state->ended && !IsDialogMessage(hwnd, &msg))
+ DispatchMessage(&msg);
+ if (state->ended)
+ break;
+ }
+
+ if (gm == 0)
+ PostQuitMessage(msg.wParam); /* We got a WM_QUIT, pass it on */
+
+ DestroyWindow(hwnd);
+ return state->result;
+}
+
+void ShinyEndDialog(HWND hwnd, int ret)
+{
+ struct ShinyDialogBoxState *state = ShinyDialogGetState(hwnd);
+ state->result = ret;
+ state->ended = true;
+}
diff --git a/windows/utils/split_into_argv.c b/windows/utils/split_into_argv.c
new file mode 100644
index 00000000..c42c7a0b
--- /dev/null
+++ b/windows/utils/split_into_argv.c
@@ -0,0 +1,707 @@
+/*
+ * Split a complete command line into argc/argv, attempting to do it
+ * exactly the same way the Visual Studio C library would do it (so
+ * that our console utilities, which receive argc and argv already
+ * broken apart by the C library, will have their command lines
+ * processed in the same way as the GUI utilities which get a whole
+ * command line and must call this function).
+ *
+ * Does not modify the input command line.
+ *
+ * The final parameter (argstart) is used to return a second array
+ * of char * pointers, the same length as argv, each one pointing
+ * at the start of the corresponding element of argv in the
+ * original command line. So if you get half way through processing
+ * your command line in argc/argv form and then decide you want to
+ * treat the rest as a raw string, you can. If you don't want to,
+ * `argstart' can be safely left NULL.
+ */
+
+#include "putty.h"
+
+/*
+ * The precise argument-breaking rules vary with compiler version, or
+ * rather, with the crt0-type startup code that comes with each
+ * compiler's C library. We do our best to match the compiler version,
+ * so that we faithfully imitate in our GUI utilities what the
+ * corresponding set of CLI utilities can't be prevented from doing.
+ *
+ * The basic rules are:
+ *
+ * - Single quotes are not special characters.
+ *
+ * - Double quotes are removed, but within them spaces cease to be
+ * special.
+ *
+ * - Backslashes are _only_ special when a sequence of them appear
+ * just before a double quote. In this situation, they are treated
+ * like C backslashes: so \" just gives a literal quote, \\" gives
+ * a literal backslash and then opens or closes a double-quoted
+ * segment, \\\" gives a literal backslash and then a literal
+ * quote, \\\\" gives two literal backslashes and then opens/closes
+ * a double-quoted segment, and so forth. Note that this behaviour
+ * is identical inside and outside double quotes.
+ *
+ * - Two successive double quotes become one literal double quote,
+ * but only _inside_ a double-quoted segment. Outside, they just
+ * form an empty double-quoted segment (which may cause an empty
+ * argument word).
+ *
+ * That only leaves the interesting question of what happens when one
+ * or more backslashes precedes two or more double quotes, starting
+ * inside a double-quoted string.
+ *
+ * Modern Visual Studio (as of 2021)
+ * ---------------------------------
+ *
+ * I investigated this in an ordinary CLI program, using the
+ * toolchain's crt0 to split a command line of the form
+ *
+ * "a\\\"""b c" d
+ *
+ * Here I tabulate number of backslashes (across the top) against
+ * number of quotes (down the left), and indicate how many backslashes
+ * are output, how many quotes are output, and whether a quoted
+ * segment is open at the end of the sequence:
+ *
+ * backslashes
+ *
+ * 0 1 2 3 4
+ *
+ * 0 0,0,y | 1,0,y 2,0,y 3,0,y 4,0,y
+ * --------+-----------------------------
+ * 1 0,0,n | 0,1,y 1,0,n 1,1,y 2,0,n
+ * q 2 0,1,y | 0,1,n 1,1,y 1,1,n 2,1,y
+ * u 3 0,1,n | 0,2,y 1,1,n 1,2,y 2,1,n
+ * o 4 0,2,y | 0,2,n 1,2,y 1,2,n 2,2,y
+ * t 5 0,2,n | 0,3,y 1,2,n 1,3,y 2,2,n
+ * e 6 0,3,y | 0,3,n 1,3,y 1,3,n 2,3,y
+ * s 7 0,3,n | 0,4,y 1,3,n 1,4,y 2,3,n
+ * 8 0,4,y | 0,4,n 1,4,y 1,4,n 2,4,y
+ *
+ * The row at the top of this table, with quotes=0, demonstrates what
+ * I claimed above, that when a sequence of backslashes are not
+ * followed by a double quote, they don't act specially at all. The
+ * rest of the table shows that the backslashes escape each other in
+ * pairs (so that with 2n or 2n+1 input backslashes you get n output
+ * ones); if there's an odd number of input backslashes then the last
+ * one escapes the first double quote (so you get a literal quote and
+ * enter a quoted string); thereafter, each input quote character
+ * either opens or closes a quoted string, and if it closes one, it
+ * generates a literal " as a side effect.
+ *
+ * Older Visual Studio
+ * -------------------
+ *
+ * But here's the corresponding table from the older Visual Studio 7:
+ *
+ * backslashes
+ *
+ * 0 1 2 3 4
+ *
+ * 0 0,0,y | 1,0,y 2,0,y 3,0,y 4,0,y
+ * --------+-----------------------------
+ * 1 0,0,n | 0,1,y 1,0,n 1,1,y 2,0,n
+ * q 2 0,1,n | 0,1,n 1,1,n 1,1,n 2,1,n
+ * u 3 0,1,y | 0,2,n 1,1,y 1,2,n 2,1,y
+ * o 4 0,1,n | 0,2,y 1,1,n 1,2,y 2,1,n
+ * t 5 0,2,n | 0,2,n 1,2,n 1,2,n 2,2,n
+ * e 6 0,2,y | 0,3,n 1,2,y 1,3,n 2,2,y
+ * s 7 0,2,n | 0,3,y 1,2,n 1,3,y 2,2,n
+ * 8 0,3,n | 0,3,n 1,3,n 1,3,n 2,3,n
+ * 9 0,3,y | 0,4,n 1,3,y 1,4,n 2,3,y
+ * 10 0,3,n | 0,4,y 1,3,n 1,4,y 2,3,n
+ * 11 0,4,n | 0,4,n 1,4,n 1,4,n 2,4,n
+ *
+ * There is very weird mod-3 behaviour going on here in the number of
+ * quotes, and it even applies when there aren't any backslashes! How
+ * ghastly.
+ *
+ * With a bit of thought, this extremely odd diagram suddenly
+ * coalesced itself into a coherent, if still ghastly, model of how
+ * things work:
+ *
+ * - As before, backslashes are only special when one or more of them
+ * appear contiguously before at least one double quote. In this
+ * situation the backslashes do exactly what you'd expect: each one
+ * quotes the next thing in front of it, so you end up with n/2
+ * literal backslashes (if n is even) or (n-1)/2 literal
+ * backslashes and a literal quote (if n is odd). In the latter
+ * case the double quote character right after the backslashes is
+ * used up.
+ *
+ * - After that, any remaining double quotes are processed. A string
+ * of contiguous unescaped double quotes has a mod-3 behaviour:
+ *
+ * * inside a quoted segment, a quote ends the segment.
+ * * _immediately_ after ending a quoted segment, a quote simply
+ * produces a literal quote.
+ * * otherwise, outside a quoted segment, a quote begins a quoted
+ * segment.
+ *
+ * So, for example, if we started inside a quoted segment then two
+ * contiguous quotes would close the segment and produce a literal
+ * quote; three would close the segment, produce a literal quote,
+ * and open a new segment. If we started outside a quoted segment,
+ * then two contiguous quotes would open and then close a segment,
+ * producing no output (but potentially creating a zero-length
+ * argument); but three quotes would open and close a segment and
+ * then produce a literal quote.
+ *
+ * I don't know exactly when the bug fix happened, but I know that VS7
+ * had the odd mod-3 behaviour. So the #if below will ensure that
+ * modern (2015 onwards) versions of VS use the new more sensible
+ * behaviour, and VS7 uses the old one. Things in between may be
+ * wrong; if anyone cares, patches to change the cutoff version in
+ * this #if are welcome.
+ */
+#if _MSC_VER < 1400
+#define MOD3 1
+#else
+#define MOD3 0
+#endif
+
+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 TEST
+
+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 */
+};
+
+void out_of_memory(void)
+{
+ fprintf(stderr, "out of memory!\n");
+ exit(2);
+}
+
+int main(int argc, char **argv)
+{
+ int i, j;
+
+ if (argc > 1) {
+ /*
+ * Generation of tests.
+ *
+ * Given `-splat <args>', 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 <string>', we first doctor `string' by
+ * turning forward slashes into backslashes, single quotes
+ * into double quotes and underscores into spaces; and then
+ * we feed the resulting string to ourself with `-splat'.
+ *
+ * Given `-generate', we concoct a variety of fun test
+ * cases, encode them in quote-safe form (mapping \, " and
+ * space to /, ' and _ respectively) and feed each one to
+ * `-split'.
+ */
+ if (!strcmp(argv[1], "-splat")) {
+ int i;
+ char *p;
+ for (i = 2; i < argc; i++) {
+ putchar('"');
+ for (p = argv[i]; *p; p++) {
+ if (*p == '\\' || *p == '"')
+ putchar('\\');
+ putchar(*p);
+ }
+ printf("\", ");
+ }
+ printf("NULL");
+ return 0;
+ }
+
+ if (!strcmp(argv[1], "-split") && argc > 2) {
+ strbuf *cmdline = strbuf_new();
+ char *p;
+
+ put_fmt(cmdline, "%s -splat ", argv[0]);
+ printf(" {\"");
+ size_t args_start = cmdline->len;
+ for (p = argv[2]; *p; p++) {
+ char c = (*p == '/' ? '\\' :
+ *p == '\'' ? '"' :
+ *p == '_' ? ' ' :
+ *p);
+ put_byte(cmdline, c);
+ }
+ write_c_string_literal(stdout, ptrlen_from_asciz(
+ cmdline->s + args_start));
+ printf("\", {");
+ fflush(stdout);
+
+ system(cmdline->s);
+
+ printf("}},\n");
+
+ strbuf_free(cmdline);
+ return 0;
+ }
+
+ if (!strcmp(argv[1], "-generate")) {
+ char *teststr, *p;
+ int i, initialquote, backslashes, quotes;
+
+ teststr = malloc(200 + strlen(argv[0]));
+
+ for (initialquote = 0; initialquote <= 1; initialquote++) {
+ for (backslashes = 0; backslashes < 5; backslashes++) {
+ for (quotes = 0; quotes < 9; quotes++) {
+ p = teststr + sprintf(teststr, "%s -split ", argv[0]);
+ if (initialquote) *p++ = '\'';
+ *p++ = 'a';
+ for (i = 0; i < backslashes; i++) *p++ = '/';
+ for (i = 0; i < quotes; i++) *p++ = '\'';
+ *p++ = 'b';
+ *p++ = '_';
+ *p++ = 'c';
+ *p++ = '\'';
+ *p++ = '_';
+ *p++ = 'd';
+ *p = '\0';
+
+ system(teststr);
+ }
+ }
+ }
+ return 0;
+ }
+
+ if (!strcmp(argv[1], "-tabulate")) {
+ char table[] = "\
+ * backslashes \n\
+ * \n\
+ * 0 1 2 3 4 \n\
+ * \n\
+ * 0 | \n\
+ * --------+----------------------------- \n\
+ * 1 | \n\
+ * q 2 | \n\
+ * u 3 | \n\
+ * o 4 | \n\
+ * t 5 | \n\
+ * e 6 | \n\
+ * s 7 | \n\
+ * 8 | \n\
+";
+ char *linestarts[14];
+ char *p = table;
+ for (i = 0; i < lenof(linestarts); i++) {
+ linestarts[i] = p;
+ p += strcspn(p, "\n");
+ if (*p) p++;
+ }
+
+ for (i = 0; i < lenof(argv_tests); i++) {
+ const struct argv_test *test = &argv_tests[i];
+ const char *q = test->cmdline;
+
+ /* Skip tests that aren't telling us something about
+ * the behaviour _inside_ a quoted string */
+ if (*q != '"')
+ continue;
+
+ q++;
+
+ assert(*q == 'a');
+ q++;
+ int backslashes_in = 0, quotes_in = 0;
+ while (*q == '\\') {
+ q++;
+ backslashes_in++;
+ }
+ while (*q == '"') {
+ q++;
+ quotes_in++;
+ }
+
+ q = test->argv[0];
+ assert(*q == 'a');
+ q++;
+ int backslashes_out = 0, quotes_out = 0;
+ while (*q == '\\') {
+ q++;
+ backslashes_out++;
+ }
+ while (*q == '"') {
+ q++;
+ quotes_out++;
+ }
+ assert(*q == 'b');
+ q++;
+ bool in_quoted_string = (*q == ' ');
+
+ int x = (backslashes_in == 0 ? 15 : 18 + 7 * backslashes_in);
+ int y = (quotes_in == 0 ? 4 : 5 + quotes_in);
+ char *buf = dupprintf("%d,%d,%c",
+ backslashes_out, quotes_out,
+ in_quoted_string ? 'y' : 'n');
+ memcpy(linestarts[y] + x, buf, strlen(buf));
+ sfree(buf);
+ }
+
+ fputs(table, stdout);
+ return 0;
+ }
+
+ fprintf(stderr, "unrecognised option: \"%s\"\n", argv[1]);
+ return 1;
+ }
+
+ /*
+ * If we get here, we were invoked with no arguments, so just
+ * run the tests.
+ */
+ int passes = 0, fails = 0;
+
+ for (i = 0; i < lenof(argv_tests); i++) {
+ int ac;
+ char **av;
+ bool failed = false;
+
+ split_into_argv((char *)argv_tests[i].cmdline, &ac, &av, NULL);
+
+ for (j = 0; j < ac && argv_tests[i].argv[j]; j++) {
+ if (strcmp(av[j], argv_tests[i].argv[j])) {
+ printf("failed test %d (|%s|) arg %d: |%s| should be |%s|\n",
+ i, argv_tests[i].cmdline,
+ j, av[j], argv_tests[i].argv[j]);
+ failed = true;
+ }
+#ifdef VERBOSE
+ else {
+ printf("test %d (|%s|) arg %d: |%s| == |%s|\n",
+ i, argv_tests[i].cmdline,
+ j, av[j], argv_tests[i].argv[j]);
+ }
+#endif
+ }
+ if (j < ac) {
+ printf("failed test %d (|%s|): %d args returned, should be %d\n",
+ i, argv_tests[i].cmdline, ac, j);
+ failed = true;
+ }
+ if (argv_tests[i].argv[j]) {
+ printf("failed test %d (|%s|): %d args returned, should be more\n",
+ i, argv_tests[i].cmdline, ac);
+ failed = true;
+ }
+
+ if (failed)
+ fails++;
+ else
+ passes++;
+ }
+
+ printf("passed %d failed %d (%s mode)\n",
+ passes, fails,
+#if MOD3
+ "mod 3"
+#else
+ "mod 2"
+#endif
+ );
+
+ return fails != 0;
+}
+
+#endif /* TEST */
diff --git a/windows/utils/strtoumax.c b/windows/utils/strtoumax.c
new file mode 100644
index 00000000..38d00014
--- /dev/null
+++ b/windows/utils/strtoumax.c
@@ -0,0 +1,12 @@
+/*
+ * Work around lack of strtoumax in older MSVC libraries.
+ */
+
+#include <stdlib.h>
+
+#include "defs.h"
+
+uintmax_t strtoumax(const char *nptr, char **endptr, int base)
+{
+ return _strtoui64(nptr, endptr, base);
+}
diff --git a/windows/utils/version.c b/windows/utils/version.c
new file mode 100644
index 00000000..b626710e
--- /dev/null
+++ b/windows/utils/version.c
@@ -0,0 +1,45 @@
+#include "putty.h"
+
+DWORD osMajorVersion, osMinorVersion, osPlatformId;
+
+void init_winver(void)
+{
+ static bool initialised = false;
+ if (initialised)
+ return;
+ initialised = true;
+
+ OSVERSIONINFO osVersion;
+ static HMODULE kernel32_module;
+ DECL_WINDOWS_FUNCTION(static, BOOL, GetVersionExA, (LPOSVERSIONINFO));
+
+ if (!kernel32_module) {
+ kernel32_module = load_system32_dll("kernel32.dll");
+ /* Deliberately don't type-check this function, because that
+ * would involve using its declaration in a header file which
+ * triggers a deprecation warning. I know it's deprecated (see
+ * below) and don't need telling. */
+ GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, GetVersionExA);
+ }
+
+ ZeroMemory(&osVersion, sizeof(osVersion));
+ osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
+ if (p_GetVersionExA && p_GetVersionExA(&osVersion)) {
+ osMajorVersion = osVersion.dwMajorVersion;
+ osMinorVersion = osVersion.dwMinorVersion;
+ osPlatformId = osVersion.dwPlatformId;
+ } else {
+ /*
+ * GetVersionEx is deprecated, so allow for it perhaps going
+ * away in future API versions. If it's not there, simply
+ * assume that's because Windows is too _new_, so fill in the
+ * variables we care about to a value that will always compare
+ * higher than any given test threshold.
+ *
+ * Normally we should be checking against the presence of a
+ * specific function if possible in any case.
+ */
+ osMajorVersion = osMinorVersion = UINT_MAX; /* a very high number */
+ osPlatformId = VER_PLATFORM_WIN32_NT; /* not Win32s or Win95-like */
+ }
+}
diff --git a/windows/utils/win_strerror.c b/windows/utils/win_strerror.c
new file mode 100644
index 00000000..5572bc8b
--- /dev/null
+++ b/windows/utils/win_strerror.c
@@ -0,0 +1,72 @@
+/*
+ * Wrapper around the Windows FormatMessage system for retrieving the
+ * text of a system error code, with a simple API similar to strerror.
+ *
+ * Works by keeping a tree234 containing mappings from system error
+ * codes to strings. Entries allocated in this tree are simply never
+ * freed.
+ *
+ * Also, the returned string has its trailing newline removed (so it
+ * can go in places like the Event Log that never want a newline), and
+ * is prefixed with the error number (so that if a user sends an error
+ * report containing a translated error message we can't read, we can
+ * still find out what the error actually was).
+ */
+
+#include "putty.h"
+
+struct errstring {
+ int error;
+ char *text;
+};
+
+static int errstring_find(void *av, void *bv)
+{
+ int *a = (int *)av;
+ struct errstring *b = (struct errstring *)bv;
+ if (*a < b->error)
+ return -1;
+ if (*a > b->error)
+ return +1;
+ return 0;
+}
+static int errstring_compare(void *av, void *bv)
+{
+ struct errstring *a = (struct errstring *)av;
+ return errstring_find(&a->error, bv);
+}
+
+static tree234 *errstrings = NULL;
+
+const char *win_strerror(int error)
+{
+ struct errstring *es;
+
+ if (!errstrings)
+ errstrings = newtree234(errstring_compare);
+
+ es = find234(errstrings, &error, errstring_find);
+
+ if (!es) {
+ char msgtext[65536]; /* maximum size for FormatMessage is 64K */
+
+ es = snew(struct errstring);
+ es->error = error;
+ if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ msgtext, lenof(msgtext)-1, NULL)) {
+ sprintf(msgtext,
+ "(unable to format: FormatMessage returned %u)",
+ (unsigned int)GetLastError());
+ } else {
+ int len = strlen(msgtext);
+ if (len > 0 && msgtext[len-1] == '\n')
+ msgtext[len-1] = '\0';
+ }
+ es->text = dupprintf("Error %d: %s", error, msgtext);
+ add234(errstrings, es);
+ }
+
+ return es->text;
+}
diff --git a/windows/win-gui-seat.h b/windows/win-gui-seat.h
new file mode 100644
index 00000000..19c5cbea
--- /dev/null
+++ b/windows/win-gui-seat.h
@@ -0,0 +1,14 @@
+/*
+ * Small implementation of Seat and LogPolicy shared between window.c
+ * and dialog.c.
+ */
+
+typedef struct WinGuiSeat WinGuiSeat;
+
+struct WinGuiSeat {
+ HWND term_hwnd;
+ Seat seat;
+ LogPolicy logpolicy;
+};
+
+extern const LogPolicyVtable win_gui_logpolicy_vt; /* in dialog.c */
diff --git a/windows/win_res.h b/windows/win_res.h
deleted file mode 100644
index d34f6852..00000000
--- a/windows/win_res.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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
-#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/windows/win_res.rc2 b/windows/win_res.rc2
deleted file mode 100644
index ccec3122..00000000
--- a/windows/win_res.rc2
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Windows resources shared between PuTTY and PuTTYtel, to be #include'd
- * after defining appropriate macros.
- *
- * Note that many of these strings mention PuTTY. Due to restrictions in
- * VC's handling of string concatenation, this can't easily be fixed.
- * It's fixed up at runtime.
- *
- * This file has the more or less arbitrary extension '.rc2' to avoid
- * IDEs taking it to be a top-level resource script in its own right
- * (which has been known to happen if the extension was '.rc'), and
- * also to avoid the resource compiler ignoring everything included
- * from it (which happens if the extension is '.h').
- */
-
-#include "win_res.h"
-
-IDI_MAINICON ICON "putty.ico"
-
-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
-END
-
-/* Accelerators used: aco */
-IDD_MAINBOX DIALOG DISCARDABLE 0, 0, 300, 252
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "PuTTY Configuration"
-FONT 8, "MS Shell Dlg"
-CLASS "PuTTYConfigBox"
-BEGIN
-END
-
-/* Accelerators used: co */
-IDD_LOGBOX DIALOG DISCARDABLE 100, 20, 300, 119
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "PuTTY Event Log"
-FONT 8, "MS Shell Dlg"
-BEGIN
- DEFPUSHBUTTON "&Close", IDOK, 135, 102, 44, 14
- PUSHBUTTON "C&opy", IDN_COPY, 81, 102, 44, 14
- LISTBOX IDN_LIST, 3, 3, 294, 95, LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL | LBS_EXTENDEDSEL
-END
-
-/* No accelerators used */
-IDD_LICENCEBOX DIALOG DISCARDABLE 50, 50, 326, 239
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "PuTTY Licence"
-FONT 8, "MS Shell Dlg"
-BEGIN
- DEFPUSHBUTTON "OK", IDOK, 148, 219, 44, 14
-
- EDITTEXT IDA_TEXT, 10, 10, 306, 200, ES_READONLY | ES_MULTILINE | ES_LEFT, WS_EX_STATICEDGE
-END
-
-/* 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
-
-#include "version.rc2"
diff --git a/windows/wincapi.c b/windows/wincapi.c
deleted file mode 100644
index de78988b..00000000
--- a/windows/wincapi.c
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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/windows/wincapi.h b/windows/wincapi.h
deleted file mode 100644
index 732412e2..00000000
--- a/windows/wincapi.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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
diff --git a/windows/wincfg.c b/windows/wincfg.c
deleted file mode 100644
index fab3240f..00000000
--- a/windows/wincfg.c
+++ /dev/null
@@ -1,405 +0,0 @@
-/*
- * wincfg.c - the Windows-specific parts of the PuTTY configuration
- * box.
- */
-
-#include <assert.h>
-#include <stdlib.h>
-
-#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));
- }
-}
-
-void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help,
- bool midsession, int protocol)
-{
- const struct BackendVtable *backvt;
- bool resize_forbidden = false;
- struct controlset *s;
- 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;
- if (has_help) {
- c = ctrl_pushbutton(s, "Help", 'h', HELPCTX(no_help),
- help_handler, P(hwndp));
- c->generic.column = 1;
- }
- }
-
- /*
- * Full-screen mode is a Windows peculiarity; hence
- * scrollbar_in_fullscreen is as well.
- */
- s = ctrl_getset(b, "Window", "scrollback",
- "Control the scrollback in the window");
- ctrl_checkbox(s, "Display scrollbar in full screen mode", 'i',
- HELPCTX(window_scrollback),
- conf_checkbox_handler,
- I(CONF_scrollbar_in_fullscreen));
- /*
- * Really this wants to go just after `Display scrollbar'. See
- * if we can find that control, and do some shuffling.
- */
- {
- int i;
- for (i = 0; i < s->ncontrols; i++) {
- c = s->ctrls[i];
- if (c->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));
- ctrl_checkbox(s, "System menu appears on ALT-Space", 'y',
- HELPCTX(behaviour_altspace),
- conf_checkbox_handler, I(CONF_alt_space));
- ctrl_checkbox(s, "System menu appears on ALT alone", 'l',
- HELPCTX(behaviour_altonly),
- conf_checkbox_handler, I(CONF_alt_only));
- ctrl_checkbox(s, "Ensure window is always on top", 'e',
- HELPCTX(behaviour_alwaysontop),
- conf_checkbox_handler, I(CONF_alwaysontop));
- ctrl_checkbox(s, "Full screen on Alt-Enter", 'f',
- HELPCTX(behaviour_altenter),
- conf_checkbox_handler,
- I(CONF_fullscreenonaltenter));
-
- /*
- * Windows supports a local-command proxy. 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));
- }
-}
diff --git a/windows/wincliloop.c b/windows/wincliloop.c
deleted file mode 100644
index 26a4d3aa..00000000
--- a/windows/wincliloop.c
+++ /dev/null
@@ -1,136 +0,0 @@
-#include "putty.h"
-
-void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx)
-{
- SOCKET *sklist = NULL;
- size_t skcount = 0, sksize = 0;
- unsigned long now, next, then;
- now = GETTICKCOUNT();
-
- while (true) {
- 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/windows/wincons.c b/windows/wincons.c
deleted file mode 100644
index 414167b4..00000000
--- a/windows/wincons.c
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * wincons.c - various interactive-prompt routines shared between
- * the Windows console PuTTY tools
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "putty.h"
-#include "storage.h"
-#include "ssh.h"
-#include "console.h"
-
-void cleanup_exit(int code)
-{
- /*
- * Clean up.
- */
- sk_cleanup();
-
- random_save_seed();
-
- exit(code);
-}
-
-void console_print_error_msg(const char *prefix, const char *msg)
-{
- fputs(prefix, stderr);
- fputs(": ", stderr);
- fputs(msg, stderr);
- fputc('\n', stderr);
- fflush(stderr);
-}
-
-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]);
- 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 */
-
- 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] == '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/windows/winctrls.c b/windows/winctrls.c
deleted file mode 100644
index 59129eab..00000000
--- a/windows/winctrls.c
+++ /dev/null
@@ -1,2600 +0,0 @@
-/*
- * 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 <assert.h>
-#include <ctype.h>
-
-#include "putty.h"
-#include "misc.h"
-#include "dialog.h"
-
-#include <commctrl.h>
-
-#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);
- 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;
- 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;
- }
-
- /*
- * 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;
-}
-
-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;
- 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);
-}
diff --git a/windows/windefs.c b/windows/windefs.c
deleted file mode 100644
index 006e8dc5..00000000
--- a/windows/windefs.c
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * windefs.c: default settings that are specific to Windows.
- */
-
-#include "putty.h"
-
-#include <commctrl.h>
-
-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"))
- return filename_from_str("putty.log");
- else
- return filename_from_str("");
-}
-
-char *platform_default_s(const char *name)
-{
- if (!strcmp(name, "SerialLine"))
- return dupstr("COM1");
- return NULL;
-}
-
-bool platform_default_b(const char *name, bool def)
-{
- return def;
-}
-
-int platform_default_i(const char *name, int def)
-{
- return def;
-}
diff --git a/windows/windlg.c b/windows/windlg.c
deleted file mode 100644
index 9c5fdb76..00000000
--- a/windows/windlg.c
+++ /dev/null
@@ -1,1156 +0,0 @@
-/*
- * windlg.c - dialogs for PuTTY(tel), including the configuration dialog.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-#include <assert.h>
-#include <ctype.h>
-#include <time.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "win_res.h"
-#include "winseat.h"
-#include "storage.h"
-#include "dialog.h"
-#include "licence.h"
-
-#include <commctrl.h>
-#include <commdlg.h>
-#include <shellapi.h>
-
-#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;
-
-#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;
-}
-
-static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg,
- WPARAM wParam, LPARAM lParam)
-{
- char *str;
-
- switch (msg) {
- case WM_INITDIALOG: {
- str = dupprintf("About %s", appname);
- SetWindowText(hwnd, str);
- sfree(str);
- char *buildinfo_text = buildinfo("\r\n");
- char *text = dupprintf
- ("%s\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s",
- appname, ver, buildinfo_text,
- "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved.");
- sfree(buildinfo_text);
- SetDlgItemText(hwnd, IDA_TEXT, text);
- MakeDlgItemBorderless(hwnd, IDA_TEXT);
- sfree(text);
- return 1;
- }
- case WM_COMMAND:
- switch (LOWORD(wParam)) {
- case IDOK:
- case IDCANCEL:
- EndDialog(hwnd, true);
- return 0;
- case IDA_LICENCE:
- EnableWindow(hwnd, 0);
- DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCEBOX),
- hwnd, LicenceProc);
- EnableWindow(hwnd, 1);
- SetActiveWindow(hwnd);
- return 0;
-
- case IDA_WEB:
- /* Load web browser */
- ShellExecute(hwnd, "open",
- "https://www.chiark.greenend.org.uk/~sgtatham/putty/",
- 0, 0, SW_SHOWDEFAULT);
- return 0;
- }
- return 0;
- case WM_CLOSE:
- EndDialog(hwnd, true);
- return 0;
- }
- return 0;
-}
-
-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:
- 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,
- (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);
- }
-
- /*
- * 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);
- 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);
- DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
- EnableWindow(hwnd, 1);
- SetActiveWindow(hwnd);
-}
-
-void show_help(HWND hwnd)
-{
- launch_help(hwnd, NULL);
-}
-
-void defuse_showwindow(void)
-{
- /*
- * Work around the fact that the app's first call to ShowWindow
- * will ignore the default in favour of the shell-provided
- * setting.
- */
- {
- HWND hwnd;
- hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX),
- NULL, NullDlgProc);
- ShowWindow(hwnd, SW_HIDE);
- SetActiveWindow(hwnd);
- DestroyWindow(hwnd);
- }
-}
-
-bool do_config(Conf *conf)
-{
- bool ret;
-
- 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);
-
- 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);
-
- conf_free(backup_conf);
-
- return ret;
-}
-
-static void win_gui_eventlog(LogPolicy *lp, const char *string)
-{
- char timebuf[40];
- char **location;
- struct tm tm;
-
- tm=ltime();
- strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
-
- if (ninitial < LOGEVENT_INITIAL_MAX)
- location = &events_initial[ninitial];
- else
- location = &events_circular[(circular_first + ncircular) % LOGEVENT_CIRCULAR_MAX];
-
- if (*location)
- sfree(*location);
- *location = dupcat(timebuf, string);
- if (logbox) {
- int count;
- SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING,
- 0, (LPARAM) *location);
- count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0);
- SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0);
- }
- if (ninitial < LOGEVENT_INITIAL_MAX) {
- ninitial++;
- } else if (ncircular < LOGEVENT_CIRCULAR_MAX) {
- ncircular++;
- } else if (ncircular == LOGEVENT_CIRCULAR_MAX) {
- circular_first = (circular_first + 1) % LOGEVENT_CIRCULAR_MAX;
- sfree(events_circular[circular_first]);
- events_circular[circular_first] = dupstr("..");
- }
-}
-
-static void win_gui_logging_error(LogPolicy *lp, const char *event)
-{
- WinGuiSeat *wgs = container_of(lp, WinGuiSeat, logpolicy);
-
- /* Send 'can't open log file' errors to the terminal window.
- * (Marked as stderr, although terminal.c won't care.) */
- seat_stderr_pl(&wgs->seat, ptrlen_from_asciz(event));
- seat_stderr_pl(&wgs->seat, PTRLEN_LITERAL("\r\n"));
-}
-
-void showeventlog(HWND hwnd)
-{
- if (!logbox) {
- logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX),
- hwnd, LogProc);
- ShowWindow(logbox, SW_SHOWNORMAL);
- }
- SetActiveWindow(logbox);
-}
-
-void showabout(HWND hwnd)
-{
- DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
-}
-
-struct hostkey_dialog_ctx {
- 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);
- }
-
- 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/windows/window.c b/windows/window.c
index 82bbe8e6..979e2ed3 100644
--- a/windows/window.c
+++ b/windows/window.c
@@ -121,11 +121,51 @@ static int offset_width, offset_height;
static bool was_zoomed = false;
static int prev_rows, prev_cols;
+<<<<<<< HEAD
static void flash_window(WinGuiSeat *wgs, int mode);
static void sys_cursor_update(WinGuiSeat *wgs);
static bool get_fullscreen_rect(WinGuiSeat *wgs, RECT *ss);
static void conf_cache_data(WinGuiSeat *wgs);
+=======
+static 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 cmdline_get_passwd_input_state cmdline_get_passwd_state;
+
+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;
+
+static Conf *conf;
+static LogContext *logctx;
+static Terminal *term;
+
+static void conf_cache_data(void);
+static int cursor_type;
+static int vtmode;
+>>>>>>> tags/0.78
static struct sesslist sesslist; /* for saved-session menu */
@@ -139,6 +179,24 @@ DECL_WINDOWS_FUNCTION(static, HRESULT, AdjustWindowRectExForDpi, (LPRECT lpRect,
static HBITMAP caretbm;
+<<<<<<< HEAD
+=======
+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 wchar_t *window_name, *icon_name;
+
+static int compose_state = 0;
+
+>>>>>>> tags/0.78
static UINT wm_mousewheel = WM_MOUSEWHEEL;
struct WinGuiSeatListNode wgslisthead = {
@@ -289,7 +347,13 @@ static void start_backend(WinGuiSeat *wgs)
char *error, *realhost;
int i;
+<<<<<<< HEAD
wgs->cmdline_get_passwd_state = cmdline_get_passwd_input_state_new;
+=======
+ cmdline_get_passwd_state = cmdline_get_passwd_input_state_new;
+
+ vt = backend_vt_from_conf(conf);
+>>>>>>> tags/0.78
vt = backend_vt_from_conf(wgs->conf);
@@ -308,7 +372,11 @@ static void start_backend(WinGuiSeat *wgs)
msg = dupprintf("Unable to open terminal:\n%s", error);
} else {
msg = dupprintf("Unable to open connection to\n%s\n%s",
+<<<<<<< HEAD
conf_dest(wgs->conf), error);
+=======
+ conf_dest(conf), error);
+>>>>>>> tags/0.78
}
sfree(error);
MessageBox(NULL, msg, str, MB_ICONERROR | MB_OK);
@@ -351,8 +419,13 @@ static void close_session(void *vctx)
wgs->session_closed = true;
newtitle = dupprintf("%s (inactive)", appname);
+<<<<<<< HEAD
win_set_icon_title(&wgs->termwin, newtitle, DEFAULT_CODEPAGE);
win_set_title(&wgs->termwin, newtitle, DEFAULT_CODEPAGE);
+=======
+ win_set_icon_title(wintw, newtitle, DEFAULT_CODEPAGE);
+ win_set_title(wintw, newtitle, DEFAULT_CODEPAGE);
+>>>>>>> tags/0.78
sfree(newtitle);
if (wgs->ldisc) {
@@ -514,11 +587,19 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
/*
* Process the command line.
*/
+<<<<<<< HEAD
gui_term_process_cmdline(wgs->conf, cmdline);
memset(&wgs->ucsdata, 0, sizeof(wgs->ucsdata));
conf_cache_data(wgs);
+=======
+ gui_term_process_cmdline(conf, cmdline);
+
+ memset(&ucsdata, 0, sizeof(ucsdata));
+
+ conf_cache_data();
+>>>>>>> tags/0.78
/*
* Guess some defaults for the window size. This all gets
@@ -553,9 +634,15 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN)
resize_forbidden = true;
wchar_t *uappname = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname);
+<<<<<<< HEAD
wgs->window_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname);
wgs->icon_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname);
if (!conf_get_bool(wgs->conf, CONF_scrollbar))
+=======
+ window_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname);
+ icon_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname);
+ if (!conf_get_bool(conf, CONF_scrollbar))
+>>>>>>> tags/0.78
winmode &= ~(WS_VSCROLL);
if (conf_get_int(wgs->conf, CONF_resize_action) == RESIZE_DISABLED ||
resize_forbidden)
@@ -568,40 +655,65 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
#ifdef TEST_ANSI_WINDOW
/* For developer testing of ANSI window support, pretend
* CreateWindowExW failed */
+<<<<<<< HEAD
wgs->term_hwnd = NULL;
+=======
+ wgs.term_hwnd = NULL;
+>>>>>>> tags/0.78
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
#else
unicode_window = true;
sw_PeekMessage = PeekMessageW;
sw_DispatchMessage = DispatchMessageW;
sw_DefWindowProc = DefWindowProcW;
+<<<<<<< HEAD
wgs->term_hwnd = CreateWindowExW(
+=======
+ wgs.term_hwnd = CreateWindowExW(
+>>>>>>> tags/0.78
exwinmode, terminal_window_class_w(), uappname,
winmode, CW_USEDEFAULT, CW_USEDEFAULT,
guess_width, guess_height, NULL, NULL, inst, NULL);
#endif
#if defined LEGACY_WINDOWS || defined TEST_ANSI_WINDOW
+<<<<<<< HEAD
if (!wgs->term_hwnd && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) {
+=======
+ if (!wgs.term_hwnd && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) {
+>>>>>>> tags/0.78
/* Fall back to an ANSI window, swapping in all the ANSI
* window message handling functions */
unicode_window = false;
sw_PeekMessage = PeekMessageA;
sw_DispatchMessage = DispatchMessageA;
sw_DefWindowProc = DefWindowProcA;
+<<<<<<< HEAD
wgs->term_hwnd = CreateWindowExA(
+=======
+ wgs.term_hwnd = CreateWindowExA(
+>>>>>>> tags/0.78
exwinmode, terminal_window_class_a(), appname,
winmode, CW_USEDEFAULT, CW_USEDEFAULT,
guess_width, guess_height, NULL, NULL, inst, NULL);
}
#endif
+<<<<<<< HEAD
if (!wgs->term_hwnd) {
modalfatalbox("Unable to create terminal window: %s",
win_strerror(GetLastError()));
}
memset(&wgs->dpi_info, 0, sizeof(struct _dpi_info));
init_dpi_info(wgs);
+=======
+ if (!wgs.term_hwnd) {
+ modalfatalbox("Unable to create terminal window: %s",
+ win_strerror(GetLastError()));
+ }
+ memset(&dpi_info, 0, sizeof(struct _dpi_info));
+ init_dpi_info();
+>>>>>>> tags/0.78
sfree(uappname);
}
@@ -759,6 +871,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
gui_terminal_ready(wgs->term_hwnd, &wgs->seat, wgs->backend);
+ gui_terminal_ready(wgs.term_hwnd, &wgs.seat, backend);
+
while (1) {
int n;
DWORD timeout;
@@ -837,6 +951,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
return msg.wParam; /* ... but optimiser doesn't know */
}
+<<<<<<< HEAD
static void wgs_cleanup(WinGuiSeat *wgs)
{
deinit_fonts(wgs);
@@ -847,6 +962,8 @@ static void wgs_cleanup(WinGuiSeat *wgs)
sfree(wgs);
}
+=======
+>>>>>>> tags/0.78
char *handle_restrict_acl_cmdline_prefix(char *p)
{
/*
@@ -1345,11 +1462,19 @@ static int get_font_width(HDC hdc, const TEXTMETRIC *tm)
static void init_dpi_info(WinGuiSeat *wgs)
{
+<<<<<<< HEAD
if (wgs->dpi_info.cur_dpi.x == 0 || wgs->dpi_info.cur_dpi.y == 0) {
if (p_GetDpiForMonitor && p_MonitorFromWindow) {
UINT dpiX, dpiY;
HMONITOR currentMonitor = p_MonitorFromWindow(
wgs->term_hwnd, MONITOR_DEFAULTTOPRIMARY);
+=======
+ if (dpi_info.cur_dpi.x == 0 || dpi_info.cur_dpi.y == 0) {
+ if (p_GetDpiForMonitor && p_MonitorFromWindow) {
+ UINT dpiX, dpiY;
+ HMONITOR currentMonitor = p_MonitorFromWindow(
+ wgs.term_hwnd, MONITOR_DEFAULTTOPRIMARY);
+>>>>>>> tags/0.78
if (p_GetDpiForMonitor(currentMonitor, MDT_EFFECTIVE_DPI,
&dpiX, &dpiY) == S_OK) {
wgs->dpi_info.cur_dpi.x = (int)dpiX;
@@ -1472,12 +1597,21 @@ static void init_fonts(WinGuiSeat *wgs, int pick_width, int pick_height)
/* !!! Yes the next line is right */
if (cset == OEM_CHARSET)
+<<<<<<< HEAD
wgs->ucsdata.font_codepage = GetOEMCP();
else if (TranslateCharsetInfo ((DWORD *)(ULONG_PTR)cset,
&info, TCI_SRCCHARSET))
wgs->ucsdata.font_codepage = info.ciACP;
else
wgs->ucsdata.font_codepage = -1;
+=======
+ ucsdata.font_codepage = GetOEMCP();
+ else if (TranslateCharsetInfo ((DWORD *)(ULONG_PTR)cset,
+ &info, TCI_SRCCHARSET))
+ ucsdata.font_codepage = info.ciACP;
+ else
+ ucsdata.font_codepage = -1;
+>>>>>>> tags/0.78
GetCPInfo(wgs->ucsdata.font_codepage, &cpinfo);
wgs->ucsdata.dbcs_screenfont = (cpinfo.MaxCharSize > 1);
@@ -1658,9 +1792,15 @@ static void wintw_request_resize(TermWin *tw, int w, int h)
int width, height;
/* If the window is maximized suppress resizing attempts */
+<<<<<<< HEAD
if (IsZoomed(wgs->term_hwnd)) {
if (conf_get_int(wgs->conf, CONF_resize_action) == RESIZE_TERM) {
term_resize_request_completed(wgs->term);
+=======
+ if (IsZoomed(wgs.term_hwnd)) {
+ if (conf_get_int(conf, CONF_resize_action) == RESIZE_TERM) {
+ term_resize_request_completed(term);
+>>>>>>> tags/0.78
return;
}
}
@@ -1674,13 +1814,21 @@ static void wintw_request_resize(TermWin *tw, int w, int h)
/* Sanity checks ... */
{
RECT ss;
+<<<<<<< HEAD
if (get_fullscreen_rect(wgs, &ss)) {
+=======
+ if (get_fullscreen_rect(&ss)) {
+>>>>>>> tags/0.78
/* Make sure the values aren't too big */
width = (ss.right - ss.left - extra_width) / 4;
height = (ss.bottom - ss.top - extra_height) / 6;
if (w > width || h > height) {
+<<<<<<< HEAD
term_resize_request_completed(wgs->term);
+=======
+ term_resize_request_completed(term);
+>>>>>>> tags/0.78
return;
}
if (w < 15)
@@ -1690,12 +1838,21 @@ static void wintw_request_resize(TermWin *tw, int w, int h)
}
}
+<<<<<<< HEAD
if (conf_get_int(wgs->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,
+=======
+ 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,
+>>>>>>> tags/0.78
SWP_NOACTIVATE | SWP_NOCOPYBITS |
SWP_NOMOVE | SWP_NOZORDER);
} else {
@@ -1704,12 +1861,21 @@ static void wintw_request_resize(TermWin *tw, int w, int h)
* terminal the new size immediately, so that reset_window
* will know what to do.
*/
+<<<<<<< HEAD
term_size(wgs->term, h, w, conf_get_int(wgs->conf, CONF_savelines));
reset_window(wgs, 0);
}
term_resize_request_completed(wgs->term);
InvalidateRect(wgs->term_hwnd, NULL, true);
+=======
+ term_size(term, h, w, conf_get_int(conf, CONF_savelines));
+ reset_window(0);
+ }
+
+ term_resize_request_completed(term);
+ InvalidateRect(wgs.term_hwnd, NULL, true);
+>>>>>>> tags/0.78
}
static void recompute_window_offset(WinGuiSeat *wgs)
@@ -1837,6 +2003,7 @@ static void reset_window(WinGuiSeat *wgs, int reinit)
wgs->dpi_info.cur_dpi.x);
rect.right += (window_border * 2);
rect.bottom += (window_border * 2);
+<<<<<<< HEAD
OffsetRect(&wgs->dpi_info.new_wnd_rect,
((wgs->dpi_info.new_wnd_rect.right -
wgs->dpi_info.new_wnd_rect.left) -
@@ -1847,6 +2014,15 @@ static void reset_window(WinGuiSeat *wgs, int reinit)
SetWindowPos(wgs->term_hwnd, NULL,
wgs->dpi_info.new_wnd_rect.left,
wgs->dpi_info.new_wnd_rect.top,
+=======
+ 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,
+>>>>>>> tags/0.78
rect.right - rect.left, rect.bottom - rect.top,
SWP_NOZORDER);
@@ -1900,7 +2076,11 @@ static void reset_window(WinGuiSeat *wgs, int reinit)
static RECT ss;
int width, height;
+<<<<<<< HEAD
get_fullscreen_rect(wgs, &ss);
+=======
+ get_fullscreen_rect(&ss);
+>>>>>>> tags/0.78
width = (ss.right - ss.left - extra_width) / font_width;
height = (ss.bottom - ss.top - extra_height) / font_height;
@@ -2016,10 +2196,17 @@ static Mouse_Button translate_button(WinGuiSeat *wgs, Mouse_Button button)
if (button == MBT_LEFT)
return MBT_SELECT;
if (button == MBT_MIDDLE)
+<<<<<<< HEAD
return conf_get_int(wgs->conf, CONF_mouse_is_xterm) == 1 ?
MBT_PASTE : MBT_EXTEND;
if (button == MBT_RIGHT)
return conf_get_int(wgs->conf, CONF_mouse_is_xterm) == 1 ?
+=======
+ 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 ?
+>>>>>>> tags/0.78
MBT_EXTEND : MBT_PASTE;
return 0; /* shouldn't happen */
}
@@ -2059,6 +2246,11 @@ static bool is_alt_pressed(void)
return false;
}
+<<<<<<< HEAD
+=======
+static bool resizing;
+
+>>>>>>> tags/0.78
static void exit_callback(void *vctx)
{
WinGuiSeat *wgs = (WinGuiSeat *)vctx;
@@ -2088,6 +2280,14 @@ static void exit_callback(void *vctx)
}
static void win_seat_notify_remote_exit(Seat *seat)
+<<<<<<< HEAD
+=======
+{
+ queue_toplevel_callback(exit_callback, NULL);
+}
+
+void timer_change_notify(unsigned long next)
+>>>>>>> tags/0.78
{
WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat);
queue_toplevel_callback(exit_callback, wgs);
@@ -2256,7 +2456,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
int size;
serbuf = strbuf_new();
+<<<<<<< HEAD
conf_serialise(BinarySink_UPCAST(serbuf), wgs->conf);
+=======
+ conf_serialise(BinarySink_UPCAST(serbuf), conf);
+>>>>>>> tags/0.78
size = serbuf->len;
sa.nLength = sizeof(sa);
@@ -2348,8 +2552,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
{
/* Disable full-screen if resizing forbidden */
int i;
+<<<<<<< HEAD
for (i = 0; i < lenof(wgs->popup_menus); i++)
EnableMenuItem(wgs->popup_menus[i].menu, IDM_FULLSCREEN,
+=======
+ for (i = 0; i < lenof(popup_menus); i++)
+ EnableMenuItem(popup_menus[i].menu, IDM_FULLSCREEN,
+>>>>>>> tags/0.78
MF_BYCOMMAND |
(resize_action == RESIZE_DISABLED
? MF_GRAYED : MF_ENABLED));
@@ -2366,9 +2575,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
* Flush the line discipline's edit buffer in the
* case where local editing has just been disabled.
*/
+<<<<<<< HEAD
if (wgs->ldisc) {
ldisc_configure(wgs->ldisc, wgs->conf);
ldisc_echoedit_update(wgs->ldisc);
+=======
+ if (ldisc) {
+ ldisc_configure(ldisc, conf);
+ ldisc_echoedit_update(ldisc);
+>>>>>>> tags/0.78
}
if (conf_get_bool(wgs->conf, CONF_system_colour) !=
@@ -2412,9 +2627,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
GetWindowLongPtr(hwnd, GWL_EXSTYLE);
nexflag = exflag;
+<<<<<<< HEAD
if (conf_get_bool(wgs->conf, CONF_alwaysontop) !=
conf_get_bool(prev_conf, CONF_alwaysontop)) {
if (conf_get_bool(wgs->conf, CONF_alwaysontop)) {
+=======
+ if (conf_get_bool(conf, CONF_alwaysontop) !=
+ conf_get_bool(prev_conf, CONF_alwaysontop)) {
+ if (conf_get_bool(conf, CONF_alwaysontop)) {
+>>>>>>> tags/0.78
nexflag |= WS_EX_TOPMOST;
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE);
@@ -2424,13 +2645,21 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
SWP_NOMOVE | SWP_NOSIZE);
}
}
+<<<<<<< HEAD
if (conf_get_bool(wgs->conf, CONF_sunken_edge))
+=======
+ if (conf_get_bool(conf, CONF_sunken_edge))
+>>>>>>> tags/0.78
nexflag |= WS_EX_CLIENTEDGE;
else
nexflag &= ~(WS_EX_CLIENTEDGE);
nflg = flag;
+<<<<<<< HEAD
if (conf_get_bool(wgs->conf, is_full_screen(wgs) ?
+=======
+ if (conf_get_bool(conf, is_full_screen() ?
+>>>>>>> tags/0.78
CONF_scrollbar_in_fullscreen :
CONF_scrollbar))
nflg |= WS_VSCROLL;
@@ -2438,7 +2667,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
nflg &= ~WS_VSCROLL;
if (resize_action == RESIZE_DISABLED ||
+<<<<<<< HEAD
is_full_screen(wgs))
+=======
+ is_full_screen())
+>>>>>>> tags/0.78
nflg &= ~WS_THICKFRAME;
else
nflg |= WS_THICKFRAME;
@@ -2470,21 +2703,37 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
}
{
+<<<<<<< HEAD
FontSpec *font = conf_get_fontspec(wgs->conf, CONF_font);
+=======
+ FontSpec *font = conf_get_fontspec(conf, CONF_font);
+>>>>>>> tags/0.78
FontSpec *prev_font = conf_get_fontspec(prev_conf,
CONF_font);
if (!strcmp(font->name, prev_font->name) ||
+<<<<<<< HEAD
!strcmp(conf_get_str(wgs->conf, CONF_line_codepage),
+=======
+ !strcmp(conf_get_str(conf, CONF_line_codepage),
+>>>>>>> tags/0.78
conf_get_str(prev_conf, CONF_line_codepage)) ||
font->isbold != prev_font->isbold ||
font->height != prev_font->height ||
font->charset != prev_font->charset ||
+<<<<<<< HEAD
conf_get_int(wgs->conf, CONF_font_quality) !=
conf_get_int(prev_conf, CONF_font_quality) ||
conf_get_int(wgs->conf, CONF_vtmode) !=
conf_get_int(prev_conf, CONF_vtmode) ||
conf_get_int(wgs->conf, CONF_bold_style) !=
+=======
+ conf_get_int(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) !=
+>>>>>>> tags/0.78
conf_get_int(prev_conf, CONF_bold_style) ||
resize_action == RESIZE_DISABLED ||
resize_action == RESIZE_EITHER ||
@@ -2582,9 +2831,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
POINT cursorpos;
/* Just in case this happened in mid-select */
+<<<<<<< HEAD
term_cancel_selection_drag(wgs->term);
show_mouseptr(wgs, true); /* make sure pointer is visible */
+=======
+ term_cancel_selection_drag(term);
+
+ show_mouseptr(true); /* make sure pointer is visible */
+>>>>>>> tags/0.78
GetCursorPos(&cursorpos);
TrackPopupMenu(wgs->popup_menus[CTXMENU].menu,
TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON,
@@ -2664,7 +2919,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
if (pt.x == 0 && pt.y == 0) {
mouse_on_hotspot = true;
}
+<<<<<<< HEAD
if (is_full_screen(wgs) && press &&
+=======
+ if (is_full_screen() && press &&
+>>>>>>> tags/0.78
button == MBT_LEFT && mouse_on_hotspot) {
SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU,
MAKELPARAM(pt.x, pt.y));
@@ -2698,7 +2957,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
static LPARAM lp = 0;
if (wParam != wp || lParam != lp ||
last_mousemove != WM_MOUSEMOVE) {
+<<<<<<< HEAD
show_mouseptr(wgs, true);
+=======
+ show_mouseptr(true);
+>>>>>>> tags/0.78
wp = wParam; lp = lParam;
last_mousemove = WM_MOUSEMOVE;
}
@@ -2729,7 +2992,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
static LPARAM lp = 0;
if (wParam != wp || lParam != lp ||
last_mousemove != WM_NCMOUSEMOVE) {
+<<<<<<< HEAD
show_mouseptr(wgs, true);
+=======
+ show_mouseptr(true);
+>>>>>>> tags/0.78
wp = wParam; lp = lParam;
last_mousemove = WM_NCMOUSEMOVE;
}
@@ -2749,8 +3016,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
HideCaret(hwnd);
hdc = BeginPaint(hwnd, &p);
+<<<<<<< HEAD
if (wgs->pal) {
SelectPalette(hdc, wgs->pal, true);
+=======
+ if (pal) {
+ SelectPalette(hdc, pal, true);
+>>>>>>> tags/0.78
RealizePalette(hdc);
}
@@ -2805,10 +3077,17 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
HBRUSH fillcolour, oldbrush;
HPEN edge, oldpen;
fillcolour = CreateSolidBrush (
+<<<<<<< HEAD
wgs->colours[ATTR_DEFBG>>ATTR_BGSHIFT]);
oldbrush = SelectObject(hdc, fillcolour);
edge = CreatePen(PS_SOLID, 0,
wgs->colours[ATTR_DEFBG>>ATTR_BGSHIFT]);
+=======
+ colours[ATTR_DEFBG>>ATTR_BGSHIFT]);
+ oldbrush = SelectObject(hdc, fillcolour);
+ edge = CreatePen(PS_SOLID, 0,
+ colours[ATTR_DEFBG>>ATTR_BGSHIFT]);
+>>>>>>> tags/0.78
oldpen = SelectObject(hdc, edge);
/*
@@ -2824,8 +3103,13 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
ExcludeClipRect(hdc,
offset_width, offset_height,
+<<<<<<< HEAD
offset_width+font_width*wgs->term->cols,
offset_height+font_height*wgs->term->rows);
+=======
+ offset_width+font_width*term->cols,
+ offset_height+font_height*term->rows);
+>>>>>>> tags/0.78
Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
p.rcPaint.right, p.rcPaint.bottom);
@@ -3008,10 +3292,17 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
}
if (wParam == SIZE_MINIMIZED)
sw_SetWindowText(hwnd,
+<<<<<<< HEAD
conf_get_bool(wgs->conf, CONF_win_name_always) ?
wgs->window_name : wgs->icon_name);
if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
sw_SetWindowText(hwnd, wgs->window_name);
+=======
+ conf_get_bool(conf, CONF_win_name_always) ?
+ window_name : icon_name);
+ if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
+ sw_SetWindowText(hwnd, window_name);
+>>>>>>> tags/0.78
if (wParam == SIZE_RESTORED) {
processed_resize = false;
clear_full_screen(wgs);
@@ -3288,20 +3579,32 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
* instead we send the characters one by one.
*/
/* don't divide SURROGATE PAIR */
+<<<<<<< HEAD
if (wgs->ldisc) {
+=======
+ if (ldisc) {
+>>>>>>> tags/0.78
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(
+<<<<<<< HEAD
wgs->term, (unsigned short *)(buff+i), 2);
+=======
+ term, (unsigned short *)(buff+i), 2);
+>>>>>>> tags/0.78
i += 2;
continue;
}
}
term_keyinputw(
+<<<<<<< HEAD
wgs->term, (unsigned short *)(buff+i), 1);
+=======
+ term, (unsigned short *)(buff+i), 1);
+>>>>>>> tags/0.78
}
}
free(buff);
@@ -3347,9 +3650,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
}
} else {
char c = (unsigned char)wParam;
+<<<<<<< HEAD
term_seen_key_event(wgs->term);
if (wgs->ldisc)
term_keyinput(wgs->term, CP_ACP, &c, 1);
+=======
+ term_seen_key_event(term);
+ if (ldisc)
+ term_keyinput(term, CP_ACP, &c, 1);
+>>>>>>> tags/0.78
}
return 0;
case WM_SYSCOLORCHANGE:
@@ -4280,11 +4589,19 @@ static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam,
}
if (wParam == compose_keycode) {
+<<<<<<< HEAD
if (wgs->compose_state == 0 &&
(HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0)
wgs->compose_state = 1;
else if (wgs->compose_state == 1 && (HIWORD(lParam) & KF_UP))
wgs->compose_state = 2;
+=======
+ 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;
+>>>>>>> tags/0.78
else
wgs->compose_state = 0;
} else if (wgs->compose_state == 1 && wParam != VK_CONTROL)
@@ -4592,7 +4909,11 @@ static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam,
case VK_F20: fkey_number = 20; goto numbered_function_key;
numbered_function_key:
consumed_alt = false;
+<<<<<<< HEAD
p += format_function_key((char *)p, wgs->term, fkey_number,
+=======
+ p += format_function_key((char *)p, term, fkey_number,
+>>>>>>> tags/0.78
shift_state & 1, shift_state & 2,
left_alt, &consumed_alt);
if (consumed_alt)
@@ -4611,7 +4932,11 @@ static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam,
if (shift_state & 2)
break;
+<<<<<<< HEAD
p += format_small_keypad_key((char *)p, wgs->term, sk_key,
+=======
+ p += format_small_keypad_key((char *)p, term, sk_key,
+>>>>>>> tags/0.78
shift_state & 1, shift_state & 2,
left_alt, &consumed_alt);
if (consumed_alt)
@@ -4626,7 +4951,11 @@ static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam,
case VK_CLEAR: xkey = 'G'; goto arrow_key; /* close enough */
arrow_key:
consumed_alt = false;
+<<<<<<< HEAD
p += format_arrow_key((char *)p, wgs->term, xkey, shift_state & 1,
+=======
+ p += format_arrow_key((char *)p, term, xkey, shift_state & 1,
+>>>>>>> tags/0.78
shift_state & 2, left_alt, &consumed_alt);
if (consumed_alt)
left_alt = false; /* supersedes the usual prefixing of Esc */
@@ -4807,6 +5136,7 @@ static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam,
static void wintw_set_title(TermWin *tw, const char *title, int codepage)
{
+<<<<<<< HEAD
WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin);
wchar_t *new_window_name = dup_mb_to_wc(codepage, 0, title);
if (!wcscmp(new_window_name, wgs->window_name)) {
@@ -4818,10 +5148,22 @@ static void wintw_set_title(TermWin *tw, const char *title, int codepage)
if (conf_get_bool(wgs->conf, CONF_win_name_always) ||
!IsIconic(wgs->term_hwnd))
sw_SetWindowText(wgs->term_hwnd, wgs->window_name);
+=======
+ wchar_t *new_window_name = dup_mb_to_wc(codepage, 0, title);
+ if (!wcscmp(new_window_name, window_name)) {
+ sfree(new_window_name);
+ return;
+ }
+ sfree(window_name);
+ window_name = new_window_name;
+ if (conf_get_bool(conf, CONF_win_name_always) || !IsIconic(wgs.term_hwnd))
+ sw_SetWindowText(wgs.term_hwnd, window_name);
+>>>>>>> tags/0.78
}
static void wintw_set_icon_title(TermWin *tw, const char *title, int codepage)
{
+<<<<<<< HEAD
WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin);
wchar_t *new_icon_name = dup_mb_to_wc(codepage, 0, title);
if (!wcscmp(new_icon_name, wgs->icon_name)) {
@@ -4833,6 +5175,17 @@ static void wintw_set_icon_title(TermWin *tw, const char *title, int codepage)
if (!conf_get_bool(wgs->conf, CONF_win_name_always) &&
IsIconic(wgs->term_hwnd))
sw_SetWindowText(wgs->term_hwnd, wgs->icon_name);
+=======
+ wchar_t *new_icon_name = dup_mb_to_wc(codepage, 0, title);
+ if (!wcscmp(new_icon_name, icon_name)) {
+ sfree(new_icon_name);
+ return;
+ }
+ sfree(icon_name);
+ icon_name = new_icon_name;
+ if (!conf_get_bool(conf, CONF_win_name_always) && IsIconic(wgs.term_hwnd))
+ sw_SetWindowText(wgs.term_hwnd, icon_name);
+>>>>>>> tags/0.78
}
static void wintw_set_scrollbar(TermWin *tw, int total, int start, int page)
@@ -5134,7 +5487,11 @@ static void wintw_clip_write(
for (i = 0; i < OSC4_NCOLOURS; i++) {
if (palette[i] != 0) {
+<<<<<<< HEAD
const PALETTEENTRY *pe = &wgs->logpal->palPalEntry[i];
+=======
+ const PALETTEENTRY *pe = &logpal->palPalEntry[i];
+>>>>>>> tags/0.78
put_fmt(rtf, "\\red%d\\green%d\\blue%d;",
pe->peRed, pe->peGreen, pe->peBlue);
}
@@ -5684,7 +6041,11 @@ static void wintw_move(TermWin *tw, int x, int y)
int resize_action = conf_get_int(wgs->conf, CONF_resize_action);
if (resize_action == RESIZE_DISABLED ||
resize_action == RESIZE_FONT ||
+<<<<<<< HEAD
IsZoomed(wgs->term_hwnd))
+=======
+ IsZoomed(wgs.term_hwnd))
+>>>>>>> tags/0.78
return;
SetWindowPos(wgs->term_hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
@@ -5743,13 +6104,21 @@ static bool is_full_screen(WinGuiSeat *wgs)
/* 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. */
+<<<<<<< HEAD
static bool get_fullscreen_rect(WinGuiSeat *wgs, RECT *ss)
+=======
+static bool get_fullscreen_rect(RECT *ss)
+>>>>>>> tags/0.78
{
#if defined(MONITOR_DEFAULTTONEAREST) && !defined(NO_MULTIMON)
if (p_GetMonitorInfoA && p_MonitorFromWindow) {
HMONITOR mon;
MONITORINFO mi;
+<<<<<<< HEAD
mon = p_MonitorFromWindow(wgs->term_hwnd, MONITOR_DEFAULTTONEAREST);
+=======
+ mon = p_MonitorFromWindow(wgs.term_hwnd, MONITOR_DEFAULTTONEAREST);
+>>>>>>> tags/0.78
mi.cbSize = sizeof(mi);
p_GetMonitorInfoA(mon, &mi);
@@ -5778,7 +6147,11 @@ static void make_full_screen(WinGuiSeat *wgs)
assert(IsZoomed(wgs->term_hwnd));
+<<<<<<< HEAD
if (is_full_screen(wgs))
+=======
+ if (is_full_screen())
+>>>>>>> tags/0.78
return;
/* Remove the window furniture. */
@@ -5791,8 +6164,13 @@ static void make_full_screen(WinGuiSeat *wgs)
SetWindowLongPtr(wgs->term_hwnd, GWL_STYLE, style);
/* Resize ourselves to exactly cover the nearest monitor. */
+<<<<<<< HEAD
get_fullscreen_rect(wgs, &ss);
SetWindowPos(wgs->term_hwnd, HWND_TOP, ss.left, ss.top,
+=======
+ get_fullscreen_rect(&ss);
+ SetWindowPos(wgs.term_hwnd, HWND_TOP, ss.left, ss.top,
+>>>>>>> tags/0.78
ss.right - ss.left, ss.bottom - ss.top, SWP_FRAMECHANGED);
/* We may have changed size as a result */
@@ -5860,6 +6238,7 @@ static void flip_full_screen(WinGuiSeat *wgs)
static size_t win_seat_output(Seat *seat, SeatOutputType type,
const void *data, size_t len)
{
+<<<<<<< HEAD
WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat);
return term_data(wgs->term, data, len);
}
@@ -5869,6 +6248,15 @@ static void wintw_unthrottle(TermWin *tw, size_t bufsize)
WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin);
if (wgs->backend)
backend_unthrottle(wgs->backend, bufsize);
+=======
+ return term_data(term, data, len);
+}
+
+static void wintw_unthrottle(TermWin *win, size_t bufsize)
+{
+ if (backend)
+ backend_unthrottle(backend, bufsize);
+>>>>>>> tags/0.78
}
static bool win_seat_eof(Seat *seat)
@@ -5878,15 +6266,23 @@ static bool win_seat_eof(Seat *seat)
static SeatPromptResult win_seat_get_userpass_input(Seat *seat, prompts_t *p)
{
+<<<<<<< HEAD
WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat);
SeatPromptResult spr;
spr = cmdline_get_passwd_input(p, &wgs->cmdline_get_passwd_state, true);
if (spr.kind == SPRK_INCOMPLETE)
spr = term_get_userpass_input(wgs->term, p);
+=======
+ SeatPromptResult spr;
+ spr = cmdline_get_passwd_input(p, &cmdline_get_passwd_state, true);
+ if (spr.kind == SPRK_INCOMPLETE)
+ spr = term_get_userpass_input(term, p);
+>>>>>>> tags/0.78
return spr;
}
static void win_seat_set_trust_status(Seat *seat, bool trusted)
+<<<<<<< HEAD
{
WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat);
term_set_trust_status(wgs->term, trusted);
@@ -5894,6 +6290,14 @@ static void win_seat_set_trust_status(Seat *seat, bool trusted)
static bool win_seat_can_set_trust_status(Seat *seat)
{
+=======
+{
+ term_set_trust_status(term, trusted);
+}
+
+static bool win_seat_can_set_trust_status(Seat *seat)
+{
+>>>>>>> tags/0.78
return true;
}
diff --git a/windows/wingss.c b/windows/wingss.c
deleted file mode 100644
index 79c4921c..00000000
--- a/windows/wingss.c
+++ /dev/null
@@ -1,703 +0,0 @@
-#ifndef NO_GSSAPI
-
-#include <limits.h>
-#include "putty.h"
-
-#define SECURITY_WIN32
-#include <security.h>
-
-#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", &regkey)
- == 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/windows/winhandl.c b/windows/winhandl.c
deleted file mode 100644
index 82d2aded..00000000
--- a/windows/winhandl.c
+++ /dev/null
@@ -1,731 +0,0 @@
-/*
- * 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 <assert.h>
-
-#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/windows/winhelp.c b/windows/winhelp.c
deleted file mode 100644
index df6ac37b..00000000
--- a/windows/winhelp.c
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * winhelp.c: centralised functions to launch Windows HTML Help files.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#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) { }
-
-#else
-
-#include <htmlhelp.h>
-
-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/windows/winhelp.h b/windows/winhelp.h
deleted file mode 100644
index 9011df45..00000000
--- a/windows/winhelp.h
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * 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/windows/winhelp.rc2 b/windows/winhelp.rc2
deleted file mode 100644
index 3499d25e..00000000
--- a/windows/winhelp.rc2
+++ /dev/null
@@ -1,8 +0,0 @@
-#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/windows/winhsock.c b/windows/winhsock.c
deleted file mode 100644
index 543b77b6..00000000
--- a/windows/winhsock.c
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * General mechanism for wrapping up reading/writing of Windows
- * HANDLEs into a PuTTY Socket abstraction.
- */
-
-#include <stdio.h>
-#include <assert.h>
-#include <limits.h>
-
-#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/windows/winjump.c b/windows/winjump.c
deleted file mode 100644
index 358504fd..00000000
--- a/windows/winjump.c
+++ /dev/null
@@ -1,748 +0,0 @@
-/*
- * 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 <assert.h>
-
-#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 <shobjidl.h>, 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)
- {
- if (p_SetCurrentProcessExplicitAppUserModelID(L"SimonTatham.PuTTY") == S_OK)
- {
- return true;
- }
- return false;
- }
- /* Function doesn't exist, which is ok for Pre-7 systems */
-
- return true;
-
-}
diff --git a/windows/winmisc.c b/windows/winmisc.c
deleted file mode 100644
index 759df011..00000000
--- a/windows/winmisc.c
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * winmisc.c: miscellaneous Windows-specific things
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-#include "putty.h"
-#ifndef SECURITY_WIN32
-#define SECURITY_WIN32
-#endif
-#include <security.h>
-
-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/windows/winmiscs.c b/windows/winmiscs.c
deleted file mode 100644
index 571a9122..00000000
--- a/windows/winmiscs.c
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * 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/windows/winnet.c b/windows/winnet.c
deleted file mode 100644
index 3b4da3cc..00000000
--- a/windows/winnet.c
+++ /dev/null
@@ -1,1825 +0,0 @@
-/*
- * Windows networking abstraction.
- *
- * For the IPv6 code in here I am indebted to Jeroen Massar and
- * unfix.org.
- */
-
-#include <winsock2.h> /* need to put this first, for winelib builds */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#define NEED_DECLARATION_OF_SELECT /* in order to initialise it */
-
-#include "putty.h"
-#include "network.h"
-#include "tree234.h"
-#include "ssh.h"
-
-#include <ws2tcpip.h>
-
-#ifndef NO_IPV6
-#ifdef __clang__
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wmissing-braces"
-#endif
-const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
-const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
-#ifdef __clang__
-#pragma clang diagnostic pop
-#endif
-#endif
-
-#define ipv4_is_loopback(addr) \
- ((p_ntohl(addr.s_addr) & 0xFF000000L) == 0x7F000000L)
-
-/*
- * Mutable state that goes with a SockAddr: stores information
- * about where in the list of candidate IP(v*) addresses we've
- * currently got to.
- */
-typedef struct SockAddrStep_tag SockAddrStep;
-struct SockAddrStep_tag {
-#ifndef NO_IPV6
- struct addrinfo *ai; /* steps along addr->ais */
-#endif
- int curraddr;
-};
-
-typedef struct NetSocket NetSocket;
-struct NetSocket {
- const char *error;
- SOCKET s;
- Plug *plug;
- bufchain output_data;
- bool connected;
- bool writable;
- bool frozen; /* this causes readability notifications to be ignored */
- bool frozen_readable; /* this means we missed at least one readability
- * notification while we were frozen */
- bool localhost_only; /* for listening sockets */
- char oobdata[1];
- size_t sending_oob;
- bool oobinline, nodelay, keepalive, privport;
- enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
- SockAddr *addr;
- SockAddrStep step;
- int port;
- int pending_error; /* in case send() returns error */
- /*
- * We sometimes need pairs of Socket structures to be linked:
- * if we are listening on the same IPv6 and v4 port, for
- * example. So here we define `parent' and `child' pointers to
- * track this link.
- */
- NetSocket *parent, *child;
-
- Socket sock;
-};
-
-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;
-
-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);
-}
-
-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, "<unknown>", 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,
-};
-
-static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug)
-{
- DWORD err;
- const 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);
- 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);
- }
-
- {
- SockAddr thisaddr = sk_extractaddr_tmp(
- sock->addr, &sock->step);
- plug_log(sock->plug, PLUGLOG_CONNECT_TRYING,
- &thisaddr, sock->port, NULL, 0);
- }
-
- /*
- * Open socket.
- */
- family = SOCKADDR_FAMILY(sock->addr, sock->step);
-
- /*
- * Remove the socket from the tree before we overwrite its
- * internal socket id, because that forms part of the tree's
- * sorting criterion. We'll add it back before exiting this
- * function, whether we changed anything or not.
- */
- del234(sktree, sock);
-
- s = p_socket(family, SOCK_STREAM, 0);
- sock->s = s;
-
- if (s == INVALID_SOCKET) {
- err = p_WSAGetLastError();
- sock->error = winsock_error_string(err);
- goto ret;
- }
-
- SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0);
-
- if (sock->oobinline) {
- BOOL b = true;
- p_setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (void *) &b, sizeof(b));
- }
-
- if (sock->nodelay) {
- BOOL b = true;
- p_setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b));
- }
-
- if (sock->keepalive) {
- BOOL b = true;
- p_setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &b, sizeof(b));
- }
-
- /*
- * Bind to local address.
- */
- if (sock->privport)
- localport = 1023; /* count from 1023 downwards */
- else
- localport = 0; /* just use port 0 (ie winsock picks) */
-
- /* Loop round trying to bind */
- while (1) {
- int sockcode;
-
-#ifndef NO_IPV6
- if (family == AF_INET6) {
- memset(&a6, 0, sizeof(a6));
- a6.sin6_family = AF_INET6;
- /*a6.sin6_addr = in6addr_any; */ /* == 0 done by memset() */
- a6.sin6_port = p_htons(localport);
- } else
-#endif
- {
- a.sin_family = AF_INET;
- a.sin_addr.s_addr = p_htonl(INADDR_ANY);
- a.sin_port = p_htons(localport);
- }
-#ifndef NO_IPV6
- sockcode = p_bind(s, (family == AF_INET6 ?
- (struct sockaddr *) &a6 :
- (struct sockaddr *) &a),
- (family == AF_INET6 ? sizeof(a6) : sizeof(a)));
-#else
- sockcode = p_bind(s, (struct sockaddr *) &a, sizeof(a));
-#endif
- if (sockcode != SOCKET_ERROR) {
- err = 0;
- break; /* done */
- } else {
- err = p_WSAGetLastError();
- if (err != WSAEADDRINUSE) /* failed, for a bad reason */
- break;
- }
-
- if (localport == 0)
- break; /* we're only looping once */
- localport--;
- if (localport == 0)
- break; /* we might have got to the end */
- }
-
- if (err) {
- sock->error = winsock_error_string(err);
- goto ret;
- }
-
- /*
- * Connect to remote address.
- */
-#ifndef NO_IPV6
- if (sock->step.ai) {
- if (family == AF_INET6) {
- a6.sin6_family = AF_INET6;
- a6.sin6_port = p_htons((short) sock->port);
- a6.sin6_addr =
- ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_addr;
- a6.sin6_flowinfo = ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_flowinfo;
- a6.sin6_scope_id = ((struct sockaddr_in6 *) sock->step.ai->ai_addr)->sin6_scope_id;
- } else {
- a.sin_family = AF_INET;
- a.sin_addr =
- ((struct sockaddr_in *) sock->step.ai->ai_addr)->sin_addr;
- a.sin_port = p_htons((short) sock->port);
- }
- } else
-#endif
- {
- assert(sock->addr->addresses && sock->step.curraddr < sock->addr->naddresses);
- a.sin_family = AF_INET;
- a.sin_addr.s_addr = p_htonl(sock->addr->addresses[sock->step.curraddr]);
- a.sin_port = p_htons((short) sock->port);
- }
-
- /* Set up a select mechanism. This could be an AsyncSelect on a
- * window, or an EventSelect on an event object. */
- errstr = do_select(s, true);
- if (errstr) {
- sock->error = errstr;
- err = 1;
- goto ret;
- }
-
- if ((
-#ifndef NO_IPV6
- p_connect(s,
- ((family == AF_INET6) ? (struct sockaddr *) &a6 :
- (struct sockaddr *) &a),
- (family == AF_INET6) ? sizeof(a6) : sizeof(a))
-#else
- p_connect(s, (struct sockaddr *) &a, sizeof(a))
-#endif
- ) == SOCKET_ERROR) {
- err = p_WSAGetLastError();
- /*
- * We expect a potential EWOULDBLOCK here, because the
- * chances are the front end has done a select for
- * FD_CONNECT, so that connect() will complete
- * asynchronously.
- */
- if ( err != WSAEWOULDBLOCK ) {
- sock->error = winsock_error_string(err);
- goto ret;
- }
- } else {
- /*
- * If we _don't_ get EWOULDBLOCK, the connect has completed
- * and we should set the socket as writable.
- */
- sock->writable = true;
- SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step);
- plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS,
- &thisaddr, sock->port, NULL, 0);
- }
-
- err = 0;
-
- ret:
-
- /*
- * No matter what happened, put the socket back in the tree.
- */
- add234(sktree, sock);
-
- if (err) {
- SockAddr thisaddr = sk_extractaddr_tmp(
- sock->addr, &sock->step);
- plug_log(sock->plug, PLUGLOG_CONNECT_FAILED,
- &thisaddr, sock->port, sock->error, err);
- }
- return err;
-}
-
-Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline,
- bool nodelay, bool keepalive, Plug *plug)
-{
- NetSocket *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 */
- 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);
- 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;
-}
-
-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);
-}
-
-/*
- * 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) {
- SockAddr thisaddr = sk_extractaddr_tmp(
- s->addr, &s->step);
- plug_log(s->plug, PLUGLOG_CONNECT_FAILED, &thisaddr, s->port,
- winsock_error_string(err), err);
- while (err && s->addr && sk_nextaddr(s->addr, &s->step)) {
- err = try_connect(s);
- }
- }
- if (err != 0)
- plug_closing(s->plug, winsock_error_string(err), err, 0);
- return;
- }
-
- noise_ultralight(NOISE_SOURCE_IOID, wParam);
-
- switch (WSAGETSELECTEVENT(lParam)) {
- case FD_CONNECT:
- s->connected = true;
- s->writable = true;
-
- /*
- * Once a socket is connected, we can stop falling back
- * through the candidate addresses to connect to. But first,
- * let the plug know we were successful.
- */
- if (s->addr) {
- SockAddr thisaddr = sk_extractaddr_tmp(
- s->addr, &s->step);
- plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS,
- &thisaddr, s->port, NULL, 0);
-
- sk_addr_free(s->addr);
- s->addr = NULL;
- }
- break;
- case FD_READ:
- /* In the case the socket is still frozen, we don't even bother */
- if (s->frozen) {
- s->frozen_readable = true;
- break;
- }
-
- /*
- * We have received data on the socket. For an oobinline
- * socket, this might be data _before_ an urgent pointer,
- * in which case we send it to the back end with type==1
- * (data prior to urgent).
- */
- if (s->oobinline) {
- u_long atmark_from_ioctl = 1;
- p_ioctlsocket(s->s, SIOCATMARK, &atmark_from_ioctl);
- /*
- * Avoid checking the return value from ioctlsocket(),
- * on the grounds that some WinSock wrappers don't
- * support it. If it does nothing, we get atmark==1,
- * which is equivalent to `no OOB pending', so the
- * effect will be to non-OOB-ify any OOB data.
- */
- atmark = atmark_from_ioctl;
- } else
- atmark = true;
-
- ret = p_recv(s->s, buf, sizeof(buf), 0);
- noise_ultralight(NOISE_SOURCE_IOLEN, ret);
- if (ret < 0) {
- err = p_WSAGetLastError();
- if (err == WSAEWOULDBLOCK) {
- break;
- }
- }
- if (ret < 0) {
- plug_closing(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);
- } else {
- plug_receive(s->plug, 2, buf, ret);
- }
- break;
- case FD_WRITE: {
- int bufsize_before, bufsize_after;
- s->writable = true;
- bufsize_before = s->sending_oob + bufchain_size(&s->output_data);
- try_send(s);
- bufsize_after = s->sending_oob + bufchain_size(&s->output_data);
- if (bufsize_after < bufsize_before)
- plug_sent(s->plug, bufsize_after);
- break;
- }
- case FD_CLOSE:
- /* Signal a close on the socket. First read any outstanding data. */
- do {
- ret = p_recv(s->s, buf, sizeof(buf), 0);
- if (ret < 0) {
- err = p_WSAGetLastError();
- if (err == WSAEWOULDBLOCK)
- break;
- plug_closing(s->plug, winsock_error_string(err), err, 0);
- } 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 */
- } else if (plug_accepting(s->plug, sk_net_accept, actx)) {
- p_closesocket(t); /* denied or error */
- }
- break;
- }
- }
-}
-
-/*
- * Special error values are returned from sk_namelookup and sk_new
- * if there's a problem. These functions extract an error message,
- * or return NULL if there's no problem.
- */
-const char *sk_addr_error(SockAddr *addr)
-{
- return addr->error;
-}
-static const char *sk_net_socket_error(Socket *sock)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
- return s->error;
-}
-
-static SocketPeerInfo *sk_net_peer_info(Socket *sock)
-{
- NetSocket *s = container_of(sock, NetSocket, sock);
-#ifdef NO_IPV6
- struct sockaddr_in addr;
-#else
- struct sockaddr_storage addr;
- 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/windows/winnohlp.c b/windows/winnohlp.c
deleted file mode 100644
index 62ddc65c..00000000
--- a/windows/winnohlp.c
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * nohelp.c: implement the has_embedded_chm() function for
- * applications that have no help file at all, so that misc.c's
- * buildinfo string knows not to talk meaninglessly about whether the
- * nonexistent help file is present.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#include "putty.h"
-
-int has_embedded_chm(void) { return -1; }
diff --git a/windows/winnoise.c b/windows/winnoise.c
deleted file mode 100644
index 65c4c92d..00000000
--- a/windows/winnoise.c
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Noise generation for PuTTY's cryptographic random number
- * generator.
- */
-
-#include <stdio.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "storage.h"
-
-#include <wincrypt.h>
-
-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, &times, sizeof(times));
- GetProcessTimes(GetCurrentProcess(), times, times + 1, times + 2,
- times + 3);
- random_add_noise(NOISE_SOURCE_PROCTIME, &times, sizeof(times));
-}
-
-/*
- * This function is called on every keypress or mouse move, and
- * will add the current Windows time and performance monitor
- * counter to the noise pool. It gets the scan code or mouse
- * position passed in.
- */
-void noise_ultralight(NoiseSourceId id, unsigned long data)
-{
- DWORD wintime;
- LARGE_INTEGER perftime;
-
- random_add_noise(id, &data, sizeof(DWORD));
-
- wintime = GetTickCount();
- random_add_noise(NOISE_SOURCE_TIME, &wintime, sizeof(DWORD));
-
- if (QueryPerformanceCounter(&perftime))
- random_add_noise(NOISE_SOURCE_PERFCOUNT, &perftime, sizeof(perftime));
-}
-
-uint64_t prng_reseed_time_ms(void)
-{
- FILETIME ft;
- GetSystemTimeAsFileTime(&ft);
- uint64_t value = ft.dwHighDateTime;
- value = (value << 32) + ft.dwLowDateTime;
- return value / 10000; /* 1 millisecond / 100ns */
-}
diff --git a/windows/winnojmp.c b/windows/winnojmp.c
deleted file mode 100644
index dd61dc69..00000000
--- a/windows/winnojmp.c
+++ /dev/null
@@ -1,8 +0,0 @@
-/*
- * 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/windows/winnpc.c b/windows/winnpc.c
deleted file mode 100644
index eabfb4bc..00000000
--- a/windows/winnpc.c
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Windows support module which deals with being a named-pipe client.
- */
-
-#include <stdio.h>
-#include <assert.h>
-
-#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/windows/winnps.c b/windows/winnps.c
deleted file mode 100644
index 1757cdbb..00000000
--- a/windows/winnps.c
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * Windows support module which deals with being a named-pipe server.
- */
-
-#include <stdio.h>
-#include <assert.h>
-
-#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/windows/winpgen.c b/windows/winpgen.c
deleted file mode 100644
index 56c6a8db..00000000
--- a/windows/winpgen.c
+++ /dev/null
@@ -1,2026 +0,0 @@
-/*
- * PuTTY key generation front end (Windows).
- */
-
-#include <time.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#include "putty.h"
-#include "ssh.h"
-#include "sshkeygen.h"
-#include "licence.h"
-#include "winsecur.h"
-#include "puttygen-rc.h"
-
-#include <commctrl.h>
-
-#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;
-
-/*
- * Print a modal (Really Bad) message box and perform a fatal exit.
- */
-void modalfatalbox(const char *fmt, ...)
-{
- va_list ap;
- char *stuff;
-
- va_start(ap, fmt);
- stuff = dupvprintf(fmt, ap);
- va_end(ap);
- MessageBox(NULL, stuff, "PuTTYgen Fatal Error",
- MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
- sfree(stuff);
- exit(1);
-}
-
-/*
- * Print a non-fatal message box and do not exit.
- */
-void nonfatal(const char *fmt, ...)
-{
- va_list ap;
- char *stuff;
-
- va_start(ap, fmt);
- stuff = dupvprintf(fmt, ap);
- va_end(ap);
- MessageBox(NULL, stuff, "PuTTYgen Error",
- MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
- sfree(stuff);
-}
-
-/* ----------------------------------------------------------------------
- * ProgressReceiver implementation.
- */
-
-#define PROGRESSRANGE 65535
-
-struct progressphase {
- double startpoint, total;
- /* For exponential phases */
- double exp_probability, exp_current_value;
-};
-
-struct progress {
- size_t nphases, phasessize;
- struct progressphase *phases, *currphase;
-
- double scale;
- HWND progbar;
-
- ProgressReceiver rec;
-};
-
-static ProgressPhase win_progress_add_linear(
- ProgressReceiver *prog, double overall_cost) {
- struct progress *p = container_of(prog, struct progress, rec);
-
- sgrowarray(p->phases, p->phasessize, p->nphases);
- int phase = p->nphases++;
-
- p->phases[phase].total = overall_cost;
-
- ProgressPhase ph = { .n = phase };
- return ph;
-}
-
-static ProgressPhase win_progress_add_probabilistic(
- ProgressReceiver *prog, double cost_per_attempt, double probability) {
- struct progress *p = container_of(prog, struct progress, rec);
-
- sgrowarray(p->phases, p->phasessize, p->nphases);
- int phase = p->nphases++;
-
- p->phases[phase].exp_probability = 1.0 - probability;
- p->phases[phase].exp_current_value = 1.0;
- /* Expected number of attempts = 1 / probability of attempt succeeding */
- p->phases[phase].total = cost_per_attempt / probability;
-
- ProgressPhase ph = { .n = phase };
- return ph;
-}
-
-static void win_progress_ready(ProgressReceiver *prog)
-{
- struct progress *p = container_of(prog, struct progress, rec);
-
- double total = 0;
- for (int i = 0; i < p->nphases; i++) {
- p->phases[i].startpoint = total;
- total += p->phases[i].total;
- }
- p->scale = PROGRESSRANGE / total;
-
- SendMessage(p->progbar, PBM_SETRANGE, 0, MAKELPARAM(0, PROGRESSRANGE));
-}
-
-static void win_progress_start_phase(ProgressReceiver *prog,
- ProgressPhase phase)
-{
- struct progress *p = container_of(prog, struct progress, rec);
-
- assert(phase.n < p->nphases);
- p->currphase = &p->phases[phase.n];
-}
-
-static void win_progress_update(struct progress *p, double phasepos)
-{
- double position = (p->currphase->startpoint +
- p->currphase->total * phasepos);
- position *= p->scale;
- if (position < 0)
- position = 0;
- if (position > PROGRESSRANGE)
- position = PROGRESSRANGE;
-
- SendMessage(p->progbar, PBM_SETPOS, (WPARAM)position, 0);
-}
-
-static void win_progress_report(ProgressReceiver *prog, double progress)
-{
- struct progress *p = container_of(prog, struct progress, rec);
-
- win_progress_update(p, progress);
-}
-
-static void win_progress_report_attempt(ProgressReceiver *prog)
-{
- struct progress *p = container_of(prog, struct progress, rec);
-
- p->currphase->exp_current_value *= p->currphase->exp_probability;
- win_progress_update(p, 1.0 - p->currphase->exp_current_value);
-}
-
-static void win_progress_report_phase_complete(ProgressReceiver *prog)
-{
- struct progress *p = container_of(prog, struct progress, rec);
-
- win_progress_update(p, 1.0);
-}
-
-static const ProgressReceiverVtable win_progress_vt = {
- .add_linear = win_progress_add_linear,
- .add_probabilistic = win_progress_add_probabilistic,
- .ready = win_progress_ready,
- .start_phase = win_progress_start_phase,
- .report = win_progress_report,
- .report_attempt = win_progress_report_attempt,
- .report_phase_complete = win_progress_report_phase_complete,
-};
-
-static void win_progress_initialise(struct progress *p)
-{
- p->nphases = p->phasessize = 0;
- p->phases = p->currphase = NULL;
- p->rec.vt = &win_progress_vt;
-}
-
-static void win_progress_cleanup(struct progress *p)
-{
- sfree(p->phases);
-}
-
-struct PassphraseProcStruct {
- char **passphrase;
- char *comment;
-};
-
-/*
- * Dialog-box function for the passphrase box.
- */
-static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg,
- WPARAM wParam, LPARAM lParam)
-{
- static char **passphrase = NULL;
- struct PassphraseProcStruct *p;
-
- switch (msg) {
- case WM_INITDIALOG:
- SetForegroundWindow(hwnd);
- SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
- SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
-
- /*
- * Centre the window.
- */
- { /* centre the window */
- RECT rs, rd;
- HWND hw;
-
- hw = GetDesktopWindow();
- if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
- MoveWindow(hwnd,
- (rs.right + rs.left + rd.left - rd.right) / 2,
- (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
- rd.right - rd.left, rd.bottom - rd.top, true);
- }
-
- p = (struct PassphraseProcStruct *) lParam;
- passphrase = p->passphrase;
- if (p->comment)
- SetDlgItemText(hwnd, 101, p->comment);
- burnstr(*passphrase);
- *passphrase = dupstr("");
- SetDlgItemText(hwnd, 102, *passphrase);
- return 0;
- case WM_COMMAND:
- switch (LOWORD(wParam)) {
- case IDOK:
- if (*passphrase)
- EndDialog(hwnd, 1);
- else
- MessageBeep(0);
- return 0;
- case IDCANCEL:
- EndDialog(hwnd, 0);
- return 0;
- case 102: /* edit box */
- if ((HIWORD(wParam) == EN_CHANGE) && passphrase) {
- burnstr(*passphrase);
- *passphrase = GetDlgItemText_alloc(hwnd, 102);
- }
- return 0;
- }
- return 0;
- case WM_CLOSE:
- EndDialog(hwnd, 0);
- return 0;
- }
- return 0;
-}
-
-static void try_get_dlg_item_uint32(HWND hwnd, int id, uint32_t *out)
-{
- char buf[128];
- if (!GetDlgItemText(hwnd, id, buf, sizeof(buf)))
- return;
-
- if (!*buf)
- return;
-
- char *end;
- unsigned long val = strtoul(buf, &end, 10);
- if (*end)
- return;
-
- if ((val >> 16) >> 16)
- return;
-
- *out = val;
-}
-
-static ppk_save_parameters save_params;
-
-struct PPKParams {
- ppk_save_parameters params;
- uint32_t time_passes, time_ms;
-};
-
-/*
- * Dialog-box function for the passphrase box.
- */
-static INT_PTR CALLBACK PPKParamsProc(HWND hwnd, UINT msg,
- WPARAM wParam, LPARAM lParam)
-{
- struct PPKParams *pp;
- char *buf;
-
- if (msg == WM_INITDIALOG) {
- pp = (struct PPKParams *)lParam;
- SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pp);
- } else {
- pp = (struct PPKParams *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
- }
-
- switch (msg) {
- case WM_INITDIALOG:
- SetForegroundWindow(hwnd);
- SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
- SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
-
- if (has_help())
- SetWindowLongPtr(hwnd, GWL_EXSTYLE,
- GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
- WS_EX_CONTEXTHELP);
-
- /*
- * Centre the window.
- */
- { /* centre the window */
- RECT rs, rd;
- HWND hw;
-
- hw = GetDesktopWindow();
- if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
- MoveWindow(hwnd,
- (rs.right + rs.left + rd.left - rd.right) / 2,
- (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
- rd.right - rd.left, rd.bottom - rd.top, true);
- }
-
- CheckRadioButton(hwnd, IDC_PPKVER_2, IDC_PPKVER_3,
- IDC_PPKVER_2 + (pp->params.fmt_version - 2));
-
- CheckRadioButton(
- hwnd, IDC_KDF_ARGON2ID, IDC_KDF_ARGON2D,
- (pp->params.argon2_flavour == Argon2id ? IDC_KDF_ARGON2ID :
- pp->params.argon2_flavour == Argon2i ? IDC_KDF_ARGON2I :
- /* pp->params.argon2_flavour == Argon2d ? */ IDC_KDF_ARGON2D));
-
- buf = dupprintf("%"PRIu32, pp->params.argon2_mem);
- SetDlgItemText(hwnd, IDC_ARGON2_MEM, buf);
- sfree(buf);
-
- if (pp->params.argon2_passes_auto) {
- CheckRadioButton(hwnd, IDC_PPK_AUTO_YES, IDC_PPK_AUTO_NO,
- IDC_PPK_AUTO_YES);
- buf = dupprintf("%"PRIu32, pp->time_ms);
- SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf);
- sfree(buf);
- } else {
- CheckRadioButton(hwnd, IDC_PPK_AUTO_YES, IDC_PPK_AUTO_NO,
- IDC_PPK_AUTO_NO);
- buf = dupprintf("%"PRIu32, pp->time_passes);
- SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf);
- sfree(buf);
- }
-
- buf = dupprintf("%"PRIu32, pp->params.argon2_parallelism);
- SetDlgItemText(hwnd, IDC_ARGON2_PARALLEL, buf);
- sfree(buf);
-
- return 0;
- case WM_COMMAND:
- switch (LOWORD(wParam)) {
- case IDOK:
- EndDialog(hwnd, 1);
- return 0;
- case IDCANCEL:
- EndDialog(hwnd, 0);
- return 0;
- case IDC_PPKVER_2:
- pp->params.fmt_version = 2;
- return 0;
- case IDC_PPKVER_3:
- pp->params.fmt_version = 3;
- return 0;
- case IDC_KDF_ARGON2ID:
- pp->params.argon2_flavour = Argon2id;
- return 0;
- case IDC_KDF_ARGON2I:
- pp->params.argon2_flavour = Argon2i;
- return 0;
- case IDC_KDF_ARGON2D:
- pp->params.argon2_flavour = Argon2d;
- return 0;
- case IDC_ARGON2_MEM:
- try_get_dlg_item_uint32(hwnd, IDC_ARGON2_MEM,
- &pp->params.argon2_mem);
- return 0;
- case IDC_PPK_AUTO_YES:
- pp->params.argon2_passes_auto = true;
- buf = dupprintf("%"PRIu32, pp->time_ms);
- SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf);
- sfree(buf);
- return 0;
- case IDC_PPK_AUTO_NO:
- pp->params.argon2_passes_auto = false;
- buf = dupprintf("%"PRIu32, pp->time_passes);
- SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf);
- sfree(buf);
- return 0;
- case IDC_ARGON2_TIME:
- try_get_dlg_item_uint32(hwnd, IDC_ARGON2_TIME,
- pp->params.argon2_passes_auto ?
- &pp->time_ms : &pp->time_passes);
- return 0;
- case IDC_ARGON2_PARALLEL:
- try_get_dlg_item_uint32(hwnd, IDC_ARGON2_PARALLEL,
- &pp->params.argon2_parallelism);
- return 0;
- }
- return 0;
- case WM_HELP: {
- int id = ((LPHELPINFO)lParam)->iCtrlId;
- const char *topic = NULL;
- switch (id) {
- case IDC_PPKVER_STATIC:
- case IDC_PPKVER_2:
- case IDC_PPKVER_3:
- topic = WINHELP_CTX_puttygen_ppkver; break;
- case IDC_KDF_STATIC:
- case IDC_KDF_ARGON2ID:
- case IDC_KDF_ARGON2I:
- case IDC_KDF_ARGON2D:
- case IDC_ARGON2_MEM_STATIC:
- case IDC_ARGON2_MEM:
- case IDC_ARGON2_MEM_STATIC2:
- case IDC_ARGON2_TIME_STATIC:
- case IDC_ARGON2_TIME:
- case IDC_PPK_AUTO_YES:
- case IDC_PPK_AUTO_NO:
- case IDC_ARGON2_PARALLEL_STATIC:
- case IDC_ARGON2_PARALLEL:
- topic = WINHELP_CTX_puttygen_kdfparam; break;
- }
- if (topic) {
- launch_help(hwnd, topic);
- } else {
- MessageBeep(0);
- }
- break;
- }
- case WM_CLOSE:
- EndDialog(hwnd, 0);
- return 0;
- }
- return 0;
-}
-
-/*
- * Prompt for a key file. Assumes the filename buffer is of size
- * FILENAME_MAX.
- */
-static bool prompt_keyfile(HWND hwnd, char *dlgtitle,
- char *filename, bool save, bool ppk)
-{
- OPENFILENAME of;
- memset(&of, 0, sizeof(of));
- of.hwndOwner = hwnd;
- if (ppk) {
- of.lpstrFilter = "PuTTY Private Key Files (*.ppk)\0*.ppk\0"
- "All Files (*.*)\0*\0\0\0";
- of.lpstrDefExt = ".ppk";
- } else {
- of.lpstrFilter = "All Files (*.*)\0*\0\0\0";
- }
- of.lpstrCustomFilter = NULL;
- of.nFilterIndex = 1;
- of.lpstrFile = filename;
- *filename = '\0';
- of.nMaxFile = FILENAME_MAX;
- of.lpstrFileTitle = NULL;
- of.lpstrTitle = dlgtitle;
- of.Flags = 0;
- return request_file(NULL, &of, false, save);
-}
-
-/*
- * Dialog-box function for the Licence box.
- */
-static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg,
- WPARAM wParam, LPARAM lParam)
-{
- switch (msg) {
- case WM_INITDIALOG: {
- /*
- * Centre the window.
- */
- RECT rs, rd;
- HWND hw;
-
- hw = GetDesktopWindow();
- if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
- MoveWindow(hwnd,
- (rs.right + rs.left + rd.left - rd.right) / 2,
- (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
- rd.right - rd.left, rd.bottom - rd.top, true);
-
- SetDlgItemText(hwnd, 1000, LICENCE_TEXT("\r\n\r\n"));
- return 1;
- }
- case WM_COMMAND:
- switch (LOWORD(wParam)) {
- case IDOK:
- case IDCANCEL:
- EndDialog(hwnd, 1);
- return 0;
- }
- return 0;
- case WM_CLOSE:
- EndDialog(hwnd, 1);
- return 0;
- }
- return 0;
-}
-
-/*
- * Dialog-box function for the About box.
- */
-static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg,
- WPARAM wParam, LPARAM lParam)
-{
- switch (msg) {
- case WM_INITDIALOG:
- /*
- * Centre the window.
- */
- { /* centre the window */
- RECT rs, rd;
- HWND hw;
-
- hw = GetDesktopWindow();
- if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
- MoveWindow(hwnd,
- (rs.right + rs.left + rd.left - rd.right) / 2,
- (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
- rd.right - rd.left, rd.bottom - rd.top, true);
- }
-
- {
- char *buildinfo_text = buildinfo("\r\n");
- char *text = dupprintf
- ("PuTTYgen\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s",
- ver, buildinfo_text,
- "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved.");
- sfree(buildinfo_text);
- SetDlgItemText(hwnd, 1000, text);
- MakeDlgItemBorderless(hwnd, 1000);
- sfree(text);
- }
- return 1;
- case WM_COMMAND:
- switch (LOWORD(wParam)) {
- case IDOK:
- case IDCANCEL:
- EndDialog(hwnd, 1);
- return 0;
- case 101:
- EnableWindow(hwnd, 0);
- DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc);
- EnableWindow(hwnd, 1);
- SetActiveWindow(hwnd);
- return 0;
- case 102:
- /* Load web browser */
- ShellExecute(hwnd, "open",
- "https://www.chiark.greenend.org.uk/~sgtatham/putty/",
- 0, 0, SW_SHOWDEFAULT);
- return 0;
- }
- return 0;
- case WM_CLOSE:
- EndDialog(hwnd, 1);
- return 0;
- }
- return 0;
-}
-
-typedef enum {RSA, DSA, ECDSA, EDDSA} keytype;
-
-/*
- * Thread to generate a key.
- */
-struct rsa_key_thread_params {
- HWND progressbar; /* notify this with progress */
- HWND dialog; /* notify this on completion */
- int key_bits; /* bits in key modulus (RSA, DSA) */
- int curve_bits; /* bits in elliptic curve (ECDSA) */
- keytype keytype;
- const PrimeGenerationPolicy *primepolicy;
- bool rsa_strong;
- union {
- RSAKey *key;
- struct 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);
-}
-
-/*
- * 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);
-}
-
-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,
- MAKEINTRESOURCE(210),
- NULL, PassphraseProc,
- (LPARAM) &pps);
- if (!dlgret) {
- ret = -2;
- break;
- }
- assert(passphrase != NULL);
- } else
- passphrase = dupstr("");
- if (type == SSH_KEYTYPE_SSH1) {
- if (realtype == type)
- ret = rsa1_load_f(filename, &newkey1, passphrase, &errmsg);
- else
- ret = import_ssh1(filename, realtype, &newkey1,
- passphrase, &errmsg);
- } else {
- if (realtype == type)
- newkey2 = ppk_load_f(filename, passphrase, &errmsg);
- else
- newkey2 = import_ssh2(filename, realtype, passphrase, &errmsg);
- if (newkey2 == SSH2_WRONG_PASSPHRASE)
- ret = -1;
- else if (!newkey2)
- ret = 0;
- else
- ret = 1;
- }
- } while (ret == -1);
- if (comment)
- sfree(comment);
- if (ret == 0) {
- char *msg = dupprintf("Couldn't load private key (%s)", errmsg);
- message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR,
- 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.
- */
- }
- SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
- (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(200)));
-
- 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);
- DialogBox(hinst, MAKEINTRESOURCE(213), hwnd, AboutProc);
- EnableWindow(hwnd, 1);
- SetActiveWindow(hwnd);
- return 0;
- case IDC_GIVEHELP:
- if (HIWORD(wParam) == BN_CLICKED ||
- HIWORD(wParam) == BN_DOUBLECLICKED) {
- launch_help(hwnd, WINHELP_CTX_puttygen_general);
- }
- return 0;
- case IDC_GENERATE:
- if (HIWORD(wParam) != BN_CLICKED &&
- HIWORD(wParam) != BN_DOUBLECLICKED)
- break;
- state =
- (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
- if (!state->generation_thread_exists) {
- unsigned raw_entropy_required;
- unsigned char *raw_entropy_buf;
- BOOL ok;
-
- state->key_bits = GetDlgItemInt(hwnd, IDC_BITS, &ok, false);
- if (!ok)
- state->key_bits = DEFAULT_KEY_BITS;
- state->ssh2 = true;
-
- if (IsDlgButtonChecked(hwnd, IDC_KEYSSH1)) {
- state->ssh2 = false;
- state->keytype = RSA;
- } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2RSA)) {
- state->keytype = RSA;
- } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA)) {
- state->keytype = DSA;
- } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2ECDSA)) {
- state->keytype = ECDSA;
- int curveindex = SendDlgItemMessage(hwnd, IDC_ECCURVE,
- CB_GETCURSEL, 0, 0);
- assert(curveindex >= 0);
- assert(curveindex < n_ec_nist_curve_lengths);
- state->curve_bits = ec_nist_curve_lengths[curveindex];
- } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2EDDSA)) {
- state->keytype = EDDSA;
- int curveindex = SendDlgItemMessage(hwnd, IDC_EDCURVE,
- CB_GETCURSEL, 0, 0);
- assert(curveindex >= 0);
- assert(curveindex < n_ec_ed_curve_lengths);
- state->curve_bits = ec_ed_curve_lengths[curveindex];
- } else {
- /* Somehow, no button was checked */
- break;
- }
-
- if ((state->keytype == RSA || state->keytype == DSA) &&
- state->key_bits < 256) {
- char *message = dupprintf
- ("PuTTYgen will not generate a key smaller than 256"
- " bits.\nKey length reset to default %d. Continue?",
- DEFAULT_KEY_BITS);
- int ret = MessageBox(hwnd, message, "PuTTYgen Warning",
- MB_ICONWARNING | MB_OKCANCEL);
- sfree(message);
- if (ret != IDOK)
- break;
- state->key_bits = DEFAULT_KEY_BITS;
- SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, false);
- } else if ((state->keytype == RSA || state->keytype == DSA) &&
- state->key_bits < DEFAULT_KEY_BITS) {
- char *message = dupprintf
- ("Keys shorter than %d bits are not recommended. "
- "Really generate this key?", DEFAULT_KEY_BITS);
- int ret = MessageBox(hwnd, message, "PuTTYgen Warning",
- MB_ICONWARNING | MB_OKCANCEL);
- sfree(message);
- if (ret != IDOK)
- break;
- }
-
- if (state->keytype == RSA || state->keytype == DSA)
- raw_entropy_required = (state->key_bits / 2) * 2;
- else if (state->keytype == ECDSA || state->keytype == EDDSA)
- raw_entropy_required = (state->curve_bits / 2) * 2;
- else
- unreachable("we must have initialised keytype by now");
-
- /* Bound the entropy collection above by the amount of
- * data we can actually fit into the PRNG. Any more
- * than that and it's doing no more good. */
- if (raw_entropy_required > random_seed_bits())
- raw_entropy_required = random_seed_bits();
-
- raw_entropy_buf = snewn(raw_entropy_required, unsigned char);
- if (win_read_random(raw_entropy_buf, raw_entropy_required)) {
- /*
- * If we can get entropy from CryptGenRandom, use
- * it. But CryptGenRandom isn't a kernel-level
- * CPRNG (according to Wikipedia), and papers have
- * been published cryptanalysing it. So we'll
- * still do manual entropy collection; we'll just
- * do it _as well_ as this.
- */
- random_reseed(
- make_ptrlen(raw_entropy_buf, raw_entropy_required));
- }
-
- /*
- * Manual entropy input, by making the user wave the
- * mouse over the window a lot.
- *
- * My brief statistical tests on mouse movements
- * suggest that there are about 2.5 bits of randomness
- * in the x position, 2.5 in the y position, and 1.7
- * in the message time, making 5.7 bits of
- * unpredictability per mouse movement. However, other
- * people have told me it's far less than that, so I'm
- * going to be stupidly cautious and knock that down
- * to a nice round 2. With this method, we require two
- * words per mouse movement, so with 2 bits per mouse
- * movement we expect 2 bits every 2 words, i.e. the
- * number of _words_ of mouse data we want to collect
- * is just the same as the number of _bits_ of entropy
- * we want.
- */
- state->entropy_required = raw_entropy_required;
-
- ui_set_state(hwnd, state, 1);
- SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg);
- state->key_exists = false;
- state->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;
-}
-
-void cleanup_exit(int code)
-{
- shutdown_help();
- exit(code);
-}
-
-HINSTANCE hinst;
-
-int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
-{
- 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/windows/winpgnt.c b/windows/winpgnt.c
deleted file mode 100644
index d6e960b6..00000000
--- a/windows/winpgnt.c
+++ /dev/null
@@ -1,1723 +0,0 @@
-/*
- * Pageant: the PuTTY Authentication Agent.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stddef.h>
-#include <ctype.h>
-#include <assert.h>
-#include <tchar.h>
-
-#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 <shellapi.h>
-
-#ifndef NO_SECURITY
-#include <aclapi.h>
-#ifdef DEBUG_IPC
-#define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */
-#include <sddl.h>
-#endif
-#endif
-
-#define WM_SYSTRAY (WM_APP + 6)
-#define WM_SYSTRAY2 (WM_APP + 7)
-
-#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */
-
-#define APPNAME "Pageant"
-
-/* Titles and class names for invisible windows. IPCWINTITLE and
- * IPCCLASSNAME are critical to backwards compatibility: WM_COPYDATA
- * based Pageant clients will call FindWindow with those parameters
- * and expect to find the Pageant IPC receiver. */
-#define TRAYWINTITLE "Pageant"
-#define TRAYCLASSNAME "PageantSysTray"
-#define IPCWINTITLE "Pageant"
-#define IPCCLASSNAME "Pageant"
-
-static HWND traywindow;
-static HWND keylist;
-static HWND aboutbox;
-static HMENU systray_menu, session_menu;
-static bool already_running;
-static FingerprintType fptype = SSH_FPTYPE_DEFAULT;
-
-static char *putty_path;
-static bool restrict_putty_acl = false;
-
-/* CWD for "add key" file requester. */
-static filereq *keypath = NULL;
-
-/* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
- * wParam are used by Windows, and should be masked off, so we shouldn't
- * attempt to store information in them. Hence all these identifiers have
- * the low 4 bits clear. Also, identifiers should < 0xF000. */
-
-#define IDM_CLOSE 0x0010
-#define IDM_VIEWKEYS 0x0020
-#define IDM_ADDKEY 0x0030
-#define IDM_ADDKEY_ENCRYPTED 0x0040
-#define IDM_REMOVE_ALL 0x0050
-#define IDM_REENCRYPT_ALL 0x0060
-#define IDM_HELP 0x0070
-#define IDM_ABOUT 0x0080
-#define IDM_PUTTY 0x0090
-#define IDM_SESSIONS_BASE 0x1000
-#define IDM_SESSIONS_MAX 0x2000
-#define PUTTY_REGKEY "Software\\SimonTatham\\PuTTY\\Sessions"
-#define PUTTY_DEFAULT "Default%20Settings"
-static int initial_menuitems_count;
-
-/*
- * Print a modal (Really Bad) message box and perform a fatal exit.
- */
-void modalfatalbox(const char *fmt, ...)
-{
- va_list ap;
- char *buf;
-
- va_start(ap, fmt);
- buf = dupvprintf(fmt, ap);
- va_end(ap);
- MessageBox(traywindow, buf, "Pageant Fatal Error",
- MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
- sfree(buf);
- exit(1);
-}
-
-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;
-}
-
-/*
- * Warn about the obsolescent key file format.
- */
-void old_keyfile_warning(void)
-{
- static const char mbtitle[] = "PuTTY Key File Warning";
- static const char message[] =
- "You are loading an SSH-2 private key which has an\n"
- "old version of the file format. This means your key\n"
- "file is not fully tamperproof. Future versions of\n"
- "PuTTY may stop supporting this private key format,\n"
- "so we recommend you convert your key to the new\n"
- "format.\n"
- "\n"
- "You can perform this conversion by loading the key\n"
- "into PuTTYgen and then saving it again.";
-
- MessageBox(NULL, message, mbtitle, MB_OK);
-}
-
-struct keylist_update_ctx {
- 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;
- 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;
- tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201));
- strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)");
-
- res = Shell_NotifyIcon(NIM_ADD, &tnid);
-
- if (hicon) DestroyIcon(hicon);
-
- return res;
-}
-
-/* Update the saved-sessions menu. */
-static void update_sessions(void)
-{
- int num_entries;
- HKEY hkey;
- TCHAR buf[MAX_PATH + 1];
- MENUITEMINFO mii;
- strbuf *sb;
-
- int index_key, index_menu;
-
- if (!putty_path)
- return;
-
- if(ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey))
- return;
-
- for(num_entries = GetMenuItemCount(session_menu);
- num_entries > initial_menuitems_count;
- num_entries--)
- RemoveMenu(session_menu, 0, MF_BYPOSITION);
-
- index_key = 0;
- index_menu = 0;
-
- sb = strbuf_new();
- while(ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) {
- if(strcmp(buf, PUTTY_DEFAULT) != 0) {
- strbuf_clear(sb);
- unescape_registry_key(buf, sb);
-
- memset(&mii, 0, sizeof(mii));
- mii.cbSize = sizeof(mii);
- mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
- mii.fType = MFT_STRING;
- mii.fState = MFS_ENABLED;
- mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE;
- mii.dwTypeData = sb->s;
- InsertMenuItem(session_menu, index_menu, true, &mii);
- index_menu++;
- }
- index_key++;
- }
- strbuf_free(sb);
-
- RegCloseKey(hkey);
-
- if(index_menu == 0) {
- mii.cbSize = sizeof(mii);
- mii.fMask = MIIM_TYPE | MIIM_STATE;
- mii.fType = MFT_STRING;
- mii.fState = MFS_GRAYED;
- mii.dwTypeData = _T("(No sessions)");
- InsertMenuItem(session_menu, index_menu, true, &mii);
- }
-}
-
-#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);
- 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);
- }
-}
-
-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);
-}
-
-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;
-
-int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
-{
- 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;
-
- /*
- * 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);
- } else
- putty_path = NULL;
- }
-
- /*
- * 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;
- } 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;
- wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
- 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/windows/winpgntc.c b/windows/winpgntc.c
deleted file mode 100644
index 557dc532..00000000
--- a/windows/winpgntc.c
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Pageant client code.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-
-#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/windows/winplink.c b/windows/winplink.c
deleted file mode 100644
index 58d43e6d..00000000
--- a/windows/winplink.c
+++ /dev/null
@@ -1,535 +0,0 @@
-/*
- * PLink - a Windows command-line (stdin/stdout) variant of PuTTY.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <stdarg.h>
-
-#include "putty.h"
-#include "storage.h"
-#include "tree234.h"
-#include "winsecur.h"
-
-void cmdline_error(const char *fmt, ...)
-{
- va_list ap;
- va_start(ap, fmt);
- console_print_error_msg_fmt_v("plink", fmt, ap);
- va_end(ap);
- exit(1);
-}
-
-static HANDLE inhandle, outhandle, errhandle;
-static struct handle *stdin_handle, *stdout_handle, *stderr_handle;
-static handle_sink stdout_hs, stderr_hs;
-static StripCtrlChars *stdout_scc, *stderr_scc;
-static BinarySink *stdout_bs, *stderr_bs;
-static DWORD orig_console_mode;
-
-static Backend *backend;
-static LogContext *logctx;
-static Conf *conf;
-
-static void plink_echoedit_update(Seat *seat, bool echo, bool edit)
-{
- /* Update stdin read mode to reflect changes in line discipline. */
- DWORD mode;
-
- mode = ENABLE_PROCESSED_INPUT;
- if (echo)
- mode = mode | ENABLE_ECHO_INPUT;
- else
- mode = mode & ~ENABLE_ECHO_INPUT;
- if (edit)
- mode = mode | ENABLE_LINE_INPUT;
- else
- mode = mode & ~ENABLE_LINE_INPUT;
- SetConsoleMode(inhandle, mode);
-}
-
-static size_t plink_output(
- Seat *seat, 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");
- 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;
-}
-
-int main(int argc, char **argv)
-{
- int exitcode;
- bool errors;
- bool use_subsystem = false;
- bool just_test_share_exists = false;
- enum TriState sanitise_stdout = AUTO, sanitise_stderr = AUTO;
- const struct BackendVtable *vt;
-
- dll_hijacking_protection();
-
- /*
- * Initialise port and protocol to sensible defaults. (These
- * will be overridden by more or less anything.)
- */
- settings_set_default_protocol(PROT_SSH);
- settings_set_default_port(22);
-
- /*
- * Process the command line.
- */
- conf = conf_new();
- do_defaults(NULL, conf);
- settings_set_default_protocol(conf_get_int(conf, CONF_protocol));
- settings_set_default_port(conf_get_int(conf, CONF_port));
- errors = false;
- {
- /*
- * Override the default protocol if PLINK_PROTOCOL is set.
- */
- char *p = getenv("PLINK_PROTOCOL");
- if (p) {
- const struct BackendVtable *vt = backend_vt_from_name(p);
- if (vt) {
- settings_set_default_protocol(vt->protocol);
- settings_set_default_port(vt->default_port);
- conf_set_int(conf, CONF_protocol, vt->protocol);
- conf_set_int(conf, CONF_port, vt->default_port);
- }
- }
- }
- while (--argc) {
- char *p = *++argv;
- int ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
- 1, conf);
- if (ret == -2) {
- fprintf(stderr,
- "plink: option \"%s\" requires an argument\n", p);
- errors = true;
- } else if (ret == 2) {
- --argc, ++argv;
- } else if (ret == 1) {
- continue;
- } else if (!strcmp(p, "-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;
- }
-
- if (restricted_acl()) {
- lp_eventlog(console_cli_logpolicy,
- "Running with restricted process ACL");
- }
-
- inhandle = GetStdHandle(STD_INPUT_HANDLE);
- outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
- errhandle = GetStdHandle(STD_ERROR_HANDLE);
-
- /*
- * Turn off ECHO and LINE input modes. We don't care if this
- * call fails, because we know we aren't necessarily running in
- * a console.
- */
- GetConsoleMode(inhandle, &orig_console_mode);
- SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT);
-
- /*
- * Pass the output handles to the handle-handling subsystem.
- * (The input one we leave until we're through the
- * authentication process.)
- */
- stdout_handle = handle_output_new(outhandle, stdouterr_sent, NULL, 0);
- stderr_handle = handle_output_new(errhandle, stdouterr_sent, NULL, 0);
- handle_sink_init(&stdout_hs, stdout_handle);
- handle_sink_init(&stderr_hs, stderr_handle);
- stdout_bs = BinarySink_UPCAST(&stdout_hs);
- stderr_bs = BinarySink_UPCAST(&stderr_hs);
-
- /*
- * Decide whether to sanitise control sequences out of standard
- * output and standard error.
- *
- * If we weren't given a command-line override, we do this if (a)
- * the fd in question is pointing at a console, and (b) we aren't
- * trying to allocate a terminal as part of the session.
- *
- * (Rationale: the risk of control sequences is that they cause
- * confusion when sent to a local console, so if there isn't one,
- * no problem. Also, if we allocate a remote terminal, then we
- * sent a terminal type, i.e. we told it what kind of escape
- * sequences we _like_, i.e. we were expecting to receive some.)
- */
- if (sanitise_stdout == FORCE_ON ||
- (sanitise_stdout == AUTO && is_console_handle(outhandle) &&
- conf_get_bool(conf, CONF_nopty))) {
- stdout_scc = stripctrl_new(stdout_bs, true, L'\0');
- stdout_bs = BinarySink_UPCAST(stdout_scc);
- }
- if (sanitise_stderr == FORCE_ON ||
- (sanitise_stderr == AUTO && is_console_handle(errhandle) &&
- conf_get_bool(conf, CONF_nopty))) {
- stderr_scc = stripctrl_new(stderr_bs, true, L'\0');
- stderr_bs = BinarySink_UPCAST(stderr_scc);
- }
-
- /*
- * Start up the connection.
- */
- winselcli_setup(); /* ensure event object exists */
- {
- char *error, *realhost;
- /* nodelay is only useful if stdin is a character device (console) */
- bool nodelay = conf_get_bool(conf, CONF_tcp_nodelay) &&
- (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR);
-
- error = backend_init(vt, plink_seat, &backend, logctx, conf,
- conf_get_str(conf, CONF_host),
- conf_get_int(conf, CONF_port),
- &realhost, nodelay,
- conf_get_bool(conf, CONF_tcp_keepalives));
- if (error) {
- fprintf(stderr, "Unable to open connection:\n%s", error);
- sfree(error);
- return 1;
- }
- ldisc_create(conf, NULL, backend, plink_seat);
- sfree(realhost);
- }
-
- main_thread_id = GetCurrentThreadId();
-
- sending = false;
-
- cli_main_loop(plink_mainloop_pre, plink_mainloop_post, NULL);
-
- exitcode = backend_exitcode(backend);
- if (exitcode < 0) {
- fprintf(stderr, "Remote process exit code unavailable\n");
- exitcode = 1; /* this is an error condition */
- }
- cleanup_exit(exitcode);
- return 0; /* placate compiler warning */
-}
diff --git a/windows/winprint.c b/windows/winprint.c
deleted file mode 100644
index e6b3531d..00000000
--- a/windows/winprint.c
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Printing interface for PuTTY.
- */
-
-#include "putty.h"
-#include <winspool.h>
-
-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/windows/winproxy.c b/windows/winproxy.c
deleted file mode 100644
index 94e31fcb..00000000
--- a/windows/winproxy.c
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * winproxy.c: Windows implementation of platform_new_connection(),
- * supporting an OpenSSH-like proxy command via the winhandl.c
- * mechanism.
- */
-
-#include <stdio.h>
-#include <assert.h>
-
-#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/windows/winseat.h b/windows/winseat.h
deleted file mode 100644
index c6b5fa96..00000000
--- a/windows/winseat.h
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Small implementation of Seat and LogPolicy shared between window.c
- * and windlg.c.
- */
-
-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/windows/winsecur.c b/windows/winsecur.c
deleted file mode 100644
index a1164af5..00000000
--- a/windows/winsecur.c
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * winsecur.c: implementation of winsecur.h.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-
-#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/windows/winsecur.h b/windows/winsecur.h
deleted file mode 100644
index fdd39d81..00000000
--- a/windows/winsecur.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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 <aclapi.h>
-
-/*
- * 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/windows/winselcli.c b/windows/winselcli.c
deleted file mode 100644
index f19a0bbe..00000000
--- a/windows/winselcli.c
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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/windows/winselgui.c b/windows/winselgui.c
deleted file mode 100644
index 48a15212..00000000
--- a/windows/winselgui.c
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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/windows/winser.c b/windows/winser.c
deleted file mode 100644
index 7f4bcf2e..00000000
--- a/windows/winser.c
+++ /dev/null
@@ -1,465 +0,0 @@
-/*
- * Serial back end (Windows-specific).
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-
-#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
- * <http://msdn2.microsoft.com/en-us/library/aa365247.aspx>
- * ("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 <http://msdn2.microsoft.com/en-us/library/aa363858.aspx>
- * (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/windows/winsftp.c b/windows/winsftp.c
deleted file mode 100644
index 0c695d2e..00000000
--- a/windows/winsftp.c
+++ /dev/null
@@ -1,650 +0,0 @@
-/*
- * winsftp.c: the Windows-specific parts of PSFTP and PSCP.
- */
-
-#include <winsock2.h> /* need to put this first, for winelib builds */
-#include <assert.h>
-
-#define NEED_DECLARATION_OF_SELECT
-
-#include "putty.h"
-#include "psftp.h"
-#include "ssh.h"
-#include "winsecur.h"
-
-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;
-
- dll_hijacking_protection();
-
- ret = psftp_main(argc, argv);
-
- return ret;
-}
diff --git a/windows/winshare.c b/windows/winshare.c
deleted file mode 100644
index f0e409ac..00000000
--- a/windows/winshare.c
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Windows implementation of SSH connection-sharing IPC setup.
- */
-
-#include <stdio.h>
-#include <assert.h>
-
-#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/windows/winsocks.c b/windows/winsocks.c
deleted file mode 100644
index 83ba364c..00000000
--- a/windows/winsocks.c
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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/windows/winstore.c b/windows/winstore.c
deleted file mode 100644
index 09e5c028..00000000
--- a/windows/winstore.c
+++ /dev/null
@@ -1,873 +0,0 @@
-/*
- * winstore.c: Windows-specific implementation of the interface
- * defined in storage.h.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <limits.h>
-#include <assert.h>
-#include "putty.h"
-#include "storage.h"
-
-#include <shlobj.h>
-#ifndef CSIDL_APPDATA
-#define CSIDL_APPDATA 0x001a
-#endif
-#ifndef CSIDL_LOCAL_APPDATA
-#define CSIDL_LOCAL_APPDATA 0x001c
-#endif
-
-static const char *const reg_jumplist_key = PUTTY_REG_POS "\\Jumplist";
-static const char *const reg_jumplist_value = "Recent sessions";
-static const char *const puttystr = PUTTY_REG_POS "\\Sessions";
-
-static bool tried_shgetfolderpath = false;
-static HMODULE shell32_module = NULL;
-DECL_WINDOWS_FUNCTION(static, HRESULT, SHGetFolderPathA,
- (HWND, int, HANDLE, DWORD, LPSTR));
-
-struct settings_w {
- HKEY sesskey;
-};
-
-settings_w *open_settings_w(const char *sessionname, char **errmsg)
-{
- HKEY subkey1, sesskey;
- int ret;
- strbuf *sb;
-
- *errmsg = NULL;
-
- if (!sessionname || !*sessionname)
- sessionname = "Default Settings";
-
- 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)
-{
- 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)
-{
- if (handle)
- RegSetValueEx(handle->sesskey, key, 0, REG_DWORD,
- (CONST BYTE *) &value, sizeof(value));
-}
-
-void close_settings_w(settings_w *handle)
-{
- RegCloseKey(handle->sesskey);
- sfree(handle);
-}
-
-struct settings_r {
- HKEY sesskey;
-};
-
-settings_r *open_settings_r(const char *sessionname)
-{
- HKEY subkey1, sesskey;
- strbuf *sb;
-
- if (!sessionname || !*sessionname)
- sessionname = "Default Settings";
-
- 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;
-
- /* 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);
-
- 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)
-{
- if (handle) {
- RegCloseKey(handle->sesskey);
- sfree(handle);
- }
-}
-
-void del_settings(const char *sessionname)
-{
- HKEY subkey1;
- strbuf *sb;
-
- 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;
-};
-
-settings_e *enum_settings_start(void)
-{
- settings_e *ret;
- HKEY key;
-
- if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS)
- return NULL;
-
- ret = snew(settings_e);
- if (ret) {
- ret->key = key;
- ret->i = 0;
- }
-
- return ret;
-}
-
-bool enum_settings_next(settings_e *e, strbuf *sb)
-{
- 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)
-{
- RegCloseKey(e->key);
- 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);
-
- 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);
-
- 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)
-{
- 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;
-
- 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';
- }
-
- /* 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. */
- 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. */
- RegCloseKey(pjumplist_key);
-
- if (ret != ERROR_SUCCESS) {
- return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE;
- } else {
- return JUMPLISTREG_OK;
- }
-}
-
-/* Adds a new entry to the jumplist entries in the registry. */
-int add_to_jumplist_registry(const char *item)
-{
- return transform_jumplist_registry(item, item, NULL);
-}
-
-/* Removes an item from the jumplist entries in the registry. */
-int remove_from_jumplist_registry(const char *item)
-{
- return transform_jumplist_registry(NULL, item, NULL);
-}
-
-/* Returns the jumplist entries from the registry. Caller must free
- * the returned pointer. */
-char *get_jumplist_registry_entries (void)
-{
- char *list_value;
-
- if (transform_jumplist_registry(NULL,NULL,&list_value) != JUMPLISTREG_OK) {
- list_value = snewn(2, char);
- *list_value = '\0';
- *(list_value + 1) = '\0';
- }
- return list_value;
-}
-
-/*
- * Recursively delete a registry key and everything under it.
- */
-static void registry_recursive_remove(HKEY key)
-{
- DWORD i;
- char name[MAX_PATH + 1];
- HKEY subkey;
-
- i = 0;
- while (RegEnumKey(key, i, name, sizeof(name)) == ERROR_SUCCESS) {
- if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS) {
- registry_recursive_remove(subkey);
- RegCloseKey(subkey);
- }
- RegDeleteKey(key, name);
- }
-}
-
-void cleanup_all(void)
-{
- HKEY key;
- int ret;
- char name[MAX_PATH + 1];
-
- /* ------------------------------------------------------------
- * Wipe out the random seed file, in all of its possible
- * locations.
- */
- access_random_seed(DEL);
-
- /* ------------------------------------------------------------
- * Ask Windows to delete any jump list information associated
- * with this installation of PuTTY.
- */
- clear_jumplist();
-
- /* ------------------------------------------------------------
- * Destroy all registry information associated with PuTTY.
- */
-
- /*
- * Open the main PuTTY registry key and remove everything in it.
- */
- if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &key) ==
- ERROR_SUCCESS) {
- registry_recursive_remove(key);
- RegCloseKey(key);
- }
- /*
- * Now open the parent key and remove the PuTTY main key. Once
- * we've done that, see if the parent key has any other
- * children.
- */
- if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_PARENT,
- &key) == ERROR_SUCCESS) {
- RegDeleteKey(key, PUTTY_REG_PARENT_CHILD);
- ret = RegEnumKey(key, 0, name, sizeof(name));
- RegCloseKey(key);
- /*
- * If the parent key had no other children, we must delete
- * it in its turn. That means opening the _grandparent_
- * key.
- */
- if (ret != ERROR_SUCCESS) {
- if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_GPARENT,
- &key) == ERROR_SUCCESS) {
- RegDeleteKey(key, PUTTY_REG_GPARENT_CHILD);
- RegCloseKey(key);
- }
- }
- }
- /*
- * Now we're done.
- */
-}
diff --git a/windows/winstuff.h b/windows/winstuff.h
deleted file mode 100644
index c0df5a31..00000000
--- a/windows/winstuff.h
+++ /dev/null
@@ -1,704 +0,0 @@
-/*
- * winstuff.h: Windows-specific inter-module stuff.
- */
-
-#ifndef PUTTY_WINSTUFF_H
-#define PUTTY_WINSTUFF_H
-
-#ifndef AUTO_WINSOCK
-#include <winsock2.h>
-#endif
-#include <windows.h>
-#include <stdio.h> /* 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 <stddef.h>
-#if !defined _MSC_VER || _MSC_VER >= 1600 || defined __clang__
-#include <stdint.h>
-#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)
-
-#define PUTTY_REG_POS "Software\\SimonTatham\\PuTTY"
-#define PUTTY_REG_PARENT "Software\\SimonTatham"
-#define PUTTY_REG_PARENT_CHILD "PuTTY"
-#define PUTTY_REG_GPARENT "Software"
-#define PUTTY_REG_GPARENT_CHILD "SimonTatham"
-
-/* Result values for the jumplist registry functions. */
-#define JUMPLISTREG_OK 0
-#define JUMPLISTREG_ERROR_INVALID_PARAMETER 1
-#define JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE 2
-#define JUMPLISTREG_ERROR_VALUEREAD_FAILURE 3
-#define JUMPLISTREG_ERROR_VALUEWRITE_FAILURE 4
-#define JUMPLISTREG_ERROR_INVALID_VALUE 5
-
-#define PUTTY_CHM_FILE "putty.chm"
-
-#define GETTICKCOUNT GetTickCount
-#define CURSORBLINK GetCaretBlinkTime()
-#define TICKSPERSEC 1000 /* GetTickCount returns milliseconds */
-
-#define DEFAULT_CODEPAGE CP_ACP
-#define USES_VTLINE_HACK
-
-#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")
-
-/*
- * 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/windows/wintime.c b/windows/wintime.c
deleted file mode 100644
index 5fa3b0de..00000000
--- a/windows/wintime.c
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * wintime.c - Avoid trouble with time() returning (time_t)-1 on Windows.
- */
-
-#include "putty.h"
-#include <time.h>
-
-struct tm ltime(void)
-{
- SYSTEMTIME st;
- struct tm tm;
-
- memset(&tm, 0, sizeof(tm)); /* in case there are any other fields */
-
- GetLocalTime(&st);
- tm.tm_sec=st.wSecond;
- tm.tm_min=st.wMinute;
- tm.tm_hour=st.wHour;
- tm.tm_mday=st.wDay;
- tm.tm_mon=st.wMonth-1;
- tm.tm_year=(st.wYear>=1900?st.wYear-1900:0);
- tm.tm_wday=st.wDayOfWeek;
- tm.tm_yday=-1; /* GetLocalTime doesn't tell us */
- tm.tm_isdst=0; /* GetLocalTime doesn't tell us */
- return tm;
-}
diff --git a/windows/winucs.c b/windows/winucs.c
deleted file mode 100644
index 9ffff5e9..00000000
--- a/windows/winucs.c
+++ /dev/null
@@ -1,1213 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <time.h>
-#include <assert.h>
-
-#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 <http://www.unicode.org/Public/MAPPINGS/> -- 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&#0142;), 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/windows/winutils.c b/windows/winutils.c
deleted file mode 100644
index dec8984b..00000000
--- a/windows/winutils.c
+++ /dev/null
@@ -1,788 +0,0 @@
-/*
- * winutils.c: miscellaneous Windows utilities for GUI apps
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <ctype.h>
-
-#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 <args>', 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 <string>', 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/windows/winx11.c b/windows/winx11.c
deleted file mode 100644
index 800d8509..00000000
--- a/windows/winx11.c
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * winx11.c: fetch local auth data for X forwarding.
- */
-
-#include <ctype.h>
-#include <assert.h>
-#include <stdlib.h>
-
-#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/windows/x11.c b/windows/x11.c
new file mode 100644
index 00000000..98bbb627
--- /dev/null
+++ b/windows/x11.c
@@ -0,0 +1,19 @@
+/*
+ * x11.c: fetch local auth data for X forwarding.
+ */
+
+#include <ctype.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#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/x11disp.c b/x11disp.c
new file mode 100644
index 00000000..1dbc9c07
--- /dev/null
+++ b/x11disp.c
@@ -0,0 +1,189 @@
+/*
+ * Functions to manage an X11Display structure, by creating one from
+ * an ordinary display name string, and freeing one.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <time.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "ssh/channel.h"
+#include "tree234.h"
+
+struct X11Display *x11_setup_display(const char *display, Conf *conf,
+ char **error_msg)
+{
+ struct X11Display *disp = snew(struct X11Display);
+ char *localcopy;
+
+ *error_msg = NULL;
+
+ if (!display || !*display) {
+ localcopy = platform_get_x_display();
+ if (!localcopy || !*localcopy) {
+ sfree(localcopy);
+ localcopy = dupstr(":0"); /* plausible default for any platform */
+ }
+ } else
+ localcopy = dupstr(display);
+
+ /*
+ * Parse the display name.
+ *
+ * We expect this to have one of the following forms:
+ *
+ * - the standard X format which looks like
+ * [ [ protocol '/' ] host ] ':' displaynumber [ '.' screennumber ]
+ * (X11 also permits a double colon to indicate DECnet, but
+ * that's not our problem, thankfully!)
+ *
+ * - only seen in the wild on MacOS (so far): a pathname to a
+ * Unix-domain socket, which will typically and confusingly
+ * end in ":0", and which I'm currently distinguishing from
+ * the standard scheme by noting that it starts with '/'.
+ */
+ if (localcopy[0] == '/') {
+ disp->unixsocketpath = localcopy;
+ disp->unixdomain = true;
+ disp->hostname = NULL;
+ disp->displaynum = -1;
+ disp->screennum = 0;
+ disp->addr = NULL;
+ } else {
+ char *colon, *dot, *slash;
+ char *protocol, *hostname;
+
+ colon = host_strrchr(localcopy, ':');
+ if (!colon) {
+ *error_msg = dupprintf("display name '%s' has no ':number'"
+ " suffix", localcopy);
+
+ sfree(disp);
+ sfree(localcopy);
+ return NULL;
+ }
+
+ *colon++ = '\0';
+ dot = strchr(colon, '.');
+ if (dot)
+ *dot++ = '\0';
+
+ disp->displaynum = atoi(colon);
+ if (dot)
+ disp->screennum = atoi(dot);
+ else
+ disp->screennum = 0;
+
+ protocol = NULL;
+ hostname = localcopy;
+ if (colon > localcopy) {
+ slash = strchr(localcopy, '/');
+ if (slash) {
+ *slash++ = '\0';
+ protocol = localcopy;
+ hostname = slash;
+ }
+ }
+
+ disp->hostname = *hostname ? dupstr(hostname) : NULL;
+
+ if (protocol)
+ disp->unixdomain = (!strcmp(protocol, "local") ||
+ !strcmp(protocol, "unix"));
+ else if (!*hostname || !strcmp(hostname, "unix"))
+ disp->unixdomain = platform_uses_x11_unix_by_default;
+ else
+ disp->unixdomain = false;
+
+ if (!disp->hostname && !disp->unixdomain)
+ disp->hostname = dupstr("localhost");
+
+ disp->unixsocketpath = NULL;
+ disp->addr = NULL;
+
+ sfree(localcopy);
+ }
+
+ /*
+ * Look up the display hostname, if we need to.
+ */
+ if (!disp->unixdomain) {
+ const char *err;
+
+ disp->port = 6000 + disp->displaynum;
+ disp->addr = name_lookup(disp->hostname, disp->port,
+ &disp->realhost, conf, ADDRTYPE_UNSPEC,
+ NULL, NULL);
+
+ if ((err = sk_addr_error(disp->addr)) != NULL) {
+ *error_msg = dupprintf("unable to resolve host name '%s' in "
+ "display name", disp->hostname);
+
+ sk_addr_free(disp->addr);
+ sfree(disp->hostname);
+ sfree(disp->unixsocketpath);
+ sfree(disp);
+ return NULL;
+ }
+ }
+
+ /*
+ * Try upgrading an IP-style localhost display to a Unix-socket
+ * display (as the standard X connection libraries do).
+ */
+ if (!disp->unixdomain && sk_address_is_local(disp->addr)) {
+ SockAddr *ux = platform_get_x11_unix_address(NULL, disp->displaynum);
+ const char *err = sk_addr_error(ux);
+ if (!err) {
+ /* Create trial connection to see if there is a useful Unix-domain
+ * socket */
+ Socket *s = sk_new(sk_addr_dup(ux), 0, false, false,
+ false, false, nullplug);
+ err = sk_socket_error(s);
+ sk_close(s);
+ }
+ if (err) {
+ sk_addr_free(ux);
+ } else {
+ sk_addr_free(disp->addr);
+ disp->unixdomain = true;
+ disp->addr = ux;
+ /* Fill in the rest in a moment */
+ }
+ }
+
+ if (disp->unixdomain) {
+ if (!disp->addr)
+ disp->addr = platform_get_x11_unix_address(disp->unixsocketpath,
+ disp->displaynum);
+ if (disp->unixsocketpath)
+ disp->realhost = dupstr(disp->unixsocketpath);
+ else
+ disp->realhost = dupprintf("unix:%d", disp->displaynum);
+ disp->port = 0;
+ }
+
+ /*
+ * Fetch the local authorisation details.
+ */
+ disp->localauthproto = X11_NO_AUTH;
+ disp->localauthdata = NULL;
+ disp->localauthdatalen = 0;
+ platform_get_x11_auth(disp, conf);
+
+ return disp;
+}
+
+void x11_free_display(struct X11Display *disp)
+{
+ sfree(disp->hostname);
+ sfree(disp->unixsocketpath);
+ if (disp->localauthdata)
+ smemclr(disp->localauthdata, disp->localauthdatalen);
+ sfree(disp->localauthdata);
+ sk_addr_free(disp->addr);
+ sfree(disp);
+}
diff --git a/x11fwd.c b/x11fwd.c
deleted file mode 100644
index 86f85831..00000000
--- a/x11fwd.c
+++ /dev/null
@@ -1,1201 +0,0 @@
-/*
- * Platform-independent bits of X11 forwarding.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <time.h>
-
-#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;
-}